Skip to content

Commit 5cd4bf6

Browse files
mvaligurskyMartin Valigurskywilleastcott
authored
Shadow mapping refactoring (playcanvas#3155)
* renamed point to omni light * render action only stores shadow casting directional lights, not all direcrional lights * example uses vsm shadow * shadow map / shadowmapcache refactor * spot cookie camera separate from shadow camera * added spotlight to lights-baked example * adjusted starting angle to immediately see directional shadow * refactoring of shadowCamera * tiny cleanup * Remove whitespace * Comment tweaks * removed unneeded optional chaining operator Co-authored-by: Martin Valigursky <mvaligursky@snapchat.com> Co-authored-by: Will Eastcott <will@playcanvas.com>
1 parent 825ca50 commit 5cd4bf6

File tree

13 files changed

+628
-475
lines changed

13 files changed

+628
-475
lines changed

examples/graphics/lights-baked.html

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
// Create an entity with a directional light component that is configured as a baked light
7272
var light = new pc.Entity();
7373
light.addComponent("light", {
74+
type: "directional",
7475
affectDynamic: false,
7576
affectLightmapped: true,
7677
bake: true,
@@ -81,14 +82,15 @@
8182
shadowResolution: 2048,
8283
shadowType: pc.SHADOW_PCF3,
8384
color: pc.Color.GREEN,
84-
type: "directional"
85+
intensity: 0.5
8586
});
8687
app.root.addChild(light);
8788
light.setLocalEulerAngles(45, 30, 0);
8889

89-
// Create an entity with a point light component that is configured as a baked light
90-
var lightPoint = new pc.Entity();
91-
lightPoint.addComponent("light", {
90+
// Create an entity with an omni light component that is configured as a baked light
91+
var lightOmni = new pc.Entity();
92+
lightOmni.addComponent("light", {
93+
type: "omni",
9294
affectDynamic: false,
9395
affectLightmapped: true,
9496
bake: true,
@@ -99,11 +101,32 @@
99101
shadowResolution: 512,
100102
shadowType: pc.SHADOW_PCF3,
101103
color: pc.Color.RED,
102-
range: 100,
103-
type: "point"
104+
range: 10
104105
});
105-
lightPoint.setLocalPosition(0, 2, 0);
106-
app.root.addChild(lightPoint);
106+
lightOmni.setLocalPosition(4, 2, 0);
107+
app.root.addChild(lightOmni);
108+
109+
// Create an entity with an omni light component that is configured as a baked light
110+
var lightSpot = new pc.Entity();
111+
lightSpot.addComponent("light", {
112+
type: "spot",
113+
affectDynamic: false,
114+
affectLightmapped: true,
115+
bake: true,
116+
castShadows: true,
117+
normalOffsetBias: 0.05,
118+
shadowBias: 0.2,
119+
shadowDistance: 50,
120+
shadowResolution: 512,
121+
shadowType: pc.SHADOW_PCF3,
122+
color: pc.Color.YELLOW,
123+
range: 20,
124+
innerConeAngle: 10,
125+
outerConeAngle: 25
126+
});
127+
lightSpot.setLocalPosition(-3, 10, 0);
128+
lightSpot.lookAt(-3, 0, 0);
129+
app.root.addChild(lightSpot);
107130

108131
// Create an entity with a camera component
109132
var camera = new pc.Entity();

examples/graphics/lights.html

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,11 @@
162162
shadowDistance: 100,
163163
shadowBias: 0.2,
164164
normalOffsetBias: 0.1,
165-
intensity: 0.6
165+
intensity: 0.6,
166+
167+
// soft shadow
168+
shadowType: pc.SHADOW_VSM16,
169+
vsmBlurSize: 16
166170
});
167171
app.root.addChild(directionallight);
168172

@@ -206,7 +210,7 @@
206210
}, this);
207211

