Die Community zu .NET und Classic VB.
Menü

Einführung in Lighting

 von 

Einführung  

Das Programmieren der Beleuchtung ist mein Lieblingsteil beim Computerspiel-designen. Nachdem man die Beleuchtung hinzugefügt hat bekommt das Spiel eine ganz neue Qualität - besonders wenn man es richtig macht. Bis jetzt haben wir immer die Farben für unsere Verticen bestimmt - was uns ermöglicht eine eigene Lightingengine zu schreiben. Für ein Beispiel spielen Sie einfach mal ihr Lieblings-Voll-3D-Spiel(solche wie Deus Ex oder Half Life) und untersuchen mal für eine kurze Zeit die Beleuchtung. Dann stellen Sie sich einfach mal vor, wie es ohne diese Lichter wäre; alles von der selben Helligkeit und keine Schatten. Wie oben erwähnt ist Deus Ex ein sehr gutes Beispiel (und ein super Spiel Punkt), es gibt sehr viele Stellen im Spiel in denen man in dunklen Bereichen ist. Wie schon weiter oben erwähnt, DeusX ist ein perfektes beispiel für Lighting. Viele Bereiche des Spieles sind sehr dunkel und nur von Spot- und Straßenlichern ausgeleuchtet. In diesem Spiel ist es überlebenswichtig sich im Schatten zu verstecken, was zeigt das Lighting den Spielverlauf stark beeinflusst.

Jetzt wo ich herausgedeutet habe, wie sehr die Beleuchtung die Atmosphäre verbessert, will ich jetzt auch noch herausdeuten, wie sehr sie die Atmosphäre zerstören kann - wenn sie falsch eingesetzt wird. Wie viele Sachen bei der Spieleentwicklung, kann die Beleuchtung als eine Art Kunst gesehen werden - setzen Sie die Lichter an die falschen Stellen, in einer falschen Kombination oder zur falschen Zeit und Sie bekommen ein großes Durcheinander. Bunte Lichter können z.B. sehr schön aussehen, doch 100 von ihnen, die durch die Gegend sausen werden bald sehr blöd aussehen.

Um die Beleuchtung zu benutzen müssen Sie zuerst verstehen, was Beleuchtung ist - und wie sie zu benutzen ist; was wieder mal nach eine bisschen Theorie schreit...

Lighting-Theorie  

Was ist Direct3D Lighting?

Die Beleuchtung von Direct3D ist eine Annäherung daran, wie Licht in der wirklichen Welt funktioniert. Es ist unwahrscheinlich, dass sie in den nächsten fünf Jahren einen Computer bekommen werden, welcher wirkliches Licht genau berechnen kann. Hoffentlich wissen Sie wie Licht "funktioniert" - es läuft immer in geraden Linien und verschwindet über eine gewisse Entfernung(und ist sehr schnell). Bis jetzt haben sie nur 3D-Geometry gesehen, in der die Farbe gesetzt war und Direct3D hat dann diese Farbe über das Dreieck geblendet. Diese Farbe ist Licht. Die Direct3D-Lighting-Engine ersetzt die Fähigkeit die Farbe selbst zu setzen dadurch, dass Direct3D die Farbe selbst berechnet (basiert auf den verschiedenen Lichttypen) und dann über die Dreicke blendet.

Welche Lichttypen sind verfügbar? Es gibt hauptsächlich vier Lichttypen, die Sie benutzen können. Es gibt allerdings auch noch weitere Features, die es ermöglichen eigene Lichttypen zu erstellen(Pixel Shader und Lit Vertices, die wir ja schon kennen gelernt haben). Die vier Haupttypen werden hier erklärt:

Point Light: Point Light wird von einem Punkt in der 3D-Landschaft repräsentiert, welcher bestimmte Lichtabgabeeigenschaften besitzt - Reichweite, Farbe und die Verminderung(über die Entfernung). Dieser Punkt gibt Licht in alle Richtungen ab, also wie eine Kerze oder ein Stern(ein ganz kleiner).

