Die Community zu .NET und Classic VB.
Menü

2D-Grafiken (1)

 von 

Einführung  

Wenn Sie alle Tutorien in der richtigen Reihenfolge 1..2..3 :) durchgelesen haben, dann werden Sie bemerkt haben, dass wir bis jetzt noch nichts richtig Interessantes gemacht haben – zumindest zum Anschauen. Am Ende diese Kapitels sollten Sie fähig sein, ein paar wirklich interessante DirectX-Grafiken zu erstellen.

Ein Ding, was ich an DirectX8 nicht mag, ist, dass jeder automatisch in die Welt von 3D geschoben wird. Wann immer jemand mit DirectX7 angefangen hat, wurde ihm gesagt, dass er mit DirectDraw (die alte 2D-Komponente) anfangen sollte und dann zu Direct3D überwecheln kann. Wenn Sie ein volles 2D-Spiel schreiben wollen, dann sollten Sie besser DirectX7 lernen, oder eine Mischung von DirectX 7 und 8 benutzten.

Es ist aber immer noch wichtig, zu wissen, wie all das gemacht wird, weil Sie, egal was Sie für ein Spiel schreiben, immer Text, Bilder, Gesundheitsanzeiger, usw. brauchen werden... Alle diese Techniken brauchen diese Techniken hier.

Eine Sache wäre noch, dass alle Grafiken in den 2D-Tutorien eigentlich 3D sind. Ich erkläre viele Dinge nur sehr wenig – halt nur das was man für 2D-Grafiken braucht; diese Dinge (die nur für 3D wichtig sind), werden in den späteren Kapiteln näher erklärt.

Ein bisschen Theorie  

Verticen: Diese können als Punkte verstanden werden. Sie können ein Dreieck mit drei Punkten definieren - oder mit 3 Verticen. Direct3D zeichnet eine gerade Linie zwischen diesen Verticen und in den meisten Fällen werden die eingeschlossenen Regionen eingefärbt. Jedes Vertex kann noch zusätzliche Eigenschaften haben, wie Texturkoordinaten, Farbe, Specular-Werte(für reflektierende/glänzende Objekte) - alle abhängig von der Entfernung zwischen zwei Verticen; z.B. wenn Sie zwei Verticen haben, der eine Rot, der andere blau, dann wird die Line dazwischen immer mehr von rot nach blau gezeichnet. Beispiel:


Abbildung 1:

Texturen: Texturen werden über Polygone(3 oder mehr Verticen) „gehängt“, um sie mehr lebensecht zu machen; ohne Texturen können Sie nur farbige Polygone erstellen. Texturen werden als 2D-Bitmaps in den Grafikkarten-(oder System)speicher geladen und werden dann über ein Rechteck gezogen und gedrückt. Es gibt zwei Dinge auf die man aufpassen muss:
  1. Die Koordinaten für Texturen gehen von 0 bis 1 und werden NICHT als Pixel angegeben(mehr gibt es später)
  2. Die Größe der Textur muss immer ein Quadrat aus 2 (2^X) sein. Zum Beispiel: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, ... - Obwohl allgemein normalerweise keine Texturen größer als 256 benutzt werden.
Koordinaten: Anfänglich werden wir nur mit zwei Koordinaten arbeiten - X und Y, aber wenn wir die dritte Dimension dazunehmen, dann kommt noch eine dazu(woher das wohl kommt :) - Z. Diese Koordinaten haben hiddentheoretisch keine Minimum oder Maximum(Praktisch hängt von der benutzten Variable ab).
hiddentransformationen: hiddentransformationen nennt sich das, was passiert, wenn Direct3D unsere 3D-Verticen, für den zweidimensionalen Bildschirm anpasst. Das wird auch später besprochen, da es sehr viel in diesem Bereich zu beschreiben gibt. In diesem Kapitel kommen Sie nur mit hiddentransformierten und beleuchteten Verticen in Berührung - diese sind schon für die 2D angepasst.
Lighting: Lighting is „Beleuchtung“(Übersetzungsmöglichkeit?) - eigentlich sollten Sie wissen, was das ist! Direct3D kann versuchen eine möglichst realistische Beleuchtung für Sie zu machen, oder Sie machen es selbst. Der einzige Grund warum das hier angesprochen wird ist, dass wir hiddentransformierte und beleuchtete Verticen benutzten, d.h. wir haben die Farbe schon vorher berechnet... Sie werden noch einiges mehr in dieser Richtung sehen, aber bis jetzt sollten Sie damit auskommen.

