Die Community zu .NET und Classic VB.
Menü

Lighting

 von 

Übersicht 

Lighting ist ein weiterer wichtiger Teil Direct3Ds und natürlich wieder eine Grundlage dreidimensionaler Grafik. Außerdem müssen wir uns hier zum ersten Mal mit ein wenig der unterliegenden Mathematik beschäftigen (so schlimm wird es aber nicht). Auch zu diesem Kapitel exisitiert ein Beispielprojekt, welches sie hier finden.

Theorie: Lighting  

Vertices und Polygone geben der 3D-Welt ihre Form, Texturen erweitern das ganze im Detail und Licht verändert die Stimmung des gezeichneten Bildes Was wären Spiele, wie z.B. Splinter Cell und ähnliche, wenn es keine dunklen Bereiche gäbe und alles eine Helligkeit besäße. Zum Einen sähe das Spiel dann nicht mehr reell aus und zum Anderen käme man ohne Schatten, in denen man sich verstecken kann, auch nicht besonders weit.

Jetzt von dem Einsatzbeispiel für Lighting zurück zur Theorie (leider gibts die auch noch). Mit Lighting kontrollieren wir die Beleuchtung unserer 3D-Welt (um z.B. die obengenannten Effekte zu erreichen). Grundsätzlich ist dies nichts anderes als das was wir schon im zweiten Tutorial getan haben, allerdings lassen wir jetzt Direct3D, natürlich auf Basis der Texturen, über die Farbe bestimmen. Dabei versucht Direct3D das Licht in der realen Welt möglichst genau nachzubilden, dies gelingt selbstverständlich nur begrenzt.

Die vier Lichttypen

Direct3D bietet vier unterschiedliche Lichttypen: Ambient-, Directional-, Point- und Spot-Lighting, obwohl dem Programmierer mit Pixel Shadern praktisch eine Möglichkeit geboten wird eigene “Lichter” zu definieren (was allerdings ein sehr weit fortgeschrittenes Thema ist). Für uns sollten die vier gebotenen Typen jedoch genügen.

Ein paar Worte zur Geschwindigkeit

Die verschiedenen Lichttypen unterscheiden sich teilweise ziemlich stark in der Geschwindigkeit. Am schnellsten ist meistens Ambient Lighting, gefolgt von Directional Lighting. Point-Lighting braucht ein wenig mehr Zeit als Directional Lighting und Spot-Lighting ist meistens am langsamsten. Die Berechnungszeit hängt natürlich nicht nur von dem verwendeten Typ ab, sondern auch vom Detail der gezeichneten 3D-Welt. Bei der Verwendung von Lighting muss die Farbe für jedes Vertex getrennt berechnet werden, so dass die dafür gebrauchte Zeit bei höherer Vertexanzahl dementsprechend steigt. Das Gegenteil davon wäre es ein Licht dort zu positionieren wo sich keine Körper befinden, oder an eine Stelle an die der Spiele sowieso nicht hinsieht. Auch das wäre Verschwendung von wertvollen Resourcen.

Zusätzlich beeinträchtigt auch die Reichweite des Lichtes die Geschwindigkeit. Logisch, da je weiter das Licht scheint mehr Vertices beleuchtet werden. Reflektierendes Lichter sind ein nettes Feature, brauchen allerdings mehr als die doppelte Zeit zum Rendern.

Zuletzt muss noch ein Wort zu der Anzahl der maximal aktiven Lichter gesagt werden. Viele Grafikkarten setzen eine Grenze bei den aktiven Lichter. Ein Geforce 4 Ti erlaubt z.B. maximal 8 aktive Lichter in einer Szene, andere wie z.B. der Vorgänger Geforce 2 erlauben immerhin 16. Deswegen sollten Sie entweder die Anzahl der Lichter in einer Szene unter acht halten oder zumindestens zuerst mit