Spot Light: Ein Spot Light hat eine Position und eine Richtung - das Licht gibt in diese Richtung Licht ab, ungefähr wie eine Straßenlaterne oder eine Taschenlampe. Sie müssen außerdem noch den Winkel des Kegels und die Reichweite angeben.

Directional Light: Diese Licht hat keine bestimmte Position, sondern beleuchtet die Verticen, als ob Licht aus einer definierten Richtung kommen würde. Directional Lights sind am Besten um besonders große Lichter, wie z.B. die Sonne, nachzuahmen.

Ambient Light: Wenn Sie Ambient Light einstellen, dann müssen Sie eine Farbe angeben - und alle Verticen werden nicht dunkler als diese Farbe. Ambient Light wird von Spielen benutzt, um Nachmittag- oder Nachtszenen zu erstellen - ohne hunderte Lichter überall. Es ist ziemlich logisch, dass es nichts bringt, weitere Lichter in einer Szene zu haben, wenn das Ambient Light hellweiß ist.

Ein paar Worte zur Geschwindigkeit:

Obwohl die meisten Render-Devices keine Begrenzung für die Anzahl der Lichter in einer Szene haben, ist es ziemlich klar, dass je mehr Lichter Sie in einer Szene haben, desto langsamer die Szene gerendert wird. Außerdem haben die meisten Hardware Transform & Lighting-Grafikkarten doch eine Grenze: 8 bei einer GeForce256, bei der GeForce2 sind immerhin schon 16; also: für vollständige Kompatibilität überprüfen Sie die maximale Anzahl Lichter über Enumeration oder halten Sie die Anzahl der Lichter unter 8.

Sie sollten auch wissen, dass die verschiedenen Lichttypen zur Berechnung unterschiedliche Zeit brauchen: normalerweise ist Ambient Light am schnellsten, gefolgt von den Directional Lights und den Point Lights, am langsamsten sind die Spot Lights.

Die Reichweite zählt - wenn ihr Licht einen großen Bereich überspannt, dann ist die Anzahl der betroffenen Verticen entsprechend hoch - jedes dieser Verticen verlängert die Berechnung um ein bisschen. Die generelle Regel ist allerdings: Halten Sie die Reichweite so gering wie möglich.

Specular Lighting ist ein lustiges kleines Feature, welches Ihnen erlaubt spiegelnde Objekte zu erschaffen - Diamanten oder Kristalle zum Beispiel. Der Grund für diesen Punkt ist, dass Specular Lighting ungefähr die Berechnungszeit verdoppelt - wenn Sie sie also benutzen, benutzen Sie sie wenig.

Das Detail der 3D-Welt ist ebenso wichtig; wie schon gesagt, das Licht wird von Vertex per Vertex berechnet - je mehr Verticen, desto länger die Berechnungszeit. Obwohl es für dieses Problem keine richtige Lösung gibt sollten Sie beim Leveldesign immer im Kopf behalten - weniger Lichter in komplexen Regionen. Auch ziemlich logisch ist es, dass es nichts bringt, ein Licht irgendwo anzubringen, wo der Spieler nie hinkommen wird - Sie verschwenden nur wertvolle Berechnungszeit damit.

Schatten tauchen nicht von alleine auf - sie sind ein weit fortgeschrittenes Thema. Wie Sie wissen, wandert Licht in einer geraden Linie, wenn es also ein festes Objekt trifft, geht es nicht weiter - alles dahinter bekommt kein Licht von dieser Quelle. Eine Berechnung, ob jeder Strahl von irgendeinem Objekt gestoppt wurde, ist sehr aufwändig - deshalb lässt Direct3D diese Berechnung weg. Es berechnet das Licht über die Ausrichtung der Verticen, so dass die Rückseite eines Objects(welches von der Lichtquelle wegzeigt) kein Licht bekommt.

Wörter und Begriffe, die Sie kennen sollten:

Es gibt ein paar Wörter und Begriffe, die Sie verstehen sollten, bevor Sie anfangen eigene Lichter aufzustellen. Allerdings sind keine besonders schwer:

