Monday, April 25, 2011

Multiple textures

Rendering a single texture on a surface can be very dull, what we will see in this tutorial is how we can combine more than one texture to obtain a more complex effect.

Substracting a texture from our textured cube

This tutorial will be more theoretical than the others as I don't actually show any new technique here, just how to better use things you already know.

Don't forget that backreferences are recursive!

This time I will give you the complete code with all techniques from the beginning. So what will you do? Just follow what I'm explaining on the code.

First off the Game1 class, as you can see it is rendered as I did up till now, nothing new there. In the update method I'm also checking to the input to change the technique in use (numpad 0-5). The add and subtract keys change the value of a variable that is set to the shader, it's used in the interpolation technique (num 4) and a time variable is always incremented, this one is used for the animated texture (num 5).
A very important thing to see in the Game1 class is that I have added 2 more textures. Another 2D texture and a 3D texture. You can think of a 3D texture as a stack of normal 2D textures, the one we are using is a stack of 120 images each 256*256 pixels. The image is that of a fire animation. The original files are taken from a DirectX book that has the source code free for download on the internet. I've created a new volume texture in the DirectX Texture Tool and added each face manually, after that the image was save with the DXT1 surface format.

Looking in the shader from the top you will notice the 2 floats that we set from application and 3 texture samplers (one for each texture). The first 2 are almost identical, the only difference is that they have other names and other textures. The third one is more interesting as it is a 3D sampler.

texture3D Texture3;
sampler3D texSampler3 = sampler_state
    Texture = <Texture3>;
    MinFilter = anisotropic;
    MagFilter = anisotropic;
AddressU = CLAMP;
AddressV = CLAMP;
AddressW = WRAP;
    MipFilter = linear;
MaxAnisotropy = 16;

The syntax is almost identical to the 2D one, we only change the 2 with a 3 and we also have one more Address. Because this is used for a 3D texture we also have to tell it what layer to use. This is the role of the W component, of course texture filtering works for this coordinate too so if we ever read a value between 2 layers they will be interpolated. I've set the AddressU and AddressV to CLAMP because the top of the image is black and the bottom is bright red, sometimes red would have appeared at the top and that was a problem. AddressW is set to WRAP so I can loop through the animation again and again.

Lower in the files you can see shaders where I sample the 2 textures and combine the 2 results. Here is the additive shader:

float4 PSAdditive(VertexShaderOutput input) : COLOR0
    return tex2D(texSampler1,input.Texture)+tex2D(texSampler2,input.Texture);

When you perform color operation like this (with operators) inside shaders, the operations are componentwise. This means that the operation is done per channel. So if I have 2 colors, Color1 and Color2. Adding them together to form Color0 would be the same thing as adding each channel separately:

Color0 = Color1 + Color2;

is the same thing as:

Color0.r = Color1.r + Color2.r;
 Color0.g = Color1.g + Color2.g;
 Color0.b = Color1.b + Color2.b;
 Color0.a = Color1.a + Color2.a;

This is true for all color operations (addition, multiplication, subtraction, division). But remember that color channels are clamped in the 0 - 1 range, so if you exceed 1 or go under 0 it won't make any difference. Because of the way these operations work, Addition tends to generate brighter images (you are adding color) so you reach white very fast with just a few textures. Subtraction does the exact opposite so you will reach black very soon. Color multiplication is used very often in illumination techniques. Multiplying a color by white will not change the original color, but if you multiply it by a shade of gray you will get a darker image (think shadows).

Our next pixel shader (and technique) uses an intrinsic function called lerp(). The function name is a short version of linear interpolation. The function receives 3 parameters, the objects to linearly interpolate and the interpolation factor, it will return the interpolated value. For example if we set the first parameter to red, the second to green and the factor to 0.25 we will get a color that is 75% red + 25 % green. If we change the interpolation factor to 60 we will get 40% red and 60 % green. The factor is clamped to the 0 - 1 range. In this demo I have used a linear interpolation with a controllable factor (+ and - keys) so you can see a simple face effect.

The last shader uses the 3D texture. Because the texture is a fire animation of 120 frames I've decided to let it loop on it's own. The intrinsic function tex3D samples a volume texture in the same way tex2D sample a flat texture. The second parameter of tex3D is a float3 this time, and the third component tells the sampler from what layer to extract the data. This is incremented at the application level to create the fluid animation that wee see on screen.

float4 PSAnim(VertexShaderOutput input) : COLOR0
    return tex3D(texSampler3,float3(input.Texture,time));

We are using a intrinsic constructor here. In HLSL this type of constructors (where we give a vector with fewer components and the other components separately) are very common.

Here's a short video with these effects:

No comments:

Post a Comment