Last active
August 9, 2016 11:11
-
-
Save sporsh/cb2678eee31ca756ff11 to your computer and use it in GitHub Desktop.
go raytracer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
application: soft3dge | |
version: 0 | |
runtime: go | |
api_version: go1 | |
handlers: | |
- url: / | |
script: _go_app |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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