 |
 |
 |
 |
Sol's dirty DX7 tutorial
Polygons
Next we'll dump some polygons in. In this chapter we do it with normal
array of vertices, and next chapter is devoted to wrapping the array into a vertexbuffer
for various reasons.
#define WIN32_LEAN_AND_MEAN
#include <windows.h> // windows stuff
#include <stdio.h> // standard IO
#include <stdlib.h> // standard C lib
#include <ddraw.h> // DirectDraw
#include <d3d.h> // Direct3D
#include <d3dx.h> // Direct3DX
/*
* Griddy1
* d3dx7 single threaded app
* sol/trauma 1999
*/
char progname[]="Griddy1 - Sol";
HWND mainhWnd;
HINSTANCE mainhInst;
LPD3DXCONTEXT dxctx;
LPDIRECT3DDEVICE7 d3dd;
LPDIRECT3D7 d3d;
LRESULT CALLBACK WindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam) {
if (uMsg==WM_DESTROY) {
d3d->Release();
d3dd->Release();
D3DXUninitialize();
exit(wParam);
}
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
#define GRID_X 64
#define GRID_Y 48
#define XMUL (640.0f/(GRID_X))
#define YMUL (480.0f/(GRID_Y))
D3DTLVERTEX buf[(GRID_X+1)*(GRID_Y+1)]; // array of transformed & lit vertices
WORD idx[2*(GRID_X+1)*(GRID_Y+1)]; // array of indexes
void init(void)
{
for (int y=0;y<(GRID_Y+1);y++)
for (int x=0;x<GRID_X+1;x++) {
idx[y*(GRID_X+1)*2+x*2+1]=(WORD)((y)*(GRID_X+1)+x);
idx[y*(GRID_X+1)*2+x*2]=(WORD)((y+1)*(GRID_X+1)+x);
buf[y*(GRID_X+1)+x].sx=x*XMUL;
buf[y*(GRID_X+1)+x].sy=y*YMUL;
buf[y*(GRID_X+1)+x].sz=0;
buf[y*(GRID_X+1)+x].rhw=1;
buf[y*(GRID_X+1)+x].color=0;
}
}
void setup(void)
{
float i=GetTickCount()*0.02f;
for (int y=0,c=0;y<GRID_Y+1;y++)
for (int x=0;x<GRID_X+1;x++,c++) {
buf[c].color=RGB((int)(
sin((y+i)*0.2251474)*44+
cos((y-i)*0.2354128)*
cos((x+i)*0.3434913)*84
)+128,
(int)(
cos((y-i)*0.2252474)*44+
cos((x-i)*0.3334923)*84
)+128,
(int)(
sin((y-i)*0.1453474)*44+
sin((y+i)*0.2354328)*
cos((x-i)*0.3234933)*84
)+128);
}
}
void reindeer(void)
{
static int inited=0;
if (!inited) {
init();
inited++;
}
setup();
//dxctx->Clear(D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER);
d3dd->BeginScene();
for (int y=0;y<GRID_Y;y++)
d3dd->DrawIndexedPrimitive(D3DPT_TRIANGLESTRIP,D3DFVF_TLVERTEX,
(void*)(buf),(GRID_X+1)*(GRID_Y+1),
(idx+y*(GRID_X+1)*2),(GRID_X+1)*2,0);
d3dd->EndScene();
}
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
WNDCLASSEX winclass;
HWND hWnd;
MSG msg;
int fs=0;
if (MessageBox(NULL,"Shall we do it in fullscreen mode?",
"Silly question",MB_YESNO)==IDYES) fs=1;
lpCmdLine=lpCmdLine;
hPrevInstance=hPrevInstance;
if (FAILED(D3DXInitialize())) return 0;
mainhInst=hInstance;
winclass.cbSize=sizeof(WNDCLASSEX);
winclass.style=CS_DBLCLKS;
winclass.lpfnWndProc=&WindowProc;
winclass.cbClsExtra=0;
winclass.cbWndExtra=0;
winclass.hInstance=hInstance;
winclass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
winclass.hCursor=LoadCursor(NULL,IDC_ARROW);
winclass.hbrBackground=GetSysColorBrush(COLOR_APPWORKSPACE);
winclass.lpszMenuName=NULL;
winclass.lpszClassName=progname;
winclass.hIconSm=NULL;
if (!RegisterClassEx(&winclass))
return 0;
hWnd=CreateWindow(
progname,
progname,
WS_SYSMENU|WS_CAPTION|WS_BORDER|WS_OVERLAPPED|WS_VISIBLE|WS_MINIMIZEBOX,
CW_USEDEFAULT,
0,
640,
480,
NULL,
NULL,
hInstance,
NULL);
mainhWnd=hWnd;
if (FAILED(D3DXCreateContext(
D3DX_DEFAULT,
D3DX_CONTEXT_FULLSCREEN*fs, //windowed = 0
hWnd,
D3DX_DEFAULT,
D3DX_DEFAULT,
&dxctx))) return 0;
d3dd=dxctx->GetD3DDevice();
d3d=dxctx->GetD3D();
d3dd->SetRenderState(D3DRENDERSTATE_CULLMODE,D3DCULL_NONE ); // no cull
d3dd->SetRenderState(D3DRENDERSTATE_DITHERENABLE,TRUE); // dither on
d3dd->SetRenderState(D3DRENDERSTATE_ZENABLE,D3DZB_FALSE); // no zbuf
d3dd->SetRenderState(D3DRENDERSTATE_CLIPPING,FALSE);
d3dd->SetRenderState(D3DRENDERSTATE_LIGHTING,FALSE);
ShowWindow(hWnd,nCmdShow);
int frame=0;
int starttime;
starttime=GetTickCount();;
char str[200];
while (1) {
reindeer();
frame++;
int sec=GetTickCount()-starttime;
if (sec>0) sprintf(str,"frame:%05d sec:%05d.%03d fps:%3.3f (alt-F4 quits)",
frame,sec/1000,sec%1000,(frame*1000.0)/sec);
dxctx->DrawDebugText((float)(2/640.0),(float)(2/480.0),0xffffff,str);
dxctx->UpdateFrame(0);
if(PeekMessage(&msg,hWnd,0,0,PM_REMOVE)) {
switch(msg.message) {
case WM_QUIT:
case WM_DESTROY:
d3d->Release();
d3dd->Release();
D3DXUninitialize();
return (msg.wParam);
default:
DefWindowProc(hWnd,msg.message,msg.wParam,msg.lParam);
}
}
}
}
As before, we'll go through the source top-down, ignoring stuff you
should already know about.
D3DTLVERTEX buf[(GRID_X+1)*(GRID_Y+1)]; // array of transformed & lit vertices
WORD idx[2*(GRID_X+1)*(GRID_Y+1)]; // array of indexes
Direct3d can handle all sorts of vertices, but there are three main types;
unlit and untransformed, lit and untransformed and finally lit and transformed vertices.
Normally we'd start with lit (or unlit) untransformed vertices and let d3d do the
transformations (and maybe lighting) for us. However, in this tutorial we don't care
about that and just use the final screen-coordinate vertices.
The array of indexes is an array of indexes to the vertex buffer. We'll come
back to the array of indexes shortly.
The init function fills the index buffer and vertex buffer with our values.
This is only done once.
The setup function merely renders some nice rgb values to the vertices, which
is done per frame (this is the only real "effect" in this program).
Now the most interesting changes have happened in the reindeer-function:
void reindeer(void)
{
static int inited=0;
if (!inited) {
init();
inited++;
}
setup();
//dxctx->Clear(D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER);
d3dd->BeginScene();
for (int y=0;yDrawIndexedPrimitive(D3DPT_TRIANGLESTRIP,D3DFVF_TLVERTEX,
(void*)(buf),(GRID_X+1)*(GRID_Y+1),
(idx+y*(GRID_X+1)*2),(GRID_X+1)*2,0);
d3dd->EndScene();
}
(Before anyone mails me and says that it's "render" not "reindeer", yes, I'm aware of the fact).
First we check if init has been called and call the init if it has not. Then
we call setup, which fills in those nice rgb values. We have commented the clear call out
since we don't use zbuffer and we fill the whole screen every time.
The real rendering part is actually just that one DrawIndexedPrimitive call.
The SDK doc says:
HRESULT DrawIndexedPrimitive(
D3DPRIMITIVETYPE d3dptPrimitiveType,
DWORD dwVertexTypeDesc,
LPVOID lpvVertices,
DWORD dwVertexCount,
LPWORD lpwIndices,
DWORD dwIndexCount,
DWORD dwFlags
);
Primitive type can be any of the following: point list, line list,
line strip, triangle list, triangle strip or triangle fan. What do they mean?
For point list, every vertex in the list (or in the case of this call,
for every index to the vertex buffer in the index buffer) means one point rendered.
For line list, every two vertices means one line. For line strip, every vertex after
the first one means one line (i.e.. 3 vertices means 2 lines; 4 vertices means 3 lines
etc, from vertex 1 to 2, 2 to 3, 3 to 4).
For triangle list, every three vertices means one three-sided polygon
rendered. This is most probably the one primitive type you'll use the most. Triangle
strip, as used in this example, means one triangle per vertex after the original two,
formatted like:
1---3---5 7
| /| /
| / | /
|/ |/
2---4 6 8
In here the first poly would be 1-2-3, second 2-3-4, third 3-4-5 etc. Triangle
fans can be used to render N-sided convex polygons, and work in much the same way, except
that the first vertex stays the same (1-2-3, 1-3-4, 1-4-5 etc), like this:
3---4---5
|\ | /
| \ | /
| \|/
2---1 6
Now that you know how the triangle strip works you should be able to figure out
how init and setup functions work, and why. If you can't figure them out, you're quite
likely outside the target audience of this document anyway =), but don't worry, you
probably will not be using triangle strips too much in any case.
If you've seen Traumatique (asm99 2nd place), I used triangle
strip in the electricity effect in the beginning, triangle lists for most of the
stuff and triangle fans for sprites (as any of the square sprites could be clipped to be
up to 8-sided).
Let's get back to the DrawIndexedPrimitive call. VertexTypeDesc naturally
describes the type of the vertex (the call can take nontransformed vertices as well,
and will perform any transformations you have defined (which we will not be covering
in this tutorial at all) which is nifty feature but not too useful as I see it - you
should do transformations before you get to drawing stuff). The next parameters are
pointers to vertex buffers and index buffers and counts of the data in each. You can
set the flags parameter to D3DDP_WAIT to wait until all polys have been sent to the
card. This does not mean that they'd actually get rendered before the call returns.
Apart from DrawIndexedPrimitive d3d also supports DrawPrimitive call family.
So why use indexed calls? There are three reasons; First, less work done.
Especially when you let the d3d do the transformations for you, you'd just be recalculating
vertices of neighboring polygons without reason. Second, bandwidth. 3d card drivers should
be able to optimize the data thrown to the card. Third, gaps. Even if you use the same
vertex data in two calls, some cards manage to render gaps between polygons if you don't
use the indexed call system.
Yes, I said call family. The primitive draw calls can be to normal vertex arrays,
strided vertices or vertexbuffer objects. We'll ignore strided vertices here (as they can't
be used with screen space only) and move on to vertex buffers..
|
| 
 |  |
|  |
 |
| 
 |