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
@@ -1,6 +1,6 @@
1
1
  import * as THREE from "three";
2
- import { Vector3, Vector2, Matrix4, ColorManagement, NeverDepth, AlwaysDepth, Color, NoToneMapping, ShaderMaterial, NoBlending, GLSL3, WebGLRenderTarget, DepthTexture, HalfFloatType, FloatType, WebGLCoordinateSystem, DefaultLoadingManager, Loader } from "three";
3
- import { NodeUpdateType, NodeMaterial, QuadMesh, StorageArrayTexture, IndirectStorageBufferAttribute, StorageBufferAttribute, TempNode, RenderTarget, RendererUtils } from "three/webgpu";
2
+ import { Vector3, Vector2, Matrix4, ColorManagement, NeverDepth, AlwaysDepth, Color, ShaderMaterial, NoBlending, GLSL3, NoToneMapping, SRGBTransfer, WebGLRenderTarget, DepthTexture, HalfFloatType, FloatType, WebGLCoordinateSystem, DefaultLoadingManager, Loader } from "three";
3
+ import { NodeUpdateType, NodeMaterial, QuadMesh, StorageArrayTexture, IndirectStorageBufferAttribute, StorageBufferAttribute, TempNode, RenderTarget, RendererUtils, CubeRenderTarget, PMREMGenerator } from "three/webgpu";
4
4
  import * as TSL from "three/tsl";
5
5
  import { FullScreenQuad, Pass } from "three/addons/postprocessing/Pass.js";
6
6
  function getTransferable(ctx) {
@@ -2001,9 +2001,9 @@ class SplatMesh extends THREE.Object3D {
2001
2001
  }
2002
2002
  return ((_a2 = this.splats) == null ? void 0 : _a2.getBoundingBox(centersOnly, target)) ?? target.makeEmpty();
2003
2003
  }