Reichweite: Wie weit das Licht geht; alle Verticen hinter dieser Reichweite werden nicht beleuchtet. Wenn Sie die Reichweite klein halten, dann umso besser.

Verminderung über die Entfernung: Beschreibt, wie sich das Licht über die Entfernung verändert - wie es verschwindet normalerweise. Es ist möglich verrückte Dinge mit diesem anzustellen z.B. Licht das in der Entfernung immer heller wird.

Richtung des Lichts: Die Richtung ist ein normalisierter Vektor, der die Richtung beschreibt in die das Licht zeigt - ein normalisierter Vektor ist ein Vektor mit der Länge 1. Hoffentlich habe Sie eine kleine Vorstellung wie Vektoren funktionieren; obwohl Sie sie benutzen können eine Position darzustellen, können benutzt man sie normalerweise dazu Richtungen/Entfernungen relativ zum Ursprung(Nullpunkt) anzuzeigen. Zum Beispiel hat der Vektor [0,8,0] eine Länge von 8(8 Einheiten vom Ursprung) und verursacht, dass sich irgendetwas in die +Y-Achse verändert.

Position: Ziemlich klar - aber es ist meist die Position mit denen die Leute lustige Ergebnisse erhalten. Direct3D beleuchtet ein Vertex nach seinem Normal; wenn Sie ein Gitter von Verticen als Boden haben und ein Licht, das nah am Boden ist, dann wird es so aussehen, als ob das Licht einen kleinen Einflussbereich hat - unbeachtet der Reichweite und der Verminderung. Das ist wirklich einfache Mathematik - wenn ein Vertex also nach seinem Normal(eine Richtung) berechnet wird, dann ist es klar, dass das Vertex wenig Licht bekommt, wenn der Winkel zwischen Normal und Richtung sehr klein ist(das passiert in diesem Fall).

Das Normal eines Vertex: Das ist jetzt mal wieder ein sehr wichtiger Teil und ich werde Ihnen auch noch später zeigen, wie man ein Normal erzeugt. Als ein Beispiel, nehmen Sie ein Dreieck das in der Gegend rumrotiert wird. Wie soll Direct3D wissen in welche Richtung es zeigt? Das menschliche Auge könnte das vielleicht sagen, was der Direct3D Engine allerdings nichts nutzt; sie braucht Zahlen. Ein Normal erzählt Direct3D also, in welche Richtung das Vertex NICHT zeigt - wenn das Licht also hinter dem Dreieck ist, dann bekommt es kein Licht ab. Ein Normal muss eine Länge von 1 haben, was kein größeres Problem darstellt, da es verschiedene Features gibt, die das für Sie übernehmen.

Ein Normal erstellen  

Bevor wir in größeres Detail übergehen, müssen Sie erst noch wissen wie Sie ein Normal für ihr Dreieck berechnen. Nachdem Sie gelernt haben, wie, ist es nicht mehr sehr schwer. Wenn Sie den Normal allerdings falsch berechnen, dann kann das zu verrückten und frustrierenden Ergebnissen führen. Um ein Normal zu generieren, folgen Sie diesen Schritten:

  1. Sorgen sie dafür, dass die Verticen in z.B. im Uhrzeigersinn angeordnet sind.
  2. Erstellen Sie einen Vektor von Vertex 0 nach Vertex 1
  3. Erstellen Sie einen Vektor von Vertex 0 nach Vertex 2
  4. Berechnen Sie das Kreuzprodukt der beiden eben-erstellten Vektoren
  5. Normalisieren Sie dieses Ergebnis. Das ist zwar nicht besonders nötig, aber garantiert, dass es zu keinen Fehlern kommt.

Diese Anweisungen bauen wir jetzt zu einem Stück Code zusammen, der uns das Normal berechnet. Vorher sollten Sie noch wissen, dass die obigen Anweisungen den Code für das gesamte Dreieck berechnen - was ok ist, wenn sie es so wollen(kopieren Sie einfach die Werte zu allen drei Verticen) - aber wenn mehr als ein Dreieck sich ein Vertex teilen, dann wollen Sie vielleicht, dass das Normal beide Seiten repräsentiert. Dies kann ganz einfach durch Addieren der beiden Verticen getan werden, wie dieses Diagramm zeigt:


