News
DirectX
Links
Contact Me

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

$5 via PayPal


Direct3D Lesson 8


Direct3D Lesson 8:Alpha Blending (Vertex Alpha)

Since we're using the dhSimpleMesh class (discussed below) we have to add the #include for it.

#include "dhSimpleMesh.h"

We're using Lesson 7 as a base for this lesson, so make sure you understand everything in that lesson before you tackle this one.

Since we're going to set our diffuse colour in our vertices to the same value for each vertex, I defined a constant variable (or you could use a #define) and used that in each vertex. This way when I wanted to tweak the values I only had to change the value in one place.

In this case only the first part of the colour will be used, it's the alpha (transparency) value. Though it might be better to think of it as an opacity value since 0xFF (255) is fully opaque and 0 is fully transparent. 0x7F is 50% opaque/transparent.

//This is the diffuse colour that we'll set for each vertex in our cube.  The significant part
//is the 0x7F at the beginning, that's the alpha component and in this case it's set to 50%
//transparent.
const DWORD cube_colour=0x7FFFFFFF;

The vertices are the same as in Lesson 7, I'll include the definition of the first face just to illustrate how I used the cube_colour variable.

my_vertex g_vertices[] ={
   {-1.0f,-1.0f,-1.0f,   0.0f, 0.0f,-1.0f,   cube_colour, 0.0f, 1.0f },  //Front face
   {-1.0f, 1.0f,-1.0f,   0.0f, 0.0f,-1.0f,   cube_colour, 0.0f, 0.0f },
   { 1.0f, 1.0f,-1.0f,   0.0f, 0.0f,-1.0f,   cube_colour, 1.0f, 0.0f },
   { 1.0f, 1.0f,-1.0f,   0.0f, 0.0f,-1.0f,   cube_colour, 1.0f, 0.0f },
   { 1.0f,-1.0f,-1.0f,   0.0f, 0.0f,-1.0f,   cube_colour, 1.0f, 1.0f },
   {-1.0f,-1.0f,-1.0f,   0.0f, 0.0f,-1.0f,   cube_colour, 0.0f, 1.0f },

As I get through more lesson, the size of the code is growing. Since the lessons are getting more complex, that makes sense. However, having all this extra code that you've seen for the past 7 lessons can get in the way and distract you from the new material.

To make things clearer our Vertex Buffers will be handled by the dhPrimitive class. It's a fairly thin wrapper that makes it easy to set up your Vertex buffers with minimum fuss.

Also note that in this lesson we have 2 cubes. Our goal is to draw 1 textured, partially transparent cube. I considered doing a quick hack around the issue, but decided it would be best to do a 'proper' solution. So we have 2 cubes. An outer cube (identical to the one used in Lesson 7) and an inner cube that has it's normals reversed (so they face inwards).

As we saw in the previous lesson, faces get lit based on the 'normal' of their vertices. If they face the light directly they get the full amount of light, facing away they get none. If we just drew a single cube, the faces farthest away from the light would be facing away and thus get no light.

Another problem is with the Alpha Blending. When drawing transparent objects you have to draw them back-to-front. If you draw the front objects first, they have nothing to blend with. With a single cube you would have to determine which faces were at the front each frame (since it's rotating, that's constantly changing) and then draw them in that order.

Using 2 cubes solves both problems. We set culling so that the near faces of the inner cube will get culled (not drawn). So when we draw the inner cube only the far faces are drawn. They blend with the empty (black) background and are lit properly since the normals are reversed in that cube.

Then we reverse the culling and draw our outer cube. This time the far away (facing away) faces aren't drawn, only the near (facing) ones are. They blend properly with the already drawn inner cube and since they have normals facing outwards, it's faces get lit correctly.

Whoo. That was a lot in one go.

//Note:We're using the dhPrimitive class to wrap our Vertex Buffers.  Since the code
//to allocate and otherwise mess with VBs is in all of the previous examples I thought
//it would be nice to trim down the code where possible to focus on the new material.
//The dhPrimitive class is available at http://www.drunkenhyena.com/docs/dhPrimitive.phtml

//For this we need 2 cubes.  The inner cube will have the normals pointing inwards, so
//that we see the back of our cube, and the outer cube has them facing outwards.  Otherwise
//the lighting would be wrong.
dhSimpleMesh outer_cube(NUM_VERTICES,D3D8T_CUSTOMVERTEX,D3DPT_TRIANGLELIST,NUM_VERTICES/3);
dhSimpleMesh inner_cube(NUM_VERTICES,D3D8T_CUSTOMVERTEX,D3DPT_TRIANGLELIST,NUM_VERTICES/3);

In our init_scene procedure we have to set things up to enable Alpha Blending. Some the values I set below are the defaults, but I think it's best to always set an States that you need. Defaults can change, and code changes can know things out of whack.

   //Similarly to the COLOR TextureStageStates, the D3DTOP_SELECTARG1
   //says to take the result from arguement 1. We set D3DTSS_ALPHAARG1
   //to D3DTA_DIFFUSE, which makes the alpha value for each vertex come
   //completely from the diffuse colour of the vertex(which we set to 0x7f
   // or 50% transparent).
   g_d3d_device->SetTextureStageState(0,D3DTSS_ALPHAOP,D3DTOP_SELECTARG1);
   g_d3d_device->SetTextureStageState(0,D3DTSS_ALPHAARG1,D3DTA_DIFFUSE);

   //The next 2 RenderStates determine how the blending is done.
   //The source is our object, destination is the drawing surface.
   //SRCBLEND specifies that the final colour should be 50% (because
   //that's what we set our souce alpha to) from our source and 50%
   //(INVSRCALPHA, 100%-SRCALPHA(50% in this case))
   g_d3d_device->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA);
   g_d3d_device->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA);

   //This enables Alpha Blending
   g_d3d_device->SetRenderState(D3DRS_ALPHABLENDENABLE,TRUE);

