1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114 | import * as THREE from 'three/webgpu'
import {
clamp,
cos,
dot,
float,
Fn,
sin,
uniform,
vec3,
length,
Loop,
If,
Break,
acos,
atan,
pow,
log,
reflect,
normalize,
mix,
ShaderNodeObject
} from 'three/tsl'
import GUI from 'three/examples/jsm/libs/lil-gui.module.min.js'
import AmbientOcclusion from './AmbientOcclusion'
import ShadowMarcher from './ShadowMarcher'
import { atmosphericScattering } from './AtmosphericScattering'
import { Vector3 } from 'three'
export default class Juliabulb {
private static options = {
power: 8,
iterations: 8,
juliaC: new Vector3(-0.8, 0.62, -0.07)
}
private static power = uniform(this.options.power)
private static iterations = uniform(this.options.iterations)
private static juliaC = uniform(this.options.juliaC)
static scene = Fn(([position]: [ShaderNodeObject<THREE.Node>]) => {
const p = position.toVar()
const r = float(0).toVar()
const dr = float(1).toVar()
Loop({ start: 0, end: this.iterations, condition: '<' }, () => {
r.assign(length(p))
If(r.greaterThan(2), () => {
Break()
})
// Update the derivative accumulator based on radius
const r_pow = r.pow(this.power.sub(1.0))
dr.assign(r_pow.mul(this.power).mul(dr).add(1.0))
// Convert to spherical coordinates
const theta = acos(clamp(p.z.div(r), -1.0, 1.0))
const phi = atan(p.y, p.x)
// Scale by the fractal power
const pr = pow(r, this.power)
theta.mulAssign(this.power)
phi.mulAssign(this.power)
// Convert back to cartesian
p.assign(pr.mul(vec3(sin(theta).mul(cos(phi)), sin(theta).mul(sin(phi)), cos(theta))))
// Add to the Julia vec3 constant
p.addAssign(this.juliaC)
})
return log(r).mul(r).div(dr).mul(0.5)
})
static render = Fn(
([position, normal, rayDirection, lightPosition, lightDirection]: [
ShaderNodeObject<THREE.Node>,
ShaderNodeObject<THREE.Node>,
ShaderNodeObject<THREE.Node>,
ShaderNodeObject<THREE.Node>,
ShaderNodeObject<THREE.Node>
]) => {
const diffuse = clamp(dot(normal, lightDirection), 0.75, 1).toVar()
const shadow = ShadowMarcher.render(position.add(normal.mul(0.001)), lightDirection, this.scene)
const colour = atmosphericScattering(reflect(rayDirection, normal), normalize(lightPosition))
colour.assign(mix(colour.mul(shadow), diffuse.mul(normal).mul(0.5).add(0.5), shadow))
colour.mulAssign(AmbientOcclusion.render(position, normal, this.scene))
return colour
}
)
static setGUI(gui: GUI) {
const folder = gui.addFolder('Juliabulb')
folder.add(this.options.juliaC, 'x', -1.0, 1.0, 0.01).onChange((v) => (this.juliaC.value.x = v))
folder.add(this.options.juliaC, 'y', -1.0, 1.0, 0.01).onChange((v) => (this.juliaC.value.y = v))
folder.add(this.options.juliaC, 'z', -1.0, 1.0, 0.01).onChange((v) => (this.juliaC.value.z = v))
folder
.add(this.options, 'power', 2, 20, 0.1)
.name('Power')
.onChange((v) => {
this.power.value = v
})
folder
.add(this.options, 'iterations', 1, 20, 1)
.name('Iterations')
.onChange((v) => {
this.iterations.value = v
})
//folder.close()
}
}
|