Abbildung 1: Die Erstellung eines Normals

In diesem Diagram sehen wir zwei Seiten(in 2D) repräsentiert durch schwarze Linien; die grünen Pfeile stellen die Normals da. Bei dem verbindenden Vertex in der Mitte gibt es einen "Kampf", welches Vertex benutzt wird. Wenn eines benutzt wird, dann sieht die andere Seite schlecht aus. Also müssen wir versuchen, beide Vektoren zu einander zu addieren, gezeigt durch die zwei grünen Pfeile an der Spitze. Dadurch erhalten wie den blauen Pfeil, ein Durchschnittsvektor, der weder die eine noch die andere Seite darstellt. Jetzt müssen wir den neuen Vektor noch mal re-normalisieren, da er wahrscheinlich eine Länge von 2 hat(2*Vektor der Länge 1). Das letzte, was noch zu sagen ist, ist, dass diese Methode etwas "schmutzig" ist - weil Direct3D die Farbe über alle Verticen blendet, werden Sie keinen Unterschied zwischen den beiden erkennen können.

Dies ist der Code um ein Normal zu berechnen:

Private Function GenerateTriangleNormals( _
  p0 As UnlitVertex, p1 As UnlitVertex, _
  p2 As UnlitVertex) As D3DVECTOR
   '//0. Variables required
   Dim v01 As D3DVECTOR
     'Vector from points 0 to 1
   Dim v02 As D3DVECTOR
     'Vector from points 0 to 2
   Dim vNorm As D3DVECTOR
     'The final vector

'//1. Create the vectors from points
'//   0 to 1 and 0 to 2
   D3DXVec3Subtract v01, _
     MakeVector(p1.X, p1.Y, p1.Z), _
     MakeVector(p0.X, p0.Y, p0.Z)
   D3DXVec3Subtract v02, _
     MakeVector(p2.X, p2.Y, p2.Z), _
     MakeVector(p0.X, p0.Y, p0.Z)

'//2. Get the cross product
   D3DXVec3Cross vNorm, v01, v02

'//3. Normalize this vector
   D3DXVec3Normalize vNorm, vNorm

'//4. Return the value
   GenerateTriangleNormals.X = vNorm.X
   GenerateTriangleNormals.Y = vNorm.Y
   GenerateTriangleNormals.Z = vNorm.Z
End Function

Der Code ist nicht sehr schwer zu verstehen, wenn man die Anweisungen verstanden hat, wir können einfach drei Verticen übergeben und die Funktion gibt Ihnen den Normal zurück, den Sie dann in alle drei Verticen kopieren müssen. Es wäre gut, wenn Sie sich diesen Code merken könnten(Ich merke ihn mir als "Normalisiertes Kreuzprodukt der Vektoren 0 nach 1 und 0 nach 2"). Sie könnten diesen Code jetzt auch noch leicht verändern, so dass er die drei übergebenen Verticen mit dem Normal füllt.

Die Geometrie  

Das sollte nicht lange dauern - hoffentlich wissen Sie so langsam wie die Geometrie zu benutzen ist, da dieser Code nur kurz die Veränderungen für das Lighting zeigt.

Das erste, was verändert werden muss ist das Vertex Format, diesmal wieder ein neuer Typ. Die einzige Veränderung ist, dass die Variable für die Farbe fehlt(wir können die Farbe nicht setzen) und dass drei neue Variablen dazugekommen sind, die das Normal beinhalten.

Private Type UnlitVertex
   X As Single
   Y As Single
   Z As Single
   nx As Single
   ny As Single
   nz As Single
   tu As Single
   tv As Single
End Type

Const Unlit_FVF = (D3DFVF_XYZ Or _
                  D3DFVF_NORMAL Or _
                  D3DFVF_TEX1)

