[ 32bits.co.uk ] Coding Direct3D for real people

32Bits - Coding for real people!

.:[ Please always use http://www.32bits.co.uk to access the site. Thanks! ]:.

32Bits main page Introduction to the site The tutorials index Our releases The license for content on this site Public forum for questions and feedback We provide professional services Contact us

 

Last updated: 10:55 AM Monday October 2008

welcome.gif (1805 bytes)

I chose this effect as the next tutorial for 2 reasons. Firstly, a friend showed me a feedback effect they'd coded which looked slightly similar to this one. Second, another friend (plek/damage) submitted this to the Flipcode IOTD a while back. Third, Scali wrote an extremely kewl pseudo-raytracing effect (which I'll show you at the end of this tutorial). The common factor amongst all of these is that they use the same techniques I'm about to show you. Anyway, get ready to scratch your head in a confused matter, because this tutorial is going to make you go crazy. We're going to learn 2 things, and touch on a 3rd. Firstly, we're going to see how to render stuff to textures. This is important for a lot of reasons I'll explain later. Second, we're going to take a look at orthogonal projection. This one is really important, for reasons I'll explain right after this screenshot:


Fig 1.1: It's better when it's moving!


That is what we'll be achieving in this tutorial! You can't see it from the screenshot, but the texture is a rotozoomer type effect and moves in it's own right, as well as the cube doing it's good old rotating thing.

Before we start, I want to talk about those two specific concepts we'll be learning in this tutorial, the first being Orthogonal projection. If you're tempted to skip this bit, don't. Here's a statement for you, to show how important this is - "Orthogonal projection is the only way to get a 1 to 1 world space unit to pixel mapping when using D3D in a non RHW setup".

Wondering what RHW is? Check DirectX Basics Series 3 for a complete explanation. RHW allows for a 1:1 world space to pixel mapping, but at the loss of the transformation pipeline. RHW and 1:1 space to pixel ratio are both also called "screen space".


This is a question you'll run across a lot. There is simply no easy way to use the 3D T&L pipeline and retain a 1:1 world space to pixel mapping. Let me clarify this a bit. Take a look at DirectX Basics Series 3 Tutorial 4, where I showed you how texturemapping works by texturemapping a quad with a picture of Jessica Alba. You'll notice that I had to guess the dimensions of the quad to allow the image to appear with a correct aspect ratio (width to height). But as you can see, it's not accurate - in fact, because it's a total guess it's of no use for any kind of precise calculation. This is just one of the problems the object to world space transformations produce.

So, our problem is how to get quads and other primitives sized to exact pixel dimensions when we're only working in object and world space. Technically using RHW space would be ideal, but we lose too much of the pipeline - transformations, lighting and more. This means we need another option, and it's called "orthogonal projection".

Orthogonal projection gives us the benefits of RHW space with a 1:1 worldspace to pixel mapping, but also allows us to use the T&L pipeline as normal. In other words, we can do everything possible in object-world space, and at the same time use and specify the exact pixel coordinates for our primitives. We do this by creating our 3D objects in object space as normal, but bearing in mind that we're on a 1 unit = 1 pixel scale. Then, when we pass our vertices through the transformation pipeline, the orthographic projection ensures that this scale takes effect in world space. Therefore if we create a quad 10 units high by 10 units wide centered around (0, 0), and use an identity matrix for our world transform (remember the identity matrix? Here and here), we will prouce a 10*10 pixel quad in the middle of world space (ie: centered on screen).

Okay, thats nice, but surely we can just use RHW space right? I mean, if we want a texturemapped sprite using a quad, we can just do it and skip the transformation pipeline yeah?

From this point on, when I say 1:1 worldspace to pixel mapping I'm talking about on-screen scales. As you should know by now, in the 3D T&L world we don't use pixels to describe positions and sizes - we use conceptual "units". We do this because the T&L pipeline handles the translation of units to pixel positions for us, when doing the object to world space transform. This was discussed in depth back in DirectX Basics Series 3 Tutorial 3. This is really handy for 3D operations, as we don't need to worry about transforming all the vertices of a 3D object ourselves. It's not so handy when you really do need to use pixel based scales, for example to display a 128*128pixel image on a 128*128pixel quad, which is the problem this tutorial addresses. So, when I say 1:1 worldspace to pixel mapping, I mean that 1 logical object or world space unit is exactly equal to 1 pixel - a quad with worldspace unit dimensions of 100, 100 would be exactly 100 pixels wide by 100 pixels high. This is also called "screen space". Terminology is fun, eh ;)


