playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
226 lines (225 loc) • 7.3 kB
JavaScript
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
};