gaussian-splat-lite 1.1.2 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,13 @@ and this project follows [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.1.3] - 2026-09-23
11
+
12
+ ### Changed
13
+
14
+ - Optimized manual sorted rendering across WebGL, WebGPU, and WebGL fallback by removing unused stochastic and depth branches from the color fragment shader. Auto and stochastic modes retain uniform-based switching; custom GLSL shaders are unchanged.
15
+ - Native WebGPU reuses projection, visibility compaction, and sort results when source data, views, and projection settings are unchanged.
16
+
10
17
  ## [1.1.2] - 2026-09-21
11
18
 
12
19
  ### Added
@@ -388,7 +395,8 @@ and this project follows [Semantic Versioning](https://semver.org/).
388
395
 
389
396
  - Initial public release of the Three.js Gaussian Splatting renderer.
390
397
 
391
- [Unreleased]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.1.2...HEAD
398
+ [Unreleased]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.1.3...HEAD
399
+ [1.1.3]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.1.2...v1.1.3
392
400
  [1.1.2]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.1.1...v1.1.2
393
401
  [1.1.1]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.1.0...v1.1.1
394
402
  [1.1.0]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.0.4...v1.1.0
@@ -24456,7 +24456,7 @@ class FullScreenQuad {
24456
24456
  }
24457
24457
  }
24458
24458
  var splatDefines_default = "const uint SPLAT_TEX_WIDTH_BITS = 11u;\nconst uint SPLAT_TEX_HEIGHT_BITS = 11u;\nconst uint SPLAT_TEX_LAYER_BITS = SPLAT_TEX_WIDTH_BITS + SPLAT_TEX_HEIGHT_BITS;\n\nconst uint SPLAT_TEX_WIDTH = 1u << SPLAT_TEX_WIDTH_BITS;\nconst uint SPLAT_TEX_HEIGHT = 1u << SPLAT_TEX_HEIGHT_BITS;\n\nconst uint SPLAT_TEX_WIDTH_MASK = SPLAT_TEX_WIDTH - 1u;\nconst uint SPLAT_TEX_HEIGHT_MASK = SPLAT_TEX_HEIGHT - 1u;\n\nconst float PI = 3.1415926535897932384626433832795;\n\nvec3 srgbToLinear(vec3 rgb) {\n \n return mix(\n pow(rgb * 0.9478672986 + vec3(0.0521327014), vec3(2.4)),\n rgb * 0.0773993808, lessThanEqual(rgb, vec3(0.04045)));\n}\n\nuint encodeQuatOctXy1010R12(vec4 q) {\n \n if (q.w < 0.0) {\n q = -q;\n }\n \n float theta = 2.0 * acos(clamp(q.w, 0.0, 1.0));\n\n \n \n \n float sum = abs(q.x) + abs(q.y) + abs(q.z);\n \n vec2 p = (sum < 1e-6) ? vec2(1.0, 0.0) : q.xy / sum;\n \n if (q.z < 0.0) {\n float oldPx = p.x;\n p.x = (1.0 - abs(p.y)) * (p.x >= 0.0 ? 1.0 : -1.0);\n p.y = (1.0 - abs(oldPx)) * (p.y >= 0.0 ? 1.0 : -1.0);\n }\n \n float u_f = p.x * 0.5 + 0.5;\n float v_f = p.y * 0.5 + 0.5;\n \n uint quantU = uint(clamp(round(u_f * 1023.0), 0.0, 1023.0));\n uint quantV = uint(clamp(round(v_f * 1023.0), 0.0, 1023.0));\n \n \n \n uint angleInt = uint(clamp(round((theta / PI) * 4095.0), 0.0, 4095.0));\n \n \n return (angleInt << 20u) | (quantV << 10u) | quantU;\n}\n\nvec4 decodeQuatOctXy1010R12(uint encoded) {\n \n uint quantU = encoded & uint(0x3FFu); \n uint quantV = (encoded >> 10u) & uint(0x3FFu); \n uint angleInt = encoded >> 20u; \n\n \n float u_f = float(quantU) / 1023.0;\n float v_f = float(quantV) / 1023.0;\n vec2 f = vec2(u_f * 2.0 - 1.0, v_f * 2.0 - 1.0);\n\n vec3 axis = vec3(f.xy, 1.0 - abs(f.x) - abs(f.y));\n float t = max(-axis.z, 0.0);\n axis.x += (axis.x >= 0.0) ? -t : t;\n axis.y += (axis.y >= 0.0) ? -t : t;\n axis = normalize(axis);\n \n \n float theta = (float(angleInt) / 4095.0) * PI;\n float halfTheta = theta * 0.5;\n float s = sin(halfTheta);\n float w = cos(halfTheta);\n \n return vec4(axis * s, w);\n}\n\nvec3 decodeSplatCenter(uvec4 splatData) {\n return vec3(\n uintBitsToFloat(splatData.x),\n uintBitsToFloat(splatData.y),\n uintBitsToFloat(splatData.z)\n );\n}\n\nvoid encodeSplatLnScale(\n out uvec4 splatData, out uvec4 splatData2,\n vec3 center, vec3 lnScales, vec4 quaternion, vec4 rgba, float shapeAmount\n) {\n splatData.x = floatBitsToUint(center.x);\n splatData.y = floatBitsToUint(center.y);\n splatData.z = floatBitsToUint(center.z);\n splatData.w = packHalf2x16(clamp(vec2(rgba.a, shapeAmount), 0.0, 1.0));\n\n splatData2.x = packHalf2x16(rgba.rg);\n splatData2.y = packHalf2x16(vec2(rgba.b, lnScales.x));\n splatData2.z = packHalf2x16(lnScales.yz);\n splatData2.w = encodeQuatOctXy1010R12(quaternion);\n}\n\nvec2 decodeSplatAlphaShapeAmount(uvec4 splatData) {\n return unpackHalf2x16(splatData.w);\n}\n\nvoid decodeSplatAttributesLnScale(\n uvec4 splatData2,\n float alpha,\n out vec3 lnScales, out vec4 quaternion, out vec4 rgba\n) {\n rgba.a = alpha;\n\n rgba.rg = unpackHalf2x16(splatData2.x);\n vec2 split = unpackHalf2x16(splatData2.y);\n rgba.b = split.x;\n lnScales.x = split.y;\n lnScales.yz = unpackHalf2x16(splatData2.z);\n quaternion = decodeQuatOctXy1010R12(splatData2.w);\n}\n\nvec3 decodeSplatShRgb(uint encoded) {\n uint biasedBase = (encoded >> 27u) & 0x1fu;\n \n \n float divisor = uintBitsToFloat((biasedBase + 112u) << 23u) / 255.0;\n\n vec3 rgb = vec3(uvec3(encoded & 0xffu, (encoded >> 8u) & 0xffu, (encoded >> 16u) & 0xffu));\n rgb *= divisor;\n\n return vec3(\n ((encoded & 0x1000000u) != 0u) ? -rgb.r : rgb.r,\n ((encoded & 0x2000000u) != 0u) ? -rgb.g : rgb.g,\n ((encoded & 0x4000000u) != 0u) ? -rgb.b : rgb.b\n );\n}\n\nvec3 quatVec(vec4 q, vec3 v) {\n \n vec3 t = 2.0 * cross(q.xyz, v);\n return v + q.w * t + cross(q.xyz, t);\n}\n\nvec4 quatQuat(vec4 q1, vec4 q2) {\n return vec4(\n q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y,\n q1.w * q2.y - q1.x * q2.z + q1.y * q2.w + q1.z * q2.x,\n q1.w * q2.z + q1.x * q2.y - q1.y * q2.x + q1.z * q2.w,\n q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z\n );\n}\n\nmat3 scaleQuaternionToMatrix(vec3 s, vec4 q) {\n \n return mat3(\n s.x * (1.0 - 2.0 * (q.y * q.y + q.z * q.z)),\n s.x * (2.0 * (q.x * q.y + q.w * q.z)),\n s.x * (2.0 * (q.x * q.z - q.w * q.y)),\n s.y * (2.0 * (q.x * q.y - q.w * q.z)),\n s.y * (1.0 - 2.0 * (q.x * q.x + q.z * q.z)),\n s.y * (2.0 * (q.y * q.z + q.w * q.x)),\n s.z * (2.0 * (q.x * q.z + q.w * q.y)),\n s.z * (2.0 * (q.y * q.z - q.w * q.x)),\n s.z * (1.0 - 2.0 * (q.x * q.x + q.y * q.y))\n );\n}\n\nivec3 splatTexCoord(int index) {\n uint x = uint(index) & SPLAT_TEX_WIDTH_MASK;\n uint y = (uint(index) >> SPLAT_TEX_WIDTH_BITS) & SPLAT_TEX_HEIGHT_MASK;\n uint z = uint(index) >> SPLAT_TEX_LAYER_BITS;\n return ivec3(x, y, z);\n}";