Als nächstes müssen wir das Erstellen unserer Geometrie ein bisschen verändern. Ich benutze wieder den Würfel, den wir vorhin erstellt haben, habe ihn aber so verändert, dass alle Seiten im Uhrzeigersinn angeordnet sind und habe den Code für die Normalberechnung hinzugefügt.

Cube2(0) = CreateVertex(-1, -1, 1, 0, 0, 0, 0, 0)
Cube2(1) = CreateVertex(1, 1, 1, 0, 0, 0, 1, 1)
Cube2(2) = CreateVertex(-1, 1, 1, 0, 0, 0, 0, 1)

vN = GenerateTriangleNormals(Cube2(0), Cube2(1), Cube2(2))
Cube2(0).nx = vN.X: Cube2(0).ny = vN.Y: Cube2(0).nz = vN.Z
Cube2(1).nx = vN.X: Cube2(1).ny = vN.Y: Cube2(1).nz = vN.Z
Cube2(2).nx = vN.X: Cube2(2).ny = vN.Y: Cube2(2).nz = vN.Z

Das ist jetzt allerdings nur ein Dreieck von den zwölf, die den Würfel bilden. Sie können sehen, dass ich in der Erstellung der Verticen, die Normals wegegelassen habe(sie bleiben [0,0,0]). Diese werden dann später berechnet und eingefügt. Die CreateVertex-Funktion ist eine einfach Wrapper-Funktion, welche wir auch in den vorigen Lektionen eingesetzt haben - sie spart uns einfach die Arbeit für jedes Vertex 8 Zeilen Code zu schreiben.

Das Licht initialisieren  

Jetzt gibt es nur noch ein paar Dinge zu tun, damit Lichter in unserer Szene funktionieren; die meisten davon können am Anfang eingestellt und später so gelassen werden.

Zuerst müssen wir ein Material erstellen - wenn Sie das vergessen, dann wird alles schwarz bleiben (der Grund für viele "Newbie"-Probleme).

Dim Mtrl As D3DMATERIAL8, Col As D3DCOLORVALUE
Col.a = 1: Col.r = 1: Col.g = 1: Col.b = 1

Mtrl.Ambient = Col
Mtrl.diffuse = Col
D3DDevice.SetMaterial Mtrl

Sie können die Col-Werte ändern, wenn Sie global das Lighting verändern wollen. Im Moment ist es auf weiß eingestellt - es verändert so nichts in der Szene; wenn Sie aber einen gewissen Blauton einstellen, dann erscheint alles leicht blau:

D3DDevice.SetLight 0, Lights(0)
  '//Replace Lights(0) with any valid D3DLight8 object

Nachdem Sie dies aufgerufen haben, können Sie das Licht benutzen, wenn Sie allerdings das D3Dlight8-Objekt verändern, dann müssen Sie diese Funktion wieder aufrufen. Der erste Parameter der Funktion ist ein Long-Wert Index, Sie können also theoretisch um die 2 Billionen Lichter in Ihrer Szene haben - aber es ist unwahrscheinlich, dass Sie das je brauchen.

Drittens müssen Sie noch das Licht anschalten ;-) In der Theorieabteilung dieses Kapitels habe ich davon geredet, dass Sie die Anzahl der Lichter möglichst klein halten sollten und hier müssen wir diese Regel anwenden. Sie können so viele Lichter wie Sie wollen registrieren, Sie müssen allerdings darauf achtgeben, wie viele von ihnen gerade angeschaltet sind. Die Lichter können an und aus geschaltet werden, wie es Ihnen gefällt - Lichter für gewisse Bereiche zu aktivieren ist auch ein guter Weg die Berechnungszeit zu drücken.

D3DDevice.LightEnable 0, 1 '1 = On 0 = Off

Zuletzt müssen Sie Direct3D in der Initialisierung Ihres Programmes noch erzählen, dass Sie Lighting benutzen wollen - bis jetzt haben wir es immer ausgeschaltet, hier ist also der Code um es anzuschalten:

D3DDevice.SetRenderState D3DRS_LIGHTING, 1

Nichts schweres hier. Also weiter zu den verschiedenen Lichttypen!

Ambient Light  

