playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
273 lines (272 loc) • 9.7 kB
JavaScript
import { SEMANTIC_POSITION, CULLFACE_NONE, PIXELFORMAT_RGBA16U } from "../../platform/graphics/constants.js";
import {
BLEND_NONE,
BLEND_PREMULTIPLIED,
BLEND_ADDITIVE,
GSPLAT_FORWARD,
GSPLAT_SHADOW,
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 {
_material;
meshInstance;
originalBlendType = BLEND_ADDITIVE;
_internalDefines = /* @__PURE__ */ new Set();
forceCopyMaterial = true;
_lastFisheyeEnabled = false;
_lastSourceChunksKey = "";
constructor(device, node, cameraNode, layer, workBuffer) {
super(device, node, cameraNode, layer, workBuffer);
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();
}
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();
const dither = false;
this._material.setParameter("numSplats", 0);
this.setOrderData();
this._material.setDefine(`DITHER_${dither ? "BLUENOISE" : "NONE"}`, "");
this._material.cull = CULLFACE_NONE;
this._material.blendType = dither ? BLEND_NONE : BLEND_PREMULTIPLIED;
this._material.depthWrite = !!dither;
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) && !sourceMaterial.defines.has(key)) {
keysToDelete.push(key);
}
});
keysToDelete.forEach((key) => this._material.setDefine(key, void 0));
sourceMaterial.defines.forEach((value, key) => {
this._material.setDefine(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) {
const sourceChunksKey = sourceMaterial.shaderChunks.key;
if (sourceChunksKey !== this._lastSourceChunksKey) {
this._material.shaderChunks.copy(sourceMaterial.shaderChunks);
this._injectFormatChunks();
this._lastSourceChunksKey = sourceChunksKey;
}
}
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;
}
}
export {
GSplatQuadRenderer
};