Der Anfang  

Ich werde dort einspringen, wo wir in Kapitel 1 angekommen sind. Sie sollten entweder den Initialisierungs-Code kennen, oder ihre Vorlage hervorkramen – er wird hier nicht wieder erklärt.

Wir müssen ein paar Änderung an unserem Rahmen machen, den wir haben(sollten):

'Dies ist das Flexible-Vertex-Format
'für 2D Verticen (Transformiert & beleuchtet)
Const FVF = D3DFVF_XYZRHW Or _
           D3DFVF_TEX1 Or _
           D3DFVF_DIFFUSE Or_
           D3DFVF_SPECULAR
'Diese Struktur beschreibt ein
' transformiertes und beleuchtetes Vertex -
'gleich dem Typ D3DTLVERTEX in DirectX 7.
Private Type TLVERTEX
   X As Single
   Y As Single
   Z As Single
   rhw As Single
   color As Long
   specular As Long
   tu As Single
   tv As Single
End Type

Dim TriStrip(0 To 3) As TLVERTEX
 'Wir brauchen vier Verticen für ein einfaches Rechteck .
Dim TriList(0 To 5) As TLVERTEX
Dim TriFan(0 To 5) As TLVERTEX

Ok, hier ist noch nichts kompliziertes. Der einzige neue Teil ist wahrscheinlich das Flexible Vertex Format. Das war eine Möglichkeit in DirectX7, nun müssen wir sie benutzten. Wenn wir Daten zum Renderer schicken, dann will dieser wissen, in was für einem Format sie sind - also welche Daten vorhanden sind. Die Reihenfolge, in dem Sie die Variablen in dem Typ zusammensetzen ist wichtig! Hier eine einfache Reihenfolge:Position X, Y, Z Koordinaten als ‚Single’

RHW Reciprocal Homogenus W als ‘Single’
BLEND1 Blendingwerte für Vertexmanipulation alle ‚Single’
BLEND2
BLEND3
NX Vertexnormal als 'Single'
NY
NZ
POINT SIZE Nur für Pointsprites as ‘Single’
DIFFUSE Vertex-Diffuse-Farbe als 32 Bit ARGB long.
SPECULAR Vertex-Specular als 32 Bit ARGB long
TEXCOORD1 Erstes Set für Texturekoordinaten
... ...
TEXCOORD8 Erstes Set für Texturekoordinaten

Eine Menge Zeug, das fern von dem Punkt ist, an dem wir jetzt sind. Aber wenn wirs brauchen, werden Sie erklärt. Wenn Sie das Flexible-Vertex-Format benutzten, dann werden Sie die Meisten sowieso nicht brauchen, es sei denn Sie wollen irgendetwas besonderes machen. Alle Standard-Vertex-Formate, die Sie vielleicht brauchen werden, werden später erklärt wenn Sie gebraucht werden. Sie müssen sich nur dann nur noch daran erinnern. :).

Jetzt müssen wir noch drei wichtige Änderungen in der Initialise-Funktion machen:

'################################
'## ZUERST ERSTELLEN WIR DAS OBJEKT
'################################
'Wir benutzten Fullscreen, weil ich ihn
'dem Fenster-Modus vorziehe :)

'DispMode.Format = D3DFMT_X8R8G8B8
DispMode.Format = D3DFMT_R5G6B5
  'Falls dieser Modus nicht läuft, benutzten Sie obigen.
DispMode.Width = 640
DispMode.Height = 480
D3DWindow.SwapEffect = D3DSWAPEFFECT_FLIP
D3DWindow.BackBufferCount = 1 'Nur ein Backbuffer

