UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

100 lines (97 loc) 4.86 kB
import { math } from '../../core/math/math.js'; import { LIGHTTYPE_SPOT, LIGHTTYPE_OMNI } from '../constants.js'; import { ShadowMap } from './shadow-map.js'; import { RenderPassShadowLocalNonClustered } from './render-pass-shadow-local-non-clustered.js'; /** * @import { FrameGraph } from '../../scene/frame-graph.js' * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js' * @import { Light } from '../../scene/light.js' * @import { Renderer } from './renderer.js' * @import { ShadowRenderer } from './shadow-renderer.js' */ class ShadowRendererLocal { constructor(renderer, shadowRenderer){ // temporary list to collect lights to render shadows for this.shadowLights = []; this.renderer = renderer; this.shadowRenderer = shadowRenderer; this.device = renderer.device; } // cull local shadow map cull(light, comp, casters = null) { const isClustered = this.renderer.scene.clusteredLightingEnabled; // force light visibility if function was manually called light.visibleThisFrame = true; // allocate shadow map unless in clustered lighting mode if (!isClustered) { if (!light._shadowMap) { light._shadowMap = ShadowMap.create(this.device, light); } } const type = light._type; const faceCount = type === LIGHTTYPE_SPOT ? 1 : 6; for(let face = 0; face < faceCount; face++){ // render data are shared between cameras for local lights, so pass null for camera const lightRenderData = light.getRenderData(null, face); const shadowCam = lightRenderData.shadowCamera; shadowCam.nearClip = light.attenuationEnd / 1000; shadowCam.farClip = light.attenuationEnd; const shadowCamNode = shadowCam._node; const lightNode = light._node; shadowCamNode.setPosition(lightNode.getPosition()); if (type === LIGHTTYPE_SPOT) { shadowCam.fov = light._outerConeAngle * 2; // Camera looks down the negative Z, and spot light points down the negative Y shadowCamNode.setRotation(lightNode.getRotation()); shadowCamNode.rotateLocal(-90, 0, 0); } else if (type === LIGHTTYPE_OMNI) { // when rendering omni shadows to an atlas, use larger fov by few pixels to allow shadow filtering to stay on a single face if (isClustered) { const tileSize = this.shadowRenderer.lightTextureAtlas.shadowAtlasResolution * light.atlasViewport.z / 3; // using 3x3 for cubemap const texelSize = 2 / tileSize; const filterSize = texelSize * this.shadowRenderer.lightTextureAtlas.shadowEdgePixels; shadowCam.fov = Math.atan(1 + filterSize) * math.RAD_TO_DEG * 2; } else { shadowCam.fov = 90; } } // cull shadow casters this.renderer.updateCameraFrustum(shadowCam); this.shadowRenderer.cullShadowCasters(comp, light, lightRenderData.visibleCasters, shadowCam, casters); } } prepareLights(shadowLights, lights) { let shadowCamera; for(let i = 0; i < lights.length; i++){ const light = lights[i]; if (this.shadowRenderer.needsShadowRendering(light) && light.atlasViewportAllocated) { shadowLights.push(light); for(let face = 0; face < light.numShadowFaces; face++){ shadowCamera = this.shadowRenderer.prepareFace(light, null, face); } } } return shadowCamera; } /** * Prepare render passes for rendering of shadows for local non-clustered lights. Each shadow face * is a separate render pass as it renders to a separate render target. * * @param {FrameGraph} frameGraph - The frame graph. * @param {Light[]} localLights - The list of local lights. */ buildNonClusteredRenderPasses(frameGraph, localLights) { for(let i = 0; i < localLights.length; i++){ const light = localLights[i]; if (this.shadowRenderer.needsShadowRendering(light)) { // only spot lights support VSM const applyVsm = light._type === LIGHTTYPE_SPOT; // create render pass per face const faceCount = light.numShadowFaces; for(let face = 0; face < faceCount; face++){ const renderPass = new RenderPassShadowLocalNonClustered(this.device, this.shadowRenderer, light, face, applyVsm); frameGraph.addRenderPass(renderPass); } } } } } export { ShadowRendererLocal };