playcanvas
Version:
PlayCanvas WebGL game engine
146 lines (143 loc) • 5.64 kB
JavaScript
import { SEMANTIC_ATTR13, SEMANTIC_POSITION, PIXELFORMAT_RGBA16U, CULLFACE_NONE } from '../../platform/graphics/constants.js';
import { BLEND_ADDITIVE, 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';
class GSplatRenderer {
constructor(device, node, cameraNode, layer, workBuffer){
this.instanceIndices = null;
this.instanceIndicesCount = 0;
this.viewportParams = [
0,
0
];
this.originalBlendType = BLEND_ADDITIVE;
this.device = device;
this.node = node;
this.cameraNode = cameraNode;
this.layer = layer;
this.workBuffer = workBuffer;
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
}
});
this.configureMaterial();
this.meshInstance = this.createMeshInstance();
layer.addMeshInstances([
this.meshInstance
]);
}
destroy() {
this.layer.removeMeshInstances([
this.meshInstance
]);
this._material.destroy();
this.meshInstance.destroy();
}
get material() {
return this._material;
}
configureMaterial() {
const { device, workBuffer } = this;
this._material.setDefine('GSPLAT_WORKBUFFER_DATA', true);
this._material.setDefine('STORAGE_ORDER', device.isWebGPU);
const isColorUint = workBuffer.colorTextureFormat === PIXELFORMAT_RGBA16U;
this._material.setDefine('GSPLAT_COLOR_UINT', isColorUint);
this._material.setParameter('splatColor', workBuffer.colorTexture);
this._material.setParameter('splatTexture0', workBuffer.splatTexture0);
this._material.setParameter('splatTexture1', workBuffer.splatTexture1);
this._material.setDefine('SH_BANDS', '0');
this._material.setParameter('numSplats', 0);
if (workBuffer.orderTexture) {
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();
}
update(count, textureSize) {
this.meshInstance.instancingCount = Math.ceil(count / GSplatResourceBase.instanceSize);
this._material.setParameter('numSplats', count);
this._material.setParameter('splatTextureSize', textureSize);
this.meshInstance.visible = count > 0;
}
frameUpdate(params) {
if (this.device.isWebGPU) {
this._material.setParameter('splatOrder', this.workBuffer.orderBuffer);
} else {
this._material.setParameter('splatOrder', this.workBuffer.orderTexture);
}
if (params.colorRamp) {
this._material.setParameter('colorRampIntensity', params.colorRampIntensity);
}
}
updateOverdrawMode(params) {
const overdrawEnabled = !!params.colorRamp;
const wasOverdrawEnabled = this._material.getDefine('GSPLAT_OVERDRAW');
if (overdrawEnabled) {
this._material.setParameter('colorRamp', params.colorRamp);
this._material.setParameter('colorRampIntensity', params.colorRampIntensity);
}
if (overdrawEnabled !== wasOverdrawEnabled) {
this._material.setDefine('GSPLAT_OVERDRAW', overdrawEnabled);
if (overdrawEnabled) {
this.originalBlendType = this._material.blendType;
this._material.blendType = BLEND_ADDITIVE;
} else {
this._material.blendType = this.originalBlendType;
}
this._material.update();
}
}
setMaxNumSplats(numSplats) {
const roundedNumSplats = math.roundUp(numSplats, GSplatResourceBase.instanceSize);
if (this.instanceIndicesCount < roundedNumSplats) {
this.instanceIndicesCount = roundedNumSplats;
this.instanceIndices?.destroy();
this.instanceIndices = GSplatResourceBase.createInstanceIndices(this.device, numSplats);
this.meshInstance.setInstancing(this.instanceIndices, true);
this._material.setParameter('splatTextureSize', this.workBuffer.textureSize);
}
}
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);
meshInstance.instancingCount = 0;
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;
const xr = camera?.camera?.xr;
if (xr?.active && xr.views.list.length === 2) {
viewport[0] *= 0.5;
}
this._material.setParameter('viewport', viewport);
}
}
export { GSplatRenderer };