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.
- package/CHANGELOG.md +25 -2
- package/README.md +22 -13
- package/dist/gaussian-splat-lite.cjs +700 -51
- package/dist/gaussian-splat-lite.cjs.map +1 -1
- package/dist/gaussian-splat-lite.min.cjs +40 -13
- package/dist/gaussian-splat-lite.min.cjs.map +1 -1
- package/dist/gaussian-splat-lite.module.js +702 -53
- package/dist/gaussian-splat-lite.module.js.map +1 -1
- package/dist/gaussian-splat-lite.module.min.js +2660 -2199
- package/dist/gaussian-splat-lite.module.min.js.map +1 -1
- package/dist/types/addons/TAAPass.d.cts +6 -15
- package/dist/types/addons/TAAPass.d.ts +6 -15
- package/dist/types/capture/SplatCapture.d.cts +66 -0
- package/dist/types/capture/SplatCapture.d.ts +66 -0
- package/dist/types/capture/WebGLCapture.d.cts +13 -0
- package/dist/types/capture/WebGLCapture.d.ts +13 -0
- package/dist/types/capture/WebGPUCapture.d.cts +17 -0
- package/dist/types/capture/WebGPUCapture.d.ts +17 -0
- package/dist/types/capture/pixels.d.cts +5 -0
- package/dist/types/capture/pixels.d.ts +5 -0
- package/dist/types/capture/rendererState.d.cts +3 -0
- package/dist/types/capture/rendererState.d.ts +3 -0
- package/dist/types/capture/sceneView.d.cts +3 -0
- package/dist/types/capture/sceneView.d.ts +3 -0
- package/dist/types/index.d.cts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/rendering/GaussianSplatRenderer.d.cts +13 -11
- package/dist/types/rendering/GaussianSplatRenderer.d.ts +13 -11
- package/dist/types/rendering/SplatAccumulator.d.cts +2 -1
- package/dist/types/rendering/SplatAccumulator.d.ts +2 -1
- package/dist/types/rendering/backend.d.cts +1 -1
- package/dist/types/rendering/backend.d.ts +1 -1
- package/dist/types/rendering/rendererUtils.d.cts +1 -5
- package/dist/types/rendering/rendererUtils.d.ts +1 -5
- package/dist/types/rendering/tsl/tslCompat.d.cts +1 -0
- package/dist/types/rendering/tsl/tslCompat.d.ts +1 -0
- package/dist/types/rendering/uniforms.d.cts +0 -3
- package/dist/types/rendering/uniforms.d.ts +0 -3
- package/dist/types/scene/SplatMesh.d.cts +1 -1
- package/dist/types/scene/SplatMesh.d.ts +1 -1
- package/docs/Architecture.md +48 -3
- package/docs/GaussianSplatRenderer.md +71 -55
- package/docs/PostDecode.md +52 -36
- package/docs/RadStreamScheduler.md +53 -41
- package/docs/SogStreamScheduler.md +49 -39
- package/docs/SplatCapture.md +147 -0
- package/docs/SplatEdit.md +33 -26
- package/docs/SplatFileType.md +11 -13
- package/docs/SplatLoader.md +19 -14
- package/docs/SplatMesh.md +69 -49
- package/docs/Splats.md +33 -26
- package/docs/StochasticRendering.md +27 -30
- package/docs/TAAPass.md +97 -73
- package/package.json +1 -1
- 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,
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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
|
-
//
|
|
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
|
|
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 =
|
|
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(
|
|
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:
|
|
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() {
|
|
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
|
|
6499
|
-
render(renderer, writeBuffer
|
|
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
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
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
|
-
|
|
12084
|
+
SplatCapture,
|
|
11436
12085
|
SplatEdit,
|
|
11437
12086
|
SplatEditRgbaBlendMode,
|
|
11438
12087
|
SplatEditSdf,
|