playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
266 lines (263 loc) • 9.97 kB
JavaScript
import { PIXELFORMAT_RGBA16U, CULLFACE_NONE, SEMANTIC_POSITION } from '../../platform/graphics/constants.js';
import { GSPLAT_FORWARD, GSPLAT_SHADOW, BLEND_PREMULTIPLIED, BLEND_ADDITIVE, SHADOWCAMERA_NAME } 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 { GSplatRenderer } from './gsplat-renderer.js';
class GSplatQuadRenderer extends GSplatRenderer {
setRenderMode(renderMode) {
const oldRenderMode = this.renderMode ?? 0;
const wasForward = (oldRenderMode & GSPLAT_FORWARD) !== 0;
const wasShadow = (oldRenderMode & GSPLAT_SHADOW) !== 0;
const isForward = (renderMode & GSPLAT_FORWARD) !== 0;
const isShadow = (renderMode & GSPLAT_SHADOW) !== 0;
this.meshInstance.castShadow = isShadow;
if (wasForward && !isForward) {
this.layer.removeMeshInstances([
this.meshInstance
], true);
}
if (wasShadow && !isShadow) {
this.layer.removeShadowCasters([
this.meshInstance
]);
}
if (!wasForward && isForward) {
this.layer.addMeshInstances([
this.meshInstance
], true);
}
if (!wasShadow && isShadow) {
this.layer.addShadowCasters([
this.meshInstance
]);
}
super.setRenderMode(renderMode);
}
destroy() {
if (this.renderMode) {
if (this.renderMode & GSPLAT_FORWARD) {
this.layer.removeMeshInstances([
this.meshInstance
], true);
}
if (this.renderMode & GSPLAT_SHADOW) {
this.layer.removeShadowCasters([
this.meshInstance
]);
}
}
this._material.destroy();
this.meshInstance.destroy();
super.destroy();
}
get material() {
return this._material;
}
onWorkBufferFormatChanged() {
this.configureMaterial();
}
configureMaterial() {
const { workBuffer } = this;
this._injectFormatChunks();
this._material.setDefine('SH_BANDS', '0');
this._material.setDefine('GSPLAT_SEPARATE_OPACITY', '');
const colorStream = workBuffer.format.getStream('dataColor');
if (colorStream && colorStream.format !== PIXELFORMAT_RGBA16U) {
this._material.setDefine('GSPLAT_COLOR_FLOAT', '');
}
this._updateIdDefines();
this._bindWorkBufferTextures();
this._material.setParameter('numSplats', 0);
this.setOrderData();
this._material.setDefine(`DITHER_${'NONE'}`, '');
this._material.cull = CULLFACE_NONE;
this._material.blendType = BLEND_PREMULTIPLIED;
this._material.depthWrite = false;
this._material.update();
}
_bindWorkBufferTextures() {
const { workBuffer } = this;
for (const stream of workBuffer.format.resourceStreams){
const texture = workBuffer.getTexture(stream.name);
if (texture) {
this._material.setParameter(stream.name, texture);
}
}
}
_injectFormatChunks() {
const chunks = this.device.isWebGPU ? this._material.shaderChunks.wgsl : this._material.shaderChunks.glsl;
const wbFormat = this.workBuffer.format;
chunks.set('gsplatDeclarationsVS', wbFormat.getInputDeclarations());
chunks.set('gsplatReadVS', wbFormat.getReadCode());
}
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;
}
setGpuSortedRendering(drawSlot, sortedIds, numSplatsBuffer, textureSize) {
this.meshInstance.setIndirect(null, drawSlot, 1);
this._material.setParameter('compactedSplatIds', sortedIds);
this._material.setParameter('numSplatsStorage', numSplatsBuffer);
if (!this._material.getDefine('GSPLAT_INDIRECT_DRAW')) {
this._material.setDefine('GSPLAT_INDIRECT_DRAW', true);
this._material.update();
}
this._material.setParameter('splatTextureSize', textureSize);
this.meshInstance.visible = true;
if (this.meshInstance.instancingCount <= 0) {
this.meshInstance.instancingCount = 1;
}
}
setCpuSortedRendering() {
this.meshInstance.setIndirect(null, -1);
if (this._material.getDefine('GSPLAT_INDIRECT_DRAW')) {
this._material.setDefine('GSPLAT_INDIRECT_DRAW', false);
this._material.update();
}
this.setOrderData();
this.meshInstance.visible = false;
}
setOrderData() {
if (this.device.isWebGPU) {
this._material.setParameter('splatOrder', this.workBuffer.orderBuffer);
} else {
this._material.setParameter('splatOrder', this.workBuffer.orderTexture);
}
}
frameUpdate(params) {
if (params.colorRamp) {
this._material.setParameter('colorRampIntensity', params.colorRampIntensity);
}
const cam = this.cameraNode.camera;
this.fisheyeProj.update(params.fisheye, cam.fov, cam.projectionMatrix);
const fisheyeEnabled = this.fisheyeProj.enabled;
if (fisheyeEnabled !== this._lastFisheyeEnabled) {
this._lastFisheyeEnabled = fisheyeEnabled;
this._material.setDefine('GSPLAT_FISHEYE', fisheyeEnabled);
this._material.update();
}
if (fisheyeEnabled) {
const fp = this.fisheyeProj;
this._material.setParameter('fisheye_k', fp.k);
this._material.setParameter('fisheye_inv_k', fp.invK);
this._material.setParameter('fisheye_projMat00', fp.projMat00);
this._material.setParameter('fisheye_projMat11', fp.projMat11);
}
const noFog = !params.useFog;
if (noFog !== this._lastNoFog) {
this._lastNoFog = noFog;
this._material.setDefine('GSPLAT_NO_FOG', noFog);
this._material.update();
}
this._syncWithWorkBufferFormat();
if (this.forceCopyMaterial || params.material.dirty) {
this.copyMaterialSettings(params.material);
this.forceCopyMaterial = false;
}
}
_updateIdDefines() {
const hasPcId = !!this.workBuffer.format.getStream('pcId');
this._material.setDefine('GSPLAT_UNIFIED_ID', hasPcId);
this._material.setDefine('PICK_CUSTOM_ID', hasPcId);
}
_syncWithWorkBufferFormat() {
const wbFormat = this.workBuffer.format;
if (this._workBufferFormatVersion !== wbFormat.extraStreamsVersion) {
this._workBufferFormatVersion = wbFormat.extraStreamsVersion;
this.workBuffer.syncWithFormat();
this._injectFormatChunks();
this._bindWorkBufferTextures();
this._updateIdDefines();
this._material.update();
}
}
copyMaterialSettings(sourceMaterial) {
const keysToDelete = [];
this._material.defines.forEach((value, key)=>{
if (!this._internalDefines.has(key)) {
keysToDelete.push(key);
}
});
keysToDelete.forEach((key)=>this._material.defines.delete(key));
sourceMaterial.defines.forEach((value, key)=>{
this._material.defines.set(key, value);
});
const srcParams = sourceMaterial.parameters;
for(const paramName in srcParams){
if (srcParams.hasOwnProperty(paramName)) {
this._material.setParameter(paramName, srcParams[paramName].data);
}
}
if (sourceMaterial.hasShaderChunks) {
this._material.shaderChunks.copy(sourceMaterial.shaderChunks);
}
this._injectFormatChunks();
this._material.update();
}
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();
}
}
createMeshInstance() {
const mesh = GSplatResourceBase.createMesh(this.device);
const meshInstance = new MeshInstance(mesh, this._material);
meshInstance.node = this.node;
meshInstance.setInstancing(true, true);
meshInstance.instancingCount = 0;
const thisCamera = this.cameraNode.camera;
meshInstance.isVisibleFunc = (camera)=>{
const renderMode = this.renderMode ?? 0;
if (thisCamera.camera === camera && renderMode & GSPLAT_FORWARD) {
return true;
}
if (renderMode & GSPLAT_SHADOW) {
return camera.node?.name === SHADOWCAMERA_NAME;
}
return false;
};
return meshInstance;
}
constructor(device, node, cameraNode, layer, workBuffer){
super(device, node, cameraNode, layer, workBuffer), this.originalBlendType = BLEND_ADDITIVE, this._internalDefines = new Set(), this.forceCopyMaterial = true, this._lastFisheyeEnabled = false;
this._material = new ShaderMaterial({
uniqueName: 'UnifiedSplatMaterial',
vertexGLSL: '#include "gsplatVS"',
fragmentGLSL: '#include "gsplatPS"',
vertexWGSL: '#include "gsplatVS"',
fragmentWGSL: '#include "gsplatPS"',
attributes: {
vertex_position: SEMANTIC_POSITION
}
});
this._material.setDefine('{GSPLAT_INSTANCE_SIZE}', GSplatResourceBase.instanceSize);
this.configureMaterial();
this._material.defines.forEach((value, key)=>{
this._internalDefines.add(key);
});
this._internalDefines.add('{GSPLAT_INSTANCE_SIZE}');
this._internalDefines.add('GSPLAT_UNIFIED_ID');
this._internalDefines.add('PICK_CUSTOM_ID');
this._internalDefines.add('GSPLAT_INDIRECT_DRAW');
this._internalDefines.add('GSPLAT_SEPARATE_OPACITY');
this._internalDefines.add('GSPLAT_FISHEYE');
this.meshInstance = this.createMeshInstance();
}
}
export { GSplatQuadRenderer };