@vgai/engine 0.4.1 → 0.5.0-canary.20260719.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.
Files changed (103) hide show
  1. package/README.md +48 -15
  2. package/package.json +11 -25
  3. package/schemas/engine-capabilities.json +10 -10
  4. package/schemas/entity2d.schema.json +468 -0
  5. package/schemas/mat.schema.json +2 -33
  6. package/schemas/prefab.schema.json +16 -172
  7. package/schemas/scn2d.schema.json +42 -23
  8. package/schemas/vgai-game.schema.json +34 -0
  9. package/schemas/vscn.schema.json +16 -172
  10. package/src/adapter/authoring.ts +152 -2
  11. package/src/adapter/colyseus-networking-adapter.ts +35 -1
  12. package/src/adapter/first-party-systems.ts +7 -1
  13. package/src/adapter/game-adapter.ts +13 -0
  14. package/src/adapter/index.ts +25 -0
  15. package/src/adapter/rapier-physics-adapter.ts +55 -2
  16. package/src/adapter/system-adapter.ts +249 -2
  17. package/src/adapter/vgai-scene-game-adapter.ts +149 -25
  18. package/src/ai/navigation.ts +28 -0
  19. package/src/animation/clip-map.ts +1 -8
  20. package/src/animation/theatre-director.ts +50 -0
  21. package/src/animation/xstate-animation-binding.ts +6 -0
  22. package/src/audio/audio-introspection.ts +290 -0
  23. package/src/audio/tone-context.ts +46 -0
  24. package/src/dev/chrome-trace.ts +153 -0
  25. package/src/dev/performance-profiler.ts +93 -6
  26. package/src/dev/render-debug-adapter.ts +199 -0
  27. package/src/dev/render-memory.ts +243 -0
  28. package/src/dev/webgl-frame-capture.ts +424 -0
  29. package/src/ecs/component-manager.ts +43 -10
  30. package/src/ecs/game-component.ts +39 -10
  31. package/src/input/input-manager.ts +24 -19
  32. package/src/input/input-types.ts +1 -1
  33. package/src/loader.ts +7 -0
  34. package/src/manifest/load.ts +14 -0
  35. package/src/manifest/schema.ts +65 -0
  36. package/src/react/game-state.tsx +1 -1
  37. package/src/render/render-batch-system.ts +26 -12
  38. package/src/render/spark-renderer-lifecycle.ts +64 -0
  39. package/src/runtime/create-runtime.ts +33 -5
  40. package/src/runtime/debug-bridge.ts +5 -5
  41. package/src/runtime/game.ts +102 -20
  42. package/src/runtime/mount-manifest.ts +1 -1
  43. package/src/runtime/render-control.ts +121 -0
  44. package/src/runtime/types.ts +1 -1
  45. package/src/scene/asset-loaders.ts +77 -3
  46. package/src/scene/instance-mesh.ts +25 -0
  47. package/src/scene/material-factory.ts +4 -15
  48. package/src/scene/mesh-shadow.ts +18 -0
  49. package/src/scene/particles-factory.ts +59 -0
  50. package/src/scene/scene-loader.ts +41 -16
  51. package/src/scene/schema/instances.ts +1 -2
  52. package/src/scene/schema/material.ts +83 -94
  53. package/src/scene/schema/mesh.ts +76 -90
  54. package/src/scene/schema/scene-file.ts +1 -2
  55. package/src/scene/user-data.ts +30 -14
  56. package/src/setup/setup-renderer.ts +6 -1
  57. package/src/world2d/asset-paths2d.ts +44 -0
  58. package/src/world2d/collision-2d.ts +7 -14
  59. package/src/world2d/entity2d-asset.ts +22 -0
  60. package/src/world2d/index.ts +27 -2
  61. package/src/world2d/physics2d-transform.ts +173 -0
  62. package/src/world2d/physics2d-units.ts +10 -0
  63. package/src/world2d/pixi-game-adapter.ts +148 -36
  64. package/src/world2d/scene2d-identity.ts +49 -0
  65. package/src/world2d/scene2d-loader.ts +243 -119
  66. package/src/world2d/schema/entity2d.ts +51 -33
  67. package/src/world2d/schema/physics2d.ts +14 -3
  68. package/src/world2d/schema/sprite.ts +32 -4
  69. package/src/world2d/schema/tilemap.ts +26 -9
  70. package/src/world2d/transform-writer-2d.ts +29 -11
  71. package/src/world2d/types.ts +21 -8
  72. package/src/world3d-react/behavior.tsx +138 -0
  73. package/src/world3d-react/engine-bridge.ts +48 -0
  74. package/src/world3d-react/index.ts +44 -0
  75. package/src/world3d-react/r3f-adapter.tsx +303 -0
  76. package/src/world3d-react/world-context.ts +294 -0
  77. package/vendor/realism-effects/LICENSE.md +21 -0
  78. package/vendor/realism-effects/UPSTREAM.md +19 -0
  79. package/vendor/realism-effects/dist/index.cjs +3447 -0
  80. package/vendor/realism-effects/dist/index.d.ts +59 -0
  81. package/vendor/realism-effects/dist/index.js +3434 -0
  82. package/vendor/realism-effects/package.json +23 -0
  83. package/src/character/cloth-sim.ts +0 -533
  84. package/src/character/spring-chain.ts +0 -307
  85. package/src/humanoid/body.ts +0 -663
  86. package/src/humanoid/clips.ts +0 -149
  87. package/src/humanoid/compose.ts +0 -209
  88. package/src/humanoid/generate.ts +0 -189
  89. package/src/humanoid/index.ts +0 -36
  90. package/src/humanoid/schema.ts +0 -108
  91. package/src/humanoid/skeleton.ts +0 -345
  92. package/src/react/humanoid-bake.document.tsx +0 -337
  93. package/src/scene/geometries/index.ts +0 -7
  94. package/src/scene/geometries/terrain.ts +0 -42
  95. package/src/scene/geometry-registry.ts +0 -42
  96. package/src/scene/instance-registry.ts +0 -84
  97. package/src/scene/instancers/grid.ts +0 -38
  98. package/src/scene/instancers/index.ts +0 -7
  99. package/src/scene/material-registry.ts +0 -73
  100. package/src/scene/materials/index.ts +0 -7
  101. package/src/scene/materials/water.ts +0 -56
  102. package/src/world2d/components-2d.ts +0 -86
  103. package/tools/humanoid-bake.tool.ts +0 -274
