Piotr Danilewski

The Candle

desc_main_small.png
(all links will open in new window)
List of features
FeatureDescriptionExample
Displacement
Mapping
It is the core element of my tracer as many other features depend on it. It can be used both at global scale to shape the scene (i.e. terrain) as well as at detail level to add realsim (i.e. candle surface).

Main component of my displacement mapping is the HeightMap class. It works similarly to a texture - it is a two-dimentional matrix of sampled positive height numbers. Arbitrary two dimentional coordinates of the heightmap may be applied to a vertex of a triangle primitive (HeightTriangle, SmoothHeightTriangle) pritty much the same as it is done with texture.
HeightMap provides an intersect(Ray,minT,maxT) method to check if and where the given ray intersects the map. HeightMap has no information about spatial location of the triangle(s) it is used on, so the ray must be transformed into local reference system of the map. Map lies on XY surface and perturbation goes into Z direction. Whole array is spread over square [0..1]x[0..1] (later the ray is scaled internally). minT and maxT are the extreme points where ray is over the triangle we are intersecting. This saves time and increases quality - we do not want to intersect heightmap somewhere while not being over the triangle.
Very naive implementation of HeightMap could be done by spawning all its triangles, putting them into kdtree and rendering by already written algorithms. This would actually spawn a great number of triangles. Displacement array of dimension 256 would generate at least 131072 triangles and would take a lot of time to compute even with good kd-tree. (For comparison - cow.obj uses less than 6000 triangles)
My implementation of HeightMap takes advantage over spatial organisation of its triangles. It temporarly spawns four triangles only at the last step of the algorithm, when it localised the hit position there. Before that, my algorithm projects the ray over a grid and checks z coordinate at each point where x or y coordinate is integer (when unit distance is the distance between two height samples). The value is checked against linearly interpolated height from two closest grid points. Such check is very fast to compute in comparison to triangle intersection, and it is quick even without additional structuring of the matrix.
Additional global value of max height is stored to avoid unnecessary checks. Also algorithm is terminated when we pass behind maxT.
Special care is taken for rays incoming from perpendicular angle to the heightmap, when ray never intersects any integer coordinate and maxT is infinite.