Okay, you're still set on that easy old RHW setup eh! Let me convince you otherwise. The second concept this tutorial will show you is how to render to a texture. This is really important, because it's the basis for so many different effects it scares me. The neat moving texture effect you'll see in this tutorial is just the tip of the iceberg! Rendering directly to a texture is a very powerful tool, because it does exactly what it says on the tin - everything you can do "on screen", or more technically on the back buffer, you can do on a texture surface. Hopefully you're getting some ideas about what this could be used for.

There is one super important major difference between how coordinates work in RHW space, and how they work when using orthographic projection. When using RHW, you specify the exact pixel positions of vertices - for example, to place a vertice at 200 pixels from the left of the screen and 10 pixels from the top you would use coordinates of (200, 10). However with orthographic projection you're using a principle called "foreshortening" - when passed through the projection transform, a special matrix effectively drops the Z component of the transformation, causing every unit of measurement in object space to equate to one pixel in world space. This means that you still need to use the same principles to specify your object's vertices in object space - center them around 0,0,0 and use the world transformation to move them. We discussed the principles of the object-world transformation here.


The thing about rendering to textures is that they are a little bit tied to orthogonal projection. This one is a bit mindblowing, so take your time. As I said, anything you can render to the back buffer can also be rendered to a texture, using exactly the same methods. All you need to do is to change the "render target" - by default, the D3D rendertarget is the backbuffer. That's the easy part, you just make one call to an IDirect3DDevice8 interface method. The problem occurs because you're using the same transformation pipeline to render to a texture as you're using to render to the back buffer - you can't get a 1:1 world space to pixel mapping. You may now be wondering why this is an issue. For some effects you're absolutely right; you don't need to worry about a 1:1 mapping. However in some instances (and for this tutorial), it's very important. Because we're rendering output onto a texture, and then using that texture to texturemap an object, we need to make sure that the texture surface is completely covered by our render output - if it's not, we'll have big black bits on our texture.

By now you may be a little confused as to what all this means, so let's take a look at the tutorial and see if that clears things up. First of all, let me explain exactly what's coming up. In this tutorial, we're going to create a rotozoomer type effect, where an image appears to distort and feed back onto itself to produce an optical illusion like a kaleidoscope. The basis of this effect is very handy for other techniques such as trails on an object. We're going to use an image I stole from a demo as the source for the feedback, and create the effect on a texture surface which will finally be mapped onto a 3D cube using the cube class we created in DirectX Basics Series 3 Tutorial 5. I like cubes.

Here's the steps we're going to go through. First, we need to load our source texture, the one we're going to create the effect with. We then need to create 2 extra texture surfaces with the same dimensions as the source image, which we use for the feedback effect. We designate one of those surfaces as the accumulator surface, and the other as the scratch surface. We then make a quad, texturemap it with the accumulator surface and, using orthographic projection to ensure it's pixel size is the same as the source image, render it onto the scratch surface with a slight rotation and increase in size. The rotation and size increase are necessary to produce the rotozoomer effect - it wouldn't be much of an effect otherwise! Next, we render a small version of the original image onto the middle of the scratch surface. This is kind of a "recharge" for our effect - because we scale the accumulated texture up each frame, we need to ensure that the effect is being fed with new images to scale up for later frames. Finally, we render the whole lot back to the accumulator texture with no changes. Technically we could skip this step and use a pointer to switch between the 2 textures so we know which one holds the current image, but doing it this way is more clear for a tutorial. Once it's on the accumulator surface, we're free to do whatever we want with it - in our case, we'll use it for the texturemap of our cube. Here's some faked-up screenshots of the steps we'll take:


Fig 1.2: Theory behind effect

First of all, we have the source texture plus 2 empty textures. The effect will fill the textures for us as we progress.


Fig 1.3: Theory behind effect


The first action is to use orthographic projection to render a quad - the same pixel size as the texture surface - using the accumulation texture as a texturemap onto the scratch texture. We apply a slight rotation matrix for this render, but because this is our first iteration there is nothing on the accumulator texture, therefore nothing will show up on the scratch texture. Next we render a quad, texturemapped with the source texture, with a scaling matrix to reduce it's size onto the scratch surface. As above, this is our "recharge" for the effect.


Fig 1.4: Theory behind effect


The final step is to render a quad - again, the same pixel size as the texture surface using orthographic projection - texturemapped with the scratch texture back onto the accumulation texture. We then wipe the scratch texture clear, ready for the next iteration.


Fig 1.5: Theory behind effect


Here we can start to see the effect appearing. I've split these steps up for clarity. On our second iteration, we have something on the accumulation texture. We apply a slight rotation and scale-up, and using the quad with orthographic projection we render it onto the scratch texture.


Fig 1.6: Theory behind effect


Again, we render a quad, texturemapped with the source texture, with a scaling matrix to reduce it's size onto the scratch surface. See how the effect is appearing?



Fig 1.7: Theory behind effect


Just as before, we use the quad with orthographic projection to render the scratch texture back onto the accumulation texture. The scratch texture is then wiped clean for the next iteration.


Fig 1.8: Theory behind effect

And here we go again. The accumulation texture is rendered back onto the scratch texture with a slight rotation, and the original texture is scaled down and stuck in the middle. This cycle repeats itself over and over, producing the rotozoomer effect.

As you've no doubt noticed, the image in the scratch texture walk-through above looks nothing like the texture on the cube in fig1.1. And yes, if you check the .bmp inside the workspace zip (link below), it is the same image. So what's different?

Two things. The first is filtering, and the second is alphablending. Both of these topics are tutorials in their own right, so I'll just quickly explain here. There are various types of filtering, and if you've played any recent games you'll probably have heard of them - bilinear filtering, trilinear filtering, anisotropic filtering etc. These are all different ways of smoothing out textures when they are manipulated, by scaling for example. In this tutorial we use bilinear filtering, which acts on each pixel in the texture. In simple terms, after you've manipulated a texture by rotating or scaling it, D3D attempts to find the closest match to the actual pixel it's working on, and then takes an average of the pixels immediately to the left, right, top and bottom of it. It then uses the resulting RGB value as the new colour for this texel (texture pixel). This smooths out any jagged or hard artifacts produced by manipulating the texture. It also has the side effect of slightly blurring the image, so it won't look as sharp as it did originally. That just leaves alphablending. Alphablending is an extremely powerful technique that allows you to add transparency and other effects to your D3D app. There are 2 ways of using alphablending, either as a renderstate operation where it applies to everything being rendered, or as a texture stage state operator. For this tutorial, we'll be using alphablending as a renderstate operation to give us a transparent blend between each effect stage on the scratch surface. As a side note, we discussed texture stage states in DirectX Basics Series 3 Tutorial 4. This is only the briefest of explanations of these 2 techniques, and I'll post a tutorial covering these topics in depth at a later date. Anyway, suffice to say that these 2 techniques together change the effect from a clear set of stages to a nicely blended and blurred appearance.

We've very quickly covered the theory behind these effects, so it's time to get down to the code. There is one change to the tutorial source here, and that's the actual amount of code I'm including with this tutorial. You're out of the basics series now, and it's probably a good time to get used to a lot of source across multiple files in your D3D apps. For the actual tutorial code though, it's all contained in just one file - the other source files are classes and code we've already been through, such as the timer and font rasteriser code.

[ Download complete VC6 workspace as .zip ]

RenderToTexture.cpp
[ View source code in this window ] [ View source code in a new window ] [ Source code in plain ascii .cpp file ]

Before we start, make sure you understand what we're trying to do and the steps we'll take to do it, because it's pretty easy to get confused about what we're rendering where! I've put a lot of comments and checkpoints into the code itself, but I won't copy all the comments here. Time to get started, and first up are the global declarations:

LPDIRECT3DTEXTURE8 g_pTextureOriginal;
LPDIRECT3DTEXTURE8 g_pTexture1;
LPDIRECT3DTEXTURE8 g_pTexture2;
LPDIRECT3DSURFACE8 g_pTextureSurface1;
LPDIRECT3DSURFACE8 g_pTextureSurface2;

D3DXIMAGE_INFO g_ImageInfoOriginal;

LPDIRECT3DVERTEXBUFFER8 g_pVertexBuffer;

typedef struct _tagTextureVertex
{
    D3DVECTOR vPos;
    D3DCOLOR dwDiffuse;
    float tu, tv;
} TEXTUREVERTEX;

#define FVF_TEXTUREVERTEX (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1 | D3DFVF_TEXCOORDSIZE2(1))

CD3DCube g_Cube;
CD3DTimer g_FPSTimer;
CD3DRasterFont g_FPSFont;


Most of that is simple stuff we've seen before. There are a couple of things to note, though. First of all, I've created 2 LPDIRECT3DSURFACE8 objects. As you'll see in a moment, these will be paired with our LPDIRECT3DTEXTURE8 textures. I also create a D3DXIMAGE_INFO var. This is a struct provided by the D3DX library that we can use to hold information about a texture - as we discussed above, we need to make our accumulation and scratch textures the same size as the source image. Here's it's definition:

typedef struct _D3DXIMAGE_INFO { UINT Width;
                                 UINT Height;
                                
UINT Depth;
                                 UINT MipLevels;
                                
D3DFORMAT Format;
                                
D3DRESOURCETYPE ResourceType;
                                
D3DXIMAGE_FILEFORMAT ImageFileFormat;
                              
} D3DXIMAGE_INFO;

It's a pretty handy struct, and when filled out by D3DX it gives us everything we need to know about an image. Other than that, everything else is old news to us, as experienced D3D coders ;) Let's move on to the initialisation in our Gameinit() function.

g_pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
g_pDevice->SetRenderState(D3DRS_LIGHTING, FALSE);

g_pDevice->SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR);
g_pDevice->SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR);
g_pDevice->SetTextureStageState( 0, D3DTSS_MIPFILTER, D3DTEXF_LINEAR);


The first 2 statements you've seen before, and we're just setting CCW culling and turning lighting off. The next 3 statements turn on the bilinear filtering we discussed earlier. D3DTSS_MAGFILTER refers to the operation we want to use for our texture magnification filter. Similarly, D3DTSS_MINFILTER refers to the operation we want to use for our texture minification (that really shouldn't be a word) filter. D3DTSS_MIPFILTER refers to the filter we want to use to interpolate between mipmap levels (we discussed mipmapping in the texturemapping tutorial). We don't use mipmaps in this tutorial, so technically this code isn't required. I just included it for completeness.

rslt=D3DXCreateTextureFromFileEx(g_pDevice, "ball.bmp", D3DX_DEFAULT, D3DX_DEFAULT, 1, 0, D3DFMT_UNKNOWN, D3DPOOL_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT, D3DCOLOR_XRGB(0,0,0), &g_ImageInfoOriginal, NULL, &g_pTextureOriginal);
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Could not create texture"); }

rslt=D3DXCreateTexture(g_pDevice, g_ImageInfoOriginal.Width, g_ImageInfoOriginal.Height, 1, D3DUSAGE_RENDERTARGET, g_ImageInfoOriginal.Format, D3DPOOL_DEFAULT, &g_pTexture1);
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Could not create texture 1"); }

rslt=D3DXCreateTexture(g_pDevice, g_ImageInfoOriginal.Width, g_ImageInfoOriginal.Height, 1, D3DUSAGE_RENDERTARGET, g_ImageInfoOriginal.Format, D3DPOOL_DEFAULT,&g_pTexture2);
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Could not create texture 2"); }


Again, nothing new here. We use D3DXCreateTextureFromFileEx() to create a texture surface and load our source image onto it. The only notable parts of this code is where I've specified 1 for the 5th parameter (number of mipmaps) - specifying 0 causes a complete mipmap chain to be created. I've also put the address of our D3DXIMAGE_INFO var into parameter 12, so D3DX will fill it out with information about our source image. The next 2 calls use D3DXCreateTexture() to create 2 more texture surfaces with the same width, height and format as our source bitmap, using the information D3DX stored in g_ImageInfoOriginal for us.

rslt=g_pTexture1->GetSurfaceLevel(0, &g_pTextureSurface1);
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Could not get the texture 1 surface level."); }

rslt=g_pTexture2->GetSurfaceLevel(0, &g_pTextureSurface2);
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Could not get the texture 2 surface level."); }


These 2 lines of code are necessary to allow us to use a texture as a rendertarget. D3D only allows you to set the render target to IDirect3DSurface8 surfaces, and of course our textures are actually IDirect3DTexture8 surfaces. Calling the GetSurfaceLevel() method of the IDirect3DTexture8 interface returns a pointer to an IDirect3DSurface8 surface, so it's kind of like casting a pointer, only different ;). Here's it's prototype:

HRESULT GetSurfaceLevel(UINT Level, IDirect3DSurface8** ppSurfaceLevel);


The Level parameter is an interesting one, and I'm going to take a quick detour back to texturemapping to explain it. As you should know from the texturemapping tutorial, you can create multiple versions of your texture image at different sizes and, depending on the distance of the object being texturemapped from the camera, D3D selects the best version of the image to use. Using multiple images (mipmaps) in this way greatly speeds up texturemapping, as it removes a lot of the need for image rescaling. When you create your texture, you can specify how many mipmaps you want to create - this is done differently depending on whether you create your texture surface with D3DX or directly with your IDirect3DDevice8 interface. Whichever method you choose, D3D creates a new surface (called a "level") for each mipmap required, and attaches them to the "parent" surface - the original texture. In addition, the parent surface is considered to be mipmap level 0, therefore a texture with 2 mipmaps will have 3 surface levels. The interesting thing about all this is that you can actually get a pointer to an individual mipmap's surface using the GetSurfaceLevel() method. Anyway, for this tutorial we're retrieving a pointer to surface level 0, which is the main (and only because we didn't create any mipmaps!) texture surface. We'll be using this for setting our rendertargets shortly.

