UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

125 lines (122 loc) 5.49 kB
import { SEMANTIC_ATTR13, SEMANTIC_POSITION, CULLFACE_NONE } from '../../platform/graphics/constants.js'; import { 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'; /** * @import { VertexBuffer } from '../../platform/graphics/vertex-buffer.js' * @import { Layer } from '../layer.js' * @import { GraphNode } from '../graph-node.js' */ /** * Class that renders the splats from the work buffer. * * @ignore */ class GSplatRenderer { constructor(device, node, cameraNode, layer, workBuffer){ /** @type {VertexBuffer|null} */ this.instanceIndices = null; /** @type {number} */ this.instanceIndicesCount = 0; this.viewportParams = [ 0, 0 ]; this.device = device; this.node = node; this.cameraNode = cameraNode; this.layer = layer; this.workBuffer = workBuffer; // construct the material which renders the splats from the work buffer 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 } }); // input format this._material.setDefine('GSPLAT_WORKBUFFER_DATA', true); // input textures (work buffer textures) this._material.setParameter('splatColor', workBuffer.colorTexture); this._material.setParameter('covA', workBuffer.covATexture); this._material.setParameter('covB', workBuffer.covBTexture); this._material.setParameter('center', workBuffer.centerTexture); this._material.setDefine('SH_BANDS', '0'); this._material.setParameter('numSplats', 0); 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(); this.meshInstance = this.createMeshInstance(); layer.addMeshInstances([ this.meshInstance ]); } destroy() { this.layer.removeMeshInstances([ this.meshInstance ]); this._material.destroy(); this.meshInstance.destroy(); } setNumSplats(count) { // limit splat render count to exclude those behind the camera this.meshInstance.instancingCount = Math.ceil(count / GSplatResourceBase.instanceSize); // update splat count on the material this._material.setParameter('numSplats', count); // disable rendering if no splats to render this.meshInstance.visible = count > 0; } setMaxNumSplats(numSplats) { // round up to the nearest multiple of instanceSize (same as createInstanceIndices does internally) const roundedNumSplats = math.roundUp(numSplats, GSplatResourceBase.instanceSize); if (this.instanceIndicesCount < roundedNumSplats) { this.instanceIndicesCount = roundedNumSplats; // destroy old instance indices this.instanceIndices?.destroy(); // create new instance indices this.instanceIndices = GSplatResourceBase.createInstanceIndices(this.device, numSplats); this.meshInstance.setInstancing(this.instanceIndices, true); } } 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); // only start rendering the splat after we've received the splat order data meshInstance.instancingCount = 0; // custom culling to only disable rendering for matching camera // TODO: consider using aabb as well to avoid rendering off-screen splats 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; // adjust viewport for stereoscopic VR sessions const xr = camera?.camera?.xr; if (xr?.active && xr.views.list.length === 2) { viewport[0] *= 0.5; } this._material.setParameter('viewport', viewport); } } export { GSplatRenderer };