The amazing task to implement a Ray Tracer is at hand. Through the whole semester, prof. dr. Selan Rodrigues dos Santos, in his 2019.1's class of Computer Graphics I, will assign incremental projects towards this goal. Hopefully, by the end of it, I earn my degree as padawan in this fine art.
For the very firs Ray Tracer project, we must start with the basics. It consists simply on having a XML scene describer file, with informations regarding only the colors of the background, the size of the camera and the outputing of the resulting image.
Putting in common words, a scene is the description of the image to be generated by the Ray Tracer. It consists of elements such as background, camera, etc.
For this simple project, a scene could have the following format:
<raytracer>
<settings>
<output_file type="PNG" name="images/saida.png"/>
</settings>
<background>
<color r="0" g="0" b="51"/> <!-- bottom left -->
<color r="0" g="255" b="51"/> <!-- top left -->
<color r="255" g="255" b="51"/> <!-- top right -->
<color r="255" g="0" b="51"/> <!-- bottom right -->
</background>
<camera type="orthographic">
<width value="500"/>
<height value="500"/>
</camera>
</raytracer>
There are a few tags in here that must be explained. The first one is <raytracer>
. This is the root of the file and
defines our scope. Everything, for now, must be inside of it.
<settings>
contains informations about what should be done with the generated image. Right now we can only have a
<output_file>
. This will say the type (i.e. PPM or PNG) and the name/path for the image.
<background>
is the real star here. It can have the maximum of four <color>
tags inside it. But hey! No need for you
to define all four colors. If you define only one, the remaining three will have the same value as the one you defined.
If you define two, the remaining colors will have the same value as the last color you provided. This is a rule that
applies to any number of colors provided, but you must provide at least one!
So, basically, if you provide more than one color, you'll have a gradient. If you want your background as a solid color, simply provide one color. For all cases, internally, my Background class implements bilinear interpolation (and that is a pretty dope thing).
<camera>
is the last one. Right now we don't have much complexity on this one, so basically you should just provide
the <width>
and the <height>
values, as described in the scene. This will tell how much of the digital world we will see.
Of course! Let me show you some examples.
The first one is quite pretty, is the resulting image of the scene described above:
Changing the background a litte bit like this:
<background>
<color r="55" g="59" b="68"/> <!-- bottom left -->
<color r="55" g="59" b="68"/> <!-- top left -->
<color r="66" g="134" b="244"/> <!-- top right -->
<color r="66" g="134" b="244"/> <!-- bottom right -->
</background>
Will give us this horizontal gradient:
Now, generating a vertical gradient will look similar to this:
<background>
<color r="16" g="141" b="199"/> <!-- bottom left -->
<color r="239" g="142" b="56"/> <!-- top left -->
<color r="239" g="142" b="56"/> <!-- top right -->
<color r="16" g="141" b="199"/> <!-- bottom right -->
</background>
And we generate this output:
And lastly, if we want a simple solid background, it can be done as this:
<background>
<color r="60" g="59" b="63"/>
</background>
And the output will be:
In the previous project, we've created a bunch of beautiful background images using bilinear interpolation. Now, for the second project, we must implement cameras! Specifically two: the orthographic and the perspective ones.
Sadly, we don't have anything but the console to test them yet. So, after you run the code, you'll see on the prompt a message informing which ray was generated at a certain position.
Its pretty much like in the previous chapter, but with a few additions on the tag . First I'm gonna show you and then I'll explain.
So, suppose you want to write the scene for a perspective camera. Here's a sample scene for you:
<raytracer>
<settings>
<output_file type="PNG" name="sky.png"/>
</settings>
<background>
<color r="153" g="204" b="255"/> <!-- bottom left -->
<color r="18" g="10" b="143"/> <!-- top left -->
<color r="18" g="10" b="143"/> <!-- top right -->
<color r="153" g="204" b="255"/> <!-- bottom right -->
</background>
<camera type="perspective">
<!--- The camera frame -->
<position x="0" y="0" z="0"/> <!--- located at the origin -->
<target x="0" y="0" z="-10"/> <!--- looking down the -Z axis -->
<up x="0" y="1" z="1"/> <!--- the camera's up vector -->
<!--- Specific parameters for perspective -->
<fovy value="45.0" /> <!--- The vertical field of view -->
<aspect value="1.33"/> <!--- Optional parameter, aspect ratio W/H -->
<fdistance value="1" /> <!--- The focal distance -->
<!--- Retina/image dimensions -->
<width value="40"/>
<height value="30"/>
</camera>
</raytracer>
Previously on, we've implemented two types of cameras: perspective and orthogonal. Since we couldn't test them with something other than the console, things were a bit tedious. Now we finally have something (yet simple) to see: red spheres on a blue sky (or any color you want on your sky to be).
Before going further in the description, please read how to describe the cameras, this will give all the previous details you need.
There's only a simple addition, which is the tag <scene>
and the <object>
.
A sample scene for a perspective camera would be:
<raytracer>
<settings>
<output_file type="PNG" name="images/red_spheres.png"/>
</settings>
<background> <!-- This defines an interpolated background -->
<color r="153" g="204" b="255"/> <!-- bottom left -->
<color r="18" g="10" b="143"/> <!-- top left -->
<color r="18" g="10" b="143"/> <!-- top right -->
<color r="153" g="204" b="255"/> <!-- bottom right -->
</background>
<camera type="perspective">
<!--- The camera frame -->
<position x="0" y="0" z="0"/> <!--- located at the origin -->
<target x="0" y="0" z="-10"/> <!--- looking down the -Z axis -->
<up x="0" y="1" z="1"/> <!--- the camera's up vector -->
<!--- Specific parameters for perspective projection -->
<fovy value="65.0" /> <!--- The vertical field of view -->
<aspect value="1.33"/> <!--- Optional parameter, aspect ration W/H -->
<fdistance value="1" /> <!--- The focal distance -->
<!--- Retina/image dimensions -->
<width value="800"/>
<height value="600"/>
</camera>
<scene>
<object type="sphere" name="sphere1">
<radius value="0.4"/>
<center x="-1.0" y="0.5" z="-5"/>
</object>
<object type="sphere" name="sphere2">
<radius value="0.4"/>
<center x="1.0" y="-0.5" z="-8"/>
</object>
<object type="sphere" name="sphere3">
<radius value="0.4"/>
<center x="-1.0" y="-1.5" z="-3.5"/>
</object>
</scene>
</raytracer>
And a sample scene for an orthographic camera would be:
<raytracer>
<settings>
<output_file type="PNG" name="images/red_spheres.png"/>
</settings>
<background> <!-- This defines an interpolated background -->
<color r="153" g="204" b="255"/> <!-- bottom left -->
<color r="18" g="10" b="143"/> <!-- top left -->
<color r="18" g="10" b="143"/> <!-- top right -->
<color r="153" g="204" b="255"/> <!-- bottom right -->
</background>
<camera type="orthographic">
<!--- The camera frame -->
<position x="0" y="0" z="0"/> <!--- located at the origin -->
<target x="0" y="0" z="-10"/> <!--- looking down the -Z axis -->
<up x="0" y="1" z="1"/> <!--- the camera's up vector -->
<!--- Specific parameters for orthographic projection -->
<vpdim l="-3" r="3" b="-2.25" t="2.25" /> <!--- View plane dimensions [left right bottom top] -->
<!--- Retina/image dimensions -->
<width value="800"/>
<height value="600"/>
</camera>
<scene>
<object type="sphere" name="sphere1">
<radius value="0.4"/>
<center x="-1.0" y="0.5" z="-5"/>
</object>
<object type="sphere" name="sphere2">
<radius value="0.4"/>
<center x="1.0" y="-0.5" z="-8"/>
</object>
<object type="sphere" name="sphere3">
<radius value="0.4"/>
<center x="-1.0" y="-1.5" z="-3.5"/>
</object>
</scene>
</raytracer>
Where <scene>
contains all the things (in lack of a proper word) that are going to be rendered in the image.
These things are basically an arbitray (up to you) number of <object>
tags.
An object must have a type
and a name
. For now, there's only implementation for sphere
as type.
Each of the spheres must have a <radius>
with an specif value
attribute and a <center>
, with
x
, y
and z
as attributes.
For now, all the spheres will be painted red.
Of course! Let me show you some examples.
The first one was generated with the sample perspective camera scene, described above:
And this was was generated with the sample orthographic camera scene, also described above:
Feel free to change the scenes and generate different kinds of images.
Moving forwards, we're adding a little bit more dynamicity to our spheres. Now they can have different colors and/or show a simple depth aspect. Also, our objects can have materials, and each material deals differently with rays/lights. Up to this point, there's only a flat material, which outputs a diffuse color.
Before going further in the description, please read the previous scene description, this will give all the previous details you need.
I've added a simply tag called <integrator>
and <material>
and moved a little the position of other tags.
A sample scene and model for this project goes as follows:
<raytracer>
<camera type="perspective" name="cam_flat">
<!--- The camera frame -->
<position x="0" y="0" z="2"/> <!--- located at the origin -->
<target x="0" y="0" z="-10"/> <!--- looking down the -Z axis -->
<up x="0" y="1" z="1"/> <!--- the camera's up vector -->
<!--- Specific parameters for perspective projection -->
<fovy value="65.0" /> <!--- The vertical field of view -->
<aspect value="1.33"/> <!--- Optional parameter, aspect ration W/H -->
<fdistance value="1" /> <!--- The focal distance -->
<!--- Retina/image dimensions -->
<width value="800"/>
<height value="600"/>
<img_file name="images/flat_spheres.png" />
</camera>
<scene>
<background> <!-- This defines an interpolated background -->
<color r="153" g="204" b="255"/> <!-- bottom left -->
<color r="18" g="10" b="143"/> <!-- top left -->
<color r="18" g="10" b="143"/> <!-- top right -->
<color r="153" g="204" b="255"/> <!-- bottom right -->
</background>
<object type="sphere" name="sphere1" material="green" >
<radius value="0.4"/>
<center x="-1.0" y="0.5" z="-5"/>
</object>
<object type="sphere" name="sphere2" material="green">
<radius value="0.4"/>
<center x="1.0" y="-0.5" z="-8"/>
</object>
<object type="sphere" name="sphere3" material="gold">
<radius value="0.4"/>
<center x="-1.0" y="-1.5" z="-3.5"/>
</object>
<object type="sphere" name="sphere4" material="pink">
<radius value="0.4"/>
<center x="7.0" y="1.5" z="-10"/>
</object>
<!--- Library of materials -->
<material type="flat" name="gold">
<diffuse r="236" g="124" b="17"/>
</material>
<material type="flat" name="green">
<diffuse r="42" g="201" b="51"/>
</material>
<material type="flat" name="pink">
<diffuse r="253" g="153" b="253"/>
</material>
</scene>
<running> <!--- Running setup -->
<integrator type="flat">
<spp value="1"/> <!--- Samples per pixel -->
</integrator>
</running>
</raytracer>
Notice now that the <object>
possesses a material
attribute. These materials must be declared and defined
inside the scene file. A material
has a type
, a name
and the color it reflects. For materials of type flat,
there must be a tag diffuse
in order to describe this.
Also, there's the tag <running>
, which contains the <integrator>
one. Currently, there are three types of integrators:
Flat Integrator
(this one simply outputs the diffuse color of a flat material), Normal Map Integrator
(generates colors
based on the normal map of the shape) and Depth Integrator
(generates a color linear interpolated from a far and a near
parameter).
Of course! This image was generated from the sample scene given above:
Now, if we change the contents of the <running>
tag in order to test the same scene, but with a different integrator (say the Normal Map Integrator), we have this:
<running> <!--- Running setup -->
<integrator type="normal_map">
<spp value="1"/> <!--- Samples per pixel -->
</integrator>
</running>
And the output produces:
Finally, testint the Depth Integrator:
<running> <!--- Running setup -->
<integrator type="depth">
<spp value="1"/> <!--- Samples per pixel -->
<near_color r="0" g="0" b="0"/>
<far_color r="255" g="255" b="255"/>
</integrator>
</running>
Gives this:
This is only the very first version of the project. I could not invest all my time on it, unfortunaly. So, if you try to break the program... it probably will. Its in my todo, actually, to implement exception handling and a more friendly interface to the user.