Dim D3DCaps As Caps = Manager.GetDeviceCaps(0, DeviceType.Hardware)
If D3DCaps.MaxActiveLights > 8 Then '...

Listing 1

kontrollieren, ob das Device mehr als 8 Lichter unterstützt.

Noch ein paar Begriffe

Die Lichtrichtung wird als normalisierter Richtungsvektor angegeben. Ein Vektor wie z.B. [0,1,0] würde das Licht dazu bringen von unten nach oben zu leuchten (es sei denn Sie haben mit der Projection-Matrix Mist gebaut :)

Licht wird auf seinem Weg immer schwächer. Mit der Attenuation-Eigenschaft der Licht-Objekte (dazu später mehr) können Sie dies kontrollieren. Werte zwischen 0 und 1 sind der normale Fall und geben an, dass das Licht über die Entfernung immer schwächer wird. Mit Werten über 1 produziert man Licht das bis zum Ende immer stärker wird.

Ein Normalenvektor zeigt Direct3D in welche Richtung das gerenderte Polygon zeigt. Das ist wichtig da Direct3D nicht einfach so entscheiden kann, in welche Richtung das Polygon zeigt und ein Polygon, welches von der Lichtquelle abgewendet ist, logischerweise kein Licht abbekommt. Um die Berechnung des Normalenvektors geht es im nächsten Abschnitt.

Die Berechnung der Normalen  

Das mathematische Problem bei der Berechnung einer Normalen liegt darin, einen Vektor zu berechnen, der senkrecht von der Fläche wegzeigt. Unser Problem liegt darin, dass wir ohne den Vektor nur einen schwarzen Bildschirm erhalten :-)


Abbildung 1: Bild aus der DirectX9-Hilfe

Zuerst müssen wir zwei Vektoren erstellen, die z.B. von Vertex 2 zu Vertex 1 und Vertex 3 zu Vertex 1 zeigen. Danach können wir über das Kreuz-Produkt einen Vektor berechnen der senkrecht von der Fläche wegzeigt. Im Code sieht das ganze so aus:

Private Function ComputeNormal(ByVal v1 As Vector3, _
                                ByVal v2 As Vector3, _
                                ByVal v3 As Vector3) As Vector3
  Dim retval As Vector3

  retval = Vector3.Cross(Vector3.Subtract(v2, v1), Vector3.Subtract(v3, v1))
  Return Vector3.Normalize(retval)
End Function

Listing 2

Dank den Helferfunktionen der Vector3-Klasse geht die Sache glücklicherweise recht einfach.

Lighting initialisieren  

Bevor wir jedoch anfangen können wie verrückt Lichter zu erstellen, müssen wir noch ein paar Dinge verändern, damit das Beleuchten unserer Szene endlich funktioniert.

Zuerst einmal müssen wir mal wieder ein neues Vertex-Format verwenden, dass zusätzlich noch Platz für unsere Normale bietet. Danach kommt das Problem der Berechnung. Hier zahlt es sich wieder aus, dass wir eine TriangleList anstatt eines TriangleStrips verwendet haben. Bei einem TriangleStrip hätten wir das Problem gehabt, dass viele Vertices für mehrere Flächen verwendet werden würden, welche allerdings unterschiedliche Normalen besitzen.

v1 = New Vector3(-1, 1, 1) 
  v2 = New Vector3(1, 1, 1) 
  v3 = New Vector3(-1, 1, -1)
  n = ComputeNormal(v1, v2, v3)
cube(0) = New CustomVertex.PositionNormalTextured(v1, n, 0, 0)
cube(1) = New CustomVertex.PositionNormalTextured(v2, n, 1, 0)
cube(2) = New CustomVertex.PositionNormalTextured(v3, n, 0, 1)
  v1 = New Vector3(1, 1, -1) 
  v2 = New Vector3(-1, 1, -1)
  v3 = New Vector3(1, 1, 1)
  n = ComputeNormal(v1, v2, v3)
cube(3) = New CustomVertex.PositionNormalTextured(v1, n, 1, 1)
cube(4) = New CustomVertex.PositionNormalTextured(v2, n, 0, 1)
cube(5) = New CustomVertex.PositionNormalTextured(v3, n, 1, 0)

Listing 3

Da wir auch weiterhin die Texturkoordinaten brauchen ist PositionNormalTextured genau das richtig Format. Das Angeben der Position funktioniert hier etwas anders, da sie zuerst zwischengespeichert wird, damit sie beim Berechnen der Normalen nicht nochmal angegeben werden muss.

Zweitens braucht unser Würfel ein Material. Mit dem Material können wir z.B. kontrollieren, ob ein Objekt Licht reflektiert. Nachdem wir das Material erstellt und Direct3D bekannt gemacht haben, können wir es in unserem Fall einfach vergessen:

With p_Material
  .Ambient = Color.White
  .Diffuse = Color.White
End With

Listing 4

Last but not least müssen wir noch ein paar Direct3D-Einstellungen setzen. Zuerst einmal müssen wir Lighting aktivieren:

.RenderState.Lighting = True

Listing 5

Wenn wir das Licht initialisiert haben, so müssen wir es nur noch Direct3D bekannt machen und anschalten:

With p_D3DDevice.Lights.Item(0)
  ' hier setzen wir die Eigenschaften
  .Commit()
  .Enabled = True
End With

Listing 6

Knipps! Und es ward Licht :-)

Die verschiedenen Lichttypen  

Ambient Lighting

Ambient Lighting ist von den vier Lichttypen am leichtesten zu initialisieren:

.RenderState.Ambient = Color.Gray

Listing 7

Eine Zeile Code, und das ist alles. Für Ambient Lighting müssen wir nur eine Farbe setzen, mit der dann die Szene gleichmäßig beleuchtet wird.


Abbildung 2

Directional Light


Abbildung 3

Directional Lighting ist am Besten wenn es darum geht eine komplette Szene auszuleuchten. Es hat eine Farbe und eine Richtung, aber keine Position oder Reichweite.

With p_D3DDevice.Lights.Item(0)
  .Type = LightType.Directional
  .Direction = New Vector3(0, -1, 0)
  .Diffuse = Color.White
  .Commit()
  .Enabled = False
End With

Listing 8

Als Richtung können Sie jeden beliebigen Vektor außer [0,0,0] verwenden

Point Light


Abbildung 4

Point Lights besitzen eine Position, eine Farbe, eine Reichweite und strahlen in alle Richtungen. Hier der Code zum Initialisieren eines Point Lights:

With p_D3DDevice.Lights.Item(1)
  .Type = LightType.Point
  .Position = New Vector3(3, 0, -5)
  .Diffuse = Color.Blue
  .Range = 100
  .Attenuation0 = 0.5
  .Commit()
  .Enabled = False
End With

Listing 9

Hiermit haben wir ein blaues Point-Light erstellt.

Spot Light


Abbildung 5

Spot-Lights sind die komplexesten der vier Grundlichttypen. Sie haben eine Position von der aus sie in eine bestimmte Richtung mit einer bestimmten Farbe scheinen:

With p_D3DDevice.Lights.Item(2)
  .Type = LightType.Spot
  .Position = New Vector3(0, 0, -5)
  .Range = 100
  .Direction = New Vector3(0, 0, 1)
  .InnerConeAngle = 20 * (Math.PI / 180)
  .OuterConeAngle = 50 * (Math.PI / 180)
  .Diffuse = Color.Yellow
  .Commit()
  .Enabled = False
End With

Listing 10

Zusätzlich müssen noch zwei Winkel (im Bogenmaß) angegeben werden. Der OuterConeAngle beschränkt den Bereich in den das Licht scheint, im Inner Cone scheint es intensiver.


Abbildung 6

Das Beispielprojekt zu diesem Tutorial herunterladen [11243 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.