News
DirectX
Links
Contact Me

In Association with Amazon.com
In Association with Amazon.ca

$5 via PayPal


Direct3D Lesson 7



Direct3D Lesson 7:Texture Filters, Lighting & Keyboard Control
For lighting to work properly we need to redefine our vertex structure, and that means the vertex 'macro' has to change as well as the definition of our cube.
//For the lighting to work properly, we have to add a 'normal' to the vertices.  A normal
//basically shows the direction that it faces.
struct my_vertex{
    FLOAT x, y, z;   // The untransformed position for the vertex.
    FLOAT nx,ny,nz;
    DWORD colour;
    FLOAT tu, tv;    // The texture coordinates
};

//A handy little 'macro' for our definition of the vertex.  When we use the vertex data
//we have to tell D3D what data we're passing it.The D3DFVF_XYZ specifies that the vertex
//will have coordinate given in model space. D3DFVF_TEX1 specifies that 1 set of texture
//coordinates will be provided as well.  D3DFVF_NORMAL indicates the presence of a normal
//for each vertex. D3DFVF_DIFFUSE indicates that a diffuse colour is part of the vertex as
//well, when lighting is turned off, the DIFFUSE colour will be used for lighting.
#define D3D8T_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_TEX1|D3DFVF_NORMAL|D3DFVF_DIFFUSE)

//The normal is a 3 component vector (x,y,z) which indicates the direction that the vertex
//faces.  For example, on the front face of our cube, the normal is set to (0,0,-1) which is
//pointing directly along the Z-axis towards the screen.  The back face is set to (0,0,1)
//which is pointing in the opposite direction.
my_vertex g_vertices[] ={
   {-1.0f,-1.0f,-1.0f,   0.0f, 0.0f,-1.0f,   0xFFFFFFFF, 0.0f, 1.0f },  //Front face
   {-1.0f, 1.0f,-1.0f,   0.0f, 0.0f,-1.0f,   0xFFFFFFFF, 0.0f, 0.0f },
   { 1.0f, 1.0f,-1.0f,   0.0f, 0.0f,-1.0f,   0xFFFFFFFF, 1.0f, 0.0f },
   { 1.0f, 1.0f,-1.0f,   0.0f, 0.0f,-1.0f,   0xFFFFFFFF, 1.0f, 0.0f },
   { 1.0f,-1.0f,-1.0f,   0.0f, 0.0f,-1.0f,   0xFFFFFFFF, 1.0f, 1.0f },
   {-1.0f,-1.0f,-1.0f,   0.0f, 0.0f,-1.0f,   0xFFFFFFFF, 0.0f, 1.0f },

   { 1.0f,-1.0f, 1.0f,   0.0f, 0.0f, 1.0f,   0xFFFFFFFF, 0.0f, 1.0f },  //Back face
   { 1.0f, 1.0f, 1.0f,   0.0f, 0.0f, 1.0f,   0xFFFFFFFF, 0.0f, 0.0f },
   {-1.0f, 1.0f, 1.0f,   0.0f, 0.0f, 1.0f,   0xFFFFFFFF, 1.0f, 0.0f },
   {-1.0f, 1.0f, 1.0f,   0.0f, 0.0f, 1.0f,   0xFFFFFFFF, 1.0f, 0.0f },
   {-1.0f,-1.0f, 1.0f,   0.0f, 0.0f, 1.0f,   0xFFFFFFFF, 1.0f, 1.0f },
   { 1.0f,-1.0f, 1.0f,   0.0f, 0.0f, 1.0f,   0xFFFFFFFF, 0.0f, 1.0f },

   {-1.0f, 1.0f,-1.0f,   0.0f, 1.0f, 0.0f,   0xFFFFFFFF, 0.0f, 1.0f },  //Top face
   {-1.0f, 1.0f, 1.0f,   0.0f, 1.0f, 0.0f,   0xFFFFFFFF, 0.0f, 0.0f },
   { 1.0f, 1.0f, 1.0f,   0.0f, 1.0f, 0.0f,   0xFFFFFFFF, 1.0f, 0.0f },
   { 1.0f, 1.0f, 1.0f,   0.0f, 1.0f, 0.0f,   0xFFFFFFFF, 1.0f, 0.0f },
   { 1.0f, 1.0f,-1.0f,   0.0f, 1.0f, 0.0f,   0xFFFFFFFF, 1.0f, 1.0f },
   {-1.0f, 1.0f,-1.0f,   0.0f, 1.0f, 0.0f,   0xFFFFFFFF, 0.0f, 1.0f },

   { 1.0f,-1.0f,-1.0f,   0.0f,-1.0f, 0.0f,   0xFFFFFFFF, 0.0f, 1.0f },  //Bottom face
   { 1.0f,-1.0f, 1.0f,   0.0f,-1.0f, 0.0f,   0xFFFFFFFF, 0.0f, 0.0f },
   {-1.0f,-1.0f, 1.0f,   0.0f,-1.0f, 0.0f,   0xFFFFFFFF, 1.0f, 0.0f },
   {-1.0f,-1.0f, 1.0f,   0.0f,-1.0f, 0.0f,   0xFFFFFFFF, 1.0f, 0.0f },
   {-1.0f,-1.0f,-1.0f,   0.0f,-1.0f, 0.0f,   0xFFFFFFFF, 1.0f, 1.0f },
   { 1.0f,-1.0f,-1.0f,   0.0f,-1.0f, 0.0f,   0xFFFFFFFF, 0.0f, 1.0f },

   {-1.0f,-1.0f, 1.0f,  -1.0f, 0.0f, 0.0f,   0xFFFFFFFF, 0.0f, 1.0f },  //Left face
   {-1.0f, 1.0f, 1.0f,  -1.0f, 0.0f, 0.0f,   0xFFFFFFFF, 0.0f, 0.0f },
   {-1.0f, 1.0f,-1.0f,  -1.0f, 0.0f, 0.0f,   0xFFFFFFFF, 1.0f, 0.0f },
   {-1.0f, 1.0f,-1.0f,  -1.0f, 0.0f, 0.0f,   0xFFFFFFFF, 1.0f, 0.0f },
   {-1.0f,-1.0f,-1.0f,  -1.0f, 0.0f, 0.0f,   0xFFFFFFFF, 1.0f, 1.0f },
   {-1.0f,-1.0f, 1.0f,  -1.0f, 0.0f, 0.0f,   0xFFFFFFFF, 0.0f, 1.0f },

   { 1.0f,-1.0f,-1.0f,   1.0f, 0.0f, 0.0f,   0xFFFFFFFF, 0.0f, 1.0f },  //Right face
   { 1.0f, 1.0f,-1.0f,   1.0f, 0.0f, 0.0f,   0xFFFFFFFF, 0.0f, 0.0f },
   { 1.0f, 1.0f, 1.0f,   1.0f, 0.0f, 0.0f,   0xFFFFFFFF, 1.0f, 0.0f },
   { 1.0f, 1.0f, 1.0f,   1.0f, 0.0f, 0.0f,   0xFFFFFFFF, 1.0f, 0.0f },
   { 1.0f,-1.0f, 1.0f,   1.0f, 0.0f, 0.0f,   0xFFFFFFFF, 1.0f, 1.0f },
   { 1.0f,-1.0f,-1.0f,   1.0f, 0.0f, 0.0f,   0xFFFFFFFF, 0.0f, 1.0f },

};

