Skip to content

Instantly share code, notes, and snippets.

@mattnewport
Created August 29, 2019 03:54
Show Gist options
  • Save mattnewport/f3eaa71c3f71cafe83da541cf3c052ae to your computer and use it in GitHub Desktop.
Save mattnewport/f3eaa71c3f71cafe83da541cf3c052ae to your computer and use it in GitHub Desktop.
Procedural Planet raytracer in Hoon
|= dim=@ud ^- (list @t)
=/ dx (div:rs .1 (sun:rs dim))
=/ white [.1 .1 .1]
=< (genppm dim)
|%
++ min
|= [x=@rs y=@rs] ^- @rs
?: (lth:rs x y) x y
++ max
|= [x=@rs y=@rs] ^- @rs
?: (lth:rs x y) y x
++ maxv
|= [x=(list @rs) y=@rs] ^- (list @rs)
(turn x |=(u=@rs (max u y)))
++ neg
|= x=@rs ^- @rs
(sub:rs .0 x)
++ abs
|= x=@rs ^- @rs
?: (lth:rs x .0) (neg x) x
++ sqr
|= x=@rs ^- @rs
(mul:rs x x)
++ pow
|= [x=@rs y=@rs] ^- @rs
=/ prec .0.001
|- ^- @rs
?: (lth:rs y .0)
(div:rs .1 $(y (neg y)))
?: (gte:rs y .10)
(sqr $(y (mul:rs y .0.5), prec (mul:rs prec .0.5)))
?: (gte:rs y .1)
(mul:rs x $(y (sub:rs y .1)))
?: (gte:rs prec .1)
(sqt:rs x)
(sqt:rs $(y (mul:rs y .2), prec (mul:rs prec .2)))
++ gain
|= [x=@rs k=@rs] ^- @rs
=/ a (mul:rs .0.5 (pow (mul:rs .2 ?:((lth:rs x .0.5) x (sub:rs .1 x))) k))
?:((lth:rs x .0.5) a (sub:rs .1 a))
++ clamp
|= [x=@rs a=@rs b=@rs] ^- @rs
(min (max x a) b)
++ saturate
|= x=@rs ^- @rs
(clamp x .0 .1)
++ smoothstep
|= [a=@rs b=@rs x=@rs] ^- @rs
=/ t (saturate (div:rs (sub:rs x a) (sub:rs b a)))
(mul:rs t (mul:rs t (sub:rs .3 (mul:rs .2 t))))
++ binopv
|= [x=(list @rs) y=(list @rs) f=$-([@rs @rs] @rs)] ^- (list @rs)
p:(spin x y |=([u=@rs v=(list @rs)] ?~(v !! [(f u i.v) t.v])))
++ addv
|= [x=(list @rs) y=(list @rs)] ^- (list @rs)
(binopv x y add:rs)
++ subv
|= [x=(list @rs) y=(list @rs)] ^- (list @rs)
(binopv x y sub:rs)
++ mulv
|= [x=(list @rs) y=(list @rs)] ^- (list @rs)
(binopv x y mul:rs)
++ dot
|= [x=(list @rs) y=(list @rs)] ^- @rs
(roll (mulv x y) add:rs)
++ mag
|= x=(list @rs) ^- @rs
(sqt:rs (dot x x))
++ muls
|= [x=(list @rs) y=@rs] ^- (list @rs)
(turn x |=(u=@rs (mul:rs u y)))
++ lerpv
|= [x=(list @rs) y=(list @rs) t=@rs] ^- (list @rs)
(addv (muls x (sub:rs .1 t)) (muls y t))
++ norm
|= x=(list @rs) ^- (list @rs)
=/ m (mag x)
=/ mi (div:rs .1 m)
(muls x mi)
++ rtz
|= x=@rs ^- @rs
(san:rs (fall (toi:rs x) --0))
++ floor
|= x=@rs ^- @rs
?:((lth x .0) (sub:rs (rtz x) .1) (rtz x))
++ floorv
|= x=(list @rs) ^- (list @rs)
(turn x floor)
++ frac
|= x=@rs ^- @rs
(sub:rs x (floor x))
++ fracv
|= x=(list @rs) ^- (list @rs)
(turn x frac)
++ xv
|= x=(list @rs) ^- @rs
(snag 0 x)
++ yv
|= x=(list @rs) ^- @rs
(snag 1 x)
++ zv
|= x=(list @rs) ^- @rs
(snag 2 x)
++ hash2d
|= x=(list @rs) ^- (list @rs)
=/ k ~[.0.3183099 .0.3678794]
=/ y (addv (mulv x k) (flop k))
=/ a (mul:rs .16 (frac (mul:rs (mul:rs (xv y) (yv y)) (add:rs (xv y) (yv y)))))
(addv ~[.-1 .-1] (muls (fracv (muls k a)) .2))
++ noise2dd
|= p=(list @rs) ^- (list @rs)
=/ i (floorv p)
=/ f (fracv p)
=/ u (mulv f (mulv f (mulv f (addv (mulv f (subv (muls f .6) ~[.15 .15])) ~[.10 .10]))))
=/ ga (hash2d (addv i ~[.0 .0]))
=/ gb (hash2d (addv i ~[.1 .0]))
=/ gc (hash2d (addv i ~[.0 .1]))
=/ gd (hash2d (addv i ~[.1 .1]))
=/ va (dot ga (subv f ~[.0 .0]))
=/ vb (dot gb (subv f ~[.1 .0]))
=/ vc (dot gc (subv f ~[.0 .1]))
=/ vd (dot gd (subv f ~[.1 .1]))
=/ t4 (mul:rs (xv u) (mul:rs (yv u) (add:rs (sub:rs (sub:rs va vb) vc) vd)))
=/ t3 (mul:rs (yv u) (sub:rs vc va))
=/ t2 (mul:rs (xv u) (sub:rs vb va))
=/ v (add:rs (add:rs va t2) (add:rs t3 t4))
~[v .0 .0]
++ fbm2dd
|= p=(list @rs) ^- (list @rs)
=/ n 0
=/ w .0.66
=/ acc (limo ~[.0 .0 .0])
|- ^- (list @rs)
=/ nois (muls (noise2dd p) w)
?:(=(n 6) acc $(n +(n), p (muls p .2), w (mul:rs w .0.5), acc (addv nois acc)))
++ len2
|= x=(list @rs) ^- @rs
(dot x x)
++ minelem
|= x=(list @rs) ^- @rs
=/ d=@rs .1e30
|-
?~ x
d
$(d (min d i.x), x t.x)
++ worley
|= p=(list @rs) ^- @rs
=/ offs ~[~[.-1 .-1] ~[.0 .-1] ~[.1 .-1] ~[.-1 .0] ~[.0 .0] ~[.1 .0] ~[.-1 .1] ~[.0 .1] ~[.1 .1]]
=/ f |= off=(list @rs) ^- @rs
=/ tp (addv (floorv p) off)
(len2 (subv (subv p tp) (hash2d tp)))
=/ ds=(list @rs) (turn offs f)
=/ d (minelem (limo ds))
(mul:rs .3 (pow .2.718 (mul:rs .-4 (abs (sub:rs (mul:rs d .2) .1)))))
++ fworley
|= p=(list @rs) ^- @rs
=/ n 0
=/ off=(list @rs) ~[.2.5 .2.5]
=/ r=@rs .1
|- ^- @rs
=/ worl=@rs (worley (addv p off))
=/ newp=(list @rs) (muls p .2)
=/ newoff=(list @rs) (addv (muls off .0.5) ~[.1.173 .1.173])
?:(=(n 6) (sqt:rs (sqt:rs (sqt:rs r))) $(n +(n), p newp, off newoff, r (mul:rs worl r)))
++ col
|= [x=@rs y=@rs] ^- [@rs @rs @rs]
=/ xt (mul:rs (sub:rs x .0.5) .2)
=/ yt (mul:rs (sub:rs y .0.5) .2)
=/ ro ~[.0 .0 .2.5]
=/ rd (norm ~[xt yt .-2])
=/ b (dot ro rd)
=/ c (sub:rs (dot ro ro) .1)
=/ h (sub:rs (mul:rs b b) c)
?: (gth:rs h .0)
=/ t (sub:rs (neg b) (sqt:rs h))
=/ pos (addv ro (muls rd t))
=/ n pos
=/ dif (max (add:rs (mul:rs (snag 0 n) .2) (snag 2 n)) .0)
::[dif dif dif]
=/ zlus1 (add:rs (zv n) .1)
=/ nc (muls (addv ~[(div:rs (xv n) zlus1) (div:rs (yv n) zlus1)] ~[.1 .1]) .3)
=/ nois (fbm2dd nc)
=/ wateramt .0.5
=/ wateramtv (sub:rs (mul:rs wateramt .2) .1)
=/ nv (saturate (xv nois))
=/ albt (smoothstep wateramtv (add:rs wateramtv .0.01) nv)
=/ alb (lerpv (limo ~[.0.05 .0.27 .0.44]) (lerpv (limo ~[.0.4 .0.45 .0.32]) (limo ~[.0.3 .0.2 .0.1]) nv) albt)
=/ spe (saturate (dot n (norm ~[.0.4 .-0.3 .1])))
=/ spev (mul:rs (pow spe .64) (sub:rs .1 albt))
=/ cityamt (mul:rs (smoothstep .0.0 dif .0.2) albt)
=/ fwor ?:((gth:rs cityamt .0) (fworley nc) .0)
=/ city (pow fwor .2.5)
=/ emm (maxv (muls ~[.1.8 (mul:rs .1.8 city) .0.5] city) .0)
=/ cloud (gain (smoothstep .-0.15 .0.3 (xv (fbm2dd (addv nc ~[.3.7 .9.6])))) .1.5)
=/ albc (lerpv alb ~[.1.2 .1.2 .1.2] cloud)
=/ lit (addv (addv (muls albc (add:rs .0.08 (mul:rs dif .0.75))) ~[spev spev spev]) (muls emm cityamt))
:: [cityamt cityamt cityamt]
[(xv lit) (yv lit) (zv lit)]
:: [nv nv nv]
[.0.1 .0.1 .0.1]
++ rs-to-byte
|= x=@rs ^- @ud
`@ud`(abs:si (fall (toi:rs (mul:rs (saturate x) .255)) --0))
++ pix
|= [px=@ py=@] ^- [@ud @ud @ud]
=/ x=@rs (sub:rs .1 (div:rs (sun:rs px) (sun:rs (dec dim))))
=/ y=@rs (sub:rs .1 (div:rs (sun:rs py) (sun:rs (dec dim))))
=/ c (col x y)
[(rs-to-byte -:c) (rs-to-byte +<:c) (rs-to-byte +>:c)]
++ genrow
|= y=@ud ^- tape
=/ x=@ud 0
=/ acc ""
|- ^- tape
?: =(x dim) acc
=/ col (pix x y)
$(x +(x), acc (weld "{<-:col>} {<+<:col>} {<+>:col>} " acc))
++ genimg
|= dim=@ud ^- (list @t)
=/ y=@ud 0
=/ acc=(list @t) ~
|- ^- (list @t)
?: =(y dim) acc
~& "Row: {<y>}"
=/ row `@t`(crip (genrow y))
$(y +(y), acc [row acc])
++ genppm
|= dim=@ud ^- (list @t)
=/ header ~['P3' (crip "{<dim>} {<dim>}") '255']
(weld header (genimg dim))
--
@0x70b1a5
Copy link

