@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,117 @@
1
+ import { Vector2 } from 'three'
2
+ import { type PartialDeep, type SharedUnionFieldsDeep } from 'type-fest'
3
+
4
+ import { type CloudsEffect } from './CloudsEffect'
5
+
6
+ export type QualityPreset = 'low' | 'medium' | 'high' | 'ultra'
7
+
8
+ const values = {
9
+ resolutionScale: 1,
10
+ lightShafts: true,
11
+ shapeDetail: true,
12
+ turbulence: true,
13
+ haze: true,
14
+ clouds: {
15
+ multiScatteringOctaves: 8,
16
+ accurateSunSkyIrradiance: true,
17
+ accuratePhaseFunction: false,
18
+
19
+ // Primary raymarch
20
+ maxIterationCount: 500,
21
+ minStepSize: 50,
22
+ maxStepSize: 1000,
23
+ maxRayDistance: 2e5,
24
+ perspectiveStepScale: 1.01,
25
+ minDensity: 1e-5,
26
+ minExtinction: 1e-5,
27
+ minTransmittance: 1e-2,
28
+
29
+ // Secondary raymarch
30
+ maxIterationCountToGround: 3,
31
+ maxIterationCountToSun: 2,
32
+ minSecondaryStepSize: 100,
33
+ secondaryStepScale: 2,
34
+
35
+ // Shadow length
36
+ maxShadowLengthIterationCount: 500,
37
+ minShadowLengthStepSize: 50,
38
+ maxShadowLengthRayDistance: 2e5
39
+ },
40
+ shadow: {
41
+ cascadeCount: 3,
42
+ mapSize: /*#__PURE__*/ new Vector2(512, 512),
43
+
44
+ // Primary raymarch
45
+ maxIterationCount: 50,
46
+ minStepSize: 100,
47
+ maxStepSize: 1000,
48
+ minDensity: 1e-5,
49
+ minExtinction: 1e-5,
50
+ minTransmittance: 1e-4
51
+ }
52
+ } satisfies PartialDeep<CloudsEffect>
53
+
54
+ // Relax types narrowed down by satisfies operator.
55
+ type Schema = SharedUnionFieldsDeep<typeof values | CloudsEffect>
56
+
57
+ export const defaults: Schema = values
58
+
59
+ export const qualityPresets: Record<QualityPreset, Schema> = {
60
+ // TODO: We cloud decrease multi-scattering octaves for lower quality presets,
61
+ // but it leads to a loss of higher frequency scattering, making it darker
62
+ // overall, which suggests the need for a fudge factor to scale the radiance.
63
+ low: {
64
+ ...defaults,
65
+ lightShafts: false, // Expensive
66
+ shapeDetail: false, // Expensive
67
+ turbulence: false, // Expensive
68
+ clouds: {
69
+ ...defaults.clouds,
70
+ accurateSunSkyIrradiance: false, // Greatly reduces texel reads.
71
+ maxIterationCount: 200,
72
+ minStepSize: 100,
73
+ maxRayDistance: 1e5,
74
+ minDensity: 1e-4,
75
+ minExtinction: 1e-4,
76
+ minTransmittance: 1e-1, // Makes the primary march terminate earlier.
77
+ maxIterationCountToGround: 0, // Expensive
78
+ maxIterationCountToSun: 1 // Only 1 march makes big difference
79
+ },
80
+ shadow: {
81
+ ...defaults.shadow,
82
+ maxIterationCount: 25,
83
+ minDensity: 1e-4,
84
+ minExtinction: 1e-4,
85
+ minTransmittance: 1e-2, // Makes the primary march terminate earlier.
86
+ cascadeCount: 2, // Obvious
87
+ mapSize: /*#__PURE__*/ new Vector2(256, 256) // Obvious
88
+ }
89
+ },
90
+ medium: {
91
+ ...defaults,
92
+ lightShafts: false, // Expensive
93
+ turbulence: false, // Expensive
94
+ clouds: {
95
+ ...defaults.clouds,
96
+ minDensity: 1e-4,
97
+ minExtinction: 1e-4,
98
+ accurateSunSkyIrradiance: false,
99
+ maxIterationCountToSun: 2,
100
+ maxIterationCountToGround: 1
101
+ },
102
+ shadow: {
103
+ ...defaults.shadow,
104
+ minDensity: 1e-4,
105
+ minExtinction: 1e-4,
106
+ mapSize: /*#__PURE__*/ new Vector2(256, 256)
107
+ }
108
+ },
109
+ high: defaults, // Consider high quality preset as default.
110
+ ultra: {
111
+ ...defaults,
112
+ shadow: {
113
+ ...defaults.shadow,
114
+ mapSize: /*#__PURE__*/ new Vector2(1024, 1024)
115
+ }
116
+ }
117
+ }
@@ -0,0 +1,95 @@
1
+ import {
2
+ forwardRef,
3
+ useContext,
4
+ useEffect,
5
+ useLayoutEffect,
6
+ useState
7
+ } from 'react'
8
+
9
+ import { type ExpandNestedProps } from '@takram/three-geospatial/r3f'
10
+
11
+ import {
12
+ CloudLayer as CloudLayerImpl,
13
+ type CloudLayerLike
14
+ } from '../CloudLayer'
15
+ import { CloudLayers } from '../CloudLayers'
16
+ import { CloudLayersContext } from './CloudLayers'
17
+
18
+ export interface CloudLayerProps
19
+ extends CloudLayerLike,
20
+ ExpandNestedProps<CloudLayerLike, 'densityProfile'> {
21
+ index?: number
22
+ }
23
+
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
+ }
32
+
33
+ const { layers, indexPool, disableDefault } = context
34
+ const [index, setIndex] = useState<number>()
35
+
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
+ }
46
+ }
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
+ }
77
+ if (typeof forwardedRef === 'function') {
78
+ forwardedRef(layers[index])
79
+ } else if (forwardedRef != null) {
80
+ forwardedRef.current = layers[index]
81
+ }
82
+ }, [forwardedRef, layers, index])
83
+
84
+ // Surely this resets any modifications made via forwarded ref.
85
+ if (index != null) {
86
+ const layer = layers[index]
87
+ layer.copy(
88
+ disableDefault ? CloudLayerImpl.DEFAULT : CloudLayers.DEFAULT[index]
89
+ )
90
+ layer.set(props)
91
+ }
92
+
93
+ return null
94
+ }
95
+ )
@@ -0,0 +1,54 @@
1
+ import {
2
+ createContext,
3
+ useLayoutEffect,
4
+ useState,
5
+ type FC,
6
+ type ReactNode
7
+ } from 'react'
8
+
9
+ import { CloudLayer } from '../CloudLayer'
10
+ import { CloudLayers as CloudLayersImpl } from '../CloudLayers'
11
+
12
+ export interface CloudLayersContextValue {
13
+ layers: CloudLayersImpl
14
+ indexPool: number[]
15
+ disableDefault: boolean
16
+ }
17
+
18
+ export const CloudLayersContext =
19
+ /*#__PURE__*/ createContext<CloudLayersContextValue | null>(null)
20
+
21
+ export interface CloudLayersProps {
22
+ layers: CloudLayersImpl
23
+ disableDefault?: boolean
24
+ children?: ReactNode
25
+ }
26
+
27
+ export const CloudLayers: FC<CloudLayersProps> = ({
28
+ layers,
29
+ disableDefault = false,
30
+ children
31
+ }) => {
32
+ const [context, setContext] = useState<CloudLayersContextValue>()
33
+
34
+ useLayoutEffect(() => {
35
+ layers.set(
36
+ disableDefault
37
+ ? Array(4).fill(CloudLayer.DEFAULT)
38
+ : CloudLayersImpl.DEFAULT
39
+ )
40
+ setContext({
41
+ layers,
42
+ indexPool: [0, 1, 2, 3],
43
+ disableDefault
44
+ })
45
+ }, [layers, disableDefault])
46
+
47
+ return (
48
+ context != null && (
49
+ <CloudLayersContext.Provider value={context}>
50
+ {children}
51
+ </CloudLayersContext.Provider>
52
+ )
53
+ )
54
+ }
@@ -0,0 +1,278 @@
1
+ import { useFrame, useThree } from '@react-three/fiber'
2
+ import { EffectComposerContext } from '@react-three/postprocessing'
3
+ import {
4
+ forwardRef,
5
+ useCallback,
6
+ useContext,
7
+ useEffect,
8
+ useMemo,
9
+ useState
10
+ } from 'react'
11
+ import {
12
+ LinearFilter,
13
+ LinearMipMapLinearFilter,
14
+ NoColorSpace,
15
+ RedFormat,
16
+ RepeatWrapping,
17
+ TextureLoader,
18
+ type Data3DTexture,
19
+ type Texture,
20
+ type WebGLRenderer
21
+ } from 'three'
22
+
23
+ import { AtmosphereContext, separateProps } from '@takram/three-atmosphere/r3f'
24
+ import {
25
+ createData3DTextureLoaderClass,
26
+ DEFAULT_STBN_URL,
27
+ parseUint8Array,
28
+ STBNLoader
29
+ } from '@takram/three-geospatial'
30
+ import {
31
+ type ExpandNestedProps,
32
+ type PassThoughInstanceProps
33
+ } from '@takram/three-geospatial/r3f'
34
+
35
+ import {
36
+ CloudsEffect,
37
+ cloudsPassOptionsDefaults,
38
+ type CloudsEffectChangeEvent
39
+ } from '../CloudsEffect'
40
+ import {
41
+ CLOUD_SHAPE_DETAIL_TEXTURE_SIZE,
42
+ CLOUD_SHAPE_TEXTURE_SIZE,
43
+ DEFAULT_LOCAL_WEATHER_URL,
44
+ DEFAULT_SHAPE_DETAIL_URL,
45
+ DEFAULT_SHAPE_URL,
46
+ DEFAULT_TURBULENCE_URL
47
+ } from '../constants'
48
+ import { type Procedural3DTexture } from '../Procedural3DTexture'
49
+ import { type ProceduralTexture } from '../ProceduralTexture'
50
+ import { CloudLayers } from './CloudLayers'
51
+
52
+ function useTextureState(
53
+ input: string | Texture | ProceduralTexture,
54
+ gl: WebGLRenderer
55
+ ): Texture | ProceduralTexture | null {
56
+ const [data, setData] = useState(typeof input !== 'string' ? input : null)
57
+ useEffect(() => {
58
+ if (typeof input === 'string') {
59
+ const loader = new TextureLoader()
60
+ ;(async () => {
61
+ const texture = await loader.loadAsync(input)
62
+ texture.minFilter = LinearMipMapLinearFilter
63
+ texture.magFilter = LinearFilter
64
+ texture.wrapS = RepeatWrapping
65
+ texture.wrapT = RepeatWrapping
66
+ texture.colorSpace = NoColorSpace
67
+ texture.needsUpdate = true
68
+
69
+ // WORKAROUND: The color space resets to sRGB for unknown reason, unless
70
+ // the texture is initialized here.
71
+ gl.initTexture(texture)
72
+
73
+ setData(texture)
74
+ })().catch(error => {
75
+ console.error(error)
76
+ })
77
+ } else {
78
+ setData(input)
79
+ }
80
+ }, [input, gl])
81
+
82
+ return data
83
+ }
84
+
85
+ function use3DTextureState(
86
+ input: string | Data3DTexture | Procedural3DTexture,
87
+ size: number
88
+ ): Data3DTexture | Procedural3DTexture | null {
89
+ const [data, setData] = useState(typeof input !== 'string' ? input : null)
90
+ useEffect(() => {
91
+ if (typeof input === 'string') {
92
+ const Loader = createData3DTextureLoaderClass(parseUint8Array, {
93
+ width: size,
94
+ height: size,
95
+ depth: size,
96
+ format: RedFormat,
97
+ minFilter: LinearFilter,
98
+ magFilter: LinearFilter,
99
+ wrapS: RepeatWrapping,
100
+ wrapT: RepeatWrapping,
101
+ wrapR: RepeatWrapping,
102
+ colorSpace: NoColorSpace
103
+ })
104
+ const loader = new Loader()
105
+ ;(async () => {
106
+ setData(await loader.loadAsync(input))
107
+ })().catch(error => {
108
+ console.error(error)
109
+ })
110
+ } else {
111
+ setData(input)
112
+ }
113
+ }, [input, size])
114
+
115
+ return data
116
+ }
117
+
118
+ function useSTBNTextureState(
119
+ input: string | Data3DTexture
120
+ ): Data3DTexture | null {
121
+ const [data, setData] = useState(typeof input !== 'string' ? input : null)
122
+ useEffect(() => {
123
+ if (typeof input === 'string') {
124
+ const loader = new STBNLoader()
125
+ ;(async () => {
126
+ setData(await loader.loadAsync(input))
127
+ })().catch(error => {
128
+ console.error(error)
129
+ })
130
+ } else {
131
+ setData(input)
132
+ }
133
+ }, [input])
134
+
135
+ return data
136
+ }
137
+
138
+ export interface CloudsProps
139
+ extends Omit<
140
+ PassThoughInstanceProps<
141
+ CloudsEffect,
142
+ [],
143
+ Partial<
144
+ CloudsEffect &
145
+ ExpandNestedProps<CloudsEffect, 'clouds'> &
146
+ ExpandNestedProps<CloudsEffect, 'shadow'>
147
+ >
148
+ >,
149
+ | 'localWeatherTexture'
150
+ | 'shapeTexture'
151
+ | 'shapeDetailTexture'
152
+ | 'turbulenceTexture'
153
+ | 'stbnTexture'
154
+ > {
155
+ disableDefaultLayers?: boolean
156
+ localWeatherTexture?: Texture | ProceduralTexture | string
157
+ shapeTexture?: Data3DTexture | Procedural3DTexture | string
158
+ shapeDetailTexture?: Data3DTexture | Procedural3DTexture | string
159
+ turbulenceTexture?: Texture | ProceduralTexture | string
160
+ stbnTexture?: Data3DTexture | string
161
+ }
162
+
163
+ export const Clouds = /*#__PURE__*/ forwardRef<CloudsEffect, CloudsProps>(
164
+ function Clouds(
165
+ {
166
+ disableDefaultLayers = false,
167
+ localWeatherTexture: localWeatherTextureProp = DEFAULT_LOCAL_WEATHER_URL,
168
+ shapeTexture: shapeTextureProp = DEFAULT_SHAPE_URL,
169
+ shapeDetailTexture: shapeDetailTextureProp = DEFAULT_SHAPE_DETAIL_URL,
170
+ turbulenceTexture: turbulenceTextureProp = DEFAULT_TURBULENCE_URL,
171
+ stbnTexture: stbnTextureProp = DEFAULT_STBN_URL,
172
+ children,
173
+ ...props
174
+ },
175
+ forwardedRef
176
+ ) {
177
+ const { textures, transientStates, ...contextProps } =
178
+ useContext(AtmosphereContext)
179
+
180
+ const [atmosphereParameters, others] = separateProps({
181
+ ...cloudsPassOptionsDefaults,
182
+ ...contextProps,
183
+ ...textures,
184
+ ...props
185
+ })
186
+
187
+ const effect = useMemo(() => new CloudsEffect(), [])
188
+ useEffect(() => {
189
+ return () => {
190
+ effect.dispose()
191
+ }
192
+ }, [effect])
193
+
194
+ useFrame(() => {
195
+ if (transientStates != null) {
196
+ effect.sunDirection.copy(transientStates.sunDirection)
197
+ effect.ellipsoidCenter.copy(transientStates.ellipsoidCenter)
198
+ effect.ellipsoidMatrix.copy(transientStates.ellipsoidMatrix)
199
+ }
200
+ })
201
+
202
+ useEffect(() => {
203
+ if (transientStates != null) {
204
+ transientStates.overlay = effect.atmosphereOverlay
205
+ transientStates.shadow = effect.atmosphereShadow
206
+ transientStates.shadowLength = effect.atmosphereShadowLength
207
+ return () => {
208
+ transientStates.overlay = null
209
+ transientStates.shadow = null
210
+ transientStates.shadowLength = null
211
+ }
212
+ }
213
+ }, [effect, transientStates])
214
+
215
+ const handleChange = useCallback(
216
+ (event: CloudsEffectChangeEvent) => {
217
+ if (transientStates == null) {
218
+ return
219
+ }
220
+ switch (event.property) {
221
+ case 'atmosphereOverlay':
222
+ transientStates.overlay = effect.atmosphereOverlay
223
+ break
224
+ case 'atmosphereShadow':
225
+ transientStates.shadow = effect.atmosphereShadow
226
+ break
227
+ case 'atmosphereShadowLength':
228
+ transientStates.shadowLength = effect.atmosphereShadowLength
229
+ break
230
+ }
231
+ },
232
+ [effect, transientStates]
233
+ )
234
+ useEffect(() => {
235
+ effect.events.addEventListener('change', handleChange)
236
+ return () => {
237
+ effect.events.removeEventListener('change', handleChange)
238
+ }
239
+ }, [effect, handleChange])
240
+
241
+ const gl = useThree(({ gl }) => gl)
242
+ const localWeatherTexture = useTextureState(localWeatherTextureProp, gl)
243
+ const shapeTexture = use3DTextureState(
244
+ shapeTextureProp,
245
+ CLOUD_SHAPE_TEXTURE_SIZE
246
+ )
247
+ const shapeDetailTexture = use3DTextureState(
248
+ shapeDetailTextureProp,
249
+ CLOUD_SHAPE_DETAIL_TEXTURE_SIZE
250
+ )
251
+ const turbulenceTexture = useTextureState(turbulenceTextureProp, gl)
252
+ const stbnTexture = useSTBNTextureState(stbnTextureProp)
253
+
254
+ const { camera } = useContext(EffectComposerContext)
255
+ return (
256
+ <>
257
+ <primitive
258
+ ref={forwardedRef}
259
+ object={effect}
260
+ mainCamera={camera}
261
+ {...atmosphereParameters}
262
+ localWeatherTexture={localWeatherTexture}
263
+ shapeTexture={shapeTexture}
264
+ shapeDetailTexture={shapeDetailTexture}
265
+ turbulenceTexture={turbulenceTexture}
266
+ stbnTexture={stbnTexture}
267
+ {...others}
268
+ />
269
+ <CloudLayers
270
+ layers={effect.cloudLayers}
271
+ disableDefault={disableDefaultLayers}
272
+ >
273
+ {children}
274
+ </CloudLayers>
275
+ </>
276
+ )
277
+ }
278
+ )
@@ -0,0 +1,2 @@
1
+ export * from './Clouds'
2
+ export * from './CloudLayer'
@@ -0,0 +1,113 @@
1
+ // Taken from https://gist.github.com/TheRealMJP/c83b8c0f46b63f3a88a5986f4fa982b1
2
+ // TODO: Use 5-taps version: https://www.shadertoy.com/view/MtVGWz
3
+ // Or even 4 taps (requires preprocessing in the input buffer):
4
+ // https://www.shadertoy.com/view/4tyGDD
5
+
6
+ /**
7
+ * MIT License
8
+ *
9
+ * Copyright (c) 2019 MJP
10
+ *
11
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ * of this software and associated documentation files (the "Software"), to deal
13
+ * in the Software without restriction, including without limitation the rights
14
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ * copies of the Software, and to permit persons to whom the Software is
16
+ * furnished to do so, subject to the following conditions:
17
+ *
18
+ * The above copyright notice and this permission notice shall be included in all
19
+ * copies or substantial portions of the Software.
20
+ *
21
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ * SOFTWARE.
28
+ */
29
+
30
+ vec4 textureCatmullRom(sampler2D tex, vec2 uv) {
31
+ vec2 texSize = vec2(textureSize(tex, 0));
32
+
33
+ // We're going to sample a a 4x4 grid of texels surrounding the target UV
34
+ // coordinate. We'll do this by rounding down the sample location to get the
35
+ // exact center of our "starting" texel. The starting texel will be at
36
+ // location [1, 1] in the grid, where [0, 0] is the top left corner.
37
+ vec2 samplePos = uv * texSize;
38
+ vec2 texPos1 = floor(samplePos - 0.5) + 0.5;
39
+
40
+ // Compute the fractional offset from our starting texel to our original
41
+ // sample location, which we'll feed into the Catmull-Rom spline function to
42
+ // get our filter weights.
43
+ vec2 f = samplePos - texPos1;
44
+
45
+ // Compute the Catmull-Rom weights using the fractional offset that we
46
+ // calculated earlier. These equations are pre-expanded based on our knowledge
47
+ // of where the texels will be located, which lets us avoid having to evaluate
48
+ // a piece-wise function.
49
+ vec2 w0 = f * (-0.5 + f * (1.0 - 0.5 * f));
50
+ vec2 w1 = 1.0 + f * f * (-2.5 + 1.5 * f);
51
+ vec2 w2 = f * (0.5 + f * (2.0 - 1.5 * f));
52
+ vec2 w3 = f * f * (-0.5 + 0.5 * f);
53
+
54
+ // Work out weighting factors and sampling offsets that will let us use
55
+ // bilinear filtering to simultaneously evaluate the middle 2 samples from the
56
+ // 4x4 grid.
57
+ vec2 w12 = w1 + w2;
58
+ vec2 offset12 = w2 / (w1 + w2);
59
+
60
+ // Compute the final UV coordinates we'll use for sampling the texture
61
+ vec2 texPos0 = texPos1 - 1.0;
62
+ vec2 texPos3 = texPos1 + 2.0;
63
+ vec2 texPos12 = texPos1 + offset12;
64
+
65
+ texPos0 /= texSize;
66
+ texPos3 /= texSize;
67
+ texPos12 /= texSize;
68
+
69
+ vec4 result = vec4(0.0);
70
+ result += texture(tex, vec2(texPos0.x, texPos0.y)) * w0.x * w0.y;
71
+ result += texture(tex, vec2(texPos12.x, texPos0.y)) * w12.x * w0.y;
72
+ result += texture(tex, vec2(texPos3.x, texPos0.y)) * w3.x * w0.y;
73
+
74
+ result += texture(tex, vec2(texPos0.x, texPos12.y)) * w0.x * w12.y;
75
+ result += texture(tex, vec2(texPos12.x, texPos12.y)) * w12.x * w12.y;
76
+ result += texture(tex, vec2(texPos3.x, texPos12.y)) * w3.x * w12.y;
77
+
78
+ result += texture(tex, vec2(texPos0.x, texPos3.y)) * w0.x * w3.y;
79
+ result += texture(tex, vec2(texPos12.x, texPos3.y)) * w12.x * w3.y;
80
+ result += texture(tex, vec2(texPos3.x, texPos3.y)) * w3.x * w3.y;
81
+
82
+ return result;
83
+ }
84
+
85
+ vec4 textureCatmullRom(sampler2DArray tex, vec3 uv) {
86
+ vec2 texSize = vec2(textureSize(tex, 0));
87
+ vec2 samplePos = uv.xy * texSize;
88
+ vec2 texPos1 = floor(samplePos - 0.5) + 0.5;
89
+ vec2 f = samplePos - texPos1;
90
+ vec2 w0 = f * (-0.5 + f * (1.0 - 0.5 * f));
91
+ vec2 w1 = 1.0 + f * f * (-2.5 + 1.5 * f);
92
+ vec2 w2 = f * (0.5 + f * (2.0 - 1.5 * f));
93
+ vec2 w3 = f * f * (-0.5 + 0.5 * f);
94
+ vec2 w12 = w1 + w2;
95
+ vec2 offset12 = w2 / (w1 + w2);
96
+ vec2 texPos0 = texPos1 - 1.0;
97
+ vec2 texPos3 = texPos1 + 2.0;
98
+ vec2 texPos12 = texPos1 + offset12;
99
+ texPos0 /= texSize;
100
+ texPos3 /= texSize;
101
+ texPos12 /= texSize;
102
+ vec4 result = vec4(0.0);
103
+ result += texture(tex, vec3(texPos0.x, texPos0.y, uv.z)) * w0.x * w0.y;
104
+ result += texture(tex, vec3(texPos12.x, texPos0.y, uv.z)) * w12.x * w0.y;
105
+ result += texture(tex, vec3(texPos3.x, texPos0.y, uv.z)) * w3.x * w0.y;
106
+ result += texture(tex, vec3(texPos0.x, texPos12.y, uv.z)) * w0.x * w12.y;
107
+ result += texture(tex, vec3(texPos12.x, texPos12.y, uv.z)) * w12.x * w12.y;
108
+ result += texture(tex, vec3(texPos3.x, texPos12.y, uv.z)) * w3.x * w12.y;
109
+ result += texture(tex, vec3(texPos0.x, texPos3.y, uv.z)) * w0.x * w3.y;
110
+ result += texture(tex, vec3(texPos12.x, texPos3.y, uv.z)) * w12.x * w3.y;
111
+ result += texture(tex, vec3(texPos3.x, texPos3.y, uv.z)) * w3.x * w3.y;
112
+ return result;
113
+ }