@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.
- package/CHANGELOG.md +5 -0
- package/README.md +1130 -0
- package/assets/local_weather.png +0 -0
- package/assets/shape.bin +1 -0
- package/assets/shape_detail.bin +1 -0
- package/assets/turbulence.png +0 -0
- package/build/index.cjs +583 -0
- package/build/index.cjs.map +1 -0
- package/build/index.js +728 -0
- package/build/index.js.map +1 -0
- package/build/r3f.cjs +2 -0
- package/build/r3f.cjs.map +1 -0
- package/build/r3f.js +205 -0
- package/build/r3f.js.map +1 -0
- package/build/shared.cjs +2189 -0
- package/build/shared.cjs.map +1 -0
- package/build/shared.js +3825 -0
- package/build/shared.js.map +1 -0
- package/package.json +77 -0
- package/src/CascadedShadowMaps.ts +288 -0
- package/src/CloudLayer.ts +85 -0
- package/src/CloudLayers.test.ts +61 -0
- package/src/CloudLayers.ts +181 -0
- package/src/CloudShape.ts +22 -0
- package/src/CloudShapeDetail.ts +22 -0
- package/src/CloudsEffect.ts +810 -0
- package/src/CloudsMaterial.ts +467 -0
- package/src/CloudsPass.ts +285 -0
- package/src/CloudsResolveMaterial.ts +108 -0
- package/src/DensityProfile.ts +38 -0
- package/src/LocalWeather.ts +21 -0
- package/src/PassBase.ts +28 -0
- package/src/Procedural3DTexture.ts +94 -0
- package/src/ProceduralTexture.ts +94 -0
- package/src/ShaderArrayPass.ts +32 -0
- package/src/ShadowMaterial.ts +141 -0
- package/src/ShadowPass.ts +185 -0
- package/src/ShadowResolveMaterial.ts +72 -0
- package/src/Turbulence.ts +21 -0
- package/src/bayer.ts +23 -0
- package/src/constants.ts +8 -0
- package/src/helpers/FrustumCorners.ts +138 -0
- package/src/helpers/setArrayRenderTargetLayers.ts +32 -0
- package/src/helpers/splitFrustum.ts +59 -0
- package/src/index.ts +14 -0
- package/src/qualityPresets.ts +117 -0
- package/src/r3f/CloudLayer.tsx +95 -0
- package/src/r3f/CloudLayers.tsx +54 -0
- package/src/r3f/Clouds.tsx +278 -0
- package/src/r3f/index.ts +2 -0
- package/src/shaders/catmullRomSampling.glsl +113 -0
- package/src/shaders/cloudShape.frag +78 -0
- package/src/shaders/cloudShapeDetail.frag +56 -0
- package/src/shaders/clouds.frag +996 -0
- package/src/shaders/clouds.glsl +190 -0
- package/src/shaders/clouds.vert +69 -0
- package/src/shaders/cloudsEffect.frag +11 -0
- package/src/shaders/cloudsResolve.frag +202 -0
- package/src/shaders/cloudsResolve.vert +10 -0
- package/src/shaders/localWeather.frag +83 -0
- package/src/shaders/parameters.glsl +64 -0
- package/src/shaders/perlin.glsl +211 -0
- package/src/shaders/shadow.frag +197 -0
- package/src/shaders/shadow.vert +16 -0
- package/src/shaders/shadowResolve.frag +76 -0
- package/src/shaders/shadowResolve.vert +10 -0
- package/src/shaders/structuredSampling.glsl +101 -0
- package/src/shaders/tileableNoise.glsl +88 -0
- package/src/shaders/turbulence.frag +51 -0
- package/src/shaders/types.glsl +18 -0
- package/src/shaders/varianceClipping.glsl +114 -0
- package/src/uniforms.ts +218 -0
- package/types/CascadedShadowMaps.d.ts +52 -0
- package/types/CloudLayer.d.ts +26 -0
- package/types/CloudLayers.d.ts +21 -0
- package/types/CloudShape.d.ts +5 -0
- package/types/CloudShapeDetail.d.ts +5 -0
- package/types/CloudsEffect.d.ts +170 -0
- package/types/CloudsMaterial.d.ts +86 -0
- package/types/CloudsPass.d.ts +44 -0
- package/types/CloudsResolveMaterial.d.ts +30 -0
- package/types/DensityProfile.d.ts +12 -0
- package/types/LocalWeather.d.ts +5 -0
- package/types/PassBase.d.ts +14 -0
- package/types/Procedural3DTexture.d.ts +20 -0
- package/types/ProceduralTexture.d.ts +24 -0
- package/types/ShaderArrayPass.d.ts +7 -0
- package/types/ShadowMaterial.d.ts +34 -0
- package/types/ShadowPass.d.ts +34 -0
- package/types/ShadowResolveMaterial.d.ts +20 -0
- package/types/Turbulence.d.ts +5 -0
- package/types/bayer.d.ts +4 -0
- package/types/constants.d.ts +6 -0
- package/types/helpers/FrustumCorners.d.ts +18 -0
- package/types/helpers/setArrayRenderTargetLayers.d.ts +3 -0
- package/types/helpers/splitFrustum.d.ts +9 -0
- package/types/index.d.ts +13 -0
- package/types/qualityPresets.d.ts +46 -0
- package/types/r3f/CloudLayer.d.ts +7 -0
- package/types/r3f/CloudLayers.d.ts +15 -0
- package/types/r3f/Clouds.d.ts +16 -0
- package/types/r3f/index.d.ts +2 -0
- 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
|
+
)
|
package/src/constants.ts
ADDED
@@ -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'
|