UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

1,084 lines (1,083 loc) 36.5 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); import { Debug, DebugHelper } from "../core/debug.js"; 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 { VertexBuffer } from "../platform/graphics/vertex-buffer.js"; import { DrawCommands } from "../platform/graphics/draw-commands.js"; import { indexFormatByteSize } from "../platform/graphics/constants.js"; import { LAYER_WORLD, MASK_AFFECT_DYNAMIC, MASK_BAKE, MASK_AFFECT_LIGHTMAPPED, RENDERSTYLE_SOLID, SHADERDEF_UV0, SHADERDEF_UV1, SHADERDEF_VCOLOR, SHADERDEF_TANGENTS, SHADERDEF_NOSHADOW, SHADERDEF_SKIN, SHADERDEF_SCREENSPACE, SHADERDEF_MORPH_POSITION, SHADERDEF_MORPH_NORMAL, SHADERDEF_BATCH, SHADERDEF_LM, SHADERDEF_DIRLM, SHADERDEF_LMAMBIENT, SHADERDEF_INSTANCING, SHADERDEF_MORPH_TEXTURE_BASED_INT, SHADOW_CASCADE_ALL } from "./constants.js"; import { GraphNode } from "./graph-node.js"; import { getDefaultMaterial } from "./materials/default-material.js"; import { LightmapCache } from "./graphics/lightmap-cache.js"; import { DebugGraphics } from "../platform/graphics/debug-graphics.js"; import { hash32Fnv1a } from "../core/hash.js"; import { array } from "../core/array-utils.js"; import { PickerId } from "./picker-id.js"; const _tmpAabb = new BoundingBox(); const _tempBoneAabb = new BoundingBox(); const _tempSphere = new BoundingSphere(); const _meshSet = /* @__PURE__ */ new Set(); const lookupHashes = new Uint32Array(4); class InstancingData { /** * @param {number} numObjects - The number of objects instanced. */ constructor(numObjects) { /** @type {VertexBuffer|null} */ __publicField(this, "vertexBuffer", null); /** * True if the vertex buffer is destroyed when the mesh instance is destroyed. */ __publicField(this, "_destroyVertexBuffer", false); this.count = numObjects; } destroy() { if (this._destroyVertexBuffer) { this.vertexBuffer?.destroy(); } this.vertexBuffer = null; } } class ShaderInstance { constructor() { /** * A shader. * * @type {Shader|undefined} */ __publicField(this, "shader"); /** * A bind group storing mesh textures / samplers for the shader. but not the uniform buffer. * * @type {BindGroup|null} */ __publicField(this, "bindGroup", null); /** * A uniform buffer storing mesh uniforms for the shader. * * @type {UniformBuffer|null} */ __publicField(this, "uniformBuffer", null); /** * The full array of hashes used to lookup the pipeline, used in case of hash collision. * * @type {Uint32Array} */ __publicField(this, "hashes"); } /** * Returns the mesh bind group for the shader. * * @param {GraphicsDevice} device - The graphics device. * @returns {BindGroup} - The mesh bind group. */ getBindGroup(device) { if (!this.bindGroup) { const shader = this.shader; Debug.assert(shader); const bindGroupFormat = shader.meshBindGroupFormat; Debug.assert(bindGroupFormat); this.bindGroup = new BindGroup(device, bindGroupFormat); DebugHelper.setName(this.bindGroup, `MeshBindGroup_${this.bindGroup.id}`); } return this.bindGroup; } /** * Returns the uniform buffer for the shader. * * @param {GraphicsDevice} device - The graphics device. * @returns {UniformBuffer} - The uniform buffer. */ getUniformBuffer(device) { if (!this.uniformBuffer) { const shader = this.shader; Debug.assert(shader); const ubFormat = shader.meshUniformBufferFormat; Debug.assert(ubFormat); this.uniformBuffer = new UniformBuffer(device, ubFormat, false); } return this.uniformBuffer; } destroy() { this.bindGroup?.destroy(); this.bindGroup = null; this.uniformBuffer?.destroy(); this.uniformBuffer = null; } } const _MeshInstance = class _MeshInstance { /** * Create a new MeshInstance instance. * * @param {Mesh} mesh - The graphics mesh to instance. * @param {Material} material - The material to use for this mesh instance. * @param {GraphNode} [node] - The graph node defining the transform for this instance. This * parameter is optional when used with {@link RenderComponent} and will use the node the * component is attached to. * @example * // Create a mesh instance pointing to a 1x1x1 'cube' mesh * const mesh = pc.Mesh.fromGeometry(app.graphicsDevice, new pc.BoxGeometry()); * const material = new pc.StandardMaterial(); * * const meshInstance = new pc.MeshInstance(mesh, material); * * const entity = new pc.Entity(); * entity.addComponent('render', { * meshInstances: [meshInstance] * }); * * // Add the entity to the scene hierarchy * this.app.scene.root.addChild(entity); */ constructor(mesh, material, node = null) { /** * Enable shadow casting for this mesh instance. Use this property to enable/disable shadow * casting without overhead of removing from scene. Note that this property does not add the * mesh instance to appropriate list of shadow casters on a {@link Layer}, but allows mesh to * be skipped from shadow casting while it is in the list already. Defaults to false. */ __publicField(this, "castShadow", false); /** * Specifies a bitmask that controls which shadow cascades a mesh instance contributes * to when rendered with a {@link LIGHTTYPE_DIRECTIONAL} light source. * This setting is only effective if the {@link castShadow} property is enabled. * Defaults to {@link SHADOW_CASCADE_ALL}, which means the mesh casts shadows into all available cascades. * * @type {number} */ __publicField(this, "shadowCascadeMask", SHADOW_CASCADE_ALL); /** * Controls whether the mesh instance can be culled by frustum culling (see * {@link CameraComponent#frustumCulling}). Defaults to true. */ __publicField(this, "cull", true); /** * Determines the rendering order of mesh instances. Only used when mesh instances are added to * a {@link Layer} with {@link Layer#opaqueSortMode} or {@link Layer#transparentSortMode} * (depending on the material) set to {@link SORTMODE_MANUAL}. */ __publicField(this, "drawOrder", 0); /** @ignore */ __publicField(this, "_drawBucket", 127); /** * The graph node defining the transform for this instance. * * @type {GraphNode} */ __publicField(this, "node"); /** * Enable rendering for this mesh instance. Use visible property to enable/disable rendering * without overhead of removing from scene. But note that the mesh instance is still in the * hierarchy and still in the draw call list. */ __publicField(this, "visible", true); /** * Read this value in the {@link Scene.EVENT_POSTCULL} event to determine if the object is * actually going to be rendered. */ __publicField(this, "visibleThisFrame", false); /** * Negative scale batching support. * * @ignore */ __publicField(this, "flipFacesFactor", 1); /** * @type {GSplatInstance|null} * @ignore */ __publicField(this, "gsplatInstance", null); /** @ignore */ __publicField(this, "id", PickerId.get()); /** * Custom function used to customize culling (e.g. for 2D UI elements). * * @type {Function|null} * @ignore */ __publicField(this, "isVisibleFunc", null); /** * @type {InstancingData|null} * @ignore */ __publicField(this, "instancingData", null); /** * @type {DrawCommands|null} * @ignore */ __publicField(this, "indirectData", null); /** * Map of camera to their corresponding indirect draw data. Lazily allocated. * * @type {Map<Camera|null, DrawCommands>|null} * @ignore */ __publicField(this, "drawCommands", null); /** * Stores mesh metadata used for indirect rendering. Lazily allocated on first access * via getIndirectMetaData(). * * @type {Int32Array|null} * @ignore */ __publicField(this, "meshMetaData", null); /** * @type {Record<string, {scopeId: ScopeId|null, data: any, passFlags: number}>} * @ignore */ __publicField(this, "parameters", {}); /** * True if the mesh instance is pickable by the {@link Picker}. Defaults to true. * * @ignore */ __publicField(this, "pick", true); /** * The stencil parameters for front faces or null if no stencil is enabled. * * @type {StencilParameters|null} * @ignore */ __publicField(this, "stencilFront", null); /** * The stencil parameters for back faces or null if no stencil is enabled. * * @type {StencilParameters|null} * @ignore */ __publicField(this, "stencilBack", null); /** * True if the material of the mesh instance is transparent. Optimization to avoid accessing * the material. Updated by the material instance itself. * * @ignore */ __publicField(this, "transparent", false); /** @private */ __publicField(this, "_aabb", new BoundingBox()); /** @private */ __publicField(this, "_aabbVer", -1); /** @private */ __publicField(this, "_aabbMeshVer", -1); /** * @type {BoundingBox|null} * @private */ __publicField(this, "_customAabb", null); /** @private */ __publicField(this, "_updateAabb", true); /** @private */ __publicField(this, "_updateAabbFunc", null); /** * The internal sorting key used by the shadow renderer. * * @ignore */ __publicField(this, "_sortKeyShadow", 0); /** * The internal sorting key used by the forward renderer, in case SORTMODE_MATERIALMESH sorting * is used. * * @private */ __publicField(this, "_sortKeyForward", 0); /** * The internal sorting key used by the forward renderer, in case SORTMODE_BACK2FRONT or * SORTMODE_FRONT2BACK sorting is used. * * @ignore */ __publicField(this, "_sortKeyDynamic", 0); /** @private */ __publicField(this, "_layer", LAYER_WORLD); /** * @type {Material|null} * @private */ __publicField(this, "_material", null); /** * @type {SkinInstance|null} * @private */ __publicField(this, "_skinInstance", null); /** * @type {MorphInstance|null} * @private */ __publicField(this, "_morphInstance", null); /** @private */ __publicField(this, "_receiveShadow", true); /** @private */ __publicField(this, "_renderStyle", RENDERSTYLE_SOLID); /** @private */ __publicField(this, "_screenSpace", false); /** * The cache of shaders, indexed by a hash value. * * @type {Map<number, ShaderInstance>} * @private */ __publicField(this, "_shaderCache", /* @__PURE__ */ new Map()); /** * 2 byte toggles, 2 bytes light mask; Default value is no toggles and mask = pc.MASK_AFFECT_DYNAMIC * * @private */ __publicField(this, "_shaderDefs", MASK_AFFECT_DYNAMIC << 16); /** * @type {CalculateSortDistanceCallback|null} * @private */ __publicField(this, "_calculateSortDistance", null); Debug.assert(!(mesh instanceof GraphNode), "Incorrect parameters for MeshInstance's constructor. Use new MeshInstance(mesh, material, node)"); 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(); } /** * Sets the draw bucket for mesh instances. The draw bucket, an integer from 0 to 255 (default * 127), serves as the primary sort key for mesh rendering. Meshes are sorted by draw bucket, * then by sort mode. This setting is only effective when mesh instances are added to a * {@link Layer} with its {@link Layer#opaqueSortMode} or {@link Layer#transparentSortMode} * (depending on the material) set to {@link SORTMODE_BACK2FRONT}, {@link SORTMODE_FRONT2BACK}, * or {@link SORTMODE_MATERIALMESH}. * * Note: When {@link SORTMODE_BACK2FRONT} is used, a descending sort order is used; otherwise, * an ascending sort order is used. * * @type {number} */ set drawBucket(bucket) { this._drawBucket = Math.floor(bucket) & 255; this.updateKey(); } /** * Gets the draw bucket for mesh instance. * * @type {number} */ get drawBucket() { return this._drawBucket; } /** * Sets the render style of the mesh instance. Can be: * * - {@link RENDERSTYLE_SOLID} * - {@link RENDERSTYLE_WIREFRAME} * - {@link RENDERSTYLE_POINTS} * * Defaults to {@link RENDERSTYLE_SOLID}. * * @type {number} */ set renderStyle(renderStyle) { this._renderStyle = renderStyle; this.mesh.prepareRenderState(renderStyle); } /** * Gets the render style of the mesh instance. * * @type {number} */ get renderStyle() { return this._renderStyle; } /** * Sets the graphics mesh being instanced. * * @type {Mesh} */ set mesh(mesh) { if (mesh === this._mesh) { return; } if (this._mesh) { this._mesh.decRefCount(); } this._mesh = mesh; if (mesh) { mesh.incRefCount(); } } /** * Gets the graphics mesh being instanced. * * @type {Mesh} */ get mesh() { return this._mesh; } /** * Sets the world space axis-aligned bounding box for this mesh instance. * * @type {BoundingBox} */ set aabb(aabb) { this._aabb = aabb; } /** * Gets the world space axis-aligned bounding box for this mesh instance. * * @type {BoundingBox} */ 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; } /** * Clear the internal shader cache. * * @ignore */ clearShaders() { this._shaderCache.forEach((shaderInstance) => { shaderInstance.destroy(); }); this._shaderCache.clear(); } /** * Returns the shader instance for the specified shader pass and light hash that is compatible * with this mesh instance. * * @param {number} shaderPass - The shader pass index. * @param {number} lightHash - The hash value of the lights that are affecting this mesh instance. * @param {Scene} scene - The scene. * @param {CameraShaderParams} cameraShaderParams - The camera shader parameters. * @param {UniformBufferFormat} [viewUniformFormat] - The format of the view uniform buffer. * @param {BindGroupFormat} [viewBindGroupFormat] - The format of the view bind group. * @param {any} [sortedLights] - Array of arrays of lights. * @returns {ShaderInstance} - the shader instance. * @ignore */ 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) { DebugGraphics.pushGpuMarker(this.mesh.device, `Node: ${this.node.name}`); const shader = mat.getShaderVariant({ device: this.mesh.device, scene, objDefs: shaderDefs, cameraShaderParams, pass: shaderPass, sortedLights, viewUniformFormat, viewBindGroupFormat, vertexFormat: this.mesh.vertexBuffer?.format }); DebugGraphics.popGpuMarker(this.mesh.device); mat.variants.set(hash, shader); shaderInstance.shader = shader; } this._shaderCache.set(hash, shaderInstance); } Debug.call(() => { if (!array.equals(shaderInstance.hashes, lookupHashes)) { Debug.errorOnce("Hash collision in the shader cache for mesh instance. This is very unlikely but still possible. Please report this issue."); } }); return shaderInstance; } /** * Sets the material used by this mesh instance. * * @type {Material} */ 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(); } } /** * Gets the material used by this mesh instance. * * @type {Material} */ get material() { return this._material; } /** * @param {number} shaderDefs - The shader definitions to set. * @private */ _updateShaderDefs(shaderDefs) { if (shaderDefs !== this._shaderDefs) { this._shaderDefs = shaderDefs; this.clearShaders(); } } /** * Sets the callback to calculate sort distance. In some circumstances mesh instances are * sorted by a distance calculation to determine their rendering order. Set this callback to * override the default distance calculation, which gives the dot product of the camera forward * vector and the vector between the camera position and the center of the mesh instance's * axis-aligned bounding box. This option can be particularly useful for rendering transparent * meshes in a better order than the default. * * @type {CalculateSortDistanceCallback|null} */ set calculateSortDistance(calculateSortDistance) { this._calculateSortDistance = calculateSortDistance; } /** * Gets the callback to calculate sort distance. * * @type {CalculateSortDistanceCallback|null} */ get calculateSortDistance() { return this._calculateSortDistance; } set receiveShadow(val) { if (this._receiveShadow !== val) { this._receiveShadow = val; this._updateShaderDefs(val ? this._shaderDefs & ~SHADERDEF_NOSHADOW : this._shaderDefs | SHADERDEF_NOSHADOW); } } get receiveShadow() { return this._receiveShadow; } set batching(val) { this._updateShaderDefs(val ? this._shaderDefs | SHADERDEF_BATCH : this._shaderDefs & ~SHADERDEF_BATCH); } get batching() { return (this._shaderDefs & SHADERDEF_BATCH) !== 0; } /** * Sets the skin instance managing skinning of this mesh instance. Set to null if skinning is * not used. * * @type {SkinInstance|null} */ set skinInstance(val) { this._skinInstance = val; this._updateShaderDefs(val ? this._shaderDefs | SHADERDEF_SKIN : this._shaderDefs & ~SHADERDEF_SKIN); this._setupSkinUpdate(); } /** * Gets the skin instance managing skinning of this mesh instance. * * @type {SkinInstance|null} */ get skinInstance() { return this._skinInstance; } /** * Sets the morph instance managing morphing of this mesh instance. Set to null if morphing is * not used. * * @type {MorphInstance|null} */ set morphInstance(val) { this._morphInstance?.destroy(); this._morphInstance = val; let shaderDefs = this._shaderDefs; shaderDefs = val && val.morph.morphPositions ? shaderDefs | SHADERDEF_MORPH_POSITION : shaderDefs & ~SHADERDEF_MORPH_POSITION; shaderDefs = val && val.morph.morphNormals ? shaderDefs | SHADERDEF_MORPH_NORMAL : shaderDefs & ~SHADERDEF_MORPH_NORMAL; shaderDefs = val && val.morph.intRenderFormat ? shaderDefs | SHADERDEF_MORPH_TEXTURE_BASED_INT : shaderDefs & ~SHADERDEF_MORPH_TEXTURE_BASED_INT; this._updateShaderDefs(shaderDefs); } /** * Gets the morph instance managing morphing of this mesh instance. * * @type {MorphInstance|null} */ get morphInstance() { return this._morphInstance; } set screenSpace(val) { if (this._screenSpace !== val) { this._screenSpace = val; this._updateShaderDefs(val ? this._shaderDefs | SHADERDEF_SCREENSPACE : this._shaderDefs & ~SHADERDEF_SCREENSPACE); } } get screenSpace() { return this._screenSpace; } set key(val) { this._sortKeyForward = val; } get key() { return this._sortKeyForward; } /** * Sets the mask controlling which {@link LightComponent}s light this mesh instance, which * {@link CameraComponent} sees it and in which {@link Layer} it is rendered. Defaults to 1. * * @type {number} */ set mask(val) { const toggles = this._shaderDefs & 65535; this._updateShaderDefs(toggles | val << 16); } /** * Gets the mask controlling which {@link LightComponent}s light this mesh instance, which * {@link CameraComponent} sees it and in which {@link Layer} it is rendered. * * @type {number} */ get mask() { return this._shaderDefs >> 16; } /** * Sets the number of instances when using hardware instancing to render the mesh. * * @type {number} */ set instancingCount(value) { if (this.instancingData) { this.instancingData.count = value; } } /** * Gets the number of instances when using hardware instancing to render the mesh. * * @type {number} */ 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(); this.destroyDrawCommands(); } destroyDrawCommands() { if (this.drawCommands) { for (const cmd of this.drawCommands.values()) { cmd?.destroy(); } this.drawCommands = null; } } /** * Sets the render style for an array of mesh instances. * * @param {MeshInstance[]} meshInstances - The mesh instances to set the render style for. * @param {number} renderStyle - The render style to set. * @ignore */ 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(); } } /** * Test if meshInstance is visible by camera. It requires the frustum of the camera to be up to * date, which forward-renderer takes care of. This function should not be called elsewhere. * * @param {Camera} camera - The camera to test visibility against. * @returns {boolean} - True if the mesh instance is visible by the camera, false otherwise. * @ignore */ _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 ? 4194304 : 0) | material.id & 4194303; } /** * Sets up {@link MeshInstance} to be rendered using Hardware Instancing. * Note that {@link instancingCount} is automatically set to the number of vertices of the * vertex buffer when it is provided. * * @param {VertexBuffer|true|null} vertexBuffer - Vertex buffer to hold per-instance vertex data * (usually world matrices). Pass `true` to enable attributeless instancing where the instance * index is derived from `gl_InstanceID` / `instance_index` builtins rather than a vertex * buffer attribute — the caller must set {@link instancingCount} manually. Pass null to turn * off hardware instancing. * @param {boolean} cull - Whether to perform frustum culling on this instance. If true, the whole * instance will be culled by the camera frustum. This often involves setting * {@link RenderComponent#customAabb} containing all instances. Defaults to false, which means * the whole instance is always rendered. */ setInstancing(vertexBuffer, cull = false) { if (vertexBuffer) { if (vertexBuffer === true) { this.instancingData = new InstancingData(0); } else { 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 instanceof VertexBuffer ? this._shaderDefs | SHADERDEF_INSTANCING : this._shaderDefs & ~SHADERDEF_INSTANCING); } /** * Sets the {@link MeshInstance} to be rendered using indirect rendering, where the GPU, * typically using a Compute shader, stores draw call parameters in a buffer. * Note that this is only supported on WebGPU, and ignored on other platforms. * * @param {CameraComponent|null} camera - Camera component to set indirect data for, or * null if the indirect slot should be used for all cameras. * @param {number} slot - Slot in the buffer to set the draw call parameters. Allocate a slot * in the buffer by calling {@link GraphicsDevice#getIndirectDrawSlot}. Pass -1 to disable * indirect rendering for the specified camera (or the shared entry when camera is null). * @param {number} [count] - Optional number of consecutive slots to use. Defaults to 1. */ setIndirect(camera, slot, count = 1) { const key = camera?.camera ?? null; if (slot === -1) { this._deleteDrawCommandsKey(key); } else { this.drawCommands ?? (this.drawCommands = /* @__PURE__ */ new Map()); const cmd = this.drawCommands.get(key) ?? new DrawCommands(this.mesh.device); cmd.slotIndex = slot; cmd.update(count); this.drawCommands.set(key, cmd); const device = this.mesh.device; device.mapsToClear.add(this.drawCommands); } } /** * Sets the {@link MeshInstance} to be rendered using multi-draw, where multiple sub-draws are * executed with a single draw call. * * Note: Each call to this method invalidates any previously stored draw command data for the * specified camera. * * @param {CameraComponent|null} camera - Camera component to bind commands to, or null to share * across all cameras. * @param {number} [maxCount] - Maximum number of sub-draws to allocate. Defaults to 1. Pass 0 * to disable multi-draw for the specified camera (or the shared entry when camera is null). * @returns {DrawCommands|undefined} The commands container to populate with sub-draw commands. */ setMultiDraw(camera, maxCount = 1) { const key = camera?.camera ?? null; let cmd; if (maxCount === 0) { this._deleteDrawCommandsKey(key); } else { this.drawCommands ?? (this.drawCommands = /* @__PURE__ */ new Map()); cmd = this.drawCommands.get(key); if (!cmd) { const indexBuffer = this.mesh.indexBuffer?.[0]; const indexFormat = indexBuffer?.format; const indexSizeBytes = indexFormat !== void 0 ? indexFormatByteSize[indexFormat] : 0; cmd = new DrawCommands(this.mesh.device, indexSizeBytes); this.drawCommands.set(key, cmd); } cmd.allocate(maxCount); } return cmd; } _deleteDrawCommandsKey(key) { const cmds = this.drawCommands; if (cmds) { const cmd = cmds.get(key); cmd?.destroy(); cmds.delete(key); if (cmds.size === 0) { this.destroyDrawCommands(); } } } /** * Retrieves the draw commands for a specific camera, or the default commands when none are * bound to that camera. * * @param {Camera} camera - The camera to retrieve commands for. * @returns {DrawCommands|undefined} - The draw commands, or undefined. * @ignore */ getDrawCommands(camera) { const cmds = this.drawCommands; if (!cmds) return void 0; return cmds.get(camera) ?? cmds.get(null); } /** * Retrieves the mesh metadata needed for indirect rendering. * * @returns {Int32Array} - A typed array with 4 elements representing the mesh metadata, which * is typically needed when generating indirect draw call parameters using Compute shader. These * can be provided to the Compute shader using vec4i uniform. The values are based on * {@link Mesh#primitive}, stored in this order: [count, base, baseVertex, 0]. The last value is * always zero and is reserved for future use. */ getIndirectMetaData() { const prim = this.mesh?.primitive[this.renderStyle]; const data = this.meshMetaData ?? (this.meshMetaData = new Int32Array(4)); data[0] = prim.count; data[1] = prim.base; data[2] = prim.baseVertex; return data; } ensureMaterial(device) { if (!this.material) { Debug.warn(`Mesh attached to entity '${this.node.name}' does not have a material, using a default one.`); this.material = getDefaultMaterial(device); } } // Parameter management clearParameters() { this.parameters = {}; } getParameters() { return this.parameters; } /** * Retrieves the specified shader parameter from a mesh instance. * * @param {string} name - The name of the parameter to query. * @returns {object|undefined} The named parameter, or `undefined` if no parameter with that * name is set on this mesh instance. */ getParameter(name) { return this.parameters[name]; } /** * Sets a shader parameter on a mesh instance. Note that this parameter will take precedence * over parameter of the same name if set on Material this mesh instance uses for rendering. * * @param {string} name - The name of the parameter to set. * @param {number|number[]|Texture|Float32Array} data - The value for the specified parameter. * @param {number} [passFlags] - Mask describing which passes the material should be included * in. Defaults to 0xFFFFFFFF (all passes). */ setParameter(name, data, passFlags = 4294967295) { const param = this.parameters[name]; if (param) { param.data = data; param.passFlags = passFlags; } else { this.parameters[name] = { scopeId: null, data, passFlags }; } } /** * A wrapper over settings parameter specifically for realtime baked lightmaps. This handles * reference counting of lightmaps and releases them when no longer referenced. * * @param {string} name - The name of the parameter to set. * @param {Texture|null} texture - The lightmap texture to set. * @ignore */ 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); } } /** * Deletes a shader parameter on a mesh instance. * * @param {string} name - The name of the parameter to delete. */ deleteParameter(name) { if (this.parameters[name]) { delete this.parameters[name]; } } /** * Used to apply parameters from this mesh instance into scope of uniforms, called internally * by forward-renderer. * * @param {GraphicsDevice} device - The graphics device. * @param {number} passFlag - The pass flag for the current render pass. * @ignore */ 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); } } } /** * @param {boolean} value - True to enable lightmapped rendering, false to disable. * @ignore */ setLightmapped(value) { if (value) { this.mask = (this.mask | MASK_AFFECT_LIGHTMAPPED) & ~(MASK_AFFECT_DYNAMIC | MASK_BAKE); } else { this.setRealtimeLightmap(_MeshInstance.lightmapParamNames[0], null); this.setRealtimeLightmap(_MeshInstance.lightmapParamNames[1], null); this._shaderDefs &= ~(SHADERDEF_LM | SHADERDEF_DIRLM | SHADERDEF_LMAMBIENT); this.mask = (this.mask | MASK_AFFECT_DYNAMIC) & ~(MASK_AFFECT_LIGHTMAPPED | MASK_BAKE); } } /** * @param {BoundingBox|null} aabb - The custom axis-aligned bounding box or null to reset to * the mesh's bounding box. * @ignore */ 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(); } /** @private */ _setupSkinUpdate() { if (this._skinInstance) { this._skinInstance._updateBeforeCull = !this._customAabb; } } }; // shader uniform names for lightmaps __publicField(_MeshInstance, "lightmapParamNames", ["texture_lightMap", "texture_dirLightMap"]); let MeshInstance = _MeshInstance; export { MeshInstance };