gaussian-splat-lite 1.1.6 → 1.1.8

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,29 @@ and this project follows [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.1.8] - 2026-09-29
11
+
12
+ ### Fixed
13
+
14
+ - Fixed TAA output color conversion and tone mapping on WebGPU and WebGL fallback when required by the renderer settings.
15
+
16
+ ### Changed
17
+
18
+ - TAA now reconstructs positions in view space on all backends, reducing small-near-plane numerical error and preserving logarithmic depth precision. History acceptance and accumulation rules are unchanged.
19
+ - TAA selects the nearest sample directly from logarithmic depth, and its node backends match Three.js's minimum near plane when decoding it.
20
+ - Packed per-Splat color and kernel varyings from 24 to 16 bytes across WebGL, WebGPU, and WebGL fallback. RGB and kernel power use half precision; source alpha and squared support radius retain float32 precision. RGB is capped at 65504, and kernel-power rounding can slightly change wide-kernel coverage.
21
+ - Moved fragment support-radius rejection before unpacking kernel data and deferred color assembly until after alpha and coverage rejection on all backends.
22
+
23
+ ### Security
24
+
25
+ - Updated the development dependency `fast-uri` to 3.1.7 to resolve high-severity security advisories.
26
+
27
+ ## [1.1.7] - 2026-09-27
28
+
29
+ ### Fixed
30
+
31
+ - Removed the constant directional drift of stochastic noise under TAA by reordering temporal blue-noise offsets across all rendering backends.
32
+
10
33
  ## [1.1.6] - 2026-09-27
11
34
 
12
35
  ### Changed
