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.