• Keine Ergebnisse gefunden

Kontakt und Updates

2.5 Das erste Dreieck

2.6.8 Das Beispielprogramm

// Informationen der ersten MIP-Map-Ebene abfragen HRESULT hResult;

D3DSURFACE_DESC Desc;

if(FAILED(hResult = pTexture->GetLevelDesc(0, &Desc))) {

// Fehler!

// ...

}

Listing 2.70 Wie man Informationen über eine Oberfläche einer Textur erhält

2.6.7.2 Oberflächen einer Textur

Wenn wir später einmal eine Textur direkt verändern möchten (zum Beispiel eine Textur als Anzeige in einem Cockpit), ist es wichtig, dass wir die IDirect3DSurface9-Schnittstelle jeder MIP-Map-Ebene abfragen können, denn nur so ist es möglich, vollen Zugriff auf die Oberflä-che zu erlangen. Zu diesem Zweck gibt es die Methode IDirect3DTexture9::GetSurfaceLevel

(kleine Kritik an DirectX: Die Methode sollte besser GetLevelSurface heißen). Ihr erster Para-meter ist wieder die MIP-Map-Ebene, und der zweite ist die Adresse eines Zeigers auf eine

IDirect3DSurface9-Schnittstelle. Sie kennen das schon – die Methode füllt den Zeiger dann aus, so dass er auf die entsprechende Oberflächenschnittstelle zeigt, mit der wir dann weiter arbeiten können.

Die Methode erhöht den Referenzzähler des IDirect3DSurface9-Objekts, das zurückgeliefert wird. Ist also die Arbeit mit der Schnittstelle beendet, sollte man nicht vergessen, ihre Release-Methode aufzurufen. Ansonsten droht wieder einmal ein Furcht erregendes Speicherleck, weil der Referenzzähler die Null nicht mehr erreicht!

2.6.8 Das Beispielprogramm

Nun habe ich Ihnen bereits so viel über Texturen erzählt, dass es nun Zeit für ein neues Bei-spielprogramm wird. Diesmal wollen wir mehr als nur ein einziges simples sich drehendes Dreieck. Das Beispielprogramm Nr. 4 stellt ein Feld von herumfliegenden und sich drehenden Dreiecken dar, deren Größe, Form, Vertexfarben und Texturkoordinaten zufällig generiert werden. Dabei verwendet das Programm als Textur eine Art von Schachbrettmuster, wobei jedes Feld aus nur einem einzigen Texel besteht. Solch eine Textur eignet sich hervorragend dafür, die Unterschiede der Texturfilter zu demonstrieren. Es gibt drei Texturfilterkombinatio-nen (PlayStation-Effekt, bilinearer Filter und anisotropischer Filter), zwischen deTexturfilterkombinatio-nen das Pro-gramm im Dreisekundentakt hin- und herschaltet. Die aktuelle Filterkombination steht dabei im Titel des Fensters. Aus diesem Grund empfiehlt es sich, das Programm nur im Fenstermo-dus laufen zu lassen.

2.6.8.1 Die Struktur der Dreiecke

Im Programm wird jedes Dreieck als ein einzelnes Objekt betrachtet, was bereits ein erster Schritt in Richtung Spieleprogrammierung ist. Dazu wurde die Struktur STriangle erschaffen, die wie folgt definiert ist:

// Struktur für ein Dreieck struct STriangle