@@ -420,7 +443,9 @@ and this project follows [Semantic Versioning](https://semver.org/).
420
443
 
421
444
  - Initial public release of the Three.js Gaussian Splatting renderer.
422
445
 
423
- [Unreleased]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.1.6...HEAD
446
+ [Unreleased]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.1.8...HEAD
447
+ [1.1.8]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.1.7...v1.1.8
448
+ [1.1.7]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.1.6...v1.1.7
424
449
  [1.1.6]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.1.5...v1.1.6
425
450
  [1.1.5]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.1.4...v1.1.5
426
451
  [1.1.4]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.1.3...v1.1.4
package/README.md CHANGED
@@ -8,16 +8,22 @@
8
8
 
9
9
  **Three.js Gaussian Splatting · WebGPU · Depth Rendering · Streaming**
10
10
 
11
+ Gaussian Splatting renderer for **Three.js**, with **WebGPU/WebGL2**, **depth rendering**, and **large-scene streaming**. Load PLY/SPZ/SOG/RAD models, combine them with regular 3D objects, and explore large scenes as detail loads around the camera.
12
+
13
+ <div align="center">
14
+
11
15
  **[Try Live Demo](https://gaussian-splat-lite.vercel.app/)**
12
16
 
17
+ </div>
18
+
13
19
  <p align="center">
14
- <img src="./Gaussian-Splat-Lite.svg" alt="Gaussian Splat Lite" width="1000">
20
+ <a href="https://gaussian-splat-lite.vercel.app/">
21
+ <img src="./.github/assets/default-demo.gif" alt="Gaussian Splat Lite — default example with a seamless 90-degree camera orbit" width="800">
22
+ </a>
15
23
  </p>
16
24
 
17
25
  </div>
18
26
 
19
- Gaussian Splatting renderer for **Three.js**, with **WebGPU/WebGL2**, **depth rendering**, and **large-scene streaming**. Load PLY/SPZ/SOG/RAD models, combine them with regular 3D objects, and explore large scenes as detail loads around the camera.
20
-
21
27
  ## Features
22
28
 
23
29
  | Focus | What you get |
@@ -184,6 +190,8 @@ See [Contributing](CONTRIBUTING.md#validation) for validation commands. `npm run
184
190
 
185
191
  The overall architecture of Gaussian Splat Lite draws on [Spark](https://github.com/sparkjsdev/spark) and [SuperSplat](https://github.com/playcanvas/supersplat).
186
192
 
193
+ Example model adapted from [shehabmekky](https://superspl.at/scene/c1e6297e) ([CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)).
194
+
187
195
  ## License
188
196
 
189
197
  Licensed under [Apache 2.0](LICENSE). See [NOTICE](NOTICE) and [third-party licenses](THIRD_PARTY_LICENSES.md) for attribution.
@@ -24455,8 +24455,8 @@ class FullScreenQuad {
24455
24455
  }
24456
24456
  }
24457
24457
  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}";
24458
- var splatFragment_default = "precision highp float;\nprecision highp int;\n\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 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}";
24459
- 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 minAlpha;\nuniform float blurAmount;\nuniform float preBlurAmount;\nuniform float clipXY;\nuniform float focalAdjustment;\nuniform bool stochastic;\nuniform bool stochasticOrdering;\nuniform vec4 stochasticTemporalSample;\nuniform bool encodeLinear;\nuniform bool depthOnly;\nuniform uint splatCount;\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 index = uint(gl_InstanceID) * uint(SPLATS_PER_INSTANCE) + uint(position.z);\n if (index >= splatCount) return;\n\n uint splatIndex;\n if ((stochastic && !stochasticOrdering) || depthOnly) {\n \n splatIndex = index;\n } else {\n ivec2 orderingCoord = ivec2(int((index >> 2u) & 4095u), int(index >> 14u));\n splatIndex = texelFetch(ordering, orderingCoord, 0)[index & 3u];\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 if (encodeLinear && !depthOnly) {\n vRgba.rgb = srgbToLinear(vRgba.rgb);\n }\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}";
24458
+ var splatFragment_default = "precision highp float;\nprecision highp int;\n\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\nflat in uvec4 vSplat;\nin vec2 vSplatUv;\nflat in uint vStochasticHash;\n\n#include <logdepthbuf_pars_fragment>\n\nvoid main() {\n float z2 = dot(vSplatUv, vSplatUv);\n if (z2 > uintBitsToFloat(vSplat.w)) {\n discard;\n }\n\n vec2 blueKernelPower = unpackHalf2x16(vSplat.y);\n float kernelPower = blueKernelPower.y;\n float kernelAlpha = exp(-0.5 * z2);\n if (kernelPower != 0.0) {\n kernelAlpha = 1.0 - pow(1.0 - kernelAlpha, kernelPower);\n }\n float alpha = uintBitsToFloat(vSplat.z) * kernelAlpha;\n\n if (alpha < 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 >= alpha) {\n discard;\n }\n\n if (depthOnly) {\n fragColor = vec4(0.0);\n #include <logdepthbuf_fragment>\n return;\n }\n }\n #endif\n\n \n vec4 rgba = vec4(unpackHalf2x16(vSplat.x), blueKernelPower.x, alpha);\n #if !GSL_SORTED_FRAGMENT\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}";
24459
+ var splatVertex_default = "precision highp float;\nprecision highp int;\nprecision highp usampler2DArray;\n\n#include <splatDefines>\n\nflat out uvec4 vSplat;\nout vec2 vSplatUv;\nflat out uint vStochasticHash;\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 minAlpha;\nuniform float blurAmount;\nuniform float preBlurAmount;\nuniform float clipXY;\nuniform float focalAdjustment;\nuniform bool stochastic;\nuniform bool stochasticOrdering;\nuniform vec4 stochasticTemporalSample;\nuniform bool encodeLinear;\nuniform bool depthOnly;\nuniform uint splatCount;\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 index = uint(gl_InstanceID) * uint(SPLATS_PER_INSTANCE) + uint(position.z);\n if (index >= splatCount) return;\n\n uint splatIndex;\n if ((stochastic && !stochasticOrdering) || depthOnly) {\n \n splatIndex = index;\n } else {\n ivec2 orderingCoord = ivec2(int((index >> 2u) & 4095u), int(index >> 14u));\n splatIndex = texelFetch(ordering, orderingCoord, 0)[index & 3u];\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\n \n float maximumSupportRadius = maxStdDev\n + 0.7 * max(kernelShape - 1.0, 0.0);\n float supportRadius = maximumSupportRadius;\n\n scales *= renderToViewScale;\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\n \n if (kernelPower == 0.0) {\n supportRadius = gaussianSupportRadius(alpha, supportRadius);\n } else {\n supportRadius = wideSupportRadius(alpha, kernelPower, supportRadius);\n }\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 if (encodeLinear && !depthOnly) {\n rgba.rgb = srgbToLinear(rgba.rgb);\n }\n \n vec3 rgb = min(rgba.rgb, vec3(65504.0));\n vSplat = uvec4(\n packHalf2x16(rgb.rg),\n packHalf2x16(vec2(rgb.b, kernelPower)),\n floatBitsToUint(alpha),\n floatBitsToUint(supportRadius * supportRadius)\n );\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}";
24460
24460
  let shaders = null;
24461
24461
  function getShaders() {
24462
24462
  if (!shaders) {
@@ -25613,47 +25613,50 @@ function createSplatFragment({
25613
25613
  depthOnly,
25614
25614
  premultipliedAlpha
25615
25615
  }) {
25616
- const vRgba = N.varyingProperty("vec4", "gslRgba");
25616
+ const vSplat = N.varyingProperty("uvec4", "gslSplat");
25617
25617
  const vSplatUv = N.varyingProperty("vec2", "gslSplatUv");
25618
25618
  const vStochasticHash = N.varyingProperty("uint", "gslStochasticHash");
25619
- const vSupportRadiusSquared = N.varyingProperty(
25620
- "float",
25621
- "gslSupportRadiusSquared"
25622
- );
25623
- const vKernelPower = N.varyingProperty("float", "gslKernelPower");
25624
25619
  const vViewportOrigin = N.varyingProperty("vec2", "gslViewportOrigin");
25625
25620
  const fragmentNode = N.Fn(() => {
25626
- const rgba = vRgba.toVar();
25627
25621
  const z2 = vSplatUv.dot(vSplatUv);
25628
- z2.greaterThan(vSupportRadiusSquared).discard();
25622
+ z2.greaterThan(N.uintBitsToFloat(vSplat.w)).discard();
25623
+ const blueKernelPower = N.unpackHalf2x16(vSplat.y);
25624
+ const kernelPower = blueKernelPower.y;
25629
25625
  const kernelAlpha = z2.mul(-0.5).exp().toVar();
25630
- N.If(vKernelPower.notEqual(0), () => {
25626
+ N.If(kernelPower.notEqual(0), () => {
25631
25627
  kernelAlpha.assign(
25632
- N.float(1).sub(N.float(1).sub(kernelAlpha).pow(vKernelPower))
25628
+ N.float(1).sub(N.float(1).sub(kernelAlpha).pow(kernelPower))
25633
25629
  );
25634
25630
  });
25635
- rgba.a.mulAssign(kernelAlpha);
25636
- rgba.a.lessThan(minAlpha).discard();
25631
+ const alpha = N.uintBitsToFloat(vSplat.z).mul(kernelAlpha).toVar();
25632
+ alpha.lessThan(minAlpha).discard();
25633
+ if (!sorted) {
25634
+ N.If(stochastic.or(depthOnly), () => {
25635
+ const pixel = N.uvec2(N.screenCoordinate.xy.sub(vViewportOrigin));
25636
+ const offset = N.uvec2(
25637
+ vStochasticHash,
25638
+ vStochasticHash.shiftRight(5)
25639
+ ).add(
25640
+ N.select(
25641
+ stochastic.and(depthOnly.not()),
25642
+ N.uvec2(temporalSample.zw),
25643
+ N.uvec2(0)
25644
+ )
25645
+ );
25646
+ const coord = N.ivec2(
25647
+ pixel.x.add(offset.x).bitAnd(31),
25648
+ pixel.y.add(offset.y).bitAnd(31)
25649
+ );
25650
+ const randomValue = N.float(load2D(stochasticNoise, coord).r).add(0.5).div(1024);
25651
+ randomValue.greaterThanEqual(alpha).discard();
25652
+ });
25653
+ }
25654
+ const rgba = N.vec4(
25655
+ N.unpackHalf2x16(vSplat.x),
25656
+ blueKernelPower.x,
25657
+ alpha
25658
+ ).toVar();
25637
25659
  if (sorted) return rgba;
25638
- N.If(stochastic.or(depthOnly), () => {
25639
- const pixel = N.uvec2(N.screenCoordinate.xy.sub(vViewportOrigin));
25640
- const offset = N.uvec2(
25641
- vStochasticHash,
25642
- vStochasticHash.shiftRight(5)
25643
- ).add(
25644
- N.select(
25645
- stochastic.and(depthOnly.not()),
25646
- N.uvec2(temporalSample.zw),
25647
- N.uvec2(0)
25648
- )
25649
- );
25650
- const coord = N.ivec2(
25651
- pixel.x.add(offset.x).bitAnd(31),
25652
- pixel.y.add(offset.y).bitAnd(31)
25653
- );
25654
- const randomValue = N.float(load2D(stochasticNoise, coord).r).add(0.5).div(1024);
25655
- randomValue.greaterThanEqual(rgba.a).discard();
25656
- });
25657
25660
  N.If(stochastic.and(depthOnly.not()), () => {
25658
25661
  N.If(premultipliedAlpha.and(stochasticResolve), () => {
25659
25662
  rgba.rgb.mulAssign(0.5);
@@ -25663,15 +25666,22 @@ function createSplatFragment({
25663
25666
  return rgba;
25664
25667
  })();
25665
25668
  return {
25666
- vRgba,
25669
+ vSplat,
25667
25670
  vSplatUv,
25668
25671
  vStochasticHash,
25669
- vSupportRadiusSquared,
25670
- vKernelPower,
25671
25672
  vViewportOrigin,
25672
25673
  fragmentNode
25673
25674
  };
25674
25675
  }
25676
+ function packSplatVarying(rgba, supportRadiusSquared, kernelPower) {
25677
+ const rgb = rgba.rgb.min(65504);
25678
+ return N.uvec4(
25679
+ N.packHalf2x16(rgb.xy),
25680
+ N.packHalf2x16(N.vec2(rgb.z, kernelPower)),
25681
+ N.floatBitsToUint(rgba.a),
25682
+ N.floatBitsToUint(supportRadiusSquared)
25683
+ );
25684
+ }
25675
25685
  function createSplatNodeMaterial({
25676
25686
  uniforms,
25677
25687
  orderingNode: providedOrderingNode,
@@ -25707,15 +25717,7 @@ function createSplatNodeMaterial({
25707
25717
  "bool"
25708
25718
  );
25709
25719
  const depthOnly = uniformBinding(uniforms, "depthOnly", "bool");
25710
- const {
25711
- vRgba,
25712
- vSplatUv,
25713
- vStochasticHash,
25714
- vSupportRadiusSquared,
25715
- vKernelPower,
25716
- vViewportOrigin,
25717
- fragmentNode
25718
- } = createSplatFragment({
25720
+ const { vSplat, vSplatUv, vStochasticHash, vViewportOrigin, fragmentNode } = createSplatFragment({
25719
25721
  sorted,
25720
25722
  minAlpha,
25721
25723
  stochastic,
@@ -25728,24 +25730,22 @@ function createSplatNodeMaterial({
25728
25730
  function buildVertex(builder) {
25729
25731
  const camera = materialCamera(builder);
25730
25732
  const clipPosition = N.vec4(0, 0, 2, 1).toVar();
25731
- vRgba.assign(N.vec4(0));
25733
+ vSplat.assign(N.uvec4(0));
25732
25734
  vSplatUv.assign(N.vec2(0));
25733
25735
  vStochasticHash.assign(N.uint(0));
25734
- vSupportRadiusSquared.assign(0);
25735
- vKernelPower.assign(0);
25736
25736
  const assignVertexData = (data) => {
25737
25737
  const rgba = data.rgba.toVar();
25738
25738
  N.If(encodeLinear.and(depthOnly.not()), () => {
25739
25739
  rgba.rgb.assign(N.sRGBTransferEOTF(rgba.rgb));
25740
25740
  });
25741
25741
  clipPosition.assign(data.clipPosition);
25742
- vRgba.assign(rgba);
25742
+ vSplat.assign(
25743
+ packSplatVarying(rgba, data.supportRadiusSquared, data.kernelPower)
25744
+ );
25743
25745
  vSplatUv.assign(data.splatUv);
25744
25746
  N.If(stochastic.or(depthOnly), () => {
25745
25747
  vStochasticHash.assign(stochasticHash(data.stochasticSeed));
25746
25748
  });
25747
- vSupportRadiusSquared.assign(data.supportRadiusSquared);
25748
- vKernelPower.assign(data.kernelPower);
25749
25749
  vViewportOrigin.assign(data.viewportOrigin);
25750
25750
  };
25751
25751
  if (vertexData) {
@@ -29261,15 +29261,14 @@ function createTAAPresentation(renderer, views) {
29261
29261
  const depth2 = N.textureLoad(uniforms.depth.value).onObjectUpdate(
29262
29262
  () => uniforms.depth.value
29263
29263
  );
29264
- material.fragmentNode = load2D(color2, coord);
29265
29264
  material.depthNode = load2D(depth2, coord).r;
29266
29265
  material.vertexNode = N.vec4(N.positionGeometry.xy, 0, 1);
29267
29266
  const output2 = N.renderOutput(
29268
- N.output,
29267
+ load2D(color2, coord),
29269
29268
  THREE__namespace.NoToneMapping,
29270
29269
  THREE__namespace.NoColorSpace
29271
29270
  );
29272
- material.outputNode = output2;
29271
+ material.fragmentNode = output2;
29273
29272
  configureOutput = (tone, space) => {
29274
29273
  if (output2.getToneMapping() !== tone || output2.outputColorSpace !== space) {
29275
29274
  output2.setToneMapping(tone);
@@ -29367,9 +29366,9 @@ function createNodeTAAPipeline(renderer, scene, camera, sample2, renderSize, isS
29367
29366
  () => history[1 - index2].textures[1]
29368
29367
  );
29369
29368
  const targets = [sourceTarget, ...history];
29370
- const currentVP = new THREE__namespace.Matrix4();
29371
- const previousVP = N.uniform(new THREE__namespace.Matrix4());
29372
- const inverseVP = N.uniform(new THREE__namespace.Matrix4());
29369
+ const previousVP = new THREE__namespace.Matrix4();
29370
+ const projectionMatrix = N.uniform(camera.projectionMatrix);
29371
+ const viewToPreviousClip = N.uniform(new THREE__namespace.Matrix4());
29373
29372
  const jitter = N.uniform(sample2);
29374
29373
  const size = N.uniform(renderSize);
29375
29374
  const useHistory = N.uniform(false, "bool");
@@ -29377,16 +29376,12 @@ function createNodeTAAPipeline(renderer, scene, camera, sample2, renderSize, isS
29377
29376
  const sampleCount = N.property("float", "taaHistorySamples");
29378
29377
  const logDepth = N.uniform(new THREE__namespace.Vector2());
29379
29378
  const depthProjection = N.uniform(new THREE__namespace.Vector2());
29379
+ const logarithmic = logDepth.x.greaterThan(0);
29380
29380
  const reversed = renderer.reversedDepthBuffer;
29381
29381
  const zeroToOne = reversed || renderer.coordinateSystem === THREE__namespace.WebGPUCoordinateSystem;
29382
- const hardwareDepth = N.Fn(([encoded]) => {
29383
- const value = encoded.toVar();
29384
- N.If(logDepth.x.greaterThan(0), () => {
29385
- const viewZ = logDepth.x.negate().mul(encoded.mul(logDepth.y).exp2());
29386
- value.assign(depthProjection.x.add(depthProjection.y.div(viewZ)));
29387
- });
29388
- return value;
29389
- });
29382
+ const logarithmicViewZ = N.Fn(
29383
+ ([encoded]) => logDepth.x.negate().mul(encoded.mul(logDepth.y).exp2())
29384
+ );
29390
29385
  const pixel = N.ivec2(N.screenCoordinate.xy);
29391
29386
  const material = new NodeMaterial();
29392
29387
  material.vertexNode = N.vec4(N.positionGeometry.xy, 0, 1);
@@ -29400,7 +29395,8 @@ function createNodeTAAPipeline(renderer, scene, camera, sample2, renderSize, isS
29400
29395
  const marked = N.bool(false).toVar();
29401
29396
  const low = current.toVar();
29402
29397
  const high = current.toVar();
29403
- const closest = N.float(reversed ? 0 : 1).toVar();
29398
+ const reverseSelection = reversed ? logarithmic.not() : N.bool(false);
29399
+ const closest = N.select(reverseSelection, 0, 1).toVar();
29404
29400
  const closestPixel = pixel.toVar();
29405
29401
  for (let y = -1; y <= 1; y++) {
29406
29402
  for (let x = -1; x <= 1; x++) {
@@ -29411,11 +29407,18 @@ function createNodeTAAPipeline(renderer, scene, camera, sample2, renderSize, isS
29411
29407
  const bounded = x !== 0 || y !== 0 ? color2.max(0) : color2;
29412
29408
  low.assign(low.min(bounded));
29413
29409
  high.assign(high.max(bounded));
29414
- const z = hardwareDepth(load2D(depth2, p).r).toVar();
29415
- N.If(reversed ? z.greaterThan(closest) : z.lessThan(closest), () => {
29416
- closest.assign(z);
29417
- closestPixel.assign(p);
29418
- });
29410
+ const z = load2D(depth2, p).r.toVar();
29411
+ N.If(
29412
+ N.select(
29413
+ reverseSelection,
29414
+ z.greaterThan(closest),
29415
+ z.lessThan(closest)
29416
+ ),
29417
+ () => {
29418
+ closest.assign(z);
29419
+ closestPixel.assign(p);
29420
+ }
29421
+ );
29419
29422
  }
29420
29423
  }
29421
29424
  N.If(marked, () => {
@@ -29425,10 +29428,20 @@ function createNodeTAAPipeline(renderer, scene, camera, sample2, renderSize, isS
29425
29428
  N.If(load2D(source, closestPixel).a.greaterThan(1), () => {
29426
29429
  ndc.subAssign(jitter.xy);
29427
29430
  });
29428
- const world = inverseVP.mul(
29429
- N.vec4(ndc, zeroToOne ? closest : closest.mul(2).sub(1), 1)
29430
- );
29431
- const projected = previousVP.mul(world).toVar();
29431
+ const p = projectionMatrix;
29432
+ const pz = p.element(2);
29433
+ const pw = p.element(3);
29434
+ const z = zeroToOne ? closest : closest.mul(2).sub(1);
29435
+ const orientation = reversed ? -1 : 1;
29436
+ const viewZ = pw.z.sub(z.mul(pw.w)).mul(orientation).toVar();
29437
+ const viewW = z.mul(pz.w).sub(pz.z).mul(orientation).toVar();
29438
+ N.If(logarithmic, () => {
29439
+ viewZ.assign(logarithmicViewZ(closest));
29440
+ viewW.assign(1);
29441
+ });
29442
+ const clipW = pz.w.mul(viewZ).add(pw.w.mul(viewW));
29443
+ const viewXY = ndc.mul(clipW).sub(pz.xy.mul(viewZ)).sub(pw.xy.mul(viewW)).div(N.vec2(p.element(0).x, p.element(1).y));
29444
+ const projected = viewToPreviousClip.mul(N.vec4(viewXY, viewZ, viewW)).toVar();
29432
29445
  N.If(projected.w.greaterThan(0), () => {
29433
29446
  const previous = projected.xyz.div(projected.w);
29434
29447
  const previousUV = uv2.add(previous.xy.sub(ndc).mul(N.vec2(0.5, -0.5))).toVar();
@@ -29446,19 +29459,26 @@ function createNodeTAAPipeline(renderer, scene, camera, sample2, renderSize, isS
29446
29459
  const validWeight = N.float(0).toVar();
29447
29460
  for (let y = 0; y < 2; y++) {
29448
29461
  for (let x = 0; x < 2; x++) {
29449
- const p = base.add(N.ivec2(x, y));
29462
+ const p2 = base.add(N.ivec2(x, y));
29450
29463
  const weight = (x === 0 ? N.float(1).sub(fraction.x) : fraction.x).mul(y === 0 ? N.float(1).sub(fraction.y) : fraction.y);
29451
29464
  N.If(
29452
- weight.greaterThan(0).and(p.x.greaterThanEqual(0)).and(p.y.greaterThanEqual(0)).and(p.x.lessThan(N.int(size.x))).and(p.y.lessThan(N.int(size.y))),
29465
+ weight.greaterThan(0).and(p2.x.greaterThanEqual(0)).and(p2.y.greaterThanEqual(0)).and(p2.x.lessThan(N.int(size.x))).and(p2.y.lessThan(N.int(size.y))),
29453
29466
  () => {
29454
- const oldDepth = hardwareDepth(load2D(historyDepth, p).r);
29467
+ const oldDepth = load2D(historyDepth, p2).r.toVar();
29468
+ N.If(logarithmic, () => {
29469
+ oldDepth.assign(
29470
+ depthProjection.x.add(
29471
+ depthProjection.y.div(logarithmicViewZ(oldDepth))
29472
+ )
29473
+ );
29474
+ });
29455
29475
  const disocclusion = reversed ? oldDepth.sub(expectedDepth) : expectedDepth.sub(oldDepth);
29456
29476
  N.If(disocclusion.lessThanEqual(5e-4), () => {
29457
29477
  historySum.addAssign(
29458
- load2D(historyColor, p).mul(weight)
29478
+ load2D(historyColor, p2).mul(weight)
29459
29479
  );
29460
29480
  sampleSum.addAssign(
29461
- load2D(historySamples, p).r.mul(weight)
29481
+ load2D(historySamples, p2).r.mul(weight)
29462
29482
  );
29463
29483
  validWeight.addAssign(weight);
29464
29484
  });
@@ -29532,17 +29552,14 @@ function createNodeTAAPipeline(renderer, scene, camera, sample2, renderSize, isS
29532
29552
  renderer.autoClearStencil
29533
29553
  );
29534
29554
  renderer.render(scene, camera);
29535
- currentVP.multiplyMatrices(
29536
- camera.projectionMatrix,
29537
- camera.matrixWorldInverse
29538
- );
29539
- inverseVP.value.copy(currentVP).invert();
29555
+ viewToPreviousClip.value.multiplyMatrices(previousVP, camera.matrixWorld);
29540
29556
  stochasticFrame.value = isStochastic();
29541
29557
  useHistory.value = stochasticFrame.value && valid;
29542
29558
  const perspective = camera;
29559
+ const near = Math.max(perspective.near, 1e-6);
29543
29560
  logDepth.value.set(
29544
- renderer.logarithmicDepthBuffer && perspective.isPerspectiveCamera ? perspective.near : 0,
29545
- Math.log2(perspective.far / perspective.near)
29561
+ renderer.logarithmicDepthBuffer && perspective.isPerspectiveCamera ? near : 0,
29562
+ Math.log2(perspective.far / near)
29546
29563
  );
29547
29564
  const projection = camera.projectionMatrix.elements;
29548
29565
  const scale = zeroToOne ? 1 : 0.5;
@@ -29552,7 +29569,10 @@ function createNodeTAAPipeline(renderer, scene, camera, sample2, renderSize, isS
29552
29569
  );
29553
29570
  renderer.setRenderTarget(history[index2]);
29554
29571
  renderer.render(mesh, fullscreenCamera);
29555
- previousVP.value.copy(currentVP);
29572
+ previousVP.multiplyMatrices(
29573
+ camera.projectionMatrix,
29574
+ camera.matrixWorldInverse
29575
+ );
29556
29576
  index2 = 1 - index2;
29557
29577
  valid = true;
29558
29578
  },
@@ -29583,8 +29603,8 @@ uniform sampler2D historyDepth;
29583
29603
  uniform sampler2D historySamples;
29584
29604
  uniform vec2 renderSize;
29585
29605
  uniform vec4 sampleOffset;
29586
- uniform mat4 inverseVP;
29587
- uniform mat4 previousVP;
29606
+ uniform mat4 projection;
29607
+ uniform mat4 viewToPreviousClip;
29588
29608
  uniform bool valid;
29589
29609
  uniform bool stochasticFrame;
29590
29610
  uniform bool reversed;
@@ -29593,13 +29613,10 @@ uniform vec2 depthProjection;
29593
29613
  layout(location = 0) out vec4 fragColor;
29594
29614
  layout(location = 1) out float fragSamples;
29595
29615
 
29596
- float hardwareDepth(float encoded) {
29597
- if (logDepth.x == 0.0) return encoded;
29598
- float viewZ = -exp2(encoded * logDepth.y) + 1.0;
29599
- return depthProjection.x + depthProjection.y / viewZ;
29616
+ float logarithmicViewZ(float encoded) {
29617
+ return -exp2(encoded * logDepth.y) + 1.0;
29600
29618
  }
29601
29619
 
29602
- float clipZ(float depth) { return reversed ? depth : depth * 2.0 - 1.0; }
29603
29620
  ivec2 bounded(ivec2 p) { return clamp(p, ivec2(0), ivec2(renderSize) - 1); }
29604
29621
  vec4 colorAt(ivec2 p) {
29605
29622
  vec4 color = texelFetch(source, bounded(p), 0);
@@ -29618,7 +29635,10 @@ void main() {
29618
29635
  bool marked = false;
29619
29636
  vec4 low = current;
29620
29637
  vec4 high = current;
29621
- float closest = reversed ? 0.0 : 1.0;
29638
+ // Log depth increases with distance; compare it before any conversion.
29639
+ bool logarithmic = logDepth.x > 0.0;
29640
+ bool reverseSelection = reversed && !logarithmic;
29641
+ float closest = reverseSelection ? 0.0 : 1.0;
29622
29642
  ivec2 closestPixel = pixel;
29623
29643
  // Match the node backend's top-left traversal when depths are equal.
29624
29644
  for (int y = 1; y >= -1; --y) {
@@ -29630,8 +29650,8 @@ void main() {
29630
29650
  if (x != 0 || y != 0) color = max(color, vec4(0.0));
29631
29651
  low = min(low, color);
29632
29652
  high = max(high, color);
29633
- float z = hardwareDepth(texelFetch(sourceDepth, p, 0).r);
29634
- if (reversed ? z > closest : z < closest) {
29653
+ float z = texelFetch(sourceDepth, p, 0).r;
29654
+ if (reverseSelection ? z > closest : z < closest) {
29635
29655
  closest = z;
29636
29656
  closestPixel = p;
29637
29657
  }
@@ -29642,8 +29662,21 @@ void main() {
29642
29662
  vec2 closestUV = (vec2(closestPixel) + 0.5) / renderSize;
29643
29663
  vec2 ndc = closestUV * 2.0 - 1.0;
29644
29664
  if (texelFetch(source, closestPixel, 0).a > 1.0) ndc -= sampleOffset.xy;
29645
- vec4 world = inverseVP * vec4(ndc, clipZ(closest), 1.0);
29646
- vec4 projected = previousVP * world;
29665
+ // Reconstruct homogeneous view coordinates without inverse VP cancellation.
29666
+ float z = reversed ? closest : closest * 2.0 - 1.0;
29667
+ // Keep W positive in both depth modes; W = 0 represents a point at infinity.
29668
+ float orientation = reversed ? -1.0 : 1.0;
29669
+ float viewZ = (projection[3][2] - z * projection[3][3]) * orientation;
29670
+ float viewW = (z * projection[2][3] - projection[2][2]) * orientation;
29671
+ if (logarithmic) {
29672
+ // Decode log depth directly to retain its precision.
29673
+ viewZ = logarithmicViewZ(closest);
29674
+ viewW = 1.0;
29675
+ }
29676
+ float clipW = projection[2][3] * viewZ + projection[3][3] * viewW;
29677
+ vec2 viewXY = (ndc * clipW - projection[2].xy * viewZ
29678
+ - projection[3].xy * viewW) / vec2(projection[0][0], projection[1][1]);
29679
+ vec4 projected = viewToPreviousClip * vec4(viewXY, viewZ, viewW);
29647
29680
  if (projected.w <= 0.0) return;
29648
29681
  vec3 previous = projected.xyz / projected.w;
29649
29682
  vec2 previousUV = uv + (previous.xy - ndc) * 0.5;
@@ -29663,7 +29696,8 @@ void main() {
29663
29696
  float weight = (x == 0 ? 1.0 - fraction.x : fraction.x)
29664
29697
  * (y == 0 ? 1.0 - fraction.y : fraction.y);
29665
29698
  if (weight > 0.0 && all(greaterThanEqual(p, ivec2(0))) && all(lessThan(p, ivec2(renderSize)))) {
29666
- float oldDepth = hardwareDepth(texelFetch(historyDepth, p, 0).r);
29699
+ float oldDepth = texelFetch(historyDepth, p, 0).r;
29700
+ if (logarithmic) oldDepth = depthProjection.x + depthProjection.y / logarithmicViewZ(oldDepth);
29667
29701
  float disocclusion = reversed ? oldDepth - expectedDepth : expectedDepth - oldDepth;
29668
29702
  if (disocclusion <= 0.0005) {
29669
29703
  historySum += texelFetch(history, p, 0) * weight;
@@ -29710,8 +29744,7 @@ function createWebGLTAAPipeline(renderer, scene, camera, sample2, renderSize, is
29710
29744
  target.textures.push(samples);
29711
29745
  });
29712
29746
  const targets = [source, ...history];
29713
- const currentVP = new THREE__namespace.Matrix4();
29714
- const previousVP = { value: new THREE__namespace.Matrix4() };
29747
+ const previousVP = new THREE__namespace.Matrix4();
29715
29748
  const reversed = renderer.capabilities.reversedDepthBuffer;
29716
29749
  const uniforms = {
29717
29750
  source: { value: source.texture },
@@ -29721,8 +29754,8 @@ function createWebGLTAAPipeline(renderer, scene, camera, sample2, renderSize, is
29721
29754
  historySamples: { value: history[0].textures[1] },
29722
29755
  renderSize: { value: renderSize },
29723
29756
  sampleOffset: { value: sample2 },
29724
- inverseVP: { value: new THREE__namespace.Matrix4() },
29725
- previousVP,
29757
+ projection: { value: camera.projectionMatrix },
29758
+ viewToPreviousClip: { value: new THREE__namespace.Matrix4() },
29726
29759
  valid: { value: false },
29727
29760
  stochasticFrame: { value: false },
29728
29761
  reversed: { value: reversed },
@@ -29776,16 +29809,15 @@ function createWebGLTAAPipeline(renderer, scene, camera, sample2, renderSize, is
29776
29809
  renderer.autoClearStencil
29777
29810
  );
29778
29811
  renderer.render(scene, camera);
29779
- currentVP.multiplyMatrices(
29780
- camera.projectionMatrix,
29781
- camera.matrixWorldInverse
29782
- );
29783
29812
  const input = history[1 - index2];
29784
29813
  const output2 = history[index2];
29785
29814
  uniforms.history.value = input.texture;
29786
29815
  uniforms.historyDepth.value = input.depthTexture;
29787
29816
  uniforms.historySamples.value = input.textures[1];
29788
- uniforms.inverseVP.value.copy(currentVP).invert();
29817
+ uniforms.viewToPreviousClip.value.multiplyMatrices(
29818
+ previousVP,
29819
+ camera.matrixWorld
29820
+ );
29789
29821
  const stochastic = isStochastic();
29790
29822
  uniforms.valid.value = stochastic && valid;
29791
29823
  uniforms.stochasticFrame.value = stochastic;
@@ -29802,7 +29834,10 @@ function createWebGLTAAPipeline(renderer, scene, camera, sample2, renderSize, is
29802
29834
  );
29803
29835
  renderer.setRenderTarget(output2);
29804
29836
  renderer.render(mesh, fullscreenCamera);
29805
- previousVP.value.copy(currentVP);
29837
+ previousVP.multiplyMatrices(
29838
+ camera.projectionMatrix,
29839
+ camera.matrixWorldInverse
29840
+ );
29806
29841
  index2 = 1 - index2;
29807
29842
  valid = true;
29808
29843
  },
@@ -29814,7 +29849,8 @@ function createWebGLTAAPipeline(renderer, scene, camera, sample2, renderSize, is
29814
29849
  }
29815
29850
  };
29816
29851
  }
29817
- const SAMPLES = 8;
29852
+ const NOISE_PHASES = [0, 4, 1, 6, 2, 7, 3, 5];
29853
+ const SAMPLES = NOISE_PHASES.length;
29818
29854
  function halton(index2, base) {
29819
29855
  let result = 0;
29820
29856
  let fraction = 1;
@@ -29991,6 +30027,7 @@ class StochasticTAAPass {
29991
30027
  const xrEnabled = renderer.xr.enabled;
29992
30028
  const autoClear = renderer.autoClear;
29993
30029
  const index2 = this.frame % SAMPLES;
30030
+ const noisePhase = NOISE_PHASES[index2];
29994
30031
  try {
29995
30032
  renderer.xr.enabled = false;
29996
30033
  for (const view of this.views) {
@@ -29998,8 +30035,8 @@ class StochasticTAAPass {
29998
30035
  sample2.set(
29999
30036
  2 * (halton(index2 + 1, 2) - 0.5) / size.x,
30000
30037
  2 * (halton(index2 + 1, 3) - 0.5) / size.y,
30001
- index2 * 17 % 32,
30002
- index2 * 29 % 32
30038
+ noisePhase * 17 % 32,
30039
+ noisePhase * 29 % 32
30003
30040
  );
30004
30041
  if (!stochastic) sample2.set(0, 0, 0, 0);
30005
30042
  this.splatRenderer[stochasticTemporalSample](sample2);