@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,285 @@
1
+ import { ShaderPass } from 'postprocessing'
2
+ import {
3
+ HalfFloatType,
4
+ LinearFilter,
5
+ RedFormat,
6
+ WebGLRenderTarget,
7
+ type Camera,
8
+ type DataArrayTexture,
9
+ type DepthPackingStrategies,
10
+ type Texture,
11
+ type TextureDataType,
12
+ type WebGLRenderer
13
+ } from 'three'
14
+
15
+ import { type AtmosphereParameters } from '@takram/three-atmosphere'
16
+
17
+ import { CloudsMaterial } from './CloudsMaterial'
18
+ import { CloudsResolveMaterial } from './CloudsResolveMaterial'
19
+ import { PassBase, type PassBaseOptions } from './PassBase'
20
+ import { defaults } from './qualityPresets'
21
+ import {
22
+ type AtmosphereUniforms,
23
+ type CloudLayerUniforms,
24
+ type CloudParameterUniforms
25
+ } from './uniforms'
26
+
27
+ type RenderTarget = WebGLRenderTarget & {
28
+ depthVelocity: Texture | null
29
+ shadowLength: Texture | null
30
+ }
31
+
32
+ interface RenderTargetOptions {
33
+ depthVelocity: boolean
34
+ shadowLength: boolean
35
+ }
36
+
37
+ function createRenderTarget(
38
+ name: string,
39
+ { depthVelocity, shadowLength }: RenderTargetOptions
40
+ ): RenderTarget {
41
+ const renderTarget: WebGLRenderTarget & {
42
+ depthVelocity?: Texture
43
+ shadowLength?: Texture
44
+ } = new WebGLRenderTarget(1, 1, {
45
+ depthBuffer: false,
46
+ stencilBuffer: false,
47
+ type: HalfFloatType
48
+ })
49
+ renderTarget.texture.minFilter = LinearFilter
50
+ renderTarget.texture.magFilter = LinearFilter
51
+ renderTarget.texture.name = name
52
+
53
+ let depthVelocityBuffer
54
+ if (depthVelocity) {
55
+ depthVelocityBuffer = renderTarget.texture.clone()
56
+ depthVelocityBuffer.isRenderTargetTexture = true
57
+ renderTarget.depthVelocity = depthVelocityBuffer
58
+ renderTarget.textures.push(depthVelocityBuffer)
59
+ }
60
+ let shadowLengthBuffer
61
+ if (shadowLength) {
62
+ shadowLengthBuffer = renderTarget.texture.clone()
63
+ shadowLengthBuffer.isRenderTargetTexture = true
64
+ shadowLengthBuffer.format = RedFormat
65
+ renderTarget.shadowLength = shadowLengthBuffer
66
+ renderTarget.textures.push(shadowLengthBuffer)
67
+ }
68
+
69
+ return Object.assign(renderTarget, {
70
+ depthVelocity: depthVelocityBuffer ?? null,
71
+ shadowLength: shadowLengthBuffer ?? null
72
+ })
73
+ }
74
+
75
+ export interface CloudsPassOptions extends PassBaseOptions {
76
+ parameterUniforms: CloudParameterUniforms
77
+ layerUniforms: CloudLayerUniforms
78
+ atmosphereUniforms: AtmosphereUniforms
79
+ }
80
+
81
+ export class CloudsPass extends PassBase {
82
+ private currentRenderTarget!: RenderTarget
83
+ readonly currentMaterial: CloudsMaterial
84
+ readonly currentPass: ShaderPass
85
+ private resolveRenderTarget!: RenderTarget
86
+ readonly resolveMaterial: CloudsResolveMaterial
87
+ readonly resolvePass: ShaderPass
88
+ private historyRenderTarget!: RenderTarget
89
+
90
+ private width = 0
91
+ private height = 0
92
+
93
+ constructor(
94
+ {
95
+ parameterUniforms,
96
+ layerUniforms,
97
+ atmosphereUniforms,
98
+ ...options
99
+ }: CloudsPassOptions,
100
+ private readonly atmosphere: AtmosphereParameters
101
+ ) {
102
+ super('CloudsPass', options)
103
+
104
+ this.currentMaterial = new CloudsMaterial(
105
+ {
106
+ parameterUniforms,
107
+ layerUniforms,
108
+ atmosphereUniforms
109
+ },
110
+ atmosphere
111
+ )
112
+ this.currentPass = new ShaderPass(this.currentMaterial)
113
+ this.resolveMaterial = new CloudsResolveMaterial()
114
+ this.resolvePass = new ShaderPass(this.resolveMaterial)
115
+
116
+ this.initRenderTargets({
117
+ depthVelocity: true,
118
+ shadowLength: defaults.lightShafts
119
+ })
120
+ }
121
+
122
+ copyCameraSettings(camera: Camera): void {
123
+ this.currentMaterial.copyCameraSettings(camera)
124
+ }
125
+
126
+ override initialize(
127
+ renderer: WebGLRenderer,
128
+ alpha: boolean,
129
+ frameBufferType: TextureDataType
130
+ ): void {
131
+ this.currentPass.initialize(renderer, alpha, frameBufferType)
132
+ this.resolvePass.initialize(renderer, alpha, frameBufferType)
133
+ }
134
+
135
+ private initRenderTargets(options: RenderTargetOptions): void {
136
+ this.currentRenderTarget?.dispose()
137
+ this.resolveRenderTarget?.dispose()
138
+ this.historyRenderTarget?.dispose()
139
+ const current = createRenderTarget('Clouds', options)
140
+ const resolve = createRenderTarget('Clouds.A', {
141
+ ...options,
142
+ depthVelocity: false
143
+ })
144
+ const history = createRenderTarget('Clouds.B', {
145
+ ...options,
146
+ depthVelocity: false
147
+ })
148
+ this.currentRenderTarget = current
149
+ this.resolveRenderTarget = resolve
150
+ this.historyRenderTarget = history
151
+
152
+ const resolveUniforms = this.resolveMaterial.uniforms
153
+ resolveUniforms.colorBuffer.value = current.texture
154
+ resolveUniforms.depthVelocityBuffer.value = current.depthVelocity
155
+ resolveUniforms.shadowLengthBuffer.value = current.shadowLength
156
+ resolveUniforms.colorHistoryBuffer.value = history.texture
157
+ resolveUniforms.shadowLengthHistoryBuffer.value = history.shadowLength
158
+ }
159
+
160
+ private copyShadow(): void {
161
+ const shadow = this.shadow
162
+ const currentUniforms = this.currentMaterial.uniforms
163
+ for (let i = 0; i < shadow.cascadeCount; ++i) {
164
+ const cascade = shadow.cascades[i]
165
+ currentUniforms.shadowIntervals.value[i].copy(cascade.interval)
166
+ currentUniforms.shadowMatrices.value[i].copy(cascade.matrix)
167
+ }
168
+ currentUniforms.shadowFar.value = shadow.far
169
+ }
170
+
171
+ private copyReprojection(): void {
172
+ this.currentMaterial.copyReprojectionMatrix(this.mainCamera)
173
+ }
174
+
175
+ private swapBuffers(): void {
176
+ const nextResolve = this.historyRenderTarget
177
+ const nextHistory = this.resolveRenderTarget
178
+ this.resolveRenderTarget = nextResolve
179
+ this.historyRenderTarget = nextHistory
180
+
181
+ const resolveUniforms = this.resolveMaterial.uniforms
182
+ resolveUniforms.colorHistoryBuffer.value = nextHistory.texture
183
+ resolveUniforms.shadowLengthHistoryBuffer.value = nextHistory.shadowLength
184
+ }
185
+
186
+ update(renderer: WebGLRenderer, frame: number, deltaTime: number): void {
187
+ // Update frame uniforms before copyCameraSettings.
188
+ this.currentMaterial.uniforms.frame.value = frame
189
+ this.resolveMaterial.uniforms.frame.value = frame
190
+
191
+ this.copyCameraSettings(this.mainCamera)
192
+ this.copyShadow()
193
+
194
+ this.currentPass.render(renderer, null, this.currentRenderTarget)
195
+ this.resolvePass.render(renderer, null, this.resolveRenderTarget)
196
+
197
+ // Store the current view and projection matrices for the next reprojection.
198
+ this.copyReprojection()
199
+
200
+ // Swap resolve and history render targets for the next render.
201
+ this.swapBuffers()
202
+ }
203
+
204
+ override setSize(width: number, height: number): void {
205
+ this.width = width
206
+ this.height = height
207
+
208
+ if (this.temporalUpscale) {
209
+ const lowResWidth = Math.ceil(width / 4)
210
+ const lowResHeight = Math.ceil(height / 4)
211
+ this.currentRenderTarget.setSize(lowResWidth, lowResHeight)
212
+ this.currentMaterial.setSize(
213
+ lowResWidth * 4,
214
+ lowResHeight * 4,
215
+ width,
216
+ height
217
+ )
218
+ } else {
219
+ this.currentRenderTarget.setSize(width, height)
220
+ this.currentMaterial.setSize(width, height)
221
+ }
222
+ this.resolveRenderTarget.setSize(width, height)
223
+ this.resolveMaterial.setSize(width, height)
224
+ this.historyRenderTarget.setSize(width, height)
225
+ }
226
+
227
+ setShadowSize(width: number, height: number, depth: number): void {
228
+ this.currentMaterial.shadowCascadeCount = depth
229
+ this.currentMaterial.setShadowSize(width, height)
230
+ }
231
+
232
+ override setDepthTexture(
233
+ depthTexture: Texture,
234
+ depthPacking?: DepthPackingStrategies
235
+ ): void {
236
+ this.currentMaterial.depthBuffer = depthTexture
237
+ this.currentMaterial.depthPacking = depthPacking ?? 0
238
+ }
239
+
240
+ get outputBuffer(): Texture {
241
+ // Resolve and history render targets are already swapped.
242
+ return this.historyRenderTarget.texture
243
+ }
244
+
245
+ get shadowBuffer(): DataArrayTexture | null {
246
+ return this.currentMaterial.uniforms.shadowBuffer.value
247
+ }
248
+
249
+ set shadowBuffer(value: DataArrayTexture | null) {
250
+ this.currentMaterial.uniforms.shadowBuffer.value = value
251
+ }
252
+
253
+ get shadowLengthBuffer(): Texture | null {
254
+ // Resolve and history render targets are already swapped.
255
+ return this.historyRenderTarget.shadowLength
256
+ }
257
+
258
+ get temporalUpscale(): boolean {
259
+ return this.currentMaterial.temporalUpscale
260
+ }
261
+
262
+ set temporalUpscale(value: boolean) {
263
+ if (value !== this.temporalUpscale) {
264
+ this.currentMaterial.temporalUpscale = value
265
+ this.resolveMaterial.temporalUpscale = value
266
+ this.setSize(this.width, this.height)
267
+ }
268
+ }
269
+
270
+ get lightShafts(): boolean {
271
+ return this.currentMaterial.shadowLength
272
+ }
273
+
274
+ set lightShafts(value: boolean) {
275
+ if (value !== this.lightShafts) {
276
+ this.currentMaterial.shadowLength = value
277
+ this.resolveMaterial.shadowLength = value
278
+ this.initRenderTargets({
279
+ depthVelocity: true,
280
+ shadowLength: value
281
+ })
282
+ this.setSize(this.width, this.height)
283
+ }
284
+ }
285
+ }
@@ -0,0 +1,108 @@
1
+ import {
2
+ GLSL3,
3
+ RawShaderMaterial,
4
+ Uniform,
5
+ Vector2,
6
+ type BufferGeometry,
7
+ type Camera,
8
+ type Group,
9
+ type Object3D,
10
+ type Scene,
11
+ type Texture,
12
+ type WebGLRenderer
13
+ } from 'three'
14
+
15
+ import { define, resolveIncludes, unrollLoops } from '@takram/three-geospatial'
16
+ import { turbo } from '@takram/three-geospatial/shaders'
17
+
18
+ import { bayerOffsets } from './bayer'
19
+
20
+ import catmullRomSampling from './shaders/catmullRomSampling.glsl?raw'
21
+ import fragmentShader from './shaders/cloudsResolve.frag?raw'
22
+ import vertexShader from './shaders/cloudsResolve.vert?raw'
23
+ import varianceClipping from './shaders/varianceClipping.glsl?raw'
24
+
25
+ export interface CloudsResolveMaterialParameters {
26
+ colorBuffer?: Texture | null
27
+ depthVelocityBuffer?: Texture | null
28
+ shadowLengthBuffer?: Texture | null
29
+ colorHistoryBuffer?: Texture | null
30
+ shadowLengthHistoryBuffer?: Texture | null
31
+ }
32
+
33
+ export interface CloudsResolveMaterialUniforms {
34
+ [key: string]: Uniform<unknown>
35
+ colorBuffer: Uniform<Texture | null>
36
+ depthVelocityBuffer: Uniform<Texture | null>
37
+ shadowLengthBuffer: Uniform<Texture | null>
38
+ colorHistoryBuffer: Uniform<Texture | null>
39
+ shadowLengthHistoryBuffer: Uniform<Texture | null>
40
+ texelSize: Uniform<Vector2>
41
+ frame: Uniform<number>
42
+ jitterOffset: Uniform<Vector2>
43
+ varianceGamma: Uniform<number>
44
+ temporalAlpha: Uniform<number>
45
+ }
46
+
47
+ export class CloudsResolveMaterial extends RawShaderMaterial {
48
+ declare uniforms: CloudsResolveMaterialUniforms
49
+
50
+ constructor({
51
+ colorBuffer = null,
52
+ depthVelocityBuffer = null,
53
+ shadowLengthBuffer = null,
54
+ colorHistoryBuffer = null,
55
+ shadowLengthHistoryBuffer = null
56
+ }: CloudsResolveMaterialParameters = {}) {
57
+ super({
58
+ name: 'CloudsResolveMaterial',
59
+ glslVersion: GLSL3,
60
+ vertexShader,
61
+ fragmentShader: unrollLoops(
62
+ resolveIncludes(fragmentShader, {
63
+ core: { turbo },
64
+ catmullRomSampling,
65
+ varianceClipping
66
+ })
67
+ ),
68
+ uniforms: {
69
+ colorBuffer: new Uniform(colorBuffer),
70
+ depthVelocityBuffer: new Uniform(depthVelocityBuffer),
71
+ shadowLengthBuffer: new Uniform(shadowLengthBuffer),
72
+ colorHistoryBuffer: new Uniform(colorHistoryBuffer),
73
+ shadowLengthHistoryBuffer: new Uniform(shadowLengthHistoryBuffer),
74
+ texelSize: new Uniform(new Vector2()),
75
+ frame: new Uniform(0),
76
+ jitterOffset: new Uniform(new Vector2()),
77
+ varianceGamma: new Uniform(2),
78
+ temporalAlpha: new Uniform(0.1)
79
+ } satisfies CloudsResolveMaterialUniforms
80
+ })
81
+ }
82
+
83
+ setSize(width: number, height: number): void {
84
+ this.uniforms.texelSize.value.set(1 / width, 1 / height)
85
+ }
86
+
87
+ onBeforeRender(
88
+ renderer: WebGLRenderer,
89
+ scene: Scene,
90
+ camera: Camera,
91
+ geometry: BufferGeometry,
92
+ object: Object3D,
93
+ group: Group
94
+ ): void {
95
+ const uniforms = this.uniforms
96
+ const frame = uniforms.frame.value % 16
97
+ const offset = bayerOffsets[frame]
98
+ const dx = (offset.x - 0.5) * 4
99
+ const dy = (offset.y - 0.5) * 4
100
+ this.uniforms.jitterOffset.value.set(dx, dy)
101
+ }
102
+
103
+ @define('TEMPORAL_UPSCALE')
104
+ temporalUpscale = true
105
+
106
+ @define('SHADOW_LENGTH')
107
+ shadowLength = true
108
+ }
@@ -0,0 +1,38 @@
1
+ export interface DensityProfileLike
2
+ extends Partial<
3
+ Pick<DensityProfile, 'expTerm' | 'exponent' | 'linearTerm' | 'constantTerm'>
4
+ > {}
5
+
6
+ export class DensityProfile {
7
+ constructor(
8
+ public expTerm = 0,
9
+ public exponent = 0,
10
+ public linearTerm = 0,
11
+ public constantTerm = 0
12
+ ) {}
13
+
14
+ set(expTerm = 0, exponent = 0, linearTerm = 0, constantTerm = 0): this {
15
+ this.expTerm = expTerm
16
+ this.exponent = exponent
17
+ this.linearTerm = linearTerm
18
+ this.constantTerm = constantTerm
19
+ return this
20
+ }
21
+
22
+ clone(): DensityProfile {
23
+ return new DensityProfile(
24
+ this.expTerm,
25
+ this.exponent,
26
+ this.linearTerm,
27
+ this.constantTerm
28
+ )
29
+ }
30
+
31
+ copy(other: DensityProfile): this {
32
+ this.expTerm = other.expTerm
33
+ this.exponent = other.exponent
34
+ this.linearTerm = other.linearTerm
35
+ this.constantTerm = other.constantTerm
36
+ return this
37
+ }
38
+ }
@@ -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 fragmentShader from './shaders/localWeather.frag?raw'
7
+ import perlin from './shaders/perlin.glsl?raw'
8
+ import tileableNoise from './shaders/tileableNoise.glsl?raw'
9
+
10
+ export class LocalWeather extends ProceduralTextureBase {
11
+ constructor() {
12
+ super({
13
+ size: 512,
14
+ fragmentShader: resolveIncludes(fragmentShader, {
15
+ core: { math },
16
+ perlin,
17
+ tileableNoise
18
+ })
19
+ })
20
+ }
21
+ }
@@ -0,0 +1,28 @@
1
+ import { Pass } from 'postprocessing'
2
+ import { Camera } from 'three'
3
+
4
+ import { type CascadedShadowMaps } from './CascadedShadowMaps'
5
+
6
+ export interface PassBaseOptions {
7
+ shadow: CascadedShadowMaps
8
+ }
9
+
10
+ export abstract class PassBase extends Pass {
11
+ shadow: CascadedShadowMaps
12
+
13
+ private _mainCamera = new Camera()
14
+
15
+ constructor(name: string, options: PassBaseOptions) {
16
+ super(name)
17
+ const { shadow } = options
18
+ this.shadow = shadow
19
+ }
20
+
21
+ get mainCamera(): Camera {
22
+ return this._mainCamera
23
+ }
24
+
25
+ set mainCamera(value: Camera) {
26
+ this._mainCamera = value
27
+ }
28
+ }
@@ -0,0 +1,94 @@
1
+ import {
2
+ Camera,
3
+ GLSL3,
4
+ LinearFilter,
5
+ Mesh,
6
+ NoColorSpace,
7
+ PlaneGeometry,
8
+ RawShaderMaterial,
9
+ RedFormat,
10
+ RepeatWrapping,
11
+ Uniform,
12
+ WebGL3DRenderTarget,
13
+ type Data3DTexture,
14
+ type WebGLRenderer
15
+ } from 'three'
16
+
17
+ import { type ProceduralTexture } from './ProceduralTexture'
18
+
19
+ export type Procedural3DTexture = ProceduralTexture<Data3DTexture>
20
+
21
+ export interface Procedural3DTextureBaseParameters {
22
+ size: number
23
+ fragmentShader: string
24
+ }
25
+
26
+ export class Procedural3DTextureBase implements Procedural3DTexture {
27
+ readonly size: number
28
+ needsRender = true
29
+
30
+ private readonly material: RawShaderMaterial
31
+ private readonly mesh: Mesh
32
+ private readonly renderTarget: WebGL3DRenderTarget
33
+ private readonly camera = new Camera()
34
+
35
+ constructor({ size, fragmentShader }: Procedural3DTextureBaseParameters) {
36
+ this.size = size
37
+ this.material = new RawShaderMaterial({
38
+ glslVersion: GLSL3,
39
+ vertexShader: /* glsl */ `
40
+ in vec3 position;
41
+ out vec2 vUv;
42
+ void main() {
43
+ vUv = position.xy * 0.5 + 0.5;
44
+ gl_Position = vec4(position.xy, 0.0, 1.0);
45
+ }
46
+ `,
47
+ fragmentShader,
48
+ uniforms: {
49
+ layer: new Uniform(0)
50
+ }
51
+ })
52
+ this.mesh = new Mesh(new PlaneGeometry(2, 2), this.material)
53
+
54
+ this.renderTarget = new WebGL3DRenderTarget(size, size, size, {
55
+ depthBuffer: false,
56
+ stencilBuffer: false,
57
+ format: RedFormat
58
+ })
59
+ const texture = this.renderTarget.texture
60
+ texture.minFilter = LinearFilter
61
+ texture.magFilter = LinearFilter
62
+ texture.wrapS = RepeatWrapping
63
+ texture.wrapT = RepeatWrapping
64
+ texture.wrapR = RepeatWrapping
65
+ texture.colorSpace = NoColorSpace
66
+ texture.needsUpdate = true
67
+ }
68
+
69
+ dispose(): void {
70
+ this.renderTarget.dispose()
71
+ this.material.dispose()
72
+ }
73
+
74
+ render(renderer: WebGLRenderer, deltaTime?: number): void {
75
+ if (!this.needsRender) {
76
+ return
77
+ }
78
+ this.needsRender = false
79
+
80
+ // Unfortunately, rendering into 3D target requires as many draw calls as
81
+ // the value of "size".
82
+ const renderTarget = renderer.getRenderTarget()
83
+ for (let layer = 0; layer < this.size; ++layer) {
84
+ this.material.uniforms.layer.value = layer / this.size
85
+ renderer.setRenderTarget(this.renderTarget, layer)
86
+ renderer.render(this.mesh, this.camera)
87
+ }
88
+ renderer.setRenderTarget(renderTarget)
89
+ }
90
+
91
+ get texture(): Data3DTexture {
92
+ return this.renderTarget.texture
93
+ }
94
+ }
@@ -0,0 +1,94 @@
1
+ import {
2
+ Camera,
3
+ GLSL3,
4
+ LinearFilter,
5
+ LinearMipMapLinearFilter,
6
+ Mesh,
7
+ NoColorSpace,
8
+ PlaneGeometry,
9
+ RawShaderMaterial,
10
+ RepeatWrapping,
11
+ RGBAFormat,
12
+ Uniform,
13
+ WebGLRenderTarget,
14
+ type Texture,
15
+ type WebGLRenderer
16
+ } from 'three'
17
+
18
+ export interface ProceduralTexture<T extends Texture = Texture> {
19
+ readonly size: number
20
+ readonly texture: T
21
+
22
+ dispose: () => void
23
+ render: (renderer: WebGLRenderer, deltaTime?: number) => void
24
+ }
25
+
26
+ export interface ProceduralTextureBaseParameters {
27
+ size: number
28
+ fragmentShader: string
29
+ }
30
+
31
+ export class ProceduralTextureBase implements ProceduralTexture {
32
+ readonly size: number
33
+ needsRender = true
34
+
35
+ private readonly material: RawShaderMaterial
36
+ private readonly mesh: Mesh
37
+ private readonly renderTarget: WebGLRenderTarget
38
+ private readonly camera = new Camera()
39
+
40
+ constructor({ size, fragmentShader }: ProceduralTextureBaseParameters) {
41
+ this.size = size
42
+ this.material = new RawShaderMaterial({
43
+ glslVersion: GLSL3,
44
+ vertexShader: /* glsl */ `
45
+ in vec3 position;
46
+ out vec2 vUv;
47
+ void main() {
48
+ vUv = position.xy * 0.5 + 0.5;
49
+ gl_Position = vec4(position.xy, 0.0, 1.0);
50
+ }
51
+ `,
52
+ fragmentShader,
53
+ uniforms: {
54
+ layer: new Uniform(0)
55
+ }
56
+ })
57
+ this.mesh = new Mesh(new PlaneGeometry(2, 2), this.material)
58
+
59
+ this.renderTarget = new WebGLRenderTarget(size, size, {
60
+ depthBuffer: false,
61
+ stencilBuffer: false,
62
+ format: RGBAFormat
63
+ })
64
+ const texture = this.renderTarget.texture
65
+ texture.generateMipmaps = true
66
+ texture.minFilter = LinearMipMapLinearFilter
67
+ texture.magFilter = LinearFilter
68
+ texture.wrapS = RepeatWrapping
69
+ texture.wrapT = RepeatWrapping
70
+ texture.colorSpace = NoColorSpace
71
+ texture.needsUpdate = true
72
+ }
73
+
74
+ dispose(): void {
75
+ this.renderTarget.dispose()
76
+ this.material.dispose()
77
+ }
78
+
79
+ render(renderer: WebGLRenderer, deltaTime?: number): void {
80
+ if (!this.needsRender) {
81
+ return
82
+ }
83
+ this.needsRender = false
84
+
85
+ const renderTarget = renderer.getRenderTarget()
86
+ renderer.setRenderTarget(this.renderTarget)
87
+ renderer.render(this.mesh, this.camera)
88
+ renderer.setRenderTarget(renderTarget)
89
+ }
90
+
91
+ get texture(): Texture {
92
+ return this.renderTarget.texture
93
+ }
94
+ }
@@ -0,0 +1,32 @@
1
+ import { ShaderPass, type CopyMaterial } from 'postprocessing'
2
+ import {
3
+ type Uniform,
4
+ type WebGLArrayRenderTarget,
5
+ type WebGLRenderer,
6
+ type WebGLRenderTarget
7
+ } from 'three'
8
+
9
+ import { setArrayRenderTargetLayers } from './helpers/setArrayRenderTargetLayers'
10
+
11
+ export class ShaderArrayPass extends ShaderPass {
12
+ declare input: string
13
+
14
+ override render(
15
+ renderer: WebGLRenderer,
16
+ inputBuffer: WebGLRenderTarget | null,
17
+ outputBuffer: WebGLArrayRenderTarget,
18
+ deltaTime?: number,
19
+ stencilTest?: boolean
20
+ ): void {
21
+ const uniforms = (
22
+ this.fullscreenMaterial as CopyMaterial & {
23
+ uniforms?: Record<string, Uniform>
24
+ }
25
+ ).uniforms
26
+ if (inputBuffer !== null && uniforms?.[this.input] != null) {
27
+ uniforms[this.input].value = inputBuffer.texture
28
+ }
29
+ setArrayRenderTargetLayers(renderer, outputBuffer)
30
+ renderer.render(this.scene, this.camera)
31
+ }
32
+ }