UNPKG

@babylonjs/viewer

Version:

The Babylon Viewer aims to simplify a specific but common Babylon.js use case: loading, viewing, and interacting with a 3D model.

1,241 lines (1,234 loc) 49.5 kB
import { l as appendMeshLightUboFields, m as meshLightIndexWGSL, n as _getPbrExts, P as PBR2_HAS_UV2, o as PBR2_HAS_UV_TRANSFORM, p as PBR_HAS_ANISOTROPY, q as PBR_HAS_SKYBOX, s as PBR_HAS_SPECULAR_AA, u as PBR_HAS_NORMAL_MAP, v as MSH_HAS_TANGENTS, w as PBR_HAS_ENV, x as MSH_RECEIVE_SHADOWS, y as PBR_HAS_EMISSIVE, z as MSH_HAS_THIN_INSTANCES, A as MSH_HAS_VERTEX_COLOR, C as MSH_HAS_UV2, D as PBR_HAS_SPEC_GLOSS, E as PBR_HAS_FOG, G as MSH_HAS_INSTANCE_COLOR, H as PBR2_ESM_SHADOW_OUTPUT, J as PBR2_NO_COLOR_OUTPUT, K as PBR_HAS_OCCLUSION, N as PBR2_HAS_BASE_COLOR_FACTOR, O as PBR_HAS_GAMMA_ALBEDO, Q as PBR_HAS_ALPHA_BLEND, R as PBR_HAS_TONEMAP, V as PBR_HAS_DOUBLE_SIDED, W as MSH_FLAT_NORMAL, X as MSH_HAS_MORPH_TARGETS, Y as PBR_HAS_EMISSIVE_COLOR, Z as PBR_HAS_METALLIC_REFLECTANCE_MAP, $ as PBR_HAS_REFLECTANCE_MAP, a0 as PBR2_HAS_REFLECTANCE_FACTORS, a1 as writeMeshLightSelection, a2 as _registerPbrExt, a3 as StandardToneMapping, a4 as clearPbrPipelineCache, a5 as clearSamplerCache, a6 as _computePbrMaterialFeatures, a7 as _computeMeshFeatures, a8 as getOrCreatePbrBindings, F as F32, a9 as packMat4IntoF32, e as createUniformBuffer, aa as PBR2_HAS_REFRACTION, ab as createPbrMeshBindGroup, ac as collectPbrBoundTextures, ad as acquireTexture, ae as releaseTexture, af as getOrCreatePbrPipeline } from './index-By0tcgYN.esm.js'; import { S as SCENE_UBO_WGSL } from './scene-uniforms-ucwQkfF-.esm.js'; const TYPE_INFO = { f32: { align: 4, size: 4 }, u32: { align: 4, size: 4 }, i32: { align: 4, size: 4 }, "vec2<f32>": { align: 8, size: 8 }, "vec3<f32>": { align: 16, size: 12 }, "vec4<f32>": { align: 16, size: 16 }, "vec4<u32>": { align: 16, size: 16 }, "mat4x4<f32>": { align: 16, size: 64 } }; function alignUp(offset, alignment) { return offset + alignment - 1 & ~(alignment - 1); } function typeInfo(type) { const info = TYPE_INFO[type]; if (info) { return info; } const m = /^array<vec4<u32>,\s*(\d+)>$/.exec(type); if (m) { return { align: 16, size: Number(m[1]) * 16 }; } throw new Error(`Unknown UBO field type: ${type}`); } function computeUboLayout(fields) { const _offsets = /* @__PURE__ */ new Map(); const lines = []; let cursor = 0; for (const field of fields) { const info = typeInfo(field._type); cursor = alignUp(cursor, info.align); _offsets.set(field._name, cursor); lines.push(`${field._name}: ${field._type},`); cursor += info.size; } const _totalBytes = fields.length > 0 ? alignUp(cursor, 16) : 0; const _structBody = lines.join("\n"); return { _totalBytes, _offsets, _structBody }; } const STAGE_VERTEX = 1; const STAGE_FRAGMENT$1 = 2; function topoSort(fragments) { const byId = /* @__PURE__ */ new Map(); for (const f of fragments) { if (byId.has(f._id)) { throw Error(); } byId.set(f._id, f); } const inDeg = /* @__PURE__ */ new Map(); const deps = /* @__PURE__ */ new Map(); for (const f of fragments) { if (!inDeg.has(f._id)) { inDeg.set(f._id, 0); } for (const d of f._dependencies ?? []) { if (!byId.has(d)) { throw Error(); } inDeg.set(f._id, (inDeg.get(f._id) ?? 0) + 1); let arr = deps.get(d); if (!arr) { arr = []; deps.set(d, arr); } arr.push(f._id); } } const q = []; for (const [id, d] of inDeg) { if (d === 0) { q.push(id); } } q.sort(); const out = []; let qi = 0; while (qi < q.length) { const id = q[qi++]; out.push(byId.get(id)); for (const d of deps.get(id) ?? []) { const nd = (inDeg.get(d) ?? 1) - 1; inDeg.set(d, nd); if (nd === 0) { let i = qi; while (i < q.length && q[i] < d) { i++; } q.splice(i, 0, d); } } } if (out.length !== fragments.length) { throw Error(); } return out; } function dedup(base, extra) { const seen = /* @__PURE__ */ new Set(); const all = []; for (const v of base) { if (!seen.has(v._name)) { seen.add(v._name); all.push(v); } } for (const v of extra) { if (!seen.has(v._name)) { seen.add(v._name); all.push(v); } } return all; } function bglEntry(binding, decl) { const e = { binding, visibility: decl._visibility }; switch (decl._type._kind) { case "uniform-buffer": e.buffer = { type: "uniform" }; break; case "texture": { const def = decl._type._textureType === "texture_depth_2d" ? "depth" : decl._type._textureType === "texture_2d<u32>" ? "uint" : "float"; e.texture = { sampleType: decl._type._sampleType ?? def, viewDimension: decl._type._textureType.includes("array") ? "2d-array" : decl._type._textureType.includes("cube") ? "cube" : "2d" }; break; } case "sampler": e.sampler = { type: decl._type._samplerType === "sampler_comparison" ? "comparison" : decl._type._samplerType === "sampler_non_filtering" ? "non-filtering" : "filtering" }; break; case "storage-texture": e.storageTexture = { access: decl._type._access, format: decl._type._format }; break; } return e; } function declWGSL(g, b, d) { switch (d._type._kind) { case "uniform-buffer": return `@group(${g})@binding(${b}) var<uniform> ${d._name}:${d._name}Uniforms;`; case "texture": return `@group(${g})@binding(${b}) var ${d._name}:${d._type._textureType};`; case "sampler": return `@group(${g})@binding(${b}) var ${d._name}:${d._type._samplerType === "sampler_non_filtering" ? "sampler" : d._type._samplerType};`; case "storage-texture": return `@group(${g})@binding(${b}) var ${d._name}:texture_storage_2d<${d._type._format},${d._type._access}>;`; } } const SLOT_RE = /\/\*([A-Z_0-9]+)\*\//g; function injectSlots(tpl, sorted, key) { return tpl.replace(SLOT_RE, (_, slot) => { const parts = []; for (const f of sorted) { const s = f[key]; if (s?.[slot]) { parts.push(s[slot]); } } return parts.join("\n"); }); } function composeShader(template, fragments) { const sorted = topoSort(fragments); const fragAttrs = []; const fragVaryings = []; const helpers = []; const vHelpers = []; const vBuiltins = []; for (const f of sorted) { if (f._vertexAttributes) { fragAttrs.push(...f._vertexAttributes); } if (f._varyings) { fragVaryings.push(...f._varyings); } if (f._helperFunctions) { helpers.push(f._helperFunctions); } if (f._vertexHelperFunctions) { vHelpers.push(f._vertexHelperFunctions); } for (const b of f._vertexBuiltins ?? []) { vBuiltins.push(`@builtin(${b._builtin}) ${b._name}:${b._type},`); } } const allAttrs = dedup(template._baseVertexAttributes, fragAttrs); const inputLines = []; const _vertexBufferLayouts = []; const groups = /* @__PURE__ */ new Map(); const firstOfGroup = /* @__PURE__ */ new Map(); for (let i = 0; i < allAttrs.length; i++) { const a = allAttrs[i]; inputLines.push(`@location(${i}) ${a._name}:${a._type},`); if (a._bufferGroup) { if (!groups.has(a._bufferGroup)) { groups.set(a._bufferGroup, []); firstOfGroup.set(a._bufferGroup, a); } groups.get(a._bufferGroup).push({ loc: i, off: a._offset ?? 0, fmt: a._gpuFormat }); } else { _vertexBufferLayouts.push({ arrayStride: a._arrayStride, stepMode: a._stepMode ?? "vertex", attributes: [{ shaderLocation: i, offset: a._offset ?? 0, format: a._gpuFormat }] }); } } for (const [grp, attrs] of groups) { const f = firstOfGroup.get(grp); _vertexBufferLayouts.push({ arrayStride: f._arrayStride, stepMode: f._stepMode ?? "vertex", attributes: attrs.map((a) => ({ shaderLocation: a.loc, offset: a.off, format: a.fmt })) }); } let nextLoc = allAttrs.length; for (const f of sorted) { if (f._pipelineVertexBuffers) { const r = f._pipelineVertexBuffers(nextLoc); _vertexBufferLayouts.push(...r._buffers); nextLoc = r._nextLoc; } } const allVary = dedup(template._baseVaryings, fragVaryings); const varyBody = `@builtin(position) clipPos:vec4f, ` + allVary.map((v, i) => `@location(${i}) ${v._name}:${v._type},`).join("\n"); const hasMaterialUbo = !!(template._baseMaterialUboFields && template._baseMaterialUboFields.length > 0); const meshFields = [...template._baseMeshUboFields]; const materialFields = hasMaterialUbo ? [...template._baseMaterialUboFields] : []; for (const f of sorted) { if (f._uboFields?.length) { (hasMaterialUbo ? materialFields : meshFields).push(...f._uboFields); } } const _meshUboSpec = computeUboLayout(meshFields); const _materialUboSpec = hasMaterialUbo ? computeUboLayout(materialFields) : void 0; const meshBGL = [{ binding: 0, visibility: STAGE_VERTEX | STAGE_FRAGMENT$1, buffer: { type: "uniform" } }]; if (hasMaterialUbo) { meshBGL.push({ binding: 1, visibility: STAGE_FRAGMENT$1, buffer: { type: "uniform" } }); } const shadowBGL = []; const vDecls = []; const fDecls = []; let mb = hasMaterialUbo ? 2 : 1, sb = 0; function addBinding(d, _isVertex) { const isShadow = d._group === "shadow"; const b = isShadow ? sb++ : mb++; const g = isShadow ? 2 : 1; (isShadow ? shadowBGL : meshBGL).push(bglEntry(b, d)); const w = declWGSL(g, b, d); if (d._visibility & STAGE_VERTEX) { vDecls.push(w); } if (d._visibility & STAGE_FRAGMENT$1) { fDecls.push(w); } } for (const d of template._baseVertexBindings ?? []) { addBinding(d); } for (const f of sorted) { for (const d of f._vertexBindings ?? []) { addBinding(d); } } for (const d of template._baseBindings ?? []) { addBinding(d); } for (const f of sorted) { for (const d of (f._bindings ?? []).filter((b) => (b._group ?? "mesh") === "mesh")) { addBinding(d); } } for (const f of sorted) { for (const d of (f._bindings ?? []).filter((b) => b._group === "shadow")) { addBinding(d); } } const _fragmentKey = sorted.map((f) => f._id).join("|"); const vParams = (vBuiltins.length ? vBuiltins.join("\n") + "\n" : "") + inputLines.join("\n"); const meshStruct = `struct MeshUniforms{ ${_meshUboSpec._structBody} }`; const materialStruct = _materialUboSpec ? ` struct MaterialUniforms{ ${_materialUboSpec._structBody} } @group(1)@binding(1) var<uniform> material:MaterialUniforms;` : ""; let _vertexWGSL = template._vertexTemplate; _vertexWGSL = _vertexWGSL.replace("/*SU*/", SCENE_UBO_WGSL); _vertexWGSL = _vertexWGSL.replace("/*MU*/", meshStruct); _vertexWGSL = _vertexWGSL.replace("/*VI*/", `struct VertexInput{ ${inputLines.join("\n")} }`); _vertexWGSL = _vertexWGSL.replace("/*VO*/", `struct VertexOutput{ ${varyBody} }`); _vertexWGSL = _vertexWGSL.replace("/*VD*/", vDecls.join("\n")); _vertexWGSL = _vertexWGSL.replace("/*VP*/", vParams); _vertexWGSL = _vertexWGSL.replace("/*VH*/", vHelpers.join("\n")); _vertexWGSL = injectSlots(_vertexWGSL, sorted, "_vertexSlots"); let _fragmentWGSL = template._fragmentTemplate; _fragmentWGSL = _fragmentWGSL.replace("/*SU*/", SCENE_UBO_WGSL); _fragmentWGSL = _fragmentWGSL.replace("/*MU*/", meshStruct + materialStruct); _fragmentWGSL = _fragmentWGSL.replace("/*FI*/", `struct FragmentInput{ ${varyBody} }`); _fragmentWGSL = _fragmentWGSL.replace("/*HF*/", helpers.join("\n")); _fragmentWGSL = _fragmentWGSL.replace("/*FB*/", fDecls.join("\n")); _fragmentWGSL = injectSlots(_fragmentWGSL, sorted, "_fragmentSlots"); const _meshBGLDescriptor = { entries: meshBGL }; const _shadowBGLDescriptor = shadowBGL.length ? { entries: shadowBGL } : null; return { _vertexWGSL, _fragmentWGSL, _meshBGLDescriptor, _shadowBGLDescriptor, _vertexBufferLayouts, _meshUboSpec, _materialUboSpec, _fragmentKey }; } const STAGE_FRAGMENT = 2; const BRDF_FUNCTIONS = ` const PI:f32=3.14159265358979323846; fn distributionGGX(NdotH:f32,alphaG:f32)->f32{ let a2=alphaG*alphaG; let d=NdotH*NdotH*(a2-1.0)+1.0; return a2/(PI*d*d); } fn geometrySmithGGX(NdotL:f32,NdotV:f32,alphaG:f32)->f32{ let a2=alphaG*alphaG; let gl=NdotL*sqrt(NdotV*(NdotV-a2*NdotV)+a2); let gv=NdotV*sqrt(NdotL*(NdotL-a2*NdotL)+a2); return 0.5/(gl+gv); } fn fresnelSchlick(cosTheta:f32,F0:vec3<f32>,F90:vec3<f32>)->vec3<f32>{ let t=1.0-cosTheta; let t2=t*t; return F0+(F90-F0)*(t2*t2*t); } `; function createPbrTemplate(config) { const { _hasSingleLight = false, _hasMultiLight = false, _singleLightWGSL = "", _singleLightBlock = "", _multiLightWGSL = "", _multiLightLoop = "", _normalMode = "none", _flatGeometricNormal = false, _flatNormalWgsl = "", _hasEmissiveTexture = false, _hasSpecGloss = false, _hasDoubleSided = false, _hasTonemap = false, _fogHelper = "", _fogBlock = "", _toneMappingHelpers = "", _toneMappingCall = "", _hasAlphaBlend = false, _hasSpecularAA = false, _hasGammaAlbedo = false, _hasBaseColorFactor = false, _hasMorph = false, _hasOcclusion = false, _hasEmissiveColor = false, _hasReflectanceExt = false, _hasIbl = false, _hasAnisotropy = false, _anisoBrdfFunctions = "", _anisoTBBlock = "", _ext, _gammaTemplate, _noColorOutput = false, _esmShadowOutput = false, _esmShadowDepthCode = "", _vbStrides } = config; const hasNormal = _normalMode === "tangent"; const hasCotangentNormal = _normalMode === "cotangent"; const hasAnyNormal = hasNormal || hasCotangentNormal; const _baseVertexAttributes = [ { _name: "position", _type: "vec3<f32>", _gpuFormat: "float32x3", _arrayStride: _vbStrides?._p?._stride ?? 12, _offset: _vbStrides?._p?._offset ?? 0 }, { _name: "normal", _type: "vec3<f32>", _gpuFormat: "float32x3", _arrayStride: _vbStrides?._n?._stride ?? 12, _offset: _vbStrides?._n?._offset ?? 0 } ]; if (hasNormal) { _baseVertexAttributes.push({ _name: "tangent", _type: "vec4<f32>", _gpuFormat: "float32x4", _arrayStride: _vbStrides?._t?._stride ?? 16, _offset: _vbStrides?._t?._offset ?? 0 }); } _baseVertexAttributes.push({ _name: "uv", _type: "vec2<f32>", _gpuFormat: "float32x2", _arrayStride: _vbStrides?._u?._stride ?? 8, _offset: _vbStrides?._u?._offset ?? 0 }); if (_ext) { _baseVertexAttributes.push(..._ext.extraVertexAttributes); } const _baseVaryings = [ { _name: "worldPos", _type: "vec3<f32>" }, { _name: "worldNormal", _type: "vec3<f32>" } ]; if (hasNormal) { _baseVaryings.push({ _name: "worldTangent", _type: "vec3<f32>" }, { _name: "worldBitangent", _type: "vec3<f32>" }); } _baseVaryings.push({ _name: "uv", _type: "vec2<f32>" }); if (_ext) { _baseVaryings.push(..._ext.extraVaryings); } const _baseMeshUboFields = [{ _name: "world", _type: "mat4x4<f32>" }]; appendMeshLightUboFields(_baseMeshUboFields); const _baseMaterialUboFields = [ { _name: "environmentIntensity", _type: "f32" }, { _name: "directIntensity", _type: "f32" }, { _name: "reflectance", _type: "f32" }, { _name: "materialAlpha", _type: "f32" }, ..._hasBaseColorFactor ? [{ _name: "baseColorFactor", _type: "vec4<f32>" }] : [], // glTF metallicFactor / roughnessFactor (default 1.0) — applied over MR texture channels. { _name: "metallicFactor", _type: "f32" }, { _name: "roughnessFactor", _type: "f32" }, { _name: "normalScale", _type: "f32" }, { _name: "lightFalloffMode", _type: "f32" }, // Anisotropy UBO field stays on the base template because anisotropy is // template-only (no ShaderFragment) — the anisotropyExt just writes its // slice through the unified ext.writeUbo hook. ..._hasAnisotropy ? [{ _name: "anisotropyParams", _type: "vec4<f32>" }] : [], // ── Extension fields (per-texture UV transforms, etc.) ─ ..._ext ? _ext.extraMaterialUboFields : [] ]; const tex2d = (name, sampler) => [ { _name: name, _type: { _kind: "texture", _textureType: "texture_2d<f32>" }, _visibility: STAGE_FRAGMENT }, { _name: sampler, _type: { _kind: "sampler", _samplerType: "sampler" }, _visibility: STAGE_FRAGMENT } ]; const _baseBindings = tex2d("baseColorTexture", "baseColorSampler"); if (hasAnyNormal) { _baseBindings.push(...tex2d("normalTexture", "normalSampler_")); } _baseBindings.push(...tex2d("ormTexture", "ormSampler")); if (_ext) { _baseBindings.push(..._ext.extraBindings); } if (_hasEmissiveTexture) { _baseBindings.push(...tex2d("emissiveTexture", "emissiveSampler")); } if (_hasSpecGloss) { _baseBindings.push(...tex2d("specGlossTexture", "specGlossSampler")); } if (_esmShadowOutput) { _baseBindings.push({ _name: "shadowParams", _type: { _kind: "uniform-buffer" }, _visibility: STAGE_FRAGMENT }); } const posVar = _hasMorph ? "morphedPos" : "position"; const normVar = _hasMorph ? "morphedNorm" : "normal"; const tangentBlock = hasNormal ? `let N_local=normalize(${normVar}); let T_local=normalize(tangent.xyz); let B_local=cross(N_local,T_local)*tangent.w; out.worldTangent=(finalWorld*vec4<f32>(T_local,0.0)).xyz; out.worldBitangent=(finalWorld*vec4<f32>(B_local,0.0)).xyz;` : ""; const _vertexTemplate = `/*SU*/ /*MU*/ @group(1) @binding(0) var<uniform> mesh: MeshUniforms; /*VH*/ /*VD*/ /*VO*/ @vertex fn main( /*VP*/ ) -> VertexOutput { var out: VertexOutput; /*VR*/ var finalWorld = mesh.world; /*VW*/ let worldPos4 = finalWorld * vec4<f32>(${posVar}, 1.0); out.worldPos = worldPos4.xyz; out.clipPos = scene.viewProjection * worldPos4; out.worldNormal = (finalWorld * vec4<f32>(normalize(${normVar}), 0.0)).xyz; ${tangentBlock} out.uv = uv; ${_ext ? _ext.vertexBodyExtra : ""}/*VB*/ return out; }`; const normalUV = _ext?.uvForNormal ?? "input.uv"; const normalScaleMod = _ext?.normalScaleMod ?? ""; const normalRef = _ext?.normalScaleMod ? "scaledNormal" : "normalMapRaw"; const normalRefCt = _ext?.normalScaleMod ? "scaledNormalCT" : "normalMapSample"; let normalBlock; if (hasNormal) { normalBlock = `let normalMapRaw=textureSample(normalTexture,normalSampler_,${normalUV}).rgb*2.0-1.0; ${normalScaleMod}let normalMapNorm=normalize(${normalRef}); var N_geom=normalize(input.worldNormal); let TBN=mat3x3<f32>(input.worldTangent,input.worldBitangent,input.worldNormal); var N=normalize(TBN*normalMapNorm);`; } else if (hasCotangentNormal) { normalBlock = `let normalMapSample=textureSample(normalTexture,normalSampler_,${normalUV}).rgb*2.0-1.0; ${normalScaleMod.replace(/normalMapRaw/g, "normalMapSample").replace(/scaledNormal/g, "scaledNormalCT")}var N_geom=normalize(input.worldNormal); let dp1=dpdx(input.worldPos); let dp2=dpdy(input.worldPos); let duv1=dpdx(${normalUV}); let duv2=dpdy(${normalUV}); let dp2perp=cross(dp2,N_geom); let dp1perp=cross(N_geom,dp1); let tangent_ct=dp2perp*duv1.x+dp1perp*duv2.x; let bitangent_ct=-(dp2perp*duv1.y+dp1perp*duv2.y); let det=max(dot(tangent_ct,tangent_ct),dot(bitangent_ct,bitangent_ct)); let invmax=select(inverseSqrt(det),0.0,det==0.0); let cotangentFrame=mat3x3<f32>(tangent_ct*invmax,bitangent_ct*invmax,N_geom); var N=normalize(cotangentFrame*normalize(${normalRefCt}));`; } else if (_flatGeometricNormal && _flatNormalWgsl) { normalBlock = `${_flatNormalWgsl}`; } else { normalBlock = `var N_geom=normalize(input.worldNormal); var N=N_geom;`; } const anisotropyTBBlock = _hasAnisotropy ? _anisoTBBlock : ""; const vertexColorMod = _ext?.baseColorMod ?? ""; const baseColorFactorRgb = _hasBaseColorFactor ? "*material.baseColorFactor.rgb" : ""; const baseColorFactorAlpha = _hasBaseColorFactor ? "*material.baseColorFactor.a" : ""; const baseColorDecode = _hasGammaAlbedo ? _gammaTemplate.gammaBaseColor(baseColorFactorRgb, baseColorFactorAlpha, vertexColorMod) : `var baseColor=baseColorSample.rgb${baseColorFactorRgb}; var alpha=baseColorSample.a${baseColorFactorAlpha};${vertexColorMod}`; const specGlossUV = _ext?.uvForSpecGloss ?? "input.uv"; const roughnessMetallic = _hasSpecGloss ? `let specGloss=textureSample(specGlossTexture,specGlossSampler,${specGlossUV}); let roughness=clamp(1.0-specGloss.a,0.0,1.0); let metallic=0.0;` : `let roughness=clamp(orm.g*material.roughnessFactor,0.0,1.0); let metallic=orm.b*material.metallicFactor;`; const emissiveUV = _ext?.uvForEmissive ?? "input.uv"; const emissiveDefault = _hasEmissiveColor || !_hasEmissiveTexture ? `var emissive:vec3f;` : `let emissive=textureSample(emissiveTexture,emissiveSampler,${emissiveUV}).rgb;`; const occlusionDefault = _hasReflectanceExt ? `` : _ext?.occlusionOverride ? _ext.occlusionOverride : _hasOcclusion ? `let occlusion=orm.r;` : `let occlusion=1.0;`; const f0Default = _hasReflectanceExt ? `` : _hasSpecGloss ? `var colorF0=specGloss.rgb; let colorF90=vec3<f32>(1.0); let maxSpecular=max(colorF0.r,max(colorF0.g,colorF0.b)); let surfaceAlbedo=baseColor*(1.0-maxSpecular);` : `let dielectricF0=material.reflectance; var colorF0=mix(vec3<f32>(dielectricF0),baseColor,metallic); let colorF90=vec3<f32>(1.0); let surfaceAlbedo=baseColor*(1.0-dielectricF0)*(1.0-metallic);`; const specularAABlock = _hasSpecularAA || hasAnyNormal ? `var AA_factor_x=0.0; var AA_factor_y=0.0; {let nDfdx_AA=dpdx(N); let nDfdy_AA=dpdy(N); let slopeSquare_AA=max(dot(nDfdx_AA,nDfdx_AA),dot(nDfdy_AA,nDfdy_AA)); AA_factor_x=pow(saturate(slopeSquare_AA),0.333); AA_factor_y=sqrt(slopeSquare_AA)*0.75; alphaG+=AA_factor_y;}` : `var AA_factor_x=0.0; var AA_factor_y=0.0;`; const directLightBlock = _hasMultiLight ? _multiLightLoop : _hasSingleLight ? _singleLightBlock : `var directDiffuse=vec3<f32>(0.0); var directSpecular=vec3<f32>(0.0); /*BL*/`; const toneMappingHelpersBlock = _hasTonemap ? _toneMappingHelpers : ""; const tonemapBlock = _hasTonemap ? _toneMappingCall : `color*=scene.vImageInfos.x;`; const fogHelper = _fogHelper; const fogBlock = _fogBlock; const alphaBlock = _noColorOutput ? "" : _hasAlphaBlend ? `var finalAlpha=alpha*material.materialAlpha; var luminanceOverAlpha=0.0; /*BA*/ luminanceOverAlpha+=dot(${_hasIbl ? `finalSpecularScaled` : `directSpecular`},vec3<f32>(0.2126,0.7152,0.0722)); finalAlpha=saturate(finalAlpha+luminanceOverAlpha*luminanceOverAlpha); /*FA*/ return vec4<f32>(color,finalAlpha);` : `return vec4<f32>(color,alpha*material.materialAlpha);`; const doubleSidedEntry = _hasDoubleSided ? `@fragment fn main(input: FragmentInput, @builtin(front_facing) frontFacing: bool)${_noColorOutput ? "" : " -> @location(0) vec4<f32>"} {` : `@fragment fn main(input: FragmentInput)${_noColorOutput ? "" : " -> @location(0) vec4<f32>"} {`; const doubleSidedGeomFlip = _flatGeometricNormal ? "" : " N_geom = -N_geom;"; const doubleSidedFlip = _hasDoubleSided ? `if (!frontFacing) { N = -N;${doubleSidedGeomFlip} }` : ""; const lightDecls = _hasMultiLight ? _multiLightWGSL : _hasSingleLight ? _singleLightWGSL : ""; const lightBindingDecl = _hasSingleLight || _hasMultiLight ? `@group(0) @binding(1) var<uniform> lights: lightsUniforms;` : ""; const meshLightIndexHelper = _hasSingleLight || _hasMultiLight ? meshLightIndexWGSL("mesh") : ""; const anisoBrdfBlock = _hasAnisotropy ? _anisoBrdfFunctions : ""; const fragmentHelpers = _ext?.fragmentHelpers ?? ""; const fragmentPrelude = _ext?.fragmentPrelude ?? ""; const baseColorUV = _ext?.uvForBaseColor ?? "input.uv"; const ormUV = _ext?.uvForOrm ?? "input.uv"; const _fragmentTemplate = `/*SU*/ ${_esmShadowOutput ? "struct shadowParamsUniforms { biasAndScale: vec4<f32>, depthValues: vec4<f32>, }" : ""} /*MU*/ @group(1) @binding(0) var<uniform> mesh: MeshUniforms; /*HF*/ /*FB*/ /*FI*/ ${BRDF_FUNCTIONS} ${toneMappingHelpersBlock} ${fogHelper} ${anisoBrdfBlock} ${lightDecls} ${lightBindingDecl} ${meshLightIndexHelper} ${fragmentHelpers} ${doubleSidedEntry} ${fragmentPrelude}/*SV*/ let baseColorSample=textureSample(baseColorTexture,baseColorSampler,${baseColorUV}); ${baseColorDecode} let orm=textureSample(ormTexture,ormSampler,${ormUV}).rgb; ${occlusionDefault} ${roughnessMetallic} ${emissiveDefault} /*AT*/ ${// When the fragment terminates early (no color output, or ESM shadow // depth output), emit only the terminating return. Appending the // color-path body after the return would make it unreachable and // trigger a "code is unreachable" shader compilation warning. _noColorOutput ? "return;" : _esmShadowOutput ? _esmShadowDepthCode : `${normalBlock} ${doubleSidedFlip} ${anisotropyTBBlock} /*AC*/ let V=normalize(scene.vEyePosition.xyz-input.worldPos); let NdotVUnclamped=dot(N,V); let NdotV=abs(NdotVUnclamped)+0.0000001; ${f0Default} /*MF*/ var alphaG=roughness*roughness+0.0005; ${specularAABlock} ${directLightBlock} var color=directDiffuse+directSpecular+emissive; /*AI*/ /*NI*/ ${fogBlock} ${tonemapBlock} color=pow(color,vec3<f32>(1.0/2.2)); color=clamp(color,vec3<f32>(0.0),vec3<f32>(1.0)); let highContrast=color*color*(3.0-2.0*color); if(scene.vImageInfos.y<1.0){color=mix(vec3<f32>(0.5),color,scene.vImageInfos.y);} else{color=mix(color,highContrast,scene.vImageInfos.y-1.0);} color=max(color,vec3<f32>(0.0)); /*BC*/ ${alphaBlock}`} }`; return { _vertexTemplate, _fragmentTemplate, _baseMeshUboFields, _baseMaterialUboFields, _baseVertexAttributes, _baseVaryings, _baseBindings }; } function createPbrComposer(deps) { const cache = /* @__PURE__ */ new Map(); const { _singleLightWGSL, _getSingleLightBlock, _multiLightWGSL, _multiLightLoop, _toneMappingHelpers, _toneMappingCall, _fogHelper, _fogBlock, _createPbrTemplateExt, _anisoExt, _iblSkyboxCalc, _flatNormalWgsl, _gammaTemplate, _createPbrShadowFragment, _shadowLights, _createThinInstanceFragment } = deps; return function composePbr(features, features2 = 0, meshFeatures = 0, sceneFeatures = 0, lightMode = 0, singleLightType = "", _esmShadowDepthCode = "", vbStrides, vbKey = "", uv2Mask = 0) { const ckey = `${features}:${features2}:${meshFeatures}:${sceneFeatures}:${lightMode}:${singleLightType}${vbKey}:${uv2Mask}`; const cached = cache.get(ckey); if (cached) { return cached; } const has = (bit) => (features & bit) !== 0; const hasMesh = (bit) => (meshFeatures & bit) !== 0; const hasScene = (bit) => (sceneFeatures & bit) !== 0; const hasNormal = has(PBR_HAS_NORMAL_MAP) && hasMesh(MSH_HAS_TANGENTS); const hasCotangent = has(PBR_HAS_NORMAL_MAP) && !hasMesh(MSH_HAS_TANGENTS); const _hasAnyNormal = hasNormal || hasCotangent; const _hasReflectanceExt = has(PBR_HAS_METALLIC_REFLECTANCE_MAP | PBR_HAS_REFLECTANCE_MAP) || (features2 & PBR2_HAS_REFLECTANCE_FACTORS) !== 0; const _hasIbl = hasScene(PBR_HAS_ENV); const _hasMorph = hasMesh(MSH_HAS_MORPH_TARGETS); const hasShadow = hasMesh(MSH_RECEIVE_SHADOWS); const _hasAnisotropy = has(PBR_HAS_ANISOTROPY); const _hasEmissiveColor = has(PBR_HAS_EMISSIVE_COLOR); const _hasEmissiveTexture = has(PBR_HAS_EMISSIVE); const hasTI = hasMesh(MSH_HAS_THIN_INSTANCES); const _hasUvTransform = (features2 & PBR2_HAS_UV_TRANSFORM) !== 0; const _hasVertexColor = hasMesh(MSH_HAS_VERTEX_COLOR); const _hasUv2 = (features2 & PBR2_HAS_UV2) !== 0 && hasMesh(MSH_HAS_UV2); const needsExt = _hasUvTransform || _hasVertexColor || _hasUv2; const _hasSpecularAA = has(PBR_HAS_SPECULAR_AA); const _ext = needsExt && _createPbrTemplateExt ? _createPbrTemplateExt({ _hasUvTransform, _hasVertexColor, _hasUv2, _uv2Mask: uv2Mask, _features2: features2, _hasAnyNormal, _hasEmissiveTexture, _hasSpecGloss: has(PBR_HAS_SPEC_GLOSS) }) : void 0; const template = createPbrTemplate({ _hasSingleLight: lightMode === 1, _hasMultiLight: lightMode === 2, _singleLightWGSL, _singleLightBlock: lightMode === 1 && _getSingleLightBlock ? _getSingleLightBlock(singleLightType) : "", _multiLightWGSL, _multiLightLoop, _normalMode: hasNormal ? "tangent" : hasCotangent ? "cotangent" : "none", _flatGeometricNormal: !_hasAnyNormal && hasMesh(MSH_FLAT_NORMAL), _flatNormalWgsl, _hasEmissiveTexture, _hasSpecGloss: has(PBR_HAS_SPEC_GLOSS), _hasDoubleSided: has(PBR_HAS_DOUBLE_SIDED), _hasTonemap: hasScene(PBR_HAS_TONEMAP), _fogHelper: hasScene(PBR_HAS_FOG) ? _fogHelper : "", _fogBlock: hasScene(PBR_HAS_FOG) ? _fogBlock : "", _toneMappingHelpers, _toneMappingCall, _hasAlphaBlend: has(PBR_HAS_ALPHA_BLEND), _hasSpecularAA, _hasGammaAlbedo: has(PBR_HAS_GAMMA_ALBEDO), _hasBaseColorFactor: (features2 & PBR2_HAS_BASE_COLOR_FACTOR) !== 0, _hasMorph, _hasOcclusion: has(PBR_HAS_OCCLUSION) && !_hasReflectanceExt, _hasEmissiveColor, _hasReflectanceExt, _hasIbl, _hasAnisotropy, _anisoBrdfFunctions: _hasAnisotropy && _anisoExt ? _anisoExt.ANISO_BRDF_FUNCTIONS : "", _anisoTBBlock: _hasAnisotropy && _anisoExt ? _anisoExt.makeAnisotropyTBBlock(hasNormal, (features2 & _anisoExt.PBR2_HAS_ANISO_TEX) !== 0) : "", _ext, _gammaTemplate, _noColorOutput: (features2 & PBR2_NO_COLOR_OUTPUT) !== 0, _esmShadowOutput: (features2 & PBR2_ESM_SHADOW_OUTPUT) !== 0, _esmShadowDepthCode, _vbStrides: vbStrides }); const frags = []; const fragCtx = { _features: features, _features2: features2, _meshFeatures: meshFeatures, _uv2Mask: _hasUv2 ? uv2Mask : 0, _hasIbl, _hasAnyNormal, _hasSpecularAA, _anisoBentNormalCode: _hasAnisotropy && _anisoExt ? _anisoExt.ANISO_BENT_NORMAL : "", _iblSkyboxCalc: has(PBR_HAS_SKYBOX) ? _iblSkyboxCalc : "" }; let pc; for (const regExt of _getPbrExts().values()) { if (regExt.frag) { const fr = regExt.frag(fragCtx); if (fr) { frags.push(fr); pc ||= fr._pc; } } } if (hasShadow && _createPbrShadowFragment) { const slots = _shadowLights.map((sl) => ({ lightIndex: sl.lightIndex, shadowType: sl.shadowType })); frags.push(_createPbrShadowFragment(slots)); } if (hasTI && _createThinInstanceFragment) { frags.push(_createThinInstanceFragment(hasMesh(MSH_HAS_INSTANCE_COLOR))); } let composed = composeShader(template, frags); pc && (composed = pc(composed)); cache.set(ckey, composed); return composed; }; } async function buildPbrRenderables(scene, meshes, envTextures) { const engine = scene.surface.engine; const device = engine._device; const materialScratch = /* @__PURE__ */ new Map(); const hasEnv = !!envTextures; const shadowLights = []; for (let i = 0; i < scene.lights.length; i++) { const sg = scene.lights[i].shadowGenerator; if (sg) { shadowLights.push({ lightIndex: i, shadowType: sg._shadowType, gen: sg }); } } const hasSomeShadows = shadowLights.length > 0; let hasAnyAffectedLight = false; let needsSingleLightPath = false; let needsMultiLightPath = false; const singleLightTypes = []; for (const mesh of meshes) { const lr = writeMeshLightSelection(mesh, scene.lights); const affectedCount = lr > 0 ? 1 : -lr; hasAnyAffectedLight ||= affectedCount > 0; if (affectedCount === 1 && !(mesh.receiveShadows && hasSomeShadows)) { needsSingleLightPath = true; const type = getPackedSingleLightType(scene.lights, lr - 1); if (!singleLightTypes.includes(type)) { singleLightTypes.push(type); } } else if (affectedCount > 0) { needsMultiLightPath = true; } } let hasSkybox = false; let hasMetallicReflectance = false; let hasClearcoat = false; let hasSheen = false; let hasIridescence = false; let hasAnyAnisotropy = false; let hasAnySubsurface = false; let hasAlphaTest = false; let hasTransmissionRefraction = false; let needsEmissiveColor = false; let hasSomeSkeletons = false; let hasSomeMorphs = false; let hasSomeThinInstances = false; let hasCullingTI = false; let hasAnyUnlit = false; let hasAnyShadowOnly = false; let hasAnyUvTransform = false; let hasAnyUv2 = false; let hasAnyVertexColor = false; let hasAnyFlatNormal = false; let hasGammaAlbedo = false; for (let i = 0; i < meshes.length; i++) { const m = meshes[i]; const mat = m.material; const refractionIntensity = mat.subsurface?.refraction?.intensity ?? 0; hasSkybox ||= !!mat.skyboxMode; hasMetallicReflectance ||= !!(mat.metallicReflectanceTexture || mat.reflectanceTexture || mat._hasReflExt); hasClearcoat ||= !!mat.clearCoat?.isEnabled; hasSheen ||= !!mat.sheen?.isEnabled; hasIridescence ||= !!mat.iridescence?.isEnabled; hasAnyAnisotropy ||= !!mat.anisotropy?.isEnabled; hasAnySubsurface ||= !!mat.subsurface?.translucency; hasAlphaTest ||= mat.alphaCutOff > 0; hasTransmissionRefraction ||= refractionIntensity > 0 && !!mat.transmissive; needsEmissiveColor ||= !!mat.emissiveColor; hasSomeSkeletons ||= !!m.skeleton; hasSomeMorphs ||= !!m.morphTargets; hasSomeThinInstances ||= !!m.thinInstances; hasCullingTI ||= !!m.thinInstances?._gpuCullingEnabled; hasAnyUnlit ||= !!mat.unlit; hasAnyShadowOnly ||= !!mat.shadowOnly; hasAnyUvTransform ||= !!mat._hasUvTx; hasAnyUv2 ||= !!m._gpu.uv2Buffer && !!mat._uv2Mask; hasAnyVertexColor ||= !!m._gpu.colorBuffer; hasAnyFlatNormal ||= !!m._flatNormal; hasGammaAlbedo ||= !!mat.gammaAlbedo; } let _iblSkyboxCalc = ""; if (hasEnv) { const mod = await import('./ibl-fragment-DmZObtww.esm.js'); _registerPbrExt(mod.pbrExt); if (hasSkybox) { const sky = await import('./ibl-skybox-wgsl-CPHVLg5v.esm.js'); _iblSkyboxCalc = sky.IBL_SKYBOX_CALCULATION; } } let _flatNormalWgsl = ""; if (hasAnyFlatNormal) { const flatNormal = await import('./flat-normal-wgsl-DrXocvN1.esm.js'); _flatNormalWgsl = flatNormal.FLAT_NORMAL_WGSL; } let _createPbrShadowFragment = null; let _singleLightWGSL = ""; let _getSingleLightBlock = null; const singleLightBlocks = {}; let _multiLightWGSL = ""; let _multiLightLoop = ""; if (needsSingleLightPath) { for (const type of singleLightTypes) { const single = await importSingleLightWgsl(type); _singleLightWGSL = single.SINGLE_LIGHT_STRUCTS; singleLightBlocks[type] = single.getSingleLightBlock; } _getSingleLightBlock = (type) => singleLightBlocks[toSingleLightType(type)]?.() ?? ""; } if (needsMultiLightPath) { const wgslMod = await import('./multilight-wgsl-f1eQI9X9.esm.js'); _multiLightWGSL = wgslMod.MULTI_LIGHT_STRUCTS() + wgslMod.COMPUTE_PBR_LIGHT; _multiLightLoop = wgslMod.getMultiLightLoop(); } if (hasAnyAffectedLight && hasSomeShadows) { const shadowMod = await import('./pbr-shadow-fragment-B3Z5_h5w.esm.js'); _createPbrShadowFragment = shadowMod.createPbrShadowFragment; } const _drainPbrExts = async (loaders) => { for (const [flag, load] of loaders) { if (flag) { _registerPbrExt((await load()).pbrExt); } } }; await _drainPbrExts([ [hasAlphaTest, () => import('./alpha-test-fragment-Bhs2fCyq.esm.js')], [hasMetallicReflectance, () => import('./reflectance-fragment-BPVyxyKG.esm.js')], [hasClearcoat, () => import('./clearcoat-fragment-EB6BX2a_.esm.js')], [hasSheen, () => import('./sheen-fragment-BXKqQYuO.esm.js')], [hasIridescence, () => import('./iridescence-fragment-DVlPUZac.esm.js')], [hasAnySubsurface, () => import('./subsurface-fragment-CU8o0WrC.esm.js')] ]); if (hasTransmissionRefraction) { const mod = await import('./pbr-refraction-D16daeHb.esm.js'); await mod.R(scene, engine, _registerPbrExt); } await _drainPbrExts([ [needsEmissiveColor, () => import('./emissive-fragment-BsFgxeC9.esm.js')], [hasAnyUnlit, () => import('./unlit-fragment-CuNecQU_.esm.js')], [hasAnyShadowOnly, () => import('./shadow-only-fragment-D5wIoMG3.esm.js')], [hasSomeSkeletons, () => import('./skeleton-fragment-BOugsfGI.esm.js')], [hasSomeMorphs, () => import('./morph-fragment-D-Hyduj7.esm.js')], [hasAnyUvTransform, () => import('./uv-transform-fragment-BUAeaIA1.esm.js')] ]); let _anisoExt = null; if (hasAnyAnisotropy) { _anisoExt = await import('./anisotropy-fragment-BG-zt_tV.esm.js'); _registerPbrExt(_anisoExt.pbrExt); } let _createPbrTemplateExt = null; if (hasAnyUvTransform || hasAnyVertexColor || hasAnyUv2) { const extMod = await import('./pbr-template-ext-DDHxtIkt.esm.js'); _createPbrTemplateExt = extMod.createPbrTemplateExt; } const _gammaTemplate = hasGammaAlbedo ? await import('./pbr-template-gamma-CBKuz6q9.esm.js') : null; let _createThinInstanceFragment = null; let _syncThinInstanceBuffers = null; let _cull; let _syncThinInstanceForDraw = null; if (hasSomeThinInstances) { const mod = await import('./thin-instance-fragment-AwHktOAp.esm.js'); _createThinInstanceFragment = mod.createThinInstanceFragment; const gpuMod = await import('./thin-instance-gpu-Cg2wPFiI.esm.js'); _syncThinInstanceBuffers = gpuMod.syncThinInstanceBuffers; if (hasCullingTI) { _cull = await import('./thin-instance-cull-binding-CWoyR4dA.esm.js'); } _syncThinInstanceForDraw = gpuMod.syncThinInstanceForDraw; } let _toneMappingHelpers = ""; let _toneMappingCall = ""; const hasTonemap = scene.imageProcessing.toneMappingEnabled; if (hasTonemap) { const toneMapping = scene.imageProcessing.toneMapping ?? StandardToneMapping; _toneMappingHelpers = toneMapping.helpersWGSL; _toneMappingCall = toneMapping.callWGSL; } let _fogHelper = ""; let _fogBlock = ""; if (scene.fog) { const fogMod = await import('./pbr-fog-wgsl-Bm1AhwLx.esm.js'); _fogHelper = fogMod.PBR_FOG_HELPER; _fogBlock = fogMod.PBR_FOG_BLOCK; } const composePbr = createPbrComposer({ _singleLightWGSL, _getSingleLightBlock, _multiLightWGSL, _multiLightLoop, _toneMappingHelpers, _toneMappingCall, _fogHelper, _fogBlock, _createPbrTemplateExt, _anisoExt, _iblSkyboxCalc, _flatNormalWgsl, _gammaTemplate, _createPbrShadowFragment, _shadowLights: shadowLights, _createThinInstanceFragment }); const sceneFeatures = (hasEnv ? PBR_HAS_ENV : 0) | (hasTonemap ? PBR_HAS_TONEMAP : 0) | (scene.fog ? PBR_HAS_FOG : 0); const shadowBGCache = /* @__PURE__ */ new Map(); const syncThinInstanceBuffers = _syncThinInstanceBuffers; const syncThinInstanceForDraw = _syncThinInstanceForDraw; const rebuildSingle = (s, mesh, materialOverride) => { const mat = materialOverride ?? mesh.material; const renderFeatures = mat._renderFeatures ??= _computePbrMaterialFeatures(mat); const isOverride = materialOverride != null; const lr = writeMeshLightSelection(mesh, s.lights); const lightCount = lr > 0 ? 1 : -lr; const features = renderFeatures.features; const features2 = renderFeatures.features2 ?? 0; const shadowOutput = (features2 & (PBR2_NO_COLOR_OUTPUT | PBR2_ESM_SHADOW_OUTPUT)) !== 0; const receiveShadows = !shadowOutput && mesh.receiveShadows && hasSomeShadows; const lightMode = lightCount === 0 ? 0 : lightCount === 1 && !receiveShadows ? 1 : 2; const singleLightType = lightMode === 1 ? getPackedSingleLightType(s.lights, lr - 1) : ""; const meshFeatures = _computeMeshFeatures(mesh, receiveShadows); const esmShadowDepthCode = (features2 & PBR2_ESM_SHADOW_OUTPUT) !== 0 ? mat._esmShadowDepthCode : ""; const vbLayout = mesh._gpu._vbLayout; const vbKey = mesh._gpu._vbKey ?? ""; const uv2Mask = mat._uv2Mask ?? 0; const composed = composePbr(features, features2, meshFeatures, sceneFeatures, lightMode, singleLightType, esmShadowDepthCode, vbLayout, vbKey, uv2Mask); const bindings = getOrCreatePbrBindings( engine, features, features2, meshFeatures, sceneFeatures, composed, `${lightMode}:${singleLightType}${vbKey}:${uv2Mask}`, mat.stencil ?? null ); const meshUboData = new F32(composed._meshUboSpec._totalBytes / 4); const _packMeshWorld = engine._makePackMeshWorld?.(s) ?? packMat4IntoF32; _packMeshWorld(meshUboData, mesh.worldMatrix, 0, 0); writeMeshLightSelection(mesh, s.lights, meshUboData); const meshUBO = createUniformBuffer(engine, meshUboData); const materialSpec = composed._materialUboSpec; const matInitData = new F32(materialSpec._totalBytes / 4); _writeMaterialData(matInitData, mat, materialSpec); const materialUBO = createUniformBuffer(engine, matInitData); const needsTaskRefraction = !!mat.transmissive && (features2 & PBR2_HAS_REFRACTION) !== 0; const materialBindGroupStatic = needsTaskRefraction ? null : createPbrMeshBindGroup(engine, bindings, composed, meshUBO, materialUBO, mat, envTextures ?? null, mesh); let shadowBindGroup = null; const meshShadowLights = receiveShadows ? shadowLights : []; if (meshShadowLights.length > 0 && bindings._shadowBGL) { let cached = shadowBGCache.get(bindings._shadowBGL); if (!cached) { const entries = []; let b = 0; for (const sl of meshShadowLights) { const sg = sl.gen; entries.push({ binding: b++, resource: sg._depthTexture.createView() }); entries.push({ binding: b++, resource: sg._depthSampler }); entries.push({ binding: b++, resource: { buffer: sg._shadowUBO } }); } cached = device.createBindGroup({ layout: bindings._shadowBGL, entries }); shadowBGCache.set(bindings._shadowBGL, cached); } shadowBindGroup = cached; } const boundTextures = collectPbrBoundTextures(mat); for (const t of boundTextures) { acquireTexture(t); } s._meshDisposables.set(mesh, [ () => { meshUBO.destroy(); materialUBO.destroy(); }, () => { for (const t of boundTextures) { releaseTexture(t); } } ]); const isTransparent = (features2 & (PBR2_NO_COLOR_OUTPUT | PBR2_ESM_SHADOW_OUTPUT)) === 0 && (features & PBR_HAS_ALPHA_BLEND) !== 0; const order = mesh.renderOrder ?? (isTransparent || needsTaskRefraction ? 150 : 100); const hasNormalMap = (features & PBR_HAS_NORMAL_MAP) !== 0; const hasUV2 = (features2 & PBR2_HAS_UV2) !== 0 && (meshFeatures & MSH_HAS_UV2) !== 0; const hasVertexColor = (meshFeatures & MSH_HAS_VERTEX_COLOR) !== 0; const hasTI = (meshFeatures & MSH_HAS_THIN_INSTANCES) !== 0; const hasTIColor = (meshFeatures & MSH_HAS_INSTANCE_COLOR) !== 0; let _lastWorldVersion = mesh.worldMatrixVersion; let _lastLightsCount = s.lights.length; let thinDrawArgs = null; const sortCenter = isTransparent || needsTaskRefraction ? [mesh.worldMatrix[12], mesh.worldMatrix[13], mesh.worldMatrix[14]] : null; const _baseUpdate = () => { const worldVersion = mesh.worldMatrixVersion; if (worldVersion !== _lastWorldVersion || s.lights.length !== _lastLightsCount) { if (sortCenter) { sortCenter[0] = mesh.worldMatrix[12]; sortCenter[1] = mesh.worldMatrix[13]; sortCenter[2] = mesh.worldMatrix[14]; } _packMeshWorld(meshUboData, mesh.worldMatrix, 0, 0); writeMeshLightSelection(mesh, s.lights, meshUboData); device.queue.writeBuffer(meshUBO, 0, meshUboData); _lastWorldVersion = worldVersion; _lastLightsCount = s.lights.length; } const uboVersion = mat._uboVersion; if (uboVersion !== _lastUboVersion) { _lastUboVersion = uboVersion; let data = materialScratch.get(materialSpec._totalBytes); if (!data) { data = new F32(materialSpec._totalBytes / 4); materialScratch.set(materialSpec._totalBytes, data); } else { data.fill(0); } _writeMaterialData(data, mat, materialSpec); device.queue.writeBuffer(materialUBO, 0, data.buffer, 0, data.byteLength); } if (hasTI) { thinDrawArgs = syncThinInstanceForDraw(engine, mesh.thinInstances, hasTIColor, mesh._gpu.indexCount); } }; const _invalidate = () => { _lastWorldVersion = -1; }; const update = engine._wrapRenderableForFO?.(_baseUpdate, s, _invalidate) ?? _baseUpdate; const drawWith = (pass, materialBindGroup, cullBinding) => { if (!isOverride && mesh.material !== mat) { return 0; } const gpu = mesh._gpu; pass.setBindGroup(1, materialBindGroup); if (shadowBindGroup) { pass.setBindGroup(2, shadowBindGroup); } let slot = 0; pass.setVertexBuffer(slot++, gpu.positionBuffer); pass.setVertexBuffer(slot++, gpu.normalBuffer); if (hasNormalMap && gpu.tangentBuffer) { pass.setVertexBuffer(slot++, gpu.tangentBuffer); } pass.setVertexBuffer(slot++, gpu.uvBuffer); if (hasUV2 && gpu.uv2Buffer) { pass.setVertexBuffer(slot++, gpu.uv2Buffer); } if (hasVertexColor && gpu.colorBuffer) { pass.setVertexBuffer(slot++, gpu.colorBuffer); } const skin = mesh.skeleton ?? mesh.vat; if (skin) { pass.setVertexBuffer(slot++, skin.jointsBuffer); pass.setVertexBuffer(slot++, skin.weightsBuffer); if (skin.joints1Buffer && skin.weights1Buffer) { pass.setVertexBuffer(slot++, skin.joints1Buffer); pass.setVertexBuffer(slot++, skin.weights1Buffer); } } const ti = hasTI ? mesh.thinInstances : null; if (ti && syncThinInstanceBuffers) { slot = syncThinInstanceBuffers(engine, ti, pass, slot, hasTIColor, cullBinding?.cullDrawBufs); } pass.setIndexBuffer(gpu.indexBuffer, gpu.indexFormat); if (cullBinding) { cullBinding.draw(pass, gpu.indexCount, ti.count); } else if (thinDrawArgs) { pass.drawIndexedIndirect(thinDrawArgs, 0); } else { pass.drawIndexed(gpu.indexCount, ti?.count); } return 1; }; const r = { order, isTransparent, _transmissive: needsTaskRefraction, mesh, bind(eng, sig) { const pipeline = getOrCreatePbrPipeline(eng, sig, bindings); const materialBindGroup = needsTaskRefraction ? createPbrMeshBindGroup(engine, bindings, composed, meshUBO, materialUBO, mat, envTextures ?? null, mesh, sig._transmissionTexture) : materialBindGroupStatic; const cb = _cull?.tryBind(r, s, mesh, engine, hasTIColor, isTransparent || needsTaskRefraction, update, sig); return { renderable: r, pipeline, ...cb ? { _updateBatches: [cb._updateBatch] } : {}, update: cb ? cb.update : update, draw: (pass) => drawWith(pass, materialBindGroup, cb) }; } }; if (sortCenter) { r._worldCenter = sortCenter; } let _lastUboVersion = mat._uboVersion; return r; }; const renderables = meshes.map((m) => rebuildSingle(scene, m)); scene._pbrGeomContext = { _composePbr: composePbr, _sceneFeatures: sceneFeatures, _envTextures: envTextures ?? null, _shadowLights: shadowLights, _syncThinInstanceBuffers, _syncThinInstanceForDraw }; scene._disposables.push( engine._pbrCleanup ??= () => { clearPbrPipelineCache(); clearSamplerCache(engine); } ); return { renderables, rebuildSingle }; } function toSingleLightType(type) { return type === "hemispheric" || type === "directional" || type === "spot" ? type : "point"; } function getPackedSingleLightType(lights, packedIndex) { let packed = 0; for (const light of lights) { if (!light._writeLightUbo) { continue; } if (packed === packedIndex) { return toSingleLightType(light.lightType); } packed++; } return "point"; } async function importSingleLightWgsl(type) { if (type === "hemispheric") { return import('./singlelight-hemispheric-wgsl-DKEHpmKc.esm.js'); } if (type === "directional") { return import('./singlelight-directional-wgsl-DqDXOSN5.esm.js'); } if (type === "spot") { return import('./singlelight-spot-wgsl-CPDyUldq.esm.js'); } return import('./singlelight-point-wgsl-DHMlXIF4.esm.js'); } function _writeMaterialData(data, material, spec) { data[0] = material.environmentIntensity ?? 1; data[1] = material.directIntensity ?? 1; data[2] = material.reflectance ?? 0.04; data[3] = material.alpha ?? 1; const baseColorFactorOffset = spec._offsets.get("baseColorFactor"); if (baseColorFactorOffset !== void 0) { const off = baseColorFactorOffset / 4; const factor = material.baseColorFactor; data[off] = factor ? factor[0] : 1; data[off + 1] = factor ? factor[1] : 1; data[off + 2] = factor ? factor[2] : 1; data[off + 3] = factor ? factor[3] : 1; } if (spec._offsets.has("metallicFactor")) { const off = spec._offsets.get("metallicFactor") / 4; data[off] = material.metallicFactor ?? 1; data[off + 1] = material.roughnessFactor ?? 1; data[off + 2] = material.normalTextureScale ?? 1; data[off + 3] = material.usePhysicalLightFalloff === false ? 0 : 1; } for (const ext of _getPbrExts().values()) { if (ext.writeUbo) { ext.writeUbo(data, material, spec._offsets); } } } export { _writeMaterialData, buildPbrRenderables }; //# sourceMappingURL=pbr-renderable-CkadH7lN.esm.js.map