141 lines
No EOL
12 KiB
Markdown
141 lines
No EOL
12 KiB
Markdown
# RaytracerGO
|
|
|
|
A raytracer and renderer writen in Go (Golang) following the book [Raytracing in one weekend](https://raytracing.github.io/books/RayTracingInOneWeekend.html#overview)
|
|
|
|
## Motivation
|
|
|
|
I've been meaning to tinker with graphics for quite a while and was motivated by a video programming a raytracing algorithm on a TI-84. Back in my school days I used to love to tinker with the TI-84 and also did some very elementary programming on it myself.
|
|
|
|
Video that started it all:
|
|
[Raytracing on a Graphing Calculator (again)](https://youtu.be/rY413t5fArw)
|
|
|
|
<img src="https://www.techeblog.com/wp-content/uploads/2022/12/ray-tracing-ti-84-plus-ce-graphing-calculator.jpg" alt="TI-84 Raytracing" width="720"/>
|
|
|
|
## Why Go?
|
|
|
|
The story starts with Zig. A few colleagues of mine and I tried to work on a Guake-like browser using Zig. We chose Zig because it's C-like, modern and because their mascot is a lizard, of course. But I don't like Zig all that much, and right now Go has caught my attention.
|
|
|
|
I like the language a lot, it's still very C-like, doesn't have classes :), is open-source and resembles Python a lot, while still being fast and having crazy fast compiling speeds (which is something I've started to appreciate since dabbling with Linux From Scratch). The only draw back of Go is that it is a little bit slower than C++, but I just want to experience what the Python-version of C is like :)
|
|
|
|
<img src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fprogramaenlinea.net%2Fwp-content%2Fuploads%2F2020%2F04%2Fgolang-1024x516.png&f=1&nofb=1&ipt=363a10979aa43c8925c481b76c8a2b03a50ce948b3548b40a29432caf8a534d1&ipo=images" alt="Go-Programming" width="720"/>
|
|
|
|
## Taglibro
|
|
### Chapter 1: The PPM-image format
|
|
|
|
<img src="https://raytracing.github.io/images/img-1.01-first-ppm-image.png" alt="First PPM image" width="480"/>
|
|
|
|
Yay this is the first image I've ever generated without using any external imported library ^^
|
|
|
|
### Chapter 2: Vector operations: Ditching OOP for GO
|
|
|
|
Implemented the vec3 Class and colour utility functions without classes. Struktoj estas sufiĉa.
|
|
|
|
### Chapter 3: Implementing the camera, rays and a background
|
|
|
|
To understand computer graphics better I will try to describe some important definitions with my own words:
|
|
- Rays: A ray is a 3D-vector with an origin pointing with some length in one direction (P(x)=O+x*b, where O is the origin and b is a vector pointing in one direction)
|
|
- Viewport: Is essentially a window (typically the user window) through which we see the 3D environment. Adjusting the viewport allows you to zoom in, pan around, or focus on specific areas of your scene.
|
|
- Camera: Is essentially a mathematical function (a virtual eye of sorts) that captures rays that originate from its position and pass through the viewport and generates an image that the user can see.
|
|
|
|
By the end of this chapter I generated the following background:
|
|
|
|
<img src="https://raytracing.github.io/images/img-1.02-blue-to-white.png" alt="Second PPM image" width="480"/>
|
|
|
|
### Chapter 4: Creating a sphere
|
|
|
|
In this chapter I created a simple sphere and detect its surface by solving quadratic equations, which represent vectors. The sphere has no shading yet:
|
|
|
|
<img src="https://raytracing.github.io/images/img-1.03-red-sphere.png" alt="Second PPM image" width="480"/>
|
|
|
|
### Chapter 5: Surface normals
|
|
|
|
The first part of the chapter was the calculation and visualization of the normals, which we need for shading. But I also implemented the [fast inverse square algorithm](https://youtu.be/p8u_k2LIZyo) in Go just like in Quake III, which is pretty clever and fast. I did primarily because 1) I'm working with 32-bit floats for the sake of memory efficiency and 2) I don't care about accuracy - also it's very educational. But [as explained in this video](https://youtu.be/tmb6bLbxd08) the fast inverse square isn't always the better choice and in my case it probably isn't, because I need to import a Go standard library to disable data type safety and processor padding (something I've noticed when working with small data types in Zig). This is the first visualization of the normals of the sphere:
|
|
|
|
<img src="https://raytracing.github.io/images/img-1.04-normals-sphere.png" alt="Second PPM image" width="480"/>
|
|
|
|
The second part of the capter involved standardizing the code so that it can process multiple objects - it was quite translating this part into Go, as a lot of C++ OOP features are missing - but finally I managed to produce this neat output:
|
|
|
|
<img src="https://raytracing.github.io/images/img-1.05-normals-sphere-ground.png" alt="Second PPM image" width="480"/>
|
|
|
|
At the end I also added an intervals struct, to further simplify the code.
|
|
|
|
### Chapter 6: The Camera struct
|
|
|
|
This chapter was yet another simplification of the existing code - the viewport and the camera were moved out of main and got their own struct. The camera struct and the render function have two tasks:
|
|
1) Construct and dispatch rays into the world and
|
|
2) Use the results of these rays to construct the rendered image
|
|
|
|
### Chapter 7: Anti-Aliasing
|
|
|
|
The images rendered thus far have jagged edges and this is known as aliasing. To soften the edges each pixel gets sampled multiple times and the colour gets averaged. I've spend quite some time on this chapter due to a veeeery small bug in the code. This chapter produces a similar but softer image as before. Here an comparison:
|
|
|
|
<img src="https://raytracing.github.io/images/img-1.06-antialias-before-after.png" alt="Second PPM image" width="480"/>
|
|
|
|
This is also the first time the code starts to take some time to execute.
|
|
|
|
### Chapter 8: Diffuse objects
|
|
|
|
A diffuse object is any object that is not emitting light, takes on the colours of the environment and reflects light in all directions.
|
|
|
|
<img src="https://upload.wikimedia.org/wikipedia/commons/b/bd/Lambert2.gif" width="480"/>
|
|
|
|
I generated random reflection vectors and got the following (this is the first time a shadow can be observed, left one is mine, right one from the book - note the stripe is probably a consequence of fast inverse square and of using 32-bit floats instead of 64-bit ones):
|
|
|
|
<img src="https://preview.redd.it/some-projects-ive-been-working-on-3-v0-xondbx2r1emc1.png?width=1080&crop=smart&auto=webp&s=2cd684d53024c5756c7459e3762723faefb00182" width="480"/><img src="https://raytracing.github.io/images/img-1.07-first-diffuse.png" width="480"/>
|
|
|
|
A problem that occurs on this image is the **shadow acne** problem: A ray will attempt to accurately calculate the intersection point when it intersects with a surface. Unfortunately for us, this calculation is susceptible to floating point rounding errors which can cause the intersection point to be ever so slightly off. This means that the origin of the next ray, the ray that is randomly scattered off of the surface, is unlikely to be perfectly flush with the surface. It might be just above the surface. It might be just below the surface. If the ray's origin is just below the surface then it could intersect with that surface again. Which means that it will find the nearest surface at t=0.00000001 or whatever floating point approximation the hit function gives us. The simple fix yields this image:
|
|
|
|
<img src="https://raytracing.github.io/images/img-1.09-no-acne.png" width="480"/>
|
|
|
|
To make the shadows look more realistic, I had to implement the Lambertian reflections: basically, most of the reflected rays will be close to the sphere normal and the probability density is dependent on cos(phi), where phi is the angle from the normal. It was very easy to implement and yields the following:
|
|
|
|
<img src="https://raytracing.github.io/images/img-1.10-correct-lambertian.png" width="480"/>
|
|
|
|
The last issue left in the chapter is the darkness of the image and it's due to some strange linear-gamma space conversion? I don't quite understand how that works, but by taking the square root of the colour values we get the right brightness. Nice
|
|
|
|
<img src="https://preview.redd.it/some-projects-ive-been-working-on-3-v0-a4a1203r1emc1.png?width=914&format=png&auto=webp&s=f14960bbbb7bc99d9209b6a84c1a9a7c99cd4c9f" width="480"/>
|
|
|
|
### Chapter 8: The Materials struct and Metals
|
|
|
|
In this chapter I made a special struct that saves the properties of the material (its fuzz and albedo) and checks whether it is lambertian or metallic. Working without OOP gets a bit awkward here and I had some problems with debugging (due to pointers). But reflections on metals really do look nice:
|
|
|
|
<img src="https://raytracing.github.io/images/img-1.13-metal-shiny.png" width="480"/>
|
|
|
|
Most metals however aren't purely reflective, they fuzz the reflection a little bit - so they are actually something inbetween a lambertian object and a mirror. Accounting for this gives the following neat image:
|
|
|
|
<img src="https://raytracing.github.io/images/img-1.14-metal-fuzz.png" width="480"/>
|
|
|
|
### Chapter 9: Glass and Slowness
|
|
|
|
In this chapter glass and other dielectric materials have been implemented using Snell's law which describes refraction. The first attempt yields the following rendering:
|
|
|
|
<img src="https://raytracing.github.io/images/img-1.16-glass-always-refract.png" width="480"/>
|
|
|
|
In the later part I implemented Schlick's Approximation which approximates the reflectivity of dielectric objects depending on the angle.
|
|
|
|
Here I got some strange result (actually I already noticed that the previous render had a strange cyan sheen on the upper part of the ball, but dismissed it as a result of inaccuracies).
|
|
|
|
I oddly enough got this wrong result, when trying to render the hollow ball:
|
|
<img src="https://preview.redd.it/some-projects-ive-been-working-on-3-v0-7biqlfxt1emc1.png?width=570&format=png&auto=webp&s=7931c97310e94b91b0ca363d7ce83d5bfeb7524f" width="480"/>
|
|
|
|
It took me some time to find the error and believe it or not, it was the 3% inaccuracy caused by the fast inverse square algorithm! I had to remove it and replace it with a more simple and accurate but slower calculation. The slow down is small but visible! Now I get:
|
|
|
|
<img src="https://raytracing.github.io/images/img-1.18-glass-hollow.png" width="480"/>
|
|
|
|
### Chapters 10 and 11: Extra camera utilities and depth of field (blur)
|
|
|
|
I implemented the camera to be movable and also simulated the lens blur effect we see in cameras. The blur effect can be emulated by implemented the following six steps:
|
|
1. The focus plane is orthogonal to the camera view direction.
|
|
2. The focus distance is the distance between the camera center and the focus plane.
|
|
3. The viewport lies on the focus plane, centered on the camera view direction vector.
|
|
4. The grid of pixel locations lies inside the viewport (located in the 3D world).
|
|
5. Random image sample locations are chosen from the region around the current pixel location.
|
|
6. The camera fires rays from random points on the lens through the current image sample location. I get the following output:
|
|
|
|
<img src="https://preview.redd.it/more-progress-3-v0-w1iblg591dnc1.png?width=575&format=png&auto=webp&s=68dbbcdf9ae54d625664019f6bd7189367de7be9" width="480"/>
|
|
|
|
### Project complete!
|
|
|
|
Rendering the final image took quite a bit of time, but I'm glad the whole program finally works. I had to spend quite a lot of time to try to fix the glass - I still don't know exactly what's gone wrong, but to fix the issue I simply decided to (vertically) flip the reflected image of glass balls which magically fixed the code (the glass-balls are rendered correctly but their reflections are of course flipped, though in a way they appear more natural this way). This is the final render of my project (I translated the final scene from the guide into Go):
|
|
|
|
<img src="https://preview.redd.it/final-result-v0-t6z7744lfjnc1.png?width=640&crop=smart&auto=webp&s=44dd44ceedd87f6fa84ab2ccaf9390163bfaf23b" width="480"/> |