@takram/three-geospatial 0.3.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.
- package/CHANGELOG.md +28 -1
- package/build/index.cjs +1 -1
- package/build/index.cjs.map +1 -1
- package/build/index.js +1109 -2108
- package/build/index.js.map +1 -1
- package/build/r3f.cjs +1 -1
- package/build/r3f.cjs.map +1 -1
- package/build/r3f.js +1 -1
- package/build/r3f.js.map +1 -1
- package/build/shaders.cjs +4 -4
- package/build/shaders.js +7 -7
- package/build/shared.cjs +1 -1
- package/build/shared.cjs.map +1 -1
- package/build/shared.js +8 -206
- package/build/shared.js.map +1 -1
- package/build/shared2.cjs +2 -0
- package/build/shared2.cjs.map +1 -0
- package/build/shared2.js +224 -0
- package/build/shared2.js.map +1 -0
- package/build/shared3.cjs +2 -0
- package/build/shared3.cjs.map +1 -0
- package/build/shared3.js +13 -0
- package/build/shared3.js.map +1 -0
- package/build/webgpu.cjs +2 -0
- package/build/webgpu.cjs.map +1 -0
- package/build/webgpu.js +1336 -0
- package/build/webgpu.js.map +1 -0
- package/package.json +14 -9
- package/src/Ellipsoid.ts +25 -0
- package/src/EllipsoidGeometry.ts +2 -2
- package/src/QuadGeometry.ts +14 -0
- package/src/bufferGeometry.ts +1 -1
- package/src/capabilities.ts +12 -3
- package/src/decorators.ts +11 -6
- package/src/index.ts +2 -2
- package/src/shaders/depth.glsl +3 -3
- package/src/types.ts +2 -0
- package/src/unrollLoops.ts +1 -1
- package/src/webgpu/DownsampleThresholdNode.ts +40 -0
- package/src/webgpu/DualMipmapFilterNode.ts +130 -0
- package/src/webgpu/FilterNode.ts +93 -0
- package/src/webgpu/FnLayout.ts +86 -0
- package/src/webgpu/FnVar.ts +26 -0
- package/src/webgpu/GaussianBlurNode.ts +129 -0
- package/src/webgpu/HighpVelocityNode.ts +115 -0
- package/src/webgpu/KawaseBlurNode.ts +76 -0
- package/src/webgpu/LensFlareNode.ts +128 -0
- package/src/webgpu/LensGhostNode.ts +62 -0
- package/src/webgpu/LensGlareNode.ts +318 -0
- package/src/webgpu/LensHaloNode.ts +99 -0
- package/src/webgpu/MipmapBlurNode.ts +113 -0
- package/src/webgpu/MipmapSurfaceBlurNode.ts +140 -0
- package/src/webgpu/OutputTexture3DNode.ts +34 -0
- package/src/webgpu/OutputTextureNode.ts +33 -0
- package/src/webgpu/RTTextureNode.ts +132 -0
- package/src/webgpu/SeparableFilterNode.ts +98 -0
- package/src/webgpu/SingleFilterNode.ts +80 -0
- package/src/webgpu/TemporalAntialiasNode.ts +571 -0
- package/src/webgpu/accessors.ts +67 -0
- package/src/webgpu/debug.ts +86 -0
- package/src/webgpu/generators.ts +40 -0
- package/src/webgpu/index.ts +21 -0
- package/src/webgpu/internals.ts +37 -0
- package/src/webgpu/math.ts +81 -0
- package/src/webgpu/node.ts +100 -0
- package/src/webgpu/sampling.ts +103 -0
- package/src/webgpu/transformations.ts +106 -0
- package/src/webgpu/utils.ts +13 -0
- package/types/Ellipsoid.d.ts +4 -0
- package/types/QuadGeometry.d.ts +4 -0
- package/types/bufferGeometry.d.ts +2 -2
- package/types/capabilities.d.ts +2 -1
- package/types/decorators.d.ts +8 -5
- package/types/index.d.ts +2 -2
- package/types/types.d.ts +1 -0
- package/types/webgpu/DownsampleThresholdNode.d.ts +11 -0
- package/types/webgpu/DualMipmapFilterNode.d.ts +21 -0
- package/types/webgpu/FilterNode.d.ts +17 -0
- package/types/webgpu/FnLayout.d.ts +21 -0
- package/types/webgpu/FnVar.d.ts +6 -0
- package/types/webgpu/GaussianBlurNode.d.ts +10 -0
- package/types/webgpu/HighpVelocityNode.d.ts +18 -0
- package/types/webgpu/KawaseBlurNode.d.ts +10 -0
- package/types/webgpu/LensFlareNode.d.ts +25 -0
- package/types/webgpu/LensGhostNode.d.ts +8 -0
- package/types/webgpu/LensGlareNode.d.ts +32 -0
- package/types/webgpu/LensHaloNode.d.ts +11 -0
- package/types/webgpu/MipmapBlurNode.d.ts +12 -0
- package/types/webgpu/MipmapSurfaceBlurNode.d.ts +11 -0
- package/types/webgpu/OutputTexture3DNode.d.ts +11 -0
- package/types/webgpu/OutputTextureNode.d.ts +11 -0
- package/types/webgpu/RTTextureNode.d.ts +23 -0
- package/types/webgpu/SeparableFilterNode.d.ts +20 -0
- package/types/webgpu/SingleFilterNode.d.ts +17 -0
- package/types/webgpu/TemporalAntialiasNode.d.ts +56 -0
- package/types/webgpu/accessors.d.ts +10 -0
- package/types/webgpu/debug.d.ts +7 -0
- package/types/webgpu/generators.d.ts +4 -0
- package/types/webgpu/index.d.ts +21 -0
- package/types/webgpu/internals.d.ts +3 -0
- package/types/webgpu/math.d.ts +4 -0
- package/types/webgpu/node.d.ts +33 -0
- package/types/webgpu/sampling.d.ts +2 -0
- package/types/webgpu/transformations.d.ts +12 -0
- package/types/webgpu/utils.d.ts +3 -0
- package/src/assertions.ts +0 -1
- package/types/assertions.d.ts +0 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HalfFloatType,
|
|
3
|
+
LinearFilter,
|
|
4
|
+
RenderTarget,
|
|
5
|
+
RGBAFormat,
|
|
6
|
+
type Texture
|
|
7
|
+
} from 'three'
|
|
8
|
+
import {
|
|
9
|
+
NodeUpdateType,
|
|
10
|
+
TempNode,
|
|
11
|
+
type NodeBuilder,
|
|
12
|
+
type TextureNode
|
|
13
|
+
} from 'three/webgpu'
|
|
14
|
+
import invariant from 'tiny-invariant'
|
|
15
|
+
|
|
16
|
+
import { outputTexture } from './OutputTextureNode'
|
|
17
|
+
|
|
18
|
+
// Represents a node that applies a shader on the input texture and outputs
|
|
19
|
+
// another texture of the same dimensions regardless of the drawing buffer size.
|
|
20
|
+
export abstract class FilterNode extends TempNode {
|
|
21
|
+
static override get type(): string {
|
|
22
|
+
return 'FilterNode'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
inputNode?: TextureNode | null
|
|
26
|
+
resolutionScale = 1
|
|
27
|
+
|
|
28
|
+
private textureNode?: TextureNode
|
|
29
|
+
private readonly renderTargets: RenderTarget[] = []
|
|
30
|
+
|
|
31
|
+
constructor(inputNode?: TextureNode | null) {
|
|
32
|
+
super('vec4')
|
|
33
|
+
this.inputNode = inputNode
|
|
34
|
+
this.updateBeforeType = NodeUpdateType.FRAME
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
protected createRenderTarget(name?: string): RenderTarget {
|
|
38
|
+
const renderTarget = new RenderTarget(1, 1, {
|
|
39
|
+
depthBuffer: false,
|
|
40
|
+
type: HalfFloatType,
|
|
41
|
+
format: RGBAFormat
|
|
42
|
+
})
|
|
43
|
+
const texture = renderTarget.texture
|
|
44
|
+
texture.minFilter = LinearFilter
|
|
45
|
+
texture.magFilter = LinearFilter
|
|
46
|
+
texture.generateMipmaps = false
|
|
47
|
+
|
|
48
|
+
const typeName = (this.constructor as typeof FilterNode).type
|
|
49
|
+
texture.name = name != null ? `${typeName}.${name}` : typeName
|
|
50
|
+
|
|
51
|
+
this.renderTargets.push(renderTarget)
|
|
52
|
+
return renderTarget
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
getTextureNode(): TextureNode {
|
|
56
|
+
invariant(
|
|
57
|
+
this.textureNode != null,
|
|
58
|
+
'outputTexture must be specified before getTextureNode() is called.'
|
|
59
|
+
)
|
|
60
|
+
return this.textureNode
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
protected get outputTexture(): Texture | null {
|
|
64
|
+
return this.textureNode?.value ?? null
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
protected set outputTexture(value: Texture | null) {
|
|
68
|
+
this.textureNode = value != null ? outputTexture(this, value) : undefined
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
abstract setSize(width: number, height: number): this
|
|
72
|
+
|
|
73
|
+
override setup(builder: NodeBuilder): unknown {
|
|
74
|
+
const { inputNode, textureNode: outputNode } = this
|
|
75
|
+
invariant(
|
|
76
|
+
inputNode != null,
|
|
77
|
+
'inputNode must be specified before being setup.'
|
|
78
|
+
)
|
|
79
|
+
invariant(
|
|
80
|
+
outputNode != null,
|
|
81
|
+
'outputTexture must be specified before being setup.'
|
|
82
|
+
)
|
|
83
|
+
outputNode.uvNode = inputNode.uvNode
|
|
84
|
+
return outputNode
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
override dispose(): void {
|
|
88
|
+
for (const renderTarget of this.renderTargets) {
|
|
89
|
+
renderTarget.dispose()
|
|
90
|
+
}
|
|
91
|
+
super.dispose()
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -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
|
+
)
|