Textures

 Part 4: Texture Maps


Hello everyone, in this post I will be talking about textures maps and I will show my ray tracing outputs with texture mapping extension. 

 

What are Textures and why use them?

So up to now in my ray tracing posts I have been assigning a material for each object and made shading computations for that object according to properties of that material and that material only. This restricts us because in real life an object rarely consists of a single material or color. Even the simplest objects are made of a few components. For instance let's say we want to model and render the box below for a video game.


Figure 1: wooden box




With the tools we have up to now it will be like torture for a 3D designer to create this box because we can only assign one material per object so we would have to create millions of triangles for this single cube so that we can add all the little details such as metal scratches and veins and cracks in the wood. The sad part is this box is probably an object that will be passed by the player in a scene in mere seconds and will be never looked again. To create a whole scene with details we would have to a lot of work.

Fortunately some smart people in computer graphics developed a very efficient way yo solve this problem: texture mapping. Instead of creating a complex geometry or a complex material arrangement we create a single cube and we paint an image like below and paste it to cube's every face. While doing the shading computations we will use the texture's value as diffuse and/or specular components.

Figure 2: wood box texture

Figure 3: wrapping textures around an object

As a final result we will get a pretty cool wooden box with almost no effort as a scene designer.


Figure 4: wooden box final


Of course I have to warn you that in this image there are two textures one for diffuse component and the other for specular component. Since the wood doesn't really shine with specular component it's specular components will be all black and as a result we will have the following specular map. 

 

Figure 5: wood box specular map


If you had not used a specular map you could get a result like below.

Figure 6: No specular map


This is because all points have the same specular reflantance coefficient. 