Still in init_scene, we set up our outer cube:

   //Build our outer_cube
   outer_cube.SetDevice(g_d3d_device);

   //Allocate space for the cube
   if(!outer_cube.Alloc()){
      FatalError("Error creating outer_cube vertex buffer");
   }

   //Copy vertices into the cube
   outer_cube.Fill(&g_vertices);

Now that our outer cube is built, we reverse the normals in our vertex array so they can be used by the inner cube.

   //Change our normals to be their inverse (so they point the other way)
   int count;
   for(count=0;count < NUM_VERTICES;count++){

      g_vertices[count].nx*=-1.0f;
      g_vertices[count].ny*=-1.0f;
      g_vertices[count].nz*=-1.0f;

   }

Now using our freshly updated vertices we can build our inner cube:

   //Build our inner cube with our reversed normals
   inner_cube.SetDevice(g_d3d_device);

   if(!inner_cube.Alloc()){
      FatalError("Error creating inner_cube vertex buffer");
   }

   inner_cube.Fill(&g_vertices);

Our new kill_scene has to clean up our cubes. Since it's small, here it is in its entirety:

// Function:kill_scene
// Whazzit:Clean up any objects we required for rendering.
void kill_scene(void){

   if(g_texture){
      g_texture->Release();
      g_texture=NULL;
   }

   outer_cube.Free();

   inner_cube.Free();

}

Both cubes share the same texture, so we set it at the beginning of the render function. We could set it once in init_scene since it never changes, but I'm trying to keep the code commonly used in the rendering stage in the render function.

      g_d3d_device->SetTexture( 0, g_texture );

Here we reverse the culling order for the inner cube to be drawn properly, then we draw it.

      //We set our culling to target faces whose vertices are specified
      //in Clockwise order.  This is the reverse of the default.  This way
      //the far faces are kept, and near faces are culled.
      g_d3d_device->SetRenderState(D3DRS_CULLMODE,D3DCULL_CW);

      //Set D3D to read from our inner cube.  We draw the inner cube first.
      //The faces that our near the camera will be culled away, leaving the
      //only the faces pointed at us.
      //When doing alphablending it's important to draw objects that are far
      //away first, that way the front objects have something to blend with.
      g_d3d_device->SetStreamSource(0,inner_cube.GetVB(),sizeof(my_vertex));

      //Draw our cube
      g_d3d_device->DrawPrimitive(D3DPT_TRIANGLELIST,0,NUM_VERTICES/3);

Now we set things up for our outer cube.

      //Set D3D to read from our outer cube.  The back faces will be culled
      //away leaving the faces from our inner cube untouched.  Only the front
      //faces will be drawn and they'll blend nicely.
      g_d3d_device->SetStreamSource(0,outer_cube.GetVB(),sizeof(my_vertex));

      //Set culling to target Counter-Clockwise faces.  This is the default.
      //Now the near faces will be kept (since they're facing us) and the
      //far (facing away) faces will be culled.
      g_d3d_device->SetRenderState(D3DRS_CULLMODE,D3DCULL_CCW);

      //Draw our cube
      g_d3d_device->DrawPrimitive(D3DPT_TRIANGLELIST,0,NUM_VERTICES/3);