This is the first tutorial that has some user interactivity in it. We need a few variables to track our various states (spin speed, Z location)
//Our rotation speed for the X-axis & Y-axis.  Initially 0.
float x_speed=0.0f;
float y_speed=0.0f;

//Initial position on the Z-axis
float z_pos=0.0f;

Lighting can be turned off and on in this tutorial and the user can change the type of texture filtering being used, so we add a couple variables to track the current state.
//What kind of texture filtering should we use?
DWORD filter=0;

//Track whether lighting is on or off.
BOOL lighting_on=TRUE;

Since we're adding lighting, we need a light.
//Our one lonely light
D3DLIGHT8 light;

In our init_scene function we set up our light parameters.
   //Zero out our light structure and fill in the fields we need
   ZeroMemory( &light, sizeof(D3DLIGHT8) );

   //A point light shines in all directions and it's position is given in world coordinates.
   light.Type=D3DLIGHT_POINT;
   //Set what colour the light is.
   light.Diffuse.r=1.0f;
   light.Diffuse.g=1.0f;
   light.Diffuse.b=1.0f;
   //The light will not illuminate objects out of this range.  This value is a lot larger
   //than is necessary, but it doesn't hurt.
   light.Range=1000.0f;
   //This positions the light at the same location as our camera.
   light.Position=D3DXVECTOR3(0.0f,0.0f,-8.0f);
   //This controls how the light 'falls off' or has less intensity as the distance increases.
   //For now we set it so it's at full intensity for its full range (no fall off).
   light.Attenuation0=1.0f;

   //When dealing with lights in D3D you often refer to them by an light index.  This call
   //to SetLight associates our light with index 0.
   g_d3d_device->SetLight( 0, &light );

   //Enable our light.  Note this doesn't turn on lighting, it states that this light should be
   //used when lighting is on.
   g_d3d_device->LightEnable( 0, TRUE);

Also in init_scene we set our texture stages to allow lighting.
   //D3DTSS_COLOROP specifies how the colour of each pixel will be determined
   //In this case it's set to D3DTOP_MODULATE which means the colour is determined
   //by combining COLORARG1 & COLORARG2.
   g_d3d_device->SetTextureStageState(0,D3DTSS_COLOROP,D3DTOP_MODULATE);

   //D3DTSS_COLORARG1 is set to D3DTA_TEXTURE which means that colour1 is
   //entirely taken from the texture and nothing else.  D3DTSS_COLORARG2
   //is set to D3DTA_CURRENT which will be our vertex colour if lighting
   //is turned off, or the result of the lighting if it is on.
   g_d3d_device->SetTextureStageState(0,D3DTSS_COLORARG1,D3DTA_TEXTURE);
   g_d3d_device->SetTextureStageState(0,D3DTSS_COLORARG2,D3DTA_CURRENT);

