Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split ShadowRenderer into class for local and clsas for directional lights #4915

Merged
merged 1 commit into from
Dec 7, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/framework/lightmapper/lightmapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class Lightmapper {
this.scene = scene;
this.renderer = renderer;
this.assets = assets;
this.shadowMapCache = renderer._shadowRenderer.shadowMapCache;
this.shadowMapCache = renderer.shadowMapCache;

this._tempSet = new Set();
this._initCalled = false;
Expand Down Expand Up @@ -852,12 +852,12 @@ class Lightmapper {
}

if (light.type === LIGHTTYPE_DIRECTIONAL) {
this.renderer._shadowRenderer.cullDirectional(light, casters, this.camera);
this.renderer._shadowRendererDirectional.cull(light, casters, this.camera);
this.renderer.renderShadowsDirectional(lightArray[light.type], this.camera);
} else {
this.renderer._shadowRenderer.cullLocal(light, casters);
this.renderer._shadowRendererLocal.cull(light, casters);
this.renderer.renderShadowsLocal(lightArray[light.type], this.camera);
}

this.renderer.renderShadows(lightArray[light.type], this.camera);
}

return true;
Expand Down
31 changes: 25 additions & 6 deletions src/scene/renderer/forward-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ class ForwardRenderer extends Renderer {
}
}

