Die Community zu .NET und Classic VB.
Menü

Die Verwendung von Texturen und Schriften

 von 

Übersicht 

Dieses Kapitel wird bei weitem nicht so riesig und kompliziert werden, wie das Letzte. Trotzdem geht es in diesem Tutorial um zwei weitere wichtige Dinge: Texturen und Schriften.

Die Verwendung von Schriften  

Um einen Text auf den Bildschirm zu bekommen brauchen wie nur zwei Zeilen Programmcode (mal von der Deklaration abgesehen). In der ersten Zeile wird ein Direct3D.Font-Objekt erstellt:

p_Font = New Direct3D.Font(p_D3DDevice, _
  New Drawing.Font("Courier New", 13, FontStyle.Bold))

Listing 1

Für das Erstellen brauchen wir lediglich ein initialisiertes Device und ein Font-Objekt, diesmal jedoch eines aus dem System.Drawing-Namespace. Das Zeichnen irgendeines Textes geht dann auch einfach:

p_Font.DrawText(Nothing, "FPS: " & p_FPS & "   Transparenz (t): " & p_transparent, _
  New Rectangle(10, 10, 400, 50), DrawTextFormat.None, Color.Red)

Listing 2

Dieser Aufruf gehört zwischen den Aufruf von BeginScene und EndScene. Das übergebene Viereck hat nur die Aufgabe den Text zu begrenzen, überstehender Text wird allerdings nicht umgebrochen.

Texturen  

Texturen sind ein weiterer, sehr wichtiger Teil für dreidimensionale Grafiken. Bis jetzt kennen wir nur 3D-Körper, die wir beliebig einfärben können. Allerdings dürfte es uns auf diese Weise sehr schwer fallen, z.B. die Oberfläche eines Baumes oder sogar ein Gesicht darzustellen. Das ist der Punkt, an dem Texturen in Spiel kommen, sie geben den eher einfach gehaltenen 3D-Objekten eine detailliertere Oberfäche.

Als Textur kann jedes beliebige 2D-Bitmap verwendet werden und dank Direct3DX, den Helferfunktionen von DirectX, werden auch eine große Anzahl von Standard-Dateiformaten unterstützt (BMP, JPG, TGA, PNG, usw.). Die einzige wirkliche Begrenzung wird häufig von der Grafikkarte gesetzt: die Texturen müssen/sollten sowohl eine Breite als auch eine Höhe von 2n (128x128; 256x256; 256x128; 512x1024) besitzen. Außerdem besitzen viele Grafikkarten Begrenzungen, was die maximale Größe angeht. Bei den meisten Karten liegt diese in Höhen, die sowieso keinen Sinn manchen (wie 2048x2048 oder 4096x4096), verschiedene ältere Karten wie z.B. die recht beliebte Voodoo-Serie machen schon bei 256x256 dicht. Deswegen ist bei der Verwendung von größeren Texturen Enumeration sehr zu empfehlen. Dies geht so:

Dim D3DCaps As Caps = Manager.GetDeviceCaps(0, DeviceType.Hardware)
If D3DCaps.MaxTextureHeight > 256 And D3DCaps.MaxTextureWidth > 256 Then
  MessageBox.Show("Na dann ist ja alles in Ordnung")
End If

Listing 3

Es gibt übrigends noch ein anderes Format neben den Standardformaten, welches in betracht gezogen werden können: das DirectDraw Surface (DDS) Format. Der Vorteil dieses Formates liegt darin, dass es die Texturen genauso speichert, wie Direct3D diese im Speicher ablegen würde. Gerade beim Laden bietet das einen enormen Geschwindigkeitsvorteil. Falls Sie vorhaben nur einen 2-Bit-Alpha-Channel zu verwenden, so können sie auch gleich die Textur mit einem 2-Bit-Alpha-Channel erstellen. Interessierte sollten sich dazu einmal das Programm DXTex (DX-SDK-Ordner\bin\DXUtils\DXTex.exe) anschauen.

