@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.
570 lines (565 loc) • 21.1 kB
JavaScript
import { S as SS, aw as getPickingSceneBGL, h as createSingleUniformBGL, d as createMappedBuffer, B as BU, F as F32, ai as U32, e as createUniformBuffer, j as U8 } from './index-By0tcgYN.esm.js';
const PICK_SCENE = (
/* wgsl */
`
struct SceneUniforms {
viewProjection: mat4x4f,
fragmentCoord: vec2f,
};
@group(0) @binding(0) var<uniform> scene: SceneUniforms;
`
);
const DEFAULT_PICK_DISCARD = (
/* wgsl */
`
fn shouldDiscardPick(input: PickDiscardInput) -> bool {
return false;
}
`
);
const DEFAULT_PICK_WORLD_ADJUST = (
/* wgsl */
`
fn adjustPickWorld(input: PickWorldInput) -> vec3f {
return input.worldPos;
}
`
);
function inputStructs(exposeVertexData) {
return (
/* wgsl */
`
struct PickDiscardInput {
worldPos: vec3f,
fragmentCoord: vec2f,
pickId: u32,
thinInstanceIndex: u32,
hasThinInstance: u32,
instanceExtras: vec4f,
${exposeVertexData ? "vertexData: vec4f," : ""}
};
struct PickWorldInput {
worldPos: vec3f,
localPos: vec3f,
basis0: vec3f,
basis1: vec3f,
basis2: vec3f,
origin: vec3f,
instanceExtras: vec4f,
thinInstanceIndex: u32,
hasThinInstance: u32,
vertexData: vec4f,
};
`
);
}
function storageDecls(opts) {
return opts.storage?.length ? opts.storage.map((storage, binding) => `@group(2) @binding(${binding}) var<storage, read> ${storage.name}: ${storage.type};`).join("\n") : "";
}
function fragmentShader(exposeVertexData, detailed) {
return (
/* wgsl */
`
struct VsOut {
@builtin(position) p: vec4f,
@location(0) @interpolate(flat) pickId: u32,
@location(1) worldPos: vec3f,
@location(2) @interpolate(flat) thinInstanceIndex: u32,
@location(3) @interpolate(flat) hasThinInstance: u32,
@location(4) @interpolate(flat) instanceExtras: vec4f,
${exposeVertexData ? "@location(5) @interpolate(flat) vertexData: vec4f," : ""}
@location(6) @interpolate(flat) excluded: u32,
${detailed ? "@location(7) localPos: vec3f," : ""}
};
struct FsOut {
@location(0) color: vec4f,
@location(1) depth: f32,
${detailed ? "@location(2) detail: vec4u," : ""}
};
@fragment fn fs(input: VsOut${detailed ? ", @builtin(primitive_index) primitiveIndex: u32" : ""}) -> FsOut {
if (input.excluded != 0u) { discard; }
if (shouldDiscardPick(PickDiscardInput(input.worldPos, scene.fragmentCoord, input.pickId, input.thinInstanceIndex, input.hasThinInstance, input.instanceExtras${exposeVertexData ? ", input.vertexData" : ""}))) { discard; }
let id = input.pickId;
let r = f32((id >> 16u) & 0xFFu) / 255.0;
let g = f32((id >> 8u) & 0xFFu) / 255.0;
let b = f32(id & 0xFFu) / 255.0;
return FsOut(vec4f(r, g, b, 1.0), input.p.z${detailed ? ", vec4u(primitiveIndex, bitcast<u32>(input.localPos.x), bitcast<u32>(input.localPos.y), bitcast<u32>(input.localPos.z))" : ""});
}
`
);
}
function regularInput(components) {
return components === 0 ? "" : `, @location(5) vertexData: vec${components}f`;
}
function paddedVertexData(components) {
if (components === 2) {
return "vec4f(vertexData, 0.0, 0.0)";
}
if (components === 3) {
return "vec4f(vertexData, 0.0)";
}
if (components === 4) {
return "vertexData";
}
return "vec4f(0.0)";
}
function pickingShaderVariantSource(thinInstance, opts = {}) {
const components = opts.vertexDataComponents ?? 0;
const exposeVertexData = opts.exposeVertexData ?? false;
const detailed = opts.detailed ?? false;
const projection = opts._vertexProjection ?? null;
const shared = (
/* wgsl */
`
${detailed ? "enable primitive_index;" : ""}
${PICK_SCENE}
${inputStructs(exposeVertexData)}
${storageDecls(opts)}
${opts.discardWgsl ?? DEFAULT_PICK_DISCARD}
${opts.worldAdjustWgsl ?? DEFAULT_PICK_WORLD_ADJUST}
${thinInstance ? projection?.thinDeclarations ?? "" : projection?.regularDeclarations ?? ""}
${fragmentShader(exposeVertexData, detailed)}
`
);
if (!thinInstance) {
return (
/* wgsl */
`
${shared}
struct MeshUniforms {
world: mat4x4f,
pickId: u32,
};
@group(1) @binding(0) var<uniform> mesh: MeshUniforms;
@vertex fn vs(@location(0) position: vec3f${regularInput(components)}${projection?.regularInputs ?? ""}) -> VsOut {
var out: VsOut;
let vertexPayload = ${paddedVertexData(components)};
let baseWorld = (mesh.world * vec4f(position, 1.0)).xyz;
${projection?.regularBody ?? "let projectedTransform = mesh.world;\nlet projectedWorld = baseWorld;"}
let wp = adjustPickWorld(PickWorldInput(projectedWorld, position, projectedTransform[0].xyz, projectedTransform[1].xyz, projectedTransform[2].xyz, projectedTransform[3].xyz, vec4f(0.0), 0xffffffffu, 0u, vertexPayload));
out.p = scene.viewProjection * vec4f(wp, 1.0);
out.pickId = mesh.pickId;
out.worldPos = wp;
out.thinInstanceIndex = 0xffffffffu;
out.hasThinInstance = 0u;
out.instanceExtras = vec4f(0.0);
out.excluded = 0u;
${detailed ? "out.localPos = position;" : ""}
${exposeVertexData ? "out.vertexData = vertexPayload;" : ""}
return out;
}
`
);
}
return (
/* wgsl */
`
${shared}
struct TIMeshUniforms {
world: mat4x4f,
baseMeshPickId: u32,
excludedThinInstanceStart: u32,
excludedThinInstanceCount: u32,
};
@group(1) @binding(0) var<uniform> tiMesh: TIMeshUniforms;
@group(1) @binding(1) var<storage, read> instances: array<mat4x4f>;
@vertex fn vs(@location(0) position: vec3f${projection?.thinInputs ?? ""}, @builtin(instance_index) instanceIndex: u32) -> VsOut {
let packed = instances[instanceIndex];
let instanceWorld = mat4x4f(
vec4f(packed[0].xyz, 0.0),
vec4f(packed[1].xyz, 0.0),
vec4f(packed[2].xyz, 0.0),
vec4f(packed[3].xyz, 1.0),
);
let world = tiMesh.world * instanceWorld;
let extras = vec4f(packed[0].w, packed[1].w, packed[2].w, packed[3].w);
let baseWorld = (world * vec4f(position, 1.0)).xyz;
${projection?.thinBody ?? "let projectedTransform = world;\nlet projectedWorld = baseWorld;"}
let wp = adjustPickWorld(PickWorldInput(projectedWorld, position, projectedTransform[0].xyz, projectedTransform[1].xyz, projectedTransform[2].xyz, projectedTransform[3].xyz, extras, instanceIndex, 1u, vec4f(0.0)));
var out: VsOut;
out.p = scene.viewProjection * vec4f(wp, 1.0);
out.pickId = tiMesh.baseMeshPickId + instanceIndex;
out.worldPos = wp;
out.thinInstanceIndex = instanceIndex;
out.hasThinInstance = 1u;
out.instanceExtras = extras;
let excludedOffset = instanceIndex - tiMesh.excludedThinInstanceStart;
out.excluded = select(0u, 1u, tiMesh.excludedThinInstanceCount > 0u && instanceIndex >= tiMesh.excludedThinInstanceStart && excludedOffset < tiMesh.excludedThinInstanceCount);
${detailed ? "out.localPos = position;" : ""}
${exposeVertexData ? "out.vertexData = vec4f(0.0);" : ""}
return out;
}
`
);
}
let _cachedDevice = null;
let _meshBGL = null;
let _tiMeshBGL = null;
let _emptyBGL = null;
let _pipelineSets = null;
let _regularVariants = null;
let _thinVariants = null;
function invalidateIfNeeded(engine) {
if (_cachedDevice === engine._device) {
return;
}
_cachedDevice = engine._device;
_meshBGL = null;
_tiMeshBGL = null;
_emptyBGL = null;
_pipelineSets = null;
_regularVariants = null;
_thinVariants = null;
}
function getEmptyBGL(engine) {
invalidateIfNeeded(engine);
return _emptyBGL ??= engine._device.createBindGroupLayout({ label: "picking-empty-bgl", entries: [] });
}
function getPickingMeshBGL(engine) {
invalidateIfNeeded(engine);
return _meshBGL ??= createSingleUniformBGL(engine, "picking-mesh-bgl", SS.VERTEX | SS.FRAGMENT);
}
function getPickingTIMeshBGL(engine) {
invalidateIfNeeded(engine);
if (!_tiMeshBGL) {
_tiMeshBGL = engine._device.createBindGroupLayout({
label: "picking-ti-mesh-bgl",
entries: [
{ binding: 0, visibility: SS.VERTEX | SS.FRAGMENT, buffer: { type: "uniform" } },
{ binding: 1, visibility: SS.VERTEX, buffer: { type: "read-only-storage" } }
]
});
}
return _tiMeshBGL;
}
function createDiscardBGL(engine, rule) {
if (!rule.storage?.length) {
return null;
}
return engine._device.createBindGroupLayout({
label: `picking-discard-${rule.key}-bgl`,
entries: rule.storage.map((storage, binding) => ({
binding,
visibility: SS.FRAGMENT | (storage.vertex ? SS.VERTEX : 0),
buffer: { type: "read-only-storage" }
}))
});
}
function ruleKey(rule) {
if (!rule) {
return "default";
}
const stages = rule.storage?.map((storage) => storage.vertex ? "v" : "f").join("") ?? "none";
return `rule:${rule.key}:${rule.vertexData ?? "none"}:${rule.worldAdjustWgsl ? "adjust" : "identity"}:${stages}`;
}
function positionLayout(interleave) {
return {
arrayStride: interleave?._stride ?? 12,
attributes: [{ shaderLocation: 0, offset: interleave?._offset ?? 0, format: "float32x3" }]
};
}
function vertexDataLayout(attribute, interleave) {
const components = attribute === "normal" ? 3 : attribute === "uv" || attribute === "uv2" ? 2 : 4;
return {
components,
layout: {
arrayStride: interleave?._stride ?? components * 4,
attributes: [{ shaderLocation: 5, offset: interleave?._offset ?? 0, format: `float32x${components}` }]
}
};
}
function createPipeline(engine, thinInstance, rule, discardBGL, label, vertexBuffers, vertexDataComponents, detailed, vertexProjection) {
const module = engine._device.createShaderModule({
label: `${label}-shader`,
code: pickingShaderVariantSource(thinInstance, {
discardWgsl: rule?.wgsl,
worldAdjustWgsl: rule?.worldAdjustWgsl,
storage: rule?.storage,
vertexDataComponents,
exposeVertexData: !!rule?.vertexData,
detailed,
_vertexProjection: vertexProjection?.shader
})
});
const meshBGL = thinInstance ? getPickingTIMeshBGL(engine) : getPickingMeshBGL(engine);
const bindGroupLayouts = vertexProjection ? [getPickingSceneBGL(engine), meshBGL, discardBGL ?? getEmptyBGL(engine), thinInstance ? vertexProjection.thinBGL : vertexProjection.regularBGL] : discardBGL ? [getPickingSceneBGL(engine), meshBGL, discardBGL] : [getPickingSceneBGL(engine), meshBGL];
const layout = engine._device.createPipelineLayout({ label: `${label}-pipeline-layout`, bindGroupLayouts });
return engine._device.createRenderPipeline({
label: `${label}-pipeline`,
layout,
vertex: { module, entryPoint: "vs", buffers: vertexBuffers },
fragment: {
module,
entryPoint: "fs",
targets: detailed ? [{ format: "rgba8unorm" }, { format: "r32float" }, { format: "rgba32uint" }] : [{ format: "rgba8unorm" }, { format: "r32float" }]
},
depthStencil: { format: "depth24plus", depthCompare: "greater", depthWriteEnabled: true },
primitive: { topology: "triangle-list", cullMode: "none" },
multisample: { count: 1 }
});
}
function getPickingPipelineSet(engine, rule, detailed = false, vertexProjection = null) {
invalidateIfNeeded(engine);
const exactDetailed = detailed && engine._device.features.has("primitive-index");
const key = `${ruleKey(rule)}:${exactDetailed ? "detailed" : "basic"}:${vertexProjection?.key ?? "affine"}`;
const sets = _pipelineSets ??= /* @__PURE__ */ new Map();
const cached = sets.get(key);
if (cached) {
return cached;
}
const activeRule = rule ?? null;
const discardBGL = activeRule ? createDiscardBGL(engine, activeRule) : null;
const set = {
regularPipeline: createPipeline(
engine,
false,
activeRule,
discardBGL,
activeRule ? `picking-${activeRule.key}-${vertexProjection?.key ?? "affine"}` : `picking-${vertexProjection?.key ?? "affine"}`,
[positionLayout(), ...vertexProjection?.vertexBuffers ?? []],
0,
exactDetailed,
vertexProjection
),
thinInstancePipeline: createPipeline(
engine,
true,
activeRule,
discardBGL,
activeRule ? `picking-ti-${activeRule.key}-${vertexProjection?.key ?? "affine"}` : `picking-ti-${vertexProjection?.key ?? "affine"}`,
[positionLayout(), ...vertexProjection?.vertexBuffers ?? []],
0,
exactDetailed,
vertexProjection
),
discardBGL,
detailed: exactDetailed,
_vertexProjection: vertexProjection
};
sets.set(key, set);
return set;
}
function getPickVertexDataBinding(mesh, attribute) {
const gpu = mesh._gpu;
switch (attribute) {
case "normal":
return { buffer: gpu.normalBuffer, interleave: gpu._vbLayout?._n };
case "uv":
return gpu.hasUv === false ? null : { buffer: gpu.uvBuffer, interleave: gpu._vbLayout?._u };
case "uv2":
return gpu.hasUv2 === false || !gpu.uv2Buffer ? null : { buffer: gpu.uv2Buffer, interleave: gpu._vbLayout?._u2 };
case "tangent":
return gpu.hasTangent === false || !gpu.tangentBuffer ? null : { buffer: gpu.tangentBuffer, interleave: gpu._vbLayout?._t };
case "color":
return gpu.hasColor === false || !gpu.colorBuffer ? null : { buffer: gpu.colorBuffer, interleave: gpu._vbLayout?._c };
}
}
function getPickingRegularPipeline(engine, set, rule, positionInterleave, vertexData) {
if (!positionInterleave && !vertexData) {
return set.regularPipeline;
}
invalidateIfNeeded(engine);
const data = vertexData ? vertexDataLayout(vertexData.attribute, vertexData.interleave) : null;
const key = `${ruleKey(rule)}:${set.detailed ? "detailed" : "basic"}:${set._vertexProjection?.key ?? "affine"}:p${positionInterleave?._stride ?? 12},${positionInterleave?._offset ?? 0}:d${vertexData ? `${vertexData.attribute},${data.layout.arrayStride},${vertexData.interleave?._offset ?? 0}` : "none"}`;
const variants = _regularVariants ??= /* @__PURE__ */ new Map();
const cached = variants.get(key);
if (cached) {
return cached;
}
const label = `picking-vb-${key}`;
const pipeline = createPipeline(
engine,
false,
rule,
set.discardBGL,
label,
data ? [positionLayout(positionInterleave), data.layout, ...set._vertexProjection?.vertexBuffers ?? []] : [positionLayout(positionInterleave), ...set._vertexProjection?.vertexBuffers ?? []],
data?.components ?? 0,
set.detailed,
set._vertexProjection
);
variants.set(key, pipeline);
return pipeline;
}
function getPickingThinInstancePipeline(engine, set, rule, positionInterleave) {
if (!positionInterleave) {
return set.thinInstancePipeline;
}
invalidateIfNeeded(engine);
const key = `${ruleKey(rule)}:${set.detailed ? "detailed" : "basic"}:${set._vertexProjection?.key ?? "affine"}:p${positionInterleave._stride},${positionInterleave._offset}`;
const variants = _thinVariants ??= /* @__PURE__ */ new Map();
const cached = variants.get(key);
if (cached) {
return cached;
}
const pipeline = createPipeline(
engine,
true,
rule,
set.discardBGL,
`picking-ti-vb-${key}`,
[positionLayout(positionInterleave), ...set._vertexProjection?.vertexBuffers ?? []],
0,
set.detailed,
set._vertexProjection
);
variants.set(key, pipeline);
return pipeline;
}
const UBO_BYTES = 80;
const _scratch = new ArrayBuffer(UBO_BYTES);
const _f32 = new F32(_scratch);
const _u32 = new U32(_scratch);
const _view = new U8(_scratch);
function ignoredRange(ignore) {
if (ignore?.thinInstanceRange) {
return [Math.max(0, ignore.thinInstanceRange.start | 0), Math.max(0, ignore.thinInstanceRange.count | 0)];
}
return ignore?.thinInstanceIndex === void 0 ? [0, 0] : [Math.max(0, ignore.thinInstanceIndex | 0), 1];
}
function discardGroup(engine, layout, rule, mesh, temporary) {
if (!rule.storage?.length) {
return null;
}
const entries = [];
for (let i = 0; i < rule.storage.length; i++) {
const data = rule.storage[i].data(mesh);
if (!data) {
return null;
}
const buffer = createMappedBuffer(engine, data, BU.STORAGE, "pick-discard-storage");
temporary.push(buffer);
entries.push({ binding: i, resource: { buffer } });
}
return engine._device.createBindGroup({ layout, entries });
}
async function prepareAdvancedDraw(engine, candidates) {
const vat = candidates.some((candidate) => !!candidate.mesh.vat) ? await import('./vat-picking-pipeline-C0gkoqbF.esm.js') : null;
return {
draw(pass, sceneBG, startId, rule, detailed, deformed, detail, temporary, detailedPositions, detailedNormals) {
let nextId = startId;
const ranges = [];
for (const candidate of candidates) {
const mesh = candidate.mesh;
const gpu = mesh._gpu;
const ti = mesh.thinInstances;
const projection = vat?.getVatPickingProjection(engine, mesh) ?? null;
const defaults = getPickingPipelineSet(engine, null, detailed, projection);
const discarded = rule ? getPickingPipelineSet(engine, rule, detailed, projection) : null;
const discardBG = rule && discarded?.discardBGL ? discardGroup(engine, discarded.discardBGL, rule, mesh, temporary) : null;
const set = discarded && (!discarded.discardBGL || discardBG) ? discarded : defaults;
const activeRule = set === discarded ? rule : null;
let position = gpu.positionBuffer;
let pickPositions;
if (deformed && (mesh.morphTargets || mesh.skeleton)) {
const positions = deformed.computeDeformedPositions(mesh);
if (positions) {
position = createMappedBuffer(engine, positions, BU.VERTEX, "pick-deformed-position");
temporary.push(position);
pickPositions = positions;
}
} else if (detailed) {
pickPositions = mesh._cpuPositions;
}
if (detailedPositions && pickPositions) {
detailedPositions.set(mesh, pickPositions);
}
if (detailedNormals && mesh._cpuNormals) {
detailedNormals.set(mesh, mesh._cpuNormals);
}
const world = detail ? detail.copyDetailedWorldMatrix(mesh.worldMatrix) : null;
_f32.set(mesh.worldMatrix, 0);
_u32[16] = nextId;
if (ti) {
if (ti.count <= 0 || !ti._gpuBuffer) {
continue;
}
const [ignoredStart, ignoredCount] = ignoredRange(candidate.ignore);
_u32[17] = ignoredStart;
_u32[18] = ignoredCount;
const ubo2 = createUniformBuffer(engine, _view, "pick-thin-instance-ubo");
temporary.push(ubo2);
const interleave2 = position === gpu.positionBuffer ? gpu._vbLayout?._p : void 0;
const pipeline2 = getPickingThinInstancePipeline(engine, set, activeRule, interleave2);
pass.setPipeline(pipeline2);
pass.setBindGroup(0, sceneBG);
pass.setBindGroup(
1,
engine._device.createBindGroup({
layout: pipeline2.getBindGroupLayout(1),
entries: [
{ binding: 0, resource: { buffer: ubo2 } },
{ binding: 1, resource: { buffer: ti._gpuBuffer } }
]
})
);
if (discardBG) {
pass.setBindGroup(2, discardBG);
}
pass.setVertexBuffer(0, position);
if (projection && vat) {
vat.bindVatPickingProjection(engine, pass, pipeline2, mesh, true, 1);
}
pass.setIndexBuffer(gpu.indexBuffer, gpu.indexFormat);
pass.drawIndexed(gpu.indexCount, ti.count);
ranges.push({
base: nextId,
count: ti.count,
mesh,
thin: true,
world,
thinVersion: ti._version,
worldAdjusted: !!activeRule?.worldAdjustWgsl || !!projection
});
nextId += ti.count;
continue;
}
const ubo = createUniformBuffer(engine, _view, "pick-mesh-ubo");
temporary.push(ubo);
const vertexData = activeRule?.vertexData ? getPickVertexDataBinding(mesh, activeRule.vertexData) : null;
const interleave = position === gpu.positionBuffer ? gpu._vbLayout?._p : void 0;
const pipeline = getPickingRegularPipeline(
engine,
set,
activeRule,
interleave,
vertexData && activeRule?.vertexData ? { attribute: activeRule.vertexData, interleave: vertexData.interleave } : null
);
pass.setPipeline(pipeline);
pass.setBindGroup(0, sceneBG);
pass.setBindGroup(
1,
engine._device.createBindGroup({
layout: pipeline.getBindGroupLayout(1),
entries: [{ binding: 0, resource: { buffer: ubo } }]
})
);
if (discardBG) {
pass.setBindGroup(2, discardBG);
}
pass.setVertexBuffer(0, position);
if (vertexData) {
pass.setVertexBuffer(1, vertexData.buffer);
}
if (projection && vat) {
vat.bindVatPickingProjection(engine, pass, pipeline, mesh, false, vertexData ? 2 : 1);
}
pass.setIndexBuffer(gpu.indexBuffer, gpu.indexFormat);
pass.drawIndexed(gpu.indexCount);
ranges.push({
base: nextId,
count: 1,
mesh,
thin: false,
world,
thinVersion: 0,
worldAdjusted: !!activeRule?.worldAdjustWgsl || !!projection
});
nextId++;
}
return { nextId, ranges };
}
};
}
export { prepareAdvancedDraw };
//# sourceMappingURL=picking-advanced-draw-DLTEQUYb.esm.js.map