playcanvas
Version:
PlayCanvas WebGL game engine
125 lines (122 loc) • 5.49 kB
JavaScript
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 };