gaussian-splat-lite 1.4.1 → 1.4.2
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 +9 -1
- package/dist/gaussian-splat-lite.cjs +44 -129
- package/dist/gaussian-splat-lite.cjs.map +1 -1
- package/dist/gaussian-splat-lite.min.cjs +62 -150
- package/dist/gaussian-splat-lite.min.cjs.map +1 -1
- package/dist/gaussian-splat-lite.module.js +44 -129
- package/dist/gaussian-splat-lite.module.js.map +1 -1
- package/dist/gaussian-splat-lite.module.min.js +497 -590
- package/dist/gaussian-splat-lite.module.min.js.map +1 -1
- package/dist/types/addons/neuralDenoiseWeights.d.cts +3 -21
- package/dist/types/addons/neuralDenoiseWeights.d.ts +3 -21
- package/dist/types/rendering/GaussianSplatRenderer.d.cts +1 -1
- package/dist/types/rendering/GaussianSplatRenderer.d.ts +1 -1
- package/dist/types/rendering/SplatGeometry.d.cts +2 -2
- package/dist/types/rendering/SplatGeometry.d.ts +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,13 @@ and this project follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.4.2] - 2026-10-24
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Stochastic rendering now uses one triangle per Splat instead of a quad, reducing vertex and triangle counts on WebGL, WebGPU, and the WebGL2 fallback.
|
|
15
|
+
- Neural denoising's `"quality"` mode no longer develops a worm-like pattern on fine detail during slow camera movement; detail there is slightly softer.
|
|
16
|
+
|
|
10
17
|
## [1.4.1] - 2026-10-24
|
|
11
18
|
|
|
12
19
|
### Added
|
|
@@ -653,7 +660,8 @@ and this project follows [Semantic Versioning](https://semver.org/).
|
|
|
653
660
|
|
|
654
661
|
- Initial public release of the Three.js Gaussian Splatting renderer.
|
|
655
662
|
|
|
656
|
-
[Unreleased]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.4.
|
|
663
|
+
[Unreleased]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.4.2...HEAD
|
|
664
|
+
[1.4.2]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.4.1...v1.4.2
|
|
657
665
|
[1.4.1]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.4.0...v1.4.1
|
|
658
666
|
[1.4.0]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.3.2...v1.4.0
|
|
659
667
|
[1.3.2]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.3.1...v1.3.2
|
|
@@ -3914,19 +3914,22 @@ function hasViewDependentColor(mapping) {
|
|
|
3914
3914
|
}
|
|
3915
3915
|
const SPLATS_PER_INSTANCE = 128;
|
|
3916
3916
|
class SplatGeometry extends THREE__namespace.InstancedBufferGeometry {
|
|
3917
|
-
constructor() {
|
|
3917
|
+
constructor(triangle = false) {
|
|
3918
3918
|
super();
|
|
3919
|
-
const
|
|
3920
|
-
const
|
|
3919
|
+
const corners = triangle ? TRIANGLE_CORNERS : QUAD_CORNERS;
|
|
3920
|
+
const order = triangle ? TRIANGLE_INDICES : QUAD_INDICES;
|
|
3921
|
+
const vertexCount = corners.length / 2;
|
|
3922
|
+
const vertices = new Float32Array(SPLATS_PER_INSTANCE * vertexCount * 3);
|
|
3923
|
+
const indices = new Uint16Array(SPLATS_PER_INSTANCE * order.length);
|
|
3921
3924
|
for (let splat = 0; splat < SPLATS_PER_INSTANCE; splat++) {
|
|
3922
|
-
for (let vertex = 0; vertex <
|
|
3923
|
-
const offset = splat *
|
|
3924
|
-
vertices[offset] =
|
|
3925
|
-
vertices[offset + 1] =
|
|
3925
|
+
for (let vertex = 0; vertex < vertexCount; vertex++) {
|
|
3926
|
+
const offset = (splat * vertexCount + vertex) * 3;
|
|
3927
|
+
vertices[offset] = corners[vertex * 2];
|
|
3928
|
+
vertices[offset + 1] = corners[vertex * 2 + 1];
|
|
3926
3929
|
vertices[offset + 2] = splat;
|
|
3927
3930
|
}
|
|
3928
|
-
for (let index2 = 0; index2 <
|
|
3929
|
-
indices[splat *
|
|
3931
|
+
for (let index2 = 0; index2 < order.length; index2++)
|
|
3932
|
+
indices[splat * order.length + index2] = splat * vertexCount + order[index2];
|
|
3930
3933
|
}
|
|
3931
3934
|
this.setAttribute("position", new THREE__namespace.BufferAttribute(vertices, 3));
|
|
3932
3935
|
this.setIndex(new THREE__namespace.BufferAttribute(indices, 1));
|
|
@@ -3935,21 +3938,17 @@ class SplatGeometry extends THREE__namespace.InstancedBufferGeometry {
|
|
|
3935
3938
|
this.instanceCount = Math.ceil(count / SPLATS_PER_INSTANCE);
|
|
3936
3939
|
}
|
|
3937
3940
|
}
|
|
3938
|
-
const
|
|
3941
|
+
const QUAD_CORNERS = new Float32Array([-1, -1, 1, -1, 1, 1, -1, 1]);
|
|
3942
|
+
const TRIANGLE_CORNERS = new Float32Array([
|
|
3943
|
+
-Math.sqrt(3),
|
|
3939
3944
|
-1,
|
|
3945
|
+
Math.sqrt(3),
|
|
3940
3946
|
-1,
|
|
3941
3947
|
0,
|
|
3942
|
-
|
|
3943
|
-
-1,
|
|
3944
|
-
0,
|
|
3945
|
-
1,
|
|
3946
|
-
1,
|
|
3947
|
-
0,
|
|
3948
|
-
-1,
|
|
3949
|
-
1,
|
|
3950
|
-
0
|
|
3948
|
+
2
|
|
3951
3949
|
]);
|
|
3952
3950
|
const QUAD_INDICES = new Uint16Array([0, 2, 1, 0, 3, 2]);
|
|
3951
|
+
const TRIANGLE_INDICES = new Uint16Array([0, 2, 1]);
|
|
3953
3952
|
const variants = /* @__PURE__ */ new WeakMap();
|
|
3954
3953
|
function registerStochasticSplatMaterial(material, createVariant) {
|
|
3955
3954
|
variants.set(material, createVariant);
|
|
@@ -5620,10 +5619,7 @@ class ProjectedSplats {
|
|
|
5620
5619
|
constructor(renderer, uniforms) {
|
|
5621
5620
|
this.renderer = renderer;
|
|
5622
5621
|
this.uniforms = uniforms;
|
|
5623
|
-
this.indirect = new webgpu.IndirectStorageBufferAttribute(
|
|
5624
|
-
new Uint32Array([SPLATS_PER_INSTANCE * 6, 0, 0, 0, 0]),
|
|
5625
|
-
1
|
|
5626
|
-
);
|
|
5622
|
+
this.indirect = new webgpu.IndirectStorageBufferAttribute(new Uint32Array(5), 1);
|
|
5627
5623
|
this.error = null;
|
|
5628
5624
|
this.cache = new ProjectionCache();
|
|
5629
5625
|
this.visibleCount = new webgpu.StorageBufferAttribute(new Uint32Array(1), 1);
|
|
@@ -5663,7 +5659,9 @@ class ProjectedSplats {
|
|
|
5663
5659
|
// RADIX_SORT_MODE_IDS value of the current key encoding.
|
|
5664
5660
|
sortMode: { value: 0 },
|
|
5665
5661
|
// NDC translation the draw applies to unjittered projections.
|
|
5666
|
-
projectionJitter: { value: new THREE__namespace.Vector2() }
|
|
5662
|
+
projectionJitter: { value: new THREE__namespace.Vector2() },
|
|
5663
|
+
// Indices per instance of the drawn geometry, quads or triangles.
|
|
5664
|
+
indexCount: { value: 0 }
|
|
5667
5665
|
};
|
|
5668
5666
|
const count = N__namespace.storage(this.visibleCount, "uint").setName("gslVisibleCount").toReadOnly().element(0);
|
|
5669
5667
|
this.sorter = new WebGPURadixSort(1, this.keys, {
|
|
@@ -5675,13 +5673,16 @@ class ProjectedSplats {
|
|
|
5675
5673
|
this.limits.maxBufferSize
|
|
5676
5674
|
)
|
|
5677
5675
|
});
|
|
5678
|
-
const
|
|
5676
|
+
const drawArgs = N__namespace.storage(this.indirect, "uint").setName(
|
|
5677
|
+
"gslDrawIndirect"
|
|
5678
|
+
);
|
|
5679
5679
|
const counter = N__namespace.storage(this.visibleCount, "uint").setName("gslVisibleCount").toAtomic();
|
|
5680
5680
|
this.resetCount = N__namespace.Fn(() => {
|
|
5681
5681
|
N__namespace.atomicStore(counter.element(0), N__namespace.uint(0));
|
|
5682
5682
|
})().compute(1, [1]).setName("Splat reset visible count");
|
|
5683
5683
|
this.finish = N__namespace.Fn(() => {
|
|
5684
|
-
|
|
5684
|
+
drawArgs.element(0).assign(uniformBinding(this.state, "indexCount", "uint"));
|
|
5685
|
+
drawArgs.element(1).assign(
|
|
5685
5686
|
count.add(SPLATS_PER_INSTANCE - 1).div(N__namespace.uint(SPLATS_PER_INSTANCE))
|
|
5686
5687
|
);
|
|
5687
5688
|
})().compute(1, [1]).setName("Splat visible draw arguments");
|
|
@@ -5933,6 +5934,7 @@ class ProjectedSplats {
|
|
|
5933
5934
|
const monoProjection = !multiView && unjittered && getProjectionJitter(camera.projectionMatrix, unjittered, jitter) ? unjittered : null;
|
|
5934
5935
|
geometry.setIndirect(this.indirect);
|
|
5935
5936
|
geometry.setSplatCount(Math.max(1, accumulator.numSplats));
|
|
5937
|
+
this.state.indexCount.value = geometry.index.count;
|
|
5936
5938
|
const inputs = this.pendingInputs;
|
|
5937
5939
|
let length = 0;
|
|
5938
5940
|
inputs[length++] = accumulator;
|
|
@@ -5941,6 +5943,7 @@ class ProjectedSplats {
|
|
|
5941
5943
|
inputs[length++] = accumulator.viewOrigin.y;
|
|
5942
5944
|
inputs[length++] = accumulator.viewOrigin.z;
|
|
5943
5945
|
inputs[length++] = cameras.length;
|
|
5946
|
+
inputs[length++] = geometry.index.count;
|
|
5944
5947
|
inputs[length++] = stochastic;
|
|
5945
5948
|
inputs[length++] = !stochastic && radial;
|
|
5946
5949
|
inputs[length++] = sortMode;
|
|
@@ -6127,9 +6130,11 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
|
|
|
6127
6130
|
depthTest: options.depthTest ?? true,
|
|
6128
6131
|
depthWrite: options.depthWrite ?? false
|
|
6129
6132
|
});
|
|
6130
|
-
const
|
|
6133
|
+
const geometries = [new SplatGeometry(), new SplatGeometry(true)];
|
|
6134
|
+
const geometry = geometries[Number(stochastic)];
|
|
6131
6135
|
const material = backend.selectMaterial(stochastic);
|
|
6132
6136
|
super(geometry, material);
|
|
6137
|
+
this.geometries = geometries;
|
|
6133
6138
|
this.materialState = new MaterialState(material);
|
|
6134
6139
|
this.frameCallbacks = true;
|
|
6135
6140
|
this.renderSize = new THREE__namespace.Vector2();
|
|
@@ -6233,7 +6238,7 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
|
|
|
6233
6238
|
accumulator.dispose();
|
|
6234
6239
|
}
|
|
6235
6240
|
this.accumulators.length = 0;
|
|
6236
|
-
this.geometry.dispose();
|
|
6241
|
+
for (const geometry of this.geometries) geometry.dispose();
|
|
6237
6242
|
}
|
|
6238
6243
|
/** Native WebGPU sorts on the GPU before drawing; WebGL sorts in a worker. */
|
|
6239
6244
|
get synchronousSort() {
|
|
@@ -6285,13 +6290,11 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
|
|
|
6285
6290
|
const active = this.backend.kind === "webgpu" || this.display.numSplats === 0 ? this._stochastic : this.display.hasStochasticSeeds;
|
|
6286
6291
|
if (active === this.stochasticFrame) return;
|
|
6287
6292
|
this.stochasticFrame = active;
|
|
6288
|
-
this.
|
|
6289
|
-
this.
|
|
6290
|
-
|
|
6291
|
-
selectMaterial() {
|
|
6292
|
-
const material = this.backend.selectMaterial(this.stochasticFrame);
|
|
6293
|
-
this.applyMaterialState(this.stochasticFrame, material);
|
|
6293
|
+
this.geometry = this.geometries[Number(active)];
|
|
6294
|
+
const material = this.backend.selectMaterial(active);
|
|
6295
|
+
this.applyMaterialState(active, material);
|
|
6294
6296
|
this.material = material;
|
|
6297
|
+
this.setDirty();
|
|
6295
6298
|
}
|
|
6296
6299
|
applyMaterialState(stochasticActive, material = this.material) {
|
|
6297
6300
|
this.materialState.sync(material, this.material);
|
|
@@ -7215,74 +7218,6 @@ ${CATMULL_ROM_WEIGHTS}
|
|
|
7215
7218
|
fn catmullRom(image: texture_2d<f32>, uv: vec2f) -> vec4f {
|
|
7216
7219
|
return catmullRomSpan(image, uv).value;
|
|
7217
7220
|
}
|
|
7218
|
-
|
|
7219
|
-
// Lanczos-3 without its corner taps, from nine bilinear taps: the middle pair
|
|
7220
|
-
// of each axis is one tap and the other four pixels of an axis one each. A
|
|
7221
|
-
// history that is resampled once per frame loses fine detail with every pass,
|
|
7222
|
-
// and this kernel loses about half of what Catmull-Rom does. Its negative
|
|
7223
|
-
// lobes would ring over such a history, so the result is kept inside what the
|
|
7224
|
-
// four pixels around the position span. At a pixel's centre it returns the
|
|
7225
|
-
// pixel.
|
|
7226
|
-
struct LanczosWeights {
|
|
7227
|
-
low: vec3f,
|
|
7228
|
-
high: vec3f,
|
|
7229
|
-
};
|
|
7230
|
-
fn lanczosWeights(t: f32) -> LanczosWeights {
|
|
7231
|
-
// Integer-shifted sinc taps share their sine. Angle addition supplies the
|
|
7232
|
-
// other factor, reducing twelve sine evaluations per axis to three trig calls.
|
|
7233
|
-
let x: f32 = 3.14159265 * t;
|
|
7234
|
-
let s: f32 = sin(x / 3.0);
|
|
7235
|
-
let c: f32 = 0.866025404 * cos(x / 3.0);
|
|
7236
|
-
let scale: f32 = 0.303963551 * sin(x);
|
|
7237
|
-
let terms: vec3f = scale * vec3f(c - 0.5 * s, -c - 0.5 * s, s);
|
|
7238
|
-
let low: vec3f = vec3f(t + 2.0, t + 1.0, t);
|
|
7239
|
-
let high: vec3f = vec3f(t - 1.0, t - 2.0, t - 3.0);
|
|
7240
|
-
var w: LanczosWeights;
|
|
7241
|
-
w.low = terms / max(low * low, vec3f(1e-20));
|
|
7242
|
-
w.high = terms / max(high * high, vec3f(1e-20));
|
|
7243
|
-
if (abs(x) < 1e-5) { w.low.z = 1.0; }
|
|
7244
|
-
if (abs(3.14159265 * (t - 1.0)) < 1e-5) { w.high.x = 1.0; }
|
|
7245
|
-
return w;
|
|
7246
|
-
}
|
|
7247
|
-
struct LanczosKernel {
|
|
7248
|
-
base: vec2f,
|
|
7249
|
-
centre: vec2f,
|
|
7250
|
-
middle: f32,
|
|
7251
|
-
wx: vec4f,
|
|
7252
|
-
wy: vec4f,
|
|
7253
|
-
total: f32,
|
|
7254
|
-
}
|
|
7255
|
-
fn lanczosKernel(uv: vec2f) -> LanczosKernel {
|
|
7256
|
-
let position: vec2f = uv * P.size - 0.5;
|
|
7257
|
-
var k: LanczosKernel;
|
|
7258
|
-
k.base = floor(position);
|
|
7259
|
-
let t: vec2f = position - k.base;
|
|
7260
|
-
let x: LanczosWeights = lanczosWeights(t.x);
|
|
7261
|
-
let y: LanczosWeights = lanczosWeights(t.y);
|
|
7262
|
-
let middle: vec2f = vec2f(x.low.z + x.high.x, y.low.z + y.high.x);
|
|
7263
|
-
k.centre = (k.base + vec2f(x.high.x, y.high.x) / middle + 0.5) * P.invSize;
|
|
7264
|
-
k.middle = middle.x * middle.y;
|
|
7265
|
-
k.wx = vec4f(x.low.xy, x.high.yz) * middle.y;
|
|
7266
|
-
k.wy = vec4f(y.low.xy, y.high.yz) * middle.x;
|
|
7267
|
-
k.total = k.middle;
|
|
7268
|
-
for (var i: i32 = 0; i < 4; i++) { k.total += k.wx[i] + k.wy[i]; }
|
|
7269
|
-
return k;
|
|
7270
|
-
}
|
|
7271
|
-
fn lanczosCross(image: texture_2d<f32>, k: LanczosKernel) -> vec4f {
|
|
7272
|
-
var sum: vec4f = textureSampleLevel(image, linearSampler, k.centre, 0.0) * k.middle;
|
|
7273
|
-
const offsets = array<f32, 4>(-1.5, -0.5, 2.5, 3.5);
|
|
7274
|
-
for (var i: i32 = 0; i < 4; i++) {
|
|
7275
|
-
let along: vec2f = (k.base + offsets[i]) * P.invSize;
|
|
7276
|
-
sum += textureSampleLevel(image, linearSampler, vec2f(along.x, k.centre.y), 0.0) * k.wx[i];
|
|
7277
|
-
sum += textureSampleLevel(image, linearSampler, vec2f(k.centre.x, along.y), 0.0) * k.wy[i];
|
|
7278
|
-
}
|
|
7279
|
-
let p: vec2i = vec2i(k.base);
|
|
7280
|
-
let a: vec4f = textureLoad(image, bounded(p), 0);
|
|
7281
|
-
let b: vec4f = textureLoad(image, bounded(p + vec2i(1, 0)), 0);
|
|
7282
|
-
let c: vec4f = textureLoad(image, bounded(p + vec2i(0, 1)), 0);
|
|
7283
|
-
let d: vec4f = textureLoad(image, bounded(p + vec2i(1, 1)), 0);
|
|
7284
|
-
return clamp(sum / k.total, min(min(a, b), min(c, d)), max(max(a, b), max(c, d)));
|
|
7285
|
-
}
|
|
7286
7221
|
`
|
|
7287
7222
|
);
|
|
7288
7223
|
const TAPS = (
|
|
@@ -7636,7 +7571,7 @@ fn mean3x3(image: texture_2d<f32>, position: vec2f, offset: vec2f) -> vec4f {
|
|
|
7636
7571
|
const QUALITY_RESAMPLING = (
|
|
7637
7572
|
/* wgsl */
|
|
7638
7573
|
`
|
|
7639
|
-
// Full Catmull-Rom for
|
|
7574
|
+
// Full Catmull-Rom for Quality histories, retaining all four corner taps.
|
|
7640
7575
|
fn catmullRomNine(image: texture_2d<f32>, uv: vec2f) -> vec4f {
|
|
7641
7576
|
let position: vec2f = uv * P.size;
|
|
7642
7577
|
${CATMULL_ROM_WEIGHTS}
|
|
@@ -7954,7 +7889,7 @@ ${groups.map(
|
|
|
7954
7889
|
// half-resolution result.
|
|
7955
7890
|
@group(0) @binding(11) var wideTexture: texture_2d<f32>;
|
|
7956
7891
|
@group(0) @binding(12) var depthPrevious: texture_2d<f32>;
|
|
7957
|
-
${
|
|
7892
|
+
${weights.compact ? MEAN_3X3 : QUALITY_RESAMPLING}${BLOCKS}
|
|
7958
7893
|
// The frame's existing 4x4 mean, used only for rejected surfaces.
|
|
7959
7894
|
@group(0) @binding(13) var rejectedTexture: texture_2d<f32>;
|
|
7960
7895
|
${COARSE}
|
|
@@ -8002,13 +7937,10 @@ ${weights.compact ? "" : " let d: vec4f = textureLoad(filteredTexture, p, 0);"}
|
|
|
8002
7937
|
var same: f32 = 1.0;
|
|
8003
7938
|
let sparse: f32 = blockSparse(p);
|
|
8004
7939
|
var sparseMotion: f32 = 0.0;
|
|
8005
|
-
${weights.movingMean ? ` // Where the history was, for the accumulated path's mean below.
|
|
8006
|
-
var historyUv: vec2f = position.xy * P.invSize;` : ""}
|
|
8007
7940
|
if (P.historyValid > 0.5) {
|
|
8008
7941
|
let z: f32 = blockDepth(p);
|
|
8009
7942
|
let h: History = reproject(position.xy, z);
|
|
8010
7943
|
valid = h.valid;
|
|
8011
|
-
${weights.movingMean ? " historyUv = h.uv;" : ""}
|
|
8012
7944
|
// The pixel's own motion, or what a turn of the camera sweeps over half
|
|
8013
7945
|
// the view, whichever is more: at the centre of a turn the first is
|
|
8014
7946
|
// small while every surface there shows a new face. An orthographic
|
|
@@ -8039,9 +7971,9 @@ ${weights.movingMean ? " historyUv = h.uv;" : ""}
|
|
|
8039
7971
|
${weights.compact ? ` // The accumulated history keeps many frames while the camera moves
|
|
8040
7972
|
// slowly: read bilinearly, it lost detail with every one of them.
|
|
8041
7973
|
sr = max(catmullRom(previousAccumulated, h.uv), vec4f(0.0));
|
|
8042
|
-
dr = max(textureSampleLevel(previousDenoised, linearSampler, h.uv, 0.0), vec4f(0.0));` : `
|
|
8043
|
-
//
|
|
8044
|
-
|
|
7974
|
+
dr = max(textureSampleLevel(previousDenoised, linearSampler, h.uv, 0.0), vec4f(0.0));` : ` // Full Catmull-Rom avoids the Lanczos cross kernel's frequency gain
|
|
7975
|
+
// when periodic surface detail is reprojected over many frames.
|
|
7976
|
+
sr = max(catmullRomNine(previousAccumulated, h.uv), vec4f(0.0));
|
|
8045
7977
|
dr = max(catmullRomNine(previousDenoised, h.uv), vec4f(0.0));`}
|
|
8046
7978
|
a = previousStatistics(h.uv);
|
|
8047
7979
|
}
|
|
@@ -8118,24 +8050,7 @@ ${weights.compact ? ` let source: vec4f = shifted;
|
|
|
8118
8050
|
let error: f32 = sqrt(max(y2 - ys * ys, 0.0) / (ns + 1e-3) + 1e-10);
|
|
8119
8051
|
let ramp: f32 = 1.0 - exp(-ns / ${float(weights.gateFrames)});
|
|
8120
8052
|
let keep: f32 = ramp * (1.0 - clamp(${float(weights.gamma)} * error, 0.0, 1.0)) * trust.w;
|
|
8121
|
-
|
|
8122
|
-
// new view and its grain shows: it is shown from the 3x3 mean of its
|
|
8123
|
-
// history and this frame's filtered sample. What it keeps stays sharp, so
|
|
8124
|
-
// a camera that stops settles as before. This display-only smoothing does
|
|
8125
|
-
// not track moving objects: with a still camera they continue to use the
|
|
8126
|
-
// existing history rejection.
|
|
8127
|
-
var shownAccumulated: vec4f = s;
|
|
8128
|
-
if (P.historyValid > 0.5 && P.cameraStill < 0.5) {
|
|
8129
|
-
let held: vec4f = max(mean3x3(previousAccumulated, historyUv * P.size, vec2f(0.0)), vec4f(0.0));
|
|
8130
|
-
let moving: f32 = speed * speed / (speed * speed + ${float(weights.movingKnee * weights.movingKnee)});
|
|
8131
|
-
let smoothed: vec4f = (held * ws + filtered) / ds;
|
|
8132
|
-
// Only where the two differ by what the mean's noise explains: a larger
|
|
8133
|
-
// difference is detail, and stays.
|
|
8134
|
-
let differ: f32 = luma(abs(smoothed.rgb - s.rgb));
|
|
8135
|
-
let spread: f32 = ${float(weights.movingNoise)} * error * error;
|
|
8136
|
-
shownAccumulated = mix(s, smoothed, ${float(weights.movingMean)} * moving * spread / (spread + differ * differ + 1e-10));
|
|
8137
|
-
}
|
|
8138
|
-
let composite: vec4f = mix(dn, shownAccumulated, keep);` : " let composite: vec4f = mix(dn, s, keep);"}
|
|
8053
|
+
let composite: vec4f = mix(dn, s, keep);
|
|
8139
8054
|
// Stabilization: a predicted blend with the previous image, which may stay
|
|
8140
8055
|
// as far from this frame's result as half of what it spans around the
|
|
8141
8056
|
// pixel, plus a slack. Taking that span from this frame's result instead
|
|
@@ -10973,7 +10888,7 @@ const quality = {
|
|
|
10973
10888
|
}
|
|
10974
10889
|
};
|
|
10975
10890
|
const neuralDenoiseModels = {
|
|
10976
|
-
quality
|
|
10891
|
+
quality,
|
|
10977
10892
|
performance: { ...quality, compact: true, compactMix: [0.5, 0.75] }
|
|
10978
10893
|
};
|
|
10979
10894
|
class StochasticCoverage {
|