Ambient Light ist am leichtesten zu initialisieren, man braucht nur eine Zeile Code. Sie können Ambient Light über einen SetRenderState-Aufruf setzen, der Parameter ist eine Farbe im Format RRGGBB (und nicht AARRGGBB).

D3DDevice.SetRenderState D3DRS_AMBIENT, &H202020
  'Dieser Aufruf stellt einen dunkelgrauen Wert ein

Directional Light  


Abbildung 2: Directional Light

Directional Light ist nach Ambient Light am besten um eine ganze Szene zu beleuchten. Sie können damit zum Beispiel die Sonne simulieren und wenn Sie es dazu bekomen über die Zeit die Richtung zu wechseln, dann wird sogar fast so aussehen. Ein Directional Light hat eine Richtung und eine Farbe, aber keine Position, Verminderung und keine Entfernung. Die Richtung ist normalerweise ein normalisierter Vektor, aber es muss nicht sein(solange es nicht [0,0,0] ist) Hier ist, wie man ein Directional Light initialisiert:

Lights(0).Type = D3DLIGHT_DIRECTIONAL
Lights(0).diffuse.r = 1
Lights(0).diffuse.g = 1
Lights(0).diffuse.b = 1
Lights(0).Direction = MakeVector(0, -1, 0)

Wie Sie sicher leicht sehen können, ist, dass das Licht gerade von oben scheint und weiß ist; Sie können den Vektor setzen so wie Sie wollen - solange eine Länge hat.

Point Light  


Abbildung 3: Point Light

Point Lights sind am einfachsten zu initialisieren; geben Sie ihm eine Position, Farbe, Reichweite und Verminderung und Sie sind fertig(Sie haben keine Richtung). Hier der Code für Point Lights:

Lights(1).Type = D3DLIGHT_POINT  'Typ
Lights(1).position = MakeVector(5, 0, 2)
Lights(1).diffuse.b = 1   'Farbe
Lights(1).Range = 100    'Reichweite
Lights(1).Attenuation1 = 0.05
   'Setzt die lineare Verminderung als 0.05

Jetzt haben wir ein blaues Point Light erstellt. Die Reichweite ist sehr gering, damit Sie die Auswirkungen der Verminderung sehen können. Es gibt 3 Variablen für die Verminderung: Attenuation0, Attenuation1 und Attenuation2. Diese drei sind einmal für konstante, lineare und quadratische Verminderung. Spielen Sie mal damit rum und schauen sich die Veränderungen an - welche natürlich nicht zu bemerken sind, wenn die Reichweite auf 100000 gesetzt ist(oder irgendeine andere passende, große Nummer)

Spot Light  


Abbildung 4: Spot Light

Point Lights sind sehr clever - dafür sind sie aber auch schwer aufzustellen(im Gegensatz zu den anderen) und dauern in der Berechnung am längsten. Wie Sie an dem Bild sehen ist der innere Kegel heller als der äußere. Das nächste Diagramm erklärt dies näher:


Abbildung 5: Spot Light im Detail

Es gibt zwei Radien für die beiden Kegel - einen inneren(Theta) und einen äußeren(Phi); Sie können beide setzen. Die Radien werden übrigens in Radianten und nicht in Grad gemessen - um die Gradwerte umzurechnen multiplizieren Sie die Zahl einfach mit (PI / 180). Der folgende Code wird vom Beispiel dieses Kapitels benutzt um ein einfaches Spot Light aufzustellen:

Lights(2).Type = D3DLIGHT_SPOT
Lights(2).position = MakeVector(-4, 0, 0)
Lights(2).Range = 100
Lights(2).Direction = MakeVector(1, 0, 0)
  'Along the X axis
Lights(2).Theta = 30 * (Pi / 180)
  'Inner Cone
Lights(2).Phi = 50 * (Pi / 180)
  'Outer Cone
Lights(2).diffuse.g = 1
Lights(2).Attenuation1 = 0.05

Nichts richtig schwieriges hier. Sie müssen nur immer im Kopf behalten, dass die Winkel in Radianten gemessen werden.

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