Skip to content

Commit 70c251a

Browse files
Triangles (#51)
Fixes #50 Added triangle primitive, and some new benchmarks.
1 parent 9822d02 commit 70c251a

File tree

15 files changed

+464
-90
lines changed

15 files changed

+464
-90
lines changed

README.md

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ Raytracer written in Go, based on the book [The Ray Tracer Challenge by Jamis Bu
1919
- ✅ Chapter 13: [Cylinders and Cones](https://user-images.githubusercontent.com/40322086/108651820-98a51380-7490-11eb-8519-c72a496c025c.png)
2020
- ✅ Chapter 14: [Groups](https://user-images.githubusercontent.com/40322086/110737622-b5b14480-81fb-11eb-8b70-ff4517a84bac.png)
2121
- ✅ Chapter 14.5: [Bounding Boxes](https://user-images.githubusercontent.com/40322086/112742776-6e4ae800-8f5f-11eb-8a4e-66a5d145fc3f.png)
22-
- Chapter 15: Triangles
22+
- Chapter 15: [Triangles](https://user-images.githubusercontent.com/40322086/113527126-8be50680-958a-11eb-8521-6a738a7189c3.png)
23+
- Chapter 15.5: OBJ files
2324
- Chapter 16: Constructive Solid Geometry
2425

2526
## Latest Render
@@ -28,22 +29,28 @@ Raytracer written in Go, based on the book [The Ray Tracer Challenge by Jamis Bu
2829

2930
## Benchmark stats
3031

31-
### Benchmarks of different bounding box setups with 4096 spheres
32+
### Benchmarks of primitives calculation
3233

33-
pkg: github.com/factorion/graytracer/pkg/components
34+
pkg: github.com/factorion/graytracer/pkg/primitives
3435
cpu: AMD Ryzen 7 2700 Eight-Core Processor
3536
| Function | Iterations | Speed | Memory | Allocations |
36-
| -------- | ---------- | ----- | ------ | ----------- |
37-
| BenchmarkNoBoundingBoxes-16 | 1711 | 614977 ns/op | 5960 B/op | 113 allocs/op |
38-
| Benchmark8BoundingBoxes-16 | 10000 | 117799 ns/op | 7336 B/op | 124 allocs/op |
39-
| Benchmark64BoundingBoxes-16 | 32006 | 37650 ns/op | 7432 B/op | 129 allocs/op |
37+
| -------- | ---------: | ----: | -----: | ----------: |
38+
| BenchmarkSubmatrix4x4-16 | 5329711 | 223.5 ns/op | 152 B/op | 4 allocs/op |
39+
| BenchmarkMatrixMultiply-16 | 3388286 | 357.6 ns/op | 224 B/op | 5 allocs/op |
40+
| BenchmarkMatrixDeterminant4x4-16 | 387013 | 3099 ns/op | 1568 B/op | 52 allocs/op |
41+
| BenchmarkMatrixMinor3x3-16 | 7517265 | 160.8 ns/op | 80 B/op | 3 allocs/op |
42+
| BenchmarkMatrixCofactor3x3-16 | 7162288 | 168.5 ns/op | 80 B/op | 3 allocs/op |
43+
| BenchmarkMatrixInverse4x4-16 | 74745 | 16077 ns/op | 8064 B/op | 265 allocs/op |
44+
| BenchmarkPVTransform-16 | 131638082 | 9.265 ns/op | 0 B/op | 0 allocs/op |
45+
| BenchmarkPVReflect-16 | 178992032 | 6.959 ns/op | 0 B/op | 0 allocs/op |
46+
| BenchmarkRayTransform-16 | 37492969 | 32.03 ns/op | 0 B/op | 0 allocs/op |
4047

4148
### Benchmarks of basic shape calculations
4249

4350
pkg: github.com/factorion/graytracer/pkg/shapes
44-
cpu: AMD Ryzen 7 2700 Eight-Core Processor
51+
cpu: AMD Ryzen 7 2700 Eight-Core Processor
4552
| Function | Iterations | Speed | Memory | Allocations |
46-
| -------- | ---------- | ----- | ------ | ----------- |
53+
| -------- | ---------: | ----: | -----: | ----------: |
4754
| BenchmarkConeIntersection-16 | 5701870 | 207.7 ns/op | 72 B/op | 2 allocs/op |
4855
| BenchmarkConeNormal-16 | 3989524 | 305.8 ns/op | 224 B/op | 5 allocs/op |
4956
| BenchmarkCubeIntersection-16 | 9322422 | 130.6 ns/op | 48 B/op | 1 allocs/op |
@@ -54,3 +61,15 @@ cpu: AMD Ryzen 7 2700 Eight-Core Processor
5461
| BenchmarkPlaneNormal-16 | 4438746 | 273.1 ns/op | 224 B/op | 5 allocs/op |
5562
| BenchmarkSphereIntersection-16 | 6149660 | 200.8 ns/op | 72 B/op | 2 allocs/op |
5663
| BenchmarkSphereNormal-16 | 3968947 | 300.2 ns/op | 224 B/op | 5 allocs/op |
64+
| BenchmarkTriangleIntersection-16 | 11317957 | 107.5 ns/op | 24 B/op | 1 allocs/op |
65+
| BenchmarkTriangleNormal-16 | 4596672 | 266.7 ns/op | 224 B/op | 5 allocs/op |
66+
67+
### Benchmarks of different bounding box setups with 4096 spheres
68+
69+
pkg: github.com/factorion/graytracer/pkg/components
70+
cpu: AMD Ryzen 7 2700 Eight-Core Processor
71+
| Function | Iterations | Speed | Memory | Allocations |
72+
| -------- | ---------: | ----: | -----: | ----------: |
73+
| BenchmarkNoBoundingBoxes-16 | 1711 | 614977 ns/op | 5960 B/op | 113 allocs/op |
74+
| Benchmark8BoundingBoxes-16 | 10000 | 117799 ns/op | 7336 B/op | 124 allocs/op |
75+
| Benchmark64BoundingBoxes-16 | 32006 | 37650 ns/op | 7432 B/op | 129 allocs/op |

cmd/graytracer/main.go

Lines changed: 91 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func MakeHex(mat patterns.Material, transform primitives.Matrix) shapes.Shape {
7171
bottom := shapes.MakeCone(true)
7272
bottom.SetMaterial(mat)
7373
bottom.SetTransform(primitives.RotationY(i * math.Pi / 3).Multiply(
74-
primitives.Translation(0, -1, 0).Multiply(
74+
primitives.Translation(0, -1, 0).Multiply(
7575
primitives.RotationX(3 * math.Pi / 4).Multiply(
7676
primitives.Scaling(0.25, math.Sqrt(2), 0.25)))))
7777
hex.AddShape(corner)
@@ -88,13 +88,13 @@ func main() {
8888
var threads int
8989
var fov float64
9090
flag.IntVar(&threads, "threads", runtime.NumCPU(), "Number of threads for rendering")
91-
flag.UintVar(&width, "width", 320, "Width of rendered image")
92-
flag.UintVar(&height, "height", 240, "Height of rendered image")
93-
flag.Float64Var(&fov, "fov", math.Pi/4, "Field of View (in Radians)")
91+
flag.UintVar(&width, "width", 480, "Width of rendered image")
92+
flag.UintVar(&height, "height", 270, "Height of rendered image")
93+
flag.Float64Var(&fov, "fov", math.Pi/3, "Field of View (in Radians)")
9494
flag.Parse()
9595
camera = components.MakeCamera(width, height, fov)
96-
camera.ViewTransform(primitives.MakePoint(-20, 50, -35),
97-
primitives.MakePoint(15, 10, 15),
96+
camera.ViewTransform(primitives.MakePoint(0, 1.5, -4.5),
97+
primitives.MakePoint(0, 1, 0),
9898
primitives.MakeVector(0, 1, 0))
9999
ch = make(chan XY, 1000)
100100
imgMutex = &sync.Mutex{}
@@ -104,25 +104,93 @@ func main() {
104104
img = image.NewRGBA(image.Rectangle{upLeft, lowRight})
105105
world = components.MakeWorld()
106106
world.SetBackground(*patterns.MakeRGB(0.9, 0.9, 0.9))
107-
for x := 0.0; x < 10; x++ {
108-
for y := 0.0; y < 10; y++ {
109-
for z := 0.0; z < 10; z++ {
110-
mat := patterns.Material{Pat: patterns.MakeRGB(x * 0.1, y * 0.1, z * 0.1),
111-
Ambient: 0.1, Diffuse: 0.7, Specular: 0.5,
112-
Shininess: 200, Reflective: 0.1, Transparency: 0, RefractiveIndex: 0}
113-
hex := MakeHex(mat, primitives.Translation(x * 4, y * 3, z * 4))
114-
world.AddObject(hex)
115-
}
116-
}
107+
d4 := shapes.MakeGroup()
108+
d4.SetTransform(primitives.Translation(1.75, 0.5, 0.5).Multiply(
109+
primitives.RotationY(math.Pi / 2).Multiply(
110+
primitives.Translation(0, 1.0 / 3.0, 0))))
111+
d4_mat := patterns.Material{Pat: patterns.MakeRGB(0.8, 0, 0.8), Ambient: 0.1, Diffuse: 0.7, Specular: 0.5,
112+
Shininess: 200, Reflective: 0.1, Transparency: 0, RefractiveIndex: 0}
113+
d4_points := []primitives.PV{primitives.MakePoint(0, -1.0 / 3.0, math.Sqrt(8.0 / 9.0)),
114+
primitives.MakePoint(math.Sqrt(2.0 / 3.0), -1.0 / 3.0, -math.Sqrt(2.0 / 9.0)),
115+
primitives.MakePoint(-math.Sqrt(2.0 / 3.0), -1.0 / 3.0, -math.Sqrt(2.0 / 9.0)),
116+
primitives.MakePoint(0, 1, 0)}
117+
d4_triangles := []*shapes.Triangle{shapes.MakeTriangle(d4_points[0], d4_points[1], d4_points[2]),
118+
shapes.MakeTriangle(d4_points[0], d4_points[1], d4_points[3]),
119+
shapes.MakeTriangle(d4_points[0], d4_points[2], d4_points[3]),
120+
shapes.MakeTriangle(d4_points[1], d4_points[2], d4_points[3])}
121+
for _, triangle := range d4_triangles {
122+
triangle.SetMaterial(d4_mat)
123+
d4.AddShape(triangle)
117124
}
118-
119-
// Render time without bounding boxes
120-
// Render finished : 1m34.8119446s
121-
// Render time with bounding boxes
122-
// Render finished : 2.4859576s
123-
125+
world.AddObject(d4)
126+
d8 := shapes.MakeGroup()
127+
d8.SetTransform(primitives.Translation(0, 1, 0.5).Multiply(
128+
primitives.Scaling(0.75, 0.75, 0.75)))
129+
d8_mat := patterns.Material{Pat: patterns.MakeRGB(0, 0.8, 0), Ambient: 0.1, Diffuse: 0.7, Specular: 0.5,
130+
Shininess: 200, Reflective: 0.1, Transparency: 0, RefractiveIndex: 0}
131+
d8_points := []primitives.PV{primitives.MakePoint(0, 1, 0),
132+
primitives.MakePoint(1, 0, 0),
133+
primitives.MakePoint(0, 0, 1),
134+
primitives.MakePoint(-1, 0, 0),
135+
primitives.MakePoint(0, 0, -1),
136+
primitives.MakePoint(0, -1, 0)}
137+
d8_triangles := []*shapes.Triangle{shapes.MakeTriangle(d8_points[0], d8_points[1], d8_points[2]),
138+
shapes.MakeTriangle(d8_points[0], d8_points[2], d8_points[3]),
139+
shapes.MakeTriangle(d8_points[0], d8_points[3], d8_points[4]),
140+
shapes.MakeTriangle(d8_points[0], d8_points[4], d8_points[1]),
141+
shapes.MakeTriangle(d8_points[5], d8_points[1], d8_points[2]),
142+
shapes.MakeTriangle(d8_points[5], d8_points[2], d8_points[3]),
143+
shapes.MakeTriangle(d8_points[5], d8_points[3], d8_points[4]),
144+
shapes.MakeTriangle(d8_points[5], d8_points[4], d8_points[1])}
145+
for _, triangle := range d8_triangles {
146+
triangle.SetMaterial(d8_mat)
147+
d8.AddShape(triangle)
148+
}
149+
world.AddObject(d8)
150+
d20 := shapes.MakeGroup()
151+
d20.SetTransform(primitives.Translation(-2, 1, 2).Multiply(
152+
primitives.Scaling(0.5, 0.5, 0.5)))
153+
d20_mat := patterns.Material{Pat: patterns.MakeRGB(0.9, 0, 0), Ambient: 0.1, Diffuse: 0.7, Specular: 0.5,
154+
Shininess: 200, Reflective: 0.1, Transparency: 0, RefractiveIndex: 0}
155+
d20_points := []primitives.PV{primitives.MakePoint(0, -1, -math.Phi), // 0
156+
primitives.MakePoint(0, -1, math.Phi), // 1
157+
primitives.MakePoint(0, 1, -math.Phi), // 2
158+
primitives.MakePoint(0, 1, math.Phi), // 3
159+
primitives.MakePoint(-1, -math.Phi, 0), // 4
160+
primitives.MakePoint(-1, math.Phi, 0), // 5
161+
primitives.MakePoint(1, -math.Phi, 0), // 6
162+
primitives.MakePoint(1, math.Phi, 0), // 7
163+
primitives.MakePoint(-math.Phi, 0, -1), // 8
164+
primitives.MakePoint(math.Phi, 0, -1), // 9
165+
primitives.MakePoint(-math.Phi, 0, 1), // 10
166+
primitives.MakePoint(math.Phi, 0, 1)} // 11
167+
d20_triangles := []*shapes.Triangle{shapes.MakeTriangle(d20_points[0], d20_points[2], d20_points[8]), // front
168+
shapes.MakeTriangle(d20_points[0], d20_points[2], d20_points[9]),
169+
shapes.MakeTriangle(d20_points[1], d20_points[3], d20_points[10]), // back
170+
shapes.MakeTriangle(d20_points[1], d20_points[3], d20_points[11]),
171+
shapes.MakeTriangle(d20_points[9], d20_points[11], d20_points[6]), // right
172+
shapes.MakeTriangle(d20_points[9], d20_points[11], d20_points[7]),
173+
shapes.MakeTriangle(d20_points[8], d20_points[10], d20_points[4]), // left
174+
shapes.MakeTriangle(d20_points[8], d20_points[10], d20_points[5]),
175+
shapes.MakeTriangle(d20_points[4], d20_points[6], d20_points[0]), // bottom
176+
shapes.MakeTriangle(d20_points[4], d20_points[6], d20_points[1]),
177+
shapes.MakeTriangle(d20_points[4], d20_points[8], d20_points[0]), // bottom split
178+
shapes.MakeTriangle(d20_points[4], d20_points[10], d20_points[1]),
179+
shapes.MakeTriangle(d20_points[6], d20_points[9], d20_points[0]),
180+
shapes.MakeTriangle(d20_points[6], d20_points[11], d20_points[1]),
181+
shapes.MakeTriangle(d20_points[5], d20_points[7], d20_points[2]), // top
182+
shapes.MakeTriangle(d20_points[5], d20_points[7], d20_points[3]),
183+
shapes.MakeTriangle(d20_points[5], d20_points[8], d20_points[2]), //top split
184+
shapes.MakeTriangle(d20_points[5], d20_points[10], d20_points[3]),
185+
shapes.MakeTriangle(d20_points[7], d20_points[9], d20_points[2]),
186+
shapes.MakeTriangle(d20_points[7], d20_points[11], d20_points[3])}
187+
for _, triangle := range d20_triangles {
188+
triangle.SetMaterial(d20_mat)
189+
d20.AddShape(triangle)
190+
}
191+
world.AddObject(d20)
124192
light := components.PointLight{Intensity: patterns.MakeRGB(1, 1, 1),
125-
Position: primitives.MakePoint(-30, 60, -30)}
193+
Position: primitives.MakePoint(-1, 0.5, -5)}
126194
world.AddLight(light)
127195
fmt.Println("Creating goroutines")
128196
wg.Add(threads)

image.png

Lines changed: 2 additions & 2 deletions
Loading

pkg/primitives/matrix.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ func (m Matrix) Cofactor(row, column uint8) float64 {
130130
func (m Matrix) Inverse() (Matrix, error) {
131131
determinant := m.Determinant()
132132
if determinant == 0 {
133-
return nil, errors.New("Not invertible")
133+
return nil, errors.New("not invertible")
134134
}
135135
size := uint8(len(m))
136136
inverse := MakeMatrix(size)

pkg/primitives/matrix_test.go

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func TestMatrixEquals(t *testing.T) {
4747
}
4848
}
4949

50-
func TestSubmatrix(t * testing.T) {
50+
func TestSubmatrix(t *testing.T) {
5151
tables := []struct {
5252
matrix primitives.Matrix
5353
row uint8
@@ -67,6 +67,14 @@ func TestSubmatrix(t * testing.T) {
6767
}
6868
}
6969

70+
func BenchmarkSubmatrix4x4(b *testing.B) {
71+
matrix := primitives.Matrix{{-6, 1, 1, 6}, {-8, 5, 8, 6}, {-1, 0, 8, 2}, {-7, 1, -1, 1}}
72+
b.ResetTimer()
73+
for i := 0; i < b.N; i++ {
74+
matrix.Submatrix(2, 1)
75+
}
76+
}
77+
7078
func TestMatrixMultiply(t *testing.T) {
7179
tables := []struct {
7280
matrix1, matrix2, product primitives.Matrix
@@ -85,6 +93,15 @@ func TestMatrixMultiply(t *testing.T) {
8593
}
8694
}
8795

96+
func BenchmarkMatrixMultiply(b *testing.B) {
97+
matrix1 := primitives.Matrix{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 8, 7, 6}, {5, 4, 3, 2}}
98+
matrix2 := primitives.Matrix{{-2, 1, 2, 3}, {3, 2, 1, -1}, {4, 3, 6, 5}, {1, 2, 7, 8}}
99+
b.ResetTimer()
100+
for i := 0; i < b.N; i++ {
101+
matrix1.Multiply(matrix2)
102+
}
103+
}
104+
88105
func TestMatrixTranspose(t *testing.T) {
89106
tables := []struct {
90107
matrix1, transpose primitives.Matrix
@@ -119,6 +136,14 @@ func TestMatrixDeterminant(t *testing.T) {
119136
}
120137
}
121138

139+
func BenchmarkMatrixDeterminant4x4(b *testing.B) {
140+
matrix := primitives.Matrix{{-2, -8, 3, 5}, {-3, 1, 7, 3}, {1, 2, -9, 6}, {-6, 7, 7, -9}}
141+
b.ResetTimer()
142+
for i := 0; i < b.N; i++ {
143+
matrix.Determinant()
144+
}
145+
}
146+
122147
func TestMatrixMinor(t *testing.T) {
123148
tables := []struct {
124149
matrix1 primitives.Matrix
@@ -136,6 +161,14 @@ func TestMatrixMinor(t *testing.T) {
136161
}
137162
}
138163

164+
func BenchmarkMatrixMinor3x3(b *testing.B) {
165+
matrix := primitives.Matrix{{3, 5, 0}, {2, -1, -7}, {6, -1, 5}}
166+
b.ResetTimer()
167+
for i := 0; i < b.N; i++ {
168+
matrix.Minor(1, 0)
169+
}
170+
}
171+
139172
func TestMatrixCofactor(t *testing.T) {
140173
tables := []struct {
141174
matrix1 primitives.Matrix
@@ -153,6 +186,14 @@ func TestMatrixCofactor(t *testing.T) {
153186
}
154187
}
155188

189+
func BenchmarkMatrixCofactor3x3(b *testing.B) {
190+
matrix := primitives.Matrix{{3, 5, 0}, {2, -1, -7}, {6, -1, 5}}
191+
b.ResetTimer()
192+
for i := 0; i < b.N; i++ {
193+
matrix.Cofactor(0, 0)
194+
}
195+
}
196+
156197
func TestNonInvertibleMatrix(t *testing.T) {
157198
tables := []struct {
158199
matrix1 primitives.Matrix
@@ -196,7 +237,14 @@ func TestMatrixInverse(t *testing.T) {
196237
}
197238
}
198239

199-
//Test
240+
func BenchmarkMatrixInverse4x4(b *testing.B) {
241+
matrix := primitives.Matrix{{-5, 2, 6, -8}, {1, -5, 1, 8}, {7, 7, -6, -7}, {1, -3, 7, 4}}
242+
b.ResetTimer()
243+
for i := 0; i < b.N; i++ {
244+
matrix.Inverse()
245+
}
246+
}
247+
200248
func TestMatrixProcess(t *testing.T) {
201249
tables := []struct {
202250
matrix1, matrix2 primitives.Matrix
@@ -216,4 +264,4 @@ func TestMatrixProcess(t *testing.T) {
216264
t.Errorf("Expected %v, got %v", table.matrix1, matrix1)
217265
}
218266
}
219-
}
267+
}

pkg/primitives/pv_test.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,15 @@ func TestPVTransform(t *testing.T) {
8080
}
8181
}
8282

83+
func BenchmarkPVTransform(b *testing.B) {
84+
point := primitives.MakePoint(1, 2, 3)
85+
transform := primitives.Translation(5, 6, 7)
86+
b.ResetTimer()
87+
for i := 0; i < b.N; i++ {
88+
point.Transform(transform)
89+
}
90+
}
91+
8392
func TestPVNegate(t *testing.T) {
8493
tables := []struct {
8594
v, n primitives.PV
@@ -175,7 +184,7 @@ func TestCrossProduct(t *testing.T) {
175184
}
176185
}
177186

178-
func TestReflect(t *testing.T) {
187+
func TestPVReflect(t *testing.T) {
179188
tables := []struct {
180189
start, normal, reflect primitives.PV
181190
}{
@@ -189,3 +198,12 @@ func TestReflect(t *testing.T) {
189198
}
190199
}
191200
}
201+
202+
func BenchmarkPVReflect(b *testing.B) {
203+
vector := primitives.MakeVector(1, -1, 0)
204+
normal := primitives.MakeVector(0, 1, 0)
205+
b.ResetTimer()
206+
for i := 0; i < b.N; i++ {
207+
vector.Reflect(normal)
208+
}
209+
}

pkg/primitives/ray_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,12 @@ func TestRayTransform(t *testing.T) {
6969
}
7070
}
7171
}
72+
73+
func BenchmarkRayTransform(b *testing.B) {
74+
ray := primitives.Ray{primitives.MakePoint(1, 2, 3), primitives.MakeVector(0, 1, 0)}
75+
transform := primitives.Translation(3, 4, 5)
76+
b.ResetTimer()
77+
for i := 0; i < b.N; i++ {
78+
ray.Transform(transform)
79+
}
80+
}

0 commit comments

Comments
 (0)