@@ -0,0 +1,3434 @@
1
+ import { Pass, Effect, RenderPass, Selection, NormalPass } from 'postprocessing';
2
+ import { DataTexture, RGBAFormat, FloatType, ShaderChunk, ShaderLib, UniformsUtils, ShaderMaterial, GLSL3, NoBlending, Uniform, Vector2, Matrix4, Vector3, Clock, Quaternion, LinearFilter, HalfFloatType, UnsignedByteType, RGBADepthPacking, BasicDepthPacking, NearestFilter, WebGLRenderTarget, ClampToEdgeWrapping, LinearMipMapLinearFilter, EquirectangularReflectionMapping, Color, Matrix3, TangentSpaceNormalMap, RepeatWrapping, RedFormat, Source, MeshDepthMaterial, BackSide, TextureLoader, NoColorSpace, DepthTexture, SRGBColorSpace, Texture, NoToneMapping, PerspectiveCamera, FramebufferTexture } from 'three';
3
+
4
+ const createMultipleRenderTargets = (width, height, count, options) => new WebGLRenderTarget(width, height, { ...options, count });
5
+
6
+ const getVisibleChildren = object => {
7
+ const queue = [object];
8
+ const objects = [];
9
+
10
+ while (queue.length !== 0) {
11
+ const mesh = queue.shift();
12
+ if (mesh.material) objects.push(mesh);
13
+
14
+ for (const c of mesh.children) {
15
+ if (c.visible) queue.push(c);
16
+ }
17
+ }
18
+
19
+ return objects;
20
+ };
21
+ const keepMaterialMapUpdated = (mrtMaterial, originalMaterial, prop, define, useKey) => {
22
+ if (useKey) {
23
+ if (originalMaterial[prop] !== mrtMaterial[prop]) {
24
+ mrtMaterial[prop] = originalMaterial[prop];
25
+ mrtMaterial.uniforms[prop].value = originalMaterial[prop];
26
+
27
+ if (originalMaterial[prop]) {
28
+ mrtMaterial.defines[define] = "";
29
+
30
+ if (define === "USE_NORMALMAP") {
31
+ mrtMaterial.defines.TANGENTSPACE_NORMALMAP = "";
32
+ }
33
+ } else {
34
+ delete mrtMaterial.defines[define];
35
+ }
36
+
37
+ mrtMaterial.needsUpdate = true;
38
+ }
39
+ } else if (mrtMaterial[prop] !== undefined) {
40
+ mrtMaterial[prop] = undefined;
41
+ mrtMaterial.uniforms[prop].value = undefined;
42
+ delete mrtMaterial.defines[define];
43
+ mrtMaterial.needsUpdate = true;
44
+ }
45
+ };
46
+ const getMaxMipLevel = texture => {
47
+ const {
48
+ width,
49
+ height
50
+ } = texture.image;
51
+ return Math.floor(Math.log2(Math.max(width, height))) + 1;
52
+ };
53
+ const saveBoneTexture = object => {
54
+ let boneTexture = object.material.uniforms.prevBoneTexture.value;
55
+
56
+ if (boneTexture && boneTexture.image.width === object.skeleton.boneTexture.width) {
57
+ boneTexture = object.material.uniforms.prevBoneTexture.value;
58
+ boneTexture.image.data.set(object.skeleton.boneTexture.image.data);
59
+ } else {
60
+ var _boneTexture;
61
+
62
+ (_boneTexture = boneTexture) == null ? void 0 : _boneTexture.dispose();
63
+ const boneMatrices = object.skeleton.boneTexture.image.data.slice();
64
+ const size = object.skeleton.boneTexture.image.width;
65
+ boneTexture = new DataTexture(boneMatrices, size, size, RGBAFormat, FloatType);
66
+ object.material.uniforms.prevBoneTexture.value = boneTexture;
67
+ boneTexture.needsUpdate = true;
68
+ }
69
+ };
70
+ const updateVelocityDepthNormalMaterialBeforeRender = (c, camera) => {
71
+ var _c$skeleton;
72
+
73
+ if ((_c$skeleton = c.skeleton) != null && _c$skeleton.boneTexture) {
74
+ c.material.uniforms.boneTexture.value = c.skeleton.boneTexture;
75
+
76
+ if (!("USE_SKINNING" in c.material.defines)) {
77
+ c.material.defines.USE_SKINNING = "";
78
+ c.material.defines.BONE_TEXTURE = "";
79
+ c.material.needsUpdate = true;
80
+ }
81
+ }
82
+
83
+ c.modelViewMatrix.multiplyMatrices(camera.matrixWorldInverse, c.matrixWorld);
84
+ c.material.uniforms.velocityMatrix.value.multiplyMatrices(camera.projectionMatrix, c.modelViewMatrix);
85
+ };
86
+ const updateVelocityDepthNormalMaterialAfterRender = (c, camera) => {
87
+ var _c$skeleton2;
88
+
89
+ c.material.uniforms.prevVelocityMatrix.value.multiplyMatrices(camera.projectionMatrix, c.modelViewMatrix);
90
+ if ((_c$skeleton2 = c.skeleton) != null && _c$skeleton2.boneTexture) saveBoneTexture(c);
91
+ };
92
+ const createGlobalDisableIblRadianceUniform = () => {
93
+ if (!ShaderChunk.envmap_physical_pars_fragment.includes("iblRadianceDisabled")) {
94
+ ShaderChunk.envmap_physical_pars_fragment = ShaderChunk.envmap_physical_pars_fragment.replace("vec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {",
95
+ /* glsl */
96
+ `
97
+ uniform bool iblRadianceDisabled;
98
+
99
+ vec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {
100
+ if(iblRadianceDisabled) return vec3(0.);
101
+ `);
102
+ }
103
+
104
+ if ("iblRadianceDisabled" in ShaderLib.physical.uniforms) return ShaderLib.physical.uniforms["iblRadianceDisabled"];
105
+ const globalIblRadianceDisabledUniform = {
106
+ value: false
107
+ };
108
+ ShaderLib.physical.uniforms.iblRadianceDisabled = globalIblRadianceDisabledUniform;
109
+ const {
110
+ clone
111
+ } = UniformsUtils;
112
+
113
+ UniformsUtils.clone = uniforms => {
114
+ const result = clone(uniforms);
115
+
116
+ if ("iblRadianceDisabled" in uniforms) {
117
+ result.iblRadianceDisabled = globalIblRadianceDisabledUniform;
118
+ }
119
+
120
+ return result;
121
+ };
122
+
123
+ return globalIblRadianceDisabledUniform;
124
+ };
125
+ const createGlobalDisableIblIradianceUniform = () => {
126
+ if (!ShaderChunk.envmap_physical_pars_fragment.includes("iblIrradianceDisabled")) {
127
+ ShaderChunk.envmap_physical_pars_fragment = ShaderChunk.envmap_physical_pars_fragment.replace("vec3 getIBLIrradiance( const in vec3 normal ) {",
128
+ /* glsl */
129
+ `
130
+ uniform bool iblIrradianceDisabled;
131
+
132
+ vec3 getIBLIrradiance( const in vec3 normal ) {
133
+ if(iblIrradianceDisabled) return vec3(0.);
134
+ `);
135
+ }
136
+
137
+ if ("iblIrradianceDisabled" in ShaderLib.physical.uniforms) return ShaderLib.physical.uniforms["iblIrradianceDisabled"];
138
+ const globalIblIrradianceDisabledUniform = {
139
+ value: false
140
+ };
141
+ ShaderLib.physical.uniforms.iblIrradianceDisabled = globalIblIrradianceDisabledUniform;
142
+ const {
143
+ clone
144
+ } = UniformsUtils;
145
+
146
+ UniformsUtils.clone = uniforms => {
147
+ const result = clone(uniforms);
148
+
149
+ if ("iblIrradianceDisabled" in uniforms) {
150
+ result.iblIrradianceDisabled = globalIblIrradianceDisabledUniform;
151
+ }
152
+
153
+ return result;
154
+ };
155
+
156
+ return globalIblIrradianceDisabledUniform;
157
+ }; // source: https://github.com/mrdoob/three.js/blob/b9bc47ab1978022ab0947a9bce1b1209769b8d91/src/renderers/webgl/WebGLProgram.js#L228
158
+ // Unroll Loops
159
+
160
+ const unrollLoopPattern = /#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g;
161
+ function unrollLoops(string) {
162
+ return string.replace(unrollLoopPattern, loopReplacer);
163
+ }
164
+
165
+ function loopReplacer(match, start, end, snippet) {
166
+ let string = "";
167
+
168
+ for (let i = parseInt(start); i < parseInt(end); i++) {
169
+ string += snippet.replace(/\[\s*i\s*\]/g, "[ " + i + " ]").replace(/UNROLLED_LOOP_INDEX/g, i);
170
+ }
171
+
172
+ return string;
173
+ } //
174
+ const isGroundProjectedEnv = c => {
175
+ var _c$material$fragmentS;
176
+
177
+ return (_c$material$fragmentS = c.material.fragmentShader) == null ? void 0 : _c$material$fragmentS.includes("float intersection2 = diskIntersectWithBackFaceCulling( camPos, p, h, vec3( 0.0, 1.0, 0.0 ), radius );");
178
+ };
179
+ const isChildMaterialRenderable = (c, material = c.material) => {
180
+ return material.visible && material.depthWrite && material.depthTest && (!material.transparent || material.opacity > 0) && !isGroundProjectedEnv(c);
181
+ };
182
+ const materialProps = ["vertexTangent", "vertexColors", "vertexAlphas", "vertexUvs", "uvsVertexOnly", "supportsVertexTextures", "instancing", "instancingColor", "side", "flatShading", "skinning", "doubleSided", "flipSided"];
183
+ const copyNecessaryProps = (originalMaterial, newMaterial) => {
184
+ for (const props of materialProps) newMaterial[props] = originalMaterial[props];
185
+ };
186
+
187
+ var vertexShader = "#define GLSLIFY 1\nvarying vec2 vUv;void main(){vUv=position.xy*0.5+0.5;gl_Position=vec4(position.xy,1.0,1.0);}"; // eslint-disable-line
188
+
189
+ class CopyPass extends Pass {
190
+ constructor(textureCount = 1) {
191
+ super("CopyPass");
192
+ this.needsSwap = false;
193
+ this.renderTarget = createMultipleRenderTargets(1, 1, 1, {
194
+ depthBuffer: false
195
+ });
196
+ this.setTextureCount(textureCount);
197
+ }
198
+
199
+ setTextureCount(textureCount) {
200
+ var _this$fullscreenMater;
201
+
202
+ let definitions = "";
203
+ let body = "";
204
+
205
+ for (let i = 0; i < textureCount; i++) {
206
+ definitions +=
207
+ /* glsl */
208
+ `
209
+ uniform sampler2D inputTexture${i};
210
+ layout(location = ${i}) out vec4 gOutput${i};
211
+ `;
212
+ body +=
213
+ /* glsl */
214
+ `gOutput${i} = textureLod(inputTexture${i}, vUv, 0.);`;
215
+ }
216
+
217
+ (_this$fullscreenMater = this.fullscreenMaterial) == null ? void 0 : _this$fullscreenMater.dispose();
218
+ this.fullscreenMaterial = new ShaderMaterial({
219
+ fragmentShader:
220
+ /* glsl */
221
+ `
222
+ varying vec2 vUv;
223
+
224
+ ${definitions}
225
+
226
+ void main() {
227
+ ${body}
228
+ }
229
+ `,
230
+ vertexShader: vertexShader,
231
+ glslVersion: GLSL3,
232
+ blending: NoBlending,
233
+ depthWrite: false,
234
+ depthTest: false,
235
+ toneMapped: false
236
+ });
237
+
238
+ for (let i = 0; i < textureCount; i++) {
239
+ this.fullscreenMaterial.uniforms["inputTexture" + i] = new Uniform(null);
240
+
241
+ if (i >= this.renderTarget.textures.length) {
242
+ const texture = this.renderTarget.textures[0].clone();
243
+ texture.isRenderTargetTexture = true;
244
+ this.renderTarget.textures.push(texture);
245
+ }
246
+ }
247
+ }
248
+
249
+ setSize(width, height) {
250
+ this.renderTarget.setSize(width, height);
251
+ }
252
+
253
+ render(renderer) {
254
+ renderer.setRenderTarget(this.renderTarget);
255
+ renderer.render(this.scene, this.camera);
256
+ }
257
+
258
+ }
259
+
260
+ var fragmentShader$5 = "#define GLSLIFY 1\nvarying vec2 vUv;uniform sampler2D velocityTexture;uniform sampler2D depthTexture;uniform sampler2D lastDepthTexture;uniform float blend;uniform float neighborhoodClampIntensity;uniform bool constantBlend;uniform bool fullAccumulate;uniform vec2 invTexSize;uniform mat4 projectionMatrix;uniform mat4 projectionMatrixInverse;uniform mat4 cameraMatrixWorld;uniform vec3 cameraPos;uniform mat4 prevViewMatrix;uniform mat4 prevCameraMatrixWorld;uniform mat4 prevProjectionMatrix;uniform mat4 prevProjectionMatrixInverse;uniform bool reset;uniform float delta;\n#define EPSILON 0.00001\n#include <packing>\n#include <reproject>\nvoid main(){getDepthAndDilatedUVOffset(depthTexture,vUv,depth,dilatedDepth,depthTexel);if(dot(depthTexel.rgb,depthTexel.rgb)==0.0){\n#ifdef neighborhoodClamp\n#pragma unroll_loop_start\nfor(int i=0;i<textureCount;i++){gOutput[i]=textureLod(inputTexture[i],vUv,0.0);}\n#pragma unroll_loop_end\n#else\ndiscard;\n#endif\nreturn;}vec2 dilatedUv=vUv+dilatedUvOffset;edgeStrength=computeEdgeStrength(depth,invTexSize);vec4 inputTexel[textureCount];vec4 accumulatedTexel[textureCount];bool textureSampledThisFrame[textureCount];\n#pragma unroll_loop_start\nfor(int i=0;i<textureCount;i++){inputTexel[i]=textureLod(inputTexture[i],vUv,0.0);doColorTransform[i]=luminance(inputTexel[i].rgb)>0.0;textureSampledThisFrame[i]=inputTexel[i].r>=0.;if(textureSampledThisFrame[i]){transformColor(inputTexel[i].rgb);}else{inputTexel[i].rgb=vec3(0.0);}texIndex++;}\n#pragma unroll_loop_end\ntexIndex=0;velocityTexel=textureLod(velocityTexture,vUv,0.0);didMove=dot(velocityTexel.xy,velocityTexel.xy)>0.000000001;\n#ifdef dilation\nvec2 octahedronEncodedNormal=textureLod(velocityTexture,dilatedUv,0.0).ba;\n#else\nvec2 octahedronEncodedNormal=velocityTexel.ba;\n#endif\nvec3 worldNormal=Decode(octahedronEncodedNormal);vec3 worldPos=screenSpaceToWorldSpace(vUv,depth,cameraMatrixWorld,projectionMatrixInverse);vec2 reprojectedUvDiffuse=vec2(-10.0);vec2 reprojectedUvSpecular[textureCount];vec2 reprojectedUv;bool reprojectHitPoint;\n#pragma unroll_loop_start\nfor(int i=0;i<textureCount;i++){reprojectHitPoint=reprojectSpecular[i]&&inputTexel[i].a>0.0;if(reprojectHitPoint){reprojectedUvSpecular[i]=getReprojectedUV(depth,worldPos,worldNormal,inputTexel[i].a);}else{reprojectedUvSpecular[i]=vec2(-1.0);}if(reprojectedUvDiffuse.x==-10.0&&reprojectedUvSpecular[i].x<0.0){reprojectedUvDiffuse=getReprojectedUV(depth,worldPos,worldNormal,0.0);}reprojectedUv=reprojectedUvSpecular[i].x>=0.0 ? reprojectedUvSpecular[i]: reprojectedUvDiffuse;if(reprojectedUv.x<0.0){accumulatedTexel[i]=vec4(inputTexel[i].rgb,0.0);}else{accumulatedTexel[i]=sampleReprojectedTexture(accumulatedTexture[i],reprojectedUv);transformColor(accumulatedTexel[i].rgb);if(textureSampledThisFrame[i]){accumulatedTexel[i].a++;if(neighborhoodClamp[i]){vec3 clampedColor=accumulatedTexel[i].rgb;clampNeighborhood(inputTexture[i],clampedColor,inputTexel[i].rgb);accumulatedTexel[i].rgb=mix(accumulatedTexel[i].rgb,clampedColor,neighborhoodClampIntensity);}}else{inputTexel[i].rgb=accumulatedTexel[i].rgb;}}texIndex++;}\n#pragma unroll_loop_end\ntexIndex=0;float m=1.-delta/(1./60.);float fpsAdjustedBlend=blend+max(0.,(1.-blend)*m);float maxValue=(fullAccumulate&&!didMove)? 1.0 : fpsAdjustedBlend;vec3 outputColor;float temporalReprojectMix;\n#pragma unroll_loop_start\nfor(int i=0;i<textureCount;i++){if(constantBlend){temporalReprojectMix=accumulatedTexel[i].a==0.0 ? 0.0 : fpsAdjustedBlend;}else{temporalReprojectMix=fpsAdjustedBlend;if(reset)accumulatedTexel[i].a=0.0;temporalReprojectMix=min(1.-1./(accumulatedTexel[i].a+1.0),maxValue);}outputColor=mix(inputTexel[i].rgb,accumulatedTexel[i].rgb,temporalReprojectMix);undoColorTransform(outputColor);gOutput[i]=vec4(outputColor,accumulatedTexel[i].a);texIndex++;}\n#pragma unroll_loop_end\n#ifdef useTemporalReprojectCustomComposeShader\ntemporalReprojectCustomComposeShader\n#endif\n}"; // eslint-disable-line
261
+
262
+ var reproject = "#define GLSLIFY 1\nvec4 velocityTexel;float dilatedDepth;vec2 dilatedUvOffset;int texIndex;bool didMove;vec4 depthTexel;float depth;float edgeStrength;\n#define luminance(a) dot(vec3(0.2125, 0.7154, 0.0721), a)\nvec3 screenSpaceToWorldSpace(const vec2 uv,const float depth,mat4 curMatrixWorld,const mat4 projMatrixInverse){vec4 ndc=vec4((uv.x-0.5)*2.0,(uv.y-0.5)*2.0,(depth-0.5)*2.0,1.0);vec4 clip=projMatrixInverse*ndc;vec4 view=curMatrixWorld*(clip/clip.w);return view.xyz;}vec2 viewSpaceToScreenSpace(const vec3 position,const mat4 projMatrix){vec4 projectedCoord=projMatrix*vec4(position,1.0);projectedCoord.xy/=projectedCoord.w;projectedCoord.xy=projectedCoord.xy*0.5+0.5;return projectedCoord.xy;}bool doColorTransform[textureCount];\n#ifdef logTransform\nvoid transformColor(inout vec3 color){if(!doColorTransform[texIndex])return;float lum=luminance(color);float diff=min(1.0,lum-0.99);if(diff>0.0){color=vec3(diff*0.1);return;}color=log(max(color,vec3(EPSILON)));}void undoColorTransform(inout vec3 color){if(!doColorTransform[texIndex])return;color=exp(color);}\n#else\n#define transformColor\n#define undoColorTransform\n#endif\nvoid getNeighborhoodAABB(const sampler2D tex,inout vec3 minNeighborColor,inout vec3 maxNeighborColor){for(int x=-neighborhoodClampRadius;x<=neighborhoodClampRadius;x++){for(int y=-neighborhoodClampRadius;y<=neighborhoodClampRadius;y++){if(x!=0||y!=0){vec2 offset=vec2(x,y)*invTexSize;vec2 neighborUv=vUv+offset;vec4 neighborTexel=textureLod(tex,neighborUv,0.0);transformColor(neighborTexel.rgb);minNeighborColor=min(neighborTexel.rgb,minNeighborColor);maxNeighborColor=max(neighborTexel.rgb,maxNeighborColor);}}}}void clampNeighborhood(const sampler2D tex,inout vec3 color,const vec3 inputColor){vec3 minNeighborColor=inputColor;vec3 maxNeighborColor=inputColor;getNeighborhoodAABB(tex,minNeighborColor,maxNeighborColor);color=clamp(color,minNeighborColor,maxNeighborColor);}\n#ifdef dilation\nvoid getDilatedDepthUVOffset(const sampler2D tex,const vec2 centerUv,out float depth,out float dilatedDepth,out vec4 closestDepthTexel){float closestDepth=0.0;for(int x=-1;x<=1;x++){for(int y=-1;y<=1;y++){vec2 offset=vec2(x,y)*invTexSize;vec2 neighborUv=centerUv+offset;vec4 neighborDepthTexel=textureLod(tex,neighborUv,0.0);float neighborDepth=unpackRGBAToDepth(neighborDepthTexel);if(x==0&&y==0)depth=neighborDepth;if(neighborDepth>closestDepth){closestDepth=neighborDepth;closestDepthTexel=neighborDepthTexel;dilatedUvOffset=offset;}}}dilatedDepth=closestDepth;}\n#endif\nvoid getDepthAndDilatedUVOffset(sampler2D depthTex,vec2 uv,out float depth,out float dilatedDepth,out vec4 depthTexel){\n#ifdef dilation\ngetDilatedDepthUVOffset(depthTex,uv,depth,dilatedDepth,depthTexel);\n#else\ndepthTexel=textureLod(depthTex,uv,0.);depth=unpackRGBAToDepth(depthTexel);dilatedDepth=depth;\n#endif\n}bool planeDistanceDisocclusionCheck(const vec3 worldPos,const vec3 lastWorldPos,const vec3 worldNormal,const float worldDistFactor){if(abs(dot(worldNormal,worldPos))==0.0)return false;vec3 toCurrent=worldPos-lastWorldPos;float distToPlane=abs(dot(toCurrent,worldNormal));return distToPlane>depthDistance*worldDistFactor;}bool worldDistanceDisocclusionCheck(const vec3 worldPos,const vec3 lastWorldPos,const float worldDistFactor){return distance(worldPos,lastWorldPos)>worldDistance*worldDistFactor;}bool validateReprojectedUV(const vec2 reprojectedUv,const vec3 worldPos,const vec3 worldNormal){if(reprojectedUv.x>1.0||reprojectedUv.x<0.0||reprojectedUv.y>1.0||reprojectedUv.y<0.0)return false;vec3 dilatedWorldPos=worldPos;vec3 lastWorldPos;float dilatedLastDepth,lastDepth;vec4 lastDepthTexel;vec2 dilatedReprojectedUv;\n#ifdef dilation\ndilatedWorldPos=screenSpaceToWorldSpace(vUv+dilatedUvOffset,dilatedDepth,cameraMatrixWorld,projectionMatrixInverse);getDepthAndDilatedUVOffset(lastDepthTexture,reprojectedUv,lastDepth,dilatedLastDepth,lastDepthTexel);dilatedReprojectedUv=reprojectedUv+dilatedUvOffset;\n#else\nlastDepthTexel=textureLod(lastDepthTexture,reprojectedUv,0.);lastDepth=unpackRGBAToDepth(lastDepthTexel);dilatedLastDepth=lastDepth;dilatedReprojectedUv=reprojectedUv;\n#endif\nlastWorldPos=screenSpaceToWorldSpace(dilatedReprojectedUv,dilatedLastDepth,prevCameraMatrixWorld,prevProjectionMatrixInverse);float worldDistFactor=clamp((50.0+distance(dilatedWorldPos,cameraPos))/100.,0.25,1.);if(worldDistanceDisocclusionCheck(dilatedWorldPos,lastWorldPos,worldDistFactor))return false;return!planeDistanceDisocclusionCheck(dilatedWorldPos,lastWorldPos,worldNormal,worldDistFactor);}vec2 reprojectHitPoint(const vec3 rayOrig,const float rayLength,const float depth){vec3 cameraRay=normalize(rayOrig-cameraPos);float cameraRayLength=distance(rayOrig,cameraPos);vec3 parallaxHitPoint=cameraPos+cameraRay*(cameraRayLength+rayLength);vec4 reprojectedParallaxHitPoint=prevViewMatrix*vec4(parallaxHitPoint,1.0);vec2 hitPointUv=viewSpaceToScreenSpace(reprojectedParallaxHitPoint.xyz,prevProjectionMatrix);return hitPointUv;}vec2 getReprojectedUV(const float depth,const vec3 worldPos,const vec3 worldNormal,const float rayLength){if(rayLength!=0.0){vec2 reprojectedUv=reprojectHitPoint(worldPos,rayLength,depth);if(validateReprojectedUV(reprojectedUv,worldPos,worldNormal)){return reprojectedUv;}return vec2(-1.);}vec2 reprojectedUv=vUv-velocityTexel.rg;if(validateReprojectedUV(reprojectedUv,worldPos,worldNormal)){return reprojectedUv;}return vec2(-1.);}vec4 SampleTextureCatmullRom(const sampler2D tex,const vec2 uv,const vec2 texSize){vec2 samplePos=uv*texSize;vec2 texPos1=floor(samplePos-0.5f)+0.5f;vec2 f=samplePos-texPos1;vec2 w0=f*(-0.5f+f*(1.0f-0.5f*f));vec2 w1=1.0f+f*f*(-2.5f+1.5f*f);vec2 w2=f*(0.5f+f*(2.0f-1.5f*f));vec2 w3=f*f*(-0.5f+0.5f*f);vec2 w12=w1+w2;vec2 offset12=w2/(w1+w2);vec2 texPos0=texPos1-1.;vec2 texPos3=texPos1+2.;vec2 texPos12=texPos1+offset12;texPos0/=texSize;texPos3/=texSize;texPos12/=texSize;vec4 result=vec4(0.0);result+=textureLod(tex,vec2(texPos0.x,texPos0.y),0.0f)*w0.x*w0.y;result+=textureLod(tex,vec2(texPos12.x,texPos0.y),0.0f)*w12.x*w0.y;result+=textureLod(tex,vec2(texPos3.x,texPos0.y),0.0f)*w3.x*w0.y;result+=textureLod(tex,vec2(texPos0.x,texPos12.y),0.0f)*w0.x*w12.y;result+=textureLod(tex,vec2(texPos12.x,texPos12.y),0.0f)*w12.x*w12.y;result+=textureLod(tex,vec2(texPos3.x,texPos12.y),0.0f)*w3.x*w12.y;result+=textureLod(tex,vec2(texPos0.x,texPos3.y),0.0f)*w0.x*w3.y;result+=textureLod(tex,vec2(texPos12.x,texPos3.y),0.0f)*w12.x*w3.y;result+=textureLod(tex,vec2(texPos3.x,texPos3.y),0.0f)*w3.x*w3.y;result=max(result,vec4(0.));return result;}vec2 sampleBlocky(vec2 p){vec2 d=vec2(dFdx(p.x),dFdy(p.y))/invTexSize;p/=invTexSize;vec2 fA=p-0.5*d,iA=floor(fA);vec2 fB=p+0.5*d,iB=floor(fB);return(iA+(iB-iA)*(fB-iB)/d+0.5)*invTexSize;}float computeEdgeStrength(float unpackedDepth,vec2 texelSize){float depthX=unpackRGBAToDepth(textureLod(depthTexture,vUv+vec2(texelSize.x,0.0),0.0))-unpackRGBAToDepth(textureLod(depthTexture,vUv-vec2(texelSize.x,0.0),0.0));float depthY=unpackRGBAToDepth(textureLod(depthTexture,vUv+vec2(0.0,texelSize.y),0.0))-unpackRGBAToDepth(textureLod(depthTexture,vUv-vec2(0.0,texelSize.y),0.0));float gradientMagnitude=sqrt(depthX*depthX+depthY*depthY);float edgeStrength=min(100000.*gradientMagnitude/(unpackedDepth+0.001),1.);return edgeStrength*edgeStrength;}float computeEdgeStrengthFast(float unpackedDepth){float depthX=dFdx(unpackedDepth);float depthY=dFdy(unpackedDepth);float edgeStrength=depthX*depthX+depthY*depthY;return min(1.,pow(pow(edgeStrength,0.25)*500.,4.));}vec4 sampleReprojectedTexture(const sampler2D tex,const vec2 reprojectedUv){vec4 catmull=SampleTextureCatmullRom(tex,reprojectedUv,1.0/invTexSize);vec4 blocky=SampleTextureCatmullRom(tex,sampleBlocky(reprojectedUv),1.0/invTexSize);vec4 reprojectedTexel=mix(catmull,blocky,edgeStrength);reprojectedTexel.a=min(catmull.a,blocky.a);return reprojectedTexel;}vec2 OctWrap(vec2 v){vec2 w=1.0-abs(v.yx);if(v.x<0.0)w.x=-w.x;if(v.y<0.0)w.y=-w.y;return w;}vec2 Encode(vec3 n){n/=(abs(n.x)+abs(n.y)+abs(n.z));n.xy=n.z>0.0 ? n.xy : OctWrap(n.xy);n.xy=n.xy*0.5+0.5;return n.xy;}vec3 Decode(vec2 f){f=f*2.0-1.0;vec3 n=vec3(f.x,f.y,1.0-abs(f.x)-abs(f.y));float t=max(-n.z,0.0);n.x+=n.x>=0.0 ?-t : t;n.y+=n.y>=0.0 ?-t : t;return normalize(n);}"; // eslint-disable-line
263
+
264
+ class TemporalReprojectMaterial extends ShaderMaterial {
265
+ constructor(textureCount = 1, temporalReprojectCustomComposeShader = "") {
266
+ let finalFragmentShader = fragmentShader$5.replace("#include <reproject>", reproject);
267
+
268
+ if (typeof temporalReprojectCustomComposeShader === "string") {
269
+ finalFragmentShader = finalFragmentShader.replace("temporalReprojectCustomComposeShader", temporalReprojectCustomComposeShader);
270
+ }
271
+
272
+ let definitions = "";
273
+
274
+ for (let i = 0; i < textureCount; i++) {
275
+ definitions +=
276
+ /* glsl */
277
+ `
278
+ uniform sampler2D inputTexture${i};
279
+ uniform sampler2D accumulatedTexture${i};
280
+
281
+ layout(location = ${i}) out vec4 gOutput${i};
282
+ `;
283
+ }
284
+
285
+ finalFragmentShader = definitions + finalFragmentShader.replaceAll("textureCount", textureCount);
286
+ finalFragmentShader = unrollLoops(finalFragmentShader);
287
+ const matches = finalFragmentShader.matchAll(/inputTexture\[\s*[0-9]+\s*]/g);
288
+
289
+ for (const [key] of matches) {
290
+ const number = key.replace(/[^0-9]/g, "");
291
+ finalFragmentShader = finalFragmentShader.replace(key, "inputTexture" + number);
292
+ }
293
+
294
+ const matches2 = finalFragmentShader.matchAll(/accumulatedTexture\[\s*[0-9]+\s*]/g);
295
+
296
+ for (const [key] of matches2) {
297
+ const number = key.replace(/[^0-9]/g, "");
298
+ finalFragmentShader = finalFragmentShader.replace(key, "accumulatedTexture" + number);
299
+ }
300
+
301
+ const matches3 = finalFragmentShader.matchAll(/gOutput\[\s*[0-9]+\s*]/g);
302
+
303
+ for (const [key] of matches3) {
304
+ const number = key.replace(/[^0-9]/g, "");
305
+ finalFragmentShader = finalFragmentShader.replace(key, "gOutput" + number);
306
+ }
307
+
308
+ super({
309
+ type: "TemporalReprojectMaterial",
310
+ uniforms: {
311
+ velocityTexture: new Uniform(null),
312
+ depthTexture: new Uniform(null),
313
+ lastDepthTexture: new Uniform(null),
314
+ blend: new Uniform(0),
315
+ neighborhoodClampIntensity: new Uniform(0),
316
+ constantBlend: new Uniform(false),
317
+ fullAccumulate: new Uniform(false),
318
+ reset: new Uniform(false),
319
+ delta: new Uniform(0),
320
+ invTexSize: new Uniform(new Vector2()),
321
+ projectionMatrix: new Uniform(new Matrix4()),
322
+ projectionMatrixInverse: new Uniform(new Matrix4()),
323
+ cameraMatrixWorld: new Uniform(new Matrix4()),
324
+ viewMatrix: new Uniform(new Matrix4()),
325
+ prevViewMatrix: new Uniform(new Matrix4()),
326
+ prevCameraMatrixWorld: new Uniform(new Matrix4()),
327
+ prevProjectionMatrix: new Uniform(new Matrix4()),
328
+ prevProjectionMatrixInverse: new Uniform(new Matrix4()),
329
+ cameraPos: new Uniform(new Vector3())
330
+ },
331
+ vertexShader,
332
+ fragmentShader: finalFragmentShader,
333
+ blending: NoBlending,
334
+ depthWrite: false,
335
+ depthTest: false,
336
+ toneMapped: false,
337
+ glslVersion: GLSL3
338
+ });
339
+
340
+ for (let i = 0; i < textureCount; i++) {
341
+ this.uniforms["inputTexture" + i] = new Uniform(null);
342
+ this.uniforms["accumulatedTexture" + i] = new Uniform(null);
343
+ }
344
+
345
+ if (typeof temporalReprojectCustomComposeShader === "string") this.defines.useTemporalReprojectCustomComposeShader = "";
346
+ }
347
+
348
+ }
349
+
350
+ // from: https://news.ycombinator.com/item?id=17876741
351
+
352
+ const g = 1.32471795724474602596090885447809; // Plastic number
353
+
354
+ const a1 = 1.0 / g;
355
+ const a2 = 1.0 / (g * g);
356
+ const base = 1.1127756842787055; // harmoniousNumber(7), yields better coverage compared to using 0.5
357
+
358
+ const generateR2 = count => {
359
+ const points = [];
360
+
361
+ for (let n = 0; n < count; n++) {
362
+ points.push([(base + a1 * n) % 1, (base + a2 * n) % 1]);
363
+ }
364
+
365
+ return points;
366
+ };
367
+
368
+ const defaultTemporalReprojectPassOptions = {
369
+ blend: 0.9,
370
+ dilation: false,
371
+ constantBlend: false,
372
+ fullAccumulate: false,
373
+ neighborhoodClamp: false,
374
+ neighborhoodClampRadius: 1,
375
+ neighborhoodClampIntensity: 1,
376
+ logTransform: false,
377
+ depthDistance: 0.25,
378
+ worldDistance: 0.375,
379
+ reprojectSpecular: false,
380
+ temporalReprojectCustomComposeShader: null,
381
+ renderTarget: null
382
+ };
383
+ const tmpProjectionMatrix$1 = new Matrix4();
384
+ const tmpProjectionMatrixInverse$1 = new Matrix4();
385
+ class TemporalReprojectPass extends Pass {
386
+ constructor(scene, camera, velocityDepthNormalPass, textureCount = 1, options = defaultTemporalReprojectPassOptions) {
387
+ super("TemporalReprojectPass");
388
+ this.needsSwap = false;
389
+ this.clock = new Clock();
390
+ this.r2Sequence = [];
391
+ this.pointsIndex = 0;
392
+ this.lastCameraTransform = {
393
+ position: new Vector3(),
394
+ quaternion: new Quaternion()
395
+ };
396
+ this._scene = scene;
397
+ this._camera = camera;
398
+ this.textureCount = textureCount;
399
+ options = { ...defaultTemporalReprojectPassOptions,
400
+ ...options
401
+ };
402
+ this.renderTarget = createMultipleRenderTargets(1, 1, textureCount, {
403
+ minFilter: LinearFilter,
404
+ magFilter: LinearFilter,
405
+ type: HalfFloatType,
406
+ depthBuffer: false
407
+ });
408
+ this.renderTarget.textures.map((texture, index) => texture.name = "TemporalReprojectPass.accumulatedTexture" + index);
409
+ this.fullscreenMaterial = new TemporalReprojectMaterial(textureCount, options.temporalReprojectCustomComposeShader);
410
+ this.fullscreenMaterial.defines.textureCount = textureCount;
411
+ if (options.dilation) this.fullscreenMaterial.defines.dilation = "";
412
+ if (options.neighborhoodClamp) this.fullscreenMaterial.defines.neighborhoodClamp = "";
413
+ if (options.logTransform) this.fullscreenMaterial.defines.logTransform = "";
414
+ this.fullscreenMaterial.defines.neighborhoodClampRadius = parseInt(options.neighborhoodClampRadius);
415
+ this.fullscreenMaterial.defines.depthDistance = options.depthDistance.toPrecision(5);
416
+ this.fullscreenMaterial.defines.worldDistance = options.worldDistance.toPrecision(5);
417
+ this.fullscreenMaterial.uniforms.blend.value = options.blend;
418
+ this.fullscreenMaterial.uniforms.constantBlend.value = options.constantBlend;
419
+ this.fullscreenMaterial.uniforms.fullAccumulate.value = options.fullAccumulate;
420
+ this.fullscreenMaterial.uniforms.neighborhoodClampIntensity.value = options.neighborhoodClampIntensity;
421
+ this.fullscreenMaterial.uniforms.projectionMatrix.value = camera.projectionMatrix.clone();
422
+ this.fullscreenMaterial.uniforms.projectionMatrixInverse.value = camera.projectionMatrixInverse.clone();
423
+ this.fullscreenMaterial.uniforms.cameraMatrixWorld.value = camera.matrixWorld;
424
+ this.fullscreenMaterial.uniforms.viewMatrix.value = camera.matrixWorldInverse;
425
+ this.fullscreenMaterial.uniforms.cameraPos.value = camera.position;
426
+ this.fullscreenMaterial.uniforms.prevViewMatrix.value = camera.matrixWorldInverse.clone();
427
+ this.fullscreenMaterial.uniforms.prevCameraMatrixWorld.value = camera.matrixWorld.clone();
428
+ this.fullscreenMaterial.uniforms.prevProjectionMatrix.value = camera.projectionMatrix.clone();
429
+ this.fullscreenMaterial.uniforms.prevProjectionMatrixInverse.value = camera.projectionMatrixInverse.clone(); // init copy pass to save the accumulated textures and the textures from the last frame
430
+
431
+ this.copyPass = new CopyPass(textureCount);
432
+
433
+ for (let i = 0; i < textureCount; i++) {
434
+ const accumulatedTexture = this.copyPass.renderTarget.textures[i];
435
+ accumulatedTexture.type = HalfFloatType;
436
+ accumulatedTexture.minFilter = LinearFilter;
437
+ accumulatedTexture.magFilter = LinearFilter;
438
+ accumulatedTexture.needsUpdate = true;
439
+ }
440
+
441
+ this.fullscreenMaterial.uniforms.velocityTexture.value = velocityDepthNormalPass.texture;
442
+ this.fullscreenMaterial.uniforms.depthTexture.value = velocityDepthNormalPass.depthTexture;
443
+
444
+ for (const opt of ["reprojectSpecular", "neighborhoodClamp"]) {
445
+ let value = options[opt];
446
+ if (typeof value !== "array") value = Array(textureCount).fill(value);
447
+ this.fullscreenMaterial.defines[opt] =
448
+ /* glsl */
449
+ `bool[](${value.join(", ")})`;
450
+ }
451
+
452
+ this.options = options;
453
+ this.velocityDepthNormalPass = velocityDepthNormalPass;
454
+ }
455
+
456
+ setTextures(textures) {
457
+ if (!Array.isArray(textures)) textures = [textures];
458
+
459
+ for (let i = 0; i < textures.length; i++) {
460
+ const texture = textures[i];
461
+ this.fullscreenMaterial.uniforms["inputTexture" + i] = new Uniform(texture);
462
+ }
463
+ }
464
+
465
+ dispose() {
466
+ super.dispose();
467
+ this.renderTarget.dispose();
468
+ this.copyPass.dispose();
469
+ this.fullscreenMaterial.dispose();
470
+ }
471
+
472
+ setSize(width, height) {
473
+ this.renderTarget.setSize(width, height);
474
+ this.copyPass.setSize(width, height);
475
+ this.fullscreenMaterial.uniforms.invTexSize.value.set(1 / width, 1 / height);
476
+ }
477
+
478
+ get texture() {
479
+ return this.renderTarget.textures[0];
480
+ }
481
+
482
+ reset() {
483
+ this.fullscreenMaterial.uniforms.reset.value = true;
484
+ }
485
+
486
+ render(renderer) {
487
+ const delta = Math.min(1 / 10, this.clock.getDelta());
488
+ this.fullscreenMaterial.uniforms.delta.value = delta;
489
+ tmpProjectionMatrix$1.copy(this._camera.projectionMatrix);
490
+ tmpProjectionMatrixInverse$1.copy(this._camera.projectionMatrixInverse);
491
+ if (this._camera.view) this._camera.view.enabled = false;
492
+
493
+ this._camera.updateProjectionMatrix();
494
+
495
+ this.fullscreenMaterial.uniforms.projectionMatrix.value.copy(this._camera.projectionMatrix);
496
+ this.fullscreenMaterial.uniforms.projectionMatrixInverse.value.copy(this._camera.projectionMatrixInverse);
497
+ this.fullscreenMaterial.uniforms.lastDepthTexture.value = this.velocityDepthNormalPass.lastDepthTexture;
498
+ if (this._camera.view) this._camera.view.enabled = true;
499
+
500
+ this._camera.projectionMatrix.copy(tmpProjectionMatrix$1);
501
+
502
+ this._camera.projectionMatrixInverse.copy(tmpProjectionMatrixInverse$1);
503
+
504
+ renderer.setRenderTarget(this.renderTarget);
505
+ renderer.render(this.scene, this.camera);
506
+ this.fullscreenMaterial.uniforms.reset.value = false;
507
+
508
+ for (let i = 0; i < this.textureCount; i++) {
509
+ this.copyPass.fullscreenMaterial.uniforms["inputTexture" + i].value = this.renderTarget.textures[i];
510
+ this.fullscreenMaterial.uniforms["accumulatedTexture" + i].value = this.copyPass.renderTarget.textures[i];
511
+ }
512
+
513
+ this.copyPass.render(renderer); // save last transformations
514
+
515
+ this.fullscreenMaterial.uniforms.prevCameraMatrixWorld.value.copy(this._camera.matrixWorld);
516
+ this.fullscreenMaterial.uniforms.prevViewMatrix.value.copy(this._camera.matrixWorldInverse);
517
+ this.fullscreenMaterial.uniforms.prevProjectionMatrix.value.copy(this.fullscreenMaterial.uniforms.projectionMatrix.value);
518
+ this.fullscreenMaterial.uniforms.prevProjectionMatrixInverse.value.copy(this.fullscreenMaterial.uniforms.projectionMatrixInverse.value);
519
+ }
520
+
521
+ jitter(jitterScale = 1) {
522
+ this.unjitter();
523
+ if (this.r2Sequence.length === 0) this.r2Sequence = generateR2(256).map(([a, b]) => [a - 0.5, b - 0.5]);
524
+ this.pointsIndex = (this.pointsIndex + 1) % this.r2Sequence.length;
525
+ const [x, y] = this.r2Sequence[this.pointsIndex];
526
+ const {
527
+ width,
528
+ height
529
+ } = this.renderTarget;
530
+ if (this._camera.setViewOffset) this._camera.setViewOffset(width, height, x * jitterScale, y * jitterScale, width, height);
531
+ }
532
+
533
+ unjitter() {
534
+ if (this._camera.clearViewOffset) this._camera.clearViewOffset();
535
+ }
536
+
537
+ }
538
+
539
+ var traa_compose = "#define GLSLIFY 1\nuniform sampler2D inputTexture;void mainImage(const in vec4 inputColor,const in vec2 uv,out vec4 outputColor){vec4 accumulatedTexel=textureLod(inputTexture,vUv,0.);outputColor=vec4(accumulatedTexel.rgb,1.);}"; // eslint-disable-line
540
+
541
+ const defaultTRAAOptions = {
542
+ blend: 0.8,
543
+ constantBlend: true,
544
+ dilation: true,
545
+ logTransform: false,
546
+ // ! todo: check if can use logTransform without artifacts
547
+ neighborhoodClampRadius: 2,
548
+ depthDistance: 10,
549
+ worldDistance: 5,
550
+ neighborhoodClamp: true
551
+ };
552
+ class TRAAEffect extends Effect {
553
+ constructor(scene, camera, velocityDepthNormalPass, options = defaultTRAAOptions) {
554
+ super("TRAAEffect", traa_compose, {
555
+ type: "FinalTRAAEffectMaterial",
556
+ uniforms: new Map([["inputTexture", new Uniform(null)]])
557
+ });
558
+ this._scene = scene;
559
+ this._camera = camera;
560
+ options = { ...defaultTRAAOptions,
561
+ ...options
562
+ };
563
+ this.temporalReprojectPass = new TemporalReprojectPass(scene, camera, velocityDepthNormalPass, 1, options);
564
+ this.uniforms.get("inputTexture").value = this.temporalReprojectPass.texture;
565
+ this.setSize(options.width, options.height);
566
+ }
567
+
568
+ setSize(width, height) {
569
+ this.temporalReprojectPass.setSize(width, height);
570
+ }
571
+
572
+ dispose() {
573
+ super.dispose();
574
+ this.temporalReprojectPass.dispose();
575
+ }
576
+
577
+ update(renderer, inputBuffer) {
578
+ this.temporalReprojectPass.unjitter();
579
+ this.unjitteredProjectionMatrix = this._camera.projectionMatrix.clone();
580
+
581
+ this._camera.projectionMatrix.copy(this.unjitteredProjectionMatrix);
582
+
583
+ const noJitterMeshes = getVisibleChildren(this._scene).filter(c => isGroundProjectedEnv(c));
584
+
585
+ for (const mesh of noJitterMeshes) {
586
+ const renderData = renderer.properties.get(mesh.material);
587
+ if (!(renderData != null && renderData.programs)) continue;
588
+ const uniforms = Array.from(renderData.programs.values())[0].getUniforms();
589
+
590
+ if (!uniforms._patchedProjectionMatrix) {
591
+ const oldSetValue = uniforms.setValue.bind(uniforms);
592
+ uniforms._oldSetValue = oldSetValue;
593
+
594
+ uniforms.setValue = (gl, name, value, ...args) => {
595
+ if (name === "projectionMatrix") {
596
+ value = this.unjitteredProjectionMatrix;
597
+ }
598
+
599
+ oldSetValue(gl, name, value, ...args);
600
+ };
601
+
602
+ uniforms._patchedProjectionMatrix = true;
603
+ }
604
+
605
+ cancelAnimationFrame(uniforms._destroyPatchRAF);
606
+ cancelAnimationFrame(uniforms._destroyPatchRAF2);
607
+ uniforms._destroyPatchRAF = requestAnimationFrame(() => {
608
+ uniforms._destroyPatchRAF2 = requestAnimationFrame(() => {
609
+ uniforms.setValue = uniforms._oldSetValue;
610
+ delete uniforms._oldSetValue;
611
+ delete uniforms._patchedProjectionMatrix;
612
+ });
613
+ });
614
+ }
615
+
616
+ this.temporalReprojectPass.fullscreenMaterial.uniforms.inputTexture0.value = inputBuffer.texture;
617
+ this.temporalReprojectPass.jitter();
618
+ this.temporalReprojectPass.render(renderer);
619
+ }
620
+
621
+ }
622
+ TRAAEffect.DefaultOptions = defaultTRAAOptions;
623
+
624
+ var fragmentShader$4 = "#define GLSLIFY 1\nvarying vec2 vUv;uniform sampler2D depthTexture;uniform sampler2D normalTexture;uniform sampler2D momentTexture;uniform vec2 invTexSize;uniform bool horizontal;uniform bool blurHorizontal;uniform float denoise[textureCount];uniform float depthPhi;uniform float normalPhi;uniform float roughnessPhi;uniform float denoiseKernel;uniform float stepSize;uniform mat4 projectionMatrixInverse;uniform mat4 projectionMatrix;uniform mat4 cameraMatrixWorld;uniform bool isFirstIteration;uniform bool isLastIteration;\n#include <packing>\n#define EPSILON 0.00001\n#define M_PI 3.1415926535897932384626433832795\n#define PI M_PI\n#define luminance(a) dot(a, vec3(0.2125, 0.7154, 0.0721))\n#include <denoiseCustomComposeShaderFunctions>\nvec3 screenSpaceToWorldSpace(const vec2 uv,const float depth,const mat4 curMatrixWorld){vec4 ndc=vec4((uv.x-0.5)*2.0,(uv.y-0.5)*2.0,(depth-0.5)*2.0,1.0);vec4 clip=projectionMatrixInverse*ndc;vec4 view=curMatrixWorld*(clip/clip.w);return view.xyz;}float distToPlane(const vec3 worldPos,const vec3 neighborWorldPos,const vec3 worldNormal){vec3 toCurrent=worldPos-neighborWorldPos;float distToPlane=abs(dot(toCurrent,worldNormal));return distToPlane;}float getDisocclusionBoostVariance(float visibleFrames){\n#ifdef useTemporalReprojectTextures\nreturn max(0.,-pow(visibleFrames,2.0)+50.0);\n#else\nreturn 0.;\n#endif\n}void tap(const vec2 neighborVec,const vec2 pixelStepOffset,const vec3 normal,const float depth,const float roughness,const vec3 worldPos,const float luma[textureCount],const float colorPhi[textureCount],inout vec3 denoisedColor[textureCount],inout float totalWeight[textureCount],inout float sumVariance[textureCount],inout float variance[textureCount]){vec2 fullNeighborUv=neighborVec*pixelStepOffset;vec2 neighborUvNearest=vUv+fullNeighborUv;vec2 bilinearOffset=neighborVec.y>0. ? invTexSize :-invTexSize;vec2 neighborUv=vUv+fullNeighborUv+bilinearOffset*0.5;vec2 neighborUvRoughness=vUv+fullNeighborUv*(roughness<0.15 ? roughness/0.15 : 1.)+bilinearOffset*0.5;float basicWeight=1.0;\n#ifdef useDepth\nvec4 neighborDepthTexel=textureLod(depthTexture,neighborUvNearest,0.);\n#ifdef RGBA_DEPTH_PACKING\nfloat neighborDepth=unpackRGBAToDepth(neighborDepthTexel);\n#else\nfloat neighborDepth=neighborDepthTexel.r;\n#endif\nvec3 neighborWorldPos=screenSpaceToWorldSpace(neighborUvNearest,neighborDepth,cameraMatrixWorld);\n#ifdef useNormal\nfloat depthDiff=1.-distToPlane(worldPos,neighborWorldPos,normal);\n#else\nfloat depthDiff=1.-abs(depth-neighborDepth);\n#endif\nfloat depthSimilarity=max(depthDiff/depthPhi,0.);basicWeight*=depthSimilarity;\n#endif\n#if defined(useNormal) || defined(useRoughness)\nvec4 neighborNormalTexel=textureLod(normalTexture,neighborUvNearest,0.);\n#endif\n#ifdef useNormal\nvec3 neighborNormal=neighborNormalTexel.xyz;float normalDiff=dot(neighborNormal,normal);float normalSimilarity=pow(max(0.,normalDiff),normalPhi);basicWeight*=normalSimilarity;\n#endif\n#ifdef useRoughness\nfloat neighborRoughness=neighborNormalTexel.a;neighborRoughness*=neighborRoughness;float roughnessDiff=abs(roughness-neighborRoughness);float roughnessSimilarity=exp(-roughnessDiff*roughnessPhi);basicWeight*=roughnessSimilarity;\n#endif\nvec4 neighborInputTexel[textureCount];vec3 neighborColor;float neighborLuma,lumaDiff,lumaSimilarity,disocclusionBoost;float weight[textureCount];\n#pragma unroll_loop_start\nfor(int i=0;i<textureCount;i++){neighborInputTexel[i]=textureLod(inputTexture[i],roughnessDependent[i]? neighborUvRoughness : neighborUv,0.);neighborColor=neighborInputTexel[i].rgb;neighborLuma=luminance(neighborColor);lumaDiff=abs(luma[i]-neighborLuma);lumaSimilarity=max(1.0-lumaDiff/colorPhi[i],0.0);weight[i]=min(basicWeight*lumaSimilarity,1.0);disocclusionBoost=variance[i]/1000.;weight[i]=mix(weight[i],1.,disocclusionBoost);denoisedColor[i]+=neighborColor*weight[i];totalWeight[i]+=weight[i];}\n#pragma unroll_loop_end\nif(isFirstIteration){\n#ifdef useMoment\nvec4 neighborMoment=textureLod(momentTexture,neighborUvNearest,0.);neighborInputTexel[0].a=neighborMoment.g-neighborMoment.r*neighborMoment.r;\n#if momentTextureCount > 1\nneighborInputTexel[1].a=neighborMoment.a-neighborMoment.b*neighborMoment.b;\n#endif\n#else\nfor(int i=0;i<textureCount;i++){neighborInputTexel[i].a=getDisocclusionBoostVariance(neighborInputTexel[i].a);}\n#endif\n}\n#pragma unroll_loop_start\nfor(int i=0;i<momentTextureCount;i++){\n#ifndef useMoment\nif(isFirstIteration)neighborInputTexel[i].a=1.0;\n#endif\nsumVariance[i]+=weight[i]*weight[i]*neighborInputTexel[i].a;}\n#pragma unroll_loop_end\n}void main(){vec4 depthTexel=textureLod(depthTexture,vUv,0.);if(depthTexel.r>0.9999||dot(depthTexel.rgb,depthTexel.rgb)==0.){discard;return;}vec3 worldPos;float depth;\n#ifdef useDepth\n#ifdef RGBA_DEPTH_PACKING\ndepth=unpackRGBAToDepth(depthTexel);\n#else\ndepth=depthTexel.r;\n#endif\nworldPos=screenSpaceToWorldSpace(vUv,depth,cameraMatrixWorld);\n#endif\nvec3 normal;float roughness;\n#ifdef useNormal\nvec4 normalTexel=textureLod(normalTexture,vUv,0.);normal=normalTexel.xyz;\n#endif\n#ifdef useRoughness\nroughness=normalTexel.a;roughness*=roughness;\n#endif\nvec3 denoisedColor[textureCount];float sumVariance[textureCount];float variance[textureCount];\n#ifdef doDenoise\nvec4 texel[textureCount];float luma[textureCount];float totalWeight[textureCount];float colorPhi[textureCount];\n#pragma unroll_loop_start\nfor(int i=0;i<textureCount;i++){totalWeight[i]=1.0;texel[i]=textureLod(inputTexture[i],vUv,0.);denoisedColor[i]=texel[i].rgb;luma[i]=luminance(texel[i].rgb);}\n#pragma unroll_loop_end\nif(isFirstIteration){\n#ifdef useMoment\nvec4 moment=textureLod(momentTexture,vUv,0.);texel[0].a=max(0.0,moment.g-moment.r*moment.r);\n#if momentTextureCount > 1\ntexel[1].a=max(0.0,moment.a-moment.b*moment.b);\n#endif\n#else\nfor(int i=0;i<textureCount;i++){texel[i].a=getDisocclusionBoostVariance(texel[i].a);}\n#endif\n}\n#pragma unroll_loop_start\nfor(int i=0;i<momentTextureCount;i++){variance[i]=min(1000.,texel[i].a);sumVariance[i]=texel[i].a;if(roughnessDependent[i]){colorPhi[i]=denoise[i]*sqrt(basicVariance[i]*roughness+sumVariance[i]);}else{colorPhi[i]=denoise[i]*sqrt(basicVariance[i]+sumVariance[i]);}}\n#pragma unroll_loop_end\nvec2 pixelStepOffset=invTexSize*stepSize;if(blurHorizontal){for(float i=-denoiseKernel;i<=denoiseKernel;i++){if(i!=0.){vec2 neighborVec=horizontal ? vec2(i,0.): vec2(0.,i);tap(neighborVec,pixelStepOffset,normal,depth,roughness,worldPos,luma,colorPhi,denoisedColor,totalWeight,sumVariance,variance);}}}else{for(float i=-denoiseKernel;i<=denoiseKernel;i++){if(i!=0.){vec2 neighborVec=horizontal ? vec2(-i,-i): vec2(i,-i);tap(neighborVec,pixelStepOffset,normal,depth,roughness,worldPos,luma,colorPhi,denoisedColor,totalWeight,sumVariance,variance);}}}\n#pragma unroll_loop_start\nfor(int i=0;i<textureCount;i++){sumVariance[i]/=totalWeight[i]*totalWeight[i];denoisedColor[i]/=totalWeight[i];}\n#pragma unroll_loop_end\n#else\n#pragma unroll_loop_start\nfor(int i=0;i<textureCount;i++){denoisedColor[i]=textureLod(inputTexture[i],vUv,0.).rgb;}\n#pragma unroll_loop_end\n#endif\nif(isLastIteration){\n#include <denoiseCustomComposeShader>\n}\n#include <outputShader>\n}"; // eslint-disable-line
625
+
626
+ // https://diharaw.github.io/post/adventures_in_hybrid_rendering/
627
+ // https://github.com/NVIDIAGameWorks/Falcor/tree/master/Source/RenderPasses/SVGFPass
628
+
629
+ const defaultDenoisePassOptions = {
630
+ moment: false,
631
+ depth: false,
632
+ normal: false,
633
+ roughness: false,
634
+ diffuse: true,
635
+ roughnessDependent: false,
636
+ basicVariance: 0.0005,
637
+ denoiseCustomComposeShader: "",
638
+ denoiseCustomComposeShaderFunctions: ""
639
+ };
640
+ const useEdgeStoppingTypes = [["moment", "", "useMoment"], ["depth", "depthPhi", "useDepth"], ["normal", "normalPhi", "useNormal"], ["roughness", "roughnessPhi", "useRoughness"]];
641
+ class DenoisePass extends Pass {
642
+ constructor(camera, textures = [], options = defaultDenoisePassOptions) {
643
+ super("DenoisePass");
644
+ this.iterations = 1;
645
+ if (!Array.isArray(textures)) textures = [textures];
646
+ options = { ...defaultDenoisePassOptions,
647
+ ...options
648
+ };
649
+ this.options = options;
650
+ this.fullscreenMaterial = new ShaderMaterial({
651
+ fragmentShader: "",
652
+ vertexShader: vertexShader,
653
+ uniforms: {
654
+ depthTexture: new Uniform(null),
655
+ normalTexture: new Uniform(null),
656
+ momentTexture: new Uniform(null),
657
+ invTexSize: new Uniform(new Vector2()),
658
+ horizontal: new Uniform(true),
659
+ blurHorizontal: new Uniform(true),
660
+ denoiseKernel: new Uniform(1),
661
+ denoiseDiffuse: new Uniform(1),
662
+ denoise: new Uniform([0]),
663
+ depthPhi: new Uniform(1),
664
+ normalPhi: new Uniform(1),
665
+ roughnessPhi: new Uniform(1),
666
+ stepSize: new Uniform(1),
667
+ isFirstIteration: new Uniform(false),
668
+ isLastIteration: new Uniform(false),
669
+ viewMatrix: new Uniform(camera.matrixWorldInverse),
670
+ projectionMatrix: new Uniform(camera.projectionMatrix),
671
+ cameraMatrixWorld: new Uniform(camera.matrixWorld),
672
+ projectionMatrixInverse: new Uniform(camera.projectionMatrixInverse)
673
+ },
674
+ glslVersion: GLSL3,
675
+ blending: NoBlending,
676
+ depthWrite: false,
677
+ depthTest: false,
678
+ toneMapped: false
679
+ });
680
+ const renderTargetOptions = {
681
+ type: HalfFloatType,
682
+ depthBuffer: false
683
+ };
684
+ this.renderTargetA = createMultipleRenderTargets(1, 1, textures.length, renderTargetOptions);
685
+ this.renderTargetB = createMultipleRenderTargets(1, 1, textures.length, renderTargetOptions);
686
+ if (typeof options.roughnessDependent === "boolean") options.roughnessDependent = Array(textures.length).fill(options.roughnessDependent);
687
+ this.fullscreenMaterial.defines.roughnessDependent =
688
+ /* glsl */
689
+ `bool[](${options.roughnessDependent.join(", ")})`;
690
+ if (typeof options.basicVariance === "number") options.basicVariance = Array(textures.length).fill(options.basicVariance);
691
+ this.fullscreenMaterial.defines.basicVariance =
692
+ /* glsl */
693
+ `float[](${options.basicVariance.map(n => n.toPrecision(5)).join(", ")})`; // register the texture uniforms
694
+
695
+ this.setTextures(textures);
696
+ }
697
+
698
+ setTextures(textures) {
699
+ if (!Array.isArray(textures)) textures = [textures];
700
+ this.textures = textures;
701
+ let definitions = "";
702
+ let outputShader = "";
703
+
704
+ for (let i = 0; i < this.textures.length; i++) {
705
+ definitions +=
706
+ /* glsl */
707
+ `layout(location = ${i}) out vec4 gTexture${i};\n`;
708
+ definitions +=
709
+ /* glsl */
710
+ `uniform sampler2D inputTexture${i};\n`;
711
+ outputShader +=
712
+ /* glsl */
713
+ `gTexture${i} = vec4(denoisedColor[${i}], sumVariance[${i}]);\n`;
714
+ }
715
+
716
+ let finalFragmentShader = definitions + fragmentShader$4.replace("#include <denoiseCustomComposeShaderFunctions>", this.options.denoiseCustomComposeShaderFunctions).replace("#include <denoiseCustomComposeShader>", this.options.denoiseCustomComposeShader).replace("#include <outputShader>", outputShader).replaceAll("textureCount", this.textures.length).replaceAll("momentTextureCount", Math.min(this.textures.length, 2));
717
+ finalFragmentShader = unrollLoops(finalFragmentShader);
718
+ const matches = finalFragmentShader.matchAll(/inputTexture\[\s*[0-9]+\s*]/g);
719
+
720
+ for (const [key] of matches) {
721
+ const number = key.replace(/[^0-9]/g, "");
722
+ finalFragmentShader = finalFragmentShader.replace(key, "inputTexture" + number);
723
+ }
724
+
725
+ delete this.fullscreenMaterial.defines.useTemporalReprojectTextures;
726
+
727
+ for (let i = 0; i < textures.length; i++) {
728
+ const texture = textures[i];
729
+ this.fullscreenMaterial.uniforms["inputTexture" + i] = new Uniform(texture);
730
+ if (texture.name.includes("TemporalReprojectPass.accumulatedTexture")) this.fullscreenMaterial.defines.useTemporalReprojectTextures = "";
731
+ }
732
+
733
+ this.fullscreenMaterial.fragmentShader = finalFragmentShader;
734
+ this.fullscreenMaterial.needsUpdate = true;
735
+ }
736
+
737
+ setDepthTexture(depthTexture) {
738
+ this.fullscreenMaterial.uniforms.depthTexture.value = depthTexture;
739
+ const packing = depthTexture.type === UnsignedByteType ? RGBADepthPacking : BasicDepthPacking;
740
+
741
+ if (packing === RGBADepthPacking) {
742
+ this.fullscreenMaterial.defines.RGBA_DEPTH_PACKING = "";
743
+ } else {
744
+ delete this.fullscreenMaterial.defines.RGBA_DEPTH_PACKING;
745
+ }
746
+
747
+ this.options.depth = true;
748
+ }
749
+
750
+ setNormalTexture(normalTexture, {
751
+ useRoughnessInAlphaChannel = false
752
+ } = {}) {
753
+ this.fullscreenMaterial.uniforms.normalTexture.value = normalTexture;
754
+ this.options.normal = true;
755
+ this.options.roughness = useRoughnessInAlphaChannel;
756
+ }
757
+
758
+ setMomentTexture(momentTexture) {
759
+ this.fullscreenMaterial.uniforms.momentTexture.value = momentTexture;
760
+ this.options.moment = true;
761
+ }
762
+
763
+ setSize(width, height) {
764
+ this.renderTargetA.setSize(width, height);
765
+ this.renderTargetB.setSize(width, height);
766
+ this.fullscreenMaterial.uniforms.invTexSize.value.set(1 / width, 1 / height);
767
+ }
768
+
769
+ dispose() {
770
+ super.dispose();
771
+ this.renderTargetA.dispose();
772
+ this.renderTargetB.dispose();
773
+ }
774
+
775
+ keepEdgeStoppingDefinesUpdated() {
776
+ for (const [name, phi, define] of useEdgeStoppingTypes) {
777
+ var _this$fullscreenMater;
778
+
779
+ const useEdgeStoppingType = this.options[name] && (phi === "" || ((_this$fullscreenMater = this.fullscreenMaterial.uniforms[phi]) == null ? void 0 : _this$fullscreenMater.value) > 0.001);
780
+
781
+ if (useEdgeStoppingType !== define in this.fullscreenMaterial.defines) {
782
+ useEdgeStoppingType ? this.fullscreenMaterial.defines[define] = "" : delete this.fullscreenMaterial.defines[define];
783
+ this.fullscreenMaterial.needsUpdate = true;
784
+ }
785
+ }
786
+ }
787
+
788
+ render(renderer) {
789
+ this.keepEdgeStoppingDefinesUpdated();
790
+ const denoiseKernel = this.fullscreenMaterial.uniforms.denoiseKernel.value;
791
+
792
+ if (this.iterations > 0) {
793
+ if (!("doDenoise" in this.fullscreenMaterial.defines)) {
794
+ this.fullscreenMaterial.defines.doDenoise = "";
795
+ this.fullscreenMaterial.needsUpdate = true;
796
+ }
797
+
798
+ for (let i = 0; i < 2 * this.iterations; i++) {
799
+ const horizontal = i % 2 === 0;
800
+ const stepSize = 2 ** ~~(i / 2);
801
+ const n = Math.log2(stepSize);
802
+ const blurHorizontal = n % 2 == 0;
803
+ this.fullscreenMaterial.uniforms.horizontal.value = horizontal;
804
+ this.fullscreenMaterial.uniforms.blurHorizontal.value = blurHorizontal;
805
+ this.fullscreenMaterial.uniforms.stepSize.value = stepSize;
806
+ this.fullscreenMaterial.uniforms.isFirstIteration.value = i === 0;
807
+ this.fullscreenMaterial.uniforms.isLastIteration.value = i === 2 * this.iterations - 1;
808
+ const renderTarget = horizontal ? this.renderTargetA : this.renderTargetB;
809
+
810
+ for (let j = 0; j < this.textures.length; j++) {
811
+ this.fullscreenMaterial.uniforms["inputTexture" + j].value = horizontal ? i === 0 ? this.textures[j] : this.renderTargetB.textures[j] : this.renderTargetA.textures[j];
812
+ }
813
+
814
+ renderer.setRenderTarget(renderTarget);
815
+ renderer.render(this.scene, this.camera);
816
+ }
817
+ } else {
818
+ if ("doDenoise" in this.fullscreenMaterial.defines) {
819
+ delete this.fullscreenMaterial.defines.doDenoise;
820
+ this.fullscreenMaterial.needsUpdate = true;
821
+ }
822
+
823
+ renderer.setRenderTarget(this.renderTargetB);
824
+ renderer.render(this.scene, this.camera);
825
+ this.fullscreenMaterial.uniforms.denoiseKernel.value = denoiseKernel;
826
+ } // reset the input textures
827
+
828
+
829
+ for (let i = 0; i < this.textures.length; i++) {
830
+ this.fullscreenMaterial.uniforms["inputTexture" + i].value = this.textures[i];
831
+ }
832
+ } // final composition will be written to buffer 0
833
+
834
+
835
+ get texture() {
836
+ return this.renderTargetB.textures[0];
837
+ }
838
+
839
+ }
840
+
841
+ var svgf_temporal_reproject = "#define GLSLIFY 1\nvec4 moment;if(!reset&&reprojectedUvDiffuse.x>=0.0){vec4 historyMoment=sampleReprojectedTexture(lastMomentTexture,reprojectedUvDiffuse);moment.r=luminance(gOutput[0].rgb);moment.g=moment.r*moment.r;\n#if textureCount > 1\nmoment.b=luminance(gOutput[1].rgb);moment.a=moment.b*moment.b;\n#endif\ngMoment=mix(moment,historyMoment,0.8);}else{moment.rg=vec2(0.,1000.);moment.ba=vec2(0.,1000.);gMoment=moment;return;}"; // eslint-disable-line
842
+
843
+ /* eslint-disable camelcase */
844
+ const defaultSVGFTemporalReprojectPassOptions = {
845
+ fullAccumulate: true,
846
+ temporalReprojectCustomComposeShader: svgf_temporal_reproject
847
+ };
848
+ class SVGFTemporalReprojectPass extends TemporalReprojectPass {
849
+ constructor(scene, camera, velocityDepthNormalPass, textureCount = 1, options = defaultSVGFTemporalReprojectPassOptions) {
850
+ options = { ...defaultSVGFTemporalReprojectPassOptions,
851
+ ...options
852
+ };
853
+ super(scene, camera, velocityDepthNormalPass, textureCount, options); // moment
854
+
855
+ this.momentTexture = this.renderTarget.textures[0].clone();
856
+ this.momentTexture.isRenderTargetTexture = true;
857
+ this.momentTexture.type = FloatType;
858
+ this.momentTexture.minFilter = NearestFilter;
859
+ this.momentTexture.magFilter = NearestFilter;
860
+ this.momentTexture.needsUpdate = true;
861
+ this.renderTarget.textures.push(this.momentTexture);
862
+ const momentBuffers =
863
+ /* glsl */
864
+ `
865
+ layout(location = ${textureCount}) out vec4 gMoment;
866
+
867
+ uniform sampler2D lastMomentTexture;
868
+ `;
869
+ this.fullscreenMaterial.fragmentShader = momentBuffers + this.fullscreenMaterial.fragmentShader;
870
+ this.fullscreenMaterial.uniforms = { ...this.fullscreenMaterial.uniforms,
871
+ ...{
872
+ lastMomentTexture: new Uniform(null)
873
+ }
874
+ };
875
+ const copyPassTextureCount = textureCount + 1;
876
+ this.copyPass.setTextureCount(copyPassTextureCount);
877
+ this.copyPass.fullscreenMaterial.uniforms["inputTexture" + (copyPassTextureCount - 1)].value = this.momentTexture;
878
+ const lastMomentTexture = this.copyPass.renderTarget.textures[copyPassTextureCount - 1];
879
+ lastMomentTexture.type = FloatType;
880
+ lastMomentTexture.minFilter = LinearFilter; // need to use linear filter over nearest filter
881
+
882
+ lastMomentTexture.magFilter = LinearFilter;
883
+ lastMomentTexture.needsUpdate = true;
884
+ this.fullscreenMaterial.uniforms.lastMomentTexture.value = lastMomentTexture;
885
+ this.fullscreenMaterial.defines.momentTextureCount = Math.min(2, textureCount);
886
+ }
887
+
888
+ }
889
+
890
+ class SVGF {
891
+ constructor(scene, camera, velocityDepthNormalPass, textureCount = 1, options = {}) {
892
+ this.svgfTemporalReprojectPass = new SVGFTemporalReprojectPass(scene, camera, velocityDepthNormalPass, textureCount, options);
893
+ const textures = this.svgfTemporalReprojectPass.renderTarget.textures.slice(0, textureCount);
894
+ this.denoisePass = new DenoisePass(camera, textures, options);
895
+ this.denoisePass.setMomentTexture(this.svgfTemporalReprojectPass.momentTexture);
896
+ this.setNonJitteredDepthTexture(velocityDepthNormalPass.depthTexture);
897
+ } // the denoised texture
898
+
899
+
900
+ get texture() {
901
+ return this.denoisePass.texture;
902
+ }
903
+
904
+ setGBuffers(depthTexture, normalTexture) {
905
+ this.setJitteredGBuffers(depthTexture, normalTexture);
906
+ this.setNonJitteredGBuffers(depthTexture, normalTexture);
907
+ }
908
+
909
+ setJitteredGBuffers(depthTexture, normalTexture, {
910
+ useRoughnessInAlphaChannel = false
911
+ } = {}) {
912
+ this.denoisePass.setDepthTexture(depthTexture);
913
+ this.denoisePass.setNormalTexture(normalTexture, {
914
+ useRoughnessInAlphaChannel
915
+ });
916
+ }
917
+
918
+ setNonJitteredDepthTexture(depthTexture) {
919
+ this.svgfTemporalReprojectPass.fullscreenMaterial.uniforms.depthTexture.value = depthTexture;
920
+ }
921
+
922
+ setVelocityTexture(texture) {
923
+ this.svgfTemporalReprojectPass.fullscreenMaterial.uniforms.velocityTexture.value = texture;
924
+ }
925
+
926
+ setSize(width, height) {
927
+ this.denoisePass.setSize(width, height);
928
+ this.svgfTemporalReprojectPass.setSize(width, height);
929
+ }
930
+
931
+ dispose() {
932
+ this.denoisePass.dispose();
933
+ this.svgfTemporalReprojectPass.dispose();
934
+ }
935
+
936
+ render(renderer) {
937
+ this.svgfTemporalReprojectPass.render(renderer);
938
+ this.denoisePass.render(renderer);
939
+ }
940
+
941
+ }
942
+
943
+ class CubeToEquirectEnvPass extends Pass {
944
+ constructor() {
945
+ super("CubeToEquirectEnvPass");
946
+ this.renderTarget = new WebGLRenderTarget(1, 1, {
947
+ depthBuffer: false,
948
+ type: FloatType
949
+ });
950
+ this.fullscreenMaterial = new ShaderMaterial({
951
+ fragmentShader:
952
+ /* glsl */
953
+ `
954
+ varying vec2 vUv;
955
+ uniform samplerCube cubeMap;
956
+
957
+ #define M_PI 3.1415926535897932384626433832795
958
+
959
+ // source: https://github.com/spite/CubemapToEquirectangular/blob/master/src/CubemapToEquirectangular.js
960
+ void main() {
961
+ float longitude = vUv.x * 2. * M_PI - M_PI + M_PI / 2.;
962
+ float latitude = vUv.y * M_PI;
963
+
964
+ vec3 dir = vec3(
965
+ - sin( longitude ) * sin( latitude ),
966
+ cos( latitude ),
967
+ - cos( longitude ) * sin( latitude )
968
+ );
969
+
970
+ dir.y = -dir.y;
971
+
972
+ gl_FragColor = textureCube( cubeMap, dir );
973
+ }
974
+ `,
975
+ vertexShader: vertexShader,
976
+ uniforms: {
977
+ cubeMap: {
978
+ value: null
979
+ }
980
+ },
981
+ blending: NoBlending,
982
+ depthWrite: false,
983
+ depthTest: false,
984
+ toneMapped: false
985
+ });
986
+ }
987
+
988
+ dispose() {
989
+ this.renderTarget.dispose();
990
+ }
991
+
992
+ generateEquirectEnvMap(renderer, cubeMap, width = null, height = null, maxWidth = 4096) {
993
+ if (width === null && height === null) {
994
+ const w = cubeMap.source.data[0].width;
995
+ const widthEquirect = 2 ** Math.ceil(Math.log2(2 * w * 3 ** 0.5));
996
+ const heightEquirect = 2 ** Math.ceil(Math.log2(w * 3 ** 0.5));
997
+ width = widthEquirect;
998
+ height = heightEquirect;
999
+ }
1000
+
1001
+ if (width > maxWidth) {
1002
+ width = maxWidth;
1003
+ height = maxWidth / 2;
1004
+ }
1005
+
1006
+ this.renderTarget.setSize(width, height);
1007
+ this.fullscreenMaterial.uniforms.cubeMap.value = cubeMap;
1008
+ const {
1009
+ renderTarget
1010
+ } = this;
1011
+ renderer.setRenderTarget(renderTarget);
1012
+ renderer.render(this.scene, this.camera); // Create a new Float32Array to store the pixel data
1013
+
1014
+ const pixelBuffer = new Float32Array(width * height * 4);
1015
+ renderer.readRenderTargetPixels(renderTarget, 0, 0, width, height, pixelBuffer); // Create a new data texture
1016
+
1017
+ const equirectEnvMap = new DataTexture(pixelBuffer, width, height, RGBAFormat, FloatType); // Set texture options
1018
+
1019
+ equirectEnvMap.wrapS = ClampToEdgeWrapping;
1020
+ equirectEnvMap.wrapT = ClampToEdgeWrapping;
1021
+ equirectEnvMap.minFilter = LinearMipMapLinearFilter;
1022
+ equirectEnvMap.magFilter = LinearMipMapLinearFilter;
1023
+ equirectEnvMap.needsUpdate = true;
1024
+ equirectEnvMap.mapping = EquirectangularReflectionMapping;
1025
+ return equirectEnvMap;
1026
+ }
1027
+
1028
+ }
1029
+
1030
+ // and velocity to "gVelocity" buffer
1031
+
1032
+ class MRTMaterial extends ShaderMaterial {
1033
+ constructor() {
1034
+ super({
1035
+ type: "MRTMaterial",
1036
+ defines: {
1037
+ USE_UV: "",
1038
+ TEMPORAL_RESOLVE: ""
1039
+ },
1040
+ uniforms: {
1041
+ color: new Uniform(new Color()),
1042
+ emissive: new Uniform(new Color()),
1043
+ map: new Uniform(null),
1044
+ roughnessMap: new Uniform(null),
1045
+ metalnessMap: new Uniform(null),
1046
+ emissiveMap: new Uniform(null),
1047
+ alphaMap: new Uniform(null),
1048
+ normalMap: new Uniform(null),
1049
+ normalScale: new Uniform(new Vector2(1, 1)),
1050
+ roughness: new Uniform(0),
1051
+ metalness: new Uniform(0),
1052
+ emissiveIntensity: new Uniform(0),
1053
+ uvTransform: new Uniform(new Matrix3()),
1054
+ boneTexture: new Uniform(null),
1055
+ blueNoiseTexture: new Uniform(null),
1056
+ blueNoiseRepeat: new Uniform(new Vector2(1, 1)),
1057
+ texSize: new Uniform(new Vector2(1, 1)),
1058
+ frame: new Uniform(0)
1059
+ },
1060
+ vertexShader:
1061
+ /* glsl */
1062
+ `
1063
+ varying vec2 vHighPrecisionZW;
1064
+
1065
+ #define NORMAL
1066
+ #if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )
1067
+ varying vec3 vViewPosition;
1068
+ #endif
1069
+
1070
+ #include <common>
1071
+ #include <uv_pars_vertex>
1072
+ #include <displacementmap_pars_vertex>
1073
+ #include <normal_pars_vertex>
1074
+ #include <morphtarget_pars_vertex>
1075
+ #include <logdepthbuf_pars_vertex>
1076
+ #include <clipping_planes_pars_vertex>
1077
+ #include <skinning_pars_vertex>
1078
+ #include <color_pars_vertex>
1079
+
1080
+ varying vec2 screenUv;
1081
+
1082
+ void main() {
1083
+ #include <uv_vertex>
1084
+
1085
+ #include <skinbase_vertex>
1086
+ #include <beginnormal_vertex>
1087
+ #include <skinnormal_vertex>
1088
+ #include <defaultnormal_vertex>
1089
+
1090
+ #include <morphnormal_vertex>
1091
+ #include <normal_vertex>
1092
+ #include <begin_vertex>
1093
+ #include <morphtarget_vertex>
1094
+
1095
+ #include <skinning_vertex>
1096
+
1097
+ #include <displacementmap_vertex>
1098
+ #include <project_vertex>
1099
+ #include <logdepthbuf_vertex>
1100
+ #include <clipping_planes_vertex>
1101
+
1102
+ #include <color_vertex>
1103
+
1104
+ #if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )
1105
+ vViewPosition = - mvPosition.xyz;
1106
+ #endif
1107
+
1108
+ screenUv = gl_Position.xy * 0.5 + 0.5;
1109
+
1110
+ vHighPrecisionZW = gl_Position.zw;
1111
+ }
1112
+ `,
1113
+ fragmentShader:
1114
+ /* glsl */
1115
+ `
1116
+ #define NORMAL
1117
+ #if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )
1118
+ varying vec3 vViewPosition;
1119
+ #endif
1120
+ #include <packing>
1121
+ #include <uv_pars_fragment>
1122
+ #include <normal_pars_fragment>
1123
+ #include <bumpmap_pars_fragment>
1124
+ #include <normalmap_pars_fragment>
1125
+ #include <logdepthbuf_pars_fragment>
1126
+ #include <clipping_planes_pars_fragment>
1127
+ #include <color_pars_fragment>
1128
+ #include <alphamap_pars_fragment>
1129
+
1130
+ layout(location = 0) out vec4 gDepth;
1131
+ layout(location = 1) out vec4 gNormal;
1132
+ layout(location = 2) out vec4 gDiffuse;
1133
+ layout(location = 3) out vec4 gEmissive;
1134
+
1135
+ #include <map_pars_fragment>
1136
+ uniform vec3 color;
1137
+
1138
+ varying vec2 vHighPrecisionZW;
1139
+
1140
+ #include <metalnessmap_pars_fragment>
1141
+ uniform float metalness;
1142
+
1143
+ #include <roughnessmap_pars_fragment>
1144
+ uniform float roughness;
1145
+
1146
+ #include <emissivemap_pars_fragment>
1147
+ uniform vec3 emissive;
1148
+ uniform float emissiveIntensity;
1149
+
1150
+ #ifdef USE_ALPHAMAP
1151
+ uniform sampler2D blueNoiseTexture;
1152
+ uniform vec2 blueNoiseRepeat;
1153
+ uniform vec2 texSize;
1154
+ uniform int frame;
1155
+
1156
+ varying vec2 screenUv;
1157
+
1158
+ const float g = 1.6180339887498948482;
1159
+ const float a1 = 1.0 / g;
1160
+
1161
+ // reference: https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/
1162
+ float r1(float n) {
1163
+ // 7th harmonious number
1164
+ return fract(1.1127756842787055 + a1 * n);
1165
+ }
1166
+
1167
+ const vec4 hn = vec4(0.618033988749895, 0.3247179572447458, 0.2207440846057596, 0.1673039782614187);
1168
+
1169
+ vec4 sampleBlueNoise(vec2 uv, int seed) {
1170
+ vec2 size = uv * texSize;
1171
+ vec2 blueNoiseSize = texSize / blueNoiseRepeat;
1172
+ float blueNoiseIndex = floor(floor(size.y / blueNoiseSize.y) * blueNoiseRepeat.x) + floor(size.x / blueNoiseSize.x);
1173
+
1174
+ // get the offset of this pixel's blue noise tile
1175
+ int blueNoiseTileOffset = int(r1(blueNoiseIndex + 1.0) * 65536.);
1176
+
1177
+ vec2 blueNoiseUv = uv * blueNoiseRepeat;
1178
+
1179
+ // fetch blue noise for this pixel
1180
+ vec4 blueNoise = textureLod(blueNoiseTexture, blueNoiseUv, 0.);
1181
+
1182
+ // animate blue noise
1183
+ blueNoise = fract(blueNoise + hn * float(seed + blueNoiseTileOffset));
1184
+
1185
+ blueNoise.r = (blueNoise.r > 0.5 ? 1.0 - blueNoise.r : blueNoise.r) * 2.0;
1186
+ blueNoise.g = (blueNoise.g > 0.5 ? 1.0 - blueNoise.g : blueNoise.g) * 2.0;
1187
+ blueNoise.b = (blueNoise.b > 0.5 ? 1.0 - blueNoise.b : blueNoise.b) * 2.0;
1188
+ blueNoise.a = (blueNoise.a > 0.5 ? 1.0 - blueNoise.a : blueNoise.a) * 2.0;
1189
+
1190
+ return blueNoise;
1191
+ }
1192
+ #endif
1193
+
1194
+ void main() {
1195
+ // !todo: properly implement alpha hashing
1196
+ // #ifdef USE_ALPHAMAP
1197
+ // float alpha = textureLod( alphaMap, vUv, 0. ).g;
1198
+
1199
+ // float alphaThreshold = sampleBlueNoise(screenUv, frame).a;
1200
+ // if(alpha < alphaThreshold){
1201
+ // discard;
1202
+ // return;
1203
+ // }metalnessnor
1204
+ // #endif
1205
+
1206
+ //! todo: find better solution
1207
+ #define vMapUv vUv
1208
+ #define vMetalnessMapUv vUv
1209
+ #define vRoughnessMapUv vUv
1210
+ #define vNormalMapUv vUv
1211
+ #define vEmissiveMapUv vUv
1212
+
1213
+ #include <clipping_planes_fragment>
1214
+ #include <logdepthbuf_fragment>
1215
+ #include <normal_fragment_begin>
1216
+ #include <normal_fragment_maps>
1217
+
1218
+ float roughnessFactor = roughness;
1219
+ bool isDeselected = roughness > 10.0e9;
1220
+
1221
+ if(isDeselected){
1222
+ roughnessFactor = 1.;
1223
+ gNormal = vec4(0.);
1224
+ }else{
1225
+ #ifdef USE_ROUGHNESSMAP
1226
+ vec4 texelRoughness = textureLod( roughnessMap, vUv, 0. );
1227
+ // reads channel G, compatible with a combined OcclusionRoughnessMetallic (RGB) texture
1228
+ roughnessFactor *= texelRoughness.g;
1229
+ #endif
1230
+
1231
+ // roughness of 1.0 is reserved for deselected meshes
1232
+ roughnessFactor = min(0.99, roughnessFactor);
1233
+
1234
+ vec3 worldNormal = normalize((vec4(normal, 1.) * viewMatrix).xyz);
1235
+ gNormal = vec4( worldNormal, roughnessFactor );
1236
+ }
1237
+
1238
+
1239
+ if(isDeselected){
1240
+ discard;
1241
+ return;
1242
+ }
1243
+
1244
+ float fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;
1245
+
1246
+ vec4 depthColor = packDepthToRGBA( fragCoordZ );
1247
+ gDepth = depthColor;
1248
+
1249
+ #include <metalnessmap_fragment>
1250
+
1251
+ vec4 diffuseColor = vec4(color, metalnessFactor);
1252
+
1253
+ #include <map_fragment>
1254
+ #include <color_fragment>
1255
+
1256
+ gDiffuse = diffuseColor;
1257
+
1258
+ vec3 totalEmissiveRadiance = emissive * emissiveIntensity;
1259
+ #include <emissivemap_fragment>
1260
+
1261
+ gEmissive = vec4(totalEmissiveRadiance, 0.);
1262
+ }
1263
+ `,
1264
+ glslVersion: GLSL3,
1265
+ toneMapped: false,
1266
+ alphaTest: false,
1267
+ fog: false,
1268
+ lights: false
1269
+ });
1270
+ this.normalMapType = TangentSpaceNormalMap;
1271
+ this.normalScale = new Vector2(1, 1);
1272
+ }
1273
+
1274
+ }
1275
+
1276
+ var fragmentShader$3 = "#define GLSLIFY 1\n#if !defined(diffuseOnly) && !defined(specularOnly)\nlayout(location=0)out vec4 gDiffuse;layout(location=1)out vec4 gSpecular;\n#else\n#ifdef diffuseOnly\nlayout(location=0)out vec4 gDiffuse;\n#else\nlayout(location=0)out vec4 gSpecular;\n#endif\n#endif\nvarying vec2 vUv;uniform sampler2D directLightTexture;uniform sampler2D accumulatedTexture;uniform sampler2D normalTexture;uniform sampler2D depthTexture;uniform sampler2D diffuseTexture;uniform sampler2D emissiveTexture;uniform sampler2D blueNoiseTexture;uniform sampler2D velocityTexture;\n#ifdef autoThickness\nuniform sampler2D backSideDepthTexture;\n#endif\nuniform mat4 projectionMatrix;uniform mat4 inverseProjectionMatrix;uniform mat4 cameraMatrixWorld;uniform float cameraNear;uniform float cameraFar;uniform float maxEnvMapMipLevel;uniform float rayDistance;uniform float maxRoughness;uniform float thickness;uniform float envBlur;uniform int frame;uniform vec2 texSize;uniform vec2 blueNoiseRepeat;struct EquirectHdrInfo{sampler2D marginalWeights;sampler2D conditionalWeights;sampler2D map;vec2 size;float totalSumWhole;float totalSumDecimal;};uniform EquirectHdrInfo envMapInfo;\n#define INVALID_RAY_COORDS vec2(-1.0);\n#define EPSILON 0.00001\n#define ONE_MINUS_EPSILON 1.0 - EPSILON\nfloat nearMinusFar;float nearMulFar;float farMinusNear;vec2 invTexSize;\n#include <packing>\n#include <sampleBlueNoise>\n#include <ssgi_utils>\nvec2 RayMarch(inout vec3 dir,inout vec3 hitPos);vec2 BinarySearch(inout vec3 dir,inout vec3 hitPos);float fastGetViewZ(const float depth);vec3 doSample(const vec3 viewPos,const vec3 viewDir,const vec3 viewNormal,const vec3 worldPosition,const float metalness,const float roughness,const bool isDiffuseSample,const bool isEnvMisSample,const float NoV,const float NoL,const float NoH,const float LoH,const float VoH,const vec2 random,inout vec3 l,inout vec3 hitPos,out bool isMissedRay,out vec3 brdf,out float pdf);void main(){vec4 depthTexel=textureLod(depthTexture,vUv,0.0);if(dot(depthTexel.rgb,depthTexel.rgb)==0.){discard;return;}vec4 normalTexel=textureLod(normalTexture,vUv,0.0);float roughness=normalTexel.a;if(roughness==1.0||roughness>maxRoughness){discard;return;}invTexSize=1./texSize;roughness=clamp(roughness*roughness,0.0001,1.0);nearMinusFar=cameraNear-cameraFar;nearMulFar=cameraNear*cameraFar;farMinusNear=cameraFar-cameraNear;float unpackedDepth=unpackRGBAToDepth(depthTexel);float depth=fastGetViewZ(unpackedDepth);vec3 viewPos=getViewPosition(depth);vec3 viewDir=normalize(viewPos);vec3 worldNormal=normalTexel.xyz;vec3 viewNormal=normalize((vec4(worldNormal,0.)*cameraMatrixWorld).xyz);vec3 worldPos=vec4(vec4(viewPos,1.)*viewMatrix).xyz;vec4 diffuseTexel=textureLod(diffuseTexture,vUv,0.);vec3 diffuse=diffuseTexel.rgb;float metalness=diffuseTexel.a;vec3 n=viewNormal;vec3 v=-viewDir;float NoV=max(EPSILON,dot(n,v));vec3 V=(vec4(v,0.)*viewMatrix).xyz;vec3 N=worldNormal;vec4 blueNoise;vec3 H,l,h,F,T,B,envMisDir,gi;vec3 diffuseGI,specularGI,brdf,hitPos;Onb(N,T,B);V=ToLocal(T,B,N,V);vec3 f0=mix(vec3(0.04),diffuse,metalness);float NoL,NoH,LoH,VoH,diffW,specW,invW,pdf,envPdf,diffuseSamples,specularSamples,envMisProbability,envMisMultiplier;bool isDiffuseSample,isEnvMisSample,isMissedRay;int sampleCounter=0;\n#pragma unroll_loop_start\nfor(int i=0;i<spp;i++){blueNoise=sampleBlueNoise(blueNoiseTexture,frame+sampleCounter++,blueNoiseRepeat,texSize);H=SampleGGXVNDF(V,roughness,roughness,blueNoise.r,blueNoise.g);if(H.z<0.0)H=-H;l=normalize(reflect(-V,H));l=ToWorld(T,B,N,l);l=(vec4(l,0.)*cameraMatrixWorld).xyz;l=normalize(l);h=normalize(v+l);NoL=clamp(dot(n,l),EPSILON,ONE_MINUS_EPSILON);NoH=clamp(dot(n,h),EPSILON,ONE_MINUS_EPSILON);LoH=clamp(dot(l,h),EPSILON,ONE_MINUS_EPSILON);VoH=clamp(dot(v,h),EPSILON,ONE_MINUS_EPSILON);\n#if !defined(diffuseOnly) && !defined(specularOnly)\nF=F_Schlick(f0,VoH);diffW=(1.-metalness)*luminance(diffuse);specW=luminance(F);diffW=max(diffW,EPSILON);specW=max(specW,EPSILON);invW=1./(diffW+specW);diffW*=invW;specW*=invW;isDiffuseSample=blueNoise.b<diffW;\n#else\n#ifdef diffuseOnly\nisDiffuseSample=true;\n#else\nisDiffuseSample=false;\n#endif\n#endif\nenvMisDir=vec3(0.0);\n#ifdef importanceSampling\nenvPdf=sampleEquirectProbability(envMapInfo,blueNoise.rg,envMisDir);envMisDir=normalize((vec4(envMisDir,0.)*cameraMatrixWorld).xyz);envMisProbability=0.25+dot(envMisDir,viewNormal)*0.5;isEnvMisSample=blueNoise.a<envMisProbability;envMisMultiplier=1./(1.-envMisProbability);if(isEnvMisSample){envPdf/=1.-envMisProbability;}else{envPdf=0.0001;}\n#else\nenvPdf=0.0;envMisMultiplier=1.;\n#endif\nif(isDiffuseSample){if(isEnvMisSample){l=envMisDir;}else{l=cosineSampleHemisphere(viewNormal,blueNoise.rg);}h=normalize(v+l);NoL=clamp(dot(n,l),EPSILON,ONE_MINUS_EPSILON);NoH=clamp(dot(n,h),EPSILON,ONE_MINUS_EPSILON);LoH=clamp(dot(l,h),EPSILON,ONE_MINUS_EPSILON);VoH=clamp(dot(v,h),EPSILON,ONE_MINUS_EPSILON);gi=doSample(viewPos,viewDir,viewNormal,worldPos,metalness,roughness,isDiffuseSample,isEnvMisSample,NoV,NoL,NoH,LoH,VoH,blueNoise.rg,l,hitPos,isMissedRay,brdf,pdf);gi*=brdf;if(isEnvMisSample){gi*=misHeuristic(envPdf,pdf);gi/=envPdf;}else{gi/=pdf;gi*=envMisMultiplier;}diffuseSamples++;diffuseGI=mix(diffuseGI,gi,1./diffuseSamples);}else{isEnvMisSample=isEnvMisSample&&roughness>=0.025;if(isEnvMisSample){l=envMisDir;h=normalize(v+l);NoL=clamp(dot(n,l),EPSILON,ONE_MINUS_EPSILON);NoH=clamp(dot(n,h),EPSILON,ONE_MINUS_EPSILON);LoH=clamp(dot(l,h),EPSILON,ONE_MINUS_EPSILON);VoH=clamp(dot(v,h),EPSILON,ONE_MINUS_EPSILON);}gi=doSample(viewPos,viewDir,viewNormal,worldPos,metalness,roughness,isDiffuseSample,isEnvMisSample,NoV,NoL,NoH,LoH,VoH,blueNoise.rg,l,hitPos,isMissedRay,brdf,pdf);gi*=brdf;if(isEnvMisSample){gi*=misHeuristic(envPdf,pdf);gi/=envPdf;}else{gi/=pdf;gi*=envMisMultiplier;}specularSamples++;specularGI=mix(specularGI,gi,1./specularSamples);}}\n#pragma unroll_loop_end\nroughness=sqrt(roughness);\n#ifndef specularOnly\nif(diffuseSamples==0.0)diffuseGI=vec3(-1.0);gDiffuse=vec4(diffuseGI,roughness);\n#endif\n#ifndef diffuseOnly\nfloat rayLength=0.0;if(!isMissedRay&&roughness<0.375&&getCurvature(viewNormal,depth)<0.0005){vec3 hitPosWS=(vec4(hitPos,1.)*viewMatrix).xyz;rayLength=distance(worldPos,hitPosWS);}if(specularSamples==0.0)specularGI=vec3(-1.0);gSpecular=vec4(specularGI,rayLength);\n#endif\n}vec3 doSample(const vec3 viewPos,const vec3 viewDir,const vec3 viewNormal,const vec3 worldPosition,const float metalness,const float roughness,const bool isDiffuseSample,const bool isEnvMisSample,const float NoV,const float NoL,const float NoH,const float LoH,const float VoH,const vec2 random,inout vec3 l,inout vec3 hitPos,out bool isMissedRay,out vec3 brdf,out float pdf){float cosTheta=max(0.0,dot(viewNormal,l));if(isDiffuseSample){vec3 diffuseBrdf=vec3(evalDisneyDiffuse(NoL,NoV,LoH,roughness,metalness));pdf=NoL/M_PI;pdf=max(EPSILON,pdf);brdf=diffuseBrdf;}else{vec3 specularBrdf=evalDisneySpecular(roughness,NoH,NoV,NoL);pdf=GGXVNDFPdf(NoH,NoV,roughness);pdf=max(EPSILON,pdf);brdf=specularBrdf;}brdf*=cosTheta;hitPos=viewPos;\n#if steps == 0\nhitPos+=l;vec2 coords=viewSpaceToScreenSpace(hitPos);\n#else\nvec2 coords=RayMarch(l,hitPos);\n#endif\nbool allowMissedRays=false;\n#ifdef missedRays\nallowMissedRays=true;\n#endif\nisMissedRay=coords.x==-1.0;vec3 envMapSample=vec3(0.);if(isMissedRay||allowMissedRays){\n#ifdef USE_ENVMAP\nvec3 reflectedWS=normalize((vec4(l,0.)*viewMatrix).xyz);\n#ifdef BOX_PROJECTED_ENV_MAP\nreflectedWS=parallaxCorrectNormal(reflectedWS.xyz,envMapSize,envMapPosition,worldPosition);reflectedWS=normalize(reflectedWS.xyz);\n#endif\nfloat mip=envBlur*maxEnvMapMipLevel;if(!isDiffuseSample&&roughness<0.15)mip*=roughness/0.15;envMapSample=sampleEquirectEnvMapColor(reflectedWS,envMapInfo.map,mip);float maxEnvLum=isEnvMisSample ? 50.0 : 5.0;if(maxEnvLum!=0.0){float envLum=luminance(envMapSample);if(envLum>maxEnvLum){envMapSample*=maxEnvLum/envLum;}}return envMapSample;\n#else\nreturn vec3(0.0);\n#endif\n}vec4 velocity=textureLod(velocityTexture,coords.xy,0.0);vec2 reprojectedUv=coords.xy-velocity.xy;vec3 SSGI;if(reprojectedUv.x>=0.0&&reprojectedUv.x<=1.0&&reprojectedUv.y>=0.0&&reprojectedUv.y<=1.0){vec4 emissiveTexel=textureLod(emissiveTexture,coords.xy,0.);vec3 emissiveColor=emissiveTexel.rgb*10.;vec3 reprojectedGI=getTexel(accumulatedTexture,reprojectedUv,0.).rgb;SSGI=reprojectedGI+emissiveColor;\n#ifdef useDirectLight\nSSGI+=textureLod(directLightTexture,coords.xy,0.).rgb*directLightMultiplier;\n#endif\n}else{SSGI=textureLod(directLightTexture,vUv,0.).rgb;}if(allowMissedRays){float ssgiLum=luminance(SSGI);float envLum=luminance(envMapSample);if(envLum>ssgiLum)SSGI=envMapSample;}return SSGI;}vec2 RayMarch(inout vec3 dir,inout vec3 hitPos){float rayHitDepthDifference;dir*=rayDistance/float(steps);vec2 uv;for(int i=1;i<steps;i++){float m=exp(pow(float(i)/4.0,0.05))-2.0;hitPos+=dir*min(m,1.);if(hitPos.z>0.0)return INVALID_RAY_COORDS;uv=viewSpaceToScreenSpace(hitPos);\n#ifndef missedRays\nif(uv.x<0.||uv.y<0.||uv.x>1.||uv.y>1.)return INVALID_RAY_COORDS;\n#endif\nfloat unpackedDepth=unpackRGBAToDepth(textureLod(depthTexture,uv,0.0));float depth=fastGetViewZ(unpackedDepth);\n#ifdef autoThickness\nfloat unpackedBackSideDepth=unpackRGBAToDepth(textureLod(backSideDepthTexture,uv,0.0));float backSideDepth=fastGetViewZ(unpackedBackSideDepth);float currentThickness=max(abs(depth-backSideDepth),thickness);\n#else\nfloat currentThickness=thickness;\n#endif\nrayHitDepthDifference=depth-hitPos.z;if(rayHitDepthDifference>=0.0&&rayHitDepthDifference<currentThickness){\n#if refineSteps == 0\nreturn uv;\n#else\nreturn BinarySearch(dir,hitPos);\n#endif\n}}\n#ifndef missedRays\nreturn INVALID_RAY_COORDS;\n#endif\nreturn uv;}vec2 BinarySearch(inout vec3 dir,inout vec3 hitPos){float rayHitDepthDifference;vec2 uv;dir*=0.5;hitPos-=dir;for(int i=0;i<refineSteps;i++){uv=viewSpaceToScreenSpace(hitPos);float unpackedDepth=unpackRGBAToDepth(textureLod(depthTexture,uv,0.0));float depth=fastGetViewZ(unpackedDepth);rayHitDepthDifference=depth-hitPos.z;dir*=0.5;hitPos+=rayHitDepthDifference>0.0 ?-dir : dir;}uv=viewSpaceToScreenSpace(hitPos);return uv;}float fastGetViewZ(const float depth){\n#ifdef PERSPECTIVE_CAMERA\nreturn nearMulFar/(farMinusNear*depth-cameraFar);\n#else\nreturn depth*nearMinusFar-cameraNear;\n#endif\n}"; // eslint-disable-line
1277
+
1278
+ var ssgi_utils = "#define GLSLIFY 1\n#define PI M_PI\n#define luminance(a) dot(vec3(0.2125, 0.7154, 0.0721), a)\nvec4 getTexel(const sampler2D tex,vec2 p,const float mip){p=p/invTexSize+0.5;vec2 i=floor(p);vec2 f=p-i;f=f*f*f*(f*(f*6.0-15.0)+10.0);p=i+f;p=(p-0.5)*invTexSize;return textureLod(tex,p,mip);}float getViewZ(const float depth){\n#ifdef PERSPECTIVE_CAMERA\nreturn perspectiveDepthToViewZ(depth,cameraNear,cameraFar);\n#else\nreturn orthographicDepthToViewZ(depth,cameraNear,cameraFar);\n#endif\n}\n#ifdef PERSPECTIVE_CAMERA\nvec3 getViewPosition(const float depth){float clipW=projectionMatrix[2][3]*depth+projectionMatrix[3][3];vec4 clipPosition=vec4((vec3(vUv,depth)-0.5)*2.0,1.0);clipPosition*=clipW;return(inverseProjectionMatrix*clipPosition).xyz;}\n#else\nvec3 getViewPosition(const float depth){float z=getViewZ(depth);vec4 clipPosition=vec4(vUv*2.0-1.0,z,1.0);clipPosition*=clipPosition.z;return(inverseProjectionMatrix*clipPosition).xyz;}\n#endif\nvec2 viewSpaceToScreenSpace(const vec3 position){vec4 projectedCoord=projectionMatrix*vec4(position,1.0);projectedCoord.xy/=projectedCoord.w;projectedCoord.xy=projectedCoord.xy*0.5+0.5;return projectedCoord.xy;}\n#ifdef BOX_PROJECTED_ENV_MAP\nuniform vec3 envMapSize;uniform vec3 envMapPosition;vec3 parallaxCorrectNormal(const vec3 v,const vec3 cubeSize,const vec3 cubePos,const vec3 worldPosition){vec3 nDir=normalize(v);vec3 rbmax=(.5*cubeSize+cubePos-worldPosition)/nDir;vec3 rbmin=(-.5*cubeSize+cubePos-worldPosition)/nDir;vec3 rbminmax;rbminmax.x=(nDir.x>0.)? rbmax.x : rbmin.x;rbminmax.y=(nDir.y>0.)? rbmax.y : rbmin.y;rbminmax.z=(nDir.z>0.)? rbmax.z : rbmin.z;float correction=min(min(rbminmax.x,rbminmax.y),rbminmax.z);vec3 boxIntersection=worldPosition+nDir*correction;return boxIntersection-cubePos;}\n#endif\n#define M_PI 3.1415926535897932384626433832795\nvec2 equirectDirectionToUv(const vec3 direction){vec2 uv=vec2(atan(direction.z,direction.x),acos(direction.y));uv/=vec2(2.0*M_PI,M_PI);uv.x+=0.5;uv.y=1.0-uv.y;return uv;}vec3 equirectUvToDirection(vec2 uv){uv.x-=0.5;uv.y=1.0-uv.y;float theta=uv.x*2.0*PI;float phi=uv.y*PI;float sinPhi=sin(phi);return vec3(sinPhi*cos(theta),cos(phi),sinPhi*sin(theta));}vec3 sampleEquirectEnvMapColor(const vec3 direction,const sampler2D map,const float lod){return getTexel(map,equirectDirectionToUv(direction),lod).rgb;}mat3 getBasisFromNormal(const vec3 normal){vec3 other;if(abs(normal.x)>0.5){other=vec3(0.0,1.0,0.0);}else{other=vec3(1.0,0.0,0.0);}vec3 ortho=normalize(cross(normal,other));vec3 ortho2=normalize(cross(normal,ortho));return mat3(ortho2,ortho,normal);}vec3 F_Schlick(const vec3 f0,const float theta){return f0+(1.-f0)*pow(1.0-theta,5.);}float F_Schlick(const float f0,const float f90,const float theta){return f0+(f90-f0)*pow(1.0-theta,5.0);}float D_GTR(const float roughness,const float NoH,const float k){float a2=pow(roughness,2.);return a2/(PI*pow((NoH*NoH)*(a2*a2-1.)+1.,k));}float SmithG(const float NDotV,const float alphaG){float a=alphaG*alphaG;float b=NDotV*NDotV;return(2.0*NDotV)/(NDotV+sqrt(a+b-a*b));}float GGXVNDFPdf(const float NoH,const float NoV,const float roughness){float D=D_GTR(roughness,NoH,2.);float G1=SmithG(NoV,roughness*roughness);return(D*G1)/max(0.00001,4.0f*NoV);}float GeometryTerm(const float NoL,const float NoV,const float roughness){float a2=roughness*roughness;float G1=SmithG(NoV,a2);float G2=SmithG(NoL,a2);return G1*G2;}float evalDisneyDiffuse(const float NoL,const float NoV,const float LoH,const float roughness,const float metalness){float FD90=0.5+2.*roughness*pow(LoH,2.);float a=F_Schlick(1.,FD90,NoL);float b=F_Schlick(1.,FD90,NoV);return(a*b/PI)*(1.-metalness);}vec3 evalDisneySpecular(const float roughness,const float NoH,const float NoV,const float NoL){float D=D_GTR(roughness,NoH,2.);float G=GeometryTerm(NoL,NoV,pow(0.5+roughness*.5,2.));vec3 spec=vec3(D*G/(4.*NoL*NoV));return spec;}vec3 SampleGGXVNDF(const vec3 V,const float ax,const float ay,const float r1,const float r2){vec3 Vh=normalize(vec3(ax*V.x,ay*V.y,V.z));float lensq=Vh.x*Vh.x+Vh.y*Vh.y;vec3 T1=lensq>0. ? vec3(-Vh.y,Vh.x,0.)*inversesqrt(lensq): vec3(1.,0.,0.);vec3 T2=cross(Vh,T1);float r=sqrt(r1);float phi=2.0*PI*r2;float t1=r*cos(phi);float t2=r*sin(phi);float s=0.5*(1.0+Vh.z);t2=(1.0-s)*sqrt(1.0-t1*t1)+s*t2;vec3 Nh=t1*T1+t2*T2+sqrt(max(0.0,1.0-t1*t1-t2*t2))*Vh;return normalize(vec3(ax*Nh.x,ay*Nh.y,max(0.0,Nh.z)));}void Onb(const vec3 N,inout vec3 T,inout vec3 B){vec3 up=abs(N.z)<0.9999999 ? vec3(0,0,1): vec3(1,0,0);T=normalize(cross(up,N));B=cross(N,T);}vec3 ToLocal(const vec3 X,const vec3 Y,const vec3 Z,const vec3 V){return vec3(dot(V,X),dot(V,Y),dot(V,Z));}vec3 ToWorld(const vec3 X,const vec3 Y,const vec3 Z,const vec3 V){return V.x*X+V.y*Y+V.z*Z;}vec3 cosineSampleHemisphere(const vec3 n,const vec2 u){float r=sqrt(u.x);float theta=2.0*PI*u.y;vec3 b=normalize(cross(n,vec3(0.0,1.0,1.0)));vec3 t=cross(b,n);return normalize(r*sin(theta)*b+sqrt(1.0-u.x)*n+r*cos(theta)*t);}float equirectDirectionPdf(vec3 direction){vec2 uv=equirectDirectionToUv(direction);float theta=uv.y*PI;float sinTheta=sin(theta);if(sinTheta==0.0){return 0.0;}return 1.0/(2.0*PI*PI*sinTheta);}float sampleEquirectProbability(EquirectHdrInfo info,vec2 r,out vec3 direction){float v=textureLod(info.marginalWeights,vec2(r.x,0.0),0.).x;float u=textureLod(info.conditionalWeights,vec2(r.y,v),0.).x;vec2 uv=vec2(u,v);vec3 derivedDirection=equirectUvToDirection(uv);direction=derivedDirection;vec3 color=texture(info.map,uv).rgb;float totalSum=info.totalSumWhole+info.totalSumDecimal;float lum=luminance(color);float pdf=lum/totalSum;return info.size.x*info.size.y*pdf;}float misHeuristic(float a,float b){float aa=a*a;float bb=b*b;return aa/(aa+bb);}float getCurvature(const vec3 n,const float depth){vec3 dx=dFdx(n);vec3 dy=dFdy(n);vec3 xneg=n-dx;vec3 xpos=n+dx;vec3 yneg=n-dy;vec3 ypos=n+dy;float curvature=(cross(xneg,xpos).y-cross(yneg,ypos).x)*4.0/depth;return curvature;}"; // eslint-disable-line
1279
+
1280
+ var sampleBlueNoise = "#define GLSLIFY 1\nconst float g=1.6180339887498948482;const float a1=1.0/g;float r1(float n){return fract(1.1127756842787055+a1*n);}const vec4 hn=vec4(0.618033988749895,0.3247179572447458,0.2207440846057596,0.1673039782614187);vec4 sampleBlueNoise(sampler2D texture,int seed,vec2 repeat,vec2 texSize){vec2 size=vUv*texSize;vec2 blueNoiseSize=texSize/repeat;float blueNoiseIndex=floor(floor(size.y/blueNoiseSize.y)*repeat.x)+floor(size.x/blueNoiseSize.x);vec2 blueNoiseUv=vUv*repeat;vec4 blueNoise=textureLod(texture,blueNoiseUv,0.);if(seed!=0){blueNoise=fract(blueNoise+hn*float(seed));blueNoise.r=(blueNoise.r>0.5 ? 1.0-blueNoise.r : blueNoise.r)*2.0;blueNoise.g=(blueNoise.g>0.5 ? 1.0-blueNoise.g : blueNoise.g)*2.0;blueNoise.b=(blueNoise.b>0.5 ? 1.0-blueNoise.b : blueNoise.b)*2.0;blueNoise.a=(blueNoise.a>0.5 ? 1.0-blueNoise.a : blueNoise.a)*2.0;}return blueNoise;}"; // eslint-disable-line
1281
+
1282
+ // source: https://github.com/gkjohnson/three-gpu-pathtracer/blob/main/src/uniforms/EquirectHdrInfoUniform.js
1283
+
1284
+ const workerOnMessage = ({
1285
+ data: {
1286
+ width,
1287
+ height,
1288
+ isFloatType,
1289
+ flipY,
1290
+ data
1291
+ }
1292
+ }) => {
1293
+ // from: https://github.com/mrdoob/three.js/blob/dev/src/extras/DataUtils.js
1294
+ // importing modules doesn't seem to work for workers that were generated through createObjectURL() for some reason
1295
+ const _tables = /* @__PURE__*/_generateTables();
1296
+
1297
+ function _generateTables() {
1298
+ // float32 to float16 helpers
1299
+ const buffer = new ArrayBuffer(4);
1300
+ const floatView = new Float32Array(buffer);
1301
+ const uint32View = new Uint32Array(buffer);
1302
+ const baseTable = new Uint32Array(512);
1303
+ const shiftTable = new Uint32Array(512);
1304
+
1305
+ for (let i = 0; i < 256; ++i) {
1306
+ const e = i - 127; // very small number (0, -0)
1307
+
1308
+ if (e < -27) {
1309
+ baseTable[i] = 0x0000;
1310
+ baseTable[i | 0x100] = 0x8000;
1311
+ shiftTable[i] = 24;
1312
+ shiftTable[i | 0x100] = 24; // small number (denorm)
1313
+ } else if (e < -14) {
1314
+ baseTable[i] = 0x0400 >> -e - 14;
1315
+ baseTable[i | 0x100] = 0x0400 >> -e - 14 | 0x8000;
1316
+ shiftTable[i] = -e - 1;
1317
+ shiftTable[i | 0x100] = -e - 1; // normal number
1318
+ } else if (e <= 15) {
1319
+ baseTable[i] = e + 15 << 10;
1320
+ baseTable[i | 0x100] = e + 15 << 10 | 0x8000;
1321
+ shiftTable[i] = 13;
1322
+ shiftTable[i | 0x100] = 13; // large number (Infinity, -Infinity)
1323
+ } else if (e < 128) {
1324
+ baseTable[i] = 0x7c00;
1325
+ baseTable[i | 0x100] = 0xfc00;
1326
+ shiftTable[i] = 24;
1327
+ shiftTable[i | 0x100] = 24; // stay (NaN, Infinity, -Infinity)
1328
+ } else {
1329
+ baseTable[i] = 0x7c00;
1330
+ baseTable[i | 0x100] = 0xfc00;
1331
+ shiftTable[i] = 13;
1332
+ shiftTable[i | 0x100] = 13;
1333
+ }
1334
+ } // float16 to float32 helpers
1335
+
1336
+
1337
+ const mantissaTable = new Uint32Array(2048);
1338
+ const exponentTable = new Uint32Array(64);
1339
+ const offsetTable = new Uint32Array(64);
1340
+
1341
+ for (let i = 1; i < 1024; ++i) {
1342
+ let m = i << 13; // zero pad mantissa bits
1343
+
1344
+ let e = 0; // zero exponent
1345
+ // normalized
1346
+
1347
+ while ((m & 0x00800000) === 0) {
1348
+ m <<= 1;
1349
+ e -= 0x00800000; // decrement exponent
1350
+ }
1351
+
1352
+ m &= ~0x00800000; // clear leading 1 bit
1353
+
1354
+ e += 0x38800000; // adjust bias
1355
+
1356
+ mantissaTable[i] = m | e;
1357
+ }
1358
+
1359
+ for (let i = 1024; i < 2048; ++i) {
1360
+ mantissaTable[i] = 0x38000000 + (i - 1024 << 13);
1361
+ }
1362
+
1363
+ for (let i = 1; i < 31; ++i) {
1364
+ exponentTable[i] = i << 23;
1365
+ }
1366
+
1367
+ exponentTable[31] = 0x47800000;
1368
+ exponentTable[32] = 0x80000000;
1369
+
1370
+ for (let i = 33; i < 63; ++i) {
1371
+ exponentTable[i] = 0x80000000 + (i - 32 << 23);
1372
+ }
1373
+
1374
+ exponentTable[63] = 0xc7800000;
1375
+
1376
+ for (let i = 1; i < 64; ++i) {
1377
+ if (i !== 32) {
1378
+ offsetTable[i] = 1024;
1379
+ }
1380
+ }
1381
+
1382
+ return {
1383
+ floatView: floatView,
1384
+ uint32View: uint32View,
1385
+ baseTable: baseTable,
1386
+ shiftTable: shiftTable,
1387
+ mantissaTable: mantissaTable,
1388
+ exponentTable: exponentTable,
1389
+ offsetTable: offsetTable
1390
+ };
1391
+ }
1392
+
1393
+ function fromHalfFloat(val) {
1394
+ const m = val >> 10;
1395
+ _tables.uint32View[0] = _tables.mantissaTable[_tables.offsetTable[m] + (val & 0x3ff)] + _tables.exponentTable[m];
1396
+ return _tables.floatView[0];
1397
+ }
1398
+
1399
+ function colorToLuminance(r, g, b) {
1400
+ // https://en.wikipedia.org/wiki/Relative_luminance
1401
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
1402
+ }
1403
+
1404
+ const binarySearchFindClosestIndexOf = (array, targetValue, offset = 0, count = array.length) => {
1405
+ let lower = offset;
1406
+ let upper = offset + count - 1;
1407
+
1408
+ while (lower < upper) {
1409
+ const mid = lower + upper >> 1; // check if the middle array value is above or below the target and shift
1410
+ // which half of the array we're looking at
1411
+
1412
+ if (array[mid] < targetValue) {
1413
+ lower = mid + 1;
1414
+ } else {
1415
+ upper = mid;
1416
+ }
1417
+ }
1418
+
1419
+ return lower - offset;
1420
+ };
1421
+
1422
+ const gatherData = (data, width, height, flipY, marginalDataArray, conditionalDataArray) => {
1423
+ // "conditional" = "pixel relative to row pixels sum"
1424
+ // "marginal" = "row relative to row sum"
1425
+ // remove any y flipping for cdf computation
1426
+ if (flipY) {
1427
+ for (let y = 0, h = height - 1; y <= h; y++) {
1428
+ for (let x = 0, w = width * 4; x < w; x += 4) {
1429
+ const newY = h - y;
1430
+ const ogIndex = y * w + x;
1431
+ const newIndex = newY * w + x;
1432
+ data[newIndex] = data[ogIndex];
1433
+ data[newIndex + 1] = data[ogIndex + 1];
1434
+ data[newIndex + 2] = data[ogIndex + 2];
1435
+ data[newIndex + 3] = data[ogIndex + 3];
1436
+ }
1437
+ }
1438
+ } // track the importance of any given pixel in the image by tracking its weight relative to other pixels in the image
1439
+
1440
+
1441
+ const pdfConditional = new Float32Array(width * height);
1442
+ const cdfConditional = new Float32Array(width * height);
1443
+ const pdfMarginal = new Float32Array(height);
1444
+ const cdfMarginal = new Float32Array(height);
1445
+ let totalSumValue = 0.0;
1446
+ let cumulativeWeightMarginal = 0.0;
1447
+
1448
+ for (let y = 0; y < height; y++) {
1449
+ let cumulativeRowWeight = 0.0;
1450
+
1451
+ for (let x = 0; x < width; x++) {
1452
+ const i = y * width + x;
1453
+ const r = data[4 * i + 0];
1454
+ const g = data[4 * i + 1];
1455
+ const b = data[4 * i + 2]; // the probability of the pixel being selected in this row is the
1456
+ // scale of the luminance relative to the rest of the pixels.
1457
+ // TODO: this should also account for the solid angle of the pixel when sampling
1458
+
1459
+ const weight = colorToLuminance(r, g, b);
1460
+ cumulativeRowWeight += weight;
1461
+ totalSumValue += weight;
1462
+ pdfConditional[i] = weight;
1463
+ cdfConditional[i] = cumulativeRowWeight;
1464
+ } // can happen if the row is all black
1465
+
1466
+
1467
+ if (cumulativeRowWeight !== 0) {
1468
+ // scale the pdf and cdf to [0.0, 1.0]
1469
+ for (let i = y * width, l = y * width + width; i < l; i++) {
1470
+ pdfConditional[i] /= cumulativeRowWeight;
1471
+ cdfConditional[i] /= cumulativeRowWeight;
1472
+ }
1473
+ }
1474
+
1475
+ cumulativeWeightMarginal += cumulativeRowWeight; // compute the marginal pdf and cdf along the height of the map.
1476
+
1477
+ pdfMarginal[y] = cumulativeRowWeight;
1478
+ cdfMarginal[y] = cumulativeWeightMarginal;
1479
+ } // can happen if the texture is all black
1480
+
1481
+
1482
+ if (cumulativeWeightMarginal !== 0) {
1483
+ // scale the marginal pdf and cdf to [0.0, 1.0]
1484
+ for (let i = 0, l = pdfMarginal.length; i < l; i++) {
1485
+ pdfMarginal[i] /= cumulativeWeightMarginal;
1486
+ cdfMarginal[i] /= cumulativeWeightMarginal;
1487
+ }
1488
+ } // compute a sorted index of distributions and the probabilities along them for both
1489
+ // the marginal and conditional data. These will be used to sample with a random number
1490
+ // to retrieve a uv value to sample in the environment map.
1491
+ // These values continually increase so it's okay to interpolate between them.
1492
+ // we add a half texel offset so we're sampling the center of the pixel
1493
+
1494
+
1495
+ for (let i = 0; i < height; i++) {
1496
+ const dist = (i + 1) / height;
1497
+ const row = binarySearchFindClosestIndexOf(cdfMarginal, dist);
1498
+ marginalDataArray[i] = (row + 0.5) / height;
1499
+ }
1500
+
1501
+ for (let y = 0; y < height; y++) {
1502
+ for (let x = 0; x < width; x++) {
1503
+ const i = y * width + x;
1504
+ const dist = (x + 1) / width;
1505
+ const col = binarySearchFindClosestIndexOf(cdfConditional, dist, y * width, width);
1506
+ conditionalDataArray[i] = (col + 0.5) / width;
1507
+ }
1508
+ }
1509
+
1510
+ return totalSumValue;
1511
+ };
1512
+
1513
+ if (!isFloatType) {
1514
+ const newData = new Float32Array(data.length); // eslint-disable-next-line guard-for-in
1515
+
1516
+ for (const i in data) {
1517
+ newData[i] = fromHalfFloat(data[i]);
1518
+ }
1519
+
1520
+ data = newData;
1521
+ }
1522
+
1523
+ const marginalDataArray = new Float32Array(height);
1524
+ const conditionalDataArray = new Float32Array(width * height);
1525
+ const totalSumValue = gatherData(data, width, height, flipY, marginalDataArray, conditionalDataArray);
1526
+
1527
+ if (isFloatType) {
1528
+ postMessage({
1529
+ totalSumValue,
1530
+ marginalDataArray,
1531
+ conditionalDataArray
1532
+ });
1533
+ } else {
1534
+ postMessage({
1535
+ data,
1536
+ totalSumValue,
1537
+ marginalDataArray,
1538
+ conditionalDataArray
1539
+ });
1540
+ }
1541
+ };
1542
+
1543
+ const blob = new Blob(["onmessage = " + workerOnMessage], {
1544
+ type: "application/javascript"
1545
+ });
1546
+ const workerUrl = URL.createObjectURL(blob);
1547
+ class EquirectHdrInfoUniform {
1548
+ constructor() {
1549
+ // Default to a white texture and associated weights so we don't
1550
+ // just render black initially.
1551
+ const whiteTex = new DataTexture(new Float32Array([1, 1, 1, 1]), 1, 1);
1552
+ whiteTex.type = FloatType;
1553
+ whiteTex.format = RGBAFormat;
1554
+ whiteTex.minFilter = LinearFilter;
1555
+ whiteTex.magFilter = LinearFilter;
1556
+ whiteTex.wrapS = RepeatWrapping;
1557
+ whiteTex.wrapT = RepeatWrapping;
1558
+ whiteTex.generateMipmaps = false;
1559
+ whiteTex.needsUpdate = true; // Stores a map of [0, 1] value -> cumulative importance row & pdf
1560
+ // used to sampling a random value to a relevant row to sample from
1561
+
1562
+ const marginalWeights = new DataTexture(new Float32Array([0, 1]), 1, 2);
1563
+ marginalWeights.type = FloatType;
1564
+ marginalWeights.format = RedFormat;
1565
+ marginalWeights.minFilter = LinearFilter;
1566
+ marginalWeights.magFilter = LinearFilter;
1567
+ marginalWeights.generateMipmaps = false;
1568
+ marginalWeights.needsUpdate = true; // Stores a map of [0, 1] value -> cumulative importance column & pdf
1569
+ // used to sampling a random value to a relevant pixel to sample from
1570
+
1571
+ const conditionalWeights = new DataTexture(new Float32Array([0, 0, 1, 1]), 2, 2);
1572
+ conditionalWeights.type = FloatType;
1573
+ conditionalWeights.format = RedFormat;
1574
+ conditionalWeights.minFilter = LinearFilter;
1575
+ conditionalWeights.magFilter = LinearFilter;
1576
+ conditionalWeights.generateMipmaps = false;
1577
+ conditionalWeights.needsUpdate = true;
1578
+ this.map = whiteTex;
1579
+ this.marginalWeights = marginalWeights;
1580
+ this.conditionalWeights = conditionalWeights; // the total sum value is separated into two values to work around low precision
1581
+ // storage of floating values in structs
1582
+
1583
+ this.totalSumWhole = 1;
1584
+ this.totalSumDecimal = 0;
1585
+ this.size = new Vector2();
1586
+ }
1587
+
1588
+ dispose() {
1589
+ this.marginalWeights.dispose();
1590
+ this.conditionalWeights.dispose();
1591
+ this.map.dispose();
1592
+ }
1593
+
1594
+ updateFrom(map) {
1595
+ map = map.clone();
1596
+ const {
1597
+ width,
1598
+ height,
1599
+ data
1600
+ } = map.image;
1601
+ const {
1602
+ type
1603
+ } = map;
1604
+ this.size.set(width, height);
1605
+ return new Promise(resolve => {
1606
+ var _this$worker;
1607
+
1608
+ (_this$worker = this.worker) == null ? void 0 : _this$worker.terminate();
1609
+ this.worker = new Worker(workerUrl);
1610
+ this.worker.postMessage({
1611
+ width,
1612
+ height,
1613
+ isFloatType: type === FloatType,
1614
+ flipY: map.flipY,
1615
+ data
1616
+ });
1617
+
1618
+ this.worker.onmessage = ({
1619
+ data: {
1620
+ data,
1621
+ totalSumValue,
1622
+ marginalDataArray,
1623
+ conditionalDataArray
1624
+ }
1625
+ }) => {
1626
+ this.dispose();
1627
+ const {
1628
+ marginalWeights,
1629
+ conditionalWeights
1630
+ } = this;
1631
+ marginalWeights.image = {
1632
+ width: height,
1633
+ height: 1,
1634
+ data: marginalDataArray
1635
+ };
1636
+ marginalWeights.needsUpdate = true;
1637
+ conditionalWeights.image = {
1638
+ width,
1639
+ height,
1640
+ data: conditionalDataArray
1641
+ };
1642
+ conditionalWeights.needsUpdate = true;
1643
+ const totalSumWhole = ~~totalSumValue;
1644
+ const totalSumDecimal = totalSumValue - totalSumWhole;
1645
+ this.totalSumWhole = totalSumWhole;
1646
+ this.totalSumDecimal = totalSumDecimal;
1647
+
1648
+ if (data) {
1649
+ map.source = new Source({ ...map.image
1650
+ });
1651
+ map.image = {
1652
+ width,
1653
+ height,
1654
+ data
1655
+ };
1656
+ map.type = FloatType;
1657
+ }
1658
+
1659
+ this.map = map;
1660
+ this.worker = null;
1661
+ resolve(map);
1662
+ };
1663
+ });
1664
+ }
1665
+
1666
+ }
1667
+
1668
+ class SSGIMaterial extends ShaderMaterial {
1669
+ constructor() {
1670
+ super({
1671
+ type: "SSGIMaterial",
1672
+ uniforms: {
1673
+ directLightTexture: new Uniform(null),
1674
+ accumulatedTexture: new Uniform(null),
1675
+ normalTexture: new Uniform(null),
1676
+ depthTexture: new Uniform(null),
1677
+ diffuseTexture: new Uniform(null),
1678
+ emissiveTexture: new Uniform(null),
1679
+ velocityTexture: new Uniform(null),
1680
+ blueNoiseTexture: new Uniform(null),
1681
+ backSideDepthTexture: new Uniform(null),
1682
+ projectionMatrix: new Uniform(new Matrix4()),
1683
+ inverseProjectionMatrix: new Uniform(new Matrix4()),
1684
+ cameraMatrixWorld: new Uniform(new Matrix4()),
1685
+ viewMatrix: new Uniform(new Matrix4()),
1686
+ cameraNear: new Uniform(0),
1687
+ cameraFar: new Uniform(0),
1688
+ rayDistance: new Uniform(0),
1689
+ thickness: new Uniform(0),
1690
+ frame: new Uniform(0),
1691
+ envBlur: new Uniform(0),
1692
+ maxRoughness: new Uniform(0),
1693
+ maxEnvMapMipLevel: new Uniform(0),
1694
+ envMapInfo: {
1695
+ value: new EquirectHdrInfoUniform()
1696
+ },
1697
+ envMapPosition: new Uniform(new Vector3()),
1698
+ envMapSize: new Uniform(new Vector3()),
1699
+ viewMatrix: new Uniform(new Matrix4()),
1700
+ texSize: new Uniform(new Vector2()),
1701
+ blueNoiseRepeat: new Uniform(new Vector2())
1702
+ },
1703
+ defines: {
1704
+ steps: 20,
1705
+ refineSteps: 5,
1706
+ spp: 1,
1707
+ directLightMultiplier: 1,
1708
+ CUBEUV_TEXEL_WIDTH: 0,
1709
+ CUBEUV_TEXEL_HEIGHT: 0,
1710
+ CUBEUV_MAX_MIP: 0,
1711
+ vWorldPosition: "worldPos"
1712
+ },
1713
+ fragmentShader: fragmentShader$3.replace("#include <ssgi_utils>", ssgi_utils).replace("#include <sampleBlueNoise>", sampleBlueNoise),
1714
+ vertexShader,
1715
+ blending: NoBlending,
1716
+ depthWrite: false,
1717
+ depthTest: false,
1718
+ toneMapped: false,
1719
+ glslVersion: GLSL3
1720
+ });
1721
+ }
1722
+
1723
+ }
1724
+
1725
+ const backgroundColor$2 = new Color(0);
1726
+ const overrideMaterial = new MeshDepthMaterial({
1727
+ depthPacking: RGBADepthPacking,
1728
+ side: BackSide
1729
+ });
1730
+ class BackSideDepthPass extends Pass {
1731
+ constructor(scene, camera) {
1732
+ super("BackSideDepthPass");
1733
+ this._scene = scene;
1734
+ this._camera = camera;
1735
+ this.renderTarget = new WebGLRenderTarget(1, 1, {
1736
+ minFilter: NearestFilter,
1737
+ magFilter: NearestFilter
1738
+ });
1739
+ }
1740
+
1741
+ setSize(width, height) {
1742
+ this.renderTarget.setSize(width, height);
1743
+ }
1744
+
1745
+ dispose() {
1746
+ super.dispose();
1747
+ this.renderTarget.dispose();
1748
+ }
1749
+
1750
+ render(renderer) {
1751
+ const {
1752
+ background
1753
+ } = this._scene;
1754
+ this._scene.background = backgroundColor$2;
1755
+ this._scene.overrideMaterial = overrideMaterial;
1756
+ renderer.setRenderTarget(this.renderTarget);
1757
+ renderer.render(this._scene, this._camera);
1758
+ this._scene.background = background;
1759
+ this._scene.overrideMaterial = null;
1760
+ }
1761
+
1762
+ }
1763
+
1764
+ var img = "data:image/png;base64,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";
1765
+ var blueNoiseImage = img;
1766
+
1767
+ const backgroundColor$1 = new Color(0);
1768
+ class SSGIPass extends Pass {
1769
+ constructor(ssgiEffect, options) {
1770
+ super("SSGIPass");
1771
+ this.needsSwap = false;
1772
+ this.defaultFragmentShader = "";
1773
+ this.frame = 0;
1774
+ this.cachedMaterials = new WeakMap();
1775
+ this.visibleMeshes = [];
1776
+ this.ssgiEffect = ssgiEffect;
1777
+ this._scene = ssgiEffect._scene;
1778
+ this._camera = ssgiEffect._camera;
1779
+ this.fullscreenMaterial = new SSGIMaterial();
1780
+ this.defaultFragmentShader = this.fullscreenMaterial.fragmentShader;
1781
+ const bufferCount = !options.diffuseOnly && !options.specularOnly ? 2 : 1;
1782
+ this.renderTarget = createMultipleRenderTargets(1, 1, bufferCount, {
1783
+ type: HalfFloatType,
1784
+ depthBuffer: false
1785
+ }); // set up basic uniforms that we don't have to update
1786
+
1787
+ this.fullscreenMaterial.uniforms.cameraMatrixWorld.value = this._camera.matrixWorld;
1788
+ this.fullscreenMaterial.uniforms.viewMatrix.value = this._camera.matrixWorldInverse;
1789
+ this.fullscreenMaterial.uniforms.projectionMatrix.value = this._camera.projectionMatrix;
1790
+ this.fullscreenMaterial.uniforms.inverseProjectionMatrix.value = this._camera.projectionMatrixInverse;
1791
+ if (ssgiEffect._camera.isPerspectiveCamera) this.fullscreenMaterial.defines.PERSPECTIVE_CAMERA = "";
1792
+ if (options.diffuseOnly) this.fullscreenMaterial.defines.diffuseOnly = "";
1793
+ if (options.specularOnly) this.fullscreenMaterial.defines.specularOnly = "";
1794
+ this.initMRTRenderTarget();
1795
+ }
1796
+
1797
+ initialize(renderer, ...args) {
1798
+ super.initialize(renderer, ...args);
1799
+ new TextureLoader().load(blueNoiseImage, blueNoiseTexture => {
1800
+ blueNoiseTexture.minFilter = NearestFilter;
1801
+ blueNoiseTexture.magFilter = NearestFilter;
1802
+ blueNoiseTexture.wrapS = RepeatWrapping;
1803
+ blueNoiseTexture.wrapT = RepeatWrapping;
1804
+ blueNoiseTexture.colorSpace = NoColorSpace;
1805
+ this.fullscreenMaterial.uniforms.blueNoiseTexture.value = blueNoiseTexture;
1806
+ });
1807
+ }
1808
+
1809
+ get texture() {
1810
+ return this.renderTarget.textures[0];
1811
+ }
1812
+
1813
+ get specularTexture() {
1814
+ const index = "specularOnly" in this.fullscreenMaterial.defines ? 0 : 1;
1815
+ return this.renderTarget.textures[index];
1816
+ }
1817
+
1818
+ initMRTRenderTarget() {
1819
+ this.gBuffersRenderTarget = createMultipleRenderTargets(1, 1, 4, {
1820
+ minFilter: NearestFilter,
1821
+ magFilter: NearestFilter
1822
+ });
1823
+ this.gBuffersRenderTarget.depthTexture = new DepthTexture(1, 1);
1824
+ this.gBuffersRenderTarget.depthTexture.type = FloatType;
1825
+ this.backSideDepthPass = new BackSideDepthPass(this._scene, this._camera);
1826
+ this.depthTexture = this.gBuffersRenderTarget.textures[0];
1827
+ this.normalTexture = this.gBuffersRenderTarget.textures[1];
1828
+ this.diffuseTexture = this.gBuffersRenderTarget.textures[2];
1829
+ this.emissiveTexture = this.gBuffersRenderTarget.textures[3];
1830
+ this.diffuseTexture.minFilter = LinearFilter;
1831
+ this.diffuseTexture.magFilter = LinearFilter;
1832
+ this.diffuseTexture.colorSpace = SRGBColorSpace;
1833
+ this.diffuseTexture.needsUpdate = true;
1834
+ this.emissiveTexture.minFilter = LinearFilter;
1835
+ this.emissiveTexture.magFilter = LinearFilter;
1836
+ this.emissiveTexture.type = HalfFloatType;
1837
+ this.emissiveTexture.needsUpdate = true;
1838
+ this.normalTexture.type = HalfFloatType;
1839
+ this.normalTexture.needsUpdate = true;
1840
+ this.fullscreenMaterial.uniforms.normalTexture.value = this.normalTexture;
1841
+ this.fullscreenMaterial.uniforms.depthTexture.value = this.depthTexture;
1842
+ this.fullscreenMaterial.uniforms.diffuseTexture.value = this.diffuseTexture;
1843
+ this.fullscreenMaterial.uniforms.emissiveTexture.value = this.emissiveTexture;
1844
+ this.fullscreenMaterial.uniforms.backSideDepthTexture.value = this.backSideDepthPass.renderTarget.texture;
1845
+ }
1846
+
1847
+ setSize(width, height) {
1848
+ this.renderTarget.setSize(width * this.ssgiEffect.resolutionScale, height * this.ssgiEffect.resolutionScale);
1849
+ this.gBuffersRenderTarget.setSize(width, height);
1850
+ this.backSideDepthPass.setSize(width, height);
1851
+ this.fullscreenMaterial.uniforms.texSize.value.set(this.renderTarget.width, this.renderTarget.height);
1852
+ }
1853
+
1854
+ dispose() {
1855
+ super.dispose();
1856
+ this.renderTarget.dispose();
1857
+ this.gBuffersRenderTarget.dispose();
1858
+ this.backSideDepthPass.dispose();
1859
+ this.fullscreenMaterial.dispose();
1860
+ this.normalTexture = null;
1861
+ this.depthTexture = null;
1862
+ this.diffuseTexture = null;
1863
+ this.emissiveTexture = null;
1864
+ }
1865
+
1866
+ setMRTMaterialInScene() {
1867
+ this.visibleMeshes = getVisibleChildren(this._scene);
1868
+
1869
+ for (const c of this.visibleMeshes) {
1870
+ var _originalMaterial$rou, _c$material$metalness, _c$material$emissiveI;
1871
+
1872
+ const originalMaterial = c.material;
1873
+ let [cachedOriginalMaterial, mrtMaterial] = this.cachedMaterials.get(c) || [];
1874
+
1875
+ if (originalMaterial !== cachedOriginalMaterial) {
1876
+ var _c$skeleton;
1877
+
1878
+ if (mrtMaterial) mrtMaterial.dispose();
1879
+ mrtMaterial = new MRTMaterial();
1880
+ copyNecessaryProps(originalMaterial, mrtMaterial);
1881
+ mrtMaterial.uniforms.normalScale.value = originalMaterial.normalScale;
1882
+
1883
+ if ((_c$skeleton = c.skeleton) != null && _c$skeleton.boneTexture) {
1884
+ mrtMaterial.defines.USE_SKINNING = "";
1885
+ mrtMaterial.defines.BONE_TEXTURE = "";
1886
+ mrtMaterial.uniforms.boneTexture.value = c.skeleton.boneTexture;
1887
+ mrtMaterial.needsUpdate = true;
1888
+ }
1889
+
1890
+ const textureKey = Object.keys(originalMaterial).find(key => {
1891
+ const value = originalMaterial[key];
1892
+ return value instanceof Texture && value.matrix;
1893
+ });
1894
+ if (textureKey) mrtMaterial.uniforms.uvTransform.value = originalMaterial[textureKey].matrix;
1895
+ this.cachedMaterials.set(c, [originalMaterial, mrtMaterial]);
1896
+ }
1897
+
1898
+ if (originalMaterial.emissive) mrtMaterial.uniforms.emissive.value = originalMaterial.emissive;
1899
+ if (originalMaterial.color) mrtMaterial.uniforms.color.value = originalMaterial.color; // update the child's MRT material
1900
+
1901
+ keepMaterialMapUpdated(mrtMaterial, originalMaterial, "normalMap", "USE_NORMALMAP", true);
1902
+ keepMaterialMapUpdated(mrtMaterial, originalMaterial, "roughnessMap", "USE_ROUGHNESSMAP", true);
1903
+ keepMaterialMapUpdated(mrtMaterial, originalMaterial, "metalnessMap", "USE_ METALNESSMAP", true);
1904
+ keepMaterialMapUpdated(mrtMaterial, originalMaterial, "map", "USE_MAP", true);
1905
+ keepMaterialMapUpdated(mrtMaterial, originalMaterial, "emissiveMap", "USE_EMISSIVEMAP", true);
1906
+ keepMaterialMapUpdated(mrtMaterial, originalMaterial, "alphaMap", "USE_ALPHAMAP", true);
1907
+ const noiseTexture = this.fullscreenMaterial.uniforms.blueNoiseTexture.value;
1908
+
1909
+ if (noiseTexture) {
1910
+ const {
1911
+ width,
1912
+ height
1913
+ } = noiseTexture.source.data;
1914
+ mrtMaterial.uniforms.blueNoiseTexture.value = noiseTexture;
1915
+ mrtMaterial.uniforms.blueNoiseRepeat.value.set(this.renderTarget.width / width, this.renderTarget.height / height);
1916
+ }
1917
+
1918
+ mrtMaterial.uniforms.texSize.value.set(this.renderTarget.width, this.renderTarget.height);
1919
+ mrtMaterial.uniforms.frame.value = this.frame;
1920
+ c.visible = isChildMaterialRenderable(c, originalMaterial);
1921
+ const origRoughness = (_originalMaterial$rou = originalMaterial.roughness) !== null && _originalMaterial$rou !== void 0 ? _originalMaterial$rou : 1;
1922
+ mrtMaterial.uniforms.roughness.value = this.ssgiEffect.selection.size === 0 || this.ssgiEffect.selection.has(c) ? origRoughness : 10e10;
1923
+ mrtMaterial.uniforms.metalness.value = (_c$material$metalness = c.material.metalness) !== null && _c$material$metalness !== void 0 ? _c$material$metalness : 0;
1924
+ mrtMaterial.uniforms.emissiveIntensity.value = (_c$material$emissiveI = c.material.emissiveIntensity) !== null && _c$material$emissiveI !== void 0 ? _c$material$emissiveI : 0;
1925
+ c.material = mrtMaterial;
1926
+ }
1927
+ }
1928
+
1929
+ unsetMRTMaterialInScene() {
1930
+ for (const c of this.visibleMeshes) {
1931
+ c.visible = true; // set material back to the original one
1932
+
1933
+ const [originalMaterial] = this.cachedMaterials.get(c);
1934
+ c.material = originalMaterial;
1935
+ }
1936
+ }
1937
+
1938
+ render(renderer) {
1939
+ this.frame = (this.frame + this.ssgiEffect.spp) % 65536;
1940
+ const {
1941
+ background
1942
+ } = this._scene;
1943
+ this._scene.background = backgroundColor$1;
1944
+ this.setMRTMaterialInScene();
1945
+ renderer.setRenderTarget(this.gBuffersRenderTarget);
1946
+ renderer.render(this._scene, this._camera);
1947
+ this.unsetMRTMaterialInScene();
1948
+ if (this.ssgiEffect.autoThickness) this.backSideDepthPass.render(renderer); // update uniforms
1949
+
1950
+ this.fullscreenMaterial.uniforms.frame.value = this.frame;
1951
+ this.fullscreenMaterial.uniforms.cameraNear.value = this._camera.near;
1952
+ this.fullscreenMaterial.uniforms.cameraFar.value = this._camera.far;
1953
+ this.fullscreenMaterial.uniforms.viewMatrix.value.copy(this._camera.matrixWorldInverse);
1954
+ this.fullscreenMaterial.uniforms.accumulatedTexture.value = this.ssgiEffect.svgf.denoisePass.texture;
1955
+ const noiseTexture = this.fullscreenMaterial.uniforms.blueNoiseTexture.value;
1956
+
1957
+ if (noiseTexture) {
1958
+ const {
1959
+ width,
1960
+ height
1961
+ } = noiseTexture.source.data;
1962
+ this.fullscreenMaterial.uniforms.blueNoiseRepeat.value.set(this.renderTarget.width / width, this.renderTarget.height / height);
1963
+ }
1964
+
1965
+ renderer.setRenderTarget(this.renderTarget);
1966
+ renderer.render(this.scene, this.camera);
1967
+ this._scene.background = background;
1968
+ }
1969
+
1970
+ }
1971
+
1972
+ var ssgi_compose = "#define GLSLIFY 1\nuniform sampler2D inputTexture;uniform sampler2D sceneTexture;uniform sampler2D depthTexture;uniform int toneMapping;\n#include <tonemapping_pars_fragment>\n#pragma tonemapping_pars_fragment\nvoid mainImage(const in vec4 inputColor,const in vec2 uv,out vec4 outputColor){vec4 depthTexel=textureLod(depthTexture,uv,0.);vec3 ssgiClr;if(dot(depthTexel.rgb,depthTexel.rgb)==0.){ssgiClr=textureLod(sceneTexture,uv,0.).rgb;}else{ssgiClr=textureLod(inputTexture,uv,0.).rgb;switch(toneMapping){case 1:ssgiClr=LinearToneMapping(ssgiClr);break;case 2:ssgiClr=ReinhardToneMapping(ssgiClr);break;case 3:ssgiClr=OptimizedCineonToneMapping(ssgiClr);break;case 4:ssgiClr=ACESFilmicToneMapping(ssgiClr);break;case 5:ssgiClr=CustomToneMapping(ssgiClr);break;}ssgiClr*=toneMappingExposure;}outputColor=vec4(ssgiClr,1.0);}"; // eslint-disable-line
1973
+
1974
+ var denoise_compose = "#define GLSLIFY 1\nvec3 viewNormal=(vec4(normal,0.)*cameraMatrixWorld).xyz;roughness*=roughness;vec3 viewPos=getViewPosition(depth);vec3 viewDir=normalize(viewPos);vec3 T,B;vec3 n=viewNormal;vec3 v=viewDir;vec3 V=(vec4(v,0.)*viewMatrix).xyz;vec3 N=normal;Onb(N,T,B);V=ToLocal(T,B,N,V);vec3 H=SampleGGXVNDF(V,roughness,roughness,0.25,0.25);if(H.z<0.0)H=-H;vec3 l=normalize(reflect(-V,H));l=ToWorld(T,B,N,l);l=(vec4(l,1.)*cameraMatrixWorld).xyz;l=normalize(l);if(dot(viewNormal,l)<0.)l=-l;vec3 h=normalize(v+l);float VoH=max(EPSILON,dot(v,h));VoH=pow(VoH,0.875);vec4 diffuseTexel=textureLod(diffuseTexture,vUv,0.);vec3 diffuse=diffuseTexel.rgb;float metalness=diffuseTexel.a;vec3 f0=mix(vec3(0.04),diffuse,metalness);vec3 F=F_Schlick(f0,VoH);vec3 directLight=textureLod(directLightTexture,vUv,0.).rgb;\n#ifdef ssgi\nvec3 diffuseLightingColor=denoisedColor[0];vec3 diffuseComponent=diffuse*(1.-metalness)*(1.-F)*diffuseLightingColor;vec3 specularLightingColor=denoisedColor[1];vec3 specularComponent=specularLightingColor*F;denoisedColor[0]=diffuseComponent+specularComponent;\n#endif\n#ifdef ssdgi\nvec3 diffuseLightingColor=denoisedColor[0];vec3 diffuseComponent=diffuse*(1.-metalness)*(1.-F)*diffuseLightingColor;denoisedColor[0]=diffuseComponent;\n#endif\n#ifdef ssr\nvec3 specularLightingColor=denoisedColor[0];vec3 specularComponent=specularLightingColor*F;denoisedColor[0]=specularComponent;\n#endif\n#ifdef useDirectLight\ndenoisedColor[0]+=directLight;\n#endif\n"; // eslint-disable-line
1975
+
1976
+ var denoise_compose_functions = "#define GLSLIFY 1\nuniform sampler2D diffuseTexture;uniform sampler2D directLightTexture;vec3 getViewPosition(const float depth){float clipW=projectionMatrix[2][3]*depth+projectionMatrix[3][3];vec4 clipPosition=vec4((vec3(vUv,depth)-0.5)*2.0,1.0);clipPosition*=clipW;return(projectionMatrixInverse*clipPosition).xyz;}vec3 F_Schlick(const vec3 f0,const float theta){return f0+(1.-f0)*pow(1.0-theta,5.);}vec3 SampleGGXVNDF(const vec3 V,const float ax,const float ay,const float r1,const float r2){vec3 Vh=normalize(vec3(ax*V.x,ay*V.y,V.z));float lensq=Vh.x*Vh.x+Vh.y*Vh.y;vec3 T1=lensq>0. ? vec3(-Vh.y,Vh.x,0.)*inversesqrt(lensq): vec3(1.,0.,0.);vec3 T2=cross(Vh,T1);float r=sqrt(r1);float phi=2.0*PI*r2;float t1=r*cos(phi);float t2=r*sin(phi);float s=0.5*(1.0+Vh.z);t2=(1.0-s)*sqrt(1.0-t1*t1)+s*t2;vec3 Nh=t1*T1+t2*T2+sqrt(max(0.0,1.0-t1*t1-t2*t2))*Vh;return normalize(vec3(ax*Nh.x,ay*Nh.y,max(0.0,Nh.z)));}void Onb(const vec3 N,inout vec3 T,inout vec3 B){vec3 up=abs(N.z)<0.9999999 ? vec3(0,0,1): vec3(1,0,0);T=normalize(cross(up,N));B=cross(N,T);}vec3 ToLocal(const vec3 X,const vec3 Y,const vec3 Z,const vec3 V){return vec3(dot(V,X),dot(V,Y),dot(V,Z));}vec3 ToWorld(const vec3 X,const vec3 Y,const vec3 Z,const vec3 V){return V.x*X+V.y*Y+V.z*Z;}"; // eslint-disable-line
1977
+
1978
+ /**
1979
+ * Options of the SSGI effect
1980
+ * @typedef {Object} SSGIOptions
1981
+ * @property {Number} [distance] maximum distance a SSGI ray can travel to find what it reflects
1982
+ * @property {Number} [thickness] maximum depth difference between a ray and the particular depth at its screen position before refining with binary search; higher values will result in better performance
1983
+ * @property {Number} [autoThickness] whether to use a back-side depth buffer to approximate the actual thickness; enabling this may decrease performance; the thickness parameter will also be used as the minimum value
1984
+ * @property {Number} [maxRoughness] maximum roughness a texel can have to have SSGI calculated for it
1985
+ * @property {Number} [blend] a value between 0 and 1 to set how much the last frame's SSGI should be blended in; higher values will result in less noisy SSGI when moving the camera but a more smeary look
1986
+ * @property {Number} [denoiseIterations] how many times the denoise filter runs, more iterations will denoise the frame better but need more performance
1987
+ * @property {Number} [denoiseKernel] the kernel (~ number of neighboring pixels) to take into account when denoising a pixel
1988
+ * @property {Number} [denoiseDiffuse] diffuse luminance factor of the denoiser, higher values will denoise areas with varying luminance more aggressively
1989
+ * @property {Number} [denoiseSpecular] specular luminance factor of the denoiser, higher values will denoise areas with varying luminance more aggressively
1990
+ * @property {Number} [depthPhi] depth factor of the denoiser, higher values will use neighboring areas with different depth values more resulting in less noise but loss of details
1991
+ * @property {Number} [depthPhi] normals factor of the denoiser, higher values will use neighboring areas with different normals more resulting in less noise but loss of details and sharpness
1992
+ * @property {Number} [roughnessPhi] roughness factor of the denoiser setting how much the denoiser should only apply the blur to rougher surfaces, a value of 0 means the denoiser will blur mirror-like surfaces the same as rough surfaces
1993
+ * @property {Number} [directLightMultiplier] how much to boost direct lighting
1994
+ * @property {Number} [envBlur] higher values will result in lower mipmaps being sampled which will cause less noise but also less detail regarding environment lighting
1995
+ * @property {Number} [importanceSampling] whether to use importance sampling for the environment map
1996
+ * @property {Number} [steps] number of steps a SSGI ray can maximally do to find an object it intersected (and thus reflects)
1997
+ * @property {Number} [refineSteps] once we had our ray intersect something, we need to find the exact point in space it intersected and thus it reflects; this can be done through binary search with the given number of maximum steps
1998
+ * @property {Number} [spp] number of samples per pixel
1999
+ * @property {boolean} [missedRays] if there should still be SSGI for rays for which a reflecting point couldn't be found; enabling this will result in stretched looking SSGI which can look good or bad depending on the angle
2000
+ * @property {Number} [resolutionScale] resolution of the SSGI effect, a resolution of 0.5 means the effect will be rendered at half resolution
2001
+ */
2002
+
2003
+ /**
2004
+ * The options of the SSGI effect
2005
+ * @type {SSGIOptions}
2006
+ */
2007
+ const defaultSSGIOptions = {
2008
+ distance: 10,
2009
+ thickness: 10,
2010
+ autoThickness: false,
2011
+ maxRoughness: 1,
2012
+ blend: 0.9,
2013
+ denoiseIterations: 1,
2014
+ denoiseKernel: 2,
2015
+ denoiseDiffuse: 10,
2016
+ denoiseSpecular: 10,
2017
+ depthPhi: 2,
2018
+ normalPhi: 50,
2019
+ roughnessPhi: 1,
2020
+ envBlur: 0.5,
2021
+ importanceSampling: true,
2022
+ directLightMultiplier: 1,
2023
+ steps: 20,
2024
+ refineSteps: 5,
2025
+ spp: 1,
2026
+ resolutionScale: 1,
2027
+ missedRays: false
2028
+ };
2029
+
2030
+ const {
2031
+ render
2032
+ } = RenderPass.prototype;
2033
+ const globalIblIrradianceDisabledUniform = createGlobalDisableIblIradianceUniform();
2034
+ const globalIblRadianceDisabledUniform = createGlobalDisableIblRadianceUniform();
2035
+ class SSGIEffect extends Effect {
2036
+ /**
2037
+ * @param {THREE.Scene} scene The scene of the SSGI effect
2038
+ * @param {THREE.Camera} camera The camera with which SSGI is being rendered
2039
+ * @param {velocityDepthNormalPass} velocityDepthNormalPass Required velocity pass
2040
+ * @param {SSGIOptions} [options] The optional options for the SSGI effect
2041
+ */
2042
+ constructor(scene, camera, velocityDepthNormalPass, options = defaultSSGIOptions) {
2043
+ options = { ...defaultSSGIOptions,
2044
+ ...options
2045
+ };
2046
+ super("SSGIEffect", ssgi_compose, {
2047
+ type: "FinalSSGIMaterial",
2048
+ uniforms: new Map([["inputTexture", new Uniform(null)], ["sceneTexture", new Uniform(null)], ["depthTexture", new Uniform(null)], ["toneMapping", new Uniform(NoToneMapping)]])
2049
+ });
2050
+ this.selection = new Selection();
2051
+ this.isUsingRenderPass = true;
2052
+
2053
+ if (!(camera instanceof PerspectiveCamera)) {
2054
+ throw new Error(this.constructor.name + " doesn't support cameras of type '" + camera.constructor.name + "' yet. Only cameras of type 'PerspectiveCamera' are supported.");
2055
+ }
2056
+
2057
+ this._scene = scene;
2058
+ this._camera = camera;
2059
+ let definesName;
2060
+
2061
+ if (options.diffuseOnly) {
2062
+ definesName = "ssdgi";
2063
+ options.reprojectSpecular = false;
2064
+ options.roughnessDependent = false;
2065
+ options.basicVariance = 0.00025;
2066
+ options.neighborhoodClamp = false;
2067
+ } else if (options.specularOnly) {
2068
+ definesName = "ssr";
2069
+ options.reprojectSpecular = true;
2070
+ options.roughnessDependent = true;
2071
+ options.basicVariance = 0.00025;
2072
+ options.neighborhoodClamp = true;
2073
+ } else {
2074
+ definesName = "ssgi";
2075
+ options.reprojectSpecular = [false, true];
2076
+ options.neighborhoodClamp = [false, true];
2077
+ options.roughnessDependent = [false, true];
2078
+ options.basicVariance = [0.00025, 0.00025];
2079
+ }
2080
+
2081
+ options.neighborhoodClampRadius = 2;
2082
+ options.neighborhoodClampIntensity = 0.5;
2083
+ const textureCount = options.diffuseOnly || options.specularOnly ? 1 : 2;
2084
+ options = { ...options,
2085
+ ...{
2086
+ denoiseCustomComposeShader: denoise_compose,
2087
+ denoiseCustomComposeShaderFunctions: denoise_compose_functions
2088
+ }
2089
+ };
2090
+ this.svgf = new SVGF(scene, camera, velocityDepthNormalPass, textureCount, options);
2091
+
2092
+ if (definesName === "ssgi") {
2093
+ this.svgf.svgfTemporalReprojectPass.fullscreenMaterial.fragmentShader = this.svgf.svgfTemporalReprojectPass.fullscreenMaterial.fragmentShader.replace("accumulatedTexel[ 1 ].rgb = clampedColor;", `
2094
+ float roughness = inputTexel[ 0 ].a;
2095
+ accumulatedTexel[ 1 ].rgb = mix(accumulatedTexel[ 1 ].rgb, clampedColor, 1. - sqrt(roughness));
2096
+ `);
2097
+ } else if (definesName === "ssr") {
2098
+ this.svgf.svgfTemporalReprojectPass.fullscreenMaterial.fragmentShader = this.svgf.svgfTemporalReprojectPass.fullscreenMaterial.fragmentShader.replace("accumulatedTexel[ 0 ].rgb = clampedColor;", `
2099
+ accumulatedTexel[ 0 ].rgb = mix(accumulatedTexel[ 0 ].rgb, clampedColor, 0.5);
2100
+ `);
2101
+ }
2102
+
2103
+ this.svgf.svgfTemporalReprojectPass.fullscreenMaterial.needsUpdate = true; // ssgi pass
2104
+
2105
+ this.ssgiPass = new SSGIPass(this, options);
2106
+
2107
+ if (options.diffuseOnly) {
2108
+ this.svgf.svgfTemporalReprojectPass.fullscreenMaterial.uniforms.inputTexture0.value = this.ssgiPass.texture;
2109
+ } else if (options.specularOnly) {
2110
+ this.svgf.svgfTemporalReprojectPass.fullscreenMaterial.uniforms.inputTexture0.value = this.ssgiPass.specularTexture;
2111
+ } else {
2112
+ this.svgf.svgfTemporalReprojectPass.fullscreenMaterial.uniforms.inputTexture0.value = this.ssgiPass.texture;
2113
+ this.svgf.svgfTemporalReprojectPass.fullscreenMaterial.uniforms.inputTexture1.value = this.ssgiPass.specularTexture;
2114
+ }
2115
+
2116
+ this.svgf.setJitteredGBuffers(this.ssgiPass.depthTexture, this.ssgiPass.normalTexture, {
2117
+ useRoughnessInAlphaChannel: true
2118
+ }); // patch the denoise pass
2119
+
2120
+ this.svgf.denoisePass.fullscreenMaterial.uniforms = { ...this.svgf.denoisePass.fullscreenMaterial.uniforms,
2121
+ ...{
2122
+ diffuseTexture: new Uniform(null),
2123
+ directLightTexture: new Uniform(null)
2124
+ }
2125
+ };
2126
+ this.svgf.denoisePass.fullscreenMaterial.defines[definesName] = "";
2127
+ this.svgf.denoisePass.fullscreenMaterial.uniforms.diffuseTexture.value = this.ssgiPass.diffuseTexture;
2128
+ this.lastSize = {
2129
+ width: options.width,
2130
+ height: options.height,
2131
+ resolutionScale: options.resolutionScale
2132
+ };
2133
+ this.sceneRenderTarget = new WebGLRenderTarget(1, 1, {
2134
+ colorSpace: SRGBColorSpace
2135
+ });
2136
+ this.renderPass = new RenderPass(this._scene, this._camera);
2137
+ this.renderPass.renderToScreen = false;
2138
+ this.setSize(options.width, options.height);
2139
+ const th = this;
2140
+ const ssgiRenderPass = this.renderPass; // eslint-disable-next-line space-before-function-paren
2141
+
2142
+ RenderPass.prototype.render = function (...args) {
2143
+ if (this !== ssgiRenderPass) {
2144
+ const wasUsingRenderPass = th.isUsingRenderPass;
2145
+ th.isUsingRenderPass = true;
2146
+ if (wasUsingRenderPass != th.isUsingRenderPass) th.updateUsingRenderPass();
2147
+ }
2148
+
2149
+ render.call(this, ...args);
2150
+ };
2151
+
2152
+ this.makeOptionsReactive(options);
2153
+ }
2154
+
2155
+ updateUsingRenderPass() {
2156
+ if (this.isUsingRenderPass) {
2157
+ this.ssgiPass.fullscreenMaterial.defines.useDirectLight = "";
2158
+ this.svgf.denoisePass.fullscreenMaterial.defines.useDirectLight = "";
2159
+ } else {
2160
+ delete this.ssgiPass.fullscreenMaterial.defines.useDirectLight;
2161
+ delete this.svgf.denoisePass.fullscreenMaterial.defines.useDirectLight;
2162
+ }
2163
+
2164
+ this.ssgiPass.fullscreenMaterial.needsUpdate = true;
2165
+ this.svgf.denoisePass.fullscreenMaterial.needsUpdate = true;
2166
+ }
2167
+
2168
+ makeOptionsReactive(options) {
2169
+ let needsUpdate = false;
2170
+ const ssgiPassFullscreenMaterialUniforms = this.ssgiPass.fullscreenMaterial.uniforms;
2171
+ const ssgiPassFullscreenMaterialUniformsKeys = Object.keys(ssgiPassFullscreenMaterialUniforms);
2172
+ const temporalReprojectPass = this.svgf.svgfTemporalReprojectPass;
2173
+
2174
+ for (const key of Object.keys(options)) {
2175
+ Object.defineProperty(this, key, {
2176
+ get() {
2177
+ return options[key];
2178
+ },
2179
+
2180
+ set(value) {
2181
+ if (options[key] === value && needsUpdate) return;
2182
+ options[key] = value;
2183
+
2184
+ switch (key) {
2185
+ // denoiser
2186
+ case "denoiseIterations":
2187
+ this.svgf.denoisePass.iterations = value;
2188
+ break;
2189
+
2190
+ case "denoiseDiffuse":
2191
+ this.svgf.denoisePass.fullscreenMaterial.uniforms.denoise.value[0] = value;
2192
+ break;
2193
+
2194
+ case "denoiseSpecular":
2195
+ this.svgf.denoisePass.fullscreenMaterial.uniforms.denoise.value[1] = value;
2196
+ break;
2197
+
2198
+ case "denoiseKernel":
2199
+ case "depthPhi":
2200
+ case "normalPhi":
2201
+ case "roughnessPhi":
2202
+ this.svgf.denoisePass.fullscreenMaterial.uniforms[key].value = value;
2203
+ break;
2204
+ // SSGI
2205
+
2206
+ case "resolutionScale":
2207
+ this.setSize(this.lastSize.width, this.lastSize.height);
2208
+ temporalReprojectPass.reset();
2209
+ break;
2210
+ // defines
2211
+
2212
+ case "spp":
2213
+ this.ssgiPass.fullscreenMaterial.fragmentShader = this.ssgiPass.defaultFragmentShader.replaceAll("spp", value);
2214
+
2215
+ if (value !== 1) {
2216
+ this.ssgiPass.fullscreenMaterial.fragmentShader = unrollLoops(this.ssgiPass.fullscreenMaterial.fragmentShader.replace("#pragma unroll_loop_start", "").replace("#pragma unroll_loop_end", ""));
2217
+ }
2218
+
2219
+ this.ssgiPass.fullscreenMaterial.needsUpdate = needsUpdate;
2220
+ temporalReprojectPass.reset();
2221
+ break;
2222
+
2223
+ case "steps":
2224
+ case "refineSteps":
2225
+ this.ssgiPass.fullscreenMaterial.defines[key] = parseInt(value);
2226
+ this.ssgiPass.fullscreenMaterial.needsUpdate = needsUpdate;
2227
+ temporalReprojectPass.reset();
2228
+ break;
2229
+
2230
+ case "directLightMultiplier":
2231
+ this.ssgiPass.fullscreenMaterial.defines[key] = value.toPrecision(5);
2232
+ this.ssgiPass.fullscreenMaterial.needsUpdate = needsUpdate;
2233
+ temporalReprojectPass.reset();
2234
+ break;
2235
+
2236
+ case "importanceSampling":
2237
+ case "missedRays":
2238
+ case "autoThickness":
2239
+ if (value) {
2240
+ this.ssgiPass.fullscreenMaterial.defines[key] = "";
2241
+ } else {
2242
+ delete this.ssgiPass.fullscreenMaterial.defines[key];
2243
+ }
2244
+
2245
+ this.ssgiPass.fullscreenMaterial.needsUpdate = needsUpdate;
2246
+ temporalReprojectPass.reset();
2247
+ break;
2248
+
2249
+ case "blend":
2250
+ this.svgf.svgfTemporalReprojectPass.fullscreenMaterial.uniforms[key].value = value;
2251
+ temporalReprojectPass.reset();
2252
+ break;
2253
+
2254
+ case "distance":
2255
+ ssgiPassFullscreenMaterialUniforms.rayDistance.value = value;
2256
+ temporalReprojectPass.reset();
2257
+ break;
2258
+ // must be a uniform
2259
+
2260
+ default:
2261
+ if (ssgiPassFullscreenMaterialUniformsKeys.includes(key)) {
2262
+ ssgiPassFullscreenMaterialUniforms[key].value = value;
2263
+ temporalReprojectPass.reset();
2264
+ }
2265
+
2266
+ }
2267
+ }
2268
+
2269
+ }); // apply all uniforms and defines
2270
+
2271
+ this[key] = options[key];
2272
+ }
2273
+
2274
+ needsUpdate = true;
2275
+ }
2276
+
2277
+ initialize(renderer, ...args) {
2278
+ super.initialize(renderer, ...args);
2279
+ this.ssgiPass.initialize(renderer, ...args);
2280
+ }
2281
+
2282
+ setSize(width, height, force = false) {
2283
+ var _this$cubeToEquirectE;
2284
+
2285
+ if (width === undefined && height === undefined) return;
2286
+
2287
+ if (!force && width === this.lastSize.width && height === this.lastSize.height && this.resolutionScale === this.lastSize.resolutionScale) {
2288
+ return;
2289
+ }
2290
+
2291
+ this.ssgiPass.setSize(width, height);
2292
+ this.svgf.setSize(width, height);
2293
+ this.sceneRenderTarget.setSize(width, height);
2294
+ (_this$cubeToEquirectE = this.cubeToEquirectEnvPass) == null ? void 0 : _this$cubeToEquirectE.setSize(width, height);
2295
+ this.lastSize = {
2296
+ width,
2297
+ height,
2298
+ resolutionScale: this.resolutionScale
2299
+ };
2300
+ }
2301
+
2302
+ dispose() {
2303
+ var _this$cubeToEquirectE2;
2304
+
2305
+ super.dispose();
2306
+ this.ssgiPass.dispose();
2307
+ this.svgf.dispose();
2308
+ (_this$cubeToEquirectE2 = this.cubeToEquirectEnvPass) == null ? void 0 : _this$cubeToEquirectE2.dispose();
2309
+ RenderPass.prototype.render = render;
2310
+ }
2311
+
2312
+ keepEnvMapUpdated(renderer) {
2313
+ const ssgiMaterial = this.ssgiPass.fullscreenMaterial;
2314
+ let environment = this._scene.environment;
2315
+
2316
+ if (environment) {
2317
+ if (ssgiMaterial.uniforms.envMapInfo.value.mapUuid !== environment.uuid) {
2318
+ // if the environment is a cube texture, convert it to an equirectangular texture so we can sample it in the SSGI pass and use MIS
2319
+ if (environment.isCubeTexture) {
2320
+ if (!this.cubeToEquirectEnvPass) this.cubeToEquirectEnvPass = new CubeToEquirectEnvPass();
2321
+ environment = this.cubeToEquirectEnvPass.generateEquirectEnvMap(renderer, environment);
2322
+ environment.uuid = this._scene.environment.uuid;
2323
+ }
2324
+
2325
+ if (!environment.generateMipmaps) {
2326
+ environment.generateMipmaps = true;
2327
+ environment.minFilter = LinearMipMapLinearFilter;
2328
+ environment.magFilter = LinearMipMapLinearFilter;
2329
+ environment.needsUpdate = true;
2330
+ }
2331
+
2332
+ ssgiMaterial.uniforms.envMapInfo.value.mapUuid = environment.uuid;
2333
+ const maxEnvMapMipLevel = getMaxMipLevel(environment);
2334
+ ssgiMaterial.uniforms.maxEnvMapMipLevel.value = maxEnvMapMipLevel;
2335
+ ssgiMaterial.uniforms.envMapInfo.value.map = environment;
2336
+ ssgiMaterial.defines.USE_ENVMAP = "";
2337
+ delete ssgiMaterial.defines.importanceSampling;
2338
+
2339
+ if (this.importanceSampling) {
2340
+ ssgiMaterial.uniforms.envMapInfo.value.updateFrom(environment, renderer).then(() => {
2341
+ ssgiMaterial.defines.importanceSampling = "";
2342
+ ssgiMaterial.needsUpdate = true;
2343
+ });
2344
+ } else {
2345
+ ssgiMaterial.uniforms.envMapInfo.value.map = environment;
2346
+ }
2347
+
2348
+ this.svgf.svgfTemporalReprojectPass.reset();
2349
+ ssgiMaterial.needsUpdate = true;
2350
+ }
2351
+ } else if ("USE_ENVMAP" in ssgiMaterial.defines) {
2352
+ delete ssgiMaterial.defines.USE_ENVMAP;
2353
+ delete ssgiMaterial.defines.importanceSampling;
2354
+ ssgiMaterial.needsUpdate = true;
2355
+ }
2356
+ }
2357
+
2358
+ update(renderer, inputBuffer) {
2359
+ this.keepEnvMapUpdated(renderer);
2360
+ const sceneBuffer = this.isUsingRenderPass ? inputBuffer : this.sceneRenderTarget;
2361
+ const hideMeshes = [];
2362
+
2363
+ if (!this.isUsingRenderPass) {
2364
+ const children = [];
2365
+
2366
+ for (const c of getVisibleChildren(this._scene)) {
2367
+ if (c.isScene) return;
2368
+ c.visible = !isChildMaterialRenderable(c);
2369
+ c.visible ? hideMeshes.push(c) : children.push(c);
2370
+ }
2371
+
2372
+ this.renderPass.render(renderer, this.sceneRenderTarget);
2373
+
2374
+ for (const c of children) c.visible = true;
2375
+
2376
+ for (const c of hideMeshes) c.visible = false;
2377
+ }
2378
+
2379
+ this.ssgiPass.fullscreenMaterial.uniforms.directLightTexture.value = sceneBuffer.texture;
2380
+ this.svgf.denoisePass.fullscreenMaterial.uniforms.directLightTexture.value = sceneBuffer.texture;
2381
+ this.ssgiPass.render(renderer);
2382
+ this.svgf.render(renderer);
2383
+ this.uniforms.get("inputTexture").value = this.svgf.texture;
2384
+ this.uniforms.get("sceneTexture").value = sceneBuffer.texture;
2385
+ this.uniforms.get("depthTexture").value = this.ssgiPass.depthTexture;
2386
+ this.uniforms.get("toneMapping").value = renderer.toneMapping;
2387
+
2388
+ for (const c of hideMeshes) c.visible = true;
2389
+
2390
+ const fullGi = !this.diffuseOnly && !this.specularOnly;
2391
+ globalIblIrradianceDisabledUniform.value = fullGi || this.diffuseOnly === true;
2392
+ globalIblRadianceDisabledUniform.value = fullGi || this.specularOnly == true;
2393
+ cancelAnimationFrame(this.rAF2);
2394
+ cancelAnimationFrame(this.rAF);
2395
+ cancelAnimationFrame(this.usingRenderPassRAF);
2396
+ this.rAF = requestAnimationFrame(() => {
2397
+ this.rAF2 = requestAnimationFrame(() => {
2398
+ globalIblIrradianceDisabledUniform.value = false;
2399
+ globalIblRadianceDisabledUniform.value = false;
2400
+ });
2401
+ });
2402
+ this.usingRenderPassRAF = requestAnimationFrame(() => {
2403
+ const wasUsingRenderPass = this.isUsingRenderPass;
2404
+ this.isUsingRenderPass = false;
2405
+ if (wasUsingRenderPass != this.isUsingRenderPass) this.updateUsingRenderPass();
2406
+ });
2407
+ }
2408
+
2409
+ }
2410
+ SSGIEffect.DefaultOptions = defaultSSGIOptions;
2411
+
2412
+ class SSREffect extends SSGIEffect {
2413
+ constructor(scene, camera, velocityDepthNormalPass, options = defaultSSGIOptions) {
2414
+ options = { ...defaultSSGIOptions,
2415
+ ...options
2416
+ };
2417
+ options.specularOnly = true;
2418
+ super(scene, camera, velocityDepthNormalPass, options);
2419
+ }
2420
+
2421
+ }
2422
+
2423
+ class SSDGIEffect extends SSGIEffect {
2424
+ constructor(scene, camera, velocityDepthNormalPass, options = defaultSSGIOptions) {
2425
+ options = { ...defaultSSGIOptions,
2426
+ ...options
2427
+ };
2428
+ options.diffuseOnly = true;
2429
+ super(scene, camera, velocityDepthNormalPass, options);
2430
+ }
2431
+
2432
+ }
2433
+
2434
+ var motion_blur = "#define GLSLIFY 1\nuniform sampler2D inputTexture;uniform sampler2D velocityTexture;uniform sampler2D blueNoiseTexture;uniform ivec2 blueNoiseSize;uniform vec2 texSize;uniform float intensity;uniform float jitter;uniform float deltaTime;uniform int frame;uvec4 s0,s1;ivec2 pixel;void rng_initialize(vec2 p,int frame){pixel=ivec2(p);s0=uvec4(p,uint(frame),uint(p.x)+uint(p.y));s1=uvec4(frame,frame*15843,frame*31+4566,frame*2345+58585);}void pcg4d(inout uvec4 v){v=v*1664525u+1013904223u;v.x+=v.y*v.w;v.y+=v.z*v.x;v.z+=v.x*v.y;v.w+=v.y*v.z;v=v ^(v>>16u);v.x+=v.y*v.w;v.y+=v.z*v.x;v.z+=v.x*v.y;v.w+=v.y*v.z;}ivec2 shift2(){pcg4d(s1);return(pixel+ivec2(s1.xy % 0x0fffffffu))% blueNoiseSize;}void mainImage(const in vec4 inputColor,const in vec2 uv,out vec4 outputColor){vec4 velocity=textureLod(velocityTexture,vUv,0.0);if(dot(velocity.xyz,velocity.xyz)==0.0){outputColor=inputColor;return;}velocity.xy*=intensity;rng_initialize(vUv*texSize,frame);vec2 blueNoise=texelFetch(blueNoiseTexture,shift2(),0).rg-0.5;vec2 jitterOffset=jitter*velocity.xy*blueNoise;float frameSpeed=(1./100.)/deltaTime;vec2 startUv=vUv+(jitterOffset-velocity.xy*0.5)*frameSpeed;vec2 endUv=vUv+(jitterOffset+velocity.xy*0.5)*frameSpeed;startUv=max(vec2(0.),startUv);endUv=min(vec2(1.),endUv);vec3 motionBlurredColor;for(float i=0.0;i<=samplesFloat;i++){vec2 reprojectedUv=mix(startUv,endUv,i/samplesFloat);vec3 neighborColor=textureLod(inputTexture,reprojectedUv,0.0).rgb;motionBlurredColor+=neighborColor;}motionBlurredColor/=samplesFloat;outputColor=vec4(motionBlurredColor,inputColor.a);}"; // eslint-disable-line
2435
+
2436
+ /* eslint-disable camelcase */
2437
+ // http://john-chapman-graphics.blogspot.com/2013/01/per-object-motion-blur.html
2438
+ // reference code: https://github.com/gkjohnson/threejs-sandbox/blob/master/motionBlurPass/src/CompositeShader.js
2439
+
2440
+ const defaultOptions = {
2441
+ intensity: 1,
2442
+ jitter: 1,
2443
+ samples: 16
2444
+ };
2445
+ class MotionBlurEffect extends Effect {
2446
+ constructor(velocityPass, options = defaultOptions) {
2447
+ options = { ...defaultOptions,
2448
+ ...options
2449
+ };
2450
+ super("MotionBlurEffect", motion_blur, {
2451
+ type: "MotionBlurMaterial",
2452
+ uniforms: new Map([["inputTexture", new Uniform(null)], ["velocityTexture", new Uniform(velocityPass.texture)], ["blueNoiseTexture", new Uniform(null)], ["blueNoiseSize", new Uniform(new Vector2())], ["texSize", new Uniform(new Vector2())], ["intensity", new Uniform(1)], ["jitter", new Uniform(1)], ["frame", new Uniform(0)], ["deltaTime", new Uniform(0)]]),
2453
+ defines: new Map([["samples", options.samples.toFixed(0)], ["samplesFloat", options.samples.toFixed(0) + ".0"]])
2454
+ });
2455
+ this.pointsIndex = 0;
2456
+ this.makeOptionsReactive(options);
2457
+ }
2458
+
2459
+ makeOptionsReactive(options) {
2460
+ for (const key of Object.keys(options)) {
2461
+ Object.defineProperty(this, key, {
2462
+ get() {
2463
+ return options[key];
2464
+ },
2465
+
2466
+ set(value) {
2467
+ options[key] = value;
2468
+
2469
+ switch (key) {
2470
+ case "intensity":
2471
+ case "jitter":
2472
+ this.uniforms.get(key).value = value;
2473
+ break;
2474
+ }
2475
+ }
2476
+
2477
+ });
2478
+ this[key] = options[key];
2479
+ }
2480
+ }
2481
+
2482
+ initialize(renderer, ...args) {
2483
+ super.initialize(renderer, ...args);
2484
+ new TextureLoader().load(blueNoiseImage, blueNoiseTexture => {
2485
+ blueNoiseTexture.minFilter = NearestFilter;
2486
+ blueNoiseTexture.magFilter = NearestFilter;
2487
+ blueNoiseTexture.wrapS = RepeatWrapping;
2488
+ blueNoiseTexture.wrapT = RepeatWrapping;
2489
+ blueNoiseTexture.colorSpace = NoColorSpace;
2490
+ this.uniforms.get("blueNoiseTexture").value = blueNoiseTexture;
2491
+ });
2492
+ }
2493
+
2494
+ update(renderer, inputBuffer, deltaTime) {
2495
+ this.uniforms.get("inputTexture").value = inputBuffer.texture;
2496
+ this.uniforms.get("deltaTime").value = Math.max(1 / 1000, deltaTime);
2497
+ const frame = renderer.info.render.frame % 65536;
2498
+ this.uniforms.get("frame").value = frame;
2499
+ this.uniforms.get("texSize").value.set(window.innerWidth, window.innerHeight);
2500
+ const noiseTexture = this.uniforms.get("blueNoiseTexture").value;
2501
+
2502
+ if (noiseTexture) {
2503
+ const {
2504
+ width,
2505
+ height
2506
+ } = noiseTexture.source.data;
2507
+ this.uniforms.get("blueNoiseSize").value.set(width, height);
2508
+ }
2509
+ }
2510
+
2511
+ }
2512
+
2513
+ // this shader is from: https://github.com/gkjohnson/threejs-sandbox
2514
+ // a second set of bone information from the previous frame
2515
+
2516
+ const prev_skinning_pars_vertex =
2517
+ /* glsl */
2518
+ `
2519
+ #ifdef USE_SKINNING
2520
+ #ifdef BONE_TEXTURE
2521
+ uniform sampler2D prevBoneTexture;
2522
+ mat4 getPrevBoneMatrix( const in float i ) {
2523
+ float j = i * 4.0;
2524
+ float x = mod( j, float( boneTextureSize ) );
2525
+ float y = floor( j / float( boneTextureSize ) );
2526
+ float dx = 1.0 / float( boneTextureSize );
2527
+ float dy = 1.0 / float( boneTextureSize );
2528
+ y = dy * ( y + 0.5 );
2529
+ vec4 v1 = textureLod( prevBoneTexture, vec2( dx * ( x + 0.5 ), y ), 0. );
2530
+ vec4 v2 = textureLod( prevBoneTexture, vec2( dx * ( x + 1.5 ), y ), 0. );
2531
+ vec4 v3 = textureLod( prevBoneTexture, vec2( dx * ( x + 2.5 ), y ), 0. );
2532
+ vec4 v4 = textureLod( prevBoneTexture, vec2( dx * ( x + 3.5 ), y ), 0. );
2533
+ mat4 bone = mat4( v1, v2, v3, v4 );
2534
+ return bone;
2535
+ }
2536
+ #else
2537
+ uniform mat4 prevBoneMatrices[ MAX_BONES ];
2538
+ mat4 getPrevBoneMatrix( const in float i ) {
2539
+ mat4 bone = prevBoneMatrices[ int(i) ];
2540
+ return bone;
2541
+ }
2542
+ #endif
2543
+ #endif
2544
+ `;
2545
+ const velocity_vertex_pars =
2546
+ /* glsl */
2547
+ `
2548
+ #define MAX_BONES 64
2549
+
2550
+ ${ShaderChunk.skinning_pars_vertex}
2551
+ ${prev_skinning_pars_vertex}
2552
+
2553
+ uniform mat4 velocityMatrix;
2554
+ uniform mat4 prevVelocityMatrix;
2555
+ varying vec4 prevPosition;
2556
+ varying vec4 newPosition;
2557
+
2558
+ #ifdef renderDepth
2559
+ varying vec2 vHighPrecisionZW;
2560
+ #endif
2561
+ `; // Returns the body of the vertex shader for the velocity buffer
2562
+
2563
+ const velocity_vertex_main =
2564
+ /* glsl */
2565
+ `
2566
+ // Get the current vertex position
2567
+ transformed = vec3( position );
2568
+ ${ShaderChunk.skinning_vertex}
2569
+ newPosition = velocityMatrix * vec4( transformed, 1.0 );
2570
+
2571
+ // Get the previous vertex position
2572
+ transformed = vec3( position );
2573
+ ${ShaderChunk.skinbase_vertex.replace(/mat4 /g, "").replace(/getBoneMatrix/g, "getPrevBoneMatrix")}
2574
+ ${ShaderChunk.skinning_vertex.replace(/vec4 /g, "")}
2575
+ prevPosition = prevVelocityMatrix * vec4( transformed, 1.0 );
2576
+
2577
+ gl_Position = newPosition;
2578
+
2579
+ #ifdef renderDepth
2580
+ vHighPrecisionZW = gl_Position.zw;
2581
+ #endif
2582
+ `;
2583
+ const velocity_fragment_pars =
2584
+ /* glsl */
2585
+ `
2586
+ varying vec4 prevPosition;
2587
+ varying vec4 newPosition;
2588
+
2589
+ #ifdef renderDepth
2590
+ varying vec2 vHighPrecisionZW;
2591
+ #endif
2592
+ `;
2593
+ const velocity_fragment_main =
2594
+ /* glsl */
2595
+ `
2596
+ vec2 pos0 = (prevPosition.xy / prevPosition.w) * 0.5 + 0.5;
2597
+ vec2 pos1 = (newPosition.xy / newPosition.w) * 0.5 + 0.5;
2598
+
2599
+ vec2 vel = pos1 - pos0;
2600
+
2601
+ #ifdef renderDepth
2602
+ float fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;
2603
+ #endif
2604
+
2605
+ gl_FragColor = vec4(vel.x, vel.y, 0., 0.);
2606
+ `;
2607
+ const velocity_uniforms = {
2608
+ prevVelocityMatrix: {
2609
+ value: new Matrix4()
2610
+ },
2611
+ velocityMatrix: {
2612
+ value: new Matrix4()
2613
+ },
2614
+ prevBoneTexture: {
2615
+ value: null
2616
+ },
2617
+ boneTexture: {
2618
+ value: null
2619
+ },
2620
+ normalMap: {
2621
+ value: null
2622
+ },
2623
+ normalScale: {
2624
+ value: new Vector2()
2625
+ },
2626
+ uvTransform: {
2627
+ value: new Matrix3()
2628
+ }
2629
+ };
2630
+ class VelocityDepthNormalMaterial extends ShaderMaterial {
2631
+ constructor() {
2632
+ super({
2633
+ uniforms: UniformsUtils.clone(velocity_uniforms),
2634
+ glslVersion: GLSL3,
2635
+ vertexShader:
2636
+ /* glsl */
2637
+ `
2638
+ #include <common>
2639
+ #include <uv_pars_vertex>
2640
+ #include <displacementmap_pars_vertex>
2641
+ #include <normal_pars_vertex>
2642
+ #include <morphtarget_pars_vertex>
2643
+ #include <logdepthbuf_pars_vertex>
2644
+ #include <clipping_planes_pars_vertex>
2645
+
2646
+ #if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )
2647
+ varying vec3 vViewPosition;
2648
+ #endif
2649
+
2650
+ ${velocity_vertex_pars}
2651
+
2652
+ void main() {
2653
+ vec3 transformed;
2654
+
2655
+ #include <uv_vertex>
2656
+
2657
+ #include <skinbase_vertex>
2658
+ #include <beginnormal_vertex>
2659
+ #include <skinnormal_vertex>
2660
+ #include <defaultnormal_vertex>
2661
+
2662
+ #include <morphnormal_vertex>
2663
+ #include <normal_vertex>
2664
+ #include <morphtarget_vertex>
2665
+ #include <displacementmap_vertex>
2666
+ #include <project_vertex>
2667
+ #include <logdepthbuf_vertex>
2668
+ #include <clipping_planes_vertex>
2669
+
2670
+ ${velocity_vertex_main}
2671
+
2672
+ #if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )
2673
+ vViewPosition = - mvPosition.xyz;
2674
+ #endif
2675
+
2676
+ }`,
2677
+ fragmentShader:
2678
+ /* glsl */
2679
+ `
2680
+ #if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )
2681
+ varying vec3 vViewPosition;
2682
+ #endif
2683
+
2684
+ #ifdef renderDepth
2685
+ layout(location = 0) out vec4 gDepth;
2686
+ layout(location = 1) out vec4 gVelocity;
2687
+ #else
2688
+ #define gVelocity gl_FragColor
2689
+ #endif
2690
+
2691
+ ${velocity_fragment_pars}
2692
+ #include <packing>
2693
+
2694
+ #include <uv_pars_fragment>
2695
+ #include <normal_pars_fragment>
2696
+
2697
+ // source: https://knarkowicz.wordpress.com/2014/04/16/octahedron-normal-vector-encoding/
2698
+ vec2 OctWrap( vec2 v ) {
2699
+ vec2 w = 1.0 - abs( v.yx );
2700
+ if (v.x < 0.0) w.x = -w.x;
2701
+ if (v.y < 0.0) w.y = -w.y;
2702
+ return w;
2703
+ }
2704
+
2705
+ vec2 Encode( vec3 n ) {
2706
+ n /= ( abs( n.x ) + abs( n.y ) + abs( n.z ) );
2707
+ n.xy = n.z > 0.0 ? n.xy : OctWrap( n.xy );
2708
+ n.xy = n.xy * 0.5 + 0.5;
2709
+ return n.xy;
2710
+ }
2711
+
2712
+ void main() {
2713
+ #include <normal_fragment_begin>
2714
+ #include <normal_fragment_maps>
2715
+
2716
+ ${velocity_fragment_main.replaceAll("gl_FragColor", "gVelocity")}
2717
+ vec3 worldNormal = normalize((vec4(normal, 0.) * viewMatrix).xyz);
2718
+ gVelocity.ba = Encode(worldNormal);
2719
+
2720
+ #ifdef renderDepth
2721
+ gDepth = packDepthToRGBA(fragCoordZ);
2722
+ #endif
2723
+ }`
2724
+ });
2725
+ this.isVelocityMaterial = true;
2726
+ }
2727
+
2728
+ }
2729
+
2730
+ const backgroundColor = new Color(0);
2731
+ const zeroVec2 = new Vector2();
2732
+ const tmpProjectionMatrix = new Matrix4();
2733
+ const tmpProjectionMatrixInverse = new Matrix4();
2734
+ class VelocityDepthNormalPass extends Pass {
2735
+ constructor(scene, camera, renderDepth = true) {
2736
+ super("velocityDepthNormalPass");
2737
+ this.cachedMaterials = new WeakMap();
2738
+ this.visibleMeshes = [];
2739
+ this.needsSwap = false;
2740
+
2741
+ if (!(camera instanceof PerspectiveCamera)) {
2742
+ throw new Error(this.constructor.name + " doesn't support cameras of type '" + camera.constructor.name + "' yet. Only cameras of type 'PerspectiveCamera' are supported.");
2743
+ }
2744
+
2745
+ this._scene = scene;
2746
+ this._camera = camera;
2747
+ const bufferCount = renderDepth ? 2 : 1;
2748
+ this.renderTarget = createMultipleRenderTargets(1, 1, bufferCount, {
2749
+ minFilter: NearestFilter,
2750
+ magFilter: NearestFilter
2751
+ });
2752
+ this.renderTarget.depthTexture = new DepthTexture(1, 1);
2753
+ this.renderTarget.depthTexture.type = FloatType;
2754
+
2755
+ if (renderDepth) {
2756
+ this.renderTarget.textures[0].type = UnsignedByteType;
2757
+ this.renderTarget.textures[0].needsUpdate = true;
2758
+ this.renderTarget.textures[1].type = FloatType;
2759
+ this.renderTarget.textures[1].needsUpdate = true;
2760
+ }
2761
+
2762
+ this.renderDepth = renderDepth;
2763
+ }
2764
+
2765
+ setVelocityDepthNormalMaterialInScene() {
2766
+ this.visibleMeshes = getVisibleChildren(this._scene);
2767
+
2768
+ for (const c of this.visibleMeshes) {
2769
+ const originalMaterial = c.material;
2770
+ let [cachedOriginalMaterial, velocityDepthNormalMaterial] = this.cachedMaterials.get(c) || [];
2771
+
2772
+ if (originalMaterial !== cachedOriginalMaterial) {
2773
+ var _c$skeleton;
2774
+
2775
+ velocityDepthNormalMaterial = new VelocityDepthNormalMaterial();
2776
+ copyNecessaryProps(originalMaterial, velocityDepthNormalMaterial);
2777
+ c.material = velocityDepthNormalMaterial;
2778
+ if ((_c$skeleton = c.skeleton) != null && _c$skeleton.boneTexture) saveBoneTexture(c);
2779
+ this.cachedMaterials.set(c, [originalMaterial, velocityDepthNormalMaterial]);
2780
+ }
2781
+
2782
+ c.material = velocityDepthNormalMaterial;
2783
+ c.visible = isChildMaterialRenderable(c, originalMaterial);
2784
+ if (this.renderDepth) velocityDepthNormalMaterial.defines.renderDepth = "";
2785
+ const map = originalMaterial.map || originalMaterial.normalMap || originalMaterial.roughnessMap || originalMaterial.metalnessMap;
2786
+ if (map) velocityDepthNormalMaterial.uniforms.uvTransform.value = map.matrix;
2787
+ updateVelocityDepthNormalMaterialBeforeRender(c, this._camera);
2788
+ }
2789
+ }
2790
+
2791
+ unsetVelocityDepthNormalMaterialInScene() {
2792
+ for (const c of this.visibleMeshes) {
2793
+ c.visible = true;
2794
+ updateVelocityDepthNormalMaterialAfterRender(c, this._camera);
2795
+ c.material = this.cachedMaterials.get(c)[0];
2796
+ }
2797
+ }
2798
+
2799
+ setSize(width, height) {
2800
+ var _this$lastDepthTextur;
2801
+
2802
+ this.renderTarget.setSize(width, height);
2803
+ (_this$lastDepthTextur = this.lastDepthTexture) == null ? void 0 : _this$lastDepthTextur.dispose();
2804
+ this.lastDepthTexture = new FramebufferTexture(width, height, RGBAFormat);
2805
+ this.lastDepthTexture.minFilter = NearestFilter;
2806
+ this.lastDepthTexture.magFilter = NearestFilter;
2807
+ }
2808
+
2809
+ dispose() {
2810
+ super.dispose();
2811
+ this.renderTarget.dispose();
2812
+ }
2813
+
2814
+ get texture() {
2815
+ return this.renderTarget.textures.length > 1 ? this.renderTarget.textures[1] : this.renderTarget.texture;
2816
+ }
2817
+
2818
+ get depthTexture() {
2819
+ return this.renderTarget.textures[0];
2820
+ }
2821
+
2822
+ render(renderer) {
2823
+ tmpProjectionMatrix.copy(this._camera.projectionMatrix);
2824
+ tmpProjectionMatrixInverse.copy(this._camera.projectionMatrixInverse);
2825
+ if (this._camera.view) this._camera.view.enabled = false;
2826
+
2827
+ this._camera.updateProjectionMatrix(); // in case a RenderPass is not being used, so we need to update the camera's world matrix manually
2828
+
2829
+
2830
+ this._camera.updateMatrixWorld();
2831
+
2832
+ this.setVelocityDepthNormalMaterialInScene();
2833
+ const {
2834
+ background
2835
+ } = this._scene;
2836
+ this._scene.background = backgroundColor;
2837
+ renderer.setRenderTarget(this.renderTarget);
2838
+ renderer.copyFramebufferToTexture(zeroVec2, this.lastDepthTexture);
2839
+ renderer.render(this._scene, this._camera);
2840
+ this._scene.background = background;
2841
+ this.unsetVelocityDepthNormalMaterialInScene();
2842
+ if (this._camera.view) this._camera.view.enabled = true;
2843
+
2844
+ this._camera.projectionMatrix.copy(tmpProjectionMatrix);
2845
+
2846
+ this._camera.projectionMatrixInverse.copy(tmpProjectionMatrixInverse);
2847
+ }
2848
+
2849
+ }
2850
+
2851
+ class VelocityPass extends VelocityDepthNormalPass {
2852
+ constructor(scene, camera) {
2853
+ super(scene, camera, false);
2854
+ }
2855
+
2856
+ }
2857
+
2858
+ var fragmentShader$2 = "#define GLSLIFY 1\nvarying vec2 vUv;uniform sampler2D inputTexture;uniform sampler2D depthTexture;uniform sampler2D normalTexture;uniform mat4 projectionMatrixInverse;uniform mat4 cameraMatrixWorld;uniform float lumaPhi;uniform float depthPhi;uniform float normalPhi;uniform sampler2D blueNoiseTexture;uniform vec2 blueNoiseRepeat;uniform int index;uniform vec2 resolution;\n#include <common>\n#include <sampleBlueNoise>\nvec3 getWorldPos(float depth,vec2 coord){float z=depth*2.0-1.0;vec4 clipSpacePosition=vec4(coord*2.0-1.0,z,1.0);vec4 viewSpacePosition=projectionMatrixInverse*clipSpacePosition;vec4 worldSpacePosition=cameraMatrixWorld*viewSpacePosition;worldSpacePosition.xyz/=worldSpacePosition.w;return worldSpacePosition.xyz;}\n#define luminance(a) dot(vec3(0.2125, 0.7154, 0.0721), a)\nvec3 getNormal(vec2 uv,vec4 texel){\n#ifdef NORMAL_IN_RGB\nreturn texel.rgb;\n#else\nreturn normalize(textureLod(normalTexture,uv,0.).xyz*2.0-1.0);\n#endif\n}float distToPlane(const vec3 worldPos,const vec3 neighborWorldPos,const vec3 worldNormal){vec3 toCurrent=worldPos-neighborWorldPos;float distToPlane=abs(dot(toCurrent,worldNormal));return distToPlane;}void main(){vec4 depthTexel=textureLod(depthTexture,vUv,0.);if(depthTexel.r==1.0||dot(depthTexel.rgb,depthTexel.rgb)==0.){discard;return;}vec4 texel=textureLod(inputTexture,vUv,0.0);vec3 normal=getNormal(vUv,texel);\n#ifdef NORMAL_IN_RGB\nfloat denoised=texel.a;float center=texel.a;\n#else\nvec3 denoised=texel.rgb;float center=texel.rgb;\n#endif\nfloat depth=depthTexel.x;vec3 worldPos=getWorldPos(depth,vUv);float totalWeight=1.0;vec4 blueNoise=sampleBlueNoise(blueNoiseTexture,0,blueNoiseRepeat,resolution);float angle=blueNoise[index];float s=sin(angle),c=cos(angle);mat2 rotationMatrix=mat2(c,-s,s,c);for(int i=0;i<samples;i++){vec2 offset=rotationMatrix*poissonDisk[i];vec2 neighborUv=vUv+offset;vec4 neighborTexel=textureLod(inputTexture,neighborUv,0.0);vec3 neighborNormal=getNormal(neighborUv,neighborTexel);float neighborColor=neighborTexel.a;float sampleDepth=textureLod(depthTexture,neighborUv,0.0).x;vec3 worldPosSample=getWorldPos(sampleDepth,neighborUv);float tangentPlaneDist=abs(dot(worldPos-worldPosSample,normal));float normalDiff=dot(normal,neighborNormal);float normalSimilarity=pow(max(normalDiff,0.),normalPhi);\n#ifdef NORMAL_IN_RGB\nfloat lumaDiff=abs(neighborColor-center);\n#else\nfloat lumaDiff=abs(luminance(neighborColor)-luminance(center));\n#endif\nfloat lumaSimilarity=max(1.0-lumaDiff/lumaPhi,0.0);float depthDiff=1.-distToPlane(worldPos,worldPosSample,normal);float depthSimilarity=max(depthDiff/depthPhi,0.);float w=lumaSimilarity*depthSimilarity*normalSimilarity;denoised+=w*neighborColor;totalWeight+=w;}if(totalWeight>0.)denoised/=totalWeight;\n#ifdef NORMAL_IN_RGB\ngl_FragColor=vec4(normal,denoised);\n#else\ngl_FragColor=vec4(denoised,1.);\n#endif\n}"; // eslint-disable-line
2859
+
2860
+ function generateDenoiseSamples(numSamples, numRings, r, texelSize) {
2861
+ const angleStep = 2 * Math.PI * numRings / numSamples;
2862
+ const invNumSamples = 1.0 / numSamples;
2863
+ const radiusStep = invNumSamples;
2864
+ const samples = [];
2865
+ let radius = invNumSamples;
2866
+ let angle = 0;
2867
+
2868
+ for (let i = 0; i < numSamples; i++) {
2869
+ const v = new Vector2(Math.cos(angle), Math.sin(angle)).multiplyScalar(Math.pow(radius, 0.75)).multiply(texelSize).multiplyScalar(r);
2870
+ samples.push(v);
2871
+ radius += radiusStep;
2872
+ angle += angleStep;
2873
+ }
2874
+
2875
+ return samples;
2876
+ }
2877
+ function generatePoissonDiskConstant(poissonDisk) {
2878
+ const samples = poissonDisk.length;
2879
+ let glslCode = "const vec2 poissonDisk[samples] = vec2[samples](\n";
2880
+
2881
+ for (let i = 0; i < samples; i++) {
2882
+ const sample = poissonDisk[i];
2883
+ glslCode += ` vec2(${sample.x}, ${sample.y})`;
2884
+
2885
+ if (i < samples - 1) {
2886
+ glslCode += ",";
2887
+ }
2888
+
2889
+ glslCode += "\n";
2890
+ }
2891
+
2892
+ glslCode += ");";
2893
+ return glslCode;
2894
+ }
2895
+
2896
+ const finalFragmentShader$1 = fragmentShader$2.replace("#include <sampleBlueNoise>", sampleBlueNoise);
2897
+ const defaultPoissonBlurOptions = {
2898
+ iterations: 1,
2899
+ radius: 8,
2900
+ rings: 5.625,
2901
+ lumaPhi: 10,
2902
+ depthPhi: 2,
2903
+ normalPhi: 3.25,
2904
+ samples: 16,
2905
+ normalTexture: null
2906
+ };
2907
+ class PoissionDenoisePass extends Pass {
2908
+ constructor(camera, inputTexture, depthTexture, options = defaultPoissonBlurOptions) {
2909
+ super("PoissionBlurPass");
2910
+ this.iterations = defaultPoissonBlurOptions.iterations;
2911
+ this.index = 0;
2912
+ options = { ...defaultPoissonBlurOptions,
2913
+ ...options
2914
+ };
2915
+ this.inputTexture = inputTexture;
2916
+ this.fullscreenMaterial = new ShaderMaterial({
2917
+ fragmentShader: finalFragmentShader$1,
2918
+ vertexShader,
2919
+ uniforms: {
2920
+ depthTexture: {
2921
+ value: null
2922
+ },
2923
+ inputTexture: {
2924
+ value: null
2925
+ },
2926
+ projectionMatrixInverse: {
2927
+ value: new Matrix4()
2928
+ },
2929
+ cameraMatrixWorld: {
2930
+ value: new Matrix4()
2931
+ },
2932
+ lumaPhi: {
2933
+ value: 5.0
2934
+ },
2935
+ depthPhi: {
2936
+ value: 5.0
2937
+ },
2938
+ normalPhi: {
2939
+ value: 5.0
2940
+ },
2941
+ resolution: {
2942
+ value: new Vector2()
2943
+ },
2944
+ blueNoiseTexture: {
2945
+ value: null
2946
+ },
2947
+ index: {
2948
+ value: 0
2949
+ },
2950
+ blueNoiseRepeat: {
2951
+ value: new Vector2()
2952
+ }
2953
+ }
2954
+ });
2955
+ const renderTargetOptions = {
2956
+ type: HalfFloatType,
2957
+ depthBuffer: false
2958
+ };
2959
+ this.renderTargetA = new WebGLRenderTarget(1, 1, renderTargetOptions);
2960
+ this.renderTargetB = new WebGLRenderTarget(1, 1, renderTargetOptions);
2961
+ const {
2962
+ uniforms
2963
+ } = this.fullscreenMaterial;
2964
+ uniforms["inputTexture"].value = this.inputTexture;
2965
+ uniforms["depthTexture"].value = depthTexture;
2966
+ uniforms["projectionMatrixInverse"].value = camera.projectionMatrixInverse;
2967
+ uniforms["cameraMatrixWorld"].value = camera.matrixWorld;
2968
+ uniforms["depthPhi"].value = options.depthPhi;
2969
+ uniforms["normalPhi"].value = options.normalPhi;
2970
+
2971
+ if (options.normalTexture) {
2972
+ uniforms["normalTexture"].value = options.normalTexture;
2973
+ } else {
2974
+ this.fullscreenMaterial.defines.NORMAL_IN_RGB = "";
2975
+ } // these properties need the shader to be recompiled
2976
+
2977
+
2978
+ for (const prop of ["radius", "rings", "samples"]) {
2979
+ Object.defineProperty(this, prop, {
2980
+ get: () => options[prop],
2981
+ set: value => {
2982
+ options[prop] = value;
2983
+ this.setSize(this.renderTargetA.width, this.renderTargetA.height);
2984
+ }
2985
+ });
2986
+ }
2987
+
2988
+ new TextureLoader().load(blueNoiseImage, blueNoiseTexture => {
2989
+ blueNoiseTexture.minFilter = NearestFilter;
2990
+ blueNoiseTexture.magFilter = NearestFilter;
2991
+ blueNoiseTexture.wrapS = RepeatWrapping;
2992
+ blueNoiseTexture.wrapT = RepeatWrapping;
2993
+ blueNoiseTexture.colorSpace = NoColorSpace;
2994
+ this.fullscreenMaterial.uniforms.blueNoiseTexture.value = blueNoiseTexture;
2995
+ });
2996
+ }
2997
+
2998
+ setSize(width, height) {
2999
+ this.renderTargetA.setSize(width, height);
3000
+ this.renderTargetB.setSize(width, height);
3001
+ this.fullscreenMaterial.uniforms.resolution.value.set(width, height);
3002
+ const poissonDisk = generateDenoiseSamples(this.samples, this.rings, this.radius, new Vector2(1 / width, 1 / height));
3003
+ const sampleDefine = `const int samples = ${this.samples};\n`;
3004
+ const poissonDiskConstant = generatePoissonDiskConstant(poissonDisk);
3005
+ this.fullscreenMaterial.fragmentShader = sampleDefine + poissonDiskConstant + "\n" + finalFragmentShader$1;
3006
+ this.fullscreenMaterial.needsUpdate = true;
3007
+ }
3008
+
3009
+ get texture() {
3010
+ return this.renderTargetB.texture;
3011
+ }
3012
+
3013
+ render(renderer) {
3014
+ this.fullscreenMaterial.uniforms.index.value = 0;
3015
+ const noiseTexture = this.fullscreenMaterial.uniforms.blueNoiseTexture.value;
3016
+
3017
+ if (noiseTexture) {
3018
+ const {
3019
+ width,
3020
+ height
3021
+ } = noiseTexture.source.data;
3022
+ this.fullscreenMaterial.uniforms.blueNoiseRepeat.value.set(this.renderTargetA.width / width, this.renderTargetA.height / height);
3023
+ }
3024
+
3025
+ for (let i = 0; i < 2 * this.iterations; i++) {
3026
+ const horizontal = i % 2 === 0;
3027
+ const inputRenderTarget = horizontal ? this.renderTargetB : this.renderTargetA;
3028
+ this.fullscreenMaterial.uniforms["inputTexture"].value = i === 0 ? this.inputTexture : inputRenderTarget.texture;
3029
+ const renderTarget = horizontal ? this.renderTargetA : this.renderTargetB;
3030
+ renderer.setRenderTarget(renderTarget);
3031
+ renderer.render(this.scene, this.camera);
3032
+ this.fullscreenMaterial.uniforms.index.value = (this.fullscreenMaterial.uniforms.index.value + 1) % 4;
3033
+ }
3034
+ }
3035
+
3036
+ }
3037
+ PoissionDenoisePass.DefaultOptions = defaultPoissonBlurOptions;
3038
+
3039
+ class AOPass extends Pass {
3040
+ constructor(camera, scene, fragmentShader) {
3041
+ super();
3042
+ this._camera = camera;
3043
+ this._scene = scene;
3044
+ this.renderTarget = new WebGLRenderTarget(1, 1, {
3045
+ type: HalfFloatType,
3046
+ depthBuffer: false
3047
+ });
3048
+ const finalFragmentShader = fragmentShader.replace("#include <sampleBlueNoise>", sampleBlueNoise);
3049
+ this.fullscreenMaterial = new ShaderMaterial({
3050
+ fragmentShader: finalFragmentShader,
3051
+ vertexShader,
3052
+ uniforms: {
3053
+ depthTexture: {
3054
+ value: null
3055
+ },
3056
+ normalTexture: {
3057
+ value: null
3058
+ },
3059
+ cameraNear: {
3060
+ value: 0
3061
+ },
3062
+ cameraFar: {
3063
+ value: 0
3064
+ },
3065
+ viewMatrix: {
3066
+ value: this._camera.matrixWorldInverse
3067
+ },
3068
+ projectionViewMatrix: {
3069
+ value: new Matrix4()
3070
+ },
3071
+ projectionMatrixInverse: {
3072
+ value: this._camera.projectionMatrixInverse
3073
+ },
3074
+ cameraMatrixWorld: {
3075
+ value: this._camera.matrixWorld
3076
+ },
3077
+ texSize: {
3078
+ value: new Vector2()
3079
+ },
3080
+ blueNoiseTexture: {
3081
+ value: null
3082
+ },
3083
+ blueNoiseRepeat: {
3084
+ value: new Vector2()
3085
+ },
3086
+ aoDistance: {
3087
+ value: 0
3088
+ },
3089
+ distancePower: {
3090
+ value: 0
3091
+ },
3092
+ bias: {
3093
+ value: 0
3094
+ },
3095
+ thickness: {
3096
+ value: 0
3097
+ },
3098
+ power: {
3099
+ value: 0
3100
+ },
3101
+ frame: {
3102
+ value: 0
3103
+ }
3104
+ },
3105
+ blending: NoBlending,
3106
+ depthWrite: false,
3107
+ depthTest: false,
3108
+ toneMapped: false
3109
+ });
3110
+ new TextureLoader().load(blueNoiseImage, blueNoiseTexture => {
3111
+ blueNoiseTexture.minFilter = NearestFilter;
3112
+ blueNoiseTexture.magFilter = NearestFilter;
3113
+ blueNoiseTexture.wrapS = RepeatWrapping;
3114
+ blueNoiseTexture.wrapT = RepeatWrapping;
3115
+ blueNoiseTexture.colorSpace = NoColorSpace;
3116
+ this.fullscreenMaterial.uniforms.blueNoiseTexture.value = blueNoiseTexture;
3117
+ });
3118
+ }
3119
+
3120
+ get texture() {
3121
+ return this.renderTarget.texture;
3122
+ }
3123
+
3124
+ setSize(width, height) {
3125
+ this.renderTarget.setSize(width, height);
3126
+ this.fullscreenMaterial.uniforms.texSize.value.set(this.renderTarget.width, this.renderTarget.height);
3127
+ }
3128
+
3129
+ render(renderer) {
3130
+ const spp = +this.fullscreenMaterial.defines.spp;
3131
+ this.fullscreenMaterial.uniforms.frame.value = (this.fullscreenMaterial.uniforms.frame.value + spp) % 65536;
3132
+ this.fullscreenMaterial.uniforms.cameraNear.value = this._camera.near;
3133
+ this.fullscreenMaterial.uniforms.cameraFar.value = this._camera.far;
3134
+ this.fullscreenMaterial.uniforms.projectionViewMatrix.value.multiplyMatrices(this._camera.projectionMatrix, this._camera.matrixWorldInverse);
3135
+ const noiseTexture = this.fullscreenMaterial.uniforms.blueNoiseTexture.value;
3136
+
3137
+ if (noiseTexture) {
3138
+ const {
3139
+ width,
3140
+ height
3141
+ } = noiseTexture.source.data;
3142
+ this.fullscreenMaterial.uniforms.blueNoiseRepeat.value.set(this.renderTarget.width / width, this.renderTarget.height / height);
3143
+ }
3144
+
3145
+ renderer.setRenderTarget(this.renderTarget);
3146
+ renderer.render(this.scene, this.camera);
3147
+ }
3148
+
3149
+ }
3150
+
3151
+ var hbao_utils = "#define GLSLIFY 1\n#include <sampleBlueNoise>\nuniform sampler2D normalTexture;uniform float cameraNear;uniform float cameraFar;uniform mat4 projectionMatrixInverse;uniform mat4 cameraMatrixWorld;float getViewZ(const float depth){\n#ifdef PERSPECTIVE_CAMERA\nreturn perspectiveDepthToViewZ(depth,cameraNear,cameraFar);\n#else\nreturn orthographicDepthToViewZ(depth,cameraNear,cameraFar);\n#endif\n}vec3 getWorldPos(const float depth,const vec2 coord){float z=depth*2.0-1.0;vec4 clipSpacePosition=vec4(coord*2.0-1.0,z,1.0);vec4 viewSpacePosition=projectionMatrixInverse*clipSpacePosition;vec4 worldSpacePosition=cameraMatrixWorld*viewSpacePosition;worldSpacePosition.xyz/=worldSpacePosition.w;return worldSpacePosition.xyz;}vec3 slerp(const vec3 a,const vec3 b,const float t){float cosAngle=dot(a,b);float angle=acos(cosAngle);if(abs(angle)<0.001){return mix(a,b,t);}float sinAngle=sin(angle);float t1=sin((1.0-t)*angle)/sinAngle;float t2=sin(t*angle)/sinAngle;return(a*t1)+(b*t2);}vec3 computeWorldNormal(){vec2 size=vec2(textureSize(depthTexture,0));ivec2 p=ivec2(vUv*size);float c0=texelFetch(depthTexture,p,0).x;float l2=texelFetch(depthTexture,p-ivec2(2,0),0).x;float l1=texelFetch(depthTexture,p-ivec2(1,0),0).x;float r1=texelFetch(depthTexture,p+ivec2(1,0),0).x;float r2=texelFetch(depthTexture,p+ivec2(2,0),0).x;float b2=texelFetch(depthTexture,p-ivec2(0,2),0).x;float b1=texelFetch(depthTexture,p-ivec2(0,1),0).x;float t1=texelFetch(depthTexture,p+ivec2(0,1),0).x;float t2=texelFetch(depthTexture,p+ivec2(0,2),0).x;float dl=abs((2.0*l1-l2)-c0);float dr=abs((2.0*r1-r2)-c0);float db=abs((2.0*b1-b2)-c0);float dt=abs((2.0*t1-t2)-c0);vec3 ce=getWorldPos(c0,vUv).xyz;vec3 dpdx=(dl<dr)? ce-getWorldPos(l1,(vUv-vec2(1.0/size.x,0.0))).xyz:-ce+getWorldPos(r1,(vUv+vec2(1.0/size.x,0.0))).xyz;vec3 dpdy=(db<dt)? ce-getWorldPos(b1,(vUv-vec2(0.0,1.0/size.y))).xyz:-ce+getWorldPos(t1,(vUv+vec2(0.0,1.0/size.y))).xyz;return normalize(cross(dpdx,dpdy));}vec3 getWorldNormal(const vec2 uv){\n#ifdef useNormalTexture\nvec3 worldNormal=unpackRGBToNormal(textureLod(normalTexture,uv,0.).rgb);worldNormal=(vec4(worldNormal,1.)*viewMatrix).xyz;return normalize(worldNormal);\n#else\nreturn computeWorldNormal();\n#endif\n}\n#define PI 3.14159265358979323846264338327950288\nvec3 cosineSampleHemisphere(const vec3 n,const vec2 u){float r=sqrt(u.x);float theta=2.0*PI*u.y;vec3 b=normalize(cross(n,vec3(0.0,1.0,1.0)));vec3 t=cross(b,n);return normalize(r*sin(theta)*b+sqrt(1.0-u.x)*n+r*cos(theta)*t);}"; // eslint-disable-line
3152
+
3153
+ var fragmentShader$1 = "#define GLSLIFY 1\nvarying vec2 vUv;uniform sampler2D depthTexture;uniform mat4 projectionViewMatrix;uniform int frame;uniform sampler2D blueNoiseTexture;uniform vec2 blueNoiseRepeat;uniform vec2 texSize;uniform float aoDistance;uniform float distancePower;uniform float bias;uniform float thickness;\n#include <packing>\n#include <hbao_utils>\nfloat getOcclusion(const vec3 cameraPosition,const vec3 worldPos,const vec3 worldNormal,const float depth,const int seed,inout float totalWeight){vec4 blueNoise=sampleBlueNoise(blueNoiseTexture,seed,blueNoiseRepeat,texSize);vec3 sampleWorldDir=cosineSampleHemisphere(worldNormal,blueNoise.rg);vec3 sampleWorldPos=worldPos+aoDistance*pow(blueNoise.b,distancePower+1.0)*sampleWorldDir;vec4 sampleUv=projectionViewMatrix*vec4(sampleWorldPos,1.);sampleUv.xy/=sampleUv.w;sampleUv.xy=sampleUv.xy*0.5+0.5;float sampleDepth=textureLod(depthTexture,sampleUv.xy,0.0).r;float deltaDepth=depth-sampleDepth;float d=distance(sampleWorldPos,cameraPosition);deltaDepth*=0.001*d*d;float th=thickness*0.01;float theta=dot(worldNormal,sampleWorldDir);totalWeight+=theta;if(deltaDepth<th){float horizon=sampleDepth+deltaDepth*bias*1000.;float occlusion=max(0.0,horizon-depth)*theta;float m=max(0.,1.-deltaDepth/th);occlusion=10.*occlusion*m/d;occlusion=sqrt(occlusion);return occlusion;}return 0.;}void main(){float depth=textureLod(depthTexture,vUv,0.0).r;if(depth==1.0){discard;return;}vec4 cameraPosition=cameraMatrixWorld*vec4(0.0,0.0,0.0,1.0);vec3 worldPos=getWorldPos(depth,vUv);vec3 worldNormal=getWorldNormal(vUv);float ao=0.0,totalWeight=0.0;for(int i=0;i<spp;i++){int seed=i;\n#ifdef animatedNoise\nseed+=frame;\n#endif\nfloat occlusion=getOcclusion(cameraPosition.xyz,worldPos,worldNormal,depth,seed,totalWeight);ao+=occlusion;}if(totalWeight>0.)ao/=totalWeight;ao=clamp(1.-ao,0.,1.);gl_FragColor=vec4(worldNormal,ao);}"; // eslint-disable-line
3154
+
3155
+ const finalFragmentShader = fragmentShader$1.replace("#include <hbao_utils>", hbao_utils);
3156
+
3157
+ class HBAOPass extends AOPass {
3158
+ constructor(camera, scene) {
3159
+ super(camera, scene, finalFragmentShader);
3160
+ }
3161
+
3162
+ }
3163
+
3164
+ var ao_compose = "#define GLSLIFY 1\nuniform sampler2D inputTexture;uniform sampler2D depthTexture;uniform float power;uniform vec3 color;void mainImage(const in vec4 inputColor,const in vec2 uv,out vec4 outputColor){float unpackedDepth=textureLod(depthTexture,uv,0.).r;float ao=unpackedDepth>0.9999 ? 1.0 : textureLod(inputTexture,uv,0.0).a;ao=pow(ao,power);vec3 aoColor=mix(color,vec3(1.),ao);aoColor*=inputColor.rgb;outputColor=vec4(aoColor,inputColor.a);}"; // eslint-disable-line
3165
+
3166
+ const defaultAOOptions = {
3167
+ resolutionScale: 1,
3168
+ spp: 8,
3169
+ distance: 2,
3170
+ distancePower: 1,
3171
+ power: 2,
3172
+ bias: 40,
3173
+ thickness: 0.075,
3174
+ color: new Color("black"),
3175
+ useNormalPass: false,
3176
+ velocityDepthNormalPass: null,
3177
+ normalTexture: null,
3178
+ ...PoissionDenoisePass.DefaultOptions
3179
+ };
3180
+
3181
+ class AOEffect extends Effect {
3182
+ constructor(composer, camera, scene, aoPass, options = defaultAOOptions) {
3183
+ super("AOEffect", ao_compose, {
3184
+ type: "FinalAOMaterial",
3185
+ uniforms: new Map([["inputTexture", new Uniform(null)], ["depthTexture", new Uniform(null)], ["power", new Uniform(0)], ["color", new Uniform(new Color("black"))]])
3186
+ });
3187
+ this.lastSize = {
3188
+ width: 0,
3189
+ height: 0,
3190
+ resolutionScale: 0
3191
+ };
3192
+ this.composer = composer;
3193
+ this.aoPass = aoPass;
3194
+ options = { ...defaultAOOptions,
3195
+ ...options
3196
+ }; // set up depth texture
3197
+
3198
+ if (!composer.depthTexture) composer.createDepthTexture();
3199
+ this.aoPass.fullscreenMaterial.uniforms.depthTexture.value = composer.depthTexture;
3200
+ this.uniforms.get("depthTexture").value = composer.depthTexture; // set up optional normal texture
3201
+
3202
+ if (options.useNormalPass || options.normalTexture) {
3203
+ var _options$normalTextur;
3204
+
3205
+ if (options.useNormalPass) this.normalPass = new NormalPass(scene, camera);
3206
+ const normalTexture = (_options$normalTextur = options.normalTexture) !== null && _options$normalTextur !== void 0 ? _options$normalTextur : this.normalPass.texture;
3207
+ this.aoPass.fullscreenMaterial.uniforms.normalTexture.value = normalTexture;
3208
+ this.aoPass.fullscreenMaterial.defines.useNormalTexture = "";
3209
+ }
3210
+
3211
+ this.poissionDenoisePass = new PoissionDenoisePass(camera, this.aoPass.texture, composer.depthTexture);
3212
+ this.makeOptionsReactive(options);
3213
+ }
3214
+
3215
+ makeOptionsReactive(options) {
3216
+ for (const key of Object.keys(options)) {
3217
+ Object.defineProperty(this, key, {
3218
+ get() {
3219
+ return options[key];
3220
+ },
3221
+
3222
+ set(value) {
3223
+ if (value === null || value === undefined) return;
3224
+ options[key] = value;
3225
+
3226
+ switch (key) {
3227
+ case "spp":
3228
+ this.aoPass.fullscreenMaterial.defines.spp = value.toFixed(0);
3229
+ this.aoPass.fullscreenMaterial.needsUpdate = true;
3230
+ break;
3231
+
3232
+ case "distance":
3233
+ this.aoPass.fullscreenMaterial.uniforms.aoDistance.value = value;
3234
+ break;
3235
+
3236
+ case "resolutionScale":
3237
+ this.setSize(this.lastSize.width, this.lastSize.height);
3238
+ break;
3239
+
3240
+ case "power":
3241
+ this.uniforms.get("power").value = value;
3242
+ break;
3243
+
3244
+ case "color":
3245
+ this.uniforms.get("color").value.copy(new Color(value));
3246
+ break;
3247
+ // denoiser
3248
+
3249
+ case "iterations":
3250
+ case "radius":
3251
+ case "rings":
3252
+ case "samples":
3253
+ this.poissionDenoisePass[key] = value;
3254
+ break;
3255
+
3256
+ case "lumaPhi":
3257
+ case "depthPhi":
3258
+ case "normalPhi":
3259
+ this.poissionDenoisePass.fullscreenMaterial.uniforms[key].value = Math.max(value, 0.0001);
3260
+ break;
3261
+
3262
+ default:
3263
+ if (key in this.aoPass.fullscreenMaterial.uniforms) {
3264
+ this.aoPass.fullscreenMaterial.uniforms[key].value = value;
3265
+ }
3266
+
3267
+ }
3268
+ },
3269
+
3270
+ configurable: true
3271
+ }); // apply all uniforms and defines
3272
+
3273
+ this[key] = options[key];
3274
+ }
3275
+ }
3276
+
3277
+ setSize(width, height) {
3278
+ var _this$normalPass;
3279
+
3280
+ if (width === undefined || height === undefined) return;
3281
+
3282
+ if (width === this.lastSize.width && height === this.lastSize.height && this.resolutionScale === this.lastSize.resolutionScale) {
3283
+ return;
3284
+ }
3285
+
3286
+ (_this$normalPass = this.normalPass) == null ? void 0 : _this$normalPass.setSize(width, height);
3287
+ this.aoPass.setSize(width * this.resolutionScale, height * this.resolutionScale);
3288
+ this.poissionDenoisePass.setSize(width, height);
3289
+ this.lastSize = {
3290
+ width,
3291
+ height,
3292
+ resolutionScale: this.resolutionScale
3293
+ };
3294
+ }
3295
+
3296
+ update(renderer) {
3297
+ var _this$normalPass2;
3298
+
3299
+ // check if TRAA is being used so we can animate the noise
3300
+ const hasTRAA = this.composer.passes.some(pass => {
3301
+ var _pass$effects;
3302
+
3303
+ return pass.enabled && !pass.skipRendering && ((_pass$effects = pass.effects) == null ? void 0 : _pass$effects.some(effect => effect instanceof TRAAEffect));
3304
+ }); // set animated noise depending on TRAA
3305
+
3306
+ if (hasTRAA && !("animatedNoise" in this.aoPass.fullscreenMaterial.defines)) {
3307
+ this.aoPass.fullscreenMaterial.defines.animatedNoise = "";
3308
+ this.aoPass.fullscreenMaterial.needsUpdate = true;
3309
+ } else if (!hasTRAA && "animatedNoise" in this.aoPass.fullscreenMaterial.defines) {
3310
+ delete this.aoPass.fullscreenMaterial.defines.animatedNoise;
3311
+ this.aoPass.fullscreenMaterial.needsUpdate = true;
3312
+ } // set input texture
3313
+
3314
+
3315
+ if (this.iterations > 0) {
3316
+ this.uniforms.get("inputTexture").value = this.poissionDenoisePass.texture;
3317
+ } else {
3318
+ this.uniforms.get("inputTexture").value = this.aoPass.texture;
3319
+ }
3320
+
3321
+ (_this$normalPass2 = this.normalPass) == null ? void 0 : _this$normalPass2.render(renderer);
3322
+ this.aoPass.render(renderer);
3323
+ this.poissionDenoisePass.render(renderer);
3324
+ }
3325
+
3326
+ }
3327
+
3328
+ AOEffect.DefaultOptions = defaultAOOptions;
3329
+
3330
+ class HBAOEffect extends AOEffect {
3331
+ constructor(composer, camera, scene, options = AOEffect.DefaultOptions) {
3332
+ const hbaoPass = new HBAOPass(camera, scene);
3333
+ options = { ...AOEffect.DefaultOptions,
3334
+ ...HBAOEffect.DefaultOptions,
3335
+ ...options
3336
+ };
3337
+ super(composer, camera, scene, hbaoPass, options);
3338
+ this.lastSize = {
3339
+ width: 0,
3340
+ height: 0,
3341
+ resolutionScale: 0
3342
+ };
3343
+ options = { ...AOEffect.DefaultOptions,
3344
+ ...options
3345
+ };
3346
+ }
3347
+
3348
+ }
3349
+
3350
+ var fragmentShader = "#define GLSLIFY 1\nvarying vec2 vUv;uniform sampler2D depthTexture;uniform sampler2D normalTexture;uniform mat4 projectionViewMatrix;uniform mat4 cameraMatrixWorld;uniform sampler2D blueNoiseTexture;uniform vec2 blueNoiseRepeat;uniform vec2 texSize;uniform mat4 projectionMatrixInverse;uniform float aoDistance;uniform float distancePower;uniform float cameraNear;uniform float cameraFar;uniform int frame;uniform vec3[spp]samples;uniform float[spp]samplesR;\n#include <common>\n#include <packing>\n#include <sampleBlueNoise>\nvec3 getWorldPos(const float depth,const vec2 coord){float z=depth*2.0-1.0;vec4 clipSpacePosition=vec4(coord*2.0-1.0,z,1.0);vec4 viewSpacePosition=projectionMatrixInverse*clipSpacePosition;vec4 worldSpacePosition=cameraMatrixWorld*viewSpacePosition;worldSpacePosition.xyz/=worldSpacePosition.w;return worldSpacePosition.xyz;}vec3 computeNormal(vec3 worldPos,vec2 vUv){vec2 size=vec2(textureSize(depthTexture,0));ivec2 p=ivec2(vUv*size);float c0=texelFetch(depthTexture,p,0).x;float l2=texelFetch(depthTexture,p-ivec2(2,0),0).x;float l1=texelFetch(depthTexture,p-ivec2(1,0),0).x;float r1=texelFetch(depthTexture,p+ivec2(1,0),0).x;float r2=texelFetch(depthTexture,p+ivec2(2,0),0).x;float b2=texelFetch(depthTexture,p-ivec2(0,2),0).x;float b1=texelFetch(depthTexture,p-ivec2(0,1),0).x;float t1=texelFetch(depthTexture,p+ivec2(0,1),0).x;float t2=texelFetch(depthTexture,p+ivec2(0,2),0).x;float dl=abs((2.0*l1-l2)-c0);float dr=abs((2.0*r1-r2)-c0);float db=abs((2.0*b1-b2)-c0);float dt=abs((2.0*t1-t2)-c0);vec3 ce=getWorldPos(c0,vUv).xyz;vec3 dpdx=(dl<dr)? ce-getWorldPos(l1,(vUv-vec2(1.0/size.x,0.0))).xyz:-ce+getWorldPos(r1,(vUv+vec2(1.0/size.x,0.0))).xyz;vec3 dpdy=(db<dt)? ce-getWorldPos(b1,(vUv-vec2(0.0,1.0/size.y))).xyz:-ce+getWorldPos(t1,(vUv+vec2(0.0,1.0/size.y))).xyz;return normalize(cross(dpdx,dpdy));}highp float linearize_depth(highp float d,highp float zNear,highp float zFar){highp float z_n=2.0*d-1.0;return 2.0*zNear*zFar/(zFar+zNear-z_n*(zFar-zNear));}void main(){float depth=textureLod(depthTexture,vUv,0.).x;if(depth==1.0){discard;return;}vec3 worldPos=getWorldPos(depth,vUv);vec3 normal=computeNormal(worldPos,vUv);\n#ifdef animatedNoise\nint seed=frame;\n#else\nint seed=0;\n#endif\nvec4 noise=sampleBlueNoise(blueNoiseTexture,seed,blueNoiseRepeat,texSize);vec3 randomVec=normalize(noise.rgb*2.0-1.0);vec3 tangent=normalize(randomVec-normal*dot(randomVec,normal));vec3 bitangent=cross(normal,tangent);mat3 tbn=mat3(tangent,bitangent,normal);float occluded=0.0;float totalWeight=0.0;vec3 samplePos;float sppF=float(spp);for(float i=0.0;i<sppF;i++){vec3 sampleDirection=tbn*samples[int(i)];if(dot(sampleDirection,normal)<0.0)sampleDirection*=-1.0;float moveAmt=samplesR[int(mod(i+noise.a*sppF,sppF))];samplePos=worldPos+aoDistance*moveAmt*sampleDirection;vec4 offset=projectionViewMatrix*vec4(samplePos,1.0);offset.xyz/=offset.w;offset.xyz=offset.xyz*0.5+0.5;float sampleDepth=textureLod(depthTexture,offset.xy,0.0).x;float distSample=linearize_depth(sampleDepth,cameraNear,cameraFar);float distWorld=linearize_depth(offset.z,cameraNear,cameraFar);float rangeCheck=smoothstep(0.0,1.0,aoDistance/(aoDistance*abs(distSample-distWorld)));rangeCheck=pow(rangeCheck,distancePower);float weight=dot(sampleDirection,normal);occluded+=rangeCheck*weight*(distSample<distWorld ? 1.0 : 0.0);totalWeight+=weight;}float occ=clamp(1.0-occluded/totalWeight,0.0,1.0);gl_FragColor=vec4(normal,occ);}"; // eslint-disable-line
3351
+
3352
+ class SSAOPass extends AOPass {
3353
+ constructor(camera, scene) {
3354
+ super(camera, scene, fragmentShader);
3355
+ }
3356
+
3357
+ }
3358
+
3359
+ function getPointsOnSphere(n) {
3360
+ const points = [];
3361
+ const inc = Math.PI * (3 - Math.sqrt(5));
3362
+ const off = 2 / n;
3363
+
3364
+ for (let k = 0; k < n; k++) {
3365
+ const y = k * off - 1 + off / 2;
3366
+ const r = Math.sqrt(1 - y * y);
3367
+ const phi = k * inc;
3368
+ points.push(new Vector3(Math.cos(phi) * r, y, Math.sin(phi) * r));
3369
+ }
3370
+
3371
+ return points;
3372
+ }
3373
+
3374
+ class SSAOEffect extends AOEffect {
3375
+ constructor(composer, camera, scene, options = {}) {
3376
+ SSAOEffect.DefaultOptions = { ...AOEffect.DefaultOptions,
3377
+ ...{
3378
+ spp: 16,
3379
+ distance: 1,
3380
+ distancePower: 0.25,
3381
+ power: 2
3382
+ }
3383
+ };
3384
+ options = { ...SSAOEffect.DefaultOptions,
3385
+ ...options
3386
+ };
3387
+ const aoPass = new SSAOPass(camera, scene);
3388
+ super(composer, camera, scene, aoPass, options);
3389
+ }
3390
+
3391
+ makeOptionsReactive(options) {
3392
+ super.makeOptionsReactive(options);
3393
+
3394
+ for (const key of ["spp"]) {
3395
+ Object.defineProperty(this, key, {
3396
+ get() {
3397
+ return options[key];
3398
+ },
3399
+
3400
+ set(value) {
3401
+ if (value === null || value === undefined) return;
3402
+ options[key] = value;
3403
+
3404
+ switch (key) {
3405
+ case "spp":
3406
+ this.aoPass.fullscreenMaterial.defines.spp = value.toFixed(0);
3407
+ const samples = getPointsOnSphere(value);
3408
+ const samplesR = [];
3409
+
3410
+ for (let i = 0; i < value; i++) {
3411
+ samplesR.push((i + 1) / value);
3412
+ }
3413
+
3414
+ this.aoPass.fullscreenMaterial.uniforms.samples = {
3415
+ value: samples
3416
+ };
3417
+ this.aoPass.fullscreenMaterial.uniforms.samplesR = {
3418
+ value: samplesR
3419
+ };
3420
+ this.aoPass.fullscreenMaterial.needsUpdate = true;
3421
+ break;
3422
+ }
3423
+ },
3424
+
3425
+ configurable: true
3426
+ });
3427
+ }
3428
+
3429
+ this.spp = options["spp"];
3430
+ }
3431
+
3432
+ }
3433
+
3434
+ export { HBAOEffect, MotionBlurEffect, PoissionDenoisePass, SSAOEffect, SSDGIEffect, SSGIEffect, SSREffect, SVGF, TRAAEffect, TemporalReprojectPass, VelocityDepthNormalPass, VelocityPass };