UNPKG

@animech-public/playcanvas

Version:
191 lines (172 loc) 6.92 kB
import { Mat4 } from '../../core/math/mat4.js'; import { Vec3 } from '../../core/math/vec3.js'; import { PIXELFORMAT_R32U, SEMANTIC_ATTR13, TYPE_UINT32, BUFFER_STATIC } from '../../platform/graphics/constants.js'; import { DITHER_NONE } from '../constants.js'; import { MeshInstance } from '../mesh-instance.js'; import { Mesh } from '../mesh.js'; import { GSplatSorter } from './gsplat-sorter.js'; import { VertexFormat } from '../../platform/graphics/vertex-format.js'; import { VertexBuffer } from '../../platform/graphics/vertex-buffer.js'; const mat = new Mat4(); const cameraPosition = new Vec3(); const cameraDirection = new Vec3(); const viewport = [0, 0]; /** @ignore */ class GSplatInstance { /** * @param {import('./gsplat.js').GSplat} splat - The splat instance. * @param {import('./gsplat-material.js').SplatMaterialOptions} options - The options. */ constructor(splat, options) { /** @type {import('./gsplat.js').GSplat} */ this.splat = void 0; /** @type {Mesh} */ this.mesh = void 0; /** @type {MeshInstance} */ this.meshInstance = void 0; /** @type {import('../materials/material.js').Material} */ this.material = void 0; /** @type {import('../../platform/graphics/texture.js').Texture} */ this.orderTexture = void 0; this.options = {}; /** @type {GSplatSorter | null} */ this.sorter = null; this.lastCameraPosition = new Vec3(); this.lastCameraDirection = new Vec3(); /** * List of cameras this instance is visible for. Updated every frame by the renderer. * * @type {import('../camera.js').Camera[]} * @ignore */ this.cameras = []; this.splat = splat; // clone options object options = Object.assign(this.options, options); // not supported on WebGL1 const device = splat.device; // create the order texture this.orderTexture = this.splat.createTexture('splatOrder', PIXELFORMAT_R32U, this.splat.evalTextureSize(this.splat.numSplats)); // material this.createMaterial(options); // number of quads to combine into a single instance. this is to increase occupancy // in the vertex shader. const splatInstanceSize = 128; const numSplats = Math.ceil(splat.numSplats / splatInstanceSize) * splatInstanceSize; const numSplatInstances = numSplats / splatInstanceSize; // specify the base splat index per instance const indexData = new Uint32Array(numSplatInstances); for (let i = 0; i < numSplatInstances; ++i) { indexData[i] = i * splatInstanceSize; } const vertexFormat = new VertexFormat(device, [{ semantic: SEMANTIC_ATTR13, components: 1, type: TYPE_UINT32, asInt: true }]); const indicesVB = new VertexBuffer(device, vertexFormat, numSplatInstances, { usage: BUFFER_STATIC, data: indexData.buffer }); // build the instance mesh const meshPositions = new Float32Array(12 * splatInstanceSize); const meshIndices = new Uint32Array(6 * splatInstanceSize); for (let i = 0; i < splatInstanceSize; ++i) { meshPositions.set([-2, -2, i, 2, -2, i, 2, 2, i, -2, 2, i], i * 12); const b = i * 4; meshIndices.set([0 + b, 1 + b, 2 + b, 0 + b, 2 + b, 3 + b], i * 6); } const mesh = new Mesh(device); mesh.setPositions(meshPositions, 3); mesh.setIndices(meshIndices); mesh.update(); this.mesh = mesh; this.mesh.aabb.copy(splat.aabb); this.meshInstance = new MeshInstance(this.mesh, this.material); this.meshInstance.setInstancing(indicesVB, true); this.meshInstance.gsplatInstance = this; // only start rendering the splat after we've received the splat order data this.meshInstance.instancingCount = 0; // clone centers to allow multiple instances of sorter this.centers = new Float32Array(splat.centers); // create sorter if (!options.dither || options.dither === DITHER_NONE) { this.sorter = new GSplatSorter(); this.sorter.init(this.orderTexture, this.centers); this.sorter.on('updated', count => { // limit splat render count to exclude those behind the camera this.meshInstance.instancingCount = Math.ceil(count / splatInstanceSize); // update splat count on the material const tex_params = this.material.getParameter('tex_params'); if (tex_params != null && tex_params.data) { tex_params.data[0] = count; } }); } } destroy() { var _this$material, _this$meshInstance, _this$sorter; (_this$material = this.material) == null || _this$material.destroy(); (_this$meshInstance = this.meshInstance) == null || _this$meshInstance.destroy(); (_this$sorter = this.sorter) == null || _this$sorter.destroy(); } clone() { return new GSplatInstance(this.splat, this.options); } createMaterial(options) { this.material = this.splat.createMaterial(options); this.material.setParameter('splatOrder', this.orderTexture); if (this.meshInstance) { this.meshInstance.material = this.material; } } updateViewport() { // TODO: improve, needs to handle render targets of different sizes const device = this.splat.device; viewport[0] = device.width; viewport[1] = device.height; // adjust viewport for stereoscopic VR sessions if (this.cameras.length > 0) { const camera = this.cameras[0]; const xr = camera.xr; if (xr && xr.active && xr.views.list.length === 2) { viewport[0] /= 2; } } this.material.setParameter('viewport', viewport); } /** * Sorts the GS vertices based on the given camera. * @param {import('../graph-node.js').GraphNode} cameraNode - The camera node used for sorting. */ 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); // sort if the camera has changed if (!cameraPosition.equalsApprox(this.lastCameraPosition) || !cameraDirection.equalsApprox(this.lastCameraDirection)) { this.lastCameraPosition.copy(cameraPosition); this.lastCameraDirection.copy(cameraDirection); this.sorter.setCamera(cameraPosition, cameraDirection); } } this.updateViewport(); } update() { if (this.cameras.length > 0) { // sort by the first camera it's visible for // TODO: extend to support multiple cameras const camera = this.cameras[0]; this.sort(camera._node); // we get new list of cameras each frame this.cameras.length = 0; } } } export { GSplatInstance };