UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

508 lines (505 loc) 15.4 kB
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 };