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.

570 lines (565 loc) 21.1 kB
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