Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions examples/webgl_loader_gltf.html
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,13 @@
const loader = new GLTFLoader().setPath( 'models/gltf/DamagedHelmet/glTF/' );
loader.load( 'DamagedHelmet.gltf', async function ( gltf ) {

scene.add( gltf.scene );
const model = gltf.scene;

await renderer.compileAsync( scene, camera );
// wait until the model can be added to the scene without blocking due to shader compilation

await renderer.compileAsync( model, camera, scene );

scene.add( model );

render();

Expand Down
92 changes: 35 additions & 57 deletions src/renderers/WebGLRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -939,14 +939,18 @@ class WebGLRenderer {

}

this.compile = function ( scene, camera ) {
this.compile = function ( scene, camera, targetScene = null ) {

currentRenderState = renderStates.get( scene );
if ( targetScene === null ) targetScene = scene;

currentRenderState = renderStates.get( targetScene );
currentRenderState.init();

renderStateStack.push( currentRenderState );

scene.traverseVisible( function ( object ) {
// gather lights from both the target scene and the new object that will be added to the scene.

targetScene.traverseVisible( function ( object ) {

if ( object.isLight && object.layers.test( camera.layers ) ) {

Expand All @@ -962,67 +966,31 @@ class WebGLRenderer {

} );

currentRenderState.setupLights( _this._useLegacyLights );

scene.traverse( function ( object ) {

const material = object.material;
if ( scene !== targetScene ) {

if ( material ) {
scene.traverseVisible( function ( object ) {

if ( Array.isArray( material ) ) {
if ( object.isLight && object.layers.test( camera.layers ) ) {

for ( let i = 0; i < material.length; i ++ ) {
currentRenderState.pushLight( object );

const material2 = material[ i ];
if ( object.castShadow ) {

prepareMaterial( material2, scene, object );
currentRenderState.pushShadow( object );

}

} else {

prepareMaterial( material, scene, object );

}

}

} );

renderStateStack.pop();
currentRenderState = null;

};

// compileAsync

this.compileAsync = function ( scene, camera ) {

currentRenderState = renderStates.get( scene );
currentRenderState.init();

renderStateStack.push( currentRenderState );

scene.traverseVisible( function ( object ) {

if ( object.isLight && object.layers.test( camera.layers ) ) {

currentRenderState.pushLight( object );

if ( object.castShadow ) {

currentRenderState.pushShadow( object );

}

}
} );

} );
}

currentRenderState.setupLights( _this._useLegacyLights );

const compiling = new Set();
// Only initialize materials in the new scene, not the targetScene.

const materials = new Set();

scene.traverse( function ( object ) {

Expand All @@ -1036,15 +1004,15 @@ class WebGLRenderer {

const material2 = material[ i ];

prepareMaterial( material2, scene, object );
compiling.add( material2 );
prepareMaterial( material2, targetScene, object );
Copy link
Collaborator Author

@Mugen87 Mugen87 Oct 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a subtle but important change. We have to use targetScene here since otherwise we miss environment definitions (and then end up with recompilations during the first render).

materials.add( material2 );

}

} else {

prepareMaterial( material, scene, object );
compiling.add( material );
prepareMaterial( material, targetScene, object );
materials.add( material );

}

Expand All @@ -1055,30 +1023,40 @@ class WebGLRenderer {
renderStateStack.pop();
currentRenderState = null;

return materials;

};

// compileAsync

this.compileAsync = function ( scene, camera, targetScene = null ) {

const materials = this.compile( scene, camera, targetScene );

// Wait for all the materials in the new object to indicate that they're
// ready to be used before resolving the promise.

return new Promise( ( resolve ) => {

function checkMaterialsReady() {

compiling.forEach( function ( material ) {
materials.forEach( function ( material ) {

const materialProperties = properties.get( material );
const program = materialProperties.currentProgram;

if ( program.isReady() ) {

// remove any programs that report they're ready to use from the list
compiling.delete( material );
materials.delete( material );

}

} );

// once the list of compiling materials is empty, call the callback

if ( compiling.size === 0 ) {
if ( materials.size === 0 ) {

resolve( scene );
return;
Expand Down