three-realtime-rt 0.4.0 → 0.5.0
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/README.md +151 -10
- package/package.json +2 -1
- package/src/CompositePass.js +6 -2
- package/src/DenoisePass.js +33 -5
- package/src/GBufferPass.js +132 -46
- package/src/GIReservoirPass.js +572 -0
- package/src/RTLightingPass.js +166 -53
- package/src/RealtimeRaytracer.js +187 -7
- package/src/SceneCompiler.js +174 -25
- package/src/bvhAnyHit.glsl.js +15 -0
- package/src/index.d.ts +64 -3
package/src/RTLightingPass.js
CHANGED
|
@@ -75,6 +75,18 @@ uniform float uEnvIntensity;
|
|
|
75
75
|
uniform float uFrame;
|
|
76
76
|
uniform float uEps;
|
|
77
77
|
uniform bool uGIEnabled;
|
|
78
|
+
// EXPERIMENTAL: when an external ReSTIR GI pass supplies the 1-bounce indirect
|
|
79
|
+
// (added downstream at the denoise stage), skip the inline GI trace so it isn't
|
|
80
|
+
// counted twice. A uniform, NOT a sampler — the lighting pass is already at the
|
|
81
|
+
// WebGL2 16-sampler minimum and cannot take another.
|
|
82
|
+
uniform bool uExternalGI;
|
|
83
|
+
|
|
84
|
+
// BVH traversal-cost heatmap (outputMode 7). When uCostView is on, main() writes
|
|
85
|
+
// the per-pixel shadow-ray node-visit count (gBvhVisits, from bvhAnyHit.glsl.js)
|
|
86
|
+
// through costPalette() into the irradiance attachment INSTEAD of the accumulated
|
|
87
|
+
// lighting — bypassing temporal blending — so the debug view reads the raw cost.
|
|
88
|
+
uniform bool uCostView;
|
|
89
|
+
uniform float uCostScale; // multiplies the visit count before the palette (default 1/96)
|
|
78
90
|
|
|
79
91
|
// Procedural sky (when enabled, replaces the flat env colour as the "miss" term
|
|
80
92
|
// for GI rays — this is what gives natural outdoor bounce light).
|
|
@@ -541,13 +553,13 @@ vec3 analyticGlint(vec3 P, vec3 refl) {
|
|
|
541
553
|
|
|
542
554
|
// Glass: Fresnel-weighted blend of a surface reflection and a two-interface
|
|
543
555
|
// refraction (enter at P, march to the exit surface, refract again).
|
|
544
|
-
vec3 glassRadiance(vec3 P, vec3 N, vec3 V, float rough) {
|
|
556
|
+
vec3 glassRadiance(vec3 P, vec3 N, vec3 V, float rough, float ior) {
|
|
545
557
|
vec3 refl = glossyReflect(V, N, rough);
|
|
546
558
|
vec3 reflRad = dot(refl, N) > 0.0
|
|
547
559
|
? traceRadiance(P + N * uEps, refl, true) + analyticGlint(P, refl)
|
|
548
560
|
: vec3(0.0);
|
|
549
561
|
|
|
550
|
-
float eta = 1.0 /
|
|
562
|
+
float eta = 1.0 / ior;
|
|
551
563
|
vec3 rd = refract(V, N, eta);
|
|
552
564
|
if (rd == vec3(0.0)) return reflRad; // total internal reflection at entry
|
|
553
565
|
float fres = schlick(clamp(-dot(V, N), 0.0, 1.0), eta);
|
|
@@ -563,7 +575,7 @@ vec3 glassRadiance(vec3 P, vec3 N, vec3 V, float rough) {
|
|
|
563
575
|
vec3 xN = normalize(attr.xyz);
|
|
564
576
|
if (dot(xN, rd) > 0.0) xN = -xN;
|
|
565
577
|
vec3 xP = ro + rd * dist;
|
|
566
|
-
vec3 rd2 = refract(rd, xN,
|
|
578
|
+
vec3 rd2 = refract(rd, xN, ior);
|
|
567
579
|
if (rd2 == vec3(0.0)) rd2 = reflect(rd, xN);
|
|
568
580
|
refrRad = traceRadiance(xP - xN * uEps, rd2, true);
|
|
569
581
|
} else {
|
|
@@ -574,6 +586,26 @@ vec3 glassRadiance(vec3 P, vec3 N, vec3 V, float rough) {
|
|
|
574
586
|
return mix(refrRad, reflRad, fres);
|
|
575
587
|
}
|
|
576
588
|
|
|
589
|
+
// Compact cold->hot ramp for the BVH-cost heatmap. Piecewise mix of five
|
|
590
|
+
// anchors (deep blue -> green -> yellow -> red -> white) over four equal
|
|
591
|
+
// segments — cheap, no textures, no extra samplers. t is the normalised cost
|
|
592
|
+
// (visit count * uCostScale), clamped to [0,1]; saturating at white = the most
|
|
593
|
+
// expensive pixels.
|
|
594
|
+
vec3 costPalette(float t) {
|
|
595
|
+
t = clamp(t, 0.0, 1.0);
|
|
596
|
+
const vec3 c0 = vec3(0.02, 0.05, 0.45); // cold: cheap (few boxes)
|
|
597
|
+
const vec3 c1 = vec3(0.05, 0.55, 0.25); // green
|
|
598
|
+
const vec3 c2 = vec3(0.95, 0.85, 0.10); // yellow
|
|
599
|
+
const vec3 c3 = vec3(0.90, 0.10, 0.05); // red
|
|
600
|
+
const vec3 c4 = vec3(1.00, 1.00, 1.00); // hot: expensive (many boxes)
|
|
601
|
+
float s = t * 4.0;
|
|
602
|
+
vec3 col = mix(c0, c1, clamp(s, 0.0, 1.0));
|
|
603
|
+
col = mix(col, c2, clamp(s - 1.0, 0.0, 1.0));
|
|
604
|
+
col = mix(col, c3, clamp(s - 2.0, 0.0, 1.0));
|
|
605
|
+
col = mix(col, c4, clamp(s - 3.0, 0.0, 1.0));
|
|
606
|
+
return col;
|
|
607
|
+
}
|
|
608
|
+
|
|
577
609
|
void main() {
|
|
578
610
|
vec4 wp = texture(uGWorldPos, vUv);
|
|
579
611
|
if (wp.w < 0.5) {
|
|
@@ -599,6 +631,10 @@ void main() {
|
|
|
599
631
|
float transmission = (matW >= 2.0 && matW < 4.0) ? clamp(matW - 2.0, 0.0, 1.0) : 0.0;
|
|
600
632
|
float metal = matW < 2.0 ? matW : 0.0;
|
|
601
633
|
float rough = clamp(wp.w - 1.0, 0.0, 1.0);
|
|
634
|
+
// Per-material IOR rides the [3,4) glass sub-band (full-transmission glass, see
|
|
635
|
+
// GBufferPass). Below 3 (partial glass) or non-glass, fall back to the global
|
|
636
|
+
// rt.ior uniform. material.ior wins whenever it was encoded. (Task 2)
|
|
637
|
+
float ior = (matW >= 3.0 && matW < 4.0) ? (1.0 + (matW - 3.0)) : uIor;
|
|
602
638
|
|
|
603
639
|
// Cook-Torrance specular state for this primary surface. gWantSpec gates the
|
|
604
640
|
// GGX term to PRIMARY direct lighting only (GI-bounce direct light, below,
|
|
@@ -608,6 +644,11 @@ void main() {
|
|
|
608
644
|
gSpecRough = rough;
|
|
609
645
|
gWantSpec = true;
|
|
610
646
|
|
|
647
|
+
// Reset the shadow-ray traversal-cost counter for this pixel. It accumulates
|
|
648
|
+
// across every occluded() call below (direct, GI, reflection, glass) and is
|
|
649
|
+
// read once at the end when uCostView is on (see the cost-heatmap branch).
|
|
650
|
+
gBvhVisits = 0;
|
|
651
|
+
|
|
611
652
|
// --- direct lighting ---
|
|
612
653
|
// ReSTIR: shade the reservoir's winner with one visibility ray (flat cost in
|
|
613
654
|
// light count). Stochastic: one blind random sample. Full: one shadow ray
|
|
@@ -634,11 +675,32 @@ void main() {
|
|
|
634
675
|
// (unbiased) while GI's ray cost halves; accumulation + denoise absorb
|
|
635
676
|
// the alternation.
|
|
636
677
|
gWantSpec = false; // secondary bounces contribute to diffuse GI only
|
|
678
|
+
// BLEND pixels reuse THIS call site as their straight-through view
|
|
679
|
+
// continuation instead of a GI bounce (their behind-image rides the specular
|
|
680
|
+
// attachment; the pane forgoes its own GI bounce — visually negligible, and
|
|
681
|
+
// it saves a ray). CRITICAL CALL-SITE BUDGET: traceRadiance may appear at
|
|
682
|
+
// most THREE times in this shader (glass refraction exit, this unified
|
|
683
|
+
// secondary site, the metal-reflection path). WebKit's GLSL->Metal
|
|
684
|
+
// translation silently emits a broken program at a FOURTH inlined call site
|
|
685
|
+
// (clean compile, black output on every iOS browser) — bisected live on an
|
|
686
|
+
// iPad, 2026-07-22. Never add a call site; extend this one.
|
|
637
687
|
vec3 indirect = vec3(0.0);
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
688
|
+
vec3 blendBehind = vec3(0.0);
|
|
689
|
+
bool wantBehind = uBlendEnabled && blend;
|
|
690
|
+
// uExternalGI (experimental ReSTIR GI): the GIReservoirPass supplies the
|
|
691
|
+
// bounce, so the inline GI ray is skipped — but the blend continuation is
|
|
692
|
+
// NOT GI and must keep tracing regardless.
|
|
693
|
+
bool wantGI = uGIEnabled && !uExternalGI && !wantBehind
|
|
694
|
+
&& (!uGIHalfRate || (((px.x + px.y + int(uFrame)) & 1) == 0));
|
|
695
|
+
if (wantBehind || wantGI) {
|
|
696
|
+
vec3 Vv = normalize(P - uCameraPos);
|
|
697
|
+
vec3 dir = wantBehind ? Vv : cosineSampleHemisphere(N, rand2());
|
|
698
|
+
vec3 org = wantBehind ? P + Vv * uEps : P + N * uEps;
|
|
699
|
+
vec3 r = traceRadiance(org, dir, wantBehind);
|
|
700
|
+
if (wantBehind) {
|
|
701
|
+
blendBehind = r;
|
|
702
|
+
} else {
|
|
703
|
+
indirect = r;
|
|
642
704
|
if (uGIHalfRate) indirect *= 2.0;
|
|
643
705
|
}
|
|
644
706
|
}
|
|
@@ -667,7 +729,7 @@ void main() {
|
|
|
667
729
|
// --- traced glass: Fresnel reflection + two-interface refraction ---
|
|
668
730
|
if (uRefrEnabled && transmission > 0.001) {
|
|
669
731
|
vec3 V = normalize(P - uCameraPos);
|
|
670
|
-
sampleIrr = mix(sampleIrr, glassRadiance(P, N, V, rough), transmission);
|
|
732
|
+
sampleIrr = mix(sampleIrr, glassRadiance(P, N, V, rough, ior), transmission);
|
|
671
733
|
}
|
|
672
734
|
|
|
673
735
|
// --- alpha blend: straight-through view continuation ---
|
|
@@ -685,11 +747,8 @@ void main() {
|
|
|
685
747
|
// where the pane's albedo is actually available. sampleIrr keeps only the
|
|
686
748
|
// pane's own surface lighting, which is static on the surface and accumulates
|
|
687
749
|
// with normal full-length history.
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
vec3 V = normalize(P - uCameraPos);
|
|
691
|
-
blendBehind = traceRadiance(P + V * uEps, V, true);
|
|
692
|
-
}
|
|
750
|
+
// (The straight-through trace itself happens at the unified secondary-ray
|
|
751
|
+
// call site above — see the Metal call-site-count note there.)
|
|
693
752
|
|
|
694
753
|
// A single NaN/Inf sample would poison the EMA history for good (mix() with
|
|
695
754
|
// NaN stays NaN until a disocclusion resets the pixel) — sanitize first.
|
|
@@ -757,6 +816,17 @@ void main() {
|
|
|
757
816
|
// the fresh sample is used as-is.
|
|
758
817
|
vec3 blended = mix(history, sampleIrr, 1.0 / count);
|
|
759
818
|
outIrradiance = vec4(blended, count);
|
|
819
|
+
|
|
820
|
+
// BVH traversal-cost heatmap (outputMode 7). Overwrite the accumulated
|
|
821
|
+
// lighting with the palette-mapped shadow-ray node-visit count for this pixel.
|
|
822
|
+
// Alpha is forced to 1.0 so temporal history never builds on the cost image
|
|
823
|
+
// (each frame is a fresh snapshot), and the specular attachment is cleared so
|
|
824
|
+
// the composite's cost branch shows the palette alone. Uniform branch: when
|
|
825
|
+
// uCostView is off this is skipped and the writes above stand unchanged.
|
|
826
|
+
if (uCostView) {
|
|
827
|
+
outIrradiance = vec4(costPalette(float(gBvhVisits) * uCostScale), 1.0);
|
|
828
|
+
outSpecular = vec4(0.0);
|
|
829
|
+
}
|
|
760
830
|
}
|
|
761
831
|
`;
|
|
762
832
|
|
|
@@ -854,18 +924,31 @@ void main() {
|
|
|
854
924
|
* into a second ping-pong pair, specA/specB).
|
|
855
925
|
*/
|
|
856
926
|
export class RTLightingPass {
|
|
857
|
-
|
|
927
|
+
// `specMRT: false` is the WebKit fallback: iOS Safari (every iOS browser)
|
|
928
|
+
// silently fails the 2-attachment half-float MRT draw — the whole lighting
|
|
929
|
+
// pass renders black. RealtimeRaytracer probes this functionally at
|
|
930
|
+
// construction; in fallback mode this pass allocates a single-attachment
|
|
931
|
+
// target exactly like 0.3.x, the shader's second output collapses to a dead
|
|
932
|
+
// local variable, and render() returns { specular: null } (the caller then
|
|
933
|
+
// runs with specular off; blend surfaces degrade to opaque as documented).
|
|
934
|
+
constructor(width, height, { specMRT = true } = {}) {
|
|
935
|
+
this.specMRT = specMRT;
|
|
858
936
|
this.targetA = this._makeTarget(width, height);
|
|
859
937
|
this.targetB = this._makeTarget(width, height);
|
|
860
938
|
// Accumulated specular history (ping-pong), fed by the fresh specular in
|
|
861
939
|
// targetA/B attachment 1.
|
|
862
|
-
this.specA = this._makeSpecTarget(width, height);
|
|
863
|
-
this.specB = this._makeSpecTarget(width, height);
|
|
940
|
+
this.specA = specMRT ? this._makeSpecTarget(width, height) : null;
|
|
941
|
+
this.specB = specMRT ? this._makeSpecTarget(width, height) : null;
|
|
864
942
|
|
|
865
943
|
this.material = new THREE.ShaderMaterial({
|
|
866
944
|
glslVersion: THREE.GLSL3,
|
|
867
945
|
vertexShader: fullscreenVert,
|
|
868
|
-
fragmentShader:
|
|
946
|
+
fragmentShader: specMRT
|
|
947
|
+
? rtLightingFrag
|
|
948
|
+
: rtLightingFrag.replace(
|
|
949
|
+
"layout(location = 1) out vec4 outSpecular;",
|
|
950
|
+
"vec4 outSpecular; // single-target fallback: dead store"
|
|
951
|
+
),
|
|
869
952
|
uniforms: {
|
|
870
953
|
bvhStatic: { value: null },
|
|
871
954
|
bvhDynamic: { value: null },
|
|
@@ -902,6 +985,9 @@ export class RTLightingPass {
|
|
|
902
985
|
uFrame: { value: 0 },
|
|
903
986
|
uEps: { value: 1e-3 },
|
|
904
987
|
uGIEnabled: { value: true },
|
|
988
|
+
uExternalGI: { value: false },
|
|
989
|
+
uCostView: { value: false },
|
|
990
|
+
uCostScale: { value: 1 / 96 },
|
|
905
991
|
uSkyEnabled: { value: false },
|
|
906
992
|
uSunDir: { value: new THREE.Vector3(0.4, 0.8, 0.45).normalize() },
|
|
907
993
|
uSunColor: { value: new THREE.Color(1.0, 0.9, 0.75) },
|
|
@@ -936,11 +1022,18 @@ export class RTLightingPass {
|
|
|
936
1022
|
depthWrite: false,
|
|
937
1023
|
});
|
|
938
1024
|
|
|
939
|
-
// 2-output history carry (see mrtCarryFrag) — matches the MRT's draw
|
|
1025
|
+
// 2-output history carry (see mrtCarryFrag) — matches the MRT's draw
|
|
1026
|
+
// buffers. In single-target fallback the second output collapses the same
|
|
1027
|
+
// way as the lighting shader's.
|
|
940
1028
|
this.carryMaterial = new THREE.ShaderMaterial({
|
|
941
1029
|
glslVersion: THREE.GLSL3,
|
|
942
1030
|
vertexShader: fullscreenVert,
|
|
943
|
-
fragmentShader:
|
|
1031
|
+
fragmentShader: specMRT
|
|
1032
|
+
? mrtCarryFrag
|
|
1033
|
+
: mrtCarryFrag.replace(
|
|
1034
|
+
"layout(location = 1) out vec4 o1;",
|
|
1035
|
+
"vec4 o1; // single-target fallback: dead store"
|
|
1036
|
+
),
|
|
944
1037
|
uniforms: { uTex: { value: null }, uCountClamp: { value: -1 } },
|
|
945
1038
|
depthTest: false,
|
|
946
1039
|
depthWrite: false,
|
|
@@ -957,19 +1050,31 @@ export class RTLightingPass {
|
|
|
957
1050
|
// Half-float + linear: history is sampled bilinearly at reprojected UVs,
|
|
958
1051
|
// and fp16 halves the bandwidth (EMA blending never accumulates a raw sum,
|
|
959
1052
|
// so fp16 precision is sufficient). Two attachments: [0] irradiance,
|
|
960
|
-
// [1] fresh dielectric specular
|
|
961
|
-
|
|
1053
|
+
// [1] fresh dielectric specular — or a single attachment in the WebKit
|
|
1054
|
+
// fallback (see the constructor note).
|
|
1055
|
+
const opts = {
|
|
962
1056
|
minFilter: THREE.LinearFilter,
|
|
963
1057
|
magFilter: THREE.LinearFilter,
|
|
964
1058
|
format: THREE.RGBAFormat,
|
|
965
1059
|
type: THREE.HalfFloatType,
|
|
966
1060
|
depthBuffer: false,
|
|
967
1061
|
stencilBuffer: false,
|
|
968
|
-
}
|
|
1062
|
+
};
|
|
1063
|
+
if (!this.specMRT) {
|
|
1064
|
+
const t = new THREE.WebGLRenderTarget(width, height, opts);
|
|
1065
|
+
t.texture.generateMipmaps = false;
|
|
1066
|
+
return t;
|
|
1067
|
+
}
|
|
1068
|
+
const t = new THREE.WebGLMultipleRenderTargets(width, height, 2, opts);
|
|
969
1069
|
for (const tex of t.texture) tex.generateMipmaps = false;
|
|
970
1070
|
return t;
|
|
971
1071
|
}
|
|
972
1072
|
|
|
1073
|
+
// The accumulated-irradiance texture of a history target, either layout.
|
|
1074
|
+
_irrTex(target) {
|
|
1075
|
+
return this.specMRT ? target.texture[0] : target.texture;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
973
1078
|
_makeSpecTarget(width, height) {
|
|
974
1079
|
const t = new THREE.WebGLRenderTarget(width, height, {
|
|
975
1080
|
minFilter: THREE.LinearFilter,
|
|
@@ -991,6 +1096,7 @@ export class RTLightingPass {
|
|
|
991
1096
|
const prevAlpha = renderer.getClearAlpha();
|
|
992
1097
|
renderer.setClearColor(0x000000, 0);
|
|
993
1098
|
for (const t of [this.targetA, this.targetB, this.specA, this.specB]) {
|
|
1099
|
+
if (!t) continue; // spec pair is absent in the single-target fallback
|
|
994
1100
|
renderer.setRenderTarget(t);
|
|
995
1101
|
renderer.clear(true, false, false);
|
|
996
1102
|
}
|
|
@@ -1001,8 +1107,8 @@ export class RTLightingPass {
|
|
|
1001
1107
|
setSize(width, height) {
|
|
1002
1108
|
this.targetA.setSize(width, height);
|
|
1003
1109
|
this.targetB.setSize(width, height);
|
|
1004
|
-
this.specA.setSize(width, height);
|
|
1005
|
-
this.specB.setSize(width, height);
|
|
1110
|
+
if (this.specA) this.specA.setSize(width, height);
|
|
1111
|
+
if (this.specB) this.specB.setSize(width, height);
|
|
1006
1112
|
}
|
|
1007
1113
|
|
|
1008
1114
|
/**
|
|
@@ -1025,7 +1131,7 @@ export class RTLightingPass {
|
|
|
1025
1131
|
// material so the draw matches the MRT's draw buffers (a 1-output CopyPass
|
|
1026
1132
|
// blit here is INVALID_OPERATION on ANGLE/D3D11). Attachment 1 is fresh-
|
|
1027
1133
|
// written every frame, so it needs no carry.
|
|
1028
|
-
this.carryMaterial.uniforms.uTex.value = this.targetB
|
|
1134
|
+
this.carryMaterial.uniforms.uTex.value = this._irrTex(this.targetB);
|
|
1029
1135
|
this.carryMaterial.uniforms.uCountClamp.value = carryFrames;
|
|
1030
1136
|
this.quad.material = this.carryMaterial;
|
|
1031
1137
|
const prev = renderer.getRenderTarget();
|
|
@@ -1039,13 +1145,15 @@ export class RTLightingPass {
|
|
|
1039
1145
|
this.targetB = newB;
|
|
1040
1146
|
|
|
1041
1147
|
// Specular history carries the same way (freshest is specB — see render()).
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1148
|
+
if (this.specMRT) {
|
|
1149
|
+
const newSpecA = this._makeSpecTarget(width, height);
|
|
1150
|
+
const newSpecB = this._makeSpecTarget(width, height);
|
|
1151
|
+
copyPass.blit(renderer, this.specB.texture, newSpecB, carryFrames);
|
|
1152
|
+
this.specA.dispose();
|
|
1153
|
+
this.specB.dispose();
|
|
1154
|
+
this.specA = newSpecA;
|
|
1155
|
+
this.specB = newSpecB;
|
|
1156
|
+
}
|
|
1049
1157
|
}
|
|
1050
1158
|
|
|
1051
1159
|
setCompiledScene(compiled) {
|
|
@@ -1074,7 +1182,7 @@ export class RTLightingPass {
|
|
|
1074
1182
|
u.uGWorldPos.value = gbuffer.worldPos;
|
|
1075
1183
|
u.uGNormalMetal.value = gbuffer.normalMetal;
|
|
1076
1184
|
u.uPrevGWorldPos.value = gbuffer.prevWorldPos;
|
|
1077
|
-
u.uPrevAccum.value = this.targetB
|
|
1185
|
+
u.uPrevAccum.value = this._irrTex(this.targetB);
|
|
1078
1186
|
u.uReservoir.value = reservoirTexture;
|
|
1079
1187
|
u.uRestirEnabled.value = reservoirTexture !== null;
|
|
1080
1188
|
u.uFrame.value = frame;
|
|
@@ -1085,37 +1193,42 @@ export class RTLightingPass {
|
|
|
1085
1193
|
renderer.render(this.scene, this.camera);
|
|
1086
1194
|
|
|
1087
1195
|
// 2. specular temporal accumulation: fresh (targetA[1]) + history (specB).
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1196
|
+
// Skipped entirely in the single-target fallback — there is no fresh
|
|
1197
|
+
// specular attachment to accumulate.
|
|
1198
|
+
let outSpec = null;
|
|
1199
|
+
if (this.specMRT) {
|
|
1200
|
+
const su = this.specMaterial.uniforms;
|
|
1201
|
+
su.uFreshSpec.value = this.targetA.texture[1];
|
|
1202
|
+
su.uPrevSpec.value = this.specB.texture;
|
|
1203
|
+
su.uGWorldPos.value = gbuffer.worldPos;
|
|
1204
|
+
su.uGNormalMetal.value = gbuffer.normalMetal;
|
|
1205
|
+
su.uPrevGWorldPos.value = gbuffer.prevWorldPos;
|
|
1206
|
+
su.uPrevViewProj.value.copy(u.uPrevViewProj.value);
|
|
1207
|
+
su.uViewProj.value.copy(u.uViewProj.value);
|
|
1208
|
+
su.uCameraPos.value.copy(u.uCameraPos.value);
|
|
1209
|
+
su.uEps.value = u.uEps.value;
|
|
1210
|
+
su.uMaxHistory.value = u.uMaxHistory.value;
|
|
1211
|
+
su.uTemporalReprojection.value = u.uTemporalReprojection.value;
|
|
1212
|
+
this.quad.material = this.specMaterial;
|
|
1213
|
+
renderer.setRenderTarget(this.specA);
|
|
1214
|
+
renderer.render(this.scene, this.camera);
|
|
1215
|
+
outSpec = this.specA.texture;
|
|
1216
|
+
}
|
|
1103
1217
|
|
|
1104
1218
|
this.quad.material = this.material; // restore for the next caller
|
|
1105
1219
|
renderer.setRenderTarget(null);
|
|
1106
1220
|
|
|
1107
|
-
const outIrr = this.targetA
|
|
1108
|
-
const outSpec = this.specA.texture;
|
|
1221
|
+
const outIrr = this._irrTex(this.targetA);
|
|
1109
1222
|
[this.targetA, this.targetB] = [this.targetB, this.targetA];
|
|
1110
|
-
[this.specA, this.specB] = [this.specB, this.specA];
|
|
1223
|
+
if (this.specMRT) [this.specA, this.specB] = [this.specB, this.specA];
|
|
1111
1224
|
return { irradiance: outIrr, specular: outSpec };
|
|
1112
1225
|
}
|
|
1113
1226
|
|
|
1114
1227
|
dispose() {
|
|
1115
1228
|
this.targetA.dispose();
|
|
1116
1229
|
this.targetB.dispose();
|
|
1117
|
-
this.specA.dispose();
|
|
1118
|
-
this.specB.dispose();
|
|
1230
|
+
if (this.specA) this.specA.dispose();
|
|
1231
|
+
if (this.specB) this.specB.dispose();
|
|
1119
1232
|
this.material.dispose();
|
|
1120
1233
|
this.specMaterial.dispose();
|
|
1121
1234
|
this.carryMaterial.dispose();
|