@takram/three-clouds 0.1.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/CHANGELOG.md +5 -0
  2. package/README.md +1130 -0
  3. package/assets/local_weather.png +0 -0
  4. package/assets/shape.bin +1 -0
  5. package/assets/shape_detail.bin +1 -0
  6. package/assets/turbulence.png +0 -0
  7. package/build/index.cjs +583 -0
  8. package/build/index.cjs.map +1 -0
  9. package/build/index.js +728 -0
  10. package/build/index.js.map +1 -0
  11. package/build/r3f.cjs +2 -0
  12. package/build/r3f.cjs.map +1 -0
  13. package/build/r3f.js +205 -0
  14. package/build/r3f.js.map +1 -0
  15. package/build/shared.cjs +2189 -0
  16. package/build/shared.cjs.map +1 -0
  17. package/build/shared.js +3825 -0
  18. package/build/shared.js.map +1 -0
  19. package/package.json +77 -0
  20. package/src/CascadedShadowMaps.ts +288 -0
  21. package/src/CloudLayer.ts +85 -0
  22. package/src/CloudLayers.test.ts +61 -0
  23. package/src/CloudLayers.ts +181 -0
  24. package/src/CloudShape.ts +22 -0
  25. package/src/CloudShapeDetail.ts +22 -0
  26. package/src/CloudsEffect.ts +810 -0
  27. package/src/CloudsMaterial.ts +467 -0
  28. package/src/CloudsPass.ts +285 -0
  29. package/src/CloudsResolveMaterial.ts +108 -0
  30. package/src/DensityProfile.ts +38 -0
  31. package/src/LocalWeather.ts +21 -0
  32. package/src/PassBase.ts +28 -0
  33. package/src/Procedural3DTexture.ts +94 -0
  34. package/src/ProceduralTexture.ts +94 -0
  35. package/src/ShaderArrayPass.ts +32 -0
  36. package/src/ShadowMaterial.ts +141 -0
  37. package/src/ShadowPass.ts +185 -0
  38. package/src/ShadowResolveMaterial.ts +72 -0
  39. package/src/Turbulence.ts +21 -0
  40. package/src/bayer.ts +23 -0
  41. package/src/constants.ts +8 -0
  42. package/src/helpers/FrustumCorners.ts +138 -0
  43. package/src/helpers/setArrayRenderTargetLayers.ts +32 -0
  44. package/src/helpers/splitFrustum.ts +59 -0
  45. package/src/index.ts +14 -0
  46. package/src/qualityPresets.ts +117 -0
  47. package/src/r3f/CloudLayer.tsx +95 -0
  48. package/src/r3f/CloudLayers.tsx +54 -0
  49. package/src/r3f/Clouds.tsx +278 -0
  50. package/src/r3f/index.ts +2 -0
  51. package/src/shaders/catmullRomSampling.glsl +113 -0
  52. package/src/shaders/cloudShape.frag +78 -0
  53. package/src/shaders/cloudShapeDetail.frag +56 -0
  54. package/src/shaders/clouds.frag +996 -0
  55. package/src/shaders/clouds.glsl +190 -0
  56. package/src/shaders/clouds.vert +69 -0
  57. package/src/shaders/cloudsEffect.frag +11 -0
  58. package/src/shaders/cloudsResolve.frag +202 -0
  59. package/src/shaders/cloudsResolve.vert +10 -0
  60. package/src/shaders/localWeather.frag +83 -0
  61. package/src/shaders/parameters.glsl +64 -0
  62. package/src/shaders/perlin.glsl +211 -0
  63. package/src/shaders/shadow.frag +197 -0
  64. package/src/shaders/shadow.vert +16 -0
  65. package/src/shaders/shadowResolve.frag +76 -0
  66. package/src/shaders/shadowResolve.vert +10 -0
  67. package/src/shaders/structuredSampling.glsl +101 -0
  68. package/src/shaders/tileableNoise.glsl +88 -0
  69. package/src/shaders/turbulence.frag +51 -0
  70. package/src/shaders/types.glsl +18 -0
  71. package/src/shaders/varianceClipping.glsl +114 -0
  72. package/src/uniforms.ts +218 -0
  73. package/types/CascadedShadowMaps.d.ts +52 -0
  74. package/types/CloudLayer.d.ts +26 -0
  75. package/types/CloudLayers.d.ts +21 -0
  76. package/types/CloudShape.d.ts +5 -0
  77. package/types/CloudShapeDetail.d.ts +5 -0
  78. package/types/CloudsEffect.d.ts +170 -0
  79. package/types/CloudsMaterial.d.ts +86 -0
  80. package/types/CloudsPass.d.ts +44 -0
  81. package/types/CloudsResolveMaterial.d.ts +30 -0
  82. package/types/DensityProfile.d.ts +12 -0
  83. package/types/LocalWeather.d.ts +5 -0
  84. package/types/PassBase.d.ts +14 -0
  85. package/types/Procedural3DTexture.d.ts +20 -0
  86. package/types/ProceduralTexture.d.ts +24 -0
  87. package/types/ShaderArrayPass.d.ts +7 -0
  88. package/types/ShadowMaterial.d.ts +34 -0
  89. package/types/ShadowPass.d.ts +34 -0
  90. package/types/ShadowResolveMaterial.d.ts +20 -0
  91. package/types/Turbulence.d.ts +5 -0
  92. package/types/bayer.d.ts +4 -0
  93. package/types/constants.d.ts +6 -0
  94. package/types/helpers/FrustumCorners.d.ts +18 -0
  95. package/types/helpers/setArrayRenderTargetLayers.d.ts +3 -0
  96. package/types/helpers/splitFrustum.d.ts +9 -0
  97. package/types/index.d.ts +13 -0
  98. package/types/qualityPresets.d.ts +46 -0
  99. package/types/r3f/CloudLayer.d.ts +7 -0
  100. package/types/r3f/CloudLayers.d.ts +15 -0
  101. package/types/r3f/Clouds.d.ts +16 -0
  102. package/types/r3f/index.d.ts +2 -0
  103. package/types/uniforms.d.ts +66 -0