This approach is extreamly fast if incoming rays are nearly perpendicular to heightmap or maximal height is small - in that case only few comparison are being made before hitpoint is found. The algorithm is slower when maximal height is big but there are vast low regions and the ray is parallel to the surface. In that case intersection time is linear to the dimension of height array.
This can be resolved by adding 2d quad-tree to the array. At each node the maximal height of its children would be stored which would hopefully be smaller than global maximal height. This way if height is big only at one or few positions, ray traversal elsewhere will be fast. I was attempting to implement the structure, but time was pressing on and there were still lots of bugs elsewhere, so I reverted back to the simpler - yet sufficently fast version. Additionaly, heightmap interpolates gradient value at given 2D point, which is later used by HeightTriangle and SmoothHeightTriangle to compute perturbed normals. While HeightTriangle adds up vector to the gradient, SmoothHeightTriangle combines interpolated normal vector to it.
SmoothHeightTriangle is used on the candle, everywhere else simpler HeightTriangle is being used.
Displacement mapping can be seen everywhere: Terrain, Candle, Table, Water.
Frame 1054 - candle just before falling
On this frame it is probably seen the best, how it interacts with world. The candle is not just a straight cylinder, and it is unevenly illuminated. Also note the shadow of a mountain right from view point casted across the table. The mountain is also driven by the displacement map
Fractal GeometryFractal geometry is used in two cases:
Firstly it is used to generate terrain simply by applying 2d perlin noise to generate heightmap. The heightmap is generated at the beginning of program and then stored in raw format in memory.
Secondly it is used in a candle. Basicly it uses perlin noise as well, but the heightmap is slightly streached and convolved with weighted motion blur to simulate frozen vax on the surface of the candle.
For candle - same as above
Frame 1054 - candle just before falling
Terrain (not on the video):
Terrain map
This image shows terrain seen from top, with different lightning and replaced texture to emphasise height differences. Approximated camera path for first part of video is marked.
Water simulation
Reflective and Refractive Transparency
To simulate water surface a special WaterShader is created. For given incoming ray it generates two new rays, one for reflected component and one for refracted one (provided it exists). It uses Snell's law to compute refraction and Fresnel equations to weight the resulting colors. Additionaly, an additional attenuation for refracted ray is applied depending on the hit distance.
Water surface uses small displacement map as well to generate tiny waves and make use of perturbed normals to add realism to water surface. It can be particularly seen by distortions of reflected objects.
Frame 317 - wavy water
Frame 317 with increased brightness
Frame 220 - aproaching the pond
Here, on the bottom of the image, you can see how the rocky surface is refracted. Below the water surface it seems to be flatter than above.
3D texturingUsage of heightmap allows to store third coordinate after a hit, which can be used later for texturing. This is being done with landscape texture. Terrain uses a single texture, which checks height coordinate and applies different color based on it. There are main 3 regions - ground, grass and rocks which are slightly melted one with another to avoid ugly straight lines. Also using 3d texture allowed me to avoid ugly streching artefacts at high gradient regions.Frame 501 - aproaching the candle
Here all three levels can be easily seen.
The slightly brighter brown surface at the horison is a background texture and is not a part of heightmap.
Procedural TexturingThere are no external textures in the animation, all are generated at run-time, mostly using different combination of perlin noise. Main two textures is the landscape texture and wood texture. Wood texture takes a value of perlin noise as an argument for a cosine function. This way I obtain repetitive curves which - when streached - simulate wood quite well. Landscape 3d texture is more complicated one. The rocks use several layers of perlin noise to generate rocks of different size. The biggest ones are allowed to appear lower, over the grass. Edges of the rocks are blended with whatever they hide to avoid sharp edges.Frame 770 - after a jump
Viewer is very close to the table here, you can observe the texture in detail, especially in the brightened part at the bottom of the image.
Frame 128 - rocky ground
Rocky region. Observe different sizes of rocks. There is a single component on the second plan on the left, while lots of smaller rocks on the right. You can also see rocks on the grass on the first plan. This is all generated by a single texture object!
Table object (not on the video)
Edges at main image
Main image with applied edge detection filter to emphasise the pattern of the wooden texture
Remote texturingThe sky texture is not associated with any object, instead it is applied to world itself.
The texture is not shaded just applied directly when ray does not hit any object in the scene. It has to be a 3d texture because it takes ray direction vector as an argument to get a pixel. As a result you see a unit sphere curved out of the texture, seen from the point [0,0,0] which does not depend on camera location.
This approach guarantees you will never see nothingness. At least you see a brown ground instead of black, green or pink(!) background color.
Frame 501 - aproaching the candle
You can see stars when direction vector goes up and brown surface when it is moving down. Thus horison is exactly where it should be - at the height of camera.
Simplified Integrated Intensity Volume RenderingA very simple integration aproach was applied to render the fire.
It is controled by GlassShader which projects ray inside and behind the object and then blends the background color with the object's color depending linearly on traversal length inside the object. Mathematically it can be seen as integraction over box-like-function scalar field. The flame is rather opaque but this aproach smooths its edges and it no longer looks like quake1 squarish fire meshes.
Frame 1036 - The Candle
Here if you look closely, you will see how top of the fire object blends with dark edge of a mountain behind. Observe, the edge is less visible in the middle, because flame is wider there and thus less transparent.
Similar effect can be seen on the main image
Cubic B-Spline Camera PathCamera movement is defined by a path route.
Knot points are explicitly provided and a smooth path is generated by joining cubic B-splines. It is guaranteed that camera moves through the knot points.
A nice webpage on splines was helpful for me to code it:
http://www.ibiblio.org/e-notes/Splines/Intro.htm
N/A
Implementation specific
ElementDescriptionExample (if applicable)
VectorsVector class has been highly edited.
It is now a template, taking dimension as a parameter. To avoid time loss and increase functionality 2d and 3d class instances are specifically written.
N/A
MatricesSquare matrices are also templates, depending on their dimension.
I use only 2d and 3d matrices, some functionality of higher dimension matrices are not implemented (i.e. inversing) because efficient algorithms are not that easy and there would be of no use for the rendering. Matrices are used to change coordinate system of a HeightMap and optionaly, for entities (see below)
N/A
QuaternionsProvide basic operation in the quaternion number space, and some useful vector transformation - namely rotation.
Used to rotate entities (see below)
Frame 1062 - Falling candle
Candle being rotated when falling
BVHIn my version of MicroTracer the Object class is a derivative of Primitive. This way one object may be an element of another forming a hierarchy. What is more, one object may be referenced many times. Very simple garbage collector is implemented to free memory of such object only when it is not referenced by anything.
There are also Entities, which are more advanced objects (thus also primitives!). Entity allow transformations - translation, rotation or even (optionally) arbitrary linear transformation. When ray hits the bounding box of entity, it is transformed into local reference system and intersection procedure continues.
Care has been taken to correctly transform back hit time (ray.t), and normals.
All objects in the video are entities (terrain, table, candle, candle fire). They could be easily tested, modeled etc, and then put together using translations.
It would be difficult for me to put all primitives into one huge object, it would be against the design of my raytracer. Therefore I am unable to make time comparison between those two approaches.
Table object (not on video)
All table legs is only a signle object in memory, with four references by Entities with applied different translation vectors.
Volume Heuristic KD-TreeEquivalent of 2D Surface Area Heuristc.
To find minimum of cost function i project bounding boxes of all primitives into one axis, then I convert them into points with flags "beginning" and "end", and then sort them. When it is done, I go through all points and compute the cost function. At each point I know many "begin"-s and "end"-s I encountered so far, so I know how many primitives are on the left and right side of my point. This way I can compute cost function in constant time at each position.
Algorithm complexity is equal to sorting time. STL sort function has guaranteed O(n log n) complexity including the worst case.