rslt=g_pDevice->SetRenderTarget(g_pTextureSurface1, NULL);
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Could not SetRenderTarget()."); }

g_pDevice->Clear(0,0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0);


Because we don't clear the accumulator surface in the main Render() function, we need to clear it here during initialisation, otherwise it's likely to be full of junk data. It's also our first look at how to change the rendertarget - it's as simple as I promised. Here's the SetRenderTarget() prototype:

HRESULT SetRenderTarget(IDirect3DSurface8* pRenderTarget, IDirect3DSurface8* pNewZStencil);


Parameter one is a pointer to the IDirect3DSurface8 you wish to set as the rendertarget. Parameter two is a pointer to a depth buffer stencil surface. Depth buffering and stencil surfaces are something we're going to cover in a later tutorial, so for now just accept that it's something D3D manages for us automatically (we actually tell it do do this by setting the EnableAutoDepthStencil member of our D3DPRESENT_PARAMETERS struct to TRUE in our InitDirect3DDevice() function). That's all there is to setting a new rendertarget, and from now on all rendering and operations will be performed on the g_pTexture1 texture. In this case we're doing some bad practice, and leaving the rendertarget set to g_pTexture1 after we've cleared it. I'm only doing this for simplicity, because I know that the first operation in the Render() function will be to set the rendertarget again. If you're going to turn this code into a class based system, remember that it's good practice to leave the various render states as you found them!

TEXTUREVERTEX Vertices[4];

Vertices[0].vPos=D3DXVECTOR3( -0.5f, -0.5f, 0.0f);
Vertices[1].vPos=D3DXVECTOR3( -0.5f, 0.5f, 0.0f);
Vertices[2].vPos=D3DXVECTOR3( 0.5f, -0.5f, 0.0f);
Vertices[3].vPos=D3DXVECTOR3( 0.5f, 0.5f, 0.0f);


Next up in our initialisation, we create a quad centered in object space with vertices specified in a clockwise order (for CCW culling). We'll be using this quad for our render-to-texture. The interesting thing about this quad is it's dimensions - it's exactly 1 unit by 1 unit, and there is a good reason for this. A little bit above here we discussed how orthographic projection causes foreshortening of the Z axis, which means we can work on a 1:1 worldspace to pixel ratio. We've also discussed that our accumulator and scratch textures need to be the same size as our source image for the purposes of this effect. As you've probably figured out by now, we also need to be able to render a quad with exact pixel dimensions that are the same as our source image - if they're not, D3D will scale the texture to fit and screw up the effect. Because we don't want to hard code the size of the quad to the source image's dimensions (for example, if we wanted to change the ball.bmp texture included in the workspace), we need to ensure the quad is correctly sized a different way. I generally do this by giving it dimensions of 1 unit by 1 unit. Obviously with ortho projection, this would cause the quad to be exactly 1 pixel high by 1 pixel wide, so I apply a scaling matrix as part of the world transformation. By creating the scaling matrix and specifying the X and Y scale-up factor to be the X and Y dimensions of the source image, the quad will be automagically sized correctly (do the math, 1*1 unit quad becomes a 1*1 pixel quad with orthogonal projection, which when scaled up by the source image dimensions of 128*128 becomes a correctly sized 128*128 pixel quad!).

I'm going to skip the rest of the initialisation code, and move directly to the Render() function which is where all the action takes place. You've seen the remaining code before anyway, we just complete the vertex array for our quad and copy it into a vertex buffer, set up our cube using the CD3DCube class we wrote here, set up our bitmap font to display the FPS counter, and finally set up the timer to actually count the FPS!

