@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
JavaScript
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 ` var<uniform> ${d._name}:${d._name}Uniforms;`;
case "texture":
return ` var ${d._name}:${d._type._textureType};`;
case "sampler":
return ` var ${d._name}:${d._type._samplerType === "sampler_non_filtering" ? "sampler" : d._type._samplerType};`;
case "storage-texture":
return ` 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(` ${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(` ${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 = ` clipPos:vec4f,
` + allVary.map((v, 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}
}
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*/
var<uniform> mesh: MeshUniforms;
/*VH*/
/*VD*/
/*VO*/
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 ? ` fn main(input: FragmentInput, frontFacing: bool)${_noColorOutput ? "" : " -> @location(0) vec4<f32>"} {` : ` 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 ? ` 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*/
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