@takram/three-geospatial 0.4.0 → 0.5.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 (105) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/build/index.cjs +1 -1
  3. package/build/index.cjs.map +1 -1
  4. package/build/index.js +1109 -2108
  5. package/build/index.js.map +1 -1
  6. package/build/r3f.cjs +1 -1
  7. package/build/r3f.cjs.map +1 -1
  8. package/build/r3f.js +1 -1
  9. package/build/shaders.cjs +4 -4
  10. package/build/shaders.js +7 -7
  11. package/build/shared.cjs +1 -1
  12. package/build/shared.cjs.map +1 -1
  13. package/build/shared.js +8 -210
  14. package/build/shared.js.map +1 -1
  15. package/build/shared2.cjs +2 -0
  16. package/build/shared2.cjs.map +1 -0
  17. package/build/shared2.js +224 -0
  18. package/build/shared2.js.map +1 -0
  19. package/build/shared3.cjs +2 -0
  20. package/build/shared3.cjs.map +1 -0
  21. package/build/shared3.js +13 -0
  22. package/build/shared3.js.map +1 -0
  23. package/build/webgpu.cjs +2 -0
  24. package/build/webgpu.cjs.map +1 -0
  25. package/build/webgpu.js +1336 -0
  26. package/build/webgpu.js.map +1 -0
  27. package/package.json +14 -9
  28. package/src/Ellipsoid.ts +17 -0
  29. package/src/EllipsoidGeometry.ts +2 -2
  30. package/src/QuadGeometry.ts +14 -0
  31. package/src/bufferGeometry.ts +1 -1
  32. package/src/capabilities.ts +12 -3
  33. package/src/decorators.ts +2 -2
  34. package/src/index.ts +2 -2
  35. package/src/shaders/depth.glsl +3 -3
  36. package/src/types.ts +2 -0
  37. package/src/unrollLoops.ts +1 -1
  38. package/src/webgpu/DownsampleThresholdNode.ts +40 -0
  39. package/src/webgpu/DualMipmapFilterNode.ts +130 -0
  40. package/src/webgpu/FilterNode.ts +93 -0
  41. package/src/webgpu/FnLayout.ts +86 -0
  42. package/src/webgpu/FnVar.ts +26 -0
  43. package/src/webgpu/GaussianBlurNode.ts +129 -0
  44. package/src/webgpu/HighpVelocityNode.ts +115 -0
  45. package/src/webgpu/KawaseBlurNode.ts +76 -0
  46. package/src/webgpu/LensFlareNode.ts +128 -0
  47. package/src/webgpu/LensGhostNode.ts +62 -0
  48. package/src/webgpu/LensGlareNode.ts +318 -0
  49. package/src/webgpu/LensHaloNode.ts +99 -0
  50. package/src/webgpu/MipmapBlurNode.ts +113 -0
  51. package/src/webgpu/MipmapSurfaceBlurNode.ts +140 -0
  52. package/src/webgpu/OutputTexture3DNode.ts +34 -0
  53. package/src/webgpu/OutputTextureNode.ts +33 -0
  54. package/src/webgpu/RTTextureNode.ts +132 -0
  55. package/src/webgpu/SeparableFilterNode.ts +98 -0
  56. package/src/webgpu/SingleFilterNode.ts +80 -0
  57. package/src/webgpu/TemporalAntialiasNode.ts +571 -0
  58. package/src/webgpu/accessors.ts +67 -0
  59. package/src/webgpu/debug.ts +86 -0
  60. package/src/webgpu/generators.ts +40 -0
  61. package/src/webgpu/index.ts +21 -0
  62. package/src/webgpu/internals.ts +37 -0
  63. package/src/webgpu/math.ts +81 -0
  64. package/src/webgpu/node.ts +100 -0
  65. package/src/webgpu/sampling.ts +103 -0
  66. package/src/webgpu/transformations.ts +106 -0
  67. package/src/webgpu/utils.ts +13 -0
  68. package/types/Ellipsoid.d.ts +3 -0
  69. package/types/QuadGeometry.d.ts +4 -0
  70. package/types/bufferGeometry.d.ts +1 -1
  71. package/types/capabilities.d.ts +2 -1
  72. package/types/index.d.ts +2 -2
  73. package/types/types.d.ts +1 -0
  74. package/types/webgpu/DownsampleThresholdNode.d.ts +11 -0
  75. package/types/webgpu/DualMipmapFilterNode.d.ts +21 -0
  76. package/types/webgpu/FilterNode.d.ts +17 -0
  77. package/types/webgpu/FnLayout.d.ts +21 -0
  78. package/types/webgpu/FnVar.d.ts +6 -0
  79. package/types/webgpu/GaussianBlurNode.d.ts +10 -0
  80. package/types/webgpu/HighpVelocityNode.d.ts +18 -0
  81. package/types/webgpu/KawaseBlurNode.d.ts +10 -0
  82. package/types/webgpu/LensFlareNode.d.ts +25 -0
  83. package/types/webgpu/LensGhostNode.d.ts +8 -0
  84. package/types/webgpu/LensGlareNode.d.ts +32 -0
  85. package/types/webgpu/LensHaloNode.d.ts +11 -0
  86. package/types/webgpu/MipmapBlurNode.d.ts +12 -0
  87. package/types/webgpu/MipmapSurfaceBlurNode.d.ts +11 -0
  88. package/types/webgpu/OutputTexture3DNode.d.ts +11 -0
  89. package/types/webgpu/OutputTextureNode.d.ts +11 -0
  90. package/types/webgpu/RTTextureNode.d.ts +23 -0
  91. package/types/webgpu/SeparableFilterNode.d.ts +20 -0
  92. package/types/webgpu/SingleFilterNode.d.ts +17 -0
  93. package/types/webgpu/TemporalAntialiasNode.d.ts +56 -0
  94. package/types/webgpu/accessors.d.ts +10 -0
  95. package/types/webgpu/debug.d.ts +7 -0
  96. package/types/webgpu/generators.d.ts +4 -0
  97. package/types/webgpu/index.d.ts +21 -0
  98. package/types/webgpu/internals.d.ts +3 -0
  99. package/types/webgpu/math.d.ts +4 -0
  100. package/types/webgpu/node.d.ts +33 -0
  101. package/types/webgpu/sampling.d.ts +2 -0
  102. package/types/webgpu/transformations.d.ts +12 -0
  103. package/types/webgpu/utils.d.ts +3 -0
  104. package/src/assertions.ts +0 -1
  105. package/types/assertions.d.ts +0 -1
