UNPKG

playcanvas

Version:

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

242 lines (241 loc) 8.94 kB
import { Mat4 } from "../../core/math/mat4.js"; import { SEMANTIC_POSITION, CULLFACE_NONE } from "../../platform/graphics/constants.js"; import { BLEND_NONE, BLEND_PREMULTIPLIED, BLEND_ADDITIVE, GSPLAT_FORWARD, SHADOWCAMERA_NAME } from "../constants.js"; import { ShaderMaterial } from "../materials/shader-material.js"; import { GSplatResourceBase } from "../gsplat/gsplat-resource-base.js"; import { MeshInstance } from "../mesh-instance.js"; import { GSplatRenderer } from "./gsplat-renderer.js"; import { CACHE_STRIDE } from "./gsplat-projector-constants.js"; import { Camera } from "../camera.js"; const _invProjMat = new Mat4(); const _shaderProjMat = new Mat4(); class GSplatHybridRenderer extends GSplatRenderer { _material; meshInstance; _pickMaterial = null; _pickMeshInstance = null; _clipToViewZ = new Float32Array(4); _clipToViewZPick = null; originalBlendType = BLEND_ADDITIVE; _internalDefines = /* @__PURE__ */ new Set(); forceCopyMaterial = true; constructor(device, node, cameraNode, layer, workBuffer) { super(device, node, cameraNode, layer, workBuffer); this._material = new ShaderMaterial({ uniqueName: "UnifiedSplatHybridMaterial", vertexWGSL: '#include "gsplatHybridVS"', fragmentWGSL: '#include "gsplatPS"', attributes: { vertex_position: SEMANTIC_POSITION } }); this._material.setDefine("{GSPLAT_INSTANCE_SIZE}", GSplatResourceBase.instanceSize); this._material.setDefine("{CACHE_STRIDE}", CACHE_STRIDE); this.configureMaterial(); this._material.defines.forEach((value, key) => { this._internalDefines.add(key); }); this._internalDefines.add("{GSPLAT_INSTANCE_SIZE}"); this._internalDefines.add("{CACHE_STRIDE}"); this._internalDefines.add("GSPLAT_UNIFIED_ID"); this._internalDefines.add("PICK_CUSTOM_ID"); this._internalDefines.add("GSPLAT_OVERDRAW"); this._internalDefines.add("GSPLAT_NO_FOG"); this.meshInstance = this.createMeshInstance(); } setRenderMode(renderMode) { const oldRenderMode = this.renderMode ?? 0; const wasForward = (oldRenderMode & GSPLAT_FORWARD) !== 0; const isForward = (renderMode & GSPLAT_FORWARD) !== 0; if (wasForward && !isForward) { this.layer.removeMeshInstances([this.meshInstance], true); } if (!wasForward && isForward) { this.layer.addMeshInstances([this.meshInstance], true); } super.setRenderMode(renderMode); } destroy() { if (this.renderMode && this.renderMode & GSPLAT_FORWARD) { this.layer.removeMeshInstances([this.meshInstance], true); } this._material.destroy(); this._pickMaterial?.destroy(); this.meshInstance.destroy(); this._pickMeshInstance?.destroy(); super.destroy(); } get material() { return this._material; } onWorkBufferFormatChanged() { this.configureMaterial(); } configureMaterial() { this._material.setDefine("SH_BANDS", "0"); this._material.setDefine("GSPLAT_INDIRECT_DRAW", true); this._updateIdDefines(this._material); const dither = false; this._material.setDefine(`DITHER_${dither ? "BLUENOISE" : "NONE"}`, ""); this._material.cull = CULLFACE_NONE; this._material.blendType = dither ? BLEND_NONE : BLEND_PREMULTIPLIED; this._material.depthWrite = !!dither; this._material.update(); } update(count, textureSize) { if (this.meshInstance.instancingCount <= 0) { this.meshInstance.instancingCount = 1; } this.meshInstance.visible = count > 0; } setHybridSortedRendering(drawSlot, sortedIndices, projCache, numSplatsBuffer) { this.meshInstance.setIndirect(null, drawSlot, 1); this._material.setParameter("sortedIndices", sortedIndices); this._material.setParameter("projCache", projCache); this._material.setParameter("numSplatsStorage", numSplatsBuffer); this._computeClipToViewZ(this.cameraNode, this._clipToViewZ); this._material.setParameter("clipToViewZ", this._clipToViewZ); this.meshInstance.visible = true; if (this.meshInstance.instancingCount <= 0) { this.meshInstance.instancingCount = 1; } } prepareForPicking(drawSlot, sortedIndices, projCache, numSplatsBuffer, alphaClip, alphaClipForward, cameraNode) { if (!this._pickMaterial) { this._pickMaterial = new ShaderMaterial({ uniqueName: "UnifiedSplatHybridPickMaterial", vertexWGSL: '#include "gsplatHybridVS"', fragmentWGSL: '#include "gsplatPS"', attributes: { vertex_position: SEMANTIC_POSITION } }); this._pickMaterial.setDefine("{GSPLAT_INSTANCE_SIZE}", GSplatResourceBase.instanceSize); this._pickMaterial.setDefine("{CACHE_STRIDE}", CACHE_STRIDE); this._pickMaterial.setDefine("SH_BANDS", "0"); this._pickMaterial.setDefine("GSPLAT_INDIRECT_DRAW", true); this._pickMaterial.setDefine("DITHER_NONE", ""); this._updateIdDefines(this._pickMaterial); this._pickMaterial.cull = CULLFACE_NONE; this._pickMaterial.blendType = BLEND_NONE; this._pickMaterial.depthWrite = false; this._pickMaterial.update(); const mesh = GSplatResourceBase.createMesh(this.device); this._pickMeshInstance = new MeshInstance(mesh, this._pickMaterial); this._pickMeshInstance.node = this.node; this._pickMeshInstance.setInstancing(true, true); this._pickMeshInstance.instancingCount = 1; } else { if (this._updateIdDefines(this._pickMaterial)) { this._pickMaterial.update(); } } const pickMaterial = this._pickMaterial; const pickMeshInstance = this._pickMeshInstance; pickMeshInstance.setIndirect(null, drawSlot, 1); pickMaterial.setParameter("sortedIndices", sortedIndices); pickMaterial.setParameter("projCache", projCache); pickMaterial.setParameter("numSplatsStorage", numSplatsBuffer); pickMaterial.setParameter("alphaClip", alphaClip); pickMaterial.setParameter("alphaClipForward", alphaClipForward); this._clipToViewZPick ?? (this._clipToViewZPick = new Float32Array(4)); this._computeClipToViewZ(cameraNode, this._clipToViewZPick); pickMaterial.setParameter("clipToViewZ", this._clipToViewZPick); return pickMeshInstance; } _computeClipToViewZ(cameraNode, dst) { const camComp = cameraNode.camera; const cam = camComp.camera; if (this.fisheyeProj.enabled) { const near = cam.nearClip; const far = cam.farClip; dst[0] = 0; dst[1] = 0; dst[2] = far - near; dst[3] = near; return; } const flipY = !!camComp.renderTarget?.flipY; _invProjMat.copy(Camera.applyShaderProjectionTransform(cam.projectionMatrix, _shaderProjMat, flipY, this.device.isWebGPU)).invert(); const d = _invProjMat.data; dst[0] = -d[2]; dst[1] = -d[6]; dst[2] = -d[10]; dst[3] = -d[14]; } setCpuSortedRendering() { this.meshInstance.setIndirect(null, -1); this.meshInstance.visible = false; } setOrderData() { } frameUpdate(params) { this._material.setParameter("alphaClip", params.alphaClip); this._material.setParameter("alphaClipForward", params.alphaClipForward); this._pickMaterial?.setParameter("alphaClip", params.alphaClip); this._pickMaterial?.setParameter("alphaClipForward", params.alphaClipForward); if (params.colorRamp) { this._material.setParameter("colorRampIntensity", params.colorRampIntensity); } const noFog = !params.useFog; if (noFog !== this._lastNoFog) { this._lastNoFog = noFog; this._material.setDefine("GSPLAT_NO_FOG", noFog); this._material.update(); } } _updateIdDefines(material) { const hasPcId = !!this.workBuffer.format.getStream("pcId"); const changed = material.getDefine("GSPLAT_UNIFIED_ID") !== hasPcId || material.getDefine("PICK_CUSTOM_ID") !== hasPcId; material.setDefine("GSPLAT_UNIFIED_ID", hasPcId); material.setDefine("PICK_CUSTOM_ID", hasPcId); return changed; } updateOverdrawMode(params) { const overdrawEnabled = !!params.colorRamp; const wasOverdrawEnabled = this._material.getDefine("GSPLAT_OVERDRAW"); if (overdrawEnabled) { this._material.setParameter("colorRamp", params.colorRamp); this._material.setParameter("colorRampIntensity", params.colorRampIntensity); } if (overdrawEnabled !== wasOverdrawEnabled) { this._material.setDefine("GSPLAT_OVERDRAW", overdrawEnabled); if (overdrawEnabled) { this.originalBlendType = this._material.blendType; this._material.blendType = BLEND_ADDITIVE; } else { this._material.blendType = this.originalBlendType; } this._material.update(); } } createMeshInstance() { const mesh = GSplatResourceBase.createMesh(this.device); const meshInstance = new MeshInstance(mesh, this._material); meshInstance.node = this.node; meshInstance.setInstancing(true, true); meshInstance.instancingCount = 0; meshInstance.pick = false; const thisCamera = this.cameraNode.camera; meshInstance.isVisibleFunc = (camera) => { const renderMode = this.renderMode ?? 0; if (thisCamera.camera === camera && renderMode & GSPLAT_FORWARD) { return true; } if (camera.node?.name === SHADOWCAMERA_NAME) { return false; } return false; }; return meshInstance; } } export { GSplatHybridRenderer };