{

tbVector3 vPosition; // Position

tbVector3 vVelocity; // Bewegung (Richtung und Geschwindigkeit) tbVector3 vRotation; // Rotationszustand

tbVector3 vRotVelocity; // Rotationsbewegung float fSize; // Größe

SVertex aVertex[3]; // Die drei Vertizes };

Listing 2.71 Die Dreiecksstruktur STriangle

Ein Dreieck ist also durch seine Position, seine Bewegung und Geschwindigkeit (die in Ein-heiten pro Sekunde angegeben wird), seinen aktuellen Rotationszustand, die Rotationsge-schwindigkeit (WinkelgeRotationsge-schwindigkeit auf allen drei Achsen), seine Größe und durch seine drei Vertizes definiert. Die Vertexstruktur SVertex bietet Platz für Position, Farbe und Textur-koordinaten (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1). Das Array g_aTriangle speichert alle Dreiecke des Programms.

2.6.8.2 Zu Beginn: Erstellung der Dreiecke

Wie viele Dreiecke angezeigt werden sollen, steht in der Konstanten g_iNumTriangles, die standardmäßig auf 1024 gesetzt ist. In der Funktion InitScene werden die Dreiecke initiali-siert, nachdem die Projektionsmatrix erzeugt und einige Render-States gesetzt wurden:

for(int iTri = 0; iTri < g_iNumTriangles; iTri++) {

// Alle Dreiecke starten am Punkt (0, 0, 50).

g_aTriangle[iTri].vPosition = tbVector3(0.0f, 0.0f, 50.0f);

// Die Bewegung ist zufällig.

g_aTriangle[iTri].vVelocity = tbVector3Random() * tbFloatRandom(0.1f, 5.0f);

// Rotation zurücksetzen

g_aTriangle[iTri].vRotation = tbVector3(0.0f, 0.0f, 0.0f);

// Rotationsgeschwindigkeit ist zufällig auf allen drei Achsen g_aTriangle[iTri].vRotVelocity.x = tbFloatRandom(-1.0f, 1.0f);

g_aTriangle[iTri].vRotVelocity.y = tbFloatRandom(-1.0f, 1.0f);

g_aTriangle[iTri].vRotVelocity.z = tbFloatRandom(-1.0f, 1.0f);

// Größe zufällig zwischen 1 und 5 festlegen g_aTriangle[iTri].fSize = tbFloatRandom(1.0f, 5.0f);

// Nun die einzelnen Vertizes generieren for(int iVertex = 0; iVertex < 3; iVertex++) {

// Position und Farbe

g_aTriangle[iTri].aVertex[iVertex].vPosition = tbVector3Random();

g_aTriangle[iTri].aVertex[iVertex].dwColor = tbColor(tbFloatRandom(0.0f, 1.0f), tbFloatRandom(0.0f, 1.0f), tbFloatRandom(0.0f, 1.0f));

// Texturkoordinaten zufällig zwischen -1 und 2 erzeugen

g_aTriangle[iTri].aVertex[iVertex].vTexture.u = tbFloatRandom(-1.0f, 2.0f);

g_aTriangle[iTri].aVertex[iVertex].vTexture.v = tbFloatRandom(-1.0f, 2.0f);

} }

Listing 2.72 Generieren aller Dreiecke

2.6.8.3 Die Textur laden und einsetzen

Als Nächstes lädt das Beispielprogramm die Textur TEXTURE.BMP, die sich im selben Ordner wie das Projekt selbst befindet. Dazu wird, wie bereits besprochen, die Hilfsfunktion

D3DXCreateTextureFromFileEx verwendet. Anschließend aktiviert das Programm die Textur mit

SetTexture. g_pTexture ist der globale Texturzeiger (PDIRECT3DTEXTURE9).

// Die Textur laden

