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
|
@@ -3,8 +3,8 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
|
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
var _a;
|
|
5
5
|
import * as THREE from "three";
|
|
6
|
-
import { REVISION, FloatType,
|
|
7
|
-
import { TextureNode, NodeUpdateType, NodeMaterial, QuadMesh, Node, StorageArrayTexture, IndirectStorageBufferAttribute, StorageBufferAttribute, RenderTarget,
|
|
6
|
+
import { REVISION, FloatType, Matrix4, Vector2, DepthStencilFormat, DepthFormat, WebGLRenderTarget, Vector4, HalfFloatType, RawShaderMaterial, GLSL3, NoBlending, RGBAFormat, RGFormat, RedFormat, NearestFilter, LinearFilter, NeverDepth, AlwaysDepth, DepthTexture, ColorManagement, Color, NoToneMapping, LinearTransfer, SRGBColorSpace, ShaderMaterial, SRGBTransfer, PerspectiveCamera, Mesh, PlaneGeometry, DefaultLoadingManager, Loader } from "three";
|
|
7
|
+
import { TextureNode, NodeUpdateType, NodeMaterial, QuadMesh, Node, StorageArrayTexture, IndirectStorageBufferAttribute, StorageBufferAttribute, RenderTarget, CubeRenderTarget, PMREMGenerator } from "three/webgpu";
|
|
8
8
|
import * as N from "three/tsl";
|
|
9
9
|
import { FullScreenQuad, Pass } from "three/addons/postprocessing/Pass.js";
|
|
10
10
|
const patchedRenderers = /* @__PURE__ */ new WeakSet();
|
|
@@ -3895,19 +3895,22 @@ function hasViewDependentColor(mapping) {
|
|
|
3895
3895
|
}
|
|
3896
3896
|
const SPLATS_PER_INSTANCE = 128;
|
|
3897
3897
|
class SplatGeometry extends THREE.InstancedBufferGeometry {
|
|
3898
|
-
constructor() {
|
|
3898
|
+
constructor(triangle = false) {
|
|
3899
3899
|
super();
|
|
3900
|
-
const
|
|
3901
|
-
const
|
|
3900
|
+
const corners = triangle ? TRIANGLE_CORNERS : QUAD_CORNERS;
|
|
3901
|
+
const order = triangle ? TRIANGLE_INDICES : QUAD_INDICES;
|
|
3902
|
+
const vertexCount = corners.length / 2;
|
|
3903
|
+
const vertices = new Float32Array(SPLATS_PER_INSTANCE * vertexCount * 3);
|
|
3904
|
+
const indices = new Uint16Array(SPLATS_PER_INSTANCE * order.length);
|
|
3902
3905
|
for (let splat = 0; splat < SPLATS_PER_INSTANCE; splat++) {
|
|
3903
|
-
for (let vertex = 0; vertex <
|
|
3904
|
-
const offset = splat *
|
|
3905
|
-
vertices[offset] =
|
|
3906
|
-
vertices[offset + 1] =
|
|
3906
|
+
for (let vertex = 0; vertex < vertexCount; vertex++) {
|
|
3907
|
+
const offset = (splat * vertexCount + vertex) * 3;
|
|
3908
|
+
vertices[offset] = corners[vertex * 2];
|
|
3909
|
+
vertices[offset + 1] = corners[vertex * 2 + 1];
|
|
3907
3910
|
vertices[offset + 2] = splat;
|
|
3908
3911
|
}
|
|
3909
|
-
for (let index2 = 0; index2 <
|
|
3910
|
-
indices[splat *
|
|
3912
|
+
for (let index2 = 0; index2 < order.length; index2++)
|
|
3913
|
+
indices[splat * order.length + index2] = splat * vertexCount + order[index2];
|
|
3911
3914
|
}
|
|
3912
3915
|
this.setAttribute("position", new THREE.BufferAttribute(vertices, 3));
|
|
3913
3916
|
this.setIndex(new THREE.BufferAttribute(indices, 1));
|
|
@@ -3916,21 +3919,17 @@ class SplatGeometry extends THREE.InstancedBufferGeometry {
|
|
|
3916
3919
|
this.instanceCount = Math.ceil(count / SPLATS_PER_INSTANCE);
|
|
3917
3920
|
}
|
|
3918
3921
|
}
|
|
3919
|
-
const
|
|
3920
|
-
|
|
3922
|
+
const QUAD_CORNERS = new Float32Array([-1, -1, 1, -1, 1, 1, -1, 1]);
|
|
3923
|
+
const TRIANGLE_CORNERS = new Float32Array([
|
|
3924
|
+
-Math.sqrt(3),
|
|
3921
3925
|
-1,
|
|
3922
|
-
|
|
3923
|
-
1,
|
|
3926
|
+
Math.sqrt(3),
|
|
3924
3927
|
-1,
|
|
3925
3928
|
0,
|
|
3926
|
-
|
|
3927
|
-
1,
|
|
3928
|
-
0,
|
|
3929
|
-
-1,
|
|
3930
|
-
1,
|
|
3931
|
-
0
|
|
3929
|
+
2
|
|
3932
3930
|
]);
|
|
3933
3931
|
const QUAD_INDICES = new Uint16Array([0, 2, 1, 0, 3, 2]);
|
|
3932
|
+
const TRIANGLE_INDICES = new Uint16Array([0, 2, 1]);
|
|
3934
3933
|
const variants = /* @__PURE__ */ new WeakMap();
|
|
3935
3934
|
function registerStochasticSplatMaterial(material, createVariant) {
|
|
3936
3935
|
variants.set(material, createVariant);
|
|
@@ -4738,37 +4737,16 @@ class WebGLSplatBackend {
|
|
|
4738
4737
|
this.stochasticMaterial.dispose();
|
|
4739
4738
|
}
|
|
4740
4739
|
}
|
|
4741
|
-
function
|
|
4742
|
-
const uniforms = {
|
|
4743
|
-
renderSize: makeUniform(new Vector2(1, 1)),
|
|
4744
|
-
projection: makeUniform(new Matrix4()),
|
|
4745
|
-
unjitteredProjection: makeUniform(new Matrix4()),
|
|
4746
|
-
previousProjection: makeUniform(new Matrix4()),
|
|
4747
|
-
viewToPreviousClip: makeUniform(new Matrix4()),
|
|
4748
|
-
previousViewToView: makeUniform(new Matrix4()),
|
|
4749
|
-
valid: makeUniform(false),
|
|
4750
|
-
reversed: makeUniform(false),
|
|
4751
|
-
logarithmic: makeUniform(false),
|
|
4752
|
-
// Log-depth parameters: x is the current frame, y is the previous frame.
|
|
4753
|
-
logFar: makeUniform(new Vector2()),
|
|
4754
|
-
logNear: makeUniform(new Vector2()),
|
|
4755
|
-
// Parameters are copied from the owner before each resolve.
|
|
4756
|
-
depthThreshold: makeUniform(0),
|
|
4757
|
-
edgeDepthDiff: makeUniform(0),
|
|
4758
|
-
maxMotionLength: makeUniform(0),
|
|
4759
|
-
useSubpixelCorrection: makeUniform(false),
|
|
4760
|
-
luminanceCoefficients: makeUniform(new Vector3())
|
|
4761
|
-
};
|
|
4740
|
+
function createNeuralDenoiseState(targets, margin) {
|
|
4762
4741
|
const baseProjection = new Matrix4();
|
|
4763
|
-
const previousViewProjection = new Matrix4();
|
|
4764
|
-
const previousWorld = new Matrix4();
|
|
4765
|
-
const viewMatrix = new Matrix4();
|
|
4766
4742
|
return {
|
|
4767
|
-
|
|
4743
|
+
size: new Vector2(1, 1),
|
|
4744
|
+
projection: new Matrix4(),
|
|
4745
|
+
valid: false,
|
|
4768
4746
|
historyIndex: 0,
|
|
4769
4747
|
jitterIndex: 0,
|
|
4770
4748
|
reset() {
|
|
4771
|
-
|
|
4749
|
+
this.valid = false;
|
|
4772
4750
|
this.jitterIndex = 0;
|
|
4773
4751
|
},
|
|
4774
4752
|
setSize(width, height) {
|
|
@@ -4778,10 +4756,9 @@ function createTAAState(makeUniform, targets, nodeLogDepth = false, margin = 0)
|
|
|
4778
4756
|
if (source.width === w + margin && source.height === h + margin) return;
|
|
4779
4757
|
source.setSize(w + margin, h + margin);
|
|
4780
4758
|
for (const target of others) target.setSize(w, h);
|
|
4781
|
-
|
|
4759
|
+
this.size.set(w, h);
|
|
4782
4760
|
this.reset();
|
|
4783
4761
|
},
|
|
4784
|
-
// Only the capture target takes stencil; history stores plain depth.
|
|
4785
4762
|
setStencil(stencil) {
|
|
4786
4763
|
const source = targets[0];
|
|
4787
4764
|
const depthTexture = source.depthTexture;
|
|
@@ -4795,50 +4772,18 @@ function createTAAState(makeUniform, targets, nodeLogDepth = false, margin = 0)
|
|
|
4795
4772
|
camera,
|
|
4796
4773
|
baseProjection,
|
|
4797
4774
|
this.jitterIndex,
|
|
4798
|
-
|
|
4799
|
-
|
|
4775
|
+
this.size.x,
|
|
4776
|
+
this.size.y
|
|
4800
4777
|
);
|
|
4801
4778
|
},
|
|
4802
4779
|
endCapture(camera) {
|
|
4803
|
-
|
|
4780
|
+
this.projection.copy(camera.projectionMatrix);
|
|
4804
4781
|
restoreProjection(camera, baseProjection);
|
|
4805
4782
|
},
|
|
4806
|
-
|
|
4807
|
-
const near = Math.max(camera.near, 1e-6);
|
|
4808
|
-
uniforms.logFar.value.x = Math.log2(
|
|
4809
|
-
nodeLogDepth ? camera.far / near : camera.far + 1
|
|
4810
|
-
);
|
|
4811
|
-
uniforms.logNear.value.x = near;
|
|
4812
|
-
uniforms.unjitteredProjection.value.copy(camera.projectionMatrix);
|
|
4813
|
-
uniforms.viewToPreviousClip.value.multiplyMatrices(
|
|
4814
|
-
previousViewProjection,
|
|
4815
|
-
camera.matrixWorld
|
|
4816
|
-
);
|
|
4817
|
-
uniforms.previousViewToView.value.multiplyMatrices(
|
|
4818
|
-
viewMatrix.copy(camera.matrixWorld).invert(),
|
|
4819
|
-
previousWorld
|
|
4820
|
-
);
|
|
4821
|
-
uniforms.depthThreshold.value = parameters2.depthThreshold;
|
|
4822
|
-
uniforms.edgeDepthDiff.value = parameters2.edgeDepthDiff;
|
|
4823
|
-
uniforms.maxMotionLength.value = parameters2.maxMotionLength;
|
|
4824
|
-
uniforms.useSubpixelCorrection.value = parameters2.useSubpixelCorrection;
|
|
4825
|
-
ColorManagement.getLuminanceCoefficients(
|
|
4826
|
-
uniforms.luminanceCoefficients.value
|
|
4827
|
-
);
|
|
4828
|
-
return uniforms.reversed.value ? NeverDepth : AlwaysDepth;
|
|
4829
|
-
},
|
|
4830
|
-
advance(camera) {
|
|
4831
|
-
previousViewProjection.multiplyMatrices(
|
|
4832
|
-
camera.projectionMatrix,
|
|
4833
|
-
viewMatrix
|
|
4834
|
-
);
|
|
4835
|
-
previousWorld.copy(camera.matrixWorld);
|
|
4836
|
-
uniforms.previousProjection.value.copy(uniforms.projection.value);
|
|
4837
|
-
uniforms.logFar.value.y = uniforms.logFar.value.x;
|
|
4838
|
-
uniforms.logNear.value.y = uniforms.logNear.value.x;
|
|
4783
|
+
advance() {
|
|
4839
4784
|
this.historyIndex = 1 - this.historyIndex;
|
|
4840
|
-
|
|
4841
|
-
this.jitterIndex = (this.jitterIndex + 1) %
|
|
4785
|
+
this.valid = true;
|
|
4786
|
+
this.jitterIndex = (this.jitterIndex + 1) % jitterOffsets.length;
|
|
4842
4787
|
}
|
|
4843
4788
|
};
|
|
4844
4789
|
}
|
|
@@ -4853,16 +4798,16 @@ function halton(index2, base) {
|
|
|
4853
4798
|
}
|
|
4854
4799
|
return result;
|
|
4855
4800
|
}
|
|
4856
|
-
const
|
|
4801
|
+
const jitterOffsets = Array.from({ length: 32 }, (_, i) => [
|
|
4857
4802
|
halton(i + 1, 2) - 0.5,
|
|
4858
4803
|
halton(i + 1, 3) - 0.5
|
|
4859
4804
|
]);
|
|
4860
4805
|
const projections = /* @__PURE__ */ new WeakMap();
|
|
4861
|
-
const
|
|
4806
|
+
const getUnjitteredProjection = (camera) => projections.get(camera);
|
|
4862
4807
|
function jitterProjection(camera, base, index2, width, height) {
|
|
4863
4808
|
base.copy(camera.projectionMatrix);
|
|
4864
4809
|
projections.set(camera, base);
|
|
4865
|
-
const [x, y] =
|
|
4810
|
+
const [x, y] = jitterOffsets[index2];
|
|
4866
4811
|
const elements = camera.projectionMatrix.elements;
|
|
4867
4812
|
for (let column = 0; column < 16; column += 4) {
|
|
4868
4813
|
elements[column] -= 2 * x / width * elements[column + 3];
|
|
@@ -5655,10 +5600,7 @@ class ProjectedSplats {
|
|
|
5655
5600
|
constructor(renderer, uniforms) {
|
|
5656
5601
|
this.renderer = renderer;
|
|
5657
5602
|
this.uniforms = uniforms;
|
|
5658
|
-
this.indirect = new IndirectStorageBufferAttribute(
|
|
5659
|
-
new Uint32Array([SPLATS_PER_INSTANCE * 6, 0, 0, 0, 0]),
|
|
5660
|
-
1
|
|
5661
|
-
);
|
|
5603
|
+
this.indirect = new IndirectStorageBufferAttribute(new Uint32Array(5), 1);
|
|
5662
5604
|
this.error = null;
|
|
5663
5605
|
this.cache = new ProjectionCache();
|
|
5664
5606
|
this.visibleCount = new StorageBufferAttribute(new Uint32Array(1), 1);
|
|
@@ -5698,7 +5640,9 @@ class ProjectedSplats {
|
|
|
5698
5640
|
// RADIX_SORT_MODE_IDS value of the current key encoding.
|
|
5699
5641
|
sortMode: { value: 0 },
|
|
5700
5642
|
// NDC translation the draw applies to unjittered projections.
|
|
5701
|
-
projectionJitter: { value: new THREE.Vector2() }
|
|
5643
|
+
projectionJitter: { value: new THREE.Vector2() },
|
|
5644
|
+
// Indices per instance of the drawn geometry, quads or triangles.
|
|
5645
|
+
indexCount: { value: 0 }
|
|
5702
5646
|
};
|
|
5703
5647
|
const count = N.storage(this.visibleCount, "uint").setName("gslVisibleCount").toReadOnly().element(0);
|
|
5704
5648
|
this.sorter = new WebGPURadixSort(1, this.keys, {
|
|
@@ -5710,13 +5654,16 @@ class ProjectedSplats {
|
|
|
5710
5654
|
this.limits.maxBufferSize
|
|
5711
5655
|
)
|
|
5712
5656
|
});
|
|
5713
|
-
const
|
|
5657
|
+
const drawArgs = N.storage(this.indirect, "uint").setName(
|
|
5658
|
+
"gslDrawIndirect"
|
|
5659
|
+
);
|
|
5714
5660
|
const counter = N.storage(this.visibleCount, "uint").setName("gslVisibleCount").toAtomic();
|
|
5715
5661
|
this.resetCount = N.Fn(() => {
|
|
5716
5662
|
N.atomicStore(counter.element(0), N.uint(0));
|
|
5717
5663
|
})().compute(1, [1]).setName("Splat reset visible count");
|
|
5718
5664
|
this.finish = N.Fn(() => {
|
|
5719
|
-
|
|
5665
|
+
drawArgs.element(0).assign(uniformBinding(this.state, "indexCount", "uint"));
|
|
5666
|
+
drawArgs.element(1).assign(
|
|
5720
5667
|
count.add(SPLATS_PER_INSTANCE - 1).div(N.uint(SPLATS_PER_INSTANCE))
|
|
5721
5668
|
);
|
|
5722
5669
|
})().compute(1, [1]).setName("Splat visible draw arguments");
|
|
@@ -5964,10 +5911,11 @@ class ProjectedSplats {
|
|
|
5964
5911
|
this.state.sortRadial.value = radial;
|
|
5965
5912
|
const jitter = this.state.projectionJitter.value;
|
|
5966
5913
|
jitter.set(0, 0);
|
|
5967
|
-
const unjittered =
|
|
5914
|
+
const unjittered = getUnjitteredProjection(camera);
|
|
5968
5915
|
const monoProjection = !multiView && unjittered && getProjectionJitter(camera.projectionMatrix, unjittered, jitter) ? unjittered : null;
|
|
5969
5916
|
geometry.setIndirect(this.indirect);
|
|
5970
5917
|
geometry.setSplatCount(Math.max(1, accumulator.numSplats));
|
|
5918
|
+
this.state.indexCount.value = geometry.index.count;
|
|
5971
5919
|
const inputs = this.pendingInputs;
|
|
5972
5920
|
let length = 0;
|
|
5973
5921
|
inputs[length++] = accumulator;
|
|
@@ -5976,6 +5924,7 @@ class ProjectedSplats {
|
|
|
5976
5924
|
inputs[length++] = accumulator.viewOrigin.y;
|
|
5977
5925
|
inputs[length++] = accumulator.viewOrigin.z;
|
|
5978
5926
|
inputs[length++] = cameras.length;
|
|
5927
|
+
inputs[length++] = geometry.index.count;
|
|
5979
5928
|
inputs[length++] = stochastic;
|
|
5980
5929
|
inputs[length++] = !stochastic && radial;
|
|
5981
5930
|
inputs[length++] = sortMode;
|
|
@@ -6162,9 +6111,11 @@ class GaussianSplatRenderer extends THREE.Mesh {
|
|
|
6162
6111
|
depthTest: options.depthTest ?? true,
|
|
6163
6112
|
depthWrite: options.depthWrite ?? false
|
|
6164
6113
|
});
|
|
6165
|
-
const
|
|
6114
|
+
const geometries = [new SplatGeometry(), new SplatGeometry(true)];
|
|
6115
|
+
const geometry = geometries[Number(stochastic)];
|
|
6166
6116
|
const material = backend.selectMaterial(stochastic);
|
|
6167
6117
|
super(geometry, material);
|
|
6118
|
+
this.geometries = geometries;
|
|
6168
6119
|
this.materialState = new MaterialState(material);
|
|
6169
6120
|
this.frameCallbacks = true;
|
|
6170
6121
|
this.renderSize = new THREE.Vector2();
|
|
@@ -6268,7 +6219,7 @@ class GaussianSplatRenderer extends THREE.Mesh {
|
|
|
6268
6219
|
accumulator.dispose();
|
|
6269
6220
|
}
|
|
6270
6221
|
this.accumulators.length = 0;
|
|
6271
|
-
this.geometry.dispose();
|
|
6222
|
+
for (const geometry of this.geometries) geometry.dispose();
|
|
6272
6223
|
}
|
|
6273
6224
|
/** Native WebGPU sorts on the GPU before drawing; WebGL sorts in a worker. */
|
|
6274
6225
|
get synchronousSort() {
|
|
@@ -6320,13 +6271,11 @@ class GaussianSplatRenderer extends THREE.Mesh {
|
|
|
6320
6271
|
const active = this.backend.kind === "webgpu" || this.display.numSplats === 0 ? this._stochastic : this.display.hasStochasticSeeds;
|
|
6321
6272
|
if (active === this.stochasticFrame) return;
|
|
6322
6273
|
this.stochasticFrame = active;
|
|
6323
|
-
this.
|
|
6324
|
-
this.
|
|
6325
|
-
|
|
6326
|
-
selectMaterial() {
|
|
6327
|
-
const material = this.backend.selectMaterial(this.stochasticFrame);
|
|
6328
|
-
this.applyMaterialState(this.stochasticFrame, material);
|
|
6274
|
+
this.geometry = this.geometries[Number(active)];
|
|
6275
|
+
const material = this.backend.selectMaterial(active);
|
|
6276
|
+
this.applyMaterialState(active, material);
|
|
6329
6277
|
this.material = material;
|
|
6278
|
+
this.setDirty();
|
|
6330
6279
|
}
|
|
6331
6280
|
applyMaterialState(stochasticActive, material = this.material) {
|
|
6332
6281
|
this.materialState.sync(material, this.material);
|
|
@@ -6877,676 +6826,6 @@ class GaussianSplatRenderer extends THREE.Mesh {
|
|
|
6877
6826
|
function isOpaqueMaterial(material) {
|
|
6878
6827
|
return material.transparent === false && material.blending === THREE.NormalBlending && material.alphaToCoverage === false;
|
|
6879
6828
|
}
|
|
6880
|
-
class StochasticCoverage {
|
|
6881
|
-
constructor() {
|
|
6882
|
-
__publicField(this, "materials", /* @__PURE__ */ new Map());
|
|
6883
|
-
}
|
|
6884
|
-
material(source, renderer) {
|
|
6885
|
-
let entry = this.materials.get(source);
|
|
6886
|
-
if (!entry) {
|
|
6887
|
-
const variant = createStochasticSplatVariant(source, renderer);
|
|
6888
|
-
if (!variant) return source;
|
|
6889
|
-
const dispose = () => {
|
|
6890
|
-
source.removeEventListener("dispose", dispose);
|
|
6891
|
-
variant.dispose();
|
|
6892
|
-
this.materials.delete(source);
|
|
6893
|
-
};
|
|
6894
|
-
entry = { variant, state: new MaterialState(source), dispose };
|
|
6895
|
-
this.materials.set(source, entry);
|
|
6896
|
-
source.addEventListener("dispose", dispose);
|
|
6897
|
-
}
|
|
6898
|
-
entry.state.sync(entry.variant, source);
|
|
6899
|
-
return entry.variant;
|
|
6900
|
-
}
|
|
6901
|
-
render(renderer, scene, camera) {
|
|
6902
|
-
if (!usesNativeWebGPU(renderer) && !renderer.getContext().getExtension("OES_sample_variables"))
|
|
6903
|
-
throw new Error(
|
|
6904
|
-
"Stochastic rendering with MSAA requires OES_sample_variables on WebGL2; use msaa = false on this device"
|
|
6905
|
-
);
|
|
6906
|
-
const nodeRenderer = renderer.isWebGPURenderer === true;
|
|
6907
|
-
const previous = nodeRenderer ? renderer.getRenderObjectFunction() : renderer.renderBufferDirect;
|
|
6908
|
-
const draw = previous ?? renderer.renderObject;
|
|
6909
|
-
const materialIndex = nodeRenderer ? 4 : 3;
|
|
6910
|
-
const hook = (...args) => {
|
|
6911
|
-
args[materialIndex] = this.material(args[materialIndex], renderer);
|
|
6912
|
-
return draw.apply(renderer, args);
|
|
6913
|
-
};
|
|
6914
|
-
if (nodeRenderer) renderer.setRenderObjectFunction(hook);
|
|
6915
|
-
else renderer.renderBufferDirect = hook;
|
|
6916
|
-
try {
|
|
6917
|
-
renderer.render(scene, camera);
|
|
6918
|
-
} finally {
|
|
6919
|
-
if (nodeRenderer) renderer.setRenderObjectFunction(previous);
|
|
6920
|
-
else renderer.renderBufferDirect = previous;
|
|
6921
|
-
}
|
|
6922
|
-
}
|
|
6923
|
-
dispose() {
|
|
6924
|
-
for (const entry of this.materials.values()) entry.dispose();
|
|
6925
|
-
}
|
|
6926
|
-
}
|
|
6927
|
-
const vertexShader$1 = (
|
|
6928
|
-
/* glsl */
|
|
6929
|
-
`
|
|
6930
|
-
out vec2 vUv;
|
|
6931
|
-
void main() {
|
|
6932
|
-
vUv = uv;
|
|
6933
|
-
gl_Position = vec4(position.xy, 0.0, 1.0);
|
|
6934
|
-
}
|
|
6935
|
-
`
|
|
6936
|
-
);
|
|
6937
|
-
const fragmentShader = (
|
|
6938
|
-
/* glsl */
|
|
6939
|
-
`
|
|
6940
|
-
precision highp float;
|
|
6941
|
-
precision highp int;
|
|
6942
|
-
uniform sampler2D source;
|
|
6943
|
-
uniform sampler2D sourceDepth;
|
|
6944
|
-
uniform sampler2D history;
|
|
6945
|
-
uniform sampler2D historyDepth;
|
|
6946
|
-
uniform vec2 renderSize;
|
|
6947
|
-
uniform mat4 projection;
|
|
6948
|
-
uniform mat4 unjitteredProjection;
|
|
6949
|
-
uniform mat4 previousProjection;
|
|
6950
|
-
uniform mat4 viewToPreviousClip;
|
|
6951
|
-
uniform mat4 previousViewToView;
|
|
6952
|
-
uniform bool valid;
|
|
6953
|
-
uniform bool reversed;
|
|
6954
|
-
uniform bool logarithmic;
|
|
6955
|
-
uniform vec2 logFar;
|
|
6956
|
-
uniform float depthThreshold;
|
|
6957
|
-
uniform float edgeDepthDiff;
|
|
6958
|
-
uniform float maxMotionLength;
|
|
6959
|
-
uniform bool useSubpixelCorrection;
|
|
6960
|
-
uniform vec3 luminanceCoefficients;
|
|
6961
|
-
in vec2 vUv;
|
|
6962
|
-
out vec4 fragColor;
|
|
6963
|
-
|
|
6964
|
-
ivec2 bounded(ivec2 p) {
|
|
6965
|
-
return clamp(p, ivec2(0), ivec2(renderSize) - 1);
|
|
6966
|
-
}
|
|
6967
|
-
|
|
6968
|
-
// Homogeneous view coordinates retain far-depth precision and avoid a
|
|
6969
|
-
// world-space round trip.
|
|
6970
|
-
vec4 viewPosition(vec2 uv, float depth, mat4 p, float logFarValue) {
|
|
6971
|
-
float z = reversed ? depth : depth * 2.0 - 1.0;
|
|
6972
|
-
float orientation = reversed ? -1.0 : 1.0;
|
|
6973
|
-
float viewZ = (p[3][2] - z * p[3][3]) * orientation;
|
|
6974
|
-
float viewW = (z * p[2][3] - p[2][2]) * orientation;
|
|
6975
|
-
if (logarithmic) {
|
|
6976
|
-
viewZ = 1.0 - exp2(depth * logFarValue);
|
|
6977
|
-
viewW = 1.0;
|
|
6978
|
-
}
|
|
6979
|
-
float clipW = p[2][3] * viewZ + p[3][3] * viewW;
|
|
6980
|
-
vec2 viewXY = ((uv * 2.0 - 1.0) * clipW - p[2].xy * viewZ
|
|
6981
|
-
- p[3].xy * viewW) / vec2(p[0][0], p[1][1]);
|
|
6982
|
-
return vec4(viewXY, viewZ, viewW);
|
|
6983
|
-
}
|
|
6984
|
-
|
|
6985
|
-
float forwardDepth(vec4 positionView) {
|
|
6986
|
-
vec4 clip = projection * positionView;
|
|
6987
|
-
float depth = clip.z / clip.w;
|
|
6988
|
-
return reversed ? 1.0 - depth : depth * 0.5 + 0.5;
|
|
6989
|
-
}
|
|
6990
|
-
|
|
6991
|
-
vec4 clipAABB(vec4 center, vec4 historyColor, vec4 extent) {
|
|
6992
|
-
vec4 delta = historyColor - center;
|
|
6993
|
-
vec3 unit = abs(delta.rgb / (extent.rgb + 1e-7));
|
|
6994
|
-
float maxUnit = max(unit.r, max(unit.g, unit.b));
|
|
6995
|
-
return maxUnit > 1.0 ? center + delta / maxUnit : historyColor;
|
|
6996
|
-
}
|
|
6997
|
-
|
|
6998
|
-
vec4 flickerReduction(vec4 currentColor, vec4 historyColor, float currentWeight) {
|
|
6999
|
-
vec3 compressedCurrent = currentColor.rgb / (max(currentColor.r, max(currentColor.g, currentColor.b)) + 1.0);
|
|
7000
|
-
vec3 compressedHistory = historyColor.rgb / (max(historyColor.r, max(historyColor.g, historyColor.b)) + 1.0);
|
|
7001
|
-
float weightCurrent = currentWeight / (dot(compressedCurrent, luminanceCoefficients) + 1.0);
|
|
7002
|
-
float weightHistory = (1.0 - currentWeight) / (dot(compressedHistory, luminanceCoefficients) + 1.0);
|
|
7003
|
-
return (currentColor * weightCurrent + historyColor * weightHistory)
|
|
7004
|
-
/ max(weightCurrent + weightHistory, 0.00001);
|
|
7005
|
-
}
|
|
7006
|
-
|
|
7007
|
-
void main() {
|
|
7008
|
-
ivec2 pixel = ivec2(gl_FragCoord.xy);
|
|
7009
|
-
vec4 currentColor = texelFetch(source, pixel, 0);
|
|
7010
|
-
fragColor = currentColor;
|
|
7011
|
-
gl_FragDepth = texelFetch(sourceDepth, pixel, 0).r;
|
|
7012
|
-
if (!valid) return;
|
|
7013
|
-
|
|
7014
|
-
float closestDepth = 2.0;
|
|
7015
|
-
float farthestDepth = -1.0;
|
|
7016
|
-
float closestRawDepth = gl_FragDepth;
|
|
7017
|
-
ivec2 closestPixel = pixel;
|
|
7018
|
-
vec4 moment1 = currentColor;
|
|
7019
|
-
vec4 moment2 = currentColor * currentColor;
|
|
7020
|
-
for (int x = -1; x <= 1; ++x) {
|
|
7021
|
-
for (int y = -1; y <= 1; ++y) {
|
|
7022
|
-
ivec2 p = bounded(pixel + ivec2(x, y));
|
|
7023
|
-
float rawDepth = texelFetch(sourceDepth, p, 0).r;
|
|
7024
|
-
float depth = reversed && !logarithmic ? 1.0 - rawDepth : rawDepth;
|
|
7025
|
-
if (depth < closestDepth) {
|
|
7026
|
-
closestDepth = depth;
|
|
7027
|
-
closestRawDepth = rawDepth;
|
|
7028
|
-
closestPixel = p;
|
|
7029
|
-
}
|
|
7030
|
-
farthestDepth = max(farthestDepth, depth);
|
|
7031
|
-
if (x != 0 || y != 0) {
|
|
7032
|
-
vec4 neighbor = max(texelFetch(source, p, 0), vec4(0.0));
|
|
7033
|
-
moment1 += neighbor;
|
|
7034
|
-
moment2 += neighbor * neighbor;
|
|
7035
|
-
}
|
|
7036
|
-
}
|
|
7037
|
-
}
|
|
7038
|
-
|
|
7039
|
-
vec2 closestUV = (vec2(closestPixel) + 0.5) / renderSize;
|
|
7040
|
-
vec4 positionView = viewPosition(closestUV, closestRawDepth, projection, logFar.x);
|
|
7041
|
-
vec4 previousClip = viewToPreviousClip * positionView;
|
|
7042
|
-
if (previousClip.w <= 0.0) return;
|
|
7043
|
-
vec4 currentClip = unjitteredProjection * positionView;
|
|
7044
|
-
// Exclude projection jitter from camera motion.
|
|
7045
|
-
vec2 offsetUV = (currentClip.xy / currentClip.w - previousClip.xy / previousClip.w) * 0.5;
|
|
7046
|
-
vec2 historyUV = vUv - offsetUV;
|
|
7047
|
-
if (any(lessThan(historyUV, vec2(0.0))) || any(greaterThanEqual(historyUV, vec2(1.0)))) return;
|
|
7048
|
-
|
|
7049
|
-
float oldDepth = texture(historyDepth, historyUV).r;
|
|
7050
|
-
vec4 previousView = viewPosition(historyUV, oldDepth, previousProjection, logFar.y);
|
|
7051
|
-
float previousDepth = forwardDepth(previousViewToView * previousView);
|
|
7052
|
-
if (logarithmic) {
|
|
7053
|
-
// Log depth is monotonic; convert only the extrema for depth thresholds.
|
|
7054
|
-
closestDepth = forwardDepth(positionView);
|
|
7055
|
-
float farthestZ = 1.0 - exp2(farthestDepth * logFar.x);
|
|
7056
|
-
farthestDepth = forwardDepth(vec4(0.0, 0.0, farthestZ, 1.0));
|
|
7057
|
-
}
|
|
7058
|
-
bool isEdge = farthestDepth - closestDepth > edgeDepthDiff;
|
|
7059
|
-
bool isDisocclusion = closestDepth - previousDepth > depthThreshold;
|
|
7060
|
-
if (!isEdge && isDisocclusion) return;
|
|
7061
|
-
|
|
7062
|
-
float motionFactor = clamp(length(offsetUV * renderSize) / maxMotionLength, 0.0, 1.0);
|
|
7063
|
-
float currentWeight = 0.05;
|
|
7064
|
-
if (useSubpixelCorrection) {
|
|
7065
|
-
vec2 phase = fract(offsetUV * renderSize);
|
|
7066
|
-
vec2 weight = max(phase, 1.0 - phase);
|
|
7067
|
-
currentWeight += (1.0 - weight.x * weight.y) / 0.75 * 0.25;
|
|
7068
|
-
}
|
|
7069
|
-
currentWeight = clamp(currentWeight + motionFactor, 0.0, 1.0);
|
|
7070
|
-
float gamma = mix(0.5, 1.0, pow(1.0 - motionFactor, 2.0));
|
|
7071
|
-
vec4 mean = moment1 / 9.0;
|
|
7072
|
-
vec4 extent = sqrt(max(moment2 / 9.0 - mean * mean, vec4(0.0))) * gamma;
|
|
7073
|
-
vec4 historyColor = clipAABB(mean, texture(history, historyUV), extent);
|
|
7074
|
-
fragColor = flickerReduction(currentColor, historyColor, currentWeight);
|
|
7075
|
-
}
|
|
7076
|
-
`
|
|
7077
|
-
);
|
|
7078
|
-
function makeTarget(name) {
|
|
7079
|
-
const target = new WebGLRenderTarget(1, 1, {
|
|
7080
|
-
type: HalfFloatType,
|
|
7081
|
-
depthTexture: new DepthTexture(1, 1, FloatType)
|
|
7082
|
-
});
|
|
7083
|
-
target.texture.name = name;
|
|
7084
|
-
return target;
|
|
7085
|
-
}
|
|
7086
|
-
class TAAPass extends Pass {
|
|
7087
|
-
/** @param msaa Enable 4× MSAA (default false). Fixed for the pass's lifetime. */
|
|
7088
|
-
constructor(scene, camera, msaa = false) {
|
|
7089
|
-
super();
|
|
7090
|
-
this.scene = scene;
|
|
7091
|
-
this.camera = camera;
|
|
7092
|
-
this.msaa = msaa;
|
|
7093
|
-
this._coverage = msaa ? new StochasticCoverage() : null;
|
|
7094
|
-
this.depthThreshold = 5e-4;
|
|
7095
|
-
this.edgeDepthDiff = 1e-3;
|
|
7096
|
-
this.maxMotionLength = 128;
|
|
7097
|
-
this.useSubpixelCorrection = true;
|
|
7098
|
-
this._source = Object.assign(makeTarget("TAAPass.scene"), {
|
|
7099
|
-
isXRRenderTarget: true,
|
|
7100
|
-
samples: msaa ? 4 : 0
|
|
7101
|
-
});
|
|
7102
|
-
this._history = [
|
|
7103
|
-
makeTarget("TAAPass.history0"),
|
|
7104
|
-
makeTarget("TAAPass.history1")
|
|
7105
|
-
];
|
|
7106
|
-
this._clearColor = new Color();
|
|
7107
|
-
this._state = createTAAState(
|
|
7108
|
-
(value) => ({ value }),
|
|
7109
|
-
[this._source, ...this._history]
|
|
7110
|
-
);
|
|
7111
|
-
this._uniforms = {
|
|
7112
|
-
source: { value: this._source.texture },
|
|
7113
|
-
sourceDepth: { value: this._source.depthTexture },
|
|
7114
|
-
history: { value: this._history[0].texture },
|
|
7115
|
-
historyDepth: { value: this._history[0].depthTexture },
|
|
7116
|
-
...this._state.uniforms
|
|
7117
|
-
};
|
|
7118
|
-
this._resolveMaterial = new ShaderMaterial({
|
|
7119
|
-
name: "TAAPass.resolve",
|
|
7120
|
-
uniforms: this._uniforms,
|
|
7121
|
-
vertexShader: vertexShader$1,
|
|
7122
|
-
fragmentShader,
|
|
7123
|
-
glslVersion: GLSL3,
|
|
7124
|
-
blending: NoBlending,
|
|
7125
|
-
depthTest: true,
|
|
7126
|
-
depthWrite: true,
|
|
7127
|
-
toneMapped: false
|
|
7128
|
-
});
|
|
7129
|
-
this._copyMaterial = new ShaderMaterial({
|
|
7130
|
-
name: "TAAPass.copy",
|
|
7131
|
-
uniforms: { source: { value: null }, decode: { value: false } },
|
|
7132
|
-
vertexShader: vertexShader$1,
|
|
7133
|
-
// Inverts OutputPass's transfer, so the two round-trip at any alpha.
|
|
7134
|
-
fragmentShader: (
|
|
7135
|
-
/* glsl */
|
|
7136
|
-
`
|
|
7137
|
-
uniform sampler2D source;
|
|
7138
|
-
uniform bool decode;
|
|
7139
|
-
in vec2 vUv;
|
|
7140
|
-
out vec4 fragColor;
|
|
7141
|
-
void main() {
|
|
7142
|
-
fragColor = texture(source, vUv);
|
|
7143
|
-
if (decode) {
|
|
7144
|
-
vec4 color = vec4(max(fragColor.rgb, 0.0), 1.0);
|
|
7145
|
-
fragColor.rgb = sRGBTransferEOTF(color).rgb;
|
|
7146
|
-
}
|
|
7147
|
-
}
|
|
7148
|
-
`
|
|
7149
|
-
),
|
|
7150
|
-
glslVersion: GLSL3,
|
|
7151
|
-
blending: NoBlending,
|
|
7152
|
-
depthTest: false,
|
|
7153
|
-
depthWrite: false,
|
|
7154
|
-
toneMapped: false
|
|
7155
|
-
});
|
|
7156
|
-
this._quad = new FullScreenQuad(this._resolveMaterial);
|
|
7157
|
-
}
|
|
7158
|
-
/** Current scene depth, before temporal accumulation. Owned by this pass. */
|
|
7159
|
-
get depthTexture() {
|
|
7160
|
-
return this._source.depthTexture;
|
|
7161
|
-
}
|
|
7162
|
-
/** Jittered projection used to capture depthTexture. Treat as read-only. */
|
|
7163
|
-
get projectionMatrix() {
|
|
7164
|
-
return this._uniforms.projection.value;
|
|
7165
|
-
}
|
|
7166
|
-
/** Resize all internal targets in physical pixels; a size change clears history. */
|
|
7167
|
-
setSize(width, height) {
|
|
7168
|
-
this._state.setSize(width, height);
|
|
7169
|
-
}
|
|
7170
|
-
/** Discard history after a camera cut, scene replacement, or color-space change. */
|
|
7171
|
-
reset() {
|
|
7172
|
-
this._state.reset();
|
|
7173
|
-
}
|
|
7174
|
-
/** Render the scene and resolve TAA into the composer's working-space buffer. */
|
|
7175
|
-
render(renderer, writeBuffer) {
|
|
7176
|
-
var _a2;
|
|
7177
|
-
const { camera, _state: state, _uniforms: uniforms } = this;
|
|
7178
|
-
this.setSize(writeBuffer.width, writeBuffer.height);
|
|
7179
|
-
const colorSpace = renderer.outputColorSpace;
|
|
7180
|
-
if (this._source.texture.colorSpace !== colorSpace) {
|
|
7181
|
-
this._source.texture.colorSpace = colorSpace;
|
|
7182
|
-
this.reset();
|
|
7183
|
-
}
|
|
7184
|
-
state.setStencil(((_a2 = renderer.getContextAttributes()) == null ? void 0 : _a2.stencil) === true);
|
|
7185
|
-
const previousTarget = renderer.getRenderTarget();
|
|
7186
|
-
const autoClear = renderer.autoClear;
|
|
7187
|
-
const xrEnabled = renderer.xr.enabled;
|
|
7188
|
-
const toneMapping = renderer.toneMapping;
|
|
7189
|
-
try {
|
|
7190
|
-
renderer.xr.enabled = false;
|
|
7191
|
-
renderer.autoClear = false;
|
|
7192
|
-
renderer.toneMapping = NoToneMapping;
|
|
7193
|
-
const reversedDepth = camera.reversedDepth;
|
|
7194
|
-
state.beginCapture(camera);
|
|
7195
|
-
try {
|
|
7196
|
-
renderer.setRenderTarget(this._source);
|
|
7197
|
-
renderer.setClearColor(
|
|
7198
|
-
renderer.getClearColor(this._clearColor),
|
|
7199
|
-
renderer.getClearAlpha()
|
|
7200
|
-
);
|
|
7201
|
-
renderer.clear();
|
|
7202
|
-
if (this._coverage) this._coverage.render(renderer, this.scene, camera);
|
|
7203
|
-
else renderer.render(this.scene, camera);
|
|
7204
|
-
} finally {
|
|
7205
|
-
state.endCapture(camera);
|
|
7206
|
-
if (camera.reversedDepth !== reversedDepth)
|
|
7207
|
-
camera.updateProjectionMatrix();
|
|
7208
|
-
}
|
|
7209
|
-
const input = this._history[state.historyIndex];
|
|
7210
|
-
const output = this._history[1 - state.historyIndex];
|
|
7211
|
-
if (!uniforms.valid.value) {
|
|
7212
|
-
for (const target of this._history) renderer.initRenderTarget(target);
|
|
7213
|
-
}
|
|
7214
|
-
uniforms.history.value = input.texture;
|
|
7215
|
-
uniforms.historyDepth.value = input.depthTexture;
|
|
7216
|
-
uniforms.reversed.value = renderer.capabilities.reversedDepthBuffer;
|
|
7217
|
-
uniforms.logarithmic.value = renderer.capabilities.logarithmicDepthBuffer && "isPerspectiveCamera" in camera && camera.isPerspectiveCamera;
|
|
7218
|
-
this._resolveMaterial.depthFunc = state.prepareResolve(camera, this);
|
|
7219
|
-
renderer.setRenderTarget(output);
|
|
7220
|
-
this._quad.material = this._resolveMaterial;
|
|
7221
|
-
this._quad.render(renderer);
|
|
7222
|
-
this._copyMaterial.uniforms.source.value = output.texture;
|
|
7223
|
-
this._copyMaterial.uniforms.decode.value = !this.renderToScreen && ColorManagement.getTransfer(colorSpace) === SRGBTransfer;
|
|
7224
|
-
renderer.setRenderTarget(this.renderToScreen ? null : writeBuffer);
|
|
7225
|
-
this._quad.material = this._copyMaterial;
|
|
7226
|
-
this._quad.render(renderer);
|
|
7227
|
-
state.advance(camera);
|
|
7228
|
-
} finally {
|
|
7229
|
-
renderer.setRenderTarget(previousTarget);
|
|
7230
|
-
renderer.autoClear = autoClear;
|
|
7231
|
-
renderer.xr.enabled = xrEnabled;
|
|
7232
|
-
renderer.toneMapping = toneMapping;
|
|
7233
|
-
}
|
|
7234
|
-
}
|
|
7235
|
-
dispose() {
|
|
7236
|
-
var _a2;
|
|
7237
|
-
(_a2 = this._coverage) == null ? void 0 : _a2.dispose();
|
|
7238
|
-
this._source.dispose();
|
|
7239
|
-
for (const target of this._history) target.dispose();
|
|
7240
|
-
this._resolveMaterial.dispose();
|
|
7241
|
-
this._copyMaterial.dispose();
|
|
7242
|
-
this._quad.dispose();
|
|
7243
|
-
}
|
|
7244
|
-
}
|
|
7245
|
-
class TAANode extends Node {
|
|
7246
|
-
static get type() {
|
|
7247
|
-
return "TAANode";
|
|
7248
|
-
}
|
|
7249
|
-
/** @param msaa Enable 4× MSAA (default false). Fixed for the node's lifetime. */
|
|
7250
|
-
constructor(scene, camera, msaa = false) {
|
|
7251
|
-
super("vec4");
|
|
7252
|
-
this.scene = scene;
|
|
7253
|
-
this.camera = camera;
|
|
7254
|
-
this.msaa = msaa;
|
|
7255
|
-
this.coverage = msaa ? new StochasticCoverage() : null;
|
|
7256
|
-
this.depthThreshold = 5e-4;
|
|
7257
|
-
this.edgeDepthDiff = 1e-3;
|
|
7258
|
-
this.maxMotionLength = 128;
|
|
7259
|
-
this.useSubpixelCorrection = true;
|
|
7260
|
-
this.source = this.makeTarget("TAA.scene");
|
|
7261
|
-
this.source.samples = msaa ? 4 : 0;
|
|
7262
|
-
this.source.storeMultisampledColorBuffer = false;
|
|
7263
|
-
this.history = [
|
|
7264
|
-
this.makeTarget("TAA.history0"),
|
|
7265
|
-
this.makeTarget("TAA.history1")
|
|
7266
|
-
];
|
|
7267
|
-
this.historyColor = N.texture(this.history[0].texture, N.screenUV);
|
|
7268
|
-
this.historyDepth = N.texture(this.history[0].depthTexture, N.screenUV);
|
|
7269
|
-
this.textureNode = N.passTexture(this, this.history[0].texture);
|
|
7270
|
-
this.material = new NodeMaterial();
|
|
7271
|
-
this.quad = new QuadMesh(this.material);
|
|
7272
|
-
this.state = createTAAState(
|
|
7273
|
-
N.uniform,
|
|
7274
|
-
[this.source, ...this.history],
|
|
7275
|
-
true
|
|
7276
|
-
);
|
|
7277
|
-
this.zeroToOne = N.uniform(true);
|
|
7278
|
-
this.drawingSize = new Vector2();
|
|
7279
|
-
this.workingColorSpace = ColorManagement.workingColorSpace;
|
|
7280
|
-
this.pipelineStates = /* @__PURE__ */ new WeakSet();
|
|
7281
|
-
this.pipelineRendering = false;
|
|
7282
|
-
this.capturePending = true;
|
|
7283
|
-
this.updateBeforeType = NodeUpdateType.RENDER;
|
|
7284
|
-
this.material.name = "TAA.resolve";
|
|
7285
|
-
this.material.blending = NoBlending;
|
|
7286
|
-
this.material.depthTest = true;
|
|
7287
|
-
this.material.depthWrite = true;
|
|
7288
|
-
this.material.toneMapped = false;
|
|
7289
|
-
this.quad.name = "TAA.resolve";
|
|
7290
|
-
}
|
|
7291
|
-
makeTarget(name) {
|
|
7292
|
-
const target = new RenderTarget(1, 1, {
|
|
7293
|
-
type: HalfFloatType,
|
|
7294
|
-
depthTexture: new DepthTexture(1, 1, FloatType),
|
|
7295
|
-
samples: 0
|
|
7296
|
-
});
|
|
7297
|
-
target.texture.name = name;
|
|
7298
|
-
return target;
|
|
7299
|
-
}
|
|
7300
|
-
get depthTexture() {
|
|
7301
|
-
return this.source.depthTexture;
|
|
7302
|
-
}
|
|
7303
|
-
get projectionMatrix() {
|
|
7304
|
-
return this.state.uniforms.projection.value;
|
|
7305
|
-
}
|
|
7306
|
-
getTextureNode() {
|
|
7307
|
-
return this.textureNode;
|
|
7308
|
-
}
|
|
7309
|
-
setSize(width, height) {
|
|
7310
|
-
this.state.setSize(width, height);
|
|
7311
|
-
}
|
|
7312
|
-
reset() {
|
|
7313
|
-
this.state.reset();
|
|
7314
|
-
}
|
|
7315
|
-
updateBefore(frame) {
|
|
7316
|
-
if (this.pipelineRendering && !this.capturePending) return;
|
|
7317
|
-
const renderer = frame.renderer;
|
|
7318
|
-
if (this.workingColorSpace !== ColorManagement.workingColorSpace) {
|
|
7319
|
-
this.workingColorSpace = ColorManagement.workingColorSpace;
|
|
7320
|
-
this.reset();
|
|
7321
|
-
}
|
|
7322
|
-
const rendererState = RendererUtils.saveRendererState(renderer);
|
|
7323
|
-
const xrEnabled = renderer.xr.enabled;
|
|
7324
|
-
const { camera, state } = this;
|
|
7325
|
-
const { uniforms } = state;
|
|
7326
|
-
if (this.pipelineRendering) {
|
|
7327
|
-
this.setSize(this.drawingSize.x, this.drawingSize.y);
|
|
7328
|
-
} else if (rendererState.renderTarget) {
|
|
7329
|
-
this.setSize(
|
|
7330
|
-
rendererState.renderTarget.width,
|
|
7331
|
-
rendererState.renderTarget.height
|
|
7332
|
-
);
|
|
7333
|
-
} else {
|
|
7334
|
-
renderer.getDrawingBufferSize(this.drawingSize);
|
|
7335
|
-
this.setSize(this.drawingSize.x, this.drawingSize.y);
|
|
7336
|
-
}
|
|
7337
|
-
state.setStencil(renderer.stencil);
|
|
7338
|
-
try {
|
|
7339
|
-
renderer.xr.enabled = false;
|
|
7340
|
-
renderer.setMRT(null);
|
|
7341
|
-
renderer.setScissorTest(false);
|
|
7342
|
-
renderer.autoClear = true;
|
|
7343
|
-
const reversedDepth = camera.reversedDepth;
|
|
7344
|
-
const coordinateSystem = camera.coordinateSystem;
|
|
7345
|
-
state.beginCapture(camera);
|
|
7346
|
-
try {
|
|
7347
|
-
renderer.setRenderTarget(this.source);
|
|
7348
|
-
if (this.coverage) this.coverage.render(renderer, this.scene, camera);
|
|
7349
|
-
else renderer.render(this.scene, camera);
|
|
7350
|
-
} finally {
|
|
7351
|
-
state.endCapture(camera);
|
|
7352
|
-
if (camera.reversedDepth !== reversedDepth || camera.coordinateSystem !== coordinateSystem)
|
|
7353
|
-
camera.updateProjectionMatrix();
|
|
7354
|
-
}
|
|
7355
|
-
uniforms.reversed.value = renderer.reversedDepthBuffer;
|
|
7356
|
-
this.zeroToOne.value = renderer.coordinateSystem !== WebGLCoordinateSystem || renderer.reversedDepthBuffer;
|
|
7357
|
-
uniforms.logarithmic.value = renderer.logarithmicDepthBuffer && "isPerspectiveCamera" in camera && camera.isPerspectiveCamera;
|
|
7358
|
-
this.material.depthFunc = state.prepareResolve(camera, this);
|
|
7359
|
-
if (!uniforms.valid.value)
|
|
7360
|
-
for (const target of this.history) renderer.initRenderTarget(target);
|
|
7361
|
-
const previous = this.history[state.historyIndex];
|
|
7362
|
-
this.historyColor.value = previous.texture;
|
|
7363
|
-
this.historyDepth.value = previous.depthTexture;
|
|
7364
|
-
const output = this.history[1 - state.historyIndex];
|
|
7365
|
-
renderer.setRenderObjectFunction(null);
|
|
7366
|
-
renderer.setRenderTarget(output);
|
|
7367
|
-
this.quad.render(renderer);
|
|
7368
|
-
this.textureNode.value = output.texture;
|
|
7369
|
-
state.advance(camera);
|
|
7370
|
-
this.capturePending = false;
|
|
7371
|
-
} finally {
|
|
7372
|
-
RendererUtils.restoreRendererState(renderer, rendererState);
|
|
7373
|
-
renderer.xr.enabled = xrEnabled;
|
|
7374
|
-
}
|
|
7375
|
-
return void 0;
|
|
7376
|
-
}
|
|
7377
|
-
setup(builder) {
|
|
7378
|
-
applyThreeR186Patch(builder.renderer);
|
|
7379
|
-
const pipelineState = builder.context.renderPipelineState;
|
|
7380
|
-
if (pipelineState && !this.pipelineStates.has(pipelineState)) {
|
|
7381
|
-
this.pipelineStates.add(pipelineState);
|
|
7382
|
-
const begin = () => {
|
|
7383
|
-
this.pipelineRendering = true;
|
|
7384
|
-
this.capturePending = true;
|
|
7385
|
-
const renderer = builder.renderer;
|
|
7386
|
-
const target = renderer.getRenderTarget() ?? renderer.getOutputRenderTarget();
|
|
7387
|
-
if (target) this.drawingSize.set(target.width, target.height);
|
|
7388
|
-
else renderer.getDrawingBufferSize(this.drawingSize);
|
|
7389
|
-
};
|
|
7390
|
-
begin();
|
|
7391
|
-
N.OnBeforeRenderPipeline(begin);
|
|
7392
|
-
N.OnAfterRenderPipeline(() => {
|
|
7393
|
-
this.pipelineRendering = false;
|
|
7394
|
-
});
|
|
7395
|
-
}
|
|
7396
|
-
const u = this.state.uniforms;
|
|
7397
|
-
const source = N.texture(this.source.texture, N.screenUV);
|
|
7398
|
-
const sourceDepth = N.texture(this.depthTexture, N.screenUV);
|
|
7399
|
-
const history = this.historyColor;
|
|
7400
|
-
const historyDepth = this.historyDepth;
|
|
7401
|
-
const bounded = (p) => p.clamp(N.ivec2(0), N.ivec2(u.renderSize).sub(1));
|
|
7402
|
-
const viewPosition = (uv, depth2, p, far, near) => {
|
|
7403
|
-
const z = N.select(this.zeroToOne, depth2, depth2.mul(2).sub(1));
|
|
7404
|
-
const orientation = N.select(u.reversed, -1, 1);
|
|
7405
|
-
const viewZ = p.element(3).z.sub(z.mul(p.element(3).w)).mul(orientation).toVar();
|
|
7406
|
-
const viewW = z.mul(p.element(2).w).sub(p.element(2).z).mul(orientation).toVar();
|
|
7407
|
-
N.If(u.logarithmic, () => {
|
|
7408
|
-
viewZ.assign(near.mul(depth2.mul(far).exp2()).negate());
|
|
7409
|
-
viewW.assign(1);
|
|
7410
|
-
});
|
|
7411
|
-
const clipW = p.element(2).w.mul(viewZ).add(p.element(3).w.mul(viewW));
|
|
7412
|
-
const xy = uv.mul(2).sub(1).mul(N.vec2(1, -1)).mul(clipW).sub(p.element(2).xy.mul(viewZ)).sub(p.element(3).xy.mul(viewW)).div(N.vec2(p.element(0).x, p.element(1).y));
|
|
7413
|
-
return N.vec4(xy, viewZ, viewW);
|
|
7414
|
-
};
|
|
7415
|
-
const forwardDepth = (position) => {
|
|
7416
|
-
const clip = u.projection.mul(position).toVar();
|
|
7417
|
-
const depth2 = clip.z.div(clip.w);
|
|
7418
|
-
return N.select(
|
|
7419
|
-
u.reversed,
|
|
7420
|
-
N.float(1).sub(depth2),
|
|
7421
|
-
N.select(this.zeroToOne, depth2, depth2.mul(0.5).add(0.5))
|
|
7422
|
-
);
|
|
7423
|
-
};
|
|
7424
|
-
const pixel = N.ivec2(N.screenCoordinate.xy);
|
|
7425
|
-
this.material.depthNode = sourceDepth.load(pixel).r;
|
|
7426
|
-
this.material.colorNode = N.Fn(() => {
|
|
7427
|
-
const currentColor = source.load(pixel).toVar();
|
|
7428
|
-
const result = currentColor.toVar();
|
|
7429
|
-
N.If(u.valid, () => {
|
|
7430
|
-
const closestDepth = N.float(2).toVar();
|
|
7431
|
-
const farthestDepth = N.float(-1).toVar();
|
|
7432
|
-
const closestRawDepth = sourceDepth.load(pixel).r.toVar();
|
|
7433
|
-
const closestPixel = pixel.toVar();
|
|
7434
|
-
const moment1 = currentColor.toVar();
|
|
7435
|
-
const moment2 = currentColor.mul(currentColor).toVar();
|
|
7436
|
-
for (let x = -1; x <= 1; x++) {
|
|
7437
|
-
for (let y = -1; y <= 1; y++) {
|
|
7438
|
-
const p = bounded(pixel.add(N.ivec2(x, y))).toVar();
|
|
7439
|
-
const rawDepth = sourceDepth.load(p).r.toVar();
|
|
7440
|
-
const depth2 = N.select(
|
|
7441
|
-
u.reversed.and(u.logarithmic.not()),
|
|
7442
|
-
N.float(1).sub(rawDepth),
|
|
7443
|
-
rawDepth
|
|
7444
|
-
).toVar();
|
|
7445
|
-
N.If(depth2.lessThan(closestDepth), () => {
|
|
7446
|
-
closestDepth.assign(depth2);
|
|
7447
|
-
closestRawDepth.assign(rawDepth);
|
|
7448
|
-
closestPixel.assign(p);
|
|
7449
|
-
});
|
|
7450
|
-
farthestDepth.assign(farthestDepth.max(depth2));
|
|
7451
|
-
if (x !== 0 || y !== 0) {
|
|
7452
|
-
const neighbor = source.load(p).max(0).toVar();
|
|
7453
|
-
moment1.addAssign(neighbor);
|
|
7454
|
-
moment2.addAssign(neighbor.mul(neighbor));
|
|
7455
|
-
}
|
|
7456
|
-
}
|
|
7457
|
-
}
|
|
7458
|
-
const closestUV = N.vec2(closestPixel).add(0.5).div(u.renderSize);
|
|
7459
|
-
const positionView = viewPosition(
|
|
7460
|
-
closestUV,
|
|
7461
|
-
closestRawDepth,
|
|
7462
|
-
u.projection,
|
|
7463
|
-
u.logFar.x,
|
|
7464
|
-
u.logNear.x
|
|
7465
|
-
);
|
|
7466
|
-
const previousClip = u.viewToPreviousClip.mul(positionView).toVar();
|
|
7467
|
-
N.If(previousClip.w.greaterThan(0), () => {
|
|
7468
|
-
const currentClip = u.unjitteredProjection.mul(positionView).toVar();
|
|
7469
|
-
const offsetUV = currentClip.xy.div(currentClip.w).sub(previousClip.xy.div(previousClip.w)).mul(N.vec2(0.5, -0.5)).toVar();
|
|
7470
|
-
const historyUV = N.screenUV.sub(offsetUV).toVar();
|
|
7471
|
-
N.If(
|
|
7472
|
-
historyUV.greaterThanEqual(0).all().and(historyUV.lessThan(1).all()),
|
|
7473
|
-
() => {
|
|
7474
|
-
const oldDepth = historyDepth.sample(historyUV).r.toVar();
|
|
7475
|
-
const previousView = viewPosition(
|
|
7476
|
-
historyUV,
|
|
7477
|
-
oldDepth,
|
|
7478
|
-
u.previousProjection,
|
|
7479
|
-
u.logFar.y,
|
|
7480
|
-
u.logNear.y
|
|
7481
|
-
);
|
|
7482
|
-
const previousDepth = forwardDepth(
|
|
7483
|
-
u.previousViewToView.mul(previousView)
|
|
7484
|
-
);
|
|
7485
|
-
N.If(u.logarithmic, () => {
|
|
7486
|
-
closestDepth.assign(forwardDepth(positionView));
|
|
7487
|
-
const farthestZ = u.logNear.x.mul(farthestDepth.mul(u.logFar.x).exp2()).negate();
|
|
7488
|
-
farthestDepth.assign(forwardDepth(N.vec4(0, 0, farthestZ, 1)));
|
|
7489
|
-
});
|
|
7490
|
-
const isEdge = farthestDepth.sub(closestDepth).greaterThan(u.edgeDepthDiff);
|
|
7491
|
-
const isDisocclusion = closestDepth.sub(previousDepth).greaterThan(u.depthThreshold);
|
|
7492
|
-
N.If(isEdge.or(isDisocclusion.not()), () => {
|
|
7493
|
-
const motionFactor = offsetUV.mul(u.renderSize).length().div(u.maxMotionLength).clamp(0, 1).toVar();
|
|
7494
|
-
const currentWeight = N.float(0.05).toVar();
|
|
7495
|
-
N.If(u.useSubpixelCorrection, () => {
|
|
7496
|
-
const phase = offsetUV.mul(u.renderSize).fract();
|
|
7497
|
-
const weight = phase.max(N.vec2(1).sub(phase));
|
|
7498
|
-
currentWeight.addAssign(
|
|
7499
|
-
N.float(1).sub(weight.x.mul(weight.y)).div(0.75).mul(0.25)
|
|
7500
|
-
);
|
|
7501
|
-
});
|
|
7502
|
-
currentWeight.assign(
|
|
7503
|
-
currentWeight.add(motionFactor).clamp(0, 1)
|
|
7504
|
-
);
|
|
7505
|
-
const gamma = N.mix(
|
|
7506
|
-
0.5,
|
|
7507
|
-
1,
|
|
7508
|
-
N.float(1).sub(motionFactor).pow(2)
|
|
7509
|
-
);
|
|
7510
|
-
const mean = moment1.div(9).toVar();
|
|
7511
|
-
const extent = moment2.div(9).sub(mean.mul(mean)).max(0).sqrt().mul(gamma);
|
|
7512
|
-
const historyColor = history.sample(historyUV).toVar();
|
|
7513
|
-
const delta = historyColor.sub(mean).toVar();
|
|
7514
|
-
const unit = delta.rgb.div(extent.rgb.add(1e-7)).abs();
|
|
7515
|
-
const maxUnit = unit.x.max(unit.y).max(unit.z);
|
|
7516
|
-
N.If(maxUnit.greaterThan(1), () => {
|
|
7517
|
-
historyColor.assign(mean.add(delta.div(maxUnit)));
|
|
7518
|
-
});
|
|
7519
|
-
const compressedCurrent = currentColor.rgb.div(
|
|
7520
|
-
currentColor.r.max(currentColor.g).max(currentColor.b).add(1)
|
|
7521
|
-
);
|
|
7522
|
-
const compressedHistory = historyColor.rgb.div(
|
|
7523
|
-
historyColor.r.max(historyColor.g).max(historyColor.b).add(1)
|
|
7524
|
-
);
|
|
7525
|
-
const weightCurrent = currentWeight.div(
|
|
7526
|
-
compressedCurrent.dot(u.luminanceCoefficients).add(1)
|
|
7527
|
-
);
|
|
7528
|
-
const weightHistory = N.float(1).sub(currentWeight).div(compressedHistory.dot(u.luminanceCoefficients).add(1));
|
|
7529
|
-
result.assign(
|
|
7530
|
-
currentColor.mul(weightCurrent).add(historyColor.mul(weightHistory)).div(weightCurrent.add(weightHistory).max(1e-5))
|
|
7531
|
-
);
|
|
7532
|
-
});
|
|
7533
|
-
}
|
|
7534
|
-
);
|
|
7535
|
-
});
|
|
7536
|
-
});
|
|
7537
|
-
return result;
|
|
7538
|
-
})();
|
|
7539
|
-
return this.textureNode;
|
|
7540
|
-
}
|
|
7541
|
-
dispose() {
|
|
7542
|
-
var _a2;
|
|
7543
|
-
super.dispose();
|
|
7544
|
-
(_a2 = this.coverage) == null ? void 0 : _a2.dispose();
|
|
7545
|
-
this.source.dispose();
|
|
7546
|
-
for (const target of this.history) target.dispose();
|
|
7547
|
-
this.material.dispose();
|
|
7548
|
-
}
|
|
7549
|
-
}
|
|
7550
6829
|
const types = {
|
|
7551
6830
|
f32: "float",
|
|
7552
6831
|
i32: "int",
|
|
@@ -7792,6 +7071,17 @@ fn chroma(c: vec3f) -> vec2f {
|
|
|
7792
7071
|
fn silu(x: vec4f) -> vec4f { return x / (1.0 + exp(-x)); }
|
|
7793
7072
|
fn sigmoid(x: f32) -> f32 { return 1.0 / (1.0 + exp(-x)); }`
|
|
7794
7073
|
);
|
|
7074
|
+
const CATMULL_ROM_WEIGHTS = (
|
|
7075
|
+
/* wgsl */
|
|
7076
|
+
`
|
|
7077
|
+
let center: vec2f = floor(position - 0.5) + 0.5;
|
|
7078
|
+
let t: vec2f = position - center;
|
|
7079
|
+
let w0: vec2f = t * (-0.5 + t * (1.0 - 0.5 * t));
|
|
7080
|
+
let w1: vec2f = 1.0 + t * t * (-2.5 + 1.5 * t);
|
|
7081
|
+
let w2: vec2f = t * (0.5 + t * (2.0 - 1.5 * t));
|
|
7082
|
+
let w3: vec2f = t * t * (-0.5 + 0.5 * t);
|
|
7083
|
+
let w12: vec2f = w1 + w2;`
|
|
7084
|
+
);
|
|
7795
7085
|
const PARAMS = (
|
|
7796
7086
|
/* wgsl */
|
|
7797
7087
|
`
|
|
@@ -7816,6 +7106,7 @@ struct Params {
|
|
|
7816
7106
|
cameraStill: f32, // unchanged view and projection; history stays on the same pixel
|
|
7817
7107
|
offset: vec2f, // where in its target the scene was drawn
|
|
7818
7108
|
colorOffset: vec2f, // zero after encoding, otherwise the scene's offset
|
|
7109
|
+
jitter: vec2f, // where this frame's samples lie, in pixels from pixel centres
|
|
7819
7110
|
};
|
|
7820
7111
|
@group(0) @binding(0) var<uniform> P: Params;
|
|
7821
7112
|
@group(0) @binding(1) var linearSampler: sampler;
|
|
@@ -7885,13 +7176,7 @@ struct Resampled {
|
|
|
7885
7176
|
};
|
|
7886
7177
|
fn catmullRomSpan(image: texture_2d<f32>, uv: vec2f) -> Resampled {
|
|
7887
7178
|
let position: vec2f = uv * P.size;
|
|
7888
|
-
|
|
7889
|
-
let t: vec2f = position - center;
|
|
7890
|
-
let w0: vec2f = t * (-0.5 + t * (1.0 - 0.5 * t));
|
|
7891
|
-
let w1: vec2f = 1.0 + t * t * (-2.5 + 1.5 * t);
|
|
7892
|
-
let w2: vec2f = t * (0.5 + t * (2.0 - 1.5 * t));
|
|
7893
|
-
let w3: vec2f = t * t * (-0.5 + 0.5 * t);
|
|
7894
|
-
let w12: vec2f = w1 + w2;
|
|
7179
|
+
${CATMULL_ROM_WEIGHTS}
|
|
7895
7180
|
let p0: vec2f = (center - 1.0) * P.invSize;
|
|
7896
7181
|
let p12: vec2f = (center + w2 / w12) * P.invSize;
|
|
7897
7182
|
let p3: vec2f = (center + 2.0) * P.invSize;
|
|
@@ -7914,75 +7199,6 @@ fn catmullRomSpan(image: texture_2d<f32>, uv: vec2f) -> Resampled {
|
|
|
7914
7199
|
fn catmullRom(image: texture_2d<f32>, uv: vec2f) -> vec4f {
|
|
7915
7200
|
return catmullRomSpan(image, uv).value;
|
|
7916
7201
|
}
|
|
7917
|
-
|
|
7918
|
-
// Lanczos-3 without its corner taps, from nine bilinear taps: the middle pair
|
|
7919
|
-
// of each axis is one tap and the other four pixels of an axis one each. A
|
|
7920
|
-
// history that is resampled once per frame loses fine detail with every pass,
|
|
7921
|
-
// and this kernel loses about half of what Catmull-Rom does. Its negative
|
|
7922
|
-
// lobes would ring over such a history, so the result is kept inside what the
|
|
7923
|
-
// four pixels around the position span. At a pixel's centre it returns the
|
|
7924
|
-
// pixel.
|
|
7925
|
-
struct LanczosWeights {
|
|
7926
|
-
low: vec3f,
|
|
7927
|
-
high: vec3f,
|
|
7928
|
-
};
|
|
7929
|
-
fn lanczosWeights(t: f32) -> LanczosWeights {
|
|
7930
|
-
// Integer-shifted sinc taps share their sine. Angle addition supplies the
|
|
7931
|
-
// other factor, reducing twelve sine evaluations per axis to three trig calls.
|
|
7932
|
-
let x: f32 = 3.14159265 * t;
|
|
7933
|
-
let s: f32 = sin(x / 3.0);
|
|
7934
|
-
let c: f32 = 0.866025404 * cos(x / 3.0);
|
|
7935
|
-
let scale: f32 = 0.303963551 * sin(x);
|
|
7936
|
-
let terms: vec3f = scale * vec3f(c - 0.5 * s, -c - 0.5 * s, s);
|
|
7937
|
-
let low: vec3f = vec3f(t + 2.0, t + 1.0, t);
|
|
7938
|
-
let high: vec3f = vec3f(t - 1.0, t - 2.0, t - 3.0);
|
|
7939
|
-
var w: LanczosWeights;
|
|
7940
|
-
w.low = terms / max(low * low, vec3f(1e-20));
|
|
7941
|
-
w.high = terms / max(high * high, vec3f(1e-20));
|
|
7942
|
-
if (abs(x) < 1e-5) { w.low.z = 1.0; }
|
|
7943
|
-
if (abs(3.14159265 * (t - 1.0)) < 1e-5) { w.high.x = 1.0; }
|
|
7944
|
-
return w;
|
|
7945
|
-
}
|
|
7946
|
-
struct LanczosKernel {
|
|
7947
|
-
base: vec2f,
|
|
7948
|
-
centre: vec2f,
|
|
7949
|
-
middle: f32,
|
|
7950
|
-
wx: vec4f,
|
|
7951
|
-
wy: vec4f,
|
|
7952
|
-
total: f32,
|
|
7953
|
-
}
|
|
7954
|
-
fn lanczosKernel(uv: vec2f) -> LanczosKernel {
|
|
7955
|
-
let position: vec2f = uv * P.size - 0.5;
|
|
7956
|
-
var k: LanczosKernel;
|
|
7957
|
-
k.base = floor(position);
|
|
7958
|
-
let t: vec2f = position - k.base;
|
|
7959
|
-
let x: LanczosWeights = lanczosWeights(t.x);
|
|
7960
|
-
let y: LanczosWeights = lanczosWeights(t.y);
|
|
7961
|
-
let middle: vec2f = vec2f(x.low.z + x.high.x, y.low.z + y.high.x);
|
|
7962
|
-
k.centre = (k.base + vec2f(x.high.x, y.high.x) / middle + 0.5) * P.invSize;
|
|
7963
|
-
// All three histories share these 2D tap weights and their normalization.
|
|
7964
|
-
k.middle = middle.x * middle.y;
|
|
7965
|
-
k.wx = vec4f(x.low.xy, x.high.yz) * middle.y;
|
|
7966
|
-
k.wy = vec4f(y.low.xy, y.high.yz) * middle.x;
|
|
7967
|
-
k.total = k.middle;
|
|
7968
|
-
for (var i: i32 = 0; i < 4; i++) { k.total += k.wx[i] + k.wy[i]; }
|
|
7969
|
-
return k;
|
|
7970
|
-
}
|
|
7971
|
-
fn lanczosCross(image: texture_2d<f32>, k: LanczosKernel) -> vec4f {
|
|
7972
|
-
var sum: vec4f = textureSampleLevel(image, linearSampler, k.centre, 0.0) * k.middle;
|
|
7973
|
-
const offsets = array<f32, 4>(-1.5, -0.5, 2.5, 3.5);
|
|
7974
|
-
for (var i: i32 = 0; i < 4; i++) {
|
|
7975
|
-
let along: vec2f = (k.base + offsets[i]) * P.invSize;
|
|
7976
|
-
sum += textureSampleLevel(image, linearSampler, vec2f(along.x, k.centre.y), 0.0) * k.wx[i];
|
|
7977
|
-
sum += textureSampleLevel(image, linearSampler, vec2f(k.centre.x, along.y), 0.0) * k.wy[i];
|
|
7978
|
-
}
|
|
7979
|
-
let p: vec2i = vec2i(k.base);
|
|
7980
|
-
let a: vec4f = textureLoad(image, bounded(p), 0);
|
|
7981
|
-
let b: vec4f = textureLoad(image, bounded(p + vec2i(1, 0)), 0);
|
|
7982
|
-
let c: vec4f = textureLoad(image, bounded(p + vec2i(0, 1)), 0);
|
|
7983
|
-
let d: vec4f = textureLoad(image, bounded(p + vec2i(1, 1)), 0);
|
|
7984
|
-
return clamp(sum / k.total, min(min(a, b), min(c, d)), max(max(a, b), max(c, d)));
|
|
7985
|
-
}
|
|
7986
7202
|
`
|
|
7987
7203
|
);
|
|
7988
7204
|
const TAPS = (
|
|
@@ -8312,15 +7528,60 @@ ${robust ? ` let under: vec4f = coarse(below, position.xy, 2.0);
|
|
|
8312
7528
|
groups: groups.length
|
|
8313
7529
|
};
|
|
8314
7530
|
}
|
|
8315
|
-
const
|
|
7531
|
+
const MEAN_3X3 = (
|
|
8316
7532
|
/* wgsl */
|
|
8317
7533
|
`
|
|
8318
|
-
// Exact clamped 3x3 mean
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
|
|
8323
|
-
|
|
7534
|
+
// Exact clamped 3x3 mean at a subpixel position, from four bilinear taps.
|
|
7535
|
+
// Each axis has weights (1 - t, 1, 1, t) / 3 over four texels. Pair the
|
|
7536
|
+
// first two and the last two, moving each tap with its pair's weights.
|
|
7537
|
+
fn mean3x3(image: texture_2d<f32>, position: vec2f, offset: vec2f) -> vec4f {
|
|
7538
|
+
let center: vec2f = floor(position - 0.5) + 0.5;
|
|
7539
|
+
let t: vec2f = position - center;
|
|
7540
|
+
let low: vec2f = 2.0 - t;
|
|
7541
|
+
let high: vec2f = 1.0 + t;
|
|
7542
|
+
let size: vec2f = vec2f(textureDimensions(image));
|
|
7543
|
+
let p0: vec2f = (clamp(center - 1.0 + 1.0 / low, vec2f(0.5), P.size - 0.5) + offset) / size;
|
|
7544
|
+
let p1: vec2f = (clamp(center + 1.0 + t / high, vec2f(0.5), P.size - 0.5) + offset) / size;
|
|
7545
|
+
return (textureSampleLevel(image, linearSampler, p0, 0.0) * (low.x * low.y) +
|
|
7546
|
+
textureSampleLevel(image, linearSampler, vec2f(p1.x, p0.y), 0.0) * (high.x * low.y) +
|
|
7547
|
+
textureSampleLevel(image, linearSampler, vec2f(p0.x, p1.y), 0.0) * (low.x * high.y) +
|
|
7548
|
+
textureSampleLevel(image, linearSampler, p1, 0.0) * (high.x * high.y)) / 9.0;
|
|
7549
|
+
}
|
|
7550
|
+
`
|
|
7551
|
+
);
|
|
7552
|
+
const QUALITY_RESAMPLING = (
|
|
7553
|
+
/* wgsl */
|
|
7554
|
+
`
|
|
7555
|
+
// Full Catmull-Rom for Quality histories, retaining all four corner taps.
|
|
7556
|
+
fn catmullRomNine(image: texture_2d<f32>, uv: vec2f) -> vec4f {
|
|
7557
|
+
let position: vec2f = uv * P.size;
|
|
7558
|
+
${CATMULL_ROM_WEIGHTS}
|
|
7559
|
+
let p0: vec2f = (center - 1.0) * P.invSize;
|
|
7560
|
+
let p12: vec2f = (center + w2 / w12) * P.invSize;
|
|
7561
|
+
let p3: vec2f = (center + 2.0) * P.invSize;
|
|
7562
|
+
var result: vec4f = textureSampleLevel(image, linearSampler, p12, 0.0) * (w12.x * w12.y);
|
|
7563
|
+
result += textureSampleLevel(image, linearSampler, vec2f(p0.x, p12.y), 0.0) * (w0.x * w12.y);
|
|
7564
|
+
result += textureSampleLevel(image, linearSampler, vec2f(p3.x, p12.y), 0.0) * (w3.x * w12.y);
|
|
7565
|
+
result += textureSampleLevel(image, linearSampler, vec2f(p12.x, p0.y), 0.0) * (w12.x * w0.y);
|
|
7566
|
+
result += textureSampleLevel(image, linearSampler, vec2f(p12.x, p3.y), 0.0) * (w12.x * w3.y);
|
|
7567
|
+
result += textureSampleLevel(image, linearSampler, p0, 0.0) * (w0.x * w0.y);
|
|
7568
|
+
result += textureSampleLevel(image, linearSampler, vec2f(p3.x, p0.y), 0.0) * (w3.x * w0.y);
|
|
7569
|
+
result += textureSampleLevel(image, linearSampler, vec2f(p0.x, p3.y), 0.0) * (w0.x * w3.y);
|
|
7570
|
+
result += textureSampleLevel(image, linearSampler, p3, 0.0) * (w3.x * w3.y);
|
|
7571
|
+
return result / ((w0.x + w12.x + w3.x) * (w0.y + w12.y + w3.y));
|
|
7572
|
+
}
|
|
7573
|
+
|
|
7574
|
+
// Catmull-Rom of the frame at a position between its pixels, from the five
|
|
7575
|
+
// bilinear taps catmullRomSpan takes, read through the scene's offset.
|
|
7576
|
+
fn frameCatmullRom(position: vec2f) -> vec4f {
|
|
7577
|
+
${CATMULL_ROM_WEIGHTS}
|
|
7578
|
+
let p12: vec2f = center + w2 / w12;
|
|
7579
|
+
var result: vec4f = frameSample(colorTexture, p12) * (w12.x * w12.y);
|
|
7580
|
+
result += frameSample(colorTexture, vec2f(center.x - 1.0, p12.y)) * (w0.x * w12.y);
|
|
7581
|
+
result += frameSample(colorTexture, vec2f(center.x + 2.0, p12.y)) * (w3.x * w12.y);
|
|
7582
|
+
result += frameSample(colorTexture, vec2f(p12.x, center.y - 1.0)) * (w12.x * w0.y);
|
|
7583
|
+
result += frameSample(colorTexture, vec2f(p12.x, center.y + 2.0)) * (w12.x * w3.y);
|
|
7584
|
+
return result / (w12.x * w12.y + (w0.x + w3.x) * w12.y + w12.x * (w0.y + w3.y));
|
|
8324
7585
|
}
|
|
8325
7586
|
`
|
|
8326
7587
|
);
|
|
@@ -8363,7 +7624,7 @@ fn blockFiltered(block: vec2i, color: vec4f) -> vec4f {
|
|
|
8363
7624
|
sum += wx * wy * frameSample(colorTexture, c + vec2f(f32(x), f32(y)) * (11.0 / 6.0));
|
|
8364
7625
|
}
|
|
8365
7626
|
}
|
|
8366
|
-
return mix(color, sum,
|
|
7627
|
+
return mix(color, sum, ${float(weights.compactMix[1])});
|
|
8367
7628
|
}
|
|
8368
7629
|
`
|
|
8369
7630
|
) : (
|
|
@@ -8609,7 +7870,7 @@ ${groups.map(
|
|
|
8609
7870
|
// half-resolution result.
|
|
8610
7871
|
@group(0) @binding(11) var wideTexture: texture_2d<f32>;
|
|
8611
7872
|
@group(0) @binding(12) var depthPrevious: texture_2d<f32>;
|
|
8612
|
-
${weights.compact ?
|
|
7873
|
+
${weights.compact ? MEAN_3X3 : QUALITY_RESAMPLING}${BLOCKS}
|
|
8613
7874
|
// The frame's existing 4x4 mean, used only for rejected surfaces.
|
|
8614
7875
|
@group(0) @binding(13) var rejectedTexture: texture_2d<f32>;
|
|
8615
7876
|
${COARSE}
|
|
@@ -8642,8 +7903,8 @@ struct Output {
|
|
|
8642
7903
|
};
|
|
8643
7904
|
@fragment fn fragment(@builtin(position) position: vec4f) -> Output {
|
|
8644
7905
|
let p: vec2i = vec2i(position.xy);
|
|
8645
|
-
let d: vec4f = ${weights.compact ? "mix(frameLoad(colorTexture, p), compactFiltered(position.xy), 0.5)" : "textureLoad(filteredTexture, p, 0)"};
|
|
8646
7906
|
let color: vec4f = frameLoad(colorTexture, p);
|
|
7907
|
+
${weights.compact ? "" : " let d: vec4f = textureLoad(filteredTexture, p, 0);"}
|
|
8647
7908
|
var sr: vec4f = vec4f(0.0);
|
|
8648
7909
|
var dr: vec4f = vec4f(0.0);
|
|
8649
7910
|
// Second moment of luma, the two history lengths, slow low-pass luma.
|
|
@@ -8688,14 +7949,14 @@ struct Output {
|
|
|
8688
7949
|
a = textureLoad(previousAux, p, 0);
|
|
8689
7950
|
previous.value = textureLoad(previousOutput, p, 0);
|
|
8690
7951
|
} else {
|
|
8691
|
-
${weights.compact ? `
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
7952
|
+
${weights.compact ? ` // The accumulated history keeps many frames while the camera moves
|
|
7953
|
+
// slowly: read bilinearly, it lost detail with every one of them.
|
|
7954
|
+
sr = max(catmullRom(previousAccumulated, h.uv), vec4f(0.0));
|
|
7955
|
+
dr = max(textureSampleLevel(previousDenoised, linearSampler, h.uv, 0.0), vec4f(0.0));` : ` // Full Catmull-Rom avoids the Lanczos cross kernel's frequency gain
|
|
7956
|
+
// when periodic surface detail is reprojected over many frames.
|
|
7957
|
+
sr = max(catmullRomNine(previousAccumulated, h.uv), vec4f(0.0));
|
|
7958
|
+
dr = max(catmullRomNine(previousDenoised, h.uv), vec4f(0.0));`}
|
|
8697
7959
|
a = previousStatistics(h.uv);
|
|
8698
|
-
previous.value = lanczosCross(previousOutput, interpolation);`}
|
|
8699
7960
|
}
|
|
8700
7961
|
// Where a block has no depth of its own, where to read its history is a
|
|
8701
7962
|
// guess, so while the camera moves it keeps little of the accumulated
|
|
@@ -8706,6 +7967,17 @@ ${weights.compact ? ` sr = max(textureSampleLevel(previousAccumulated, line
|
|
|
8706
7967
|
}
|
|
8707
7968
|
${weights.correction ? "var" : "let"} trust: vec4f = coarse(trustBlocks, position.xy, 4.0) * valid * same;
|
|
8708
7969
|
${weights.correction ? "var" : "let"} strength: f32 = coarse(strengthBlocks, position.xy, 4.0).r;
|
|
7970
|
+
${weights.compact ? ` // A frame drawn with projection jitter shows at pixel p what lies at
|
|
7971
|
+
// p + jitter. Performance reads the frame at p - jitter instead, with one
|
|
7972
|
+
// bilinear tap that also averages up to four samples, and takes its fixed
|
|
7973
|
+
// filter there: the slow luma, the correction and the denoised path see
|
|
7974
|
+
// the frame where it belongs. The filter takes its full share of the 3x3
|
|
7975
|
+
// mean where the view moves or the denoised history is short, and the
|
|
7976
|
+
// smaller one where that history settles, which keeps more detail.
|
|
7977
|
+
let unjittered: vec2f = position.xy - P.jitter;
|
|
7978
|
+
let shifted: vec4f = frameSample(colorTexture, unjittered);
|
|
7979
|
+
let fullShare: f32 = max(speed * speed / (speed * speed + ${float(weights.movingKnee * weights.movingKnee)}), exp(-aux.z * trust.y / ${float(weights.reach)}));
|
|
7980
|
+
let d: vec4f = mix(shifted, mean3x3(colorTexture, unjittered, P.colorOffset), mix(${float(weights.compactMix[0])}, ${float(weights.compactMix[1])}, fullShare));` : ""}
|
|
8709
7981
|
${weights.correction ? ` let corrected: vec4f = correctedPixels(color, d, sr, dr, aux, trust, strength);
|
|
8710
7982
|
trust = vec4f(corrected.xy, trust.z, corrected.z);
|
|
8711
7983
|
strength = corrected.w;` : ""}
|
|
@@ -8733,7 +8005,19 @@ ${weights.correction ? ` let corrected: vec4f = correctedPixels(color, d, sr, d
|
|
|
8733
8005
|
// nothing, which the learned filter leaves standing at any camera speed.
|
|
8734
8006
|
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);`}
|
|
8735
8007
|
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)"};
|
|
8736
|
-
|
|
8008
|
+
${weights.compact ? ` let source: vec4f = shifted;
|
|
8009
|
+
let filtered: vec4f = d;` : ` // A frame drawn with projection jitter shows at pixel p what lies at
|
|
8010
|
+
// p + jitter. Where history is short the denoised path carries the frame,
|
|
8011
|
+
// and moving content would shift back and forth by up to half a pixel: the
|
|
8012
|
+
// path reads the frame and its filtered image at p - jitter instead.
|
|
8013
|
+
var source: vec4f = color;
|
|
8014
|
+
var filtered: vec4f = d;
|
|
8015
|
+
if (P.jitter.x != 0.0 || P.jitter.y != 0.0) {
|
|
8016
|
+
let unjittered: vec2f = position.xy - P.jitter;
|
|
8017
|
+
source = frameCatmullRom(unjittered);
|
|
8018
|
+
filtered = catmullRom(filteredTexture, unjittered * P.invSize);
|
|
8019
|
+
}`}
|
|
8020
|
+
var current: vec4f = mix(mix(source, filtered, strength), wide, short);
|
|
8737
8021
|
// Strengthen only the share whose history belonged to another surface.
|
|
8738
8022
|
// Normal fast motion keeps the learned fallback's detail.
|
|
8739
8023
|
let rejected: vec4f = coarse(rejectedTexture, position.xy, 4.0);
|
|
@@ -9293,7 +8577,7 @@ P.historyValid = ndUniform7.z; P.stabilize = ndUniform7.w;
|
|
|
9293
8577
|
P.halfBias = ndUniform8.x; P.logDepth = ndUniform8.y;
|
|
9294
8578
|
P.logNear = ndUniform8.z; P.logFar = ndUniform8.w;
|
|
9295
8579
|
P.turn = ndUniform9.x; P.cameraStill = ndUniform9.y; P.offset = ndUniform9.zw;
|
|
9296
|
-
P.colorOffset = ndUniform10.xy;
|
|
8580
|
+
P.colorOffset = ndUniform10.xy; P.jitter = ndUniform10.zw;
|
|
9297
8581
|
`
|
|
9298
8582
|
);
|
|
9299
8583
|
class WebGLDenoisePasses extends NeuralDenoisePasses {
|
|
@@ -9371,6 +8655,7 @@ class WebGLDenoisePasses extends NeuralDenoisePasses {
|
|
|
9371
8655
|
}
|
|
9372
8656
|
allocate(views, depth2) {
|
|
9373
8657
|
var _a2, _b;
|
|
8658
|
+
if (depth2) depth2.texture.renderTarget = null;
|
|
9374
8659
|
const target = new this.Target(
|
|
9375
8660
|
((_a2 = views[0]) == null ? void 0 : _a2.width) ?? this.width,
|
|
9376
8661
|
((_b = views[0]) == null ? void 0 : _b.height) ?? this.height,
|
|
@@ -9544,6 +8829,7 @@ const quality = {
|
|
|
9544
8829
|
denoisedFrames: 24,
|
|
9545
8830
|
gateFrames: 8,
|
|
9546
8831
|
motionKnee: 20,
|
|
8832
|
+
movingKnee: 0.5,
|
|
9547
8833
|
stabilizeBlend: 0.9,
|
|
9548
8834
|
stabilizeSlack: 0.04,
|
|
9549
8835
|
evidenceFrames: [3, 6, 12, 24],
|
|
@@ -11584,8 +10870,55 @@ const quality = {
|
|
|
11584
10870
|
};
|
|
11585
10871
|
const neuralDenoiseModels = {
|
|
11586
10872
|
quality,
|
|
11587
|
-
performance: { ...quality, compact: true }
|
|
10873
|
+
performance: { ...quality, compact: true, compactMix: [0.5, 0.75] }
|
|
11588
10874
|
};
|
|
10875
|
+
class StochasticCoverage {
|
|
10876
|
+
constructor() {
|
|
10877
|
+
__publicField(this, "materials", /* @__PURE__ */ new Map());
|
|
10878
|
+
}
|
|
10879
|
+
material(source, renderer) {
|
|
10880
|
+
let entry = this.materials.get(source);
|
|
10881
|
+
if (!entry) {
|
|
10882
|
+
const variant = createStochasticSplatVariant(source, renderer);
|
|
10883
|
+
if (!variant) return source;
|
|
10884
|
+
const dispose = () => {
|
|
10885
|
+
source.removeEventListener("dispose", dispose);
|
|
10886
|
+
variant.dispose();
|
|
10887
|
+
this.materials.delete(source);
|
|
10888
|
+
};
|
|
10889
|
+
entry = { variant, state: new MaterialState(source), dispose };
|
|
10890
|
+
this.materials.set(source, entry);
|
|
10891
|
+
source.addEventListener("dispose", dispose);
|
|
10892
|
+
}
|
|
10893
|
+
entry.state.sync(entry.variant, source);
|
|
10894
|
+
return entry.variant;
|
|
10895
|
+
}
|
|
10896
|
+
render(renderer, scene, camera) {
|
|
10897
|
+
if (!usesNativeWebGPU(renderer) && !renderer.getContext().getExtension("OES_sample_variables"))
|
|
10898
|
+
throw new Error(
|
|
10899
|
+
"Stochastic rendering with MSAA requires OES_sample_variables on WebGL2; use msaa = false on this device"
|
|
10900
|
+
);
|
|
10901
|
+
const nodeRenderer = renderer.isWebGPURenderer === true;
|
|
10902
|
+
const previous = nodeRenderer ? renderer.getRenderObjectFunction() : renderer.renderBufferDirect;
|
|
10903
|
+
const draw = previous ?? renderer.renderObject;
|
|
10904
|
+
const materialIndex = nodeRenderer ? 4 : 3;
|
|
10905
|
+
const hook = (...args) => {
|
|
10906
|
+
args[materialIndex] = this.material(args[materialIndex], renderer);
|
|
10907
|
+
return draw.apply(renderer, args);
|
|
10908
|
+
};
|
|
10909
|
+
if (nodeRenderer) renderer.setRenderObjectFunction(hook);
|
|
10910
|
+
else renderer.renderBufferDirect = hook;
|
|
10911
|
+
try {
|
|
10912
|
+
renderer.render(scene, camera);
|
|
10913
|
+
} finally {
|
|
10914
|
+
if (nodeRenderer) renderer.setRenderObjectFunction(previous);
|
|
10915
|
+
else renderer.renderBufferDirect = previous;
|
|
10916
|
+
}
|
|
10917
|
+
}
|
|
10918
|
+
dispose() {
|
|
10919
|
+
for (const entry of this.materials.values()) entry.dispose();
|
|
10920
|
+
}
|
|
10921
|
+
}
|
|
11589
10922
|
const MARGIN = 3;
|
|
11590
10923
|
const VIEW_OFFSETS = [
|
|
11591
10924
|
[2, 2],
|
|
@@ -11643,12 +10976,7 @@ class NeuralDenoiseNode extends Node {
|
|
|
11643
10976
|
});
|
|
11644
10977
|
return color;
|
|
11645
10978
|
})());
|
|
11646
|
-
__publicField(this, "state",
|
|
11647
|
-
(value) => ({ value }),
|
|
11648
|
-
[this.source, ...this.history],
|
|
11649
|
-
true,
|
|
11650
|
-
MARGIN
|
|
11651
|
-
));
|
|
10979
|
+
__publicField(this, "state", createNeuralDenoiseState([this.source, ...this.history], MARGIN));
|
|
11652
10980
|
__publicField(this, "passes", null);
|
|
11653
10981
|
// The scene target's depth has the margin; this one has the image's size.
|
|
11654
10982
|
__publicField(this, "depth", new DepthTexture(1, 1, FloatType));
|
|
@@ -11686,7 +11014,7 @@ class NeuralDenoiseNode extends Node {
|
|
|
11686
11014
|
}
|
|
11687
11015
|
/** The projection `depthTexture` was rendered with. */
|
|
11688
11016
|
get projectionMatrix() {
|
|
11689
|
-
return this.state.
|
|
11017
|
+
return this.state.projection;
|
|
11690
11018
|
}
|
|
11691
11019
|
/** The denoised image, for chaining effects. */
|
|
11692
11020
|
getTextureNode() {
|
|
@@ -11754,7 +11082,7 @@ class NeuralDenoiseNode extends Node {
|
|
|
11754
11082
|
renderer.toneMapping = NoToneMapping;
|
|
11755
11083
|
const reversedDepth = camera.reversedDepth;
|
|
11756
11084
|
const coordinateSystem = camera.coordinateSystem;
|
|
11757
|
-
const { x: width, y: height } = state.
|
|
11085
|
+
const { x: width, y: height } = state.size;
|
|
11758
11086
|
const [offsetX, offsetY] = VIEW_OFFSETS[Math.floor(this.frame / 32) % VIEW_OFFSETS.length];
|
|
11759
11087
|
this.frame++;
|
|
11760
11088
|
this.source.viewport.set(offsetX, offsetY, width, height);
|
|
@@ -11768,11 +11096,11 @@ class NeuralDenoiseNode extends Node {
|
|
|
11768
11096
|
if (camera.reversedDepth !== reversedDepth || camera.coordinateSystem !== coordinateSystem)
|
|
11769
11097
|
camera.updateProjectionMatrix();
|
|
11770
11098
|
}
|
|
11771
|
-
const valid = state.
|
|
11099
|
+
const valid = state.valid;
|
|
11772
11100
|
if (!valid && native)
|
|
11773
11101
|
for (const target of this.history) renderer.initRenderTarget(target);
|
|
11774
11102
|
const u = passes.uniforms;
|
|
11775
|
-
const jittered = state.
|
|
11103
|
+
const jittered = state.projection.elements;
|
|
11776
11104
|
const logarithmic = (nodeRenderer ? renderer.logarithmicDepthBuffer : renderer.capabilities.logarithmicDepthBuffer) && "isPerspectiveCamera" in camera && camera.isPerspectiveCamera;
|
|
11777
11105
|
const near = Math.max(camera.near, 1e-6);
|
|
11778
11106
|
const reversed = nodeRenderer ? renderer.reversedDepthBuffer : renderer.capabilities.reversedDepthBuffer;
|
|
@@ -11814,6 +11142,17 @@ class NeuralDenoiseNode extends Node {
|
|
|
11814
11142
|
this.source.texture.colorSpace || this.workingColorSpace
|
|
11815
11143
|
) === LinearTransfer;
|
|
11816
11144
|
u.set(encode ? [0, 0] : [u[62], u[63]], 64);
|
|
11145
|
+
const p = camera.projectionMatrix.elements;
|
|
11146
|
+
let jitterX = 0;
|
|
11147
|
+
let jitterY = 0;
|
|
11148
|
+
if ([8, 9, 12, 13].some((i) => jittered[i] !== p[i])) {
|
|
11149
|
+
const x = (jittered[8] - jittered[12]) / jittered[0];
|
|
11150
|
+
const y = (jittered[9] - jittered[13]) / jittered[5];
|
|
11151
|
+
const w = p[3] * x + p[7] * y - p[11] + p[15];
|
|
11152
|
+
jitterX = (p[0] * x + p[4] * y - p[8] + p[12]) / w * width / 2;
|
|
11153
|
+
jitterY = -((p[1] * x + p[5] * y - p[9] + p[13]) / w) * height / 2;
|
|
11154
|
+
}
|
|
11155
|
+
u.set([jitterX, jitterY], 66);
|
|
11817
11156
|
this.encoded.value = ColorManagement.getTransfer(this.workingColorSpace) === LinearTransfer;
|
|
11818
11157
|
const gpu = (texture) => {
|
|
11819
11158
|
const data = backend.get(texture).texture;
|
|
@@ -11846,7 +11185,7 @@ class NeuralDenoiseNode extends Node {
|
|
|
11846
11185
|
this.textureNode.value = native ? this.history[1 - previous].texture : passes.result;
|
|
11847
11186
|
this.previousWorld.copy(camera.matrixWorld);
|
|
11848
11187
|
this.previousProjection.copy(camera.projectionMatrix);
|
|
11849
|
-
state.advance(
|
|
11188
|
+
state.advance();
|
|
11850
11189
|
this.capturePending = false;
|
|
11851
11190
|
} finally {
|
|
11852
11191
|
renderer.setRenderTarget(
|
|
@@ -11931,7 +11270,7 @@ void main() {
|
|
|
11931
11270
|
vec4 color = texture2D(image, vUv);
|
|
11932
11271
|
if (encoded) {
|
|
11933
11272
|
// OutputPass applies its transfer directly to premultiplied RGB. Invert
|
|
11934
|
-
// that transfer here
|
|
11273
|
+
// that transfer here to round-trip transparent pixels.
|
|
11935
11274
|
float scale = outputSRGB ? 1.0 : max(color.a, 1e-6);
|
|
11936
11275
|
color.rgb = sRGBTransferEOTF(vec4(max(color.rgb / scale, 0.0), 1.0)).rgb * scale;
|
|
11937
11276
|
}
|
|
@@ -12005,6 +11344,175 @@ void main() {
|
|
|
12005
11344
|
this.quad.dispose();
|
|
12006
11345
|
}
|
|
12007
11346
|
}
|
|
11347
|
+
const EYE_PATTERN_OFFSET = 16;
|
|
11348
|
+
class NeuralDenoiseXR {
|
|
11349
|
+
constructor(renderer, scene, camera, mode = "quality", msaa = false) {
|
|
11350
|
+
__publicField(this, "stabilize", true);
|
|
11351
|
+
__publicField(this, "views", []);
|
|
11352
|
+
__publicField(this, "output", null);
|
|
11353
|
+
assertSupportedRenderer(renderer);
|
|
11354
|
+
this.renderer = renderer;
|
|
11355
|
+
this.scene = scene;
|
|
11356
|
+
this.camera = camera;
|
|
11357
|
+
this.mode = mode;
|
|
11358
|
+
this.msaa = msaa;
|
|
11359
|
+
this.nodeRenderer = isWebGPURenderer(renderer);
|
|
11360
|
+
if (this.nodeRenderer) applyThreeR186Patch(renderer);
|
|
11361
|
+
this.onSessionEnd = () => this.disposeViews();
|
|
11362
|
+
renderer.xr.addEventListener("sessionend", this.onSessionEnd);
|
|
11363
|
+
}
|
|
11364
|
+
createViews(eyes) {
|
|
11365
|
+
this.disposeViews();
|
|
11366
|
+
this.views = eyes.map((eye) => {
|
|
11367
|
+
const camera = new PerspectiveCamera();
|
|
11368
|
+
const pass = this.nodeRenderer ? new NeuralDenoiseNode(this.scene, camera, this.mode, this.msaa) : new NeuralDenoisePass(this.scene, camera, this.mode, this.msaa);
|
|
11369
|
+
const denoise = this.nodeRenderer ? pass : pass.denoise;
|
|
11370
|
+
denoise.updateBeforeType = NodeUpdateType.NONE;
|
|
11371
|
+
return { eye, pass, denoise };
|
|
11372
|
+
});
|
|
11373
|
+
if (this.nodeRenderer) {
|
|
11374
|
+
const material = new NodeMaterial();
|
|
11375
|
+
material.name = "NeuralDenoiseXR.output";
|
|
11376
|
+
material.vertexNode = N.vec4(N.positionGeometry.xy, 0, 1);
|
|
11377
|
+
const color = N.Fn((builder) => {
|
|
11378
|
+
const index2 = viewIndex(builder.camera);
|
|
11379
|
+
const color2 = N.vec4(0).toVar();
|
|
11380
|
+
this.views.forEach((view, i) => {
|
|
11381
|
+
N.If(index2.equal(i), () => {
|
|
11382
|
+
color2.assign(view.denoise.getTextureNode());
|
|
11383
|
+
});
|
|
11384
|
+
});
|
|
11385
|
+
return color2;
|
|
11386
|
+
})();
|
|
11387
|
+
material.fragmentNode = N.renderOutput(
|
|
11388
|
+
color,
|
|
11389
|
+
this.renderer.toneMapping,
|
|
11390
|
+
this.renderer.outputColorSpace
|
|
11391
|
+
);
|
|
11392
|
+
material.depthTest = false;
|
|
11393
|
+
material.depthWrite = false;
|
|
11394
|
+
material.blending = NoBlending;
|
|
11395
|
+
this.output = new QuadMesh(material);
|
|
11396
|
+
} else {
|
|
11397
|
+
this.output = new Mesh(new PlaneGeometry(2, 2), this.views[0].pass.copy);
|
|
11398
|
+
this.output.onBeforeRender = (renderer, scene, camera) => {
|
|
11399
|
+
const { denoise } = this.views.find((view) => view.eye === camera);
|
|
11400
|
+
const uniforms = this.output.material.uniforms;
|
|
11401
|
+
uniforms.image.value = denoise.textureNode.value;
|
|
11402
|
+
uniforms.encoded.value = denoise.encoded.value;
|
|
11403
|
+
uniforms.outputSRGB.value = ColorManagement.getTransfer(renderer.outputColorSpace) === SRGBTransfer;
|
|
11404
|
+
this.output.material.uniformsNeedUpdate = true;
|
|
11405
|
+
};
|
|
11406
|
+
}
|
|
11407
|
+
this.output.frustumCulled = false;
|
|
11408
|
+
this.output.layers.enableAll();
|
|
11409
|
+
}
|
|
11410
|
+
render() {
|
|
11411
|
+
const { renderer } = this;
|
|
11412
|
+
const { xr } = renderer;
|
|
11413
|
+
if (!xr.enabled || !xr.isPresenting) {
|
|
11414
|
+
throw new Error(
|
|
11415
|
+
"NeuralDenoiseXR.render() requires an active WebXR session"
|
|
11416
|
+
);
|
|
11417
|
+
}
|
|
11418
|
+
if (xr.cameraAutoUpdate) {
|
|
11419
|
+
this.camera.updateWorldMatrix(true, false);
|
|
11420
|
+
xr.updateCamera(this.camera);
|
|
11421
|
+
}
|
|
11422
|
+
const eyes = xr.getCamera().cameras;
|
|
11423
|
+
if (eyes.length === 0) return;
|
|
11424
|
+
if (eyes.length !== this.views.length || eyes.some((eye, i) => eye !== this.views[i].eye))
|
|
11425
|
+
this.createViews(eyes);
|
|
11426
|
+
const target = renderer.getRenderTarget();
|
|
11427
|
+
const cubeFace = renderer.getActiveCubeFace();
|
|
11428
|
+
const mipLevel = renderer.getActiveMipmapLevel();
|
|
11429
|
+
const cameraAutoUpdate = xr.cameraAutoUpdate;
|
|
11430
|
+
const autoClear = renderer.autoClear;
|
|
11431
|
+
const toneMapping = renderer.toneMapping;
|
|
11432
|
+
const outputColorSpace = renderer.outputColorSpace;
|
|
11433
|
+
const outputTarget = this.nodeRenderer ? renderer.getOutputRenderTarget() : null;
|
|
11434
|
+
const outputDepth = outputTarget == null ? void 0 : outputTarget.depthBuffer;
|
|
11435
|
+
const samples = /* @__PURE__ */ new Map();
|
|
11436
|
+
this.scene.traverse((object) => {
|
|
11437
|
+
if (object instanceof GaussianSplatRenderer && object.autoAdvanceStochasticSample)
|
|
11438
|
+
samples.set(object, object.stochasticSample + 1 >>> 0);
|
|
11439
|
+
});
|
|
11440
|
+
try {
|
|
11441
|
+
xr.cameraAutoUpdate = false;
|
|
11442
|
+
for (const splat of samples.keys())
|
|
11443
|
+
splat.autoAdvanceStochasticSample = false;
|
|
11444
|
+
for (const [index2, { eye, denoise }] of this.views.entries()) {
|
|
11445
|
+
const { camera } = denoise;
|
|
11446
|
+
for (const [splat, sample] of samples)
|
|
11447
|
+
splat.stochasticSample = sample + index2 * EYE_PATTERN_OFFSET >>> 0;
|
|
11448
|
+
camera.copy(eye, false);
|
|
11449
|
+
camera.matrixAutoUpdate = false;
|
|
11450
|
+
camera.matrixWorldAutoUpdate = false;
|
|
11451
|
+
camera.coordinateSystem = renderer.coordinateSystem;
|
|
11452
|
+
const reversed = this.nodeRenderer ? renderer.reversedDepthBuffer : renderer.capabilities.reversedDepthBuffer;
|
|
11453
|
+
camera._reversedDepth = reversed;
|
|
11454
|
+
if (reversed) {
|
|
11455
|
+
const p = camera.projectionMatrix.elements;
|
|
11456
|
+
const scale = usesNativeWebGPU(renderer) ? 1 : 0.5;
|
|
11457
|
+
for (let i = 0; i < 16; i += 4)
|
|
11458
|
+
p[i + 2] = scale * (p[i + 3] - p[i + 2]);
|
|
11459
|
+
camera.projectionMatrixInverse.copy(camera.projectionMatrix).invert();
|
|
11460
|
+
}
|
|
11461
|
+
denoise.scene = this.scene;
|
|
11462
|
+
denoise.stabilize = this.stabilize;
|
|
11463
|
+
denoise.setSize(eye.viewport.z, eye.viewport.w);
|
|
11464
|
+
renderer.setRenderTarget(denoise.history[0]);
|
|
11465
|
+
denoise.updateBefore({ renderer });
|
|
11466
|
+
}
|
|
11467
|
+
renderer.setRenderTarget(target, cubeFace, mipLevel);
|
|
11468
|
+
if (this.nodeRenderer) {
|
|
11469
|
+
const output = this.output.material.fragmentNode;
|
|
11470
|
+
if (output.getToneMapping() !== toneMapping || output.outputColorSpace !== outputColorSpace) {
|
|
11471
|
+
this.output.material.fragmentNode = N.renderOutput(
|
|
11472
|
+
output.colorNode,
|
|
11473
|
+
toneMapping,
|
|
11474
|
+
outputColorSpace
|
|
11475
|
+
);
|
|
11476
|
+
this.output.material.needsUpdate = true;
|
|
11477
|
+
}
|
|
11478
|
+
renderer.toneMapping = NoToneMapping;
|
|
11479
|
+
renderer.outputColorSpace = ColorManagement.workingColorSpace;
|
|
11480
|
+
if (usesNativeWebGPU(renderer) && (outputTarget == null ? void 0 : outputTarget.useArrayDepthTexture))
|
|
11481
|
+
outputTarget.depthBuffer = true;
|
|
11482
|
+
}
|
|
11483
|
+
renderer.render(this.output, this.camera);
|
|
11484
|
+
} finally {
|
|
11485
|
+
for (const [splat, sample] of samples) {
|
|
11486
|
+
splat.stochasticSample = sample;
|
|
11487
|
+
splat.autoAdvanceStochasticSample = true;
|
|
11488
|
+
}
|
|
11489
|
+
xr.enabled = true;
|
|
11490
|
+
xr.cameraAutoUpdate = cameraAutoUpdate;
|
|
11491
|
+
renderer.autoClear = autoClear;
|
|
11492
|
+
renderer.toneMapping = toneMapping;
|
|
11493
|
+
renderer.outputColorSpace = outputColorSpace;
|
|
11494
|
+
if (outputTarget) outputTarget.depthBuffer = outputDepth;
|
|
11495
|
+
renderer.setRenderTarget(target, cubeFace, mipLevel);
|
|
11496
|
+
}
|
|
11497
|
+
}
|
|
11498
|
+
/** Discards both eyes' history after a camera cut or scene replacement. */
|
|
11499
|
+
reset() {
|
|
11500
|
+
for (const { denoise } of this.views) denoise.reset();
|
|
11501
|
+
}
|
|
11502
|
+
disposeViews() {
|
|
11503
|
+
for (const { pass } of this.views) pass.dispose();
|
|
11504
|
+
this.views = [];
|
|
11505
|
+
if (this.output) {
|
|
11506
|
+
if (this.nodeRenderer) this.output.material.dispose();
|
|
11507
|
+
else this.output.geometry.dispose();
|
|
11508
|
+
this.output = null;
|
|
11509
|
+
}
|
|
11510
|
+
}
|
|
11511
|
+
dispose() {
|
|
11512
|
+
this.renderer.xr.removeEventListener("sessionend", this.onSessionEnd);
|
|
11513
|
+
this.disposeViews();
|
|
11514
|
+
}
|
|
11515
|
+
}
|
|
12008
11516
|
class WebGLCapture {
|
|
12009
11517
|
constructor(renderer) {
|
|
12010
11518
|
this.renderer = renderer;
|
|
@@ -17335,6 +16843,7 @@ export {
|
|
|
17335
16843
|
GaussianSplatRenderer,
|
|
17336
16844
|
NeuralDenoiseNode,
|
|
17337
16845
|
NeuralDenoisePass,
|
|
16846
|
+
NeuralDenoiseXR,
|
|
17338
16847
|
RadStreamScheduler,
|
|
17339
16848
|
SogStreamScheduler,
|
|
17340
16849
|
SplatCapture,
|
|
@@ -17347,8 +16856,6 @@ export {
|
|
|
17347
16856
|
SplatLoader,
|
|
17348
16857
|
SplatMesh,
|
|
17349
16858
|
Splats,
|
|
17350
|
-
TAANode,
|
|
17351
|
-
TAAPass,
|
|
17352
16859
|
defines,
|
|
17353
16860
|
fromHalf,
|
|
17354
16861
|
postDecode,
|