@babylonjs/lite 1.11.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/dist/index.js +444 -368
- package/dist/index.js.map +1 -1
- package/index.d.ts +208 -6
- package/lib/asset-container.js.map +1 -1
- package/lib/audio/index.js +1 -0
- package/lib/audio/index.js.map +1 -1
- package/lib/audio/media-stream-output.js +44 -0
- package/lib/audio/media-stream-output.js.map +1 -0
- package/lib/engine/engine.js +1 -1
- package/lib/frame-graph/render-task.js +1 -1
- package/lib/frame-graph/render-task.js.map +1 -1
- package/lib/frame-graph/shadow-inputs.js +10 -1
- package/lib/frame-graph/shadow-inputs.js.map +1 -1
- package/lib/frame-graph/transmission.js +1 -16
- package/lib/frame-graph/transmission.js.map +1 -1
- package/lib/index.js +6 -3
- package/lib/index.js.map +1 -1
- package/lib/loader-gltf/gltf-json-asset.js +1 -1
- package/lib/loader-gltf/gltf-json-asset.js.map +1 -1
- package/lib/loader-gltf/gltf-multi-buffer.js +2 -10
- package/lib/loader-gltf/gltf-multi-buffer.js.map +1 -1
- package/lib/loader-gltf/gltf-pbr-builder-ext.js +4 -18
- package/lib/loader-gltf/gltf-pbr-builder-ext.js.map +1 -1
- package/lib/loader-gltf/gltf-pbr-builder.js +17 -14
- package/lib/loader-gltf/gltf-pbr-builder.js.map +1 -1
- package/lib/loader-gltf/gltf-sampler-desc.js +3 -16
- package/lib/loader-gltf/gltf-sampler-desc.js.map +1 -1
- package/lib/material/node/blocks/ambient-occlusion-block.js +1 -4
- package/lib/material/node/blocks/ambient-occlusion-block.js.map +1 -1
- package/lib/material/node/blocks/biplanar-block.js +3 -6
- package/lib/material/node/blocks/biplanar-block.js.map +1 -1
- package/lib/material/node/blocks/image-source.js +2 -5
- package/lib/material/node/blocks/image-source.js.map +1 -1
- package/lib/material/node/blocks/input-block.js +5 -26
- package/lib/material/node/blocks/input-block.js.map +1 -1
- package/lib/material/node/blocks/pbr-mr-helper-full.js +5 -8
- package/lib/material/node/blocks/pbr-mr-helper-full.js.map +1 -1
- package/lib/material/node/blocks/reflection-texture-block.js +1 -4
- package/lib/material/node/blocks/reflection-texture-block.js.map +1 -1
- package/lib/material/node/blocks/texture-block.js +2 -5
- package/lib/material/node/blocks/texture-block.js.map +1 -1
- package/lib/material/node/blocks/triplanar-block.js +3 -6
- package/lib/material/node/blocks/triplanar-block.js.map +1 -1
- package/lib/material/node/node-emitter.js +24 -1
- package/lib/material/node/node-emitter.js.map +1 -1
- package/lib/material/node/node-geometry-renderable.js +2 -2
- package/lib/material/node/node-material.js +1 -22
- package/lib/material/node/node-material.js.map +1 -1
- package/lib/material/node/node-types.js.map +1 -1
- package/lib/material/pbr/fragments/shadow-only-fragment.js +68 -0
- package/lib/material/pbr/fragments/shadow-only-fragment.js.map +1 -0
- package/lib/material/pbr/pbr-flag-bits.js.map +1 -1
- package/lib/material/pbr/pbr-material.js.map +1 -1
- package/lib/material/pbr/pbr-primitive-resolver.js +2 -2
- package/lib/material/pbr/pbr-primitive-resolver.js.map +1 -1
- package/lib/material/pbr/pbr-renderable.js +3 -0
- package/lib/material/pbr/pbr-renderable.js.map +1 -1
- package/lib/material/pbr/pbr-template.js +1 -0
- package/lib/material/pbr/pbr-template.js.map +1 -1
- package/lib/mesh/GaussianSplatting/gaussian-splatting-mesh.js +2 -8
- package/lib/mesh/GaussianSplatting/gaussian-splatting-mesh.js.map +1 -1
- package/lib/mesh/get-mesh-geometry.js +24 -0
- package/lib/mesh/get-mesh-geometry.js.map +1 -0
- package/lib/mesh/mesh-dispose.js +2 -0
- package/lib/mesh/mesh-dispose.js.map +1 -1
- package/lib/mesh/mesh.js +2 -8
- package/lib/mesh/mesh.js.map +1 -1
- package/lib/mesh/thin-instance-cull-binding.js +54 -3
- package/lib/mesh/thin-instance-cull-binding.js.map +1 -1
- package/lib/mesh/thin-instance-gpu-culling.js +241 -24
- package/lib/mesh/thin-instance-gpu-culling.js.map +1 -1
- package/lib/mesh/thin-instance.js +112 -1
- package/lib/mesh/thin-instance.js.map +1 -1
- package/lib/picking/gpu-picker.js +4 -2
- package/lib/picking/gpu-picker.js.map +1 -1
- package/lib/picking/picking-pipeline.js +1 -1
- package/lib/picking/picking-pipeline.js.map +1 -1
- package/lib/picking/picking-shader.js +38 -16
- package/lib/picking/picking-shader.js.map +1 -1
- package/lib/scene/scene-core.js +7 -2
- package/lib/scene/scene-core.js.map +1 -1
- package/lib/scene/scene-node.js +2 -6
- package/lib/scene/scene-node.js.map +1 -1
- package/lib/scene/scene-remove.js +70 -1
- package/lib/scene/scene-remove.js.map +1 -1
- package/lib/scene/transform-node.js +3 -0
- package/lib/scene/transform-node.js.map +1 -1
- package/lib/scene/world-matrix-state.js +7 -1
- package/lib/scene/world-matrix-state.js.map +1 -1
- package/lib/shadow/csm-directional-shadow-generator.js +22 -1
- package/lib/shadow/csm-directional-shadow-generator.js.map +1 -1
- package/lib/shadow/csm-shadow-task-hooks.js +19 -11
- package/lib/shadow/csm-shadow-task-hooks.js.map +1 -1
- package/lib/shadow/esm-directional-shadow-generator.js +2 -51
- package/lib/shadow/esm-directional-shadow-generator.js.map +1 -1
- package/lib/shadow/pcf-directional-shadow-generator.js +2 -48
- package/lib/shadow/pcf-directional-shadow-generator.js.map +1 -1
- package/lib/shadow/shadow-base.js +48 -1
- package/lib/shadow/shadow-base.js.map +1 -1
- package/lib/text/text-renderable.js +2 -10
- package/lib/text/text-renderable.js.map +1 -1
- package/lib/texture/texture-array.js +96 -0
- package/lib/texture/texture-array.js.map +1 -0
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pbr-template.js","sources":["../../../../src/material/pbr/pbr-template.ts"],"sourcesContent":["/**\n * PBR Base Template\n *\n * Provides the base PBR shader structure with slot markers where\n * fragments inject their code. This is the PBR equivalent of\n * what composePbrVertex/composePbrFragment produce today.\n *\n * The template is parameterized by light type and feature flags\n * because these fundamentally change the shader structure.\n */\n\nimport type { ShaderTemplate, UboField, VertexAttribute, Varying, BindingDecl } from \"../../shader/fragment-types.js\";\nimport type { PbrTemplateExt } from \"./pbr-template-ext.js\";\nimport type { MeshVbLayout } from \"../../mesh/mesh.js\";\nimport { appendMeshLightUboFields, meshLightIndexWGSL } from \"../../render/lights-ubo.js\";\n\ntype PbrGammaTemplate = typeof import(\"./pbr-template-gamma.js\");\n\nconst STAGE_FRAGMENT = 0x2;\n\n// ── BRDF functions (always present in PBR) ──────────────────────\n\nconst BRDF_FUNCTIONS = `\nconst PI:f32=3.14159265358979323846;\nfn distributionGGX(NdotH:f32,alphaG:f32)->f32{\nlet a2=alphaG*alphaG;\nlet d=NdotH*NdotH*(a2-1.0)+1.0;\nreturn a2/(PI*d*d);\n}\nfn geometrySmithGGX(NdotL:f32,NdotV:f32,alphaG:f32)->f32{\nlet a2=alphaG*alphaG;\nlet gl=NdotL*sqrt(NdotV*(NdotV-a2*NdotV)+a2);\nlet gv=NdotV*sqrt(NdotL*(NdotL-a2*NdotL)+a2);\nreturn 0.5/(gl+gv);\n}\nfn fresnelSchlick(cosTheta:f32,F0:vec3<f32>,F90:vec3<f32>)->vec3<f32>{\nlet t=1.0-cosTheta;\nlet t2=t*t;\nreturn F0+(F90-F0)*(t2*t2*t);\n}\n`;\n\nexport interface PbrTemplateConfig {\n /** When true, generates a non-looping single-light direct block + lights UBO binding. */\n /** @internal */\n readonly _hasSingleLight?: boolean;\n /** When true, generates a multi-light loop + lights UBO binding.\n * Used for multiple lights or shadow receivers. */\n /** @internal */\n readonly _hasMultiLight?: boolean;\n /** Pre-built WGSL for the single-light UBO structs. */\n /** @internal */\n readonly _singleLightWGSL?: string;\n /** Pre-built WGSL for the single-light direct lighting block. */\n /** @internal */\n readonly _singleLightBlock?: string;\n /** Pre-built WGSL for multi-light (structs + computePbrLight). Passed from\n * dynamically imported fragments/multilight-wgsl.ts to keep it out of non-shadow bundles. */\n /** @internal */\n readonly _multiLightWGSL?: string;\n /** Pre-built WGSL for the multi-light direct lighting loop body. */\n /** @internal */\n readonly _multiLightLoop?: string;\n /** Normal map mode (default: \"none\") */\n /** @internal */\n readonly _normalMode?: \"tangent\" | \"cotangent\" | \"none\";\n /** @internal Mesh has no NORMAL attribute → flat-shade via screen-space derivatives (glTF spec). */\n readonly _flatGeometricNormal?: boolean;\n /** @internal Flat-normal WGSL (from flat-normal-wgsl.ts), lazily supplied only when a no-NORMAL mesh exists. */\n readonly _flatNormalWgsl?: string;\n /** Has emissive texture */\n /** @internal */\n readonly _hasEmissiveTexture?: boolean;\n /** Has specular-glossiness workflow */\n /** @internal */\n readonly _hasSpecGloss?: boolean;\n /** Has double-sided rendering */\n /** @internal */\n readonly _hasDoubleSided?: boolean;\n /** Has tonemap */\n /** @internal */\n readonly _hasTonemap?: boolean;\n /** Scene has fog (scene.fog != null). `calcFogFactor` helper WGSL, supplied by the dynamic\n * pbr-fog-wgsl import only for fog scenes; \"\" otherwise (zero bytes for non-fog scenes). */\n /** @internal */\n readonly _fogHelper?: string;\n /** Fog blend WGSL, emitted just before the tonemap block; \"\" for non-fog scenes. */\n /** @internal */\n readonly _fogBlock?: string;\n /** Tone mapping WGSL: module-scope helper functions/consts. Empty string = inline default (no helpers). */\n /** @internal */\n readonly _toneMappingHelpers?: string;\n /** Tone mapping WGSL: per-fragment call block. Empty string = default standard exponential. */\n /** @internal */\n readonly _toneMappingCall?: string;\n /** Has alpha blending */\n /** @internal */\n readonly _hasAlphaBlend?: boolean;\n /** Has specular AA */\n /** @internal */\n readonly _hasSpecularAA?: boolean;\n /** Has gamma albedo (sRGB base color decode) */\n /** @internal */\n readonly _hasGammaAlbedo?: boolean;\n /** Has a non-default base-color factor multiplied over the base-color texture. */\n /** @internal */\n readonly _hasBaseColorFactor?: boolean;\n /** Has morph targets (changes position/normal variable names in vertex shader) */\n /** @internal */\n readonly _hasMorph?: boolean;\n /** Has occlusion in ORM texture (simple path, no reflectance ext) */\n /** @internal */\n readonly _hasOcclusion?: boolean;\n /** Has emissive color UBO field (fragment handles emissive computation) */\n /** @internal */\n readonly _hasEmissiveColor?: boolean;\n /** When true, the reflectance fragment handles F0 + occlusion computation */\n /** @internal */\n readonly _hasReflectanceExt?: boolean;\n /** When true, include IBL SH coefficients in scene UBO */\n /** @internal */\n readonly _hasIbl?: boolean;\n /** Has anisotropy layer */\n /** @internal */\n readonly _hasAnisotropy?: boolean;\n /** Anisotropy WGSL: BRDF helper functions (dynamically imported). */\n /** @internal */\n readonly _anisoBrdfFunctions?: string;\n /** Anisotropy WGSL: T/B computation block (dynamically imported). */\n /** @internal */\n readonly _anisoTBBlock?: string;\n /** Optional extension config for advanced features (UV transforms, UV2, vertex colors).\n * When undefined, base template defaults to master-like behavior (no feature strings). */\n /** @internal */\n readonly _ext?: PbrTemplateExt;\n /** @internal */\n readonly _gammaTemplate?: PbrGammaTemplate | null;\n /** Generate a fragment stage that runs discard/alpha-test logic and writes no color. */\n /** @internal */\n readonly _noColorOutput?: boolean;\n /** Generate a fragment stage that runs discard/alpha-test logic and writes ESM shadow color. */\n /** @internal */\n readonly _esmShadowOutput?: boolean;\n /** ESM shadow depth output code. Supplied by the ESM material view so normal PBR bundles don't retain it. */\n /** @internal */\n readonly _esmShadowDepthCode?: string;\n /** @internal Per-attribute vertex-buffer interleave layout. Undefined (or per-attribute\n * undefined) → canonical tight strides (12/12/16/8). Only set for meshes\n * sourcing attributes from a strided bufferView. */\n readonly _vbStrides?: MeshVbLayout;\n}\n\n/**\n * Create a PBR ShaderTemplate from the given configuration.\n * The template contains slot markers that the composer fills with fragment code.\n */\nexport function createPbrTemplate(config: PbrTemplateConfig): ShaderTemplate {\n const {\n _hasSingleLight = false,\n _hasMultiLight = false,\n _singleLightWGSL = \"\",\n _singleLightBlock = \"\",\n _multiLightWGSL = \"\",\n _multiLightLoop = \"\",\n _normalMode = \"none\",\n _flatGeometricNormal = false,\n _flatNormalWgsl = \"\",\n _hasEmissiveTexture = false,\n _hasSpecGloss = false,\n _hasDoubleSided = false,\n _hasTonemap = false,\n _fogHelper = \"\",\n _fogBlock = \"\",\n _toneMappingHelpers = \"\",\n _toneMappingCall = \"\",\n _hasAlphaBlend = false,\n _hasSpecularAA = false,\n _hasGammaAlbedo = false,\n _hasBaseColorFactor = false,\n _hasMorph = false,\n _hasOcclusion = false,\n _hasEmissiveColor = false,\n _hasReflectanceExt = false,\n _hasIbl = false,\n _hasAnisotropy = false,\n _anisoBrdfFunctions = \"\",\n _anisoTBBlock = \"\",\n _ext,\n _gammaTemplate,\n _noColorOutput = false,\n _esmShadowOutput = false,\n _esmShadowDepthCode = \"\",\n _vbStrides,\n } = config;\n const hasNormal = _normalMode === \"tangent\";\n const hasCotangentNormal = _normalMode === \"cotangent\";\n const hasAnyNormal = hasNormal || hasCotangentNormal;\n\n // ── Base vertex attributes ──────────────────────────────────\n // arrayStride defaults to the canonical tight element size; interleaved meshes\n // override it (e.g. 48 for POSITION+NORMAL+UV+TANGENT sharing one stride-48\n // bufferView). `_offset` is the attribute's byte offset WITHIN that shared\n // buffer (0 for tight meshes); it is baked into the pipeline vertex layout so\n // the draw can bind the shared buffer at offset 0 (matches Babylon.js WebGPU —\n // a non-zero setVertexBuffer bind offset corrupts vertex fetch on some AMD/Dawn paths).\n const _baseVertexAttributes: VertexAttribute[] = [\n { _name: \"position\", _type: \"vec3<f32>\", _gpuFormat: \"float32x3\", _arrayStride: _vbStrides?._p?._stride ?? 12, _offset: _vbStrides?._p?._offset ?? 0 },\n { _name: \"normal\", _type: \"vec3<f32>\", _gpuFormat: \"float32x3\", _arrayStride: _vbStrides?._n?._stride ?? 12, _offset: _vbStrides?._n?._offset ?? 0 },\n ];\n if (hasNormal) {\n _baseVertexAttributes.push({\n _name: \"tangent\",\n _type: \"vec4<f32>\",\n _gpuFormat: \"float32x4\",\n _arrayStride: _vbStrides?._t?._stride ?? 16,\n _offset: _vbStrides?._t?._offset ?? 0,\n });\n }\n _baseVertexAttributes.push({ _name: \"uv\", _type: \"vec2<f32>\", _gpuFormat: \"float32x2\", _arrayStride: _vbStrides?._u?._stride ?? 8, _offset: _vbStrides?._u?._offset ?? 0 });\n if (_ext) {\n _baseVertexAttributes.push(..._ext.extraVertexAttributes);\n }\n\n // ── Base varyings ───────────────────────────────────────────\n const _baseVaryings: Varying[] = [\n { _name: \"worldPos\", _type: \"vec3<f32>\" },\n { _name: \"worldNormal\", _type: \"vec3<f32>\" },\n ];\n if (hasNormal) {\n _baseVaryings.push({ _name: \"worldTangent\", _type: \"vec3<f32>\" }, { _name: \"worldBitangent\", _type: \"vec3<f32>\" });\n }\n _baseVaryings.push({ _name: \"uv\", _type: \"vec2<f32>\" });\n if (_ext) {\n _baseVaryings.push(..._ext.extraVaryings);\n }\n\n // ── Base mesh UBO fields (world matrix + affected light indices — UV transforms are\n // per-texture now, emitted on the material UBO when hasUvTransform). ─\n const _baseMeshUboFields: UboField[] = [{ _name: \"world\", _type: \"mat4x4<f32>\" }];\n appendMeshLightUboFields(_baseMeshUboFields);\n\n // ── Base material UBO fields ────────────────────────────────────\n const _baseMaterialUboFields: UboField[] = [\n { _name: \"environmentIntensity\", _type: \"f32\" },\n { _name: \"directIntensity\", _type: \"f32\" },\n { _name: \"reflectance\", _type: \"f32\" },\n { _name: \"materialAlpha\", _type: \"f32\" },\n ...(_hasBaseColorFactor ? [{ _name: \"baseColorFactor\", _type: \"vec4<f32>\" as const }] : []),\n // glTF metallicFactor / roughnessFactor (default 1.0) — applied over MR texture channels.\n { _name: \"metallicFactor\", _type: \"f32\" },\n { _name: \"roughnessFactor\", _type: \"f32\" },\n { _name: \"normalScale\", _type: \"f32\" },\n { _name: \"lightFalloffMode\", _type: \"f32\" },\n // Anisotropy UBO field stays on the base template because anisotropy is\n // template-only (no ShaderFragment) — the anisotropyExt just writes its\n // slice through the unified ext.writeUbo hook.\n ...(_hasAnisotropy ? [{ _name: \"anisotropyParams\", _type: \"vec4<f32>\" as const }] : []),\n // ── Extension fields (per-texture UV transforms, etc.) ─\n ...(_ext ? _ext.extraMaterialUboFields : []),\n ];\n\n // ── Helper: texture + sampler binding pair ────────────────────\n const tex2d = (name: string, sampler: string): BindingDecl[] => [\n { _name: name, _type: { _kind: \"texture\", _textureType: \"texture_2d<f32>\" }, _visibility: STAGE_FRAGMENT },\n { _name: sampler, _type: { _kind: \"sampler\", _samplerType: \"sampler\" }, _visibility: STAGE_FRAGMENT },\n ];\n\n // ── Base bindings (always-present textures) ─────────────────\n const _baseBindings: BindingDecl[] = tex2d(\"baseColorTexture\", \"baseColorSampler\");\n if (hasAnyNormal) {\n _baseBindings.push(...tex2d(\"normalTexture\", \"normalSampler_\"));\n }\n _baseBindings.push(...tex2d(\"ormTexture\", \"ormSampler\"));\n if (_ext) {\n _baseBindings.push(..._ext.extraBindings);\n }\n if (_hasEmissiveTexture) {\n _baseBindings.push(...tex2d(\"emissiveTexture\", \"emissiveSampler\"));\n }\n if (_hasSpecGloss) {\n _baseBindings.push(...tex2d(\"specGlossTexture\", \"specGlossSampler\"));\n }\n if (_esmShadowOutput) {\n _baseBindings.push({ _name: \"shadowParams\", _type: { _kind: \"uniform-buffer\" }, _visibility: STAGE_FRAGMENT });\n }\n // ── Vertex template ─────────────────────────────────────────\n // When morph targets are active, the morph fragment's VR\n // defines morphedPos/morphedNorm. The base template uses those instead\n // of the raw vertex attributes.\n const posVar = _hasMorph ? \"morphedPos\" : \"position\";\n const normVar = _hasMorph ? \"morphedNorm\" : \"normal\";\n\n // The vertex template uses morphedPos/morphedNorm when morph fragment is present,\n // falling back to position/normal. The morph fragment's VR defines these vars.\n const tangentBlock = hasNormal\n ? `let N_local=normalize(${normVar});\nlet T_local=normalize(tangent.xyz);\nlet B_local=cross(N_local,T_local)*tangent.w;\nout.worldTangent=(finalWorld*vec4<f32>(T_local,0.0)).xyz;\nout.worldBitangent=(finalWorld*vec4<f32>(B_local,0.0)).xyz;`\n : \"\";\n\n const _vertexTemplate = `/*SU*/\n/*MU*/\n@group(1) @binding(0) var<uniform> mesh: MeshUniforms;\n/*VH*/\n/*VD*/\n/*VO*/\n@vertex fn main(\n/*VP*/\n) -> VertexOutput {\nvar out: VertexOutput;\n/*VR*/\nvar finalWorld = mesh.world;\n/*VW*/\nlet worldPos4 = finalWorld * vec4<f32>(${posVar}, 1.0);\nout.worldPos = worldPos4.xyz;\nout.clipPos = scene.viewProjection * worldPos4;\nout.worldNormal = (finalWorld * vec4<f32>(normalize(${normVar}), 0.0)).xyz;\n${tangentBlock}\nout.uv = uv;\n ${_ext ? _ext.vertexBodyExtra : \"\"}/*VB*/\nreturn out;\n}`;\n\n // ── Fragment template ────────────────────────────────────────\n\n // Normal handling block\n const normalUV = _ext?.uvForNormal ?? \"input.uv\";\n const normalScaleMod = _ext?.normalScaleMod ?? \"\";\n const normalRef = _ext?.normalScaleMod ? \"scaledNormal\" : \"normalMapRaw\";\n const normalRefCt = _ext?.normalScaleMod ? \"scaledNormalCT\" : \"normalMapSample\";\n let normalBlock: string;\n if (hasNormal) {\n normalBlock = `let normalMapRaw=textureSample(normalTexture,normalSampler_,${normalUV}).rgb*2.0-1.0;\n${normalScaleMod}let normalMapNorm=normalize(${normalRef});\nvar N_geom=normalize(input.worldNormal);\nlet TBN=mat3x3<f32>(input.worldTangent,input.worldBitangent,input.worldNormal);\nvar N=normalize(TBN*normalMapNorm);`;\n } else if (hasCotangentNormal) {\n normalBlock = `let normalMapSample=textureSample(normalTexture,normalSampler_,${normalUV}).rgb*2.0-1.0;\n${normalScaleMod.replace(/normalMapRaw/g, \"normalMapSample\").replace(/scaledNormal/g, \"scaledNormalCT\")}var N_geom=normalize(input.worldNormal);\nlet dp1=dpdx(input.worldPos);\nlet dp2=dpdy(input.worldPos);\nlet duv1=dpdx(${normalUV});\nlet duv2=dpdy(${normalUV});\nlet dp2perp=cross(dp2,N_geom);\nlet dp1perp=cross(N_geom,dp1);\nlet tangent_ct=dp2perp*duv1.x+dp1perp*duv2.x;\nlet bitangent_ct=-(dp2perp*duv1.y+dp1perp*duv2.y);\nlet det=max(dot(tangent_ct,tangent_ct),dot(bitangent_ct,bitangent_ct));\nlet invmax=select(inverseSqrt(det),0.0,det==0.0);\nlet cotangentFrame=mat3x3<f32>(tangent_ct*invmax,bitangent_ct*invmax,N_geom);\nvar N=normalize(cotangentFrame*normalize(${normalRefCt}));`;\n } else if (_flatGeometricNormal && _flatNormalWgsl) {\n // glTF spec: a primitive without a NORMAL attribute MUST be flat-shaded.\n // WGSL (face normal from screen-space derivatives, oriented to the viewer)\n // is supplied lazily by pbr-renderable from flat-normal-wgsl.ts — kept out\n // of this shared template so normal-having scenes pay zero bundle cost.\n normalBlock = `${_flatNormalWgsl}`;\n } else {\n normalBlock = `var N_geom=normalize(input.worldNormal);\nvar N=N_geom;`;\n }\n\n // Anisotropy: tangent/bitangent frame (passed in from dynamic import, empty if not used)\n const anisotropyTBBlock = _hasAnisotropy ? _anisoTBBlock : \"\";\n\n // Base color decoding\n const vertexColorMod = _ext?.baseColorMod ?? \"\";\n const baseColorFactorRgb = _hasBaseColorFactor ? \"*material.baseColorFactor.rgb\" : \"\";\n const baseColorFactorAlpha = _hasBaseColorFactor ? \"*material.baseColorFactor.a\" : \"\";\n const baseColorDecode = _hasGammaAlbedo\n ? _gammaTemplate!.gammaBaseColor(baseColorFactorRgb, baseColorFactorAlpha, vertexColorMod)\n : `var baseColor=baseColorSample.rgb${baseColorFactorRgb};\nvar alpha=baseColorSample.a${baseColorFactorAlpha};${vertexColorMod}`;\n\n // Roughness / metallic\n const specGlossUV = _ext?.uvForSpecGloss ?? \"input.uv\";\n const roughnessMetallic = _hasSpecGloss\n ? `let specGloss=textureSample(specGlossTexture,specGlossSampler,${specGlossUV});\nlet roughness=clamp(1.0-specGloss.a,0.0,1.0);\nlet metallic=0.0;`\n : `let roughness=clamp(orm.g*material.roughnessFactor,0.0,1.0);\nlet metallic=orm.b*material.metallicFactor;`;\n\n // Material-view / pass variants can skip extension slots while still compiling the colour path.\n const emissiveUV = _ext?.uvForEmissive ?? \"input.uv\";\n const emissiveDefault = _hasEmissiveColor || !_hasEmissiveTexture ? `var emissive:vec3f;` : `let emissive=textureSample(emissiveTexture,emissiveSampler,${emissiveUV}).rgb;`;\n\n // Occlusion default (overridden by reflectance fragment's AT slot or ext occlusion override)\n const occlusionDefault = _hasReflectanceExt ? `` : _ext?.occlusionOverride ? _ext.occlusionOverride : _hasOcclusion ? `let occlusion=orm.r;` : `let occlusion=1.0;`;\n // F0 computation (overridden by reflectance fragment's MF slot)\n const f0Default = _hasReflectanceExt\n ? ``\n : _hasSpecGloss\n ? `var colorF0=specGloss.rgb;\nlet colorF90=vec3<f32>(1.0);\nlet maxSpecular=max(colorF0.r,max(colorF0.g,colorF0.b));\nlet surfaceAlbedo=baseColor*(1.0-maxSpecular);`\n : `let dielectricF0=material.reflectance;\nvar colorF0=mix(vec3<f32>(dielectricF0),baseColor,metallic);\nlet colorF90=vec3<f32>(1.0);\nlet surfaceAlbedo=baseColor*(1.0-dielectricF0)*(1.0-metallic);`;\n\n // Specular AA + geometric-curvature roughness factors (BJS getAARoughnessFactors).\n // AA_factor_x is the direct-light roughness floor (matches BJS `computeSheenLighting`\n // which clamps info.roughness upward). AA_factor_y is the IBL/alphaG additive bump.\n // Emitted unconditionally as var so sheen/other fragments can reference them\n // without needing a define; zero on the no-curvature path makes them a no-op.\n const specularAABlock =\n _hasSpecularAA || hasAnyNormal\n ? `var AA_factor_x=0.0;\nvar AA_factor_y=0.0;\n{let nDfdx_AA=dpdx(N);\nlet nDfdy_AA=dpdy(N);\nlet slopeSquare_AA=max(dot(nDfdx_AA,nDfdx_AA),dot(nDfdy_AA,nDfdy_AA));\nAA_factor_x=pow(saturate(slopeSquare_AA),0.333);\nAA_factor_y=sqrt(slopeSquare_AA)*0.75;\nalphaG+=AA_factor_y;}`\n : `var AA_factor_x=0.0;\nvar AA_factor_y=0.0;`;\n\n // Direct lighting block — use the compact non-looping shader for one non-shadow light,\n // and the generic multi-light loop for multiple lights or shadow receivers.\n const directLightBlock: string = _hasMultiLight\n ? _multiLightLoop\n : _hasSingleLight\n ? _singleLightBlock\n : `var directDiffuse=vec3<f32>(0.0);\nvar directSpecular=vec3<f32>(0.0);\n/*BL*/`;\n\n // Tonemap: when enabled, the resolved tone mapping's WGSL (imageProcessing.toneMapping, defaulted to\n // StandardToneMapping upstream in pbr-renderable — so _toneMappingCall is always non-empty here when\n // _hasTonemap). When disabled, only exposure is applied. Helpers are emitted alongside the call.\n const toneMappingHelpersBlock = _hasTonemap ? _toneMappingHelpers : \"\";\n const tonemapBlock = _hasTonemap ? _toneMappingCall : `color*=scene.vImageInfos.x;`;\n\n // Fog (opt-in via scene.fog). The fog WGSL — `_fogHelper` (calcFogFactor) and `_fogBlock`\n // (the blend, emitted just before the tonemap block) — is supplied by pbr-renderable, which\n // dynamically imports pbr-fog-wgsl.ts only when scene.fog is set. Both are \"\" for non-fog\n // scenes, so those bundles carry zero fog bytes (see pbr-fog-wgsl.ts for the parity details).\n const fogHelper = _fogHelper;\n const fogBlock = _fogBlock;\n\n // Alpha output\n const alphaBlock = _noColorOutput\n ? \"\"\n : _hasAlphaBlend\n ? `var finalAlpha=alpha*material.materialAlpha;\nvar luminanceOverAlpha=0.0;\n/*BA*/\nluminanceOverAlpha+=dot(${_hasIbl ? `finalSpecularScaled` : `directSpecular`},vec3<f32>(0.2126,0.7152,0.0722));\nfinalAlpha=saturate(finalAlpha+luminanceOverAlpha*luminanceOverAlpha);\nreturn vec4<f32>(color,finalAlpha);`\n : `return vec4<f32>(color,alpha*material.materialAlpha);`;\n\n const doubleSidedEntry = _hasDoubleSided\n ? `@fragment fn main(input: FragmentInput, @builtin(front_facing) frontFacing: bool)${_noColorOutput ? \"\" : \" -> @location(0) vec4<f32>\"} {`\n : `@fragment fn main(input: FragmentInput)${_noColorOutput ? \"\" : \" -> @location(0) vec4<f32>\"} {`;\n // On a double-sided backface, flip the shading normal to face the viewer.\n // The geometric normal (`N_geom`) must flip with it so view-dependent terms\n // that consume it (e.g. specular environment horizon occlusion) stay consistent\n // — matching Babylon.js's `geometricNormalW` flip under TWOSIDEDLIGHTING && NORMAL.\n // The flat-shaded path (no vertex NORMAL) already orients N_geom to the viewer via\n // the eye vector, so it is excluded here (mirrors BJS gating on `defined(NORMAL)`).\n const doubleSidedGeomFlip = _flatGeometricNormal ? \"\" : \" N_geom = -N_geom;\";\n const doubleSidedFlip = _hasDoubleSided ? `if (!frontFacing) { N = -N;${doubleSidedGeomFlip} }` : \"\";\n\n const lightDecls = _hasMultiLight ? _multiLightWGSL : _hasSingleLight ? _singleLightWGSL : \"\";\n const lightBindingDecl = _hasSingleLight || _hasMultiLight ? `@group(0) @binding(1) var<uniform> lights: lightsUniforms;` : \"\";\n const meshLightIndexHelper = _hasSingleLight || _hasMultiLight ? meshLightIndexWGSL(\"mesh\") : \"\";\n\n const anisoBrdfBlock = _hasAnisotropy ? _anisoBrdfFunctions : \"\";\n\n const fragmentHelpers = _ext?.fragmentHelpers ?? \"\";\n const fragmentPrelude = _ext?.fragmentPrelude ?? \"\";\n const baseColorUV = _ext?.uvForBaseColor ?? \"input.uv\";\n const ormUV = _ext?.uvForOrm ?? \"input.uv\";\n\n const _fragmentTemplate = `/*SU*/\n${_esmShadowOutput ? \"struct shadowParamsUniforms { biasAndScale: vec4<f32>, depthValues: vec4<f32>, }\" : \"\"}\n/*MU*/\n@group(1) @binding(0) var<uniform> mesh: MeshUniforms;\n/*HF*/\n/*FB*/\n/*FI*/\n${BRDF_FUNCTIONS}\n${toneMappingHelpersBlock}\n${fogHelper}\n${anisoBrdfBlock}\n${lightDecls}\n${lightBindingDecl}\n${meshLightIndexHelper}\n${fragmentHelpers}\n${doubleSidedEntry}\n${fragmentPrelude}/*SV*/\nlet baseColorSample=textureSample(baseColorTexture,baseColorSampler,${baseColorUV});\n${baseColorDecode}\nlet orm=textureSample(ormTexture,ormSampler,${ormUV}).rgb;\n${occlusionDefault}\n${roughnessMetallic}\n${emissiveDefault}\n/*AT*/\n${\n // When the fragment terminates early (no color output, or ESM shadow\n // depth output), emit only the terminating return. Appending the\n // color-path body after the return would make it unreachable and\n // trigger a \"code is unreachable\" shader compilation warning.\n _noColorOutput\n ? \"return;\"\n : _esmShadowOutput\n ? _esmShadowDepthCode\n : `${normalBlock}\n${doubleSidedFlip}\n${anisotropyTBBlock}\n/*AC*/\nlet V=normalize(scene.vEyePosition.xyz-input.worldPos);\nlet NdotVUnclamped=dot(N,V);\nlet NdotV=abs(NdotVUnclamped)+0.0000001;\n${f0Default}\n/*MF*/\nvar alphaG=roughness*roughness+0.0005;\n${specularAABlock}\n${directLightBlock}\nvar color=directDiffuse+directSpecular+emissive;\n/*AI*/\n/*NI*/\n${fogBlock}\n${tonemapBlock}\ncolor=pow(color,vec3<f32>(1.0/2.2));\ncolor=clamp(color,vec3<f32>(0.0),vec3<f32>(1.0));\nlet highContrast=color*color*(3.0-2.0*color);\nif(scene.vImageInfos.y<1.0){color=mix(vec3<f32>(0.5),color,scene.vImageInfos.y);}\nelse{color=mix(color,highContrast,scene.vImageInfos.y-1.0);}\ncolor=max(color,vec3<f32>(0.0));\n/*BC*/\n${alphaBlock}`\n}\n}`;\n\n return {\n _vertexTemplate,\n _fragmentTemplate,\n _baseMeshUboFields,\n _baseMaterialUboFields,\n _baseVertexAttributes,\n _baseVaryings,\n _baseBindings,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pbr-template.js","sources":["../../../../src/material/pbr/pbr-template.ts"],"sourcesContent":["/**\n * PBR Base Template\n *\n * Provides the base PBR shader structure with slot markers where\n * fragments inject their code. This is the PBR equivalent of\n * what composePbrVertex/composePbrFragment produce today.\n *\n * The template is parameterized by light type and feature flags\n * because these fundamentally change the shader structure.\n */\n\nimport type { ShaderTemplate, UboField, VertexAttribute, Varying, BindingDecl } from \"../../shader/fragment-types.js\";\nimport type { PbrTemplateExt } from \"./pbr-template-ext.js\";\nimport type { MeshVbLayout } from \"../../mesh/mesh.js\";\nimport { appendMeshLightUboFields, meshLightIndexWGSL } from \"../../render/lights-ubo.js\";\n\ntype PbrGammaTemplate = typeof import(\"./pbr-template-gamma.js\");\n\nconst STAGE_FRAGMENT = 0x2;\n\n// ── BRDF functions (always present in PBR) ──────────────────────\n\nconst BRDF_FUNCTIONS = `\nconst PI:f32=3.14159265358979323846;\nfn distributionGGX(NdotH:f32,alphaG:f32)->f32{\nlet a2=alphaG*alphaG;\nlet d=NdotH*NdotH*(a2-1.0)+1.0;\nreturn a2/(PI*d*d);\n}\nfn geometrySmithGGX(NdotL:f32,NdotV:f32,alphaG:f32)->f32{\nlet a2=alphaG*alphaG;\nlet gl=NdotL*sqrt(NdotV*(NdotV-a2*NdotV)+a2);\nlet gv=NdotV*sqrt(NdotL*(NdotL-a2*NdotL)+a2);\nreturn 0.5/(gl+gv);\n}\nfn fresnelSchlick(cosTheta:f32,F0:vec3<f32>,F90:vec3<f32>)->vec3<f32>{\nlet t=1.0-cosTheta;\nlet t2=t*t;\nreturn F0+(F90-F0)*(t2*t2*t);\n}\n`;\n\nexport interface PbrTemplateConfig {\n /** When true, generates a non-looping single-light direct block + lights UBO binding. */\n /** @internal */\n readonly _hasSingleLight?: boolean;\n /** When true, generates a multi-light loop + lights UBO binding.\n * Used for multiple lights or shadow receivers. */\n /** @internal */\n readonly _hasMultiLight?: boolean;\n /** Pre-built WGSL for the single-light UBO structs. */\n /** @internal */\n readonly _singleLightWGSL?: string;\n /** Pre-built WGSL for the single-light direct lighting block. */\n /** @internal */\n readonly _singleLightBlock?: string;\n /** Pre-built WGSL for multi-light (structs + computePbrLight). Passed from\n * dynamically imported fragments/multilight-wgsl.ts to keep it out of non-shadow bundles. */\n /** @internal */\n readonly _multiLightWGSL?: string;\n /** Pre-built WGSL for the multi-light direct lighting loop body. */\n /** @internal */\n readonly _multiLightLoop?: string;\n /** Normal map mode (default: \"none\") */\n /** @internal */\n readonly _normalMode?: \"tangent\" | \"cotangent\" | \"none\";\n /** @internal Mesh has no NORMAL attribute → flat-shade via screen-space derivatives (glTF spec). */\n readonly _flatGeometricNormal?: boolean;\n /** @internal Flat-normal WGSL (from flat-normal-wgsl.ts), lazily supplied only when a no-NORMAL mesh exists. */\n readonly _flatNormalWgsl?: string;\n /** Has emissive texture */\n /** @internal */\n readonly _hasEmissiveTexture?: boolean;\n /** Has specular-glossiness workflow */\n /** @internal */\n readonly _hasSpecGloss?: boolean;\n /** Has double-sided rendering */\n /** @internal */\n readonly _hasDoubleSided?: boolean;\n /** Has tonemap */\n /** @internal */\n readonly _hasTonemap?: boolean;\n /** Scene has fog (scene.fog != null). `calcFogFactor` helper WGSL, supplied by the dynamic\n * pbr-fog-wgsl import only for fog scenes; \"\" otherwise (zero bytes for non-fog scenes). */\n /** @internal */\n readonly _fogHelper?: string;\n /** Fog blend WGSL, emitted just before the tonemap block; \"\" for non-fog scenes. */\n /** @internal */\n readonly _fogBlock?: string;\n /** Tone mapping WGSL: module-scope helper functions/consts. Empty string = inline default (no helpers). */\n /** @internal */\n readonly _toneMappingHelpers?: string;\n /** Tone mapping WGSL: per-fragment call block. Empty string = default standard exponential. */\n /** @internal */\n readonly _toneMappingCall?: string;\n /** Has alpha blending */\n /** @internal */\n readonly _hasAlphaBlend?: boolean;\n /** Has specular AA */\n /** @internal */\n readonly _hasSpecularAA?: boolean;\n /** Has gamma albedo (sRGB base color decode) */\n /** @internal */\n readonly _hasGammaAlbedo?: boolean;\n /** Has a non-default base-color factor multiplied over the base-color texture. */\n /** @internal */\n readonly _hasBaseColorFactor?: boolean;\n /** Has morph targets (changes position/normal variable names in vertex shader) */\n /** @internal */\n readonly _hasMorph?: boolean;\n /** Has occlusion in ORM texture (simple path, no reflectance ext) */\n /** @internal */\n readonly _hasOcclusion?: boolean;\n /** Has emissive color UBO field (fragment handles emissive computation) */\n /** @internal */\n readonly _hasEmissiveColor?: boolean;\n /** When true, the reflectance fragment handles F0 + occlusion computation */\n /** @internal */\n readonly _hasReflectanceExt?: boolean;\n /** When true, include IBL SH coefficients in scene UBO */\n /** @internal */\n readonly _hasIbl?: boolean;\n /** Has anisotropy layer */\n /** @internal */\n readonly _hasAnisotropy?: boolean;\n /** Anisotropy WGSL: BRDF helper functions (dynamically imported). */\n /** @internal */\n readonly _anisoBrdfFunctions?: string;\n /** Anisotropy WGSL: T/B computation block (dynamically imported). */\n /** @internal */\n readonly _anisoTBBlock?: string;\n /** Optional extension config for advanced features (UV transforms, UV2, vertex colors).\n * When undefined, base template defaults to master-like behavior (no feature strings). */\n /** @internal */\n readonly _ext?: PbrTemplateExt;\n /** @internal */\n readonly _gammaTemplate?: PbrGammaTemplate | null;\n /** Generate a fragment stage that runs discard/alpha-test logic and writes no color. */\n /** @internal */\n readonly _noColorOutput?: boolean;\n /** Generate a fragment stage that runs discard/alpha-test logic and writes ESM shadow color. */\n /** @internal */\n readonly _esmShadowOutput?: boolean;\n /** ESM shadow depth output code. Supplied by the ESM material view so normal PBR bundles don't retain it. */\n /** @internal */\n readonly _esmShadowDepthCode?: string;\n /** @internal Per-attribute vertex-buffer interleave layout. Undefined (or per-attribute\n * undefined) → canonical tight strides (12/12/16/8). Only set for meshes\n * sourcing attributes from a strided bufferView. */\n readonly _vbStrides?: MeshVbLayout;\n}\n\n/**\n * Create a PBR ShaderTemplate from the given configuration.\n * The template contains slot markers that the composer fills with fragment code.\n */\nexport function createPbrTemplate(config: PbrTemplateConfig): ShaderTemplate {\n const {\n _hasSingleLight = false,\n _hasMultiLight = false,\n _singleLightWGSL = \"\",\n _singleLightBlock = \"\",\n _multiLightWGSL = \"\",\n _multiLightLoop = \"\",\n _normalMode = \"none\",\n _flatGeometricNormal = false,\n _flatNormalWgsl = \"\",\n _hasEmissiveTexture = false,\n _hasSpecGloss = false,\n _hasDoubleSided = false,\n _hasTonemap = false,\n _fogHelper = \"\",\n _fogBlock = \"\",\n _toneMappingHelpers = \"\",\n _toneMappingCall = \"\",\n _hasAlphaBlend = false,\n _hasSpecularAA = false,\n _hasGammaAlbedo = false,\n _hasBaseColorFactor = false,\n _hasMorph = false,\n _hasOcclusion = false,\n _hasEmissiveColor = false,\n _hasReflectanceExt = false,\n _hasIbl = false,\n _hasAnisotropy = false,\n _anisoBrdfFunctions = \"\",\n _anisoTBBlock = \"\",\n _ext,\n _gammaTemplate,\n _noColorOutput = false,\n _esmShadowOutput = false,\n _esmShadowDepthCode = \"\",\n _vbStrides,\n } = config;\n const hasNormal = _normalMode === \"tangent\";\n const hasCotangentNormal = _normalMode === \"cotangent\";\n const hasAnyNormal = hasNormal || hasCotangentNormal;\n\n // ── Base vertex attributes ──────────────────────────────────\n // arrayStride defaults to the canonical tight element size; interleaved meshes\n // override it (e.g. 48 for POSITION+NORMAL+UV+TANGENT sharing one stride-48\n // bufferView). `_offset` is the attribute's byte offset WITHIN that shared\n // buffer (0 for tight meshes); it is baked into the pipeline vertex layout so\n // the draw can bind the shared buffer at offset 0 (matches Babylon.js WebGPU —\n // a non-zero setVertexBuffer bind offset corrupts vertex fetch on some AMD/Dawn paths).\n const _baseVertexAttributes: VertexAttribute[] = [\n { _name: \"position\", _type: \"vec3<f32>\", _gpuFormat: \"float32x3\", _arrayStride: _vbStrides?._p?._stride ?? 12, _offset: _vbStrides?._p?._offset ?? 0 },\n { _name: \"normal\", _type: \"vec3<f32>\", _gpuFormat: \"float32x3\", _arrayStride: _vbStrides?._n?._stride ?? 12, _offset: _vbStrides?._n?._offset ?? 0 },\n ];\n if (hasNormal) {\n _baseVertexAttributes.push({\n _name: \"tangent\",\n _type: \"vec4<f32>\",\n _gpuFormat: \"float32x4\",\n _arrayStride: _vbStrides?._t?._stride ?? 16,\n _offset: _vbStrides?._t?._offset ?? 0,\n });\n }\n _baseVertexAttributes.push({ _name: \"uv\", _type: \"vec2<f32>\", _gpuFormat: \"float32x2\", _arrayStride: _vbStrides?._u?._stride ?? 8, _offset: _vbStrides?._u?._offset ?? 0 });\n if (_ext) {\n _baseVertexAttributes.push(..._ext.extraVertexAttributes);\n }\n\n // ── Base varyings ───────────────────────────────────────────\n const _baseVaryings: Varying[] = [\n { _name: \"worldPos\", _type: \"vec3<f32>\" },\n { _name: \"worldNormal\", _type: \"vec3<f32>\" },\n ];\n if (hasNormal) {\n _baseVaryings.push({ _name: \"worldTangent\", _type: \"vec3<f32>\" }, { _name: \"worldBitangent\", _type: \"vec3<f32>\" });\n }\n _baseVaryings.push({ _name: \"uv\", _type: \"vec2<f32>\" });\n if (_ext) {\n _baseVaryings.push(..._ext.extraVaryings);\n }\n\n // ── Base mesh UBO fields (world matrix + affected light indices — UV transforms are\n // per-texture now, emitted on the material UBO when hasUvTransform). ─\n const _baseMeshUboFields: UboField[] = [{ _name: \"world\", _type: \"mat4x4<f32>\" }];\n appendMeshLightUboFields(_baseMeshUboFields);\n\n // ── Base material UBO fields ────────────────────────────────────\n const _baseMaterialUboFields: UboField[] = [\n { _name: \"environmentIntensity\", _type: \"f32\" },\n { _name: \"directIntensity\", _type: \"f32\" },\n { _name: \"reflectance\", _type: \"f32\" },\n { _name: \"materialAlpha\", _type: \"f32\" },\n ...(_hasBaseColorFactor ? [{ _name: \"baseColorFactor\", _type: \"vec4<f32>\" as const }] : []),\n // glTF metallicFactor / roughnessFactor (default 1.0) — applied over MR texture channels.\n { _name: \"metallicFactor\", _type: \"f32\" },\n { _name: \"roughnessFactor\", _type: \"f32\" },\n { _name: \"normalScale\", _type: \"f32\" },\n { _name: \"lightFalloffMode\", _type: \"f32\" },\n // Anisotropy UBO field stays on the base template because anisotropy is\n // template-only (no ShaderFragment) — the anisotropyExt just writes its\n // slice through the unified ext.writeUbo hook.\n ...(_hasAnisotropy ? [{ _name: \"anisotropyParams\", _type: \"vec4<f32>\" as const }] : []),\n // ── Extension fields (per-texture UV transforms, etc.) ─\n ...(_ext ? _ext.extraMaterialUboFields : []),\n ];\n\n // ── Helper: texture + sampler binding pair ────────────────────\n const tex2d = (name: string, sampler: string): BindingDecl[] => [\n { _name: name, _type: { _kind: \"texture\", _textureType: \"texture_2d<f32>\" }, _visibility: STAGE_FRAGMENT },\n { _name: sampler, _type: { _kind: \"sampler\", _samplerType: \"sampler\" }, _visibility: STAGE_FRAGMENT },\n ];\n\n // ── Base bindings (always-present textures) ─────────────────\n const _baseBindings: BindingDecl[] = tex2d(\"baseColorTexture\", \"baseColorSampler\");\n if (hasAnyNormal) {\n _baseBindings.push(...tex2d(\"normalTexture\", \"normalSampler_\"));\n }\n _baseBindings.push(...tex2d(\"ormTexture\", \"ormSampler\"));\n if (_ext) {\n _baseBindings.push(..._ext.extraBindings);\n }\n if (_hasEmissiveTexture) {\n _baseBindings.push(...tex2d(\"emissiveTexture\", \"emissiveSampler\"));\n }\n if (_hasSpecGloss) {\n _baseBindings.push(...tex2d(\"specGlossTexture\", \"specGlossSampler\"));\n }\n if (_esmShadowOutput) {\n _baseBindings.push({ _name: \"shadowParams\", _type: { _kind: \"uniform-buffer\" }, _visibility: STAGE_FRAGMENT });\n }\n // ── Vertex template ─────────────────────────────────────────\n // When morph targets are active, the morph fragment's VR\n // defines morphedPos/morphedNorm. The base template uses those instead\n // of the raw vertex attributes.\n const posVar = _hasMorph ? \"morphedPos\" : \"position\";\n const normVar = _hasMorph ? \"morphedNorm\" : \"normal\";\n\n // The vertex template uses morphedPos/morphedNorm when morph fragment is present,\n // falling back to position/normal. The morph fragment's VR defines these vars.\n const tangentBlock = hasNormal\n ? `let N_local=normalize(${normVar});\nlet T_local=normalize(tangent.xyz);\nlet B_local=cross(N_local,T_local)*tangent.w;\nout.worldTangent=(finalWorld*vec4<f32>(T_local,0.0)).xyz;\nout.worldBitangent=(finalWorld*vec4<f32>(B_local,0.0)).xyz;`\n : \"\";\n\n const _vertexTemplate = `/*SU*/\n/*MU*/\n@group(1) @binding(0) var<uniform> mesh: MeshUniforms;\n/*VH*/\n/*VD*/\n/*VO*/\n@vertex fn main(\n/*VP*/\n) -> VertexOutput {\nvar out: VertexOutput;\n/*VR*/\nvar finalWorld = mesh.world;\n/*VW*/\nlet worldPos4 = finalWorld * vec4<f32>(${posVar}, 1.0);\nout.worldPos = worldPos4.xyz;\nout.clipPos = scene.viewProjection * worldPos4;\nout.worldNormal = (finalWorld * vec4<f32>(normalize(${normVar}), 0.0)).xyz;\n${tangentBlock}\nout.uv = uv;\n ${_ext ? _ext.vertexBodyExtra : \"\"}/*VB*/\nreturn out;\n}`;\n\n // ── Fragment template ────────────────────────────────────────\n\n // Normal handling block\n const normalUV = _ext?.uvForNormal ?? \"input.uv\";\n const normalScaleMod = _ext?.normalScaleMod ?? \"\";\n const normalRef = _ext?.normalScaleMod ? \"scaledNormal\" : \"normalMapRaw\";\n const normalRefCt = _ext?.normalScaleMod ? \"scaledNormalCT\" : \"normalMapSample\";\n let normalBlock: string;\n if (hasNormal) {\n normalBlock = `let normalMapRaw=textureSample(normalTexture,normalSampler_,${normalUV}).rgb*2.0-1.0;\n${normalScaleMod}let normalMapNorm=normalize(${normalRef});\nvar N_geom=normalize(input.worldNormal);\nlet TBN=mat3x3<f32>(input.worldTangent,input.worldBitangent,input.worldNormal);\nvar N=normalize(TBN*normalMapNorm);`;\n } else if (hasCotangentNormal) {\n normalBlock = `let normalMapSample=textureSample(normalTexture,normalSampler_,${normalUV}).rgb*2.0-1.0;\n${normalScaleMod.replace(/normalMapRaw/g, \"normalMapSample\").replace(/scaledNormal/g, \"scaledNormalCT\")}var N_geom=normalize(input.worldNormal);\nlet dp1=dpdx(input.worldPos);\nlet dp2=dpdy(input.worldPos);\nlet duv1=dpdx(${normalUV});\nlet duv2=dpdy(${normalUV});\nlet dp2perp=cross(dp2,N_geom);\nlet dp1perp=cross(N_geom,dp1);\nlet tangent_ct=dp2perp*duv1.x+dp1perp*duv2.x;\nlet bitangent_ct=-(dp2perp*duv1.y+dp1perp*duv2.y);\nlet det=max(dot(tangent_ct,tangent_ct),dot(bitangent_ct,bitangent_ct));\nlet invmax=select(inverseSqrt(det),0.0,det==0.0);\nlet cotangentFrame=mat3x3<f32>(tangent_ct*invmax,bitangent_ct*invmax,N_geom);\nvar N=normalize(cotangentFrame*normalize(${normalRefCt}));`;\n } else if (_flatGeometricNormal && _flatNormalWgsl) {\n // glTF spec: a primitive without a NORMAL attribute MUST be flat-shaded.\n // WGSL (face normal from screen-space derivatives, oriented to the viewer)\n // is supplied lazily by pbr-renderable from flat-normal-wgsl.ts — kept out\n // of this shared template so normal-having scenes pay zero bundle cost.\n normalBlock = `${_flatNormalWgsl}`;\n } else {\n normalBlock = `var N_geom=normalize(input.worldNormal);\nvar N=N_geom;`;\n }\n\n // Anisotropy: tangent/bitangent frame (passed in from dynamic import, empty if not used)\n const anisotropyTBBlock = _hasAnisotropy ? _anisoTBBlock : \"\";\n\n // Base color decoding\n const vertexColorMod = _ext?.baseColorMod ?? \"\";\n const baseColorFactorRgb = _hasBaseColorFactor ? \"*material.baseColorFactor.rgb\" : \"\";\n const baseColorFactorAlpha = _hasBaseColorFactor ? \"*material.baseColorFactor.a\" : \"\";\n const baseColorDecode = _hasGammaAlbedo\n ? _gammaTemplate!.gammaBaseColor(baseColorFactorRgb, baseColorFactorAlpha, vertexColorMod)\n : `var baseColor=baseColorSample.rgb${baseColorFactorRgb};\nvar alpha=baseColorSample.a${baseColorFactorAlpha};${vertexColorMod}`;\n\n // Roughness / metallic\n const specGlossUV = _ext?.uvForSpecGloss ?? \"input.uv\";\n const roughnessMetallic = _hasSpecGloss\n ? `let specGloss=textureSample(specGlossTexture,specGlossSampler,${specGlossUV});\nlet roughness=clamp(1.0-specGloss.a,0.0,1.0);\nlet metallic=0.0;`\n : `let roughness=clamp(orm.g*material.roughnessFactor,0.0,1.0);\nlet metallic=orm.b*material.metallicFactor;`;\n\n // Material-view / pass variants can skip extension slots while still compiling the colour path.\n const emissiveUV = _ext?.uvForEmissive ?? \"input.uv\";\n const emissiveDefault = _hasEmissiveColor || !_hasEmissiveTexture ? `var emissive:vec3f;` : `let emissive=textureSample(emissiveTexture,emissiveSampler,${emissiveUV}).rgb;`;\n\n // Occlusion default (overridden by reflectance fragment's AT slot or ext occlusion override)\n const occlusionDefault = _hasReflectanceExt ? `` : _ext?.occlusionOverride ? _ext.occlusionOverride : _hasOcclusion ? `let occlusion=orm.r;` : `let occlusion=1.0;`;\n // F0 computation (overridden by reflectance fragment's MF slot)\n const f0Default = _hasReflectanceExt\n ? ``\n : _hasSpecGloss\n ? `var colorF0=specGloss.rgb;\nlet colorF90=vec3<f32>(1.0);\nlet maxSpecular=max(colorF0.r,max(colorF0.g,colorF0.b));\nlet surfaceAlbedo=baseColor*(1.0-maxSpecular);`\n : `let dielectricF0=material.reflectance;\nvar colorF0=mix(vec3<f32>(dielectricF0),baseColor,metallic);\nlet colorF90=vec3<f32>(1.0);\nlet surfaceAlbedo=baseColor*(1.0-dielectricF0)*(1.0-metallic);`;\n\n // Specular AA + geometric-curvature roughness factors (BJS getAARoughnessFactors).\n // AA_factor_x is the direct-light roughness floor (matches BJS `computeSheenLighting`\n // which clamps info.roughness upward). AA_factor_y is the IBL/alphaG additive bump.\n // Emitted unconditionally as var so sheen/other fragments can reference them\n // without needing a define; zero on the no-curvature path makes them a no-op.\n const specularAABlock =\n _hasSpecularAA || hasAnyNormal\n ? `var AA_factor_x=0.0;\nvar AA_factor_y=0.0;\n{let nDfdx_AA=dpdx(N);\nlet nDfdy_AA=dpdy(N);\nlet slopeSquare_AA=max(dot(nDfdx_AA,nDfdx_AA),dot(nDfdy_AA,nDfdy_AA));\nAA_factor_x=pow(saturate(slopeSquare_AA),0.333);\nAA_factor_y=sqrt(slopeSquare_AA)*0.75;\nalphaG+=AA_factor_y;}`\n : `var AA_factor_x=0.0;\nvar AA_factor_y=0.0;`;\n\n // Direct lighting block — use the compact non-looping shader for one non-shadow light,\n // and the generic multi-light loop for multiple lights or shadow receivers.\n const directLightBlock: string = _hasMultiLight\n ? _multiLightLoop\n : _hasSingleLight\n ? _singleLightBlock\n : `var directDiffuse=vec3<f32>(0.0);\nvar directSpecular=vec3<f32>(0.0);\n/*BL*/`;\n\n // Tonemap: when enabled, the resolved tone mapping's WGSL (imageProcessing.toneMapping, defaulted to\n // StandardToneMapping upstream in pbr-renderable — so _toneMappingCall is always non-empty here when\n // _hasTonemap). When disabled, only exposure is applied. Helpers are emitted alongside the call.\n const toneMappingHelpersBlock = _hasTonemap ? _toneMappingHelpers : \"\";\n const tonemapBlock = _hasTonemap ? _toneMappingCall : `color*=scene.vImageInfos.x;`;\n\n // Fog (opt-in via scene.fog). The fog WGSL — `_fogHelper` (calcFogFactor) and `_fogBlock`\n // (the blend, emitted just before the tonemap block) — is supplied by pbr-renderable, which\n // dynamically imports pbr-fog-wgsl.ts only when scene.fog is set. Both are \"\" for non-fog\n // scenes, so those bundles carry zero fog bytes (see pbr-fog-wgsl.ts for the parity details).\n const fogHelper = _fogHelper;\n const fogBlock = _fogBlock;\n\n // Alpha output\n const alphaBlock = _noColorOutput\n ? \"\"\n : _hasAlphaBlend\n ? `var finalAlpha=alpha*material.materialAlpha;\nvar luminanceOverAlpha=0.0;\n/*BA*/\nluminanceOverAlpha+=dot(${_hasIbl ? `finalSpecularScaled` : `directSpecular`},vec3<f32>(0.2126,0.7152,0.0722));\nfinalAlpha=saturate(finalAlpha+luminanceOverAlpha*luminanceOverAlpha);\n/*FA*/\nreturn vec4<f32>(color,finalAlpha);`\n : `return vec4<f32>(color,alpha*material.materialAlpha);`;\n\n const doubleSidedEntry = _hasDoubleSided\n ? `@fragment fn main(input: FragmentInput, @builtin(front_facing) frontFacing: bool)${_noColorOutput ? \"\" : \" -> @location(0) vec4<f32>\"} {`\n : `@fragment fn main(input: FragmentInput)${_noColorOutput ? \"\" : \" -> @location(0) vec4<f32>\"} {`;\n // On a double-sided backface, flip the shading normal to face the viewer.\n // The geometric normal (`N_geom`) must flip with it so view-dependent terms\n // that consume it (e.g. specular environment horizon occlusion) stay consistent\n // — matching Babylon.js's `geometricNormalW` flip under TWOSIDEDLIGHTING && NORMAL.\n // The flat-shaded path (no vertex NORMAL) already orients N_geom to the viewer via\n // the eye vector, so it is excluded here (mirrors BJS gating on `defined(NORMAL)`).\n const doubleSidedGeomFlip = _flatGeometricNormal ? \"\" : \" N_geom = -N_geom;\";\n const doubleSidedFlip = _hasDoubleSided ? `if (!frontFacing) { N = -N;${doubleSidedGeomFlip} }` : \"\";\n\n const lightDecls = _hasMultiLight ? _multiLightWGSL : _hasSingleLight ? _singleLightWGSL : \"\";\n const lightBindingDecl = _hasSingleLight || _hasMultiLight ? `@group(0) @binding(1) var<uniform> lights: lightsUniforms;` : \"\";\n const meshLightIndexHelper = _hasSingleLight || _hasMultiLight ? meshLightIndexWGSL(\"mesh\") : \"\";\n\n const anisoBrdfBlock = _hasAnisotropy ? _anisoBrdfFunctions : \"\";\n\n const fragmentHelpers = _ext?.fragmentHelpers ?? \"\";\n const fragmentPrelude = _ext?.fragmentPrelude ?? \"\";\n const baseColorUV = _ext?.uvForBaseColor ?? \"input.uv\";\n const ormUV = _ext?.uvForOrm ?? \"input.uv\";\n\n const _fragmentTemplate = `/*SU*/\n${_esmShadowOutput ? \"struct shadowParamsUniforms { biasAndScale: vec4<f32>, depthValues: vec4<f32>, }\" : \"\"}\n/*MU*/\n@group(1) @binding(0) var<uniform> mesh: MeshUniforms;\n/*HF*/\n/*FB*/\n/*FI*/\n${BRDF_FUNCTIONS}\n${toneMappingHelpersBlock}\n${fogHelper}\n${anisoBrdfBlock}\n${lightDecls}\n${lightBindingDecl}\n${meshLightIndexHelper}\n${fragmentHelpers}\n${doubleSidedEntry}\n${fragmentPrelude}/*SV*/\nlet baseColorSample=textureSample(baseColorTexture,baseColorSampler,${baseColorUV});\n${baseColorDecode}\nlet orm=textureSample(ormTexture,ormSampler,${ormUV}).rgb;\n${occlusionDefault}\n${roughnessMetallic}\n${emissiveDefault}\n/*AT*/\n${\n // When the fragment terminates early (no color output, or ESM shadow\n // depth output), emit only the terminating return. Appending the\n // color-path body after the return would make it unreachable and\n // trigger a \"code is unreachable\" shader compilation warning.\n _noColorOutput\n ? \"return;\"\n : _esmShadowOutput\n ? _esmShadowDepthCode\n : `${normalBlock}\n${doubleSidedFlip}\n${anisotropyTBBlock}\n/*AC*/\nlet V=normalize(scene.vEyePosition.xyz-input.worldPos);\nlet NdotVUnclamped=dot(N,V);\nlet NdotV=abs(NdotVUnclamped)+0.0000001;\n${f0Default}\n/*MF*/\nvar alphaG=roughness*roughness+0.0005;\n${specularAABlock}\n${directLightBlock}\nvar color=directDiffuse+directSpecular+emissive;\n/*AI*/\n/*NI*/\n${fogBlock}\n${tonemapBlock}\ncolor=pow(color,vec3<f32>(1.0/2.2));\ncolor=clamp(color,vec3<f32>(0.0),vec3<f32>(1.0));\nlet highContrast=color*color*(3.0-2.0*color);\nif(scene.vImageInfos.y<1.0){color=mix(vec3<f32>(0.5),color,scene.vImageInfos.y);}\nelse{color=mix(color,highContrast,scene.vImageInfos.y-1.0);}\ncolor=max(color,vec3<f32>(0.0));\n/*BC*/\n${alphaBlock}`\n}\n}`;\n\n return {\n _vertexTemplate,\n _fragmentTemplate,\n _baseMeshUboFields,\n _baseMaterialUboFields,\n _baseVertexAttributes,\n _baseVaryings,\n _baseBindings,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { F32, U16, U32 } from '../../engine/typed-arrays.js';
|
|
2
2
|
import { BU, TU } from '../../engine/gpu-flags.js';
|
|
3
|
-
import { mat4Identity } from '../../math/mat4-identity.js';
|
|
4
|
-
import { mat4Compose } from '../../math/mat4-compose.js';
|
|
5
3
|
import { ObservableVec3 } from '../../math/observable-vec3.js';
|
|
6
4
|
import { ObservableQuat } from '../../math/observable-quat.js';
|
|
7
|
-
import { createWorldMatrixState, attachWorldMatrixState } from '../../scene/world-matrix-state.js';
|
|
5
|
+
import { createWorldMatrixState, attachWorldMatrixState, composeTrsLocalMatrix } from '../../scene/world-matrix-state.js';
|
|
8
6
|
import { eulerToQuat, createEulerProxy } from '../../scene/scene-node.js';
|
|
9
7
|
import { buildSplatGeometry } from '../../loader-splat/splat-data.js';
|
|
10
8
|
|
|
@@ -137,11 +135,7 @@ function disposeGaussianSplattingMesh(mesh) {
|
|
|
137
135
|
mesh._worker.terminate();
|
|
138
136
|
}
|
|
139
137
|
function initSplatTransform(node) {
|
|
140
|
-
const wm = createWorldMatrixState(() =>
|
|
141
|
-
const p = node.position, rq2 = node.rotationQuaternion, s = node.scaling;
|
|
142
|
-
const isIdentity = p.x === 0 && p.y === 0 && p.z === 0 && rq2.x === 0 && rq2.y === 0 && rq2.z === 0 && rq2.w === 1 && s.x === 1 && s.y === 1 && s.z === 1;
|
|
143
|
-
return isIdentity ? mat4Identity() : mat4Compose(p.x, p.y, p.z, rq2.x, rq2.y, rq2.z, rq2.w, s.x, s.y, s.z);
|
|
144
|
-
});
|
|
138
|
+
const wm = createWorldMatrixState(() => composeTrsLocalMatrix(node.position, node.rotationQuaternion, node.scaling));
|
|
145
139
|
const onDirty = () => wm.markLocalDirty();
|
|
146
140
|
const [iqx, iqy, iqz, iqw] = eulerToQuat(0, 0, 0);
|
|
147
141
|
const rq = new ObservableQuat(iqx, iqy, iqz, iqw, onDirty);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gaussian-splatting-mesh.js","sources":["../../../../src/mesh/GaussianSplatting/gaussian-splatting-mesh.ts"],"sourcesContent":["/** GaussianSplattingMesh — pure data describing a renderable Gaussian splat cloud.\n *\n * Plain state with TRS + parent + children (`SceneNode`-shaped, no methods),\n * plus splat-specific GPU resources and a worker handle for back-to-front sort.\n * All behaviour lives in standalone functions in this file or in\n * `gaussian-splatting-pipeline.ts`.\n *\n * Renderable + dispose hook registration is performed by `loadSplat()` via\n * `attachGaussianSplattingMesh()` — scene-core stays GS-agnostic so non-GS\n * scenes never pull in this pipeline. */\n\nimport { F32, U32, U16 } from \"../../engine/typed-arrays.js\";\nimport { TU, BU } from \"../../engine/gpu-flags.js\";\nimport type { SceneNode } from \"../../scene/scene-node.js\";\nimport type { EngineContext } from \"../../engine/engine.js\";\nimport type { Mat4 } from \"../../math/types.js\";\nimport { mat4Identity, mat4Compose } from \"../../math/mat4.js\";\nimport { ObservableVec3 } from \"../../math/observable-vec3.js\";\nimport { ObservableQuat } from \"../../math/observable-quat.js\";\nimport { createWorldMatrixState, attachWorldMatrixState } from \"../../scene/world-matrix-state.js\";\nimport { eulerToQuat, createEulerProxy } from \"../../scene/scene-node.js\";\nimport { buildSplatGeometry, type SplatGeometry, type ParsedSplat } from \"../../loader-splat/splat-data.js\";\n\n/** Names of the four WGSL slots a `GsShaderFragment` may inject into the\n * Gaussian-splat fragment shader. Markers in the WGSL source look like\n * `\\/*GS_FRAGMENT_MAIN_END*\\/` — valid comments when no plugin is present. */\nexport type GsFragmentSlot = \"GS_FRAGMENT_DEFINITIONS\" | \"GS_FRAGMENT_MAIN_BEGIN\" | \"GS_FRAGMENT_BEFORE_FRAGCOLOR\" | \"GS_FRAGMENT_MAIN_END\";\n\n/** Data-only descriptor of a GS shader plugin. Lite equivalent of a BJS\n * `MaterialPluginBase`: snippets get spliced into the four GS fragment slots. */\nexport interface GsShaderFragment {\n readonly id: string;\n readonly fragmentSlots?: Partial<Record<GsFragmentSlot, string>>;\n readonly helperFunctions?: string;\n}\n\n/** Per-mesh GPU resources owned by a GaussianSplattingMesh. */\nexport interface GaussianSplattingGpu {\n /** @internal */\n _centersTex: GPUTexture;\n /** @internal */\n _centersView: GPUTextureView;\n /** @internal */\n _covATex: GPUTexture;\n /** @internal */\n _covAView: GPUTextureView;\n /** @internal */\n _covBTex: GPUTexture;\n /** @internal */\n _covBView: GPUTextureView;\n /** @internal */\n _colorsTex: GPUTexture;\n /** @internal */\n _colorsView: GPUTextureView;\n /** @internal */\n _sampler: GPUSampler;\n /** @internal Quad vertex buffer (4 vec2 corners). */\n _quadBuffer: GPUBuffer;\n /** @internal Quad index buffer (uint16 [0,1,2,0,2,3]). */\n _indexBuffer: GPUBuffer;\n /** @internal Per-instance splatIndex (Float32 × vertexCount), back-to-front order. */\n _splatIndexBuffer: GPUBuffer;\n /** @internal CPU-side scratch matching `splatIndexBuffer`. */\n _splatIndexCpu: Float32Array;\n /** Packed view-dependent SH textures (1..5 rgba32uint), `null` when\n * the cloud has no SH data. Layout: 16 bytes per splat per texture. */\n /** @internal */\n _shTextures: GPUTexture[] | null;\n /** @internal */\n _shViews: GPUTextureView[] | null;\n}\n\n/** Public Gaussian-splatting mesh handle. `_kind` is a brand so consumers can\n * narrow on it; the renderable is wired up by `loadSplat()` directly. */\nexport interface GaussianSplattingMesh extends SceneNode {\n /** @internal */\n readonly _kind: \"gs-mesh\";\n /** Number of splats in the cloud. */\n readonly vertexCount: number;\n /** RGBA32F texture dimensions used for centers/covA/covB/colors. */\n readonly textureWidth: number;\n readonly textureHeight: number;\n /** World-space AABB across all splat centres (for camera framing). */\n boundMin: [number, number, number];\n boundMax: [number, number, number];\n /** Spherical-harmonics degree (0 means no view-dependent SH). Set at load\n * time and immutable afterwards — `updateData` rejects a degree change. */\n readonly shDegree: number;\n /** @internal Sort worker. Owned by the mesh; terminated on dispose. */\n _worker: Worker;\n /** @internal Scratch for the worker round-trip. high-32 = depth, low-32 = index. */\n _depthMix: BigInt64Array;\n /** Snapshot of the world matrix posted to the worker on the last sort.\n * Used to decide whether a re-sort is needed this frame. Mirrors BJS\n * `ICameraViewInfo.sortWorldMatrix`. */\n /** @internal */\n _sortWorldMatrix: Float32Array;\n /** @internal Snapshot of the camera-forward vector (`view[2,6,10]`) on the last sort. */\n _sortCameraForward: Float32Array;\n /** @internal Snapshot of the camera world-space position on the last sort. */\n _sortCameraPosition: Float32Array;\n /** @internal True between postMessage and onmessage; throttles re-sort requests. */\n _canPostToWorker: boolean;\n /** Resolves on the first sort completion. The lab scene awaits this\n * before flagging `dataset.ready`. */\n readonly firstSortReady: Promise<void>;\n /** @internal Resolver for {@link firstSortReady}; called once the first sort completes, then cleared to null. */\n _firstSortResolve: (() => void) | null;\n /** @internal GPU resources, populated by `createGaussianSplattingMesh`. */\n _gs: GaussianSplattingGpu;\n /** Raw 32-byte/splat row buffer. Mirrors BJS `splatsData` (with\n * `keepInRam:true`) — exposed for inspection + `updateData` round-trips. */\n readonly splatsData: ArrayBuffer;\n /** Replace the splat data in place. Re-uploads centres / covariance /\n * colour textures, re-posts positions to the sort worker, and updates the\n * AABB. Vertex count must match the original buffer. Mirrors BJS\n * `GaussianSplattingMesh.updateData(buffer, _sh, opts)`. */\n updateData(splatBuffer: ArrayBuffer): void;\n}\n\n/** Create a GaussianSplattingMesh from a parsed splat asset. Uploads textures +\n * initial identity splat-index buffer, spawns the sort worker, and (when the\n * asset includes SH coefficients) packs SH into rgba32uint textures.\n *\n * `parsed.data` is retained on the mesh as `splatsData` so callers can mutate\n * the row data and round-trip it via `mesh.updateData(buffer)` — matches\n * `keepInRam:true` semantics on BJS `GaussianSplattingMesh`. */\nexport function createGaussianSplattingMesh(engine: EngineContext, name: string, geom: SplatGeometry, worker: Worker, parsed: ParsedSplat): GaussianSplattingMesh {\n const device = engine._device;\n const queue = device.queue;\n const { textureWidth, textureHeight, vertexCount } = geom;\n\n // ── Textures (RGBA32F, one texel per splat) ──────────────────────\n const makeRgba32f = (data: Float32Array): { tex: GPUTexture; view: GPUTextureView } => {\n const tex = device.createTexture({\n size: [textureWidth, textureHeight],\n format: \"rgba32float\",\n usage: TU.TEXTURE_BINDING | TU.COPY_DST,\n });\n queue.writeTexture({ texture: tex }, data.buffer, { bytesPerRow: textureWidth * 16 }, { width: textureWidth, height: textureHeight });\n return { tex, view: tex.createView() };\n };\n const centers = makeRgba32f(geom.centersRGBA);\n const covA = makeRgba32f(geom.covARGBA);\n const covB = makeRgba32f(geom.covBRGBA);\n const colors = makeRgba32f(geom.colorsRGBA);\n\n const sampler = device.createSampler({\n magFilter: \"nearest\",\n minFilter: \"nearest\",\n addressModeU: \"clamp-to-edge\",\n addressModeV: \"clamp-to-edge\",\n });\n\n // ── Quad geometry (shared by all instances) ──────────────────────\n const quadBuffer = device.createBuffer({ size: 32, usage: BU.VERTEX, mappedAtCreation: true });\n new F32(quadBuffer.getMappedRange()).set([-2, -2, 2, -2, 2, 2, -2, 2]);\n quadBuffer.unmap();\n\n const indexBuffer = device.createBuffer({ size: 12, usage: BU.INDEX, mappedAtCreation: true });\n new U16(indexBuffer.getMappedRange()).set([0, 1, 2, 0, 2, 3]);\n indexBuffer.unmap();\n\n // ── Instance buffer: identity splatIndex until the first sort lands. ──\n const splatIndexCpu = new F32(vertexCount);\n for (let i = 0; i < vertexCount; i++) {\n splatIndexCpu[i] = i;\n }\n const splatIndexBuffer = device.createBuffer({\n size: splatIndexCpu.byteLength,\n usage: BU.VERTEX | BU.COPY_DST,\n });\n queue.writeBuffer(splatIndexBuffer, 0, splatIndexCpu.buffer, 0, splatIndexCpu.byteLength);\n\n // ── First-sort gate ──────────────────────────────────────────────\n let firstResolve: (() => void) | null = null;\n const firstSortReady = new Promise<void>((res) => {\n firstResolve = res;\n });\n\n // ── Retained source buffer (for splatsData + updateData) ─────────\n let retainedSplatsData = parsed.data;\n\n // ── Compose mesh ─────────────────────────────────────────────────\n // `shDegree` comes from the parser (0 means \"no view-dependent SH\").\n // The SH attacher is dynamic-imported when needed and patches `_gs` in place,\n // keeping SH-specific code out of static splat scenes.\n const mesh = {\n _kind: \"gs-mesh\",\n name,\n vertexCount,\n textureWidth,\n textureHeight,\n boundMin: geom.boundMin.slice() as [number, number, number],\n boundMax: geom.boundMax.slice() as [number, number, number],\n shDegree: parsed.shDegree ?? 0,\n _worker: worker,\n _depthMix: new BigInt64Array(vertexCount),\n _sortWorldMatrix: new F32(16),\n _sortCameraForward: new F32(3),\n _sortCameraPosition: new F32(3),\n _canPostToWorker: true,\n firstSortReady,\n _firstSortResolve: firstResolve,\n _gs: {\n _centersTex: centers.tex,\n _centersView: centers.view,\n _covATex: covA.tex,\n _covAView: covA.view,\n _covBTex: covB.tex,\n _covBView: covB.view,\n _colorsTex: colors.tex,\n _colorsView: colors.view,\n _sampler: sampler,\n _quadBuffer: quadBuffer,\n _indexBuffer: indexBuffer,\n _splatIndexBuffer: splatIndexBuffer,\n _splatIndexCpu: splatIndexCpu,\n _shTextures: null,\n _shViews: null,\n },\n } as unknown as GaussianSplattingMesh;\n\n // splatsData getter — always returns the most-recently-loaded raw row buffer.\n Object.defineProperty(mesh, \"splatsData\", {\n get: () => retainedSplatsData,\n });\n\n // updateData: replace splat data in place. Vertex count must match.\n (mesh as { updateData: (b: ArrayBuffer) => void }).updateData = (newBuffer: ArrayBuffer): void => {\n const newGeom = buildSplatGeometry(newBuffer);\n if (newGeom.vertexCount !== mesh.vertexCount) {\n throw Error(\"GS vertex count mismatch\");\n }\n const gs = mesh._gs;\n const writeTex = (tex: GPUTexture, data: Float32Array): void => {\n queue.writeTexture({ texture: tex }, data.buffer, { bytesPerRow: newGeom.textureWidth * 16 }, { width: newGeom.textureWidth, height: newGeom.textureHeight });\n };\n writeTex(gs._centersTex, newGeom.centersRGBA);\n writeTex(gs._covATex, newGeom.covARGBA);\n writeTex(gs._covBTex, newGeom.covBRGBA);\n writeTex(gs._colorsTex, newGeom.colorsRGBA);\n\n mesh.boundMin = newGeom.boundMin.slice() as [number, number, number];\n mesh.boundMax = newGeom.boundMax.slice() as [number, number, number];\n\n // Re-init the worker with the new positions buffer. The previous\n // positions array was transferred and is gone on this side, so we\n // hand the worker a fresh transferable. If a sort is currently in\n // flight, the message queues behind it and the worker swaps to the\n // new positions when it lands.\n mesh._worker.postMessage({ p: newGeom.positions, n: newGeom.vertexCount }, [newGeom.positions.buffer]);\n // Force a re-sort on the next eligible frame by zeroing the snapshot\n // state — any real camera/world state will differ by more than the\n // gating threshold. (`_canPostToWorker` is left untouched — it's owned\n // by the worker protocol and toggling it here would risk double-posting\n // a `_depthMix` buffer that's still detached on the worker side.)\n mesh._sortWorldMatrix.fill(0);\n mesh._sortCameraForward.fill(0);\n mesh._sortCameraPosition.fill(0);\n\n retainedSplatsData = newBuffer;\n };\n\n initSplatTransform(mesh);\n\n // Ship the positions buffer to the worker once. After this `geom.positions`\n // is detached on this side — that's fine, we never need it again.\n worker.postMessage({ p: geom.positions, n: vertexCount }, [geom.positions.buffer]);\n\n worker.onmessage = (e: MessageEvent) => {\n const data = e.data as { d: BigInt64Array };\n mesh._depthMix = data.d;\n const indices = new U32(data.d.buffer);\n const cpu = mesh._gs._splatIndexCpu;\n for (let j = 0; j < mesh.vertexCount; j++) {\n cpu[j] = indices[2 * j]!;\n }\n queue.writeBuffer(mesh._gs._splatIndexBuffer, 0, cpu.buffer, 0, cpu.byteLength);\n mesh._canPostToWorker = true;\n if (mesh._firstSortResolve) {\n mesh._firstSortResolve();\n mesh._firstSortResolve = null;\n }\n };\n\n return mesh;\n}\n\n/** Free all GPU + worker resources owned by a GS mesh. */\nexport function disposeGaussianSplattingMesh(mesh: GaussianSplattingMesh): void {\n const gs = mesh._gs;\n [gs._centersTex, gs._covATex, gs._covBTex, gs._colorsTex, gs._quadBuffer, gs._indexBuffer, gs._splatIndexBuffer, ...(gs._shTextures ?? [])].forEach((resource) =>\n resource.destroy()\n );\n mesh._worker.terminate();\n}\n\n// Same TRS + worldMatrix wiring as `initMeshTransform` in mesh/mesh.ts but\n// duplicated here to avoid pulling the Mesh module into the GS code path.\nfunction initSplatTransform(node: GaussianSplattingMesh): void {\n const wm = createWorldMatrixState(() => {\n const p = node.position,\n rq = node.rotationQuaternion,\n s = node.scaling;\n const isIdentity = p.x === 0 && p.y === 0 && p.z === 0 && rq.x === 0 && rq.y === 0 && rq.z === 0 && rq.w === 1 && s.x === 1 && s.y === 1 && s.z === 1;\n return isIdentity ? mat4Identity() : mat4Compose(p.x, p.y, p.z, rq.x, rq.y, rq.z, rq.w, s.x, s.y, s.z);\n });\n const onDirty = (): void => wm.markLocalDirty();\n const [iqx, iqy, iqz, iqw] = eulerToQuat(0, 0, 0);\n const rq = new ObservableQuat(iqx, iqy, iqz, iqw, onDirty);\n (node as unknown as Record<string, unknown>).rotationQuaternion = rq;\n (node as unknown as Record<string, unknown>).rotation = createEulerProxy(rq);\n (node as unknown as Record<string, unknown>).position = new ObservableVec3(0, 0, 0, onDirty);\n (node as unknown as Record<string, unknown>).scaling = new ObservableVec3(1, 1, 1, onDirty);\n (node as unknown as Record<string, unknown>).children = [];\n\n Object.defineProperty(node, \"parent\", {\n get() {\n return wm.parent;\n },\n set(v) {\n wm.parent = v;\n },\n configurable: true,\n enumerable: true,\n });\n Object.defineProperty(node, \"worldMatrix\", {\n get(): Mat4 {\n return wm.getWorldMatrix();\n },\n configurable: true,\n enumerable: false,\n });\n Object.defineProperty(node, \"worldMatrixVersion\", {\n get(): number {\n return wm.getWorldMatrixVersion();\n },\n configurable: true,\n enumerable: false,\n });\n // Tag so children parented to this splat mesh get push invalidation.\n attachWorldMatrixState(node, wm);\n}\n"],"names":["rq"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"gaussian-splatting-mesh.js","sources":["../../../../src/mesh/GaussianSplatting/gaussian-splatting-mesh.ts"],"sourcesContent":["/** GaussianSplattingMesh — pure data describing a renderable Gaussian splat cloud.\n *\n * Plain state with TRS + parent + children (`SceneNode`-shaped, no methods),\n * plus splat-specific GPU resources and a worker handle for back-to-front sort.\n * All behaviour lives in standalone functions in this file or in\n * `gaussian-splatting-pipeline.ts`.\n *\n * Renderable + dispose hook registration is performed by `loadSplat()` via\n * `attachGaussianSplattingMesh()` — scene-core stays GS-agnostic so non-GS\n * scenes never pull in this pipeline. */\n\nimport { F32, U32, U16 } from \"../../engine/typed-arrays.js\";\nimport { TU, BU } from \"../../engine/gpu-flags.js\";\nimport type { SceneNode } from \"../../scene/scene-node.js\";\nimport type { EngineContext } from \"../../engine/engine.js\";\nimport type { Mat4 } from \"../../math/types.js\";\nimport { ObservableVec3 } from \"../../math/observable-vec3.js\";\nimport { ObservableQuat } from \"../../math/observable-quat.js\";\nimport { createWorldMatrixState, attachWorldMatrixState, composeTrsLocalMatrix } from \"../../scene/world-matrix-state.js\";\nimport { eulerToQuat, createEulerProxy } from \"../../scene/scene-node.js\";\nimport { buildSplatGeometry, type SplatGeometry, type ParsedSplat } from \"../../loader-splat/splat-data.js\";\n\n/** Names of the four WGSL slots a `GsShaderFragment` may inject into the\n * Gaussian-splat fragment shader. Markers in the WGSL source look like\n * `\\/*GS_FRAGMENT_MAIN_END*\\/` — valid comments when no plugin is present. */\nexport type GsFragmentSlot = \"GS_FRAGMENT_DEFINITIONS\" | \"GS_FRAGMENT_MAIN_BEGIN\" | \"GS_FRAGMENT_BEFORE_FRAGCOLOR\" | \"GS_FRAGMENT_MAIN_END\";\n\n/** Data-only descriptor of a GS shader plugin. Lite equivalent of a BJS\n * `MaterialPluginBase`: snippets get spliced into the four GS fragment slots. */\nexport interface GsShaderFragment {\n readonly id: string;\n readonly fragmentSlots?: Partial<Record<GsFragmentSlot, string>>;\n readonly helperFunctions?: string;\n}\n\n/** Per-mesh GPU resources owned by a GaussianSplattingMesh. */\nexport interface GaussianSplattingGpu {\n /** @internal */\n _centersTex: GPUTexture;\n /** @internal */\n _centersView: GPUTextureView;\n /** @internal */\n _covATex: GPUTexture;\n /** @internal */\n _covAView: GPUTextureView;\n /** @internal */\n _covBTex: GPUTexture;\n /** @internal */\n _covBView: GPUTextureView;\n /** @internal */\n _colorsTex: GPUTexture;\n /** @internal */\n _colorsView: GPUTextureView;\n /** @internal */\n _sampler: GPUSampler;\n /** @internal Quad vertex buffer (4 vec2 corners). */\n _quadBuffer: GPUBuffer;\n /** @internal Quad index buffer (uint16 [0,1,2,0,2,3]). */\n _indexBuffer: GPUBuffer;\n /** @internal Per-instance splatIndex (Float32 × vertexCount), back-to-front order. */\n _splatIndexBuffer: GPUBuffer;\n /** @internal CPU-side scratch matching `splatIndexBuffer`. */\n _splatIndexCpu: Float32Array;\n /** Packed view-dependent SH textures (1..5 rgba32uint), `null` when\n * the cloud has no SH data. Layout: 16 bytes per splat per texture. */\n /** @internal */\n _shTextures: GPUTexture[] | null;\n /** @internal */\n _shViews: GPUTextureView[] | null;\n}\n\n/** Public Gaussian-splatting mesh handle. `_kind` is a brand so consumers can\n * narrow on it; the renderable is wired up by `loadSplat()` directly. */\nexport interface GaussianSplattingMesh extends SceneNode {\n /** @internal */\n readonly _kind: \"gs-mesh\";\n /** Number of splats in the cloud. */\n readonly vertexCount: number;\n /** RGBA32F texture dimensions used for centers/covA/covB/colors. */\n readonly textureWidth: number;\n readonly textureHeight: number;\n /** World-space AABB across all splat centres (for camera framing). */\n boundMin: [number, number, number];\n boundMax: [number, number, number];\n /** Spherical-harmonics degree (0 means no view-dependent SH). Set at load\n * time and immutable afterwards — `updateData` rejects a degree change. */\n readonly shDegree: number;\n /** @internal Sort worker. Owned by the mesh; terminated on dispose. */\n _worker: Worker;\n /** @internal Scratch for the worker round-trip. high-32 = depth, low-32 = index. */\n _depthMix: BigInt64Array;\n /** Snapshot of the world matrix posted to the worker on the last sort.\n * Used to decide whether a re-sort is needed this frame. Mirrors BJS\n * `ICameraViewInfo.sortWorldMatrix`. */\n /** @internal */\n _sortWorldMatrix: Float32Array;\n /** @internal Snapshot of the camera-forward vector (`view[2,6,10]`) on the last sort. */\n _sortCameraForward: Float32Array;\n /** @internal Snapshot of the camera world-space position on the last sort. */\n _sortCameraPosition: Float32Array;\n /** @internal True between postMessage and onmessage; throttles re-sort requests. */\n _canPostToWorker: boolean;\n /** Resolves on the first sort completion. The lab scene awaits this\n * before flagging `dataset.ready`. */\n readonly firstSortReady: Promise<void>;\n /** @internal Resolver for {@link firstSortReady}; called once the first sort completes, then cleared to null. */\n _firstSortResolve: (() => void) | null;\n /** @internal GPU resources, populated by `createGaussianSplattingMesh`. */\n _gs: GaussianSplattingGpu;\n /** Raw 32-byte/splat row buffer. Mirrors BJS `splatsData` (with\n * `keepInRam:true`) — exposed for inspection + `updateData` round-trips. */\n readonly splatsData: ArrayBuffer;\n /** Replace the splat data in place. Re-uploads centres / covariance /\n * colour textures, re-posts positions to the sort worker, and updates the\n * AABB. Vertex count must match the original buffer. Mirrors BJS\n * `GaussianSplattingMesh.updateData(buffer, _sh, opts)`. */\n updateData(splatBuffer: ArrayBuffer): void;\n}\n\n/** Create a GaussianSplattingMesh from a parsed splat asset. Uploads textures +\n * initial identity splat-index buffer, spawns the sort worker, and (when the\n * asset includes SH coefficients) packs SH into rgba32uint textures.\n *\n * `parsed.data` is retained on the mesh as `splatsData` so callers can mutate\n * the row data and round-trip it via `mesh.updateData(buffer)` — matches\n * `keepInRam:true` semantics on BJS `GaussianSplattingMesh`. */\nexport function createGaussianSplattingMesh(engine: EngineContext, name: string, geom: SplatGeometry, worker: Worker, parsed: ParsedSplat): GaussianSplattingMesh {\n const device = engine._device;\n const queue = device.queue;\n const { textureWidth, textureHeight, vertexCount } = geom;\n\n // ── Textures (RGBA32F, one texel per splat) ──────────────────────\n const makeRgba32f = (data: Float32Array): { tex: GPUTexture; view: GPUTextureView } => {\n const tex = device.createTexture({\n size: [textureWidth, textureHeight],\n format: \"rgba32float\",\n usage: TU.TEXTURE_BINDING | TU.COPY_DST,\n });\n queue.writeTexture({ texture: tex }, data.buffer, { bytesPerRow: textureWidth * 16 }, { width: textureWidth, height: textureHeight });\n return { tex, view: tex.createView() };\n };\n const centers = makeRgba32f(geom.centersRGBA);\n const covA = makeRgba32f(geom.covARGBA);\n const covB = makeRgba32f(geom.covBRGBA);\n const colors = makeRgba32f(geom.colorsRGBA);\n\n const sampler = device.createSampler({\n magFilter: \"nearest\",\n minFilter: \"nearest\",\n addressModeU: \"clamp-to-edge\",\n addressModeV: \"clamp-to-edge\",\n });\n\n // ── Quad geometry (shared by all instances) ──────────────────────\n const quadBuffer = device.createBuffer({ size: 32, usage: BU.VERTEX, mappedAtCreation: true });\n new F32(quadBuffer.getMappedRange()).set([-2, -2, 2, -2, 2, 2, -2, 2]);\n quadBuffer.unmap();\n\n const indexBuffer = device.createBuffer({ size: 12, usage: BU.INDEX, mappedAtCreation: true });\n new U16(indexBuffer.getMappedRange()).set([0, 1, 2, 0, 2, 3]);\n indexBuffer.unmap();\n\n // ── Instance buffer: identity splatIndex until the first sort lands. ──\n const splatIndexCpu = new F32(vertexCount);\n for (let i = 0; i < vertexCount; i++) {\n splatIndexCpu[i] = i;\n }\n const splatIndexBuffer = device.createBuffer({\n size: splatIndexCpu.byteLength,\n usage: BU.VERTEX | BU.COPY_DST,\n });\n queue.writeBuffer(splatIndexBuffer, 0, splatIndexCpu.buffer, 0, splatIndexCpu.byteLength);\n\n // ── First-sort gate ──────────────────────────────────────────────\n let firstResolve: (() => void) | null = null;\n const firstSortReady = new Promise<void>((res) => {\n firstResolve = res;\n });\n\n // ── Retained source buffer (for splatsData + updateData) ─────────\n let retainedSplatsData = parsed.data;\n\n // ── Compose mesh ─────────────────────────────────────────────────\n // `shDegree` comes from the parser (0 means \"no view-dependent SH\").\n // The SH attacher is dynamic-imported when needed and patches `_gs` in place,\n // keeping SH-specific code out of static splat scenes.\n const mesh = {\n _kind: \"gs-mesh\",\n name,\n vertexCount,\n textureWidth,\n textureHeight,\n boundMin: geom.boundMin.slice() as [number, number, number],\n boundMax: geom.boundMax.slice() as [number, number, number],\n shDegree: parsed.shDegree ?? 0,\n _worker: worker,\n _depthMix: new BigInt64Array(vertexCount),\n _sortWorldMatrix: new F32(16),\n _sortCameraForward: new F32(3),\n _sortCameraPosition: new F32(3),\n _canPostToWorker: true,\n firstSortReady,\n _firstSortResolve: firstResolve,\n _gs: {\n _centersTex: centers.tex,\n _centersView: centers.view,\n _covATex: covA.tex,\n _covAView: covA.view,\n _covBTex: covB.tex,\n _covBView: covB.view,\n _colorsTex: colors.tex,\n _colorsView: colors.view,\n _sampler: sampler,\n _quadBuffer: quadBuffer,\n _indexBuffer: indexBuffer,\n _splatIndexBuffer: splatIndexBuffer,\n _splatIndexCpu: splatIndexCpu,\n _shTextures: null,\n _shViews: null,\n },\n } as unknown as GaussianSplattingMesh;\n\n // splatsData getter — always returns the most-recently-loaded raw row buffer.\n Object.defineProperty(mesh, \"splatsData\", {\n get: () => retainedSplatsData,\n });\n\n // updateData: replace splat data in place. Vertex count must match.\n (mesh as { updateData: (b: ArrayBuffer) => void }).updateData = (newBuffer: ArrayBuffer): void => {\n const newGeom = buildSplatGeometry(newBuffer);\n if (newGeom.vertexCount !== mesh.vertexCount) {\n throw Error(\"GS vertex count mismatch\");\n }\n const gs = mesh._gs;\n const writeTex = (tex: GPUTexture, data: Float32Array): void => {\n queue.writeTexture({ texture: tex }, data.buffer, { bytesPerRow: newGeom.textureWidth * 16 }, { width: newGeom.textureWidth, height: newGeom.textureHeight });\n };\n writeTex(gs._centersTex, newGeom.centersRGBA);\n writeTex(gs._covATex, newGeom.covARGBA);\n writeTex(gs._covBTex, newGeom.covBRGBA);\n writeTex(gs._colorsTex, newGeom.colorsRGBA);\n\n mesh.boundMin = newGeom.boundMin.slice() as [number, number, number];\n mesh.boundMax = newGeom.boundMax.slice() as [number, number, number];\n\n // Re-init the worker with the new positions buffer. The previous\n // positions array was transferred and is gone on this side, so we\n // hand the worker a fresh transferable. If a sort is currently in\n // flight, the message queues behind it and the worker swaps to the\n // new positions when it lands.\n mesh._worker.postMessage({ p: newGeom.positions, n: newGeom.vertexCount }, [newGeom.positions.buffer]);\n // Force a re-sort on the next eligible frame by zeroing the snapshot\n // state — any real camera/world state will differ by more than the\n // gating threshold. (`_canPostToWorker` is left untouched — it's owned\n // by the worker protocol and toggling it here would risk double-posting\n // a `_depthMix` buffer that's still detached on the worker side.)\n mesh._sortWorldMatrix.fill(0);\n mesh._sortCameraForward.fill(0);\n mesh._sortCameraPosition.fill(0);\n\n retainedSplatsData = newBuffer;\n };\n\n initSplatTransform(mesh);\n\n // Ship the positions buffer to the worker once. After this `geom.positions`\n // is detached on this side — that's fine, we never need it again.\n worker.postMessage({ p: geom.positions, n: vertexCount }, [geom.positions.buffer]);\n\n worker.onmessage = (e: MessageEvent) => {\n const data = e.data as { d: BigInt64Array };\n mesh._depthMix = data.d;\n const indices = new U32(data.d.buffer);\n const cpu = mesh._gs._splatIndexCpu;\n for (let j = 0; j < mesh.vertexCount; j++) {\n cpu[j] = indices[2 * j]!;\n }\n queue.writeBuffer(mesh._gs._splatIndexBuffer, 0, cpu.buffer, 0, cpu.byteLength);\n mesh._canPostToWorker = true;\n if (mesh._firstSortResolve) {\n mesh._firstSortResolve();\n mesh._firstSortResolve = null;\n }\n };\n\n return mesh;\n}\n\n/** Free all GPU + worker resources owned by a GS mesh. */\nexport function disposeGaussianSplattingMesh(mesh: GaussianSplattingMesh): void {\n const gs = mesh._gs;\n [gs._centersTex, gs._covATex, gs._covBTex, gs._colorsTex, gs._quadBuffer, gs._indexBuffer, gs._splatIndexBuffer, ...(gs._shTextures ?? [])].forEach((resource) =>\n resource.destroy()\n );\n mesh._worker.terminate();\n}\n\n// Same TRS + worldMatrix wiring as `initMeshTransform` in mesh/mesh.ts but\n// duplicated here to avoid pulling the Mesh module into the GS code path.\nfunction initSplatTransform(node: GaussianSplattingMesh): void {\n const wm = createWorldMatrixState(() => composeTrsLocalMatrix(node.position, node.rotationQuaternion, node.scaling));\n const onDirty = (): void => wm.markLocalDirty();\n const [iqx, iqy, iqz, iqw] = eulerToQuat(0, 0, 0);\n const rq = new ObservableQuat(iqx, iqy, iqz, iqw, onDirty);\n (node as unknown as Record<string, unknown>).rotationQuaternion = rq;\n (node as unknown as Record<string, unknown>).rotation = createEulerProxy(rq);\n (node as unknown as Record<string, unknown>).position = new ObservableVec3(0, 0, 0, onDirty);\n (node as unknown as Record<string, unknown>).scaling = new ObservableVec3(1, 1, 1, onDirty);\n (node as unknown as Record<string, unknown>).children = [];\n\n Object.defineProperty(node, \"parent\", {\n get() {\n return wm.parent;\n },\n set(v) {\n wm.parent = v;\n },\n configurable: true,\n enumerable: true,\n });\n Object.defineProperty(node, \"worldMatrix\", {\n get(): Mat4 {\n return wm.getWorldMatrix();\n },\n configurable: true,\n enumerable: false,\n });\n Object.defineProperty(node, \"worldMatrixVersion\", {\n get(): number {\n return wm.getWorldMatrixVersion();\n },\n configurable: true,\n enumerable: false,\n });\n // Tag so children parented to this splat mesh get push invalidation.\n attachWorldMatrixState(node, wm);\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
function getMeshGeometry(mesh) {
|
|
2
|
+
const positions = mesh._cpuPositions;
|
|
3
|
+
const normals = mesh._cpuNormals;
|
|
4
|
+
const indices = mesh._cpuIndices;
|
|
5
|
+
if (!positions || !normals || !indices) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
const uvs = mesh._cpuUvs;
|
|
9
|
+
const uvs2 = mesh._cpuUv2s;
|
|
10
|
+
const tangents = mesh._cpuTangents;
|
|
11
|
+
const colors = mesh._cpuColors;
|
|
12
|
+
return {
|
|
13
|
+
positions: positions.slice(),
|
|
14
|
+
normals: normals.slice(),
|
|
15
|
+
indices: indices.slice(),
|
|
16
|
+
...uvs ? { uvs: uvs.slice() } : {},
|
|
17
|
+
...uvs2 ? { uvs2: uvs2.slice() } : {},
|
|
18
|
+
...tangents ? { tangents: tangents.slice() } : {},
|
|
19
|
+
...colors ? { colors: colors.slice() } : {}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { getMeshGeometry };
|
|
24
|
+
//# sourceMappingURL=get-mesh-geometry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-mesh-geometry.js","sources":["../../../src/mesh/get-mesh-geometry.ts"],"sourcesContent":["import type { Mesh } from \"./mesh.js\";\n\n/** Return caller-owned copies of the CPU geometry retained by a mesh.\n * Returns `null` when positions, normals, or indices are unavailable. */\nexport function getMeshGeometry(mesh: Mesh): {\n positions: Float32Array;\n normals: Float32Array;\n indices: Uint32Array;\n uvs?: Float32Array;\n uvs2?: Float32Array;\n tangents?: Float32Array;\n colors?: Float32Array;\n} | null {\n const positions = mesh._cpuPositions;\n const normals = mesh._cpuNormals;\n const indices = mesh._cpuIndices;\n if (!positions || !normals || !indices) {\n return null;\n }\n\n const uvs = mesh._cpuUvs;\n const uvs2 = mesh._cpuUv2s;\n const tangents = mesh._cpuTangents;\n const colors = mesh._cpuColors;\n return {\n positions: positions.slice(),\n normals: normals.slice(),\n indices: indices.slice(),\n ...(uvs ? { uvs: uvs.slice() } : {}),\n ...(uvs2 ? { uvs2: uvs2.slice() } : {}),\n ...(tangents ? { tangents: tangents.slice() } : {}),\n ...(colors ? { colors: colors.slice() } : {}),\n };\n}\n"],"names":[],"mappings":"AAIO,SAAS,gBAAgB,IAAA,EAQvB;AACL,EAAA,MAAM,YAAY,IAAA,CAAK,aAAA;AACvB,EAAA,MAAM,UAAU,IAAA,CAAK,WAAA;AACrB,EAAA,MAAM,UAAU,IAAA,CAAK,WAAA;AACrB,EAAA,IAAI,CAAC,SAAA,IAAa,CAAC,OAAA,IAAW,CAAC,OAAA,EAAS;AACpC,IAAA,OAAO,IAAA;AAAA,EACX;AAEA,EAAA,MAAM,MAAM,IAAA,CAAK,OAAA;AACjB,EAAA,MAAM,OAAO,IAAA,CAAK,QAAA;AAClB,EAAA,MAAM,WAAW,IAAA,CAAK,YAAA;AACtB,EAAA,MAAM,SAAS,IAAA,CAAK,UAAA;AACpB,EAAA,OAAO;AAAA,IACH,SAAA,EAAW,UAAU,KAAA,EAAM;AAAA,IAC3B,OAAA,EAAS,QAAQ,KAAA,EAAM;AAAA,IACvB,OAAA,EAAS,QAAQ,KAAA,EAAM;AAAA,IACvB,GAAI,MAAM,EAAE,GAAA,EAAK,IAAI,KAAA,EAAM,KAAM,EAAC;AAAA,IAClC,GAAI,OAAO,EAAE,IAAA,EAAM,KAAK,KAAA,EAAM,KAAM,EAAC;AAAA,IACrC,GAAI,WAAW,EAAE,QAAA,EAAU,SAAS,KAAA,EAAM,KAAM,EAAC;AAAA,IACjD,GAAI,SAAS,EAAE,MAAA,EAAQ,OAAO,KAAA,EAAM,KAAM;AAAC,GAC/C;AACJ;;;;"}
|
package/lib/mesh/mesh-dispose.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { release } from '../resource/ref-count.js';
|
|
2
|
+
import { _detachThinInstanceLodMesh } from './thin-instance.js';
|
|
2
3
|
|
|
3
4
|
function disposeMeshGpu(mesh) {
|
|
4
5
|
const g = mesh._gpu;
|
|
@@ -13,6 +14,7 @@ function disposeMeshGpu(mesh) {
|
|
|
13
14
|
}
|
|
14
15
|
const ti = mesh.thinInstances;
|
|
15
16
|
if (ti && release(ti)) {
|
|
17
|
+
_detachThinInstanceLodMesh(mesh);
|
|
16
18
|
ti._gpuBuffer?.destroy();
|
|
17
19
|
ti._colorGpuBuffer?.destroy();
|
|
18
20
|
ti._drawArgsBuffer?.destroy();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mesh-dispose.js","sources":["../../../src/mesh/mesh-dispose.ts"],"sourcesContent":["import type { Mesh } from \"./mesh.js\";\nimport { release } from \"../resource/ref-count.js\";\n\n/** Destroy all GPU resources owned by a mesh (vertex buffers, skeleton, morph targets).\n * `_gpu`/`skeleton`/`morphTargets`/`thinInstances` may be SHARED with a clone made via\n * `cloneTransformNode` (see resource/ref-count.ts) — each resource is only actually\n * destroyed once its last owning mesh releases it, so a clone's still-in-use buffers\n * are never freed out from under it (and never double-freed once both are disposed). */\nexport function disposeMeshGpu(mesh: Mesh): void {\n const g = mesh._gpu;\n if (release(g)) {\n g.positionBuffer.destroy();\n g.normalBuffer.destroy();\n g.uvBuffer.destroy();\n g.indexBuffer.destroy();\n g.tangentBuffer?.destroy();\n g.uv2Buffer?.destroy();\n g.colorBuffer?.destroy();\n }\n const ti = mesh.thinInstances;\n if (ti && release(ti)) {\n ti._gpuBuffer?.destroy();\n ti._colorGpuBuffer?.destroy();\n ti._drawArgsBuffer?.destroy();\n }\n const sk = mesh.skeleton;\n if (sk && release(sk)) {\n sk.boneTexture.destroy();\n if (release(sk._skinBuffers)) {\n sk.jointsBuffer.destroy();\n sk.weightsBuffer.destroy();\n sk.joints1Buffer?.destroy();\n sk.weights1Buffer?.destroy();\n }\n }\n const vat = mesh.vat;\n if (vat && release(vat)) {\n vat.settingsBuffer.destroy();\n vat.instanceTexture?.destroy();\n if (release(vat._textureResource)) {\n vat._textureResource.texture.destroy();\n }\n if (release(vat._skinBuffers)) {\n vat.jointsBuffer.destroy();\n vat.weightsBuffer.destroy();\n vat.joints1Buffer?.destroy();\n vat.weights1Buffer?.destroy();\n }\n }\n const mt = mesh.morphTargets;\n if (mt && release(mt)) {\n mt.deltasBuffer.destroy();\n mt.weightsBuffer.destroy();\n }\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mesh-dispose.js","sources":["../../../src/mesh/mesh-dispose.ts"],"sourcesContent":["import type { Mesh } from \"./mesh.js\";\nimport { release } from \"../resource/ref-count.js\";\nimport { _detachThinInstanceLodMesh } from \"./thin-instance.js\";\n\n/** Destroy all GPU resources owned by a mesh (vertex buffers, skeleton, morph targets).\n * `_gpu`/`skeleton`/`morphTargets`/`thinInstances` may be SHARED with a clone made via\n * `cloneTransformNode` (see resource/ref-count.ts) — each resource is only actually\n * destroyed once its last owning mesh releases it, so a clone's still-in-use buffers\n * are never freed out from under it (and never double-freed once both are disposed). */\nexport function disposeMeshGpu(mesh: Mesh): void {\n const g = mesh._gpu;\n if (release(g)) {\n g.positionBuffer.destroy();\n g.normalBuffer.destroy();\n g.uvBuffer.destroy();\n g.indexBuffer.destroy();\n g.tangentBuffer?.destroy();\n g.uv2Buffer?.destroy();\n g.colorBuffer?.destroy();\n }\n const ti = mesh.thinInstances;\n if (ti && release(ti)) {\n _detachThinInstanceLodMesh(mesh);\n ti._gpuBuffer?.destroy();\n ti._colorGpuBuffer?.destroy();\n ti._drawArgsBuffer?.destroy();\n }\n const sk = mesh.skeleton;\n if (sk && release(sk)) {\n sk.boneTexture.destroy();\n if (release(sk._skinBuffers)) {\n sk.jointsBuffer.destroy();\n sk.weightsBuffer.destroy();\n sk.joints1Buffer?.destroy();\n sk.weights1Buffer?.destroy();\n }\n }\n const vat = mesh.vat;\n if (vat && release(vat)) {\n vat.settingsBuffer.destroy();\n vat.instanceTexture?.destroy();\n if (release(vat._textureResource)) {\n vat._textureResource.texture.destroy();\n }\n if (release(vat._skinBuffers)) {\n vat.jointsBuffer.destroy();\n vat.weightsBuffer.destroy();\n vat.joints1Buffer?.destroy();\n vat.weights1Buffer?.destroy();\n }\n }\n const mt = mesh.morphTargets;\n if (mt && release(mt)) {\n mt.deltasBuffer.destroy();\n mt.weightsBuffer.destroy();\n }\n}\n"],"names":[],"mappings":";;;AASO,SAAS,eAAe,IAAA,EAAkB;AAC7C,EAAA,MAAM,IAAI,IAAA,CAAK,IAAA;AACf,EAAA,IAAI,OAAA,CAAQ,CAAC,CAAA,EAAG;AACZ,IAAA,CAAA,CAAE,eAAe,OAAA,EAAQ;AACzB,IAAA,CAAA,CAAE,aAAa,OAAA,EAAQ;AACvB,IAAA,CAAA,CAAE,SAAS,OAAA,EAAQ;AACnB,IAAA,CAAA,CAAE,YAAY,OAAA,EAAQ;AACtB,IAAA,CAAA,CAAE,eAAe,OAAA,EAAQ;AACzB,IAAA,CAAA,CAAE,WAAW,OAAA,EAAQ;AACrB,IAAA,CAAA,CAAE,aAAa,OAAA,EAAQ;AAAA,EAC3B;AACA,EAAA,MAAM,KAAK,IAAA,CAAK,aAAA;AAChB,EAAA,IAAI,EAAA,IAAM,OAAA,CAAQ,EAAE,CAAA,EAAG;AACnB,IAAA,0BAAA,CAA2B,IAAI,CAAA;AAC/B,IAAA,EAAA,CAAG,YAAY,OAAA,EAAQ;AACvB,IAAA,EAAA,CAAG,iBAAiB,OAAA,EAAQ;AAC5B,IAAA,EAAA,CAAG,iBAAiB,OAAA,EAAQ;AAAA,EAChC;AACA,EAAA,MAAM,KAAK,IAAA,CAAK,QAAA;AAChB,EAAA,IAAI,EAAA,IAAM,OAAA,CAAQ,EAAE,CAAA,EAAG;AACnB,IAAA,EAAA,CAAG,YAAY,OAAA,EAAQ;AACvB,IAAA,IAAI,OAAA,CAAQ,EAAA,CAAG,YAAY,CAAA,EAAG;AAC1B,MAAA,EAAA,CAAG,aAAa,OAAA,EAAQ;AACxB,MAAA,EAAA,CAAG,cAAc,OAAA,EAAQ;AACzB,MAAA,EAAA,CAAG,eAAe,OAAA,EAAQ;AAC1B,MAAA,EAAA,CAAG,gBAAgB,OAAA,EAAQ;AAAA,IAC/B;AAAA,EACJ;AACA,EAAA,MAAM,MAAM,IAAA,CAAK,GAAA;AACjB,EAAA,IAAI,GAAA,IAAO,OAAA,CAAQ,GAAG,CAAA,EAAG;AACrB,IAAA,GAAA,CAAI,eAAe,OAAA,EAAQ;AAC3B,IAAA,GAAA,CAAI,iBAAiB,OAAA,EAAQ;AAC7B,IAAA,IAAI,OAAA,CAAQ,GAAA,CAAI,gBAAgB,CAAA,EAAG;AAC/B,MAAA,GAAA,CAAI,gBAAA,CAAiB,QAAQ,OAAA,EAAQ;AAAA,IACzC;AACA,IAAA,IAAI,OAAA,CAAQ,GAAA,CAAI,YAAY,CAAA,EAAG;AAC3B,MAAA,GAAA,CAAI,aAAa,OAAA,EAAQ;AACzB,MAAA,GAAA,CAAI,cAAc,OAAA,EAAQ;AAC1B,MAAA,GAAA,CAAI,eAAe,OAAA,EAAQ;AAC3B,MAAA,GAAA,CAAI,gBAAgB,OAAA,EAAQ;AAAA,IAChC;AAAA,EACJ;AACA,EAAA,MAAM,KAAK,IAAA,CAAK,YAAA;AAChB,EAAA,IAAI,EAAA,IAAM,OAAA,CAAQ,EAAE,CAAA,EAAG;AACnB,IAAA,EAAA,CAAG,aAAa,OAAA,EAAQ;AACxB,IAAA,EAAA,CAAG,cAAc,OAAA,EAAQ;AAAA,EAC7B;AACJ;;;;"}
|
package/lib/mesh/mesh.js
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
import { BU } from '../engine/gpu-flags.js';
|
|
2
2
|
import { createMappedBuffer } from '../resource/gpu-buffers.js';
|
|
3
|
-
import { mat4Compose } from '../math/mat4-compose.js';
|
|
4
|
-
import { mat4Identity } from '../math/mat4-identity.js';
|
|
5
3
|
import { ObservableVec3 } from '../math/observable-vec3.js';
|
|
6
4
|
import { ObservableQuat } from '../math/observable-quat.js';
|
|
7
|
-
import { createWorldMatrixState, attachWorldMatrixState } from '../scene/world-matrix-state.js';
|
|
5
|
+
import { createWorldMatrixState, attachWorldMatrixState, composeTrsLocalMatrix } from '../scene/world-matrix-state.js';
|
|
8
6
|
import { eulerToQuat, createEulerProxy } from '../scene/scene-node.js';
|
|
9
7
|
|
|
10
8
|
function initMeshTransform(mesh, px = 0, py = 0, pz = 0, rx = 0, ry = 0, rz = 0, sx = 1, sy = 1, sz = 1) {
|
|
11
|
-
const wm = createWorldMatrixState(() =>
|
|
12
|
-
const p = mesh.position, rq2 = mesh.rotationQuaternion, s = mesh.scaling;
|
|
13
|
-
const isIdentity = p.x === 0 && p.y === 0 && p.z === 0 && rq2.x === 0 && rq2.y === 0 && rq2.z === 0 && rq2.w === 1 && s.x === 1 && s.y === 1 && s.z === 1;
|
|
14
|
-
return isIdentity ? mat4Identity() : mat4Compose(p.x, p.y, p.z, rq2.x, rq2.y, rq2.z, rq2.w, s.x, s.y, s.z);
|
|
15
|
-
});
|
|
9
|
+
const wm = createWorldMatrixState(() => composeTrsLocalMatrix(mesh.position, mesh.rotationQuaternion, mesh.scaling));
|
|
16
10
|
const onWmDirty = () => wm.markLocalDirty();
|
|
17
11
|
const [iqx, iqy, iqz, iqw] = eulerToQuat(rx, ry, rz);
|
|
18
12
|
const rq = new ObservableQuat(iqx, iqy, iqz, iqw, onWmDirty);
|
package/lib/mesh/mesh.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mesh.js","sources":["../../../src/mesh/mesh.ts"],"sourcesContent":["/** High-level Mesh — position/rotation/scaling + material + GPU geometry.\n * Plain data (no scene reference). The scene collects meshes via addToScene(). */\n\nimport { BU } from \"../engine/gpu-flags.js\";\nimport type { EngineContext } from \"../engine/engine.js\";\nimport { createMappedBuffer } from \"../resource/gpu-buffers.js\";\nimport { mat4Compose } from \"../math/mat4-compose.js\";\nimport { mat4Identity } from \"../math/mat4-identity.js\";\nimport type { Material } from \"../material/material.js\";\nimport type { SkeletonData, MorphTargetData, VatData } from \"../animation/types.js\";\nimport { ObservableVec3 } from \"../math/observable-vec3.js\";\nimport { ObservableQuat } from \"../math/observable-quat.js\";\nimport type { ThinInstanceData } from \"./thin-instance.js\";\nimport { createWorldMatrixState, attachWorldMatrixState } from \"../scene/world-matrix-state.js\";\nimport type { SceneNode } from \"../scene/scene-node.js\";\nimport { eulerToQuat, createEulerProxy } from \"../scene/scene-node.js\";\n\n// ─── Mesh GPU Geometry ───────────────────────────────────────────────\n\n/** Per-attribute interleave override. When present, the attribute's GPU buffer\n * is a shared interleaved slice: the pipeline uses `_stride` as the vertex\n * buffer arrayStride and `_offset` as the layout `attributes[].offset`, while the\n * draw binds the buffer at offset 0 (mirrors Babylon.js WebGPU; a non-zero\n * setVertexBuffer bind offset corrupts vertex fetch on some AMD/Dawn paths).\n * Absent attributes use the canonical tight layout (own buffer, default stride,\n * offset 0) — byte-identical to non-interleaved meshes. */\nexport interface MeshVbAttr {\n /** @internal Vertex buffer arrayStride for this attribute's pipeline layout entry. */\n readonly _stride: number;\n /** @internal Byte offset within the shared buffer, encoded in the pipeline vertex\n * layout `attributes[].offset` (the buffer is bound at offset 0). */\n readonly _offset: number;\n}\n\n/** Optional per-attribute interleave layout. Only set for meshes that source one\n * or more attributes from a strided (interleaved) glTF bufferView. */\nexport interface MeshVbLayout {\n /** @internal */\n readonly _p?: MeshVbAttr;\n /** @internal */\n readonly _n?: MeshVbAttr;\n /** @internal */\n readonly _t?: MeshVbAttr;\n /** @internal */\n readonly _u?: MeshVbAttr;\n /** @internal */\n readonly _u2?: MeshVbAttr;\n /** @internal */\n readonly _c?: MeshVbAttr;\n}\n\n/** Opaque GPU geometry handle (user never touches these). */\nexport interface MeshGPU {\n readonly positionBuffer: GPUBuffer;\n readonly normalBuffer: GPUBuffer;\n readonly tangentBuffer?: GPUBuffer | null;\n readonly uvBuffer: GPUBuffer;\n readonly uv2Buffer?: GPUBuffer | null;\n readonly colorBuffer?: GPUBuffer | null;\n readonly hasUv?: boolean;\n readonly hasUv2?: boolean;\n readonly hasTangent?: boolean;\n readonly hasColor?: boolean;\n readonly indexBuffer: GPUBuffer;\n readonly indexCount: number;\n readonly indexFormat: GPUIndexFormat;\n /** @internal Reserved vertex capacity for grow-only procedural geometry. */\n _vertexCapacity?: number;\n /** @internal Reserved index capacity for grow-only procedural geometry. */\n _indexCapacity?: number;\n /** @internal Reused padded indices whose inactive tail is degenerate. */\n _indexScratch?: Uint32Array;\n /** @internal Per-attribute interleave layout. Undefined → all attributes tight (default). */\n readonly _vbLayout?: MeshVbLayout;\n /** @internal Precomputed pipeline cache-key suffix for this mesh's interleave layout.\n * Built once by the interleave module so the hot render path never assembles\n * it. Undefined → tight mesh (empty suffix, byte-identical pipeline key). */\n readonly _vbKey?: string;\n /** @internal Extra-owner count when shared with a clone via `cloneTransformNode` — see\n * resource/ref-count.ts. Absent/undefined means exactly one (implicit) owner. */\n _refCount?: number;\n}\n\n// ─── Mesh ────────────────────────────────────────────────────────────\n\n/** A renderable mesh — plain data with transform, material, and GPU geometry.\n * Works with both standard and PBR pipelines; routing is based on material type.\n * Extends SceneNode for the full TRS + parent + children hierarchy. */\nexport interface Mesh extends SceneNode {\n /** Unique ID from source file (e.g. .babylon). Used for light include/exclude filtering. */\n id?: string;\n material: Material;\n receiveShadows: boolean;\n /** World-space bounding box (set by loaders for camera framing). */\n boundMin?: [number, number, number];\n boundMax?: [number, number, number];\n /** Skeleton GPU data (skeletal animation). Type-only — no module dependency. */\n skeleton?: SkeletonData | null;\n /** Baked vertex-animation (VAT) GPU data — replaces live skinning so the mesh thin-instances.\n * Mutually exclusive with live `skeleton` skinning. Type-only — no module dependency. */\n vat?: VatData | null;\n /** Morph target GPU data. Type-only — no module dependency. */\n morphTargets?: MorphTargetData | null;\n /** User-controlled render order. Lower = drawn first within phase.\n * Only affects ordering within the opaque or transparent phase. */\n renderOrder?: number;\n /** On a transmission-enabled render task, draw this transparent mesh LAST — after the transmissive\n * surface and after the scene-colour grab — so it sits on top of the water/glass AND is excluded from\n * what that surface refracts (e.g. lily pads resting on water should not appear in the refraction).\n * Enable transmission on the task with `enableRenderTaskTransmission`; only transparent surfaces\n * (`needAlphaBlending`) are deferred. No effect on tasks without transmission. */\n renderOnTop?: boolean;\n /** Thin instance data (CPU-side). GPU buffer managed by render system. */\n thinInstances?: ThinInstanceData | null;\n /** When `false`, the GPU picker skips this mesh. Defaults to `true`\n * (undefined behaves as pickable). Mirrors BJS `AbstractMesh.isPickable`. */\n pickable?: boolean;\n // name, children, position, rotation, rotationQuaternion, scaling,\n // parent, worldMatrix, worldMatrixVersion — all inherited from SceneNode\n\n /** @internal */\n _gpu: MeshGPU;\n /** @internal */\n _cpuPositions?: Float32Array;\n /** @internal */\n _cpuNormals?: Float32Array;\n /** @internal */\n _cpuUvs?: Float32Array;\n /** @internal */\n _cpuUv2s?: Float32Array | null;\n /** @internal */\n _cpuTangents?: Float32Array | null;\n /** @internal */\n _cpuColors?: Float32Array | null;\n /** @internal */\n _cpuIndices?: Uint32Array;\n /** @internal */\n _cpuGpuIndices?: Uint16Array | Uint32Array;\n /** @internal */\n _cpuIndexFormat?: GPUIndexFormat;\n}\n\n/** Wire ObservableVec3/ObservableQuat TRS and children onto a partially-built mesh object.\n * Used by all mesh creation paths (factories, loaders). */\nexport function initMeshTransform(mesh: Mesh, px = 0, py = 0, pz = 0, rx = 0, ry = 0, rz = 0, sx = 1, sy = 1, sz = 1): void {\n const wm = createWorldMatrixState(() => {\n const p = mesh.position,\n rq = mesh.rotationQuaternion,\n s = mesh.scaling;\n const isIdentity = p.x === 0 && p.y === 0 && p.z === 0 && rq.x === 0 && rq.y === 0 && rq.z === 0 && rq.w === 1 && s.x === 1 && s.y === 1 && s.z === 1;\n return isIdentity ? mat4Identity() : mat4Compose(p.x, p.y, p.z, rq.x, rq.y, rq.z, rq.w, s.x, s.y, s.z);\n });\n const onWmDirty = () => wm.markLocalDirty();\n\n const [iqx, iqy, iqz, iqw] = eulerToQuat(rx, ry, rz);\n const rq = new ObservableQuat(iqx, iqy, iqz, iqw, onWmDirty);\n mesh.rotationQuaternion = rq;\n mesh.rotation = createEulerProxy(rq);\n mesh.position = new ObservableVec3(px, py, pz, onWmDirty);\n mesh.scaling = new ObservableVec3(sx, sy, sz, onWmDirty);\n\n if (!(mesh as unknown as Record<string, unknown>).children) {\n (mesh as unknown as Record<string, unknown>).children = [];\n }\n\n Object.defineProperty(mesh, \"parent\", {\n get() {\n return wm.parent;\n },\n set(v) {\n wm.parent = v;\n },\n configurable: true,\n enumerable: true,\n });\n Object.defineProperty(mesh, \"worldMatrix\", {\n get() {\n return wm.getWorldMatrix();\n },\n configurable: true,\n enumerable: false,\n });\n Object.defineProperty(mesh, \"worldMatrixVersion\", {\n get() {\n return wm.getWorldMatrixVersion();\n },\n configurable: true,\n enumerable: false,\n });\n attachWorldMatrixState(mesh, wm);\n}\n\n// ─── GPU Geometry Upload ─────────────────────────────────────────────\n\n/** Upload typed arrays to GPU buffers and return a MeshGPU handle. */\nexport function uploadMeshToGPU(\n engine: EngineContext,\n positions: Float32Array,\n normals: Float32Array,\n indices: Uint32Array,\n uvs?: Float32Array,\n uvs2?: Float32Array,\n tangents?: Float32Array,\n colors?: Float32Array\n): MeshGPU {\n const device = engine._device;\n const positionBuffer = createMappedBuffer(engine, positions, BU.VERTEX);\n const normalBuffer = createMappedBuffer(engine, normals, BU.VERTEX);\n const indexBuffer = createMappedBuffer(engine, indices, BU.INDEX);\n\n // UVs: use provided or create zero-filled buffer\n let uvBuffer: GPUBuffer;\n if (uvs && uvs.length > 0) {\n uvBuffer = createMappedBuffer(engine, uvs, BU.VERTEX);\n } else {\n uvBuffer = device.createBuffer({\n size: (positions.length / 3) * 8,\n usage: BU.VERTEX,\n mappedAtCreation: true,\n });\n uvBuffer.unmap();\n }\n\n // UV2: only create if provided\n let uv2Buffer: GPUBuffer | null = null;\n if (uvs2 && uvs2.length > 0) {\n uv2Buffer = createMappedBuffer(engine, uvs2, BU.VERTEX);\n }\n\n const tangentBuffer = tangents && tangents.length > 0 ? createMappedBuffer(engine, tangents, BU.VERTEX) : null;\n const colorBuffer = colors && colors.length > 0 ? createMappedBuffer(engine, colors, BU.VERTEX) : null;\n\n return {\n positionBuffer,\n normalBuffer,\n uvBuffer,\n uv2Buffer,\n tangentBuffer,\n colorBuffer,\n hasUv: !!uvs && uvs.length > 0,\n hasUv2: !!uvs2 && uvs2.length > 0,\n hasTangent: !!tangents && tangents.length > 0,\n hasColor: !!colors && colors.length > 0,\n indexBuffer,\n indexCount: indices.length,\n indexFormat: \"uint32\",\n };\n}\n"],"names":["rq"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mesh.js","sources":["../../../src/mesh/mesh.ts"],"sourcesContent":["/** High-level Mesh — position/rotation/scaling + material + GPU geometry.\n * Plain data (no scene reference). The scene collects meshes via addToScene(). */\n\nimport { BU } from \"../engine/gpu-flags.js\";\nimport type { EngineContext } from \"../engine/engine.js\";\nimport { createMappedBuffer } from \"../resource/gpu-buffers.js\";\nimport type { Material } from \"../material/material.js\";\nimport type { SkeletonData, MorphTargetData, VatData } from \"../animation/types.js\";\nimport { ObservableVec3 } from \"../math/observable-vec3.js\";\nimport { ObservableQuat } from \"../math/observable-quat.js\";\nimport type { ThinInstanceData } from \"./thin-instance.js\";\nimport { createWorldMatrixState, attachWorldMatrixState, composeTrsLocalMatrix } from \"../scene/world-matrix-state.js\";\nimport type { SceneNode } from \"../scene/scene-node.js\";\nimport { eulerToQuat, createEulerProxy } from \"../scene/scene-node.js\";\n\n// ─── Mesh GPU Geometry ───────────────────────────────────────────────\n\n/** Per-attribute interleave override. When present, the attribute's GPU buffer\n * is a shared interleaved slice: the pipeline uses `_stride` as the vertex\n * buffer arrayStride and `_offset` as the layout `attributes[].offset`, while the\n * draw binds the buffer at offset 0 (mirrors Babylon.js WebGPU; a non-zero\n * setVertexBuffer bind offset corrupts vertex fetch on some AMD/Dawn paths).\n * Absent attributes use the canonical tight layout (own buffer, default stride,\n * offset 0) — byte-identical to non-interleaved meshes. */\nexport interface MeshVbAttr {\n /** @internal Vertex buffer arrayStride for this attribute's pipeline layout entry. */\n readonly _stride: number;\n /** @internal Byte offset within the shared buffer, encoded in the pipeline vertex\n * layout `attributes[].offset` (the buffer is bound at offset 0). */\n readonly _offset: number;\n}\n\n/** Optional per-attribute interleave layout. Only set for meshes that source one\n * or more attributes from a strided (interleaved) glTF bufferView. */\nexport interface MeshVbLayout {\n /** @internal */\n readonly _p?: MeshVbAttr;\n /** @internal */\n readonly _n?: MeshVbAttr;\n /** @internal */\n readonly _t?: MeshVbAttr;\n /** @internal */\n readonly _u?: MeshVbAttr;\n /** @internal */\n readonly _u2?: MeshVbAttr;\n /** @internal */\n readonly _c?: MeshVbAttr;\n}\n\n/** Opaque GPU geometry handle (user never touches these). */\nexport interface MeshGPU {\n readonly positionBuffer: GPUBuffer;\n readonly normalBuffer: GPUBuffer;\n readonly tangentBuffer?: GPUBuffer | null;\n readonly uvBuffer: GPUBuffer;\n readonly uv2Buffer?: GPUBuffer | null;\n readonly colorBuffer?: GPUBuffer | null;\n readonly hasUv?: boolean;\n readonly hasUv2?: boolean;\n readonly hasTangent?: boolean;\n readonly hasColor?: boolean;\n readonly indexBuffer: GPUBuffer;\n readonly indexCount: number;\n readonly indexFormat: GPUIndexFormat;\n /** @internal Reserved vertex capacity for grow-only procedural geometry. */\n _vertexCapacity?: number;\n /** @internal Reserved index capacity for grow-only procedural geometry. */\n _indexCapacity?: number;\n /** @internal Reused padded indices whose inactive tail is degenerate. */\n _indexScratch?: Uint32Array;\n /** @internal Per-attribute interleave layout. Undefined → all attributes tight (default). */\n readonly _vbLayout?: MeshVbLayout;\n /** @internal Precomputed pipeline cache-key suffix for this mesh's interleave layout.\n * Built once by the interleave module so the hot render path never assembles\n * it. Undefined → tight mesh (empty suffix, byte-identical pipeline key). */\n readonly _vbKey?: string;\n /** @internal Extra-owner count when shared with a clone via `cloneTransformNode` — see\n * resource/ref-count.ts. Absent/undefined means exactly one (implicit) owner. */\n _refCount?: number;\n}\n\n// ─── Mesh ────────────────────────────────────────────────────────────\n\n/** A renderable mesh — plain data with transform, material, and GPU geometry.\n * Works with both standard and PBR pipelines; routing is based on material type.\n * Extends SceneNode for the full TRS + parent + children hierarchy. */\nexport interface Mesh extends SceneNode {\n /** Unique ID from source file (e.g. .babylon). Used for light include/exclude filtering. */\n id?: string;\n material: Material;\n receiveShadows: boolean;\n /** World-space bounding box (set by loaders for camera framing). */\n boundMin?: [number, number, number];\n boundMax?: [number, number, number];\n /** Skeleton GPU data (skeletal animation). Type-only — no module dependency. */\n skeleton?: SkeletonData | null;\n /** Baked vertex-animation (VAT) GPU data — replaces live skinning so the mesh thin-instances.\n * Mutually exclusive with live `skeleton` skinning. Type-only — no module dependency. */\n vat?: VatData | null;\n /** Morph target GPU data. Type-only — no module dependency. */\n morphTargets?: MorphTargetData | null;\n /** User-controlled render order. Lower = drawn first within phase.\n * Only affects ordering within the opaque or transparent phase. */\n renderOrder?: number;\n /** On a transmission-enabled render task, draw this transparent mesh LAST — after the transmissive\n * surface and after the scene-colour grab — so it sits on top of the water/glass AND is excluded from\n * what that surface refracts (e.g. lily pads resting on water should not appear in the refraction).\n * Enable transmission on the task with `enableRenderTaskTransmission`; only transparent surfaces\n * (`needAlphaBlending`) are deferred. No effect on tasks without transmission. */\n renderOnTop?: boolean;\n /** Thin instance data (CPU-side). GPU buffer managed by render system. */\n thinInstances?: ThinInstanceData | null;\n /** When `false`, the GPU picker skips this mesh. Defaults to `true`\n * (undefined behaves as pickable). Mirrors BJS `AbstractMesh.isPickable`. */\n pickable?: boolean;\n // name, children, position, rotation, rotationQuaternion, scaling,\n // parent, worldMatrix, worldMatrixVersion — all inherited from SceneNode\n\n /** @internal */\n _gpu: MeshGPU;\n /** @internal Reason cloning this mesh is currently forbidden. */\n _clone?: string;\n /** @internal Highest CSM cascade this mesh casts into; undefined means all cascades. */\n _shadowMaxCascade?: number;\n /** @internal */\n _cpuPositions?: Float32Array;\n /** @internal */\n _cpuNormals?: Float32Array;\n /** @internal */\n _cpuUvs?: Float32Array;\n /** @internal */\n _cpuUv2s?: Float32Array | null;\n /** @internal */\n _cpuTangents?: Float32Array | null;\n /** @internal */\n _cpuColors?: Float32Array | null;\n /** @internal */\n _cpuIndices?: Uint32Array;\n /** @internal */\n _cpuGpuIndices?: Uint16Array | Uint32Array;\n /** @internal */\n _cpuIndexFormat?: GPUIndexFormat;\n}\n\n/** Wire ObservableVec3/ObservableQuat TRS and children onto a partially-built mesh object.\n * Used by all mesh creation paths (factories, loaders). */\nexport function initMeshTransform(mesh: Mesh, px = 0, py = 0, pz = 0, rx = 0, ry = 0, rz = 0, sx = 1, sy = 1, sz = 1): void {\n const wm = createWorldMatrixState(() => composeTrsLocalMatrix(mesh.position, mesh.rotationQuaternion, mesh.scaling));\n const onWmDirty = () => wm.markLocalDirty();\n\n const [iqx, iqy, iqz, iqw] = eulerToQuat(rx, ry, rz);\n const rq = new ObservableQuat(iqx, iqy, iqz, iqw, onWmDirty);\n mesh.rotationQuaternion = rq;\n mesh.rotation = createEulerProxy(rq);\n mesh.position = new ObservableVec3(px, py, pz, onWmDirty);\n mesh.scaling = new ObservableVec3(sx, sy, sz, onWmDirty);\n\n if (!(mesh as unknown as Record<string, unknown>).children) {\n (mesh as unknown as Record<string, unknown>).children = [];\n }\n\n Object.defineProperty(mesh, \"parent\", {\n get() {\n return wm.parent;\n },\n set(v) {\n wm.parent = v;\n },\n configurable: true,\n enumerable: true,\n });\n Object.defineProperty(mesh, \"worldMatrix\", {\n get() {\n return wm.getWorldMatrix();\n },\n configurable: true,\n enumerable: false,\n });\n Object.defineProperty(mesh, \"worldMatrixVersion\", {\n get() {\n return wm.getWorldMatrixVersion();\n },\n configurable: true,\n enumerable: false,\n });\n attachWorldMatrixState(mesh, wm);\n}\n\n// ─── GPU Geometry Upload ─────────────────────────────────────────────\n\n/** Upload typed arrays to GPU buffers and return a MeshGPU handle. */\nexport function uploadMeshToGPU(\n engine: EngineContext,\n positions: Float32Array,\n normals: Float32Array,\n indices: Uint32Array,\n uvs?: Float32Array,\n uvs2?: Float32Array,\n tangents?: Float32Array,\n colors?: Float32Array\n): MeshGPU {\n const device = engine._device;\n const positionBuffer = createMappedBuffer(engine, positions, BU.VERTEX);\n const normalBuffer = createMappedBuffer(engine, normals, BU.VERTEX);\n const indexBuffer = createMappedBuffer(engine, indices, BU.INDEX);\n\n // UVs: use provided or create zero-filled buffer\n let uvBuffer: GPUBuffer;\n if (uvs && uvs.length > 0) {\n uvBuffer = createMappedBuffer(engine, uvs, BU.VERTEX);\n } else {\n uvBuffer = device.createBuffer({\n size: (positions.length / 3) * 8,\n usage: BU.VERTEX,\n mappedAtCreation: true,\n });\n uvBuffer.unmap();\n }\n\n // UV2: only create if provided\n let uv2Buffer: GPUBuffer | null = null;\n if (uvs2 && uvs2.length > 0) {\n uv2Buffer = createMappedBuffer(engine, uvs2, BU.VERTEX);\n }\n\n const tangentBuffer = tangents && tangents.length > 0 ? createMappedBuffer(engine, tangents, BU.VERTEX) : null;\n const colorBuffer = colors && colors.length > 0 ? createMappedBuffer(engine, colors, BU.VERTEX) : null;\n\n return {\n positionBuffer,\n normalBuffer,\n uvBuffer,\n uv2Buffer,\n tangentBuffer,\n colorBuffer,\n hasUv: !!uvs && uvs.length > 0,\n hasUv2: !!uvs2 && uvs2.length > 0,\n hasTangent: !!tangents && tangents.length > 0,\n hasColor: !!colors && colors.length > 0,\n indexBuffer,\n indexCount: indices.length,\n indexFormat: \"uint32\",\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,18 @@
|
|
|
1
|
-
import { createTiCullState, destroyTiCullState, getComputeDispatchBatch, prepareTiCull } from './thin-instance-gpu-culling.js';
|
|
1
|
+
import { createTiCullState, destroyTiCullState, getComputeDispatchBatch, prepareTiCull, publishTiLodBucket } from './thin-instance-gpu-culling.js';
|
|
2
2
|
|
|
3
3
|
function tryBind(renderable, scene, mesh, engine, hasColor, excluded, baseUpdate, signature) {
|
|
4
4
|
const ti = mesh.thinInstances;
|
|
5
|
-
if (
|
|
5
|
+
if (!ti) {
|
|
6
|
+
return void 0;
|
|
7
|
+
}
|
|
8
|
+
if (ti._lodSource) {
|
|
9
|
+
if (excluded) {
|
|
10
|
+
throw new Error("Thin-instance LOD partners require an opaque, non-transmissive material");
|
|
11
|
+
}
|
|
12
|
+
renderable._direct = true;
|
|
13
|
+
return bindLodPartner(ti, signature, hasColor, baseUpdate);
|
|
14
|
+
}
|
|
15
|
+
if (excluded || !ti._gpuCullingEnabled) {
|
|
6
16
|
return void 0;
|
|
7
17
|
}
|
|
8
18
|
const holder = renderable;
|
|
@@ -25,9 +35,13 @@ function tryBind(renderable, scene, mesh, engine, hasColor, excluded, baseUpdate
|
|
|
25
35
|
_updateBatch: updateBatch,
|
|
26
36
|
update(context) {
|
|
27
37
|
baseUpdate?.(context);
|
|
28
|
-
const
|
|
38
|
+
const partner = ti._lodPartner ?? null;
|
|
39
|
+
const res = prepareTiCull(engine, state, mesh, mesh._gpu, ti, hasColor, context, updateBatch, partner);
|
|
29
40
|
binding.cullDrawBufs = res?.drawBuffers ?? null;
|
|
30
41
|
binding._args = res?.argsBuffer ?? null;
|
|
42
|
+
if (ti._lodBuckets) {
|
|
43
|
+
publishTiLodBucket(ti, signature, res);
|
|
44
|
+
}
|
|
31
45
|
},
|
|
32
46
|
draw(pass, indexCount, instanceCount) {
|
|
33
47
|
if (binding._args) {
|
|
@@ -41,6 +55,43 @@ function tryBind(renderable, scene, mesh, engine, hasColor, excluded, baseUpdate
|
|
|
41
55
|
};
|
|
42
56
|
return binding;
|
|
43
57
|
}
|
|
58
|
+
function bindLodPartner(ti, signature, hasColor, baseUpdate) {
|
|
59
|
+
const currentBucket = () => {
|
|
60
|
+
const bucket = ti._lodBuckets?.get(signature);
|
|
61
|
+
if (!bucket?.active) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
if (hasColor && !bucket.colorBuffer) {
|
|
65
|
+
throw new Error("Thin-instance LOD partner requires the source draw to provide instance colors");
|
|
66
|
+
}
|
|
67
|
+
return bucket;
|
|
68
|
+
};
|
|
69
|
+
const binding = {
|
|
70
|
+
get cullDrawBufs() {
|
|
71
|
+
return currentBucket();
|
|
72
|
+
},
|
|
73
|
+
get _args() {
|
|
74
|
+
return currentBucket()?.argsBuffer ?? null;
|
|
75
|
+
},
|
|
76
|
+
_updateBatch: getComputeDispatchBatch(signature),
|
|
77
|
+
update(context) {
|
|
78
|
+
baseUpdate?.(context);
|
|
79
|
+
},
|
|
80
|
+
draw(pass, indexCount, instanceCount) {
|
|
81
|
+
const bucket = currentBucket();
|
|
82
|
+
if (bucket) {
|
|
83
|
+
pass.drawIndexedIndirect(bucket.argsBuffer, 0);
|
|
84
|
+
} else if (!ti._lodSource) {
|
|
85
|
+
if (ti._drawArgsBuffer) {
|
|
86
|
+
pass.drawIndexedIndirect(ti._drawArgsBuffer, 0);
|
|
87
|
+
} else {
|
|
88
|
+
pass.drawIndexed(indexCount, instanceCount);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
return binding;
|
|
94
|
+
}
|
|
44
95
|
|
|
45
96
|
export { tryBind };
|
|
46
97
|
//# sourceMappingURL=thin-instance-cull-binding.js.map
|