What I wouldn't give for some comments in this beast! Is there some way to study the concepts behind this code to figure out what's going on? Or should I just read it... 😅

@mattnewport
Copy link
Author

mattnewport commented Feb 22, 2020

@0x70b1a5 most of the techniques are fairly standard stuff that you'll find used in Shadertoy shaders. I actually prototyped this in Shadertoy and the Hoon version is a pretty direct translation of the GLSL there, some of the functions being mostly ripped off from other Shadertoy shaders.

Everything up to the ++hash2d arm is fairly standard math functions, 2D and 3D vectors are just lists of Hoon floats and then vector ops like addv are implemented using the binopv utility function to map Hoon floating point binary operations over those lists. ++hash2d is a hoon version of a Shadertoy 2D floating point hash function - given a 2D floating point vector it returns a pseudo-random 2D floating point value between -1 and 1. The arms down to ++col are implementations of 2D Perlin noise and Worley noise and their 'fractal' variants - 'Fractal Brownian Motion' just sums up octaves of Perlin noise and ++fworley does the same sort of thing with Worley noise.

The ++col arm then does a very hard coded ray/sphere intersection generating a normal and a 2D texture coordinate for the planet surface and then uses the noise functions to build up color and material values for the water, land, clouds and 'city lights' and feeds those into a simple lighting formula with a hard coded light position to generate a final pixel color.

The remainder of the code is just generating rays at each pixel and calling ++col to get the resulting color value, then converting from floating point colors to 24 bit RGB and generating a list of tapes representing the image in PPM format.

If you have specific questions about any parts of the code let me know and I'll do my best to answer or point you at more info on the techniques used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment