Part 4: Texture, Normal and Bump Mapping & Perlin Noise
The fourth part of the ray tracer is all about textures. For this part, the following features are introduced:
1. Texture Mapping
In this version of the ray tracer, three types of textures (Image, Perlin and Checkerboard textures) are supported. For this reason, I decided to implement a base Texture class, and three derived classes called ImageTexture, PerlinTexture and CheckerboardTexture. For texture mapping, there are several options for using the color value obtained from the texture:
- Replace all: Disable all shading computations and just paste the texture color on the object.
- Replace kd: Normalize the color value obtained from the texture to [0,1] range and use it as the diffuse reflectance constant (kd) directly.
- Blend kd: Normalize the color value obtained from the texture to [0,1] range and take the average of the obtained value and the provided diffuse reflectance constant (kd). Use this value as the new kd.
- Replace background: Whenever a primary ray does not hit any object in the scene, use the color value obtained from the texture directly,
In the input files, an object can have up to two textures associated with it, one for controlling the shading and another for controlling the normal perturbation. Therefore, I decided to keep two separate texture properties for each object:
- Shading texture: replace kd, blend kd, replace all and replace background options fall into this category
- Normal texture: replace normal and bump normal options fall into this category
For image textures, two types of interpolation are supported: Nearest Neighbor Interpolation and Bilinear Interpolation
Since most of the texture images are provided in JPEG format, lodepng did not suffice. In order to read these images, an external library called stb is utilized.
2. Perlin Noise
Besides image textures, Perlin noise, which is a procedural texture, is also supported. Since Perlin noise includes semi-randomness, it helps producing textures that are more realistic and natural looking. I implemented Perlin Noise as a separate class from Perlin Texture, such that Perlin Texture objects have Perlin Noise as a private property, in order to isolate the mathematical computations from the texture class itself.
With Perlin noise, we can produce two main pattern types depending on how the calculated noise value is used:
- In order to create a patchy pattern: (noise + 1) / 2
- In order to create a veiny pattern: abs(noise)
Just like image textures, these noise values can be used as the diffuse reflectance coefficient as explained above.
3. Normal Mapping
Normal mapping corresponds to the replace normal option defined in the input files. Once the RGB value is obtained from an image texture, we can use it to calculate the corresponding normal value at that point. The normal obtained from the texture is used to replace the geometric normal of the point where a ray intersects the object. Replacing the geometric normals introduces heights and hollows on a surface and creates a more realistic look.
It is important to note that, for efficiency reasons, the TBN matrices for the triangles are computed in advance when the triangle objects are first created. However, for spheres, the TBN matrix cannot be computed in advance, since the computations depend on the intersection point.
4. Bump Mapping
Bump mapping corresponds to the bump normal option defined in the input files. Bump mapping can be applied to both image and Perlin textures. Just like normal mapping, it also relies on replacing the geometric normals of an intersection point with the normals calculated based on the texture coordinates. However, while in normal mapping, the normals are simply replaced by the new normals calculated from the image texture, in bump mapping, the normals are adjusted in a more suitable way by performing much more complex computations. In this way, we are able to represent a very complex surface, even though we are only given a perfectly smooth surface.
As a side note, for both normal and bump mapping, instead of implementing separate classes, I handled the related computations inside the Image and Perlin texture classes, since the computations are not the same for different types of textures.
Besides these main features, support for the following properties are also added to the ray tracer:
- Composite transformations where the transformation matrix of the object is directly provided in the input file are implemented.
- Vertex and texture offset parsing is added.
- In order to be able to work with 2D points in the space, Vec2f class is implemented just like Vec3f class.
Problems & Fixes
Here are some of the most time consuming problems I encountered while implementing this part of the ray tracer.
When I finished implementing Perlin noise, the following buggy result was produced. Although I checked the Perlin noise calculations over and over again, I could not find anything wrong with the formulas. After a long search, I realized that the input provided to the weight function was supposed to be always positive. As soon as I used the absolute value of the input in the weight function, the results were corrected.
Second problem I encountered was the following: While implementing bump mapping for Perlin textures, I realized that I was performing an unnecessary normalization for the vector representing the projection of the gradient vector on the tangent plane. This extra normalization caused the image to be produced as below.
Another problem was that while I was trying to produce the output image for the scene called killeroo_bump_walls, the image below was being produced. At first, I could not figure out what was wrong with this image texture mapping, because all other image texture and bump mapping combinations were working as expected. But, then I realized that for this particular scene, the texture image should have been applied in a repeating manner in order to obtain the correct result. I also think that this could have been defined as a parameter in the input files just like DecalMode or InterpolationType.
Finally, for the galactica_static scene, the amount of bumps present in the corresponding output image was a bit too much. It looks like there is too much sharpening effect applied to the image and it seems very noisy. Also, the image contains some little artifacts (e.g. the little red mesh part in the bottom right part of the image has a black artifact). In order to fix this, the first thing that came to my mind was adjusting the bumpFactor defined in the input file. When I decreased the bumpFactor from 0.01 to 0.001, I observed that the bump amount was corrected but the little artifacts were still present. After a series of trials, I realized that while calculating the TBN matrices for the triangles, I was normalizing T and B vectors although they did not have to be unit vectors. Once this is corrected, the output turned out to be as expected and there was no need for changing the provided bump factors.
Resulting Images
All the timing results provided below are obtained with 8 threads running. I managed to render all of the scenes except veach ajar scene. For this particular scene, there were too many additional features to be implemented on top of the already existing ones. Although I implemented checkerboard texture and composite transformations for this scene, I did not have enough time to parse texture coordinates from the ply input file and perform debugging.
bump_mapping_transformed.png: 663 msecs 425 usecs |
cube_cushion.png: 321 msecs 376 usecs |
cube_perlin.png: 2 secs 479 msecs 892 usecs |
cube_perlin_bump.png: 22 secs 632 msecs 253 usecs |
cube_wall.png: 245 msecs 788 usecs |
cube_wall_normal.png: 292 msecs 94 usecs |
cube_waves.png: 279 msecs 396 usecs |
ellipsoids_texture.png: 517 msecs 857 usecs |
galactica_static.png: 1 sec 895 msecs 187 usecs |
galactica_dynamic.png: 2 mins 54 secs 574 msecs 865 usecs |
killeroo_bump_walls.png: 18 secs 705 msecs 9 usecs |
sphere_nearest_bilinear.png: 221 msecs 490 usecs |
sphere_nobump_bump.png: 331 msecs 22 usecs |
sphere_nobump_justbump.png: 303 msecs 758 usecs |
sphere_normal.png: 22 secs 951 msecs 917 usecs |
sphere_perlin.png: 4 secs 637 msecs 250 usecs |
sphere_perlin_bump.png: 31 secs 535 msecs 158 usecs |
sphere_perlin_scale.png: 4 secs 680 msecs 110 usecs |
Comments
Post a Comment