Still in init_scene (in a more serious project it would probably be a good idea to split this function up into a number of sub-functions, it's getting a bit big), we set up our Texture Filtering to None as default. The user can change this by hitting the 'F' key while the tutorial is running.
   //Initially set it to NONE.
   g_d3d_device->SetTextureStageState(0,D3DTSS_MAGFILTER,D3DTEXF_NONE);
   g_d3d_device->SetTextureStageState(0,D3DTSS_MINFILTER,D3DTEXF_NONE);

Our last change to init_scene. We turn on lighting and set the default (ambient) lighting level.
   //Turn on D3D lighting.
   g_d3d_device->SetRenderState(D3DRS_LIGHTING,TRUE);

   //Ambient light is the default lighting level of the area.  Anything not hit
   //by our light will have this level of lighting.
   g_d3d_device->SetRenderState(D3DRS_AMBIENT,0x00050505);

There are a number of changes to our render function. Like init_scene it would be a good idea to split this one up into a few different functions, but to keep things simple I'll resist the urge. Our first change is to declare the matrices we need.
D3DXMATRIX rot_x_matrix;   //Our rotation matrix for the x-axis
D3DXMATRIX rot_y_matrix;   //Our rotation matrix for the y_axis
D3DXMATRIX rot_matrix;     //Final rotation matrix
D3DXMATRIX trans_matrix;   //Our translation matrix

We have to take into account that our user now controls the rotation speed, so we need the following change.
   rot_x+=x_speed;
   rot_y+=y_speed;

Farther down, between our BeginScene/EndScene, we build our matrices. Note that for the Translation matrix we use our global z_pos which is controlled by the user.
      //Set up the rotation matrix for the triangle
      D3DXMatrixRotationY(&rot_y_matrix,rot_y);
      D3DXMatrixRotationX(&rot_x_matrix,rot_x);

      //Combine the 2 matrices to get our final Rotation Matrix
      D3DXMatrixMultiply(&rot_matrix,&rot_x_matrix,&rot_y_matrix);

      //Translate out cube in/out of the screen
      D3DXMatrixTranslation(&trans_matrix,0.0f,0.0f,z_pos);

      //Build our final World Matrix
      D3DXMatrixMultiply(&matWorld,&rot_matrix,&trans_matrix);

      //Set our World Matrix
      g_d3d_device->SetTransform(D3DTS_WORLD,&matWorld );

We're finally nearing the end of our changes. We've covered a lot of ground, so don't feel bad if it all doesn't make perfect sense the first time you read it. Take a break, drink a SoBe, and tackle it again.

In our default_window_proc we previously quit whenever any key was hit. Now we actually care what key the user hit so first we have to declare a variable and trap the key.
int virt_key = (int) wparam; //Only valid if msg==WM_KEYDOWN

Now the case statement for WM_KEYDOWN is huge! There isn't much to it though. The first 4 cases are the arrow keys and we just modify our globals that track the current speed. The next 2 are the PageUp and PageDown keys, they move the cube along the Z-axis by modifying out global variable z_pos.

When the user hits the 'F' key we change the type of filtering we were using, and 'L' toggles lighting on and off.
      case WM_KEYDOWN:  // A key has been pressed, end the app
         switch(virt_key){
            case VK_UP:
               x_speed-=0.01f;
               break;
            case VK_DOWN:
               x_speed+=0.01f;
               break;
            case VK_LEFT:
               y_speed-=0.01f;
               break;
            case VK_RIGHT:
               y_speed+=0.01f;
               break;
            case VK_NEXT:
               z_pos+=0.02f;
               break;
            case VK_PRIOR:
               z_pos-=0.01f;
               break;
            case 'F':
               filter++;
               if(filter > 2)
                  filter=0;
               switch(filter){
                  case 0:
                     g_d3d_device->SetTextureStageState(0,D3DTSS_MAGFILTER, D3DTEXF_NONE);
                     g_d3d_device->SetTextureStageState(0,D3DTSS_MINFILTER, D3DTEXF_NONE);
                     break;
                  case 1:
                     g_d3d_device->SetTextureStageState(0,D3DTSS_MAGFILTER, D3DTEXF_POINT);
                     g_d3d_device->SetTextureStageState(0,D3DTSS_MINFILTER, D3DTEXF_POINT);
                     break;
                  case 2:
                     g_d3d_device->SetTextureStageState(0,D3DTSS_MAGFILTER, D3DTEXF_LINEAR);
                     g_d3d_device->SetTextureStageState(0,D3DTSS_MINFILTER, D3DTEXF_LINEAR);
                     break;
               }
               break;
            case 'L':
               lighting_on=!lighting_on;
               g_d3d_device->SetRenderState(D3DRS_LIGHTING,lighting_on);
               break;
            default:
               g_app_done=true;
               break;
         }
         return 0;

That's it! I think the last time we covered this much ground it was the first tutorial. If it doesn't make sense, feel free to post a question to the D3D8 Tutorial mailing list. (You have to join first though, do that here).