Direct3d Alpha Blending

Introduction
The direct3d section in my third project report was a little too hastily written (and in French), so I decided to re-write it in the shape of a little tutorial (also requested by Kristian Olivero, who helped me alot with software alpha-blending). Anyhow, here it is. Any comments or questions, write to me at storde_g@epita.fr.
This tutorial will explain how to render 2d sprites with Direct3d so that you can do hardware accelerated Alpha-blending.

/*
  (all code in this tutorial is Delphi, but there’s hardly anything to change in order to have C code:
  := becomes =
  or becomes |
  and the dots seperating the objects and procedures like
   Direct3d.CreateDevice become arrows: Direct3dDevice->CreateDevice
*/

Initialize Direct3d
Ok, firstly, initialize your Direct3d devices and stuff (if you can’t do this, read the SDK documentation or mail me(I’ll probably tell you to read the SDK anyhow :))
Basically this is it:

  DirectDraw.QueryInterface(IID_IDirect3D7,Direct3d);
  Direct3D.CreateDevice(IID_IDirect3DHALDevice,BackSurface,Direct3dDev);

This attaches the Direct3d device to my BackSurface, so that when I go rendering stuff they will be rendered to the BackSurface ( which is the surface I use before blitting to the primary)
Oh and by the way, the IID_Idirect3dHalDevice is used here to acces the video card’s hardware accelerated stuff. If you don’t have a 3d card, this will not work.

Texture Flags
A direct3d texture is simply a directdraw surface with a couple different properties in it’s surfacedesc:

  SurfaceDesc.dwFlags := DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH or       DDSD_TEXTURESTAGE or DDSD_CKSRCBLT;
  SurfaceDesc.ddsCaps.dwCaps := DDSCAPS_TEXTURE;
  SurfaceDesc.ddsCaps.dwCaps2 := DDSCAPS2_TEXTUREMANAGE;

Note that some video cards don’t support these options.. This code works with my Voodoo3 2000, and I havn’t bothered to write a capability checker..
Also, remember that texture width and height MUST be powers of 2 (32,32  64,64  128,128..)
If they aren’t you simply won’t get any output from DrawPrimitive.

Then you have to load a bmp into your surface, this is standard stuff, so again, check the SDK if you don't know how to do this..

Ok, now if your doing rendering with only one texture, instead of single-pass multitexturing, you can go ahead and just set your texture.

  Direct3dDev.SetTexture(0,ShadowSurface);

Every call to DrawPrimitive will use the latest set texture.
 

Rendering Setup
Ok, now you have to setup a couple things:
A vertex list.
Material properties.
A Viewport
Projection Matrix

*The vertex list in this case is an array of 4 vertices, since we are rendering a rectangular sprite.

*Material properties represent emmissive and ambient colour properties. Set them to whatever you want.

*When rendering a sprite, it’s best to set the viewport to the  same  size as the image.
This is the viewport I used for my 128x128 image:

  ViewPort.dwX := X;
  ViewPort.dwY := Y;
  ViewPort.dwWidth:=  128;
  ViewPort.dwHeight:= 128;
  ViewPort.dvMinZ := 0.0;
  ViewPort.dvMaxZ := 1.0;
  Direct3dDev.SetViewport(viewport);

Where X and Y are the point coordinates of where on your Backsurface you want the sprite to go. Note that if X+Width > BackSurface.width then the sprite will not be drawn, so you have to make a little clipping routine.. read on to find out how!

*For the matrices, you have to have 3 of them: the World, the View, and the Projection.
I made a default matrix and changed a couple parameters before assigning to each 3.. read the SDK to find out how they work, or experiment. I won’t copy/paste de SDK here.
Anyways, this is what I used:

    mat._11 := 1.0; mat._22 := 1.0; mat._33 := 1.0; mat._44 := 1.0;
    mat._12 := 0.0; mat._13 := 0.0; mat._14 := 0.0; mat._41 := 0.0;
    mat._21 := 0.0; mat._23 := 0.0; mat._24 := 0.0; mat._42 := 0.0;
    mat._31 := 0.0; mat._32 := 0.0; mat._34 := 0.0; mat._43 := 0.0;

    matWorld := mat;
    matWOrld._11 := 1;
    matWOrld._22 := 1;
    Direct3dDev.SetTransform( D3DTRANSFORMSTATE_WORLD, matWorld );

    matView := mat;
    matView._43 := 1.0;
    Direct3dDev.SetTransform( D3DTRANSFORMSTATE_VIEW, matView );

    matProj := mat;
    matProj._11 :=  1.0;    matProj._22 :=  1.0;    matProj._34 :=  1.0;
    matProj._43 := -1.0;    matProj._44 :=  0;
    Direct3dDev.SetTransform( D3DTRANSFORMSTATE_PROJECTION, matProj );

Now that all this is done, you can initialize the Alpha-Blend parameters:

   Direct3dDev.SetRenderState(D3DRENDERSTATE_ALPHABLENDENABLE, 1 );

And now, let Direct3d do it’s thing:

   Direct3dDev.BeginScene();
   Direct3dDev.DrawPrimitive( D3DPT_TRIANGLESTRIP, D3DFVF_VERTEX,ImgVertex,   4,0);
   Direct3dDev.EndScene();

And there you go, you have an alpha-blended texture on your directdraw surface.

The Shadow technique
When you have a software alpha-blenging procedure, you can do pretty much whatever you want. To do my unit shadows in my StarCraft type game, I could use the normal unit frames and just darken whatever pixel was underneath, but with Direct3d, I wasn't able to do so (if anyone knows how to do this, please mail me !!)
So the way I do it is this:
I make shadow images out of my sprites; sprite outlines filled in with grey(not black, you'll see why).
Next you set the renderstates to 1 for source and 4 for dest:
this creates the negative image of the source( the shadow) blended with the destination (the background) which gives us shading of the background.

  Direct3dDev.SetRenderState(41, 1);
  Direct3dDev.SetRenderState( D3DRENDERSTATE_SRCBLEND, 1 );
  Direct3dDev.SetRenderState( D3DRENDERSTATE_DESTBLEND, 4 );

The first renderstate is to turn on Color-Keying, so that transparent-color pixels will not be drawn (these are set with SetColorKey).

Clipping with DrawPrimitive
When the sprite goes off the screen it isn't drawn, and sometimes you get program crashes.
Not finding anyway to prevent this with direct3d either (if anyone knows how to do this, mail me!!), I wrote a little clipping routine.
The idea is simple, have a 2nd texture-surface of 128x128 or whatever size you need, and instead of using the original texture directly, blit it to this 2nd surface and use this surface instead.
Why a 2nd surface ?
because when you find out x + texture.width is > BackSurface.width (same for heights and y),  you set newx := BackSurface.width - texture.width (so that it will be drawn) and you blit your 1st texture into the surface so that the image is offset (BackSurface.width - (x+texture.width)).
Then put the newx and y into your viewport:

  ViewPort.dwX := NewX;
  ViewPort.dwY := NewY;

This way, when you overflow, the sprite will always start on the final possible X of your BackSurface, but the image inside it will be offset, so you can have a cut sprite on the edge of your screen.
If this isn't clear (and I know it isn't, cause it's hard to explain) draw a diagram and fiddle with it for a while, you'll get it. If you don't I can send code).
Just remember:
* have a 2nd texture-surface to blit to and to call with SetTexture
* never have the Viewport.dwX start at a point where the sprite will go off the Surface attached to your Direct3d object.