renderShadows(lights, camera) {
renderShadowsLocal(lights, camera) {

const isClustered = this.scene.clusteredLightingEnabled;

Expand All @@ -428,8 +428,9 @@ class ForwardRenderer extends Renderer {

for (let i = 0; i < lights.length; i++) {
const light = lights[i];
Debug.assert(light._type !== LIGHTTYPE_DIRECTIONAL);

if (isClustered && light._type !== LIGHTTYPE_DIRECTIONAL) {
if (isClustered) {

// skip clustered shadows with no assigned atlas slot
if (!light.atlasViewportAllocated) {
Expand All @@ -442,7 +443,25 @@ class ForwardRenderer extends Renderer {
}
}

this._shadowRenderer.render(light, camera);
this._shadowRendererLocal.render(light, camera);
}

// #if _PROFILER
this._shadowMapTime += now() - shadowMapStartTime;
// #endif
}

renderShadowsDirectional(lights, camera) {

// #if _PROFILER
const shadowMapStartTime = now();
// #endif

for (let i = 0; i < lights.length; i++) {
const light = lights[i];
Debug.assert(light._type === LIGHTTYPE_DIRECTIONAL);

this._shadowRendererDirectional.render(light, camera);
}

// #if _PROFILER
Expand Down Expand Up @@ -867,8 +886,8 @@ class ForwardRenderer extends Renderer {

// render shadows for all local visible lights - these shadow maps are shared by all cameras
if (!clusteredLightingEnabled || (clusteredLightingEnabled && this.scene.lighting.shadowsEnabled)) {
this.renderShadows(layerComposition._splitLights[LIGHTTYPE_SPOT]);
this.renderShadows(layerComposition._splitLights[LIGHTTYPE_OMNI]);
this.renderShadowsLocal(layerComposition._splitLights[LIGHTTYPE_SPOT]);
this.renderShadowsLocal(layerComposition._splitLights[LIGHTTYPE_OMNI]);
}

// update light clusters
Expand Down Expand Up @@ -1048,7 +1067,7 @@ class ForwardRenderer extends Renderer {
const layer = layerComposition.layerList[renderAction.layerIndex];
const camera = layer.cameras[renderAction.cameraIndex];

this.renderShadows(renderAction.directionalLights, camera.camera);
this.renderShadowsDirectional(renderAction.directionalLights, camera.camera);
}

renderPassPostprocessing(renderAction, layerComposition) {
Expand Down
19 changes: 13 additions & 6 deletions src/scene/renderer/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ import { BindGroup } from '../../platform/graphics/bind-group.js';
import { UniformFormat, UniformBufferFormat } from '../../platform/graphics/uniform-buffer-format.js';
import { BindGroupFormat, BindBufferFormat, BindTextureFormat } from '../../platform/graphics/bind-group-format.js';

import { ShadowRenderer } from './shadow-renderer.js';
import { ShadowMapCache } from './shadow-map-cache.js';
import { ShadowRendererLocal } from './shadow-renderer-local.js';
import { ShadowRendererDirectional } from './shadow-renderer-directional.js';
import { CookieRenderer } from './cookie-renderer.js';
import { StaticMeshes } from './static-meshes.js';

Expand Down Expand Up @@ -85,7 +87,9 @@ class Renderer {
this.lightTextureAtlas = new LightTextureAtlas(graphicsDevice);

// shadows
this._shadowRenderer = new ShadowRenderer(this, this.lightTextureAtlas);
this.shadowMapCache = new ShadowMapCache();
this._shadowRendererLocal = new ShadowRendererLocal(this, this.lightTextureAtlas);
this._shadowRendererDirectional = new ShadowRendererDirectional(this, this.lightTextureAtlas);

// cookies
this._cookieRenderer = new CookieRenderer(graphicsDevice, this.lightTextureAtlas);
Expand Down Expand Up @@ -146,8 +150,11 @@ class Renderer {
}

destroy() {
this._shadowRenderer.destroy();
this._shadowRenderer = null;
this._shadowRendererLocal = null;
this._shadowRendererDirectional = null;

this.shadowMapCache.destroy();
this.shadowMapCache = null;

this._cookieRenderer.destroy();
this._cookieRenderer = null;
Expand Down Expand Up @@ -844,7 +851,7 @@ class Renderer {
if (light._type !== LIGHTTYPE_DIRECTIONAL) {
if (light.visibleThisFrame && light.castShadows && light.shadowUpdateMode !== SHADOWUPDATE_NONE) {
const casters = comp._lightCompositionData[i].shadowCastersList;
this._shadowRenderer.cullLocal(light, casters);
this._shadowRendererLocal.cull(light, casters);
}
}
}
Expand All @@ -859,7 +866,7 @@ class Renderer {
const lightIndex = renderAction.directionalLightsIndices[j];
const light = comp._lights[lightIndex];
const casters = comp._lightCompositionData[lightIndex].shadowCastersList;
this._shadowRenderer.cullDirectional(light, casters, renderAction.camera.camera);
this._shadowRendererDirectional.cull(light, casters, renderAction.camera.camera);
}
}
}
Expand Down
14 changes: 7 additions & 7 deletions src/scene/renderer/shadow-map-cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,22 @@ import { ShadowMap } from './shadow-map.js';
class ShadowMapCache {
constructor() {
// maps a shadow map key to an array of shadow maps in the cache
this.shadowMapCache = new Map();
this.cache = new Map();
}

destroy() {
this.clear();
this.shadowMapCache = null;
this.cache = null;
}

// remove all shadowmaps from the cache
clear() {
this.shadowMapCache.forEach((shadowMaps) => {
this.cache.forEach((shadowMaps) => {
shadowMaps.forEach((shadowMap) => {
shadowMap.destroy();
});
});
this.shadowMapCache.clear();
this.cache.clear();
}

// generates a string key for the shadow map required by the light
Expand All @@ -42,7 +42,7 @@ class ShadowMapCache {

// get matching shadow buffer from the cache
const key = this.getKey(light);
const shadowMaps = this.shadowMapCache.get(key);
const shadowMaps = this.cache.get(key);
if (shadowMaps && shadowMaps.length) {
return shadowMaps.pop();
}
Expand All @@ -56,11 +56,11 @@ class ShadowMapCache {
// returns shadow map for the light back to the cache
add(light, shadowMap) {
const key = this.getKey(light);
const shadowMaps = this.shadowMapCache.get(key);
const shadowMaps = this.cache.get(key);
if (shadowMaps) {
shadowMaps.push(shadowMap);
} else {
this.shadowMapCache.set(key, [shadowMap]);
this.cache.set(key, [shadowMap]);
}
}
}
Expand Down
158 changes: 158 additions & 0 deletions src/scene/renderer/shadow-renderer-directional.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { Vec3 } from '../../core/math/vec3.js';
import { Mat4 } from '../../core/math/mat4.js';
import { BoundingBox } from '../../core/shape/bounding-box.js';

import { ShadowRenderer } from "./shadow-renderer.js";
import { ShadowMap } from './shadow-map.js';

const visibleSceneAabb = new BoundingBox();
const center = new Vec3();
const shadowCamView = new Mat4();

const aabbPoints = [
new Vec3(), new Vec3(), new Vec3(), new Vec3(),
new Vec3(), new Vec3(), new Vec3(), new Vec3()
];

// evaluate depth range the aabb takes in the space of the camera
const _depthRange = { min: 0, max: 0 };
function getDepthRange(cameraViewMatrix, aabbMin, aabbMax) {
aabbPoints[0].x = aabbPoints[1].x = aabbPoints[2].x = aabbPoints[3].x = aabbMin.x;
aabbPoints[1].y = aabbPoints[3].y = aabbPoints[7].y = aabbPoints[5].y = aabbMin.y;
aabbPoints[2].z = aabbPoints[3].z = aabbPoints[6].z = aabbPoints[7].z = aabbMin.z;
aabbPoints[4].x = aabbPoints[5].x = aabbPoints[6].x = aabbPoints[7].x = aabbMax.x;
aabbPoints[0].y = aabbPoints[2].y = aabbPoints[4].y = aabbPoints[6].y = aabbMax.y;
aabbPoints[0].z = aabbPoints[1].z = aabbPoints[4].z = aabbPoints[5].z = aabbMax.z;

let minz = 9999999999;
let maxz = -9999999999;

for (let i = 0; i < 8; ++i) {
cameraViewMatrix.transformPoint(aabbPoints[i], aabbPoints[i]);
const z = aabbPoints[i].z;
if (z < minz) minz = z;
if (z > maxz) maxz = z;
}

_depthRange.min = minz;
_depthRange.max = maxz;
return _depthRange;
}

/**
* @ignore
*/
class ShadowRendererDirectional extends ShadowRenderer {
// cull directional shadow map
cull(light, drawCalls, camera) {

// force light visibility if function was manually called
light.visibleThisFrame = true;

if (!light._shadowMap) {
light._shadowMap = ShadowMap.create(this.device, light);
}

// generate splits for the cascades
const nearDist = camera._nearClip;
this.generateSplitDistances(light, nearDist, light.shadowDistance);

for (let cascade = 0; cascade < light.numCascades; cascade++) {

const lightRenderData = light.getRenderData(camera, cascade);
const shadowCam = lightRenderData.shadowCamera;

// assign render target
// Note: this is done during rendering for all shadow maps, but do it here for the case shadow rendering for the directional light
// is disabled - we need shadow map to be assigned for rendering to work even in this case. This needs further refactoring - as when
// shadow rendering is set to SHADOWUPDATE_NONE, we should not even execute shadow map culling
shadowCam.renderTarget = light._shadowMap.renderTargets[0];

// viewport
lightRenderData.shadowViewport.copy(light.cascades[cascade]);
lightRenderData.shadowScissor.copy(light.cascades[cascade]);

const shadowCamNode = shadowCam._node;
const lightNode = light._node;

shadowCamNode.setPosition(lightNode.getPosition());

// Camera looks down the negative Z, and directional light points down the negative Y
shadowCamNode.setRotation(lightNode.getRotation());
shadowCamNode.rotateLocal(-90, 0, 0);

// get camera's frustum corners for the cascade, convert them to world space and find their center
const frustumNearDist = cascade === 0 ? nearDist : light._shadowCascadeDistances[cascade - 1];
const frustumFarDist = light._shadowCascadeDistances[cascade];
const frustumPoints = camera.getFrustumCorners(frustumNearDist, frustumFarDist);
center.set(0, 0, 0);
const cameraWorldMat = camera.node.getWorldTransform();
for (let i = 0; i < 8; i++) {
cameraWorldMat.transformPoint(frustumPoints[i], frustumPoints[i]);
center.add(frustumPoints[i]);
}
center.mulScalar(1 / 8);

// radius of the world space bounding sphere for the frustum slice
let radius = 0;
for (let i = 0; i < 8; i++) {
const dist = frustumPoints[i].sub(center).length();
if (dist > radius)
radius = dist;
}

// axis of light coordinate system
const right = shadowCamNode.right;
const up = shadowCamNode.up;
const lightDir = shadowCamNode.forward;

// transform the sphere's center into the center of the shadow map, pixel aligned.
// this makes the shadow map stable and avoids shimmering on the edges when the camera moves
const sizeRatio = 0.25 * light._shadowResolution / radius;
const x = Math.ceil(center.dot(up) * sizeRatio) / sizeRatio;
const y = Math.ceil(center.dot(right) * sizeRatio) / sizeRatio;

const scaledUp = up.mulScalar(x);
const scaledRight = right.mulScalar(y);
const dot = center.dot(lightDir);
const scaledDir = lightDir.mulScalar(dot);
center.add2(scaledUp, scaledRight).add(scaledDir);

// look at the center from far away to include all casters during culling
shadowCamNode.setPosition(center);
shadowCamNode.translateLocal(0, 0, 1000000);
shadowCam.nearClip = 0.01;
shadowCam.farClip = 2000000;
shadowCam.orthoHeight = radius;

// cull shadow casters
this.renderer.updateCameraFrustum(shadowCam);
this.cullShadowCasters(drawCalls, lightRenderData.visibleCasters, shadowCam);

// find out AABB of visible shadow casters
let emptyAabb = true;
const visibleCasters = lightRenderData.visibleCasters;
for (let i = 0; i < visibleCasters.length; i++) {
const meshInstance = visibleCasters[i];

if (emptyAabb) {
emptyAabb = false;
visibleSceneAabb.copy(meshInstance.aabb);
} else {
visibleSceneAabb.add(meshInstance.aabb);
}
}

// calculate depth range of the caster's AABB from the point of view of the shadow camera
shadowCamView.copy(shadowCamNode.getWorldTransform()).invert();
const depthRange = getDepthRange(shadowCamView, visibleSceneAabb.getMin(), visibleSceneAabb.getMax());

// adjust shadow camera's near and far plane to the depth range of casters to maximize precision
// of values stored in the shadow map. Make it slightly larger to avoid clipping on near / far plane.
shadowCamNode.translateLocal(0, 0, depthRange.max + 0.1);
shadowCam.farClip = depthRange.max - depthRange.min + 0.2;
}
}
}

export { ShadowRendererDirectional };