Skip to content

Commit 00b1a20

Browse files
committed
Avoid creating per-camera render states
Now we usually only create one render state per scene. Instead of creating camera-specific render states, the view-dependent light uniforms are updated with a separate function whenever rendering. This helps avoid initMaterial calls when rendering cube maps or array cameras, improving performance dramatically. The only exception is handling nested render() calls done from callbacks. These are sometimes used to render the same scene from a different viewpoint, for example to generate a mirror reflection. They could also potentially modify the scene in some other ways. For these reasons they can't share the same render state. Related issues: #12883
1 parent 0576e70 commit 00b1a20

File tree

5 files changed

+131
-54
lines changed

5 files changed

+131
-54
lines changed

src/renderers/WebGLRenderer.js

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ function WebGLRenderer( parameters ) {
5858
let currentRenderList = null;
5959
let currentRenderState = null;
6060

61+
// render() can be called from within a callback triggered by another render.
62+
// We track this so that the nested render call gets its state isolated from the parent render call.
63+
64+
let currentRenderCallStack = [];
65+
6166
// public properties
6267

6368
this.domElement = _canvas;
@@ -870,7 +875,7 @@ function WebGLRenderer( parameters ) {
870875

871876
this.compile = function ( scene, camera ) {
872877

873-
currentRenderState = renderStates.get( scene, camera );
878+
currentRenderState = renderStates.get( scene, 0 );
874879
currentRenderState.init();
875880

876881
scene.traverse( function ( object ) {
@@ -889,7 +894,7 @@ function WebGLRenderer( parameters ) {
889894

890895
} );
891896

892-
currentRenderState.setupLights( camera );
897+
currentRenderState.setupLights();
893898

894899
const compiled = new WeakMap();
895900

@@ -1004,9 +1009,11 @@ function WebGLRenderer( parameters ) {
10041009
//
10051010
if ( scene.isScene === true ) scene.onBeforeRender( _this, scene, camera, renderTarget || _currentRenderTarget );
10061011

1007-
currentRenderState = renderStates.get( scene, camera );
1012+
currentRenderState = renderStates.get( scene, currentRenderCallStack.length );
10081013
currentRenderState.init();
10091014

1015+
currentRenderCallStack.push( currentRenderState );
1016+
10101017
_projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
10111018
_frustum.setFromProjectionMatrix( _projScreenMatrix );
10121019

@@ -1034,7 +1041,8 @@ function WebGLRenderer( parameters ) {
10341041

10351042
shadowMap.render( shadowsArray, scene, camera );
10361043

1037-
currentRenderState.setupLights( camera );
1044+
currentRenderState.setupLights();
1045+
currentRenderState.setupLightsView( camera );
10381046

10391047
if ( _clippingEnabled === true ) clipping.endShadows();
10401048

@@ -1088,8 +1096,18 @@ function WebGLRenderer( parameters ) {
10881096

10891097
// _gl.finish();
10901098

1099+
currentRenderCallStack.pop();
1100+
if ( currentRenderCallStack.length > 0 ) {
1101+
1102+
currentRenderState = currentRenderCallStack[currentRenderCallStack.length - 1];
1103+
1104+
} else {
1105+
1106+
currentRenderState = null;
1107+
1108+
}
1109+
10911110
currentRenderList = null;
1092-
currentRenderState = null;
10931111

10941112
};
10951113

@@ -1245,7 +1263,7 @@ function WebGLRenderer( parameters ) {
12451263

12461264
state.viewport( _currentViewport.copy( camera2.viewport ) );
12471265

1248-
currentRenderState.setupLights( camera2 );
1266+
currentRenderState.setupLightsView( camera2 );
12491267

12501268
renderObject( object, scene, camera2, geometry, material, group );
12511269

@@ -1268,7 +1286,6 @@ function WebGLRenderer( parameters ) {
12681286
function renderObject( object, scene, camera, geometry, material, group ) {
12691287

12701288
object.onBeforeRender( _this, scene, camera, geometry, material, group );
1271-
currentRenderState = renderStates.get( scene, _currentArrayCamera || camera );
12721289

12731290
object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
12741291
object.normalMatrix.getNormalMatrix( object.modelViewMatrix );
@@ -1290,7 +1307,6 @@ function WebGLRenderer( parameters ) {
12901307
}
12911308

12921309
object.onAfterRender( _this, scene, camera, geometry, material, group );
1293-
currentRenderState = renderStates.get( scene, _currentArrayCamera || camera );
12941310

12951311
}
12961312

src/renderers/webgl/WebGLLights.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export class WebGLLights {
3636
};
3737

3838
get( light: any ): any;
39-
setup( lights: any, shadows: any, camera: any ): void;
39+
setup( lights: any, shadows: any ): void;
40+
setupView( lights: any, camera: any ): void;
4041

4142
}

src/renderers/webgl/WebGLLights.js

Lines changed: 87 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ function WebGLLights() {
199199
const matrix4 = new Matrix4();
200200
const matrix42 = new Matrix4();
201201

202-
function setup( lights, shadows, camera ) {
202+
function setup( lights, shadows ) {
203203

204204
let r = 0, g = 0, b = 0;
205205

@@ -215,8 +215,6 @@ function WebGLLights() {
215215
let numPointShadows = 0;
216216
let numSpotShadows = 0;
217217

218-
const viewMatrix = camera.matrixWorldInverse;
219-
220218
lights.sort( shadowCastingLightsFirst );
221219

222220
for ( let i = 0, l = lights.length; i < l; i ++ ) {
@@ -248,10 +246,6 @@ function WebGLLights() {
248246
const uniforms = cache.get( light );
249247

250248
uniforms.color.copy( light.color ).multiplyScalar( light.intensity );
251-
uniforms.direction.setFromMatrixPosition( light.matrixWorld );
252-
vector3.setFromMatrixPosition( light.target.matrixWorld );
253-
uniforms.direction.sub( vector3 );
254-
uniforms.direction.transformDirection( viewMatrix );
255249

256250
if ( light.castShadow ) {
257251

@@ -281,16 +275,10 @@ function WebGLLights() {
281275
const uniforms = cache.get( light );
282276

283277
uniforms.position.setFromMatrixPosition( light.matrixWorld );
284-
uniforms.position.applyMatrix4( viewMatrix );
285278

286279
uniforms.color.copy( color ).multiplyScalar( intensity );
287280
uniforms.distance = distance;
288281

289-
uniforms.direction.setFromMatrixPosition( light.matrixWorld );
290-
vector3.setFromMatrixPosition( light.target.matrixWorld );
291-
uniforms.direction.sub( vector3 );
292-
uniforms.direction.transformDirection( viewMatrix );
293-
294282
uniforms.coneCos = Math.cos( light.angle );
295283
uniforms.penumbraCos = Math.cos( light.angle * ( 1 - light.penumbra ) );
296284
uniforms.decay = light.decay;
@@ -328,24 +316,9 @@ function WebGLLights() {
328316
// (b) intensity is the brightness of the light
329317
uniforms.color.copy( color ).multiplyScalar( intensity );
330318

331-
uniforms.position.setFromMatrixPosition( light.matrixWorld );
332-
uniforms.position.applyMatrix4( viewMatrix );
333-
334-
// extract local rotation of light to derive width/height half vectors
335-
matrix42.identity();
336-
matrix4.copy( light.matrixWorld );
337-
matrix4.premultiply( viewMatrix );
338-
matrix42.extractRotation( matrix4 );
339-
340319
uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 );
341320
uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 );
342321

343-
uniforms.halfWidth.applyMatrix4( matrix42 );
344-
uniforms.halfHeight.applyMatrix4( matrix42 );
345-
346-
// TODO (abelnation): RectAreaLight distance?
347-
// uniforms.distance = distance;
348-
349322
state.rectArea[ rectAreaLength ] = uniforms;
350323

351324
rectAreaLength ++;
@@ -354,9 +327,6 @@ function WebGLLights() {
354327

355328
const uniforms = cache.get( light );
356329

357-
uniforms.position.setFromMatrixPosition( light.matrixWorld );
358-
uniforms.position.applyMatrix4( viewMatrix );
359-
360330
uniforms.color.copy( light.color ).multiplyScalar( light.intensity );
361331
uniforms.distance = light.distance;
362332
uniforms.decay = light.decay;
@@ -390,10 +360,6 @@ function WebGLLights() {
390360

391361
const uniforms = cache.get( light );
392362

393-
uniforms.direction.setFromMatrixPosition( light.matrixWorld );
394-
uniforms.direction.transformDirection( viewMatrix );
395-
uniforms.direction.normalize();
396-
397363
uniforms.skyColor.copy( light.color ).multiplyScalar( intensity );
398364
uniforms.groundColor.copy( light.groundColor ).multiplyScalar( intensity );
399365

@@ -459,8 +425,94 @@ function WebGLLights() {
459425

460426
}
461427

428+
function setupView( lights, camera ) {
429+
430+
let directionalLength = 0;
431+
let pointLength = 0;
432+
let spotLength = 0;
433+
let rectAreaLength = 0;
434+
let hemiLength = 0;
435+
436+
const viewMatrix = camera.matrixWorldInverse;
437+
438+
for ( let i = 0, l = lights.length; i < l; i ++ ) {
439+
440+
const light = lights[ i ];
441+
442+
if ( light.isDirectionalLight ) {
443+
444+
const uniforms = state.directional[ directionalLength ];
445+
446+
uniforms.direction.setFromMatrixPosition( light.matrixWorld );
447+
vector3.setFromMatrixPosition( light.target.matrixWorld );
448+
uniforms.direction.sub( vector3 );
449+
uniforms.direction.transformDirection( viewMatrix );
450+
451+
directionalLength ++;
452+
453+
} else if ( light.isSpotLight ) {
454+
455+
const uniforms = state.spot[ spotLength ];
456+
457+
uniforms.position.setFromMatrixPosition( light.matrixWorld );
458+
uniforms.position.applyMatrix4( viewMatrix );
459+
460+
uniforms.direction.setFromMatrixPosition( light.matrixWorld );
461+
vector3.setFromMatrixPosition( light.target.matrixWorld );
462+
uniforms.direction.sub( vector3 );
463+
uniforms.direction.transformDirection( viewMatrix );
464+
465+
spotLength ++;
466+
467+
} else if ( light.isRectAreaLight ) {
468+
469+
const uniforms = state.rectArea[ rectAreaLength ];
470+
471+
uniforms.position.setFromMatrixPosition( light.matrixWorld );
472+
uniforms.position.applyMatrix4( viewMatrix );
473+
474+
// extract local rotation of light to derive width/height half vectors
475+
matrix42.identity();
476+
matrix4.copy( light.matrixWorld );
477+
matrix4.premultiply( viewMatrix );
478+
matrix42.extractRotation( matrix4 );
479+
480+
uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 );
481+
uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 );
482+
483+
uniforms.halfWidth.applyMatrix4( matrix42 );
484+
uniforms.halfHeight.applyMatrix4( matrix42 );
485+
486+
rectAreaLength ++;
487+
488+
} else if ( light.isPointLight ) {
489+
490+
const uniforms = state.point[ pointLength ];
491+
492+
uniforms.position.setFromMatrixPosition( light.matrixWorld );
493+
uniforms.position.applyMatrix4( viewMatrix );
494+
495+
pointLength ++;
496+
497+
} else if ( light.isHemisphereLight ) {
498+
499+
const uniforms = state.hemi[ hemiLength ];
500+
501+
uniforms.direction.setFromMatrixPosition( light.matrixWorld );
502+
uniforms.direction.transformDirection( viewMatrix );
503+
uniforms.direction.normalize();
504+
505+
hemiLength ++;
506+
507+
}
508+
509+
}
510+
511+
}
512+
462513
return {
463514
setup: setup,
515+
setupView: setupView,
464516
state: state
465517
};
466518

src/renderers/webgl/WebGLRenderStates.d.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,16 @@ interface WebGLRenderState {
1212
shadowsArray: Light[];
1313
lights: WebGLLights;
1414
};
15-
setupLights( camera: Camera ): void;
15+
setupLights(): void;
16+
setupLightsView( camera: Camera ): void;
1617
pushLight( light: Light ): void;
1718
pushShadow( shadowLight: Light ): void;
1819

1920
}
2021

2122
export class WebGLRenderStates {
2223

23-
get( scene: Scene, camera: Camera ): WebGLRenderState;
24+
get( scene: Scene, renderCallNesting: number ): WebGLRenderState;
2425
dispose(): void;
2526

2627
}

src/renderers/webgl/WebGLRenderStates.js

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,15 @@ function WebGLRenderState() {
2626

2727
}
2828

29-
function setupLights( camera ) {
29+
function setupLights() {
3030

31-
lights.setup( lightsArray, shadowsArray, camera );
31+
lights.setup( lightsArray, shadowsArray );
32+
33+
}
34+
35+
function setupLightsView( camera ) {
36+
37+
lights.setupView( lightsArray, camera );
3238

3339
}
3440

@@ -43,6 +49,7 @@ function WebGLRenderState() {
4349
init: init,
4450
state: state,
4551
setupLights: setupLights,
52+
setupLightsView: setupLightsView,
4653

4754
pushLight: pushLight,
4855
pushShadow: pushShadow
@@ -54,26 +61,26 @@ function WebGLRenderStates() {
5461

5562
let renderStates = new WeakMap();
5663

57-
function get( scene, camera ) {
64+
function get( scene, renderCallNesting ) {
5865

5966
let renderState;
6067

6168
if ( renderStates.has( scene ) === false ) {
6269

6370
renderState = new WebGLRenderState();
64-
renderStates.set( scene, new WeakMap() );
65-
renderStates.get( scene ).set( camera, renderState );
71+
renderStates.set( scene, [] );
72+
renderStates.get( scene ).push( renderState );
6673

6774
} else {
6875

69-
if ( renderStates.get( scene ).has( camera ) === false ) {
76+
if ( renderCallNesting >= renderStates.get( scene ).length ) {
7077

7178
renderState = new WebGLRenderState();
72-
renderStates.get( scene ).set( camera, renderState );
79+
renderStates.get( scene ).push( renderState );
7380

7481
} else {
7582

76-
renderState = renderStates.get( scene ).get( camera );
83+
renderState = renderStates.get( scene )[ renderCallNesting ];
7784

7885
}
7986

0 commit comments

Comments
 (0)