D3DWindow.BackBufferFormat = DispMode.Format
  'Was wir früher zurückgegeben bekommen haben
D3DWindow.BackBufferHeight = 480
D3DWindow.BackBufferWidth = 640
D3DWindow.hDeviceWindow=frmMain.hWnd
'#####################################
'## JETZT MÜSSEN WIR DEN DEVICE ERSTELLEN
'#####################################
'Dem Vertex-Shader beibringen, welche Vertexformat wir benutzen
D3DDevice.SetVertexShader FVF
'Transformatierte und beleuchtete Verticen brauchen kein Lighting
' also schalten wir es aus.
D3DDevice.SetRenderState D3DRS_LIGHTING, False
'#######################################
'## ZULETZT BRAUCHEN WIR EINE NEUE FUNKTION
'#######################################
'/Wir können nur weitermachen, wenn dieser Aufruf erfolg hatte.
If InitialiseGeometry() = True Then
  Initialise = True 'Alles geklappt
  Exit Function
End If

Jetzt haben wir unser Programm im 640x480x16 Modus, haben ein paar Renderoptionen gesetzt und haben eine neue Funktion implementiert. Renderoptionen(D3DDevice.SetRenderState) werden ab jetzt immer mehr benutzt – wir können fast alles was wir jemals brauchen werden damit kontrollieren.

Ich habe hier auch den wichtigsten Teil unseres neuen Codes vorgestellt – Die InitialiseGeometry-Funktion. Also kann ich Sie auch gleich erklären.

Grafik initialisieren  

Das ist praktisch das gesammte Thema dieses Kapitels - und diesen Teil müssen Sie lernen. Das heißt leider, dass Sie wieder mit etwa Theorie konfrontiert werden müssen - jetzt fängt es an ein bisschen kompliziert zu werden(na gut, nicht sehr).

Obwohl Sie Hunderte von Referenzen über die Benutzung von Polygonen in 3D-Welten finden könnten, benutzten wir nur Dreiecke, was natürlich immer noch das selbe ist(ein Dreieck ist das einfachst mögliche Polygon).Der Grund warum wir nur Dreiecke benutzten ist die Renderengine - Dreiecke sind nie nach innen gewölbt und damit sind sie leichter zu rendern. Wenn Sie ein bisschen darüber nachdenken, kann man aus Dreiecken alle Formen erstellen. Ein Rechteck sind nur zwei Dreiecke und so können alle eckigen Formen aus Dreiecken gemacht werden:


Abbildung 2: Verschiedene Figuren aus Dreiecken zusammengebaut

Wie Sie sehen können, sind einige Formen leichter als Andere.

Wenn wir die Daten für die Dreiecke zusammenstellen, dann müssen wir wissen, in welchem Format wir die Daten übergeben wollen. Es gibt mehrere Möglichkeit um Primitive zu zeichnen:

Dreieck-Strip (D3DPT_TRIANGLESTRIP)


Abbildung 3: Ein Dreieck-Strip

Dreieck-Strips werden öfter benutzt. Mit dieser Methode können Sie Ihre Dreiecke mit weniger Verticen rendern, als wenn Sie jedes einzelne Dreieck definieren. Wie Sie oben sehen, erstellen wir sechs Dreiecke mit acht Verticen(anstatt mit 18, die wir brauchen würden, wenn wir jedes einzelne definieren würden). Der einzige Nachteil ist, dass einige Grafikartefakte entstehen könne, wenn wir Lighting benutzten. Dreieck-Liste (D3DPT_TRIANGLELIST)


Abbildung 4: Eine Dreieck-Liste

Dreiecklisten sind etwas älter als die Trianglestrip Anordnung; sie bieten aber eine höhere Flexibilität. Sie müssen mit Dreiecklisten viel mehr Verticen rendern, können aber dafür den Vorteil benutzten, dass die Dreiecke nicht miteinander verbunden sein müssen. Wenn Sie versuchen, so etwas wie eine Ecke(wie eine Ecke eines Korridors), mit Dreiecklisten zu erstellen, dann werden Sie bemerken, dass Licht um die Ecke dringt - und nicht so gut aussehen würde. Dreieck-Fan (D3DPT_TRIANGLEFAN)