if(FAILED(hResult = D3DXCreateTextureFromFileEx(g_pD3DDevice, // Device "Texture.bmp", // Dateiname

TB_ERROR_DIRECTX("D3DXCreateTextureFromFileEx“, hResult, TB_ERROR); // Fehler!

}

// Und nun die Textur einsetzen

g_pD3DDevice->SetTexture(0, g_pTexture);

Listing 2.73 Laden und aktivieren der Schachbretttextur

2.6.8.4 Die Move-Funktion bewegt die Dreiecke

Da wir gerne viel Bewegung in unserem Programm hätten, fällt die Move-Funktion diesmal auch etwas größer aus. Sie erledigt die folgenden Dinge: Dreiecke fortbewegen, rotieren und ihre Distanz zum Punkt (0, 0, 0), wo der „Betrachter“ steht, berechnen. Wenn diese Distanz nämlich zu groß wird, dann dreht das Programm die Flugrichtung des Dreiecks um, so dass es wieder zurückkommt. Wir dürfen nicht vergessen, dass die Move-Funktion auf verschieden schnellen Computern auch verschieden oft pro Sekunde aufgerufen wird. Die vergangene Zeit seit dem letzten Bild bekommen wir per float-Parameter übergeben. Um ein Dreieck bei-spielsweise fortzubewegen, addieren wir nicht nur seinen Bewegungsvektor zu seinem Positi-onsvektor, sondern der Bewegungsvektor muss vorher noch mit der vergangenen Zeit (in Se-kunden) multipliziert werden, so dass die Dreiecke auf langsameren Systemen (= längere Zeit seit dem letzten Bild, also größerer Faktor) größere und dafür aber auch weniger „Sprünge“

machen als bei einem schnelleren System. Beim Rotieren ist es genau die gleiche Situation.

Die neue Move-Funktion hält außerdem die Animation an, wenn der Benutzer die Leertaste gedrückt hält. Dadurch kann man die Wirkung der verschiedenen Texturfilter besser erkennen.

// Move-Funktion

tbResult Move(float fNumSecsPassed) {

// Zeitzähler erhöhen g_fTime += fNumSecsPassed;

// Wenn der Benutzer die Leertaste drückt, wird das Programm angehalten.

// So sind die Wirkungen der verschiedenen Filter besser zu erkennen.

if(GetAsyncKeyState(VK_SPACE)) fNumSecsPassed = 0.0f;

// Jedes Dreieck bewegen float fDistance;

for(int iTriangle = 0; iTriangle < g_iNumTriangles; iTriangle++) {

// Position und Rotation aktualisieren

g_aTriangle[iTriangle].vPosition += g_aTriangle[iTriangle].vVelocity * fNumSecsPassed;

g_aTriangle[iTriangle].vRotation += g_aTriangle[iTriangle].vRotVelocity * fNumSecsPassed;

// Distanz des Dreiecks zum Beobachter (zum Nullpunkt) berechnen fDistance = tbVector3Length(g_aTriangle[iTriangle].vPosition);

// Wenn die Distanz größer als 100 ist, soll das Dreieck umkehren.

if(fDistance > 100.0f) g_aTriangle[iTriangle].vVelocity *= -1.0f;

}

return TB_OK;

}

Listing 2.74 Dieser Code bewegt die Dreiecke, dreht sie und hindert sie an der Flucht.

2.6.8.5 Die Render-Funktion

Die Render-Funktion ist im Prinzip ein alter Hut: Sie kennen sie zum größten Teil bereits aus dem vorherigen Beispielprogramm. Der einzige Unterschied ist nun, dass man mit einer for -Schleife alle Dreiecke durchläuft und jedes davon einzeln zeichnet. Zuvor muss natürlich die Weltmatrix gesetzt werden, die für jedes Dreieck neu generiert wird. Sie ist das Produkt aus der Skalierungsmatrix (hier kommt die Größe des Dreiecks ins Spiel), der Rotationsmatrix und der Translationsmatrix, die das Dreieck an seine Position verschiebt. Es wäre auch denkbar, diese Transformationen selbst durchzuführen (anstatt es von Direct3D regeln zu lassen) und dann ein großes Array von Vertizes zu generieren, das dann mit einem Mal gezeichnet werden könnte, ohne jedes Mal die Weltmatrix zu aktualisieren.

Es gibt aber noch einen wichtigen neuen Teil in der Render-Funktion: Dieser sorgt dafür, dass die verwendete Texturfilterkombination (Vergrößerungsfilter, Verkleinerungsfilter und MIP-Map-Filter) alle drei Sekunden wechselt. Das wird wie folgt gelöst:

// Abhängig von der Zeit die Texturfilter setzen.

// Alle drei Sekunden werden diese geändert.

if((int)(g_fTime / 3.0f) % 3 == 0) {

// Bilineare Filter mit linearem MIP-Mapping

g_pD3DDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);

g_pD3DDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);

g_pD3DDevice->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);

SetWindowText(g_hWindow, "Beispielprogramm Nr. 4: Texturen (MIN: Linear, MAG: Linear, MIP: Linear)");

}

else if((int)(g_fTime / 3.0f) % 3 == 2) {

// Keine Filter ("PlayStation-Effekt"), auch kein MIP-Mapping g_pD3DDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT);

g_pD3DDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);

g_pD3DDevice->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_NONE);

SetWindowText(g_hWindow, "Beispielprogramm Nr. 4: Texturen (MIN: Point, MAG: Point, MIP: None)");

}

else {

// Maximaler anisotropischer Filter ohne MIP-Mapping g_pD3DDevice->SetSamplerState(0, D3DSAMP_MAXANISOTROPY,

g_Direct3DParameters.DeviceCaps.MaxAnisotropy);

g_pD3DDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_ANISOTROPIC);

g_pD3DDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);

g_pD3DDevice->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_NONE);

SetWindowText(g_hWindow, "Beispielprogramm Nr. 4: Texturen (MIN: Anisotropic, MAG: Linear, MIP: None)");

}

Listing 2.75 Drei verschiedene Filterkombinationen

Diese Filterkombinationen sind bewusst ausgewählt, denn sie demonstrieren die Unterschiede zwischen den verschiedenen Möglichkeiten sehr gut. Wie Sie sehen, wird beim dritten Filter der maximale anisotropische Filter verwendet. Die globale Variable g_Direct3DParameters be-inhaltet ja die für die Abfrage erforderliche D3DCAPS9-Struktur.

Abbildung 2.33 Die drei Filterkombinationen im Direktvergleich: oben: hoffnungsloses Flimmern – keine Filter (PlayStation-Effekt); links: bilineare Filter mit Mapping; rechts: anisotropischer Filter ohne MIP-Mapping

Man kann einen deutlichen Unterschied zwischen dem oberen und den unteren beiden Bildern erkennen. Und obwohl das dritte Bild rechts unten ohne MIP-Mapping gemacht wurde, ist es von der Qualität her kaum vom zweiten mit dem bilinearen Filter zu unterscheiden. Das zeigt, welche hervorragende Arbeit der anisotropische Filter leistet. Trotzdem sollte man ihn

eigent-lich nicht ohne MIP-Mapping anwenden, denn MIP-Mapping spart in den meisten Fällen viel Zeit (auf Grund der kleineren Texturen).