UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

142 lines (139 loc) 5.29 kB
import { Mat4 } from '../../core/math/mat4.js'; import { Vec3 } from '../../core/math/vec3.js'; import { PIXELFORMAT_R32U, SEMANTIC_ATTR13, SEMANTIC_POSITION, CULLFACE_NONE } from '../../platform/graphics/constants.js'; import { MeshInstance } from '../mesh-instance.js'; import { GSplatResolveSH } from './gsplat-resolve-sh.js'; import { GSplatSorter } from './gsplat-sorter.js'; import { GSplatSogsData } from './gsplat-sogs-data.js'; import { GSplatResourceBase } from './gsplat-resource-base.js'; import { ShaderMaterial } from '../materials/shader-material.js'; import { BLEND_NONE, BLEND_PREMULTIPLIED } from '../constants.js'; const mat = new Mat4(); const cameraPosition = new Vec3(); const cameraDirection = new Vec3(); const viewport = [ 0, 0 ]; class GSplatInstance { constructor(resource, options = {}){ this.options = {}; this.sorter = null; this.lastCameraPosition = new Vec3(); this.lastCameraDirection = new Vec3(); this.resolveSH = null; this.cameras = []; this.resource = resource; this.orderTexture = resource.createTexture('splatOrder', PIXELFORMAT_R32U, resource.evalTextureSize(resource.numSplats)); if (options.material) { this._material = options.material; this._material.setParameter('splatOrder', this.orderTexture); } else { this._material = new ShaderMaterial({ uniqueName: 'SplatMaterial', 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._material); this._material.update(); } this.meshInstance = new MeshInstance(resource.mesh, this._material); this.meshInstance.setInstancing(resource.instanceIndices, true); this.meshInstance.gsplatInstance = this; this.meshInstance.instancingCount = 0; const centers = resource.centers.slice(); const chunks = resource.chunks?.slice(); this.sorter = new GSplatSorter(); this.sorter.init(this.orderTexture, centers, chunks); this.sorter.on('updated', (count)=>{ this.meshInstance.instancingCount = Math.ceil(count / GSplatResourceBase.instanceSize); this.material.setParameter('numSplats', count); }); this.setHighQualitySH(options.highQualitySH ?? false); } destroy() { this.orderTexture?.destroy(); this.resolveSH?.destroy(); this.material?.destroy(); this.meshInstance?.destroy(); this.sorter?.destroy(); } set material(value) { if (this._material !== value) { this._material = value; this._material.setParameter('splatOrder', this.orderTexture); if (this.meshInstance) { this.meshInstance.material = value; } } } get material() { return this._material; } configureMaterial(material, options = {}) { this.resource.configureMaterial(material); material.setParameter('numSplats', 0); material.setParameter('splatOrder', this.orderTexture); material.setParameter('alphaClip', 0.3); material.setDefine(`DITHER_${options.dither ? 'BLUENOISE' : 'NONE'}`, ''); material.cull = CULLFACE_NONE; material.blendType = options.dither ? BLEND_NONE : BLEND_PREMULTIPLIED; material.depthWrite = !!options.dither; } updateViewport(cameraNode) { const camera = cameraNode?.camera; const renderTarget = camera?.renderTarget; const { width, height } = renderTarget ?? this.resource.device; viewport[0] = width; viewport[1] = height; const xr = camera?.camera?.xr; if (xr?.active && xr.views.list.length === 2) { viewport[0] *= 0.5; } this.material.setParameter('viewport', viewport); } sort(cameraNode) { if (this.sorter) { const cameraMat = cameraNode.getWorldTransform(); cameraMat.getTranslation(cameraPosition); cameraMat.getZ(cameraDirection); const modelMat = this.meshInstance.node.getWorldTransform(); const invModelMat = mat.invert(modelMat); invModelMat.transformPoint(cameraPosition, cameraPosition); invModelMat.transformVector(cameraDirection, cameraDirection); if (!cameraPosition.equalsApprox(this.lastCameraPosition) || !cameraDirection.equalsApprox(this.lastCameraDirection)) { this.lastCameraPosition.copy(cameraPosition); this.lastCameraDirection.copy(cameraDirection); this.sorter.setCamera(cameraPosition, cameraDirection); } } this.updateViewport(cameraNode); } update() { if (this.cameras.length > 0) { const camera = this.cameras[0]; this.sort(camera._node); this.resolveSH?.render(camera._node, this.meshInstance.node.getWorldTransform()); this.cameras.length = 0; } } setHighQualitySH(value) { const { resource } = this; const { gsplatData } = resource; if (gsplatData instanceof GSplatSogsData && gsplatData.shBands > 0 && value === !!this.resolveSH) { if (this.resolveSH) { this.resolveSH.destroy(); this.resolveSH = null; } else { this.resolveSH = new GSplatResolveSH(resource.device, this); } } } } export { GSplatInstance };