UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

3 lines (2 loc) 4.63 kB
export const computeGsplatShadowCullSource: "\n\n// Conservative gaussian extent (in std-devs) for the per-splat frustum radius, so a splat whose\n// tail still reaches into the light frustum is not culled at the boundary.\nconst SPLAT_FRUSTUM_SIGMA: f32 = 3.0;\n\nstruct CullUniforms {\n frustumPlanes: array<vec4f, 6>,\n numIntervals: u32,\n splatTextureSize: u32,\n alphaClip: f32,\n worldSizeThreshold: f32\n};\n@group(0) @binding(0) var<uniform> uniforms: CullUniforms;\n\n// Pass-1 outputs (read): the shared candidate list + the candidate count at [numIntervals].\n@group(0) @binding(1) var<storage, read> compactedSplatIds: array<u32>;\n@group(0) @binding(2) var<storage, read> candidateCountBuffer: array<u32>;\n\n// Per-light outputs: dense visible work-buffer indices (unordered) + a single atomic visible count.\n@group(0) @binding(3) var<storage, read_write> outputIndices: array<u32>;\n@group(0) @binding(4) var<storage, read_write> globalCount: array<atomic<u32>>;\n\n// Work-buffer format texture bindings (binding 5+) and the splat read/modify helpers. setSplat\n// (gsplatComputeSplatCS) reads uniforms.splatTextureSize, so the uniform struct is declared above.\n#include \"gsplatComputeSplatCS\"\n#include \"gsplatFormatDeclCS\"\n#include \"gsplatFormatReadCS\"\n#include \"gsplatHelpersVS\"\n#include \"gsplatModifyVS\"\n\n// Fine per-splat cull: read + apply the render-stage modifier + opacity/size/frustum tests. Returns\n// true when the splat should be drawn into the shadow map.\nfn fineCull(splatId: u32) -> bool {\n setSplat(splatId);\n\n // world-space center + render-stage position modifier (matches the quad VS / forward projector)\n let originalCenter = getCenter();\n var center = originalCenter;\n modifySplatCenter(&center);\n\n // opacity cull (matches the forward alpha cull + the shadow PS alphaClip)\n let opacity = getOpacity();\n if (opacity <= uniforms.alphaClip) {\n return false;\n }\n\n // render-stage rotation/scale modifier; getRotation() is (w,x,y,z), the hook contract is (x,y,z,w)\n var rotation: vec4f = getRotation().yzwx;\n var scale: vec3f = getScale();\n modifySplatRotationScale(originalCenter, center, &rotation, &scale);\n\n // size cull: the orthographic shadow projection is linear, so a splat whose max world-space\n // extent maps below the precomputed per-light threshold is sub-pixel in the shadow map. The max\n // scale axis is an orientation-independent upper bound on the projected radius (conservative \u2014\n // never culls a splat the forward path would keep). A threshold of 0 disables the test.\n let maxScale = max(scale.x, max(scale.y, scale.z));\n if (maxScale < uniforms.worldSizeThreshold) {\n return false;\n }\n\n // per-splat frustum cull: the modified center (\u00B1 a conservative gaussian radius) against the\n // light's 6 frustum planes. Trims splats in boundary nodes; the ortho near/far planes also cull\n // by depth.\n let splatRadius = maxScale * SPLAT_FRUSTUM_SIGMA;\n for (var p = 0; p < 6; p++) {\n let plane = uniforms.frustumPlanes[p];\n if (dot(plane.xyz, center) + plane.w <= -splatRadius) {\n return false;\n }\n }\n\n return true;\n}\n\n// One global atomicAdd per workgroup; intra-workgroup slots via a workgroup atomic (no subgroups).\nvar<workgroup> wgCount: atomic<u32>;\nvar<workgroup> wgBase: u32;\n\n@compute @workgroup_size({WORKGROUP_SIZE})\nfn main(\n @builtin(global_invocation_id) gid: vec3u,\n @builtin(num_workgroups) numWorkgroups: vec3u,\n @builtin(local_invocation_index) localIdx: u32\n) {\n if (localIdx == 0u) {\n atomicStore(&wgCount, 0u);\n }\n workgroupBarrier();\n\n // flat thread index from the (possibly Y-tiled) 2D dispatch grid\n let threadIdx = gid.y * (numWorkgroups.x * {WORKGROUP_SIZE}u) + gid.x;\n let candidateCount = candidateCountBuffer[uniforms.numIntervals];\n\n var valid = false;\n var splatId = 0u;\n if (threadIdx < candidateCount) {\n splatId = compactedSplatIds[threadIdx];\n valid = fineCull(splatId);\n }\n\n var localSlot = 0u;\n if (valid) {\n localSlot = atomicAdd(&wgCount, 1u);\n }\n workgroupBarrier();\n\n // workgroup leader reserves a contiguous global block for this workgroup's survivors\n if (localIdx == 0u) {\n wgBase = atomicAdd(&globalCount[0], atomicLoad(&wgCount));\n }\n workgroupBarrier();\n\n if (valid) {\n outputIndices[wgBase + localSlot] = splatId;\n }\n}\n"; export default computeGsplatShadowCullSource;