24459
- var splatFragment_default = "precision highp float;\nprecision highp int;\n\n#include <splatDefines>\n\nuniform float near;\nuniform float far;\n#ifndef GSL_COLOR_IN_VERTEX\nuniform bool encodeLinear;\n#endif\nuniform float time;\nuniform float minAlpha;\nuniform bool stochastic;\nuniform vec4 stochasticTemporalSample;\nuniform bool stochasticResolve;\nuniform bool depthOnly;\nuniform highp usampler2D stochasticNoise;\nuniform vec2 viewportOrigin;\n\nout vec4 fragColor;\n\nin vec4 vRgba;\nin vec2 vSplatUv;\nflat in uint vStochasticHash;\nflat in float vSupportRadiusSquared;\nflat in float vKernelPower;\n\n#include <logdepthbuf_pars_fragment>\n\nvoid main() {\n vec4 rgba = vRgba;\n\n float z2 = dot(vSplatUv, vSplatUv);\n if (z2 > vSupportRadiusSquared) {\n discard;\n }\n\n float kernelAlpha = exp(-0.5 * z2);\n if (vKernelPower != 0.0) {\n kernelAlpha = 1.0 - pow(1.0 - kernelAlpha, vKernelPower);\n }\n rgba.a *= kernelAlpha;\n\n if (rgba.a < minAlpha) {\n discard;\n }\n if (stochastic || depthOnly) {\n \n uvec2 pixel = uvec2(gl_FragCoord.xy - viewportOrigin);\n uvec2 offset = uvec2(vStochasticHash, vStochasticHash >> 5u);\n if (stochastic && !depthOnly) offset += uvec2(stochasticTemporalSample.zw);\n ivec2 coord = ivec2((pixel + offset) & uvec2(31u));\n float randomValue = (float(texelFetch(stochasticNoise, coord, 0).r) + 0.5) / 1024.0;\n if (randomValue >= rgba.a) {\n discard;\n }\n\n if (depthOnly) {\n fragColor = vec4(0.0);\n #include <logdepthbuf_fragment>\n return;\n }\n }\n #ifndef GSL_COLOR_IN_VERTEX\n if (encodeLinear) {\n rgba.rgb = srgbToLinear(rgba.rgb);\n }\n #endif\n\n if (stochastic) {\n \n \n fragColor = vec4(rgba.rgb, stochasticResolve ? 2.0 : 1.0);\n #include <logdepthbuf_fragment>\n return;\n }\n\n #ifdef PREMULTIPLIED_ALPHA\n fragColor = vec4(rgba.rgb * rgba.a, rgba.a);\n #else\n fragColor = rgba;\n #endif\n\n #include <logdepthbuf_fragment>\n}";
24459
+ var splatFragment_default = "precision highp float;\nprecision highp int;\n\n#include <splatDefines>\n\nuniform float near;\nuniform float far;\n#ifndef GSL_COLOR_IN_VERTEX\nuniform bool encodeLinear;\n#endif\nuniform float time;\nuniform float minAlpha;\nuniform bool stochastic;\nuniform vec4 stochasticTemporalSample;\nuniform bool stochasticResolve;\nuniform bool depthOnly;\nuniform highp usampler2D stochasticNoise;\nuniform vec2 viewportOrigin;\n\nout vec4 fragColor;\n\nin vec4 vRgba;\nin vec2 vSplatUv;\nflat in uint vStochasticHash;\nflat in float vSupportRadiusSquared;\nflat in float vKernelPower;\n\n#include <logdepthbuf_pars_fragment>\n\nvoid main() {\n vec4 rgba = vRgba;\n\n float z2 = dot(vSplatUv, vSplatUv);\n if (z2 > vSupportRadiusSquared) {\n discard;\n }\n\n float kernelAlpha = exp(-0.5 * z2);\n if (vKernelPower != 0.0) {\n kernelAlpha = 1.0 - pow(1.0 - kernelAlpha, vKernelPower);\n }\n rgba.a *= kernelAlpha;\n\n if (rgba.a < minAlpha) {\n discard;\n }\n #if !GSL_SORTED_FRAGMENT\n if (stochastic || depthOnly) {\n \n uvec2 pixel = uvec2(gl_FragCoord.xy - viewportOrigin);\n uvec2 offset = uvec2(vStochasticHash, vStochasticHash >> 5u);\n if (stochastic && !depthOnly) offset += uvec2(stochasticTemporalSample.zw);\n ivec2 coord = ivec2((pixel + offset) & uvec2(31u));\n float randomValue = (float(texelFetch(stochasticNoise, coord, 0).r) + 0.5) / 1024.0;\n if (randomValue >= rgba.a) {\n discard;\n }\n\n if (depthOnly) {\n fragColor = vec4(0.0);\n #include <logdepthbuf_fragment>\n return;\n }\n }\n #ifndef GSL_COLOR_IN_VERTEX\n if (encodeLinear) {\n rgba.rgb = srgbToLinear(rgba.rgb);\n }\n #endif\n\n if (stochastic) {\n \n \n fragColor = vec4(rgba.rgb, stochasticResolve ? 2.0 : 1.0);\n #include <logdepthbuf_fragment>\n return;\n }\n #endif\n\n #ifdef PREMULTIPLIED_ALPHA\n fragColor = vec4(rgba.rgb * rgba.a, rgba.a);\n #else\n fragColor = rgba;\n #endif\n\n #include <logdepthbuf_fragment>\n}";
24460
24460
  var splatVertex_default = "precision highp float;\nprecision highp int;\nprecision highp usampler2DArray;\n\n#include <splatDefines>\n\nout vec4 vRgba;\nout vec2 vSplatUv;\nflat out uint vStochasticHash;\nflat out float vSupportRadiusSquared;\nflat out float vKernelPower;\n\nuniform vec2 renderSize;\nuniform vec4 renderToViewQuat;\nuniform vec3 renderToViewPos;\n\nuniform float renderToViewScale;\nuniform float maxStdDev;\nuniform float minPixelRadius;\nuniform float maxPixelRadius;\nuniform float time;\nuniform float deltaTime;\nuniform float minAlpha;\nuniform float blurAmount;\nuniform float preBlurAmount;\nuniform float clipXY;\nuniform float focalAdjustment;\nuniform bool stochastic;\nuniform vec4 stochasticTemporalSample;\n#ifdef GSL_COLOR_IN_VERTEX\nuniform bool encodeLinear;\n#endif\nuniform bool depthOnly;\n\nuniform usampler2D ordering;\nuniform usampler2DArray splats;\nuniform usampler2DArray splats2;\nuniform usampler2DArray stochasticSeeds;\n\nbool isPerspectiveMatrix( mat4 m ) {\n return m[ 2 ][ 3 ] == -1.0;\n}\n\n#include <logdepthbuf_pars_vertex>\n\nuint hashU32(uint value) {\n value ^= value >> 16u;\n value *= 0x7feb352du;\n value ^= value >> 15u;\n value *= 0x846ca68bu;\n value ^= value >> 16u;\n return value;\n}\n\nfloat gaussianSupportRadius(float alpha, float maximumRadius) {\n if (minAlpha <= 0.0) return maximumRadius;\n float radiusSquared = 2.0 * log(alpha / minAlpha);\n return min(maximumRadius, sqrt(max(0.0, radiusSquared)));\n}\n\nfloat wideSupportRadius(float alpha, float power, float maximumRadius) {\n if (minAlpha <= 0.0) return maximumRadius;\n \n \n float radiusSquared = 2.0 * log(alpha * power / minAlpha);\n return min(maximumRadius, sqrt(max(0.0, radiusSquared)));\n}\n\nvoid main() {\n \n gl_Position = vec4(0.0, 0.0, 2.0, 1.0);\n\n uint splatIndex;\n if (stochastic || depthOnly) {\n \n splatIndex = uint(gl_InstanceID);\n } else {\n ivec2 orderingCoord = ivec2((gl_InstanceID >> 2) & 4095, gl_InstanceID >> 14);\n splatIndex = texelFetch(ordering, orderingCoord, 0)[gl_InstanceID & 3];\n }\n if (splatIndex == 0xffffffffu) {\n \n return;\n }\n\n ivec3 texCoord = splatTexCoord(int(splatIndex));\n uvec4 splat1 = texelFetch(splats, texCoord, 0);\n vec2 alphaShapeAmount = decodeSplatAlphaShapeAmount(splat1);\n float alpha = alphaShapeAmount.x;\n if ((alpha == 0.0) || (alpha < minAlpha)) {\n return;\n }\n vec3 center = decodeSplatCenter(splat1);\n \n vec3 viewCenter = renderToViewScale * quatVec(renderToViewQuat, center) + renderToViewPos;\n\n \n if (viewCenter.z >= 0.0) {\n return;\n }\n\n \n vec4 clipCenter = projectionMatrix * vec4(viewCenter, 1.0);\n\n \n if (abs(clipCenter.z) >= clipCenter.w) {\n return;\n }\n\n \n float clip = clipXY * clipCenter.w;\n if (abs(clipCenter.x) > clip || abs(clipCenter.y) > clip) {\n return;\n }\n\n \n \n vec3 lnScales;\n vec4 quaternion, rgba;\n uvec4 splat2 = texelFetch(splats2, texCoord, 0);\n decodeSplatAttributesLnScale(splat2, alpha, lnScales, quaternion, rgba);\n vec3 scales = exp(lnScales);\n if (all(equal(scales, vec3(0.0)))) {\n return;\n }\n\n \n rgba.rgb = max(rgba.rgb, vec3(0.0));\n\n \n \n float kernelShape = 1.0 + 4.0 * min(alphaShapeAmount.y, 1.0);\n \n \n float kernelPower = 0.0;\n if (kernelShape > 1.0) {\n kernelPower = exp(\n (kernelShape * kernelShape - 1.0) / 2.718281828459045\n );\n }\n vKernelPower = kernelPower;\n\n \n float maximumSupportRadius = maxStdDev\n + 0.7 * max(kernelShape - 1.0, 0.0);\n float supportRadius = maximumSupportRadius;\n\n scales *= renderToViewScale;\n\n vRgba = vec4(rgba.rgb, alpha);\n\n \n vec4 viewQuaternion = quatQuat(renderToViewQuat, quaternion);\n\n \n mat3 RS = scaleQuaternionToMatrix(scales, viewQuaternion);\n\n \n vec2 scaledRenderSize = renderSize * focalAdjustment;\n vec2 focal = 0.5 * scaledRenderSize * vec2(projectionMatrix[0][0], projectionMatrix[1][1]);\n\n vec3 j0;\n vec3 j1;\n if (isOrthographic) {\n j0 = vec3(focal.x, 0.0, 0.0);\n j1 = vec3(0.0, focal.y, 0.0);\n } else {\n float invZ = 1.0 / viewCenter.z;\n vec2 J1 = focal * invZ;\n vec2 J2 = -(J1 * viewCenter.xy) * invZ;\n j0 = vec3(J1.x, 0.0, J2.x);\n j1 = vec3(0.0, J1.y, J2.y);\n }\n\n \n \n vec3 p0 = transpose(RS) * j0;\n vec3 p1 = transpose(RS) * j1;\n float a = dot(p0, p0);\n float b = dot(p0, p1);\n float d = dot(p1, p1);\n\n \n a += preBlurAmount;\n d += preBlurAmount;\n\n float detOrig = a * d - b * b;\n \n a += blurAmount;\n d += blurAmount;\n float det = a * d - b * b;\n if (det <= 0.0) return;\n\n \n float blurAdjust = sqrt(max(0.0, detOrig / det));\n alpha *= blurAdjust;\n if (!(alpha > 0.0) || alpha < minAlpha) {\n return;\n }\n vRgba.a = alpha;\n\n \n if (kernelPower == 0.0) {\n supportRadius = gaussianSupportRadius(alpha, supportRadius);\n } else {\n supportRadius = wideSupportRadius(alpha, kernelPower, supportRadius);\n }\n vSupportRadiusSquared = supportRadius * supportRadius;\n vSplatUv = position.xy * supportRadius;\n\n \n float eigenAvg = 0.5 * (a + d);\n float eigenDelta = length(vec2(0.5 * (a - d), b));\n float eigen1 = eigenAvg + eigenDelta;\n \n float eigen2 = max(eigenAvg - eigenDelta, 1e-4);\n\n vec2 eigenVec1 = (abs(b) > 0.001)\n ? normalize(vec2(b, eigen1 - a))\n : ((a >= d) ? vec2(1.0, 0.0) : vec2(0.0, 1.0));\n vec2 eigenVec2 = vec2(eigenVec1.y, -eigenVec1.x);\n\n \n \n float maxProjectedRadius = maxPixelRadius * focalAdjustment;\n float scale1 = min(maxProjectedRadius, maximumSupportRadius * sqrt(eigen1));\n float scale2 = min(maxProjectedRadius, maximumSupportRadius * sqrt(eigen2));\n float supportScale = (maximumSupportRadius > 0.0)\n ? supportRadius / maximumSupportRadius\n : 0.0;\n \n \n float cullScale = (kernelPower == 0.0) ? supportScale : 1.0;\n \n float minProjectedRadius = minPixelRadius * focalAdjustment;\n if (scale1 * cullScale < minProjectedRadius && scale2 * cullScale < minProjectedRadius) {\n return;\n }\n scale1 *= supportScale;\n scale2 *= supportScale;\n\n \n \n vStochasticHash = 0u;\n if (stochastic || depthOnly) {\n vStochasticHash = hashU32(texelFetch(stochasticSeeds, texCoord, 0).r);\n }\n\n \n #ifdef GSL_COLOR_IN_VERTEX\n if (encodeLinear && !depthOnly) {\n vRgba.rgb = srgbToLinear(vRgba.rgb);\n }\n #endif\n\n \n vec2 pixelOffset = position.x * eigenVec1 * scale1 + position.y * eigenVec2 * scale2;\n vec2 ndcOffset = (2.0 / scaledRenderSize) * pixelOffset;\n\n \n vec3 ndcCenter = clipCenter.xyz / clipCenter.w;\n vec3 ndc = vec3(ndcCenter.xy + ndcOffset, ndcCenter.z);\n\n gl_Position = vec4(ndc.xy * clipCenter.w, clipCenter.zw);\n if (stochastic && !depthOnly) {\n gl_Position.xy += stochasticTemporalSample.xy * gl_Position.w;\n }\n\n #include <logdepthbuf_vertex>\n}";