More primitive heuristic, which I denote AVG-SD, computes center of mass of all primitives (average) and standard deviation in three directions parallel to axis. Split goes through the center, against the axis with highest SD
Time execution comparison
Frame 1062 - Falling candle
Volume Heuristic10min 01sec
AVG-SD Heuristic48min 02sec
No KD-Tree17min 30sec
This somewhat surprising result can be explained by the fact that naive AVG-SD algorithm tends to duplicate primitives a lot. Because my primitives are usually not that primitive - those can be heightmaps or whole objects lower in hierarhy - such duplication is extremely costly. As a result KD-Tree with this heuristic is worse than if there was no KD-Tree at all. In the latter case, optimalisation is held fully by BVH.
Files
File nameDescription
box.hExtendable bounding box
camera.h
cameraperspective.h
Perspective camera model
cameramover.hControls the movement of a camera
candle.h
candle.cpp
Creates the candle without flame
candlefire.h
candlefire.cpp
Creates flame of the candle
config.hMain configuration file which controls the scene and algorithms
debug.hSet of useful macros for debugging
entity.hImplements the Entity described above. Uses ref.h for transformations
generator.h
generator.cpp
A set of useful functions for generating objects in scene, i.e. cones, cut-cones, quads etc.
heightmap.h
heightmap.cpp
Implementation of the dismplacement map.
image.h
image.cpp
I/O for images
interpolation.hA set of useful interpolation functions
interval.h
intervalset.h
classes used to store primitive extremes in Volume Heristic for kd-tree
kdtree.h
kdtree.cpp
Implementation of KD tree
landscape.h
landscape.cpp
Creates the terrain and water. Also the 3d landscape texture is here
light.hAbstract light source
lightdistant.hA distant light source with no attenuation
lightpoint.hSimple point source
lightquadarea.hQuad light source approximated by numerous point lights. Actually it is never used in the rendering
lightsphere.hA simpler version to Quad Area light. Random point lights are taken from a ball of some radius. No normal vector is defined, light intensity is equal in all direction.
mat.hImplementation of quare matrices
object.h
object.cpp
Simplest object which can contain primitives and keeps kd-tree over them.
pngHelper.hInterface to the PNG library
primitive.hAbstract primitive
primheighttriangle.hTriangle with applied heightmap
priminfiniteplane.hInfinite plane. It was used at some version of my program, but it isn't anymore
primsmoothheighttriangle.hTriangle with applied heightmap and per-vertex base normals, which are later combined with the computed gradient
primsmoothtriangle.hFlat triangle with per-vertex normals
primtexturedsmoothtriangle.hTextured flat triangle with per-vertex normals
primtexturedtriangle.hTextured triangle
primtriangle.hA single triangle with no additional attributes
quaterion.hImplements quaternions
rand.h
rand.cpp
Interface to some simple random functions, including repetitive random functions and Perlin noise
ray.hRay object used for raytracing
ref.hImplements neccessary transformations between two reference systems
sampler.hAbstract sampler for pixel coloring
samplerrandom.h
samplerregular.h
samplerstraitified.h
Different kind of samplers. The straitified version is used in the rendering
shader.hAbstract shader
shadercoud.hGenerates coulds. Never used.
shaderflattransparent.hTo be used with textures with alpha channel. Never used.
shaderglass.hImplements ray traversal through dimm object i.e. dimm glass. Used for candle fire
shaderphong.hImplements simple illumination function for given point.
shadersimple.hSimply takes the color of given object or texture texel and returns it. No lightning etc. Used mainly for debugging
shaderwater.hReflets and refracts the ray and later blends them
table.h
table.cpp
Creates the table on which the candle is standing
texture.hAbstract texture class with defined 2 and 3 argument getTexel function
textureperlin.h
texture3dperlin.h
Parametisable texture using 2D/3D perlin noise
texturestars.hImplement stars and ground for the world
texturewood.hImitates wood using perlin noise
utils.hA bunch of useful functions
vec.h
vec2f.h
vec3f.h
Implements the vector template and two specialistions
world.hThis holds the whole scene
Trivia
Video length55 seconds
Number of frames1320
Render timeapprox. 2 days
Frame longest render time3 hours
Frame shortest render time4 seconds
Number of rays per pixel4
Number of light sources70
Number of explicit trianglesapprox. 640
-terrain: 2
-water: 6
-candle: 101
-table: 22
-fire: approx 509
Number of all triangles
including heightmap virtual triangles
approx. 210000
-terrain: 32768
-water: 119000
-candle: 8261
-table: 51221
-fire: approx 509)
Memory consuption9 megabytes
Source code sizeapprox. 10000 lines
approx. 250 kilobytes
Rendering location
FramesComputer
0-350My laptop
351-599Maciek's mac
600-609virgo.ii.uj.edu.pl
610Maciek's mac
611-614virgo.ii.uj.edu.pl
615-619Maciek's mac
620-646virgo.ii.uj.edu.pl
647-764Maciek's mac
765-784virgo.ii.uj.edu.pl
785My laptop
786-809Maciek's mac
810-884virgo.ii.uj.edu.pl
885tsk
886-1320Maciek's mac
Many thanks to my friend Maciek for his hardware aid, patience and comments