UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

164 lines (161 loc) 6.9 kB
import { math } from '../../core/math/math.js'; import { Vec3 } from '../../core/math/vec3.js'; import { Mat4 } from '../../core/math/mat4.js'; import { BoundingBox } from '../../core/shape/bounding-box.js'; import { SHADOWUPDATE_NONE } from '../constants.js'; import { ShadowMap } from './shadow-map.js'; import { RenderPassShadowDirectional } from './render-pass-shadow-directional.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() ]; 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; } class ShadowRendererDirectional { cull(light, comp, camera, casters = null) { light.visibleThisFrame = true; if (!light._shadowMap) { light._shadowMap = ShadowMap.create(this.device, light); } const nearDist = camera._nearClip; this.generateSplitDistances(light, nearDist, Math.min(camera._farClip, light.shadowDistance)); const shadowUpdateOverrides = light.shadowUpdateOverrides; for(let cascade = 0; cascade < light.numCascades; cascade++){ if (shadowUpdateOverrides?.[cascade] === SHADOWUPDATE_NONE) { break; } const lightRenderData = light.getRenderData(camera, cascade); const shadowCam = lightRenderData.shadowCamera; shadowCam.renderTarget = light._shadowMap.renderTargets[0]; lightRenderData.shadowViewport.copy(light.cascades[cascade]); lightRenderData.shadowScissor.copy(light.cascades[cascade]); const shadowCamNode = shadowCam._node; const lightNode = light._node; shadowCamNode.setPosition(lightNode.getPosition()); shadowCamNode.setRotation(lightNode.getRotation()); shadowCamNode.rotateLocal(-90, 0, 0); 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); let radius = 0; for(let i = 0; i < 8; i++){ const dist = frustumPoints[i].sub(center).length(); if (dist > radius) { radius = dist; } } const right = shadowCamNode.right; const up = shadowCamNode.up; const lightDir = shadowCamNode.forward; 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); shadowCamNode.setPosition(center); shadowCamNode.translateLocal(0, 0, 1000000); shadowCam.nearClip = 0.01; shadowCam.farClip = 2000000; shadowCam.orthoHeight = radius; this.renderer.updateCameraFrustum(shadowCam); this.shadowRenderer.cullShadowCasters(comp, light, lightRenderData.visibleCasters, shadowCam, casters); const cascadeFlag = 1 << cascade; const visibleCasters = lightRenderData.visibleCasters; const origNumVisibleCasters = visibleCasters.length; let numVisibleCasters = 0; for(let i = 0; i < origNumVisibleCasters; i++){ const meshInstance = visibleCasters[i]; if (meshInstance.shadowCascadeMask & cascadeFlag) { visibleCasters[numVisibleCasters++] = meshInstance; if (numVisibleCasters === 1) { visibleSceneAabb.copy(meshInstance.aabb); } else { visibleSceneAabb.add(meshInstance.aabb); } } } if (origNumVisibleCasters !== numVisibleCasters) { visibleCasters.length = numVisibleCasters; } shadowCamView.copy(shadowCamNode.getWorldTransform()).invert(); const depthRange = getDepthRange(shadowCamView, visibleSceneAabb.getMin(), visibleSceneAabb.getMax()); shadowCamNode.translateLocal(0, 0, depthRange.max + 0.1); shadowCam.farClip = depthRange.max - depthRange.min + 0.2; lightRenderData.projectionCompensation = radius; } } generateSplitDistances(light, nearDist, farDist) { light._shadowCascadeDistances.fill(farDist); for(let i = 1; i < light.numCascades; i++){ const fraction = i / light.numCascades; const linearDist = nearDist + (farDist - nearDist) * fraction; const logDist = nearDist * (farDist / nearDist) ** fraction; const dist = math.lerp(linearDist, logDist, light.cascadeDistribution); light._shadowCascadeDistances[i - 1] = dist; } } getLightRenderPass(light, camera) { let renderPass = null; if (this.shadowRenderer.needsShadowRendering(light)) { const faceCount = light.numShadowFaces; const shadowUpdateOverrides = light.shadowUpdateOverrides; let allCascadesRendering = true; let shadowCamera; for(let face = 0; face < faceCount; face++){ if (shadowUpdateOverrides?.[face] === SHADOWUPDATE_NONE) { allCascadesRendering = false; } shadowCamera = this.shadowRenderer.prepareFace(light, camera, face); } renderPass = new RenderPassShadowDirectional(this.device, this.shadowRenderer, light, camera, allCascadesRendering); this.shadowRenderer.setupRenderPass(renderPass, shadowCamera, allCascadesRendering); } return renderPass; } constructor(renderer, shadowRenderer){ this.renderer = renderer; this.shadowRenderer = shadowRenderer; this.device = renderer.device; } } export { ShadowRendererDirectional };