24461
24461
  let shaders = null;
24462
24462
  function getShaders() {
@@ -25605,6 +25605,7 @@ const stochasticHash = N.Fn(([input]) => {
25605
25605
  return value;
25606
25606
  });
25607
25607
  function createSplatFragment({
25608
+ sorted,
25608
25609
  minAlpha,
25609
25610
  stochastic,
25610
25611
  stochasticResolve,
@@ -25634,6 +25635,7 @@ function createSplatFragment({
25634
25635
  });
25635
25636
  rgba.a.mulAssign(kernelAlpha);
25636
25637
  rgba.a.lessThan(minAlpha).discard();
25638
+ if (sorted) return rgba;
25637
25639
  N.If(stochastic.or(depthOnly), () => {
25638
25640
  const pixel = N.uvec2(N.screenCoordinate.xy.sub(vViewportOrigin));
25639
25641
  const offset = N.uvec2(
@@ -25678,7 +25680,9 @@ function createSplatNodeMaterial({
25678
25680
  premultipliedAlpha,
25679
25681
  transparent,
25680
25682
  depthTest,
25681
- depthWrite
25683
+ depthWrite,
25684
+ sorted = false,
25685
+ vertexNode: sharedVertexNode
25682
25686
  }) {
25683
25687
  const orderingNode = providedOrderingNode ?? createDefaultOrderingNode();
25684
25688
  const splats = textureBinding(uniforms, "splats", true);
@@ -25713,6 +25717,7 @@ function createSplatNodeMaterial({
25713
25717
  vViewportOrigin,
25714
25718
  fragmentNode
25715
25719
  } = createSplatFragment({
25720
+ sorted,
25716
25721
  minAlpha,
25717
25722
  stochastic,
25718
25723
  stochasticResolve,
@@ -25721,7 +25726,7 @@ function createSplatNodeMaterial({
25721
25726
  depthOnly,
25722
25727
  premultipliedAlpha: premultipliedAlphaNode
25723
25728
  });
25724
- const vertexNode = N.Fn((builder) => {
25729
+ function buildVertex(builder) {
25725
25730
  const camera = materialCamera(builder);
25726
25731
  const clipPosition = N.vec4(0, 0, 2, 1).toVar();
25727
25732
  vRgba.assign(N.vec4(0));
@@ -25805,34 +25810,51 @@ function createSplatNodeMaterial({
25805
25810
  clipPosition.xy.addAssign(temporalSample.xy.mul(clipPosition.w));
25806
25811
  });
25807
25812
  return clipPosition;
25808
- })();
25809
- const material = new NodeMaterial();
25810
- material.vertexNode = vertexNode;
25811
- material.colorNode = fragmentNode;
25812
- material.premultipliedAlpha = premultipliedAlpha;
25813
- material.transparent = transparent;
25814
- material.depthTest = depthTest;
25815
- material.depthWrite = depthWrite;
25816
- material.side = THREE__namespace.FrontSide;
25817
- material.allowOverride = false;
25818
- material.fog = false;
25819
- material.toneMapped = false;
25820
- return Object.assign(material, { uniforms, orderingNode });
25813
+ }
25814
+ const vertexNode = sharedVertexNode ?? N.Fn(buildVertex)();
25815
+ return Object.assign(new NodeMaterial(), {
25816
+ uniforms,
25817
+ orderingNode,
25818
+ vertexNode,
25819
+ colorNode: fragmentNode,
25820
+ premultipliedAlpha,
25821
+ transparent,
25822
+ depthTest,
25823
+ depthWrite,
25824
+ side: THREE__namespace.FrontSide,
25825
+ allowOverride: false,
25826
+ fog: false,
25827
+ toneMapped: false
25828
+ });
25821
25829
  }
25822
25830
  class NodeSplatBackend {
25823
25831
  constructor(renderer, uniforms, options, orderingNode, vertexData) {
25824
25832
  this.renderer = renderer;
25825
- this.material = createSplatNodeMaterial({
25833
+ this.sortedMaterial = createSplatNodeMaterial({
25826
25834
  uniforms,
25827
25835
  ...options,
25828
25836
  orderingNode,
25829
- vertexData
25837
+ vertexData,
25838
+ sorted: true
25839
+ });
25840
+ this.uniformMaterial = createSplatNodeMaterial({
25841
+ uniforms,
25842
+ ...options,
25843
+ orderingNode: this.sortedMaterial.orderingNode,
25844
+ vertexNode: this.sortedMaterial.vertexNode
25830
25845
  });
25831
25846
  }
25847
+ selectMaterial(useUniform) {
25848
+ return useUniform ? this.uniformMaterial : this.sortedMaterial;
25849
+ }
25850
+ dispose() {
25851
+ this.sortedMaterial.dispose();
25852
+ this.uniformMaterial.dispose();
25853
+ }
25832
25854
  createDepthMaterial(uniforms) {
25833
25855
  return createSplatNodeMaterial({
25834
25856
  uniforms,
25835
- orderingNode: this.material.orderingNode,
25857
+ orderingNode: this.sortedMaterial.orderingNode,
25836
25858
  premultipliedAlpha: false,
25837
25859
  transparent: false,
25838
25860
  depthTest: true,
@@ -25922,7 +25944,7 @@ class WebGLFallbackSplatBackend extends NodeSplatBackend {
25922
25944
  return this.ordering.data;
25923
25945
  }
25924
25946
  setCPUOrdering(update) {
25925
- this.material.orderingNode.value = this.ordering.update(
25947
+ this.sortedMaterial.orderingNode.value = this.ordering.update(
25926
25948
  update,
25927
25949
  (texture2, rows) => {
25928
25950
  this.renderer.initTexture(texture2);
@@ -25936,6 +25958,7 @@ class WebGLFallbackSplatBackend extends NodeSplatBackend {
25936
25958
  );
25937
25959
  }
25938
25960
  dispose() {
25961
+ super.dispose();
25939
25962
  this.ordering.dispose();
25940
25963
  }
25941
25964
  bindOrdering(material, _uniforms) {
@@ -25947,6 +25970,7 @@ function createWebGLSplatMaterial(uniforms, options) {
25947
25970
  const defines2 = {};
25948
25971
  if (options.vertexShader === void 0 && options.fragmentShader === void 0) {
25949
25972
  defines2.GSL_COLOR_IN_VERTEX = 1;
25973
+ defines2.GSL_SORTED_FRAGMENT = 0;
25950
25974
  }
25951
25975
  return new THREE__namespace.ShaderMaterial({
25952
25976
  ...options,
@@ -25968,6 +25992,16 @@ class WebGLSplatBackend {
25968
25992
  const extension = renderer.getContext().getExtension("WEBGL_provoking_vertex");
25969
25993
  extension == null ? void 0 : extension.provokingVertexWEBGL(extension.FIRST_VERTEX_CONVENTION_WEBGL);
25970
25994
  }
25995
+ selectMaterial(useUniform) {
25996
+ const { material } = this;
25997
+ const currentSorted = material.defines.GSL_SORTED_FRAGMENT;
25998
+ const sorted = Number(!useUniform);
25999
+ if (currentSorted !== void 0 && currentSorted !== sorted) {
26000
+ material.defines.GSL_SORTED_FRAGMENT = sorted;
26001
+ material.needsUpdate = true;
26002
+ }
26003
+ return material;
26004
+ }
25971
26005
  createDepthMaterial(uniforms) {
25972
26006
  return createWebGLSplatMaterial(uniforms, {
25973
26007
  premultipliedAlpha: false,
@@ -26012,6 +26046,7 @@ class WebGLSplatBackend {
26012
26046
  }
26013
26047
  dispose() {
26014
26048
  this.ordering.dispose();
26049
+ this.material.dispose();
26015
26050
  }
26016
26051
  }
26017
26052
  function makeTexture$1(channels = 4) {
@@ -26693,6 +26728,7 @@ class ProjectedSplats {
26693
26728
  this.capacity = 0;
26694
26729
  this.viewCapacity = 0;
26695
26730
  this.disposed = false;
26731
+ this.projectedInputs = [];
26696
26732
  const computeRenderer = renderer;
26697
26733
  this.limits = computeRenderer.backend.device.limits;
26698
26734
  this.state = {
@@ -26951,6 +26987,7 @@ class ProjectedSplats {
26951
26987
  this.state.viewStride.value = capacity;
26952
26988
  }
26953
26989
  render(accumulator, camera, geometry, radial, fastSort, shrink = false) {
26990
+ var _a2, _b;
26954
26991
  if (this.error) throw this.error;
26955
26992
  if (this.compiledSlots === 0 || this.disposed) {
26956
26993
  geometry.instanceCount = 0;
@@ -26966,6 +27003,40 @@ class ProjectedSplats {
26966
27003
  this.cache.ensureOrder(multiView, shrink);
26967
27004
  geometry.setIndirect(this.indirect);
26968
27005
  geometry.setSplatCount(Math.max(1, accumulator.numSplats));
27006
+ const { uniforms } = this;
27007
+ const inputs = [
27008
+ accumulator,
27009
+ accumulator.version,
27010
+ ...accumulator.viewOrigin.toArray(),
27011
+ multiView,
27012
+ cameras.length,
27013
+ radial,
27014
+ fastSort,
27015
+ uniforms.stochastic.value,
27016
+ uniforms.maxStdDev.value,
27017
+ uniforms.minPixelRadius.value,
27018
+ uniforms.maxPixelRadius.value,
27019
+ uniforms.minAlpha.value,
27020
+ uniforms.preBlurAmount.value,
27021
+ uniforms.blurAmount.value,
27022
+ uniforms.clipXY.value,
27023
+ uniforms.focalAdjustment.value
27024
+ ];
27025
+ for (const { node } of accumulator.mapping) inputs.push(node.layers.mask);
27026
+ for (const view of cameras) {
27027
+ inputs.push(
27028
+ ...view.matrixWorld.elements,
27029
+ ...view.projectionMatrix.elements,
27030
+ view.near,
27031
+ view.far,
27032
+ view.layers.mask,
27033
+ ((_a2 = view.viewport) == null ? void 0 : _a2.z) ?? uniforms.renderSize.value.x,
27034
+ ((_b = view.viewport) == null ? void 0 : _b.w) ?? uniforms.renderSize.value.y
27035
+ );
27036
+ }
27037
+ if (!shrink && inputs.length === this.projectedInputs.length && inputs.every((value, index2) => value === this.projectedInputs[index2]))
27038
+ return;
27039
+ this.projectedInputs = [];
26969
27040
  for (let eye = 0; eye < cameras.length; eye++) {
26970
27041
  const view = cameras[eye];
26971
27042
  this.state.viewIndex.value = eye;
@@ -27008,10 +27079,12 @@ class ProjectedSplats {
27008
27079
  }
27009
27080
  this.renderer.compute(pending);
27010
27081
  }
27082
+ this.projectedInputs = inputs;
27011
27083
  }
27012
27084
  dispose() {
27013
27085
  if (this.disposed) return;
27014
27086
  this.disposed = true;
27087
+ this.projectedInputs = [];
27015
27088
  void this.compilation.then(() => this.disposeResources());
27016
27089
  }
27017
27090
  disposeResources() {
@@ -27061,6 +27134,7 @@ class WebGPUSplatBackend extends NodeSplatBackend {
27061
27134
  bindOrdering(_material, _uniforms) {
27062
27135
  }
27063
27136
  dispose() {
27137
+ super.dispose();
27064
27138
  this.projection.dispose();
27065
27139
  }
27066
27140
  }
@@ -27173,7 +27247,7 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
27173
27247
  const geometry = new SplatGeometry(
27174
27248
  backend.kind === "webgpu" ? WEBGPU_SPLATS_PER_INSTANCE : 1
27175
27249
  );
27176
- const material = backend.material;
27250
+ const material = backend.selectMaterial(autoStochastic || stochastic);
27177
27251
  super(geometry, material);
27178
27252
  this.renderSize = new THREE__namespace.Vector2();
27179
27253
  this.lastFrame = -1;
@@ -27207,7 +27281,6 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
27207
27281
  this.renderer = options.renderer;
27208
27282
  this.backend = backend;
27209
27283
  this.capture = new SplatCapture(this, backend, () => this.beginCapture());
27210
- this.material = material;
27211
27284
  this.uniforms = uniforms;
27212
27285
  this.depthPass = new SplatDepthPass(
27213
27286
  this,
@@ -27224,8 +27297,7 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
27224
27297
  this._stochastic = stochastic;
27225
27298
  this._renderDepth = renderDepth;
27226
27299
  this.stochasticPhase = stochastic ? "forced" : null;
27227
- this.applyRenderOrder();
27228
- this.applyMaterialState(this.stochasticFrame);
27300
+ this.refreshRenderConfiguration();
27229
27301
  this.frustumCulled = false;
27230
27302
  this.onDirty = options.onDirty;
27231
27303
  this.dirty = true;
@@ -27290,7 +27362,6 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
27290
27362
  this.maxSplats = 0;
27291
27363
  this.activeSplats = 0;
27292
27364
  this.geometry.dispose();
27293
- this.material.dispose();
27294
27365
  }
27295
27366
  /** Native WebGPU sorts on the GPU before drawing; WebGL sorts in a worker. */
27296
27367
  get synchronousSort() {
@@ -27393,25 +27464,27 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
27393
27464
  }
27394
27465
  }
27395
27466
  applyMaterialState(stochasticActive) {
27467
+ const { material } = this;
27396
27468
  const managedAsOpaque = this.usesManagedRenderState;
27397
27469
  const transparent = managedAsOpaque ? false : this._transparent;
27398
27470
  const blending = stochasticActive ? THREE__namespace.NoBlending : managedAsOpaque && this._transparent ? THREE__namespace.CustomBlending : this.sortedBlending;
27399
- const wasOpaque = isOpaqueMaterial(this.material);
27400
- this.material.transparent = transparent;
27401
- this.material.blending = blending;
27471
+ const wasOpaque = isOpaqueMaterial(material);
27472
+ material.transparent = transparent;
27473
+ material.blending = blending;
27402
27474
  if (managedAsOpaque && this._transparent) {
27403
- this.material.blendEquation = THREE__namespace.AddEquation;
27404
- this.material.blendSrc = this._premultipliedAlpha ? THREE__namespace.OneFactor : THREE__namespace.SrcAlphaFactor;
27405
- this.material.blendDst = THREE__namespace.OneMinusSrcAlphaFactor;
27406
- this.material.blendEquationAlpha = THREE__namespace.AddEquation;
27407
- this.material.blendSrcAlpha = THREE__namespace.OneFactor;
27408
- this.material.blendDstAlpha = THREE__namespace.OneMinusSrcAlphaFactor;
27409
- }
27410
- this.material.depthTest = stochasticActive ? true : this._depthTest;
27411
- this.material.depthWrite = stochasticActive ? true : this._depthWrite;
27475
+ material.blendEquation = THREE__namespace.AddEquation;
27476
+ material.blendSrc = this._premultipliedAlpha ? THREE__namespace.OneFactor : THREE__namespace.SrcAlphaFactor;
27477
+ material.blendDst = THREE__namespace.OneMinusSrcAlphaFactor;
27478
+ material.blendEquationAlpha = THREE__namespace.AddEquation;
27479
+ material.blendSrcAlpha = THREE__namespace.OneFactor;
27480
+ material.blendDstAlpha = THREE__namespace.OneMinusSrcAlphaFactor;
27481
+ }
27482
+ material.depthTest = stochasticActive ? true : this._depthTest;
27483
+ material.depthWrite = stochasticActive ? true : this._depthWrite;
27412
27484
  this.depthPass.updateVisibility();
27413
- if (wasOpaque !== isOpaqueMaterial(this.material)) {
27414
- this.material.needsUpdate = true;
27485
+ if (material.premultipliedAlpha !== this._premultipliedAlpha || wasOpaque !== isOpaqueMaterial(material)) {
27486
+ material.premultipliedAlpha = this._premultipliedAlpha;
27487
+ material.needsUpdate = true;
27415
27488
  }
27416
27489
  }
27417
27490
  onBeforeRender(renderer, scene, camera) {
@@ -27459,7 +27532,7 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
27459
27532
  gaussianSplatRenderer.updateTimeoutId = setTimeout(() => {
27460
27533
  gaussianSplatRenderer.updateTimeoutId = -1;
27461
27534
  gaussianSplatRenderer.updateInternal(updateRequest);
27462
- }, 1);
27535
+ }, 0);
27463
27536
  }
27464
27537
  }
27465
27538
  const typedCamera = camera;
@@ -27527,6 +27600,13 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
27527
27600
  gaussianSplatRenderer.requestMotionFollowup = false;
27528
27601
  gaussianSplatRenderer.setDirty();
27529
27602
  }
27603
+ if (!this.stochasticModeEnabled && !this.stochasticFrame) {
27604
+ const material = this.backend.selectMaterial(false);
27605
+ if (this.material !== material) {
27606
+ this.material = material;
27607
+ this.applyMaterialState(false);
27608
+ }
27609
+ }
27530
27610
  }
27531
27611
  clearSplats() {
27532
27612
  this.activeSplats = 0;
@@ -27896,6 +27976,10 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
27896
27976
  );
27897
27977
  }
27898
27978
  }
27979
+ refreshRenderConfiguration() {
27980
+ this.applyRenderOrder();
27981
+ this.applyMaterialState(this.stochasticFrame);
27982
+ }
27899
27983
  refreshStochasticConfiguration() {
27900
27984
  this.requestMotionFollowup = false;
27901
27985
  if (this._stochastic) {
@@ -27909,8 +27993,10 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
27909
27993
  this.stochasticPhase = null;
27910
27994
  this.stochasticWasForced = false;
27911
27995
  }
27912
- this.applyRenderOrder();
27913
- this.applyMaterialState(this.stochasticFrame);
27996
+ this.material = this.backend.selectMaterial(
27997
+ this.stochasticModeEnabled || this.stochasticFrame
27998
+ );
27999
+ this.refreshRenderConfiguration();
27914
28000
  this.setDirty();
27915
28001
  }
27916
28002
  get premultipliedAlpha() {
@@ -27921,9 +28007,7 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
27921
28007
  if (this._premultipliedAlpha !== nextValue) {
27922
28008
  this._premultipliedAlpha = nextValue;
27923
28009
  this.uniforms.premultipliedAlpha.value = nextValue;
27924
- this.material.premultipliedAlpha = nextValue;
27925
28010
  this.applyMaterialState(this.stochasticFrame);
27926
- this.material.needsUpdate = true;
27927
28011
  }
27928
28012
  }
27929
28013
  get transparent() {
@@ -27988,8 +28072,7 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
27988
28072
  if (nextValue === this._renderDepth) return;
27989
28073
  this.assertBuiltInSplatShaders(nextValue);
27990
28074
  this._renderDepth = nextValue;
27991
- this.applyRenderOrder();
27992
- this.applyMaterialState(this.stochasticFrame);
28075
+ this.refreshRenderConfiguration();
27993
28076
  this.setDirty();
27994
28077
  }
27995
28078
  get depthTest() {
@@ -28004,8 +28087,7 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
28004
28087
  }
28005
28088
  set depthWrite(value) {
28006
28089
  this._depthWrite = Boolean(value);
28007
- this.applyRenderOrder();
28008
- this.applyMaterialState(this.stochasticFrame);
28090
+ this.refreshRenderConfiguration();
28009
28091
  }
28010
28092
  }
28011
28093
  function isOpaqueMaterial(material) {