playcanvas
Version:
PlayCanvas WebGL game engine
508 lines (505 loc) • 15.4 kB
JavaScript
import { BoundingBox } from '../core/shape/bounding-box.js';
import { BoundingSphere } from '../core/shape/bounding-sphere.js';
import { BindGroup } from '../platform/graphics/bind-group.js';
import { UniformBuffer } from '../platform/graphics/uniform-buffer.js';
import { SHADOW_CASCADE_ALL, LAYER_WORLD, RENDERSTYLE_SOLID, MASK_AFFECT_DYNAMIC, SHADERDEF_UV0, SHADERDEF_UV1, SHADERDEF_VCOLOR, SHADERDEF_TANGENTS, SHADERDEF_NOSHADOW, SHADERDEF_BATCH, SHADERDEF_SKIN, SHADERDEF_MORPH_POSITION, SHADERDEF_MORPH_NORMAL, SHADERDEF_MORPH_TEXTURE_BASED_INT, SHADERDEF_SCREENSPACE, SHADERDEF_INSTANCING, MASK_AFFECT_LIGHTMAPPED } from './constants.js';
import { getDefaultMaterial } from './materials/default-material.js';
import { LightmapCache } from './graphics/lightmap-cache.js';
import { hash32Fnv1a } from '../core/hash.js';
let id = 0;
const _tmpAabb = new BoundingBox();
const _tempBoneAabb = new BoundingBox();
const _tempSphere = new BoundingSphere();
const _meshSet = new Set();
const lookupHashes = new Uint32Array(4);
class InstancingData {
constructor(numObjects){
this.vertexBuffer = null;
this._destroyVertexBuffer = false;
this.count = numObjects;
}
destroy() {
if (this._destroyVertexBuffer) {
this.vertexBuffer?.destroy();
}
this.vertexBuffer = null;
}
}
class IndirectData {
get(camera) {
return this.map.get(camera) ?? this.map.get(null);
}
constructor(){
this.map = new Map();
this.meshMetaData = new Int32Array(4);
}
}
class ShaderInstance {
getBindGroup(device) {
if (!this.bindGroup) {
const shader = this.shader;
const bindGroupFormat = shader.meshBindGroupFormat;
this.bindGroup = new BindGroup(device, bindGroupFormat);
}
return this.bindGroup;
}
getUniformBuffer(device) {
if (!this.uniformBuffer) {
const shader = this.shader;
const ubFormat = shader.meshUniformBufferFormat;
this.uniformBuffer = new UniformBuffer(device, ubFormat, false);
}
return this.uniformBuffer;
}
destroy() {
this.bindGroup?.destroy();
this.bindGroup = null;
this.uniformBuffer?.destroy();
this.uniformBuffer = null;
}
constructor(){
this.bindGroup = null;
this.uniformBuffer = null;
}
}
class MeshInstance {
constructor(mesh, material, node = null){
this.castShadow = false;
this.shadowCascadeMask = SHADOW_CASCADE_ALL;
this.cull = true;
this.drawOrder = 0;
this._drawBucket = 127;
this.visible = true;
this.visibleThisFrame = false;
this.flipFacesFactor = 1;
this.gsplatInstance = null;
this.id = id++;
this.isVisibleFunc = null;
this.instancingData = null;
this.indirectData = null;
this.parameters = {};
this.pick = true;
this.stencilFront = null;
this.stencilBack = null;
this.transparent = false;
this._aabb = new BoundingBox();
this._aabbVer = -1;
this._aabbMeshVer = -1;
this._customAabb = null;
this._updateAabb = true;
this._updateAabbFunc = null;
this._sortKeyShadow = 0;
this._sortKeyForward = 0;
this._sortKeyDynamic = 0;
this._layer = LAYER_WORLD;
this._material = null;
this._skinInstance = null;
this._morphInstance = null;
this._receiveShadow = true;
this._renderStyle = RENDERSTYLE_SOLID;
this._screenSpace = false;
this._shaderCache = new Map();
this._shaderDefs = MASK_AFFECT_DYNAMIC << 16;
this._calculateSortDistance = null;
this.node = node;
this._mesh = mesh;
mesh.incRefCount();
this.material = material;
if (mesh.vertexBuffer) {
const format = mesh.vertexBuffer.format;
this._shaderDefs |= format.hasUv0 ? SHADERDEF_UV0 : 0;
this._shaderDefs |= format.hasUv1 ? SHADERDEF_UV1 : 0;
this._shaderDefs |= format.hasColor ? SHADERDEF_VCOLOR : 0;
this._shaderDefs |= format.hasTangents ? SHADERDEF_TANGENTS : 0;
}
this.updateKey();
}
set drawBucket(bucket) {
this._drawBucket = Math.floor(bucket) & 0xff;
this.updateKey();
}
get drawBucket() {
return this._drawBucket;
}
set renderStyle(renderStyle) {
this._renderStyle = renderStyle;
this.mesh.prepareRenderState(renderStyle);
}
get renderStyle() {
return this._renderStyle;
}
set mesh(mesh) {
if (mesh === this._mesh) {
return;
}
if (this._mesh) {
this._mesh.decRefCount();
}
this._mesh = mesh;
if (mesh) {
mesh.incRefCount();
}
}
get mesh() {
return this._mesh;
}
set aabb(aabb) {
this._aabb = aabb;
}
get aabb() {
if (!this._updateAabb) {
return this._aabb;
}
if (this._updateAabbFunc) {
return this._updateAabbFunc(this._aabb);
}
let localAabb = this._customAabb;
let toWorldSpace = !!localAabb;
if (!localAabb) {
localAabb = _tmpAabb;
if (this.skinInstance) {
if (!this.mesh.boneAabb) {
const morphTargets = this._morphInstance ? this._morphInstance.morph._targets : null;
this.mesh._initBoneAabbs(morphTargets);
}
const boneUsed = this.mesh.boneUsed;
let first = true;
for(let i = 0; i < this.mesh.boneAabb.length; i++){
if (boneUsed[i]) {
_tempBoneAabb.setFromTransformedAabb(this.mesh.boneAabb[i], this.skinInstance.matrices[i]);
if (first) {
first = false;
localAabb.center.copy(_tempBoneAabb.center);
localAabb.halfExtents.copy(_tempBoneAabb.halfExtents);
} else {
localAabb.add(_tempBoneAabb);
}
}
}
toWorldSpace = true;
} else if (this.node._aabbVer !== this._aabbVer || this.mesh._aabbVer !== this._aabbMeshVer) {
if (this.mesh) {
localAabb.center.copy(this.mesh.aabb.center);
localAabb.halfExtents.copy(this.mesh.aabb.halfExtents);
} else {
localAabb.center.set(0, 0, 0);
localAabb.halfExtents.set(0, 0, 0);
}
if (this.mesh && this.mesh.morph) {
const morphAabb = this.mesh.morph.aabb;
localAabb._expand(morphAabb.getMin(), morphAabb.getMax());
}
toWorldSpace = true;
this._aabbVer = this.node._aabbVer;
this._aabbMeshVer = this.mesh._aabbVer;
}
}
if (toWorldSpace) {
this._aabb.setFromTransformedAabb(localAabb, this.node.getWorldTransform());
}
return this._aabb;
}
clearShaders() {
this._shaderCache.forEach((shaderInstance)=>{
shaderInstance.destroy();
});
this._shaderCache.clear();
}
getShaderInstance(shaderPass, lightHash, scene, cameraShaderParams, viewUniformFormat, viewBindGroupFormat, sortedLights) {
const shaderDefs = this._shaderDefs;
lookupHashes[0] = shaderPass;
lookupHashes[1] = lightHash;
lookupHashes[2] = shaderDefs;
lookupHashes[3] = cameraShaderParams.hash;
const hash = hash32Fnv1a(lookupHashes);
let shaderInstance = this._shaderCache.get(hash);
if (!shaderInstance) {
const mat = this._material;
shaderInstance = new ShaderInstance();
shaderInstance.shader = mat.variants.get(hash);
shaderInstance.hashes = new Uint32Array(lookupHashes);
if (!shaderInstance.shader) {
const shader = mat.getShaderVariant({
device: this.mesh.device,
scene: scene,
objDefs: shaderDefs,
cameraShaderParams: cameraShaderParams,
pass: shaderPass,
sortedLights: sortedLights,
viewUniformFormat: viewUniformFormat,
viewBindGroupFormat: viewBindGroupFormat,
vertexFormat: this.mesh.vertexBuffer?.format
});
mat.variants.set(hash, shader);
shaderInstance.shader = shader;
}
this._shaderCache.set(hash, shaderInstance);
}
return shaderInstance;
}
set material(material) {
this.clearShaders();
const prevMat = this._material;
if (prevMat) {
prevMat.removeMeshInstanceRef(this);
}
this._material = material;
if (material) {
material.addMeshInstanceRef(this);
this.transparent = material.transparent;
this.updateKey();
}
}
get material() {
return this._material;
}
_updateShaderDefs(shaderDefs) {
if (shaderDefs !== this._shaderDefs) {
this._shaderDefs = shaderDefs;
this.clearShaders();
}
}
set calculateSortDistance(calculateSortDistance) {
this._calculateSortDistance = calculateSortDistance;
}
get calculateSortDistance() {
return this._calculateSortDistance;
}
set receiveShadow(val) {
if (this._receiveShadow !== val) {
this._receiveShadow = val;
this._updateShaderDefs(val ? this._shaderDefs & -2 : this._shaderDefs | SHADERDEF_NOSHADOW);
}
}
get receiveShadow() {
return this._receiveShadow;
}
set batching(val) {
this._updateShaderDefs(val ? this._shaderDefs | SHADERDEF_BATCH : this._shaderDefs & -16385);
}
get batching() {
return (this._shaderDefs & SHADERDEF_BATCH) !== 0;
}
set skinInstance(val) {
this._skinInstance = val;
this._updateShaderDefs(val ? this._shaderDefs | SHADERDEF_SKIN : this._shaderDefs & -3);
this._setupSkinUpdate();
}
get skinInstance() {
return this._skinInstance;
}
set morphInstance(val) {
this._morphInstance?.destroy();
this._morphInstance = val;
let shaderDefs = this._shaderDefs;
shaderDefs = val && val.morph.morphPositions ? shaderDefs | SHADERDEF_MORPH_POSITION : shaderDefs & -1025;
shaderDefs = val && val.morph.morphNormals ? shaderDefs | SHADERDEF_MORPH_NORMAL : shaderDefs & -2049;
shaderDefs = val && val.morph.intRenderFormat ? shaderDefs | SHADERDEF_MORPH_TEXTURE_BASED_INT : shaderDefs & -8193;
this._updateShaderDefs(shaderDefs);
}
get morphInstance() {
return this._morphInstance;
}
set screenSpace(val) {
if (this._screenSpace !== val) {
this._screenSpace = val;
this._updateShaderDefs(val ? this._shaderDefs | SHADERDEF_SCREENSPACE : this._shaderDefs & -257);
}
}
get screenSpace() {
return this._screenSpace;
}
set key(val) {
this._sortKeyForward = val;
}
get key() {
return this._sortKeyForward;
}
set mask(val) {
const toggles = this._shaderDefs & 0x0000FFFF;
this._updateShaderDefs(toggles | val << 16);
}
get mask() {
return this._shaderDefs >> 16;
}
set instancingCount(value) {
if (this.instancingData) {
this.instancingData.count = value;
}
}
get instancingCount() {
return this.instancingData ? this.instancingData.count : 0;
}
destroy() {
const mesh = this.mesh;
if (mesh) {
this.mesh = null;
if (mesh.refCount < 1) {
mesh.destroy();
}
}
this.setRealtimeLightmap(MeshInstance.lightmapParamNames[0], null);
this.setRealtimeLightmap(MeshInstance.lightmapParamNames[1], null);
this._skinInstance?.destroy();
this._skinInstance = null;
this.morphInstance?.destroy();
this.morphInstance = null;
this.clearShaders();
this.material = null;
this.instancingData?.destroy();
}
static{
this.lightmapParamNames = [
'texture_lightMap',
'texture_dirLightMap'
];
}
static _prepareRenderStyleForArray(meshInstances, renderStyle) {
if (meshInstances) {
for(let i = 0; i < meshInstances.length; i++){
meshInstances[i]._renderStyle = renderStyle;
const mesh = meshInstances[i].mesh;
if (!_meshSet.has(mesh)) {
_meshSet.add(mesh);
mesh.prepareRenderState(renderStyle);
}
}
_meshSet.clear();
}
}
_isVisible(camera) {
if (this.visible) {
if (this.isVisibleFunc) {
return this.isVisibleFunc(camera);
}
_tempSphere.center = this.aabb.center;
_tempSphere.radius = this._aabb.halfExtents.length();
return camera.frustum.containsSphere(_tempSphere) > 0;
}
return false;
}
updateKey() {
const { material } = this;
this._sortKeyForward = this._drawBucket << 23 | (material.alphaToCoverage || material.alphaTest ? 0x400000 : 0) | material.id & 0x3fffff;
}
setInstancing(vertexBuffer, cull = false) {
if (vertexBuffer) {
this.instancingData = new InstancingData(vertexBuffer.numVertices);
this.instancingData.vertexBuffer = vertexBuffer;
vertexBuffer.format.instancing = true;
this.cull = cull;
} else {
this.instancingData = null;
this.cull = true;
}
this._updateShaderDefs(vertexBuffer ? this._shaderDefs | SHADERDEF_INSTANCING : this._shaderDefs & -33);
}
setIndirect(camera, slot) {
this._allocIndirectData();
this.indirectData.map.set(camera?.camera ?? null, slot);
const device = this.mesh.device;
device.mapsToClear.add(this.indirectData.map);
}
getIndirectMetaData() {
this._allocIndirectData();
const prim = this.mesh?.primitive[this.renderStyle];
const data = this.indirectData.meshMetaData;
data[0] = prim.count;
data[1] = prim.base;
data[2] = prim.baseVertex;
return data;
}
_allocIndirectData() {
if (!this.indirectData) {
this.indirectData = new IndirectData();
}
}
ensureMaterial(device) {
if (!this.material) {
this.material = getDefaultMaterial(device);
}
}
clearParameters() {
this.parameters = {};
}
getParameters() {
return this.parameters;
}
getParameter(name) {
return this.parameters[name];
}
setParameter(name, data, passFlags = 0xFFFFFFFF) {
const param = this.parameters[name];
if (param) {
param.data = data;
param.passFlags = passFlags;
} else {
this.parameters[name] = {
scopeId: null,
data: data,
passFlags: passFlags
};
}
}
setRealtimeLightmap(name, texture) {
const old = this.getParameter(name);
if (old === texture) {
return;
}
if (old) {
LightmapCache.decRef(old.data);
}
if (texture) {
LightmapCache.incRef(texture);
this.setParameter(name, texture);
} else {
this.deleteParameter(name);
}
}
deleteParameter(name) {
if (this.parameters[name]) {
delete this.parameters[name];
}
}
setParameters(device, passFlag) {
const parameters = this.parameters;
for(const paramName in parameters){
const parameter = parameters[paramName];
if (parameter.passFlags & passFlag) {
if (!parameter.scopeId) {
parameter.scopeId = device.scope.resolve(paramName);
}
parameter.scopeId.setValue(parameter.data);
}
}
}
setLightmapped(value) {
if (value) {
this.mask = (this.mask | MASK_AFFECT_LIGHTMAPPED) & -6;
} else {
this.setRealtimeLightmap(MeshInstance.lightmapParamNames[0], null);
this.setRealtimeLightmap(MeshInstance.lightmapParamNames[1], null);
this._shaderDefs &= -4289;
this.mask = (this.mask | MASK_AFFECT_DYNAMIC) & -7;
}
}
setCustomAabb(aabb) {
if (aabb) {
if (this._customAabb) {
this._customAabb.copy(aabb);
} else {
this._customAabb = aabb.clone();
}
} else {
this._customAabb = null;
this._aabbVer = -1;
}
this._setupSkinUpdate();
}
_setupSkinUpdate() {
if (this._skinInstance) {
this._skinInstance._updateBeforeCull = !this._customAabb;
}
}
}
export { MeshInstance };