This is where things get a little confusing, so read on carefully. From here, we'll be creating the effect. Using the definitions above (shown in Fig1.2 to Fig1.8), g_pTexture1 will be our accumulator texture, and g_pTexture2 will be our scratch texture. g_pTextureSurface1 will be the IDirect3DSurface8 interface pointer for g_pTexture1, and g_pTextureSurface2 will be the IDirect3DSurface8 interface pointer for g_pTexture2. If that didn't make sense, go back to Fig1.2 and replace "Accumulation Texture" with "g_pTexture1 and g_pTextureSurface1", and replace "Scratch Texture" with "g_pTexture2 and g_pTextureSurface2". Make sense now? Good! As normal we'll skip past the initialisation code in Render() and get onto the interesting parts where we actually do something.

rslt=g_pDevice->SetRenderTarget(g_pTextureSurface2, NULL);
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Could not SetRenderTarget()."); }

g_pDevice->Clear(0,0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0);


The first thing to do is to set the rendertarget to the scratch texture, and clear it. We clear the scratch surface after every iteration as it will be receiving an enlarged, slightly rotated version of the accumulated image as discussed before.

D3DXMATRIX matOrtho;
D3DXMatrixOrthoLH(&matOrtho, (float)g_ImageInfoOriginal.Width, (float)g_ImageInfoOriginal.Height, 1.0, 10.0);
rslt=g_pDevice->SetTransform(D3DTS_PROJECTION, &matOrtho);
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Could not set the projection transform."); }


This is a really important bit of code, and creates our ortho projection setup. Here's the prototype for D3DXMatrixOrthoLH(), which actually creates the matrix we use to set our projection transform:

D3DXMATRIX* D3DXMatrixOrthoLH( D3DXMATRIX* pOut,
                               FLOAT w,
                               FLOAT h,
                               FLOAT zn,
                               FLOAT zf
                              );

You can probably guess the parameters. Parameter one is a pointer to a D3DXMATRIX struct that will be filled out with the ortho projection transformation matrix values. Parameters 2 and 3 are the width and height of the "view volume". We haven't really discussed the viewing volume (also known as the view frustrum), but in essence it controls the area we have available to render in - anything outside this area will not be drawn (technically called "clipped"). In this instance, because we want to be able to use the complete texture surface for drawing on (of our scratch and accumulation textures) we need a view volume the same size as our source texture, so we simply use our g_ImageInfoOriginal struct (remember the D3DXIMAGE_INFO var we passed to D3DXCreateTextureEx()?). Parameters 4 and 5 are the near and far clip planes, which we can just set to something sensible - as we're not using Z axis vertices, it doesn't matter.

Once we've used D3DXMatrixOrthoLH() to create our orthogonal projection matrix, it's just a matter of dropping it into the transformation pipeline using IDirect3DDevice8::SetTransform(). We've seen this code before only with a different view matrix, so there's nothing new here. From this point on, we now know that one unit in world space equals one pixel on screen. I'm also going to cut out some of the comments to save space here.

D3DXMATRIX matScaleUp, matRotation, matFinal;

// To create some funky interferance/trails, we're going to render the accumulated texture
// back with a slight rotation around the Z axis. Oh dear, it's a static var!

static float fRotation=0.0f;
D3DXMatrixRotationZ(&matRotation, D3DXToRadian(fRotation-=0.09f));

D3DXMatrixScaling(&matScaleUp, (float)g_ImageInfoOriginal.Width*(float)1.4, (float)g_ImageInfoOriginal.Height*(float)1.4, 1.0f);

// Finally multiply the rotation and scaling matrices together.

D3DXMatrixMultiply(&matFinal, &matRotation, &matScaleUp);


Here's some bad code :). First up we create 3 D3DXMATRIX vars - we'll use these for our transformations in the rest of the code. Next, we create a static var for our rotation. There are much better ways to do this, but I'm fairly lazy :). Every iteration of the Render() function will decrement the var by 0.09f, which is just a small anticlockwise rotation. Note that D3DXMatrixRotationZ() takes a parameter of an angle in radians, not of degrees. Personally I like working in degrees, so I used the very handy D3DXToRadian() macro which does what it says on the tin - takes an angle in degrees and converts it to radians. This is the rotation we'll be using to achieve Fig1.5 above - the accumulation texture rendered with a rotation to the scratch texture.

