UNPKG

playcanvas

Version:

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

226 lines (225 loc) 7.3 kB
import { now } from "../../core/time.js"; import { BoundingSphere } from "../../core/shape/bounding-sphere.js"; import { LIGHTTYPE_DIRECTIONAL, SHADOWUPDATE_NONE, SHADOWUPDATE_THISFRAME, EVENT_PRECULL, EVENT_POSTCULL, EVENT_CULL_END } from "../constants.js"; const tempSphere = new BoundingSphere(); const _tempSet = /* @__PURE__ */ new Set(); class Culler { processingMeshInstances = /* @__PURE__ */ new Set(); _cullCameras = []; cameraDirShadowLights = /* @__PURE__ */ new Map(); dirLightShadows = /* @__PURE__ */ new Map(); constructor(renderer) { this.renderer = renderer; } cullMeshInstances(camera, drawCalls, culledInstances) { const opaque = culledInstances.opaque; opaque.length = 0; const transparent = culledInstances.transparent; transparent.length = 0; const doCull = camera.frustumCulling; const count = drawCalls.length; for (let i = 0; i < count; i++) { const drawCall = drawCalls[i]; if (drawCall.visible) { const visible = !doCull || !drawCall.cull || drawCall._isVisible(camera); if (visible) { drawCall.visibleThisFrame = true; const bucket = drawCall.transparent ? transparent : opaque; bucket.push(drawCall); if (drawCall.skinInstance || drawCall.morphInstance || drawCall.gsplatInstance) { this.processingMeshInstances.add(drawCall); if (drawCall.gsplatInstance) { drawCall.gsplatInstance.cameras.push(camera); } } } } } } cullLights(camera, lights) { const { scene } = this.renderer; const clusteredLightingEnabled = scene.clusteredLightingEnabled; const physicalUnits = scene.physicalUnits; for (let i = 0; i < lights.length; i++) { const light = lights[i]; if (light.enabled) { if (light._type !== LIGHTTYPE_DIRECTIONAL) { light.getBoundingSphere(tempSphere); if (camera.frustum.containsSphere(tempSphere)) { light.visibleThisFrame = true; light.usePhysicalUnits = physicalUnits; const screenSize = camera.getScreenSize(tempSphere); light.maxScreenSize = Math.max(light.maxScreenSize, screenSize); } else { if (!clusteredLightingEnabled) { if (light.castShadows && !light.shadowMap) { light.visibleThisFrame = true; } } } } else { light.usePhysicalUnits = scene.physicalUnits; } } } } cullShadowmaps(comp) { const { renderer } = this; const localLights = renderer.localLights; for (let i = 0; i < localLights.length; i++) { const light = localLights[i]; if (light._type !== LIGHTTYPE_DIRECTIONAL) { if (light.visibleThisFrame && light.castShadows && light.shadowUpdateMode !== SHADOWUPDATE_NONE) { renderer._shadowRendererLocal.cull(light, comp); } } } this.cameraDirShadowLights.forEach((lightList, camera) => { for (let i = 0; i < lightList.length; i++) { renderer._shadowRendererDirectional.cull(lightList[i], comp, camera); } }); } consumeOneShotShadows() { const { renderer } = this; const clustered = renderer.scene.clusteredLightingEnabled; const shadowRenderer = renderer.shadowRenderer; const localLights = renderer.localLights; for (let i = 0; i < localLights.length; i++) { const light = localLights[i]; if (shadowRenderer.needsShadowRendering(light) && (!clustered || light.atlasViewportAllocated)) { renderer._shadowMapUpdates += light.numShadowFaces; if (light.shadowUpdateMode === SHADOWUPDATE_THISFRAME) { light.shadowUpdateMode = SHADOWUPDATE_NONE; } } } this.cameraDirShadowLights.forEach((lightList) => { for (let i = 0; i < lightList.length; i++) { const light = lightList[i]; if (shadowRenderer.needsShadowRendering(light)) { renderer._shadowMapUpdates += light.numShadowFaces; if (light.shadowUpdateMode === SHADOWUPDATE_THISFRAME) { light.shadowUpdateMode = SHADOWUPDATE_NONE; } } } }); } collectDirectionalShadowLights(comp) { const { renderer } = this; this.cameraDirShadowLights.clear(); const cameras = comp.cameras; for (let i = 0; i < cameras.length; i++) { const cameraComponent = cameras[i]; if (cameraComponent.enabled) { const camera = cameraComponent.camera; let lightList; const cameraLayers = camera.layers; for (let l = 0; l < cameraLayers.length; l++) { const cameraLayer = comp.getLayerById(cameraLayers[l]); if (cameraLayer) { const layerDirLights = cameraLayer.splitLights[LIGHTTYPE_DIRECTIONAL]; for (let j = 0; j < layerDirLights.length; j++) { const light = layerDirLights[j]; if (light.castShadows && !_tempSet.has(light)) { _tempSet.add(light); lightList = lightList ?? []; lightList.push(light); renderer._shadowRendererDirectional.prepareShadowMap(light); } } } } if (lightList) { this.cameraDirShadowLights.set(camera, lightList); } _tempSet.clear(); } } } updateLightVisibility(comp) { const { renderer } = this; const { scene } = renderer; const lights = renderer.lights; for (let i = 0; i < lights.length; i++) { lights[i].beginFrame(); } const numCameras = comp.cameras.length; for (let i = 0; i < numCameras; i++) { const camera = comp.cameras[i]; camera.camera.updateFrustum(); const layerIds = camera.layers; for (let j = 0; j < layerIds.length; j++) { const layer = comp.getLayerById(layerIds[j]); if (layer && layer.enabled) { this.cullLights(camera.camera, layer._lights); } } } const isClustered = scene.clusteredLightingEnabled; if (isClustered) { renderer.updateLightTextureAtlas(); } const localLights = renderer.localLights; for (let i = 0; i < localLights.length; i++) { const light = localLights[i]; if (light._type !== LIGHTTYPE_DIRECTIONAL) { if (isClustered) { if (light.atlasSlotUpdated && light.shadowUpdateMode === SHADOWUPDATE_NONE) { light.shadowUpdateMode = SHADOWUPDATE_THISFRAME; } } else { if (light.castShadows && light.visibleThisFrame && !light._shadowMap) { renderer._shadowRendererLocal.prepareShadowMap(light); if (light.shadowUpdateMode === SHADOWUPDATE_NONE) { light.shadowUpdateMode = SHADOWUPDATE_THISFRAME; } } } } } this.collectDirectionalShadowLights(comp); } requestMeshInstanceCull(camera, layer) { if (camera.addCullLayer(layer)) { this._cullCameras.push(camera); } } executeMeshInstanceCull() { const { renderer } = this; const { scene } = renderer; const cameras = this._cullCameras; for (let i = 0; i < cameras.length; i++) { const camera = cameras[i]; const cameraComponent = camera.node?.camera ?? null; scene?.fire(EVENT_PRECULL, cameraComponent); camera.updateFrustum(); for (const layer of camera.cullLayers) { this.cullMeshInstances(camera, layer.meshInstances, layer.getCulledInstances(camera)); } scene?.fire(EVENT_POSTCULL, cameraComponent); camera.clearCullLayers(); } cameras.length = 0; } cullComposition(comp) { const { renderer } = this; const { scene } = renderer; this.processingMeshInstances.clear(); renderer._camerasRendered += comp.cameras.length; this.executeMeshInstanceCull(); this.cullShadowmaps(comp); scene?.fire(EVENT_CULL_END); } } export { Culler };