gaussian-splat-lite 1.4.0 → 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 +29 -2
- package/README.md +3 -6
- package/dist/gaussian-splat-lite.cjs +391 -884
- package/dist/gaussian-splat-lite.cjs.map +1 -1
- package/dist/gaussian-splat-lite.min.cjs +196 -354
- package/dist/gaussian-splat-lite.min.cjs.map +1 -1
- package/dist/gaussian-splat-lite.module.js +393 -886
- package/dist/gaussian-splat-lite.module.js.map +1 -1
- package/dist/gaussian-splat-lite.module.min.js +2502 -2887
- package/dist/gaussian-splat-lite.module.min.js.map +1 -1
- package/dist/types/addons/NeuralDenoiseNode.d.cts +1 -2
- package/dist/types/addons/NeuralDenoiseNode.d.ts +1 -2
- package/dist/types/addons/NeuralDenoiseXR.d.cts +31 -0
- package/dist/types/addons/NeuralDenoiseXR.d.ts +31 -0
- package/dist/types/addons/neuralDenoiseState.d.cts +22 -0
- package/dist/types/addons/neuralDenoiseState.d.ts +22 -0
- package/dist/types/addons/neuralDenoiseWeights.d.cts +23 -2
- package/dist/types/addons/neuralDenoiseWeights.d.ts +23 -2
- package/dist/types/index.d.cts +1 -2
- package/dist/types/index.d.ts +1 -2
- 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/docs/GaussianSplatRenderer.md +2 -2
- package/docs/NeuralDenoiseNode.md +32 -13
- package/docs/StochasticRendering.md +20 -42
- package/package.json +1 -1
- package/dist/types/addons/TAANode.d.cts +0 -49
- package/dist/types/addons/TAANode.d.ts +0 -49
- package/dist/types/addons/TAAPass.d.cts +0 -57
- package/dist/types/addons/TAAPass.d.ts +0 -57
- package/dist/types/addons/taaShared.d.cts +0 -74
- package/dist/types/addons/taaShared.d.ts +0 -74
- package/docs/TAAPass.md +0 -184
|
@@ -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
|
|
3939
|
-
|
|
3941
|
+
const QUAD_CORNERS = new Float32Array([-1, -1, 1, -1, 1, 1, -1, 1]);
|
|
3942
|
+
const TRIANGLE_CORNERS = new Float32Array([
|
|
3943
|
+
-Math.sqrt(3),
|
|
3940
3944
|
-1,
|
|
3941
|
-
|
|
3942
|
-
1,
|
|
3945
|
+
Math.sqrt(3),
|
|
3943
3946
|
-1,
|
|
3944
3947
|
0,
|
|
3945
|
-
|
|
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);
|
|
@@ -4757,37 +4756,16 @@ class WebGLSplatBackend {
|
|
|
4757
4756
|
this.stochasticMaterial.dispose();
|
|
4758
4757
|
}
|
|
4759
4758
|
}
|
|
4760
|
-
function
|
|
4761
|
-
const uniforms = {
|
|
4762
|
-
renderSize: makeUniform(new THREE.Vector2(1, 1)),
|
|
4763
|
-
projection: makeUniform(new THREE.Matrix4()),
|
|
4764
|
-
unjitteredProjection: makeUniform(new THREE.Matrix4()),
|
|
4765
|
-
previousProjection: makeUniform(new THREE.Matrix4()),
|
|
4766
|
-
viewToPreviousClip: makeUniform(new THREE.Matrix4()),
|
|
4767
|
-
previousViewToView: makeUniform(new THREE.Matrix4()),
|
|
4768
|
-
valid: makeUniform(false),
|
|
4769
|
-
reversed: makeUniform(false),
|
|
4770
|
-
logarithmic: makeUniform(false),
|
|
4771
|
-
// Log-depth parameters: x is the current frame, y is the previous frame.
|
|
4772
|
-
logFar: makeUniform(new THREE.Vector2()),
|
|
4773
|
-
logNear: makeUniform(new THREE.Vector2()),
|
|
4774
|
-
// Parameters are copied from the owner before each resolve.
|
|
4775
|
-
depthThreshold: makeUniform(0),
|
|
4776
|
-
edgeDepthDiff: makeUniform(0),
|
|
4777
|
-
maxMotionLength: makeUniform(0),
|
|
4778
|
-
useSubpixelCorrection: makeUniform(false),
|
|
4779
|
-
luminanceCoefficients: makeUniform(new THREE.Vector3())
|
|
4780
|
-
};
|
|
4759
|
+
function createNeuralDenoiseState(targets, margin) {
|
|
4781
4760
|
const baseProjection = new THREE.Matrix4();
|
|
4782
|
-
const previousViewProjection = new THREE.Matrix4();
|
|
4783
|
-
const previousWorld = new THREE.Matrix4();
|
|
4784
|
-
const viewMatrix = new THREE.Matrix4();
|
|
4785
4761
|
return {
|
|
4786
|
-
|
|
4762
|
+
size: new THREE.Vector2(1, 1),
|
|
4763
|
+
projection: new THREE.Matrix4(),
|
|
4764
|
+
valid: false,
|
|
4787
4765
|
historyIndex: 0,
|
|
4788
4766
|
jitterIndex: 0,
|
|
4789
4767
|
reset() {
|
|
4790
|
-
|
|
4768
|
+
this.valid = false;
|
|
4791
4769
|
this.jitterIndex = 0;
|
|
4792
4770
|
},
|
|
4793
4771
|
setSize(width, height) {
|
|
@@ -4797,10 +4775,9 @@ function createTAAState(makeUniform, targets, nodeLogDepth = false, margin = 0)
|
|
|
4797
4775
|
if (source.width === w + margin && source.height === h + margin) return;
|
|
4798
4776
|
source.setSize(w + margin, h + margin);
|
|
4799
4777
|
for (const target of others) target.setSize(w, h);
|
|
4800
|
-
|
|
4778
|
+
this.size.set(w, h);
|
|
4801
4779
|
this.reset();
|
|
4802
4780
|
},
|
|
4803
|
-
// Only the capture target takes stencil; history stores plain depth.
|
|
4804
4781
|
setStencil(stencil) {
|
|
4805
4782
|
const source = targets[0];
|
|
4806
4783
|
const depthTexture = source.depthTexture;
|
|
@@ -4814,50 +4791,18 @@ function createTAAState(makeUniform, targets, nodeLogDepth = false, margin = 0)
|
|
|
4814
4791
|
camera,
|
|
4815
4792
|
baseProjection,
|
|
4816
4793
|
this.jitterIndex,
|
|
4817
|
-
|
|
4818
|
-
|
|
4794
|
+
this.size.x,
|
|
4795
|
+
this.size.y
|
|
4819
4796
|
);
|
|
4820
4797
|
},
|
|
4821
4798
|
endCapture(camera) {
|
|
4822
|
-
|
|
4799
|
+
this.projection.copy(camera.projectionMatrix);
|
|
4823
4800
|
restoreProjection(camera, baseProjection);
|
|
4824
4801
|
},
|
|
4825
|
-
|
|
4826
|
-
const near = Math.max(camera.near, 1e-6);
|
|
4827
|
-
uniforms.logFar.value.x = Math.log2(
|
|
4828
|
-
nodeLogDepth ? camera.far / near : camera.far + 1
|
|
4829
|
-
);
|
|
4830
|
-
uniforms.logNear.value.x = near;
|
|
4831
|
-
uniforms.unjitteredProjection.value.copy(camera.projectionMatrix);
|
|
4832
|
-
uniforms.viewToPreviousClip.value.multiplyMatrices(
|
|
4833
|
-
previousViewProjection,
|
|
4834
|
-
camera.matrixWorld
|
|
4835
|
-
);
|
|
4836
|
-
uniforms.previousViewToView.value.multiplyMatrices(
|
|
4837
|
-
viewMatrix.copy(camera.matrixWorld).invert(),
|
|
4838
|
-
previousWorld
|
|
4839
|
-
);
|
|
4840
|
-
uniforms.depthThreshold.value = parameters2.depthThreshold;
|
|
4841
|
-
uniforms.edgeDepthDiff.value = parameters2.edgeDepthDiff;
|
|
4842
|
-
uniforms.maxMotionLength.value = parameters2.maxMotionLength;
|
|
4843
|
-
uniforms.useSubpixelCorrection.value = parameters2.useSubpixelCorrection;
|
|
4844
|
-
THREE.ColorManagement.getLuminanceCoefficients(
|
|
4845
|
-
uniforms.luminanceCoefficients.value
|
|
4846
|
-
);
|
|
4847
|
-
return uniforms.reversed.value ? THREE.NeverDepth : THREE.AlwaysDepth;
|
|
4848
|
-
},
|
|
4849
|
-
advance(camera) {
|
|
4850
|
-
previousViewProjection.multiplyMatrices(
|
|
4851
|
-
camera.projectionMatrix,
|
|
4852
|
-
viewMatrix
|
|
4853
|
-
);
|
|
4854
|
-
previousWorld.copy(camera.matrixWorld);
|
|
4855
|
-
uniforms.previousProjection.value.copy(uniforms.projection.value);
|
|
4856
|
-
uniforms.logFar.value.y = uniforms.logFar.value.x;
|
|
4857
|
-
uniforms.logNear.value.y = uniforms.logNear.value.x;
|
|
4802
|
+
advance() {
|
|
4858
4803
|
this.historyIndex = 1 - this.historyIndex;
|
|
4859
|
-
|
|
4860
|
-
this.jitterIndex = (this.jitterIndex + 1) %
|
|
4804
|
+
this.valid = true;
|
|
4805
|
+
this.jitterIndex = (this.jitterIndex + 1) % jitterOffsets.length;
|
|
4861
4806
|
}
|
|
4862
4807
|
};
|
|
4863
4808
|
}
|
|
@@ -4872,16 +4817,16 @@ function halton(index2, base) {
|
|
|
4872
4817
|
}
|
|
4873
4818
|
return result;
|
|
4874
4819
|
}
|
|
4875
|
-
const
|
|
4820
|
+
const jitterOffsets = Array.from({ length: 32 }, (_, i) => [
|
|
4876
4821
|
halton(i + 1, 2) - 0.5,
|
|
4877
4822
|
halton(i + 1, 3) - 0.5
|
|
4878
4823
|
]);
|
|
4879
4824
|
const projections = /* @__PURE__ */ new WeakMap();
|
|
4880
|
-
const
|
|
4825
|
+
const getUnjitteredProjection = (camera) => projections.get(camera);
|
|
4881
4826
|
function jitterProjection(camera, base, index2, width, height) {
|
|
4882
4827
|
base.copy(camera.projectionMatrix);
|
|
4883
4828
|
projections.set(camera, base);
|
|
4884
|
-
const [x, y] =
|
|
4829
|
+
const [x, y] = jitterOffsets[index2];
|
|
4885
4830
|
const elements = camera.projectionMatrix.elements;
|
|
4886
4831
|
for (let column = 0; column < 16; column += 4) {
|
|
4887
4832
|
elements[column] -= 2 * x / width * elements[column + 3];
|
|
@@ -5674,10 +5619,7 @@ class ProjectedSplats {
|
|
|
5674
5619
|
constructor(renderer, uniforms) {
|
|
5675
5620
|
this.renderer = renderer;
|
|
5676
5621
|
this.uniforms = uniforms;
|
|
5677
|
-
this.indirect = new webgpu.IndirectStorageBufferAttribute(
|
|
5678
|
-
new Uint32Array([SPLATS_PER_INSTANCE * 6, 0, 0, 0, 0]),
|
|
5679
|
-
1
|
|
5680
|
-
);
|
|
5622
|
+
this.indirect = new webgpu.IndirectStorageBufferAttribute(new Uint32Array(5), 1);
|
|
5681
5623
|
this.error = null;
|
|
5682
5624
|
this.cache = new ProjectionCache();
|
|
5683
5625
|
this.visibleCount = new webgpu.StorageBufferAttribute(new Uint32Array(1), 1);
|
|
@@ -5717,7 +5659,9 @@ class ProjectedSplats {
|
|
|
5717
5659
|
// RADIX_SORT_MODE_IDS value of the current key encoding.
|
|
5718
5660
|
sortMode: { value: 0 },
|
|
5719
5661
|
// NDC translation the draw applies to unjittered projections.
|
|
5720
|
-
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 }
|
|
5721
5665
|
};
|
|
5722
5666
|
const count = N__namespace.storage(this.visibleCount, "uint").setName("gslVisibleCount").toReadOnly().element(0);
|
|
5723
5667
|
this.sorter = new WebGPURadixSort(1, this.keys, {
|
|
@@ -5729,13 +5673,16 @@ class ProjectedSplats {
|
|
|
5729
5673
|
this.limits.maxBufferSize
|
|
5730
5674
|
)
|
|
5731
5675
|
});
|
|
5732
|
-
const
|
|
5676
|
+
const drawArgs = N__namespace.storage(this.indirect, "uint").setName(
|
|
5677
|
+
"gslDrawIndirect"
|
|
5678
|
+
);
|
|
5733
5679
|
const counter = N__namespace.storage(this.visibleCount, "uint").setName("gslVisibleCount").toAtomic();
|
|
5734
5680
|
this.resetCount = N__namespace.Fn(() => {
|
|
5735
5681
|
N__namespace.atomicStore(counter.element(0), N__namespace.uint(0));
|
|
5736
5682
|
})().compute(1, [1]).setName("Splat reset visible count");
|
|
5737
5683
|
this.finish = N__namespace.Fn(() => {
|
|
5738
|
-
|
|
5684
|
+
drawArgs.element(0).assign(uniformBinding(this.state, "indexCount", "uint"));
|
|
5685
|
+
drawArgs.element(1).assign(
|
|
5739
5686
|
count.add(SPLATS_PER_INSTANCE - 1).div(N__namespace.uint(SPLATS_PER_INSTANCE))
|
|
5740
5687
|
);
|
|
5741
5688
|
})().compute(1, [1]).setName("Splat visible draw arguments");
|
|
@@ -5983,10 +5930,11 @@ class ProjectedSplats {
|
|
|
5983
5930
|
this.state.sortRadial.value = radial;
|
|
5984
5931
|
const jitter = this.state.projectionJitter.value;
|
|
5985
5932
|
jitter.set(0, 0);
|
|
5986
|
-
const unjittered =
|
|
5933
|
+
const unjittered = getUnjitteredProjection(camera);
|
|
5987
5934
|
const monoProjection = !multiView && unjittered && getProjectionJitter(camera.projectionMatrix, unjittered, jitter) ? unjittered : null;
|
|
5988
5935
|
geometry.setIndirect(this.indirect);
|
|
5989
5936
|
geometry.setSplatCount(Math.max(1, accumulator.numSplats));
|
|
5937
|
+
this.state.indexCount.value = geometry.index.count;
|
|
5990
5938
|
const inputs = this.pendingInputs;
|
|
5991
5939
|
let length = 0;
|
|
5992
5940
|
inputs[length++] = accumulator;
|
|
@@ -5995,6 +5943,7 @@ class ProjectedSplats {
|
|
|
5995
5943
|
inputs[length++] = accumulator.viewOrigin.y;
|
|
5996
5944
|
inputs[length++] = accumulator.viewOrigin.z;
|
|
5997
5945
|
inputs[length++] = cameras.length;
|
|
5946
|
+
inputs[length++] = geometry.index.count;
|
|
5998
5947
|
inputs[length++] = stochastic;
|
|
5999
5948
|
inputs[length++] = !stochastic && radial;
|
|
6000
5949
|
inputs[length++] = sortMode;
|
|
@@ -6181,9 +6130,11 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
|
|
|
6181
6130
|
depthTest: options.depthTest ?? true,
|
|
6182
6131
|
depthWrite: options.depthWrite ?? false
|
|
6183
6132
|
});
|
|
6184
|
-
const
|
|
6133
|
+
const geometries = [new SplatGeometry(), new SplatGeometry(true)];
|
|
6134
|
+
const geometry = geometries[Number(stochastic)];
|
|
6185
6135
|
const material = backend.selectMaterial(stochastic);
|
|
6186
6136
|
super(geometry, material);
|
|
6137
|
+
this.geometries = geometries;
|
|
6187
6138
|
this.materialState = new MaterialState(material);
|
|
6188
6139
|
this.frameCallbacks = true;
|
|
6189
6140
|
this.renderSize = new THREE__namespace.Vector2();
|
|
@@ -6287,7 +6238,7 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
|
|
|
6287
6238
|
accumulator.dispose();
|
|
6288
6239
|
}
|
|
6289
6240
|
this.accumulators.length = 0;
|
|
6290
|
-
this.geometry.dispose();
|
|
6241
|
+
for (const geometry of this.geometries) geometry.dispose();
|
|
6291
6242
|
}
|
|
6292
6243
|
/** Native WebGPU sorts on the GPU before drawing; WebGL sorts in a worker. */
|
|
6293
6244
|
get synchronousSort() {
|
|
@@ -6339,13 +6290,11 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
|
|
|
6339
6290
|
const active = this.backend.kind === "webgpu" || this.display.numSplats === 0 ? this._stochastic : this.display.hasStochasticSeeds;
|
|
6340
6291
|
if (active === this.stochasticFrame) return;
|
|
6341
6292
|
this.stochasticFrame = active;
|
|
6342
|
-
this.
|
|
6343
|
-
this.
|
|
6344
|
-
|
|
6345
|
-
selectMaterial() {
|
|
6346
|
-
const material = this.backend.selectMaterial(this.stochasticFrame);
|
|
6347
|
-
this.applyMaterialState(this.stochasticFrame, material);
|
|
6293
|
+
this.geometry = this.geometries[Number(active)];
|
|
6294
|
+
const material = this.backend.selectMaterial(active);
|
|
6295
|
+
this.applyMaterialState(active, material);
|
|
6348
6296
|
this.material = material;
|
|
6297
|
+
this.setDirty();
|
|
6349
6298
|
}
|
|
6350
6299
|
applyMaterialState(stochasticActive, material = this.material) {
|
|
6351
6300
|
this.materialState.sync(material, this.material);
|
|
@@ -6896,676 +6845,6 @@ class GaussianSplatRenderer extends THREE__namespace.Mesh {
|
|
|
6896
6845
|
function isOpaqueMaterial(material) {
|
|
6897
6846
|
return material.transparent === false && material.blending === THREE__namespace.NormalBlending && material.alphaToCoverage === false;
|
|
6898
6847
|
}
|
|
6899
|
-
class StochasticCoverage {
|
|
6900
|
-
constructor() {
|
|
6901
|
-
__publicField(this, "materials", /* @__PURE__ */ new Map());
|
|
6902
|
-
}
|
|
6903
|
-
material(source, renderer) {
|
|
6904
|
-
let entry = this.materials.get(source);
|
|
6905
|
-
if (!entry) {
|
|
6906
|
-
const variant = createStochasticSplatVariant(source, renderer);
|
|
6907
|
-
if (!variant) return source;
|
|
6908
|
-
const dispose = () => {
|
|
6909
|
-
source.removeEventListener("dispose", dispose);
|
|
6910
|
-
variant.dispose();
|
|
6911
|
-
this.materials.delete(source);
|
|
6912
|
-
};
|
|
6913
|
-
entry = { variant, state: new MaterialState(source), dispose };
|
|
6914
|
-
this.materials.set(source, entry);
|
|
6915
|
-
source.addEventListener("dispose", dispose);
|
|
6916
|
-
}
|
|
6917
|
-
entry.state.sync(entry.variant, source);
|
|
6918
|
-
return entry.variant;
|
|
6919
|
-
}
|
|
6920
|
-
render(renderer, scene, camera) {
|
|
6921
|
-
if (!usesNativeWebGPU(renderer) && !renderer.getContext().getExtension("OES_sample_variables"))
|
|
6922
|
-
throw new Error(
|
|
6923
|
-
"Stochastic rendering with MSAA requires OES_sample_variables on WebGL2; use msaa = false on this device"
|
|
6924
|
-
);
|
|
6925
|
-
const nodeRenderer = renderer.isWebGPURenderer === true;
|
|
6926
|
-
const previous = nodeRenderer ? renderer.getRenderObjectFunction() : renderer.renderBufferDirect;
|
|
6927
|
-
const draw = previous ?? renderer.renderObject;
|
|
6928
|
-
const materialIndex = nodeRenderer ? 4 : 3;
|
|
6929
|
-
const hook = (...args) => {
|
|
6930
|
-
args[materialIndex] = this.material(args[materialIndex], renderer);
|
|
6931
|
-
return draw.apply(renderer, args);
|
|
6932
|
-
};
|
|
6933
|
-
if (nodeRenderer) renderer.setRenderObjectFunction(hook);
|
|
6934
|
-
else renderer.renderBufferDirect = hook;
|
|
6935
|
-
try {
|
|
6936
|
-
renderer.render(scene, camera);
|
|
6937
|
-
} finally {
|
|
6938
|
-
if (nodeRenderer) renderer.setRenderObjectFunction(previous);
|
|
6939
|
-
else renderer.renderBufferDirect = previous;
|
|
6940
|
-
}
|
|
6941
|
-
}
|
|
6942
|
-
dispose() {
|
|
6943
|
-
for (const entry of this.materials.values()) entry.dispose();
|
|
6944
|
-
}
|
|
6945
|
-
}
|
|
6946
|
-
const vertexShader$1 = (
|
|
6947
|
-
/* glsl */
|
|
6948
|
-
`
|
|
6949
|
-
out vec2 vUv;
|
|
6950
|
-
void main() {
|
|
6951
|
-
vUv = uv;
|
|
6952
|
-
gl_Position = vec4(position.xy, 0.0, 1.0);
|
|
6953
|
-
}
|
|
6954
|
-
`
|
|
6955
|
-
);
|
|
6956
|
-
const fragmentShader = (
|
|
6957
|
-
/* glsl */
|
|
6958
|
-
`
|
|
6959
|
-
precision highp float;
|
|
6960
|
-
precision highp int;
|
|
6961
|
-
uniform sampler2D source;
|
|
6962
|
-
uniform sampler2D sourceDepth;
|
|
6963
|
-
uniform sampler2D history;
|
|
6964
|
-
uniform sampler2D historyDepth;
|
|
6965
|
-
uniform vec2 renderSize;
|
|
6966
|
-
uniform mat4 projection;
|
|
6967
|
-
uniform mat4 unjitteredProjection;
|
|
6968
|
-
uniform mat4 previousProjection;
|
|
6969
|
-
uniform mat4 viewToPreviousClip;
|
|
6970
|
-
uniform mat4 previousViewToView;
|
|
6971
|
-
uniform bool valid;
|
|
6972
|
-
uniform bool reversed;
|
|
6973
|
-
uniform bool logarithmic;
|
|
6974
|
-
uniform vec2 logFar;
|
|
6975
|
-
uniform float depthThreshold;
|
|
6976
|
-
uniform float edgeDepthDiff;
|
|
6977
|
-
uniform float maxMotionLength;
|
|
6978
|
-
uniform bool useSubpixelCorrection;
|
|
6979
|
-
uniform vec3 luminanceCoefficients;
|
|
6980
|
-
in vec2 vUv;
|
|
6981
|
-
out vec4 fragColor;
|
|
6982
|
-
|
|
6983
|
-
ivec2 bounded(ivec2 p) {
|
|
6984
|
-
return clamp(p, ivec2(0), ivec2(renderSize) - 1);
|
|
6985
|
-
}
|
|
6986
|
-
|
|
6987
|
-
// Homogeneous view coordinates retain far-depth precision and avoid a
|
|
6988
|
-
// world-space round trip.
|
|
6989
|
-
vec4 viewPosition(vec2 uv, float depth, mat4 p, float logFarValue) {
|
|
6990
|
-
float z = reversed ? depth : depth * 2.0 - 1.0;
|
|
6991
|
-
float orientation = reversed ? -1.0 : 1.0;
|
|
6992
|
-
float viewZ = (p[3][2] - z * p[3][3]) * orientation;
|
|
6993
|
-
float viewW = (z * p[2][3] - p[2][2]) * orientation;
|
|
6994
|
-
if (logarithmic) {
|
|
6995
|
-
viewZ = 1.0 - exp2(depth * logFarValue);
|
|
6996
|
-
viewW = 1.0;
|
|
6997
|
-
}
|
|
6998
|
-
float clipW = p[2][3] * viewZ + p[3][3] * viewW;
|
|
6999
|
-
vec2 viewXY = ((uv * 2.0 - 1.0) * clipW - p[2].xy * viewZ
|
|
7000
|
-
- p[3].xy * viewW) / vec2(p[0][0], p[1][1]);
|
|
7001
|
-
return vec4(viewXY, viewZ, viewW);
|
|
7002
|
-
}
|
|
7003
|
-
|
|
7004
|
-
float forwardDepth(vec4 positionView) {
|
|
7005
|
-
vec4 clip = projection * positionView;
|
|
7006
|
-
float depth = clip.z / clip.w;
|
|
7007
|
-
return reversed ? 1.0 - depth : depth * 0.5 + 0.5;
|
|
7008
|
-
}
|
|
7009
|
-
|
|
7010
|
-
vec4 clipAABB(vec4 center, vec4 historyColor, vec4 extent) {
|
|
7011
|
-
vec4 delta = historyColor - center;
|
|
7012
|
-
vec3 unit = abs(delta.rgb / (extent.rgb + 1e-7));
|
|
7013
|
-
float maxUnit = max(unit.r, max(unit.g, unit.b));
|
|
7014
|
-
return maxUnit > 1.0 ? center + delta / maxUnit : historyColor;
|
|
7015
|
-
}
|
|
7016
|
-
|
|
7017
|
-
vec4 flickerReduction(vec4 currentColor, vec4 historyColor, float currentWeight) {
|
|
7018
|
-
vec3 compressedCurrent = currentColor.rgb / (max(currentColor.r, max(currentColor.g, currentColor.b)) + 1.0);
|
|
7019
|
-
vec3 compressedHistory = historyColor.rgb / (max(historyColor.r, max(historyColor.g, historyColor.b)) + 1.0);
|
|
7020
|
-
float weightCurrent = currentWeight / (dot(compressedCurrent, luminanceCoefficients) + 1.0);
|
|
7021
|
-
float weightHistory = (1.0 - currentWeight) / (dot(compressedHistory, luminanceCoefficients) + 1.0);
|
|
7022
|
-
return (currentColor * weightCurrent + historyColor * weightHistory)
|
|
7023
|
-
/ max(weightCurrent + weightHistory, 0.00001);
|
|
7024
|
-
}
|
|
7025
|
-
|
|
7026
|
-
void main() {
|
|
7027
|
-
ivec2 pixel = ivec2(gl_FragCoord.xy);
|
|
7028
|
-
vec4 currentColor = texelFetch(source, pixel, 0);
|
|
7029
|
-
fragColor = currentColor;
|
|
7030
|
-
gl_FragDepth = texelFetch(sourceDepth, pixel, 0).r;
|
|
7031
|
-
if (!valid) return;
|
|
7032
|
-
|
|
7033
|
-
float closestDepth = 2.0;
|
|
7034
|
-
float farthestDepth = -1.0;
|
|
7035
|
-
float closestRawDepth = gl_FragDepth;
|
|
7036
|
-
ivec2 closestPixel = pixel;
|
|
7037
|
-
vec4 moment1 = currentColor;
|
|
7038
|
-
vec4 moment2 = currentColor * currentColor;
|
|
7039
|
-
for (int x = -1; x <= 1; ++x) {
|
|
7040
|
-
for (int y = -1; y <= 1; ++y) {
|
|
7041
|
-
ivec2 p = bounded(pixel + ivec2(x, y));
|
|
7042
|
-
float rawDepth = texelFetch(sourceDepth, p, 0).r;
|
|
7043
|
-
float depth = reversed && !logarithmic ? 1.0 - rawDepth : rawDepth;
|
|
7044
|
-
if (depth < closestDepth) {
|
|
7045
|
-
closestDepth = depth;
|
|
7046
|
-
closestRawDepth = rawDepth;
|
|
7047
|
-
closestPixel = p;
|
|
7048
|
-
}
|
|
7049
|
-
farthestDepth = max(farthestDepth, depth);
|
|
7050
|
-
if (x != 0 || y != 0) {
|
|
7051
|
-
vec4 neighbor = max(texelFetch(source, p, 0), vec4(0.0));
|
|
7052
|
-
moment1 += neighbor;
|
|
7053
|
-
moment2 += neighbor * neighbor;
|
|
7054
|
-
}
|
|
7055
|
-
}
|
|
7056
|
-
}
|
|
7057
|
-
|
|
7058
|
-
vec2 closestUV = (vec2(closestPixel) + 0.5) / renderSize;
|
|
7059
|
-
vec4 positionView = viewPosition(closestUV, closestRawDepth, projection, logFar.x);
|
|
7060
|
-
vec4 previousClip = viewToPreviousClip * positionView;
|
|
7061
|
-
if (previousClip.w <= 0.0) return;
|
|
7062
|
-
vec4 currentClip = unjitteredProjection * positionView;
|
|
7063
|
-
// Exclude projection jitter from camera motion.
|
|
7064
|
-
vec2 offsetUV = (currentClip.xy / currentClip.w - previousClip.xy / previousClip.w) * 0.5;
|
|
7065
|
-
vec2 historyUV = vUv - offsetUV;
|
|
7066
|
-
if (any(lessThan(historyUV, vec2(0.0))) || any(greaterThanEqual(historyUV, vec2(1.0)))) return;
|
|
7067
|
-
|
|
7068
|
-
float oldDepth = texture(historyDepth, historyUV).r;
|
|
7069
|
-
vec4 previousView = viewPosition(historyUV, oldDepth, previousProjection, logFar.y);
|
|
7070
|
-
float previousDepth = forwardDepth(previousViewToView * previousView);
|
|
7071
|
-
if (logarithmic) {
|
|
7072
|
-
// Log depth is monotonic; convert only the extrema for depth thresholds.
|
|
7073
|
-
closestDepth = forwardDepth(positionView);
|
|
7074
|
-
float farthestZ = 1.0 - exp2(farthestDepth * logFar.x);
|
|
7075
|
-
farthestDepth = forwardDepth(vec4(0.0, 0.0, farthestZ, 1.0));
|
|
7076
|
-
}
|
|
7077
|
-
bool isEdge = farthestDepth - closestDepth > edgeDepthDiff;
|
|
7078
|
-
bool isDisocclusion = closestDepth - previousDepth > depthThreshold;
|
|
7079
|
-
if (!isEdge && isDisocclusion) return;
|
|
7080
|
-
|
|
7081
|
-
float motionFactor = clamp(length(offsetUV * renderSize) / maxMotionLength, 0.0, 1.0);
|
|
7082
|
-
float currentWeight = 0.05;
|
|
7083
|
-
if (useSubpixelCorrection) {
|
|
7084
|
-
vec2 phase = fract(offsetUV * renderSize);
|
|
7085
|
-
vec2 weight = max(phase, 1.0 - phase);
|
|
7086
|
-
currentWeight += (1.0 - weight.x * weight.y) / 0.75 * 0.25;
|
|
7087
|
-
}
|
|
7088
|
-
currentWeight = clamp(currentWeight + motionFactor, 0.0, 1.0);
|
|
7089
|
-
float gamma = mix(0.5, 1.0, pow(1.0 - motionFactor, 2.0));
|
|
7090
|
-
vec4 mean = moment1 / 9.0;
|
|
7091
|
-
vec4 extent = sqrt(max(moment2 / 9.0 - mean * mean, vec4(0.0))) * gamma;
|
|
7092
|
-
vec4 historyColor = clipAABB(mean, texture(history, historyUV), extent);
|
|
7093
|
-
fragColor = flickerReduction(currentColor, historyColor, currentWeight);
|
|
7094
|
-
}
|
|
7095
|
-
`
|
|
7096
|
-
);
|
|
7097
|
-
function makeTarget(name) {
|
|
7098
|
-
const target = new THREE.WebGLRenderTarget(1, 1, {
|
|
7099
|
-
type: THREE.HalfFloatType,
|
|
7100
|
-
depthTexture: new THREE.DepthTexture(1, 1, THREE.FloatType)
|
|
7101
|
-
});
|
|
7102
|
-
target.texture.name = name;
|
|
7103
|
-
return target;
|
|
7104
|
-
}
|
|
7105
|
-
class TAAPass extends Pass_js.Pass {
|
|
7106
|
-
/** @param msaa Enable 4× MSAA (default false). Fixed for the pass's lifetime. */
|
|
7107
|
-
constructor(scene, camera, msaa = false) {
|
|
7108
|
-
super();
|
|
7109
|
-
this.scene = scene;
|
|
7110
|
-
this.camera = camera;
|
|
7111
|
-
this.msaa = msaa;
|
|
7112
|
-
this._coverage = msaa ? new StochasticCoverage() : null;
|
|
7113
|
-
this.depthThreshold = 5e-4;
|
|
7114
|
-
this.edgeDepthDiff = 1e-3;
|
|
7115
|
-
this.maxMotionLength = 128;
|
|
7116
|
-
this.useSubpixelCorrection = true;
|
|
7117
|
-
this._source = Object.assign(makeTarget("TAAPass.scene"), {
|
|
7118
|
-
isXRRenderTarget: true,
|
|
7119
|
-
samples: msaa ? 4 : 0
|
|
7120
|
-
});
|
|
7121
|
-
this._history = [
|
|
7122
|
-
makeTarget("TAAPass.history0"),
|
|
7123
|
-
makeTarget("TAAPass.history1")
|
|
7124
|
-
];
|
|
7125
|
-
this._clearColor = new THREE.Color();
|
|
7126
|
-
this._state = createTAAState(
|
|
7127
|
-
(value) => ({ value }),
|
|
7128
|
-
[this._source, ...this._history]
|
|
7129
|
-
);
|
|
7130
|
-
this._uniforms = {
|
|
7131
|
-
source: { value: this._source.texture },
|
|
7132
|
-
sourceDepth: { value: this._source.depthTexture },
|
|
7133
|
-
history: { value: this._history[0].texture },
|
|
7134
|
-
historyDepth: { value: this._history[0].depthTexture },
|
|
7135
|
-
...this._state.uniforms
|
|
7136
|
-
};
|
|
7137
|
-
this._resolveMaterial = new THREE.ShaderMaterial({
|
|
7138
|
-
name: "TAAPass.resolve",
|
|
7139
|
-
uniforms: this._uniforms,
|
|
7140
|
-
vertexShader: vertexShader$1,
|
|
7141
|
-
fragmentShader,
|
|
7142
|
-
glslVersion: THREE.GLSL3,
|
|
7143
|
-
blending: THREE.NoBlending,
|
|
7144
|
-
depthTest: true,
|
|
7145
|
-
depthWrite: true,
|
|
7146
|
-
toneMapped: false
|
|
7147
|
-
});
|
|
7148
|
-
this._copyMaterial = new THREE.ShaderMaterial({
|
|
7149
|
-
name: "TAAPass.copy",
|
|
7150
|
-
uniforms: { source: { value: null }, decode: { value: false } },
|
|
7151
|
-
vertexShader: vertexShader$1,
|
|
7152
|
-
// Inverts OutputPass's transfer, so the two round-trip at any alpha.
|
|
7153
|
-
fragmentShader: (
|
|
7154
|
-
/* glsl */
|
|
7155
|
-
`
|
|
7156
|
-
uniform sampler2D source;
|
|
7157
|
-
uniform bool decode;
|
|
7158
|
-
in vec2 vUv;
|
|
7159
|
-
out vec4 fragColor;
|
|
7160
|
-
void main() {
|
|
7161
|
-
fragColor = texture(source, vUv);
|
|
7162
|
-
if (decode) {
|
|
7163
|
-
vec4 color = vec4(max(fragColor.rgb, 0.0), 1.0);
|
|
7164
|
-
fragColor.rgb = sRGBTransferEOTF(color).rgb;
|
|
7165
|
-
}
|
|
7166
|
-
}
|
|
7167
|
-
`
|
|
7168
|
-
),
|
|
7169
|
-
glslVersion: THREE.GLSL3,
|
|
7170
|
-
blending: THREE.NoBlending,
|
|
7171
|
-
depthTest: false,
|
|
7172
|
-
depthWrite: false,
|
|
7173
|
-
toneMapped: false
|
|
7174
|
-
});
|
|
7175
|
-
this._quad = new Pass_js.FullScreenQuad(this._resolveMaterial);
|
|
7176
|
-
}
|
|
7177
|
-
/** Current scene depth, before temporal accumulation. Owned by this pass. */
|
|
7178
|
-
get depthTexture() {
|
|
7179
|
-
return this._source.depthTexture;
|
|
7180
|
-
}
|
|
7181
|
-
/** Jittered projection used to capture depthTexture. Treat as read-only. */
|
|
7182
|
-
get projectionMatrix() {
|
|
7183
|
-
return this._uniforms.projection.value;
|
|
7184
|
-
}
|
|
7185
|
-
/** Resize all internal targets in physical pixels; a size change clears history. */
|
|
7186
|
-
setSize(width, height) {
|
|
7187
|
-
this._state.setSize(width, height);
|
|
7188
|
-
}
|
|
7189
|
-
/** Discard history after a camera cut, scene replacement, or color-space change. */
|
|
7190
|
-
reset() {
|
|
7191
|
-
this._state.reset();
|
|
7192
|
-
}
|
|
7193
|
-
/** Render the scene and resolve TAA into the composer's working-space buffer. */
|
|
7194
|
-
render(renderer, writeBuffer) {
|
|
7195
|
-
var _a2;
|
|
7196
|
-
const { camera, _state: state, _uniforms: uniforms } = this;
|
|
7197
|
-
this.setSize(writeBuffer.width, writeBuffer.height);
|
|
7198
|
-
const colorSpace = renderer.outputColorSpace;
|
|
7199
|
-
if (this._source.texture.colorSpace !== colorSpace) {
|
|
7200
|
-
this._source.texture.colorSpace = colorSpace;
|
|
7201
|
-
this.reset();
|
|
7202
|
-
}
|
|
7203
|
-
state.setStencil(((_a2 = renderer.getContextAttributes()) == null ? void 0 : _a2.stencil) === true);
|
|
7204
|
-
const previousTarget = renderer.getRenderTarget();
|
|
7205
|
-
const autoClear = renderer.autoClear;
|
|
7206
|
-
const xrEnabled = renderer.xr.enabled;
|
|
7207
|
-
const toneMapping = renderer.toneMapping;
|
|
7208
|
-
try {
|
|
7209
|
-
renderer.xr.enabled = false;
|
|
7210
|
-
renderer.autoClear = false;
|
|
7211
|
-
renderer.toneMapping = THREE.NoToneMapping;
|
|
7212
|
-
const reversedDepth = camera.reversedDepth;
|
|
7213
|
-
state.beginCapture(camera);
|
|
7214
|
-
try {
|
|
7215
|
-
renderer.setRenderTarget(this._source);
|
|
7216
|
-
renderer.setClearColor(
|
|
7217
|
-
renderer.getClearColor(this._clearColor),
|
|
7218
|
-
renderer.getClearAlpha()
|
|
7219
|
-
);
|
|
7220
|
-
renderer.clear();
|
|
7221
|
-
if (this._coverage) this._coverage.render(renderer, this.scene, camera);
|
|
7222
|
-
else renderer.render(this.scene, camera);
|
|
7223
|
-
} finally {
|
|
7224
|
-
state.endCapture(camera);
|
|
7225
|
-
if (camera.reversedDepth !== reversedDepth)
|
|
7226
|
-
camera.updateProjectionMatrix();
|
|
7227
|
-
}
|
|
7228
|
-
const input = this._history[state.historyIndex];
|
|
7229
|
-
const output = this._history[1 - state.historyIndex];
|
|
7230
|
-
if (!uniforms.valid.value) {
|
|
7231
|
-
for (const target of this._history) renderer.initRenderTarget(target);
|
|
7232
|
-
}
|
|
7233
|
-
uniforms.history.value = input.texture;
|
|
7234
|
-
uniforms.historyDepth.value = input.depthTexture;
|
|
7235
|
-
uniforms.reversed.value = renderer.capabilities.reversedDepthBuffer;
|
|
7236
|
-
uniforms.logarithmic.value = renderer.capabilities.logarithmicDepthBuffer && "isPerspectiveCamera" in camera && camera.isPerspectiveCamera;
|
|
7237
|
-
this._resolveMaterial.depthFunc = state.prepareResolve(camera, this);
|
|
7238
|
-
renderer.setRenderTarget(output);
|
|
7239
|
-
this._quad.material = this._resolveMaterial;
|
|
7240
|
-
this._quad.render(renderer);
|
|
7241
|
-
this._copyMaterial.uniforms.source.value = output.texture;
|
|
7242
|
-
this._copyMaterial.uniforms.decode.value = !this.renderToScreen && THREE.ColorManagement.getTransfer(colorSpace) === THREE.SRGBTransfer;
|
|
7243
|
-
renderer.setRenderTarget(this.renderToScreen ? null : writeBuffer);
|
|
7244
|
-
this._quad.material = this._copyMaterial;
|
|
7245
|
-
this._quad.render(renderer);
|
|
7246
|
-
state.advance(camera);
|
|
7247
|
-
} finally {
|
|
7248
|
-
renderer.setRenderTarget(previousTarget);
|
|
7249
|
-
renderer.autoClear = autoClear;
|
|
7250
|
-
renderer.xr.enabled = xrEnabled;
|
|
7251
|
-
renderer.toneMapping = toneMapping;
|
|
7252
|
-
}
|
|
7253
|
-
}
|
|
7254
|
-
dispose() {
|
|
7255
|
-
var _a2;
|
|
7256
|
-
(_a2 = this._coverage) == null ? void 0 : _a2.dispose();
|
|
7257
|
-
this._source.dispose();
|
|
7258
|
-
for (const target of this._history) target.dispose();
|
|
7259
|
-
this._resolveMaterial.dispose();
|
|
7260
|
-
this._copyMaterial.dispose();
|
|
7261
|
-
this._quad.dispose();
|
|
7262
|
-
}
|
|
7263
|
-
}
|
|
7264
|
-
class TAANode extends webgpu.Node {
|
|
7265
|
-
static get type() {
|
|
7266
|
-
return "TAANode";
|
|
7267
|
-
}
|
|
7268
|
-
/** @param msaa Enable 4× MSAA (default false). Fixed for the node's lifetime. */
|
|
7269
|
-
constructor(scene, camera, msaa = false) {
|
|
7270
|
-
super("vec4");
|
|
7271
|
-
this.scene = scene;
|
|
7272
|
-
this.camera = camera;
|
|
7273
|
-
this.msaa = msaa;
|
|
7274
|
-
this.coverage = msaa ? new StochasticCoverage() : null;
|
|
7275
|
-
this.depthThreshold = 5e-4;
|
|
7276
|
-
this.edgeDepthDiff = 1e-3;
|
|
7277
|
-
this.maxMotionLength = 128;
|
|
7278
|
-
this.useSubpixelCorrection = true;
|
|
7279
|
-
this.source = this.makeTarget("TAA.scene");
|
|
7280
|
-
this.source.samples = msaa ? 4 : 0;
|
|
7281
|
-
this.source.storeMultisampledColorBuffer = false;
|
|
7282
|
-
this.history = [
|
|
7283
|
-
this.makeTarget("TAA.history0"),
|
|
7284
|
-
this.makeTarget("TAA.history1")
|
|
7285
|
-
];
|
|
7286
|
-
this.historyColor = N__namespace.texture(this.history[0].texture, N__namespace.screenUV);
|
|
7287
|
-
this.historyDepth = N__namespace.texture(this.history[0].depthTexture, N__namespace.screenUV);
|
|
7288
|
-
this.textureNode = N__namespace.passTexture(this, this.history[0].texture);
|
|
7289
|
-
this.material = new webgpu.NodeMaterial();
|
|
7290
|
-
this.quad = new webgpu.QuadMesh(this.material);
|
|
7291
|
-
this.state = createTAAState(
|
|
7292
|
-
N__namespace.uniform,
|
|
7293
|
-
[this.source, ...this.history],
|
|
7294
|
-
true
|
|
7295
|
-
);
|
|
7296
|
-
this.zeroToOne = N__namespace.uniform(true);
|
|
7297
|
-
this.drawingSize = new THREE.Vector2();
|
|
7298
|
-
this.workingColorSpace = THREE.ColorManagement.workingColorSpace;
|
|
7299
|
-
this.pipelineStates = /* @__PURE__ */ new WeakSet();
|
|
7300
|
-
this.pipelineRendering = false;
|
|
7301
|
-
this.capturePending = true;
|
|
7302
|
-
this.updateBeforeType = webgpu.NodeUpdateType.RENDER;
|
|
7303
|
-
this.material.name = "TAA.resolve";
|
|
7304
|
-
this.material.blending = THREE.NoBlending;
|
|
7305
|
-
this.material.depthTest = true;
|
|
7306
|
-
this.material.depthWrite = true;
|
|
7307
|
-
this.material.toneMapped = false;
|
|
7308
|
-
this.quad.name = "TAA.resolve";
|
|
7309
|
-
}
|
|
7310
|
-
makeTarget(name) {
|
|
7311
|
-
const target = new webgpu.RenderTarget(1, 1, {
|
|
7312
|
-
type: THREE.HalfFloatType,
|
|
7313
|
-
depthTexture: new THREE.DepthTexture(1, 1, THREE.FloatType),
|
|
7314
|
-
samples: 0
|
|
7315
|
-
});
|
|
7316
|
-
target.texture.name = name;
|
|
7317
|
-
return target;
|
|
7318
|
-
}
|
|
7319
|
-
get depthTexture() {
|
|
7320
|
-
return this.source.depthTexture;
|
|
7321
|
-
}
|
|
7322
|
-
get projectionMatrix() {
|
|
7323
|
-
return this.state.uniforms.projection.value;
|
|
7324
|
-
}
|
|
7325
|
-
getTextureNode() {
|
|
7326
|
-
return this.textureNode;
|
|
7327
|
-
}
|
|
7328
|
-
setSize(width, height) {
|
|
7329
|
-
this.state.setSize(width, height);
|
|
7330
|
-
}
|
|
7331
|
-
reset() {
|
|
7332
|
-
this.state.reset();
|
|
7333
|
-
}
|
|
7334
|
-
updateBefore(frame) {
|
|
7335
|
-
if (this.pipelineRendering && !this.capturePending) return;
|
|
7336
|
-
const renderer = frame.renderer;
|
|
7337
|
-
if (this.workingColorSpace !== THREE.ColorManagement.workingColorSpace) {
|
|
7338
|
-
this.workingColorSpace = THREE.ColorManagement.workingColorSpace;
|
|
7339
|
-
this.reset();
|
|
7340
|
-
}
|
|
7341
|
-
const rendererState = webgpu.RendererUtils.saveRendererState(renderer);
|
|
7342
|
-
const xrEnabled = renderer.xr.enabled;
|
|
7343
|
-
const { camera, state } = this;
|
|
7344
|
-
const { uniforms } = state;
|
|
7345
|
-
if (this.pipelineRendering) {
|
|
7346
|
-
this.setSize(this.drawingSize.x, this.drawingSize.y);
|
|
7347
|
-
} else if (rendererState.renderTarget) {
|
|
7348
|
-
this.setSize(
|
|
7349
|
-
rendererState.renderTarget.width,
|
|
7350
|
-
rendererState.renderTarget.height
|
|
7351
|
-
);
|
|
7352
|
-
} else {
|
|
7353
|
-
renderer.getDrawingBufferSize(this.drawingSize);
|
|
7354
|
-
this.setSize(this.drawingSize.x, this.drawingSize.y);
|
|
7355
|
-
}
|
|
7356
|
-
state.setStencil(renderer.stencil);
|
|
7357
|
-
try {
|
|
7358
|
-
renderer.xr.enabled = false;
|
|
7359
|
-
renderer.setMRT(null);
|
|
7360
|
-
renderer.setScissorTest(false);
|
|
7361
|
-
renderer.autoClear = true;
|
|
7362
|
-
const reversedDepth = camera.reversedDepth;
|
|
7363
|
-
const coordinateSystem = camera.coordinateSystem;
|
|
7364
|
-
state.beginCapture(camera);
|
|
7365
|
-
try {
|
|
7366
|
-
renderer.setRenderTarget(this.source);
|
|
7367
|
-
if (this.coverage) this.coverage.render(renderer, this.scene, camera);
|
|
7368
|
-
else renderer.render(this.scene, camera);
|
|
7369
|
-
} finally {
|
|
7370
|
-
state.endCapture(camera);
|
|
7371
|
-
if (camera.reversedDepth !== reversedDepth || camera.coordinateSystem !== coordinateSystem)
|
|
7372
|
-
camera.updateProjectionMatrix();
|
|
7373
|
-
}
|
|
7374
|
-
uniforms.reversed.value = renderer.reversedDepthBuffer;
|
|
7375
|
-
this.zeroToOne.value = renderer.coordinateSystem !== THREE.WebGLCoordinateSystem || renderer.reversedDepthBuffer;
|
|
7376
|
-
uniforms.logarithmic.value = renderer.logarithmicDepthBuffer && "isPerspectiveCamera" in camera && camera.isPerspectiveCamera;
|
|
7377
|
-
this.material.depthFunc = state.prepareResolve(camera, this);
|
|
7378
|
-
if (!uniforms.valid.value)
|
|
7379
|
-
for (const target of this.history) renderer.initRenderTarget(target);
|
|
7380
|
-
const previous = this.history[state.historyIndex];
|
|
7381
|
-
this.historyColor.value = previous.texture;
|
|
7382
|
-
this.historyDepth.value = previous.depthTexture;
|
|
7383
|
-
const output = this.history[1 - state.historyIndex];
|
|
7384
|
-
renderer.setRenderObjectFunction(null);
|
|
7385
|
-
renderer.setRenderTarget(output);
|
|
7386
|
-
this.quad.render(renderer);
|
|
7387
|
-
this.textureNode.value = output.texture;
|
|
7388
|
-
state.advance(camera);
|
|
7389
|
-
this.capturePending = false;
|
|
7390
|
-
} finally {
|
|
7391
|
-
webgpu.RendererUtils.restoreRendererState(renderer, rendererState);
|
|
7392
|
-
renderer.xr.enabled = xrEnabled;
|
|
7393
|
-
}
|
|
7394
|
-
return void 0;
|
|
7395
|
-
}
|
|
7396
|
-
setup(builder) {
|
|
7397
|
-
applyThreeR186Patch(builder.renderer);
|
|
7398
|
-
const pipelineState = builder.context.renderPipelineState;
|
|
7399
|
-
if (pipelineState && !this.pipelineStates.has(pipelineState)) {
|
|
7400
|
-
this.pipelineStates.add(pipelineState);
|
|
7401
|
-
const begin = () => {
|
|
7402
|
-
this.pipelineRendering = true;
|
|
7403
|
-
this.capturePending = true;
|
|
7404
|
-
const renderer = builder.renderer;
|
|
7405
|
-
const target = renderer.getRenderTarget() ?? renderer.getOutputRenderTarget();
|
|
7406
|
-
if (target) this.drawingSize.set(target.width, target.height);
|
|
7407
|
-
else renderer.getDrawingBufferSize(this.drawingSize);
|
|
7408
|
-
};
|
|
7409
|
-
begin();
|
|
7410
|
-
N__namespace.OnBeforeRenderPipeline(begin);
|
|
7411
|
-
N__namespace.OnAfterRenderPipeline(() => {
|
|
7412
|
-
this.pipelineRendering = false;
|
|
7413
|
-
});
|
|
7414
|
-
}
|
|
7415
|
-
const u = this.state.uniforms;
|
|
7416
|
-
const source = N__namespace.texture(this.source.texture, N__namespace.screenUV);
|
|
7417
|
-
const sourceDepth = N__namespace.texture(this.depthTexture, N__namespace.screenUV);
|
|
7418
|
-
const history = this.historyColor;
|
|
7419
|
-
const historyDepth = this.historyDepth;
|
|
7420
|
-
const bounded = (p) => p.clamp(N__namespace.ivec2(0), N__namespace.ivec2(u.renderSize).sub(1));
|
|
7421
|
-
const viewPosition = (uv, depth2, p, far, near) => {
|
|
7422
|
-
const z = N__namespace.select(this.zeroToOne, depth2, depth2.mul(2).sub(1));
|
|
7423
|
-
const orientation = N__namespace.select(u.reversed, -1, 1);
|
|
7424
|
-
const viewZ = p.element(3).z.sub(z.mul(p.element(3).w)).mul(orientation).toVar();
|
|
7425
|
-
const viewW = z.mul(p.element(2).w).sub(p.element(2).z).mul(orientation).toVar();
|
|
7426
|
-
N__namespace.If(u.logarithmic, () => {
|
|
7427
|
-
viewZ.assign(near.mul(depth2.mul(far).exp2()).negate());
|
|
7428
|
-
viewW.assign(1);
|
|
7429
|
-
});
|
|
7430
|
-
const clipW = p.element(2).w.mul(viewZ).add(p.element(3).w.mul(viewW));
|
|
7431
|
-
const xy = uv.mul(2).sub(1).mul(N__namespace.vec2(1, -1)).mul(clipW).sub(p.element(2).xy.mul(viewZ)).sub(p.element(3).xy.mul(viewW)).div(N__namespace.vec2(p.element(0).x, p.element(1).y));
|
|
7432
|
-
return N__namespace.vec4(xy, viewZ, viewW);
|
|
7433
|
-
};
|
|
7434
|
-
const forwardDepth = (position) => {
|
|
7435
|
-
const clip = u.projection.mul(position).toVar();
|
|
7436
|
-
const depth2 = clip.z.div(clip.w);
|
|
7437
|
-
return N__namespace.select(
|
|
7438
|
-
u.reversed,
|
|
7439
|
-
N__namespace.float(1).sub(depth2),
|
|
7440
|
-
N__namespace.select(this.zeroToOne, depth2, depth2.mul(0.5).add(0.5))
|
|
7441
|
-
);
|
|
7442
|
-
};
|
|
7443
|
-
const pixel = N__namespace.ivec2(N__namespace.screenCoordinate.xy);
|
|
7444
|
-
this.material.depthNode = sourceDepth.load(pixel).r;
|
|
7445
|
-
this.material.colorNode = N__namespace.Fn(() => {
|
|
7446
|
-
const currentColor = source.load(pixel).toVar();
|
|
7447
|
-
const result = currentColor.toVar();
|
|
7448
|
-
N__namespace.If(u.valid, () => {
|
|
7449
|
-
const closestDepth = N__namespace.float(2).toVar();
|
|
7450
|
-
const farthestDepth = N__namespace.float(-1).toVar();
|
|
7451
|
-
const closestRawDepth = sourceDepth.load(pixel).r.toVar();
|
|
7452
|
-
const closestPixel = pixel.toVar();
|
|
7453
|
-
const moment1 = currentColor.toVar();
|
|
7454
|
-
const moment2 = currentColor.mul(currentColor).toVar();
|
|
7455
|
-
for (let x = -1; x <= 1; x++) {
|
|
7456
|
-
for (let y = -1; y <= 1; y++) {
|
|
7457
|
-
const p = bounded(pixel.add(N__namespace.ivec2(x, y))).toVar();
|
|
7458
|
-
const rawDepth = sourceDepth.load(p).r.toVar();
|
|
7459
|
-
const depth2 = N__namespace.select(
|
|
7460
|
-
u.reversed.and(u.logarithmic.not()),
|
|
7461
|
-
N__namespace.float(1).sub(rawDepth),
|
|
7462
|
-
rawDepth
|
|
7463
|
-
).toVar();
|
|
7464
|
-
N__namespace.If(depth2.lessThan(closestDepth), () => {
|
|
7465
|
-
closestDepth.assign(depth2);
|
|
7466
|
-
closestRawDepth.assign(rawDepth);
|
|
7467
|
-
closestPixel.assign(p);
|
|
7468
|
-
});
|
|
7469
|
-
farthestDepth.assign(farthestDepth.max(depth2));
|
|
7470
|
-
if (x !== 0 || y !== 0) {
|
|
7471
|
-
const neighbor = source.load(p).max(0).toVar();
|
|
7472
|
-
moment1.addAssign(neighbor);
|
|
7473
|
-
moment2.addAssign(neighbor.mul(neighbor));
|
|
7474
|
-
}
|
|
7475
|
-
}
|
|
7476
|
-
}
|
|
7477
|
-
const closestUV = N__namespace.vec2(closestPixel).add(0.5).div(u.renderSize);
|
|
7478
|
-
const positionView = viewPosition(
|
|
7479
|
-
closestUV,
|
|
7480
|
-
closestRawDepth,
|
|
7481
|
-
u.projection,
|
|
7482
|
-
u.logFar.x,
|
|
7483
|
-
u.logNear.x
|
|
7484
|
-
);
|
|
7485
|
-
const previousClip = u.viewToPreviousClip.mul(positionView).toVar();
|
|
7486
|
-
N__namespace.If(previousClip.w.greaterThan(0), () => {
|
|
7487
|
-
const currentClip = u.unjitteredProjection.mul(positionView).toVar();
|
|
7488
|
-
const offsetUV = currentClip.xy.div(currentClip.w).sub(previousClip.xy.div(previousClip.w)).mul(N__namespace.vec2(0.5, -0.5)).toVar();
|
|
7489
|
-
const historyUV = N__namespace.screenUV.sub(offsetUV).toVar();
|
|
7490
|
-
N__namespace.If(
|
|
7491
|
-
historyUV.greaterThanEqual(0).all().and(historyUV.lessThan(1).all()),
|
|
7492
|
-
() => {
|
|
7493
|
-
const oldDepth = historyDepth.sample(historyUV).r.toVar();
|
|
7494
|
-
const previousView = viewPosition(
|
|
7495
|
-
historyUV,
|
|
7496
|
-
oldDepth,
|
|
7497
|
-
u.previousProjection,
|
|
7498
|
-
u.logFar.y,
|
|
7499
|
-
u.logNear.y
|
|
7500
|
-
);
|
|
7501
|
-
const previousDepth = forwardDepth(
|
|
7502
|
-
u.previousViewToView.mul(previousView)
|
|
7503
|
-
);
|
|
7504
|
-
N__namespace.If(u.logarithmic, () => {
|
|
7505
|
-
closestDepth.assign(forwardDepth(positionView));
|
|
7506
|
-
const farthestZ = u.logNear.x.mul(farthestDepth.mul(u.logFar.x).exp2()).negate();
|
|
7507
|
-
farthestDepth.assign(forwardDepth(N__namespace.vec4(0, 0, farthestZ, 1)));
|
|
7508
|
-
});
|
|
7509
|
-
const isEdge = farthestDepth.sub(closestDepth).greaterThan(u.edgeDepthDiff);
|
|
7510
|
-
const isDisocclusion = closestDepth.sub(previousDepth).greaterThan(u.depthThreshold);
|
|
7511
|
-
N__namespace.If(isEdge.or(isDisocclusion.not()), () => {
|
|
7512
|
-
const motionFactor = offsetUV.mul(u.renderSize).length().div(u.maxMotionLength).clamp(0, 1).toVar();
|
|
7513
|
-
const currentWeight = N__namespace.float(0.05).toVar();
|
|
7514
|
-
N__namespace.If(u.useSubpixelCorrection, () => {
|
|
7515
|
-
const phase = offsetUV.mul(u.renderSize).fract();
|
|
7516
|
-
const weight = phase.max(N__namespace.vec2(1).sub(phase));
|
|
7517
|
-
currentWeight.addAssign(
|
|
7518
|
-
N__namespace.float(1).sub(weight.x.mul(weight.y)).div(0.75).mul(0.25)
|
|
7519
|
-
);
|
|
7520
|
-
});
|
|
7521
|
-
currentWeight.assign(
|
|
7522
|
-
currentWeight.add(motionFactor).clamp(0, 1)
|
|
7523
|
-
);
|
|
7524
|
-
const gamma = N__namespace.mix(
|
|
7525
|
-
0.5,
|
|
7526
|
-
1,
|
|
7527
|
-
N__namespace.float(1).sub(motionFactor).pow(2)
|
|
7528
|
-
);
|
|
7529
|
-
const mean = moment1.div(9).toVar();
|
|
7530
|
-
const extent = moment2.div(9).sub(mean.mul(mean)).max(0).sqrt().mul(gamma);
|
|
7531
|
-
const historyColor = history.sample(historyUV).toVar();
|
|
7532
|
-
const delta = historyColor.sub(mean).toVar();
|
|
7533
|
-
const unit = delta.rgb.div(extent.rgb.add(1e-7)).abs();
|
|
7534
|
-
const maxUnit = unit.x.max(unit.y).max(unit.z);
|
|
7535
|
-
N__namespace.If(maxUnit.greaterThan(1), () => {
|
|
7536
|
-
historyColor.assign(mean.add(delta.div(maxUnit)));
|
|
7537
|
-
});
|
|
7538
|
-
const compressedCurrent = currentColor.rgb.div(
|
|
7539
|
-
currentColor.r.max(currentColor.g).max(currentColor.b).add(1)
|
|
7540
|
-
);
|
|
7541
|
-
const compressedHistory = historyColor.rgb.div(
|
|
7542
|
-
historyColor.r.max(historyColor.g).max(historyColor.b).add(1)
|
|
7543
|
-
);
|
|
7544
|
-
const weightCurrent = currentWeight.div(
|
|
7545
|
-
compressedCurrent.dot(u.luminanceCoefficients).add(1)
|
|
7546
|
-
);
|
|
7547
|
-
const weightHistory = N__namespace.float(1).sub(currentWeight).div(compressedHistory.dot(u.luminanceCoefficients).add(1));
|
|
7548
|
-
result.assign(
|
|
7549
|
-
currentColor.mul(weightCurrent).add(historyColor.mul(weightHistory)).div(weightCurrent.add(weightHistory).max(1e-5))
|
|
7550
|
-
);
|
|
7551
|
-
});
|
|
7552
|
-
}
|
|
7553
|
-
);
|
|
7554
|
-
});
|
|
7555
|
-
});
|
|
7556
|
-
return result;
|
|
7557
|
-
})();
|
|
7558
|
-
return this.textureNode;
|
|
7559
|
-
}
|
|
7560
|
-
dispose() {
|
|
7561
|
-
var _a2;
|
|
7562
|
-
super.dispose();
|
|
7563
|
-
(_a2 = this.coverage) == null ? void 0 : _a2.dispose();
|
|
7564
|
-
this.source.dispose();
|
|
7565
|
-
for (const target of this.history) target.dispose();
|
|
7566
|
-
this.material.dispose();
|
|
7567
|
-
}
|
|
7568
|
-
}
|
|
7569
6848
|
const types = {
|
|
7570
6849
|
f32: "float",
|
|
7571
6850
|
i32: "int",
|
|
@@ -7811,6 +7090,17 @@ fn chroma(c: vec3f) -> vec2f {
|
|
|
7811
7090
|
fn silu(x: vec4f) -> vec4f { return x / (1.0 + exp(-x)); }
|
|
7812
7091
|
fn sigmoid(x: f32) -> f32 { return 1.0 / (1.0 + exp(-x)); }`
|
|
7813
7092
|
);
|
|
7093
|
+
const CATMULL_ROM_WEIGHTS = (
|
|
7094
|
+
/* wgsl */
|
|
7095
|
+
`
|
|
7096
|
+
let center: vec2f = floor(position - 0.5) + 0.5;
|
|
7097
|
+
let t: vec2f = position - center;
|
|
7098
|
+
let w0: vec2f = t * (-0.5 + t * (1.0 - 0.5 * t));
|
|
7099
|
+
let w1: vec2f = 1.0 + t * t * (-2.5 + 1.5 * t);
|
|
7100
|
+
let w2: vec2f = t * (0.5 + t * (2.0 - 1.5 * t));
|
|
7101
|
+
let w3: vec2f = t * t * (-0.5 + 0.5 * t);
|
|
7102
|
+
let w12: vec2f = w1 + w2;`
|
|
7103
|
+
);
|
|
7814
7104
|
const PARAMS = (
|
|
7815
7105
|
/* wgsl */
|
|
7816
7106
|
`
|
|
@@ -7835,6 +7125,7 @@ struct Params {
|
|
|
7835
7125
|
cameraStill: f32, // unchanged view and projection; history stays on the same pixel
|
|
7836
7126
|
offset: vec2f, // where in its target the scene was drawn
|
|
7837
7127
|
colorOffset: vec2f, // zero after encoding, otherwise the scene's offset
|
|
7128
|
+
jitter: vec2f, // where this frame's samples lie, in pixels from pixel centres
|
|
7838
7129
|
};
|
|
7839
7130
|
@group(0) @binding(0) var<uniform> P: Params;
|
|
7840
7131
|
@group(0) @binding(1) var linearSampler: sampler;
|
|
@@ -7904,13 +7195,7 @@ struct Resampled {
|
|
|
7904
7195
|
};
|
|
7905
7196
|
fn catmullRomSpan(image: texture_2d<f32>, uv: vec2f) -> Resampled {
|
|
7906
7197
|
let position: vec2f = uv * P.size;
|
|
7907
|
-
|
|
7908
|
-
let t: vec2f = position - center;
|
|
7909
|
-
let w0: vec2f = t * (-0.5 + t * (1.0 - 0.5 * t));
|
|
7910
|
-
let w1: vec2f = 1.0 + t * t * (-2.5 + 1.5 * t);
|
|
7911
|
-
let w2: vec2f = t * (0.5 + t * (2.0 - 1.5 * t));
|
|
7912
|
-
let w3: vec2f = t * t * (-0.5 + 0.5 * t);
|
|
7913
|
-
let w12: vec2f = w1 + w2;
|
|
7198
|
+
${CATMULL_ROM_WEIGHTS}
|
|
7914
7199
|
let p0: vec2f = (center - 1.0) * P.invSize;
|
|
7915
7200
|
let p12: vec2f = (center + w2 / w12) * P.invSize;
|
|
7916
7201
|
let p3: vec2f = (center + 2.0) * P.invSize;
|
|
@@ -7933,75 +7218,6 @@ fn catmullRomSpan(image: texture_2d<f32>, uv: vec2f) -> Resampled {
|
|
|
7933
7218
|
fn catmullRom(image: texture_2d<f32>, uv: vec2f) -> vec4f {
|
|
7934
7219
|
return catmullRomSpan(image, uv).value;
|
|
7935
7220
|
}
|
|
7936
|
-
|
|
7937
|
-
// Lanczos-3 without its corner taps, from nine bilinear taps: the middle pair
|
|
7938
|
-
// of each axis is one tap and the other four pixels of an axis one each. A
|
|
7939
|
-
// history that is resampled once per frame loses fine detail with every pass,
|
|
7940
|
-
// and this kernel loses about half of what Catmull-Rom does. Its negative
|
|
7941
|
-
// lobes would ring over such a history, so the result is kept inside what the
|
|
7942
|
-
// four pixels around the position span. At a pixel's centre it returns the
|
|
7943
|
-
// pixel.
|
|
7944
|
-
struct LanczosWeights {
|
|
7945
|
-
low: vec3f,
|
|
7946
|
-
high: vec3f,
|
|
7947
|
-
};
|
|
7948
|
-
fn lanczosWeights(t: f32) -> LanczosWeights {
|
|
7949
|
-
// Integer-shifted sinc taps share their sine. Angle addition supplies the
|
|
7950
|
-
// other factor, reducing twelve sine evaluations per axis to three trig calls.
|
|
7951
|
-
let x: f32 = 3.14159265 * t;
|
|
7952
|
-
let s: f32 = sin(x / 3.0);
|
|
7953
|
-
let c: f32 = 0.866025404 * cos(x / 3.0);
|
|
7954
|
-
let scale: f32 = 0.303963551 * sin(x);
|
|
7955
|
-
let terms: vec3f = scale * vec3f(c - 0.5 * s, -c - 0.5 * s, s);
|
|
7956
|
-
let low: vec3f = vec3f(t + 2.0, t + 1.0, t);
|
|
7957
|
-
let high: vec3f = vec3f(t - 1.0, t - 2.0, t - 3.0);
|
|
7958
|
-
var w: LanczosWeights;
|
|
7959
|
-
w.low = terms / max(low * low, vec3f(1e-20));
|
|
7960
|
-
w.high = terms / max(high * high, vec3f(1e-20));
|
|
7961
|
-
if (abs(x) < 1e-5) { w.low.z = 1.0; }
|
|
7962
|
-
if (abs(3.14159265 * (t - 1.0)) < 1e-5) { w.high.x = 1.0; }
|
|
7963
|
-
return w;
|
|
7964
|
-
}
|
|
7965
|
-
struct LanczosKernel {
|
|
7966
|
-
base: vec2f,
|
|
7967
|
-
centre: vec2f,
|
|
7968
|
-
middle: f32,
|
|
7969
|
-
wx: vec4f,
|
|
7970
|
-
wy: vec4f,
|
|
7971
|
-
total: f32,
|
|
7972
|
-
}
|
|
7973
|
-
fn lanczosKernel(uv: vec2f) -> LanczosKernel {
|
|
7974
|
-
let position: vec2f = uv * P.size - 0.5;
|
|
7975
|
-
var k: LanczosKernel;
|
|
7976
|
-
k.base = floor(position);
|
|
7977
|
-
let t: vec2f = position - k.base;
|
|
7978
|
-
let x: LanczosWeights = lanczosWeights(t.x);
|
|
7979
|
-
let y: LanczosWeights = lanczosWeights(t.y);
|
|
7980
|
-
let middle: vec2f = vec2f(x.low.z + x.high.x, y.low.z + y.high.x);
|
|
7981
|
-
k.centre = (k.base + vec2f(x.high.x, y.high.x) / middle + 0.5) * P.invSize;
|
|
7982
|
-
// All three histories share these 2D tap weights and their normalization.
|
|
7983
|
-
k.middle = middle.x * middle.y;
|
|
7984
|
-
k.wx = vec4f(x.low.xy, x.high.yz) * middle.y;
|
|
7985
|
-
k.wy = vec4f(y.low.xy, y.high.yz) * middle.x;
|
|
7986
|
-
k.total = k.middle;
|
|
7987
|
-
for (var i: i32 = 0; i < 4; i++) { k.total += k.wx[i] + k.wy[i]; }
|
|
7988
|
-
return k;
|
|
7989
|
-
}
|
|
7990
|
-
fn lanczosCross(image: texture_2d<f32>, k: LanczosKernel) -> vec4f {
|
|
7991
|
-
var sum: vec4f = textureSampleLevel(image, linearSampler, k.centre, 0.0) * k.middle;
|
|
7992
|
-
const offsets = array<f32, 4>(-1.5, -0.5, 2.5, 3.5);
|
|
7993
|
-
for (var i: i32 = 0; i < 4; i++) {
|
|
7994
|
-
let along: vec2f = (k.base + offsets[i]) * P.invSize;
|
|
7995
|
-
sum += textureSampleLevel(image, linearSampler, vec2f(along.x, k.centre.y), 0.0) * k.wx[i];
|
|
7996
|
-
sum += textureSampleLevel(image, linearSampler, vec2f(k.centre.x, along.y), 0.0) * k.wy[i];
|
|
7997
|
-
}
|
|
7998
|
-
let p: vec2i = vec2i(k.base);
|
|
7999
|
-
let a: vec4f = textureLoad(image, bounded(p), 0);
|
|
8000
|
-
let b: vec4f = textureLoad(image, bounded(p + vec2i(1, 0)), 0);
|
|
8001
|
-
let c: vec4f = textureLoad(image, bounded(p + vec2i(0, 1)), 0);
|
|
8002
|
-
let d: vec4f = textureLoad(image, bounded(p + vec2i(1, 1)), 0);
|
|
8003
|
-
return clamp(sum / k.total, min(min(a, b), min(c, d)), max(max(a, b), max(c, d)));
|
|
8004
|
-
}
|
|
8005
7221
|
`
|
|
8006
7222
|
);
|
|
8007
7223
|
const TAPS = (
|
|
@@ -8331,15 +7547,60 @@ ${robust ? ` let under: vec4f = coarse(below, position.xy, 2.0);
|
|
|
8331
7547
|
groups: groups.length
|
|
8332
7548
|
};
|
|
8333
7549
|
}
|
|
8334
|
-
const
|
|
7550
|
+
const MEAN_3X3 = (
|
|
8335
7551
|
/* wgsl */
|
|
8336
7552
|
`
|
|
8337
|
-
// Exact clamped 3x3 mean
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8341
|
-
|
|
8342
|
-
|
|
7553
|
+
// Exact clamped 3x3 mean at a subpixel position, from four bilinear taps.
|
|
7554
|
+
// Each axis has weights (1 - t, 1, 1, t) / 3 over four texels. Pair the
|
|
7555
|
+
// first two and the last two, moving each tap with its pair's weights.
|
|
7556
|
+
fn mean3x3(image: texture_2d<f32>, position: vec2f, offset: vec2f) -> vec4f {
|
|
7557
|
+
let center: vec2f = floor(position - 0.5) + 0.5;
|
|
7558
|
+
let t: vec2f = position - center;
|
|
7559
|
+
let low: vec2f = 2.0 - t;
|
|
7560
|
+
let high: vec2f = 1.0 + t;
|
|
7561
|
+
let size: vec2f = vec2f(textureDimensions(image));
|
|
7562
|
+
let p0: vec2f = (clamp(center - 1.0 + 1.0 / low, vec2f(0.5), P.size - 0.5) + offset) / size;
|
|
7563
|
+
let p1: vec2f = (clamp(center + 1.0 + t / high, vec2f(0.5), P.size - 0.5) + offset) / size;
|
|
7564
|
+
return (textureSampleLevel(image, linearSampler, p0, 0.0) * (low.x * low.y) +
|
|
7565
|
+
textureSampleLevel(image, linearSampler, vec2f(p1.x, p0.y), 0.0) * (high.x * low.y) +
|
|
7566
|
+
textureSampleLevel(image, linearSampler, vec2f(p0.x, p1.y), 0.0) * (low.x * high.y) +
|
|
7567
|
+
textureSampleLevel(image, linearSampler, p1, 0.0) * (high.x * high.y)) / 9.0;
|
|
7568
|
+
}
|
|
7569
|
+
`
|
|
7570
|
+
);
|
|
7571
|
+
const QUALITY_RESAMPLING = (
|
|
7572
|
+
/* wgsl */
|
|
7573
|
+
`
|
|
7574
|
+
// Full Catmull-Rom for Quality histories, retaining all four corner taps.
|
|
7575
|
+
fn catmullRomNine(image: texture_2d<f32>, uv: vec2f) -> vec4f {
|
|
7576
|
+
let position: vec2f = uv * P.size;
|
|
7577
|
+
${CATMULL_ROM_WEIGHTS}
|
|
7578
|
+
let p0: vec2f = (center - 1.0) * P.invSize;
|
|
7579
|
+
let p12: vec2f = (center + w2 / w12) * P.invSize;
|
|
7580
|
+
let p3: vec2f = (center + 2.0) * P.invSize;
|
|
7581
|
+
var result: vec4f = textureSampleLevel(image, linearSampler, p12, 0.0) * (w12.x * w12.y);
|
|
7582
|
+
result += textureSampleLevel(image, linearSampler, vec2f(p0.x, p12.y), 0.0) * (w0.x * w12.y);
|
|
7583
|
+
result += textureSampleLevel(image, linearSampler, vec2f(p3.x, p12.y), 0.0) * (w3.x * w12.y);
|
|
7584
|
+
result += textureSampleLevel(image, linearSampler, vec2f(p12.x, p0.y), 0.0) * (w12.x * w0.y);
|
|
7585
|
+
result += textureSampleLevel(image, linearSampler, vec2f(p12.x, p3.y), 0.0) * (w12.x * w3.y);
|
|
7586
|
+
result += textureSampleLevel(image, linearSampler, p0, 0.0) * (w0.x * w0.y);
|
|
7587
|
+
result += textureSampleLevel(image, linearSampler, vec2f(p3.x, p0.y), 0.0) * (w3.x * w0.y);
|
|
7588
|
+
result += textureSampleLevel(image, linearSampler, vec2f(p0.x, p3.y), 0.0) * (w0.x * w3.y);
|
|
7589
|
+
result += textureSampleLevel(image, linearSampler, p3, 0.0) * (w3.x * w3.y);
|
|
7590
|
+
return result / ((w0.x + w12.x + w3.x) * (w0.y + w12.y + w3.y));
|
|
7591
|
+
}
|
|
7592
|
+
|
|
7593
|
+
// Catmull-Rom of the frame at a position between its pixels, from the five
|
|
7594
|
+
// bilinear taps catmullRomSpan takes, read through the scene's offset.
|
|
7595
|
+
fn frameCatmullRom(position: vec2f) -> vec4f {
|
|
7596
|
+
${CATMULL_ROM_WEIGHTS}
|
|
7597
|
+
let p12: vec2f = center + w2 / w12;
|
|
7598
|
+
var result: vec4f = frameSample(colorTexture, p12) * (w12.x * w12.y);
|
|
7599
|
+
result += frameSample(colorTexture, vec2f(center.x - 1.0, p12.y)) * (w0.x * w12.y);
|
|
7600
|
+
result += frameSample(colorTexture, vec2f(center.x + 2.0, p12.y)) * (w3.x * w12.y);
|
|
7601
|
+
result += frameSample(colorTexture, vec2f(p12.x, center.y - 1.0)) * (w12.x * w0.y);
|
|
7602
|
+
result += frameSample(colorTexture, vec2f(p12.x, center.y + 2.0)) * (w12.x * w3.y);
|
|
7603
|
+
return result / (w12.x * w12.y + (w0.x + w3.x) * w12.y + w12.x * (w0.y + w3.y));
|
|
8343
7604
|
}
|
|
8344
7605
|
`
|
|
8345
7606
|
);
|
|
@@ -8382,7 +7643,7 @@ fn blockFiltered(block: vec2i, color: vec4f) -> vec4f {
|
|
|
8382
7643
|
sum += wx * wy * frameSample(colorTexture, c + vec2f(f32(x), f32(y)) * (11.0 / 6.0));
|
|
8383
7644
|
}
|
|
8384
7645
|
}
|
|
8385
|
-
return mix(color, sum,
|
|
7646
|
+
return mix(color, sum, ${float(weights.compactMix[1])});
|
|
8386
7647
|
}
|
|
8387
7648
|
`
|
|
8388
7649
|
) : (
|
|
@@ -8628,7 +7889,7 @@ ${groups.map(
|
|
|
8628
7889
|
// half-resolution result.
|
|
8629
7890
|
@group(0) @binding(11) var wideTexture: texture_2d<f32>;
|
|
8630
7891
|
@group(0) @binding(12) var depthPrevious: texture_2d<f32>;
|
|
8631
|
-
${weights.compact ?
|
|
7892
|
+
${weights.compact ? MEAN_3X3 : QUALITY_RESAMPLING}${BLOCKS}
|
|
8632
7893
|
// The frame's existing 4x4 mean, used only for rejected surfaces.
|
|
8633
7894
|
@group(0) @binding(13) var rejectedTexture: texture_2d<f32>;
|
|
8634
7895
|
${COARSE}
|
|
@@ -8661,8 +7922,8 @@ struct Output {
|
|
|
8661
7922
|
};
|
|
8662
7923
|
@fragment fn fragment(@builtin(position) position: vec4f) -> Output {
|
|
8663
7924
|
let p: vec2i = vec2i(position.xy);
|
|
8664
|
-
let d: vec4f = ${weights.compact ? "mix(frameLoad(colorTexture, p), compactFiltered(position.xy), 0.5)" : "textureLoad(filteredTexture, p, 0)"};
|
|
8665
7925
|
let color: vec4f = frameLoad(colorTexture, p);
|
|
7926
|
+
${weights.compact ? "" : " let d: vec4f = textureLoad(filteredTexture, p, 0);"}
|
|
8666
7927
|
var sr: vec4f = vec4f(0.0);
|
|
8667
7928
|
var dr: vec4f = vec4f(0.0);
|
|
8668
7929
|
// Second moment of luma, the two history lengths, slow low-pass luma.
|
|
@@ -8707,14 +7968,14 @@ struct Output {
|
|
|
8707
7968
|
a = textureLoad(previousAux, p, 0);
|
|
8708
7969
|
previous.value = textureLoad(previousOutput, p, 0);
|
|
8709
7970
|
} else {
|
|
8710
|
-
${weights.compact ? `
|
|
8711
|
-
|
|
8712
|
-
|
|
8713
|
-
|
|
8714
|
-
|
|
8715
|
-
|
|
7971
|
+
${weights.compact ? ` // The accumulated history keeps many frames while the camera moves
|
|
7972
|
+
// slowly: read bilinearly, it lost detail with every one of them.
|
|
7973
|
+
sr = max(catmullRom(previousAccumulated, h.uv), vec4f(0.0));
|
|
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));
|
|
7977
|
+
dr = max(catmullRomNine(previousDenoised, h.uv), vec4f(0.0));`}
|
|
8716
7978
|
a = previousStatistics(h.uv);
|
|
8717
|
-
previous.value = lanczosCross(previousOutput, interpolation);`}
|
|
8718
7979
|
}
|
|
8719
7980
|
// Where a block has no depth of its own, where to read its history is a
|
|
8720
7981
|
// guess, so while the camera moves it keeps little of the accumulated
|
|
@@ -8725,6 +7986,17 @@ ${weights.compact ? ` sr = max(textureSampleLevel(previousAccumulated, line
|
|
|
8725
7986
|
}
|
|
8726
7987
|
${weights.correction ? "var" : "let"} trust: vec4f = coarse(trustBlocks, position.xy, 4.0) * valid * same;
|
|
8727
7988
|
${weights.correction ? "var" : "let"} strength: f32 = coarse(strengthBlocks, position.xy, 4.0).r;
|
|
7989
|
+
${weights.compact ? ` // A frame drawn with projection jitter shows at pixel p what lies at
|
|
7990
|
+
// p + jitter. Performance reads the frame at p - jitter instead, with one
|
|
7991
|
+
// bilinear tap that also averages up to four samples, and takes its fixed
|
|
7992
|
+
// filter there: the slow luma, the correction and the denoised path see
|
|
7993
|
+
// the frame where it belongs. The filter takes its full share of the 3x3
|
|
7994
|
+
// mean where the view moves or the denoised history is short, and the
|
|
7995
|
+
// smaller one where that history settles, which keeps more detail.
|
|
7996
|
+
let unjittered: vec2f = position.xy - P.jitter;
|
|
7997
|
+
let shifted: vec4f = frameSample(colorTexture, unjittered);
|
|
7998
|
+
let fullShare: f32 = max(speed * speed / (speed * speed + ${float(weights.movingKnee * weights.movingKnee)}), exp(-aux.z * trust.y / ${float(weights.reach)}));
|
|
7999
|
+
let d: vec4f = mix(shifted, mean3x3(colorTexture, unjittered, P.colorOffset), mix(${float(weights.compactMix[0])}, ${float(weights.compactMix[1])}, fullShare));` : ""}
|
|
8728
8000
|
${weights.correction ? ` let corrected: vec4f = correctedPixels(color, d, sr, dr, aux, trust, strength);
|
|
8729
8001
|
trust = vec4f(corrected.xy, trust.z, corrected.z);
|
|
8730
8002
|
strength = corrected.w;` : ""}
|
|
@@ -8752,7 +8024,19 @@ ${weights.correction ? ` let corrected: vec4f = correctedPixels(color, d, sr, d
|
|
|
8752
8024
|
// nothing, which the learned filter leaves standing at any camera speed.
|
|
8753
8025
|
let short: f32 = max(exp(-wh / ${float(weights.reach)}) * max(fast, sparse), 1.0 - same);` : `let short: f32 = max(exp(-wh / ${float(weights.reach)}) * fast, 1.0 - same);`}
|
|
8754
8026
|
let wide: vec4f = ${weights.compact ? "mix(coarse(wideTexture, position.xy, 2.0), coarse(rejectedTexture, position.xy, 4.0), 0.5)" : "coarse(wideTexture, position.xy, 2.0)"};
|
|
8755
|
-
|
|
8027
|
+
${weights.compact ? ` let source: vec4f = shifted;
|
|
8028
|
+
let filtered: vec4f = d;` : ` // A frame drawn with projection jitter shows at pixel p what lies at
|
|
8029
|
+
// p + jitter. Where history is short the denoised path carries the frame,
|
|
8030
|
+
// and moving content would shift back and forth by up to half a pixel: the
|
|
8031
|
+
// path reads the frame and its filtered image at p - jitter instead.
|
|
8032
|
+
var source: vec4f = color;
|
|
8033
|
+
var filtered: vec4f = d;
|
|
8034
|
+
if (P.jitter.x != 0.0 || P.jitter.y != 0.0) {
|
|
8035
|
+
let unjittered: vec2f = position.xy - P.jitter;
|
|
8036
|
+
source = frameCatmullRom(unjittered);
|
|
8037
|
+
filtered = catmullRom(filteredTexture, unjittered * P.invSize);
|
|
8038
|
+
}`}
|
|
8039
|
+
var current: vec4f = mix(mix(source, filtered, strength), wide, short);
|
|
8756
8040
|
// Strengthen only the share whose history belonged to another surface.
|
|
8757
8041
|
// Normal fast motion keeps the learned fallback's detail.
|
|
8758
8042
|
let rejected: vec4f = coarse(rejectedTexture, position.xy, 4.0);
|
|
@@ -9312,7 +8596,7 @@ P.historyValid = ndUniform7.z; P.stabilize = ndUniform7.w;
|
|
|
9312
8596
|
P.halfBias = ndUniform8.x; P.logDepth = ndUniform8.y;
|
|
9313
8597
|
P.logNear = ndUniform8.z; P.logFar = ndUniform8.w;
|
|
9314
8598
|
P.turn = ndUniform9.x; P.cameraStill = ndUniform9.y; P.offset = ndUniform9.zw;
|
|
9315
|
-
P.colorOffset = ndUniform10.xy;
|
|
8599
|
+
P.colorOffset = ndUniform10.xy; P.jitter = ndUniform10.zw;
|
|
9316
8600
|
`
|
|
9317
8601
|
);
|
|
9318
8602
|
class WebGLDenoisePasses extends NeuralDenoisePasses {
|
|
@@ -9390,6 +8674,7 @@ class WebGLDenoisePasses extends NeuralDenoisePasses {
|
|
|
9390
8674
|
}
|
|
9391
8675
|
allocate(views, depth2) {
|
|
9392
8676
|
var _a2, _b;
|
|
8677
|
+
if (depth2) depth2.texture.renderTarget = null;
|
|
9393
8678
|
const target = new this.Target(
|
|
9394
8679
|
((_a2 = views[0]) == null ? void 0 : _a2.width) ?? this.width,
|
|
9395
8680
|
((_b = views[0]) == null ? void 0 : _b.height) ?? this.height,
|
|
@@ -9563,6 +8848,7 @@ const quality = {
|
|
|
9563
8848
|
denoisedFrames: 24,
|
|
9564
8849
|
gateFrames: 8,
|
|
9565
8850
|
motionKnee: 20,
|
|
8851
|
+
movingKnee: 0.5,
|
|
9566
8852
|
stabilizeBlend: 0.9,
|
|
9567
8853
|
stabilizeSlack: 0.04,
|
|
9568
8854
|
evidenceFrames: [3, 6, 12, 24],
|
|
@@ -11603,8 +10889,55 @@ const quality = {
|
|
|
11603
10889
|
};
|
|
11604
10890
|
const neuralDenoiseModels = {
|
|
11605
10891
|
quality,
|
|
11606
|
-
performance: { ...quality, compact: true }
|
|
10892
|
+
performance: { ...quality, compact: true, compactMix: [0.5, 0.75] }
|
|
11607
10893
|
};
|
|
10894
|
+
class StochasticCoverage {
|
|
10895
|
+
constructor() {
|
|
10896
|
+
__publicField(this, "materials", /* @__PURE__ */ new Map());
|
|
10897
|
+
}
|
|
10898
|
+
material(source, renderer) {
|
|
10899
|
+
let entry = this.materials.get(source);
|
|
10900
|
+
if (!entry) {
|
|
10901
|
+
const variant = createStochasticSplatVariant(source, renderer);
|
|
10902
|
+
if (!variant) return source;
|
|
10903
|
+
const dispose = () => {
|
|
10904
|
+
source.removeEventListener("dispose", dispose);
|
|
10905
|
+
variant.dispose();
|
|
10906
|
+
this.materials.delete(source);
|
|
10907
|
+
};
|
|
10908
|
+
entry = { variant, state: new MaterialState(source), dispose };
|
|
10909
|
+
this.materials.set(source, entry);
|
|
10910
|
+
source.addEventListener("dispose", dispose);
|
|
10911
|
+
}
|
|
10912
|
+
entry.state.sync(entry.variant, source);
|
|
10913
|
+
return entry.variant;
|
|
10914
|
+
}
|
|
10915
|
+
render(renderer, scene, camera) {
|
|
10916
|
+
if (!usesNativeWebGPU(renderer) && !renderer.getContext().getExtension("OES_sample_variables"))
|
|
10917
|
+
throw new Error(
|
|
10918
|
+
"Stochastic rendering with MSAA requires OES_sample_variables on WebGL2; use msaa = false on this device"
|
|
10919
|
+
);
|
|
10920
|
+
const nodeRenderer = renderer.isWebGPURenderer === true;
|
|
10921
|
+
const previous = nodeRenderer ? renderer.getRenderObjectFunction() : renderer.renderBufferDirect;
|
|
10922
|
+
const draw = previous ?? renderer.renderObject;
|
|
10923
|
+
const materialIndex = nodeRenderer ? 4 : 3;
|
|
10924
|
+
const hook = (...args) => {
|
|
10925
|
+
args[materialIndex] = this.material(args[materialIndex], renderer);
|
|
10926
|
+
return draw.apply(renderer, args);
|
|
10927
|
+
};
|
|
10928
|
+
if (nodeRenderer) renderer.setRenderObjectFunction(hook);
|
|
10929
|
+
else renderer.renderBufferDirect = hook;
|
|
10930
|
+
try {
|
|
10931
|
+
renderer.render(scene, camera);
|
|
10932
|
+
} finally {
|
|
10933
|
+
if (nodeRenderer) renderer.setRenderObjectFunction(previous);
|
|
10934
|
+
else renderer.renderBufferDirect = previous;
|
|
10935
|
+
}
|
|
10936
|
+
}
|
|
10937
|
+
dispose() {
|
|
10938
|
+
for (const entry of this.materials.values()) entry.dispose();
|
|
10939
|
+
}
|
|
10940
|
+
}
|
|
11608
10941
|
const MARGIN = 3;
|
|
11609
10942
|
const VIEW_OFFSETS = [
|
|
11610
10943
|
[2, 2],
|
|
@@ -11662,12 +10995,7 @@ class NeuralDenoiseNode extends webgpu.Node {
|
|
|
11662
10995
|
});
|
|
11663
10996
|
return color;
|
|
11664
10997
|
})());
|
|
11665
|
-
__publicField(this, "state",
|
|
11666
|
-
(value) => ({ value }),
|
|
11667
|
-
[this.source, ...this.history],
|
|
11668
|
-
true,
|
|
11669
|
-
MARGIN
|
|
11670
|
-
));
|
|
10998
|
+
__publicField(this, "state", createNeuralDenoiseState([this.source, ...this.history], MARGIN));
|
|
11671
10999
|
__publicField(this, "passes", null);
|
|
11672
11000
|
// The scene target's depth has the margin; this one has the image's size.
|
|
11673
11001
|
__publicField(this, "depth", new THREE.DepthTexture(1, 1, THREE.FloatType));
|
|
@@ -11705,7 +11033,7 @@ class NeuralDenoiseNode extends webgpu.Node {
|
|
|
11705
11033
|
}
|
|
11706
11034
|
/** The projection `depthTexture` was rendered with. */
|
|
11707
11035
|
get projectionMatrix() {
|
|
11708
|
-
return this.state.
|
|
11036
|
+
return this.state.projection;
|
|
11709
11037
|
}
|
|
11710
11038
|
/** The denoised image, for chaining effects. */
|
|
11711
11039
|
getTextureNode() {
|
|
@@ -11773,7 +11101,7 @@ class NeuralDenoiseNode extends webgpu.Node {
|
|
|
11773
11101
|
renderer.toneMapping = THREE.NoToneMapping;
|
|
11774
11102
|
const reversedDepth = camera.reversedDepth;
|
|
11775
11103
|
const coordinateSystem = camera.coordinateSystem;
|
|
11776
|
-
const { x: width, y: height } = state.
|
|
11104
|
+
const { x: width, y: height } = state.size;
|
|
11777
11105
|
const [offsetX, offsetY] = VIEW_OFFSETS[Math.floor(this.frame / 32) % VIEW_OFFSETS.length];
|
|
11778
11106
|
this.frame++;
|
|
11779
11107
|
this.source.viewport.set(offsetX, offsetY, width, height);
|
|
@@ -11787,11 +11115,11 @@ class NeuralDenoiseNode extends webgpu.Node {
|
|
|
11787
11115
|
if (camera.reversedDepth !== reversedDepth || camera.coordinateSystem !== coordinateSystem)
|
|
11788
11116
|
camera.updateProjectionMatrix();
|
|
11789
11117
|
}
|
|
11790
|
-
const valid = state.
|
|
11118
|
+
const valid = state.valid;
|
|
11791
11119
|
if (!valid && native)
|
|
11792
11120
|
for (const target of this.history) renderer.initRenderTarget(target);
|
|
11793
11121
|
const u = passes.uniforms;
|
|
11794
|
-
const jittered = state.
|
|
11122
|
+
const jittered = state.projection.elements;
|
|
11795
11123
|
const logarithmic = (nodeRenderer ? renderer.logarithmicDepthBuffer : renderer.capabilities.logarithmicDepthBuffer) && "isPerspectiveCamera" in camera && camera.isPerspectiveCamera;
|
|
11796
11124
|
const near = Math.max(camera.near, 1e-6);
|
|
11797
11125
|
const reversed = nodeRenderer ? renderer.reversedDepthBuffer : renderer.capabilities.reversedDepthBuffer;
|
|
@@ -11833,6 +11161,17 @@ class NeuralDenoiseNode extends webgpu.Node {
|
|
|
11833
11161
|
this.source.texture.colorSpace || this.workingColorSpace
|
|
11834
11162
|
) === THREE.LinearTransfer;
|
|
11835
11163
|
u.set(encode ? [0, 0] : [u[62], u[63]], 64);
|
|
11164
|
+
const p = camera.projectionMatrix.elements;
|
|
11165
|
+
let jitterX = 0;
|
|
11166
|
+
let jitterY = 0;
|
|
11167
|
+
if ([8, 9, 12, 13].some((i) => jittered[i] !== p[i])) {
|
|
11168
|
+
const x = (jittered[8] - jittered[12]) / jittered[0];
|
|
11169
|
+
const y = (jittered[9] - jittered[13]) / jittered[5];
|
|
11170
|
+
const w = p[3] * x + p[7] * y - p[11] + p[15];
|
|
11171
|
+
jitterX = (p[0] * x + p[4] * y - p[8] + p[12]) / w * width / 2;
|
|
11172
|
+
jitterY = -((p[1] * x + p[5] * y - p[9] + p[13]) / w) * height / 2;
|
|
11173
|
+
}
|
|
11174
|
+
u.set([jitterX, jitterY], 66);
|
|
11836
11175
|
this.encoded.value = THREE.ColorManagement.getTransfer(this.workingColorSpace) === THREE.LinearTransfer;
|
|
11837
11176
|
const gpu = (texture) => {
|
|
11838
11177
|
const data = backend.get(texture).texture;
|
|
@@ -11865,7 +11204,7 @@ class NeuralDenoiseNode extends webgpu.Node {
|
|
|
11865
11204
|
this.textureNode.value = native ? this.history[1 - previous].texture : passes.result;
|
|
11866
11205
|
this.previousWorld.copy(camera.matrixWorld);
|
|
11867
11206
|
this.previousProjection.copy(camera.projectionMatrix);
|
|
11868
|
-
state.advance(
|
|
11207
|
+
state.advance();
|
|
11869
11208
|
this.capturePending = false;
|
|
11870
11209
|
} finally {
|
|
11871
11210
|
renderer.setRenderTarget(
|
|
@@ -11950,7 +11289,7 @@ void main() {
|
|
|
11950
11289
|
vec4 color = texture2D(image, vUv);
|
|
11951
11290
|
if (encoded) {
|
|
11952
11291
|
// OutputPass applies its transfer directly to premultiplied RGB. Invert
|
|
11953
|
-
// that transfer here
|
|
11292
|
+
// that transfer here to round-trip transparent pixels.
|
|
11954
11293
|
float scale = outputSRGB ? 1.0 : max(color.a, 1e-6);
|
|
11955
11294
|
color.rgb = sRGBTransferEOTF(vec4(max(color.rgb / scale, 0.0), 1.0)).rgb * scale;
|
|
11956
11295
|
}
|
|
@@ -12024,6 +11363,175 @@ void main() {
|
|
|
12024
11363
|
this.quad.dispose();
|
|
12025
11364
|
}
|
|
12026
11365
|
}
|
|
11366
|
+
const EYE_PATTERN_OFFSET = 16;
|
|
11367
|
+
class NeuralDenoiseXR {
|
|
11368
|
+
constructor(renderer, scene, camera, mode = "quality", msaa = false) {
|
|
11369
|
+
__publicField(this, "stabilize", true);
|
|
11370
|
+
__publicField(this, "views", []);
|
|
11371
|
+
__publicField(this, "output", null);
|
|
11372
|
+
assertSupportedRenderer(renderer);
|
|
11373
|
+
this.renderer = renderer;
|
|
11374
|
+
this.scene = scene;
|
|
11375
|
+
this.camera = camera;
|
|
11376
|
+
this.mode = mode;
|
|
11377
|
+
this.msaa = msaa;
|
|
11378
|
+
this.nodeRenderer = isWebGPURenderer(renderer);
|
|
11379
|
+
if (this.nodeRenderer) applyThreeR186Patch(renderer);
|
|
11380
|
+
this.onSessionEnd = () => this.disposeViews();
|
|
11381
|
+
renderer.xr.addEventListener("sessionend", this.onSessionEnd);
|
|
11382
|
+
}
|
|
11383
|
+
createViews(eyes) {
|
|
11384
|
+
this.disposeViews();
|
|
11385
|
+
this.views = eyes.map((eye) => {
|
|
11386
|
+
const camera = new THREE.PerspectiveCamera();
|
|
11387
|
+
const pass = this.nodeRenderer ? new NeuralDenoiseNode(this.scene, camera, this.mode, this.msaa) : new NeuralDenoisePass(this.scene, camera, this.mode, this.msaa);
|
|
11388
|
+
const denoise = this.nodeRenderer ? pass : pass.denoise;
|
|
11389
|
+
denoise.updateBeforeType = webgpu.NodeUpdateType.NONE;
|
|
11390
|
+
return { eye, pass, denoise };
|
|
11391
|
+
});
|
|
11392
|
+
if (this.nodeRenderer) {
|
|
11393
|
+
const material = new webgpu.NodeMaterial();
|
|
11394
|
+
material.name = "NeuralDenoiseXR.output";
|
|
11395
|
+
material.vertexNode = N__namespace.vec4(N__namespace.positionGeometry.xy, 0, 1);
|
|
11396
|
+
const color = N__namespace.Fn((builder) => {
|
|
11397
|
+
const index2 = viewIndex(builder.camera);
|
|
11398
|
+
const color2 = N__namespace.vec4(0).toVar();
|
|
11399
|
+
this.views.forEach((view, i) => {
|
|
11400
|
+
N__namespace.If(index2.equal(i), () => {
|
|
11401
|
+
color2.assign(view.denoise.getTextureNode());
|
|
11402
|
+
});
|
|
11403
|
+
});
|
|
11404
|
+
return color2;
|
|
11405
|
+
})();
|
|
11406
|
+
material.fragmentNode = N__namespace.renderOutput(
|
|
11407
|
+
color,
|
|
11408
|
+
this.renderer.toneMapping,
|
|
11409
|
+
this.renderer.outputColorSpace
|
|
11410
|
+
);
|
|
11411
|
+
material.depthTest = false;
|
|
11412
|
+
material.depthWrite = false;
|
|
11413
|
+
material.blending = THREE.NoBlending;
|
|
11414
|
+
this.output = new webgpu.QuadMesh(material);
|
|
11415
|
+
} else {
|
|
11416
|
+
this.output = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), this.views[0].pass.copy);
|
|
11417
|
+
this.output.onBeforeRender = (renderer, scene, camera) => {
|
|
11418
|
+
const { denoise } = this.views.find((view) => view.eye === camera);
|
|
11419
|
+
const uniforms = this.output.material.uniforms;
|
|
11420
|
+
uniforms.image.value = denoise.textureNode.value;
|
|
11421
|
+
uniforms.encoded.value = denoise.encoded.value;
|
|
11422
|
+
uniforms.outputSRGB.value = THREE.ColorManagement.getTransfer(renderer.outputColorSpace) === THREE.SRGBTransfer;
|
|
11423
|
+
this.output.material.uniformsNeedUpdate = true;
|
|
11424
|
+
};
|
|
11425
|
+
}
|
|
11426
|
+
this.output.frustumCulled = false;
|
|
11427
|
+
this.output.layers.enableAll();
|
|
11428
|
+
}
|
|
11429
|
+
render() {
|
|
11430
|
+
const { renderer } = this;
|
|
11431
|
+
const { xr } = renderer;
|
|
11432
|
+
if (!xr.enabled || !xr.isPresenting) {
|
|
11433
|
+
throw new Error(
|
|
11434
|
+
"NeuralDenoiseXR.render() requires an active WebXR session"
|
|
11435
|
+
);
|
|
11436
|
+
}
|
|
11437
|
+
if (xr.cameraAutoUpdate) {
|
|
11438
|
+
this.camera.updateWorldMatrix(true, false);
|
|
11439
|
+
xr.updateCamera(this.camera);
|
|
11440
|
+
}
|
|
11441
|
+
const eyes = xr.getCamera().cameras;
|
|
11442
|
+
if (eyes.length === 0) return;
|
|
11443
|
+
if (eyes.length !== this.views.length || eyes.some((eye, i) => eye !== this.views[i].eye))
|
|
11444
|
+
this.createViews(eyes);
|
|
11445
|
+
const target = renderer.getRenderTarget();
|
|
11446
|
+
const cubeFace = renderer.getActiveCubeFace();
|
|
11447
|
+
const mipLevel = renderer.getActiveMipmapLevel();
|
|
11448
|
+
const cameraAutoUpdate = xr.cameraAutoUpdate;
|
|
11449
|
+
const autoClear = renderer.autoClear;
|
|
11450
|
+
const toneMapping = renderer.toneMapping;
|
|
11451
|
+
const outputColorSpace = renderer.outputColorSpace;
|
|
11452
|
+
const outputTarget = this.nodeRenderer ? renderer.getOutputRenderTarget() : null;
|
|
11453
|
+
const outputDepth = outputTarget == null ? void 0 : outputTarget.depthBuffer;
|
|
11454
|
+
const samples = /* @__PURE__ */ new Map();
|
|
11455
|
+
this.scene.traverse((object) => {
|
|
11456
|
+
if (object instanceof GaussianSplatRenderer && object.autoAdvanceStochasticSample)
|
|
11457
|
+
samples.set(object, object.stochasticSample + 1 >>> 0);
|
|
11458
|
+
});
|
|
11459
|
+
try {
|
|
11460
|
+
xr.cameraAutoUpdate = false;
|
|
11461
|
+
for (const splat of samples.keys())
|
|
11462
|
+
splat.autoAdvanceStochasticSample = false;
|
|
11463
|
+
for (const [index2, { eye, denoise }] of this.views.entries()) {
|
|
11464
|
+
const { camera } = denoise;
|
|
11465
|
+
for (const [splat, sample] of samples)
|
|
11466
|
+
splat.stochasticSample = sample + index2 * EYE_PATTERN_OFFSET >>> 0;
|
|
11467
|
+
camera.copy(eye, false);
|
|
11468
|
+
camera.matrixAutoUpdate = false;
|
|
11469
|
+
camera.matrixWorldAutoUpdate = false;
|
|
11470
|
+
camera.coordinateSystem = renderer.coordinateSystem;
|
|
11471
|
+
const reversed = this.nodeRenderer ? renderer.reversedDepthBuffer : renderer.capabilities.reversedDepthBuffer;
|
|
11472
|
+
camera._reversedDepth = reversed;
|
|
11473
|
+
if (reversed) {
|
|
11474
|
+
const p = camera.projectionMatrix.elements;
|
|
11475
|
+
const scale = usesNativeWebGPU(renderer) ? 1 : 0.5;
|
|
11476
|
+
for (let i = 0; i < 16; i += 4)
|
|
11477
|
+
p[i + 2] = scale * (p[i + 3] - p[i + 2]);
|
|
11478
|
+
camera.projectionMatrixInverse.copy(camera.projectionMatrix).invert();
|
|
11479
|
+
}
|
|
11480
|
+
denoise.scene = this.scene;
|
|
11481
|
+
denoise.stabilize = this.stabilize;
|
|
11482
|
+
denoise.setSize(eye.viewport.z, eye.viewport.w);
|
|
11483
|
+
renderer.setRenderTarget(denoise.history[0]);
|
|
11484
|
+
denoise.updateBefore({ renderer });
|
|
11485
|
+
}
|
|
11486
|
+
renderer.setRenderTarget(target, cubeFace, mipLevel);
|
|
11487
|
+
if (this.nodeRenderer) {
|
|
11488
|
+
const output = this.output.material.fragmentNode;
|
|
11489
|
+
if (output.getToneMapping() !== toneMapping || output.outputColorSpace !== outputColorSpace) {
|
|
11490
|
+
this.output.material.fragmentNode = N__namespace.renderOutput(
|
|
11491
|
+
output.colorNode,
|
|
11492
|
+
toneMapping,
|
|
11493
|
+
outputColorSpace
|
|
11494
|
+
);
|
|
11495
|
+
this.output.material.needsUpdate = true;
|
|
11496
|
+
}
|
|
11497
|
+
renderer.toneMapping = THREE.NoToneMapping;
|
|
11498
|
+
renderer.outputColorSpace = THREE.ColorManagement.workingColorSpace;
|
|
11499
|
+
if (usesNativeWebGPU(renderer) && (outputTarget == null ? void 0 : outputTarget.useArrayDepthTexture))
|
|
11500
|
+
outputTarget.depthBuffer = true;
|
|
11501
|
+
}
|
|
11502
|
+
renderer.render(this.output, this.camera);
|
|
11503
|
+
} finally {
|
|
11504
|
+
for (const [splat, sample] of samples) {
|
|
11505
|
+
splat.stochasticSample = sample;
|
|
11506
|
+
splat.autoAdvanceStochasticSample = true;
|
|
11507
|
+
}
|
|
11508
|
+
xr.enabled = true;
|
|
11509
|
+
xr.cameraAutoUpdate = cameraAutoUpdate;
|
|
11510
|
+
renderer.autoClear = autoClear;
|
|
11511
|
+
renderer.toneMapping = toneMapping;
|
|
11512
|
+
renderer.outputColorSpace = outputColorSpace;
|
|
11513
|
+
if (outputTarget) outputTarget.depthBuffer = outputDepth;
|
|
11514
|
+
renderer.setRenderTarget(target, cubeFace, mipLevel);
|
|
11515
|
+
}
|
|
11516
|
+
}
|
|
11517
|
+
/** Discards both eyes' history after a camera cut or scene replacement. */
|
|
11518
|
+
reset() {
|
|
11519
|
+
for (const { denoise } of this.views) denoise.reset();
|
|
11520
|
+
}
|
|
11521
|
+
disposeViews() {
|
|
11522
|
+
for (const { pass } of this.views) pass.dispose();
|
|
11523
|
+
this.views = [];
|
|
11524
|
+
if (this.output) {
|
|
11525
|
+
if (this.nodeRenderer) this.output.material.dispose();
|
|
11526
|
+
else this.output.geometry.dispose();
|
|
11527
|
+
this.output = null;
|
|
11528
|
+
}
|
|
11529
|
+
}
|
|
11530
|
+
dispose() {
|
|
11531
|
+
this.renderer.xr.removeEventListener("sessionend", this.onSessionEnd);
|
|
11532
|
+
this.disposeViews();
|
|
11533
|
+
}
|
|
11534
|
+
}
|
|
12027
11535
|
class WebGLCapture {
|
|
12028
11536
|
constructor(renderer) {
|
|
12029
11537
|
this.renderer = renderer;
|
|
@@ -17353,6 +16861,7 @@ const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePropert
|
|
|
17353
16861
|
exports.GaussianSplatRenderer = GaussianSplatRenderer;
|
|
17354
16862
|
exports.NeuralDenoiseNode = NeuralDenoiseNode;
|
|
17355
16863
|
exports.NeuralDenoisePass = NeuralDenoisePass;
|
|
16864
|
+
exports.NeuralDenoiseXR = NeuralDenoiseXR;
|
|
17356
16865
|
exports.RadStreamScheduler = RadStreamScheduler;
|
|
17357
16866
|
exports.SogStreamScheduler = SogStreamScheduler;
|
|
17358
16867
|
exports.SplatCapture = SplatCapture;
|
|
@@ -17365,8 +16874,6 @@ exports.SplatFileType = SplatFileType;
|
|
|
17365
16874
|
exports.SplatLoader = SplatLoader;
|
|
17366
16875
|
exports.SplatMesh = SplatMesh;
|
|
17367
16876
|
exports.Splats = Splats;
|
|
17368
|
-
exports.TAANode = TAANode;
|
|
17369
|
-
exports.TAAPass = TAAPass;
|
|
17370
16877
|
exports.defines = defines;
|
|
17371
16878
|
exports.fromHalf = fromHalf;
|
|
17372
16879
|
exports.postDecode = postDecode;
|