@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,141 @@
1
+ import {
2
+ GLSL3,
3
+ Matrix4,
4
+ RawShaderMaterial,
5
+ Uniform,
6
+ Vector2,
7
+ type Data3DTexture
8
+ } from 'three'
9
+
10
+ import {
11
+ define,
12
+ defineExpression,
13
+ defineInt,
14
+ resolveIncludes,
15
+ unrollLoops
16
+ } from '@takram/three-geospatial'
17
+ import { math, raySphereIntersection } from '@takram/three-geospatial/shaders'
18
+
19
+ import { defaults } from './qualityPresets'
20
+ import {
21
+ type AtmosphereUniforms,
22
+ type CloudLayerUniforms,
23
+ type CloudParameterUniforms
24
+ } from './uniforms'
25
+
26
+ import clouds from './shaders/clouds.glsl?raw'
27
+ import parameters from './shaders/parameters.glsl?raw'
28
+ import fragmentShader from './shaders/shadow.frag?raw'
29
+ import vertexShader from './shaders/shadow.vert?raw'
30
+ import structuredSampling from './shaders/structuredSampling.glsl?raw'
31
+ import types from './shaders/types.glsl?raw'
32
+
33
+ export interface ShadowMaterialParameters {
34
+ parameterUniforms: CloudParameterUniforms
35
+ layerUniforms: CloudLayerUniforms
36
+ atmosphereUniforms: AtmosphereUniforms
37
+ }
38
+
39
+ export interface ShadowMaterialUniforms
40
+ extends CloudParameterUniforms,
41
+ CloudLayerUniforms,
42
+ AtmosphereUniforms {
43
+ [key: string]: Uniform<unknown>
44
+ inverseShadowMatrices: Uniform<Matrix4[]>
45
+ reprojectionMatrices: Uniform<Matrix4[]>
46
+ resolution: Uniform<Vector2>
47
+ frame: Uniform<number>
48
+ stbnTexture: Uniform<Data3DTexture | null>
49
+
50
+ // Primary raymarch
51
+ maxIterationCount: Uniform<number>
52
+ minStepSize: Uniform<number>
53
+ maxStepSize: Uniform<number>
54
+ minDensity: Uniform<number>
55
+ minExtinction: Uniform<number>
56
+ minTransmittance: Uniform<number>
57
+ opticalDepthTailScale: Uniform<number>
58
+ }
59
+
60
+ export class ShadowMaterial extends RawShaderMaterial {
61
+ declare uniforms: ShadowMaterialUniforms
62
+
63
+ constructor({
64
+ parameterUniforms,
65
+ layerUniforms,
66
+ atmosphereUniforms
67
+ }: ShadowMaterialParameters) {
68
+ super({
69
+ name: 'ShadowMaterial',
70
+ glslVersion: GLSL3,
71
+ vertexShader,
72
+ fragmentShader: unrollLoops(
73
+ resolveIncludes(fragmentShader, {
74
+ core: {
75
+ math,
76
+ raySphereIntersection
77
+ },
78
+ types,
79
+ parameters,
80
+ structuredSampling,
81
+ clouds
82
+ })
83
+ ),
84
+ uniforms: {
85
+ ...parameterUniforms,
86
+ ...layerUniforms,
87
+ ...atmosphereUniforms,
88
+
89
+ inverseShadowMatrices: new Uniform(
90
+ Array.from({ length: 4 }, () => new Matrix4()) // Populate the max number of elements
91
+ ),
92
+ reprojectionMatrices: new Uniform(
93
+ Array.from({ length: 4 }, () => new Matrix4()) // Populate the max number of elements
94
+ ),
95
+ resolution: new Uniform(new Vector2()),
96
+ frame: new Uniform(0),
97
+ stbnTexture: new Uniform(null),
98
+
99
+ // Primary raymarch
100
+ maxIterationCount: new Uniform(defaults.shadow.maxIterationCount),
101
+ minStepSize: new Uniform(defaults.shadow.minStepSize),
102
+ maxStepSize: new Uniform(defaults.shadow.maxStepSize),
103
+ minDensity: new Uniform(defaults.shadow.minDensity),
104
+ minExtinction: new Uniform(defaults.shadow.minExtinction),
105
+ minTransmittance: new Uniform(defaults.shadow.minTransmittance),
106
+ opticalDepthTailScale: new Uniform(2)
107
+ } satisfies ShadowMaterialUniforms,
108
+ defines: {
109
+ SHADOW: '1',
110
+ TEMPORAL_PASS: '1',
111
+ TEMPORAL_JITTER: '1'
112
+ }
113
+ })
114
+
115
+ this.cascadeCount = defaults.shadow.cascadeCount
116
+ }
117
+
118
+ setSize(width: number, height: number): void {
119
+ this.uniforms.resolution.value.set(width, height)
120
+ }
121
+
122
+ @defineExpression('LOCAL_WEATHER_CHANNELS', {
123
+ validate: value => /^[rgba]{4}$/.test(value)
124
+ })
125
+ localWeatherChannels = 'rgba'
126
+
127
+ @defineInt('CASCADE_COUNT', { min: 1, max: 4 })
128
+ cascadeCount: number = defaults.shadow.cascadeCount
129
+
130
+ @define('TEMPORAL_PASS')
131
+ temporalPass = true
132
+
133
+ @define('TEMPORAL_JITTER')
134
+ temporalJitter = true
135
+
136
+ @define('SHAPE_DETAIL')
137
+ shapeDetail: boolean = defaults.shapeDetail
138
+
139
+ @define('TURBULENCE')
140
+ turbulence: boolean = defaults.turbulence
141
+ }
@@ -0,0 +1,185 @@
1
+ import {
2
+ HalfFloatType,
3
+ LinearFilter,
4
+ WebGLArrayRenderTarget,
5
+ type DataArrayTexture,
6
+ type TextureDataType,
7
+ type WebGLRenderer
8
+ } from 'three'
9
+ import invariant from 'tiny-invariant'
10
+
11
+ import { PassBase, type PassBaseOptions } from './PassBase'
12
+ import { ShaderArrayPass } from './ShaderArrayPass'
13
+ import { ShadowMaterial } from './ShadowMaterial'
14
+ import { ShadowResolveMaterial } from './ShadowResolveMaterial'
15
+ import {
16
+ type AtmosphereUniforms,
17
+ type CloudLayerUniforms,
18
+ type CloudParameterUniforms
19
+ } from './uniforms'
20
+
21
+ function createRenderTarget(name: string): WebGLArrayRenderTarget {
22
+ const renderTarget = new WebGLArrayRenderTarget(1, 1, 1, {
23
+ depthBuffer: false,
24
+ stencilBuffer: false
25
+ })
26
+ // Constructor option doesn't work
27
+ renderTarget.texture.type = HalfFloatType
28
+ renderTarget.texture.minFilter = LinearFilter
29
+ renderTarget.texture.magFilter = LinearFilter
30
+ renderTarget.texture.name = name
31
+ return renderTarget
32
+ }
33
+
34
+ export interface ShadowPassOptions extends PassBaseOptions {
35
+ parameterUniforms: CloudParameterUniforms
36
+ layerUniforms: CloudLayerUniforms
37
+ atmosphereUniforms: AtmosphereUniforms
38
+ }
39
+
40
+ export class ShadowPass extends PassBase {
41
+ private currentRenderTarget!: WebGLArrayRenderTarget
42
+ readonly currentMaterial: ShadowMaterial
43
+ readonly currentPass: ShaderArrayPass
44
+ private resolveRenderTarget!: WebGLArrayRenderTarget | null
45
+ readonly resolveMaterial: ShadowResolveMaterial
46
+ readonly resolvePass: ShaderArrayPass
47
+ private historyRenderTarget!: WebGLArrayRenderTarget | null
48
+
49
+ private width = 0
50
+ private height = 0
51
+
52
+ constructor({
53
+ parameterUniforms,
54
+ layerUniforms,
55
+ atmosphereUniforms,
56
+ ...options
57
+ }: ShadowPassOptions) {
58
+ super('ShadowPass', options)
59
+
60
+ this.currentMaterial = new ShadowMaterial({
61
+ parameterUniforms,
62
+ layerUniforms,
63
+ atmosphereUniforms
64
+ })
65
+ this.currentPass = new ShaderArrayPass(this.currentMaterial)
66
+ this.resolveMaterial = new ShadowResolveMaterial()
67
+ this.resolvePass = new ShaderArrayPass(this.resolveMaterial)
68
+
69
+ this.initRenderTargets()
70
+ }
71
+
72
+ override initialize(
73
+ renderer: WebGLRenderer,
74
+ alpha: boolean,
75
+ frameBufferType: TextureDataType
76
+ ): void {
77
+ this.currentPass.initialize(renderer, alpha, frameBufferType)
78
+ this.resolvePass.initialize(renderer, alpha, frameBufferType)
79
+ }
80
+
81
+ private initRenderTargets(): void {
82
+ this.currentRenderTarget?.dispose()
83
+ this.resolveRenderTarget?.dispose()
84
+ this.historyRenderTarget?.dispose()
85
+ const current = createRenderTarget('Shadow')
86
+ const resolve = this.temporalPass ? createRenderTarget('Shadow.A') : null
87
+ const history = this.temporalPass ? createRenderTarget('Shadow.B') : null
88
+ this.currentRenderTarget = current
89
+ this.resolveRenderTarget = resolve
90
+ this.historyRenderTarget = history
91
+
92
+ const resolveUniforms = this.resolveMaterial.uniforms
93
+ resolveUniforms.inputBuffer.value = current.texture
94
+ resolveUniforms.historyBuffer.value = history?.texture ?? null
95
+ }
96
+
97
+ private copyShadow(): void {
98
+ const shadow = this.shadow
99
+ const currentUniforms = this.currentMaterial.uniforms
100
+ for (let i = 0; i < shadow.cascadeCount; ++i) {
101
+ const cascade = shadow.cascades[i]
102
+ currentUniforms.inverseShadowMatrices.value[i].copy(cascade.inverseMatrix)
103
+ }
104
+ }
105
+
106
+ private copyReprojection(): void {
107
+ const shadow = this.shadow
108
+ const uniforms = this.currentMaterial.uniforms
109
+ for (let i = 0; i < shadow.cascadeCount; ++i) {
110
+ const cascade = shadow.cascades[i]
111
+ uniforms.reprojectionMatrices.value[i].copy(cascade.matrix)
112
+ }
113
+ }
114
+
115
+ private swapBuffers(): void {
116
+ invariant(this.historyRenderTarget != null)
117
+ invariant(this.resolveRenderTarget != null)
118
+ const nextResolve = this.historyRenderTarget
119
+ const nextHistory = this.resolveRenderTarget
120
+ this.resolveRenderTarget = nextResolve
121
+ this.historyRenderTarget = nextHistory
122
+ this.resolveMaterial.uniforms.historyBuffer.value = nextHistory.texture
123
+ }
124
+
125
+ update(renderer: WebGLRenderer, frame: number, deltaTime: number): void {
126
+ this.currentMaterial.uniforms.frame.value = frame
127
+ this.copyShadow()
128
+
129
+ this.currentPass.render(renderer, null, this.currentRenderTarget)
130
+
131
+ if (this.temporalPass) {
132
+ invariant(this.resolveRenderTarget != null)
133
+ this.resolvePass.render(renderer, null, this.resolveRenderTarget)
134
+
135
+ // Store the current view and projection matrices for the next reprojection.
136
+ this.copyReprojection()
137
+
138
+ // Swap resolve and history render targets for the next render.
139
+ this.swapBuffers()
140
+ }
141
+ }
142
+
143
+ setSize(
144
+ width: number,
145
+ height: number,
146
+ depth = this.shadow.cascadeCount
147
+ ): void {
148
+ this.width = width
149
+ this.height = height
150
+
151
+ this.currentMaterial.cascadeCount = depth
152
+ this.resolveMaterial.cascadeCount = depth
153
+ this.currentMaterial.setSize(width, height)
154
+ this.resolveMaterial.setSize(width, height)
155
+
156
+ this.currentRenderTarget.setSize(
157
+ width,
158
+ height,
159
+ this.temporalPass ? depth * 2 : depth // For depth velocity
160
+ )
161
+ this.resolveRenderTarget?.setSize(width, height, depth)
162
+ this.historyRenderTarget?.setSize(width, height, depth)
163
+ }
164
+
165
+ get outputBuffer(): DataArrayTexture {
166
+ if (this.temporalPass) {
167
+ // Resolve and history render targets are already swapped.
168
+ invariant(this.historyRenderTarget != null)
169
+ return this.historyRenderTarget.texture
170
+ }
171
+ return this.currentRenderTarget.texture
172
+ }
173
+
174
+ get temporalPass(): boolean {
175
+ return this.currentMaterial.temporalPass
176
+ }
177
+
178
+ set temporalPass(value: boolean) {
179
+ if (value !== this.temporalPass) {
180
+ this.currentMaterial.temporalPass = value
181
+ this.initRenderTargets()
182
+ this.setSize(this.width, this.height)
183
+ }
184
+ }
185
+ }
@@ -0,0 +1,72 @@
1
+ import {
2
+ GLSL3,
3
+ RawShaderMaterial,
4
+ Uniform,
5
+ Vector2,
6
+ type DataArrayTexture
7
+ } from 'three'
8
+
9
+ import {
10
+ defineInt,
11
+ resolveIncludes,
12
+ unrollLoops
13
+ } from '@takram/three-geospatial'
14
+
15
+ import { defaults } from './qualityPresets'
16
+
17
+ import fragmentShader from './shaders/shadowResolve.frag?raw'
18
+ import vertexShader from './shaders/shadowResolve.vert?raw'
19
+ import varianceClipping from './shaders/varianceClipping.glsl?raw'
20
+
21
+ export interface ShadowResolveMaterialParameters {
22
+ inputBuffer?: DataArrayTexture | null
23
+ historyBuffer?: DataArrayTexture | null
24
+ }
25
+
26
+ export interface ShadowResolveMaterialUniforms {
27
+ [key: string]: Uniform<unknown>
28
+ inputBuffer: Uniform<DataArrayTexture | null>
29
+ historyBuffer: Uniform<DataArrayTexture | null>
30
+ texelSize: Uniform<Vector2>
31
+ varianceGamma: Uniform<number>
32
+ temporalAlpha: Uniform<number>
33
+ }
34
+
35
+ export class ShadowResolveMaterial extends RawShaderMaterial {
36
+ declare uniforms: ShadowResolveMaterialUniforms
37
+
38
+ constructor({
39
+ inputBuffer = null,
40
+ historyBuffer = null
41
+ }: ShadowResolveMaterialParameters = {}) {
42
+ super({
43
+ name: 'ShadowResolveMaterial',
44
+ glslVersion: GLSL3,
45
+ vertexShader,
46
+ fragmentShader: unrollLoops(
47
+ resolveIncludes(fragmentShader, {
48
+ varianceClipping
49
+ })
50
+ ),
51
+ uniforms: {
52
+ inputBuffer: new Uniform(inputBuffer),
53
+ historyBuffer: new Uniform(historyBuffer),
54
+ texelSize: new Uniform(new Vector2()),
55
+ varianceGamma: new Uniform(1),
56
+ // Use a very slow alpha because a single flickering pixel can be highly
57
+ // noticeable in shadow maps. This value can be increased if temporal
58
+ // jitter is turned off in the shadows rendering, but it will suffer
59
+ // from spatial aliasing.
60
+ temporalAlpha: new Uniform(0.01)
61
+ } satisfies ShadowResolveMaterialUniforms,
62
+ defines: {}
63
+ })
64
+ }
65
+
66
+ setSize(width: number, height: number): void {
67
+ this.uniforms.texelSize.value.set(1 / width, 1 / height)
68
+ }
69
+
70
+ @defineInt('CASCADE_COUNT', { min: 1, max: 4 })
71
+ cascadeCount: number = defaults.shadow.cascadeCount
72
+ }
@@ -0,0 +1,21 @@
1
+ import { resolveIncludes } from '@takram/three-geospatial'
2
+ import { math } from '@takram/three-geospatial/shaders'
3
+
4
+ import { ProceduralTextureBase } from './ProceduralTexture'
5
+
6
+ import perlin from './shaders/perlin.glsl?raw'
7
+ import tileableNoise from './shaders/tileableNoise.glsl?raw'
8
+ import fragmentShader from './shaders/turbulence.frag?raw'
9
+
10
+ export class Turbulence extends ProceduralTextureBase {
11
+ constructor() {
12
+ super({
13
+ size: 128,
14
+ fragmentShader: resolveIncludes(fragmentShader, {
15
+ core: { math },
16
+ perlin,
17
+ tileableNoise
18
+ })
19
+ })
20
+ }
21
+ }
package/src/bayer.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { Vector2 } from 'three'
2
+
3
+ // prettier-ignore
4
+ export const bayerIndices = [
5
+ 0, 8, 2, 10,
6
+ 12, 4, 14, 6,
7
+ 3, 11, 1, 9,
8
+ 15, 7, 13, 5
9
+ ]
10
+
11
+ export const bayerOffsets = /*#__PURE__*/ bayerIndices.reduce<Vector2[]>(
12
+ (result, _, index) => {
13
+ const offset = new Vector2()
14
+ for (let i = 0; i < 16; ++i) {
15
+ if (bayerIndices[i] === index) {
16
+ offset.set(((i % 4) + 0.5) / 4, (Math.floor(i / 4) + 0.5) / 4)
17
+ break
18
+ }
19
+ }
20
+ return [...result, offset]
21
+ },
22
+ []
23
+ )
@@ -0,0 +1,8 @@
1
+ export const CLOUD_SHAPE_TEXTURE_SIZE = 128
2
+ export const CLOUD_SHAPE_DETAIL_TEXTURE_SIZE = 32
3
+
4
+ const ref = '45a1c6c1bb9fd38b3680fd120795ff4c32df68ff'
5
+ export const DEFAULT_LOCAL_WEATHER_URL = `https://media.githubusercontent.com/media/takram-design-engineering/three-geospatial/${ref}/packages/clouds/assets/local_weather.png`
6
+ export const DEFAULT_SHAPE_URL = `https://media.githubusercontent.com/media/takram-design-engineering/three-geospatial/${ref}/packages/clouds/assets/shape.bin`
7
+ export const DEFAULT_SHAPE_DETAIL_URL = `https://media.githubusercontent.com/media/takram-design-engineering/three-geospatial/${ref}/packages/clouds/assets/shape_detail.bin`
8
+ export const DEFAULT_TURBULENCE_URL = `https://media.githubusercontent.com/media/takram-design-engineering/three-geospatial/${ref}/packages/clouds/assets/turbulence.png`
@@ -0,0 +1,138 @@
1
+ // Based on the following work with slight modifications.
2
+ // https://github.com/StrandedKitty/three-csm/
3
+ // https://github.com/mrdoob/three.js/tree/r169/examples/jsm/csm
4
+
5
+ /**
6
+ * MIT License
7
+ *
8
+ * Copyright (c) 2019 vtHawk
9
+ *
10
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ * of this software and associated documentation files (the "Software"), to deal
12
+ * in the Software without restriction, including without limitation the rights
13
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ * copies of the Software, and to permit persons to whom the Software is
15
+ * furnished to do so, subject to the following conditions:
16
+ *
17
+ * The above copyright notice and this permission notice shall be included in all
18
+ * copies or substantial portions of the Software.
19
+ *
20
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ * SOFTWARE.
27
+ */
28
+
29
+ import { Vector3, type Camera, type Matrix4 } from 'three'
30
+
31
+ declare module 'three' {
32
+ interface Camera {
33
+ isOrthographicCamera?: boolean
34
+ }
35
+ }
36
+
37
+ export class FrustumCorners {
38
+ readonly near = [new Vector3(), new Vector3(), new Vector3(), new Vector3()]
39
+ readonly far = [new Vector3(), new Vector3(), new Vector3(), new Vector3()]
40
+
41
+ constructor()
42
+ constructor(camera: Camera, far: number)
43
+ constructor(camera?: Camera, far?: number) {
44
+ if (camera != null && far != null) {
45
+ this.setFromCamera(camera, far)
46
+ }
47
+ }
48
+
49
+ clone(): FrustumCorners {
50
+ return new FrustumCorners().copy(this)
51
+ }
52
+
53
+ copy(other: FrustumCorners): this {
54
+ for (let i = 0; i < 4; ++i) {
55
+ this.near[i].copy(other.near[i])
56
+ this.far[i].copy(other.far[i])
57
+ }
58
+ return this
59
+ }
60
+
61
+ setFromCamera(camera: Camera, far: number): this {
62
+ const isOrthographic = camera.isOrthographicCamera === true
63
+ const inverseProjectionMatrix = camera.projectionMatrixInverse
64
+
65
+ // 3 --- 0
66
+ // | |
67
+ // 2 --- 1
68
+ // Clip space spans from [-1, 1]
69
+ this.near[0].set(1, 1, -1)
70
+ this.near[1].set(1, -1, -1)
71
+ this.near[2].set(-1, -1, -1)
72
+ this.near[3].set(-1, 1, -1)
73
+ for (let i = 0; i < 4; ++i) {
74
+ this.near[i].applyMatrix4(inverseProjectionMatrix)
75
+ }
76
+
77
+ this.far[0].set(1, 1, 1)
78
+ this.far[1].set(1, -1, 1)
79
+ this.far[2].set(-1, -1, 1)
80
+ this.far[3].set(-1, 1, 1)
81
+ for (let i = 0; i < 4; ++i) {
82
+ const corner = this.far[i]
83
+ corner.applyMatrix4(inverseProjectionMatrix)
84
+ const absZ = Math.abs(corner.z)
85
+ if (isOrthographic) {
86
+ corner.z *= Math.min(far / absZ, 1)
87
+ } else {
88
+ corner.multiplyScalar(Math.min(far / absZ, 1))
89
+ }
90
+ }
91
+ return this
92
+ }
93
+
94
+ split(
95
+ clipDepths: readonly number[],
96
+ result: FrustumCorners[] = []
97
+ ): FrustumCorners[] {
98
+ for (let index = 0; index < clipDepths.length; ++index) {
99
+ const frustum = (result[index] ??= new FrustumCorners())
100
+ if (index === 0) {
101
+ for (let i = 0; i < 4; ++i) {
102
+ frustum.near[i].copy(this.near[i])
103
+ }
104
+ } else {
105
+ for (let i = 0; i < 4; ++i) {
106
+ frustum.near[i].lerpVectors(
107
+ this.near[i],
108
+ this.far[i],
109
+ clipDepths[index - 1]
110
+ )
111
+ }
112
+ }
113
+ if (index === clipDepths.length - 1) {
114
+ for (let i = 0; i < 4; ++i) {
115
+ frustum.far[i].copy(this.far[i])
116
+ }
117
+ } else {
118
+ for (let i = 0; i < 4; ++i) {
119
+ frustum.far[i].lerpVectors(
120
+ this.near[i],
121
+ this.far[i],
122
+ clipDepths[index]
123
+ )
124
+ }
125
+ }
126
+ }
127
+ result.length = clipDepths.length
128
+ return result
129
+ }
130
+
131
+ applyMatrix4(matrix: Matrix4): this {
132
+ for (let i = 0; i < 4; ++i) {
133
+ this.near[i].applyMatrix4(matrix)
134
+ this.far[i].applyMatrix4(matrix)
135
+ }
136
+ return this
137
+ }
138
+ }
@@ -0,0 +1,32 @@
1
+ import { type WebGLArrayRenderTarget, type WebGLRenderer } from 'three'
2
+ import invariant from 'tiny-invariant'
3
+
4
+ export function setArrayRenderTargetLayers(
5
+ renderer: WebGLRenderer,
6
+ outputBuffer: WebGLArrayRenderTarget
7
+ ): void {
8
+ const glTexture = (
9
+ renderer.properties.get(outputBuffer.texture) as {
10
+ __webglTexture?: WebGLTexture
11
+ }
12
+ ).__webglTexture
13
+
14
+ const gl = renderer.getContext()
15
+ invariant(gl instanceof WebGL2RenderingContext)
16
+
17
+ renderer.setRenderTarget(outputBuffer)
18
+ const drawBuffers: number[] = []
19
+ if (glTexture != null) {
20
+ for (let layer = 0; layer < outputBuffer.depth; ++layer) {
21
+ gl.framebufferTextureLayer(
22
+ gl.FRAMEBUFFER,
23
+ gl.COLOR_ATTACHMENT0 + layer,
24
+ glTexture,
25
+ 0,
26
+ layer
27
+ )
28
+ drawBuffers.push(gl.COLOR_ATTACHMENT0 + layer)
29
+ }
30
+ }
31
+ gl.drawBuffers(drawBuffers)
32
+ }
@@ -0,0 +1,59 @@
1
+ import { lerp } from '@takram/three-geospatial'
2
+
3
+ export type FrustumSplitFunction = (
4
+ length: number,
5
+ near: number,
6
+ far: number,
7
+ lambda?: number,
8
+ result?: number[]
9
+ ) => number[]
10
+
11
+ export interface FrustumSplitFunctions {
12
+ uniform: FrustumSplitFunction
13
+ logarithmic: FrustumSplitFunction
14
+ practical: FrustumSplitFunction
15
+ }
16
+
17
+ export type FrustumSplitMode = keyof FrustumSplitFunctions
18
+
19
+ // See: https://developer.nvidia.com/gpugems/gpugems3/part-ii-light-and-shadows/chapter-10-parallel-split-shadow-maps-programmable-gpus
20
+ const modes: FrustumSplitFunctions = {
21
+ uniform: (count, near, far, _, result = []) => {
22
+ for (let i = 0; i < count; ++i) {
23
+ result[i] = (near + ((far - near) * (i + 1)) / count) / far
24
+ }
25
+ result.length = count
26
+ return result
27
+ },
28
+
29
+ logarithmic: (count, near, far, _, result = []) => {
30
+ for (let i = 0; i < count; ++i) {
31
+ result[i] = (near * (far / near) ** ((i + 1) / count)) / far
32
+ }
33
+ result.length = count
34
+ return result
35
+ },
36
+
37
+ practical: (count, near, far, lambda = 0.5, result = []) => {
38
+ for (let i = 0; i < count; ++i) {
39
+ const uniform = (near + ((far - near) * (i + 1)) / count) / far
40
+ const logarithmic = (near * (far / near) ** ((i + 1) / count)) / far
41
+ result[i] = lerp(uniform, logarithmic, lambda)
42
+ }
43
+ result.length = count
44
+ return result
45
+ }
46
+ }
47
+
48
+ export const frustumSplitFunctions = modes
49
+
50
+ export function splitFrustum(
51
+ mode: FrustumSplitMode,
52
+ count: number,
53
+ near: number,
54
+ far: number,
55
+ lambda?: number,
56
+ result: number[] = []
57
+ ): number[] {
58
+ return modes[mode](count, near, far, lambda, result)
59
+ }
package/src/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ export type { QualityPreset as CloudsQualityPreset } from './qualityPresets'
2
+ export type { FrustumSplitMode } from './helpers/splitFrustum'
3
+
4
+ export * from './CloudLayer'
5
+ export * from './CloudLayers'
6
+ export * from './CloudsEffect'
7
+ export * from './CloudShape'
8
+ export * from './CloudShapeDetail'
9
+ export * from './constants'
10
+ export * from './DensityProfile'
11
+ export * from './LocalWeather'
12
+ export * from './Procedural3DTexture'
13
+ export * from './ProceduralTexture'
14
+ export * from './Turbulence'