gaussian-splat-lite 1.2.0 → 1.2.1

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +25 -2
  2. package/README.md +22 -13
  3. package/dist/gaussian-splat-lite.cjs +700 -51
  4. package/dist/gaussian-splat-lite.cjs.map +1 -1
  5. package/dist/gaussian-splat-lite.min.cjs +40 -13
  6. package/dist/gaussian-splat-lite.min.cjs.map +1 -1
  7. package/dist/gaussian-splat-lite.module.js +702 -53
  8. package/dist/gaussian-splat-lite.module.js.map +1 -1
  9. package/dist/gaussian-splat-lite.module.min.js +2660 -2199
  10. package/dist/gaussian-splat-lite.module.min.js.map +1 -1
  11. package/dist/types/addons/TAAPass.d.cts +6 -15
  12. package/dist/types/addons/TAAPass.d.ts +6 -15
  13. package/dist/types/capture/SplatCapture.d.cts +66 -0
  14. package/dist/types/capture/SplatCapture.d.ts +66 -0
  15. package/dist/types/capture/WebGLCapture.d.cts +13 -0
  16. package/dist/types/capture/WebGLCapture.d.ts +13 -0
  17. package/dist/types/capture/WebGPUCapture.d.cts +17 -0
  18. package/dist/types/capture/WebGPUCapture.d.ts +17 -0
  19. package/dist/types/capture/pixels.d.cts +5 -0
  20. package/dist/types/capture/pixels.d.ts +5 -0
  21. package/dist/types/capture/rendererState.d.cts +3 -0
  22. package/dist/types/capture/rendererState.d.ts +3 -0
  23. package/dist/types/capture/sceneView.d.cts +3 -0
  24. package/dist/types/capture/sceneView.d.ts +3 -0
  25. package/dist/types/index.d.cts +1 -1
  26. package/dist/types/index.d.ts +1 -1
  27. package/dist/types/rendering/GaussianSplatRenderer.d.cts +13 -11
  28. package/dist/types/rendering/GaussianSplatRenderer.d.ts +13 -11
  29. package/dist/types/rendering/SplatAccumulator.d.cts +2 -1
  30. package/dist/types/rendering/SplatAccumulator.d.ts +2 -1
  31. package/dist/types/rendering/backend.d.cts +1 -1
  32. package/dist/types/rendering/backend.d.ts +1 -1
  33. package/dist/types/rendering/rendererUtils.d.cts +1 -5
  34. package/dist/types/rendering/rendererUtils.d.ts +1 -5
  35. package/dist/types/rendering/tsl/tslCompat.d.cts +1 -0
  36. package/dist/types/rendering/tsl/tslCompat.d.ts +1 -0
  37. package/dist/types/rendering/uniforms.d.cts +0 -3
  38. package/dist/types/rendering/uniforms.d.ts +0 -3
  39. package/dist/types/scene/SplatMesh.d.cts +1 -1
  40. package/dist/types/scene/SplatMesh.d.ts +1 -1
  41. package/docs/Architecture.md +48 -3
  42. package/docs/GaussianSplatRenderer.md +71 -55
  43. package/docs/PostDecode.md +52 -36
  44. package/docs/RadStreamScheduler.md +53 -41
  45. package/docs/SogStreamScheduler.md +49 -39
  46. package/docs/SplatCapture.md +147 -0
  47. package/docs/SplatEdit.md +33 -26
  48. package/docs/SplatFileType.md +11 -13
  49. package/docs/SplatLoader.md +19 -14
  50. package/docs/SplatMesh.md +69 -49
  51. package/docs/Splats.md +33 -26
  52. package/docs/StochasticRendering.md +27 -30
  53. package/docs/TAAPass.md +97 -73
  54. package/package.json +1 -1
  55. package/docs/SplatAccumulator.md +0 -37
@@ -2020,9 +2020,9 @@ class SplatMesh extends THREE__namespace.Object3D {
2020
2020
  }
2021
2021
  return ((_a2 = this.splats) == null ? void 0 : _a2.getBoundingBox(centersOnly, target)) ?? target.makeEmpty();
2022
2022
  }