2004
- frameUpdate({ time, deltaTime, globalEdits }) {
2004
+ frameUpdate({ time, deltaTime, globalEdits }, callbacks = true) {
2005
2005
  var _a2, _b;
2006
- (_a2 = this.onFrame) == null ? void 0 : _a2.call(this, { mesh: this, time, deltaTime });
2006
+ if (callbacks) (_a2 = this.onFrame) == null ? void 0 : _a2.call(this, { mesh: this, time, deltaTime });
2007
2007
  const source = this.splats;
2008
2008
  if (!source) {
2009
2009
  return;
@@ -2175,7 +2175,7 @@ function isWebGPURenderer(renderer) {
2175
2175
  return "isWebGPURenderer" in renderer && renderer.isWebGPURenderer === true;
2176
2176
  }
2177
2177
  function usesNativeWebGPU(renderer) {
2178
- return isWebGPURenderer(renderer) && renderer.backend.isWebGPUBackend === true;
2178
+ return isWebGPURenderer(renderer) && renderer.coordinateSystem === THREE.WebGPUCoordinateSystem;
2179
2179
  }
2180
2180
  function getViews(camera) {
2181
2181
  const array = camera;
@@ -2249,7 +2249,9 @@ function textureBinding(uniforms, name, array = false) {
2249
2249
  placeholder.generateMipmaps = false;
2250
2250
  placeholder.needsUpdate = true;
2251
2251
  const getTexture = () => uniforms[name].value;
2252
- return uintTexture(placeholder).onObjectUpdate(getTexture);
2252
+ const binding = uintTexture(placeholder).onObjectUpdate(getTexture);
2253
+ binding.getUniformHash = () => binding.uuid;
2254
+ return binding;
2253
2255
  }
2254
2256
  function load2D(binding, coord) {
2255
2257
  const texel = binding.load(coord);
@@ -2260,6 +2262,7 @@ function loadArray(binding, coord) {
2260
2262
  return updateTextureLoad(binding, binding.load(coord.xy).depth(coord.z));
2261
2263
  }
2262
2264
  function updateTextureLoad(binding, texel) {
2265
+ texel.getUniformHash = (builder) => binding.getUniformHash(builder);
2263
2266
  texel.updateBeforeType = NodeUpdateType.OBJECT;
2264
2267
  texel.updateBefore = (frame) => {
2265
2268
  frame.updateNode(binding);
@@ -2895,7 +2898,7 @@ const emptyOrdering = new THREE.DataTexture(
2895
2898
  THREE.UnsignedIntType
2896
2899
  );
2897
2900
  emptyOrdering.needsUpdate = true;
2898
- const DEFAULT_MIN_ALPHA = 0.5 / 255;
2901
+ const DEFAULT_MIN_ALPHA = 1 / 255;
2899
2902
  function makeSplatUniforms() {
2900
2903
  const emptySplats = emptySplatTexture;
2901
2904
  const uniforms = {
@@ -2916,8 +2919,6 @@ function makeSplatUniforms() {
2916
2919
  maxStdDev: { value: 1 },
2917
2920
  // Minimum pixel radius for splat rendering
2918
2921
  minPixelRadius: { value: 1 },
2919
- // Maximum pixel radius for splat rendering
2920
- maxPixelRadius: { value: 256 },
2921
2922
  // Minimum alpha value for splat rendering
2922
2923
  minAlpha: { value: DEFAULT_MIN_ALPHA },
2923
2924
  // Add to projected 2D splat covariance diagonal (thickens and brightens)
@@ -2928,7 +2929,7 @@ function makeSplatUniforms() {
2928
2929
  clipXY: { value: 1.25 },
2929
2930
  // Debug renderSize scale factor
2930
2931
  focalAdjustment: { value: 2 },
2931
- // Whether to decode stored sRGB Splat colors before blending
2932
+ // Resolved from GaussianSplatRenderer.encodeLinear or the active blend space.
2932
2933
  encodeLinear: { value: false },
2933
2934
  // Exact draw count; the final instance may contain unused quads.
2934
2935
  splatCount: { value: 0 },
@@ -2985,7 +2986,7 @@ function makeGenerateUniforms() {
2985
2986
  }
2986
2987
  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}";
2987
2988
  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}";
2988
- 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}";
2989
+ 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}";
2989
2990
  let shaders = null;
2990
2991
  function getShaders() {
2991
2992
  if (!shaders) {
@@ -3357,7 +3358,8 @@ const _SplatAccumulator = class _SplatAccumulator {
3357
3358
  timer,
3358
3359
  camera,
3359
3360
  layerCamera = camera,
3360
- previous
3361
+ previous,
3362
+ frameCallbacks = true
3361
3363
  }) {
3362
3364
  const previousMapping = previous.mapping;
3363
3365
  const previousVersion = previous.version;
@@ -3386,12 +3388,13 @@ const _SplatAccumulator = class _SplatAccumulator {
3386
3388
  }
3387
3389
  if (!ancestor) globalEdits.add(node);
3388
3390
  });
3391
+ const frameContext = {
3392
+ time: this.time,
3393
+ deltaTime: this.deltaTime,
3394
+ globalEdits: Array.from(globalEdits)
3395
+ };
3389
3396
  for (const mesh of allMeshes) {
3390
- mesh.frameUpdate({
3391
- time: this.time,
3392
- deltaTime: this.deltaTime,
3393
- globalEdits: Array.from(globalEdits)
3394
- });
3397
+ mesh.frameUpdate(frameContext, frameCallbacks);
3395
3398
  }
3396
3399
  const visibleMeshes = [];
3397
3400
  scene.traverseVisible((node) => {
@@ -3583,7 +3586,6 @@ function createProjectionProgram(uniforms, view) {
3583
3586
  } = view;
3584
3587
  const maxStdDev = uniformBinding(uniforms, "maxStdDev", "float");
3585
3588
  const minPixelRadius = uniformBinding(uniforms, "minPixelRadius", "float");
3586
- const maxPixelRadius = uniformBinding(uniforms, "maxPixelRadius", "float");
3587
3589
  const minAlpha = uniformBinding(uniforms, "minAlpha", "float");
3588
3590
  const preBlurAmount = uniformBinding(uniforms, "preBlurAmount", "float");
3589
3591
  const blurAmount = uniformBinding(uniforms, "blurAmount", "float");
@@ -3698,7 +3700,7 @@ function createProjectionProgram(uniforms, view) {
3698
3700
  supportRadius.div(maximumSupportRadius),
3699
3701
  0
3700
3702
  );
3701
- const maxProjectedRadius = maxPixelRadius.mul(focalAdjustment);
3703
+ const maxProjectedRadius = renderSize.x.min(renderSize.y).mul(focalAdjustment);
3702
3704
  const fullScale1 = maxProjectedRadius.min(
3703
3705
  maximumSupportRadius.mul(eigen1.sqrt())
3704
3706
  );
@@ -5381,7 +5383,6 @@ class ProjectedSplats {
5381
5383
  sortMode,
5382
5384
  uniforms.maxStdDev.value,
5383
5385
  uniforms.minPixelRadius.value,
5384
- uniforms.maxPixelRadius.value,
5385
5386
  uniforms.minAlpha.value,
5386
5387
  uniforms.preBlurAmount.value,
5387
5388
  uniforms.blurAmount.value,
@@ -5523,13 +5524,13 @@ function createSplatBackend(renderer, uniforms, options) {
5523
5524
  }
5524
5525
  return usesNativeWebGPU(renderer) ? new WebGPUSplatBackend(renderer, uniforms, options) : new WebGLFallbackSplatBackend(renderer, uniforms, options);
5525
5526
  }
5526
- function configureSplatOutput(renderer, target, uniforms) {
5527
+ function configureSplatOutput(renderer, target, uniforms, encodeLinear) {
5527
5528
  let blendSpace = THREE.ColorManagement.workingColorSpace;
5528
5529
  if (!isWebGPURenderer(renderer)) {
5529
5530
  if (target === null) blendSpace = renderer.outputColorSpace;
5530
5531
  else if (isXRRenderTarget(target)) blendSpace = target.texture.colorSpace;
5531
5532
  }
5532
- uniforms.encodeLinear.value = blendSpace !== THREE.SRGBColorSpace;
5533
+ uniforms.encodeLinear.value = encodeLinear ?? blendSpace !== THREE.SRGBColorSpace;
5533
5534
  }
5534
5535
  const renderToViewScaleTmp = new THREE.Vector3();
5535
5536
  const renderToViewMatrixTmp = new THREE.Matrix4();
@@ -5571,6 +5572,7 @@ class GaussianSplatRenderer extends THREE.Mesh {
5571
5572
  const geometry = new SplatGeometry();
5572
5573
  const material = backend.selectMaterial(stochastic);
5573
5574
  super(geometry, material);
5575
+ this.frameCallbacks = true;
5574
5576
  this.renderSize = new THREE.Vector2();
5575
5577
  this.stochasticSample = 0;
5576
5578
  this.lastFrame = -1;
@@ -5605,6 +5607,7 @@ class GaussianSplatRenderer extends THREE.Mesh {
5605
5607
  this._depthTest = options.depthTest ?? true;
5606
5608
  this._depthWrite = options.depthWrite ?? false;
5607
5609
  this._premultipliedAlpha = premultipliedAlpha;
5610
+ this._encodeLinear = options.encodeLinear;
5608
5611
  this._transparent = options.transparent ?? true;
5609
5612
  this._stochastic = stochastic;
5610
5613
  this.autoAdvanceStochasticSample = options.autoAdvanceStochasticSample ?? true;
@@ -5618,7 +5621,6 @@ class GaussianSplatRenderer extends THREE.Mesh {
5618
5621
  this.preUpdate = options.preUpdate ?? true;
5619
5622
  this.maxStdDev = options.maxStdDev ?? Math.sqrt(8);
5620
5623
  this.minPixelRadius = options.minPixelRadius ?? 1;
5621
- this.maxPixelRadius = options.maxPixelRadius ?? 256;
5622
5624
  this.minAlpha = options.minAlpha ?? DEFAULT_MIN_ALPHA;
5623
5625
  this.preBlurAmount = options.preBlurAmount ?? 0.3;
5624
5626
  this.blurAmount = options.blurAmount ?? 0;
@@ -5840,7 +5842,6 @@ class GaussianSplatRenderer extends THREE.Mesh {
5840
5842
  this.uniforms.renderToViewScale.value = (renderToViewScaleTmp.x + renderToViewScaleTmp.y + renderToViewScaleTmp.z) / 3;
5841
5843
  this.uniforms.maxStdDev.value = this.maxStdDev;
5842
5844
  this.uniforms.minPixelRadius.value = this.minPixelRadius;
5843
- this.uniforms.maxPixelRadius.value = this.maxPixelRadius;
5844
5845
  this.uniforms.minAlpha.value = this.minAlpha;
5845
5846
  this.uniforms.preBlurAmount.value = this.preBlurAmount;
5846
5847
  this.uniforms.blurAmount.value = this.blurAmount;
@@ -5848,7 +5849,12 @@ class GaussianSplatRenderer extends THREE.Mesh {
5848
5849
  this.uniforms.focalAdjustment.value = this.focalAdjustment;
5849
5850
  this.uniforms.stochastic.value = this.stochasticFrame;
5850
5851
  this.uniforms.stochasticSample.value = this.stochasticSample >>> 0;
5851
- configureSplatOutput(renderer, currentRenderTarget, this.uniforms);
5852
+ configureSplatOutput(
5853
+ renderer,
5854
+ currentRenderTarget,
5855
+ this.uniforms,
5856
+ this._encodeLinear
5857
+ );
5852
5858
  if (this.backend.kind === "webgpu") {
5853
5859
  if (this.backend.sortError) throw this.backend.sortError;
5854
5860
  if (this.backend.precompile) {
@@ -5930,7 +5936,8 @@ class GaussianSplatRenderer extends THREE.Mesh {
5930
5936
  timer: this.timer,
5931
5937
  camera,
5932
5938
  layerCamera: request.camera,
5933
- previous: this.current
5939
+ previous: this.current,
5940
+ frameCallbacks: this.frameCallbacks
5934
5941
  });
5935
5942
  this.display = this.current;
5936
5943
  this.activeSplats = this.current.numSplats;
@@ -5997,7 +6004,8 @@ class GaussianSplatRenderer extends THREE.Mesh {
5997
6004
  timer: this.timer,
5998
6005
  camera,
5999
6006
  layerCamera: updateCamera,
6000
- previous: this.current
6007
+ previous: this.current,
6008
+ frameCallbacks: this.frameCallbacks
6001
6009
  });
6002
6010
  } catch (error) {
6003
6011
  this.releaseAccumulator(next);
@@ -6175,6 +6183,15 @@ class GaussianSplatRenderer extends THREE.Mesh {
6175
6183
  this.sorting = false;
6176
6184
  }
6177
6185
  }
6186
+ /** Decode sRGB before blending; undefined follows the active blend space. */
6187
+ get encodeLinear() {
6188
+ return this._encodeLinear;
6189
+ }
6190
+ set encodeLinear(value) {
6191
+ if (value === this._encodeLinear) return;
6192
+ this._encodeLinear = value;
6193
+ this.setDirty();
6194
+ }
6178
6195
  get premultipliedAlpha() {
6179
6196
  return this._premultipliedAlpha;
6180
6197
  }
@@ -6423,23 +6440,18 @@ class TAAPass extends Pass {
6423
6440
  this.edgeDepthDiff = 1e-3;
6424
6441
  this.maxMotionLength = 128;
6425
6442
  this.useSubpixelCorrection = true;
6426
- this.accumulateInOutputSpace = true;
6427
6443
  this._source = Object.assign(makeTarget("TAAPass.scene"), {
6428
- isXRRenderTarget: false
6444
+ isXRRenderTarget: true
6429
6445
  });
6430
6446
  this._history = [
6431
6447
  makeTarget("TAAPass.history0"),
6432
6448
  makeTarget("TAAPass.history1")
6433
6449
  ];
6434
- this._size = new Vector2();
6435
6450
  this._clearColor = new Color();
6436
6451
  this._state = createTAAState(
6437
6452
  (value) => ({ value }),
6438
6453
  [this._source, ...this._history]
6439
6454
  );
6440
- this._workingColorSpace = ColorManagement.workingColorSpace;
6441
- this._toneMapping = NoToneMapping;
6442
- this._toneMappingExposure = 1;
6443
6455
  this._uniforms = {
6444
6456
  source: { value: this._source.texture },
6445
6457
  sourceDepth: { value: this._source.depthTexture },
@@ -6460,15 +6472,23 @@ class TAAPass extends Pass {
6460
6472
  });
6461
6473
  this._copyMaterial = new ShaderMaterial({
6462
6474
  name: "TAAPass.copy",
6463
- uniforms: { source: { value: null } },
6475
+ uniforms: { source: { value: null }, decode: { value: false } },
6464
6476
  vertexShader,
6477
+ // Inverts OutputPass's transfer, so the two round-trip at any alpha.
6465
6478
  fragmentShader: (
6466
6479
  /* glsl */
6467
6480
  `
6468
6481
  uniform sampler2D source;
6482
+ uniform bool decode;
6469
6483
  in vec2 vUv;
6470
6484
  out vec4 fragColor;
6471
- void main() { fragColor = texture(source, vUv); }
6485
+ void main() {
6486
+ fragColor = texture(source, vUv);
6487
+ if (decode) {
6488
+ vec4 color = vec4(max(fragColor.rgb, 0.0), 1.0);
6489
+ fragColor.rgb = sRGBTransferEOTF(color).rgb;
6490
+ }
6491
+ }
6472
6492
  `
6473
6493
  ),
6474
6494
  glslVersion: GLSL3,
@@ -6495,33 +6515,23 @@ class TAAPass extends Pass {
6495
6515
  reset() {
6496
6516
  this._state.reset();
6497
6517
  }
6498
- /** Render the scene and TAA to the canvas, a target, or the composer's buffer. */
6499
- render(renderer, writeBuffer = null) {
6518
+ /** Render the scene and resolve TAA into the composer's working-space buffer. */
6519
+ render(renderer, writeBuffer) {
6500
6520
  const { camera, _state: state, _uniforms: uniforms } = this;
6501
- if (writeBuffer) {
6502
- this.setSize(writeBuffer.width, writeBuffer.height);
6503
- } else {
6504
- renderer.getDrawingBufferSize(this._size);
6505
- this.setSize(this._size.x, this._size.y);
6506
- }
6507
- const workingColorSpace = ColorManagement.workingColorSpace;
6508
- const colorSpace = this.accumulateInOutputSpace ? renderer.outputColorSpace : workingColorSpace;
6509
- const toneMapping = this.accumulateInOutputSpace ? renderer.toneMapping : NoToneMapping;
6510
- const exposure = toneMapping === NoToneMapping ? 1 : renderer.toneMappingExposure;
6511
- if (this._source.texture.colorSpace !== colorSpace || this._source.isXRRenderTarget !== this.accumulateInOutputSpace || this._workingColorSpace !== workingColorSpace || this._toneMapping !== toneMapping || this._toneMappingExposure !== exposure) {
6521
+ this.setSize(writeBuffer.width, writeBuffer.height);
6522
+ const colorSpace = renderer.outputColorSpace;
6523
+ if (this._source.texture.colorSpace !== colorSpace) {
6512
6524
  this._source.texture.colorSpace = colorSpace;
6513
- this._source.isXRRenderTarget = this.accumulateInOutputSpace;
6514
- this._workingColorSpace = workingColorSpace;
6515
- this._toneMapping = toneMapping;
6516
- this._toneMappingExposure = exposure;
6517
6525
  this.reset();
6518
6526
  }
6519
6527
  const previousTarget = renderer.getRenderTarget();
6520
6528
  const autoClear = renderer.autoClear;
6521
6529
  const xrEnabled = renderer.xr.enabled;
6530
+ const toneMapping = renderer.toneMapping;
6522
6531
  try {
6523
6532
  renderer.xr.enabled = false;
6524
6533
  renderer.autoClear = false;
6534
+ renderer.toneMapping = NoToneMapping;
6525
6535
  const reversedDepth = camera.reversedDepth;
6526
6536
  state.beginCapture(camera);
6527
6537
  try {
@@ -6551,6 +6561,7 @@ class TAAPass extends Pass {
6551
6561
  this._quad.material = this._resolveMaterial;
6552
6562
  this._quad.render(renderer);
6553
6563
  this._copyMaterial.uniforms.source.value = output.texture;
6564
+ this._copyMaterial.uniforms.decode.value = !this.renderToScreen && ColorManagement.getTransfer(colorSpace) === SRGBTransfer;
6554
6565
  renderer.setRenderTarget(this.renderToScreen ? null : writeBuffer);
6555
6566
  this._quad.material = this._copyMaterial;
6556
6567
  this._quad.render(renderer);
@@ -6559,6 +6570,7 @@ class TAAPass extends Pass {
6559
6570
  renderer.setRenderTarget(previousTarget);
6560
6571
  renderer.autoClear = autoClear;
6561
6572
  renderer.xr.enabled = xrEnabled;
6573
+ renderer.toneMapping = toneMapping;
6562
6574
  }
6563
6575
  }
6564
6576
  dispose() {
@@ -6859,6 +6871,643 @@ class TAANode extends TempNode {
6859
6871
  this.material.dispose();
6860
6872
  }
6861
6873
  }
6874
+ class WebGLCapture {
6875
+ constructor(renderer) {
6876
+ this.renderer = renderer;
6877
+ this.depthTexture = new THREE.DepthTexture(
6878
+ 1,
6879
+ 1,
6880
+ THREE.UnsignedIntType
6881
+ );
6882
+ this.target = Object.assign(
6883
+ new THREE.WebGLRenderTarget(1, 1, {
6884
+ type: THREE.UnsignedByteType,
6885
+ internalFormat: "RGBA8",
6886
+ generateMipmaps: false,
6887
+ depthTexture: this.depthTexture
6888
+ }),
6889
+ // Output/XR semantics enable per-material display conversion before blending.
6890
+ // Plain RGBA8 stores those output-space values without an sRGB attachment.
6891
+ { isXRRenderTarget: true }
6892
+ );
6893
+ this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
6894
+ this.material = new THREE.ShaderMaterial({
6895
+ uniforms: {
6896
+ colorBuffer: { value: this.target.texture },
6897
+ depthBuffer: { value: this.target.depthTexture },
6898
+ decodeSRGB: { value: true },
6899
+ encodeSRGB: { value: false }
6900
+ },
6901
+ vertexShader: `
6902
+ varying vec2 vUv;
6903
+ void main() {
6904
+ vUv = uv;
6905
+ gl_Position = vec4(position.xy, 0.0, 1.0);
6906
+ }
6907
+ `,
6908
+ fragmentShader: `
6909
+ uniform sampler2D colorBuffer;
6910
+ uniform sampler2D depthBuffer;
6911
+ uniform bool decodeSRGB;
6912
+ uniform bool encodeSRGB;
6913
+ varying vec2 vUv;
6914
+ void main() {
6915
+ vec4 color = texture2D(colorBuffer, vUv);
6916
+ color.rgb = color.a > 0.0 ? color.rgb / color.a : vec3(0.0);
6917
+ if (decodeSRGB) color = sRGBTransferEOTF(color);
6918
+ if (encodeSRGB) color = sRGBTransferOETF(color);
6919
+ color.rgb *= color.a;
6920
+ gl_FragColor = color;
6921
+ gl_FragDepth = texture2D(depthBuffer, vUv).r;
6922
+ }
6923
+ `,
6924
+ blending: THREE.NoBlending,
6925
+ depthFunc: THREE.AlwaysDepth,
6926
+ toneMapped: false
6927
+ });
6928
+ this.quad = new THREE.Mesh(
6929
+ new THREE.PlaneGeometry(2, 2),
6930
+ this.material
6931
+ );
6932
+ this.quad.frustumCulled = false;
6933
+ }
6934
+ render(scene, camera) {
6935
+ const { renderer, target, material } = this;
6936
+ const output = renderer.getRenderTarget();
6937
+ if (!output) throw new Error("WebGL capture requires a render target");
6938
+ const face = renderer.getActiveCubeFace();
6939
+ const mip = renderer.getActiveMipmapLevel();
6940
+ const type = output.texture.type === THREE.HalfFloatType || output.texture.type === THREE.FloatType ? output.texture.type : THREE.UnsignedByteType;
6941
+ if (target.texture.type !== type || target.depthBuffer !== output.depthBuffer || target.stencilBuffer !== output.stencilBuffer || target.samples !== output.samples) {
6942
+ target.dispose();
6943
+ target.texture.type = type;
6944
+ target.texture.internalFormat = type === THREE.UnsignedByteType ? "RGBA8" : null;
6945
+ target.depthBuffer = output.depthBuffer;
6946
+ target.stencilBuffer = output.stencilBuffer;
6947
+ this.depthTexture.format = output.stencilBuffer ? THREE.DepthStencilFormat : THREE.DepthFormat;
6948
+ this.depthTexture.type = output.stencilBuffer ? THREE.UnsignedInt248Type : THREE.UnsignedIntType;
6949
+ target.samples = output.samples;
6950
+ }
6951
+ target.setSize(output.width, output.height);
6952
+ target.texture.colorSpace = renderer.outputColorSpace;
6953
+ material.uniforms.decodeSRGB.value = target.texture.colorSpace === THREE.SRGBColorSpace;
6954
+ material.uniforms.encodeSRGB.value = output.texture.colorSpace === THREE.SRGBColorSpace && output.texture.type !== THREE.UnsignedByteType;
6955
+ material.depthTest = output.depthBuffer;
6956
+ material.depthWrite = output.depthBuffer;
6957
+ try {
6958
+ renderer.setRenderTarget(target);
6959
+ renderer.render(scene, camera);
6960
+ renderer.setRenderTarget(output, face, mip);
6961
+ material.depthFunc = renderer.state.buffers.depth.getReversed() ? THREE.NeverDepth : THREE.AlwaysDepth;
6962
+ renderer.render(this.quad, this.camera);
6963
+ } finally {
6964
+ renderer.setRenderTarget(output, face, mip);
6965
+ }
6966
+ }
6967
+ dispose() {
6968
+ this.target.dispose();
6969
+ this.quad.geometry.dispose();
6970
+ this.material.dispose();
6971
+ }
6972
+ }
6973
+ class WebGPUCapture {
6974
+ constructor(renderer) {
6975
+ this.renderer = renderer;
6976
+ this.depthTexture = new THREE.DepthTexture(1, 1, THREE.FloatType);
6977
+ this.target = new THREE.RenderTarget(1, 1, {
6978
+ generateMipmaps: false,
6979
+ depthTexture: this.depthTexture
6980
+ });
6981
+ this.material = new NodeMaterial();
6982
+ this.quad = new QuadMesh(this.material);
6983
+ this.workingColorSpace = "";
6984
+ this.outputColorSpace = "";
6985
+ this.storageColorSpace = "";
6986
+ this.toneMapping = null;
6987
+ this.material.blending = THREE.NoBlending;
6988
+ this.material.toneMapped = false;
6989
+ this.material.depthNode = N.texture(this.depthTexture).load(
6990
+ N.ivec2(N.screenCoordinate.xy)
6991
+ ).r;
6992
+ }
6993
+ render(scene, camera) {
6994
+ const { renderer, target, material } = this;
6995
+ const output = renderer.getRenderTarget();
6996
+ if (!output) throw new Error("WebGPU capture requires a render target");
6997
+ const face = renderer.getActiveCubeFace();
6998
+ const mip = renderer.getActiveMipmapLevel();
6999
+ const workingColorSpace = THREE.ColorManagement.workingColorSpace;
7000
+ const native = usesNativeWebGPU(renderer);
7001
+ const depthFormat = output.stencilBuffer ? THREE.DepthStencilFormat : THREE.DepthFormat;
7002
+ let depthType = !native && output.samples > 0 ? THREE.UnsignedIntType : THREE.FloatType;
7003
+ if (output.stencilBuffer) {
7004
+ const float = output.depthTexture ? output.depthTexture.type === THREE.FloatType : renderer.reversedDepthBuffer;
7005
+ depthType = native && float && renderer.hasFeature("depth32float-stencil8") ? THREE.FloatType : THREE.UnsignedInt248Type;
7006
+ }
7007
+ const type = output.texture.type === THREE.HalfFloatType || output.texture.type === THREE.FloatType ? output.texture.type : renderer.outputColorSpace !== workingColorSpace || renderer.toneMapping !== THREE.NoToneMapping ? renderer.getOutputBufferType() : THREE.UnsignedByteType;
7008
+ 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) {
7009
+ target.dispose();
7010
+ target.texture.type = type;
7011
+ this.depthTexture.type = depthType;
7012
+ this.depthTexture.format = depthFormat;
7013
+ target.depthBuffer = output.depthBuffer;
7014
+ target.stencilBuffer = output.stencilBuffer;
7015
+ target.depthTexture = output.depthBuffer ? this.depthTexture : null;
7016
+ if (target.samples !== output.samples) material.needsUpdate = true;
7017
+ target.samples = output.samples;
7018
+ }
7019
+ target.setSize(output.width, output.height);
7020
+ const storageColorSpace = output.texture.type === THREE.UnsignedByteType && output.texture.colorSpace === THREE.SRGBColorSpace ? THREE.LinearSRGBColorSpace : output.texture.colorSpace || THREE.LinearSRGBColorSpace;
7021
+ if (this.workingColorSpace !== workingColorSpace || this.outputColorSpace !== renderer.outputColorSpace || this.storageColorSpace !== storageColorSpace || this.toneMapping !== renderer.toneMapping) {
7022
+ const color = N.texture(target.texture).load(
7023
+ N.ivec2(N.screenCoordinate.xy)
7024
+ );
7025
+ const outputColor = N.renderOutput(
7026
+ color,
7027
+ renderer.toneMapping,
7028
+ renderer.outputColorSpace
7029
+ );
7030
+ material.fragmentNode = N.premultiplyAlpha(
7031
+ N.convertColorSpace(
7032
+ N.unpremultiplyAlpha(outputColor),
7033
+ renderer.outputColorSpace,
7034
+ storageColorSpace
7035
+ )
7036
+ );
7037
+ material.needsUpdate = true;
7038
+ this.workingColorSpace = workingColorSpace;
7039
+ this.outputColorSpace = renderer.outputColorSpace;
7040
+ this.storageColorSpace = storageColorSpace;
7041
+ this.toneMapping = renderer.toneMapping;
7042
+ }
7043
+ material.depthTest = output.depthBuffer;
7044
+ material.depthWrite = output.depthBuffer;
7045
+ material.depthFunc = renderer.reversedDepthBuffer ? THREE.NeverDepth : THREE.AlwaysDepth;
7046
+ try {
7047
+ renderer.setRenderTarget(target);
7048
+ renderer.render(scene, camera);
7049
+ renderer.setRenderTarget(output, face, mip);
7050
+ this.quad.render(renderer);
7051
+ } finally {
7052
+ renderer.setRenderTarget(output, face, mip);
7053
+ }
7054
+ }
7055
+ dispose() {
7056
+ this.target.dispose();
7057
+ this.material.dispose();
7058
+ }
7059
+ }
7060
+ async function readPixels(renderer, target, pixels, face = 0) {
7061
+ if (target.texture.format !== THREE.RGBAFormat || target.texture.type !== THREE.UnsignedByteType) {
7062
+ throw new Error(
7063
+ "Splat capture readback requires an RGBA UnsignedByte target"
7064
+ );
7065
+ }
7066
+ const { width, height } = target;
7067
+ if (!isWebGPURenderer(renderer)) {
7068
+ await renderer.readRenderTargetPixelsAsync(
7069
+ target,
7070
+ 0,
7071
+ 0,
7072
+ width,
7073
+ height,
7074
+ pixels,
7075
+ face
7076
+ );
7077
+ return;
7078
+ }
7079
+ const readback = await renderer.readRenderTargetPixelsAsync(
7080
+ target,
7081
+ 0,
7082
+ 0,
7083
+ width,
7084
+ height,
7085
+ 0,
7086
+ face
7087
+ );
7088
+ const source = new Uint8Array(
7089
+ readback.buffer,
7090
+ readback.byteOffset,
7091
+ readback.byteLength
7092
+ );
7093
+ if (!usesNativeWebGPU(renderer)) {
7094
+ pixels.set(source);
7095
+ return;
7096
+ }
7097
+ const rowBytes = width * 4;
7098
+ const rowStride = Math.ceil(rowBytes / 256) * 256;
7099
+ for (let y = 0; y < height; y++) {
7100
+ const start = (height - y - 1) * rowStride;
7101
+ pixels.set(source.subarray(start, start + rowBytes), y * rowBytes);
7102
+ }
7103
+ }
7104
+ function downsamplePixels(source, width, height, superXY, output) {
7105
+ const subWidth = width / superXY;
7106
+ const subHeight = height / superXY;
7107
+ const samples = superXY * superXY;
7108
+ for (let y = 0; y < subHeight; y++) {
7109
+ for (let x = 0; x < subWidth; x++) {
7110
+ const outputIndex = (y * subWidth + x) * 4;
7111
+ for (let channel = 0; channel < 4; channel++) {
7112
+ let sum = 0;
7113
+ for (let sy = 0; sy < superXY; sy++) {
7114
+ const row = (y * superXY + sy) * width;
7115
+ for (let sx = 0; sx < superXY; sx++) {
7116
+ sum += source[(row + x * superXY + sx) * 4 + channel];
7117
+ }
7118
+ }
7119
+ output[outputIndex + channel] = Math.round(sum / samples);
7120
+ }
7121
+ }
7122
+ }
7123
+ }
7124
+ function withCaptureState(renderer, draw) {
7125
+ const target = renderer.getRenderTarget();
7126
+ const cubeFace = renderer.getActiveCubeFace();
7127
+ const mipmapLevel = renderer.getActiveMipmapLevel();
7128
+ const xrEnabled = renderer.xr.enabled;
7129
+ const autoClear = renderer.autoClear;
7130
+ const nodeRenderer = isWebGPURenderer(renderer) ? renderer : null;
7131
+ const mrt = (nodeRenderer == null ? void 0 : nodeRenderer.getMRT()) ?? null;
7132
+ renderer.xr.enabled = false;
7133
+ renderer.autoClear = true;
7134
+ nodeRenderer == null ? void 0 : nodeRenderer.setMRT(null);
7135
+ try {
7136
+ return draw();
7137
+ } finally {
7138
+ renderer.xr.enabled = xrEnabled;
7139
+ renderer.autoClear = autoClear;
7140
+ nodeRenderer == null ? void 0 : nodeRenderer.setMRT(mrt);
7141
+ setRendererRenderTarget(renderer, target, cubeFace, mipmapLevel);
7142
+ }
7143
+ }
7144
+ function captureSceneView(scene, hideObjects) {
7145
+ if (hideObjects.length === 0) return scene;
7146
+ const hidden = new Set(hideObjects);
7147
+ const view = new THREE.Scene();
7148
+ view.matrixWorldAutoUpdate = scene.matrixWorldAutoUpdate;
7149
+ view.updateMatrixWorld = (force) => scene.updateMatrixWorld(force);
7150
+ const filter = (callback) => (object) => {
7151
+ if (!(object instanceof SplatEdit)) {
7152
+ let ancestor = object;
7153
+ while (ancestor) {
7154
+ if (hidden.has(ancestor)) return;
7155
+ ancestor = ancestor.parent;
7156
+ }
7157
+ }
7158
+ callback(object);
7159
+ };
7160
+ view.traverse = (callback) => scene.traverse(filter(callback));
7161
+ view.traverseVisible = (callback) => scene.traverseVisible(filter(callback));
7162
+ return view;
7163
+ }
7164
+ class SplatCapture {
7165
+ constructor({ splatRenderer, target }) {
7166
+ this.disposed = false;
7167
+ this.pending = Promise.resolve();
7168
+ this.cubeRenders = /* @__PURE__ */ new Map();
7169
+ this.lastCube = null;
7170
+ this.pmrem = null;
7171
+ this.splatRenderer = splatRenderer;
7172
+ this.capturePass = isWebGPURenderer(this.renderer) ? new WebGPUCapture(this.renderer) : new WebGLCapture(this.renderer);
7173
+ this.superXY = (target == null ? void 0 : target.superXY) ?? 1;
7174
+ if (!Number.isInteger(this.superXY) || this.superXY < 1 || this.superXY > 4) {
7175
+ throw new Error("Splat capture superXY must be an integer from 1 to 4");
7176
+ }
7177
+ if (target) this.initializeTarget(target);
7178
+ this.captureRenderer = new GaussianSplatRenderer({
7179
+ renderer: this.renderer,
7180
+ timer: splatRenderer.timer,
7181
+ autoUpdate: false,
7182
+ stochastic: false
7183
+ });
7184
+ this.captureRenderer.frameCallbacks = false;
7185
+ }
7186
+ initializeTarget(target) {
7187
+ const {
7188
+ width,
7189
+ height,
7190
+ doubleBuffer,
7191
+ superXY: _superXY,
7192
+ ...options
7193
+ } = target;
7194
+ if (!Number.isInteger(width) || !Number.isInteger(height) || width < 1 || height < 1 || width * this.superXY > 8192 || height * this.superXY > 8192) {
7195
+ throw new Error(
7196
+ "Splat capture dimensions must be positive integers up to 8192 / superXY"
7197
+ );
7198
+ }
7199
+ const targetOptions = {
7200
+ format: THREE.RGBAFormat,
7201
+ type: THREE.UnsignedByteType,
7202
+ colorSpace: THREE.SRGBColorSpace,
7203
+ ...options,
7204
+ // Three.js attaches stencil only together with depth.
7205
+ stencilBuffer: options.depthBuffer !== false && this.stencilBuffer
7206
+ };
7207
+ const superWidth = width * this.superXY;
7208
+ const superHeight = height * this.superXY;
7209
+ this.target = isWebGPURenderer(this.renderer) ? new THREE.RenderTarget(superWidth, superHeight, targetOptions) : new THREE.WebGLRenderTarget(superWidth, superHeight, targetOptions);
7210
+ if (doubleBuffer) {
7211
+ this.backTarget = this.target.clone();
7212
+ }
7213
+ }
7214
+ get renderer() {
7215
+ return this.splatRenderer.renderer;
7216
+ }
7217
+ get stencilBuffer() {
7218
+ var _a2;
7219
+ const { renderer } = this;
7220
+ return isWebGPURenderer(renderer) ? renderer.stencil : ((_a2 = renderer.getContextAttributes()) == null ? void 0 : _a2.stencil) === true;
7221
+ }
7222
+ assertActive() {
7223
+ if (this.disposed) throw new Error("Splat capture is disposed");
7224
+ }
7225
+ enqueue(task) {
7226
+ const result = this.pending.then(task);
7227
+ this.pending = result.catch(() => {
7228
+ });
7229
+ return result;
7230
+ }
7231
+ async renderCapture({ scene, camera }, draw, radial = false, hideObjects = []) {
7232
+ this.assertActive();
7233
+ const { splatRenderer: source, captureRenderer } = this;
7234
+ Object.assign(captureRenderer, {
7235
+ maxStdDev: source.maxStdDev,
7236
+ minPixelRadius: source.minPixelRadius,
7237
+ minAlpha: source.minAlpha,
7238
+ preBlurAmount: source.preBlurAmount,
7239
+ blurAmount: source.blurAmount,
7240
+ clipXY: source.clipXY,
7241
+ focalAdjustment: source.focalAdjustment,
7242
+ fastSort: source.fastSort,
7243
+ premultipliedAlpha: source.premultipliedAlpha,
7244
+ transparent: source.transparent,
7245
+ depthTest: source.depthTest,
7246
+ depthWrite: source.depthWrite,
7247
+ encodeLinear: source.encodeLinear,
7248
+ sortRadial: radial || source.sortRadial
7249
+ });
7250
+ const sourceMaterial = source.material;
7251
+ const captureMaterial = captureRenderer.material;
7252
+ captureMaterial.stencilWrite = sourceMaterial.stencilWrite;
7253
+ captureMaterial.stencilWriteMask = sourceMaterial.stencilWriteMask;
7254
+ captureMaterial.stencilFunc = sourceMaterial.stencilFunc;
7255
+ captureMaterial.stencilRef = sourceMaterial.stencilRef;
7256
+ captureMaterial.stencilFuncMask = sourceMaterial.stencilFuncMask;
7257
+ captureMaterial.stencilFail = sourceMaterial.stencilFail;
7258
+ captureMaterial.stencilZFail = sourceMaterial.stencilZFail;
7259
+ captureMaterial.stencilZPass = sourceMaterial.stencilZPass;
7260
+ captureRenderer.layers.mask = camera.layers.mask;
7261
+ const view = { scene: captureSceneView(scene, hideObjects), camera };
7262
+ await captureRenderer.update(view);
7263
+ if (captureRenderer.display.mapping.some(
7264
+ ({ node, version }) => node.version !== version
7265
+ )) {
7266
+ await captureRenderer.update(view);
7267
+ }
7268
+ this.assertActive();
7269
+ const layers = /* @__PURE__ */ new Map();
7270
+ scene.traverse((object) => {
7271
+ if (object instanceof GaussianSplatRenderer) {
7272
+ layers.set(object.layers, object.layers.mask);
7273
+ }
7274
+ });
7275
+ const visibility = new Map(
7276
+ hideObjects.map((object) => [object, object.visible])
7277
+ );
7278
+ try {
7279
+ for (const layer of layers.keys()) layer.mask = 0;
7280
+ for (const object of visibility.keys()) object.visible = false;
7281
+ scene.add(captureRenderer);
7282
+ withCaptureState(this.renderer, draw);
7283
+ } finally {
7284
+ scene.remove(captureRenderer);
7285
+ for (const [layer, mask] of layers) layer.mask = mask;
7286
+ for (const [object, visible] of visibility) object.visible = visible;
7287
+ }
7288
+ }
7289
+ /** Snapshots the camera now; the capture may wait for earlier ones. */
7290
+ targetCapture({ scene, camera }) {
7291
+ this.assertActive();
7292
+ camera.updateWorldMatrix(true, false);
7293
+ const Camera = camera.constructor;
7294
+ const captureCamera = new Camera().copy(camera, false);
7295
+ captureCamera.matrixAutoUpdate = false;
7296
+ captureCamera.matrix.copy(camera.matrixWorld);
7297
+ return async () => {
7298
+ this.assertActive();
7299
+ const target = this.backTarget ?? this.target;
7300
+ if (!target)
7301
+ throw new Error("Initialize SplatCapture with target options");
7302
+ const stencilBuffer = target.depthBuffer && this.stencilBuffer;
7303
+ if (target.stencilBuffer !== stencilBuffer) {
7304
+ target.dispose();
7305
+ target.stencilBuffer = stencilBuffer;
7306
+ }
7307
+ await this.renderCapture({ scene, camera: captureCamera }, () => {
7308
+ setRendererRenderTarget(this.renderer, target);
7309
+ this.capturePass.render(scene, captureCamera);
7310
+ });
7311
+ if (target !== this.target) {
7312
+ [this.target, this.backTarget] = [this.backTarget, this.target];
7313
+ }
7314
+ return target;
7315
+ };
7316
+ }
7317
+ async renderTarget(view) {
7318
+ return this.enqueue(this.targetCapture(view));
7319
+ }
7320
+ /** Reuses the returned buffer; copy it if a later read must not overwrite it. */
7321
+ async readTarget() {
7322
+ var _a2, _b;
7323
+ this.assertActive();
7324
+ const target = this.target;
7325
+ if (!target) throw new Error("Initialize SplatCapture with target options");
7326
+ const { width, height } = target;
7327
+ if (width % this.superXY !== 0 || height % this.superXY !== 0) {
7328
+ throw new Error(
7329
+ "Splat capture target dimensions must be divisible by superXY"
7330
+ );
7331
+ }
7332
+ const byteSize = width * height * 4;
7333
+ if (((_a2 = this.superPixels) == null ? void 0 : _a2.length) !== byteSize) {
7334
+ this.superPixels = new Uint8Array(byteSize);
7335
+ }
7336
+ const pixels = this.superPixels;
7337
+ await readPixels(this.renderer, target, pixels);
7338
+ this.assertActive();
7339
+ if (this.superXY === 1) return pixels;
7340
+ const subSize = byteSize / (this.superXY * this.superXY);
7341
+ if (((_b = this.targetPixels) == null ? void 0 : _b.length) !== subSize) {
7342
+ this.targetPixels = new Uint8Array(subSize);
7343
+ }
7344
+ downsamplePixels(pixels, width, height, this.superXY, this.targetPixels);
7345
+ return this.targetPixels;
7346
+ }
7347
+ async renderReadTarget(view) {
7348
+ const capture = this.targetCapture(view);
7349
+ return this.enqueue(async () => {
7350
+ await capture();
7351
+ return this.readTarget();
7352
+ });
7353
+ }
7354
+ renderCube(scene, camera) {
7355
+ const { renderer } = this;
7356
+ camera.updateMatrixWorld();
7357
+ if (camera.coordinateSystem !== renderer.coordinateSystem) {
7358
+ camera.coordinateSystem = renderer.coordinateSystem;
7359
+ camera.updateCoordinateSystem();
7360
+ }
7361
+ const target = camera.renderTarget;
7362
+ const generateMipmaps = target.texture.generateMipmaps;
7363
+ target.texture.generateMipmaps = false;
7364
+ try {
7365
+ for (let face = 0; face < 6; face++) {
7366
+ if (face === 5) target.texture.generateMipmaps = generateMipmaps;
7367
+ setRendererRenderTarget(
7368
+ renderer,
7369
+ target,
7370
+ face,
7371
+ camera.activeMipmapLevel
7372
+ );
7373
+ this.capturePass.render(scene, camera.children[face]);
7374
+ }
7375
+ target.texture.needsPMREMUpdate = true;
7376
+ } finally {
7377
+ target.texture.generateMipmaps = generateMipmaps;
7378
+ }
7379
+ }
7380
+ /** Validates and snapshots the center now; the capture may wait for earlier ones. */
7381
+ cubeCapture({
7382
+ scene,
7383
+ worldCenter,
7384
+ size: size2 = 256,
7385
+ near = 0.1,
7386
+ far = 1e3,
7387
+ hideObjects = [],
7388
+ filter = false
7389
+ }) {
7390
+ this.assertActive();
7391
+ if (!Number.isInteger(size2) || size2 < 1 || size2 > 8192) {
7392
+ throw new Error(
7393
+ "Splat capture cube size must be a positive integer up to 8192"
7394
+ );
7395
+ }
7396
+ if (!(near > 0 && far > near && Number.isFinite(far))) {
7397
+ throw new Error("Splat capture cube requires 0 < near < far < Infinity");
7398
+ }
7399
+ const center = worldCenter.clone();
7400
+ return async () => {
7401
+ this.assertActive();
7402
+ const stencilBuffer = this.stencilBuffer;
7403
+ let cubeRender = this.cubeRenders.get(filter);
7404
+ if (!cubeRender || cubeRender.target.width !== size2 || cubeRender.target.stencilBuffer !== stencilBuffer || cubeRender.near !== near || cubeRender.far !== far) {
7405
+ cubeRender == null ? void 0 : cubeRender.target.dispose();
7406
+ if (this.lastCube === cubeRender) this.lastCube = null;
7407
+ const Target = isWebGPURenderer(this.renderer) ? CubeRenderTarget : THREE.WebGLCubeRenderTarget;
7408
+ const target2 = new Target(size2, {
7409
+ format: THREE.RGBAFormat,
7410
+ type: THREE.UnsignedByteType,
7411
+ stencilBuffer,
7412
+ generateMipmaps: filter,
7413
+ minFilter: filter ? THREE.LinearMipmapLinearFilter : THREE.LinearFilter,
7414
+ colorSpace: filter ? THREE.LinearSRGBColorSpace : THREE.SRGBColorSpace
7415
+ });
7416
+ const camera2 = new THREE.CubeCamera(near, far, target2);
7417
+ cubeRender = { target: target2, camera: camera2, near, far };
7418
+ this.cubeRenders.set(filter, cubeRender);
7419
+ }
7420
+ const { target, camera } = cubeRender;
7421
+ camera.position.copy(center);
7422
+ const updateCamera = new THREE.PerspectiveCamera(90, 1, near, far);
7423
+ updateCamera.position.copy(center);
7424
+ await this.renderCapture(
7425
+ { scene, camera: updateCamera },
7426
+ () => this.renderCube(scene, camera),
7427
+ true,
7428
+ hideObjects
7429
+ );
7430
+ this.lastCube = cubeRender;
7431
+ return target.texture;
7432
+ };
7433
+ }
7434
+ async renderCubeMap(options) {
7435
+ return this.enqueue(this.cubeCapture(options));
7436
+ }
7437
+ /** Faces of the latest cube capture, in the WebGL layout on every backend. */
7438
+ async readCubeTargets() {
7439
+ return this.enqueue(async () => {
7440
+ this.assertActive();
7441
+ if (!this.lastCube)
7442
+ throw new Error("Render a cube map before reading its faces");
7443
+ const { target } = this.lastCube;
7444
+ const native = usesNativeWebGPU(this.renderer);
7445
+ const faces = [];
7446
+ for (let face = 0; face < 6; face++) {
7447
+ const pixels = new Uint8Array(target.width * target.height * 4);
7448
+ const source = native && face < 2 ? 1 - face : face;
7449
+ await readPixels(this.renderer, target, pixels, source);
7450
+ if (native) new Uint32Array(pixels.buffer).reverse();
7451
+ faces.push(pixels);
7452
+ }
7453
+ this.assertActive();
7454
+ return faces;
7455
+ });
7456
+ }
7457
+ /** The caller owns the result; texture.dispose() also releases its render target. */
7458
+ async renderEnvMap(options) {
7459
+ const capture = this.cubeCapture({ ...options, filter: true });
7460
+ return this.enqueue(async () => {
7461
+ const cube = await capture();
7462
+ this.assertActive();
7463
+ this.pmrem ?? (this.pmrem = isWebGPURenderer(this.renderer) ? new PMREMGenerator(this.renderer) : new THREE.PMREMGenerator(this.renderer));
7464
+ const pmrem = this.pmrem;
7465
+ const target = withCaptureState(
7466
+ this.renderer,
7467
+ () => pmrem.fromCubemap(cube)
7468
+ );
7469
+ const disposeTarget = () => {
7470
+ target.texture.removeEventListener("dispose", disposeTarget);
7471
+ target.dispose();
7472
+ };
7473
+ target.texture.addEventListener("dispose", disposeTarget);
7474
+ return target.texture;
7475
+ });
7476
+ }
7477
+ recurseSetEnvMap(root, envMap) {
7478
+ root.traverse((node) => {
7479
+ var _a2;
7480
+ if (!(node instanceof THREE.Mesh)) return;
7481
+ const materials = Array.isArray(node.material) ? node.material : [node.material];
7482
+ for (const material of materials) {
7483
+ if (material instanceof THREE.MeshStandardMaterial) {
7484
+ if (((_a2 = material.envMap) == null ? void 0 : _a2.mapping) !== envMap.mapping) {
7485
+ material.needsUpdate = true;
7486
+ }
7487
+ material.envMap = envMap;
7488
+ }
7489
+ }
7490
+ });
7491
+ }
7492
+ dispose() {
7493
+ var _a2, _b, _c;
7494
+ if (this.disposed) return;
7495
+ this.disposed = true;
7496
+ this.captureRenderer.dispose();
7497
+ this.capturePass.dispose();
7498
+ (_a2 = this.target) == null ? void 0 : _a2.dispose();
7499
+ (_b = this.backTarget) == null ? void 0 : _b.dispose();
7500
+ for (const { target } of this.cubeRenders.values()) target.dispose();
7501
+ (_c = this.pmrem) == null ? void 0 : _c.dispose();
7502
+ this.target = void 0;
7503
+ this.backTarget = void 0;
7504
+ this.superPixels = void 0;
7505
+ this.targetPixels = void 0;
7506
+ this.cubeRenders.clear();
7507
+ this.lastCube = null;
7508
+ this.pmrem = null;
7509
+ }
7510
+ }
6862
7511
  function getAssetBaseUrl(url) {
6863
7512
  if (!url) return void 0;
6864
7513
  try {
@@ -11432,7 +12081,7 @@ export {
11432
12081
  GaussianSplatRenderer,
11433
12082
  RadStreamScheduler,
11434
12083
  SogStreamScheduler,
11435
- SplatAccumulator,
12084
+ SplatCapture,
11436
12085
  SplatEdit,
11437
12086
  SplatEditRgbaBlendMode,
11438
12087
  SplatEditSdf,