@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,2189 @@
1
+ "use strict";const C=require("postprocessing"),n=require("three"),H=require("@takram/three-atmosphere"),u=require("@takram/three-geospatial"),b=require("@takram/three-atmosphere/shaders"),y=require("@takram/three-geospatial/shaders");class I{constructor(e=0,t=0,a=0,i=0){this.expTerm=e,this.exponent=t,this.linearTerm=a,this.constantTerm=i}set(e=0,t=0,a=0,i=0){return this.expTerm=e,this.exponent=t,this.linearTerm=a,this.constantTerm=i,this}clone(){return new I(this.expTerm,this.exponent,this.linearTerm,this.constantTerm)}copy(e){return this.expTerm=e.expTerm,this.exponent=e.exponent,this.linearTerm=e.linearTerm,this.constantTerm=e.constantTerm,this}}const me=["channel","altitude","height","densityScale","shapeAmount","shapeDetailAmount","weatherExponent","shapeAlteringBias","coverageFilterWidth","shadow","densityProfile"];function ve(o,e){if(e!=null)for(const t of me){const a=e[t];a!=null&&(o[t]instanceof I?o[t].copy(a):o[t]=a)}}const R=class R{constructor(e){this.channel="r",this.altitude=0,this.height=0,this.densityScale=.2,this.shapeAmount=1,this.shapeDetailAmount=1,this.weatherExponent=1,this.shapeAlteringBias=.35,this.coverageFilterWidth=.6,this.densityProfile=new I(0,0,.75,.25),this.shadow=!1,this.set(e)}set(e){return ve(this,e),this}clone(){return new R(this)}copy(e){return this.channel=e.channel,this.altitude=e.altitude,this.height=e.height,this.densityScale=e.densityScale,this.shapeAmount=e.shapeAmount,this.shapeDetailAmount=e.shapeDetailAmount,this.weatherExponent=e.weatherExponent,this.shapeAlteringBias=e.shapeAlteringBias,this.coverageFilterWidth=e.coverageFilterWidth,this.densityProfile.copy(e.densityProfile),this.shadow=e.shadow,this}};R.DEFAULT=new R;let x=R;const A=Array.from({length:8},()=>({value:0,flag:0})),P=Array.from({length:3},()=>({min:0,max:0}));function ge(o,e){return o.value!==e.value?o.value-e.value:o.flag-e.flag}const U=class U extends Array{constructor(e){super(new x(e==null?void 0:e[0]),new x(e==null?void 0:e[1]),new x(e==null?void 0:e[2]),new x(e==null?void 0:e[3]))}set(e){return this[0].set(e==null?void 0:e[0]),this[1].set(e==null?void 0:e[1]),this[2].set(e==null?void 0:e[2]),this[3].set(e==null?void 0:e[3]),this}reset(){return this[0].copy(x.DEFAULT),this[1].copy(x.DEFAULT),this[2].copy(x.DEFAULT),this[3].copy(x.DEFAULT),this}clone(){return new U(this)}copy(e){return this[0].copy(e[0]),this[1].copy(e[1]),this[2].copy(e[2]),this[3].copy(e[3]),this}get localWeatherChannels(){return this[0].channel+this[1].channel+this[2].channel+this[3].channel}packValues(e,t){return t.set(this[0][e],this[1][e],this[2][e],this[3][e])}packSums(e,t,a){return a.set(this[0][e]+this[0][t],this[1][e]+this[1][t],this[2][e]+this[2][t],this[3][e]+this[3][t])}packDensityProfiles(e,t){return t.set(this[0].densityProfile[e],this[1].densityProfile[e],this[2].densityProfile[e],this[3].densityProfile[e])}packIntervalHeights(e,t){for(let s=0;s<4;++s){const c=this[s];let h=A[s];h.value=c.altitude,h.flag=0,h=A[s+4],h.value=c.altitude+c.height,h.flag=1}A.sort(ge);let a=0,i=0;for(let s=0;s<A.length;++s){const{value:c,flag:h}=A[s];if(i===0&&s>0){const d=P[a++];d.min=A[s-1].value,d.max=c}i+=h===0?1:-1}for(;a<3;++a){const s=P[a];s.min=0,s.max=0}let r=P[0];e.x=r.min,t.x=r.max,r=P[1],e.y=r.min,t.y=r.max,r=P[2],e.z=r.min,t.z=r.max}};U.DEFAULT=new U([{channel:"r",altitude:750,height:650,densityScale:.2,shapeAmount:1,shapeDetailAmount:1,weatherExponent:1,shapeAlteringBias:.35,coverageFilterWidth:.6,shadow:!0},{channel:"g",altitude:1e3,height:1200,densityScale:.2,shapeAmount:1,shapeDetailAmount:1,weatherExponent:1,shapeAlteringBias:.35,coverageFilterWidth:.6,shadow:!0},{channel:"b",altitude:7500,height:500,densityScale:.003,shapeAmount:.4,shapeDetailAmount:0,weatherExponent:1,shapeAlteringBias:.35,coverageFilterWidth:.5},{channel:"a"}]);let F=U;var Se=process.env.NODE_ENV==="production",J="Invariant failed";function D(o,e){if(!o){if(Se)throw new Error(J);var t=J;throw new Error(t)}}class L{constructor(e,t){this.near=[new n.Vector3,new n.Vector3,new n.Vector3,new n.Vector3],this.far=[new n.Vector3,new n.Vector3,new n.Vector3,new n.Vector3],e!=null&&t!=null&&this.setFromCamera(e,t)}clone(){return new L().copy(this)}copy(e){for(let t=0;t<4;++t)this.near[t].copy(e.near[t]),this.far[t].copy(e.far[t]);return this}setFromCamera(e,t){const a=e.isOrthographicCamera===!0,i=e.projectionMatrixInverse;this.near[0].set(1,1,-1),this.near[1].set(1,-1,-1),this.near[2].set(-1,-1,-1),this.near[3].set(-1,1,-1);for(let r=0;r<4;++r)this.near[r].applyMatrix4(i);this.far[0].set(1,1,1),this.far[1].set(1,-1,1),this.far[2].set(-1,-1,1),this.far[3].set(-1,1,1);for(let r=0;r<4;++r){const s=this.far[r];s.applyMatrix4(i);const c=Math.abs(s.z);a?s.z*=Math.min(t/c,1):s.multiplyScalar(Math.min(t/c,1))}return this}split(e,t=[]){for(let a=0;a<e.length;++a){const i=t[a]??(t[a]=new L);if(a===0)for(let r=0;r<4;++r)i.near[r].copy(this.near[r]);else for(let r=0;r<4;++r)i.near[r].lerpVectors(this.near[r],this.far[r],e[a-1]);if(a===e.length-1)for(let r=0;r<4;++r)i.far[r].copy(this.far[r]);else for(let r=0;r<4;++r)i.far[r].lerpVectors(this.near[r],this.far[r],e[a])}return t.length=e.length,t}applyMatrix4(e){for(let t=0;t<4;++t)this.near[t].applyMatrix4(e),this.far[t].applyMatrix4(e);return this}}const ye={uniform:(o,e,t,a,i=[])=>{for(let r=0;r<o;++r)i[r]=(e+(t-e)*(r+1)/o)/t;return i.length=o,i},logarithmic:(o,e,t,a,i=[])=>{for(let r=0;r<o;++r)i[r]=e*(t/e)**((r+1)/o)/t;return i.length=o,i},practical:(o,e,t,a=.5,i=[])=>{for(let r=0;r<o;++r){const s=(e+(t-e)*(r+1)/o)/t,c=e*(t/e)**((r+1)/o)/t;i[r]=u.lerp(s,c,a)}return i.length=o,i}};function xe(o,e,t,a,i,r=[]){return ye[o](e,t,a,i,r)}const Q=new n.Vector3,ee=new n.Vector3,we=new n.Matrix4,te=new n.Matrix4,Ce=new L,Te=new n.Box3,De={maxFar:null,farScale:1,splitMode:"practical",splitLambda:.5,margin:0,fade:!0};class Ee{constructor(e){this.cascades=[],this.mapSize=new n.Vector2,this.cameraFrustum=new L,this.frusta=[],this.splits=[],this._far=0;const{cascadeCount:t,mapSize:a,maxFar:i,farScale:r,splitMode:s,splitLambda:c,margin:h,fade:d}={...De,...e};this.cascadeCount=t,this.mapSize.copy(a),this.maxFar=i,this.farScale=r,this.splitMode=s,this.splitLambda=c,this.margin=h,this.fade=d}get cascadeCount(){return this.cascades.length}set cascadeCount(e){var t;if(e!==this.cascadeCount){for(let a=0;a<e;++a)(t=this.cascades)[a]??(t[a]={interval:new n.Vector2,matrix:new n.Matrix4,inverseMatrix:new n.Matrix4,projectionMatrix:new n.Matrix4,inverseProjectionMatrix:new n.Matrix4,viewMatrix:new n.Matrix4,inverseViewMatrix:new n.Matrix4});this.cascades.length=e}}get far(){return this._far}updateIntervals(e){const t=this.cascadeCount,a=this.splits,i=this.far;xe(this.splitMode,t,e.near,i,this.splitLambda,a),this.cameraFrustum.setFromCamera(e,i),this.cameraFrustum.split(a,this.frusta);const r=this.cascades;for(let s=0;s<t;++s)r[s].interval.set(a[s-1]??0,a[s]??0)}getFrustumRadius(e,t){const a=t.near,i=t.far;let r=Math.max(i[0].distanceTo(i[2]),i[0].distanceTo(a[2]));if(this.fade){const s=e.near,c=this.far,h=i[0].z/(c-s);r+=.25*h**2*(c-s)}return r*.5}updateMatrices(e,t,a=1){const i=we.lookAt(Q.setScalar(0),ee.copy(t).multiplyScalar(-1),n.Object3D.DEFAULT_UP),r=te.multiplyMatrices(te.copy(i).invert(),e.matrixWorld),s=this.frusta,c=this.cascades;D(s.length===c.length);const h=this.margin,d=this.mapSize;for(let p=0;p<s.length;++p){const f=s[p],m=c[p],v=this.getFrustumRadius(e,s[p]),j=-v,Y=v,q=v,Z=-v;m.projectionMatrix.makeOrthographic(j,Y,q,Z,-this.margin,v*2+this.margin);const{near:pe,far:fe}=Ce.copy(f).applyMatrix4(r),N=Te.makeEmpty();for(let M=0;M<4;M++)N.expandByPoint(pe[M]),N.expandByPoint(fe[M]);const T=N.getCenter(Q);T.z=N.max.z+h;const K=(Y-j)/d.width,X=(q-Z)/d.height;T.x=Math.round(T.x/K)*K,T.y=Math.round(T.y/X)*X,T.applyMatrix4(i);const $=ee.copy(t).multiplyScalar(a).add(T);m.inverseViewMatrix.lookAt(T,$,n.Object3D.DEFAULT_UP).setPosition($)}}update(e,t,a){this._far=this.maxFar!=null?Math.min(this.maxFar,e.far*this.farScale):e.far*this.farScale,this.updateIntervals(e),this.updateMatrices(e,t,a);const i=this.cascades,r=this.cascadeCount;for(let s=0;s<r;++s){const{matrix:c,inverseMatrix:h,projectionMatrix:d,inverseProjectionMatrix:p,viewMatrix:f,inverseViewMatrix:m}=i[s];p.copy(d).invert(),f.copy(m).invert(),c.copy(d).multiply(f),h.copy(m).multiply(p)}}}const ne=[0,8,2,10,12,4,14,6,3,11,1,9,15,7,13,5],ie=ne.reduce((o,e,t)=>{const a=new n.Vector2;for(let i=0;i<16;++i)if(ne[i]===t){a.set((i%4+.5)/4,(Math.floor(i/4)+.5)/4);break}return[...o,a]},[]),Ae={resolutionScale:1,lightShafts:!0,shapeDetail:!0,turbulence:!0,haze:!0,clouds:{multiScatteringOctaves:8,accurateSunSkyIrradiance:!0,accuratePhaseFunction:!1,maxIterationCount:500,minStepSize:50,maxStepSize:1e3,maxRayDistance:2e5,perspectiveStepScale:1.01,minDensity:1e-5,minExtinction:1e-5,minTransmittance:.01,maxIterationCountToGround:3,maxIterationCountToSun:2,minSecondaryStepSize:100,secondaryStepScale:2,maxShadowLengthIterationCount:500,minShadowLengthStepSize:50,maxShadowLengthRayDistance:2e5},shadow:{cascadeCount:3,mapSize:new n.Vector2(512,512),maxIterationCount:50,minStepSize:100,maxStepSize:1e3,minDensity:1e-5,minExtinction:1e-5,minTransmittance:1e-4}},l=Ae,_e={low:{...l,lightShafts:!1,shapeDetail:!1,turbulence:!1,clouds:{...l.clouds,accurateSunSkyIrradiance:!1,maxIterationCount:200,minStepSize:100,maxRayDistance:1e5,minDensity:1e-4,minExtinction:1e-4,minTransmittance:.1,maxIterationCountToGround:0,maxIterationCountToSun:1},shadow:{...l.shadow,maxIterationCount:25,minDensity:1e-4,minExtinction:1e-4,minTransmittance:.01,cascadeCount:2,mapSize:new n.Vector2(256,256)}},medium:{...l,lightShafts:!1,turbulence:!1,clouds:{...l.clouds,minDensity:1e-4,minExtinction:1e-4,accurateSunSkyIrradiance:!1,maxIterationCountToSun:2,maxIterationCountToGround:1},shadow:{...l.shadow,minDensity:1e-4,minExtinction:1e-4,mapSize:new n.Vector2(256,256)}},high:l,ultra:{...l,shadow:{...l.shadow,mapSize:new n.Vector2(1024,1024)}}},Pe=`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
+ }
997
+ `,re=`float getSTBN() {
998
+ ivec3 size = textureSize(stbnTexture, 0);
999
+ vec3 scale = 1.0 / vec3(size);
1000
+ return texture(stbnTexture, vec3(gl_FragCoord.xy, float(frame % size.z)) * scale).r;
1001
+ }
1002
+
1003
+ // Straightforward spherical mapping
1004
+ vec2 getSphericalUv(const vec3 position) {
1005
+ vec2 st = normalize(position.yx);
1006
+ float phi = atan(st.x, st.y);
1007
+ float theta = asin(normalize(position).z);
1008
+ return vec2(phi * RECIPROCAL_PI2 + 0.5, theta * RECIPROCAL_PI + 0.5);
1009
+ }
1010
+
1011
+ vec2 getCubeSphereUv(const vec3 position) {
1012
+ // Cube-sphere relaxation by: http://mathproofs.blogspot.com/2005/07/mapping-cube-to-sphere.html
1013
+ // TODO: Tile and fix seams.
1014
+ // Possible improvements:
1015
+ // https://iquilezles.org/articles/texturerepetition/
1016
+ // https://gamedev.stackexchange.com/questions/184388/fragment-shader-map-dot-texture-repeatedly-over-the-sphere
1017
+ // https://github.com/mmikk/hextile-demo
1018
+
1019
+ vec3 n = normalize(position);
1020
+ vec3 f = abs(n);
1021
+ vec3 c = n / max(f.x, max(f.y, f.z));
1022
+ vec2 m;
1023
+ if (all(greaterThan(f.yy, f.xz))) {
1024
+ m = c.y > 0.0 ? vec2(-n.x, n.z) : n.xz;
1025
+ } else if (all(greaterThan(f.xx, f.yz))) {
1026
+ m = c.x > 0.0 ? n.yz : vec2(-n.y, n.z);
1027
+ } else {
1028
+ m = c.z > 0.0 ? n.xy : vec2(n.x, -n.y);
1029
+ }
1030
+
1031
+ vec2 m2 = m * m;
1032
+ float q = dot(m2.xy, vec2(-2.0, 2.0)) - 3.0;
1033
+ float q2 = q * q;
1034
+ vec2 uv;
1035
+ uv.x = sqrt(1.5 + m2.x - m2.y - 0.5 * sqrt(-24.0 * m2.x + q2)) * (m.x > 0.0 ? 1.0 : -1.0);
1036
+ uv.y = sqrt(6.0 / (3.0 - uv.x * uv.x)) * m.y;
1037
+ return uv * 0.5 + 0.5;
1038
+ }
1039
+
1040
+ vec2 getGlobeUv(const vec3 position) {
1041
+ return getCubeSphereUv(position);
1042
+ }
1043
+
1044
+ float getMipLevel(const vec2 uv) {
1045
+ const float mipLevelScale = 0.1;
1046
+ vec2 coord = uv * resolution;
1047
+ vec2 ddx = dFdx(coord);
1048
+ vec2 ddy = dFdy(coord);
1049
+ float deltaMaxSqr = max(dot(ddx, ddx), dot(ddy, ddy)) * mipLevelScale;
1050
+ return max(0.0, 0.5 * log2(max(1.0, deltaMaxSqr)));
1051
+ }
1052
+
1053
+ bool insideLayerIntervals(const float height) {
1054
+ bvec3 gt = greaterThan(vec3(height), minIntervalHeights);
1055
+ bvec3 lt = lessThan(vec3(height), maxIntervalHeights);
1056
+ return any(bvec3(gt.x && lt.x, gt.y && lt.y, gt.z && lt.z));
1057
+ }
1058
+
1059
+ struct WeatherSample {
1060
+ vec4 heightFraction; // Normalized height of each layer
1061
+ vec4 density;
1062
+ };
1063
+
1064
+ vec4 shapeAlteringFunction(const vec4 heightFraction, const vec4 bias) {
1065
+ // Apply a semi-circle transform to round the clouds towards the top.
1066
+ vec4 biased = pow(heightFraction, bias);
1067
+ vec4 x = clamp(biased * 2.0 - 1.0, -1.0, 1.0);
1068
+ return 1.0 - x * x;
1069
+ }
1070
+
1071
+ WeatherSample sampleWeather(const vec2 uv, const float height, const float mipLevel) {
1072
+ WeatherSample weather;
1073
+ weather.heightFraction = remapClamped(vec4(height), minLayerHeights, maxLayerHeights);
1074
+
1075
+ vec4 localWeather = pow(
1076
+ textureLod(
1077
+ localWeatherTexture,
1078
+ uv * localWeatherRepeat + localWeatherOffset,
1079
+ mipLevel
1080
+ ).LOCAL_WEATHER_CHANNELS,
1081
+ weatherExponents
1082
+ );
1083
+ #ifdef SHADOW
1084
+ localWeather *= shadowLayerMask;
1085
+ #endif // SHADOW
1086
+
1087
+ vec4 heightScale = shapeAlteringFunction(weather.heightFraction, shapeAlteringBiases);
1088
+
1089
+ // Modulation to control weather by coverage parameter.
1090
+ // Reference: https://github.com/Prograda/Skybolt/blob/master/Assets/Core/Shaders/Clouds.h#L63
1091
+ vec4 factor = 1.0 - coverage * heightScale;
1092
+ weather.density = remapClamped(
1093
+ mix(localWeather, vec4(1.0), coverageFilterWidths),
1094
+ factor,
1095
+ factor + coverageFilterWidths
1096
+ );
1097
+
1098
+ return weather;
1099
+ }
1100
+
1101
+ vec4 getLayerDensity(const vec4 heightFraction) {
1102
+ // prettier-ignore
1103
+ return densityProfile.expTerms * exp(densityProfile.exponents * heightFraction) +
1104
+ densityProfile.linearTerms * heightFraction +
1105
+ densityProfile.constantTerms;
1106
+ }
1107
+
1108
+ struct MediaSample {
1109
+ float density;
1110
+ vec4 weight;
1111
+ float scattering;
1112
+ float extinction;
1113
+ };
1114
+
1115
+ MediaSample sampleMedia(
1116
+ const WeatherSample weather,
1117
+ const vec3 position,
1118
+ const vec2 uv,
1119
+ const float mipLevel,
1120
+ const float jitter,
1121
+ out ivec3 sampleCount
1122
+ ) {
1123
+ vec4 density = weather.density;
1124
+
1125
+ // TODO: Define in physical length.
1126
+ vec3 surfaceNormal = normalize(position);
1127
+ float localWeatherSpeed = length(localWeatherOffset);
1128
+ vec3 evolution = -surfaceNormal * localWeatherSpeed * 2e4;
1129
+
1130
+ vec3 turbulence = vec3(0.0);
1131
+ #ifdef TURBULENCE
1132
+ vec2 turbulenceUv = uv * localWeatherRepeat * turbulenceRepeat;
1133
+ turbulence =
1134
+ turbulenceDisplacement *
1135
+ (texture(turbulenceTexture, turbulenceUv).rgb * 2.0 - 1.0) *
1136
+ dot(density, remapClamped(weather.heightFraction, vec4(0.3), vec4(0.0)));
1137
+ #endif // TURBULENCE
1138
+
1139
+ vec3 shapePosition = (position + evolution + turbulence) * shapeRepeat + shapeOffset;
1140
+ float shape = texture(shapeTexture, shapePosition).r;
1141
+ density = remapClamped(density, vec4(1.0 - shape) * shapeAmounts, vec4(1.0));
1142
+
1143
+ #ifdef DEBUG_SHOW_SAMPLE_COUNT
1144
+ ++sampleCount.y;
1145
+ #endif // DEBUG_SHOW_SAMPLE_COUNT
1146
+
1147
+ #ifdef SHAPE_DETAIL
1148
+ if (mipLevel * 0.5 + (jitter - 0.5) * 0.5 < 0.5) {
1149
+ vec3 detailPosition = (position + turbulence) * shapeDetailRepeat + shapeDetailOffset;
1150
+ float detail = texture(shapeDetailTexture, detailPosition).r;
1151
+ // Fluffy at the top and whippy at the bottom.
1152
+ vec4 modifier = mix(
1153
+ vec4(pow(detail, 6.0)),
1154
+ vec4(1.0 - detail),
1155
+ remapClamped(weather.heightFraction, vec4(0.2), vec4(0.4))
1156
+ );
1157
+ modifier = mix(vec4(0.0), modifier, shapeDetailAmounts);
1158
+ density = remapClamped(density * 2.0, vec4(modifier * 0.5), vec4(1.0));
1159
+
1160
+ #ifdef DEBUG_SHOW_SAMPLE_COUNT
1161
+ ++sampleCount.z;
1162
+ #endif // DEBUG_SHOW_SAMPLE_COUNT
1163
+ }
1164
+ #endif // SHAPE_DETAIL
1165
+
1166
+ // Nicely decrease the density at the bottom.
1167
+ density = saturate(density * densityScales * getLayerDensity(weather.heightFraction));
1168
+
1169
+ MediaSample media;
1170
+ float densitySum = density.x + density.y + density.z + density.w;
1171
+ media.weight = density / densitySum;
1172
+ media.scattering = densitySum * scatteringCoefficient;
1173
+ media.extinction = densitySum * absorptionCoefficient + media.scattering;
1174
+ return media;
1175
+ }
1176
+
1177
+ MediaSample sampleMedia(
1178
+ const WeatherSample weather,
1179
+ const vec3 position,
1180
+ const vec2 uv,
1181
+ const float mipLevel,
1182
+ const float jitter
1183
+ ) {
1184
+ ivec3 sampleCount;
1185
+ return sampleMedia(weather, position, uv, mipLevel, jitter, sampleCount);
1186
+ }
1187
+ `,Oe=`precision highp float;
1188
+ precision highp sampler3D;
1189
+
1190
+ #include "atmosphere/parameters"
1191
+ #include "atmosphere/functions"
1192
+ #include "types"
1193
+
1194
+ uniform mat4 inverseProjectionMatrix;
1195
+ uniform mat4 inverseViewMatrix;
1196
+ uniform vec3 cameraPosition;
1197
+ uniform vec3 ellipsoidCenter;
1198
+ uniform mat4 inverseEllipsoidMatrix;
1199
+ uniform vec3 altitudeCorrection;
1200
+
1201
+ // Atmosphere
1202
+ uniform float bottomRadius;
1203
+ uniform vec3 sunDirection;
1204
+
1205
+ // Cloud layers
1206
+ uniform float minHeight;
1207
+ uniform float maxHeight;
1208
+
1209
+ layout(location = 0) in vec3 position;
1210
+
1211
+ out vec2 vUv;
1212
+ out vec3 vCameraPosition;
1213
+ out vec3 vCameraDirection; // Direction to the center of screen
1214
+ out vec3 vRayDirection; // Direction to the texel
1215
+ out vec3 vEllipsoidCenter;
1216
+
1217
+ out GroundIrradiance vGroundIrradiance;
1218
+ out CloudsIrradiance vCloudsIrradiance;
1219
+
1220
+ void sampleSunSkyIrradiance(const vec3 positionECEF) {
1221
+ vGroundIrradiance.sun = GetSunAndSkyIrradiance(
1222
+ positionECEF * METER_TO_LENGTH_UNIT,
1223
+ sunDirection,
1224
+ vGroundIrradiance.sky
1225
+ );
1226
+
1227
+ vec3 surfaceNormal = normalize(positionECEF);
1228
+ vec2 radii = (bottomRadius + vec2(minHeight, maxHeight)) * METER_TO_LENGTH_UNIT;
1229
+ vCloudsIrradiance.minSun = GetSunAndSkyIrradiance(
1230
+ surfaceNormal * radii.x,
1231
+ sunDirection,
1232
+ vCloudsIrradiance.minSky
1233
+ );
1234
+ vCloudsIrradiance.maxSun = GetSunAndSkyIrradiance(
1235
+ surfaceNormal * radii.y,
1236
+ sunDirection,
1237
+ vCloudsIrradiance.maxSky
1238
+ );
1239
+ }
1240
+
1241
+ void main() {
1242
+ vUv = position.xy * 0.5 + 0.5;
1243
+
1244
+ vec4 viewPosition = inverseProjectionMatrix * vec4(position, 1.0);
1245
+ vec4 worldDirection = inverseViewMatrix * vec4(viewPosition.xyz, 0.0);
1246
+ mat3 rotation = mat3(inverseEllipsoidMatrix);
1247
+ vCameraPosition = rotation * cameraPosition;
1248
+ vCameraDirection = rotation * normalize((inverseViewMatrix * vec4(0.0, 0.0, -1.0, 0.0)).xyz);
1249
+ vRayDirection = rotation * worldDirection.xyz;
1250
+ vEllipsoidCenter = ellipsoidCenter + altitudeCorrection;
1251
+
1252
+ sampleSunSkyIrradiance(vCameraPosition - vEllipsoidCenter);
1253
+
1254
+ gl_Position = vec4(position.xy, 1.0, 1.0);
1255
+ }
1256
+ `,oe=`uniform vec2 resolution;
1257
+ uniform int frame;
1258
+ uniform sampler3D stbnTexture;
1259
+
1260
+ // Atmosphere
1261
+ uniform float bottomRadius;
1262
+ uniform mat4 ellipsoidMatrix;
1263
+ uniform mat4 inverseEllipsoidMatrix;
1264
+ uniform vec3 sunDirection;
1265
+
1266
+ // Participating medium
1267
+ uniform float scatteringCoefficient;
1268
+ uniform float absorptionCoefficient;
1269
+ uniform vec3 albedo;
1270
+
1271
+ // Primary raymarch
1272
+ uniform float minDensity;
1273
+ uniform float minExtinction;
1274
+ uniform float minTransmittance;
1275
+
1276
+ // Shape and weather
1277
+ uniform sampler2D localWeatherTexture;
1278
+ uniform vec2 localWeatherRepeat;
1279
+ uniform vec2 localWeatherOffset;
1280
+ uniform float coverage;
1281
+ uniform sampler3D shapeTexture;
1282
+ uniform vec3 shapeRepeat;
1283
+ uniform vec3 shapeOffset;
1284
+
1285
+ #ifdef SHAPE_DETAIL
1286
+ uniform sampler3D shapeDetailTexture;
1287
+ uniform vec3 shapeDetailRepeat;
1288
+ uniform vec3 shapeDetailOffset;
1289
+ #endif // SHAPE_DETAIL
1290
+
1291
+ #ifdef TURBULENCE
1292
+ uniform sampler2D turbulenceTexture;
1293
+ uniform vec2 turbulenceRepeat;
1294
+ uniform float turbulenceDisplacement;
1295
+ #endif // TURBULENCE
1296
+
1297
+ // Haze
1298
+ #ifdef HAZE
1299
+ uniform float hazeDensityScale;
1300
+ uniform float hazeExponent;
1301
+ #endif // HAZE
1302
+
1303
+ // Cloud layers
1304
+ uniform vec4 minLayerHeights;
1305
+ uniform vec4 maxLayerHeights;
1306
+ uniform vec3 minIntervalHeights;
1307
+ uniform vec3 maxIntervalHeights;
1308
+ uniform vec4 densityScales;
1309
+ uniform vec4 shapeAmounts;
1310
+ uniform vec4 shapeDetailAmounts;
1311
+ uniform vec4 weatherExponents;
1312
+ uniform vec4 shapeAlteringBiases;
1313
+ uniform vec4 coverageFilterWidths;
1314
+ uniform float minHeight;
1315
+ uniform float maxHeight;
1316
+ uniform float shadowTopHeight;
1317
+ uniform float shadowBottomHeight;
1318
+ uniform vec4 shadowLayerMask;
1319
+ uniform DensityProfile densityProfile;
1320
+ `,B=`struct GroundIrradiance {
1321
+ vec3 sun;
1322
+ vec3 sky;
1323
+ };
1324
+
1325
+ struct CloudsIrradiance {
1326
+ vec3 minSun;
1327
+ vec3 minSky;
1328
+ vec3 maxSun;
1329
+ vec3 maxSky;
1330
+ };
1331
+
1332
+ struct DensityProfile {
1333
+ vec4 expTerms;
1334
+ vec4 exponents;
1335
+ vec4 linearTerms;
1336
+ vec4 constantTerms;
1337
+ };
1338
+ `;var Re=Object.defineProperty,S=(o,e,t,a)=>{for(var i=void 0,r=o.length-1,s;r>=0;r--)(s=o[r])&&(i=s(e,t,i)||i);return i&&Re(e,t,i),i};const Ue=new n.Vector3,Le=new u.Geodetic;class g extends H.AtmosphereMaterialBase{constructor({parameterUniforms:e,layerUniforms:t,atmosphereUniforms:a},i=H.AtmosphereParameters.DEFAULT){super({name:"CloudsMaterial",glslVersion:n.GLSL3,vertexShader:u.resolveIncludes(Oe,{atmosphere:{parameters:b.parameters,functions:b.functions},types:B}),fragmentShader:u.unrollLoops(u.resolveIncludes(Pe,{core:{depth:y.depth,math:y.math,turbo:y.turbo,generators:y.generators,raySphereIntersection:y.raySphereIntersection,cascadedShadowMaps:y.cascadedShadowMaps,interleavedGradientNoise:y.interleavedGradientNoise,vogelDisk:y.vogelDisk},atmosphere:{parameters:b.parameters,functions:b.functions},types:B,parameters:oe,clouds:re})),uniforms:{...e,...t,...a,depthBuffer:new n.Uniform(null),viewMatrix:new n.Uniform(new n.Matrix4),inverseProjectionMatrix:new n.Uniform(new n.Matrix4),inverseViewMatrix:new n.Uniform(new n.Matrix4),reprojectionMatrix:new n.Uniform(new n.Matrix4),resolution:new n.Uniform(new n.Vector2),cameraNear:new n.Uniform(0),cameraFar:new n.Uniform(0),cameraHeight:new n.Uniform(0),frame:new n.Uniform(0),temporalJitter:new n.Uniform(new n.Vector2),targetUvScale:new n.Uniform(new n.Vector2),mipLevelScale:new n.Uniform(1),stbnTexture:new n.Uniform(null),albedo:new n.Uniform(new n.Vector3),skyIrradianceScale:new n.Uniform(2.5),groundIrradianceScale:new n.Uniform(3),powderScale:new n.Uniform(.8),powderExponent:new n.Uniform(150),maxIterationCount:new n.Uniform(l.clouds.maxIterationCount),minStepSize:new n.Uniform(l.clouds.minStepSize),maxStepSize:new n.Uniform(l.clouds.maxStepSize),maxRayDistance:new n.Uniform(l.clouds.maxRayDistance),perspectiveStepScale:new n.Uniform(l.clouds.perspectiveStepScale),minDensity:new n.Uniform(l.clouds.minDensity),minExtinction:new n.Uniform(l.clouds.minExtinction),minTransmittance:new n.Uniform(l.clouds.minTransmittance),maxIterationCountToSun:new n.Uniform(l.clouds.maxIterationCountToSun),maxIterationCountToGround:new n.Uniform(l.clouds.maxIterationCountToGround),minSecondaryStepSize:new n.Uniform(l.clouds.minSecondaryStepSize),secondaryStepScale:new n.Uniform(l.clouds.secondaryStepScale),shadowBuffer:new n.Uniform(null),shadowTexelSize:new n.Uniform(new n.Vector2),shadowIntervals:new n.Uniform(Array.from({length:4},()=>new n.Vector2)),shadowMatrices:new n.Uniform(Array.from({length:4},()=>new n.Matrix4)),shadowFar:new n.Uniform(0),maxShadowFilterRadius:new n.Uniform(6),shadowLayerMask:new n.Uniform(new n.Vector4().setScalar(1)),maxShadowLengthIterationCount:new n.Uniform(l.clouds.maxShadowLengthIterationCount),minShadowLengthStepSize:new n.Uniform(l.clouds.minShadowLengthStepSize),maxShadowLengthRayDistance:new n.Uniform(l.clouds.maxShadowLengthRayDistance),hazeDensityScale:new n.Uniform(3e-5),hazeExponent:new n.Uniform(.001)}},i),this.temporalUpscale=!0,this.depthPacking=0,this.localWeatherChannels="rgba",this.shapeDetail=l.shapeDetail,this.turbulence=l.turbulence,this.shadowLength=l.lightShafts,this.haze=l.haze,this.multiScatteringOctaves=l.clouds.multiScatteringOctaves,this.accurateSunSkyIrradiance=l.clouds.accurateSunSkyIrradiance,this.accuratePhaseFunction=l.clouds.accuratePhaseFunction,this.shadowCascadeCount=l.shadow.cascadeCount,this.shadowSampleCount=8,this.scatterAnisotropy1=.7,this.scatterAnisotropy2=-.2,this.scatterAnisotropyMix=.5}onBeforeRender(e,t,a,i,r,s){const c=this.uniforms;c.albedo.value.setScalar(c.scatteringCoefficient.value/(c.absorptionCoefficient.value+c.scatteringCoefficient.value));const h=this.defines.POWDER!=null,d=this.uniforms.powderScale.value>0;d!==h&&(d?this.defines.POWDER="1":delete this.defines.POWDER,this.needsUpdate=!0);const p=this.defines.GROUND_IRRADIANCE!=null;(this.uniforms.groundIrradianceScale.value>0&&this.uniforms.maxIterationCountToGround.value>0)!==p&&(d?this.defines.GROUND_IRRADIANCE="1":delete this.defines.GROUND_IRRADIANCE,this.needsUpdate=!0)}copyCameraSettings(e){e.isPerspectiveCamera===!0?this.defines.PERSPECTIVE_CAMERA!=="1"&&(this.defines.PERSPECTIVE_CAMERA="1",this.needsUpdate=!0):this.defines.PERSPECTIVE_CAMERA!=null&&(delete this.defines.PERSPECTIVE_CAMERA,this.needsUpdate=!0);const t=this.uniforms;t.viewMatrix.value.copy(e.matrixWorldInverse),t.inverseViewMatrix.value.copy(e.matrixWorld);const a=this.previousProjectionMatrix??e.projectionMatrix,i=this.previousViewMatrix??e.matrixWorldInverse,r=t.inverseProjectionMatrix.value,s=t.reprojectionMatrix.value;if(this.temporalUpscale){const d=t.frame.value%16,p=t.resolution.value,f=ie[d],m=(f.x-.5)/p.x*4,v=(f.y-.5)/p.y*4;t.temporalJitter.value.set(m,v),t.mipLevelScale.value=.25,r.copy(e.projectionMatrix),r.elements[8]+=m*2,r.elements[9]+=v*2,r.invert(),s.copy(a),s.elements[8]+=m*2,s.elements[9]+=v*2,s.multiply(i)}else t.temporalJitter.value.setScalar(0),t.mipLevelScale.value=1,r.copy(e.projectionMatrixInverse),s.copy(a).multiply(i);u.assertType(e),t.cameraNear.value=e.near,t.cameraFar.value=e.far;const c=e.getWorldPosition(t.cameraPosition.value),h=Ue.copy(c).applyMatrix4(t.inverseEllipsoidMatrix.value).sub(t.ellipsoidCenter.value);try{t.cameraHeight.value=Le.setFromECEF(h).height}catch{}}copyReprojectionMatrix(e){this.previousProjectionMatrix??(this.previousProjectionMatrix=new n.Matrix4),this.previousViewMatrix??(this.previousViewMatrix=new n.Matrix4),this.previousProjectionMatrix.copy(e.projectionMatrix),this.previousViewMatrix.copy(e.matrixWorldInverse)}setSize(e,t,a,i){this.uniforms.resolution.value.set(e,t),a!=null&&i!=null?this.uniforms.targetUvScale.value.set(e/a,t/i):this.uniforms.targetUvScale.value.setScalar(1),this.previousProjectionMatrix=void 0,this.previousViewMatrix=void 0}setShadowSize(e,t){this.uniforms.shadowTexelSize.value.set(1/e,1/t)}get depthBuffer(){return this.uniforms.depthBuffer.value}set depthBuffer(e){this.uniforms.depthBuffer.value=e}}S([u.defineInt("DEPTH_PACKING")],g.prototype,"depthPacking");S([u.defineExpression("LOCAL_WEATHER_CHANNELS",{validate:o=>/^[rgba]{4}$/.test(o)})],g.prototype,"localWeatherChannels");S([u.define("SHAPE_DETAIL")],g.prototype,"shapeDetail");S([u.define("TURBULENCE")],g.prototype,"turbulence");S([u.define("SHADOW_LENGTH")],g.prototype,"shadowLength");S([u.define("HAZE")],g.prototype,"haze");S([u.defineInt("MULTI_SCATTERING_OCTAVES",{min:1,max:12})],g.prototype,"multiScatteringOctaves");S([u.define("ACCURATE_SUN_SKY_IRRADIANCE")],g.prototype,"accurateSunSkyIrradiance");S([u.define("ACCURATE_PHASE_FUNCTION")],g.prototype,"accuratePhaseFunction");S([u.defineInt("SHADOW_CASCADE_COUNT",{min:1,max:4})],g.prototype,"shadowCascadeCount");S([u.defineInt("SHADOW_SAMPLE_COUNT",{min:1,max:16})],g.prototype,"shadowSampleCount");S([u.defineFloat("SCATTER_ANISOTROPY_1")],g.prototype,"scatterAnisotropy1");S([u.defineFloat("SCATTER_ANISOTROPY_2")],g.prototype,"scatterAnisotropy2");S([u.defineFloat("SCATTER_ANISOTROPY_MIX")],g.prototype,"scatterAnisotropyMix");const Ie=`// Taken from https://gist.github.com/TheRealMJP/c83b8c0f46b63f3a88a5986f4fa982b1
1339
+ // TODO: Use 5-taps version: https://www.shadertoy.com/view/MtVGWz
1340
+ // Or even 4 taps (requires preprocessing in the input buffer):
1341
+ // https://www.shadertoy.com/view/4tyGDD
1342
+
1343
+ /**
1344
+ * MIT License
1345
+ *
1346
+ * Copyright (c) 2019 MJP
1347
+ *
1348
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
1349
+ * of this software and associated documentation files (the "Software"), to deal
1350
+ * in the Software without restriction, including without limitation the rights
1351
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1352
+ * copies of the Software, and to permit persons to whom the Software is
1353
+ * furnished to do so, subject to the following conditions:
1354
+ *
1355
+ * The above copyright notice and this permission notice shall be included in all
1356
+ * copies or substantial portions of the Software.
1357
+ *
1358
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1359
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1360
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1361
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1362
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1363
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1364
+ * SOFTWARE.
1365
+ */
1366
+
1367
+ vec4 textureCatmullRom(sampler2D tex, vec2 uv) {
1368
+ vec2 texSize = vec2(textureSize(tex, 0));
1369
+
1370
+ // We're going to sample a a 4x4 grid of texels surrounding the target UV
1371
+ // coordinate. We'll do this by rounding down the sample location to get the
1372
+ // exact center of our "starting" texel. The starting texel will be at
1373
+ // location [1, 1] in the grid, where [0, 0] is the top left corner.
1374
+ vec2 samplePos = uv * texSize;
1375
+ vec2 texPos1 = floor(samplePos - 0.5) + 0.5;
1376
+
1377
+ // Compute the fractional offset from our starting texel to our original
1378
+ // sample location, which we'll feed into the Catmull-Rom spline function to
1379
+ // get our filter weights.
1380
+ vec2 f = samplePos - texPos1;
1381
+
1382
+ // Compute the Catmull-Rom weights using the fractional offset that we
1383
+ // calculated earlier. These equations are pre-expanded based on our knowledge
1384
+ // of where the texels will be located, which lets us avoid having to evaluate
1385
+ // a piece-wise function.
1386
+ vec2 w0 = f * (-0.5 + f * (1.0 - 0.5 * f));
1387
+ vec2 w1 = 1.0 + f * f * (-2.5 + 1.5 * f);
1388
+ vec2 w2 = f * (0.5 + f * (2.0 - 1.5 * f));
1389
+ vec2 w3 = f * f * (-0.5 + 0.5 * f);
1390
+
1391
+ // Work out weighting factors and sampling offsets that will let us use
1392
+ // bilinear filtering to simultaneously evaluate the middle 2 samples from the
1393
+ // 4x4 grid.
1394
+ vec2 w12 = w1 + w2;
1395
+ vec2 offset12 = w2 / (w1 + w2);
1396
+
1397
+ // Compute the final UV coordinates we'll use for sampling the texture
1398
+ vec2 texPos0 = texPos1 - 1.0;
1399
+ vec2 texPos3 = texPos1 + 2.0;
1400
+ vec2 texPos12 = texPos1 + offset12;
1401
+
1402
+ texPos0 /= texSize;
1403
+ texPos3 /= texSize;
1404
+ texPos12 /= texSize;
1405
+
1406
+ vec4 result = vec4(0.0);
1407
+ result += texture(tex, vec2(texPos0.x, texPos0.y)) * w0.x * w0.y;
1408
+ result += texture(tex, vec2(texPos12.x, texPos0.y)) * w12.x * w0.y;
1409
+ result += texture(tex, vec2(texPos3.x, texPos0.y)) * w3.x * w0.y;
1410
+
1411
+ result += texture(tex, vec2(texPos0.x, texPos12.y)) * w0.x * w12.y;
1412
+ result += texture(tex, vec2(texPos12.x, texPos12.y)) * w12.x * w12.y;
1413
+ result += texture(tex, vec2(texPos3.x, texPos12.y)) * w3.x * w12.y;
1414
+
1415
+ result += texture(tex, vec2(texPos0.x, texPos3.y)) * w0.x * w3.y;
1416
+ result += texture(tex, vec2(texPos12.x, texPos3.y)) * w12.x * w3.y;
1417
+ result += texture(tex, vec2(texPos3.x, texPos3.y)) * w3.x * w3.y;
1418
+
1419
+ return result;
1420
+ }
1421
+
1422
+ vec4 textureCatmullRom(sampler2DArray tex, vec3 uv) {
1423
+ vec2 texSize = vec2(textureSize(tex, 0));
1424
+ vec2 samplePos = uv.xy * texSize;
1425
+ vec2 texPos1 = floor(samplePos - 0.5) + 0.5;
1426
+ vec2 f = samplePos - texPos1;
1427
+ vec2 w0 = f * (-0.5 + f * (1.0 - 0.5 * f));
1428
+ vec2 w1 = 1.0 + f * f * (-2.5 + 1.5 * f);
1429
+ vec2 w2 = f * (0.5 + f * (2.0 - 1.5 * f));
1430
+ vec2 w3 = f * f * (-0.5 + 0.5 * f);
1431
+ vec2 w12 = w1 + w2;
1432
+ vec2 offset12 = w2 / (w1 + w2);
1433
+ vec2 texPos0 = texPos1 - 1.0;
1434
+ vec2 texPos3 = texPos1 + 2.0;
1435
+ vec2 texPos12 = texPos1 + offset12;
1436
+ texPos0 /= texSize;
1437
+ texPos3 /= texSize;
1438
+ texPos12 /= texSize;
1439
+ vec4 result = vec4(0.0);
1440
+ result += texture(tex, vec3(texPos0.x, texPos0.y, uv.z)) * w0.x * w0.y;
1441
+ result += texture(tex, vec3(texPos12.x, texPos0.y, uv.z)) * w12.x * w0.y;
1442
+ result += texture(tex, vec3(texPos3.x, texPos0.y, uv.z)) * w3.x * w0.y;
1443
+ result += texture(tex, vec3(texPos0.x, texPos12.y, uv.z)) * w0.x * w12.y;
1444
+ result += texture(tex, vec3(texPos12.x, texPos12.y, uv.z)) * w12.x * w12.y;
1445
+ result += texture(tex, vec3(texPos3.x, texPos12.y, uv.z)) * w3.x * w12.y;
1446
+ result += texture(tex, vec3(texPos0.x, texPos3.y, uv.z)) * w0.x * w3.y;
1447
+ result += texture(tex, vec3(texPos12.x, texPos3.y, uv.z)) * w12.x * w3.y;
1448
+ result += texture(tex, vec3(texPos3.x, texPos3.y, uv.z)) * w3.x * w3.y;
1449
+ return result;
1450
+ }
1451
+ `,Ne=`precision highp float;
1452
+ precision highp sampler2DArray;
1453
+
1454
+ #include "core/turbo"
1455
+ #include "catmullRomSampling"
1456
+ #include "varianceClipping"
1457
+
1458
+ uniform sampler2D colorBuffer;
1459
+ uniform sampler2D depthVelocityBuffer;
1460
+ uniform sampler2D colorHistoryBuffer;
1461
+
1462
+ #ifdef SHADOW_LENGTH
1463
+ uniform sampler2D shadowLengthBuffer;
1464
+ uniform sampler2D shadowLengthHistoryBuffer;
1465
+ #endif // SHADOW_LENGTH
1466
+
1467
+ uniform vec2 texelSize;
1468
+ uniform int frame;
1469
+ uniform float varianceGamma;
1470
+ uniform float temporalAlpha;
1471
+ uniform vec2 jitterOffset;
1472
+
1473
+ in vec2 vUv;
1474
+
1475
+ layout(location = 0) out vec4 outputColor;
1476
+ #ifdef SHADOW_LENGTH
1477
+ layout(location = 1) out float outputShadowLength;
1478
+ #endif // SHADOW_LENGTH
1479
+
1480
+ const ivec2 neighborOffsets[9] = ivec2[9](
1481
+ ivec2(-1, -1),
1482
+ ivec2(-1, 0),
1483
+ ivec2(-1, 1),
1484
+ ivec2(0, -1),
1485
+ ivec2(0, 0),
1486
+ ivec2(0, 1),
1487
+ ivec2(1, -1),
1488
+ ivec2(1, 0),
1489
+ ivec2(1, 1)
1490
+ );
1491
+
1492
+ const ivec4[4] bayerIndices = ivec4[4](
1493
+ ivec4(0, 12, 3, 15),
1494
+ ivec4(8, 4, 11, 7),
1495
+ ivec4(2, 14, 1, 13),
1496
+ ivec4(10, 6, 9, 5)
1497
+ );
1498
+
1499
+ vec2 getUnjitteredUv(ivec2 coord) {
1500
+ return (vec2(coord) + 0.5 - jitterOffset) * texelSize;
1501
+ }
1502
+
1503
+ vec4 getClosestFragment(const vec2 uv) {
1504
+ vec4 result = vec4(1e7, 0.0, 0.0, 0.0);
1505
+ vec4 neighbor;
1506
+ #pragma unroll_loop_start
1507
+ for (int i = 0; i < 9; ++i) {
1508
+ neighbor = textureOffset(depthVelocityBuffer, uv, neighborOffsets[i]);
1509
+ if (neighbor.r < result.r) {
1510
+ result = neighbor;
1511
+ }
1512
+ }
1513
+ #pragma unroll_loop_end
1514
+ return result;
1515
+ }
1516
+
1517
+ vec4 getClosestFragment(const ivec2 coord) {
1518
+ vec4 result = vec4(1e7, 0.0, 0.0, 0.0);
1519
+ vec4 neighbor;
1520
+ #pragma unroll_loop_start
1521
+ for (int i = 0; i < 9; ++i) {
1522
+ neighbor = texelFetchOffset(depthVelocityBuffer, coord, 0, neighborOffsets[i]);
1523
+ if (neighbor.r < result.r) {
1524
+ result = neighbor;
1525
+ }
1526
+ }
1527
+ #pragma unroll_loop_end
1528
+ return result;
1529
+ }
1530
+
1531
+ void temporalUpscale(
1532
+ const ivec2 coord,
1533
+ const ivec2 lowResCoord,
1534
+ const bool currentFrame,
1535
+ out vec4 outputColor,
1536
+ out float outputShadowLength
1537
+ ) {
1538
+ #if !defined(DEBUG_SHOW_VELOCITY)
1539
+ if (currentFrame) {
1540
+ // Use the texel just rendered without any accumulation.
1541
+ outputColor = texelFetch(colorBuffer, lowResCoord, 0);
1542
+ #ifdef SHADOW_LENGTH
1543
+ outputShadowLength = texelFetch(shadowLengthBuffer, lowResCoord, 0).r;
1544
+ #endif // SHADOW_LENGTH
1545
+ return;
1546
+ }
1547
+ #endif // !defined(DEBUG_SHOW_VELOCITY)
1548
+
1549
+ vec2 unjitteredUv = getUnjitteredUv(coord);
1550
+ vec4 currentColor = texture(colorBuffer, unjitteredUv);
1551
+ #ifdef SHADOW_LENGTH
1552
+ vec4 currentShadowLength = vec4(texture(shadowLengthBuffer, unjitteredUv).rgb, 1.0);
1553
+ #endif // SHADOW_LENGTH
1554
+
1555
+ vec4 depthVelocity = getClosestFragment(unjitteredUv);
1556
+ vec2 velocity = depthVelocity.gb * texelSize;
1557
+ vec2 prevUv = vUv - velocity;
1558
+ if (prevUv.x < 0.0 || prevUv.x > 1.0 || prevUv.y < 0.0 || prevUv.y > 1.0) {
1559
+ outputColor = currentColor;
1560
+ #ifdef SHADOW_LENGTH
1561
+ outputShadowLength = currentShadowLength.r;
1562
+ #endif // SHADOW_LENGTH
1563
+ return; // Rejection
1564
+ }
1565
+
1566
+ // Variance clipping with a large variance gamma seems to work fine for
1567
+ // upsampling. This increases ghosting, of course, but it's hard to notice on
1568
+ // clouds.
1569
+ // vec4 historyColor = textureCatmullRom(colorHistoryBuffer, prevUv);
1570
+ vec4 historyColor = texture(colorHistoryBuffer, prevUv);
1571
+ vec4 clippedColor = varianceClipping(colorBuffer, vUv, currentColor, historyColor, varianceGamma);
1572
+ outputColor = clippedColor;
1573
+
1574
+ #ifdef DEBUG_SHOW_VELOCITY
1575
+ outputColor.rgb = outputColor.rgb + vec3(abs(velocity), 0.0);
1576
+ #endif // DEBUG_SHOW_VELOCITY
1577
+
1578
+ #ifdef SHADOW_LENGTH
1579
+ // Sampling the shadow length history using scene depth doesn't make much
1580
+ // sense, but it's too hard to derive it properly. At least this approach
1581
+ // resolves the edges of scene objects.
1582
+ // vec4 historyShadowLength = vec4(textureCatmullRom(shadowLengthHistoryBuffer, prevUv).rgb, 1.0);
1583
+ vec4 historyShadowLength = vec4(texture(shadowLengthHistoryBuffer, prevUv).rgb, 1.0);
1584
+ vec4 clippedShadowLength = varianceClipping(
1585
+ shadowLengthBuffer,
1586
+ vUv,
1587
+ currentShadowLength,
1588
+ historyShadowLength,
1589
+ varianceGamma
1590
+ );
1591
+ outputShadowLength = clippedShadowLength.r;
1592
+ #endif // SHADOW_LENGTH
1593
+ }
1594
+
1595
+ void temporalAntialiasing(const ivec2 coord, out vec4 outputColor, out float outputShadowLength) {
1596
+ vec4 currentColor = texelFetch(colorBuffer, coord, 0);
1597
+ #ifdef SHADOW_LENGTH
1598
+ vec4 currentShadowLength = vec4(texelFetch(shadowLengthBuffer, coord, 0).rgb, 1.0);
1599
+ #endif // SHADOW_LENGTH
1600
+
1601
+ vec4 depthVelocity = getClosestFragment(coord);
1602
+ vec2 velocity = depthVelocity.gb * texelSize;
1603
+
1604
+ vec2 prevUv = vUv - velocity;
1605
+ if (prevUv.x < 0.0 || prevUv.x > 1.0 || prevUv.y < 0.0 || prevUv.y > 1.0) {
1606
+ outputColor = currentColor;
1607
+ #ifdef SHADOW_LENGTH
1608
+ outputShadowLength = currentShadowLength.r;
1609
+ #endif // SHADOW_LENGTH
1610
+ return; // Rejection
1611
+ }
1612
+
1613
+ vec4 historyColor = texture(colorHistoryBuffer, prevUv);
1614
+ vec4 clippedColor = varianceClipping(colorBuffer, coord, currentColor, historyColor);
1615
+ outputColor = mix(clippedColor, currentColor, temporalAlpha);
1616
+
1617
+ #ifdef DEBUG_SHOW_VELOCITY
1618
+ outputColor.rgb = outputColor.rgb + vec3(abs(velocity), 0.0);
1619
+ #endif // DEBUG_SHOW_VELOCITY
1620
+
1621
+ #ifdef SHADOW_LENGTH
1622
+ vec4 historyShadowLength = vec4(texture(shadowLengthHistoryBuffer, prevUv).rgb, 1.0);
1623
+ vec4 clippedShadowLength = varianceClipping(
1624
+ shadowLengthBuffer,
1625
+ coord,
1626
+ currentShadowLength,
1627
+ historyShadowLength
1628
+ );
1629
+ outputShadowLength = mix(clippedShadowLength.r, currentShadowLength.r, temporalAlpha);
1630
+ #endif // SHADOW_LENGTH
1631
+ }
1632
+
1633
+ void main() {
1634
+ ivec2 coord = ivec2(gl_FragCoord.xy);
1635
+
1636
+ #if !defined(SHADOW_LENGTH)
1637
+ float outputShadowLength;
1638
+ #endif // !defined(SHADOW_LENGTH)
1639
+
1640
+ #ifdef TEMPORAL_UPSCALE
1641
+ ivec2 lowResCoord = coord / 4;
1642
+ int bayerValue = bayerIndices[coord.x % 4][coord.y % 4];
1643
+ bool currentFrame = bayerValue == frame % 16;
1644
+ temporalUpscale(coord, lowResCoord, currentFrame, outputColor, outputShadowLength);
1645
+ #else // TEMPORAL_UPSCALE
1646
+ temporalAntialiasing(coord, outputColor, outputShadowLength);
1647
+ #endif // TEMPORAL_UPSCALE
1648
+
1649
+ #if defined(SHADOW_LENGTH) && defined(DEBUG_SHOW_SHADOW_LENGTH)
1650
+ outputColor = vec4(turbo(outputShadowLength * 0.05), 1.0);
1651
+ #endif // defined(SHADOW_LENGTH) && defined(DEBUG_SHOW_SHADOW_LENGTH)
1652
+ }
1653
+ `,Me=`precision highp float;
1654
+
1655
+ layout(location = 0) in vec3 position;
1656
+
1657
+ out vec2 vUv;
1658
+
1659
+ void main() {
1660
+ vUv = position.xy * 0.5 + 0.5;
1661
+ gl_Position = vec4(position.xy, 1.0, 1.0);
1662
+ }
1663
+ `,se=`#ifdef VARIANCE_9_SAMPLES
1664
+ #define VARIANCE_OFFSET_COUNT (8)
1665
+ const ivec2 varianceOffsets[8] = ivec2[8](
1666
+ ivec2(-1, -1),
1667
+ ivec2(-1, 1),
1668
+ ivec2(1, -1),
1669
+ ivec2(1, 1),
1670
+ ivec2(1, 0),
1671
+ ivec2(0, -1),
1672
+ ivec2(0, 1),
1673
+ ivec2(-1, 0)
1674
+ );
1675
+ #else // VARIANCE_9_SAMPLES
1676
+ #define VARIANCE_OFFSET_COUNT (4)
1677
+ const ivec2 varianceOffsets[4] = ivec2[4](ivec2(1, 0), ivec2(0, -1), ivec2(0, 1), ivec2(-1, 0));
1678
+ #endif // VARIANCE_9_SAMPLES
1679
+
1680
+ // Reference: https://github.com/playdeadgames/temporal
1681
+ vec4 clipAABB(const vec4 current, const vec4 history, const vec4 minColor, const vec4 maxColor) {
1682
+ vec3 pClip = 0.5 * (maxColor.rgb + minColor.rgb);
1683
+ vec3 eClip = 0.5 * (maxColor.rgb - minColor.rgb) + 1e-7;
1684
+ vec4 vClip = history - vec4(pClip, current.a);
1685
+ vec3 vUnit = vClip.xyz / eClip;
1686
+ vec3 aUnit = abs(vUnit);
1687
+ float maUnit = max(aUnit.x, max(aUnit.y, aUnit.z));
1688
+ if (maUnit > 1.0) {
1689
+ return vec4(pClip, current.a) + vClip / maUnit;
1690
+ }
1691
+ return history;
1692
+ }
1693
+
1694
+ #ifdef VARIANCE_SAMPLER_ARRAY
1695
+ #define VARIANCE_SAMPLER sampler2DArray
1696
+ #define VARIANCE_SAMPLER_COORD ivec3
1697
+ #else // VARIANCE_SAMPLER_ARRAY
1698
+ #define VARIANCE_SAMPLER sampler2D
1699
+ #define VARIANCE_SAMPLER_COORD ivec2
1700
+ #endif // VARIANCE_SAMPLER_ARRAY
1701
+
1702
+ // Variance clipping
1703
+ // Reference: https://developer.download.nvidia.com/gameworks/events/GDC2016/msalvi_temporal_supersampling.pdf
1704
+ vec4 varianceClipping(
1705
+ const VARIANCE_SAMPLER inputBuffer,
1706
+ const VARIANCE_SAMPLER_COORD coord,
1707
+ const vec4 current,
1708
+ const vec4 history,
1709
+ const float gamma
1710
+ ) {
1711
+ vec4 moment1 = current;
1712
+ vec4 moment2 = current * current;
1713
+ vec4 neighbor;
1714
+ #pragma unroll_loop_start
1715
+ for (int i = 0; i < 8; ++i) {
1716
+ #if UNROLLED_LOOP_INDEX < VARIANCE_OFFSET_COUNT
1717
+ neighbor = texelFetchOffset(inputBuffer, coord, 0, varianceOffsets[i]);
1718
+ moment1 += neighbor;
1719
+ moment2 += neighbor * neighbor;
1720
+ #endif // UNROLLED_LOOP_INDEX < VARIANCE_OFFSET_COUNT
1721
+ }
1722
+ #pragma unroll_loop_end
1723
+
1724
+ const float N = float(VARIANCE_OFFSET_COUNT + 1);
1725
+ vec4 mean = moment1 / N;
1726
+ vec4 varianceGamma = sqrt(max(moment2 / N - mean * mean, 0.0)) * gamma;
1727
+ vec4 minColor = mean - varianceGamma;
1728
+ vec4 maxColor = mean + varianceGamma;
1729
+ return clipAABB(clamp(mean, minColor, maxColor), history, minColor, maxColor);
1730
+ }
1731
+
1732
+ vec4 varianceClipping(
1733
+ const VARIANCE_SAMPLER inputBuffer,
1734
+ const VARIANCE_SAMPLER_COORD coord,
1735
+ const vec4 current,
1736
+ const vec4 history
1737
+ ) {
1738
+ return varianceClipping(inputBuffer, coord, current, history, 1.0);
1739
+ }
1740
+
1741
+ vec4 varianceClipping(
1742
+ const sampler2D inputBuffer,
1743
+ const vec2 coord,
1744
+ const vec4 current,
1745
+ const vec4 history,
1746
+ const float gamma
1747
+ ) {
1748
+ vec4 moment1 = current;
1749
+ vec4 moment2 = current * current;
1750
+ vec4 neighbor;
1751
+ #pragma unroll_loop_start
1752
+ for (int i = 0; i < 8; ++i) {
1753
+ #if UNROLLED_LOOP_INDEX < VARIANCE_OFFSET_COUNT
1754
+ neighbor = textureOffset(inputBuffer, coord, varianceOffsets[i]);
1755
+ moment1 += neighbor;
1756
+ moment2 += neighbor * neighbor;
1757
+ #endif // UNROLLED_LOOP_INDEX < VARIANCE_OFFSET_COUNT
1758
+ }
1759
+ #pragma unroll_loop_end
1760
+
1761
+ const float N = float(VARIANCE_OFFSET_COUNT + 1);
1762
+ vec4 mean = moment1 / N;
1763
+ vec4 varianceGamma = sqrt(max(moment2 / N - mean * mean, 0.0)) * gamma;
1764
+ vec4 minColor = mean - varianceGamma;
1765
+ vec4 maxColor = mean + varianceGamma;
1766
+ return clipAABB(clamp(mean, minColor, maxColor), history, minColor, maxColor);
1767
+ }
1768
+
1769
+ vec4 varianceClipping(
1770
+ const sampler2D inputBuffer,
1771
+ const vec2 coord,
1772
+ const vec4 current,
1773
+ const vec4 history
1774
+ ) {
1775
+ return varianceClipping(inputBuffer, coord, current, history, 1.0);
1776
+ }
1777
+ `;var be=Object.defineProperty,ce=(o,e,t,a)=>{for(var i=void 0,r=o.length-1,s;r>=0;r--)(s=o[r])&&(i=s(e,t,i)||i);return i&&be(e,t,i),i};class k extends n.RawShaderMaterial{constructor({colorBuffer:e=null,depthVelocityBuffer:t=null,shadowLengthBuffer:a=null,colorHistoryBuffer:i=null,shadowLengthHistoryBuffer:r=null}={}){super({name:"CloudsResolveMaterial",glslVersion:n.GLSL3,vertexShader:Me,fragmentShader:u.unrollLoops(u.resolveIncludes(Ne,{core:{turbo:y.turbo},catmullRomSampling:Ie,varianceClipping:se})),uniforms:{colorBuffer:new n.Uniform(e),depthVelocityBuffer:new n.Uniform(t),shadowLengthBuffer:new n.Uniform(a),colorHistoryBuffer:new n.Uniform(i),shadowLengthHistoryBuffer:new n.Uniform(r),texelSize:new n.Uniform(new n.Vector2),frame:new n.Uniform(0),jitterOffset:new n.Uniform(new n.Vector2),varianceGamma:new n.Uniform(2),temporalAlpha:new n.Uniform(.1)}}),this.temporalUpscale=!0,this.shadowLength=!0}setSize(e,t){this.uniforms.texelSize.value.set(1/e,1/t)}onBeforeRender(e,t,a,i,r,s){const h=this.uniforms.frame.value%16,d=ie[h],p=(d.x-.5)*4,f=(d.y-.5)*4;this.uniforms.jitterOffset.value.set(p,f)}}ce([u.define("TEMPORAL_UPSCALE")],k.prototype,"temporalUpscale");ce([u.define("SHADOW_LENGTH")],k.prototype,"shadowLength");class le extends C.Pass{constructor(e,t){super(e),this._mainCamera=new n.Camera;const{shadow:a}=t;this.shadow=a}get mainCamera(){return this._mainCamera}set mainCamera(e){this._mainCamera=e}}function W(o,{depthVelocity:e,shadowLength:t}){const a=new n.WebGLRenderTarget(1,1,{depthBuffer:!1,stencilBuffer:!1,type:n.HalfFloatType});a.texture.minFilter=n.LinearFilter,a.texture.magFilter=n.LinearFilter,a.texture.name=o;let i;e&&(i=a.texture.clone(),i.isRenderTargetTexture=!0,a.depthVelocity=i,a.textures.push(i));let r;return t&&(r=a.texture.clone(),r.isRenderTargetTexture=!0,r.format=n.RedFormat,a.shadowLength=r,a.textures.push(r)),Object.assign(a,{depthVelocity:i??null,shadowLength:r??null})}class He extends le{constructor({parameterUniforms:e,layerUniforms:t,atmosphereUniforms:a,...i},r){super("CloudsPass",i),this.atmosphere=r,this.width=0,this.height=0,this.currentMaterial=new g({parameterUniforms:e,layerUniforms:t,atmosphereUniforms:a},r),this.currentPass=new C.ShaderPass(this.currentMaterial),this.resolveMaterial=new k,this.resolvePass=new C.ShaderPass(this.resolveMaterial),this.initRenderTargets({depthVelocity:!0,shadowLength:l.lightShafts})}copyCameraSettings(e){this.currentMaterial.copyCameraSettings(e)}initialize(e,t,a){this.currentPass.initialize(e,t,a),this.resolvePass.initialize(e,t,a)}initRenderTargets(e){var s,c,h;(s=this.currentRenderTarget)==null||s.dispose(),(c=this.resolveRenderTarget)==null||c.dispose(),(h=this.historyRenderTarget)==null||h.dispose();const t=W("Clouds",e),a=W("Clouds.A",{...e,depthVelocity:!1}),i=W("Clouds.B",{...e,depthVelocity:!1});this.currentRenderTarget=t,this.resolveRenderTarget=a,this.historyRenderTarget=i;const r=this.resolveMaterial.uniforms;r.colorBuffer.value=t.texture,r.depthVelocityBuffer.value=t.depthVelocity,r.shadowLengthBuffer.value=t.shadowLength,r.colorHistoryBuffer.value=i.texture,r.shadowLengthHistoryBuffer.value=i.shadowLength}copyShadow(){const e=this.shadow,t=this.currentMaterial.uniforms;for(let a=0;a<e.cascadeCount;++a){const i=e.cascades[a];t.shadowIntervals.value[a].copy(i.interval),t.shadowMatrices.value[a].copy(i.matrix)}t.shadowFar.value=e.far}copyReprojection(){this.currentMaterial.copyReprojectionMatrix(this.mainCamera)}swapBuffers(){const e=this.historyRenderTarget,t=this.resolveRenderTarget;this.resolveRenderTarget=e,this.historyRenderTarget=t;const a=this.resolveMaterial.uniforms;a.colorHistoryBuffer.value=t.texture,a.shadowLengthHistoryBuffer.value=t.shadowLength}update(e,t,a){this.currentMaterial.uniforms.frame.value=t,this.resolveMaterial.uniforms.frame.value=t,this.copyCameraSettings(this.mainCamera),this.copyShadow(),this.currentPass.render(e,null,this.currentRenderTarget),this.resolvePass.render(e,null,this.resolveRenderTarget),this.copyReprojection(),this.swapBuffers()}setSize(e,t){if(this.width=e,this.height=t,this.temporalUpscale){const a=Math.ceil(e/4),i=Math.ceil(t/4);this.currentRenderTarget.setSize(a,i),this.currentMaterial.setSize(a*4,i*4,e,t)}else this.currentRenderTarget.setSize(e,t),this.currentMaterial.setSize(e,t);this.resolveRenderTarget.setSize(e,t),this.resolveMaterial.setSize(e,t),this.historyRenderTarget.setSize(e,t)}setShadowSize(e,t,a){this.currentMaterial.shadowCascadeCount=a,this.currentMaterial.setShadowSize(e,t)}setDepthTexture(e,t){this.currentMaterial.depthBuffer=e,this.currentMaterial.depthPacking=t??0}get outputBuffer(){return this.historyRenderTarget.texture}get shadowBuffer(){return this.currentMaterial.uniforms.shadowBuffer.value}set shadowBuffer(e){this.currentMaterial.uniforms.shadowBuffer.value=e}get shadowLengthBuffer(){return this.historyRenderTarget.shadowLength}get temporalUpscale(){return this.currentMaterial.temporalUpscale}set temporalUpscale(e){e!==this.temporalUpscale&&(this.currentMaterial.temporalUpscale=e,this.resolveMaterial.temporalUpscale=e,this.setSize(this.width,this.height))}get lightShafts(){return this.currentMaterial.shadowLength}set lightShafts(e){e!==this.lightShafts&&(this.currentMaterial.shadowLength=e,this.resolveMaterial.shadowLength=e,this.initRenderTargets({depthVelocity:!0,shadowLength:e}),this.setSize(this.width,this.height))}}function Fe(o,e){const t=o.properties.get(e.texture).__webglTexture,a=o.getContext();D(a instanceof WebGL2RenderingContext),o.setRenderTarget(e);const i=[];if(t!=null)for(let r=0;r<e.depth;++r)a.framebufferTextureLayer(a.FRAMEBUFFER,a.COLOR_ATTACHMENT0+r,t,0,r),i.push(a.COLOR_ATTACHMENT0+r);a.drawBuffers(i)}class ae extends C.ShaderPass{render(e,t,a,i,r){const s=this.fullscreenMaterial.uniforms;t!==null&&(s==null?void 0:s[this.input])!=null&&(s[this.input].value=t.texture),Fe(e,a),e.render(this.scene,this.camera)}}const ze=`precision highp float;
1778
+ precision highp sampler3D;
1779
+
1780
+ #include <common>
1781
+
1782
+ #include "core/math"
1783
+ #include "core/raySphereIntersection"
1784
+ #include "types"
1785
+ #include "parameters"
1786
+ #include "structuredSampling"
1787
+ #include "clouds"
1788
+
1789
+ uniform mat4 inverseShadowMatrices[CASCADE_COUNT];
1790
+ uniform mat4 reprojectionMatrices[CASCADE_COUNT];
1791
+
1792
+ // Primary raymarch
1793
+ uniform int maxIterationCount;
1794
+ uniform float minStepSize;
1795
+ uniform float maxStepSize;
1796
+ uniform float opticalDepthTailScale;
1797
+
1798
+ in vec2 vUv;
1799
+ in vec3 vEllipsoidCenter;
1800
+
1801
+ layout(location = 0) out vec4 outputColor[CASCADE_COUNT];
1802
+
1803
+ // Redundant notation for prettier.
1804
+ #if CASCADE_COUNT == 1
1805
+ layout(location = 1) out vec3 outputDepthVelocity[CASCADE_COUNT];
1806
+ #elif CASCADE_COUNT == 2
1807
+ layout(location = 2) out vec3 outputDepthVelocity[CASCADE_COUNT];
1808
+ #elif CASCADE_COUNT == 3
1809
+ layout(location = 3) out vec3 outputDepthVelocity[CASCADE_COUNT];
1810
+ #elif CASCADE_COUNT == 4
1811
+ layout(location = 4) out vec3 outputDepthVelocity[CASCADE_COUNT];
1812
+ #endif // CASCADE_COUNT
1813
+
1814
+ vec4 marchClouds(
1815
+ const vec3 rayOrigin,
1816
+ const vec3 rayDirection,
1817
+ const float maxRayDistance,
1818
+ const float jitter,
1819
+ const float mipLevel
1820
+ ) {
1821
+ // Setup structured volume sampling (SVS).
1822
+ // While SVS introduces spatial aliasing, it is indeed temporally stable,
1823
+ // which is important for lower-resolution shadow maps where a flickering
1824
+ // single pixel can be highly noticeable.
1825
+ vec3 normal = getStructureNormal(rayDirection, jitter);
1826
+ float rayDistance;
1827
+ float stepSize;
1828
+ intersectStructuredPlanes(
1829
+ normal,
1830
+ rayOrigin,
1831
+ rayDirection,
1832
+ clamp(maxRayDistance / float(maxIterationCount), minStepSize, maxStepSize),
1833
+ rayDistance,
1834
+ stepSize
1835
+ );
1836
+
1837
+ #ifdef TEMPORAL_JITTER
1838
+ rayDistance -= stepSize * jitter;
1839
+ #endif // TEMPORAL_JITTER
1840
+
1841
+ float extinctionSum = 0.0;
1842
+ float maxOpticalDepth = 0.0;
1843
+ float maxOpticalDepthTail = 0.0;
1844
+ float transmittanceIntegral = 1.0;
1845
+ float weightedDistanceSum = 0.0;
1846
+ float transmittanceSum = 0.0;
1847
+
1848
+ int sampleCount = 0;
1849
+ for (int i = 0; i < maxIterationCount; ++i) {
1850
+ if (rayDistance > maxRayDistance) {
1851
+ break; // Termination
1852
+ }
1853
+
1854
+ vec3 position = rayDistance * rayDirection + rayOrigin;
1855
+ float height = length(position) - bottomRadius;
1856
+
1857
+ #if !defined(DEBUG_MARCH_INTERVALS)
1858
+ if (insideLayerIntervals(height)) {
1859
+ rayDistance += stepSize;
1860
+ continue;
1861
+ }
1862
+ #endif // !defined(DEBUG_MARCH_INTERVALS)
1863
+
1864
+ // Sample rough weather.
1865
+ vec2 uv = getGlobeUv(position);
1866
+ WeatherSample weather = sampleWeather(uv, height, mipLevel);
1867
+
1868
+ if (any(greaterThan(weather.density, vec4(minDensity)))) {
1869
+ // Sample detailed participating media.
1870
+ // Note this assumes an homogeneous medium.
1871
+ MediaSample media = sampleMedia(weather, position, uv, mipLevel, jitter);
1872
+ if (media.extinction > minExtinction) {
1873
+ extinctionSum += media.extinction;
1874
+ maxOpticalDepth += media.extinction * stepSize;
1875
+ transmittanceIntegral *= exp(-media.extinction * stepSize);
1876
+ weightedDistanceSum += rayDistance * transmittanceIntegral;
1877
+ transmittanceSum += transmittanceIntegral;
1878
+ ++sampleCount;
1879
+ }
1880
+ }
1881
+
1882
+ if (transmittanceIntegral <= minTransmittance) {
1883
+ // A large amount of optical depth accumulates in the tail, beyond the
1884
+ // point of minimum transmittance. The expected optical depth seems to
1885
+ // decrease exponentially with the number of samples taken before reaching
1886
+ // the minimum transmittance.
1887
+ // See the discussion here: https://x.com/shotamatsuda/status/1886259549931520437
1888
+ maxOpticalDepthTail = min(
1889
+ opticalDepthTailScale * stepSize * exp(float(1 - sampleCount)),
1890
+ stepSize * 0.5 // Excessive optical depth only introduces aliasing.
1891
+ );
1892
+ break; // Early termination
1893
+ }
1894
+ rayDistance += stepSize;
1895
+ }
1896
+
1897
+ if (sampleCount == 0) {
1898
+ return vec4(maxRayDistance, 0.0, 0.0, 0.0);
1899
+ }
1900
+ float frontDepth = min(weightedDistanceSum / transmittanceSum, maxRayDistance);
1901
+ float meanExtinction = extinctionSum / float(sampleCount);
1902
+ return vec4(frontDepth, meanExtinction, maxOpticalDepth, maxOpticalDepthTail);
1903
+ }
1904
+
1905
+ void getRayNearFar(
1906
+ const vec3 sunPosition,
1907
+ const vec3 rayDirection,
1908
+ out float rayNear,
1909
+ out float rayFar
1910
+ ) {
1911
+ vec4 firstIntersections = raySphereFirstIntersection(
1912
+ sunPosition,
1913
+ rayDirection,
1914
+ vec3(0.0),
1915
+ bottomRadius + vec4(shadowTopHeight, shadowBottomHeight, 0.0, 0.0)
1916
+ );
1917
+ rayNear = max(0.0, firstIntersections.x);
1918
+ rayFar = firstIntersections.y;
1919
+ if (rayFar < 0.0) {
1920
+ rayFar = 1e6;
1921
+ }
1922
+ }
1923
+
1924
+ void cascade(
1925
+ const int cascadeIndex,
1926
+ const float mipLevel,
1927
+ out vec4 outputColor,
1928
+ out vec3 outputDepthVelocity
1929
+ ) {
1930
+ vec2 clip = vUv * 2.0 - 1.0;
1931
+ vec4 point = inverseShadowMatrices[cascadeIndex] * vec4(clip.xy, -1.0, 1.0);
1932
+ point /= point.w;
1933
+ vec3 sunPosition = mat3(inverseEllipsoidMatrix) * point.xyz - vEllipsoidCenter;
1934
+
1935
+ // The sun direction is in ECEF. Since the view matrix is constructed with the
1936
+ // ellipsoid matrix already applied, there's no need to apply the inverse
1937
+ // matrix here.
1938
+ vec3 rayDirection = normalize(-sunDirection);
1939
+ float rayNear;
1940
+ float rayFar;
1941
+ getRayNearFar(sunPosition, rayDirection, rayNear, rayFar);
1942
+
1943
+ vec3 rayOrigin = rayNear * rayDirection + sunPosition;
1944
+ float stbn = getSTBN();
1945
+ vec4 color = marchClouds(rayOrigin, rayDirection, rayFar - rayNear, stbn, mipLevel);
1946
+ outputColor = color;
1947
+
1948
+ // Velocity for temporal resolution.
1949
+ #ifdef TEMPORAL_PASS
1950
+ vec3 frontPosition = color.x * rayDirection + rayOrigin;
1951
+ vec3 frontPositionWorld = mat3(ellipsoidMatrix) * (frontPosition + vEllipsoidCenter);
1952
+ vec4 prevClip = reprojectionMatrices[cascadeIndex] * vec4(frontPositionWorld, 1.0);
1953
+ prevClip /= prevClip.w;
1954
+ vec2 prevUv = prevClip.xy * 0.5 + 0.5;
1955
+ vec2 velocity = (vUv - prevUv) * resolution;
1956
+ outputDepthVelocity = vec3(color.x, velocity);
1957
+ #else // TEMPORAL_PASS
1958
+ outputDepthVelocity = vec3(0.0);
1959
+ #endif // TEMPORAL_PASS
1960
+ }
1961
+
1962
+ // TODO: Calculate from the main camera frustum perhaps?
1963
+ const float mipLevels[4] = float[4](0.0, 0.5, 1.0, 2.0);
1964
+
1965
+ void main() {
1966
+ #pragma unroll_loop_start
1967
+ for (int i = 0; i < 4; ++i) {
1968
+ #if UNROLLED_LOOP_INDEX < CASCADE_COUNT
1969
+ cascade(UNROLLED_LOOP_INDEX, mipLevels[i], outputColor[i], outputDepthVelocity[i]);
1970
+ #endif // UNROLLED_LOOP_INDEX < CASCADE_COUNT
1971
+ }
1972
+ #pragma unroll_loop_end
1973
+ }
1974
+ `,We=`precision highp float;
1975
+
1976
+ uniform vec3 ellipsoidCenter;
1977
+ uniform vec3 altitudeCorrection;
1978
+
1979
+ layout(location = 0) in vec3 position;
1980
+
1981
+ out vec2 vUv;
1982
+ out vec3 vEllipsoidCenter;
1983
+
1984
+ void main() {
1985
+ vUv = position.xy * 0.5 + 0.5;
1986
+ vEllipsoidCenter = ellipsoidCenter + altitudeCorrection;
1987
+
1988
+ gl_Position = vec4(position.xy, 1.0, 1.0);
1989
+ }
1990
+ `,Ge=`// Implements Structured Volume Sampling in fragment shader:
1991
+ // https://github.com/huwb/volsample
1992
+ // Implementation reference:
1993
+ // https://www.shadertoy.com/view/ttVfDc
1994
+
1995
+ void getIcosahedralVertices(const vec3 direction, out vec3 v1, out vec3 v2, out vec3 v3) {
1996
+ // Normalization scalers to fit dodecahedron to unit sphere.
1997
+ const float a = 0.85065080835204; // phi / sqrt(2 + phi)
1998
+ const float b = 0.5257311121191336; // 1 / sqrt(2 + phi)
1999
+
2000
+ // Derive the vertices of icosahedron where triangle intersects the direction.
2001
+ // See: https://www.ppsloan.org/publications/AmbientDice.pdf
2002
+ const float kT = 0.6180339887498948; // 1 / phi
2003
+ const float kT2 = 0.38196601125010515; // 1 / phi^2
2004
+ vec3 absD = abs(direction);
2005
+ float selector1 = dot(absD, vec3(1.0, kT2, -kT));
2006
+ float selector2 = dot(absD, vec3(-kT, 1.0, kT2));
2007
+ float selector3 = dot(absD, vec3(kT2, -kT, 1.0));
2008
+ v1 = selector1 > 0.0 ? vec3(a, b, 0.0) : vec3(-b, 0.0, a);
2009
+ v2 = selector2 > 0.0 ? vec3(0.0, a, b) : vec3(a, -b, 0.0);
2010
+ v3 = selector3 > 0.0 ? vec3(b, 0.0, a) : vec3(0.0, a, -b);
2011
+ vec3 octantSign = sign(direction);
2012
+ v1 *= octantSign;
2013
+ v2 *= octantSign;
2014
+ v3 *= octantSign;
2015
+ }
2016
+
2017
+ void swapIfBigger(inout vec4 a, inout vec4 b) {
2018
+ if (a.w > b.w) {
2019
+ vec4 t = a;
2020
+ a = b;
2021
+ b = t;
2022
+ }
2023
+ }
2024
+
2025
+ void sortVertices(inout vec3 a, inout vec3 b, inout vec3 c) {
2026
+ const vec3 base = vec3(0.5, 0.5, 1.0);
2027
+ vec4 aw = vec4(a, dot(a, base));
2028
+ vec4 bw = vec4(b, dot(b, base));
2029
+ vec4 cw = vec4(c, dot(c, base));
2030
+ swapIfBigger(aw, bw);
2031
+ swapIfBigger(bw, cw);
2032
+ swapIfBigger(aw, bw);
2033
+ a = aw.xyz;
2034
+ b = bw.xyz;
2035
+ c = cw.xyz;
2036
+ }
2037
+
2038
+ vec3 getPentagonalWeights(const vec3 direction, const vec3 v1, const vec3 v2, const vec3 v3) {
2039
+ float d1 = dot(v1, direction);
2040
+ float d2 = dot(v2, direction);
2041
+ float d3 = dot(v3, direction);
2042
+ vec3 w = exp(vec3(d1, d2, d3) * 40.0);
2043
+ return w / (w.x + w.y + w.z);
2044
+ }
2045
+
2046
+ vec3 getStructureNormal(
2047
+ const vec3 direction,
2048
+ const float jitter,
2049
+ out vec3 a,
2050
+ out vec3 b,
2051
+ out vec3 c,
2052
+ out vec3 weights
2053
+ ) {
2054
+ getIcosahedralVertices(direction, a, b, c);
2055
+ sortVertices(a, b, c);
2056
+ weights = getPentagonalWeights(direction, a, b, c);
2057
+ return jitter < weights.x
2058
+ ? a
2059
+ : jitter < weights.x + weights.y
2060
+ ? b
2061
+ : c;
2062
+ }
2063
+
2064
+ vec3 getStructureNormal(const vec3 direction, const float jitter) {
2065
+ vec3 a, b, c, weights;
2066
+ return getStructureNormal(direction, jitter, a, b, c, weights);
2067
+ }
2068
+
2069
+ // Reference: https://github.com/huwb/volsample/blob/master/src/unity/Assets/Shaders/RayMarchCore.cginc
2070
+ void intersectStructuredPlanes(
2071
+ const vec3 normal,
2072
+ const vec3 rayOrigin,
2073
+ const vec3 rayDirection,
2074
+ const float samplePeriod,
2075
+ out float stepOffset,
2076
+ out float stepSize
2077
+ ) {
2078
+ float NoD = dot(rayDirection, normal);
2079
+ stepSize = samplePeriod / abs(NoD);
2080
+
2081
+ // Skips leftover bit to get from rayOrigin to first strata plane.
2082
+ stepOffset = -mod(dot(rayOrigin, normal), samplePeriod) / NoD;
2083
+
2084
+ // mod() gives different results depending on if the arg is negative or
2085
+ // positive. This line makes it consistent, and ensures the first sample is in
2086
+ // front of the viewer.
2087
+ if (stepOffset < 0.0) {
2088
+ stepOffset += stepSize;
2089
+ }
2090
+ }
2091
+ `;var Ve=Object.defineProperty,_=(o,e,t,a)=>{for(var i=void 0,r=o.length-1,s;r>=0;r--)(s=o[r])&&(i=s(e,t,i)||i);return i&&Ve(e,t,i),i};class E extends n.RawShaderMaterial{constructor({parameterUniforms:e,layerUniforms:t,atmosphereUniforms:a}){super({name:"ShadowMaterial",glslVersion:n.GLSL3,vertexShader:We,fragmentShader:u.unrollLoops(u.resolveIncludes(ze,{core:{math:y.math,raySphereIntersection:y.raySphereIntersection},types:B,parameters:oe,structuredSampling:Ge,clouds:re})),uniforms:{...e,...t,...a,inverseShadowMatrices:new n.Uniform(Array.from({length:4},()=>new n.Matrix4)),reprojectionMatrices:new n.Uniform(Array.from({length:4},()=>new n.Matrix4)),resolution:new n.Uniform(new n.Vector2),frame:new n.Uniform(0),stbnTexture:new n.Uniform(null),maxIterationCount:new n.Uniform(l.shadow.maxIterationCount),minStepSize:new n.Uniform(l.shadow.minStepSize),maxStepSize:new n.Uniform(l.shadow.maxStepSize),minDensity:new n.Uniform(l.shadow.minDensity),minExtinction:new n.Uniform(l.shadow.minExtinction),minTransmittance:new n.Uniform(l.shadow.minTransmittance),opticalDepthTailScale:new n.Uniform(2)},defines:{SHADOW:"1",TEMPORAL_PASS:"1",TEMPORAL_JITTER:"1"}}),this.localWeatherChannels="rgba",this.cascadeCount=l.shadow.cascadeCount,this.temporalPass=!0,this.temporalJitter=!0,this.shapeDetail=l.shapeDetail,this.turbulence=l.turbulence,this.cascadeCount=l.shadow.cascadeCount}setSize(e,t){this.uniforms.resolution.value.set(e,t)}}_([u.defineExpression("LOCAL_WEATHER_CHANNELS",{validate:o=>/^[rgba]{4}$/.test(o)})],E.prototype,"localWeatherChannels");_([u.defineInt("CASCADE_COUNT",{min:1,max:4})],E.prototype,"cascadeCount");_([u.define("TEMPORAL_PASS")],E.prototype,"temporalPass");_([u.define("TEMPORAL_JITTER")],E.prototype,"temporalJitter");_([u.define("SHAPE_DETAIL")],E.prototype,"shapeDetail");_([u.define("TURBULENCE")],E.prototype,"turbulence");const Be=`precision highp float;
2092
+ precision highp sampler2DArray;
2093
+
2094
+ #define VARIANCE_9_SAMPLES (1)
2095
+ #define VARIANCE_SAMPLER_ARRAY (1)
2096
+
2097
+ #include "varianceClipping"
2098
+
2099
+ uniform sampler2DArray inputBuffer;
2100
+ uniform sampler2DArray historyBuffer;
2101
+
2102
+ uniform vec2 texelSize;
2103
+ uniform float varianceGamma;
2104
+ uniform float temporalAlpha;
2105
+
2106
+ in vec2 vUv;
2107
+
2108
+ layout(location = 0) out vec4 outputColor[CASCADE_COUNT];
2109
+
2110
+ const ivec2 neighborOffsets[9] = ivec2[9](
2111
+ ivec2(-1, -1),
2112
+ ivec2(-1, 0),
2113
+ ivec2(-1, 1),
2114
+ ivec2(0, -1),
2115
+ ivec2(0, 0),
2116
+ ivec2(0, 1),
2117
+ ivec2(1, -1),
2118
+ ivec2(1, 0),
2119
+ ivec2(1, 1)
2120
+ );
2121
+
2122
+ vec4 getClosestFragment(const ivec3 coord) {
2123
+ vec4 result = vec4(1e7, 0.0, 0.0, 0.0);
2124
+ vec4 neighbor;
2125
+ #pragma unroll_loop_start
2126
+ for (int i = 0; i < 9; ++i) {
2127
+ neighbor = texelFetchOffset(
2128
+ inputBuffer,
2129
+ coord + ivec3(0, 0, CASCADE_COUNT),
2130
+ 0,
2131
+ neighborOffsets[i]
2132
+ );
2133
+ if (neighbor.r < result.r) {
2134
+ result = neighbor;
2135
+ }
2136
+ }
2137
+ #pragma unroll_loop_end
2138
+ return result;
2139
+ }
2140
+
2141
+ void cascade(const int cascadeIndex, out vec4 outputColor) {
2142
+ ivec3 coord = ivec3(gl_FragCoord.xy, cascadeIndex);
2143
+ vec4 current = texelFetch(inputBuffer, coord, 0);
2144
+
2145
+ vec4 depthVelocity = getClosestFragment(coord);
2146
+ vec2 velocity = depthVelocity.gb * texelSize;
2147
+ vec2 prevUv = vUv - velocity;
2148
+ if (prevUv.x < 0.0 || prevUv.x > 1.0 || prevUv.y < 0.0 || prevUv.y > 1.0) {
2149
+ outputColor = current;
2150
+ return; // Rejection
2151
+ }
2152
+
2153
+ vec4 history = texture(historyBuffer, vec3(prevUv, float(cascadeIndex)));
2154
+ vec4 clippedHistory = varianceClipping(inputBuffer, coord, current, history, varianceGamma);
2155
+ outputColor = mix(clippedHistory, current, temporalAlpha);
2156
+ }
2157
+
2158
+ void main() {
2159
+ #pragma unroll_loop_start
2160
+ for (int i = 0; i < 4; ++i) {
2161
+ #if UNROLLED_LOOP_INDEX < CASCADE_COUNT
2162
+ cascade(UNROLLED_LOOP_INDEX, outputColor[i]);
2163
+ #endif // UNROLLED_LOOP_INDEX < CASCADE_COUNT
2164
+ }
2165
+ #pragma unroll_loop_end
2166
+ }
2167
+ `,ke=`precision highp float;
2168
+
2169
+ layout(location = 0) in vec3 position;
2170
+
2171
+ out vec2 vUv;
2172
+
2173
+ void main() {
2174
+ vUv = position.xy * 0.5 + 0.5;
2175
+ gl_Position = vec4(position.xy, 1.0, 1.0);
2176
+ }
2177
+ `;var je=Object.defineProperty,Ye=(o,e,t,a)=>{for(var i=void 0,r=o.length-1,s;r>=0;r--)(s=o[r])&&(i=s(e,t,i)||i);return i&&je(e,t,i),i};class ue extends n.RawShaderMaterial{constructor({inputBuffer:e=null,historyBuffer:t=null}={}){super({name:"ShadowResolveMaterial",glslVersion:n.GLSL3,vertexShader:ke,fragmentShader:u.unrollLoops(u.resolveIncludes(Be,{varianceClipping:se})),uniforms:{inputBuffer:new n.Uniform(e),historyBuffer:new n.Uniform(t),texelSize:new n.Uniform(new n.Vector2),varianceGamma:new n.Uniform(1),temporalAlpha:new n.Uniform(.01)},defines:{}}),this.cascadeCount=l.shadow.cascadeCount}setSize(e,t){this.uniforms.texelSize.value.set(1/e,1/t)}}Ye([u.defineInt("CASCADE_COUNT",{min:1,max:4})],ue.prototype,"cascadeCount");function G(o){const e=new n.WebGLArrayRenderTarget(1,1,1,{depthBuffer:!1,stencilBuffer:!1});return e.texture.type=n.HalfFloatType,e.texture.minFilter=n.LinearFilter,e.texture.magFilter=n.LinearFilter,e.texture.name=o,e}class qe extends le{constructor({parameterUniforms:e,layerUniforms:t,atmosphereUniforms:a,...i}){super("ShadowPass",i),this.width=0,this.height=0,this.currentMaterial=new E({parameterUniforms:e,layerUniforms:t,atmosphereUniforms:a}),this.currentPass=new ae(this.currentMaterial),this.resolveMaterial=new ue,this.resolvePass=new ae(this.resolveMaterial),this.initRenderTargets()}initialize(e,t,a){this.currentPass.initialize(e,t,a),this.resolvePass.initialize(e,t,a)}initRenderTargets(){var r,s,c;(r=this.currentRenderTarget)==null||r.dispose(),(s=this.resolveRenderTarget)==null||s.dispose(),(c=this.historyRenderTarget)==null||c.dispose();const e=G("Shadow"),t=this.temporalPass?G("Shadow.A"):null,a=this.temporalPass?G("Shadow.B"):null;this.currentRenderTarget=e,this.resolveRenderTarget=t,this.historyRenderTarget=a;const i=this.resolveMaterial.uniforms;i.inputBuffer.value=e.texture,i.historyBuffer.value=(a==null?void 0:a.texture)??null}copyShadow(){const e=this.shadow,t=this.currentMaterial.uniforms;for(let a=0;a<e.cascadeCount;++a){const i=e.cascades[a];t.inverseShadowMatrices.value[a].copy(i.inverseMatrix)}}copyReprojection(){const e=this.shadow,t=this.currentMaterial.uniforms;for(let a=0;a<e.cascadeCount;++a){const i=e.cascades[a];t.reprojectionMatrices.value[a].copy(i.matrix)}}swapBuffers(){D(this.historyRenderTarget!=null),D(this.resolveRenderTarget!=null);const e=this.historyRenderTarget,t=this.resolveRenderTarget;this.resolveRenderTarget=e,this.historyRenderTarget=t,this.resolveMaterial.uniforms.historyBuffer.value=t.texture}update(e,t,a){this.currentMaterial.uniforms.frame.value=t,this.copyShadow(),this.currentPass.render(e,null,this.currentRenderTarget),this.temporalPass&&(D(this.resolveRenderTarget!=null),this.resolvePass.render(e,null,this.resolveRenderTarget),this.copyReprojection(),this.swapBuffers())}setSize(e,t,a=this.shadow.cascadeCount){var i,r;this.width=e,this.height=t,this.currentMaterial.cascadeCount=a,this.resolveMaterial.cascadeCount=a,this.currentMaterial.setSize(e,t),this.resolveMaterial.setSize(e,t),this.currentRenderTarget.setSize(e,t,this.temporalPass?a*2:a),(i=this.resolveRenderTarget)==null||i.setSize(e,t,a),(r=this.historyRenderTarget)==null||r.setSize(e,t,a)}get outputBuffer(){return this.temporalPass?(D(this.historyRenderTarget!=null),this.historyRenderTarget.texture):this.currentRenderTarget.texture}get temporalPass(){return this.currentMaterial.temporalPass}set temporalPass(e){e!==this.temporalPass&&(this.currentMaterial.temporalPass=e,this.initRenderTargets(),this.setSize(this.width,this.height))}}function Ze(o){return{scatteringCoefficient:new n.Uniform(1),absorptionCoefficient:new n.Uniform(0),coverage:new n.Uniform(.3),localWeatherTexture:new n.Uniform(o.localWeatherTexture),localWeatherRepeat:new n.Uniform(o.localWeatherRepeat),localWeatherOffset:new n.Uniform(o.localWeatherOffset),shapeTexture:new n.Uniform(o.shapeTexture),shapeRepeat:new n.Uniform(o.shapeRepeat),shapeOffset:new n.Uniform(o.shapeOffset),shapeDetailTexture:new n.Uniform(o.shapeDetailTexture),shapeDetailRepeat:new n.Uniform(o.shapeDetailRepeat),shapeDetailOffset:new n.Uniform(o.shapeDetailOffset),turbulenceTexture:new n.Uniform(o.turbulenceTexture),turbulenceRepeat:new n.Uniform(o.turbulenceRepeat),turbulenceDisplacement:new n.Uniform(350)}}function Ke(){return{minLayerHeights:new n.Uniform(new n.Vector4),maxLayerHeights:new n.Uniform(new n.Vector4),minIntervalHeights:new n.Uniform(new n.Vector3),maxIntervalHeights:new n.Uniform(new n.Vector3),densityScales:new n.Uniform(new n.Vector4),shapeAmounts:new n.Uniform(new n.Vector4),shapeDetailAmounts:new n.Uniform(new n.Vector4),weatherExponents:new n.Uniform(new n.Vector4),shapeAlteringBiases:new n.Uniform(new n.Vector4),coverageFilterWidths:new n.Uniform(new n.Vector4),minHeight:new n.Uniform(0),maxHeight:new n.Uniform(0),shadowTopHeight:new n.Uniform(0),shadowBottomHeight:new n.Uniform(0),shadowLayerMask:new n.Uniform(new n.Vector4),densityProfile:new n.Uniform({expTerms:new n.Vector4,exponents:new n.Vector4,linearTerms:new n.Vector4,constantTerms:new n.Vector4})}}const V=[0,0,0,0];function Xe(o,e){e.packValues("altitude",o.minLayerHeights.value),e.packSums("altitude","height",o.maxLayerHeights.value),e.packIntervalHeights(o.minIntervalHeights.value,o.maxIntervalHeights.value),e.packValues("densityScale",o.densityScales.value),e.packValues("shapeAmount",o.shapeAmounts.value),e.packValues("shapeDetailAmount",o.shapeDetailAmounts.value),e.packValues("weatherExponent",o.weatherExponents.value),e.packValues("shapeAlteringBias",o.shapeAlteringBiases.value),e.packValues("coverageFilterWidth",o.coverageFilterWidths.value);const t=o.densityProfile.value;e.packDensityProfiles("expTerm",t.expTerms),e.packDensityProfiles("exponent",t.exponents),e.packDensityProfiles("linearTerm",t.linearTerms),e.packDensityProfiles("constantTerm",t.constantTerms);let a=1/0,i=0,r=1/0,s=0;V.fill(0);for(let c=0;c<e.length;++c){const{altitude:h,height:d,shadow:p}=e[c],f=h+d;d>0&&(h<a&&(a=h),p&&h<r&&(r=h),f>i&&(i=f),p&&f>s&&(s=f)),V[c]=p?1:0}a!==1/0?(o.minHeight.value=a,o.maxHeight.value=i):(D(i===0),o.minHeight.value=0),r!==1/0?(o.shadowBottomHeight.value=r,o.shadowTopHeight.value=s):(D(s===0),o.shadowBottomHeight.value=0),o.shadowLayerMask.value.fromArray(V)}function $e(o,e){return{bottomRadius:new n.Uniform(o.bottomRadius),topRadius:new n.Uniform(o.topRadius),ellipsoidCenter:new n.Uniform(e.ellipsoidCenter),ellipsoidMatrix:new n.Uniform(e.ellipsoidMatrix),inverseEllipsoidMatrix:new n.Uniform(e.inverseEllipsoidMatrix),altitudeCorrection:new n.Uniform(e.altitudeCorrection),sunDirection:new n.Uniform(e.sunDirection)}}const Je=`uniform sampler2D cloudsBuffer;
2178
+
2179
+ void mainImage(const vec4 inputColor, const vec2 uv, out vec4 outputColor) {
2180
+ #ifdef SKIP_RENDERING
2181
+ outputColor = inputColor;
2182
+ #else // SKIP_RENDERING
2183
+ vec4 clouds = texture(cloudsBuffer, uv);
2184
+ outputColor.rgb = inputColor.rgb * (1.0 - clouds.a) + clouds.rgb;
2185
+ outputColor.a = inputColor.a * (1.0 - clouds.a) + clouds.a;
2186
+ #endif // SKIP_RENDERING
2187
+ }
2188
+ `;var Qe=Object.defineProperty,et=(o,e,t,a)=>{for(var i=void 0,r=o.length-1,s;r>=0;r--)(s=o[r])&&(i=s(e,t,i)||i);return i&&Qe(e,t,i),i};const O=new n.Vector3,tt=new n.Vector2,nt=["maxIterationCount","minStepSize","maxStepSize","maxRayDistance","perspectiveStepScale","minDensity","minExtinction","minTransmittance","maxIterationCountToSun","maxIterationCountToGround","minSecondaryStepSize","secondaryStepScale","maxShadowFilterRadius","maxShadowLengthIterationCount","minShadowLengthStepSize","maxShadowLengthRayDistance","hazeDensityScale","hazeExponent"],at=["multiScatteringOctaves","accurateSunSkyIrradiance","accuratePhaseFunction"],it=["maxIterationCount","minStepSize","maxStepSize","minDensity","minExtinction","minTransmittance","opticalDepthTailScale"],rt=["temporalJitter"],ot=["temporalPass"],st=["cascadeCount","mapSize","maxFar","farScale","splitMode","splitLambda"],w={type:"change"},he={resolutionScale:l.resolutionScale,width:C.Resolution.AUTO_SIZE,height:C.Resolution.AUTO_SIZE};class de extends C.Effect{constructor(e=new n.Camera,t,a=H.AtmosphereParameters.DEFAULT){var p,f,m,v;super("CloudsEffect",Je,{attributes:C.EffectAttribute.DEPTH,uniforms:new Map([["cloudsBuffer",new n.Uniform(null)]])}),this.camera=e,this.atmosphere=a,this.cloudLayers=F.DEFAULT.clone(),this.correctAltitude=!0,this.localWeatherRepeat=new n.Vector2().setScalar(100),this.localWeatherOffset=new n.Vector2,this.shapeRepeat=new n.Vector3().setScalar(3e-4),this.shapeOffset=new n.Vector3,this.shapeDetailRepeat=new n.Vector3().setScalar(.006),this.shapeDetailOffset=new n.Vector3,this.turbulenceRepeat=new n.Vector2().setScalar(20),this.ellipsoidCenter=new n.Vector3,this.ellipsoidMatrix=new n.Matrix4,this.inverseEllipsoidMatrix=new n.Matrix4,this.altitudeCorrection=new n.Vector3,this.sunDirection=new n.Vector3,this.localWeatherVelocity=new n.Vector2,this.shapeVelocity=new n.Vector3,this.shapeDetailVelocity=new n.Vector3,this._atmosphereOverlay=null,this._atmosphereShadow=null,this._atmosphereShadowLength=null,this.events=new n.EventDispatcher,this.frame=0,this.shadowCascadeCount=0,this.shadowMapSize=new n.Vector2,this.onResolutionChange=()=>{this.setSize(this.resolution.baseWidth,this.resolution.baseHeight)},this.skipRendering=!0;const{resolutionScale:i,width:r,height:s,resolutionX:c=r,resolutionY:h=s}={...he,...t};this.shadowMaps=new Ee({cascadeCount:l.shadow.cascadeCount,mapSize:l.shadow.mapSize,splitLambda:.6}),this.parameterUniforms=Ze({localWeatherTexture:((p=this.proceduralLocalWeather)==null?void 0:p.texture)??null,localWeatherRepeat:this.localWeatherRepeat,localWeatherOffset:this.localWeatherOffset,shapeTexture:((f=this.proceduralShape)==null?void 0:f.texture)??null,shapeRepeat:this.shapeRepeat,shapeOffset:this.shapeOffset,shapeDetailTexture:((m=this.proceduralShapeDetail)==null?void 0:m.texture)??null,shapeDetailRepeat:this.shapeDetailRepeat,shapeDetailOffset:this.shapeDetailOffset,turbulenceTexture:((v=this.proceduralTurbulence)==null?void 0:v.texture)??null,turbulenceRepeat:this.turbulenceRepeat}),this.layerUniforms=Ke(),this.atmosphereUniforms=$e(a,{ellipsoidCenter:this.ellipsoidCenter,ellipsoidMatrix:this.ellipsoidMatrix,inverseEllipsoidMatrix:this.inverseEllipsoidMatrix,altitudeCorrection:this.altitudeCorrection,sunDirection:this.sunDirection});const d={shadow:this.shadowMaps,parameterUniforms:this.parameterUniforms,layerUniforms:this.layerUniforms,atmosphereUniforms:this.atmosphereUniforms};this.shadowPass=new qe(d),this.cloudsPass=new He(d,a),this.clouds=u.definePropertyShorthand(u.defineUniformShorthand({},this.cloudsPass.currentMaterial,nt),this.cloudsPass.currentMaterial,at),this.shadow=u.definePropertyShorthand(u.defineUniformShorthand({},this.shadowPass.currentMaterial,it),this.shadowPass.currentMaterial,rt,this.shadowPass,ot,this.shadowMaps,st),this.resolution=new C.Resolution(this,c,h,i),this.resolution.addEventListener("change",this.onResolutionChange)}get mainCamera(){return this.camera}set mainCamera(e){this.camera=e,this.shadowPass.mainCamera=e,this.cloudsPass.mainCamera=e}initialize(e,t,a){this.shadowPass.initialize(e,t,a),this.cloudsPass.initialize(e,t,a)}updateSharedUniforms(e){Xe(this.layerUniforms,this.cloudLayers);const{parameterUniforms:t}=this;t.localWeatherOffset.value.add(tt.copy(this.localWeatherVelocity).multiplyScalar(e)),t.shapeOffset.value.add(O.copy(this.shapeVelocity).multiplyScalar(e)),t.shapeDetailOffset.value.add(O.copy(this.shapeDetailVelocity).multiplyScalar(e));const a=this.inverseEllipsoidMatrix.copy(this.ellipsoidMatrix).invert(),i=this.camera.getWorldPosition(O).applyMatrix4(a).sub(this.ellipsoidCenter),r=this.altitudeCorrection;this.correctAltitude?H.getAltitudeCorrectionOffset(i,this.atmosphere.bottomRadius,this.ellipsoid,r,!1):r.setScalar(0);const s=this.ellipsoid.getSurfaceNormal(i,O),c=this.sunDirection.dot(s),h=u.lerp(1e6,1e3,c);this.shadowMaps.update(this.camera,O.copy(this.sunDirection).applyMatrix4(this.ellipsoidMatrix),h)}updateWeatherTextureChannels(){const e=this.cloudLayers.localWeatherChannels;this.cloudsPass.currentMaterial.localWeatherChannels=e,this.shadowPass.currentMaterial.localWeatherChannels=e}updateAtmosphereComposition(){const{shadowMaps:e,shadowPass:t,cloudsPass:a}=this,i=t.currentMaterial.uniforms,r=a.currentMaterial.uniforms,s=this._atmosphereOverlay,c=Object.assign(this._atmosphereOverlay??{},{map:a.outputBuffer});s!==c&&(this._atmosphereOverlay=c,w.target=this,w.property="atmosphereOverlay",this.events.dispatchEvent(w));const h=this._atmosphereShadow,d=Object.assign(this._atmosphereShadow??{},{map:t.outputBuffer,mapSize:e.mapSize,cascadeCount:e.cascadeCount,intervals:r.shadowIntervals.value,matrices:r.shadowMatrices.value,inverseMatrices:i.inverseShadowMatrices.value,far:e.far,topHeight:r.shadowTopHeight.value});h!==d&&(this._atmosphereShadow=d,w.target=this,w.property="atmosphereShadow",this.events.dispatchEvent(w));const p=this._atmosphereShadowLength,f=a.shadowLengthBuffer!=null?Object.assign(this._atmosphereShadowLength??{},{map:a.shadowLengthBuffer}):null;p!==f&&(this._atmosphereShadowLength=f,w.target=this,w.property="atmosphereShadowLength",this.events.dispatchEvent(w))}update(e,t,a=0){var c,h,d,p;const{shadowMaps:i,shadowPass:r,cloudsPass:s}=this;if(i.cascadeCount!==this.shadowCascadeCount||!i.mapSize.equals(this.shadowMapSize)){const{width:f,height:m}=i.mapSize,v=i.cascadeCount;this.shadowMapSize.set(f,m),this.shadowCascadeCount=v,r.setSize(f,m,v),s.setShadowSize(f,m,v)}(c=this.proceduralLocalWeather)==null||c.render(e,a),(h=this.proceduralShape)==null||h.render(e,a),(d=this.proceduralShapeDetail)==null||d.render(e,a),(p=this.proceduralTurbulence)==null||p.render(e,a),++this.frame,this.updateSharedUniforms(a),this.updateWeatherTextureChannels(),r.update(e,this.frame,a),s.shadowBuffer=r.outputBuffer,s.update(e,this.frame,a),this.updateAtmosphereComposition(),this.uniforms.get("cloudsBuffer").value=this.cloudsPass.outputBuffer}setSize(e,t){const{resolution:a}=this;a.setBaseSize(e,t);const{width:i,height:r}=a;this.cloudsPass.setSize(i,r)}setDepthTexture(e,t){this.shadowPass.setDepthTexture(e,t),this.cloudsPass.setDepthTexture(e,t)}set qualityPreset(e){const{clouds:t,shadow:a,...i}=_e[e];Object.assign(this,i),Object.assign(this.clouds,t),Object.assign(this.shadow,a)}get localWeatherTexture(){return this.proceduralLocalWeather??this.parameterUniforms.localWeatherTexture.value}set localWeatherTexture(e){e instanceof n.Texture||e==null?(this.proceduralLocalWeather=void 0,this.parameterUniforms.localWeatherTexture.value=e):(this.proceduralLocalWeather=e,this.parameterUniforms.localWeatherTexture.value=e.texture)}get shapeTexture(){return this.proceduralShape??this.parameterUniforms.shapeTexture.value}set shapeTexture(e){e instanceof n.Data3DTexture||e==null?(this.proceduralShape=void 0,this.parameterUniforms.shapeTexture.value=e):(this.proceduralShape=e,this.parameterUniforms.shapeTexture.value=e.texture)}get shapeDetailTexture(){return this.proceduralShapeDetail??this.parameterUniforms.shapeDetailTexture.value}set shapeDetailTexture(e){e instanceof n.Data3DTexture||e==null?(this.proceduralShapeDetail=void 0,this.parameterUniforms.shapeDetailTexture.value=e):(this.proceduralShapeDetail=e,this.parameterUniforms.shapeDetailTexture.value=e.texture)}get turbulenceTexture(){return this.proceduralTurbulence??this.parameterUniforms.turbulenceTexture.value}set turbulenceTexture(e){e instanceof n.Texture||e==null?(this.proceduralTurbulence=void 0,this.parameterUniforms.turbulenceTexture.value=e):(this.proceduralTurbulence=e,this.parameterUniforms.turbulenceTexture.value=e.texture)}get stbnTexture(){return this.cloudsPass.currentMaterial.uniforms.stbnTexture.value}set stbnTexture(e){this.cloudsPass.currentMaterial.uniforms.stbnTexture.value=e,this.shadowPass.currentMaterial.uniforms.stbnTexture.value=e}get resolutionScale(){return this.resolution.scale}set resolutionScale(e){this.resolution.scale=e}get temporalUpscale(){return this.cloudsPass.temporalUpscale}set temporalUpscale(e){this.cloudsPass.temporalUpscale=e}get lightShafts(){return this.cloudsPass.lightShafts}set lightShafts(e){this.cloudsPass.lightShafts=e}get shapeDetail(){return this.cloudsPass.currentMaterial.shapeDetail}set shapeDetail(e){this.cloudsPass.currentMaterial.shapeDetail=e,this.shadowPass.currentMaterial.shapeDetail=e}get turbulence(){return this.cloudsPass.currentMaterial.turbulence}set turbulence(e){this.cloudsPass.currentMaterial.turbulence=e,this.shadowPass.currentMaterial.turbulence=e}get haze(){return this.cloudsPass.currentMaterial.haze}set haze(e){this.cloudsPass.currentMaterial.haze=e}get scatteringCoefficient(){return this.parameterUniforms.scatteringCoefficient.value}set scatteringCoefficient(e){this.parameterUniforms.scatteringCoefficient.value=e}get absorptionCoefficient(){return this.parameterUniforms.absorptionCoefficient.value}set absorptionCoefficient(e){this.parameterUniforms.absorptionCoefficient.value=e}get coverage(){return this.parameterUniforms.coverage.value}set coverage(e){this.parameterUniforms.coverage.value=e}get turbulenceDisplacement(){return this.parameterUniforms.turbulenceDisplacement.value}set turbulenceDisplacement(e){this.parameterUniforms.turbulenceDisplacement.value=e}get scatterAnisotropy1(){return this.cloudsPass.currentMaterial.scatterAnisotropy1}set scatterAnisotropy1(e){this.cloudsPass.currentMaterial.scatterAnisotropy1=e}get scatterAnisotropy2(){return this.cloudsPass.currentMaterial.scatterAnisotropy2}set scatterAnisotropy2(e){this.cloudsPass.currentMaterial.scatterAnisotropy2=e}get scatterAnisotropyMix(){return this.cloudsPass.currentMaterial.scatterAnisotropyMix}set scatterAnisotropyMix(e){this.cloudsPass.currentMaterial.scatterAnisotropyMix=e}get skyIrradianceScale(){return this.cloudsPass.currentMaterial.uniforms.skyIrradianceScale.value}set skyIrradianceScale(e){this.cloudsPass.currentMaterial.uniforms.skyIrradianceScale.value=e}get groundIrradianceScale(){return this.cloudsPass.currentMaterial.uniforms.groundIrradianceScale.value}set groundIrradianceScale(e){this.cloudsPass.currentMaterial.uniforms.groundIrradianceScale.value=e}get powderScale(){return this.cloudsPass.currentMaterial.uniforms.powderScale.value}set powderScale(e){this.cloudsPass.currentMaterial.uniforms.powderScale.value=e}get powderExponent(){return this.cloudsPass.currentMaterial.uniforms.powderExponent.value}set powderExponent(e){this.cloudsPass.currentMaterial.uniforms.powderExponent.value=e}get atmosphereOverlay(){return this._atmosphereOverlay}get atmosphereShadow(){return this._atmosphereShadow}get atmosphereShadowLength(){return this._atmosphereShadowLength}get irradianceTexture(){return this.cloudsPass.currentMaterial.irradianceTexture}set irradianceTexture(e){this.cloudsPass.currentMaterial.irradianceTexture=e}get scatteringTexture(){return this.cloudsPass.currentMaterial.scatteringTexture}set scatteringTexture(e){this.cloudsPass.currentMaterial.scatteringTexture=e}get transmittanceTexture(){return this.cloudsPass.currentMaterial.transmittanceTexture}set transmittanceTexture(e){this.cloudsPass.currentMaterial.transmittanceTexture=e}get useHalfFloat(){return this.cloudsPass.currentMaterial.useHalfFloat}set useHalfFloat(e){this.cloudsPass.currentMaterial.useHalfFloat=e}get ellipsoid(){return this.cloudsPass.currentMaterial.ellipsoid}set ellipsoid(e){this.cloudsPass.currentMaterial.ellipsoid=e}get photometric(){return this.cloudsPass.currentMaterial.photometric}set photometric(e){this.cloudsPass.currentMaterial.photometric=e}get sunAngularRadius(){return this.cloudsPass.currentMaterial.sunAngularRadius}set sunAngularRadius(e){this.cloudsPass.currentMaterial.sunAngularRadius=e}}et([u.define("SKIP_RENDERING")],de.prototype,"skipRendering");const ct=128,lt=32,z="45a1c6c1bb9fd38b3680fd120795ff4c32df68ff",ut=`https://media.githubusercontent.com/media/takram-design-engineering/three-geospatial/${z}/packages/clouds/assets/local_weather.png`,ht=`https://media.githubusercontent.com/media/takram-design-engineering/three-geospatial/${z}/packages/clouds/assets/shape.bin`,dt=`https://media.githubusercontent.com/media/takram-design-engineering/three-geospatial/${z}/packages/clouds/assets/shape_detail.bin`,pt=`https://media.githubusercontent.com/media/takram-design-engineering/three-geospatial/${z}/packages/clouds/assets/turbulence.png`;exports.CLOUD_SHAPE_DETAIL_TEXTURE_SIZE=lt;exports.CLOUD_SHAPE_TEXTURE_SIZE=ct;exports.CloudLayer=x;exports.CloudLayers=F;exports.CloudsEffect=de;exports.DEFAULT_LOCAL_WEATHER_URL=ut;exports.DEFAULT_SHAPE_DETAIL_URL=dt;exports.DEFAULT_SHAPE_URL=ht;exports.DEFAULT_TURBULENCE_URL=pt;exports.DensityProfile=I;exports.cloudsPassOptionsDefaults=he;
2189
+ //# sourceMappingURL=shared.cjs.map