Next we create our scale-up matrix, also used to achieve Fig1.5. We discussed why we need this scale-up above (the 1.4f scale up), and don't forget we still need to scale up by the pixel dimensions of the source image as well, otherwise we'll be left with a 1*1 pixel quad! I'm scaling up by a factor 1.4, as it seemed like a nice value to use - the larger the scale factor the less iterations appear on the accumulator, try increasing the scale up to see what I mean. Finally, now that we've got our rotation and scale-up matrices, we multiply them together using D3DXMatrixMultiply() for use in the world transformation. If you're not sure why or how we multiply matrices together, it's probably time to read the 32Bits Cheaters Guide to 3D Maths.

rslt=g_pDevice->SetTransform(D3DTS_WORLD, &matFinal);
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Could not set the world transform."); }

// Set up our quad for rendering, this is just the normal code.
rslt=g_pDevice->SetStreamSource(0, g_pVertexBuffer, sizeof(TEXTUREVERTEX));
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "SetStreamSource() failed."); }

rslt=g_pDevice->SetVertexShader(FVF_TEXTUREVERTEX);
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "SetVertexShader() failed.");

rslt=g_pDevice->SetTexture(0, g_pTexture1);
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "SetTexture() failed."); }

rslt=g_pDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "DrawPrimitive() failed."); }


Okay, now that we've got our transformation matrix sorted we need to texturemap our quad with the accumulator texture, and render it onto the scratch texture as per Fig1.5. We've been through all this code many, many times so I won't recap it in detail here. Remembering that we previously set the rendertarget to our scratch surface, we now set the world transform to our combined scale-up and rotation matrix, the stream source, the shader and the texture, then just render our quad as a tristrip. Easy stuff for you now!

g_pDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
g_pDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCCOLOR);
g_pDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCCOLOR);

D3DXMatrixScaling(&matScaleUp, g_ImageInfoOriginal.Width/5.0f, g_ImageInfoOriginal.Height/5.0f, 1.0);
D3DXMatrixMultiply(&matFinal, &matRotation, &matScaleUp);
rslt=g_pDevice->SetTransform(D3DTS_WORLD, &matFinal);
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Could not set the world transform."); }

rslt=g_pDevice->SetTexture(0, g_pTextureOriginal);
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "SetTexture() failed."); }

rslt=g_pDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "DrawPrimitive() failed."); }

g_pDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);


I've put some progress check comments in the code for you, but I've removed them here for space reasons. At this point we're going to render a scaled down version of the original image onto the scratch surface, to achieve Fig1.6. The first thing we do here is to enable alphablending, and set it's parameters. Enabling alphablending is easy enough, and is done via the IDirect3DDevice8::SetRenderState() method, specifying D3DRS_ALPHABLENDENABLE as the state type we're changing. Note that it's disabled by default, so don't forget to do this! Alphablending is a fairly big subject, and we'll cover it in depth in a later tutorial. So for the moment, I'll just explain what those 3 lines of code do, rather than how they do it. Quite simply, we're telling D3D how to merge every pixel we're drawing (ie: the pixels that make up our quad) with what's already on the surface we're drawing to. First, we tell D3D to use the source pixel colour as the blend source, unmodified. Thats the (D3DRS_SRCBLEND, D3DBLEND_SRCCOLOR) part. Next we tell D3D to use the inverse colour of the source pixel, and blend it with the existing destination pixel colour. This produces a neat semi-transparent effect, and causes our quad (texturemapped with our original image) to allow some of the image on the scratch texture to show through. Try removing this alphablending code and see if you can spot the difference - it's even more apparent if you also remove the code in GameInit() that sets up the bilinear filtering. If you're really interested in how this works, and you can't wait for the tutorial, have a read of the DirectX SDK (search for "Alpha blending state") - you can find a complete explanation plus all the equasions used in there.

As I've said elsewhere, there are 2 different types of alphablending. The alphablending we're using here is a kind of "global" alphablend, because once enabled it's applied to every single pixel rendered through the normal T&L pipeline. The second type is texture alphablending, and is done via texture stage states. This type of alphablending is pretty powerful, as it's done on a per-vertex basis rather than a blanket every-pixel-in-the-pipeline basis. We discussed how texture stages work in this tutorial, and we'll be coming back to alphablending in a later tutorial too.


