playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
242 lines (241 loc) • 8.94 kB
JavaScript
import { Mat4 } from "../../core/math/mat4.js";
import { SEMANTIC_POSITION, CULLFACE_NONE } from "../../platform/graphics/constants.js";
import {
BLEND_NONE,
BLEND_PREMULTIPLIED,
BLEND_ADDITIVE,
GSPLAT_FORWARD,
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";
import { CACHE_STRIDE } from "./gsplat-projector-constants.js";
import { Camera } from "../camera.js";
const _invProjMat = new Mat4();
const _shaderProjMat = new Mat4();
class GSplatHybridRenderer extends GSplatRenderer {
_material;
meshInstance;
_pickMaterial = null;
_pickMeshInstance = null;
_clipToViewZ = new Float32Array(4);
_clipToViewZPick = null;
originalBlendType = BLEND_ADDITIVE;
_internalDefines = /* @__PURE__ */ new Set();
forceCopyMaterial = true;
constructor(device, node, cameraNode, layer, workBuffer) {
super(device, node, cameraNode, layer, workBuffer);
this._material = new ShaderMaterial({
uniqueName: "UnifiedSplatHybridMaterial",
vertexWGSL: '#include "gsplatHybridVS"',
fragmentWGSL: '#include "gsplatPS"',
attributes: {
vertex_position: SEMANTIC_POSITION
}
});
this._material.setDefine("{GSPLAT_INSTANCE_SIZE}", GSplatResourceBase.instanceSize);
this._material.setDefine("{CACHE_STRIDE}", CACHE_STRIDE);
this.configureMaterial();
this._material.defines.forEach((value, key) => {
this._internalDefines.add(key);
});
this._internalDefines.add("{GSPLAT_INSTANCE_SIZE}");
this._internalDefines.add("{CACHE_STRIDE}");
this._internalDefines.add("GSPLAT_UNIFIED_ID");
this._internalDefines.add("PICK_CUSTOM_ID");
this._internalDefines.add("GSPLAT_OVERDRAW");
this._internalDefines.add("GSPLAT_NO_FOG");
this.meshInstance = this.createMeshInstance();
}
setRenderMode(renderMode) {
const oldRenderMode = this.renderMode ?? 0;
const wasForward = (oldRenderMode & GSPLAT_FORWARD) !== 0;
const isForward = (renderMode & GSPLAT_FORWARD) !== 0;
if (wasForward && !isForward) {
this.layer.removeMeshInstances([this.meshInstance], true);
}
if (!wasForward && isForward) {
this.layer.addMeshInstances([this.meshInstance], true);
}
super.setRenderMode(renderMode);
}
destroy() {
if (this.renderMode && this.renderMode & GSPLAT_FORWARD) {
this.layer.removeMeshInstances([this.meshInstance], true);
}
this._material.destroy();
this._pickMaterial?.destroy();
this.meshInstance.destroy();
this._pickMeshInstance?.destroy();
super.destroy();
}
get material() {
return this._material;
}
onWorkBufferFormatChanged() {
this.configureMaterial();
}
configureMaterial() {
this._material.setDefine("SH_BANDS", "0");
this._material.setDefine("GSPLAT_INDIRECT_DRAW", true);
this._updateIdDefines(this._material);
const dither = false;
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();
}
update(count, textureSize) {
if (this.meshInstance.instancingCount <= 0) {
this.meshInstance.instancingCount = 1;
}
this.meshInstance.visible = count > 0;
}
setHybridSortedRendering(drawSlot, sortedIndices, projCache, numSplatsBuffer) {
this.meshInstance.setIndirect(null, drawSlot, 1);
this._material.setParameter("sortedIndices", sortedIndices);
this._material.setParameter("projCache", projCache);
this._material.setParameter("numSplatsStorage", numSplatsBuffer);
this._computeClipToViewZ(this.cameraNode, this._clipToViewZ);
this._material.setParameter("clipToViewZ", this._clipToViewZ);
this.meshInstance.visible = true;
if (this.meshInstance.instancingCount <= 0) {
this.meshInstance.instancingCount = 1;
}
}
prepareForPicking(drawSlot, sortedIndices, projCache, numSplatsBuffer, alphaClip, alphaClipForward, cameraNode) {
if (!this._pickMaterial) {
this._pickMaterial = new ShaderMaterial({
uniqueName: "UnifiedSplatHybridPickMaterial",
vertexWGSL: '#include "gsplatHybridVS"',
fragmentWGSL: '#include "gsplatPS"',
attributes: {
vertex_position: SEMANTIC_POSITION
}
});
this._pickMaterial.setDefine("{GSPLAT_INSTANCE_SIZE}", GSplatResourceBase.instanceSize);
this._pickMaterial.setDefine("{CACHE_STRIDE}", CACHE_STRIDE);
this._pickMaterial.setDefine("SH_BANDS", "0");
this._pickMaterial.setDefine("GSPLAT_INDIRECT_DRAW", true);
this._pickMaterial.setDefine("DITHER_NONE", "");
this._updateIdDefines(this._pickMaterial);
this._pickMaterial.cull = CULLFACE_NONE;
this._pickMaterial.blendType = BLEND_NONE;
this._pickMaterial.depthWrite = false;
this._pickMaterial.update();
const mesh = GSplatResourceBase.createMesh(this.device);
this._pickMeshInstance = new MeshInstance(mesh, this._pickMaterial);
this._pickMeshInstance.node = this.node;
this._pickMeshInstance.setInstancing(true, true);
this._pickMeshInstance.instancingCount = 1;
} else {
if (this._updateIdDefines(this._pickMaterial)) {
this._pickMaterial.update();
}
}
const pickMaterial = this._pickMaterial;
const pickMeshInstance = this._pickMeshInstance;
pickMeshInstance.setIndirect(null, drawSlot, 1);
pickMaterial.setParameter("sortedIndices", sortedIndices);
pickMaterial.setParameter("projCache", projCache);
pickMaterial.setParameter("numSplatsStorage", numSplatsBuffer);
pickMaterial.setParameter("alphaClip", alphaClip);
pickMaterial.setParameter("alphaClipForward", alphaClipForward);
this._clipToViewZPick ?? (this._clipToViewZPick = new Float32Array(4));
this._computeClipToViewZ(cameraNode, this._clipToViewZPick);
pickMaterial.setParameter("clipToViewZ", this._clipToViewZPick);
return pickMeshInstance;
}
_computeClipToViewZ(cameraNode, dst) {
const camComp = cameraNode.camera;
const cam = camComp.camera;
if (this.fisheyeProj.enabled) {
const near = cam.nearClip;
const far = cam.farClip;
dst[0] = 0;
dst[1] = 0;
dst[2] = far - near;
dst[3] = near;
return;
}
const flipY = !!camComp.renderTarget?.flipY;
_invProjMat.copy(Camera.applyShaderProjectionTransform(cam.projectionMatrix, _shaderProjMat, flipY, this.device.isWebGPU)).invert();
const d = _invProjMat.data;
dst[0] = -d[2];
dst[1] = -d[6];
dst[2] = -d[10];
dst[3] = -d[14];
}
setCpuSortedRendering() {
this.meshInstance.setIndirect(null, -1);
this.meshInstance.visible = false;
}
setOrderData() {
}
frameUpdate(params) {
this._material.setParameter("alphaClip", params.alphaClip);
this._material.setParameter("alphaClipForward", params.alphaClipForward);
this._pickMaterial?.setParameter("alphaClip", params.alphaClip);
this._pickMaterial?.setParameter("alphaClipForward", params.alphaClipForward);
if (params.colorRamp) {
this._material.setParameter("colorRampIntensity", params.colorRampIntensity);
}
const noFog = !params.useFog;
if (noFog !== this._lastNoFog) {
this._lastNoFog = noFog;
this._material.setDefine("GSPLAT_NO_FOG", noFog);
this._material.update();
}
}
_updateIdDefines(material) {
const hasPcId = !!this.workBuffer.format.getStream("pcId");
const changed = material.getDefine("GSPLAT_UNIFIED_ID") !== hasPcId || material.getDefine("PICK_CUSTOM_ID") !== hasPcId;
material.setDefine("GSPLAT_UNIFIED_ID", hasPcId);
material.setDefine("PICK_CUSTOM_ID", hasPcId);
return changed;
}
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;
meshInstance.pick = false;
const thisCamera = this.cameraNode.camera;
meshInstance.isVisibleFunc = (camera) => {
const renderMode = this.renderMode ?? 0;
if (thisCamera.camera === camera && renderMode & GSPLAT_FORWARD) {
return true;
}
if (camera.node?.name === SHADOWCAMERA_NAME) {
return false;
}
return false;
};
return meshInstance;
}
}
export {
GSplatHybridRenderer
};