UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

146 lines (143 loc) 5.64 kB
import { SEMANTIC_ATTR13, SEMANTIC_POSITION, PIXELFORMAT_RGBA16U, CULLFACE_NONE } from '../../platform/graphics/constants.js'; import { BLEND_ADDITIVE, BLEND_PREMULTIPLIED } 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 { math } from '../../core/math/math.js'; class GSplatRenderer { constructor(device, node, cameraNode, layer, workBuffer){ this.instanceIndices = null; this.instanceIndicesCount = 0; this.viewportParams = [ 0, 0 ]; this.originalBlendType = BLEND_ADDITIVE; this.device = device; this.node = node; this.cameraNode = cameraNode; this.layer = layer; this.workBuffer = workBuffer; this._material = new ShaderMaterial({ uniqueName: 'UnifiedSplatMaterial', vertexGLSL: '#include "gsplatVS"', fragmentGLSL: '#include "gsplatPS"', vertexWGSL: '#include "gsplatVS"', fragmentWGSL: '#include "gsplatPS"', attributes: { vertex_position: SEMANTIC_POSITION, vertex_id_attrib: SEMANTIC_ATTR13 } }); this.configureMaterial(); this.meshInstance = this.createMeshInstance(); layer.addMeshInstances([ this.meshInstance ]); } destroy() { this.layer.removeMeshInstances([ this.meshInstance ]); this._material.destroy(); this.meshInstance.destroy(); } get material() { return this._material; } configureMaterial() { const { device, workBuffer } = this; this._material.setDefine('GSPLAT_WORKBUFFER_DATA', true); this._material.setDefine('STORAGE_ORDER', device.isWebGPU); const isColorUint = workBuffer.colorTextureFormat === PIXELFORMAT_RGBA16U; this._material.setDefine('GSPLAT_COLOR_UINT', isColorUint); this._material.setParameter('splatColor', workBuffer.colorTexture); this._material.setParameter('splatTexture0', workBuffer.splatTexture0); this._material.setParameter('splatTexture1', workBuffer.splatTexture1); this._material.setDefine('SH_BANDS', '0'); this._material.setParameter('numSplats', 0); if (workBuffer.orderTexture) { this._material.setParameter('splatOrder', workBuffer.orderTexture); } this._material.setParameter('alphaClip', 0.3); this._material.setDefine(`DITHER_${'NONE'}`, ''); this._material.cull = CULLFACE_NONE; this._material.blendType = BLEND_PREMULTIPLIED; this._material.depthWrite = false; this._material.update(); } 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; } frameUpdate(params) { if (this.device.isWebGPU) { this._material.setParameter('splatOrder', this.workBuffer.orderBuffer); } else { this._material.setParameter('splatOrder', this.workBuffer.orderTexture); } if (params.colorRamp) { this._material.setParameter('colorRampIntensity', params.colorRampIntensity); } } 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(); } } setMaxNumSplats(numSplats) { const roundedNumSplats = math.roundUp(numSplats, GSplatResourceBase.instanceSize); if (this.instanceIndicesCount < roundedNumSplats) { this.instanceIndicesCount = roundedNumSplats; this.instanceIndices?.destroy(); this.instanceIndices = GSplatResourceBase.createInstanceIndices(this.device, numSplats); this.meshInstance.setInstancing(this.instanceIndices, true); this._material.setParameter('splatTextureSize', this.workBuffer.textureSize); } } createMeshInstance() { const mesh = GSplatResourceBase.createMesh(this.device); const textureSize = this.workBuffer.textureSize; const instanceIndices = GSplatResourceBase.createInstanceIndices(this.device, textureSize * textureSize); const meshInstance = new MeshInstance(mesh, this._material); meshInstance.node = this.node; meshInstance.setInstancing(instanceIndices, true); meshInstance.instancingCount = 0; const thisCamera = this.cameraNode.camera; meshInstance.isVisibleFunc = (camera)=>{ const vis = thisCamera.camera === camera; return vis; }; return meshInstance; } updateViewport(cameraNode) { const camera = cameraNode.camera; const cameraRect = camera.rect; const renderTarget = camera?.renderTarget; const { width, height } = renderTarget ?? this.device; const viewport = this.viewportParams; viewport[0] = width * cameraRect.z; viewport[1] = height * cameraRect.w; const xr = camera?.camera?.xr; if (xr?.active && xr.views.list.length === 2) { viewport[0] *= 0.5; } this._material.setParameter('viewport', viewport); } } export { GSplatRenderer };