Auch beim Laden der Texturen gibt es einige interessante Dinge zu erwähnen. Beim Laden einer Textur (wie das geht, erkläre ich gleich) müssen wir genauso wie beim Erstellen eines Devices ein Format angeben. Natürlich können wir hier wieder die bekannten Formate, wie z.B. R5G6B5 verwenden, diese haben allerdings einen kleinen Nachteil: sie verbrauchen relativ viel Platz. Wiederum kann man von der Grafikkarte nicht erwarten, dass sie jedesmal komplexe Dekompressionsalgorithmen durchführen muss um ein paar Vertices zu zeichnen. Es gibt in Direct3D jedoch einige Formate, die ein einigermaßen gutes Verhältnis zwischen Geschwindigkeit und Größe bieten: DXT1 bis DXT5. DXT1 bietet sogar eine Kompression von 6 zu 1, was sehr brauchbar werden kann, wenn sie z.B. 160MB auf eine 32 MB-Grafikkarte packen wollen.

Texturkoordinaten  

Jetzt wo wir wissen, welche Eigenschaften unsere Textur-Bilddateien haben, müssen wir nur noch wissen wie diese verwendet werden. Zuersteinmal brauchen wir ein neues Vertex-Format: PositionTextured. Anstelle einer Farbe müssen wir nun zwei Koordinaten, normalerweise U und V genannt übergeben. Diese zwei Koordinaten geben eine relative Position auf unserer Textur an. Hier ein kleines Beispiel für ein Rechteck (also auch die Seiten eines Würfels).


Abbildung 1: Ein Beispiel für Texturekoordinaten

Wie man sehen kann, gibt man mit 0,0 die obere linke Ecke der Textur, mit 1,1 die untere rechte Position an. Der Punkt in der Mitte hat somit logischerweise 0.5,0.5. Die Vertices für ein einfache texturiertes Dreieck müssten also die Positionen 0,0; 1,0 und 0,1 bekommen.

Diese Form des Koordinatensystem mag am Anfang etwas merkwürdig erscheinen, warum nicht einfach die Positionen in Pixeln angeben? Direct3D unterstützt MipMapping, d.h. beim Laden der Texturen kann man Direct3D anweisen, von einer Texture mehrere kleinere Kopien im Speicher zu erstellen (bei 256x256 also 128x128, 64x64, 32x32, 16x16, usw.). Direct3D wählt dann nach der Entfernung des Polygons von der Kamera, welche Textur verwendet wird, da es absolut keinen Sinn macht eine Textur von 256x256 zur Laufzeit auf eine Größe von ein paar Pixeln (bei weit entfernten Objekten) runterzurechnen. Das Ergebnis ist bei einer kleineren Textur bei großer Entfernung genau das selbe, nur das wir durch die kleine Textur Zeit sparen. Hier ein Teil unseres Würfels, diesmal jedoch mit Texturkoordinaten:

Dim cube() As CustomVertex.PositionTextured
Try
  p_vbCube = New VertexBuffer( _
    GetType(CustomVertex.PositionTextured), 36, _
    p_D3DDevice, 0, _
    CustomVertex.PositionColored.Format, _
    Pool.Managed)
  cube = DirectCast(p_vbCube.Lock(0, LockFlags.None), _
    CustomVertex.PositionTextured())
Catch exp As Exception
  Return False
End Try

cube(0) = New CustomVertex.PositionTextured(-1, 1, 1, 0, 0)
cube(1) = New CustomVertex.PositionTextured(1, 1, 1, 1, 0)
cube(2) = New CustomVertex.PositionTextured(-1, 1, -1, 0, 1)
cube(3) = New CustomVertex.PositionTextured(1, 1, -1, 1, 1)
cube(4) = New CustomVertex.PositionTextured(-1, 1, -1, 0, 1)
cube(5) = New CustomVertex.PositionTextured(1, 1, 1, 1, 0)

Listing 4