Abbildung 5: Ein Dreieck-Fan

Dreieckfans sind gut für Dinge, die rund aussehen sollen, wie Sie im obigen Bild sehen können. Ein kreisförmige Form mit Dreieck-Strips oder Dreiecklisten zu rendern ist wahnsinnig schwierig. Dreieckfans sind grundsätzlich eine Gruppe von Dreiecken, deren erster Punkt immer 0 ist. In diesem Beispiel sind die Dreiecke 0,1,2; 0,2,3; 0,3,4. Linien-Strip (D3DPT_LINESTRIP)


Abbildung 6: Ein Linien-Strip

Linien-Strips könne sehr brauchbar werden; sie können alle Dreiecke, die Sie erstellt haben umrahmen; oder noch wichtiger können Sie einen Dreieck-Strip durch einen Linien-Strip ersetzten, um eine Gitter-ähnliches Muster zu erhalten; exzellent fürs Debugging. Linien-Liste (D3DPT_LINELIST)


Abbildung 7: Eine Linien-Liste

Linienlisten werden in einem richtigen Spiel kaum eingesetzt, höchstens für Blitze und Funken - obwohl diese eher mit Billboards realisiert werden. Punktliste (D3DPT_POINTLIST)


Abbildung 8: Eine Punktliste

Eine Punktliste kann benutzt werden um Partikel zu zeichnen - diese Funktion wird in DirectX aber eher von Pointsprites übernommen. Sie können Punktlisten wie die den Linien-Strip fürs Debugging benutzten, aber es ist manchmal schwer zu sehen, was passiert.

OK, hoffentlich wissen Sie jetzt was gemeint ist, und sollten fähig sein, für ihre Form die richtige Anordnung zu wählen. Jetzt können wir mit dem Initialisierungs-Code weitermachen:

'Diese Funktion füllt nur die Verticen
'mit dem, was wir haben wollen
Private Function InitialiseGeometry() As Boolean
  On Error Goto BailOut:
  'Fehlerbehandlung einstellen
  '##################
  '## DREIECKSTRIP
  '##################
  'Wir erstellen ein Viereck mit einem Dreieckstrip
  'im Uhrzeigersinn
  'Wenn Sie die Reihenfolge nicht einhalten
  ', dann wird das Viereck entweder
  'nicht oder anders als geplant erscheinen
    '0 - - - - - 1
    '             /
    '           /
    '         /
    '       /
    '     /
    '   /
    ' /
    '2 - - - - - 3

    'Ignorieren Sie einfach den Z-Parameter.
      'Er wird nicht gebraucht.
    'Wie Sie sehen können erstellen wir einfach 2 Dreiecke
    'vertex 0
  TriStrip(0) = CreateTLVertex(10, 10, 0, _
    1, RGB(255, 255, 255), 0, 0, 0)
     'vertex 1
  TriStrip(1) = CreateTLVertex(210, 10, 0, _
    1, RGB(255, 0, 0), 0, 0, 0)
      'vertex 2
  TriStrip(2) = CreateTLVertex(10, 210, 0, _
    1, RGB(0, 255, 0), 0, 0, 0)
       'vertex 3
  TriStrip(3) = CreateTLVertex(210, 210, 0, _
    1, RGB(0, 0, 255), 0, 0, 0)

  '#################
  '## DREIECK-LISTE
  '################
