@takram/three-clouds 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takram/three-clouds",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "A Three.js and R3F implementation of geospatial volumetric clouds",
5
5
  "keywords": [
6
6
  "three",
@@ -45,8 +45,8 @@
45
45
  "README.md"
46
46
  ],
47
47
  "dependencies": {
48
- "@takram/three-atmosphere": "0.11.1",
49
- "@takram/three-geospatial": "0.2.0",
48
+ "@takram/three-atmosphere": "0.12.0",
49
+ "@takram/three-geospatial": "0.2.2",
50
50
  "tiny-invariant": "^1.3.3",
51
51
  "type-fest": "^4.34.1"
52
52
  },
@@ -82,7 +82,9 @@ const cloudsUniformKeys = [
82
82
  'minShadowLengthStepSize',
83
83
  'maxShadowLengthRayDistance',
84
84
  'hazeDensityScale',
85
- 'hazeExponent'
85
+ 'hazeExponent',
86
+ 'hazeScatteringCoefficient',
87
+ 'hazeAbsorptionCoefficient'
86
88
  ] as const satisfies Array<keyof CloudsMaterialUniforms>
87
89
 
88
90
  // prettier-ignore
@@ -97,7 +97,6 @@ export interface CloudsMaterialUniforms
97
97
  stbnTexture: Uniform<Data3DTexture | null>
98
98
 
99
99
  // Scattering
100
- albedo: Uniform<Vector3>
101
100
  skyIrradianceScale: Uniform<number>
102
101
  groundIrradianceScale: Uniform<number>
103
102
  powderScale: Uniform<number>
@@ -135,6 +134,8 @@ export interface CloudsMaterialUniforms
135
134
  // Haze
136
135
  hazeDensityScale: Uniform<number>
137
136
  hazeExponent: Uniform<number>
137
+ hazeScatteringCoefficient: Uniform<number>
138
+ hazeAbsorptionCoefficient: Uniform<number>
138
139
  }
139
140
 
