├── .gitignore ├── LICENSE ├── README.md ├── color.go ├── lights.go ├── main.go ├── material.go ├── primitives.go ├── ray.go ├── samples ├── helix.png ├── helix.txt ├── pyramid.png ├── pyramid.txt ├── scene.png └── scene.txt ├── scene.go └── vector.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | .desktop -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Guillermo Estrada 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rayito 2 | 3 | **Rayito** is a simple but educational ray tracer written in Go meant as a project to learn programming languages and optimization in Computer Graphics. 4 | 5 | *"In computer graphics, ray tracing is a technique for generating an image by tracing the path of light through pixels in an image plane and simulating the effects of its encounters with virtual objects." - Wikipedia* 6 | 7 | **Rayito** is meant to be simple, but implements a full set of features like: 8 | 9 | + Multi Threading 10 | + Oversampling (anti aliasing) 11 | + Ambient and Point Lights 12 | + Custom Materials 13 | + Specular and Difuse shading (Phong) 14 | + Reflection (mirror) 15 | + Refraction (transparency) 16 | + Soft Shadows 17 | + Scene description and parsing 18 | + PNG Export 19 | + and more... 20 | 21 | Code is meant to be as clear as possible (no cutting names on variables) so it's meant to be read directly. In order to try **Rayito** either clone the git repository or do: 22 | 23 | ``` 24 | go get github.com/phrozen/rayito 25 | ``` 26 | 27 | After building the binary you can start to render by using some scenes in the *samples* directory or write your own. 28 | 29 | ### Usage 30 | Set workers to the number of threads your CPU can handle (defaults to CPU Max Threads). 31 | ``` 32 | rayito -workers=8 -file="samples/scene.txt" 33 | ``` 34 | 35 | ### Samples 36 | 37 | ![Helix](https://raw.githubusercontent.com/phrozen/rayito/master/samples/helix.png) 38 | 39 | ![Spheres](https://raw.githubusercontent.com/phrozen/rayito/master/samples/scene.png) 40 | -------------------------------------------------------------------------------- /color.go: -------------------------------------------------------------------------------- 1 | // color.go 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "image/color" 7 | "math" 8 | ) 9 | 10 | type Color struct { 11 | r, g, b float64 12 | } 13 | 14 | func (c Color) Add(u Color) Color { 15 | return Color{c.r + u.r, c.g + u.g, c.b + u.b} 16 | } 17 | 18 | func (c Color) Mul(f float64) Color { 19 | return Color{c.r * f, c.g * f, c.b * f} 20 | } 21 | 22 | func (c Color) String() string { 23 | return fmt.Sprintf("", c.r, c.g, c.b) 24 | } 25 | 26 | func (c Color) ToPixel() color.RGBA { 27 | c.r = math.Max(0.0, math.Min(c.r*255.0, 255.0)) 28 | c.g = math.Max(0.0, math.Min(c.g*255.0, 255.0)) 29 | c.b = math.Max(0.0, math.Min(c.b*255.0, 255.0)) 30 | return color.RGBA{uint8(c.r), uint8(c.g), uint8(c.b), 255} 31 | } 32 | -------------------------------------------------------------------------------- /lights.go: -------------------------------------------------------------------------------- 1 | // light.go 2 | package main 3 | 4 | type Light struct { 5 | position Vector 6 | color Color 7 | kind string 8 | } 9 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "image/png" 7 | "math" 8 | "os" 9 | "runtime" 10 | ) 11 | 12 | const ( 13 | MAX_DIST = 1999999999 14 | PI_180 = 0.017453292 15 | SMALL = 0.000000001 16 | ) 17 | 18 | func calcShadow(r *Ray, collisionObj int) float64 { 19 | shadow := 1.0 //starts with no shadow 20 | for i, obj := range scene.objectList { 21 | r.interObj = -1 22 | r.interDist = MAX_DIST 23 | 24 | if obj.Intersect(r, i) && i != collisionObj { 25 | shadow *= scene.materialList[obj.Material()].transmitCol 26 | } 27 | } 28 | return shadow 29 | } 30 | 31 | func trace(r *Ray, depth int) (c Color) { 32 | 33 | for i, obj := range scene.objectList { 34 | obj.Intersect(r, i) 35 | } 36 | 37 | if r.interObj >= 0 { 38 | matIndex := scene.objectList[r.interObj].Material() 39 | interPoint := r.origin.Add(r.direction.Mul(r.interDist)) 40 | incidentV := interPoint.Sub(r.origin) 41 | originBackV := r.direction.Mul(-1.0) 42 | originBackV = originBackV.Normalize() 43 | vNormal := scene.objectList[r.interObj].getNormal(interPoint) 44 | for _, light := range scene.lightList { 45 | switch light.kind { 46 | case "ambient": 47 | c = c.Add(light.color) 48 | case "point": 49 | lightDir := light.position.Sub(interPoint) 50 | lightDir = lightDir.Normalize() 51 | lightRay := Ray{interPoint, lightDir, MAX_DIST, -1} 52 | shadow := calcShadow(&lightRay, r.interObj) 53 | NL := vNormal.Dot(lightDir) 54 | 55 | if NL > 0.0 { 56 | if scene.materialList[matIndex].difuseCol > 0.0 { // ------- Difuso 57 | difuseColor := light.color.Mul(scene.materialList[matIndex].difuseCol).Mul(NL) 58 | difuseColor.r *= scene.materialList[matIndex].color.r * shadow 59 | difuseColor.g *= scene.materialList[matIndex].color.g * shadow 60 | difuseColor.b *= scene.materialList[matIndex].color.b * shadow 61 | c = c.Add(difuseColor) 62 | } 63 | if scene.materialList[matIndex].specularCol > 0.0 { // ----- Especular 64 | R := (vNormal.Mul(2).Mul(NL)).Sub(lightDir) 65 | spec := originBackV.Dot(R) 66 | if spec > 0.0 { 67 | spec = scene.materialList[matIndex].specularCol * math.Pow(spec, scene.materialList[matIndex].specularD) 68 | specularColor := light.color.Mul(spec).Mul(shadow) 69 | c = c.Add(specularColor) 70 | } 71 | } 72 | } 73 | } 74 | } 75 | if depth < scene.traceDepth { 76 | if scene.materialList[matIndex].reflectionCol > 0.0 { // -------- Reflexion 77 | T := originBackV.Dot(vNormal) 78 | if T > 0.0 { 79 | vDirRef := (vNormal.Mul(2).Mul(T)).Sub(originBackV) 80 | vOffsetInter := interPoint.Add(vDirRef.Mul(SMALL)) 81 | rayoRef := Ray{vOffsetInter, vDirRef, MAX_DIST, -1} 82 | c = c.Add(trace(&rayoRef, depth+1.0).Mul(scene.materialList[matIndex].reflectionCol)) 83 | } 84 | } 85 | if scene.materialList[matIndex].transmitCol > 0.0 { // ---- Refraccion 86 | RN := vNormal.Dot(incidentV.Mul(-1.0)) 87 | incidentV = incidentV.Normalize() 88 | var n1, n2 float64 89 | if vNormal.Dot(incidentV) > 0.0 { 90 | vNormal = vNormal.Mul(-1.0) 91 | RN = -RN 92 | n1 = scene.materialList[matIndex].IOR 93 | n2 = 1.0 94 | } else { 95 | n2 = scene.materialList[matIndex].IOR 96 | n1 = 1.0 97 | } 98 | if n1 != 0.0 && n2 != 0.0 { 99 | par_sqrt := math.Sqrt(1 - (n1*n1/n2*n2)*(1-RN*RN)) 100 | refactDirV := incidentV.Add(vNormal.Mul(RN).Mul(n1 / n2)).Sub(vNormal.Mul(par_sqrt)) 101 | vOffsetInter := interPoint.Add(refactDirV.Mul(SMALL)) 102 | refractRay := Ray{vOffsetInter, refactDirV, MAX_DIST, -1} 103 | c = c.Add(trace(&refractRay, depth+1.0).Mul(scene.materialList[matIndex].transmitCol)) 104 | } 105 | } 106 | } 107 | } 108 | return c 109 | } 110 | 111 | func renderPixel(line chan int, done chan bool) { 112 | for y := range line { // 1: 1, 5: 2, 8: 3, 113 | for x := 0; x < scene.imgWidth; x++ { 114 | var c Color 115 | yo := y * scene.oversampling 116 | xo := x * scene.oversampling 117 | for i := 0; i < scene.oversampling; i++ { 118 | for j := 0; j < scene.oversampling; j++ { 119 | var dir Vector 120 | dir.x = float64(xo)*scene.Vhor.x + float64(yo)*scene.Vver.x + scene.Vp.x 121 | dir.y = float64(xo)*scene.Vhor.y + float64(yo)*scene.Vver.y + scene.Vp.y 122 | dir.z = float64(xo)*scene.Vhor.z + float64(yo)*scene.Vver.z + scene.Vp.z 123 | dir = dir.Normalize() 124 | r := Ray{scene.cameraPos, dir, MAX_DIST, -1} 125 | c = c.Add(trace(&r, 1.0)) 126 | yo += 1 127 | } 128 | xo += 1 129 | } 130 | srq_oversampling := float64(scene.oversampling * scene.oversampling) 131 | c.r /= srq_oversampling 132 | c.g /= srq_oversampling 133 | c.b /= srq_oversampling 134 | scene.image.SetRGBA(x, y, c.ToPixel()) 135 | //fmt.Println("check") 136 | } 137 | if y%10 == 0 { 138 | fmt.Printf("%d ", y) 139 | } 140 | } 141 | done <- true 142 | } 143 | 144 | var scene *Scene 145 | 146 | func main() { 147 | var sceneFilename string 148 | var numWorkers int 149 | flag.StringVar(&sceneFilename, "file", "samples/scene.txt", "Scene file to render.") 150 | flag.IntVar(&numWorkers, "workers", runtime.NumCPU(), "Number of worker threads.") 151 | flag.Parse() 152 | 153 | scene = NewScene(sceneFilename) 154 | done := make(chan bool, numWorkers) 155 | line := make(chan int) 156 | 157 | // launch the workers 158 | for i := 0; i < numWorkers; i++ { 159 | go renderPixel(line, done) 160 | } 161 | 162 | fmt.Println("Rendering: ", sceneFilename) 163 | fmt.Printf("Line (from %d to %d): ", scene.startline, scene.endline) 164 | for y := scene.startline; y < scene.endline; y++ { 165 | line <- y 166 | } 167 | close(line) 168 | 169 | // wait for all workers to finish 170 | for i := 0; i < numWorkers; i++ { 171 | <-done 172 | } 173 | 174 | output, err := os.Create(sceneFilename[0:len(sceneFilename)-4] + ".png") 175 | if err != nil { 176 | panic(err) 177 | } 178 | 179 | if err = png.Encode(output, scene.image); err != nil { 180 | panic(err) 181 | } 182 | fmt.Println(" DONE!") 183 | } 184 | -------------------------------------------------------------------------------- /material.go: -------------------------------------------------------------------------------- 1 | // material.go 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | ) 7 | 8 | type Material struct { 9 | color Color 10 | difuseCol, specularCol, specularD, reflectionCol, transmitCol, IOR float64 11 | } 12 | 13 | func (m Material) String() string { 14 | return fmt.Sprintf("", m.color.String(), m.difuseCol, m.specularCol, m.specularD, m.reflectionCol, m.transmitCol, m.IOR) 15 | } 16 | -------------------------------------------------------------------------------- /primitives.go: -------------------------------------------------------------------------------- 1 | // primitives.go 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "math" 7 | ) 8 | 9 | // CUERPOS 10 | type Object interface { 11 | Type() string 12 | Material() int 13 | Intersect(r *Ray, i int) bool 14 | getNormal(point Vector) Vector 15 | } 16 | 17 | // ESFERA 18 | type Sphere struct { 19 | material int 20 | position Vector 21 | radius float64 22 | } 23 | 24 | func (e Sphere) Type() string { 25 | return "sphere" 26 | } 27 | 28 | func (e Sphere) Material() int { 29 | return e.material 30 | } 31 | 32 | func (e Sphere) Intersect(r *Ray, i int) bool { 33 | sphereRay := e.position.Sub(r.origin) 34 | v := sphereRay.Dot(r.direction) 35 | if v-e.radius > r.interDist { 36 | return false 37 | } 38 | collisionDist := e.radius*e.radius + v*v - sphereRay.x*sphereRay.x - sphereRay.y*sphereRay.y - sphereRay.z*sphereRay.z 39 | if collisionDist < 0.0 { 40 | return false 41 | } 42 | collisionDist = v - math.Sqrt(collisionDist) 43 | if collisionDist > r.interDist || collisionDist < 0.0 { 44 | return false 45 | } 46 | r.interDist = collisionDist 47 | r.interObj = i 48 | return true 49 | } 50 | 51 | func (e Sphere) getNormal(point Vector) Vector { 52 | normal := point.Sub(e.position) 53 | return normal.Normalize() 54 | } 55 | 56 | func (e Sphere) String() string { 57 | return fmt.Sprintf("", e.material, e.position.String(), e.radius) 58 | } 59 | 60 | // PLANO 61 | type Plane struct { 62 | material int 63 | normal Vector 64 | distancia float64 65 | } 66 | 67 | func (p Plane) Type() string { 68 | return "plane" 69 | } 70 | 71 | func (p Plane) Material() int { 72 | return p.material 73 | } 74 | 75 | func (p Plane) Intersect(r *Ray, i int) bool { 76 | v := p.normal.Dot(r.direction) 77 | if v == 0 { 78 | return false 79 | } 80 | collisionDist := -(p.normal.Dot(r.origin) + p.distancia) / v 81 | if collisionDist < 0.0 { 82 | return false 83 | } 84 | if collisionDist > r.interDist { 85 | return false 86 | } 87 | r.interDist = collisionDist 88 | r.interObj = i 89 | return true 90 | } 91 | 92 | func (p Plane) getNormal(point Vector) Vector { 93 | return p.normal 94 | } 95 | 96 | func (p Plane) String() string { 97 | return fmt.Sprintf("", p.material, p.normal.String(), p.distancia) 98 | } 99 | -------------------------------------------------------------------------------- /ray.go: -------------------------------------------------------------------------------- 1 | // ray.go 2 | package main 3 | 4 | // RAY 5 | type Ray struct { 6 | origin Vector 7 | direction Vector 8 | interDist float64 // MAX_DIST 9 | interObj int 10 | } 11 | -------------------------------------------------------------------------------- /samples/helix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phrozen/rayito/d347599251c73b94fe946087f78842848886101f/samples/helix.png -------------------------------------------------------------------------------- /samples/helix.txt: -------------------------------------------------------------------------------- 1 | # (optional, default 320 240) size = size of the final image 2 | image_size 2048 1536 3 | 4 | depth 5 5 | 6 | # (optional, default 1) oversampling=1 no oversampling 7 | oversampling 4 8 | 9 | # (optional, default 60) vision = size of the visual field 10 | field_of_view 60 11 | 12 | # (optional, default all span) renderslice: start_rendering_line end_rendering_line 13 | # renderslice note: if you use oversampling>1 it may give problems 14 | #renderslice 10 40 15 | 16 | camera_position 6.0 6.0 6.0 17 | camera_look 1.0 0.8 -0.1 18 | camera_up 0.0 0.0 1.0 19 | 20 | # sphere: material_number x y z radius 21 | sphere 0 -8 2.26163 0.993114 0.35 22 | sphere 2 -8 0.73837 3.00689 0.35 23 | sphere 0 -7.6 1.60521 0.803393 0.35 24 | sphere 2 -7.6 1.39479 3.19661 0.35 25 | sphere 0 -7.2 0.923034 0.906644 0.35 26 | sphere 2 -7.2 2.07697 3.09336 0.35 27 | sphere 0 -6.8 0.382118 1.27759 0.35 28 | sphere 2 -6.8 2.61788 2.72241 0.35 29 | sphere 0 -6.4 0.114898 1.8254 0.35 30 | sphere 2 -6.4 2.8851 2.1746 0.35 31 | sphere 0 -6 0.1868 2.41596 0.35 32 | sphere 2 -6 2.8132 1.58404 0.35 33 | sphere 0 -5.6 0.580219 2.90468 0.35 34 | sphere 2 -5.6 2.41978 1.09532 0.35 35 | sphere 0 -5.2 1.19883 3.17191 0.35 36 | sphere 2 -5.2 1.80117 0.828095 0.35 37 | sphere 0 -4.8 1.89118 3.1522 0.35 38 | sphere 2 -4.8 1.10882 0.847796 0.35 39 | sphere 0 -4.4 2.48776 2.8504 0.35 40 | sphere 2 -4.4 0.512244 1.1496 0.35 41 | sphere 0 -4 2.84249 2.34039 0.35 42 | sphere 2 -4 0.157506 1.65961 0.35 43 | sphere 0 -3.6 2.86854 1.74705 0.35 44 | sphere 2 -3.6 0.131458 2.25295 0.35 45 | sphere 0 -3.2 2.55952 1.21563 0.35 46 | sphere 2 -3.2 0.440477 2.78437 0.35 47 | sphere 0 -2.8 1.9911 0.876252 0.35 48 | sphere 2 -2.8 1.0089 3.12375 0.35 49 | sphere 0 -2.4 1.30243 0.812009 0.35 50 | sphere 2 -2.4 1.69757 3.18799 0.35 51 | sphere 0 -2 0.662139 1.03863 0.35 52 | sphere 2 -2 2.33786 2.96137 0.35 53 | sphere 0 -1.6 0.226984 1.50062 0.35 54 | sphere 2 -1.6 2.77302 2.49938 0.35 55 | sphere 0 -1.2 0.103507 2.08488 0.35 56 | sphere 2 -1.2 2.89649 1.91512 0.35 57 | sphere 0 -0.8 0.321941 2.64836 0.35 58 | sphere 2 -0.8 2.67806 1.35164 0.35 59 | sphere 0 -0.4 0.828804 3.0531 0.35 60 | sphere 2 -0.4 2.1712 0.946901 0.35 61 | sphere 0 0 1.5 3.2 0.35 62 | sphere 2 0 1.5 0.8 0.35 63 | sphere 0 0.4 2.1712 3.0531 0.35 64 | sphere 2 0.4 0.828804 0.946901 0.35 65 | sphere 0 0.8 2.67806 2.64836 0.35 66 | sphere 2 0.8 0.321941 1.35164 0.35 67 | sphere 0 1.2 2.89649 2.08488 0.35 68 | sphere 2 1.2 0.103507 1.91512 0.35 69 | sphere 0 1.6 2.77302 1.50062 0.35 70 | sphere 2 1.6 0.226984 2.49938 0.35 71 | sphere 0 2 2.33786 1.03863 0.35 72 | sphere 2 2 0.662139 2.96137 0.35 73 | sphere 0 2.4 1.69757 0.812009 0.35 74 | sphere 2 2.4 1.30243 3.18799 0.35 75 | sphere 0 2.8 1.0089 0.876252 0.35 76 | sphere 2 2.8 1.9911 3.12375 0.35 77 | sphere 0 3.2 0.440477 1.21563 0.35 78 | sphere 2 3.2 2.55952 2.78437 0.35 79 | sphere 0 3.6 0.131458 1.74705 0.35 80 | sphere 2 3.6 2.86854 2.25295 0.35 81 | sphere 0 4 0.157506 2.34039 0.35 82 | sphere 2 4 2.84249 1.65961 0.35 83 | sphere 0 4.4 0.512244 2.8504 0.35 84 | sphere 2 4.4 2.48776 1.1496 0.35 85 | sphere 0 4.8 1.10882 3.1522 0.35 86 | sphere 2 4.8 1.89118 0.847796 0.35 87 | sphere 0 5.2 1.80117 3.17191 0.35 88 | sphere 2 5.2 1.19883 0.828095 0.35 89 | sphere 0 5.6 2.41978 2.90468 0.35 90 | sphere 2 5.6 0.580219 1.09532 0.35 91 | sphere 0 6 2.8132 2.41596 0.35 92 | sphere 2 6 0.1868 1.58404 0.35 93 | sphere 0 6.4 2.8851 1.8254 0.35 94 | sphere 2 6.4 0.114898 2.1746 0.35 95 | sphere 0 6.8 2.61788 1.27759 0.35 96 | sphere 2 6.8 0.382118 2.72241 0.35 97 | sphere 0 7.2 2.07697 0.906644 0.35 98 | sphere 2 7.2 0.923034 3.09336 0.35 99 | sphere 0 7.6 1.39479 0.803393 0.35 100 | sphere 2 7.6 1.60521 3.19661 0.35 101 | 102 | # plane: material normal_x normal_y normal_z distancia: 103 | plane 1 0.0 0.0 1.0 0.0 104 | 105 | # light: x y z r g b point/ambient (rgb are in [0,1]) 106 | light point -2.0 1.0 4.0 1.0 1.0 1.0 107 | light point 4.0 -4.0 10.0 0.6 0.6 0.6 108 | light ambient -2.0 1.0 4.0 0.1 0.1 0.1 109 | 110 | # material: r g b difuseCol specularCol specularD reflectionCol transmitCol IOR 111 | material 0.3 0.3 1.0 0.7 0.4 0.8 0.3 0.0 0.0 112 | material 0.4 1.0 1.0 0.2 0.0 0.0 0.2 0.0 0.0 113 | material 1.0 0.2 0.2 0.9 0.7 1.0 0.2 0.0 0.0 114 | material 0.2 1.0 0.2 0.4 0.4 0.9 0.7 0.3 0.9 115 | -------------------------------------------------------------------------------- /samples/pyramid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phrozen/rayito/d347599251c73b94fe946087f78842848886101f/samples/pyramid.png -------------------------------------------------------------------------------- /samples/pyramid.txt: -------------------------------------------------------------------------------- 1 | # (optional, default 320 240) size = size of the final image 2 | image_size 2048 1536 3 | #image_size 1024 1024 4 | depth 5 5 | 6 | # (optional, default 1) oversampling=1 no oversampling 7 | oversampling 4 8 | 9 | # (optional, default 60) vision = size of the visual field 10 | field_of_view 40 11 | 12 | # (optional, default all span) renderslice: start_rendering_line end_rendering_line 13 | # renderslice note: if you use oversampling>1 it may give problems 14 | #renderslice 10 40 15 | 16 | camera_position 0.0 10.0 2.0 17 | camera_look 1.0 -1.0 1.0 18 | 19 | # sphere: material_number x y z radius 20 | sphere 0 0.0 1.1547 1.0 1.0 21 | sphere 1 -1.0 -0.5773 1.0 1.0 22 | sphere 2 1.0 -0.5773 1.0 1.0 23 | sphere 3 0.0 0.0 2.6 1.0 24 | 25 | # plane: material normal_x normal_y normal_z distancia: 26 | plane 4 0.0 0.0 1.0 0.0 27 | 28 | # light: x y z r g b point/ambient (rgb are in [0,1]) 29 | light point -2.0 1.0 4.0 1.0 1.0 1.0 30 | light point 5.0 5.0 5.0 0.5 0.5 0.5 31 | light ambient 0.0 0.0 4.0 0.1 0.1 0.1 32 | 33 | # material: r g b difuseCol specularCol specularD reflectionCol transmitCol IOR 34 | material 1.0 0.9 0.0 1.0 0.5 3.0 0.3 0.0 0.0 35 | material 0.0 0.4 1.0 1.0 0.5 3.0 0.3 0.0 0.0 36 | material 0.4 1.0 0.0 1.0 0.5 3.0 0.3 0.0 0.0 37 | material 1.0 0.0 0.4 2.0 0.5 9.0 0.3 0.0 0.0 38 | material 0.8 0.8 0.9 1.0 0.0 6.0 0.1 0.0 0.0 39 | -------------------------------------------------------------------------------- /samples/scene.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phrozen/rayito/d347599251c73b94fe946087f78842848886101f/samples/scene.png -------------------------------------------------------------------------------- /samples/scene.txt: -------------------------------------------------------------------------------- 1 | # scene.txt 2 | # image_size: width height (default 320x240) 3 | image_size 2048 1536 4 | 5 | # trace depth: int (default 3) 6 | depth 5 7 | 8 | # oversampling: int (default 1 = no oversampling) 9 | oversampling 4 10 | 11 | # field_of_view: int (default 60°) 12 | field_of_view 55 13 | 14 | # camera: vector(x y z) 15 | camera_position 5.0 5.0 5.0 16 | camera_look 1.0 1.0 0.0 17 | camera_up 0.0 0.0 1.0 18 | 19 | # sphere: material_index center(x y z) radius 20 | sphere 3 3.0 1.5 0.8 0.8 21 | sphere 4 0.0 0.0 1.0 1.0 22 | sphere 2 1.0 3.0 0.7 0.7 23 | sphere 0 -1.0 1.5 0.5 0.5 24 | 25 | # plane: material_index normal(x y z) distance 26 | plane 1 0.0 0.0 1.0 0.0 27 | 28 | # light: type position(x y z) color(r g b) 29 | light point -2.0 1.0 4.0 1.0 1.0 1.0 30 | light point 2.0 13.0 4.0 0.5 0.5 0.5 31 | light ambient 0.0 0.0 0.0 0.1 0.1 0.1 32 | 33 | # material: color(r g b) diffuse specular shininess reflect transmit ior 34 | material 1.0 0.4 0.0 0.9 0.8 2.0 0.15 0.0 0.0 35 | material 0.8 0.8 0.9 1.0 0.0 4.0 0.1 0.0 0.0 36 | material 1.0 0.2 0.2 0.9 0.7 1.0 0.2 0.0 0.0 37 | material 0.2 1.0 0.2 0.4 0.8 2.0 0.15 0.0 1.1 38 | material 0.3 0.3 1.0 0.7 0.4 0.8 0.3 0.0 0.0 -------------------------------------------------------------------------------- /scene.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "image" 7 | "io" 8 | "math" 9 | "os" 10 | "strconv" 11 | "strings" 12 | ) 13 | 14 | // SCENE 15 | type Scene struct { 16 | imgWidth int 17 | imgHeight int 18 | traceDepth int 19 | oversampling int 20 | visionField float64 21 | startline int 22 | endline int 23 | gridWidth int 24 | gridHeight int 25 | cameraPos Vector 26 | cameraLook Vector 27 | cameraUp Vector 28 | look Vector 29 | Vhor Vector 30 | Vver Vector 31 | Vp Vector 32 | image *image.RGBA 33 | objectList []Object 34 | lightList []Light 35 | materialList []Material 36 | } 37 | 38 | func NewScene(sceneFilename string) *Scene { 39 | scn := new(Scene) 40 | // defaults 41 | scn.imgWidth = 320 42 | scn.imgHeight = 240 43 | 44 | scn.traceDepth = 3 // bounces 45 | scn.oversampling = 1 // no oversampling 46 | scn.visionField = 60 47 | 48 | scn.startline = 0 // Start rendering line 49 | scn.endline = scn.imgHeight - 1 50 | 51 | //scn.objectList = append(scn.objectList, Sphere{0,0.0,0.0,0.0,0.0}) 52 | 53 | f, err := os.Open(sceneFilename) 54 | if err != nil { 55 | panic(err) 56 | } 57 | defer f.Close() 58 | 59 | r := bufio.NewReaderSize(f, 4*1024) 60 | if err != nil { 61 | panic(err) 62 | } 63 | line, isPrefix, err := r.ReadLine() 64 | 65 | for err == nil && !isPrefix { 66 | 67 | s := string(line) 68 | if len(s) == 0 { 69 | line, isPrefix, err = r.ReadLine() 70 | continue 71 | } 72 | 73 | if s[0:1] == "#" { 74 | line, isPrefix, err = r.ReadLine() 75 | continue 76 | } 77 | 78 | sline := strings.Split(s, " ") 79 | word := sline[0] 80 | untrimmed := sline[1:] 81 | var data []string 82 | 83 | for _, item := range untrimmed { 84 | if item == "" || item == " " { 85 | continue 86 | } 87 | data = append(data, strings.Trim(item, " ")) 88 | } 89 | 90 | switch word { 91 | case "image_size": 92 | scn.imgWidth, _ = strconv.Atoi(data[0]) 93 | scn.imgHeight, _ = strconv.Atoi(data[1]) 94 | scn.endline = scn.imgHeight - 1 // End rendering line 95 | case "depth": 96 | scn.traceDepth, _ = strconv.Atoi(data[0]) // n. bounces 97 | case "oversampling": 98 | scn.oversampling, _ = strconv.Atoi(data[0]) 99 | case "field_of_view": 100 | scn.visionField, _ = strconv.ParseFloat(data[0], 64) 101 | case "renderslice": 102 | scn.startline, _ = strconv.Atoi(data[0]) 103 | scn.endline, _ = strconv.Atoi(data[1]) 104 | case "camera_position": 105 | scn.cameraPos = ParseVector(data) 106 | case "camera_look": 107 | scn.cameraLook = ParseVector(data) 108 | case "camera_up": 109 | scn.cameraUp = ParseVector(data) 110 | case "sphere": 111 | mat, _ := strconv.Atoi(data[0]) 112 | rad, _ := strconv.ParseFloat(data[4], 64) 113 | scn.objectList = append(scn.objectList, Sphere{mat, ParseVector(data[1:4]), rad}) 114 | case "plane": 115 | mat, _ := strconv.Atoi(data[0]) 116 | dis, _ := strconv.ParseFloat(data[4], 64) 117 | scn.objectList = append(scn.objectList, Plane{mat, ParseVector(data[1:4]), dis}) 118 | case "light": 119 | light := Light{ParseVector(data[1:4]), ParseColor(data[4:7]), data[0]} 120 | scn.lightList = append(scn.lightList, light) 121 | case "material": 122 | mat := ParseMaterial(data) 123 | scn.materialList = append(scn.materialList, mat) 124 | } 125 | line, isPrefix, err = r.ReadLine() 126 | } 127 | 128 | if isPrefix { 129 | panic(errors.New("buffer size to small")) 130 | } 131 | 132 | if err != io.EOF { 133 | panic(err) 134 | } 135 | 136 | if scn.cameraUp == (Vector{}) { 137 | scn.cameraUp = scn.cameraLook.Cross(Vector{0.0, 0.0, 1.0}).Cross(scn.cameraLook) 138 | } 139 | 140 | scn.image = image.NewRGBA(image.Rect(0, 0, scn.imgWidth, scn.imgHeight)) 141 | 142 | scn.gridWidth = scn.imgWidth * scn.oversampling 143 | scn.gridHeight = scn.imgHeight * scn.oversampling 144 | 145 | scn.look = scn.cameraLook.Sub(scn.cameraPos) 146 | scn.Vhor = scn.look.Cross(scn.cameraUp) 147 | scn.Vhor = scn.Vhor.Normalize() 148 | 149 | scn.Vver = scn.look.Cross(scn.Vhor) 150 | scn.Vver = scn.Vver.Normalize() 151 | 152 | fl := float64(scn.gridWidth) / (2 * math.Tan((0.5*scn.visionField)*PI_180)) 153 | 154 | Vp := scn.look.Normalize() 155 | 156 | Vp.x = Vp.x*fl - 0.5*(float64(scn.gridWidth)*scn.Vhor.x+float64(scn.gridHeight)*scn.Vver.x) 157 | Vp.y = Vp.y*fl - 0.5*(float64(scn.gridWidth)*scn.Vhor.y+float64(scn.gridHeight)*scn.Vver.y) 158 | Vp.z = Vp.z*fl - 0.5*(float64(scn.gridWidth)*scn.Vhor.z+float64(scn.gridHeight)*scn.Vver.z) 159 | 160 | scn.Vp = Vp 161 | 162 | return scn 163 | } 164 | 165 | // Auxiliary Methods 166 | func ParseVector(line []string) Vector { 167 | x, _ := strconv.ParseFloat(line[0], 64) 168 | y, _ := strconv.ParseFloat(line[1], 64) 169 | z, _ := strconv.ParseFloat(line[2], 64) 170 | return Vector{x, y, z} 171 | } 172 | 173 | func ParseColor(line []string) Color { 174 | r, _ := strconv.ParseFloat(line[0], 64) 175 | g, _ := strconv.ParseFloat(line[1], 64) 176 | b, _ := strconv.ParseFloat(line[2], 64) 177 | return Color{r, g, b} 178 | } 179 | 180 | func ParseMaterial(line []string) Material { 181 | var f [6]float64 182 | for i, item := range line[3:] { 183 | f[i], _ = strconv.ParseFloat(item, 64) 184 | } 185 | return Material{ParseColor(line[0:3]), f[0], f[1], f[2], f[3], f[4], f[5]} 186 | } 187 | -------------------------------------------------------------------------------- /vector.go: -------------------------------------------------------------------------------- 1 | // vector.go 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "math" 7 | ) 8 | 9 | type Vector struct { 10 | x, y, z float64 11 | } 12 | 13 | // v.Dot(u) -> float64 14 | func (v Vector) Dot(u Vector) float64 { 15 | return (v.x*u.x + v.y*u.y + v.z*u.z) 16 | } 17 | 18 | func (v Vector) Cross(u Vector) (r Vector) { 19 | r.x = u.y*v.z - u.z*v.y 20 | r.y = u.z*v.z - u.x*v.z 21 | r.z = u.x*v.y - u.y*v.x 22 | return 23 | } 24 | 25 | func (v Vector) Module() float64 { 26 | return math.Sqrt(v.x*v.x + v.y*v.y + v.z*v.z) 27 | } 28 | 29 | func (v Vector) Normalize() Vector { 30 | if m := v.Module(); m != 0.0 { 31 | return Vector{v.x / m, v.y / m, v.z / m} 32 | } 33 | return v 34 | } 35 | 36 | func (v Vector) Add(u Vector) Vector { 37 | return Vector{v.x + u.x, v.y + u.y, v.z + u.z} 38 | } 39 | 40 | func (v Vector) Sub(u Vector) Vector { 41 | return Vector{v.x - u.x, v.y - u.y, v.z - u.z} 42 | } 43 | 44 | func (v Vector) Mul(u float64) Vector { 45 | return Vector{v.x * u, v.y * u, v.z * u} 46 | } 47 | 48 | func (v Vector) String() string { 49 | return fmt.Sprintf("", v.x, v.y, v.z) 50 | } 51 | --------------------------------------------------------------------------------