2023
- frameUpdate({ time, deltaTime, globalEdits }) {
2023
+ frameUpdate({ time, deltaTime, globalEdits }, callbacks = true) {
2024
2024
  var _a2, _b;
2025
- (_a2 = this.onFrame) == null ? void 0 : _a2.call(this, { mesh: this, time, deltaTime });
2025
+ if (callbacks) (_a2 = this.onFrame) == null ? void 0 : _a2.call(this, { mesh: this, time, deltaTime });
2026
2026
  const source = this.splats;
2027
2027
  if (!source) {
2028
2028
  return;
@@ -2194,7 +2194,7 @@ function isWebGPURenderer(renderer) {
2194
2194
  return "isWebGPURenderer" in renderer && renderer.isWebGPURenderer === true;
2195
2195
  }
2196
2196
  function usesNativeWebGPU(renderer) {
2197
- return isWebGPURenderer(renderer) && renderer.backend.isWebGPUBackend === true;
2197
+ return isWebGPURenderer(renderer) && renderer.coordinateSystem === THREE__namespace.WebGPUCoordinateSystem;
2198
2198
  }
2199
2199
  function getViews(camera) {
2200
2200
  const array = camera;
@@ -2268,7 +2268,9 @@ function textureBinding(uniforms, name, array = false) {
2268
2268
  placeholder.generateMipmaps = false;
2269
2269
  placeholder.needsUpdate = true;
2270
2270
  const getTexture = () => uniforms[name].value;
2271
- return uintTexture(placeholder).onObjectUpdate(getTexture);
2271
+ const binding = uintTexture(placeholder).onObjectUpdate(getTexture);
2272
+ binding.getUniformHash = () => binding.uuid;
2273
+ return binding;
2272
2274
  }
2273
2275
  function load2D(binding, coord) {
2274
2276
  const texel = binding.load(coord);
@@ -2279,6 +2281,7 @@ function loadArray(binding, coord) {
2279
2281
  return updateTextureLoad(binding, binding.load(coord.xy).depth(coord.z));
2280
2282
  }
2281
2283
  function updateTextureLoad(binding, texel) {
2284
+ texel.getUniformHash = (builder) => binding.getUniformHash(builder);
2282
2285
  texel.updateBeforeType = webgpu.NodeUpdateType.OBJECT;
2283
2286
  texel.updateBefore = (frame) => {
2284
2287
  frame.updateNode(binding);
@@ -2914,7 +2917,7 @@ const emptyOrdering = new THREE__namespace.DataTexture(
2914
2917
  THREE__namespace.UnsignedIntType
2915
2918
  );
2916
2919
  emptyOrdering.needsUpdate = true;
2917
- const DEFAULT_MIN_ALPHA = 0.5 / 255;
2920
+ const DEFAULT_MIN_ALPHA = 1 / 255;
2918
2921
  function makeSplatUniforms() {
2919
2922
  const emptySplats = emptySplatTexture;
2920
2923
  const uniforms = {
@@ -2935,8 +2938,6 @@ function makeSplatUniforms() {
2935
2938
  maxStdDev: { value: 1 },
2936
2939
  // Minimum pixel radius for splat rendering
2937
2940
  minPixelRadius: { value: 1 },
2938
- // Maximum pixel radius for splat rendering
2939
- maxPixelRadius: { value: 256 },
2940
2941
  // Minimum alpha value for splat rendering
2941
2942
  minAlpha: { value: DEFAULT_MIN_ALPHA },
2942
2943
  // Add to projected 2D splat covariance diagonal (thickens and brightens)
@@ -2947,7 +2948,7 @@ function makeSplatUniforms() {
2947
2948
  clipXY: { value: 1.25 },
2948
2949
  // Debug renderSize scale factor
2949
2950
  focalAdjustment: { value: 2 },
2950
- // Whether to decode stored sRGB Splat colors before blending
2951
+ // Resolved from GaussianSplatRenderer.encodeLinear or the active blend space.
2951
2952
  encodeLinear: { value: false },
2952
2953
  // Exact draw count; the final instance may contain unused quads.
2953
2954
  splatCount: { value: 0 },
@@ -3004,7 +3005,7 @@ function makeGenerateUniforms() {
3004
3005
  }
3005
3006
  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}";
3006
3007
  var splatFragment_default = "precision highp float;\nprecision highp int;\n\nuniform float minAlpha;\n#if GSL_STOCHASTIC\nuniform highp usampler2D stochasticNoise;\n#endif\n\nout vec4 fragColor;\n\nflat in uvec4 vSplat;\nin vec2 vSplatUv;\n#if GSL_STOCHASTIC\n\nflat in uint vStochasticOffset;\n#endif\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_STOCHASTIC\n uvec2 offset = uvec2(vStochasticOffset, vStochasticOffset >> 5u);\n ivec2 coord = ivec2((uvec2(gl_FragCoord.xy) + offset) & uvec2(31u));\n coord.y += int(vStochasticOffset >> 10u) * 32;\n float randomValue = (float(texelFetch(stochasticNoise, coord, 0).r) + 0.5) / 32768.0;\n if (randomValue >= alpha) {\n discard;\n }\n #endif\n\n \n vec4 rgba = vec4(unpackHalf2x16(vSplat.x), blueKernelPower.x, alpha);\n\n #if GSL_STOCHASTIC\n fragColor = vec4(rgba.rgb, 1.0);\n #elif defined(PREMULTIPLIED_ALPHA)\n fragColor = vec4(rgba.rgb * rgba.a, rgba.a);\n #else\n fragColor = rgba;\n #endif\n\n #include <logdepthbuf_fragment>\n}";
3007
- var splatVertex_default = "precision highp float;\nprecision highp int;\nprecision highp usampler2DArray;\n\n#include <splatDefines>\n\nflat out uvec4 vSplat;\nout vec2 vSplatUv;\n#if GSL_STOCHASTIC\n\nflat out uint vStochasticOffset;\n#endif\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 encodeLinear;\nuniform uint splatCount;\n\nuniform usampler2D ordering;\nuniform usampler2DArray splats;\nuniform usampler2DArray splats2;\n\n#if GSL_STOCHASTIC\nuniform bool stochasticOrdering;\nuniform uint stochasticSample;\nuniform vec2 viewportOrigin;\nuniform usampler2DArray stochasticSeeds;\n#endif\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 GSL_STOCHASTIC\n if (!stochasticOrdering) {\n \n splatIndex = index;\n } else\n #endif\n {\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 (isPerspectiveMatrix(projectionMatrix) && 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 #if GSL_STOCHASTIC\n \n \n \n uint seed = texelFetch(stochasticSeeds, texCoord, 0).r;\n uint hash = hashU32(seed);\n uvec2 offset = uvec2(hash, hash >> 5u) - uvec2(viewportOrigin);\n uint phase = ((hash >> 10u) + stochasticSample) & 31u;\n vStochasticOffset = (offset.x & 31u) | ((offset.y & 31u) << 5u) | (phase << 10u);\n #endif\n\n \n if (encodeLinear) {\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 #include <logdepthbuf_vertex>\n}";
3008
+ var splatVertex_default = "precision highp float;\nprecision highp int;\nprecision highp usampler2DArray;\n\n#include <splatDefines>\n\nflat out uvec4 vSplat;\nout vec2 vSplatUv;\n#if GSL_STOCHASTIC\n\nflat out uint vStochasticOffset;\n#endif\n\nuniform vec2 renderSize;\nuniform vec4 renderToViewQuat;\nuniform vec3 renderToViewPos;\n\nuniform float renderToViewScale;\nuniform float maxStdDev;\nuniform float minPixelRadius;\nuniform float minAlpha;\nuniform float blurAmount;\nuniform float preBlurAmount;\nuniform float clipXY;\nuniform float focalAdjustment;\nuniform bool encodeLinear;\nuniform uint splatCount;\n\nuniform usampler2D ordering;\nuniform usampler2DArray splats;\nuniform usampler2DArray splats2;\n\n#if GSL_STOCHASTIC\nuniform bool stochasticOrdering;\nuniform uint stochasticSample;\nuniform vec2 viewportOrigin;\nuniform usampler2DArray stochasticSeeds;\n#endif\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 GSL_STOCHASTIC\n if (!stochasticOrdering) {\n \n splatIndex = index;\n } else\n #endif\n {\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 (isPerspectiveMatrix(projectionMatrix) && 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 = min(renderSize.x, renderSize.y) * 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 #if GSL_STOCHASTIC\n \n \n \n uint seed = texelFetch(stochasticSeeds, texCoord, 0).r;\n uint hash = hashU32(seed);\n uvec2 offset = uvec2(hash, hash >> 5u) - uvec2(viewportOrigin);\n uint phase = ((hash >> 10u) + stochasticSample) & 31u;\n vStochasticOffset = (offset.x & 31u) | ((offset.y & 31u) << 5u) | (phase << 10u);\n #endif\n\n \n if (encodeLinear) {\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 #include <logdepthbuf_vertex>\n}";
3008
3009
  let shaders = null;
3009
3010
  function getShaders() {
3010
3011
  if (!shaders) {
@@ -3376,7 +3377,8 @@ const _SplatAccumulator = class _SplatAccumulator {
3376
3377
  timer,
3377
3378
  camera,
3378
3379
  layerCamera = camera,
3379
- previous
3380
+ previous,
3381
+ frameCallbacks = true
3380
3382
  }) {
3381
3383
  const previousMapping = previous.mapping;
3382
3384
  const previousVersion = previous.version;
@@ -3405,12 +3407,13 @@ const _SplatAccumulator = class _SplatAccumulator {
3405
3407
  }
3406
3408
  if (!ancestor) globalEdits.add(node);
3407
3409
  });
3410
+ const frameContext = {
3411
+ time: this.time,
3412
+ deltaTime: this.deltaTime,
3413
+ globalEdits: Array.from(globalEdits)
3414
+ };
3408
3415
  for (const mesh of allMeshes) {
3409
- mesh.frameUpdate({
3410
- time: this.time,
3411
- deltaTime: this.deltaTime,
3412
- globalEdits: Array.from(globalEdits)
3413
- });
3416
+ mesh.frameUpdate(frameContext, frameCallbacks);
3414
3417
  }
3415
3418
  const visibleMeshes = [];
3416
3419
  scene.traverseVisible((node) => {
@@ -3602,7 +3605,6 @@ function createProjectionProgram(uniforms, view) {
3602
3605
  } = view;
3603
3606
  const maxStdDev = uniformBinding(uniforms, "maxStdDev", "float");
3604
3607
  const minPixelRadius = uniformBinding(uniforms, "minPixelRadius", "float");
3605
- const maxPixelRadius = uniformBinding(uniforms, "maxPixelRadius", "float");
3606
3608
  const minAlpha = uniformBinding(uniforms, "minAlpha", "float");
3607
3609
  const preBlurAmount = uniformBinding(uniforms, "preBlurAmount", "float");
3608
3610
  const blurAmount = uniformBinding(uniforms, "blurAmount", "float");
@@ -3717,7 +3719,7 @@ function createProjectionProgram(uniforms, view) {
3717
3719
  supportRadius.div(maximumSupportRadius),
3718
3720
  0
3719
3721
  );
3720
- const maxProjectedRadius = maxPixelRadius.mul(focalAdjustment);
3722
+ const maxProjectedRadius = renderSize.x.min(renderSize.y).mul(focalAdjustment);
3721
3723
  const fullScale1 = maxProjectedRadius.min(
3722
3724
  maximumSupportRadius.mul(eigen1.sqrt())
3723
3725
  );
@@ -5400,7 +5402,6 @@ class ProjectedSplats {
5400
5402
  sortMode,
5401
5403
  uniforms.maxStdDev.value,
5402
5404
  uniforms.minPixelRadius.value,
5403
- uniforms.maxPixelRadius.value,
5404
5405
  uniforms.minAlpha.value,
5405
5406
  uniforms.preBlurAmount.value,
5406
5407
  uniforms.blurAmount.value,
@@ -5542,13 +5543,13 @@ function createSplatBackend(renderer, uniforms, options) {
5542
5543
  }
5543
5544
  return usesNativeWebGPU(renderer) ? new WebGPUSplatBackend(renderer, uniforms, options) : new WebGLFallbackSplatBackend(renderer, uniforms, options);
5544
5545
  }
5545
- function configureSplatOutput(renderer, target, uniforms) {
5546
+ function configureSplatOutput(renderer, target, uniforms, encodeLinear) {
5546
5547
  let blendSpace = THREE__namespace.ColorManagement.workingColorSpace;
5547
5548
  if (!isWebGPURenderer(renderer)) {
5548
5549
  if (target === null) blendSpace = renderer.outputColorSpace;
5549
5550
  else if (isXRRenderTarget(target)) blendSpace = target.texture.colorSpace;
5550
5551
  }
5551
- uniforms.encodeLinear.value = blendSpace !== THREE__namespace.SRGBColorSpace;
5552
+ uniforms.encodeLinear.value = encodeLinear ?? blendSpace !== THREE__namespace.SRGBColorSpace;
5552
5553
  }
5553
5554
  const renderToViewScaleTmp = new THREE__namespace.Vector3();
5554
5555
  const renderToViewMatrixTmp = new THREE__namespace.Matrix4();
@@ -5590,6 +5591,7 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
5590
5591
  const geometry = new SplatGeometry();
5591
5592
  const material = backend.selectMaterial(stochastic);
5592
5593
  super(geometry, material);
5594
+ this.frameCallbacks = true;
5593
5595
  this.renderSize = new THREE__namespace.Vector2();
5594
5596
  this.stochasticSample = 0;
5595
5597
  this.lastFrame = -1;
@@ -5624,6 +5626,7 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
5624
5626
  this._depthTest = options.depthTest ?? true;
5625
5627
  this._depthWrite = options.depthWrite ?? false;
5626
5628
  this._premultipliedAlpha = premultipliedAlpha;
5629
+ this._encodeLinear = options.encodeLinear;
5627
5630
  this._transparent = options.transparent ?? true;
5628
5631
  this._stochastic = stochastic;
5629
5632
  this.autoAdvanceStochasticSample = options.autoAdvanceStochasticSample ?? true;
@@ -5637,7 +5640,6 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
5637
5640
  this.preUpdate = options.preUpdate ?? true;
5638
5641
  this.maxStdDev = options.maxStdDev ?? Math.sqrt(8);
5639
5642
  this.minPixelRadius = options.minPixelRadius ?? 1;
5640
- this.maxPixelRadius = options.maxPixelRadius ?? 256;
5641
5643
  this.minAlpha = options.minAlpha ?? DEFAULT_MIN_ALPHA;
5642
5644
  this.preBlurAmount = options.preBlurAmount ?? 0.3;
5643
5645
  this.blurAmount = options.blurAmount ?? 0;
@@ -5859,7 +5861,6 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
5859
5861
  this.uniforms.renderToViewScale.value = (renderToViewScaleTmp.x + renderToViewScaleTmp.y + renderToViewScaleTmp.z) / 3;
5860
5862
  this.uniforms.maxStdDev.value = this.maxStdDev;
5861
5863
  this.uniforms.minPixelRadius.value = this.minPixelRadius;
5862
- this.uniforms.maxPixelRadius.value = this.maxPixelRadius;
5863
5864
  this.uniforms.minAlpha.value = this.minAlpha;
5864
5865
  this.uniforms.preBlurAmount.value = this.preBlurAmount;
5865
5866
  this.uniforms.blurAmount.value = this.blurAmount;
@@ -5867,7 +5868,12 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
5867
5868
  this.uniforms.focalAdjustment.value = this.focalAdjustment;
5868
5869
  this.uniforms.stochastic.value = this.stochasticFrame;
5869
5870
  this.uniforms.stochasticSample.value = this.stochasticSample >>> 0;
5870
- configureSplatOutput(renderer, currentRenderTarget, this.uniforms);
5871
+ configureSplatOutput(
5872
+ renderer,
5873
+ currentRenderTarget,
5874
+ this.uniforms,
5875
+ this._encodeLinear
5876
+ );
5871
5877
  if (this.backend.kind === "webgpu") {
5872
5878
  if (this.backend.sortError) throw this.backend.sortError;
5873
5879
  if (this.backend.precompile) {
@@ -5949,7 +5955,8 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
5949
5955
  timer: this.timer,
5950
5956
  camera,
5951
5957
  layerCamera: request.camera,
5952
- previous: this.current
5958
+ previous: this.current,
5959
+ frameCallbacks: this.frameCallbacks
5953
5960
  });
5954
5961
  this.display = this.current;
5955
5962
  this.activeSplats = this.current.numSplats;
@@ -6016,7 +6023,8 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
6016
6023
  timer: this.timer,
6017
6024
  camera,
6018
6025
  layerCamera: updateCamera,
6019
- previous: this.current
6026
+ previous: this.current,
6027
+ frameCallbacks: this.frameCallbacks
6020
6028
  });
6021
6029
  } catch (error) {
6022
6030
  this.releaseAccumulator(next);
@@ -6194,6 +6202,15 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
6194
6202
  this.sorting = false;
6195
6203
  }
6196
6204
  }
6205
+ /** Decode sRGB before blending; undefined follows the active blend space. */
6206
+ get encodeLinear() {
6207
+ return this._encodeLinear;
6208
+ }
6209
+ set encodeLinear(value) {
6210
+ if (value === this._encodeLinear) return;
6211
+ this._encodeLinear = value;
6212
+ this.setDirty();
6213
+ }
6197
6214
  get premultipliedAlpha() {
6198
6215
  return this._premultipliedAlpha;
6199
6216
  }
@@ -6442,23 +6459,18 @@ class TAAPass extends Pass_js.Pass {
6442
6459
  this.edgeDepthDiff = 1e-3;
6443
6460
  this.maxMotionLength = 128;
6444
6461
  this.useSubpixelCorrection = true;
6445
- this.accumulateInOutputSpace = true;
6446
6462
  this._source = Object.assign(makeTarget("TAAPass.scene"), {
6447
- isXRRenderTarget: false
6463
+ isXRRenderTarget: true
6448
6464
  });
6449
6465
  this._history = [
6450
6466
  makeTarget("TAAPass.history0"),
6451
6467
  makeTarget("TAAPass.history1")
6452
6468
  ];
6453
- this._size = new THREE.Vector2();
6454
6469
  this._clearColor = new THREE.Color();
6455
6470
  this._state = createTAAState(
6456
6471
  (value) => ({ value }),
6457
6472
  [this._source, ...this._history]
6458
6473
  );
6459
- this._workingColorSpace = THREE.ColorManagement.workingColorSpace;
6460
- this._toneMapping = THREE.NoToneMapping;
6461
- this._toneMappingExposure = 1;
6462
6474
  this._uniforms = {
6463
6475
  source: { value: this._source.texture },
6464
6476
  sourceDepth: { value: this._source.depthTexture },
@@ -6479,15 +6491,23 @@ class TAAPass extends Pass_js.Pass {
6479
6491
  });
6480
6492
  this._copyMaterial = new THREE.ShaderMaterial({
6481
6493
  name: "TAAPass.copy",
6482
- uniforms: { source: { value: null } },
6494
+ uniforms: { source: { value: null }, decode: { value: false } },
6483
6495
  vertexShader,
6496
+ // Inverts OutputPass's transfer, so the two round-trip at any alpha.
6484
6497
  fragmentShader: (
6485
6498
  /* glsl */
6486
6499
  `
6487
6500
  uniform sampler2D source;
6501
+ uniform bool decode;
6488
6502
  in vec2 vUv;
6489
6503
  out vec4 fragColor;
6490
- void main() { fragColor = texture(source, vUv); }
6504
+ void main() {
6505
+ fragColor = texture(source, vUv);
6506
+ if (decode) {
6507
+ vec4 color = vec4(max(fragColor.rgb, 0.0), 1.0);
6508
+ fragColor.rgb = sRGBTransferEOTF(color).rgb;
6509
+ }
6510
+ }
6491
6511
  `
6492
6512
  ),
6493
6513
  glslVersion: THREE.GLSL3,
@@ -6514,33 +6534,23 @@ class TAAPass extends Pass_js.Pass {
6514
6534
  reset() {
6515
6535
  this._state.reset();
6516
6536
  }
6517
- /** Render the scene and TAA to the canvas, a target, or the composer's buffer. */
6518
- render(renderer, writeBuffer = null) {
6537
+ /** Render the scene and resolve TAA into the composer's working-space buffer. */
6538
+ render(renderer, writeBuffer) {
6519
6539
  const { camera, _state: state, _uniforms: uniforms } = this;
6520
- if (writeBuffer) {
6521
- this.setSize(writeBuffer.width, writeBuffer.height);
6522
- } else {
6523
- renderer.getDrawingBufferSize(this._size);
6524
- this.setSize(this._size.x, this._size.y);
6525
- }
6526
- const workingColorSpace = THREE.ColorManagement.workingColorSpace;
6527
- const colorSpace = this.accumulateInOutputSpace ? renderer.outputColorSpace : workingColorSpace;
6528
- const toneMapping = this.accumulateInOutputSpace ? renderer.toneMapping : THREE.NoToneMapping;
6529
- const exposure = toneMapping === THREE.NoToneMapping ? 1 : renderer.toneMappingExposure;
6530
- if (this._source.texture.colorSpace !== colorSpace || this._source.isXRRenderTarget !== this.accumulateInOutputSpace || this._workingColorSpace !== workingColorSpace || this._toneMapping !== toneMapping || this._toneMappingExposure !== exposure) {
6540
+ this.setSize(writeBuffer.width, writeBuffer.height);
6541
+ const colorSpace = renderer.outputColorSpace;
6542
+ if (this._source.texture.colorSpace !== colorSpace) {
6531
6543
  this._source.texture.colorSpace = colorSpace;
6532
- this._source.isXRRenderTarget = this.accumulateInOutputSpace;
6533
- this._workingColorSpace = workingColorSpace;
6534
- this._toneMapping = toneMapping;
6535
- this._toneMappingExposure = exposure;
6536
6544
  this.reset();
6537
6545
  }
6538
6546
  const previousTarget = renderer.getRenderTarget();
6539
6547
  const autoClear = renderer.autoClear;
6540
6548
  const xrEnabled = renderer.xr.enabled;
6549
+ const toneMapping = renderer.toneMapping;
6541
6550
  try {
6542
6551
  renderer.xr.enabled = false;
6543
6552
  renderer.autoClear = false;
6553
+ renderer.toneMapping = THREE.NoToneMapping;
6544
6554
  const reversedDepth = camera.reversedDepth;
6545
6555
  state.beginCapture(camera);
6546
6556
  try {
@@ -6570,6 +6580,7 @@ class TAAPass extends Pass_js.Pass {
6570
6580
  this._quad.material = this._resolveMaterial;
6571
6581
  this._quad.render(renderer);
6572
6582
  this._copyMaterial.uniforms.source.value = output.texture;
6583
+ this._copyMaterial.uniforms.decode.value = !this.renderToScreen && THREE.ColorManagement.getTransfer(colorSpace) === THREE.SRGBTransfer;
6573
6584
  renderer.setRenderTarget(this.renderToScreen ? null : writeBuffer);
6574
6585
  this._quad.material = this._copyMaterial;
6575
6586
  this._quad.render(renderer);
@@ -6578,6 +6589,7 @@ class TAAPass extends Pass_js.Pass {
6578
6589
  renderer.setRenderTarget(previousTarget);
6579
6590
  renderer.autoClear = autoClear;
6580
6591
  renderer.xr.enabled = xrEnabled;
6592
+ renderer.toneMapping = toneMapping;
6581
6593
  }
6582
6594
  }
6583
6595
  dispose() {
@@ -6878,6 +6890,643 @@ class TAANode extends webgpu.TempNode {
6878
6890
  this.material.dispose();
6879
6891
  }
6880
6892
  }
6893
+ class WebGLCapture {
6894
+ constructor(renderer) {
6895
+ this.renderer = renderer;
6896
+ this.depthTexture = new THREE__namespace.DepthTexture(
6897
+ 1,
6898
+ 1,
6899
+ THREE__namespace.UnsignedIntType
6900
+ );
6901
+ this.target = Object.assign(
6902
+ new THREE__namespace.WebGLRenderTarget(1, 1, {
6903
+ type: THREE__namespace.UnsignedByteType,
6904
+ internalFormat: "RGBA8",
6905
+ generateMipmaps: false,
6906
+ depthTexture: this.depthTexture
6907
+ }),
6908
+ // Output/XR semantics enable per-material display conversion before blending.
6909
+ // Plain RGBA8 stores those output-space values without an sRGB attachment.
6910
+ { isXRRenderTarget: true }
6911
+ );
6912
+ this.camera = new THREE__namespace.OrthographicCamera(-1, 1, 1, -1, 0, 1);
6913
+ this.material = new THREE__namespace.ShaderMaterial({
6914
+ uniforms: {
6915
+ colorBuffer: { value: this.target.texture },
6916
+ depthBuffer: { value: this.target.depthTexture },
6917
+ decodeSRGB: { value: true },
6918
+ encodeSRGB: { value: false }
6919
+ },
6920
+ vertexShader: `
6921
+ varying vec2 vUv;
6922
+ void main() {
6923
+ vUv = uv;
6924
+ gl_Position = vec4(position.xy, 0.0, 1.0);
6925
+ }
6926
+ `,
6927
+ fragmentShader: `
6928
+ uniform sampler2D colorBuffer;
6929
+ uniform sampler2D depthBuffer;
6930
+ uniform bool decodeSRGB;
6931
+ uniform bool encodeSRGB;
6932
+ varying vec2 vUv;
6933
+ void main() {
6934
+ vec4 color = texture2D(colorBuffer, vUv);
6935
+ color.rgb = color.a > 0.0 ? color.rgb / color.a : vec3(0.0);
6936
+ if (decodeSRGB) color = sRGBTransferEOTF(color);
6937
+ if (encodeSRGB) color = sRGBTransferOETF(color);
6938
+ color.rgb *= color.a;
6939
+ gl_FragColor = color;
6940
+ gl_FragDepth = texture2D(depthBuffer, vUv).r;
6941
+ }
6942
+ `,
6943
+ blending: THREE__namespace.NoBlending,
6944
+ depthFunc: THREE__namespace.AlwaysDepth,
6945
+ toneMapped: false
6946
+ });
6947
+ this.quad = new THREE__namespace.Mesh(
6948
+ new THREE__namespace.PlaneGeometry(2, 2),
6949
+ this.material
6950
+ );
6951
+ this.quad.frustumCulled = false;
6952
+ }
6953
+ render(scene, camera) {
6954
+ const { renderer, target, material } = this;
6955
+ const output = renderer.getRenderTarget();
6956
+ if (!output) throw new Error("WebGL capture requires a render target");
6957
+ const face = renderer.getActiveCubeFace();
6958
+ const mip = renderer.getActiveMipmapLevel();
6959
+ const type = output.texture.type === THREE__namespace.HalfFloatType || output.texture.type === THREE__namespace.FloatType ? output.texture.type : THREE__namespace.UnsignedByteType;
6960
+ if (target.texture.type !== type || target.depthBuffer !== output.depthBuffer || target.stencilBuffer !== output.stencilBuffer || target.samples !== output.samples) {
6961
+ target.dispose();
6962
+ target.texture.type = type;
6963
+ target.texture.internalFormat = type === THREE__namespace.UnsignedByteType ? "RGBA8" : null;
6964
+ target.depthBuffer = output.depthBuffer;
6965
+ target.stencilBuffer = output.stencilBuffer;
6966
+ this.depthTexture.format = output.stencilBuffer ? THREE__namespace.DepthStencilFormat : THREE__namespace.DepthFormat;
6967
+ this.depthTexture.type = output.stencilBuffer ? THREE__namespace.UnsignedInt248Type : THREE__namespace.UnsignedIntType;
6968
+ target.samples = output.samples;
6969
+ }
6970
+ target.setSize(output.width, output.height);
6971
+ target.texture.colorSpace = renderer.outputColorSpace;
6972
+ material.uniforms.decodeSRGB.value = target.texture.colorSpace === THREE__namespace.SRGBColorSpace;
6973
+ material.uniforms.encodeSRGB.value = output.texture.colorSpace === THREE__namespace.SRGBColorSpace && output.texture.type !== THREE__namespace.UnsignedByteType;
6974
+ material.depthTest = output.depthBuffer;
6975
+ material.depthWrite = output.depthBuffer;
6976
+ try {
6977
+ renderer.setRenderTarget(target);
6978
+ renderer.render(scene, camera);
6979
+ renderer.setRenderTarget(output, face, mip);
6980
+ material.depthFunc = renderer.state.buffers.depth.getReversed() ? THREE__namespace.NeverDepth : THREE__namespace.AlwaysDepth;
6981
+ renderer.render(this.quad, this.camera);
6982
+ } finally {
6983
+ renderer.setRenderTarget(output, face, mip);
6984
+ }
6985
+ }
6986
+ dispose() {
6987
+ this.target.dispose();
6988
+ this.quad.geometry.dispose();
6989
+ this.material.dispose();
6990
+ }
6991
+ }
6992
+ class WebGPUCapture {
6993
+ constructor(renderer) {
6994
+ this.renderer = renderer;
6995
+ this.depthTexture = new THREE__namespace.DepthTexture(1, 1, THREE__namespace.FloatType);
6996
+ this.target = new THREE__namespace.RenderTarget(1, 1, {
6997
+ generateMipmaps: false,
6998
+ depthTexture: this.depthTexture
6999
+ });
7000
+ this.material = new webgpu.NodeMaterial();
7001
+ this.quad = new webgpu.QuadMesh(this.material);
7002
+ this.workingColorSpace = "";
7003
+ this.outputColorSpace = "";
7004
+ this.storageColorSpace = "";
7005
+ this.toneMapping = null;
7006
+ this.material.blending = THREE__namespace.NoBlending;
7007
+ this.material.toneMapped = false;
7008
+ this.material.depthNode = N.texture(this.depthTexture).load(
7009
+ N.ivec2(N.screenCoordinate.xy)
7010
+ ).r;
7011
+ }
7012
+ render(scene, camera) {
7013
+ const { renderer, target, material } = this;
7014
+ const output = renderer.getRenderTarget();
7015
+ if (!output) throw new Error("WebGPU capture requires a render target");
7016
+ const face = renderer.getActiveCubeFace();
7017
+ const mip = renderer.getActiveMipmapLevel();
7018
+ const workingColorSpace = THREE__namespace.ColorManagement.workingColorSpace;
7019
+ const native = usesNativeWebGPU(renderer);
7020
+ const depthFormat = output.stencilBuffer ? THREE__namespace.DepthStencilFormat : THREE__namespace.DepthFormat;
7021
+ let depthType = !native && output.samples > 0 ? THREE__namespace.UnsignedIntType : THREE__namespace.FloatType;
7022
+ if (output.stencilBuffer) {
7023
+ const float = output.depthTexture ? output.depthTexture.type === THREE__namespace.FloatType : renderer.reversedDepthBuffer;
7024
+ depthType = native && float && renderer.hasFeature("depth32float-stencil8") ? THREE__namespace.FloatType : THREE__namespace.UnsignedInt248Type;
7025
+ }
7026
+ const type = output.texture.type === THREE__namespace.HalfFloatType || output.texture.type === THREE__namespace.FloatType ? output.texture.type : renderer.outputColorSpace !== workingColorSpace || renderer.toneMapping !== THREE__namespace.NoToneMapping ? renderer.getOutputBufferType() : THREE__namespace.UnsignedByteType;
7027
+ if (target.texture.type !== type || target.depthBuffer !== output.depthBuffer || target.stencilBuffer !== output.stencilBuffer || this.depthTexture.type !== depthType || this.depthTexture.format !== depthFormat || target.samples !== output.samples) {
7028
+ target.dispose();
7029
+ target.texture.type = type;
7030
+ this.depthTexture.type = depthType;
7031
+ this.depthTexture.format = depthFormat;
7032
+ target.depthBuffer = output.depthBuffer;
7033
+ target.stencilBuffer = output.stencilBuffer;
7034
+ target.depthTexture = output.depthBuffer ? this.depthTexture : null;
7035
+ if (target.samples !== output.samples) material.needsUpdate = true;
7036
+ target.samples = output.samples;
7037
+ }
7038
+ target.setSize(output.width, output.height);
7039
+ const storageColorSpace = output.texture.type === THREE__namespace.UnsignedByteType && output.texture.colorSpace === THREE__namespace.SRGBColorSpace ? THREE__namespace.LinearSRGBColorSpace : output.texture.colorSpace || THREE__namespace.LinearSRGBColorSpace;
7040
+ if (this.workingColorSpace !== workingColorSpace || this.outputColorSpace !== renderer.outputColorSpace || this.storageColorSpace !== storageColorSpace || this.toneMapping !== renderer.toneMapping) {
7041
+ const color = N.texture(target.texture).load(
7042
+ N.ivec2(N.screenCoordinate.xy)
7043
+ );
7044
+ const outputColor = N.renderOutput(
7045
+ color,
7046
+ renderer.toneMapping,
7047
+ renderer.outputColorSpace
7048
+ );
7049
+ material.fragmentNode = N.premultiplyAlpha(
7050
+ N.convertColorSpace(
7051
+ N.unpremultiplyAlpha(outputColor),
7052
+ renderer.outputColorSpace,
7053
+ storageColorSpace
7054
+ )
7055
+ );
7056
+ material.needsUpdate = true;
7057
+ this.workingColorSpace = workingColorSpace;
7058
+ this.outputColorSpace = renderer.outputColorSpace;
7059
+ this.storageColorSpace = storageColorSpace;
7060
+ this.toneMapping = renderer.toneMapping;
7061
+ }
7062
+ material.depthTest = output.depthBuffer;
7063
+ material.depthWrite = output.depthBuffer;
7064
+ material.depthFunc = renderer.reversedDepthBuffer ? THREE__namespace.NeverDepth : THREE__namespace.AlwaysDepth;
7065
+ try {
7066
+ renderer.setRenderTarget(target);
7067
+ renderer.render(scene, camera);
7068
+ renderer.setRenderTarget(output, face, mip);
7069
+ this.quad.render(renderer);
7070
+ } finally {
7071
+ renderer.setRenderTarget(output, face, mip);
7072
+ }
7073
+ }
7074
+ dispose() {
7075
+ this.target.dispose();
7076
+ this.material.dispose();
7077
+ }
7078
+ }
7079
+ async function readPixels(renderer, target, pixels, face = 0) {
7080
+ if (target.texture.format !== THREE__namespace.RGBAFormat || target.texture.type !== THREE__namespace.UnsignedByteType) {
7081
+ throw new Error(
7082
+ "Splat capture readback requires an RGBA UnsignedByte target"
7083
+ );
7084
+ }
7085
+ const { width, height } = target;
7086
+ if (!isWebGPURenderer(renderer)) {
7087
+ await renderer.readRenderTargetPixelsAsync(
7088
+ target,
7089
+ 0,
7090
+ 0,
7091
+ width,
7092
+ height,
7093
+ pixels,
7094
+ face
7095
+ );
7096
+ return;
7097
+ }
7098
+ const readback = await renderer.readRenderTargetPixelsAsync(
7099
+ target,
7100
+ 0,
7101
+ 0,
7102
+ width,
7103
+ height,
7104
+ 0,
7105
+ face
7106
+ );
7107
+ const source = new Uint8Array(
7108
+ readback.buffer,
7109
+ readback.byteOffset,
7110
+ readback.byteLength
7111
+ );
7112
+ if (!usesNativeWebGPU(renderer)) {
7113
+ pixels.set(source);
7114
+ return;
7115
+ }
7116
+ const rowBytes = width * 4;
7117
+ const rowStride = Math.ceil(rowBytes / 256) * 256;
7118
+ for (let y = 0; y < height; y++) {
7119
+ const start = (height - y - 1) * rowStride;
7120
+ pixels.set(source.subarray(start, start + rowBytes), y * rowBytes);
7121
+ }
7122
+ }
7123
+ function downsamplePixels(source, width, height, superXY, output) {
7124
+ const subWidth = width / superXY;
7125
+ const subHeight = height / superXY;
7126
+ const samples = superXY * superXY;
7127
+ for (let y = 0; y < subHeight; y++) {
7128
+ for (let x = 0; x < subWidth; x++) {
7129
+ const outputIndex = (y * subWidth + x) * 4;
7130
+ for (let channel = 0; channel < 4; channel++) {
7131
+ let sum = 0;
7132
+ for (let sy = 0; sy < superXY; sy++) {
7133
+ const row = (y * superXY + sy) * width;
7134
+ for (let sx = 0; sx < superXY; sx++) {
7135
+ sum += source[(row + x * superXY + sx) * 4 + channel];
7136
+ }
7137
+ }
7138
+ output[outputIndex + channel] = Math.round(sum / samples);
7139
+ }
7140
+ }
7141
+ }
7142
+ }
7143
+ function withCaptureState(renderer, draw) {
7144
+ const target = renderer.getRenderTarget();
7145
+ const cubeFace = renderer.getActiveCubeFace();
7146
+ const mipmapLevel = renderer.getActiveMipmapLevel();
7147
+ const xrEnabled = renderer.xr.enabled;
7148
+ const autoClear = renderer.autoClear;
7149
+ const nodeRenderer = isWebGPURenderer(renderer) ? renderer : null;
7150
+ const mrt = (nodeRenderer == null ? void 0 : nodeRenderer.getMRT()) ?? null;
7151
+ renderer.xr.enabled = false;
7152
+ renderer.autoClear = true;
7153
+ nodeRenderer == null ? void 0 : nodeRenderer.setMRT(null);
7154
+ try {
7155
+ return draw();
7156
+ } finally {
7157
+ renderer.xr.enabled = xrEnabled;
7158
+ renderer.autoClear = autoClear;
7159
+ nodeRenderer == null ? void 0 : nodeRenderer.setMRT(mrt);
7160
+ setRendererRenderTarget(renderer, target, cubeFace, mipmapLevel);
7161
+ }
7162
+ }
7163
+ function captureSceneView(scene, hideObjects) {
7164
+ if (hideObjects.length === 0) return scene;
7165
+ const hidden = new Set(hideObjects);
7166
+ const view = new THREE__namespace.Scene();
7167
+ view.matrixWorldAutoUpdate = scene.matrixWorldAutoUpdate;
7168
+ view.updateMatrixWorld = (force) => scene.updateMatrixWorld(force);
7169
+ const filter = (callback) => (object) => {
7170
+ if (!(object instanceof SplatEdit)) {
7171
+ let ancestor = object;
7172
+ while (ancestor) {
7173
+ if (hidden.has(ancestor)) return;
7174
+ ancestor = ancestor.parent;
7175
+ }
7176
+ }
7177
+ callback(object);
7178
+ };
7179
+ view.traverse = (callback) => scene.traverse(filter(callback));
7180
+ view.traverseVisible = (callback) => scene.traverseVisible(filter(callback));
7181
+ return view;
7182
+ }
7183
+ class SplatCapture {
7184
+ constructor({ splatRenderer, target }) {
7185
+ this.disposed = false;
7186
+ this.pending = Promise.resolve();
7187
+ this.cubeRenders = /* @__PURE__ */ new Map();
7188
+ this.lastCube = null;
7189
+ this.pmrem = null;
7190
+ this.splatRenderer = splatRenderer;
7191
+ this.capturePass = isWebGPURenderer(this.renderer) ? new WebGPUCapture(this.renderer) : new WebGLCapture(this.renderer);
7192
+ this.superXY = (target == null ? void 0 : target.superXY) ?? 1;
7193
+ if (!Number.isInteger(this.superXY) || this.superXY < 1 || this.superXY > 4) {
7194
+ throw new Error("Splat capture superXY must be an integer from 1 to 4");
7195
+ }
7196
+ if (target) this.initializeTarget(target);
7197
+ this.captureRenderer = new GaussianSplatRenderer({
7198
+ renderer: this.renderer,
7199
+ timer: splatRenderer.timer,
7200
+ autoUpdate: false,
7201
+ stochastic: false
7202
+ });
7203
+ this.captureRenderer.frameCallbacks = false;
7204
+ }
7205
+ initializeTarget(target) {
7206
+ const {
7207
+ width,
7208
+ height,
7209
+ doubleBuffer,
7210
+ superXY: _superXY,
7211
+ ...options
7212
+ } = target;
7213
+ if (!Number.isInteger(width) || !Number.isInteger(height) || width < 1 || height < 1 || width * this.superXY > 8192 || height * this.superXY > 8192) {
7214
+ throw new Error(
7215
+ "Splat capture dimensions must be positive integers up to 8192 / superXY"
7216
+ );
7217
+ }
7218
+ const targetOptions = {
7219
+ format: THREE__namespace.RGBAFormat,
7220
+ type: THREE__namespace.UnsignedByteType,
7221
+ colorSpace: THREE__namespace.SRGBColorSpace,
7222
+ ...options,
7223
+ // Three.js attaches stencil only together with depth.
7224
+ stencilBuffer: options.depthBuffer !== false && this.stencilBuffer
7225
+ };
7226
+ const superWidth = width * this.superXY;
7227
+ const superHeight = height * this.superXY;
7228
+ this.target = isWebGPURenderer(this.renderer) ? new THREE__namespace.RenderTarget(superWidth, superHeight, targetOptions) : new THREE__namespace.WebGLRenderTarget(superWidth, superHeight, targetOptions);
7229
+ if (doubleBuffer) {
7230
+ this.backTarget = this.target.clone();
7231
+ }
7232
+ }
7233
+ get renderer() {
7234
+ return this.splatRenderer.renderer;
7235
+ }
7236
+ get stencilBuffer() {
7237
+ var _a2;
7238
+ const { renderer } = this;
7239
+ return isWebGPURenderer(renderer) ? renderer.stencil : ((_a2 = renderer.getContextAttributes()) == null ? void 0 : _a2.stencil) === true;
7240
+ }
7241
+ assertActive() {
7242
+ if (this.disposed) throw new Error("Splat capture is disposed");
7243
+ }
7244
+ enqueue(task) {
7245
+ const result = this.pending.then(task);
7246
+ this.pending = result.catch(() => {
7247
+ });
7248
+ return result;
7249
+ }
7250
+ async renderCapture({ scene, camera }, draw, radial = false, hideObjects = []) {
7251
+ this.assertActive();
7252
+ const { splatRenderer: source, captureRenderer } = this;
7253
+ Object.assign(captureRenderer, {
7254
+ maxStdDev: source.maxStdDev,
7255
+ minPixelRadius: source.minPixelRadius,
7256
+ minAlpha: source.minAlpha,
7257
+ preBlurAmount: source.preBlurAmount,
7258
+ blurAmount: source.blurAmount,
7259
+ clipXY: source.clipXY,
7260
+ focalAdjustment: source.focalAdjustment,
7261
+ fastSort: source.fastSort,
7262
+ premultipliedAlpha: source.premultipliedAlpha,
7263
+ transparent: source.transparent,
7264
+ depthTest: source.depthTest,
7265
+ depthWrite: source.depthWrite,
7266
+ encodeLinear: source.encodeLinear,
7267
+ sortRadial: radial || source.sortRadial
7268
+ });
7269
+ const sourceMaterial = source.material;
7270
+ const captureMaterial = captureRenderer.material;
7271
+ captureMaterial.stencilWrite = sourceMaterial.stencilWrite;
7272
+ captureMaterial.stencilWriteMask = sourceMaterial.stencilWriteMask;
7273
+ captureMaterial.stencilFunc = sourceMaterial.stencilFunc;
7274
+ captureMaterial.stencilRef = sourceMaterial.stencilRef;
7275
+ captureMaterial.stencilFuncMask = sourceMaterial.stencilFuncMask;
7276
+ captureMaterial.stencilFail = sourceMaterial.stencilFail;
7277
+ captureMaterial.stencilZFail = sourceMaterial.stencilZFail;
7278
+ captureMaterial.stencilZPass = sourceMaterial.stencilZPass;
7279
+ captureRenderer.layers.mask = camera.layers.mask;
7280
+ const view = { scene: captureSceneView(scene, hideObjects), camera };
7281
+ await captureRenderer.update(view);
7282
+ if (captureRenderer.display.mapping.some(
7283
+ ({ node, version }) => node.version !== version
7284
+ )) {
7285
+ await captureRenderer.update(view);
7286
+ }
7287
+ this.assertActive();
7288
+ const layers = /* @__PURE__ */ new Map();
7289
+ scene.traverse((object) => {
7290
+ if (object instanceof GaussianSplatRenderer) {
7291
+ layers.set(object.layers, object.layers.mask);
7292
+ }
7293
+ });
7294
+ const visibility = new Map(
7295
+ hideObjects.map((object) => [object, object.visible])
7296
+ );
7297
+ try {
7298
+ for (const layer of layers.keys()) layer.mask = 0;
7299
+ for (const object of visibility.keys()) object.visible = false;
7300
+ scene.add(captureRenderer);
7301
+ withCaptureState(this.renderer, draw);
7302
+ } finally {
7303
+ scene.remove(captureRenderer);
7304
+ for (const [layer, mask] of layers) layer.mask = mask;
7305
+ for (const [object, visible] of visibility) object.visible = visible;
7306
+ }
7307
+ }
7308
+ /** Snapshots the camera now; the capture may wait for earlier ones. */
7309
+ targetCapture({ scene, camera }) {
7310
+ this.assertActive();
7311
+ camera.updateWorldMatrix(true, false);
7312
+ const Camera = camera.constructor;
7313
+ const captureCamera = new Camera().copy(camera, false);
7314
+ captureCamera.matrixAutoUpdate = false;
7315
+ captureCamera.matrix.copy(camera.matrixWorld);
7316
+ return async () => {
7317
+ this.assertActive();
7318
+ const target = this.backTarget ?? this.target;
7319
+ if (!target)
7320
+ throw new Error("Initialize SplatCapture with target options");
7321
+ const stencilBuffer = target.depthBuffer && this.stencilBuffer;
7322
+ if (target.stencilBuffer !== stencilBuffer) {
7323
+ target.dispose();
7324
+ target.stencilBuffer = stencilBuffer;
7325
+ }
7326
+ await this.renderCapture({ scene, camera: captureCamera }, () => {
7327
+ setRendererRenderTarget(this.renderer, target);
7328
+ this.capturePass.render(scene, captureCamera);
7329
+ });
7330
+ if (target !== this.target) {
7331
+ [this.target, this.backTarget] = [this.backTarget, this.target];
7332
+ }
7333
+ return target;
7334
+ };
7335
+ }
7336
+ async renderTarget(view) {
7337
+ return this.enqueue(this.targetCapture(view));
7338
+ }
7339
+ /** Reuses the returned buffer; copy it if a later read must not overwrite it. */
7340
+ async readTarget() {
7341
+ var _a2, _b;
7342
+ this.assertActive();
7343
+ const target = this.target;
7344
+ if (!target) throw new Error("Initialize SplatCapture with target options");
7345
+ const { width, height } = target;
7346
+ if (width % this.superXY !== 0 || height % this.superXY !== 0) {
7347
+ throw new Error(
7348
+ "Splat capture target dimensions must be divisible by superXY"
7349
+ );
7350
+ }
7351
+ const byteSize = width * height * 4;
7352
+ if (((_a2 = this.superPixels) == null ? void 0 : _a2.length) !== byteSize) {
7353
+ this.superPixels = new Uint8Array(byteSize);
7354
+ }
7355
+ const pixels = this.superPixels;
7356
+ await readPixels(this.renderer, target, pixels);
7357
+ this.assertActive();
7358
+ if (this.superXY === 1) return pixels;
7359
+ const subSize = byteSize / (this.superXY * this.superXY);
7360
+ if (((_b = this.targetPixels) == null ? void 0 : _b.length) !== subSize) {
7361
+ this.targetPixels = new Uint8Array(subSize);
7362
+ }
7363
+ downsamplePixels(pixels, width, height, this.superXY, this.targetPixels);
7364
+ return this.targetPixels;
7365
+ }
7366
+ async renderReadTarget(view) {
7367
+ const capture = this.targetCapture(view);
7368
+ return this.enqueue(async () => {
7369
+ await capture();
7370
+ return this.readTarget();
7371
+ });
7372
+ }
7373
+ renderCube(scene, camera) {
7374
+ const { renderer } = this;
7375
+ camera.updateMatrixWorld();
7376
+ if (camera.coordinateSystem !== renderer.coordinateSystem) {
7377
+ camera.coordinateSystem = renderer.coordinateSystem;
7378
+ camera.updateCoordinateSystem();
7379
+ }
7380
+ const target = camera.renderTarget;
7381
+ const generateMipmaps = target.texture.generateMipmaps;
7382
+ target.texture.generateMipmaps = false;
7383
+ try {
7384
+ for (let face = 0; face < 6; face++) {
7385
+ if (face === 5) target.texture.generateMipmaps = generateMipmaps;
7386
+ setRendererRenderTarget(
7387
+ renderer,
7388
+ target,
7389
+ face,
7390
+ camera.activeMipmapLevel
7391
+ );
7392
+ this.capturePass.render(scene, camera.children[face]);
7393
+ }
7394
+ target.texture.needsPMREMUpdate = true;
7395
+ } finally {
7396
+ target.texture.generateMipmaps = generateMipmaps;
7397
+ }
7398
+ }
7399
+ /** Validates and snapshots the center now; the capture may wait for earlier ones. */
7400
+ cubeCapture({
7401
+ scene,
7402
+ worldCenter,
7403
+ size: size2 = 256,
7404
+ near = 0.1,
7405
+ far = 1e3,
7406
+ hideObjects = [],
7407
+ filter = false
7408
+ }) {
7409
+ this.assertActive();
7410
+ if (!Number.isInteger(size2) || size2 < 1 || size2 > 8192) {
7411
+ throw new Error(
7412
+ "Splat capture cube size must be a positive integer up to 8192"
7413
+ );
7414
+ }
7415
+ if (!(near > 0 && far > near && Number.isFinite(far))) {
7416
+ throw new Error("Splat capture cube requires 0 < near < far < Infinity");
7417
+ }
7418
+ const center = worldCenter.clone();
7419
+ return async () => {
7420
+ this.assertActive();
7421
+ const stencilBuffer = this.stencilBuffer;
7422
+ let cubeRender = this.cubeRenders.get(filter);
7423
+ if (!cubeRender || cubeRender.target.width !== size2 || cubeRender.target.stencilBuffer !== stencilBuffer || cubeRender.near !== near || cubeRender.far !== far) {
7424
+ cubeRender == null ? void 0 : cubeRender.target.dispose();
7425
+ if (this.lastCube === cubeRender) this.lastCube = null;
7426
+ const Target = isWebGPURenderer(this.renderer) ? webgpu.CubeRenderTarget : THREE__namespace.WebGLCubeRenderTarget;
7427
+ const target2 = new Target(size2, {
7428
+ format: THREE__namespace.RGBAFormat,
7429
+ type: THREE__namespace.UnsignedByteType,
7430
+ stencilBuffer,
7431
+ generateMipmaps: filter,
7432
+ minFilter: filter ? THREE__namespace.LinearMipmapLinearFilter : THREE__namespace.LinearFilter,
7433
+ colorSpace: filter ? THREE__namespace.LinearSRGBColorSpace : THREE__namespace.SRGBColorSpace
7434
+ });
7435
+ const camera2 = new THREE__namespace.CubeCamera(near, far, target2);
7436
+ cubeRender = { target: target2, camera: camera2, near, far };
7437
+ this.cubeRenders.set(filter, cubeRender);
7438
+ }
7439
+ const { target, camera } = cubeRender;
7440
+ camera.position.copy(center);
7441
+ const updateCamera = new THREE__namespace.PerspectiveCamera(90, 1, near, far);
7442
+ updateCamera.position.copy(center);
7443
+ await this.renderCapture(
7444
+ { scene, camera: updateCamera },
7445
+ () => this.renderCube(scene, camera),
7446
+ true,
7447
+ hideObjects
7448
+ );
7449
+ this.lastCube = cubeRender;
7450
+ return target.texture;
7451
+ };
7452
+ }
7453
+ async renderCubeMap(options) {
7454
+ return this.enqueue(this.cubeCapture(options));
7455
+ }
7456
+ /** Faces of the latest cube capture, in the WebGL layout on every backend. */
7457
+ async readCubeTargets() {
7458
+ return this.enqueue(async () => {
7459
+ this.assertActive();
7460
+ if (!this.lastCube)
7461
+ throw new Error("Render a cube map before reading its faces");
7462
+ const { target } = this.lastCube;
7463
+ const native = usesNativeWebGPU(this.renderer);
7464
+ const faces = [];
7465
+ for (let face = 0; face < 6; face++) {
7466
+ const pixels = new Uint8Array(target.width * target.height * 4);
7467
+ const source = native && face < 2 ? 1 - face : face;
7468
+ await readPixels(this.renderer, target, pixels, source);
7469
+ if (native) new Uint32Array(pixels.buffer).reverse();
7470
+ faces.push(pixels);
7471
+ }
7472
+ this.assertActive();
7473
+ return faces;
7474
+ });
7475
+ }
7476
+ /** The caller owns the result; texture.dispose() also releases its render target. */
7477
+ async renderEnvMap(options) {
7478
+ const capture = this.cubeCapture({ ...options, filter: true });
7479
+ return this.enqueue(async () => {
7480
+ const cube = await capture();
7481
+ this.assertActive();
7482
+ this.pmrem ?? (this.pmrem = isWebGPURenderer(this.renderer) ? new webgpu.PMREMGenerator(this.renderer) : new THREE__namespace.PMREMGenerator(this.renderer));
7483
+ const pmrem = this.pmrem;
7484
+ const target = withCaptureState(
7485
+ this.renderer,
7486
+ () => pmrem.fromCubemap(cube)
7487
+ );
7488
+ const disposeTarget = () => {
7489
+ target.texture.removeEventListener("dispose", disposeTarget);
7490
+ target.dispose();
7491
+ };
7492
+ target.texture.addEventListener("dispose", disposeTarget);
7493
+ return target.texture;
7494
+ });
7495
+ }
7496
+ recurseSetEnvMap(root, envMap) {
7497
+ root.traverse((node) => {
7498
+ var _a2;
7499
+ if (!(node instanceof THREE__namespace.Mesh)) return;
7500
+ const materials = Array.isArray(node.material) ? node.material : [node.material];
7501
+ for (const material of materials) {
7502
+ if (material instanceof THREE__namespace.MeshStandardMaterial) {
7503
+ if (((_a2 = material.envMap) == null ? void 0 : _a2.mapping) !== envMap.mapping) {
7504
+ material.needsUpdate = true;
7505
+ }
7506
+ material.envMap = envMap;
7507
+ }
7508
+ }
7509
+ });
7510
+ }
7511
+ dispose() {
7512
+ var _a2, _b, _c;
7513
+ if (this.disposed) return;
7514
+ this.disposed = true;
7515
+ this.captureRenderer.dispose();
7516
+ this.capturePass.dispose();
7517
+ (_a2 = this.target) == null ? void 0 : _a2.dispose();
7518
+ (_b = this.backTarget) == null ? void 0 : _b.dispose();
7519
+ for (const { target } of this.cubeRenders.values()) target.dispose();
7520
+ (_c = this.pmrem) == null ? void 0 : _c.dispose();
7521
+ this.target = void 0;
7522
+ this.backTarget = void 0;
7523
+ this.superPixels = void 0;
7524
+ this.targetPixels = void 0;
7525
+ this.cubeRenders.clear();
7526
+ this.lastCube = null;
7527
+ this.pmrem = null;
7528
+ }
7529
+ }
6881
7530
  function getAssetBaseUrl(url) {
6882
7531
  if (!url) return void 0;
6883
7532
  try {
@@ -11450,7 +12099,7 @@ const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePropert
11450
12099
  exports.GaussianSplatRenderer = GaussianSplatRenderer;
11451
12100
  exports.RadStreamScheduler = RadStreamScheduler;
11452
12101
  exports.SogStreamScheduler = SogStreamScheduler;
11453
- exports.SplatAccumulator = SplatAccumulator;
12102
+ exports.SplatCapture = SplatCapture;
11454
12103
  exports.SplatEdit = SplatEdit;
11455
12104
  exports.SplatEditRgbaBlendMode = SplatEditRgbaBlendMode;
11456
12105
  exports.SplatEditSdf = SplatEditSdf;