So that's the alphablending, what about the rest? As you can probably see, it's the same old code again. We change our scale-up matrix to actually scale the original image down (guess it should be a scale-down matrix now!), then re-calculate our final matrix before plugging it into the world transform. We then set the texture to the original source image, and render our quad. That's Fig1.6 completed. Note that I've disabled alphablending immediately after drawing the quad - this is really important, because alphablending is GPU intensive and will reduce our framerate substantially if left enabled when we don't need it.

rslt=g_pDevice->SetRenderTarget(g_pTextureSurface1, NULL);
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Could not SetRenderTarget()."); }

// Remember, no changes! We just need the scaling matrix to scale up our 1*1 quad to the same size
// as our source image

D3DXMatrixScaling(&matFinal, (float)g_ImageInfoOriginal.Width, (float)g_ImageInfoOriginal.Height, 1.0);
rslt=g_pDevice->SetTransform(D3DTS_WORLD, &matFinal);
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Could not set the world transform."); }

rslt=g_pDevice->SetStreamSource(0, g_pVertexBuffer, sizeof(TEXTUREVERTEX));
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "SetStreamSource() failed."); }

rslt=g_pDevice->SetVertexShader(FVF_TEXTUREVERTEX);
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "SetVertexShader() failed.");

rslt=g_pDevice->SetTexture(0, g_pTexture2);
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "SetTexture() failed."); }

rslt=g_pDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "DrawPrimitive() failed."); }


Our last job to complete the effect is to render the scratch texture back onto the accumulator surface unchanged, as per Fig1.7. We set the rendertarget to our accumulator texture, and reset our scale-up matrix for the world transform. Technically we don't need to set our stream source and vertex shader again, but I've done it anyway for completeness. D3D will reject the calls as "redundant" anyway, so it won't impact our app's speed. Finally we render a quad back onto the accumulator surface, textured with the scratch texture. And that's Fig1.7 done!

D3DXMATRIX matProjection;
ZeroMemory(&matProjection, sizeof(matProjection));

// Use D3DX to create a left handed cartesian Field Of View transform
D3DXMatrixPerspectiveFovLH(&matProjection, D3DX_PI/4, g_D3DSettings.m_fScreenAspect, 1.0f, 100.0f);

// Tell D3D to use our Projection matrix for the projection transformation stage
rslt=g_pDevice->SetTransform(D3DTS_PROJECTION, &matProjection);
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Failed to set Projection Transform."); }


At this point our effect is complete (and stored on our accumulator surface, g_pTexture1). We just need to reset all our changes back to the defaults before we use our texture for something. First up is to get rid of the orthogonal projection transformation, and replace it with our usual left hand cartesian FOV transform. I've just copied and pasted this code from our GameInit() function, so there is absolutely nothing new here.

rslt=g_pDevice->SetRenderTarget(g_pBackSurface, NULL);
if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Could not SetRenderTarget()."); }


This one deserves it's own section, because I totally forgot to do this. If you don't set the rendertarget back to the backbuffer, you'll still be left rendering to the accumulator surface. That means the backbuffer will remain empty and therefore nothing will show up on screen when you call IDirect3DDevice8::Present(). Doh!

The remainder of the code in the tutorial is all old stuff we've seen before - we just create a rotation matrix for our cube, and call CD3DCube::Render() to draw it on-screen. Add our FPS counter, and we're all done! And that, ladies and gentlemen, is how you create a trails effect using orthogonal projection and render to texture techniques!

Now would be a good time to show you why this is so important. You may be looking at this tutorial and thinking "ok, it's a nice effect but nothing earthshattering - why does he keep saying it's so important?". Fair enough, let me prove it to you. Take a look at this:


Fig 1.9: Scalimapping.

Pretty damn kewl, huh! This was coded by Scali, and looks more impressive when you can see it running. Scali has kindly provided an exe built specially for 32Bits, so go ahead and download it and see for yourself:

[ Download Scali's Demo ]


You'll agree that it's damn nice. The interesting thing about it is that it uses something called "envmapping", or "environment mapping", which is a technique that uses rendering to textures extensively. And in one screenshot and one exe, I've hopefully convinced you that ortho projection and render to texture is very useful and very important. We'll be covering envmapping in a later tutorial ;)

There is only one exercise for this tutorial, and it's as hard as you want to make it:

Exercise 1: Have a think about all the things these techniques could be used for, and try to code some of them yourself. Rotozoomers, infinite reflections, mirrors and trails are just a couple of ideas for you.

Have fun, and I'll see you in the next tutorial!