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.

339 lines (321 loc) 11.9 kB
import { B as BU, F as F32, b8 as getRenderTargetSize, b9 as getViewMatrix, ba as getProjectionMatrix, n as SS, aD as getPickingSceneBGL } from './index-DMbDahsc.esm.js'; import { a as applyGsFragments } from './gltf-feature-gaussian-splatting-BiH_nkp6.esm.js'; const gsGpuPickingFragment = { id: "gsGpuPicking", helperFunctions: ( /* wgsl */ ` struct GsPickingU { pickingColor: vec3<f32> }; @group(2) @binding(0) var<uniform> picking: GsPickingU; ` ), fragmentSlots: { GS_FRAGMENT_BEFORE_FRAGCOLOR: ( /* wgsl */ ` if (finalColor.a < 0.001) { discard; } finalColor = vec4<f32>(picking.pickingColor, 1.0); ` ) } }; function encodeIdToColor(id) { return [(id >> 16 & 255) / 255, (id >> 8 & 255) / 255, (id & 255) / 255]; } let _cache = null; const _gsPickMx = new F32(16); function buildPickingWgsl(detailed) { const wgsl = ( /* wgsl */ ` struct GsPickScene { pickMatrix: mat4x4<f32> }; @group(0) @binding(0) var<uniform> gsPickScene: GsPickScene; struct U { world: mat4x4<f32>, view: mat4x4<f32>, projection: mat4x4<f32>, viewport: vec2<f32>, focal: vec2<f32>, dataSize: vec2<f32>, alpha: f32, _pad: f32, }; @group(1) @binding(0) var<uniform> u: U; @group(1) @binding(1) var samp: sampler; @group(1) @binding(2) var centersTex: texture_2d<f32>; @group(1) @binding(3) var covATex: texture_2d<f32>; @group(1) @binding(4) var covBTex: texture_2d<f32>; @group(1) @binding(5) var colorsTex: texture_2d<f32>; struct VOut { @builtin(position) pos: vec4<f32>, @location(0) vColor: vec4<f32>, @location(1) vPos: vec2<f32>, }; fn dataUv(idx: f32) -> vec2<f32> { let y = floor(idx / u.dataSize.x); let x = idx - y * u.dataSize.x; return vec2<f32>((x + 0.5) / u.dataSize.x, (y + 0.5) / u.dataSize.y); } @vertex fn vs(@location(0) corner: vec2<f32>, @location(1) splatIndex: f32) -> VOut { var out: VOut; let uv = dataUv(splatIndex); let center = textureSampleLevel(centersTex, samp, uv, 0.0).xyz; let color = textureSampleLevel(colorsTex, samp, uv, 0.0); let covA = textureSampleLevel(covATex, samp, uv, 0.0).xyz; let covB = textureSampleLevel(covBTex, samp, uv, 0.0).xyz; let worldPos = u.world * vec4<f32>(center, 1.0); let modelView = u.view * u.world; let camspace = u.view * worldPos; let pos2d = u.projection * camspace; let bounds = 1.2 * pos2d.w; if (pos2d.z < 0.0 || pos2d.x < -bounds || pos2d.x > bounds || pos2d.y < -bounds || pos2d.y > bounds) { out.pos = vec4<f32>(0.0, 0.0, 2.0, 1.0); out.vColor = vec4<f32>(0.0); out.vPos = vec2<f32>(0.0); return out; } let Vrk = mat3x3<f32>( vec3<f32>(covA.x, covA.y, covA.z), vec3<f32>(covA.y, covB.x, covB.y), vec3<f32>(covA.z, covB.y, covB.z)); let invZ = 1.0 / camspace.z; let invZ2 = invZ * invZ; let J = mat3x3<f32>( vec3<f32>(u.focal.x * invZ, 0.0, -u.focal.x * camspace.x * invZ2), vec3<f32>(0.0, u.focal.y * invZ, -u.focal.y * camspace.y * invZ2), vec3<f32>(0.0, 0.0, 0.0)); let mv3 = mat3x3<f32>(modelView[0].xyz, modelView[1].xyz, modelView[2].xyz); let T = transpose(mv3) * J; var cov2d = transpose(T) * Vrk * T; let kernelSize: f32 = 0.3; cov2d[0][0] += kernelSize; cov2d[1][1] += kernelSize; let mid = (cov2d[0][0] + cov2d[1][1]) * 0.5; let dxy = (cov2d[0][0] - cov2d[1][1]) * 0.5; let radius = length(vec2<f32>(dxy, cov2d[0][1])); let epsilon: f32 = 0.0001; let lambda1 = mid + radius + epsilon; let lambda2 = mid - radius + epsilon; if (lambda2 < 0.0) { out.pos = vec4<f32>(0.0, 0.0, 2.0, 1.0); out.vColor = vec4<f32>(0.0); out.vPos = vec2<f32>(0.0); return out; } let diag = normalize(vec2<f32>(cov2d[0][1], lambda1 - cov2d[0][0])); let majorAxis = min(sqrt(2.0 * lambda1), 1024.0) * diag; let minorAxis = min(sqrt(2.0 * lambda2), 1024.0) * vec2<f32>(diag.y, -diag.x); let vCenter = pos2d.xy; out.pos = gsPickScene.pickMatrix * vec4<f32>( vCenter + (corner.x * majorAxis + corner.y * minorAxis) * pos2d.w / u.viewport, pos2d.z, pos2d.w); out.vColor = vec4<f32>(color.rgb, color.a * u.alpha); out.vPos = corner; return out; } /*GS_FRAGMENT_DEFINITIONS*/ struct FsOut { @location(0) color: vec4<f32>, @location(1) depth: f32${detailed ? ", @location(2) detail: vec4u" : ""} }; @fragment fn fs(in: VOut) -> FsOut { /*GS_FRAGMENT_MAIN_BEGIN*/ let A = -dot(in.vPos, in.vPos); var finalColor: vec4<f32>; if (A > -4.0) { let B = exp(A) * in.vColor.a; finalColor = vec4<f32>(in.vColor.rgb, B); } else { finalColor = vec4<f32>(0.0); } /*GS_FRAGMENT_BEFORE_FRAGCOLOR*/ /*GS_FRAGMENT_MAIN_END*/ return FsOut(finalColor, in.pos.z${detailed ? ", vec4u(0xffffffffu, 0u, 0u, 0u)" : ""}); } ` ); return applyGsFragments(wgsl, [gsGpuPickingFragment]); } function getCache(engine) { const device = engine._device; if (_cache && _cache.device === device) { return _cache; } const meshBGL = device.createBindGroupLayout({ label: "gs-picking-mesh-bgl", entries: [ { binding: 0, visibility: SS.VERTEX | SS.FRAGMENT, buffer: { type: "uniform" } }, { binding: 1, visibility: SS.VERTEX, sampler: { type: "non-filtering" } }, { binding: 2, visibility: SS.VERTEX, texture: { sampleType: "unfilterable-float" } }, { binding: 3, visibility: SS.VERTEX, texture: { sampleType: "unfilterable-float" } }, { binding: 4, visibility: SS.VERTEX, texture: { sampleType: "unfilterable-float" } }, { binding: 5, visibility: SS.VERTEX, texture: { sampleType: "unfilterable-float" } } ] }); const pickingBGL = device.createBindGroupLayout({ label: "gs-picking-pick-bgl", entries: [{ binding: 0, visibility: SS.FRAGMENT, buffer: { type: "uniform" } }] }); const pickMatrixUbo = device.createBuffer({ size: 64, usage: BU.UNIFORM | BU.COPY_DST, label: "gs-picking-scene-ubo" }); const sceneBG = device.createBindGroup({ label: "gs-picking-scene-bg", layout: getPickingSceneBGL(engine), entries: [{ binding: 0, resource: { buffer: pickMatrixUbo } }] }); _cache = { device, pipelines: /* @__PURE__ */ new Map(), meshBGL, pickingBGL, pickMatrixUbo, sceneBG }; return _cache; } function getPickingPipeline(engine, detailed) { const cache = getCache(engine); const key = detailed ? "detailed" : "basic"; const cached = cache.pipelines.get(key); if (cached) { return cached; } const device = engine._device; const module = device.createShaderModule({ label: `gs-picking-${key}-shader`, code: buildPickingWgsl(detailed) }); const pipeline = device.createRenderPipeline({ label: `gs-picking-${key}-pipeline`, layout: device.createPipelineLayout({ bindGroupLayouts: [getPickingSceneBGL(engine), cache.meshBGL, cache.pickingBGL] }), vertex: { module, entryPoint: "vs", buffers: [ { arrayStride: 8, stepMode: "vertex", attributes: [{ shaderLocation: 0, offset: 0, format: "float32x2" }] }, { arrayStride: 4, stepMode: "instance", attributes: [{ shaderLocation: 1, offset: 0, format: "float32" }] } ] }, fragment: { module, entryPoint: "fs", targets: detailed ? [{ format: "rgba8unorm" }, { format: "r32float" }, { format: "rgba32uint" }] : [{ format: "rgba8unorm" }, { format: "r32float" }] }, primitive: { topology: "triangle-list", cullMode: "none" }, depthStencil: { format: "depth24plus", depthCompare: "less", depthWriteEnabled: true }, multisample: { count: 1 } }); cache.pipelines.set(key, pipeline); return pipeline; } function gsPickWritePickMatrixAndBind(pass, engine, pickMatrix) { const cache = getCache(engine); engine._device.queue.writeBuffer(cache.pickMatrixUbo, 0, pickMatrix.buffer, pickMatrix.byteOffset, pickMatrix.byteLength); pass.setBindGroup(0, cache.sceneBG); } function createGsPickMeshResources(engine, mesh) { const device = engine._device; const cache = getCache(engine); const UBO_BYTES = 16 * 4 * 3 + 8 * 4; const meshUbo = device.createBuffer({ size: UBO_BYTES, usage: BU.UNIFORM | BU.COPY_DST, label: "gs-picking-mesh-ubo" }); const meshCpu = new F32(UBO_BYTES / 4); meshCpu[48 + 4] = mesh.textureWidth; meshCpu[48 + 5] = mesh.textureHeight; meshCpu[48 + 6] = 1; const meshBG = device.createBindGroup({ label: "gs-picking-mesh-bg", layout: cache.meshBGL, entries: [ { binding: 0, resource: { buffer: meshUbo } }, { binding: 1, resource: mesh._gs._sampler }, { binding: 2, resource: mesh._gs._centersView }, { binding: 3, resource: mesh._gs._covAView }, { binding: 4, resource: mesh._gs._covBView }, { binding: 5, resource: mesh._gs._colorsView } ] }); const pickingUbo = device.createBuffer({ size: 16, usage: BU.UNIFORM | BU.COPY_DST, label: "gs-picking-color-ubo" }); const pickingCpu = new F32(4); const pickingBG = device.createBindGroup({ label: "gs-picking-color-bg", layout: cache.pickingBGL, entries: [{ binding: 0, resource: { buffer: pickingUbo } }] }); return { meshUbo, meshBG, pickingUbo, pickingBG, meshCpu, pickingCpu }; } function disposeGsPickMeshResources(res) { res.meshUbo.destroy(); res.pickingUbo.destroy(); } function drawGsForPicking(pass, engine, scene, mesh, res, pickId, targetWidth, targetHeight, detailed = false) { const cam = scene.camera; if (!cam) { return; } const size = getRenderTargetSize(engine); const aspect = (targetWidth || size.width) / (targetHeight || size.height); const view = getViewMatrix(cam); const proj = getProjectionMatrix(cam, aspect); const world = mesh.worldMatrix; const cpu = res.meshCpu; cpu.set(world, 0); cpu.set(view, 16); cpu.set(proj, 32); cpu[48] = size.width; cpu[48 + 1] = size.height; cpu[48 + 2] = size.width * 0.5 * proj[0]; cpu[48 + 3] = size.height * 0.5 * proj[5]; engine._device.queue.writeBuffer(res.meshUbo, 0, cpu.buffer, 0, cpu.byteLength); const [r, g, b] = encodeIdToColor(pickId); res.pickingCpu[0] = r; res.pickingCpu[1] = g; res.pickingCpu[2] = b; res.pickingCpu[3] = 0; engine._device.queue.writeBuffer(res.pickingUbo, 0, res.pickingCpu.buffer, 0, 16); pass.setPipeline(getPickingPipeline(engine, detailed)); pass.setBindGroup(1, res.meshBG); pass.setBindGroup(2, res.pickingBG); pass.setVertexBuffer(0, mesh._gs._quadBuffer); pass.setVertexBuffer(1, mesh._gs._splatIndexBuffer); pass.setIndexBuffer(mesh._gs._indexBuffer, "uint16"); pass.drawIndexed(6, mesh.vertexCount); } function drawGsMeshForPicking(ctx, mesh, res, baseId) { computeGsPickMatrix(_gsPickMx, ctx.px, ctx.py, ctx.w, ctx.h); gsPickWritePickMatrixAndBind(ctx.pass, ctx.engine, _gsPickMx); drawGsForPicking(ctx.pass, ctx.engine, ctx.scene, mesh, res, baseId, ctx.w, ctx.h, ctx.detailed); } function computeGsPickMatrix(out, sampleX, sampleY, w, h) { const ndcX = 2 * sampleX / w - 1; const ndcY = 1 - 2 * sampleY / h; out[0] = w; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = h; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[10] = 1; out[11] = 0; out[12] = -ndcX * w; out[13] = -ndcY * h; out[14] = 0; out[15] = 1; } function createPickContributor(mesh) { let res = null; return { draw(ctx, baseId) { if (mesh.visible === false) { return baseId + 1; } res ??= createGsPickMeshResources(ctx.engine, mesh); drawGsMeshForPicking(ctx, mesh, res, baseId); return baseId + 1; }, resolve(info) { info.pickedMesh = mesh; }, dispose() { if (res) { disposeGsPickMeshResources(res); res = null; } } }; } export { computeGsPickMatrix, createGsPickMeshResources, createPickContributor, disposeGsPickMeshResources, drawGsForPicking, drawGsMeshForPicking, gsPickWritePickMatrixAndBind }; //# sourceMappingURL=gs-picking-pipeline-CyS6hN5Z.esm.js.map