UNPKG

playcanvas

Version:

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

737 lines (736 loc) 28.6 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); import { Debug } from "../../core/debug.js"; import { now } from "../../core/time.js"; import { BlueNoise } from "../../core/math/blue-noise.js"; import { Vec2 } from "../../core/math/vec2.js"; import { Vec3 } from "../../core/math/vec3.js"; import { Vec4 } from "../../core/math/vec4.js"; import { Mat3 } from "../../core/math/mat3.js"; import { Mat4 } from "../../core/math/mat4.js"; import { CLEARFLAG_COLOR, CLEARFLAG_DEPTH, CLEARFLAG_STENCIL, BINDGROUP_MESH, BINDGROUP_VIEW, UNIFORMTYPE_MAT4, UNIFORMTYPE_MAT3, UNIFORMTYPE_VEC4, UNIFORMTYPE_VEC3, UNIFORMTYPE_IVEC3, UNIFORMTYPE_VEC2, UNIFORMTYPE_FLOAT, UNIFORMTYPE_INT, UNIFORMTYPE_UINT, CULLFACE_NONE, BINDGROUP_MESH_UB, FRONTFACE_CCW, FRONTFACE_CW } from "../../platform/graphics/constants.js"; import { DebugGraphics } from "../../platform/graphics/debug-graphics.js"; import { UniformBuffer } from "../../platform/graphics/uniform-buffer.js"; import { DynamicBindGroup } from "../../platform/graphics/bind-group.js"; import { UniformFormat, UniformBufferFormat } from "../../platform/graphics/uniform-buffer-format.js"; import { VIEW_CENTER, LIGHTTYPE_DIRECTIONAL, MASK_AFFECT_DYNAMIC, MASK_AFFECT_LIGHTMAPPED, MASK_BAKE } from "../constants.js"; import { LightCube } from "../graphics/light-cube.js"; import { getBlueNoiseTexture } from "../graphics/noise-textures.js"; import { LightTextureAtlas } from "../lighting/light-texture-atlas.js"; import { Material } from "../materials/material.js"; import { ShadowMapCache } from "./shadow-map-cache.js"; import { ShadowRendererLocal } from "./shadow-renderer-local.js"; import { ShadowRendererDirectional } from "./shadow-renderer-directional.js"; import { ShadowRenderer } from "./shadow-renderer.js"; import { WorldClustersAllocator } from "./world-clusters-allocator.js"; import { FramePassUpdateClustered } from "./frame-pass-update-clustered.js"; import { Culler } from "./culler.js"; import { Camera } from "../camera.js"; let _skinUpdateIndex = 0; const viewProjMat = new Mat4(); const viewInvMat = new Mat4(); const viewMat = new Mat4(); const viewMat3 = new Mat3(); const _tempLightSet = /* @__PURE__ */ new Set(); const _tempLayerSet = /* @__PURE__ */ new Set(); const _dynamicBindGroup = new DynamicBindGroup(); const _tempClearColor = [0, 0, 0, 1]; const _tempClearOptions = { color: _tempClearColor, depth: 1, stencil: 0, flags: 0 }; const _haltonSequence = [ new Vec2(0.5, 0.333333), new Vec2(0.25, 0.666667), new Vec2(0.75, 0.111111), new Vec2(0.125, 0.444444), new Vec2(0.625, 0.777778), new Vec2(0.375, 0.222222), new Vec2(0.875, 0.555556), new Vec2(0.0625, 0.888889), new Vec2(0.5625, 0.037037), new Vec2(0.3125, 0.37037), new Vec2(0.8125, 0.703704), new Vec2(0.1875, 0.148148), new Vec2(0.6875, 0.481481), new Vec2(0.4375, 0.814815), new Vec2(0.9375, 0.259259), new Vec2(0.03125, 0.592593) ]; const _tempProjMat0 = new Mat4(); const _tempProjMat1 = new Mat4(); const _tempProjMat4 = new Mat4(); const _tempProjMat5 = new Mat4(); const _tempSet = /* @__PURE__ */ new Set(); const _tempMeshInstances = []; const _tempMeshInstancesSkinned = []; class Renderer { /** * Create a new instance. * * @param {GraphicsDevice} graphicsDevice - The graphics device used by the renderer. * @param {Scene} scene - The scene. */ constructor(graphicsDevice, scene) { /** @type {boolean} */ __publicField(this, "clustersDebugRendered", false); /** @type {Scene} */ __publicField(this, "scene"); /** * The scene visibility culler: per-camera light visibility, mesh-instance culling (request / * execute) and shadow-caster culling. * * @type {Culler} * @ignore */ __publicField(this, "culler"); /** * @type {WorldClustersAllocator} * @ignore */ __publicField(this, "worldClustersAllocator"); /** * A list of all unique lights in the layer composition. * * @type {Light[]} */ __publicField(this, "lights", []); /** * A list of all unique local lights (spot & omni) in the layer composition. * * @type {Light[]} */ __publicField(this, "localLights", []); /** * Shared non-persistent view uniform buffers, keyed by their uniform format. Reused every frame, * with the bind group and dynamic offset sourced from the dynamic buffer system. * * @type {WeakMap<UniformBufferFormat, UniformBuffer>} */ __publicField(this, "_viewUniformBuffers", /* @__PURE__ */ new WeakMap()); /** * Reusable receiver for a view uniform buffer's dynamic bind group + offset. * * @type {DynamicBindGroup} */ __publicField(this, "_dynamicViewBindGroup", new DynamicBindGroup()); /** * Per-view dynamic bind groups, captured during multiview view-uniform setup (allocations may * span dynamic buffers, so the bind group is captured per view alongside its offset). * * @type {BindGroup[]} */ __publicField(this, "_viewBindGroups", []); /** @type {number[]} */ __publicField(this, "_viewBindGroupOffsets", []); /** * Reused single-element array passed as the dynamic offsets to per-view setBindGroup, to avoid * per-draw allocation. * * @type {number[]} */ __publicField(this, "_viewOffsetScratch", [0]); __publicField(this, "blueNoise", new BlueNoise(123)); /** * A gsplat director for unified splat rendering. * * @type {GSplatDirector|null} */ __publicField(this, "gsplatDirector", null); this.device = graphicsDevice; this.scene = scene; this.worldClustersAllocator = new WorldClustersAllocator(graphicsDevice); this.lightTextureAtlas = new LightTextureAtlas(graphicsDevice); this.shadowMapCache = new ShadowMapCache(); this.shadowRenderer = new ShadowRenderer(this, this.lightTextureAtlas); this._shadowRendererLocal = new ShadowRendererLocal(this, this.shadowRenderer); this._shadowRendererDirectional = new ShadowRendererDirectional(this, this.shadowRenderer); this.culler = new Culler(this); if (this.scene.clusteredLightingEnabled) { this._renderPassUpdateClustered = new FramePassUpdateClustered( this.device, this, this.shadowRenderer, this._shadowRendererLocal, this.lightTextureAtlas ); } this.viewUniformFormat = null; this._skinTime = 0; this._morphTime = 0; this._cullTime = 0; this._shadowMapTime = 0; this._lightClustersTime = 0; this._layerCompositionUpdateTime = 0; this._shadowDrawCalls = 0; this._skinDrawCalls = 0; this._instancedDrawCalls = 0; this._shadowMapUpdates = 0; this._numDrawCallsCulled = 0; this._camerasRendered = 0; this._lightClusters = 0; this._gsplatCount = 0; const scope = graphicsDevice.scope; this.boneTextureId = scope.resolve("texture_poseMap"); this.modelMatrixId = scope.resolve("matrix_model"); this.normalMatrixId = scope.resolve("matrix_normal"); this.viewInvId = scope.resolve("matrix_viewInverse"); this.viewPos = new Float32Array(3); this.viewPosId = scope.resolve("view_position"); this.projId = scope.resolve("matrix_projection"); this.projSkyboxId = scope.resolve("matrix_projectionSkybox"); this.viewId = scope.resolve("matrix_view"); this.viewId3 = scope.resolve("matrix_view3"); this.viewProjId = scope.resolve("matrix_viewProjection"); this.flipYId = scope.resolve("projectionFlipY"); this.tbnBasis = scope.resolve("tbnBasis"); this.cameraParams = new Float32Array(4); this.cameraParamsId = scope.resolve("camera_params"); this.viewportSize = new Float32Array(4); this.viewportSizeId = scope.resolve("viewport_size"); this.viewIndexId = scope.resolve("view_index"); this.viewIndexId.setValue(0); this.blueNoiseJitterVersion = 0; this.blueNoiseJitterVec = new Vec4(); this.blueNoiseJitterData = new Float32Array(4); this.blueNoiseJitterId = scope.resolve("blueNoiseJitter"); this.blueNoiseTextureId = scope.resolve("blueNoiseTex32"); this.alphaTestId = scope.resolve("alpha_ref"); this.opacityMapId = scope.resolve("texture_opacityMap"); this.exposureId = scope.resolve("exposure"); this.morphPositionTex = scope.resolve("morphPositionTex"); this.morphNormalTex = scope.resolve("morphNormalTex"); this.morphTexParams = scope.resolve("morph_tex_params"); this.lightCube = new LightCube(); this.constantLightCube = scope.resolve("lightCube[0]"); } destroy() { this.shadowRenderer = null; this._shadowRendererLocal = null; this._shadowRendererDirectional = null; this.shadowMapCache.destroy(); this.shadowMapCache = null; this._renderPassUpdateClustered?.destroy(); this._renderPassUpdateClustered = null; this.lightTextureAtlas.destroy(); this.lightTextureAtlas = null; this.gsplatDirector?.destroy(); this.gsplatDirector = null; } /** * Set up the viewport and the scissor for camera rendering. * * @param {Camera} camera - The camera containing the viewport information. * @param {RenderTarget} [renderTarget] - The render target. NULL for the default one. */ setupViewport(camera, renderTarget) { const device = this.device; const pixelWidth = renderTarget ? renderTarget.width : device.width; const pixelHeight = renderTarget ? renderTarget.height : device.height; const rect = camera.rect; let x = Math.floor(rect.x * pixelWidth); let y = Math.floor(rect.y * pixelHeight); let w = Math.floor(rect.z * pixelWidth); let h = Math.floor(rect.w * pixelHeight); device.setViewport(x, y, w, h); if (camera._scissorRectClear) { const scissorRect = camera.scissorRect; x = Math.floor(scissorRect.x * pixelWidth); y = Math.floor(scissorRect.y * pixelHeight); w = Math.floor(scissorRect.z * pixelWidth); h = Math.floor(scissorRect.w * pixelHeight); } device.setScissor(x, y, w, h); } setCameraUniforms(camera, target) { const flipY = target?.flipY; let viewList = null; if (camera.xrActive) { viewList = camera.xrViews; camera.updateViewTransforms(); } else { let projMat = camera.projectionMatrix; if (camera.calculateProjection) { camera.calculateProjection(projMat, VIEW_CENTER); } let projMatSkybox = camera.getProjectionMatrixSkybox(); const webgpu = this.device.isWebGPU; projMat = Camera.applyShaderProjectionTransform(projMat, _tempProjMat0, flipY, webgpu); projMatSkybox = Camera.applyShaderProjectionTransform(projMatSkybox, _tempProjMat1, flipY, webgpu); const { jitter } = camera; let jitterX = 0; let jitterY = 0; if (jitter > 0) { const targetWidth = target ? target.width : this.device.width; const targetHeight = target ? target.height : this.device.height; const offset = _haltonSequence[this.device.renderVersion % _haltonSequence.length]; jitterX = jitter * (offset.x * 2 - 1) / targetWidth; jitterY = jitter * (offset.y * 2 - 1) / targetHeight; projMat = _tempProjMat4.copy(projMat); projMat.data[8] = jitterX; projMat.data[9] = jitterY; projMatSkybox = _tempProjMat5.copy(projMatSkybox); projMatSkybox.data[8] = jitterX; projMatSkybox.data[9] = jitterY; if (this.blueNoiseJitterVersion !== this.device.renderVersion) { this.blueNoiseJitterVersion = this.device.renderVersion; this.blueNoise.vec4(this.blueNoiseJitterVec); } } const jitterVec = jitter > 0 ? this.blueNoiseJitterVec : Vec4.ZERO; this.blueNoiseJitterData[0] = jitterVec.x; this.blueNoiseJitterData[1] = jitterVec.y; this.blueNoiseJitterData[2] = jitterVec.z; this.blueNoiseJitterData[3] = jitterVec.w; this.blueNoiseJitterId.setValue(this.blueNoiseJitterData); this.projId.setValue(projMat.data); this.projSkyboxId.setValue(projMatSkybox.data); if (camera.calculateTransform) { camera.calculateTransform(viewInvMat, VIEW_CENTER); } else { const pos = camera._node.getPosition(); const rot = camera._node.getRotation(); viewInvMat.setTRS(pos, rot, Vec3.ONE); } this.viewInvId.setValue(viewInvMat.data); viewMat.copy(viewInvMat).invert(); this.viewId.setValue(viewMat.data); viewMat3.setFromMat4(viewMat); this.viewId3.setValue(viewMat3.data); viewProjMat.mul2(projMat, viewMat); this.viewProjId.setValue(viewProjMat.data); camera._storeShaderMatrices(viewProjMat, jitterX, jitterY, this.device.renderVersion); this.flipYId.setValue(flipY ? -1 : 1); this.dispatchViewPos(camera._node.getPosition()); camera.frustum.setFromMat4(viewProjMat); } this.tbnBasis.setValue(this.device.isWebGPU !== !!flipY ? -1 : 1); this.cameraParamsId.setValue(camera.fillShaderParams(this.cameraParams)); const xrView = camera.xrActive ? camera.xrViews[0] ?? null : null; let viewportWidth = xrView ? xrView.viewport.z : target ? target.width : this.device.width; let viewportHeight = xrView ? xrView.viewport.w : target ? target.height : this.device.height; viewportWidth *= camera.rect.z; viewportHeight *= camera.rect.w; this.viewportSize[0] = viewportWidth; this.viewportSize[1] = viewportHeight; this.viewportSize[2] = 1 / viewportWidth; this.viewportSize[3] = 1 / viewportHeight; this.viewportSizeId.setValue(this.viewportSize); this.exposureId.setValue(this.scene.physicalUnits ? camera.getExposure() : this.scene.exposure); return viewList; } /** * Clears the active render target. If the viewport is already set up, only its area is cleared. * * @param {Camera} camera - The camera supplying the value to clear to. * @param {boolean} [clearColor] - True if the color buffer should be cleared. Uses the value * from the camera if not supplied. * @param {boolean} [clearDepth] - True if the depth buffer should be cleared. Uses the value * from the camera if not supplied. * @param {boolean} [clearStencil] - True if the stencil buffer should be cleared. Uses the * value from the camera if not supplied. */ clear(camera, clearColor, clearDepth, clearStencil) { const flags = (clearColor ?? camera._clearColorBuffer ? CLEARFLAG_COLOR : 0) | (clearDepth ?? camera._clearDepthBuffer ? CLEARFLAG_DEPTH : 0) | (clearStencil ?? camera._clearStencilBuffer ? CLEARFLAG_STENCIL : 0); if (flags) { const device = this.device; DebugGraphics.pushGpuMarker(device, "CLEAR"); const c = camera._clearColor; _tempClearColor[0] = c.r; _tempClearColor[1] = c.g; _tempClearColor[2] = c.b; _tempClearColor[3] = c.a; _tempClearOptions.depth = camera._clearDepth; _tempClearOptions.stencil = camera._clearStencil; _tempClearOptions.flags = flags; device.clear(_tempClearOptions); DebugGraphics.popGpuMarker(device); } } setupCullModeAndFrontFace(cullFaces, flipFactor, drawCall) { const material = drawCall.material; const flipFaces = flipFactor * drawCall.flipFacesFactor * drawCall.node.worldScaleSign; let frontFace = material.frontFace; if (flipFaces < 0) { frontFace = frontFace === FRONTFACE_CCW ? FRONTFACE_CW : FRONTFACE_CCW; } this.device.setCullMode(cullFaces ? material.cull : CULLFACE_NONE); this.device.setFrontFace(frontFace); } setupCullMode(cullFaces, flipFactor, drawCall) { Debug.deprecated("Renderer.setupCullMode is deprecated. Use 'Renderer.setupCullModeAndFrontFace(cullFaces, flipFactor, drawCall);' format instead."); this.setupCullModeAndFrontFace(cullFaces, flipFactor, drawCall); } setBaseConstants(device, material) { device.setCullMode(material.cull); device.setFrontFace(material.frontFace); if (material.opacityMap) { this.opacityMapId.setValue(material.opacityMap); } if (material.opacityMap || material.alphaTest > 0) { this.alphaTestId.setValue(material.alphaTest); } } updateCpuSkinMatrices(drawCalls) { _skinUpdateIndex++; const drawCallsCount = drawCalls.length; if (drawCallsCount === 0) return; const skinTime = now(); for (let i = 0; i < drawCallsCount; i++) { const si = drawCalls[i].skinInstance; if (si) { si.updateMatrices(drawCalls[i].node, _skinUpdateIndex); si._dirty = true; } } this._skinTime += now() - skinTime; } /** * Update skin matrices ahead of rendering. * * @param {MeshInstance[]|Set<MeshInstance>} drawCalls - MeshInstances containing skinInstance. * @ignore */ updateGpuSkinMatrices(drawCalls) { const skinTime = now(); for (const drawCall of drawCalls) { const skin = drawCall.skinInstance; if (skin && skin._dirty) { skin.updateMatrixPalette(drawCall.node, _skinUpdateIndex); skin._dirty = false; } } this._skinTime += now() - skinTime; } /** * Update morphing ahead of rendering. * * @param {MeshInstance[]|Set<MeshInstance>} drawCalls - MeshInstances containing morphInstance. * @ignore */ updateMorphing(drawCalls) { const morphTime = now(); for (const drawCall of drawCalls) { const morphInst = drawCall.morphInstance; if (morphInst && morphInst._dirty) { morphInst.update(); } } this._morphTime += now() - morphTime; } /** * Update gsplats ahead of rendering. * * @param {MeshInstance[]|Set<MeshInstance>} drawCalls - MeshInstances containing gsplatInstances. * @ignore */ updateGSplats(drawCalls) { for (const drawCall of drawCalls) { drawCall.gsplatInstance?.update(); } } /** * Update draw calls ahead of rendering. * * @param {MeshInstance[]|Set<MeshInstance>} drawCalls - MeshInstances requiring updates. * @ignore */ gpuUpdate(drawCalls) { this.updateGpuSkinMatrices(drawCalls); this.updateMorphing(drawCalls); this.updateGSplats(drawCalls); } setVertexBuffers(device, mesh) { device.setVertexBuffer(mesh.vertexBuffer); } setMorphing(device, morphInstance) { if (morphInstance) { morphInstance.prepareRendering(device); device.setVertexBuffer(morphInstance.morph.vertexBufferIds); this.morphPositionTex.setValue(morphInstance.texturePositions); this.morphNormalTex.setValue(morphInstance.textureNormals); this.morphTexParams.setValue(morphInstance._textureParams); } } setSkinning(device, meshInstance) { const skinInstance = meshInstance.skinInstance; if (skinInstance) { this._skinDrawCalls++; const boneTexture = skinInstance.boneTexture; this.boneTextureId.setValue(boneTexture); } } // sets Vec3 camera position uniform dispatchViewPos(position) { const vp = this.viewPos; vp[0] = position.x; vp[1] = position.y; vp[2] = position.z; this.viewPosId.setValue(vp); } initViewUniformFormat(isClustered) { if (!this.viewUniformFormat) { const uniforms = [ new UniformFormat("matrix_view", UNIFORMTYPE_MAT4), new UniformFormat("matrix_viewInverse", UNIFORMTYPE_MAT4), new UniformFormat("matrix_projection", UNIFORMTYPE_MAT4), new UniformFormat("matrix_projectionSkybox", UNIFORMTYPE_MAT4), new UniformFormat("matrix_viewProjection", UNIFORMTYPE_MAT4), new UniformFormat("matrix_view3", UNIFORMTYPE_MAT3), new UniformFormat("cubeMapRotationMatrix", UNIFORMTYPE_MAT3), new UniformFormat("view_position", UNIFORMTYPE_VEC3), new UniformFormat("viewport_size", UNIFORMTYPE_VEC4), new UniformFormat("skyboxIntensity", UNIFORMTYPE_FLOAT), new UniformFormat("exposure", UNIFORMTYPE_FLOAT), new UniformFormat("textureBias", UNIFORMTYPE_FLOAT), new UniformFormat("view_index", UNIFORMTYPE_UINT) ]; if (isClustered) { uniforms.push(...[ new UniformFormat("clusterCellsCountByBoundsSize", UNIFORMTYPE_VEC3), new UniformFormat("clusterBoundsMin", UNIFORMTYPE_VEC3), new UniformFormat("clusterBoundsDelta", UNIFORMTYPE_VEC3), new UniformFormat("clusterCellsDot", UNIFORMTYPE_IVEC3), new UniformFormat("clusterCellsMax", UNIFORMTYPE_IVEC3), new UniformFormat("shadowAtlasParams", UNIFORMTYPE_VEC2), new UniformFormat("clusterMaxCells", UNIFORMTYPE_INT), new UniformFormat("numClusteredLights", UNIFORMTYPE_INT), new UniformFormat("clusterTextureWidth", UNIFORMTYPE_INT) ]); } this.viewUniformFormat = new UniformBufferFormat(this.device, uniforms); } } /** * Set up uniforms for an XR view. */ setupViewUniforms(view, index) { this.projId.setValue(view.projMat.data); this.projSkyboxId.setValue(view.projMat.data); this.viewId.setValue(view.viewOffMat.data); this.viewInvId.setValue(view.viewInvOffMat.data); this.viewId3.setValue(view.viewMat3.data); this.viewProjId.setValue(view.projViewOffMat.data); this.viewPosId.setValue(view.positionData); this.viewIndexId.setValue(index); } /** * Returns the shared non-persistent view uniform buffer for the given format, creating it on * first use. It is bound via the dynamic buffer system, so it needs no explicit bind group. * * @param {UniformBufferFormat} viewUniformFormat - The view uniform buffer format. * @returns {UniformBuffer} The shared view uniform buffer. */ getViewUniformBuffer(viewUniformFormat) { let ub = this._viewUniformBuffers.get(viewUniformFormat); if (!ub) { ub = new UniformBuffer(this.device, viewUniformFormat, false); this._viewUniformBuffers.set(viewUniformFormat, ub); } return ub; } /** * Sets up the shared (per-format) view uniform buffer for the current camera. For a single view * it updates the buffer and binds it immediately; for multiview (XR) it updates the buffer per * view and captures the per-view bind group and dynamic offset, which the forward render loop * then binds per draw. The bind group and dynamic offset come from the dynamic buffer system. * * @param {UniformBufferFormat} viewUniformFormat - The view uniform buffer format. * @param {RenderView[]|null} viewList - The list of XR views for multiview, or null for a * single view. */ setupViewUniformBuffers(viewUniformFormat, viewList) { Debug.assert(viewUniformFormat); const { device } = this; const ub = this.getViewUniformBuffer(viewUniformFormat); if (viewList) { const viewCount = viewList.length; for (let i = 0; i < viewCount; i++) { this.setupViewUniforms(viewList[i], i); ub.update(this._dynamicViewBindGroup); this._viewBindGroups[i] = this._dynamicViewBindGroup.bindGroup; this._viewBindGroupOffsets[i] = this._dynamicViewBindGroup.offsets[0]; } } else { ub.update(this._dynamicViewBindGroup); device.setBindGroup(BINDGROUP_VIEW, this._dynamicViewBindGroup.bindGroup, this._dynamicViewBindGroup.offsets); } } setupMeshUniformBuffers(shaderInstance) { const device = this.device; if (device.supportsUniformBuffers) { const meshBindGroup = shaderInstance.getBindGroup(device); meshBindGroup.update(); device.setBindGroup(BINDGROUP_MESH, meshBindGroup); const meshUniformBuffer = shaderInstance.getUniformBuffer(device); meshUniformBuffer.update(_dynamicBindGroup); device.setBindGroup(BINDGROUP_MESH_UB, _dynamicBindGroup.bindGroup, _dynamicBindGroup.offsets); } } setMeshInstanceMatrices(meshInstance, setNormalMatrix = false) { const modelMatrix = meshInstance.node.worldTransform; this.modelMatrixId.setValue(modelMatrix.data); if (setNormalMatrix) { this.normalMatrixId.setValue(meshInstance.node.normalMatrix.data); } } collectLights(comp) { this.lights.length = 0; this.localLights.length = 0; const stats = this.scene._stats; stats.dynamicLights = 0; stats.bakedLights = 0; const count = comp.layerList.length; for (let i = 0; i < count; i++) { const layer = comp.layerList[i]; if (!_tempLayerSet.has(layer)) { _tempLayerSet.add(layer); const lights = layer._lights; for (let j = 0; j < lights.length; j++) { const light = lights[j]; if (!_tempLightSet.has(light)) { _tempLightSet.add(light); this.lights.push(light); if (light._type !== LIGHTTYPE_DIRECTIONAL) { this.localLights.push(light); } if (light.mask & MASK_AFFECT_DYNAMIC || light.mask & MASK_AFFECT_LIGHTMAPPED) { stats.dynamicLights++; } if (light.mask & MASK_BAKE) { stats.bakedLights++; } } } } } stats.lights = this.lights.length; _tempLightSet.clear(); _tempLayerSet.clear(); } /** * @param {MeshInstance[]} drawCalls - Mesh instances. * @param {boolean} onlyLitShaders - Limits the update to shaders affected by lighting. */ updateShaders(drawCalls, onlyLitShaders) { const count = drawCalls.length; for (let i = 0; i < count; i++) { const mat = drawCalls[i].material; if (mat) { if (!_tempSet.has(mat)) { _tempSet.add(mat); if (mat.getShaderVariant !== Material.prototype.getShaderVariant) { if (onlyLitShaders) { if (!mat.useLighting || mat.emitter && !mat.emitter.lighting) { continue; } } mat.clearVariants(); } } } } _tempSet.clear(); } updateFrameUniforms() { this.blueNoiseTextureId.setValue(getBlueNoiseTexture(this.device)); } /** * @param {LayerComposition} comp - The layer composition to update. */ beginFrame(comp) { const scene = this.scene; const updateShaders = scene.updateShaders || this.device._shadersDirty; let totalMeshInstances = 0; const layers = comp.layerList; const layerCount = layers.length; for (let i = 0; i < layerCount; i++) { const layer = layers[i]; const meshInstances = layer.meshInstances; const count = meshInstances.length; totalMeshInstances += count; for (let j = 0; j < count; j++) { const meshInst = meshInstances[j]; meshInst.visibleThisFrame = false; if (updateShaders) { _tempMeshInstances.push(meshInst); } if (meshInst.skinInstance) { _tempMeshInstancesSkinned.push(meshInst); } } } scene._stats.meshInstances = totalMeshInstances; if (updateShaders) { const onlyLitShaders = !scene.updateShaders || !this.device._shadersDirty; this.updateShaders(_tempMeshInstances, onlyLitShaders); scene.updateShaders = false; this.device._shadersDirty = false; scene._shaderVersion++; } this.updateFrameUniforms(); this.updateCpuSkinMatrices(_tempMeshInstancesSkinned); _tempMeshInstances.length = 0; _tempMeshInstancesSkinned.length = 0; } updateLightTextureAtlas() { this.lightTextureAtlas.update(this.localLights, this.scene.lighting); } /** * Updates the layer composition for rendering. * * @param {LayerComposition} comp - The layer composition to update. */ updateLayerComposition(comp) { const layerCompositionUpdateTime = now(); const len = comp.layerList.length; const scene = this.scene; const shaderVersion = scene._shaderVersion; for (let i = 0; i < len; i++) { const layer = comp.layerList[i]; layer._shaderVersion = shaderVersion; layer._skipRenderCounter = 0; layer._forwardDrawCalls = 0; layer._shadowDrawCalls = 0; layer._renderTime = 0; } comp._update(); this._layerCompositionUpdateTime += now() - layerCompositionUpdateTime; } frameUpdate() { this.clustersDebugRendered = false; this.initViewUniformFormat(this.scene.clusteredLightingEnabled); this.culler.dirLightShadows.clear(); } } export { Renderer };