Skip to content

Instantly share code, notes, and snippets.

@mrange
Last active December 1, 2023 20:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mrange/c19170f3b3892e6a31521600980f8dd0 to your computer and use it in GitHub Desktop.
Save mrange/c19170f3b3892e6a31521600980f8dd0 to your computer and use it in GitHub Desktop.
Raymarcher in F#
#nowarn "9"
open System
open System.Diagnostics
open System.Numerics
open SixLabors.ImageSharp
open SixLabors.ImageSharp.Processing
open SixLabors.ImageSharp.PixelFormats
open SixLabors.ImageSharp.Advanced
open System.Threading.Tasks
open System.Runtime.Intrinsics
open System.Runtime.Intrinsics.X86
open FSharp.NativeInterop
// Turn off Tiered Compilation
// Set-Item env:COMPlus_TieredCompilation -value 0
module RayMarcher =
type V1 = float32
let inline v1v v : V1 = float32 v
type Mat = Matrix3x2
let inline matv m11 m12 m13 m21 m22 m23 : Mat =
Mat (v1v m11, v1v m12, v1v m21, v1v m22, v1v m13, v1v m23)
type V3 = Vector3
let inline v3v x y z : V3 = V3 (v1v x, v1v y, v1v z)
let inline v3normalize v : V3 = V3.Normalize v
let inline v3cross x y: V3 = V3.Cross (x, y)
type V8 = Vector256<float32>
let inline v8add (a : V8) (b : V8) : V8 = Avx.Add (a, b)
let inline v8sub (a : V8) (b : V8) : V8 = Avx.Subtract (a, b)
let inline v8mul (a : V8) (b : V8) : V8 = Avx.Multiply (a, b)
let inline v8man (a : V8) (b : V8) : V8 = Avx.Max (a, b)
let inline v8min (a : V8) (b : V8) : V8 = Avx.Min (a, b)
let inline v8sqrt (a : V8) : V8 = Avx.Sqrt (a)
let inline v8isqrt(a : V8) : V8 = Avx.ReciprocalSqrt (a)
let inline v8inv (a : V8) : V8 = Avx.Reciprocal (a)
let inline v8mask (a : V8) : int = Avx.MoveMask (a)
let inline v8le (a : V8) (b : V8) : int = v8mask (Avx.Compare (a, b, FloatComparisonMode.LessThanOrEqualOrderedNonSignaling))
let v8v1 v : V8 =
let sa = NativePtr.stackalloc<float32> 1
NativePtr.set sa 0 v
Avx.BroadcastScalarToVector256 sa
let _0 = v8v1 0.F
let _1 = v8v1 1.F
let _2 = v8v1 2.F
let _l2 = v8v1 2.F
module FastMath =
let inline padd a b =
let a0, a1 = a
let b0, b1 = b
v8add a0 b0, v8add a1 b1
let inline psub a b =
let a0, a1 = a
let b0, b1 = b
v8sub a0 b0, v8sub a1 b1
let inline pmul a b =
let a0, a1 = a
let b0, b1 = b
v8mul a0 b0, v8mul a1 b1
let inline ple a b =
let a0, a1 = a
let b0, b1 = b
((v8le a0 b0) <<< 8) ||| (v8le a1 b1)
let inline l2 v =
let x0, y0, z0, x1, y1, z1 = v
let x0 = v8mul x0 x0
let y0 = v8mul y0 y0
let z0 = v8mul z0 z0
let x1 = v8mul x1 x1
let y1 = v8mul y1 y1
let z1 = v8mul z1 z1
v8add (v8add x0 y0) z0, v8add (v8add x1 y1) z1
let inline l8 v =
let x0, y0, z0, x1, y1, z1 = v
let x0 = v8mul x0 x0
let y0 = v8mul y0 y0
let z0 = v8mul z0 z0
let x0 = v8mul x0 x0
let y0 = v8mul y0 y0
let z0 = v8mul z0 z0
let x0 = v8mul x0 x0
let y0 = v8mul y0 y0
let z0 = v8mul z0 z0
let x1 = v8mul x1 x1
let y1 = v8mul y1 y1
let z1 = v8mul z1 z1
let x1 = v8mul x1 x1
let y1 = v8mul y1 y1
let z1 = v8mul z1 z1
let x1 = v8mul x1 x1
let y1 = v8mul y1 y1
let z1 = v8mul z1 z1
v8add (v8add x0 y0) z0, v8add (v8add x1 y1) z1
let inline l1 v =
let l20, l21 = l2 v
v8sqrt l20, v8sqrt l21
let inline scale s v =
let x0, y0, z0, x1, y1, z1 = v
v8mul s x0, v8mul s y0, v8mul s z0, v8mul s x1, v8mul s y1, v8mul s z1
let inline normal v =
let x0, y0, z0, x1, y1, z1 = v
let l20, l21 = l2 v
let i0 = v8isqrt l20
let i1 = v8isqrt l21
v8mul i0 x0, v8mul i0 y0, v8mul i0 z0, v8mul i1 x1, v8mul i1 y1, v8mul i1 z1
let inline sphere v r =
let l10, l11 = l1 v
v8sub l10 r, v8sub l11 r
let inline roundbox v r =
let l80, l81 = l8 v
let l0 = v8sqrt (v8sqrt (v8sqrt l80))
let l1 = v8sqrt (v8sqrt (v8sqrt l81))
v8sub l0 r, v8sub l1 r
let inline translate v t =
let tx, ty, tz = t
let x0, y0, z0, x1, y1, z1 = v
v8add x0 tx, v8add y0 ty, v8add z0 tz, v8add x1 tx, v8add y1 ty, v8add z1 tz
let inline ray o d t =
let ox0, oy0, oz0, ox1, oy1, oz1 = o
let dx0, dy0, dz0, dx1, dy1, dz1 = d
let t0, t1 = t
v8add ox0 (v8mul dx0 t0), v8add oy0 (v8mul dy0 t0), v8add oz0 (v8mul dz0 t0), v8add ox1 (v8mul dx1 t1), v8add oy1 (v8mul dy1 t1), v8add oz1 (v8mul dz1 t1)
let inline df r p =
sphere p r
type [<Struct>] V =
// x0 y0 z0 x1 y1 z1
| V of V8*V8*V8*V8*V8*V8
static member inline New x0 y0 z0 x1 y1 z1 = V (x0, y0, z0, x1, y1, z1)
static member Zero = V.New _0 _0 _0 _0 _0 _0
let inline vto v =
let x0, y0, z0, x1, y1, z1 = v
V (x0, y0, z0, x1, y1, z1)
let inline vfrom (V (x0, y0, z0, x1, y1, z1)) =
x0, y0, z0, x1, y1, z1
let dfOnlyLocals r p =
let p = vfrom p
let d0, d1 = FastMath.df r p
struct (d0, d1)
let df v = dfOnlyLocals _1 v
let rec rayMarch l2 ro rd =
let ro = vfrom ro
let rd = vfrom rd
let l2 = l2, l2
let mutable i = 50
let mutable t0 = _0
let mutable t1 = _0
let mutable notIntersected = true
let mutable notOutOfBounds = true
while notIntersected && notOutOfBounds do
let t = t0, t1
let p = FastMath.ray ro rd t
let struct (d0, d1) = df (vto p)
let d = (d0, d1)
let d2 = FastMath.pmul d d // One way to take abs of d
if (FastMath.ple d2 l2) = 0xFFFF then
notIntersected <- false
else
let t = FastMath.padd t d
t0 <- fst t
t1 <- snd t
let i = i - 1
notOutOfBounds <- i > 0
type Scene =
{
ViewTransform : Mat
Origin : V3
Width : int
Height : int
W : V3
U : V3
V : V3
}
static member New width height origin lookAt up : Scene =
let w = v1v width
let h = v1v height
let ratio = w/h
let xscale = 2.F*ratio/h
let xoffset = -1.F*ratio
let yscale = 2.F/h
let yoffset = -1.F
let m = matv xscale 0 xoffset 0 yscale yoffset
let w = v3normalize (lookAt - origin)
let u = v3normalize (v3cross w up)
let v = v3normalize (v3cross w u)
{
ViewTransform = m
Origin = origin
Width = width
Height = height
W = w
U = u
V = v
}
member this.Pixels (pxs : Rgba32 array) x y =
let w = this.Width
let yo = y*w
let xo = x
for yy = 0 to 3 do
for xx = 0 to 3 do
pxs.[yo + xo + w*yy + xx] <- Rgba32 (10uy, 20uy, 30uy)
type [<Struct>] Measure =
| Measure of int64*int*int*int
override x.ToString () =
let (Measure (ms, cc0, cc1, cc2)) = x
sprintf "{ Measured, %d ms, (%d, %d, %d) cc }" ms cc0 cc1 cc2
static member (-) (Measure (ams, acc0, acc1, acc2), Measure (bms, bcc0, bcc1, bcc2)) =
Measure (ams - bms, acc0 - bcc0, acc1 - bcc1, acc2 - bcc2)
let measure =
let sw = Stopwatch ()
sw.Start ()
fun () ->
let cc i = GC.CollectionCount i
Measure (sw.ElapsedMilliseconds, cc 0, cc 1, cc 2)
let sequentialFor b e a =
for i = b to e-1 do
a i
let parallelFor b e a =
Parallel.For (b, e, Action<int> a) |> ignore
open RayMarcher
let render w h =
let s = Scene.New w h (v3v 0 0 -3) (v3v 0 0 0) (v3v 0 1 0)
let w4 = w >>> 2
let h4 = h >>> 2
let pxs = Array.zeroCreate<Rgba32> (w*h)
let row y4 =
let y = y4 <<< 2
for x4 = 0 to w4 - 1 do
let x = x4 <<< 2
s.Pixels pxs x y
sequentialFor 0 h4 row
pxs
[<EntryPoint>]
let main argv =
let w = 512*2
let h = 384*2
if w % 4 <> 0 then failwith "width expected to be divisible by 4"
if h % 4 <> 0 then failwith "height expected to be divisible by 4"
let before = measure ()
let pixels = render w h
let after = measure ()
printfn "A %dx%d image cost: %s" w h (string (after - before))
use img = new Image<Rgba32>(w, h)
let ps = img.GetPixelSpan()
for i = 0 to (ps.Length - 1) do
ps.[i] <- pixels.[i]
img.Save "avx.png"
0
#nowarn "9"
open System
open System.Diagnostics
open System.Runtime.Intrinsics
open System.Runtime.Intrinsics.X86
open FSharp.NativeInterop
// Turn off Tiered Compilation
// Set-Item env:COMPlus_TieredCompilation -value 0
type [<Struct>] Measure =
| Measure of int64*int*int*int
override x.ToString () =
let (Measure (ms, cc0, cc1, cc2)) = x
sprintf "{ Measured, %d ms, (%d, %d, %d) cc }" ms cc0 cc1 cc2
static member (-) (Measure (ams, acc0, acc1, acc2), Measure (bms, bcc0, bcc1, bcc2)) =
Measure (ams - bms, acc0 - bcc0, acc1 - bcc1, acc2 - bcc2)
let measure =
let sw = Stopwatch ()
sw.Start ()
fun () ->
let cc i = GC.CollectionCount i
Measure (sw.ElapsedMilliseconds, cc 0, cc 1, cc 2)
let time a =
let before = measure ()
let r = a ()
let after = measure ()
r, (after - before)
type VF = Vector256<float32>
let inline ( +++ ) (a : VF) (b : VF) : VF = Avx.Add (a, b)
let inline ( --- ) (a : VF) (b : VF) : VF = Avx.Subtract (a, b)
let inline ( *** ) (a : VF) (b : VF) : VF = Avx.Multiply (a, b)
let vf_ v =
let sa = NativePtr.stackalloc<float32> 1
NativePtr.set sa 0 v
Avx.BroadcastScalarToVector256 sa
type [<Struct>] V3 =
| V3 of VF*VF*VF
static member Zero : V3 = V3 (VF.Zero, VF.Zero, VF.Zero)
static member inline (+) (V3 (lx, ly, lz), V3 (rx, ry, rz)) : V3 =
V3 (lx +++ rx, ly +++ ry, lz +++ rz)
static member inline (-) (V3 (lx, ly, lz), V3 (rx, ry, rz)) : V3 =
V3 (lx --- rx, ly --- ry, lz --- rz)
static member inline (*) (V3 (lx, ly, lz), V3 (rx, ry, rz)) : V3 =
V3 (lx *** rx, ly *** ry, lz *** rz)
module TupleTest =
type [<Struct>] V =
// x0 y0 z0 x1 y1 z1
| V of VF*VF*VF*VF*VF*VF
static member Zero : V = V (VF.Zero, VF.Zero, VF.Zero, VF.Zero, VF.Zero, VF.Zero)
let inline l2 xyz =
let x0, y0, z0, x1, y1, z1 = xyz
x0 *** x0 +++ y0 *** y0 +++ z0 *** z0, x1 *** x1 +++ y1 *** y1 +++ z1 *** z1
let inline l1 xyz =
let l20, l21 = l2 xyz
Avx.Sqrt l20, Avx.Sqrt l21
let inline sphere xyz r =
let l10, l11 = l1 xyz
// l10 --- r, l11 --- r
l10, l11
let inline mind (ld0 : VF, rd0 : VF) (ld1 : VF, rd1 : VF) : VF*VF =
Avx.Min (ld0, rd0), Avx.Min (ld1, rd1)
let _r0 = vf_ 1.F
let _r1 = vf_ 2.F
let inline df (V (x0, y0, z0, x1, y1, z1)) =
let r0 = _r0
let r1 = _r1
// Two spheres translated
let xyz = x0, y0, z0, x1, y1, z1
let s0 = sphere xyz r0
let s1 = sphere xyz r1
mind s0 s1
let test () =
let z = V.Zero
let n = 10000000
let mutable acc0 = VF.Zero
let mutable acc1 = VF.Zero
for x = 1 to n do
let d0, d1 = df z
acc0 <- acc0 +++ d0
acc1 <- acc1 +++ d1
acc0, acc1
[<EntryPoint>]
let main argv =
for x = 0 to 100 do
let _, m = time TupleTest.test
printfn "... it cost: %s" (string m)
0
open System
open System.Diagnostics
open System.Numerics
open SixLabors.ImageSharp
open SixLabors.ImageSharp.Processing
open SixLabors.ImageSharp.PixelFormats
open SixLabors.ImageSharp.Advanced
open System.Threading.Tasks
// Turn off Tiered Compilation
// Set-Item env:COMPlus_TieredCompilation -value 0
module Raymarcher =
type V1 = float32
type V2 = Vector2
type V3 = Vector3
type Mat = Matrix3x2
let inline v1 x = float32 x
let inline clamp_1 (x : V1) (y : V1) (z : V1) = if x < y then y elif x > z then z else x
let inline lerp_1 (x: V1) (y : V1) (z : V1) = z*(y - x) + x
let inline mod_1 (x : V1) (y : V1) = x - y*floor (x/y)
(*
let inline mod_1 (x : V1) (y : V1) =
let m = x % y
let s = Math.Sign x + Math.Sign y
if s = 0 then m + y else m
*)
let inline positive_1 x = if x > 0.F then x else 0.F
let inline v2 x y = V2 (v1 x, v1 y)
let inline abs_2 (x : V2) = V2.Abs x
let inline length_2 (x : V2) = x.Length ()
let inline length8_2 (x : V2) =
let x2 = x*x
let x4 = x2*x2
let x8 = x4*x4
let d8 = x8.X + x8.Y
Math.Pow (float d8, 1./8.) |> v1
let inline cmax_2 (x : V2) = max x.X x.Y
let inline cmin_2 (x : V2) = min x.X x.Y
let inline v3 x y z = V3 (v1 x, v1 y, v1 z)
let inline normalize_3 v = V3.Normalize v
let inline dot_3 x y = V3.Dot (x, y)
let inline cross_3 x y = V3.Cross (x, y)
let inline reflect_3 i n = i - 2.F*n*dot_3 n i
let inline clamp_3 (x : V3) (y : V3) (z : V3) = V3.Clamp (x, y, z)
let inline lerp_3 (x : V3) (y : V3) (z : V1) = V3.Lerp (x, y, z)
let inline mod_3 (x : V3) (y : V3) = v3 (mod_1 x.X y.X) (mod_1 x.Y y.Y) (mod_1 x.Z y.Z)
let inline cmax_3 (x : V3) = max x.X (max x.Y x.Z)
let inline cmin_3 (x : V3) = min x.X (min x.Y x.Z)
let inline length_3 (x : V3) = x.Length ()
let inline length8_3 (x : V3) =
let x2 = x*x
let x4 = x2*x2
let x8 = x4*x4
let d8 = x8.X + x8.Y + x8.Z
Math.Pow (float d8, 1./8.) |> v1
let inline mat m11 m12 m13 m21 m22 m23 = Mat (v1 m11, v1 m12, v1 m21, v1 m22, v1 m13, v1 m23)
let pi = v1 System.Math.PI
let tau = 2.0F*pi
let inline rgb r g b = v3 (v1 r/255.F) (v1 g/255.F) (v1 b/255.F)
type [<Struct>] Material =
{
DistanceToSurface : float32
Color : V3
Reflecting : float32
}
static member inline New d c r : Material = { DistanceToSurface = d; Color = c; Reflecting = r }
static member Empty : Material = Material.New 0.F (v3 0 0 0) 0.F
type DistanceFunction = V3 -> Material
type SkyFunction = V3 -> V3
type PostFunction = V1 -> V1 -> V3 -> V3
type PostFunction_ = OptimizedClosures.FSharpFunc<V1, V1, V3, V3>
type [<Struct>] Intersection = I of float32*V3*int*Material
module Metrics =
let now : unit -> int64 =
let sw = Stopwatch ()
sw.Start ()
fun () -> sw.ElapsedMilliseconds
#if METRICS
let mutable distanceEstimations = 0L
let mutable rayMarches = 0L
let mutable shadowMarches = 0L
let mutable outOfBounds = 0L
let mutable insideBounds = 0L
#endif
open Metrics
module Details =
let maxBounces = 5
let maxMarches = 90
let maxShadowMarches = 36
let maxDistance = 16.F
let initialStep = 0.01F
let epsilon = 0.0001F
let minShadow = 0.25F
let tolerance = 0.0001F
let epsilonX = v3 epsilon 0 0
let epsilonY = v3 0 epsilon 0
let epsilonZ = v3 0 0 epsilon
// F# compiler generates tail. attribute which is great in principle
// but causes severe performance degradation here (about 3x slower)
let inline suppressTailCall (m : Material) : Material =
if m.DistanceToSurface = 0.F then m else m
let inline suppressTailCall_ (v : V3) : V3 =
if v.X = 0.F then v else v
let inline isnanf f = System.Single.IsNaN f
let inline boxDistance_3 p b =
let d = (V3.Abs p) - b
length_3 (V3.Max (d, V3.Zero)) + cmax_3 (V3.Min (d, V3.Zero))
let computeNormal (df : DistanceFunction) (pos : V3) : V3 =
let inline c off = (df (pos + off)).DistanceToSurface
let vmax = v3 (c epsilonX) (c epsilonY) (c epsilonZ)
let vmin = v3 (c -epsilonX) (c -epsilonY) (c -epsilonZ)
normalize_3 (vmax - vmin)
module Loops =
let rec rayMarch
(df : DistanceFunction)
(ray : V3 )
(pos : V3 )
(travelled: V1 )
(iter : int ) : Intersection =
if travelled < maxDistance then
let m = df pos
let d = m.DistanceToSurface
if iter <= 0 then
#if METRICS
Interlocked.Add (&rayMarches, int64 maxMarches) |> ignore
#endif
I (travelled, pos, maxMarches, m)
elif d < tolerance then
#if METRICS
Interlocked.Add (&rayMarches, int64 (maxMarches - iter)) |> ignore
#endif
I (travelled, pos, maxMarches - iter, m)
else
rayMarch df ray (pos + d*ray) (travelled + d) (iter - 1)
else
#if METRICS
Interlocked.Add (&rayMarches, int64 (maxMarches - iter)) |> ignore
#endif
I (nanf, pos, maxMarches - iter, Material.Empty)
let rec softShadow
(df : DistanceFunction)
(pos : V3 )
(lightDir : V3 )
(minStep : V1 )
(maxt : V1 )
(k : V1 )
(shadow : V1 )
(travelled: V1 )
(iter : int ) : V1 =
if iter > 0 && travelled < maxt && minShadow < shadow then
let m = df pos
let d = m.DistanceToSurface
let shadow = min shadow (k*d/travelled)
let step = max minStep d
softShadow df (pos + step*lightDir) lightDir minStep maxt k shadow (travelled + step) (iter - 1)
else
#if METRICS
Interlocked.Add (&shadowMarches, int64 (maxShadowMarches - iter)) |> ignore
#endif
clamp_1 shadow minShadow 1.F
let raymarch
(df : DistanceFunction)
(ray : V3 )
(origin : V3 )
(initial : V1 ) : Intersection =
Loops.rayMarch df ray (origin + initial*ray) initial maxMarches
let softShadow
(df : DistanceFunction)
(pos : V3 )
(lightDir : V3 )
(mint : V1 )
(maxt : V1 )
(k : V1 ) : V1 =
Loops.softShadow df (pos + mint*lightDir) lightDir (0.5F*mint) maxt k 1.F mint maxShadowMarches
module Loops2 =
let rec pixel
(df : DistanceFunction)
(skyf : SkyFunction )
(light : V3 )
(ray : V3 )
(origin : V3 )
(limit : V1 )
(bounce : int ) : V3 =
let (I (dist, pos, iter, mat)) = raymarch df ray origin initialStep
if isnanf dist then
skyf ray |> suppressTailCall_
else
let nor = computeNormal df pos
let lv = light - pos
let ll = length_3 lv
let ld = (1.F / ll)*lv
let dif = positive_1 (dot_3 ld nor)
let occ = (v1 (maxMarches - iter)) / v1 maxMarches
let sha = softShadow df pos ld initialStep ll 24.F
let tot = lerp_1 0.1F 1.F (occ*dif*sha)
let col = mat.Color*tot
let fres= pown (1.F - abs (dot_3 nor ray)) 4
let refl= lerp_1 mat.Reflecting 1.F fres
if bounce > 0 && refl > limit then
let ref = reflect_3 ray nor
let rcol = pixel df skyf light ref pos (limit/refl) (bounce - 1)
(1.F - refl)*col + refl*rcol
else
col
open Details
module Primitives =
let inline plane (c: V3) (refl : V1) (n : V3) (o : V1) : DistanceFunction =
let n = normalize_3 n
fun p ->
let dist = dot_3 n p + o
Material.New dist c refl
let inline sphere (c: V3) (refl : V1) (r : V1) : DistanceFunction =
fun p ->
let dist = length_3 p - r
Material.New dist c refl
let inline softCube (c: V3) (refl : V1) (r : V1) : DistanceFunction =
fun p ->
let dist = length8_3 p - r
Material.New dist c refl
let inline box (c: V3) (refl : V1) (b : V3) : DistanceFunction =
fun p ->
let dist = boxDistance_3 p b
Material.New dist c refl
let inline boundingBox (limit : V1) (c: V3) (refl : V1) (b : V3) (df : DistanceFunction) : DistanceFunction =
fun p ->
let dist = boxDistance_3 p b
if dist > limit then
#if METRICS
Interlocked.Increment &outOfBounds |> ignore
#endif
Material.New dist c refl
else
#if METRICS
Interlocked.Increment &insideBounds |> ignore
#endif
let m2 = df p
if dist > m2.DistanceToSurface then
Material.New dist c refl
else
m2
module Transformations =
let inline rotateAroundX (a : V1) (df : DistanceFunction): DistanceFunction =
let c = cos a
let s = sin a
fun p ->
let pp = v3 p.X (c*p.Y + s*p.Z) (-s*p.Y + c*p.Z)
df pp |> suppressTailCall
let inline rotateAroundY (a : V1) (df : DistanceFunction): DistanceFunction =
let c = cos a
let s = sin a
fun p ->
let pp = v3 (c*p.X + s*p.Z) p.Y (-s*p.X + c*p.Z)
df pp |> suppressTailCall
let inline rotateAroundZ (a : V1) (df : DistanceFunction): DistanceFunction =
let c = cos a
let s = sin a
fun p ->
let pp = v3 (c*p.X + s*p.Y) (-s*p.X + c*p.Y) p.Z
df pp |> suppressTailCall
let inline scale (s : V1) (df : DistanceFunction): DistanceFunction =
fun p ->
let m = df (p/s)
{ m with DistanceToSurface = m.DistanceToSurface*s }
let inline translate (t : V3) (df : DistanceFunction): DistanceFunction =
fun p ->
df (p - t) |> suppressTailCall
let inline unionWith (from : DistanceFunction) (s : DistanceFunction): DistanceFunction =
fun p ->
let m1 = from p
let m2 = s p
if m1.DistanceToSurface < m2.DistanceToSurface then
m1
else
m2
let inline intersectWith (from : DistanceFunction) (s : DistanceFunction): DistanceFunction =
fun p ->
let m1 = from p
let m2 = s p
if m1.DistanceToSurface > m2.DistanceToSurface then
m1
else
m2
let inline subtractFrom (from : DistanceFunction) (s : DistanceFunction): DistanceFunction =
fun p ->
let m1 = from p
let m2 = s p
let d2 = -m2.DistanceToSurface
if m1.DistanceToSurface > d2 then
m1
else
{ m2 with DistanceToSurface = d2 }
let inline subtract (s: DistanceFunction) (from : DistanceFunction): DistanceFunction =
subtractFrom from s
let inline softUnionWith power (from : DistanceFunction) (s : DistanceFunction): DistanceFunction =
fun p ->
let m1 = from p
let m2 = s p
let sum = exp (-power*m1.DistanceToSurface) + exp (-power*m2.DistanceToSurface)
let d = -log sum / power
if abs (d - m1.DistanceToSurface) < abs (d - m2.DistanceToSurface) then
{ m1 with DistanceToSurface = d }
else
{ m2 with DistanceToSurface = d }
let inline repeat (rep : V3) (df : DistanceFunction): DistanceFunction =
let rep2 = 0.5F*rep
fun p ->
let pp = mod_3 p rep - rep2
df pp |> suppressTailCall
let inline distort (d : V3 -> V1) (df : DistanceFunction): DistanceFunction =
fun p ->
let m = df p
let dd = d p
{ m with DistanceToSurface = m.DistanceToSurface + dd }
type Scene =
{
DistanceFunction : DistanceFunction
SkyFunction : SkyFunction
PostFunction : PostFunction_
Light : V3
ViewTransform : Mat
Origin : V3
W : V3
U : V3
V : V3
}
static member New df skyf postf width height origin lookAt up light : Scene =
let width = v1 width
let height = v1 height
let ratio = width/height
let xscale = 2.F*ratio/width
let xoffset = -1.F*ratio
let yscale = 2.F/height
let yoffset = -1.F
let m = mat xscale 0 xoffset 0 yscale yoffset
let w = normalize_3 (lookAt - origin)
let u = normalize_3 (cross_3 w up)
let v = normalize_3 (cross_3 w u)
#if METRICS
let df : DistanceFunction =
fun p ->
Interlocked.Increment &distanceEstimations |> ignore
df p |> suppressTailCall
#endif
{
DistanceFunction = df
SkyFunction = skyf
PostFunction = PostFunction_.Adapt postf
Light = light
ViewTransform = m
Origin = origin
W = w
U = u
V = v
}
member t.Pixel (x : int) (y : int) : V3 =
let v = Vector2.Transform (Vector2 (v1 x, v1 y), t.ViewTransform)
let ray = normalize_3 (v.X*t.U + v.Y*t.V + 2.5F*t.W)
let col = Loops2.pixel t.DistanceFunction t.SkyFunction t.Light ray t.Origin 0.01F maxBounces
t.PostFunction.Invoke (v.X, v.Y, col) |> suppressTailCall_
module MyScene =
open Raymarcher
open Raymarcher.Primitives
open Raymarcher.Transformations
let white = v3 1 1 1
let gray = v3 0.5 0.5 0.5
let sunPos = v3 1 3 -3
let sunDir = normalize_3 sunPos
let sunColor = v3 1 0.75 0.5
let bigBox =
softCube white 0.F 4.F
|> translate (v3 0 -4 0)
let holeyBox =
sphere gray 0.F 1.2F
|> subtractFrom (softCube gray 0.F 1.F)
|> unionWith (sphere white 0.75F 1.1F)
let post x y col =
let col = V3.Clamp (col, V3.Zero, V3.One)
let col = v3 (sqrt col.X) (sqrt col.Y) (sqrt col.Z)
let col = 0.6F*col + 0.4F*(col*col*(v3 3 3 3 - 2.F*col))
let sat = dot_3 col (v3 0.33 0.33 0.33)
let col = lerp_3 col (v3 sat sat sat) -0.4F
col
let sky (ray : V3) : V3 =
let tf = dot_3 ray (v3 0 1 0)
let sf = positive_1 (dot_3 ray sunDir)
let s1 = sunColor*pown sf 80
let s2 = sunColor*pown sf 20
let t1 = 0.5F*(tf + 1.F)*(v3 0.20 0.3F 0.1) + v3 0 0 0.25F
t1 + s1 + s2
let scene w h =
let df =
(sphere white 0.75F 0.5F |> translate (v3 -2 -0.5 0))
|> unionWith holeyBox
|> translate (v3 0 1 0)
|> scale 0.75F
|> unionWith bigBox
Scene.New df sky post w h (v3 -1 1 -3) (v3 0 0 0) (v3 0 1 0) sunPos
type [<Struct>] Measure =
| Measure of int64*int*int*int
override x.ToString () =
let (Measure (ms, cc0, cc1, cc2)) = x
sprintf "{ Measured, %d ms, (%d, %d, %d) cc }" ms cc0 cc1 cc2
static member (-) (Measure (ams, acc0, acc1, acc2), Measure (bms, bcc0, bcc1, bcc2)) =
Measure (ams - bms, acc0 - bcc0, acc1 - bcc1, acc2 - bcc2)
let measure =
let sw = Stopwatch ()
sw.Start ()
fun () ->
let cc i = GC.CollectionCount i
Measure (sw.ElapsedMilliseconds, cc 0, cc 1, cc 2)
let sequentialFor b e a =
for i = b to e-1 do
a i
let parallelFor b e a =
Parallel.For (b, e, Action<int> a) |> ignore
let render w h =
let pxs = Array.zeroCreate<Rgba32> (w*h)
let s = MyScene.scene w h
let row y =
let yo = y*w
for x = 0 to w - 1 do
let p = s.Pixel x y
pxs.[yo + x] <- Rgba32 p
parallelFor 0 h row
pxs
[<EntryPoint>]
let main argv =
let w = 512*2
let h = 384*2
let before = measure ()
let pixels = render w h
let after = measure ()
printfn "A %dx%d image cost: %s" w h (string (after - before))
use img = new Image<Rgba32>(w, h)
let ps = img.GetPixelSpan()
for i = 0 to (ps.Length - 1) do
ps.[i] <- pixels.[i]
img.Save "reference.png"
0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment