2D-Grafiken (1)
von Jack Hoxley (übersetzt von Daniel Noll)
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: |
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:
|
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
Abbildung 4: Eine Dreieck-Liste
Abbildung 5: Ein Dreieck-Fan
Abbildung 6: Ein Linien-Strip
Abbildung 7: Eine Linien-Liste
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.