Just because our texture are in rectangular shapes ( well they don't have to be but no need to create a crazy image format now) we are not restricted to rectangular objects. For instance we can render cool images like following.


Figure 7: galactica

But in order to do that we need to present a convention. Usually along vertex coordinates we use texture coordinates associated with each vertex so when we are doing shading computations on a point we use the texture value associated with that texture coordinate to sample from that image. Because of this usually textures of complex objects may seem weird like galactica's texture below.


Figure 8: galactica diffuse map

 

Interpolation types

 After mentioning image textures I have to mention sampling methods. In texture sampling usually our texture coordinates doesn't directly map to any pixels center instead they usually fall somewhere between the pixels. Simplest way to handle this is to round our values so that we get the nearest pixel. This is unsurprisingly called the nearest sampling. I will also implement another method called linear interpolation. In linear interpolation we will compute a weighted average of the four neighbor pixels based on their distance to the our texture coordinates.
Figure 9: Texture sampling [1]


You can see the difference between linear interpolation and nearest sampling below. Nearest sampling is on the left. The aliasing artifacts are more noticable in nearest sampling.

Figure 10: Nearest sampling vs Linear interpolation


Textures 

 Up to this point I was using a predefined background color to fill the pixels that their rays hit no object. Now that we have textures we can actually replace the background with an image. To do that I have assigned every ray a texture coordinate based on where they were passing through the image plane and if that ray fails to hit any object I sampled the color from the background texture. And just like that galactica looks much more cooler now.

Figure 11: galactica with stars in the background


Modifying Geometry with Textures

Displacement Maps

We are not limited to modifying color with textures. We can also use them for even more stuff. Like modifying geometry. There are called displacement maps because we displaced the vertex along the surface normal according to the texture value. Like the monster frog below.


Figure 12: Monster frog [2]

At first, One might think "Why on earth would I want that?". Well, you see it might not be the best idea to modify the actual vertex data because it might consume to much memory and in real time applications that is a big problem. Even in offline rendering we like efficiency. You can see the memory usage difference between displacement maps and actually modified memory in the monster frog above.

It was not part of my homework so, at least for now, I'm not going to implement displacement maps.

Normal Maps

Displacement maps are great. They have a very simple idea and even though they are easy to implement in forward rendering pipeline, they are not that straightforward in ray tracing. They can also be costly in terms of performance sometimes. Because of the mentioned issues we also have another types of textures that are even simpler. One such texture map is normal map. In normal maps instead of using the surface's actual geometric normal we get our normals from a lookup texture. If this sounds weird, you will see why this works when you see the examples below. 

Figure 13: cube waves normal map

Figure 14: cube cushion normal map

 In these examples no geometry is modified but still they look very convincing. Also you can see two different cubes with and without normal maps for comparison. The ones on left have no normal maps while the ones on the right have also normal maps applied to them.

Figure 15: cube wall normal map comparison

Figure 16: brick wall cube normal comparison

As you can see in the examples, if the geometry is not "displaced" too much away from it's initial position the eye won't notice the difference between displacement maps and normal maps (unless you directly look towards the objects edges). 

Bump Maps

Just like normal maps we have another method to replace object normals. Bump maps are a method that is somewhat between displacement maps and normal maps. Just like displacement maps we take a height value from the texture sampler but this time instead of moving the vertex, we "act like" we moved the surface and compute the displaced surface's normal and use this normal as our normal but keep the actual vertex position. Now let's take look at the wooden box example with bump maps added to it.


Figure 17: bump map comparison

If you look towards the metal frame of the box you will notice the difference. The scratches on metal look much more detailed. You can also see other examples such as the earth with bump maps applied and with no bump maps below.

Figure 18: the earth with bump map vs tex map

Figure 19: the earth with just texture map vs texture map and bump map

Figure 20: An ellipsoid rock with bumpy surface


Procedural Textures

Up to this point I have mentioned textures as they are images and we sampled from these images but actually there can be different kinds of textures. One important type of texture type is called "procedural textures". There kind of textures are not stored in the memory but they are generated on the fly hence the name procedural.

 

Perlin Noise

One important kind of procedural texture is the perlin noise, introduced by Ken Perlin. Perlin noise is a texture that you will use when you want some kind of randomness in your texture but not so random that it feels like a total mayhem. That is the difference between classic white noise and perlin noise. If we were to create images with perlin noise and white noise you would see the difference. Perlin noise has a more smooth and gradient transition.

 

Figure 21: White noise [3]


 

Figure 22: perlin noise signal

Now let us talk about how perlin noise is generated. Perlin noise can be created for any dimension but since it is simpler I will explain the 2D version here but in my code i will implement 3D perlin noise.

Unlike white noise in perlin noise we need to achieve a smooth transition. In order to do that we are going create a grid with unit squares and assign random vectors to grid's each vertex. 

Figure 23: perlin noise grid [4]

Now in this 2D space every integer point will have a vector assigned to them we will call these gradient vectors. When we are asked to compute a sample from this texture we are going to find the unit square that encloses this point and create vectors that are starting from the unit square's corners and ending at the sampled point. These are the distance vectors.

Figure 24: Distance vectors from the corners [4]

After finding each distance vector and it's associated gradient vector, we are going to find each pairs dot product and assign this as the respective corner's noise value. Now all we need to do is interpolate these values to get the noise value of the internal point. But in the original paper suggested by ken perlin, it is better not use linear interpolation. Instead, using a smoother interpolation function creates smoother transitions. 

Figure 25: Interpolation function [4]

After applying all these steps we will get a value between -1 and 1 if we want to map it to interval [0, 1] there are two popular ways. One is the linear conversion where you add 1 to the value and divide it by 2. The other one is to take it's absolute value. And finally we got ourselves our perlin noise texture.

Figure 26: Cubes with perlin noise generated textures

Figure 27: bump mapped cubes with perlin noise


Figure 28: spheres with perlin bump map

Checkerboard Textures

There is still one simple type of texture is remaining and that is the checkerboard texture. Sometimes it can be useful to have a texture that creates the patterns of a checkerboard. For instance we can use this texture for the tiles of a kitchen. It is very easy to implement so I am just going to give my C++ code for it and give it's results. 

vec3f CheckerBoard::sample(vec3f pos)
{
    bool x = (int) ((pos.x + offset) * scale) % 2;
    bool y = (int) ((pos.y + offset) * scale) % 2;
    bool z = (int) ((pos.z + offset) * scale) % 2;
    bool xorXY = x != y;
    if (xorXY != z)
    {
        return black;
    }
    else
    {
        return white;
    }
}

Aand we've can get results like following.

Figure 29: cubes with checker texture


You may have noticed they are defined in a 3D space so if you want you can sample from them in a different way to get different result as following.

Figure 30: 3D checkerboard texture on cubes

In this image I have given the z value of this texture as (x+y)/2 so actually along the surface z values were also changing. 

With textures we can also disable shading completely and use the texture value as our color even though this is may not be very realistic this is possible in the example below you can compare the earths on the edges to see the differences between shading enabled and disabled.
 
Figure 31: Sphere and ellipsoid textures


 

Some Bugs I have encountered

Buggy normals


Even buggier normals


 

 

erroneous TBN transformation

Erroneous bump factor

erroneous TBN transformation

erroneous TBN transformation

 

References

1 - Ahmet Oğuz Akyüz, Advanced Ray Tracing Lecture Slides

2 - http://developer.download.nvidia.com/presentations/2009/GDC/GDC09_D3D11Tessellation.pdf

3 - https://en.wikipedia.org/wiki/White_noise 

4 - Ahmet Oğuz Akyüz, Computer graphics 2 lecture notes

Comments

Popular posts from this blog

Putting it all together: BRDFs, Object Lights and Path Tracing

Trace the Ray