email sol follow sol rss feed of the blog wishlist Sol::Tutorials

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.

Site design & Copyright © 2021 Jari Komppa
Possibly modified around: June 01 2010