If you’re totally new to the Ray Trace concept and if you have limited time and also If you wanted to build Ray Tracer with C#, well my friend I hope you’re one of the right places for this. Because there are not so many sources for the build ray tracer with C# (I’m not saying there is any but comparing to the C++ it is very very limited also some of them is very complex for the beginning I think) In this blog you can find my experience about developing Basic Ray Tracer with C# from zero levels. And ın the end you can find the source code for this project (I will be added it when it is finished). When I’m saying zero levels I’m serious. Because until 10 days before I started this project, my knowledge about this area was very limited. Of course, I knew about Nvidia RTX, and besides that, I dived into subjects such as sampling, light bounces, material properties in details about lighting in game engines. However, I had never found myself in a situation where I had to write myself until the camera. Let’s get started then.
First: WHAT IS RAY?
When we think of ray tracing, we can think of the rays as a laser that coming out of superman’s eye. In nature, this process isn’t like that (Rays come to our eyes, not out of our eyes.) but if we want to do it like that we’ll need a lot of power and time. We can’t do this in real-time. (yet, also not useful for many cases) If we calculate the rays in the area we see to achieve the result we want, it is enough for us. At the same time, we don’t waste energy and time to make the entire field. But how we can use (or code) this ray.
In Wikipedia Ray define as:
A light ray is a line (straight or curved) that is perpendicular to the light’s wavefronts; its tangent is collinear with the wave vector. Light rays in homogeneous media are straight. They bend at the interface between two dissimilar media and may be curved in a medium in which the refractive index changes. Geometric optics describes how rays propagate through an optical system. Objects to be imaged are treated as collections of independent point sources, each producing spherical wavefronts and corresponding outward rays. Rays from each object point can be mathematically propagated to locate the corresponding point on the image.
But what is Ray for us? For us, Ray means Vector that has an origin and direction. And if we have the length of the direction we can find the endpoint of that ray and this point is the golden key of ray tracing.
Now we know what is the rays, but how we can code rays? This is where the Vector3 class comes in. We can simply define a Vector3 object as an object that holds values in 3 axes. We can use these objects to do mathematical operations that we would apply to vectors (rays in our example). Dot product and cross product are at the beginning of these operations.
I studied many resources to understand how to write a simple ray tracer in its simplest form. But after a point, the sources I studied confused me. From this point on, I started using Peter Shirley’s book ‘Ray Tracing in One Weekend’ (C++) and my lecture notes as the main source. And that worked.
In my case, I have to read XML files which include scene properties, objects, lights, camera… and create scenes from them. And the first challenge for me was to properly read these XML files and create meaningful data for the scene. Fortunately .Net libraries came to my help. You just need to get used to using it. I have used XmlNodeList, XmlDocument, and their ChildNode constructs to read XML files. (These are the structures you can use when you call System.XML.Linq and System.Xml) One of my target scenes is below.
This scene (also XML files) include these variables:
- Camera Properties (Position, Gaze Vector, Up Vector…)
- Vertex Data
- Objects Data (Points, Material …)
All I need the do is read this variable and create the scene using these variables. Send some ray to calculate color, create objects, create light sources, place a camera. I really wish it was as easy as it is written here. Because the image that I can reach as a result of about a week of intense work is this:
It takes about 600 ms to render this scene right now.
There is no light, no shadow, the camera position, and distance is not correct, and more. But there is some right thing in here: I can create rays, spheres, and materials (at least diffuse). Because in this scene, the rays hit the spheres and manage to correctly rotate the color of the material of that sphere. (There is also ray-object interaction situation we’ll discuss later) But progress is progress after all. Because creating this picture makes you very happy and helps you to put some concepts (and mathematics) about ray tracing in your mind. Well, is it easy after this part? The answer is no, unfortunately. But how did I get so far? I’ll talk about that now.
After the read XML file ı have a bunch of information about the scene. But I have to start with one thing and that thing is calculating the lower-left corner of the image plane. Because we use this point to create and calculate the pixels in the total area one by one. After calculating this we need to send rays and look for the intersection. If there is any ınstersection find the ıntersection object type and return the final color of that point. This is the very short definition of tracing a ray. I start with sending rays and intersection with the spheres. I did this primarily by keeping the information of all the spheres and checking that for each ray I sent that ray intersected any sphere. If there is an intersect, I set the Diffuse Color of that sphere as the color of that pixel. In fact, this is the first step towards building a rail tracer, and most things seem right so far (I know there is a camera position and distance problem but look we have spheres).
It takes about 450 ms to render this scene right now
ANDD FAILS (OF COURSE!)
It takes about 1 seconds to render this scene right now
It takes about 10 seconds to render this scene right now
It takes about 900 ms to render this scene right now
It takes about 14 seconds to render this scene right now
WHAT IS NEEDED?
At that moment our program has some problems but besides that, we need lightning information. They look like they were drawn in paint now, there is no sense of depth. We have to solve this. In addition, rays interact with spheres only now, but it is a strange idea to create the whole 3-dimensional world from spheres, but not everyone may want this. So we have to make the rays we send intersect with the polygons, and we will do this by using the ray-triangle intersection. (Actually, I started this, but I will talk about this part when it’s ready because I haven’t got proper results yet)