'Wir machen ein pfeilähnliches Gebilde aus zwei Dreieck.
' Dies ging zwar auch mit Dreieck-Strips,
'aber als Übung können wir es auch so machen.

    '// DREIECK 1
    'vertex 0
  TriList(0) = CreateTLVertex(210, 210, 0, _
    1, RGB(0, 0, 0), 0, 0, 0)
    'vertex 1
  TriList(1) = CreateTLVertex(400, 250, 0, _
    1, RGB(255, 255, 255), 0, 0, 0)
    'vertex 2
  TriList(2) = CreateTLVertex(250, 250, 0, _
    1, RGB(255, 255, 0), 0, 0, 0)
    '//DREIECK 2
    'vertex 3
  TriList(3) = CreateTLVertex(210, 210, 0, _
    1, RGB(0, 255, 255), 0, 0, 0)
    'vertex 4
  TriList(4) = CreateTLVertex(250, 250, 0, _
    1, RGB(255, 0, 255), 0, 0, 0)
    'vertex 5
  TriList(5) = CreateTLVertex(250, 400, 0, _
    1, RGB(0, 255, 0), 0, 0, 0)

  '#################
  '## DREIECK-FAN
  '################
  'Hier erstellen wir eine Dreieck-Fan, welcher
  'sehr gut für kreisförmige Formen ist
  'In diesem Beispiel erstellen wir nur
  'eine hexagonale Form

  'Hauptpunkt

  TriFan(0) = CreateTLVertex(300, 100, 0, _
    1, RGB(255, 0, 0), 0, 0, 0)

  'Verticen rund um den Punkt.
   TriFan(1) = CreateTLVertex(350, 50, 0, _
     1, RGB(0, 255, 0), 0, 0, 0)
   TriFan(2) = CreateTLVertex(450, 50, 0, _
     1, RGB(0, 0, 255), 0, 0, 0)
   TriFan(3) = CreateTLVertex(500, 100, 0, _
     1, RGB(255, 0, 255), 0, 0, 0)
   TriFan(4) = CreateTLVertex(450, 150, 0, _
     1, RGB(255, 255, 0), 0, 0, 0)
   TriFan(5) = CreateTLVertex(350, 150, 0, _
     1, RGB(255, 255, 255), 0, 0, 0)

  InitialiseGeometry = True
Exit Function

BailOut:
  InitialiseGeometry = False
End Function

'Dies ist nur eine kleine Funktion,
'die das Füllen von Verticen einfacher macht.
Private Function CreateTLVertex(X As Single, _
  Y As Single, Z As Single, rhw As Single, _
  color As Long, specular As Long, _
  tu As Single, tv As Single) As TLVERTEX
'Obwohl Sie Single-Werte übergeben können,
'gibt es einen kleinen Haken
'Direct3D rundet die Werte bis zu einem bestimmten Punkt
'was unerwartete Ergebnisse geben kann.

  CreateTLVertex.X = X
  CreateTLVertex.Y = Y
  CreateTLVertex.Z = Z
  CreateTLVertex.rhw = rhw
  CreateTLVertex.color = color
  CreateTLVertex.specular = specular
  CreateTLVertex.tu = tu
  CreateTLVertex.tv = tv
End Function

Ihre erste Enhiddentdeckung wird wohl gewesen, sein, dass es ziemlich viele Zeilen sind um etwas ziemlich einfaches zu machen. Überlegen Sie sich mal, was für eine Arbeit erst komplexe Formen sind... Das ist auch der Grund, warum Spiele schon erstellte Objekte zur Laufzeit einladen; mehr dazu in einem späteren Kapitel.

Der ganze Code hat nur eine Funktion, nämlich die Daten für jedes Vertex festzulegen. Ich beschreibe jetzt noch den jeden Teil der Struktur: X Die X-Koordinate

Y Die Y-Koordinate
Z Das ist ein Wert zwischen 0,0 und 1,0; wo 0 vorne und 1 hinten ist. Hätten wir einen Z-Buffer eingebaut, könnten wir Objekte über Anderen erscheinen lassen. Falls alle den gleichen Wert haben(wie in unserem Beispiel), dann hängt alles davon ab in welcher Reihenfolge die Objekte gerendert werden- was immer auch zuletzt gerendert wird, es ist vorne.
RHW Das kann sowieso fast immer ignoriert werden.; Sie werden es wahrscheinlich sowieso nicht benutzten(In zwei Jahren habe ich es nie gebraucht). Lassen Sie es auf 1, ansonsten funktionieren Schatten nicht.
COLOR Das ist ein Long-Wert für die Farbe - Sie können oft mit der RGB(r,g,b)-Funktion wegkommen, sollten aber hexadezimale Werte benutzen.
SPECULAR Dies ist die Reflektivität des Vertexes
TU Dies ist die Vertex-X-Koordinate; Mehr dazu im nächsten Kapitel.
TV Dies ist die Vertex-X-Koordinate; Mehr dazu wieder im nächsten Kapitel.