Es ist übrigens auch möglich mehrere (genau gesagt 8) Texturen für ein Vertex zu verwenden, mehr als drei Texturen werden Sie aber wohl sehr selten brauchen. Im Moment können wir dieses Feature aber ersteinmal getrost vergessen, es ist aber trotzdem gut zu wissen :)

Die Texturen laden  

Jetzt wo wir ersteinmal die Theorie durchgekaut haben können wir mit dem Code zum Laden der Texturen anfangen. Das sollte jedoch kein Problem darstellen, solange sie auf die oben erwähnten Dinge achten.

Try
  p_CubeTexture = TextureLoader.FromFile(p_D3DDevice, _
                                        "Textur.bmp", _
                                        256, 256, _
                                        1, 0, _
                                        Format.R5G6B5, _
                                        Pool.Managed, _
                                        Filter.Linear,  _
                                        Filter.Linear, 0)
  p_TransparentCubeTexture = TextureLoader.FromFile(p_D3DDevice, _
                                                    "Textur.bmp", _
                                                    256, 256, _
                                                    1, 0, _
                                                    Format.R5G6B5, _
                                                    Pool.Managed, _
                                                    Filter.Linear, _
                                                    Filter.Linear, _
                                                    Color.White.ToArgb)
Catch
  Return False
End Try

Listing 5

Hier haben wir zwei sehr lange Anweisungen. Der Grund warum wir zwei anscheinend gleiche Anweisungen verwenden, ist, dass wir zusätzlich noch eine transparente Textur laden. Hier eine Tabelle der wichtigsten Parameter:

Parameter Verwendung
Device Hier muss natürlich unser Device-Objekt rein.
srcFile Der Pfad der Textur-Bitmap
width, height Breite und Höhe der Textur. Übringends müssen die beiden Werte nicht unbedingt mit der Breite und Höhe in der Datei übereinstimmen. Der TextureLoader skaliert die Bilder beliebig.
Format Das weiter oben angesprochen Format, also z.B. R5G6B5 oder DXT1
Pool Mit diesem Parameter stellen wir ein wo die Textur untergebracht werden soll. Mit Pool.Managed überlassen wir dies Direct3D.
colorKey Hier wird es spannend. Mit dem ColorKey geben wir eine Farbe an, die Direct3D dann als transparent ansieht. Der andere Weg wäre die Verwendung von Alpha-Channels gewesen, dies ist in diesem Fall aber ein wenig überdimensioniert.

Den Würfel rendern  

Nichts leichter als das. Wir müssen Direct3D nur unsere Textur und das neue VertexFormat klar machen:

.SetStreamSource(0, p_vbCube, 0)
If p_transparent Then
  .SetTexture(0, p_TransparentCubeTexture)
Else
  .SetTexture(0, p_CubeTexture)
End If
.VertexFormat = CustomVertex.PositionTextured.Format
.DrawPrimitives(PrimitiveType.TriangleList, 0, 12)

p_Font.DrawText("FPS: " & p_FPS & "   Transparenz (t): " & p_transparent, _
  New Rectangle(10, 10, 400, 50), DrawTextFormat.None, Color.Red)

Listing 6

Je nachdem, ob wir einen transparenten Würfel haben wollen oder nicht wird die entweder die normale oder die transparente Textur gesetzt. Unser Vertex-Format hat sich jetzt auf PositionTextured geändert und das wars auch schon.


Abbildung 2: Der texturierte Würfel ohne Transparenz

Auch hier empfehle ich es sehr sich das Beispielprojekt herunterzuladen...

Das Beispielprojekt zu diesem Tutorial herunterladen [21197 Bytes]

Ihre Meinung  

Falls Sie Fragen zu diesem Tutorial haben oder Ihre Erfahrung mit anderen Nutzern austauschen möchten, dann teilen Sie uns diese bitte in einem der unten vorhandenen Themen oder über einen neuen Beitrag mit. Hierzu können sie einfach einen Beitrag in einem zum Thema passenden Forum anlegen, welcher automatisch mit dieser Seite verknüpft wird.