140
141
  export class CloudsMaterial extends AtmosphereMaterialBase {
@@ -207,9 +208,8 @@ export class CloudsMaterial extends AtmosphereMaterialBase {
207
208
  stbnTexture: new Uniform(null),
208
209
 
209
210
  // Scattering
210
- albedo: new Uniform(new Vector3()),
211
- skyIrradianceScale: new Uniform(2.5),
212
- groundIrradianceScale: new Uniform(3),
211
+ skyIrradianceScale: new Uniform(1),
212
+ groundIrradianceScale: new Uniform(1),
213
213
  powderScale: new Uniform(0.8),
214
214
  powderExponent: new Uniform(150),
215
215
 
@@ -249,7 +249,9 @@ export class CloudsMaterial extends AtmosphereMaterialBase {
249
249
 
250
250
  // Haze
251
251
  hazeDensityScale: new Uniform(3e-5),
252
- hazeExponent: new Uniform(1e-3)
252
+ hazeExponent: new Uniform(1e-3),
253
+ hazeScatteringCoefficient: new Uniform(0.9),
254
+ hazeAbsorptionCoefficient: new Uniform(0.5),
253
255
  } satisfies Partial<AtmosphereMaterialBaseUniforms> &
254
256
  CloudsMaterialUniforms
255
257
  },
@@ -268,12 +270,16 @@ export class CloudsMaterial extends AtmosphereMaterialBase {
268
270
  // Disable onBeforeRender in AtmosphereMaterialBase because we're rendering
269
271
  // into fullscreen quad with another camera for the scene projection.
270
272
 
271
- const uniforms = this.uniforms
272
- uniforms.albedo.value.setScalar(
273
- uniforms.scatteringCoefficient.value /
274
- (uniforms.absorptionCoefficient.value +
275
- uniforms.scatteringCoefficient.value)
276
- )
273
+ const prevLogarithmicDepthBuffer = this.defines.USE_LOGDEPTHBUF != null
274
+ const nextLogarithmicDepthBuffer =
275
+ renderer.capabilities.logarithmicDepthBuffer
276
+ if (nextLogarithmicDepthBuffer !== prevLogarithmicDepthBuffer) {
277
+ if (nextLogarithmicDepthBuffer) {
278
+ this.defines.USE_LOGDEPTHBUF = '1'
279
+ } else {
280
+ delete this.defines.USE_LOGDEPTHBUF
281
+ }
282
+ }
277
283
 
278
284
  const prevPowder = this.defines.POWDER != null
279
285
  const nextPowder = this.uniforms.powderScale.value > 0
@@ -1,9 +1,10 @@
1
1
  import {
2
- forwardRef,
3
2
  useContext,
4
3
  useEffect,
5
4
  useLayoutEffect,
6
- useState
5
+ useState,
6
+ type FC,
7
+ type Ref
7
8
  } from 'react'
8
9
 
9
10
  import { type ExpandNestedProps } from '@takram/three-geospatial/r3f'
@@ -18,78 +19,79 @@ import { CloudLayersContext } from './CloudLayers'
18
19
  export interface CloudLayerProps
19
20
  extends CloudLayerLike,
20
21
  ExpandNestedProps<CloudLayerLike, 'densityProfile'> {
22
+ ref?: Ref<CloudLayerImpl>
21
23
  index?: number
22
24
  }
23
25
 
24
- export const CloudLayer = forwardRef<CloudLayerImpl, CloudLayerProps>(
25
- function CloudLayer({ index: indexProp, ...props }, forwardedRef) {
26
- const context = useContext(CloudLayersContext)
27
- if (context == null) {
28
- throw new Error(
29
- 'CloudLayer can only be used within the Clouds component!'
30
- )
31
- }
26
+ export const CloudLayer: FC<CloudLayerProps> = ({
27
+ ref: forwardedRef,
28
+ index: indexProp,
29
+ ...props
30
+ }) => {
31
+ const context = useContext(CloudLayersContext)
32
+ if (context == null) {
33
+ throw new Error('CloudLayer can only be used within the Clouds component!')
34
+ }
32
35
 
33
- const { layers, indexPool, disableDefault } = context
34
- const [index, setIndex] = useState<number>()
36
+ const { layers, indexPool, disableDefault } = context
37
+ const [index, setIndex] = useState<number>()
35
38
 
36
- useLayoutEffect(() => {
37
- if (indexProp != null) {
38
- const poolIndex = indexPool.indexOf(indexProp)
39
- if (poolIndex !== -1) {
40
- indexPool.splice(poolIndex, 1)
41
- setIndex(indexProp)
42
- return () => {
43
- indexPool.push(indexProp)
44
- setIndex(undefined)
45
- }
39
+ useLayoutEffect(() => {
40
+ if (indexProp != null) {
41
+ const poolIndex = indexPool.indexOf(indexProp)
42
+ if (poolIndex !== -1) {
43
+ indexPool.splice(poolIndex, 1)
44
+ setIndex(indexProp)
45
+ return () => {
46
+ indexPool.push(indexProp)
47
+ setIndex(undefined)
46
48
  }
47
- } else {
48
- // Sorting is just for predictability. Layer order is still not defined,
49
- // but it doesn't matter.
50
- const index = indexPool.sort((a, b) => a - b).shift()
51
- if (index != null) {
52
- setIndex(index)
53
- return () => {
54
- indexPool.push(index)
55
- setIndex(undefined)
56
- }
57
- }
58
- }
59
- }, [indexProp, layers, indexPool])
60
-
61
- useLayoutEffect(() => {
62
- if (index == null) {
63
- return
64
- }
65
- const layer = layers[index]
66
- return () => {
67
- layer.copy(
68
- disableDefault ? CloudLayerImpl.DEFAULT : CloudLayers.DEFAULT[index]
69
- )
70
- }
71
- }, [layers, index, disableDefault])
72
-
73
- useEffect(() => {
74
- if (index == null) {
75
- return
76
49
  }
77
- if (typeof forwardedRef === 'function') {
78
- forwardedRef(layers[index])
79
- } else if (forwardedRef != null) {
80
- forwardedRef.current = layers[index]
50
+ } else {
51
+ // Sorting is just for predictability. Layer order is still not defined,
52
+ // but it doesn't matter.
53
+ const index = indexPool.sort((a, b) => a - b).shift()
54
+ if (index != null) {
55
+ setIndex(index)
56
+ return () => {
57
+ indexPool.push(index)
58
+ setIndex(undefined)
59
+ }
81
60
  }
82
- }, [forwardedRef, layers, index])
61
+ }
62
+ }, [indexProp, layers, indexPool])
83
63
 
84
- // Surely this resets any modifications made via forwarded ref.
85
- if (index != null) {
86
- const layer = layers[index]
64
+ useLayoutEffect(() => {
65
+ if (index == null) {
66
+ return
67
+ }
68
+ const layer = layers[index]
69
+ return () => {
87
70
  layer.copy(
88
71
  disableDefault ? CloudLayerImpl.DEFAULT : CloudLayers.DEFAULT[index]
89
72
  )
90
- layer.set(props)
91
73
  }
74
+ }, [layers, index, disableDefault])
75
+
76
+ useEffect(() => {
77
+ if (index == null) {
78
+ return
79
+ }
80
+ if (typeof forwardedRef === 'function') {
81
+ forwardedRef(layers[index])
82
+ } else if (forwardedRef != null) {
83
+ forwardedRef.current = layers[index]
84
+ }
85
+ }, [forwardedRef, layers, index])
92
86
 
93
- return null
87
+ // Surely this resets any modifications made via forwarded ref.
88
+ if (index != null) {
89
+ const layer = layers[index]
90
+ layer.copy(
91
+ disableDefault ? CloudLayerImpl.DEFAULT : CloudLayers.DEFAULT[index]
92
+ )
93
+ layer.set(props)
94
94
  }
95
- )
95
+
96
+ return null
97
+ }
@@ -1,12 +1,12 @@
1
1
  import { useFrame, useThree, type ElementProps } from '@react-three/fiber'
2
2
  import { EffectComposerContext } from '@react-three/postprocessing'
3
3
  import {
4
- forwardRef,
5
4
  useCallback,
6
5
  useContext,
7
6
  useEffect,
8
7
  useMemo,
9
- useState
8
+ useState,
9
+ type FC
10
10
  } from 'react'
11
11
  import {
12
12
  LinearFilter,
@@ -154,119 +154,115 @@ export interface CloudsProps
154
154
  stbnTexture?: Data3DTexture | string
155
155
  }
156
156
 
157
- export const Clouds = /*#__PURE__*/ forwardRef<CloudsEffect, CloudsProps>(
158
- function Clouds(
159
- {
160
- disableDefaultLayers = false,
161
- localWeatherTexture: localWeatherTextureProp = DEFAULT_LOCAL_WEATHER_URL,
162
- shapeTexture: shapeTextureProp = DEFAULT_SHAPE_URL,
163
- shapeDetailTexture: shapeDetailTextureProp = DEFAULT_SHAPE_DETAIL_URL,
164
- turbulenceTexture: turbulenceTextureProp = DEFAULT_TURBULENCE_URL,
165
- stbnTexture: stbnTextureProp = DEFAULT_STBN_URL,
166
- children,
167
- ...props
168
- },
169
- forwardedRef
170
- ) {
171
- const { textures, transientStates, ...contextProps } =
172
- useContext(AtmosphereContext)
157
+ export const Clouds: FC<CloudsProps> = ({
158
+ ref: forwardedRef,
159
+ disableDefaultLayers = false,
160
+ localWeatherTexture: localWeatherTextureProp = DEFAULT_LOCAL_WEATHER_URL,
161
+ shapeTexture: shapeTextureProp = DEFAULT_SHAPE_URL,
162
+ shapeDetailTexture: shapeDetailTextureProp = DEFAULT_SHAPE_DETAIL_URL,
163
+ turbulenceTexture: turbulenceTextureProp = DEFAULT_TURBULENCE_URL,
164
+ stbnTexture: stbnTextureProp = DEFAULT_STBN_URL,
165
+ children,
166
+ ...props
167
+ }) => {
168
+ const { textures, transientStates, ...contextProps } =
169
+ useContext(AtmosphereContext)
173
170
 
174
- const [atmosphereParameters, others] = separateProps({
175
- ...cloudsPassOptionsDefaults,
176
- ...contextProps,
177
- ...textures,
178
- ...props
179
- })
171
+ const [atmosphereParameters, others] = separateProps({
172
+ ...cloudsPassOptionsDefaults,
173
+ ...contextProps,
174
+ ...textures,
175
+ ...props
176
+ })
180
177
 
181
- const effect = useMemo(() => new CloudsEffect(), [])
182
- useEffect(() => {
183
- return () => {
184
- effect.dispose()
185
- }
186
- }, [effect])
178
+ const effect = useMemo(() => new CloudsEffect(), [])
179
+ useEffect(() => {
180
+ return () => {
181
+ effect.dispose()
182
+ }
183
+ }, [effect])
187
184
 
188
- useFrame(() => {
189
- if (transientStates != null) {
190
- effect.sunDirection.copy(transientStates.sunDirection)
191
- effect.ellipsoidCenter.copy(transientStates.ellipsoidCenter)
192
- effect.ellipsoidMatrix.copy(transientStates.ellipsoidMatrix)
193
- }
194
- })
185
+ useFrame(() => {
186
+ if (transientStates != null) {
187
+ effect.sunDirection.copy(transientStates.sunDirection)
188
+ effect.ellipsoidCenter.copy(transientStates.ellipsoidCenter)
189
+ effect.ellipsoidMatrix.copy(transientStates.ellipsoidMatrix)
190
+ }
191
+ })
195
192
 
196
- useEffect(() => {
197
- if (transientStates != null) {
198
- transientStates.overlay = effect.atmosphereOverlay
199
- transientStates.shadow = effect.atmosphereShadow
200
- transientStates.shadowLength = effect.atmosphereShadowLength
201
- return () => {
202
- transientStates.overlay = null
203
- transientStates.shadow = null
204
- transientStates.shadowLength = null
205
- }
193
+ useEffect(() => {
194
+ if (transientStates != null) {
195
+ transientStates.overlay = effect.atmosphereOverlay
196
+ transientStates.shadow = effect.atmosphereShadow
197
+ transientStates.shadowLength = effect.atmosphereShadowLength
198
+ return () => {
199
+ transientStates.overlay = null
200
+ transientStates.shadow = null
201
+ transientStates.shadowLength = null
206
202
  }
207
- }, [effect, transientStates])
203
+ }
204
+ }, [effect, transientStates])
208
205
 
209
- const handleChange = useCallback(
210
- (event: CloudsEffectChangeEvent) => {
211
- if (transientStates == null) {
212
- return
213
- }
214
- switch (event.property) {
215
- case 'atmosphereOverlay':
216
- transientStates.overlay = effect.atmosphereOverlay
217
- break
218
- case 'atmosphereShadow':
219
- transientStates.shadow = effect.atmosphereShadow
220
- break
221
- case 'atmosphereShadowLength':
222
- transientStates.shadowLength = effect.atmosphereShadowLength
223
- break
224
- }
225
- },
226
- [effect, transientStates]
227
- )
228
- useEffect(() => {
229
- effect.events.addEventListener('change', handleChange)
230
- return () => {
231
- effect.events.removeEventListener('change', handleChange)
206
+ const handleChange = useCallback(
207
+ (event: CloudsEffectChangeEvent) => {
208
+ if (transientStates == null) {
209
+ return
210
+ }
211
+ switch (event.property) {
212
+ case 'atmosphereOverlay':
213
+ transientStates.overlay = effect.atmosphereOverlay
214
+ break
215
+ case 'atmosphereShadow':
216
+ transientStates.shadow = effect.atmosphereShadow
217
+ break
218
+ case 'atmosphereShadowLength':
219
+ transientStates.shadowLength = effect.atmosphereShadowLength
220
+ break
232
221
  }
233
- }, [effect, handleChange])
222
+ },
223
+ [effect, transientStates]
224
+ )
225
+ useEffect(() => {
226
+ effect.events.addEventListener('change', handleChange)
227
+ return () => {
228
+ effect.events.removeEventListener('change', handleChange)
229
+ }
230
+ }, [effect, handleChange])
234
231
 
235
- const gl = useThree(({ gl }) => gl)
236
- const localWeatherTexture = useTextureState(localWeatherTextureProp, gl)
237
- const shapeTexture = use3DTextureState(
238
- shapeTextureProp,
239
- CLOUD_SHAPE_TEXTURE_SIZE
240
- )
241
- const shapeDetailTexture = use3DTextureState(
242
- shapeDetailTextureProp,
243
- CLOUD_SHAPE_DETAIL_TEXTURE_SIZE
244
- )
245
- const turbulenceTexture = useTextureState(turbulenceTextureProp, gl)
246
- const stbnTexture = useSTBNTextureState(stbnTextureProp)
232
+ const gl = useThree(({ gl }) => gl)
233
+ const localWeatherTexture = useTextureState(localWeatherTextureProp, gl)
234
+ const shapeTexture = use3DTextureState(
235
+ shapeTextureProp,
236
+ CLOUD_SHAPE_TEXTURE_SIZE
237
+ )
238
+ const shapeDetailTexture = use3DTextureState(
239
+ shapeDetailTextureProp,
240
+ CLOUD_SHAPE_DETAIL_TEXTURE_SIZE
241
+ )
242
+ const turbulenceTexture = useTextureState(turbulenceTextureProp, gl)
243
+ const stbnTexture = useSTBNTextureState(stbnTextureProp)
247
244
 
248
- const { camera } = useContext(EffectComposerContext)
249
- return (
250
- <>
251
- <primitive
252
- ref={forwardedRef}
253
- object={effect}
254
- mainCamera={camera}
255
- {...atmosphereParameters}
256
- localWeatherTexture={localWeatherTexture}
257
- shapeTexture={shapeTexture}
258
- shapeDetailTexture={shapeDetailTexture}
259
- turbulenceTexture={turbulenceTexture}
260
- stbnTexture={stbnTexture}
261
- {...others}
262
- />
263
- <CloudLayers
264
- layers={effect.cloudLayers}
265
- disableDefault={disableDefaultLayers}
266
- >
267
- {children}
268
- </CloudLayers>
269
- </>
270
- )
271
- }
272
- )
245
+ const { camera } = useContext(EffectComposerContext)
246
+ return (
247
+ <>
248
+ <primitive
249
+ ref={forwardedRef}
250
+ object={effect}
251
+ mainCamera={camera}
252
+ {...atmosphereParameters}
253
+ localWeatherTexture={localWeatherTexture}
254
+ shapeTexture={shapeTexture}
255
+ shapeDetailTexture={shapeDetailTexture}
256
+ turbulenceTexture={turbulenceTexture}
257
+ stbnTexture={stbnTexture}
258
+ {...others}
259
+ />
260
+ <CloudLayers
261
+ layers={effect.cloudLayers}
262
+ disableDefault={disableDefaultLayers}
263
+ >
264
+ {children}
265
+ </CloudLayers>
266
+ </>
267
+ )
268
+ }
@@ -418,6 +418,7 @@ vec3 getGroundSunSkyIrradiance(
418
418
  #ifdef ACCURATE_SUN_SKY_IRRADIANCE
419
419
  return GetSunAndSkyIrradiance(
420
420
  (position - surfaceNormal * height) * METER_TO_LENGTH_UNIT,
421
+ surfaceNormal,
421
422
  sunDirection,
422
423
  skyIrradiance
423
424
  );
@@ -429,7 +430,11 @@ vec3 getGroundSunSkyIrradiance(
429
430
 
430
431
  vec3 getCloudsSunSkyIrradiance(const vec3 position, const float height, out vec3 skyIrradiance) {
431
432
  #ifdef ACCURATE_SUN_SKY_IRRADIANCE
432
- return GetSunAndSkyIrradiance(position * METER_TO_LENGTH_UNIT, sunDirection, skyIrradiance);
433
+ return GetSunAndSkyIrradianceForParticle(
434
+ position * METER_TO_LENGTH_UNIT,
435
+ sunDirection,
436
+ skyIrradiance
437
+ );
433
438
  #else // ACCURATE_SUN_SKY_IRRADIANCE
434
439
  float alpha = remapClamped(height, minHeight, maxHeight);
435
440
  skyIrradiance = mix(vCloudsIrradiance.minSky, vCloudsIrradiance.maxSky, alpha);
@@ -455,10 +460,9 @@ vec3 approximateIrradianceFromGround(
455
460
  vec3 skyIrradiance;
456
461
  vec3 sunIrradiance = getGroundSunSkyIrradiance(position, surfaceNormal, height, skyIrradiance);
457
462
  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;
463
+ vec3 groundIrradiance = skyIrradiance + (1.0 - coverage) * sunIrradiance;
464
+ vec3 bouncedRadiance = groundAlbedo * RECIPROCAL_PI * groundIrradiance;
465
+ return bouncedRadiance * exp(-opticalDepthToGround);
462
466
  }
463
467
  #endif // GROUND_IRRADIANCE
464
468
 
@@ -548,28 +552,28 @@ vec4 marchClouds(
548
552
  );
549
553
  }
550
554
 
551
- float scattering = approximateMultipleScattering(opticalDepth, cosTheta);
552
- vec3 radiance = albedo * sunIrradiance * scattering;
555
+ vec3 radiance = sunIrradiance * approximateMultipleScattering(opticalDepth, cosTheta);
553
556
 
554
557
  #ifdef GROUND_IRRADIANCE
555
558
  // Fudge factor for the irradiance from ground.
556
559
  if (height < shadowTopHeight && mipLevel < 0.5) {
557
- radiance += approximateIrradianceFromGround(
560
+ vec3 groundIrradiance = approximateIrradianceFromGround(
558
561
  position,
559
562
  surfaceNormal,
560
563
  height,
561
564
  mipLevel,
562
565
  jitter
563
566
  );
567
+ radiance += groundIrradiance * RECIPROCAL_PI4 * groundIrradianceScale;
564
568
  }
565
569
  #endif // GROUND_IRRADIANCE
566
570
 
567
571
  // 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;
572
+ float skyGradient = dot(weather.heightFraction * 0.5 + 0.5, media.weight);
573
+ radiance += skyIrradiance * RECIPROCAL_PI4 * skyGradient * skyIrradianceScale;
570
574
 
571
- // Finally multiply by extinction (redundant but kept for clarity).
572
- radiance *= media.extinction;
575
+ // Finally multiply by scattering.
576
+ radiance *= media.scattering;
573
577
 
574
578
  #ifdef POWDER
575
579
  radiance *= 1.0 - powderScale * exp(-media.extinction * powderExponent);
@@ -622,7 +626,7 @@ float marchShadowLength(
622
626
  float maxRayDistance = rayNearFar.y - rayNearFar.x;
623
627
  float stepSize = minShadowLengthStepSize;
624
628
  float rayDistance = stepSize * jitter;
625
- const float attenuationFactor = 1.0 - 1e-3;
629
+ const float attenuationFactor = 1.0 - 5e-4;
626
630
  float attenuation = 1.0;
627
631
 
628
632
  // TODO: This march is closed, and sample resolution can be much lower.
@@ -668,25 +672,33 @@ vec4 approximateHaze(
668
672
  return vec4(0.0); // Prevent artifact in views from space
669
673
  }
670
674
 
675
+ // Blend two normals by the difference in angle so that normal near the
676
+ // ground becomes that of the origin, and in the sky that of the horizon.
677
+ vec3 normalAtOrigin = normalize(rayOrigin);
678
+ vec3 normalAtHorizon = (rayOrigin - dot(rayOrigin, rayDirection) * rayDirection) / bottomRadius;
679
+ float alpha = remapClamped(dot(normalAtOrigin, normalAtHorizon), 0.9, 1.0);
680
+ vec3 normal = mix(normalAtOrigin, normalAtHorizon, alpha);
681
+
671
682
  // Analytical optical depth where density exponentially decreases with height.
672
683
  // Based on: https://iquilezles.org/articles/fog/
673
- float angle = max(dot(normalize(rayOrigin), rayDirection), 1e-5);
684
+ float angle = max(dot(normal, rayDirection), 1e-5);
674
685
  float exponent = angle * hazeExponent;
686
+ float linearTerm = density / hazeExponent / angle;
687
+
675
688
  // Derive the optical depths separately for with and without shadow length.
676
689
  float expTerm = 1.0 - exp(-maxRayDistance * exponent);
677
690
  float shadowExpTerm = 1.0 - exp(-min(maxRayDistance, shadowLength) * exponent);
678
- float linearTerm = density / hazeExponent / angle;
679
691
  float opticalDepth = expTerm * linearTerm;
680
- float effectiveOpticalDepth = max((expTerm - shadowExpTerm) * linearTerm, 0.0);
692
+ float shadowOpticalDepth = max((expTerm - shadowExpTerm) * linearTerm, 0.0);
693
+ float transmittance = saturate(1.0 - exp(-opticalDepth));
694
+ float shadowTransmittance = saturate(1.0 - exp(-shadowOpticalDepth));
681
695
 
682
696
  vec3 skyIrradiance = vGroundIrradiance.sky;
683
697
  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)));
698
+ vec3 inscatter = sunIrradiance * phaseFunction(cosTheta) * shadowTransmittance;
699
+ inscatter += skyIrradiance * RECIPROCAL_PI4 * skyIrradianceScale * transmittance;
700
+ inscatter *= hazeScatteringCoefficient / (hazeAbsorptionCoefficient + hazeScatteringCoefficient);
701
+ return vec4(inscatter, transmittance);
690
702
  }
691
703
 
692
704
  #endif // HAZE
@@ -705,8 +717,7 @@ void applyAerialPerspective(
705
717
  sunDirection,
706
718
  transmittance
707
719
  );
708
- float clampedAlpha = max(color.a, 1e-7);
709
- color.rgb = mix(vec3(0.0), color.rgb * transmittance / clampedAlpha + inscatter, color.a);
720
+ color.rgb = color.rgb * transmittance + inscatter * color.a;
710
721
  }
711
722
 
712
723
  bool rayIntersectsGround(const vec3 cameraPosition, const vec3 rayDirection) {
@@ -985,7 +996,8 @@ void main() {
985
996
  cosTheta,
986
997
  shadowLength
987
998
  );
988
- color = color * (1.0 - haze.a) + haze;
999
+ color.rgb = mix(color.rgb, haze.rgb, haze.a);
1000
+ color.a = color.a * (1.0 - haze.a) + haze.a;
989
1001
  #endif // HAZE
990
1002
 
991
1003
  outputColor = color;
@@ -167,7 +167,7 @@ MediaSample sampleMedia(
167
167
  }
168
168
  #endif // SHAPE_DETAIL
169
169
 
170
- // Nicely decrease the density at the bottom.
170
+ // Apply the density profiles.
171
171
  density = saturate(density * densityScales * getLayerDensity(weather.heightFraction));
172
172
 
173
173
  MediaSample media;
@@ -32,7 +32,7 @@ out GroundIrradiance vGroundIrradiance;
32
32
  out CloudsIrradiance vCloudsIrradiance;
33
33
 
34
34
  void sampleSunSkyIrradiance(const vec3 positionECEF) {
35
- vGroundIrradiance.sun = GetSunAndSkyIrradiance(
35
+ vGroundIrradiance.sun = GetSunAndSkyIrradianceForParticle(
36
36
  positionECEF * METER_TO_LENGTH_UNIT,
37
37
  sunDirection,
38
38
  vGroundIrradiance.sky
@@ -40,12 +40,12 @@ void sampleSunSkyIrradiance(const vec3 positionECEF) {
40
40
 
41
41
  vec3 surfaceNormal = normalize(positionECEF);
42
42
  vec2 radii = (bottomRadius + vec2(minHeight, maxHeight)) * METER_TO_LENGTH_UNIT;
43
- vCloudsIrradiance.minSun = GetSunAndSkyIrradiance(
43
+ vCloudsIrradiance.minSun = GetSunAndSkyIrradianceForParticle(
44
44
  surfaceNormal * radii.x,
45
45
  sunDirection,
46
46
  vCloudsIrradiance.minSky
47
47
  );
48
- vCloudsIrradiance.maxSun = GetSunAndSkyIrradiance(
48
+ vCloudsIrradiance.maxSun = GetSunAndSkyIrradianceForParticle(
49
49
  surfaceNormal * radii.y,
50
50
  sunDirection,
51
51
  vCloudsIrradiance.maxSky