@@ -0,0 +1,86 @@
1
+ import type {
2
+ ProxiedTuple,
3
+ ShaderCallNodeInternal,
4
+ ShaderNodeFn,
5
+ Struct
6
+ } from 'three/src/nodes/TSL.js'
7
+ import { Fn } from 'three/tsl'
8
+ import type { NodeBuilder, Texture3DNode, TextureNode } from 'three/webgpu'
9
+
10
+ import type { NodeObject, NodeType } from './node'
11
+
12
+ // Note that "texture" and "texture3D" are just placeholders until TSL supports
13
+ // texture types.
14
+ type FnLayoutType = NodeType | Struct | 'texture' | 'texture3D'
15
+
16
+ export interface FnLayoutInput<T extends FnLayoutType = FnLayoutType> {
17
+ name: string
18
+ type: T
19
+ }
20
+
21
+ export interface FnLayout<
22
+ T extends FnLayoutType,
23
+ Inputs extends readonly FnLayoutInput[] = []
24
+ > {
25
+ typeOnly?: boolean
26
+ name: string
27
+ type: T
28
+ inputs?: Inputs
29
+ }
30
+
31
+ type InferNodeObject<T extends FnLayoutType> = T extends NodeType
32
+ ? NodeObject<T>
33
+ : T extends Struct
34
+ ? ReturnType<T>
35
+ : T extends 'texture'
36
+ ? NodeObject<TextureNode>
37
+ : T extends 'texture3D'
38
+ ? NodeObject<Texture3DNode>
39
+ : never
40
+
41
+ type InferNodeObjects<Inputs extends readonly FnLayoutInput[]> = {
42
+ [K in keyof Inputs]: Inputs[K] extends FnLayoutInput<infer T>
43
+ ? InferNodeObject<T>
44
+ : never
45
+ }
46
+
47
+ export type FnLayoutResult<
48
+ T extends FnLayoutType,
49
+ Inputs extends readonly FnLayoutInput[],
50
+ Nodes extends readonly unknown[] = InferNodeObjects<Inputs>
51
+ > = (
52
+ callback: (
53
+ ...args: [] extends Nodes ? [NodeBuilder] : [Nodes, NodeBuilder]
54
+ ) => InferNodeObject<T> | NodeObject<ShaderCallNodeInternal>
55
+ ) => ShaderNodeFn<ProxiedTuple<Nodes>>
56
+
57
+ function transformType(type: FnLayoutType): string {
58
+ if (typeof type === 'string') {
59
+ return type
60
+ }
61
+ if (type.layout.name == null) {
62
+ throw new Error('Struct name is required.')
63
+ }
64
+ return type.layout.name
65
+ }
66
+
67
+ export function FnLayout<
68
+ T extends FnLayoutType,
69
+ const Inputs extends readonly FnLayoutInput[] = []
70
+ >({
71
+ typeOnly = false,
72
+ ...layout
73
+ }: FnLayout<T, Inputs>): FnLayoutResult<T, Inputs> {
74
+ return typeOnly
75
+ ? callback => Fn(callback as any)
76
+ : callback =>
77
+ Fn(callback as any).setLayout({
78
+ ...layout,
79
+ type: transformType(layout.type),
80
+ inputs:
81
+ layout.inputs?.map(input => ({
82
+ ...input,
83
+ type: transformType(input.type)
84
+ })) ?? []
85
+ })
86
+ }
@@ -0,0 +1,26 @@
1
+ import type { ProxiedTuple, ShaderNodeFn } from 'three/src/nodes/TSL.js'
2
+ import { Fn } from 'three/tsl'
3
+ import type { NodeBuilder } from 'three/webgpu'
4
+
5
+ type NonCallable<T> = T extends (...args: any[]) => any ? never : T
6
+
7
+ export function FnVar<Args extends readonly unknown[], R>(
8
+ callback: (...args: Args) => (builder: NodeBuilder) => R
9
+ ): ShaderNodeFn<ProxiedTuple<Args>>
10
+
11
+ export function FnVar<Args extends readonly unknown[], R>(
12
+ callback: (...args: Args) => NonCallable<R>
13
+ ): ShaderNodeFn<ProxiedTuple<Args>>
14
+
15
+ export function FnVar<Args extends readonly unknown[], R>(
16
+ callback:
17
+ | ((...args: Args) => R)
18
+ | ((...args: Args) => (builder: NodeBuilder) => R)
19
+ ): ShaderNodeFn<ProxiedTuple<Args>> {
20
+ return Fn((args: Args, builder: NodeBuilder) => {
21
+ const result = callback(...args)
22
+ return typeof result === 'function'
23
+ ? (result as (builder: NodeBuilder) => R)(builder)
24
+ : result
25
+ })
26
+ }
@@ -0,0 +1,129 @@
1
+ import { add, Fn, nodeObject, uv } from 'three/tsl'
2
+ import type { NodeBuilder, TextureNode } from 'three/webgpu'
3
+ import invariant from 'tiny-invariant'
4
+
5
+ import type { Node, NodeObject } from './node'
6
+ import { SeparableFilterNode } from './SeparableFilterNode'
7
+
8
+ // Reference: https://github.com/pmndrs/postprocessing/blob/v6.37.8/src/core/GaussKernel.js
9
+
10
+ function getCoefficients(n: number): Float64Array {
11
+ invariant(n >= 0)
12
+ if (n === 0) {
13
+ return new Float64Array(0)
14
+ }
15
+ if (n === 1) {
16
+ return new Float64Array([1])
17
+ }
18
+
19
+ let row0 = new Float64Array(n)
20
+ let row1 = new Float64Array(n)
21
+ let result = row1
22
+
23
+ // Incrementally build Pascal's Triangle to get the desired row.
24
+ for (let y = 1; y <= n; ++y) {
25
+ for (let x = 0; x < y; ++x) {
26
+ row1[x] = x === 0 || x === y - 1 ? 1 : row0[x - 1] + row0[x]
27
+ }
28
+ result = row1
29
+ row1 = row0
30
+ row0 = result
31
+ }
32
+ return result
33
+ }
34
+
35
+ interface GaussianKernel {
36
+ weights: Float64Array
37
+ offsets: Float64Array
38
+ }
39
+
40
+ function createGaussianKernel(
41
+ kernelSize: number,
42
+ edgeBias = 2
43
+ ): GaussianKernel {
44
+ invariant(kernelSize >= 3)
45
+
46
+ const n = kernelSize + edgeBias * 2
47
+ const coefficients =
48
+ edgeBias > 0
49
+ ? getCoefficients(n).slice(edgeBias, -edgeBias)
50
+ : getCoefficients(n)
51
+
52
+ const mid = Math.floor((coefficients.length - 1) / 2)
53
+ const sum = coefficients.reduce((a, b) => a + b, 0)
54
+ const weights = coefficients.slice(mid)
55
+ const offsets = [...Array(mid + 1).keys()] // [0..mid+1]
56
+
57
+ const linearWeights = new Float64Array(Math.floor(offsets.length / 2))
58
+ const linearOffsets = new Float64Array(linearWeights.length)
59
+ linearWeights[0] = weights[0] / sum
60
+
61
+ for (let i = 1, j = 1; i < offsets.length - 1; i += 2, ++j) {
62
+ const offset0 = offsets[i]
63
+ const offset1 = offsets[i + 1]
64
+ const weight0 = weights[i]
65
+ const weight1 = weights[i + 1]
66
+
67
+ const weight = weight0 + weight1
68
+ const offset = (offset0 * weight0 + offset1 * weight1) / weight
69
+ linearWeights[j] = weight / sum
70
+ linearOffsets[j] = offset
71
+ }
72
+
73
+ // Ensure that the weights add up to 1.
74
+ const linearWeightSum =
75
+ (linearWeights.reduce((a, b) => a + b, 0) - linearWeights[0] * 0.5) * 2
76
+
77
+ if (linearWeightSum !== 0) {
78
+ const scale = 1 / linearWeightSum
79
+ for (let i = 0; i < linearWeights.length; ++i) {
80
+ linearWeights[i] *= scale
81
+ }
82
+ }
83
+
84
+ return {
85
+ offsets: linearOffsets,
86
+ weights: linearWeights
87
+ }
88
+ }
89
+
90
+ export class GaussianBlurNode extends SeparableFilterNode {
91
+ static override get type(): string {
92
+ return 'GaussianBlurNode'
93
+ }
94
+
95
+ private readonly kernelSize: number
96
+
97
+ constructor(inputNode?: TextureNode | null, kernelSize = 35) {
98
+ super(inputNode)
99
+ this.kernelSize = kernelSize
100
+ }
101
+
102
+ protected override setupOutputNode(builder: NodeBuilder): Node {
103
+ const { inputNode, inputTexelSize, direction } = this
104
+ invariant(inputNode != null)
105
+
106
+ const { offsets, weights } = createGaussianKernel(this.kernelSize)
107
+
108
+ return Fn(() => {
109
+ const center = uv()
110
+ const offsetSize = direction.mul(inputTexelSize).toVertexStage()
111
+
112
+ const output = inputNode.sample(center).mul(weights[0])
113
+ for (let i = 1; i < offsets.length; ++i) {
114
+ const offset = offsetSize.mul(offsets[i])
115
+ output.addAssign(
116
+ add(
117
+ inputNode.sample(center.add(offset)),
118
+ inputNode.sample(center.sub(offset))
119
+ ).mul(weights[i])
120
+ )
121
+ }
122
+ return output
123
+ })()
124
+ }
125
+ }
126
+
127
+ export const gaussianBlur = (
128
+ ...args: ConstructorParameters<typeof GaussianBlurNode>
129
+ ): NodeObject<GaussianBlurNode> => nodeObject(new GaussianBlurNode(...args))
@@ -0,0 +1,115 @@
1
+ import { Matrix4, type Object3D } from 'three'
2
+ import type { NodeFrame } from 'three/src/Three.WebGPU.js'
3
+ import {
4
+ nodeImmutable,
5
+ positionLocal,
6
+ positionPrevious,
7
+ sub,
8
+ uniform
9
+ } from 'three/tsl'
10
+ import { NodeUpdateType, TempNode, type NodeBuilder } from 'three/webgpu'
11
+
12
+ export class HighpVelocityNode extends TempNode {
13
+ static override get type(): string {
14
+ return 'HighpVelocityNode'
15
+ }
16
+
17
+ projectionMatrix?: Matrix4 | null
18
+
19
+ private readonly currentProjectionMatrix = uniform(new Matrix4())
20
+ private readonly previousProjectionMatrix = uniform('mat4')
21
+
22
+ private readonly currentModelViewMatrix = uniform(new Matrix4())
23
+ private readonly previousModelViewMatrix = uniform('mat4')
24
+ private readonly objectModelViewMatrices = new WeakMap<Object3D, Matrix4>()
25
+
26
+ constructor() {
27
+ super('vec3')
28
+
29
+ // Sequence:
30
+ // - updateBefore() for the first object
31
+ // - update() for the current frame
32
+ // - updateAfter() for the first object
33
+ // - updateBefore() for the next object
34
+ // - updateAfter() for the next object
35
+ // - ...
36
+ this.updateType = NodeUpdateType.FRAME
37
+ this.updateBeforeType = NodeUpdateType.OBJECT
38
+ this.updateAfterType = NodeUpdateType.OBJECT
39
+ }
40
+
41
+ // Executed once per frame:
42
+ override update({ camera }: NodeFrame): void {
43
+ if (camera == null) {
44
+ return
45
+ }
46
+ const {
47
+ currentProjectionMatrix: current,
48
+ previousProjectionMatrix: previous
49
+ } = this
50
+
51
+ const projectionMatrix = this.projectionMatrix ?? camera.projectionMatrix
52
+ if (previous.value == null) {
53
+ previous.value = new Matrix4().copy(projectionMatrix)
54
+ } else {
55
+ previous.value.copy(current.value)
56
+ }
57
+ current.value.copy(projectionMatrix)
58
+ }
59
+
60
+ // Executed once per object before rendering:
61
+ override updateBefore({ object, camera }: NodeFrame): void {
62
+ if (object == null || camera == null) {
63
+ return
64
+ }
65
+ const {
66
+ currentModelViewMatrix: current,
67
+ previousModelViewMatrix: previous,
68
+ objectModelViewMatrices: matrices
69
+ } = this
70
+
71
+ current.value.multiplyMatrices(
72
+ camera.matrixWorldInverse,
73
+ object.matrixWorld
74
+ )
75
+ previous.value = matrices.get(object) ?? current.value
76
+ }
77
+
78
+ // Executed once per object after rendering:
79
+ override updateAfter({ object }: NodeFrame): void {
80
+ if (object == null) {
81
+ return
82
+ }
83
+ const {
84
+ currentModelViewMatrix: current,
85
+ objectModelViewMatrices: matrices
86
+ } = this
87
+
88
+ let matrix = matrices.get(object)
89
+ if (matrix == null) {
90
+ matrix = new Matrix4()
91
+ matrices.set(object, matrix)
92
+ }
93
+ matrix.copy(current.value)
94
+ }
95
+
96
+ override setup(builder: NodeBuilder): unknown {
97
+ const currentClip = this.currentProjectionMatrix
98
+ .mul(this.currentModelViewMatrix)
99
+ .mul(positionLocal)
100
+ .toVertexStage()
101
+ const previousClip = this.previousProjectionMatrix
102
+ .mul(this.previousModelViewMatrix)
103
+ .mul(positionPrevious)
104
+ .toVertexStage()
105
+
106
+ // Perspective divisions cannot be performed in the vertex shader.
107
+ // See: http://john-chapman-graphics.blogspot.com/2013/01/per-object-motion-blur.html
108
+ const currentNDC = currentClip.xyz.div(currentClip.w)
109
+ const previousNDC = previousClip.xyz.div(previousClip.w)
110
+
111
+ return sub(currentNDC, previousNDC)
112
+ }
113
+ }
114
+
115
+ export const highpVelocity = /*#__PURE__*/ nodeImmutable(HighpVelocityNode)
@@ -0,0 +1,76 @@
1
+ import { add, nodeObject, uv, vec2, vec4 } from 'three/tsl'
2
+ import type { NodeBuilder, TextureNode } from 'three/webgpu'
3
+ import invariant from 'tiny-invariant'
4
+
5
+ import { DualMipmapFilterNode } from './DualMipmapFilterNode'
6
+ import type { Node, NodeObject } from './node'
7
+
8
+ export class KawaseBlurNode extends DualMipmapFilterNode {
9
+ static override get type(): string {
10
+ return 'KawaseBlurNode'
11
+ }
12
+
13
+ constructor(inputNode?: TextureNode | null, levels = 4) {
14
+ super(inputNode, levels)
15
+ this.resolutionScale = 0.5
16
+ }
17
+
18
+ protected override setupDownsampleNode(builder: NodeBuilder): Node {
19
+ const { inputNode, inputTexelSize } = this
20
+ invariant(inputNode != null)
21
+
22
+ const center = uv()
23
+ const offset = vec4(1, 1, -1, -1)
24
+ .mul(inputTexelSize.xyxy.mul(0.5))
25
+ .add(center.xyxy)
26
+ const uv1 = offset.zy.toVertexStage() // -0.5, 0.5
27
+ const uv2 = offset.xy.toVertexStage() // 0.5, 0.5
28
+ const uv3 = offset.xw.toVertexStage() // 0.5, -0.5
29
+ const uv4 = offset.zw.toVertexStage() // -0.5, -0.5
30
+
31
+ return add(
32
+ inputNode.sample(center).mul(4),
33
+ inputNode.sample(uv1),
34
+ inputNode.sample(uv2),
35
+ inputNode.sample(uv3),
36
+ inputNode.sample(uv4)
37
+ ).mul(1 / 8)
38
+ }
39
+
40
+ protected override setupUpsampleNode(builder: NodeBuilder): Node {
41
+ const { inputNode, inputTexelSize } = this
42
+ invariant(inputNode != null)
43
+
44
+ const center = uv()
45
+ const offset = vec4(1, 1, -1, -1)
46
+ .mul(inputTexelSize.xyxy.mul(0.5))
47
+ .add(center.xyxy)
48
+ const uv1 = offset.zy.toVertexStage() // -0.5, 0.5
49
+ const uv2 = offset.xy.toVertexStage() // 0.5, 0.5
50
+ const uv3 = offset.xw.toVertexStage() // 0.5, -0.5
51
+ const uv4 = offset.zw.toVertexStage() // -0.5, -0.5
52
+ const uv5 = vec2(offset.z, center.y).toVertexStage() // -0.5, 0
53
+ const uv6 = vec2(offset.x, center.y).toVertexStage() // 0.5, 0
54
+ const uv7 = vec2(center.x, offset.y).toVertexStage() // 0, 0.5
55
+ const uv8 = vec2(center.x, offset.w).toVertexStage() // 0, -0.5
56
+
57
+ return add(
58
+ add(
59
+ inputNode.sample(uv1),
60
+ inputNode.sample(uv2),
61
+ inputNode.sample(uv3),
62
+ inputNode.sample(uv4)
63
+ ).mul(1 / 12),
64
+ add(
65
+ inputNode.sample(uv5),
66
+ inputNode.sample(uv6),
67
+ inputNode.sample(uv7),
68
+ inputNode.sample(uv8)
69
+ ).mul(1 / 6)
70
+ )
71
+ }
72
+ }
73
+
74
+ export const kawaseBlur = (
75
+ ...args: ConstructorParameters<typeof KawaseBlurNode>
76
+ ): NodeObject<KawaseBlurNode> => nodeObject(new KawaseBlurNode(...args))
@@ -0,0 +1,128 @@
1
+ import { add, Fn, nodeObject, uniform } from 'three/tsl'
2
+ import {
3
+ TempNode,
4
+ type Node,
5
+ type NodeBuilder,
6
+ type TextureNode
7
+ } from 'three/webgpu'
8
+ import invariant from 'tiny-invariant'
9
+
10
+ import { DownsampleThresholdNode } from './DownsampleThresholdNode'
11
+ import { GaussianBlurNode } from './GaussianBlurNode'
12
+ import { LensGhostNode } from './LensGhostNode'
13
+ import { LensGlareNode } from './LensGlareNode'
14
+ import { LensHaloNode } from './LensHaloNode'
15
+ import { MipmapSurfaceBlurNode } from './MipmapSurfaceBlurNode'
16
+ import type { NodeObject } from './node'
17
+ import {
18
+ convertToTexture,
19
+ rtTexture,
20
+ type RTTextureNode
21
+ } from './RTTextureNode'
22
+ import { isWebGPU } from './utils'
23
+
24
+ export class LensFlareNode extends TempNode {
25
+ static override get type(): string {
26
+ return 'LensFlareNode'
27
+ }
28
+
29
+ inputNode?: TextureNode | null
30
+ thresholdNode: DownsampleThresholdNode
31
+ blurNode: GaussianBlurNode
32
+ ghostNode: LensGhostNode
33
+ haloNode: LensHaloNode
34
+ bloomNode: MipmapSurfaceBlurNode
35
+ glareNode: LensGlareNode
36
+
37
+ bloomIntensity = uniform(0.05)
38
+
39
+ featuresNode: RTTextureNode
40
+
41
+ constructor(inputNode?: TextureNode | null) {
42
+ super('vec4')
43
+ this.inputNode = inputNode
44
+
45
+ this.thresholdNode = new DownsampleThresholdNode()
46
+ this.blurNode = new GaussianBlurNode()
47
+ this.ghostNode = new LensGhostNode()
48
+ this.haloNode = new LensHaloNode()
49
+ this.bloomNode = new MipmapSurfaceBlurNode(null, 8)
50
+ this.glareNode = new LensGlareNode()
51
+
52
+ this.featuresNode = rtTexture(add(this.ghostNode, this.haloNode))
53
+ this.featuresNode.value.name = 'LensFlareNode.Features'
54
+ this.featuresNode.resolutionScale = 0.5
55
+
56
+ // Use the full resolution because the thresholdNode already downsamples the
57
+ // input texture.
58
+ this.blurNode.resolutionScale = 1
59
+ this.bloomNode.resolutionScale = 1
60
+ this.glareNode.resolutionScale = 1
61
+ }
62
+
63
+ override setup(builder: NodeBuilder): unknown {
64
+ const {
65
+ inputNode,
66
+ thresholdNode,
67
+ blurNode,
68
+ ghostNode,
69
+ haloNode,
70
+ bloomNode,
71
+ featuresNode,
72
+ glareNode
73
+ } = this
74
+ invariant(inputNode != null)
75
+
76
+ const threshold = thresholdNode.getTextureNode()
77
+ const blur = blurNode.getTextureNode()
78
+
79
+ // input → threshold → blur → ghost
80
+ // input → threshold → blur → halo
81
+ thresholdNode.inputNode = inputNode
82
+ blurNode.inputNode = threshold
83
+ ghostNode.inputNode = blur
84
+ haloNode.inputNode = blur
85
+
86
+ // input → threshold → bloom
87
+ bloomNode.inputNode = threshold
88
+
89
+ // input → threshold → glare
90
+ glareNode.inputNode = threshold
91
+
92
+ const bloom = nodeObject(bloomNode.getTextureNode()).mul(
93
+ this.bloomIntensity
94
+ )
95
+ const glare = glareNode.getTextureNode()
96
+
97
+ // TODO: Add an option to switch to mixing the bloom:
98
+ return Fn(() => {
99
+ // TODO: Prevent the output from becoming too bright.
100
+ const output = nodeObject(inputNode)
101
+ output.addAssign(bloom)
102
+ if (isWebGPU(builder)) {
103
+ output.addAssign(glare)
104
+ }
105
+ return output.add(featuresNode)
106
+ })()
107
+ }
108
+
109
+ override dispose(): void {
110
+ this.thresholdNode.dispose()
111
+ this.blurNode.dispose()
112
+ this.ghostNode.dispose()
113
+ this.haloNode.dispose()
114
+ this.bloomNode.dispose()
115
+ this.glareNode.dispose()
116
+ this.featuresNode.dispose()
117
+ super.dispose()
118
+ }
119
+ }
120
+
121
+ export const lensFlare = (inputNode: Node | null): NodeObject<LensFlareNode> =>
122
+ nodeObject(
123
+ new LensFlareNode(
124
+ inputNode != null
125
+ ? convertToTexture(inputNode, 'LensFlareNode.Input')
126
+ : null
127
+ )
128
+ )
@@ -0,0 +1,62 @@
1
+ import { add, sub, uniform, uv, vec3 } from 'three/tsl'
2
+ import { TempNode, type NodeBuilder, type TextureNode } from 'three/webgpu'
3
+ import invariant from 'tiny-invariant'
4
+
5
+ import { FnLayout } from './FnLayout'
6
+
7
+ export class LensGhostNode extends TempNode {
8
+ static override get type(): string {
9
+ return 'LensGhostNode'
10
+ }
11
+
12
+ inputNode?: TextureNode | null
13
+
14
+ intensity = uniform(1e-5)
15
+
16
+ constructor(inputNode?: TextureNode | null) {
17
+ super('vec3')
18
+ this.inputNode = inputNode
19
+ }
20
+
21
+ override setup(builder: NodeBuilder): unknown {
22
+ const { inputNode, intensity } = this
23
+ invariant(inputNode != null)
24
+
25
+ const sampleGhost = FnLayout({
26
+ name: 'sampleGhost',
27
+ type: 'vec3',
28
+ inputs: [
29
+ { name: 'uv', type: 'vec2' },
30
+ { name: 'direction', type: 'vec2' },
31
+ { name: 'color', type: 'vec3' },
32
+ { name: 'offset', type: 'float' }
33
+ ]
34
+ })(([uv, direction, color, offset]) => {
35
+ const suv = direction.mul(offset).add(uv.oneMinus()).saturate()
36
+ const result = inputNode.sample(suv).rgb.mul(color)
37
+
38
+ // Falloff at the perimeter:
39
+ const d = sub(0.5, suv)
40
+ .length()
41
+ .mul(1 / (Math.SQRT2 / 4))
42
+ .saturate()
43
+ result.mulAssign(d.oneMinus().pow(3))
44
+ return result
45
+ })
46
+
47
+ const uvNode = uv()
48
+ const direction = uvNode.sub(0.5)
49
+ const color = add(
50
+ sampleGhost(uvNode, direction, vec3(0.8, 0.8, 1), -5.0),
51
+ sampleGhost(uvNode, direction, vec3(1, 0.8, 0.4), -1.5),
52
+ sampleGhost(uvNode, direction, vec3(0.9, 1, 0.8), -0.4),
53
+ sampleGhost(uvNode, direction, vec3(1, 0.8, 0.4), -0.2),
54
+ sampleGhost(uvNode, direction, vec3(0.9, 0.7, 0.7), -0.1),
55
+ sampleGhost(uvNode, direction, vec3(0.5, 1, 0.4), 0.7),
56
+ sampleGhost(uvNode, direction, vec3(0.5, 0.5, 0.5), 1),
57
+ sampleGhost(uvNode, direction, vec3(1, 1, 0.6), 2.5),
58
+ sampleGhost(uvNode, direction, vec3(0.5, 0.8, 1), 10)
59
+ )
60
+ return color.mul(intensity)
61
+ }
62
+ }