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).
|