@@ -0,0 +1,996 @@
1
+ precision highp float;
2
+ precision highp sampler3D;
3
+ precision highp sampler2DArray;
4
+
5
+ #include <common>
6
+ #include <packing>
7
+
8
+ #include "core/depth"
9
+ #include "core/math"
10
+ #include "core/turbo"
11
+ #include "core/generators"
12
+ #include "core/raySphereIntersection"
13
+ #include "core/cascadedShadowMaps"
14
+ #include "core/interleavedGradientNoise"
15
+ #include "core/vogelDisk"
16
+ #include "atmosphere/parameters"
17
+ #include "atmosphere/functions"
18
+ #include "types"
19
+ #include "parameters"
20
+ #include "clouds"
21
+
22
+ #if !defined(RECIPROCAL_PI4)
23
+ #define RECIPROCAL_PI4 (0.07957747154594767)
24
+ #endif // !defined(RECIPROCAL_PI4)
25
+
26
+ uniform sampler2D depthBuffer;
27
+ uniform mat4 viewMatrix;
28
+ uniform mat4 reprojectionMatrix;
29
+ uniform float cameraNear;
30
+ uniform float cameraFar;
31
+ uniform float cameraHeight;
32
+ uniform vec2 temporalJitter;
33
+ uniform vec2 targetUvScale;
34
+ uniform float mipLevelScale;
35
+
36
+ // Scattering
37
+ const vec2 scatterAnisotropy = vec2(SCATTER_ANISOTROPY_1, SCATTER_ANISOTROPY_2);
38
+ const float scatterAnisotropyMix = SCATTER_ANISOTROPY_MIX;
39
+ uniform float skyIrradianceScale;
40
+ uniform float groundIrradianceScale;
41
+ uniform float powderScale;
42
+ uniform float powderExponent;
43
+
44
+ // Primary raymarch
45
+ uniform int maxIterationCount;
46
+ uniform float minStepSize;
47
+ uniform float maxStepSize;
48
+ uniform float maxRayDistance;
49
+ uniform float perspectiveStepScale;
50
+
51
+ // Secondary raymarch
52
+ uniform int maxIterationCountToSun;
53
+ uniform int maxIterationCountToGround;
54
+ uniform float minSecondaryStepSize;
55
+ uniform float secondaryStepScale;
56
+
57
+ // Beer shadow map
58
+ uniform sampler2DArray shadowBuffer;
59
+ uniform vec2 shadowTexelSize;
60
+ uniform vec2 shadowIntervals[SHADOW_CASCADE_COUNT];
61
+ uniform mat4 shadowMatrices[SHADOW_CASCADE_COUNT];
62
+ uniform float shadowFar;
63
+ uniform float maxShadowFilterRadius;
64
+
65
+ // Shadow length
66
+ #ifdef SHADOW_LENGTH
67
+ uniform int maxShadowLengthIterationCount;
68
+ uniform float minShadowLengthStepSize;
69
+ uniform float maxShadowLengthRayDistance;
70
+ #endif // SHADOW_LENGTH
71
+
72
+ in vec2 vUv;
73
+ in vec3 vCameraPosition;
74
+ in vec3 vCameraDirection; // Direction to the center of screen
75
+ in vec3 vRayDirection; // Direction to the texel
76
+ in vec3 vEllipsoidCenter;
77
+ in GroundIrradiance vGroundIrradiance;
78
+ in CloudsIrradiance vCloudsIrradiance;
79
+
80
+ layout(location = 0) out vec4 outputColor;
81
+ layout(location = 1) out vec3 outputDepthVelocity;
82
+ #ifdef SHADOW_LENGTH
83
+ layout(location = 2) out float outputShadowLength;
84
+ #endif // SHADOW_LENGTH
85
+
86
+ float readDepth(const vec2 uv) {
87
+ #if DEPTH_PACKING == 3201
88
+ return unpackRGBAToDepth(texture(depthBuffer, uv));
89
+ #else // DEPTH_PACKING == 3201
90
+ return texture(depthBuffer, uv).r;
91
+ #endif // DEPTH_PACKING == 3201
92
+ }
93
+
94
+ float getViewZ(const float depth) {
95
+ #ifdef PERSPECTIVE_CAMERA
96
+ return perspectiveDepthToViewZ(depth, cameraNear, cameraFar);
97
+ #else // PERSPECTIVE_CAMERA
98
+ return orthographicDepthToViewZ(depth, cameraNear, cameraFar);
99
+ #endif // PERSPECTIVE_CAMERA
100
+ }
101
+
102
+ vec3 ECEFToWorld(const vec3 positionECEF) {
103
+ return mat3(ellipsoidMatrix) * (positionECEF + vEllipsoidCenter);
104
+ }
105
+
106
+ vec2 getShadowUv(const vec3 worldPosition, const int cascadeIndex) {
107
+ vec4 clip = shadowMatrices[cascadeIndex] * vec4(worldPosition, 1.0);
108
+ clip /= clip.w;
109
+ return clip.xy * 0.5 + 0.5;
110
+ }
111
+
112
+ float getDistanceToShadowTop(const vec3 rayPosition) {
113
+ // Distance to the top of the shadows along the sun direction, which matches
114
+ // the ray origin of BSM.
115
+ return raySphereSecondIntersection(
116
+ rayPosition,
117
+ sunDirection,
118
+ vec3(0.0),
119
+ bottomRadius + shadowTopHeight
120
+ );
121
+ }
122
+
123
+ #ifdef DEBUG_SHOW_CASCADES
124
+
125
+ const vec3 cascadeColors[4] = vec3[4](
126
+ vec3(1.0, 0.0, 0.0),
127
+ vec3(0.0, 1.0, 0.0),
128
+ vec3(0.0, 0.0, 1.0),
129
+ vec3(1.0, 1.0, 0.0)
130
+ );
131
+
132
+ vec3 getCascadeColor(const vec3 rayPosition) {
133
+ vec3 worldPosition = ECEFToWorld(rayPosition);
134
+ int cascadeIndex = getCascadeIndex(
135
+ viewMatrix,
136
+ worldPosition,
137
+ shadowIntervals,
138
+ cameraNear,
139
+ shadowFar
140
+ );
141
+ vec2 uv = getShadowUv(worldPosition, cascadeIndex);
142
+ if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
143
+ return vec3(1.0);
144
+ }
145
+ return cascadeColors[cascadeIndex];
146
+ }
147
+
148
+ vec3 getFadedCascadeColor(const vec3 rayPosition, const float jitter) {
149
+ vec3 worldPosition = ECEFToWorld(rayPosition);
150
+ int cascadeIndex = getFadedCascadeIndex(
151
+ viewMatrix,
152
+ worldPosition,
153
+ shadowIntervals,
154
+ cameraNear,
155
+ shadowFar,
156
+ jitter
157
+ );
158
+ return cascadeIndex >= 0
159
+ ? cascadeColors[cascadeIndex]
160
+ : vec3(1.0);
161
+ }
162
+
163
+ #endif // DEBUG_SHOW_CASCADES
164
+
165
+ float readShadowOpticalDepth(
166
+ const vec2 uv,
167
+ const float distanceToTop,
168
+ const float distanceOffset,
169
+ const int cascadeIndex
170
+ ) {
171
+ // r: frontDepth, g: meanExtinction, b: maxOpticalDepth, a: maxOpticalDepthTail
172
+ // Also see the discussion here: https://x.com/shotamatsuda/status/1885322308908442106
173
+ vec4 shadow = texture(shadowBuffer, vec3(uv, float(cascadeIndex)));
174
+ float distanceToFront = max(0.0, distanceToTop - distanceOffset - shadow.r);
175
+ return min(shadow.b + shadow.a, shadow.g * distanceToFront);
176
+ }
177
+
178
+ float sampleShadowOpticalDepthPCF(
179
+ const vec3 worldPosition,
180
+ const float distanceToTop,
181
+ const float distanceOffset,
182
+ const float radius,
183
+ const int cascadeIndex
184
+ ) {
185
+ vec2 uv = getShadowUv(worldPosition, cascadeIndex);
186
+ if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
187
+ return 0.0;
188
+ }
189
+ if (radius < 0.1) {
190
+ return readShadowOpticalDepth(uv, distanceToTop, distanceOffset, cascadeIndex);
191
+ }
192
+ float sum = 0.0;
193
+ vec2 offset;
194
+ #pragma unroll_loop_start
195
+ for (int i = 0; i < 16; ++i) {
196
+ #if UNROLLED_LOOP_INDEX < SHADOW_SAMPLE_COUNT
197
+ offset = vogelDisk(
198
+ UNROLLED_LOOP_INDEX,
199
+ SHADOW_SAMPLE_COUNT,
200
+ interleavedGradientNoise(gl_FragCoord.xy + temporalJitter * resolution) * PI2
201
+ );
202
+ sum += readShadowOpticalDepth(
203
+ uv + offset * radius * shadowTexelSize,
204
+ distanceToTop,
205
+ distanceOffset,
206
+ cascadeIndex
207
+ );
208
+ #endif // UNROLLED_LOOP_INDEX < SHADOW_SAMPLE_COUNT
209
+ }
210
+ #pragma unroll_loop_end
211
+ return sum / float(SHADOW_SAMPLE_COUNT);
212
+ }
213
+
214
+ float sampleShadowOpticalDepth(
215
+ const vec3 rayPosition,
216
+ const float distanceOffset,
217
+ const float radius,
218
+ const float jitter
219
+ ) {
220
+ float distanceToTop = getDistanceToShadowTop(rayPosition);
221
+ if (distanceToTop <= 0.0) {
222
+ return 0.0;
223
+ }
224
+ vec3 worldPosition = ECEFToWorld(rayPosition);
225
+ int cascadeIndex = getFadedCascadeIndex(
226
+ viewMatrix,
227
+ worldPosition,
228
+ shadowIntervals,
229
+ cameraNear,
230
+ shadowFar,
231
+ jitter
232
+ );
233
+ return cascadeIndex >= 0
234
+ ? sampleShadowOpticalDepthPCF(
235
+ worldPosition,
236
+ distanceToTop,
237
+ distanceOffset,
238
+ radius,
239
+ cascadeIndex
240
+ )
241
+ : 0.0;
242
+ }
243
+
244
+ #ifdef DEBUG_SHOW_SHADOW_MAP
245
+ vec4 getCascadedShadowMaps(vec2 uv) {
246
+ vec4 coord = vec4(vUv, vUv - 0.5) * 2.0;
247
+ vec4 shadow = vec4(0.0);
248
+ if (uv.y > 0.5) {
249
+ if (uv.x < 0.5) {
250
+ shadow = texture(shadowBuffer, vec3(coord.xw, 0.0));
251
+ } else {
252
+ #if SHADOW_CASCADE_COUNT > 1
253
+ shadow = texture(shadowBuffer, vec3(coord.zw, 1.0));
254
+ #endif // SHADOW_CASCADE_COUNT > 1
255
+ }
256
+ } else {
257
+ if (uv.x < 0.5) {
258
+ #if SHADOW_CASCADE_COUNT > 2
259
+ shadow = texture(shadowBuffer, vec3(coord.xy, 2.0));
260
+ #endif // SHADOW_CASCADE_COUNT > 2
261
+ } else {
262
+ #if SHADOW_CASCADE_COUNT > 3
263
+ shadow = texture(shadowBuffer, vec3(coord.zy, 3.0));
264
+ #endif // SHADOW_CASCADE_COUNT > 3
265
+ }
266
+ }
267
+
268
+ #if !defined(DEBUG_SHOW_SHADOW_MAP_TYPE)
269
+ #define DEBUG_SHOW_SHADOW_MAP_TYPE (0)
270
+ #endif // !defined(DEBUG_SHOW_SHADOW_MAP_TYPE
271
+
272
+ const float frontDepthScale = 1e-5;
273
+ const float meanExtinctionScale = 10.0;
274
+ const float maxOpticalDepthScale = 0.01;
275
+ vec3 color;
276
+ #if DEBUG_SHOW_SHADOW_MAP_TYPE == 1
277
+ color = vec3(shadow.r * frontDepthScale);
278
+ #elif DEBUG_SHOW_SHADOW_MAP_TYPE == 2
279
+ color = vec3(shadow.g * meanExtinctionScale);
280
+ #elif DEBUG_SHOW_SHADOW_MAP_TYPE == 3
281
+ color = vec3((shadow.b + shadow.a) * maxOpticalDepthScale);
282
+ #else // DEBUG_SHOW_SHADOW_MAP_TYPE
283
+ color =
284
+ (shadow.rgb + vec3(0.0, 0.0, shadow.a)) *
285
+ vec3(frontDepthScale, meanExtinctionScale, maxOpticalDepthScale);
286
+ #endif // DEBUG_SHOW_SHADOW_MAP_TYPE
287
+ return vec4(color, 1.0);
288
+ }
289
+ #endif // DEBUG_SHOW_SHADOW_MAP
290
+
291
+ vec2 henyeyGreenstein(const vec2 g, const float cosTheta) {
292
+ vec2 g2 = g * g;
293
+ // prettier-ignore
294
+ return RECIPROCAL_PI4 *
295
+ ((1.0 - g2) / max(vec2(1e-7), pow(1.0 + g2 - 2.0 * g * cosTheta, vec2(1.5))));
296
+ }
297
+
298
+ #ifdef ACCURATE_PHASE_FUNCTION
299
+
300
+ float draine(float u, float g, float a) {
301
+ float g2 = g * g;
302
+ // prettier-ignore
303
+ return (1.0 - g2) *
304
+ (1.0 + a * u * u) /
305
+ (4.0 * (1.0 + a * (1.0 + 2.0 * g2) / 3.0) * PI * pow(1.0 + g2 - 2.0 * g * u, 1.5));
306
+ }
307
+
308
+ // Numerically-fitted large particles (d=10) phase function It won't be
309
+ // plausible without a more precise multiple scattering.
310
+ // Reference: https://research.nvidia.com/labs/rtr/approximate-mie/
311
+ float phaseFunction(const float cosTheta, const float attenuation) {
312
+ const float gHG = 0.988176691700256; // exp(-0.0990567/(d-1.67154))
313
+ const float gD = 0.5556712547839497; // exp(-2.20679/(d+3.91029) - 0.428934)
314
+ const float alpha = 21.995520856274638; // exp(3.62489 - 8.29288/(d+5.52825))
315
+ const float weight = 0.4819554318404214; // exp(-0.599085/(d-0.641583)-0.665888)
316
+ return mix(
317
+ henyeyGreenstein(vec2(gHG) * attenuation, cosTheta).x,
318
+ draine(cosTheta, gD * attenuation, alpha),
319
+ weight
320
+ );
321
+ }
322
+
323
+ #else // ACCURATE_PHASE_FUNCTION
324
+
325
+ float phaseFunction(const float cosTheta, const float attenuation) {
326
+ const vec2 g = scatterAnisotropy;
327
+ const vec2 weights = vec2(1.0 - scatterAnisotropyMix, scatterAnisotropyMix);
328
+ // A similar approximation is described in the Frostbite's paper, where phase
329
+ // angle is attenuated instead of anisotropy.
330
+ return dot(henyeyGreenstein(g * attenuation, cosTheta), weights);
331
+ }
332
+
333
+ #endif // ACCURATE_PHASE_FUNCTION
334
+
335
+ float phaseFunction(const float cosTheta) {
336
+ return phaseFunction(cosTheta, 1.0);
337
+ }
338
+
339
+ float marchOpticalDepth(
340
+ const vec3 rayOrigin,
341
+ const vec3 rayDirection,
342
+ const int maxIterationCount,
343
+ const float mipLevel,
344
+ const float jitter,
345
+ out float rayDistance
346
+ ) {
347
+ int iterationCount = int(
348
+ max(0.0, remap(mipLevel, 0.0, 1.0, float(maxIterationCount + 1), 1.0) - jitter)
349
+ );
350
+ if (iterationCount == 0) {
351
+ // Fudge factor to approximate the mean optical depth.
352
+ // TODO: Remove it.
353
+ return 0.5;
354
+ }
355
+ float stepSize = minSecondaryStepSize / float(iterationCount);
356
+ float nextDistance = stepSize * jitter;
357
+ float opticalDepth = 0.0;
358
+ for (int i = 0; i < iterationCount; ++i) {
359
+ rayDistance = nextDistance;
360
+ vec3 position = rayDistance * rayDirection + rayOrigin;
361
+ vec2 uv = getGlobeUv(position);
362
+ float height = length(position) - bottomRadius;
363
+ WeatherSample weather = sampleWeather(uv, height, mipLevel);
364
+ MediaSample media = sampleMedia(weather, position, uv, mipLevel, jitter);
365
+ opticalDepth += media.extinction * stepSize;
366
+ nextDistance += stepSize;
367
+ stepSize *= secondaryStepScale;
368
+ }
369
+ return opticalDepth;
370
+ }
371
+
372
+ float marchOpticalDepth(
373
+ const vec3 rayOrigin,
374
+ const vec3 rayDirection,
375
+ const int maxIterationCount,
376
+ const float mipLevel,
377
+ const float jitter
378
+ ) {
379
+ float rayDistance;
380
+ return marchOpticalDepth(
381
+ rayOrigin,
382
+ rayDirection,
383
+ maxIterationCount,
384
+ mipLevel,
385
+ jitter,
386
+ rayDistance
387
+ );
388
+ }
389
+
390
+ float approximateMultipleScattering(const float opticalDepth, const float cosTheta) {
391
+ // Multiple scattering approximation
392
+ // See: https://fpsunflower.github.io/ckulla/data/oz_volumes.pdf
393
+ // a: attenuation, b: contribution, c: phase attenuation
394
+ vec3 coeffs = vec3(1.0); // [a, b, c]
395
+ const vec3 attenuation = vec3(0.5, 0.5, 0.5); // Should satisfy a <= b
396
+ float scattering = 0.0;
397
+ float beerLambert;
398
+ #pragma unroll_loop_start
399
+ for (int i = 0; i < 12; ++i) {
400
+ #if UNROLLED_LOOP_INDEX < MULTI_SCATTERING_OCTAVES
401
+ beerLambert = exp(-opticalDepth * coeffs.y);
402
+ scattering += coeffs.x * beerLambert * phaseFunction(cosTheta, coeffs.z);
403
+ coeffs *= attenuation;
404
+ #endif // UNROLLED_LOOP_INDEX < MULTI_SCATTERING_OCTAVES
405
+ }
406
+ #pragma unroll_loop_end
407
+ return scattering;
408
+ }
409
+
410
+ // TODO: Construct spherical harmonics of degree 2 using 2 sample points
411
+ // positioned near the horizon occlusion points on the sun direction plane.
412
+ vec3 getGroundSunSkyIrradiance(
413
+ const vec3 position,
414
+ const vec3 surfaceNormal,
415
+ const float height,
416
+ out vec3 skyIrradiance
417
+ ) {
418
+ #ifdef ACCURATE_SUN_SKY_IRRADIANCE
419
+ return GetSunAndSkyIrradiance(
420
+ (position - surfaceNormal * height) * METER_TO_LENGTH_UNIT,
421
+ sunDirection,
422
+ skyIrradiance
423
+ );
424
+ #else // ACCURATE_SUN_SKY_IRRADIANCE
425
+ skyIrradiance = vGroundIrradiance.sky;
426
+ return vGroundIrradiance.sun;
427
+ #endif // ACCURATE_SUN_SKY_IRRADIANCE
428
+ }
429
+
430
+ vec3 getCloudsSunSkyIrradiance(const vec3 position, const float height, out vec3 skyIrradiance) {
431
+ #ifdef ACCURATE_SUN_SKY_IRRADIANCE
432
+ return GetSunAndSkyIrradiance(position * METER_TO_LENGTH_UNIT, sunDirection, skyIrradiance);
433
+ #else // ACCURATE_SUN_SKY_IRRADIANCE
434
+ float alpha = remapClamped(height, minHeight, maxHeight);
435
+ skyIrradiance = mix(vCloudsIrradiance.minSky, vCloudsIrradiance.maxSky, alpha);
436
+ return mix(vCloudsIrradiance.minSun, vCloudsIrradiance.maxSun, alpha);
437
+ #endif // ACCURATE_SUN_SKY_IRRADIANCE
438
+ }
439
+
440
+ #ifdef GROUND_IRRADIANCE
441
+ vec3 approximateIrradianceFromGround(
442
+ const vec3 position,
443
+ const vec3 surfaceNormal,
444
+ const float height,
445
+ const float mipLevel,
446
+ const float jitter
447
+ ) {
448
+ float opticalDepthToGround = marchOpticalDepth(
449
+ position,
450
+ -surfaceNormal,
451
+ maxIterationCountToGround,
452
+ mipLevel,
453
+ jitter
454
+ );
455
+ vec3 skyIrradiance;
456
+ vec3 sunIrradiance = getGroundSunSkyIrradiance(position, surfaceNormal, height, skyIrradiance);
457
+ const float groundAlbedo = 0.3;
458
+ vec3 groundIrradiance = skyIrradiance + (1.0 - coverage) * sunIrradiance * RECIPROCAL_PI2;
459
+ vec3 bouncedLight = groundAlbedo * RECIPROCAL_PI * groundIrradiance;
460
+ vec3 bouncedIrradiance = bouncedLight * exp(-opticalDepthToGround);
461
+ return albedo * bouncedIrradiance * RECIPROCAL_PI4 * groundIrradianceScale;
462
+ }
463
+ #endif // GROUND_IRRADIANCE
464
+
465
+ vec4 marchClouds(
466
+ const vec3 rayOrigin,
467
+ const vec3 rayDirection,
468
+ const vec2 rayNearFar,
469
+ const float cosTheta,
470
+ const float jitter,
471
+ const float rayStartTexelsPerPixel,
472
+ out float frontDepth,
473
+ out ivec3 sampleCount
474
+ ) {
475
+ vec3 radianceIntegral = vec3(0.0);
476
+ float transmittanceIntegral = 1.0;
477
+ float weightedDistanceSum = 0.0;
478
+ float transmittanceSum = 0.0;
479
+
480
+ float maxRayDistance = rayNearFar.y - rayNearFar.x;
481
+ float stepSize = minStepSize + (perspectiveStepScale - 1.0) * rayNearFar.x;
482
+ // I don't understand why spatial aliasing remains unless doubling the jitter.
483
+ float rayDistance = stepSize * jitter * 2.0;
484
+
485
+ for (int i = 0; i < maxIterationCount; ++i) {
486
+ if (rayDistance > maxRayDistance) {
487
+ break; // Termination
488
+ }
489
+
490
+ vec3 position = rayDistance * rayDirection + rayOrigin;
491
+ float height = length(position) - bottomRadius;
492
+ float mipLevel = log2(max(1.0, rayStartTexelsPerPixel + rayDistance * 1e-5));
493
+
494
+ #if !defined(DEBUG_MARCH_INTERVALS)
495
+ if (insideLayerIntervals(height)) {
496
+ stepSize *= perspectiveStepScale;
497
+ rayDistance += mix(stepSize, maxStepSize, min(1.0, mipLevel));
498
+ continue;
499
+ }
500
+ #endif // !defined(DEBUG_MARCH_INTERVALS)
501
+
502
+ // Sample rough weather.
503
+ vec2 uv = getGlobeUv(position);
504
+ WeatherSample weather = sampleWeather(uv, height, mipLevel);
505
+
506
+ #ifdef DEBUG_SHOW_SAMPLE_COUNT
507
+ ++sampleCount.x;
508
+ #endif // DEBUG_SHOW_SAMPLE_COUNT
509
+
510
+ if (!any(greaterThan(weather.density, vec4(minDensity)))) {
511
+ // Step longer in empty space.
512
+ // TODO: This produces banding artifacts.
513
+ // Possible improvement: Binary search refinement
514
+ stepSize *= perspectiveStepScale;
515
+ rayDistance += mix(stepSize, maxStepSize, min(1.0, mipLevel));
516
+ continue;
517
+ }
518
+
519
+ // Sample detailed participating media.
520
+ MediaSample media = sampleMedia(weather, position, uv, mipLevel, jitter, sampleCount);
521
+
522
+ if (media.extinction > minExtinction) {
523
+ vec3 skyIrradiance;
524
+ vec3 sunIrradiance = getCloudsSunSkyIrradiance(position, height, skyIrradiance);
525
+ vec3 surfaceNormal = normalize(position);
526
+
527
+ // March optical depth to the sun for finer details, which BSM lacks.
528
+ float sunRayDistance = 0.0;
529
+ float opticalDepth = marchOpticalDepth(
530
+ position,
531
+ sunDirection,
532
+ maxIterationCountToSun,
533
+ mipLevel,
534
+ jitter,
535
+ sunRayDistance
536
+ );
537
+
538
+ if (height < shadowTopHeight) {
539
+ // Obtain the optical depth from BSM at the ray position.
540
+ opticalDepth += sampleShadowOpticalDepth(
541
+ position,
542
+ // Take account of only positions further than the marched ray
543
+ // distance.
544
+ sunRayDistance,
545
+ // Apply PCF only when the sun is close to the horizon.
546
+ maxShadowFilterRadius * remapClamped(dot(sunDirection, surfaceNormal), 0.1, 0.0),
547
+ jitter
548
+ );
549
+ }
550
+
551
+ float scattering = approximateMultipleScattering(opticalDepth, cosTheta);
552
+ vec3 radiance = albedo * sunIrradiance * scattering;
553
+
554
+ #ifdef GROUND_IRRADIANCE
555
+ // Fudge factor for the irradiance from ground.
556
+ if (height < shadowTopHeight && mipLevel < 0.5) {
557
+ radiance += approximateIrradianceFromGround(
558
+ position,
559
+ surfaceNormal,
560
+ height,
561
+ mipLevel,
562
+ jitter
563
+ );
564
+ }
565
+ #endif // GROUND_IRRADIANCE
566
+
567
+ // Crude approximation of sky gradient. Better than none in the shadows.
568
+ float skyGradient = dot(0.5 + weather.heightFraction, media.weight);
569
+ radiance += albedo * skyIrradiance * RECIPROCAL_PI4 * skyGradient * skyIrradianceScale;
570
+
571
+ // Finally multiply by extinction (redundant but kept for clarity).
572
+ radiance *= media.extinction;
573
+
574
+ #ifdef POWDER
575
+ radiance *= 1.0 - powderScale * exp(-media.extinction * powderExponent);
576
+ #endif // POWDER
577
+
578
+ #ifdef DEBUG_SHOW_CASCADES
579
+ if (height < shadowTopHeight) {
580
+ radiance = 1e-3 * getFadedCascadeColor(position, jitter);
581
+ }
582
+ #endif // DEBUG_SHOW_CASCADES
583
+
584
+ // Energy-conserving analytical integration of scattered light
585
+ // See 5.6.3 in https://media.contentapi.ea.com/content/dam/eacom/frostbite/files/s2016-pbs-frostbite-sky-clouds-new.pdf
586
+ float transmittance = exp(-media.extinction * stepSize);
587
+ float clampedExtinction = max(media.extinction, 1e-7);
588
+ vec3 scatteringIntegral = (radiance - radiance * transmittance) / clampedExtinction;
589
+ radianceIntegral += transmittanceIntegral * scatteringIntegral;
590
+ transmittanceIntegral *= transmittance;
591
+
592
+ // Aerial perspective affecting clouds
593
+ // See 5.9.1 in https://media.contentapi.ea.com/content/dam/eacom/frostbite/files/s2016-pbs-frostbite-sky-clouds-new.pdf
594
+ weightedDistanceSum += rayDistance * transmittanceIntegral;
595
+ transmittanceSum += transmittanceIntegral;
596
+ }
597
+
598
+ if (transmittanceIntegral <= minTransmittance) {
599
+ break; // Early termination
600
+ }
601
+
602
+ // Take a shorter step because we've already hit the clouds.
603
+ stepSize *= perspectiveStepScale;
604
+ rayDistance += stepSize;
605
+ }
606
+
607
+ // The final product of 5.9.1 and we'll evaluate this in aerial perspective.
608
+ frontDepth = transmittanceSum > 0.0 ? weightedDistanceSum / transmittanceSum : -1.0;
609
+
610
+ return vec4(radianceIntegral, remapClamped(transmittanceIntegral, 1.0, minTransmittance));
611
+ }
612
+
613
+ #ifdef SHADOW_LENGTH
614
+
615
+ float marchShadowLength(
616
+ const vec3 rayOrigin,
617
+ const vec3 rayDirection,
618
+ const vec2 rayNearFar,
619
+ const float jitter
620
+ ) {
621
+ float shadowLength = 0.0;
622
+ float maxRayDistance = rayNearFar.y - rayNearFar.x;
623
+ float stepSize = minShadowLengthStepSize;
624
+ float rayDistance = stepSize * jitter;
625
+ const float attenuationFactor = 1.0 - 1e-3;
626
+ float attenuation = 1.0;
627
+
628
+ // TODO: This march is closed, and sample resolution can be much lower.
629
+ // Refining the termination by binary search will make it much more efficient.
630
+ for (int i = 0; i < maxShadowLengthIterationCount; ++i) {
631
+ if (rayDistance > maxRayDistance) {
632
+ break; // Termination
633
+ }
634
+ vec3 position = rayDistance * rayDirection + rayOrigin;
635
+ float opticalDepth = sampleShadowOpticalDepth(position, 0.0, 0.0, jitter);
636
+ shadowLength += (1.0 - exp(-opticalDepth)) * stepSize * attenuation;
637
+
638
+ // Hack to prevent over-integration of shadow length. The shadow should be
639
+ // attenuated by the inscatter as the ray travels further.
640
+ attenuation *= attenuationFactor;
641
+ if (attenuation < 1e-5) {
642
+ break;
643
+ }
644
+
645
+ stepSize *= perspectiveStepScale;
646
+ rayDistance += stepSize;
647
+ }
648
+ return shadowLength;
649
+ }
650
+
651
+ #endif // SHADOW_LENGTH
652
+
653
+ #ifdef HAZE
654
+
655
+ vec4 approximateHaze(
656
+ const vec3 rayOrigin,
657
+ const vec3 rayDirection,
658
+ const float maxRayDistance,
659
+ const float cosTheta,
660
+ const float shadowLength
661
+ ) {
662
+ float modulation = remapClamped(coverage, 0.2, 0.4);
663
+ if (cameraHeight * modulation < 0.0) {
664
+ return vec4(0.0);
665
+ }
666
+ float density = modulation * hazeDensityScale * exp(-cameraHeight * hazeExponent);
667
+ if (density < 1e-7) {
668
+ return vec4(0.0); // Prevent artifact in views from space
669
+ }
670
+
671
+ // Analytical optical depth where density exponentially decreases with height.
672
+ // Based on: https://iquilezles.org/articles/fog/
673
+ float angle = max(dot(normalize(rayOrigin), rayDirection), 1e-5);
674
+ float exponent = angle * hazeExponent;
675
+ // Derive the optical depths separately for with and without shadow length.
676
+ float expTerm = 1.0 - exp(-maxRayDistance * exponent);
677
+ float shadowExpTerm = 1.0 - exp(-min(maxRayDistance, shadowLength) * exponent);
678
+ float linearTerm = density / hazeExponent / angle;
679
+ float opticalDepth = expTerm * linearTerm;
680
+ float effectiveOpticalDepth = max((expTerm - shadowExpTerm) * linearTerm, 0.0);
681
+
682
+ vec3 skyIrradiance = vGroundIrradiance.sky;
683
+ vec3 sunIrradiance = vGroundIrradiance.sun;
684
+ vec3 irradiance = sunIrradiance * phaseFunction(cosTheta);
685
+ irradiance += skyIrradiance * RECIPROCAL_PI4 * skyIrradianceScale;
686
+ vec3 inscatter = albedo * irradiance * saturate(1.0 - exp(-effectiveOpticalDepth));
687
+
688
+ // Inscatter is attenuated by shadow length, but transmittance is not.
689
+ return vec4(inscatter, saturate(1.0 - exp(-opticalDepth)));
690
+ }
691
+
692
+ #endif // HAZE
693
+
694
+ void applyAerialPerspective(
695
+ const vec3 cameraPosition,
696
+ const vec3 frontPosition,
697
+ const float shadowLength,
698
+ inout vec4 color
699
+ ) {
700
+ vec3 transmittance;
701
+ vec3 inscatter = GetSkyRadianceToPoint(
702
+ cameraPosition * METER_TO_LENGTH_UNIT,
703
+ frontPosition * METER_TO_LENGTH_UNIT,
704
+ shadowLength * METER_TO_LENGTH_UNIT,
705
+ sunDirection,
706
+ transmittance
707
+ );
708
+ float clampedAlpha = max(color.a, 1e-7);
709
+ color.rgb = mix(vec3(0.0), color.rgb * transmittance / clampedAlpha + inscatter, color.a);
710
+ }
711
+
712
+ bool rayIntersectsGround(const vec3 cameraPosition, const vec3 rayDirection) {
713
+ float r = length(cameraPosition);
714
+ float mu = dot(cameraPosition, rayDirection) / r;
715
+ return mu < 0.0 && r * r * (mu * mu - 1.0) + bottomRadius * bottomRadius >= 0.0;
716
+ }
717
+
718
+ struct IntersectionResult {
719
+ bool ground;
720
+ vec4 first;
721
+ vec4 second;
722
+ };
723
+
724
+ IntersectionResult getIntersections(const vec3 cameraPosition, const vec3 rayDirection) {
725
+ IntersectionResult intersections;
726
+ intersections.ground = rayIntersectsGround(cameraPosition, rayDirection);
727
+ raySphereIntersections(
728
+ cameraPosition,
729
+ rayDirection,
730
+ bottomRadius + vec4(0.0, minHeight, maxHeight, shadowTopHeight),
731
+ intersections.first,
732
+ intersections.second
733
+ );
734
+ return intersections;
735
+ }
736
+
737
+ vec2 getRayNearFar(const IntersectionResult intersections) {
738
+ vec2 nearFar;
739
+ if (cameraHeight < minHeight) {
740
+ // View below the clouds
741
+ if (intersections.ground) {
742
+ nearFar = vec2(-1.0); // No clouds to the ground
743
+ } else {
744
+ nearFar = vec2(intersections.second.y, intersections.second.z);
745
+ nearFar.y = min(nearFar.y, maxRayDistance);
746
+ }
747
+ } else if (cameraHeight < maxHeight) {
748
+ // View inside the total cloud layer
749
+ if (intersections.ground) {
750
+ nearFar = vec2(cameraNear, intersections.first.y);
751
+ } else {
752
+ nearFar = vec2(cameraNear, intersections.second.z);
753
+ }
754
+ } else {
755
+ // View above the clouds
756
+ nearFar = vec2(intersections.first.z, intersections.second.z);
757
+ if (intersections.ground) {
758
+ // Clamp the ray at the min height.
759
+ nearFar.y = intersections.first.y;
760
+ }
761
+ }
762
+ return nearFar;
763
+ }
764
+
765
+ #ifdef SHADOW_LENGTH
766
+ vec2 getShadowRayNearFar(const IntersectionResult intersections) {
767
+ vec2 nearFar;
768
+ if (cameraHeight < shadowTopHeight) {
769
+ if (intersections.ground) {
770
+ nearFar = vec2(cameraNear, intersections.first.x);
771
+ } else {
772
+ nearFar = vec2(cameraNear, intersections.second.w);
773
+ }
774
+ } else {
775
+ nearFar = vec2(intersections.first.w, intersections.second.w);
776
+ if (intersections.ground) {
777
+ // Clamp the ray at the ground.
778
+ nearFar.y = intersections.first.x;
779
+ }
780
+ }
781
+ nearFar.y = min(nearFar.y, maxShadowLengthRayDistance);
782
+ return nearFar;
783
+ }
784
+ #endif // SHADOW_LENGTH
785
+
786
+ #ifdef HAZE
787
+ vec2 getHazeRayNearFar(const IntersectionResult intersections) {
788
+ vec2 nearFar;
789
+ if (cameraHeight < maxHeight) {
790
+ if (intersections.ground) {
791
+ nearFar = vec2(cameraNear, intersections.first.x);
792
+ } else {
793
+ nearFar = vec2(cameraNear, intersections.second.z);
794
+ }
795
+ } else {
796
+ nearFar = vec2(cameraNear, intersections.second.z);
797
+ if (intersections.ground) {
798
+ // Clamp the ray at the ground.
799
+ nearFar.y = intersections.first.x;
800
+ }
801
+ }
802
+ return nearFar;
803
+ }
804
+ #endif // HAZE
805
+
806
+ float getRayDistanceToScene(const vec3 rayDirection) {
807
+ float depth = readDepth(vUv * targetUvScale + temporalJitter);
808
+ if (depth < 1.0 - 1e-7) {
809
+ depth = reverseLogDepth(depth, cameraNear, cameraFar);
810
+ float viewZ = getViewZ(depth);
811
+ return -viewZ / dot(rayDirection, vCameraDirection);
812
+ }
813
+ return -1.0;
814
+ }
815
+
816
+ void main() {
817
+ #ifdef DEBUG_SHOW_SHADOW_MAP
818
+ outputColor = getCascadedShadowMaps(vUv);
819
+ outputDepthVelocity = vec3(0.0);
820
+ #ifdef SHADOW_LENGTH
821
+ outputShadowLength = 0.0;
822
+ #endif // SHADOW_LENGTH
823
+ return;
824
+ #endif // DEBUG_SHOW_SHADOW_MAP
825
+
826
+ vec3 cameraPosition = vCameraPosition - vEllipsoidCenter;
827
+ vec3 rayDirection = normalize(vRayDirection);
828
+ float cosTheta = dot(sunDirection, rayDirection);
829
+
830
+ IntersectionResult intersections = getIntersections(cameraPosition, rayDirection);
831
+ vec2 rayNearFar = getRayNearFar(intersections);
832
+ #ifdef SHADOW_LENGTH
833
+ vec2 shadowRayNearFar = getShadowRayNearFar(intersections);
834
+ #endif // SHADOW_LENGTH
835
+ #ifdef HAZE
836
+ vec2 hazeRayNearFar = getHazeRayNearFar(intersections);
837
+ #endif // HAZE
838
+
839
+ float rayDistanceToScene = getRayDistanceToScene(rayDirection);
840
+ if (rayDistanceToScene >= 0.0) {
841
+ rayNearFar.y = min(rayNearFar.y, rayDistanceToScene);
842
+ #ifdef SHADOW_LENGTH
843
+ shadowRayNearFar.y = min(shadowRayNearFar.y, rayDistanceToScene);
844
+ #endif // SHADOW_LENGTH
845
+ #ifdef HAZE
846
+ hazeRayNearFar.y = min(hazeRayNearFar.y, rayDistanceToScene);
847
+ #endif // HAZE
848
+ }
849
+
850
+ bool intersectsGround = any(lessThan(rayNearFar, vec2(0.0)));
851
+ bool intersectsScene = rayNearFar.y < rayNearFar.x;
852
+
853
+ float stbn = getSTBN();
854
+
855
+ vec4 color = vec4(0.0);
856
+ float frontDepth = rayNearFar.y;
857
+ vec3 depthVelocity = vec3(0.0);
858
+ float shadowLength = 0.0;
859
+
860
+ if (!intersectsGround && !intersectsScene) {
861
+ vec3 rayOrigin = rayNearFar.x * rayDirection + cameraPosition;
862
+
863
+ vec2 globeUv = getGlobeUv(rayOrigin);
864
+ #ifdef DEBUG_SHOW_UV
865
+ outputColor = vec4(vec3(checker(globeUv, localWeatherRepeat + localWeatherOffset)), 1.0);
866
+ outputDepthVelocity = vec3(0.0);
867
+ #ifdef SHADOW_LENGTH
868
+ outputShadowLength = 0.0;
869
+ #endif // SHADOW_LENGTH
870
+ return;
871
+ #endif // DEBUG_SHOW_UV
872
+
873
+ float mipLevel = getMipLevel(globeUv * localWeatherRepeat) * mipLevelScale;
874
+ mipLevel = mix(0.0, mipLevel, min(1.0, 0.2 * cameraHeight / maxHeight));
875
+
876
+ float marchedFrontDepth;
877
+ ivec3 sampleCount = ivec3(0);
878
+ color = marchClouds(
879
+ rayOrigin,
880
+ rayDirection,
881
+ rayNearFar,
882
+ cosTheta,
883
+ stbn,
884
+ pow(2.0, mipLevel),
885
+ marchedFrontDepth,
886
+ sampleCount
887
+ );
888
+
889
+ #ifdef DEBUG_SHOW_SAMPLE_COUNT
890
+ outputColor = vec4(vec3(sampleCount) / vec3(500.0, 5.0, 5.0), 1.0);
891
+ outputDepthVelocity = vec3(0.0);
892
+ #ifdef SHADOW_LENGTH
893
+ outputShadowLength = 0.0;
894
+ #endif // SHADOW_LENGTH
895
+ return;
896
+ #endif // DEBUG_SHOW_SAMPLE_COUNT
897
+
898
+ // Front depth will be -1.0 when no samples are accumulated.
899
+ if (marchedFrontDepth >= 0.0) {
900
+ frontDepth = rayNearFar.x + marchedFrontDepth;
901
+
902
+ #ifdef SHADOW_LENGTH
903
+ // Clamp the shadow length ray at the clouds.
904
+ shadowRayNearFar.y = mix(
905
+ shadowRayNearFar.y,
906
+ min(frontDepth, shadowRayNearFar.y),
907
+ color.a // Interpolate by the alpha for smoother edges.
908
+ );
909
+ #endif // SHADOW_LENGTH
910
+
911
+ #ifdef HAZE
912
+ // Clamp the haze ray at the clouds.
913
+ hazeRayNearFar.y = mix(
914
+ hazeRayNearFar.y,
915
+ min(frontDepth, hazeRayNearFar.y),
916
+ color.a // Interpolate by the alpha for smoother edges.
917
+ );
918
+ #endif // HAZE
919
+ }
920
+
921
+ #ifdef SHADOW_LENGTH
922
+ if (all(greaterThanEqual(shadowRayNearFar, vec2(0.0)))) {
923
+ shadowLength = marchShadowLength(
924
+ shadowRayNearFar.x * rayDirection + cameraPosition,
925
+ rayDirection,
926
+ shadowRayNearFar,
927
+ stbn
928
+ );
929
+ }
930
+ #endif // SHADOW_LENGTH
931
+
932
+ // Apply aerial perspective.
933
+ vec3 frontPosition = cameraPosition + frontDepth * rayDirection;
934
+ applyAerialPerspective(cameraPosition, frontPosition, shadowLength, color);
935
+
936
+ // Velocity for temporal resolution.
937
+ vec3 frontPositionWorld = ECEFToWorld(frontPosition);
938
+ vec4 prevClip = reprojectionMatrix * vec4(frontPositionWorld, 1.0);
939
+ prevClip /= prevClip.w;
940
+ vec2 prevUv = prevClip.xy * 0.5 + 0.5;
941
+ vec2 velocity = (vUv - prevUv) * resolution;
942
+ depthVelocity = vec3(frontDepth, velocity);
943
+
944
+ } else {
945
+ #ifdef SHADOW_LENGTH
946
+ if (all(greaterThanEqual(shadowRayNearFar, vec2(0.0)))) {
947
+ shadowLength = marchShadowLength(
948
+ shadowRayNearFar.x * rayDirection + cameraPosition,
949
+ rayDirection,
950
+ shadowRayNearFar,
951
+ stbn
952
+ );
953
+ }
954
+ #endif // SHADOW_LENGTH
955
+
956
+ // TODO: We can calculate velocity to reduce occlusion errors at the edges,
957
+ // but suffers from floating-point precision errors on near objects.
958
+
959
+ // if (intersectsScene) {
960
+ // vec3 frontPosition = cameraPosition + rayNearFar.y * rayDirection;
961
+ // vec3 frontPositionWorld = ECEFToWorld(frontPosition);
962
+ // vec4 prevClip = reprojectionMatrix * vec4(frontPositionWorld, 1.0);
963
+ // prevClip /= prevClip.w;
964
+ // vec2 prevUv = prevClip.xy * 0.5 + 0.5;
965
+ // vec2 velocity = (vUv - prevUv) * resolution;
966
+ // depthVelocity = vec3(rayNearFar.y, velocity);
967
+ // }
968
+
969
+ }
970
+
971
+ #ifdef DEBUG_SHOW_FRONT_DEPTH
972
+ outputColor = vec4(turbo(frontDepth / maxRayDistance), 1.0);
973
+ outputDepthVelocity = vec3(0.0);
974
+ #ifdef SHADOW_LENGTH
975
+ outputShadowLength = 0.0;
976
+ #endif // SHADOW_LENGTH
977
+ return;
978
+ #endif // DEBUG_SHOW_FRONT_DEPTH
979
+
980
+ #ifdef HAZE
981
+ vec4 haze = approximateHaze(
982
+ cameraNear * rayDirection + cameraPosition,
983
+ rayDirection,
984
+ hazeRayNearFar.y - hazeRayNearFar.x,
985
+ cosTheta,
986
+ shadowLength
987
+ );
988
+ color = color * (1.0 - haze.a) + haze;
989
+ #endif // HAZE
990
+
991
+ outputColor = color;
992
+ outputDepthVelocity = depthVelocity;
993
+ #ifdef SHADOW_LENGTH
994
+ outputShadowLength = shadowLength * METER_TO_LENGTH_UNIT;
995
+ #endif // SHADOW_LENGTH
996
+ }