UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

124 lines (121 loc) 6.21 kB
import { CULLFACE_NONE, PRIMITIVE_TRIANGLES } from '../../platform/graphics/constants.js'; import { BLEND_PREMULTIPLIED } from '../constants.js'; import { ShaderMaterial } from '../materials/shader-material.js'; import { MeshInstance } from '../mesh-instance.js'; import { Mesh } from '../mesh.js'; /** * @import { GraphNode } from '../graph-node.js' * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js' * @import { StorageBuffer } from '../../platform/graphics/storage-buffer.js' * @import { Texture } from '../../platform/graphics/texture.js' */ /** * Manages tile-based composites for the local compute gsplat renderer. Instead of blitting a * full-screen quad, only tiles that contain splats are drawn using indirect rendering. The vertex * shader procedurally generates tile quads from the built-in vertex index and a storage buffer * of non-empty tile indices populated by the classify pass. * * Owns two separate MeshInstance objects: a color instance (permanently in the layer, pick=false) * and a pick instance (never in any layer, returned on demand by prepareForPicking). * * @ignore */ class GSplatTileComposite { destroy() { this._material.destroy(); this._mesh.destroy(); this._meshInstance.destroy(); this._pickMaterial?.destroy(); this._pickMeshInstance?.destroy(); } get material() { return this._material; } get meshInstance() { return this._meshInstance; } /** * Per-frame update for color rendering: binds the indirect draw slot and updates material * parameters on the color mesh instance. * * @param {number} drawSlot - The indirect draw slot reserved for this frame. * @param {Texture} outputTexture - The compute-rasterized splat texture. * @param {StorageBuffer} rasterizeTileList - Buffer of non-empty tile indices. * @param {number} numTilesX - Number of tiles horizontally. * @param {number} screenWidth - Viewport width in pixels. * @param {number} screenHeight - Viewport height in pixels. */ update(drawSlot, outputTexture, rasterizeTileList, numTilesX, screenWidth, screenHeight) { this._meshInstance.setIndirect(null, drawSlot, 1); this._material.setParameter('source', outputTexture); this._material.setParameter('rasterizeTileList', rasterizeTileList); this._material.setParameter('numTilesX', numTilesX); this._material.setParameter('screenWidth', screenWidth); this._material.setParameter('screenHeight', screenHeight); } /** * Lazily creates the pick material and mesh instance, configures them with pick textures * and tile parameters, and returns the pick mesh instance for the picker to render. * * @param {number} drawSlot - The indirect draw slot. * @param {Texture} pickIdTexture - Compute-generated pick ID texture (r32uint). * @param {Texture} pickDepthTexture - Compute-generated pick depth texture (rgba16float). * @param {StorageBuffer} rasterizeTileList - Buffer of non-empty tile indices. * @param {number} numTilesX - Number of tiles horizontally. * @param {number} screenWidth - Viewport width in pixels. * @param {number} screenHeight - Viewport height in pixels. * @returns {MeshInstance} The configured pick mesh instance. */ prepareForPicking(drawSlot, pickIdTexture, pickDepthTexture, rasterizeTileList, numTilesX, screenWidth, screenHeight) { if (!this._pickMaterial) { this._pickMaterial = new ShaderMaterial({ uniqueName: 'GSplatTileCompositePick', vertexWGSL: '#include "gsplatTileCompositeVS"', fragmentWGSL: '#include "gsplatTileCompositePS"' }); this._pickMaterial.setDefine('PICK_MODE', true); this._pickMaterial.cull = CULLFACE_NONE; this._pickMaterial.depthWrite = false; this._pickMaterial.update(); this._pickMeshInstance = new MeshInstance(this._mesh, this._pickMaterial); this._pickMeshInstance.node = this._node; this._pickMeshInstance.instancingCount = 1; } const pickMI = /** @type {MeshInstance} */ this._pickMeshInstance; const pickMat = /** @type {ShaderMaterial} */ this._pickMaterial; pickMI.setIndirect(null, drawSlot, 1); pickMat.setParameter('pickIdTexture', pickIdTexture); pickMat.setParameter('pickDepthTexture', pickDepthTexture); pickMat.setParameter('rasterizeTileList', rasterizeTileList); pickMat.setParameter('numTilesX', numTilesX); pickMat.setParameter('screenWidth', screenWidth); pickMat.setParameter('screenHeight', screenHeight); return pickMI; } /** * @param {GraphicsDevice} device - The graphics device. * @param {GraphNode} node - The graph node for the mesh instance. * @param {Function} isVisibleFunc - Visibility callback: `(camera) => boolean`. */ constructor(device, node, isVisibleFunc){ /** @type {ShaderMaterial|null} */ this._pickMaterial = null; /** @type {MeshInstance|null} */ this._pickMeshInstance = null; this.device = device; this._node = node; this._material = new ShaderMaterial({ uniqueName: 'GSplatTileComposite', vertexWGSL: '#include "gsplatTileCompositeVS"', fragmentWGSL: '#include "gsplatTileCompositePS"' }); this._material.blendType = BLEND_PREMULTIPLIED; this._material.cull = CULLFACE_NONE; this._material.depthWrite = false; this._material.update(); this._mesh = new Mesh(device); this._mesh.primitive[0].type = PRIMITIVE_TRIANGLES; this._mesh.primitive[0].base = 0; this._mesh.primitive[0].count = 0; this._mesh.primitive[0].indexed = false; this._meshInstance = new MeshInstance(this._mesh, this._material); this._meshInstance.node = node; this._meshInstance.instancingCount = 1; this._meshInstance.isVisibleFunc = isVisibleFunc; this._meshInstance.pick = false; } } export { GSplatTileComposite };