UNPKG

playcanvas

Version:

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

266 lines (263 loc) 9.97 kB
import { PIXELFORMAT_RGBA16U, CULLFACE_NONE, SEMANTIC_POSITION } from '../../platform/graphics/constants.js'; import { GSPLAT_FORWARD, GSPLAT_SHADOW, BLEND_PREMULTIPLIED, BLEND_ADDITIVE, 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'; class GSplatQuadRenderer extends GSplatRenderer { setRenderMode(renderMode) { const oldRenderMode = this.renderMode ?? 0; const wasForward = (oldRenderMode & GSPLAT_FORWARD) !== 0; const wasShadow = (oldRenderMode & GSPLAT_SHADOW) !== 0; const isForward = (renderMode & GSPLAT_FORWARD) !== 0; const isShadow = (renderMode & GSPLAT_SHADOW) !== 0; this.meshInstance.castShadow = isShadow; if (wasForward && !isForward) { this.layer.removeMeshInstances([ this.meshInstance ], true); } if (wasShadow && !isShadow) { this.layer.removeShadowCasters([ this.meshInstance ]); } if (!wasForward && isForward) { this.layer.addMeshInstances([ this.meshInstance ], true); } if (!wasShadow && isShadow) { this.layer.addShadowCasters([ this.meshInstance ]); } super.setRenderMode(renderMode); } destroy() { if (this.renderMode) { if (this.renderMode & GSPLAT_FORWARD) { this.layer.removeMeshInstances([ this.meshInstance ], true); } if (this.renderMode & GSPLAT_SHADOW) { this.layer.removeShadowCasters([ this.meshInstance ]); } } this._material.destroy(); this.meshInstance.destroy(); super.destroy(); } get material() { return this._material; } onWorkBufferFormatChanged() { this.configureMaterial(); } configureMaterial() { const { workBuffer } = this; this._injectFormatChunks(); this._material.setDefine('SH_BANDS', '0'); this._material.setDefine('GSPLAT_SEPARATE_OPACITY', ''); const colorStream = workBuffer.format.getStream('dataColor'); if (colorStream && colorStream.format !== PIXELFORMAT_RGBA16U) { this._material.setDefine('GSPLAT_COLOR_FLOAT', ''); } this._updateIdDefines(); this._bindWorkBufferTextures(); this._material.setParameter('numSplats', 0); this.setOrderData(); this._material.setDefine(`DITHER_${'NONE'}`, ''); this._material.cull = CULLFACE_NONE; this._material.blendType = BLEND_PREMULTIPLIED; this._material.depthWrite = false; this._material.update(); } _bindWorkBufferTextures() { const { workBuffer } = this; for (const stream of workBuffer.format.resourceStreams){ const texture = workBuffer.getTexture(stream.name); if (texture) { this._material.setParameter(stream.name, texture); } } } _injectFormatChunks() { const chunks = this.device.isWebGPU ? this._material.shaderChunks.wgsl : this._material.shaderChunks.glsl; const wbFormat = this.workBuffer.format; chunks.set('gsplatDeclarationsVS', wbFormat.getInputDeclarations()); chunks.set('gsplatReadVS', wbFormat.getReadCode()); } update(count, textureSize) { this.meshInstance.instancingCount = Math.ceil(count / GSplatResourceBase.instanceSize); this._material.setParameter('numSplats', count); this._material.setParameter('splatTextureSize', textureSize); this.meshInstance.visible = count > 0; } setGpuSortedRendering(drawSlot, sortedIds, numSplatsBuffer, textureSize) { this.meshInstance.setIndirect(null, drawSlot, 1); this._material.setParameter('compactedSplatIds', sortedIds); this._material.setParameter('numSplatsStorage', numSplatsBuffer); if (!this._material.getDefine('GSPLAT_INDIRECT_DRAW')) { this._material.setDefine('GSPLAT_INDIRECT_DRAW', true); this._material.update(); } this._material.setParameter('splatTextureSize', textureSize); this.meshInstance.visible = true; if (this.meshInstance.instancingCount <= 0) { this.meshInstance.instancingCount = 1; } } setCpuSortedRendering() { this.meshInstance.setIndirect(null, -1); if (this._material.getDefine('GSPLAT_INDIRECT_DRAW')) { this._material.setDefine('GSPLAT_INDIRECT_DRAW', false); this._material.update(); } this.setOrderData(); this.meshInstance.visible = false; } setOrderData() { if (this.device.isWebGPU) { this._material.setParameter('splatOrder', this.workBuffer.orderBuffer); } else { this._material.setParameter('splatOrder', this.workBuffer.orderTexture); } } frameUpdate(params) { if (params.colorRamp) { this._material.setParameter('colorRampIntensity', params.colorRampIntensity); } const cam = this.cameraNode.camera; this.fisheyeProj.update(params.fisheye, cam.fov, cam.projectionMatrix); const fisheyeEnabled = this.fisheyeProj.enabled; if (fisheyeEnabled !== this._lastFisheyeEnabled) { this._lastFisheyeEnabled = fisheyeEnabled; this._material.setDefine('GSPLAT_FISHEYE', fisheyeEnabled); this._material.update(); } if (fisheyeEnabled) { const fp = this.fisheyeProj; this._material.setParameter('fisheye_k', fp.k); this._material.setParameter('fisheye_inv_k', fp.invK); this._material.setParameter('fisheye_projMat00', fp.projMat00); this._material.setParameter('fisheye_projMat11', fp.projMat11); } const noFog = !params.useFog; if (noFog !== this._lastNoFog) { this._lastNoFog = noFog; this._material.setDefine('GSPLAT_NO_FOG', noFog); this._material.update(); } this._syncWithWorkBufferFormat(); if (this.forceCopyMaterial || params.material.dirty) { this.copyMaterialSettings(params.material); this.forceCopyMaterial = false; } } _updateIdDefines() { const hasPcId = !!this.workBuffer.format.getStream('pcId'); this._material.setDefine('GSPLAT_UNIFIED_ID', hasPcId); this._material.setDefine('PICK_CUSTOM_ID', hasPcId); } _syncWithWorkBufferFormat() { const wbFormat = this.workBuffer.format; if (this._workBufferFormatVersion !== wbFormat.extraStreamsVersion) { this._workBufferFormatVersion = wbFormat.extraStreamsVersion; this.workBuffer.syncWithFormat(); this._injectFormatChunks(); this._bindWorkBufferTextures(); this._updateIdDefines(); this._material.update(); } } copyMaterialSettings(sourceMaterial) { const keysToDelete = []; this._material.defines.forEach((value, key)=>{ if (!this._internalDefines.has(key)) { keysToDelete.push(key); } }); keysToDelete.forEach((key)=>this._material.defines.delete(key)); sourceMaterial.defines.forEach((value, key)=>{ this._material.defines.set(key, value); }); const srcParams = sourceMaterial.parameters; for(const paramName in srcParams){ if (srcParams.hasOwnProperty(paramName)) { this._material.setParameter(paramName, srcParams[paramName].data); } } if (sourceMaterial.hasShaderChunks) { this._material.shaderChunks.copy(sourceMaterial.shaderChunks); } this._injectFormatChunks(); this._material.update(); } 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; const thisCamera = this.cameraNode.camera; meshInstance.isVisibleFunc = (camera)=>{ const renderMode = this.renderMode ?? 0; if (thisCamera.camera === camera && renderMode & GSPLAT_FORWARD) { return true; } if (renderMode & GSPLAT_SHADOW) { return camera.node?.name === SHADOWCAMERA_NAME; } return false; }; return meshInstance; } constructor(device, node, cameraNode, layer, workBuffer){ super(device, node, cameraNode, layer, workBuffer), this.originalBlendType = BLEND_ADDITIVE, this._internalDefines = new Set(), this.forceCopyMaterial = true, this._lastFisheyeEnabled = false; this._material = new ShaderMaterial({ uniqueName: 'UnifiedSplatMaterial', vertexGLSL: '#include "gsplatVS"', fragmentGLSL: '#include "gsplatPS"', vertexWGSL: '#include "gsplatVS"', fragmentWGSL: '#include "gsplatPS"', attributes: { vertex_position: SEMANTIC_POSITION } }); this._material.setDefine('{GSPLAT_INSTANCE_SIZE}', GSplatResourceBase.instanceSize); this.configureMaterial(); this._material.defines.forEach((value, key)=>{ this._internalDefines.add(key); }); this._internalDefines.add('{GSPLAT_INSTANCE_SIZE}'); this._internalDefines.add('GSPLAT_UNIFIED_ID'); this._internalDefines.add('PICK_CUSTOM_ID'); this._internalDefines.add('GSPLAT_INDIRECT_DRAW'); this._internalDefines.add('GSPLAT_SEPARATE_OPACITY'); this._internalDefines.add('GSPLAT_FISHEYE'); this.meshInstance = this.createMeshInstance(); } } export { GSplatQuadRenderer };