playcanvas
Version:
PlayCanvas WebGL game engine
142 lines (139 loc) • 5.29 kB
JavaScript
import { Mat4 } from '../../core/math/mat4.js';
import { Vec3 } from '../../core/math/vec3.js';
import { PIXELFORMAT_R32U, SEMANTIC_ATTR13, SEMANTIC_POSITION, CULLFACE_NONE } from '../../platform/graphics/constants.js';
import { MeshInstance } from '../mesh-instance.js';
import { GSplatResolveSH } from './gsplat-resolve-sh.js';
import { GSplatSorter } from './gsplat-sorter.js';
import { GSplatSogsData } from './gsplat-sogs-data.js';
import { GSplatResourceBase } from './gsplat-resource-base.js';
import { ShaderMaterial } from '../materials/shader-material.js';
import { BLEND_NONE, BLEND_PREMULTIPLIED } from '../constants.js';
const mat = new Mat4();
const cameraPosition = new Vec3();
const cameraDirection = new Vec3();
const viewport = [
0,
0
];
class GSplatInstance {
constructor(resource, options = {}){
this.options = {};
this.sorter = null;
this.lastCameraPosition = new Vec3();
this.lastCameraDirection = new Vec3();
this.resolveSH = null;
this.cameras = [];
this.resource = resource;
this.orderTexture = resource.createTexture('splatOrder', PIXELFORMAT_R32U, resource.evalTextureSize(resource.numSplats));
if (options.material) {
this._material = options.material;
this._material.setParameter('splatOrder', this.orderTexture);
} else {
this._material = new ShaderMaterial({
uniqueName: 'SplatMaterial',
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._material);
this._material.update();
}
this.meshInstance = new MeshInstance(resource.mesh, this._material);
this.meshInstance.setInstancing(resource.instanceIndices, true);
this.meshInstance.gsplatInstance = this;
this.meshInstance.instancingCount = 0;
const centers = resource.centers.slice();
const chunks = resource.chunks?.slice();
this.sorter = new GSplatSorter();
this.sorter.init(this.orderTexture, centers, chunks);
this.sorter.on('updated', (count)=>{
this.meshInstance.instancingCount = Math.ceil(count / GSplatResourceBase.instanceSize);
this.material.setParameter('numSplats', count);
});
this.setHighQualitySH(options.highQualitySH ?? false);
}
destroy() {
this.orderTexture?.destroy();
this.resolveSH?.destroy();
this.material?.destroy();
this.meshInstance?.destroy();
this.sorter?.destroy();
}
set material(value) {
if (this._material !== value) {
this._material = value;
this._material.setParameter('splatOrder', this.orderTexture);
if (this.meshInstance) {
this.meshInstance.material = value;
}
}
}
get material() {
return this._material;
}
configureMaterial(material, options = {}) {
this.resource.configureMaterial(material);
material.setParameter('numSplats', 0);
material.setParameter('splatOrder', this.orderTexture);
material.setParameter('alphaClip', 0.3);
material.setDefine(`DITHER_${options.dither ? 'BLUENOISE' : 'NONE'}`, '');
material.cull = CULLFACE_NONE;
material.blendType = options.dither ? BLEND_NONE : BLEND_PREMULTIPLIED;
material.depthWrite = !!options.dither;
}
updateViewport(cameraNode) {
const camera = cameraNode?.camera;
const renderTarget = camera?.renderTarget;
const { width, height } = renderTarget ?? this.resource.device;
viewport[0] = width;
viewport[1] = height;
const xr = camera?.camera?.xr;
if (xr?.active && xr.views.list.length === 2) {
viewport[0] *= 0.5;
}
this.material.setParameter('viewport', viewport);
}
sort(cameraNode) {
if (this.sorter) {
const cameraMat = cameraNode.getWorldTransform();
cameraMat.getTranslation(cameraPosition);
cameraMat.getZ(cameraDirection);
const modelMat = this.meshInstance.node.getWorldTransform();
const invModelMat = mat.invert(modelMat);
invModelMat.transformPoint(cameraPosition, cameraPosition);
invModelMat.transformVector(cameraDirection, cameraDirection);
if (!cameraPosition.equalsApprox(this.lastCameraPosition) || !cameraDirection.equalsApprox(this.lastCameraDirection)) {
this.lastCameraPosition.copy(cameraPosition);
this.lastCameraDirection.copy(cameraDirection);
this.sorter.setCamera(cameraPosition, cameraDirection);
}
}
this.updateViewport(cameraNode);
}
update() {
if (this.cameras.length > 0) {
const camera = this.cameras[0];
this.sort(camera._node);
this.resolveSH?.render(camera._node, this.meshInstance.node.getWorldTransform());
this.cameras.length = 0;
}
}
setHighQualitySH(value) {
const { resource } = this;
const { gsplatData } = resource;
if (gsplatData instanceof GSplatSogsData && gsplatData.shBands > 0 && value === !!this.resolveSH) {
if (this.resolveSH) {
this.resolveSH.destroy();
this.resolveSH = null;
} else {
this.resolveSH = new GSplatResolveSH(resource.device, this);
}
}
}
}
export { GSplatInstance };