-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathraymarcher.fut
227 lines (196 loc) · 7.99 KB
/
raymarcher.fut
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
import "utils"
import "material"
import "scene"
let slow : bool = false
let height : i32 = if slow then 400 else 300
let width : i32 = if slow then 600 else 300
let maxSteps : i32 = if slow then 200 else 100
let maxBounces : i32 = if slow then 50 else 20
let maxRays : i32 = if slow then 10000 else 2000
let epsilon : f32 = 0.01
type hit = #no_hit | #hit {hitPos: vec3, mat: material, insideObj: bool}
let getNorm sdf p : vec3 =
let d = vec(fst(sdf(p vec3.+ vec(epsilon, 0, 0)))
,fst(sdf(p vec3.+ vec(0, epsilon, 0)))
,fst(sdf(p vec3.+ vec(0, 0, epsilon))))
let n = d vec3.- vec(fst(sdf(p vec3.- vec(epsilon, 0, 0)))
,fst(sdf(p vec3.- vec(0, epsilon, 0)))
,fst(sdf(p vec3.- vec(0, 0, epsilon))))
in vec3.normalise n
type wavelength = #red | #green | #blue
type ray = {rd: vec3, ro: vec3, wavelength: wavelength}
let march sdf (rd : vec3) (ro : vec3) : hit =
let steps = 0
let dist : f32 = -1
let d : f32 = 1
let mat = diffuse black
let insideObj = false
let (_, _, d, ro, mat, insideObj) : (i32, f32, f32, vec3, material, bool) =
loop (steps, dist, d, ro, mat, insideObj) while (steps < maxSteps && (dist == -1 || d >= epsilon)) do
let (d, mat) = sdf ro
let insideObj = if d < 0 then true else false
-- use unsigned distance to allow marching through objects during
-- refraction
let d = f32.abs d
in (steps+1, dist + d, d, ro vec3.+ (vec3.scale d rd), mat, insideObj)
in if (d < epsilon) then #hit {hitPos=ro, mat, insideObj} else #no_hit
let reflect (d : vec3) (n : vec3) : vec3 =
d vec3.- vec3.scale (2 * vec3.dot d n) n
let vinvert v : vec3 =
vec3.map (* (-1)) v
-- Snell's law for calculating the angle of refraction
let snell rd surfaceNorm n1 n2 : maybe vec3 =
let normal = if vec3.dot rd surfaceNorm <= 0
then surfaceNorm
else vinvert surfaceNorm
let r = n1 / n2
let rootTerm = 1 - (r*r) * vec3.quadrance (vec3.cross normal rd)
in if rootTerm < 0
then #nothing
else #just
((vec3.scale r (vec3.cross normal (vec3.cross (vinvert normal) rd)))
vec3.- (vec3.scale (f32.sqrt rootTerm) normal))
-- Schlick's approximation of the Fresnel factor for calulating the
-- contribution ratio of reflected to refracted light
let schlick n1 n2 costheta : f32 =
let r0 = ((n1 - n2) / (n1 + n2)) ** 2
in r0 + (1 - r0) * ((1 - costheta) ** 5)
let rand x y ray bounce f : f32 =
let seed : f32 = f32rand((f32rand (x*5344 + y) * f32.i32 maxRays + f32.i32 ray) * f32.i32 maxBounces + f32.i32 bounce) + f32.i32 f
in f32rand seed
let sampleSphere x y ray bounce : vec3 =
let u1 = rand x y ray bounce 0
let u2 = rand x y ray bounce 10
let r = f32.sqrt(1.0 - u1 * u1)
let phi = tau * u2
in vec3.normalise(vec(f32.cos phi * r, f32.sin phi * r, 2.0 * u1))
let normalTowards n rd =
if vec3.dot n rd < 0.0
then vinvert rd
else rd
let sampleUniformHemisphere n x y ray bounce : vec3 =
let v = sampleSphere x y ray bounce
in normalTowards n v
type mat33 = {a: vec3, b: vec3, c: vec3}
let mulMat33 (m : mat33) (v : vec3) : vec3 =
vec( v.x * m.a.x + v.y * m.b.x + v.z * m.c.x
, v.x * m.a.y + v.y * m.b.y + v.z * m.c.y
, v.x * m.a.z + v.y * m.b.z + v.z * m.c.z)
let transposeM33 (m : mat33) : mat33 =
{ a = vec(m.a.x, m.b.x, m.c.x)
, b = vec(m.a.y, m.b.y, m.c.y)
, c = vec(m.a.z, m.b.z, m.c.z)}
let sampleCosineHemisphere n x y ray bounce : vec3 =
let u1 = rand x y ray bounce 0
let u2 = rand x y ray bounce 10
let r = f32.sqrt u1
let theta = tau * u2
let tangent' = vec3.cross n (vec(0,1,0))
let tangent =
if vec3.quadrance tangent' <= 0.001
then vec(1,0,0)
else vec3.normalise tangent'
let bitangent = vec3.cross tangent n
let tangentToWorld = {a = tangent, b = bitangent, c = n}
let vTangentSpace
= vec(r * f32.cos theta, r * f32.sin theta, f32.sqrt(1 - u1))
in mulMat33 tangentToWorld vTangentSpace
let getRi (wavelength: wavelength) (ri : refractiveIndex) : f32 =
match wavelength
case #red -> ri.riRed
case #green -> ri.riGreen
case #blue -> ri.riBlue
let castRay sdf globalLight rayNum x y (ray : ray) : col3 =
let bounces = 0
let colour = col(1.0, 1.0, 1.0)
let break = false
let rd = ray.rd
let ro = ray.ro
let (bounces, colour, _, _, _) =
loop (bounces, colour, rd, ro, break) while (bounces < maxBounces && !break) do
match march sdf rd ro
case #no_hit -> (bounces, colour vec3.* globalLight rd, rd, ro, true)
case #hit {hitPos, mat, insideObj} ->
if mat.light
then (bounces, colour vec3.* mat.colour, vec(0,0,0), vec(0,0,0), true)
else
let hitNorm = getNorm sdf hitPos
let hitNorm = if insideObj then vinvert hitNorm else hitNorm
let perfectReflect = reflect rd hitNorm
let scattered = sampleCosineHemisphere hitNorm x y rayNum bounces
let reflected = lerp scattered perfectReflect (mat.reflectivity)
let newRay =
match mat.transparent
case #nothing ->
reflected
case #just ri ->
let ri = getRi ray.wavelength ri
let costheta = -(vec3.dot hitNorm rd)
let n1 = if insideObj then ri else 1
let n2 = if insideObj then 1 else ri
let u1 = rand x y rayNum bounces 5
in match snell rd hitNorm n1 n2
case #nothing -> perfectReflect
case #just refract ->
let pReflection = schlick n1 n2 costheta
in if pReflection > u1
then perfectReflect
else refract
let newRay = vec3.normalise newRay
in (bounces+1, colour vec3.* mat.colour, newRay,
hitPos vec3.+ (vec3.scale (10*epsilon) newRay), false)
in if bounces != maxBounces then colour else col(0,0,0)
let getWavelengthColour (wavelength : wavelength) : col3 =
match wavelength
case #red -> col(1.0, 0.0, 0.0)
case #green -> col(0.0, 1.0, 0.0)
case #blue -> col(0.0, 0.0, 1.0)
let jitterRay sdf globalLight i x y rd ro =
let u1 = rand x y i 0 2
let u2 = rand x y i 0 3
let u3 = rand x y i 0 11 * 3
let wavelength = if u3 <= 1
then #red
else if u3 <= 2
then #green
else #blue
--TODO: jitter position, not direction
let rd = vec3.normalise <| rd vec3.+ vec(u1 / f32.i32 width, u2 / f32.i32 height, 0)
let wavelengthColour = getWavelengthColour wavelength
in wavelengthColour vec3.* castRay sdf globalLight i x y {rd, ro, wavelength}
let pixel sdf globalLight x y rd ro : col3 =
let mr = f32.i32 maxRays
in reduce (vec3.+) (col(0,0,0))
(map (\i -> jitterRay sdf globalLight i x y rd ro)
(iota maxRays))
vec3./ col(mr,mr,mr)
let gammaCorrect c =
vec3.map (f32.** 0.45) c
let marcher sdf globalLight camPos lookAt (x: f32) (y:f32) : col3 =
let filmCentre = camPos vec3.+ vec3.normalise(lookAt vec3.- camPos)
let fov = 1
let aspectRatio = f32.i32 width / f32.i32 height
let filmPos = filmCentre vec3.+ vec(x*fov*aspectRatio, y*fov, 0)
let rd = vec3.normalise(filmPos vec3.- camPos)
let ro = camPos
in gammaCorrect <| pixel sdf globalLight x y rd ro
let shader {sdf, globalLight, camera} (y: f32) (x: f32) : col3 =
match (camera : camera)
case #simple {camPos, lookAt} ->
marcher sdf globalLight camPos lookAt x y
let packCol ({x=r, y=g, z=b} : col3) : [3]u8 = map (u8.f32 <-< clamp 0 255 <-< (* 255)) [r, g, b]
let canvas scene (y: i32) (x: i32) : [3]u8 =
packCol <| (shader scene (-((f32.i32 y) / (f32.i32 height) - 0.5)) ((f32.i32 x) / (f32.i32 width) - 0.5))
let debug = false
let debugX : i32 = 50
let debugY : i32 = 152
let raymarcherDebug scene : [height][width][3]u8 =
map (\y -> (map (\x -> if x == debugX && y == debugY
then canvas scene y x
else [0,255,0])
(iota width)))
(iota height)
let raymarcher scene : [height][width][3]u8 =
if debug
then raymarcherDebug scene
else map (\y -> (map (canvas scene y) (iota width))) (iota height)