UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

122 lines (119 loc) 4.45 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 { GSplatSorter } from './gsplat-sorter.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, material){ this.options = {}; this.sorter = null; this.lastCameraPosition = new Vec3(); this.lastCameraDirection = new Vec3(); this.cameras = []; this.resource = resource; this.orderTexture = resource.createTexture('splatOrder', PIXELFORMAT_R32U, resource.evalTextureSize(resource.numSplats)); if (material) { this._material = 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 / resource.instanceSize); this.material.setParameter('numSplats', count); }); } 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.cameras.length = 0; } } } export { GSplatInstance };