UNPKG

playcanvas

Version:

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

576 lines (575 loc) 20.9 kB
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 { 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 { clustersDebugRendered = false; scene; culler; worldClustersAllocator; lights = []; localLights = []; _viewUniformBuffers = /* @__PURE__ */ new WeakMap(); _dynamicViewBindGroup = new DynamicBindGroup(); _viewBindGroups = []; _viewBindGroupOffsets = []; _viewOffsetScratch = [0]; blueNoise = new BlueNoise(123); gsplatDirector = null; constructor(graphicsDevice, scene) { 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; } 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; } 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; 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); } } 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) { 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; for (let i = 0; i < drawCallsCount; i++) { const si = drawCalls[i].skinInstance; if (si) { si.updateMatrices(drawCalls[i].node, _skinUpdateIndex); si._dirty = true; } } } updateGpuSkinMatrices(drawCalls) { for (const drawCall of drawCalls) { const skin = drawCall.skinInstance; if (skin && skin._dirty) { skin.updateMatrixPalette(drawCall.node, _skinUpdateIndex); skin._dirty = false; } } } updateMorphing(drawCalls) { for (const drawCall of drawCalls) { const morphInst = drawCall.morphInstance; if (morphInst && morphInst._dirty) { morphInst.update(); } } } updateGSplats(drawCalls) { for (const drawCall of drawCalls) { drawCall.gsplatInstance?.update(); } } 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); } } 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); } getViewUniformBuffer(viewUniformFormat) { let ub = this._viewUniformBuffers.get(viewUniformFormat); if (!ub) { ub = new UniformBuffer(this.device, viewUniformFormat, false); this._viewUniformBuffers.set(viewUniformFormat, ub); } return ub; } setupViewUniformBuffers(viewUniformFormat, viewList) { 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; 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); } } } } } stats.lights = this.lights.length; _tempLightSet.clear(); _tempLayerSet.clear(); } 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)); } 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); } } } 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); } updateLayerComposition(comp) { 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; } comp._update(); } frameUpdate() { this.clustersDebugRendered = false; this.initViewUniformFormat(this.scene.clusteredLightingEnabled); this.culler.dirLightShadows.clear(); } } export { Renderer };