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 64483072 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.