208212
// Simple update loop to rotate the light
209-
var angleRad = 0;
213+
var angleRad = 1;
210214
app.on("update", function (dt) {
211215
angleRad += 0.3 * dt;
212216

src/framework/application.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2049,7 +2049,9 @@ class Application extends EventHandler {
20492049
this.graphicsDevice.destroy();
20502050
this.graphicsDevice = null;
20512051

2052+
this.renderer.destroy();
20522053
this.renderer = null;
2054+
20532055
this.tick = null;
20542056

20552057
this.off(); // remove all events

src/graphics/program-lib/programs/standard.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,13 @@ import {
1616
LIGHTSHAPE_PUNCTUAL, LIGHTSHAPE_RECT, LIGHTSHAPE_DISK, LIGHTSHAPE_SPHERE,
1717
LIGHTTYPE_DIRECTIONAL, LIGHTTYPE_OMNI, LIGHTTYPE_SPOT,
1818
SHADER_DEPTH, SHADER_FORWARD, SHADER_FORWARDHDR, SHADER_PICK, SHADER_SHADOW,
19-
SHADOW_PCF3, SHADOW_PCF5, SHADOW_VSM8, SHADOW_VSM16, SHADOW_VSM32,
19+
SHADOW_PCF3, SHADOW_PCF5, SHADOW_VSM8, SHADOW_VSM16, SHADOW_VSM32, SHADOW_COUNT,
2020
SPECOCC_AO,
2121
SPECULAR_PHONG,
2222
SPRITE_RENDERMODE_SLICED, SPRITE_RENDERMODE_TILED
2323
} from '../../../scene/constants.js';
2424
import { WorldClusters } from '../../../scene/world-clusters.js';
2525
import { LayerComposition } from '../../../scene/layer-composition.js';
26-
import { ShadowMapManager } from '../../../scene/renderer/shadow-map-manager.js';
2726

2827
import { begin, end, fogCode, gammaCode, precisionCode, skinCode, tonemapCode, versionCode } from './common.js';
2928

@@ -901,7 +900,7 @@ var standard = {
901900
} else if (shadowPass) {
902901
// ##### SHADOW PASS #####
903902
const smode = options.pass - SHADER_SHADOW;
904-
const numShadowModes = ShadowMapManager.numShadowModes;
903+
const numShadowModes = SHADOW_COUNT;
905904
lightType = Math.floor(smode / numShadowModes);
906905
var shadowType = smode - lightType * numShadowModes;
907906

src/scene/constants.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,9 @@ export const SHADOW_VSM32 = 3;
288288
*/
289289
export const SHADOW_PCF5 = 4;
290290

291+
// non-public number of supported depth shadow modes
292+
export const SHADOW_COUNT = 5;
293+
291294
/**
292295
* @constant
293296
* @name BLUR_BOX

src/scene/light.js

Lines changed: 79 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
SHADOWUPDATE_NONE, SHADOWUPDATE_REALTIME, SHADOWUPDATE_THISFRAME,
1414
LIGHTSHAPE_PUNCTUAL
1515
} from './constants.js';
16+
import { ShadowRenderer } from './renderer/shadow-renderer.js';
1617

1718
var spotCenter = new Vec3();
1819
var spotEndPoint = new Vec3();
@@ -28,30 +29,46 @@ const directionalCascades = [
2829
[new Vec4(0, 0, 0.5, 0.5), new Vec4(0, 0.5, 0.5, 0.5), new Vec4(0.5, 0, 0.5, 0.5), new Vec4(0.5, 0.5, 0.5, 0.5)]
2930
];
3031

31-
// Class storing rendering related private information
32+
// Class storing shadow rendering related private information
3233
class LightRenderData {
33-
constructor(camera, face, lightType) {
34+
constructor(device, camera, face, light) {
35+
36+
// light this data belongs to
37+
this.light = light;
3438

3539
// camera this applies to. Only used by directional light, as directional shadow map
3640
// is culled and rendered for each camera. Local lights' shadow is culled and rendered one time
3741
// and shared between cameras (even though it's not strictly correct and we can get shadows
3842
// from a mesh that is not visible by the camera)
3943
this.camera = camera;
4044

45+
// camera used to cull / render the shadow map
46+
this.shadowCamera = ShadowRenderer.createShadowCamera(device, light._shadowType, light._type, face);
47+
4148
// face index, value is based on light type:
4249
// - spot: always 0
4350
// - omni: cubemap face, 0..5
4451
// - directional: 0 for simple shadows, cascade index for cascaded shadow map
4552
this.face = face;
4653

47-
// shadow camera settings, only used by directional lights
48-
this.position = lightType === LIGHTTYPE_DIRECTIONAL ? new Vec3() : null;
49-
this.orthoHeight = 0;
50-
this.farClip = 0;
51-
5254
// visible shadow casters
5355
this.visibleCasters = [];
5456
}
57+
58+
// returns shadow buffer currently attached to the shadow camera
59+
get shadowBuffer() {
60+
const rt = this.shadowCamera.renderTarget;
61+
if (rt) {
62+
const light = this.light;
63+
if (light._type === LIGHTTYPE_OMNI) {
64+
return rt.colorBuffer;
65+
}
66+
67+
return light._isPcf && light.device.webgl2 ? rt.depthBuffer : rt.colorBuffer;
68+
}
69+
70+
return null;
71+
}
5572
}
5673

5774
/**
@@ -116,22 +133,21 @@ class Light {
116133
this._outerConeAngleCos = Math.cos(this._outerConeAngle * Math.PI / 180);
117134

118135
// Shadow mapping resources
119-
this._shadowCamera = null;
136+
this._shadowMap = null;
120137
this._shadowMatrix = new Mat4();
138+
this._rendererParams = [];
139+
140+
// Shadow mapping properties
121141
this.shadowDistance = 40;
122142
this._shadowResolution = 1024;
123143
this.shadowBias = -0.0005;
124144
this._normalOffsetBias = 0.0;
125145
this.shadowUpdateMode = SHADOWUPDATE_REALTIME;
146+
this._isVsm = false;
147+
this._isPcf = true;
126148

127149
this._scene = null;
128150
this._node = null;
129-
this._rendererParams = [];
130-
131-
this._isVsm = false;
132-
this._isPcf = true;
133-
this._cacheShadowMap = false;
134-
this._isCachedShadowMap = false;
135151

136152
// private rendering data
137153
this._renderData = [];
@@ -145,6 +161,24 @@ class Light {
145161
this._renderData = null;
146162
}
147163

164+
// destroys shadow map related resources, called when shadow properties change and resources
165+
// need to be recreated
166+
_destroyShadowMap() {
167+
168+
this._renderData.length = 0;
169+
170+
if (this._shadowMap) {
171+
if (!this._shadowMap.cached) {
172+
this._shadowMap.destroy();
173+
}
174+
this._shadowMap = null;
175+
}
176+
177+
if (this.shadowUpdateMode === SHADOWUPDATE_NONE) {
178+
this.shadowUpdateMode = SHADOWUPDATE_THISFRAME;
179+
}
180+
}
181+
148182
// returns LightRenderData with matching camera and face
149183
getRenderData(camera, face) {
150184

@@ -157,7 +191,7 @@ class Light {
157191
}
158192

159193
// create new one
160-
const rd = new LightRenderData(camera, face, this._type);
194+
const rd = new LightRenderData(this.device, camera, face, this);
161195
this._renderData.push(rd);
162196
return rd;
163197
}
@@ -225,6 +259,17 @@ class Light {
225259
this.cascades = directionalCascades[value - 1];
226260
}
227261

262+
get shadowMap() {
263+
return this._shadowMap;
264+
}
265+
266+
set shadowMap(shadowMap) {
267+
if (this._shadowMap !== shadowMap) {
268+
this._destroyShadowMap();
269+
this._shadowMap = shadowMap;
270+
}
271+
}
272+
228273
getColor() {
229274
return this._color;
230275
}
@@ -317,54 +362,16 @@ class Light {
317362
this._updateFinalColor();
318363
}
319364

320-
get shadowMap() {
321-
322-
if (this._shadowCamera) {
323-
const rt = this._shadowCamera.renderTarget;
324-
if (this._type === LIGHTTYPE_OMNI) {
325-
return rt.colorBuffer;
326-
}
327-
328-
return this._isPcf && this.device.webgl2 ? rt.depthBuffer : rt.colorBuffer;
329-
}
330-
331-
return null;
332-
}
333-
334-
_destroyShadowMap() {
335-
if (this._shadowCamera) {
336-
if (!this._isCachedShadowMap) {
337-
var rt = this._shadowCamera.renderTarget;
338-
var i;
339-
if (rt) {
340-
if (rt.length) {
341-
for (i = 0; i < rt.length; i++) {
342-
rt[i].destroyTextureBuffers();
343-
rt[i].destroy();
344-
}
345-
} else {
346-
rt.destroyTextureBuffers();
347-
rt.destroy();
348-
}
349-
}
350-
}
351-
this._shadowCamera.renderTarget = null;
352-
this._shadowCamera = null;
353-
this._shadowCubeMap = null;
354-
if (this.shadowUpdateMode === SHADOWUPDATE_NONE) {
355-
this.shadowUpdateMode = SHADOWUPDATE_THISFRAME;
356-
}
357-
}
358-
}
359-
360365
updateShadow() {
361366
if (this.shadowUpdateMode !== SHADOWUPDATE_REALTIME) {
362367
this.shadowUpdateMode = SHADOWUPDATE_THISFRAME;
363368
}
364369
}
365370

366371
layersDirty() {
367-
this._scene.layers._dirtyLights = true;
372+
if (this._scene?.layers) {
373+
this._scene.layers._dirtyLights = true;
374+
}
368375
}
369376

370377
updateKey() {
@@ -401,7 +408,7 @@ class Light {
401408
}
402409

403410
if (key !== this.key && this._scene !== null) {
404-
// TODO: most of the changes to the key should not invalidate the composition,
411+
// TODO: most of the changes to the key should not invalidate the composition,
405412
// probably only _type and _castShadows
406413
this.layersDirty();
407414
}
@@ -430,7 +437,7 @@ class Light {
430437
return this._shape;
431438
}
432439

433-
set shape(value = LIGHTSHAPE_PUNCTUAL) {
440+
set shape(value) {
434441
if (this._shape === value)
435442
return;
436443

@@ -490,27 +497,28 @@ class Light {
490497
}
491498

492499
set castShadows(value) {
493-
if (this._castShadows === value)
494-
return;
495-
496-
this._castShadows = value;
497-
this.updateKey();
500+
if (this._castShadows !== value) {
501+
this._castShadows = value;
502+
this._destroyShadowMap();
503+
this.layersDirty();
504+
this.updateKey();
505+
}
498506
}
499507

500508
get shadowResolution() {
501509
return this._shadowResolution;
502510
}
503511

504512
set shadowResolution(value) {
505-
if (this._shadowResolution === value)
506-
return;
507-
508-
if (this._type === LIGHTTYPE_OMNI) {
509-
value = Math.min(value, this.device.maxCubeMapSize);
510-
} else {
511-
value = Math.min(value, this.device.maxTextureSize);
513+
if (this._shadowResolution !== value) {
514+
if (this._type === LIGHTTYPE_OMNI) {
515+
value = Math.min(value, this.device.maxCubeMapSize);
516+
} else {
517+
value = Math.min(value, this.device.maxTextureSize);
518+
}
519+
this._shadowResolution = value;
520+
this._destroyShadowMap();
512521
}
513-
this._shadowResolution = value;
514522
}
515523

516524
get vsmBlurSize() {

0 commit comments

Comments
 (0)