Nun wissen wir wie wir die Daten initialisieren, jetzt können wir den Render-Code verändern.

Die Dreiecke rendern  

Das letzte was wir noch machen müssen, ist die Dreiecke zu rendern. In Kapitel 1 haben Sie die Prozedur gesehen, aber damals hat es noch nichts getan. Jetzt ändern wir das:

Public Sub Render()
  '1. Zuerst müssen wir den Bildschrim leeren.
  '  Das muss immer passieren,
  '  bevor wir irgendetwas rendern

  D3DDevice.Clear 0, ByVal 0, _
    D3DCLEAR_TARGET, &HCCCCFF, 1#, 0
  'Der Wert in der Mitte ist Hexadezimal,
  'wenn Sie das von HTML her kennen.

D3DDevice.BeginScene
  'Zuerst der Dreieck-Strip
  D3DDevice.DrawPrimitiveUP D3DPT_TRIANGLESTRIP, _
    2, TriStrip(0), Len(TriStrip(0))

  'Jetzt die Dreieckliste
  D3DDevice.DrawPrimitiveUP D3DPT_TRIANGLELIST, _
    2, TriList(0), Len(TriList(0))

  'Zuletzt der Dreieck-Fan
  D3DDevice.DrawPrimitiveUP D3DPT_TRIANGLEFAN, _
    4, TriFan(0), Len(TriFan(0))

  '//3. Den Frame auf den Bildschirm kopieren
  'Dieser Aufruf ist ungefähr das selbe wie
  'Primary.Flip() in DirectX7
  'Diese Werte solten für fast alles arbeiten.

  D3DDevice.Present ByVal 0, ByVal 0, 0, ByVal 0
End Sub

Das einzige Neue hier sind die drei Zeilen zwischen den „D3DDevice.BeginScene“ und „D3DDevice.EndScene“ Zeilen. Diese Zeilen sind die Aufrufe zum Dreieckrendern – zumindestens bis jetzt, später werden wir uns dann noch mit Vertex-Buffern beschäftigen.

<center>

  object.DrawPrimitiveUP( _
    PrimitiveType As CONST_D3DPRIMITIVETYPE, _
    PrimitiveCount As Long, _
    VertexStreamZeroDataArray As Any, _
    VertexStreamZeroStride As Long)
</center>
Object Ein Direct3Ddevice8 Objekt
PrimativeType Damit DirectX weiß, was wir ihm senden.
PrimativeCount Die Nummer der Dreiecke; Bis auf der Dreieck-Fan sind alle Objekte aus 2 Dreiecken.
VertexStream-ZeroDataArray Langer Name, aber einfach. Das ist einfach nur das erste Objekt zum Rendern – Sie könnten auch nur die Hälfte rendern, wenn Sie nur die Mitte des Array übergeben.
VertexStream-ZeroStride Die Größe der Struktur; weil Direct3D nicht richtig weiß, wie die Struktur aufgebaut. Es muss die Größe und das FVF benutzten um das auszurechnen. Übergeben Sie einfach immer die Länge des ersten Elements – es sei den, dass irgendwelche verrückten Dinge passieren. Normalerweise sollten alle Objekte die selbe Größe haben.

OK, jetzt sollten Sie wissen, wie man eine einfach 2D-Welt rendert. Im Moment ist es noch 2D, aber Sie werden auch noch lernen, wie das Ganze in 3D funktioniert.

Als eine einfach Übung können Sie ja versuchen, die Sternform auf Kapitel 3.4 zu zeichnen.

Beispielprojekt zum Tutorial [16300 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.