UNPKG

playcanvas

Version:

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

2 lines (1 loc) 7.38 kB
export const computeGsplatCommonSource: "\n\n#include \"halfTypesCS\"\n\nconst TILE_SIZE: u32 = 16u;\n\nfn quatToMat3(r: half4) -> half3x3 {\n let r2: half4 = r + r;\n let x: half = r2.x * r.w;\n let y: half4 = r2.y * r;\n let z: half4 = r2.z * r;\n let w: half = r2.w * r.w;\n\n return half3x3(\n half(1.0) - z.z - w, y.z + x, y.w - z.x,\n y.z - x, half(1.0) - y.y - w, z.w + y.x,\n y.w + z.x, z.w - y.x, half(1.0) - y.y - z.z\n );\n}\n\nstruct SplatCov2D {\n screen: vec2f,\n a: f32,\n b: f32,\n c: f32,\n viewDepth: f32,\n valid: bool,\n}\n\nfn computeSplatCov(\n worldCenter: vec3f,\n rotation: half4,\n scale: half3,\n viewMatrix: mat4x4f,\n viewProj: mat4x4f,\n focal: f32,\n viewportWidth: f32,\n viewportHeight: f32,\n nearClip: f32,\n farClip: f32,\n opacity: f32,\n minPixelSize: f32,\n isOrtho: u32,\n alphaClip: f32,\n minContribution: f32,\n #ifdef GSPLAT_FISHEYE\n fisheye_k: f32,\n fisheye_inv_k: f32,\n fisheye_projMat00: f32,\n fisheye_projMat11: f32,\n #endif\n) -> SplatCov2D {\n var result: SplatCov2D;\n result.valid = false;\n\n let viewCenter = (viewMatrix * vec4f(worldCenter, 1.0)).xyz;\n\n #ifdef GSPLAT_FISHEYE\n\n // Generalized fisheye: g(\u03B8) = k\u00B7tan(\u03B8/k)\n let fv = viewCenter;\n let r_xy = length(fv.xy);\n let neg_z = -fv.z;\n let theta = atan2(r_xy, neg_z);\n\n // Cull near the singularity at \u03B8 = k\u00B7\u03C0/2, and at camera origin\n let maxTheta = min(fisheye_k * 1.5707963, 3.13);\n if (theta > maxTheta - 0.01 || dot(fv, fv) < 0.0001) {\n return result;\n }\n\n let tk = theta * fisheye_inv_k;\n let sin_tk = sin(tk);\n let cos_tk = cos(tk);\n let g_theta = fisheye_k * sin_tk / cos_tk;\n let fisheye_s = select(select(0.0, 1.0 / neg_z, neg_z > 0.0), g_theta / r_xy, r_xy > 1e-4);\n\n let fndc = vec2f(fisheye_projMat00 * fisheye_s * fv.x, fisheye_projMat11 * fisheye_s * fv.y);\n let screen = vec2f(\n (fndc.x * 0.5 + 0.5) * viewportWidth,\n (fndc.y * 0.5 + 0.5) * viewportHeight\n );\n\n #else\n\n if (viewCenter.z > 0.0) {\n return result;\n }\n\n let clip = viewProj * vec4f(worldCenter, 1.0);\n let ndc = clip.xy / clip.w;\n let screen = vec2f(\n (ndc.x * 0.5 + 0.5) * viewportWidth,\n (ndc.y * 0.5 + 0.5) * viewportHeight\n );\n\n #endif\n\n let rot: half3x3 = quatToMat3(rotation);\n let s: vec3f = vec3f(scale);\n let M: mat3x3f = transpose(mat3x3f(\n s.x * vec3f(rot[0]),\n s.y * vec3f(rot[1]),\n s.z * vec3f(rot[2])\n ));\n\n let w0 = vec3f(viewMatrix[0].x, viewMatrix[1].x, viewMatrix[2].x);\n let w1 = vec3f(viewMatrix[0].y, viewMatrix[1].y, viewMatrix[2].y);\n let w2 = vec3f(viewMatrix[0].z, viewMatrix[1].z, viewMatrix[2].z);\n\n #ifdef GSPLAT_FISHEYE\n\n // Fisheye Jacobian for g(\u03B8) = k\u00B7tan(\u03B8/k)\n let fisheyeFocal = viewportWidth * fisheye_projMat00;\n let g_prime = 1.0 / (cos_tk * cos_tk);\n let d2 = dot(fv, fv);\n let r_sq = max(r_xy * r_xy, 1e-8);\n let K_coeff = select(0.0, (g_prime * neg_z / d2 - fisheye_s) / r_sq, r_xy > 1e-4);\n\n let Jxx = fisheyeFocal * (fisheye_s + K_coeff * fv.x * fv.x);\n let Jxy = fisheyeFocal * K_coeff * fv.x * fv.y;\n let Jyy = fisheyeFocal * (fisheye_s + K_coeff * fv.y * fv.y);\n let Jzx = fisheyeFocal * g_prime * fv.x / d2;\n let Jzy = fisheyeFocal * g_prime * fv.y / d2;\n\n let tt0 = Jxx * w0 + Jxy * w1 + Jzx * w2;\n let tt1 = Jxy * w0 + Jyy * w1 + Jzy * w2;\n\n #else\n\n let ortho = isOrtho == 1u;\n let v = select(viewCenter.xyz, vec3f(0.0, 0.0, 1.0), ortho);\n let vz = select(min(v.z, -0.001), v.z, ortho);\n let J1 = focal / vz;\n let J2 = -J1 / vz * v.xy;\n\n // Compute TT columns directly without materializing full J and W matrices.\n // Original code:\n // let J = mat3x3f(vec3f(J1, 0.0, J2.x), vec3f(0.0, J1, J2.y), vec3f(0.0, 0.0, 0.0));\n // let W = transpose(mat3x3f(viewMatrix[0].xyz, viewMatrix[1].xyz, viewMatrix[2].xyz));\n // let TT = W * J;\n let tt0 = J1 * w0 + J2.x * w2;\n let tt1 = J1 * w1 + J2.y * w2;\n\n #endif\n\n // Fused covariance: cov = TT^T * Vrk * TT = TT^T * (M^T * M) * TT = (M * TT)^T * (M * TT).\n // Compute B = M * TT then cov = B^T * B, avoiding the intermediate Vrk (mat3x3f) matrix.\n let b0 = M * tt0;\n let b1 = M * tt1;\n\n let a = dot(b0, b0) + 0.3;\n let b = dot(b0, b1);\n let c = dot(b1, b1) + 0.3;\n\n let det = a * c - b * b;\n if (det <= 0.0) {\n return result;\n }\n\n // Rejects splats whose total visual contribution (opacity * projected area) is\n // negligible. Near the camera, projected areas are large so contributions naturally\n // exceed the threshold; at distance, areas shrink and low-impact splats are culled.\n let totalContribution = opacity * 6.283185 * sqrt(det);\n if (totalContribution < minContribution) {\n return result;\n }\n\n // Opacity-aware radius tightening based on FlashGS\n // https://github.com/InternLandMark/FlashGS\n // The fixed factor 8.0 corresponds to power = -4.0 (exp(-4) \u2248 0.018).\n // For low-opacity splats, pixels become invisible (alpha < alphaClip) at a closer\n // distance. We solve for the power where opacity * exp(power) = alphaClip,\n // giving radiusFactor = min(8.0, 2.0 * ln(opacity / alphaClip)). This shrinks\n // the effective radius for low-opacity splats, reducing tile assignments.\n let radiusFactor = computeRadiusFactor(half(opacity), alphaClip);\n\n let vmin = min(1024.0, min(viewportWidth, viewportHeight));\n let maxRadius = vmin;\n let radiusXUncapped = sqrt(2.0 * a);\n let radiusYUncapped = sqrt(2.0 * c);\n let radiusX = min(radiusXUncapped, maxRadius);\n let radiusY = min(radiusYUncapped, maxRadius);\n\n if (max(radiusX, radiusY) < minPixelSize) {\n return result;\n }\n\n // Frustum cull: reject splats entirely off-screen\n if (screen.x + radiusX < 0.0 || screen.x - radiusX > viewportWidth ||\n screen.y + radiusY < 0.0 || screen.y - radiusY > viewportHeight) {\n return result;\n }\n\n // When the projected extent exceeds the radius cap, rescale the covariance\n // so the Gaussian reaches its cutoff at the capped boundary. Without this,\n // the Gaussian is still opaque at the boundary, creating hard rectangular\n // edges. This matches the quad renderer's implicit UV renormalization.\n let capScale = max(1.0, max(radiusXUncapped, radiusYUncapped) / maxRadius);\n let invCapScale2 = 1.0 / (capScale * capScale);\n\n result.screen = screen;\n let scaledCov = vec3f(a, b, c) * invCapScale2;\n result.a = scaledCov.x;\n result.b = scaledCov.y;\n result.c = scaledCov.z;\n #ifdef GSPLAT_FISHEYE\n result.viewDepth = sqrt(d2);\n #else\n result.viewDepth = -viewCenter.z;\n #endif\n result.valid = true;\n return result;\n}\n";