Sol's dirty DX7 tutorial
Texturen
In diesem letzten Kapitel werden wir die Oberfläche von Texturen kratzen.
Texturen zu benutzen ist vielleicht die komplexeste Sache in D3D (die andere
heftige Sache ist die Benutzung von Matrizen). Eine vollständige Erklärung von
Texturen würde Multitexturing, Colorkeying, blending, 'Video' Texturen (Videos
auf Texturen rendern) und so weiter beinhalten. Aber wie schon zuvor werden wir uns
nur den einfachsten Weg ansehen:
#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
/*
* Griddy3
* d3dx7 single threaded app
* sol/trauma 1999
*/
char progname[]="Griddy3 - Sol";
HWND mainhWnd;
HINSTANCE mainhInst;
LPD3DXCONTEXT dxctx;
LPDIRECT3DDEVICE7 d3dd;
LPDIRECT3D7 d3d;
LPDIRECTDRAWSURFACE7 myTexture;
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))
LPDIRECT3DVERTEXBUFFER7 vbuf;
WORD idx[2*(GRID_X+1)*(GRID_Y+1)];
void init(void)
{
D3DTLVERTEX *buf;
D3DVERTEXBUFFERDESC vbd;
vbd.dwSize=sizeof(vbd);
vbd.dwCaps=D3DVBCAPS_WRITEONLY;
vbd.dwFVF=D3DFVF_TLVERTEX;
vbd.dwNumVertices=(GRID_X+1)*(GRID_Y+1);
d3d->CreateVertexBuffer(&vbd, &vbuf,0);
vbuf->Lock(DDLOCK_WAIT|DDLOCK_WRITEONLY|DDLOCK_NOOVERWRITE,(void**)&buf,NULL);
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;
}
vbuf->Unlock();
}
void setup(void)
{
D3DTLVERTEX *buf;
vbuf->Lock(DDLOCK_WAIT|DDLOCK_WRITEONLY|DDLOCK_NOOVERWRITE,(void**)&buf,NULL);
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].tu=(-sin((y+i*0.394)*0.12251474)+cos((x-i*0.202)*0.12654374));
buf[c].tv=(-cos((x+i*0.21)*0.12123474)+sin((-y+i*0.398)*0.12452474));
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);
}
vbuf->Unlock();
}
void reindeer(void)
{
static int inited=0;
if (!inited) {
init();
inited++;
}
setup();
//dxctx->Clear(D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER);
d3dd->BeginScene();
d3dd->SetTexture(0,myTexture);
for (int y=0;y<GRID_Y;y++)
d3dd->DrawIndexedPrimitiveVB(D3DPT_TRIANGLESTRIP, vbuf,
0,(GRID_X+1)*(GRID_Y+1),
(idx+y*(GRID_X+1)*2),
(GRID_X+1)*2,0);
d3dd->SetTexture(0,NULL);
d3dd->EndScene();
}
int create_texture(void)
{
int texturewidth=0,textureheight=0;
D3DX_SURFACEFORMAT texformat=D3DX_SF_UNKNOWN;
if FAILED(D3DXCreateTextureFromFile(d3dd,
NULL,
(LPDWORD)&texturewidth,
(LPDWORD)&textureheight,
&texformat,
NULL,
&myTexture,
0,
"texmex.bmp",
D3DX_FT_LINEAR)) return 0;
return 1;
}
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);
if (create_texture()==0) {
d3d->Release();
d3dd->Release();
D3DXUninitialize();
return 0;
}
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);
}
}
}
}
Und los mit dem neuen Zeug..
LPDIRECTDRAWSURFACE7 myTexture;
Das ist der Untergrund für die Textur.
buf[c].tu=(-sin((y+i*0.394)*0.12251474)+cos((x-i*0.202)*0.12654374));
buf[c].tv=(-cos((x+i*0.21)*0.12123474)+sin((-y+i*0.398)*0.12452474));
Hier in der Setup Funktion füllen wir die U und V Werte unser Vertices.
Die Werte reichen von 0 bis 1; Wenn sie über diese Werte gehen, werden die Texturen
gewrapped. Ihr könnt den Wrapping Modus mit D3DRENDERSTATE_WRAP0 setzen. Dies
benötigt wohl ein bischen Erklärung. Sagen wir, wir möchten eine Textur um einen
Zylinder wrappen. An beiden Punkten, wo die Textur beginnt und endet muß die
Wrapping Flag aktiviert sein so daß die Textur nicht auf die falsche Weise gezeichnet wird
und diese "abgeschittenen" Oberflächen hervorruft, die schon schon in zu vielen Demos gesehen
haben.
d3dd->SetTexture(0,myTexture);
d3dd->SetTexture(0,NULL);
Die Render-Funktion hat zwei neue Zeilen. Die erste setzt unsere Textur auf das
Stadium 0 und die zweite Teile gibt sie wieder frei. Direct3D unterstützt Texturen
mit bis zu 8 Layern in einem Aufruf. Solltet ihr vorhaben 8 unterschiedliche Texturen
übereinander zu legen müßtet ihr den Texturen Stadien von 0 bis 7 zuweisen.
Multitexturing ist allerdings eine sehr komplexe Sache (und ihr müßt euch mit blending
auskennen bevor ihr damit anfangen könnt). Wir werden also nicht darauf eingehen.
int create_texture(void)
{
int texturewidth=0,textureheight=0;
D3DX_SURFACEFORMAT texformat=D3DX_SF_UNKNOWN;
if FAILED(D3DXCreateTextureFromFile(d3dd,
NULL,
(LPDWORD)&texturewidth,
(LPDWORD)&textureheight,
&texformat,
NULL,
&myTexture,
0,
"texmex.bmp",
D3DX_FT_LINEAR)) return 0;
return 1;
}
Diese neue Funktion ruft D3DXCreateTextureFromFile auf, um eine Textur aus
der Datei "textmex.bmp" zu laden. D3dx hat ebenfalls Aufrufe, um Texturen im Speicher
zu erzeugenoder einfach, um ein geeignetes Texturformat für euren 3D Beschleuniger zu
finden. Dies ist aber eindeutig der einfachste Weg (obwohl ich es selbst hasse externe
Dateien zu benutzen).
Parameter. Als erstes kommt unser D3D Device, dann Pointer zu den Flags (nur "nomipmap"
wird unterstützt), dann Pointer zu der Texturhöhe und Texturbreite (wenn null, dann
wird die in der Datei angegebene Größe genutzt). Dann Pointer zum Texturformat, welches in
unserem Falle "unknown" wie in "ist uns egal" ist, D3D wird daraufhin versuchen ein Texturformat
zu finden, welches unserem entspricht (in unserem Falle wahrscheinlich 8r8g8b). Als nächstes
folgt ein Pointer zur Palette, um den wir uns aber nicht kümmern, ein Pointer auf unser
surface handle welches bei diesem Aufruf erzeugt wird, dann die Anzahl der MipMaps, welche wir auf
null setzen, den Dateinamen der Quelldatei und den Filtertyp, der angewendet werden soll, wenn
die MipMaps generiert werden.
Wenn es diesen Aufruf nicht gäbe, müßten wir uns erst einmal ein Bitmap Format suchen, welches
von der Grafikkarte unterstützt wird, dann die Oberfläche erstellen, sperren, ausfüllen (wahrscheinlich
noch via dem GDI Buffer, was auch noch mal ein hübsches Chaos ist) und dann die MipMaps per Hand
erstellen.
Das ist eigentlich alles. Wenn ihr das Programm startet werdet ihr wahrscheinlich von der
Geschwindigkeit enttäuzscht sein (sind ja nur 64*48*3072 Polygone mit einer einfachen Textur
und Gouraud shading). Unglücklicherweise gibt es da nicht viel, was man ändern könnte. Diese
"Millionen von Polys pro Sekunde" Benchmarks beziehen sich auf 6 Pixel große Polygone, die
gleichmäßig dargestellt werden, während wir hier 50 Pixel große Polygone zeichnen. Wenn man darüber
nachdenkt nutzen Quake und Halflife in Wirklichkeit recht wenig Polys gleichzeitig. Vielleicht ändert
sich alles, wenn Transformation und Belichtung immer komplett in der Hardware abläuft und die
Füllraten sich verzehnfacht haben.
Ich hoffe das hat etwas Licht auf D3D geworfen und geholfen einige Dinge ins Rollen zu bringen.
Tut euch keinen Zwang an und schickt Kommentare an sol.