Skip to content

Instantly share code, notes, and snippets.

@sporsh
Last active August 9, 2016 11:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sporsh/cb2678eee31ca756ff11 to your computer and use it in GitHub Desktop.
Save sporsh/cb2678eee31ca756ff11 to your computer and use it in GitHub Desktop.
go raytracer
application: soft3dge
version: 0
runtime: go
api_version: go1
handlers:
- url: /
script: _go_app
package main
import "math"
type Intersection struct {
Point, Normal Vector
Color Vector
}
func (sphere *Sphere) Intersect(ray Ray, backface bool, epsilon float64) (t float64, obj Renderable) {
m := ray.Origin.Sub(sphere.Origin)
c := m.Dot(m) - sphere.Radius*sphere.Radius
b := m.Dot(ray.Direction)
if b > 0 {
return
}
discr := b*b - c
if discr < 0 {
return
}
inside := false
sqrtDiscr := math.Sqrt(discr)
t = -b - sqrtDiscr
if t < epsilon {
inside = true
t = -b + sqrtDiscr
if t < epsilon {
return
}
}
// TODO: use inside information
_ = inside
return t, sphere
}
func (sphere *Sphere) Test(ray Ray, backface bool, epsilon float64) bool {
m := ray.Origin.Sub(sphere.Origin)
c := m.Dot(m) - sphere.Radius*sphere.Radius
if c < -epsilon {
return true
}
b := m.Dot(ray.Direction)
if b > .0 {
return false
}
if b*b-c < .0 {
return false
}
return true
}
func (triangle *Triangle) Intersect(ray Ray, backface bool, epsilon float64) (t float64, obj Renderable) {
a, b, c := triangle.Points[0], triangle.Points[1], triangle.Points[2]
n := triangle.Plane.Normal
ab := b.Sub(a)
ac := c.Sub(a)
qp := ray.Direction.Scale(-1)
d := qp.Dot(n)
if (d == 0) || (d < 0 /*|| !backface*/) {
// Plane and ray are paralell or pointing away
return
}
ap := ray.Origin.Sub(a)
t = ap.Dot(n)
if t < 0 {
return
}
e := qp.Cross(ap)
v := ac.Dot(e)
if v < 0 || v > d {
return
}
w := -ab.Dot(e)
if w < 0 || v+w > d {
return
}
ood := 1 / d
t *= ood
return t, triangle
}
func (triangle *Triangle) Test(ray Ray, backface bool, epsilon float64) bool {
_, obj := triangle.Intersect(ray, backface, epsilon)
return obj != nil
}
package main
type Renderable interface {
Normal(at Vector) Vector
Intersect(ray Ray, backface bool, epsilon float64) (t float64, obj Renderable)
Test(ray Ray, backface bool, epsilon float64) bool
}
type Plane struct {
Normal Vector
d float64
}
func NewPlane(p [3]Vector) *Plane {
normal := p[1].Sub(p[0]).Cross(p[2].Sub(p[0]))
return &Plane{normal, normal.Dot(p[0])}
}
type Triangle struct {
Points [3]Vector
Plane *Plane
}
func (t *Triangle) Normal(at Vector) Vector {
return t.Plane.Normal
}
func NewTriangle(points [3]Vector) *Triangle {
return &Triangle{points, NewPlane(points)}
}
type Sphere struct {
Origin Vector
Radius float64
}
func (s *Sphere) Normal(at Vector) Vector {
return Normalize(at.Sub(s.Origin))
}
package main
import (
"fmt"
"image"
"image/color"
"image/png"
"math"
"net/http"
)
type Light struct {
Origin Vector
}
type Scene struct {
Objects []Renderable
Lights []Light
Ambient Vector
}
type Ray struct {
Origin, Direction Vector
}
type Camera struct {
FOV, FOVRadians float64
Origin, Direction, Right, Up Vector
}
func NewCamera(origin, lookAt Vector) *Camera {
fov := 45. / 2
fovRadians := fov * math.Pi / 180
direction := Normalize(lookAt.Sub(origin))
right := Normalize(direction.Cross(V3_Y))
up := right.Cross(direction)
return &Camera{fov, fovRadians, origin, direction, right, up}
}
type RayTraceImage struct {
// Rect is the image's bounds.
Rect image.Rectangle
Scene Scene
Camera *Camera
aspect float64
halfWidth, halfHeight float64
pixelWidth, pixelHeight float64
}
func (r RayTraceImage) ColorModel() color.Model { return color.RGBAModel }
func (r RayTraceImage) Bounds() image.Rectangle {
return r.Rect
}
// BRDF calculates the BRDF given by the incidence light direction i,
// reflected light direction r and the surface normal n
func BRDF(i, _, n Vector) float64 {
return math.Max(n.Dot(i), 0)
}
// At samples rays projected onto image at given coordinates
func (r RayTraceImage) At(x, y int) color.Color {
xComp := r.Camera.Right.Scale(float64(x)*r.pixelWidth - r.halfWidth)
yComp := r.Camera.Up.Scale(float64(y)*r.pixelHeight - r.halfHeight)
direction := Normalize(r.Camera.Direction.Sub(xComp).Sub(yComp))
ray := Ray{r.Camera.Origin, direction}
return r.Scene.Trace(ray, false, .1)
}
func (scene Scene) Trace(ray Ray, backface bool, epsilon float64) color.Color {
radiance := scene.Ambient
if t, obj := scene.Intersect(ray, backface, epsilon); obj != nil {
point := ray.Origin.Add(ray.Direction.Scale(t))
normal := obj.Normal(point)
c := Normalize(normal)
for _, light := range scene.Lights {
lightRay := Ray{point, Normalize(light.Origin.Sub(point))}
if !scene.Test(lightRay, true, epsilon) {
brdf := BRDF(lightRay.Direction, ray.Direction, normal)
radiance = radiance.Add(c.Scale(brdf))
}
}
}
return radiance
}
func (scene *Scene) Intersect(ray Ray, backface bool, epsilon float64) (finalT float64, finalObj Renderable) {
for _, obj := range scene.Objects {
t, obj := obj.Intersect(ray, backface, epsilon)
if obj != nil && (finalObj == nil || t < finalT) {
finalT, finalObj = t, obj
}
}
return
}
// Test performs a boolean test wether the ray intersects any object in the scene
func (s Scene) Test(ray Ray, backface bool, epsilon float64) bool {
for _, obj := range s.Objects {
if obj.Test(ray, backface, epsilon) {
return true
}
}
return false
}
// NewRayTraceImage returns a new RayTraceImage of the scene s as observed
// from camera c with the given resolution.
func NewRayTraceImage(s Scene, c *Camera, width, height int) *RayTraceImage {
aspect := float64(height) / float64(width)
halfWidth := math.Tan(c.FOVRadians)
halfHeight := aspect * halfWidth
pixelWidth := halfWidth * 2 / float64(width)
pixelHeight := halfHeight * 2 / float64(height)
return &RayTraceImage{
image.Rect(0, 0, width, height),
s, c,
aspect,
halfWidth, halfHeight,
pixelWidth, pixelHeight,
}
}
func handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "image/png")
s := Scene{
Objects: []Renderable{
&Sphere{&V3{150, 350, 350}, 100},
&Sphere{&V3{350, 150, 150}, 100},
NewTriangle([3]Vector{V3{0, 500, 0}, V3{0, 500, 500}, V3{0, 0, 500}}),
NewTriangle([3]Vector{V3{0, 0, 500}, V3{0, 0, 0}, V3{0, 500, 0}}),
NewTriangle([3]Vector{V3{0, 0, 500}, V3{500, 0, 500}, V3{500, 0, 0}}),
NewTriangle([3]Vector{V3{0, 0, 0}, V3{0, 0, 500}, V3{500, 0, 0}}),
NewTriangle([3]Vector{V3{500, 500, 0}, V3{500, 0, 500}, V3{500, 500, 500}}),
NewTriangle([3]Vector{V3{500, 0, 500}, V3{500, 500, 0}, V3{500, 0, 0}}),
},
Lights: []Light{
Light{&V3{250, 499, 250}},
},
Ambient: V3{.2, .2, .2},
}
c := NewCamera(&V3{250, 250, -800}, &V3{250, 250, 0})
img := NewRayTraceImage(s, c, 640, 480)
if err := png.Encode(w, img); err != nil {
fmt.Fprintln(w, err)
}
}
func init() {
http.HandleFunc("/", handler)
http.ListenAndServe("0.0.0.0:8000", nil)
}
package main
import "math"
var (
V3_ZERO = &V3{.0, .0, .0}
V3_X = &V3{1., .0, .0}
V3_Y = &V3{.0, 1., .0}
V3_Z = &V3{.0, .0, 1.}
)
type V3 [3]float64
type V4 struct{ Vector }
type Vector interface {
XYZ() *V3
// XYZW() (z, y, x, w float64)
Add(Vector) Vector
Sub(Vector) Vector
Scale(float64) Vector
Dot(Vector) float64
Cross(Vector) Vector
RGBA() (r, g, b, a uint32)
}
func (v V3) XYZ() *V3 {
return &v
}
func (a V3) Add(v Vector) Vector {
b := v.XYZ()
a[0] += b[0]
a[1] += b[1]
a[2] += b[2]
return a
}
func (a V3) Sub(v Vector) Vector {
b := v.XYZ()
a[0] -= b[0]
a[1] -= b[1]
a[2] -= b[2]
return a
}
func (v V3) Scale(m float64) Vector {
v[0] *= m
v[1] *= m
v[2] *= m
return v
}
func (a V3) Dot(v Vector) float64 {
b := v.XYZ()
return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]
}
func (a V3) Cross(v Vector) Vector {
b := v.XYZ()
a[0], a[1], a[2] = a[1]*b[2]-a[2]*b[1], a[2]*b[0]-a[0]*b[2], a[0]*b[1]-a[1]*b[0]
return a
}
func Length(v Vector) float64 {
return math.Sqrt(v.Dot(v))
}
func Resize(v Vector, length float64) Vector {
return v.Scale(length / Length(v))
}
func Normalize(v Vector) Vector {
return Resize(v, 1)
}
func Reflect(v, normal Vector) Vector {
return v.Sub(normal.Scale(2 * v.Dot(normal)))
}
func Refract(v, normal Vector, n1, n2 float64) Vector {
n := n1 / n2
cosi := -normal.Dot(v)
cost2 := 1. - n*n*1. - cosi*cosi
return v.Scale(n).Add(normal.Scale(n*cosi - math.Sqrt(math.Abs(cost2))))
}
func (v V3) RGBA() (r, g, b, a uint32) {
r = uint32(math.Min(1, v[0]) * 0xffff)
g = uint32(math.Min(1, v[1]) * 0xffff)
b = uint32(math.Min(1, v[2]) * 0xffff)
a = 0xffff
return
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment