Dragon Lamp

The Fire that Lies Inside

The Story

Legends tell of a legendary lamp, fueled by the flames of a legendary dragon, capable of illuminating the way even in the most hopeless situations. We decided to embark on a journey not only to find this precious treasure, but to recreate it in a digital environment and immortalize it on a one-of-a-kind image.


The Origins

Memories from our journey

The scene was carefully crafted using the outstanding tool Blender. With its power on our side and combining our own generated geometry with external models and textures, we iterated over different scenes, looking for the one that really captured the true essence of what this treasure means to us. After unsuccessfully trying with open spaces, where dragons roamed free, we realized that such a relic could only be found in a dark hidden place. Maybe it had even been stolen by some fearless Vikings! That is when we came up with the final idea, a quiet and mysterious place where the light lays around with some other items, waiting to be found.

The Features

Although the journey was long and tedious, we developed a large number of tools that helped us reach our destination.
Every single one of them was essential for this, but here we will highlight some of the most interesting ones among them.

Acceleration structures and multithreading

In order to get here, a lot of tests and trial and error was required. Taking into consideration the amount of geometry and the use of many samples per pixel, it is clear that we need something that speeds up the process. This is why we utilize the BVH tree construction with Surface Area Heuristic to make the ray intersection exponentially faster, in exchange for a small wait at the beginning. We also benefit from the OpenMP library, that allows us to parallelize the highly parallelizable ray tracing procedure.

Implemented in:
rt/groups/bvh.cpp
rt/groups/bvh.h
rt/renderer.cpp


Dragon Fire

We don’t have volumetric rendering, so the fire had to be a solid. Since solids don’t emit light in our renderer and approximating fire object as triangulated area lights would result in too many light sources, we strategically placed 4 point light sources around the fire object to emit the light. Then, to get volumetric effects of fire we played around with combined materials and finally we came up with a combination of two Phong materials (one with fire image texture and other with constant red texture), a flat material (constant blue texture) and glass material that gave us a beautiful result.

Implemented in:
main/a_scene.cpp
rt/materials/combine.cpp
rt/materials/combine.h


Our treasure, the Lamp

For the metal parts of lamp, we are using a combination of the mirror and Phong materials, Phong gives specular reflections in the inner rims of the lamp handle and mirror reflections can be seen on the lamp base. For the glass globe we initially used just a glass material, but this was not giving out the color of fire reflections, so to mimic a volumetric effect we used a combination of Phong (constant texture), flat material (image texture of fire) and glass. We had to carefully adjust the weights of each material to get desired result. One more challenge we faced with this lamp was when we place lights inside, it would cast a shadow over all the objects. So, to allow light to pass through light without any loss, we modified the integrator to not cast shadow rays if the material is glass.

Implemented in:
main/a_scene.cpp
rt/integrators/recraytrace.cpp


Starry Night Sky

We wanted to add some ambient to the scene which would give a nice night lighting effect. For this we are using a very large area light mapped with an image texture of a stary sky, so whenever a star is sampled it will add light to the scene. But the light was too low to notice, and we added a multiplier in the area light which allowed more light without increasing the brightness of the image texture. Since the plane is very large and extends over all the scene, it caused problems when building the BVH tree, so we had to add the environment map separately to make sure it was not taken into account when building the index.

Implemented in
main/a_scene.cpp
rt/lights/arealight.cpp
rt/groups/bvh.cpp


Smooth triangles

Even though most of our geometry does not have a very high polygon count, we can still get the effect of smooth surfaces by recording the normal on each of the mesh vertices, and then interpolating said normal direction during the intersection, using the hit point of the ray over the triangle surface. This very simple method gives us an impressively realistic approximation of the desired surface.

Implemented in:
rt/solids/striangle.cpp
rt/solids/striangle.h



Other additional features

Bump map added to the shield, barrels, horn and Toothless (the baby black dragon, you should know him ;).
Implemented in: rt/primmod/bmap.cpp and rt/primmod/bmap.h

Gamma correction to give our scene a more crispy lighting.
Implemented in: core/color.cpp and rt/integrators/recraytrace.cpp

Depth of field to get a more realistic camera effect.
Implemented in: rt/cameras/dofperspective.cpp and rt/cameras/dofperspective.h

Render Details

Our scene was rendered on an Intel(R) i7-8750H processor

Samples per pixel

Triangles

Minutes rendering

That time when we almost gave up

Once the scene was ready in Blender and we tried importing it to our renderer, the process would start as expected… but after running for a while it crashed :(. This problem was occurring only on a Windows PC, on MAC it was running fine, and that machine had much lower RAM, so we thought code was perfectly fine and that it was some kind of problem with Windows 11. But as we increased the complexity of scene, it crashed on MAC as well. That’s when we realized something was wrong with the code and began debugging. We observed that once BVH intersection starts our RAM fills up rapidly over time and crashes the application. After endless break points and prints we found the culprit, of all the places it was in the bounding box intersection, we had forgot to remove an unused AABox object, and just commenting that line was the turning point after which our code was running buttery smooth on 12 threads (thanks to OpenMP) and using only 1.5G of RAM! One puzzle remains, why did it run only on MAC initially? Our guess is that MAC handles memory slightly better and when it runs out of RAM it just uses internal storage…


The References

We would like to thank all these amazing external sources for letting us tell the story the way we thought it was meant to be.