ZFX
ZFX Neu
Home
Community
Neueste Posts
Chat
FAQ
IOTW
Tutorials
Bücher
zfxCON
ZFXCE
Mathlib
ASSIMP
NES
Wir über uns
Impressum
Regeln
Suchen
Mitgliederliste
Membername:
Passwort:
Besucher:
4396176
Jetzt (Chat):
20 (0)
Mitglieder:
5239
Themen:
24223
Nachrichten:
234554
Neuestes Mitglied:
-insane-
ZFX - KurzartikelDruckversion

Lightmapping in DirectX 8.0
Der Kurzartikel von Johannes Leimbach

© Copyright 2001 [whole tutorial] by Johannes Leimbach
Die Texte der Tutorials unterliegen dem Copyright und dürfen ohne schriftliche Genehmigung vom Author weder komplett noch auszugsweise vervielfältigt, auf einer anderen Homepage verwendet oder in sonst einer Form veröffentlicht werden. Die zur Verfügung gestellten Quelltexte hingegen stehen zur freien Weiterverwendung in Software Projekten zur Verfügung.
Die Quelltexte dieses Tutorials werden auf einer "as is" Basis bereit gestellt. Der Autor übernimmt keinerlei Garantie für die Lauffähigkeit der Quelltexte. Für eventuelle Schäden die sich aus der Anwendung der Quelltexte ergeben wird keinerlei Haftung übernommen.


ZFX Tutorial 

Lightmapping in DirectX 8.0
ein Tutorial von Johannes Leimbach


oder Von Doom zu Quake
There is a difference between knowing the path and walking the path, Morpheus, The Matrix
I can only show you the door, you are the one to walk in, Morpheus, The Matrix


Kurz :

Inhalt : Erklärung von Lightmapping mit Beispiel in DX8 (sollte leicht auf OGL zu portieren sein)
Zielgruppe : Fortgeschritte-Professionelle Programmierer
Download : Demoprojekt

Inhalt

1) Einleitung

2) Wie funktionieren Lightmaps

3) Technische Details

4) Die Implementation

5) Erweiterte Lightmaptechniken

6) Links

7) Letzte Worte

8) Demoprojekt

 

Einleitung

"Wer sich die Mühe gemacht hat, Zerbies Tutorial herunterzuladen und auch zu testen der wird unter
Umständen etwas enttäuscht gewesen sein was das 6. Kapitel zum Laden und Rendern von X Files betrifft.
Man hat einfach den Eindruck, dass das gezeigte Modell nicht realistisch wirkt. Auf den ersten Blick wird
man wohl nicht direkt sagen können, warum das so ist. Das Auge merkt einfach, dass da etwas nicht
stimmt. Doch was genau ist dieses etwas...?
"

Wem diese Zeilen bekannt vorkommen, der sollte sich nicht wundern.
Sie stammen aus der Hand von Stefan Zerbst selbst und leiten das 10te Kapitel seines wunderbaren
3D Tutorials ein.
In der Tat ist Licht "dieses etwas" was 3D Szenen realistisch und glaubwürdig macht. Licht hilft
uns Formen besser einzuschätzen und kann -wenn es richtig eingesetzt wird- die Atmosphäre
eines Spieles enorm erhöhen.

Nun, magst du dich vielleicht jetzt fragen : Stefan hat doch schon ein Tutorial über dieses Thema
geschrieben, wieso erstelle ich hier ein weiteres Tutorial ???

Um es kurz zu sagen :

Zerbie verwendet das sog. "Gouraud Shading" welches ja bekanntlicherweise schon in D3D integriert ist.
"Gouraud Shading" hat einen großen Nachteil im Vergleich mit Lightmaps (zu denen ich gleich noch was
sagen werden) :

Und zwar wird es auf einer "Per Vertex base" berechnet. D.h es ist nur möglich Licht für die Vertice (=Eck
punkte) eines Objektes zu definieren...
Der Nachteil liegt auf der Hand : Es ist unmöglich die Mitte eines Objektes zu beleuchten.
Mit Lightmaps geht dies jedoch :-)

Leider sind Lightmaps etwas schwieriger zu implementieren als das normale D3D Licht, das Ergebnis
entschädigt die Mühen aber auf jeden Fall !!!

 

Wie funktionieren Lightmaps ?

Und wieder mal sagt ein Bild mehr als 100 Worte :

 

Die normale Textur          multipliziert mit der Lightmap ergibt folgendes :

X

=

 

Richtig, die Lightmap ist nur eine Textur. Wer übrigens den Ausdruck "die normale Textur wird mit
der Lightmap multipliziert" als bloße rhetorik abtut liegt falsch. Tatsächlich werden Lightmap und Textur
multiplikativ miteinander verknüpft (klingt cool, gell ?)
(diese Bilder stammen übrigens aus dem Demoprojekt was wir gleich zusammen entwickeln werden)

Lightmaps sind der de-factoStandard für Beleuchtung in allen 3D - Indoorspielen.
Alle großen 3D Engines verwenden sie : Lithtech, Unreal1+2, Quake1+2+3 und die
smart Engine auch (das ist meine, auch wenn sie in dieser Liste natürlich etwas fehl am
Platz ist :-)
Der Vorreiter bei der Entwicklungwar -natürlich- wieder John Carmack, id-Software.
Quake 1 war das erste Spiel das diese Technik verwendet hat, und allein diese Tatsache macht
Lightmaps zu den "most-wanted" ;-)
Seitdem wurde diese Technik natürlich kontinuirlich verbessert und optimiert...

Man kann mit ihr nicht nur Lichter sondern auch Schatten darstellen

Lightmaps werden meist *VOR* dem Start des Programmes erstellt, da ihre Berechnung
doch sehr zeitaufwendig ist. Dynamische Lichter sind ohne weiteres nicht möglich (mehr dazu unten)

 

 

Technische Details

Lightmaps sind eigentlich nur Texturen. Jedoch werden diese meist in einer sehr kleinen Auflösung (z.B. 32*32 Pixel)
gespeichert. Dies hat mehrere Gründe.
Zum einen sind kleine Lightmaps _sehr_ schnell zu berechnen, wohingegen große Lightmaps (256*256 Pixel)
wesentlich länger brauchen und kaum einen sichtbaren Unterschied ergeben.
Weiterhin muss man bedenken dass eine Lightmapbasierte Engine durchschnittlich doppelt so viele Texturen
als normal verwendet, was den Speicherverbrauch merklich erhöht

Um Lightmaps zu rendern muss man das "Multitexturing" aktivieren. Das wird aber weiter unten erklärt.

Die eigentliche Lichtberechnung funktioniert folgendermaßen :

 


 

Ähm, ja :-)
Das rote Teil unten im Bild soll eine Glühbirne darstellen.
Das schwarze was von der roten Birne beleuchtet wird ist ein Polygon, mit einer Lightmap.
Die Strahlen repräsentieren eigentlich schon wie man Lightmaps erstellt :

Man misst für jeden Punkt die Entfernung zur Glühbirne. Je nach Entfernung wird dort ein Pixel
in einer bestimmten Farbe gesetzt -oder nicht.

Klingt einfach, oder ? Aber genau hier liegt das Problem !!!
Es ist gar nicht so einfach herauszufinden wie weit ein Punkt auf der Lightmap (der ja bekanntlicherweise
in t&u Koordinaten angegeben ist) von einem Punkt in 3D entfernt ist(der ja bekanntlicherweise
in Weltkoordinaten vorliegt) .

Aber das wird alles geklärt in :

 

Die Implementation

 

Der Code ist in C++ geschrieben. Er stammt aus dem Demoprojekt, wurde jedoch leicht
verändert, da viele Funktionen nicht direkt etwas mit Lightmaps zu tun haben und die Erklärung
sicherlich erschweren würden...


//Dies ist der Vertex den wir zum zeichnen der Polygone verwenden. Wie schon vorher gesagt
//verwenden wir dafür Multitexturing. Deshalb sollte man sich auch nicht wundern dass wir
//2 Texturkoordinaten-paare haben (u0/v0 u1/v1)
//Die Farbe spielt praktisch keine Rollte, sie sollte auf D3DCOLOR_XRGB(255,255,255), d.h.
//weiß gesetzt werden

struct CVertex
{
          float                     x,y,z;
          DWORD             color;
          float                     u0,v0,u1,v1;
};
//Das Vertexformat für D3D
const int FVF=(D3DFVF_XYZ|D3DFVF_TEX2|D3DFVF_DIFFUSE) ;


//Dies ist eine Klasse für ein sog. Pointlight. Ein Pointlight ist z.B. eine Glühbirne : Sie strahlt
//gleichmäßig Licht in alle Richtungen. Dieser Lichttyp ist recht einfach zu erstellen, ermöglicht
//aber die schönsten Effekte.

class CPointLight
{
public :
          //die Position

          CVector               m_pos;
          //Farbkomponenten, reichen von 0-255
          int                         m_r,m_g,m_b;
          //Die maximale Reichweite des Lichts
          float                      m_radius;
};


//Nun kommt die Klasse für eine Lightmap. Eigentlich erfüllt sie die gleiche Funktion wie ein
//Polygon (und wird deshalb später auch als solches bezeichnet)

class CLightmap
{
//public = jeder kann diese Werte verändern und auf sie zugreifen
public :
          //Hier werden halt die Vertice gespeichert. Leider passen z.Z. nur 4 Vertice rein...
          //Jedoch spricht nichts dagegen den Wert zu erhöhen.
          //Die Vertice werden als Triangle Strip (D3DPT_TRIANGLESTRIP) gezeichnet
          
CVertex                    m_vertex[4];

          //TEXTURE ist einfach nur ein Handle für eine Textur. Sie wird im Demoprogramm
          //m.h. einer Helperfunktion geladen : (g_smartTextureMan.Load)
          //WICHTIG : m_texture wird von der Festplatte geladen, m_lightmap wird IM PROGRAMM
          //selbst erstellt !!!
          
TEXTURE                  m_texture, m_lightmap;

          //Die Ebene für diese Lightmap. Sie wird später berechnet
          CPlane                        m_plane;
          //Hierzu sage ich später auch noch was :-) Dieser Wert gibt die größte Komponente
          //des Normalenvektors an (entweder 'x' oder 'y' oder 'z')
          char                             m_alignement;

          //Berechnet m_plane und m_alignement, mehr unten...
          void                            CalcAlignement ();
};

 

//Dies ist endlich die Klasse für unser Lightmapsystem. In ihr werden die Lightmaps und die Lichter
//gespeichert, weiterhin werden hier die Lightmaps berechnet und gerendert. Deshalb ist sie auch etwas größer ...

class CLightmapSystem
{
public :
          //Hiermit wird die Detailstufe der Lightmaps festgelegt. Je höher m_detail=>je größer die Qualität
          //Ein Wert von 16-32 sollte für jeden ausreichen. Vorsicht : Alte Grafikkarten, wie die Voodoo3
          //können nur Texturen verwenden die <=256*256px sind, neuere Grafikkarten (GeForce2 usw:)
         //haben diese Beschränkung zwar nicht, arbeiten aber NUR mit Texturen deren Größe durch zwei teilbar ist
          
void                              SetLightmapDetail (int detail) {m_detail=detail;}
          //Dies ist das "Umgebungslicht". Kein Pixel kann dunkler als das Ambient Light werden.
          //Hier mal ein Zitat aus dem DX8 SDK, was es eigentlich recht gut beschreibt :
          //You can think of it [ambient light] as a general level of light that fills an entire scene,
          // regardless of the objects and their locations in that scene

          void                              SetAmbientLight (int ar, int ag, int ab){m_r=ar;m_g=ag;m_b=ab;}

          //Nun ja, was diese Funktionen machen muss man wohl nicht extra erklären, oder ?
          void                              AddPointLight (CPointLight& l){m_pointLights.push_back(l);
          void                              AddLightmap (CLightmap& m){m_lightmaps.push_back (m);

          //Diese Funktion berechnet alle Lightmaps. Sie ist sehr langsam, und sollte deshalb nur
          //einmal -beim Start des Programmes- ausgeführt werden.
          
BOOL                          CalcLightmaps ();

          //Naja, wie der Name schon sagt.... vorher nicht vergessen CalcLightmaps aufzurufen :-)
          void                              Render ();

//auf private Elemente können nur Funktionen aus CLightmapSystem zugreifen
private :

          //Die Ambient Color
          int                                 m_r,m_g,m_b;
          //Detail der Lightmaps
          int                                 m_detail;

          //Erstellt die u&v Koordinaten für die Lightmap m und speichert die größten und kleinsten
          //u&v... Mehr über Sinn und Funktion später
          void                               SetupLightmapUV (CLightMap& m, float& umax, float& vmax, float& umin, float& vmin);

          
          //Findet den Vertex mit den Lightmap t&u Koordinaten (0,0). Mehr dazu auch später :-)
          
CVector                        FindZeroUV (CLightMap& m);

          //std::vector ist in "vector" deklariert. Ein dynamisches Array aus der STL

          std::vector<CLightmap> m_lightmaps;
          std::vector<CPointLight>m_pointLights;
};

 

Das sind alle Funktionen, Klassen und Structs die man braucht :-)

Zuerst einmal ist es wichtig zu wissen dass man für die Lightmaps eigene u&v Koordinaten berechnen
muss. Wenn man die Koordinaten des Polygons übernimmt kann es passieren dass die Lichter gespiegelt,
um 90° gedreht, oder überhaupt ziemlich schlecht aussehen. Dies war bei meinem erstem Lightmaprender
der Fall -naja, hier wird das nicht so sein

Um die Texturkoordinaten zu berechnen muss man erst einmal die Ebene (und damit u.a den Normalen)
der Lightmap berechnen.
Dann guckt man welcher Wert davon (absolut) am größten ist : so findet man heraus auf welcher Ebene das Polygon liegt.

Hier die Funktion :

 

void CLightMap::CalcAlignement ()
{
          //der code für CPlane ist in smartplane.h
          m_plane.FromVertex (m_vertex[0],m_vertex[1],m_vertex[2]);

           float norm = fabs(m_plane.normal.x);
           m_alignement = 'x';
           if (norm < fabs(m_plane.normal.y))
           {
                     norm = fabs(m_plane.normal.y);
                     m_alignement = 'y';
           }
           if (norm < fabs(m_plane.normal.z))
           {
                     norm = fabs(m_plane.normal.z);
                     m_alignement = 'z';
           }
}

 

Was jetzt kommt ist die Funktion um die u&v Koordinaten zu berechnen. Die Technik die wir hier
verwenden nennt sich "Planar mapping".
Das ist eigentlich recht simpel :

Je nach dem auf welcher Ebene das Polgon liegt, setzen wir die u&v Koordinaten gleich der 2Dimensionalen
Position des Vertexes. Bsp : Ebene = 'z' => u=x, v=y
Nun haben wir die Texturkoordinaten in Weltkoordinaten :Falsche Einheit !
Das geht natürlich nicht, Texturkoordinaten müssen (sollten) zwischen 0 und 1 liegen.
Also bilden wir die BoundingBox für die Texturkoordinaten, subtrahieren den minimalen Wert
der BoundingBox von jeder Texturkoordinate und dividieren alles dann noch durch die Größe der
Bounding Box ! (klingt komisch, weiß ich, geht aba nicht anders ... Glaub mir :-)

 

void CLightmapSystem::SetupLightmapUV (CSmartLightMap& m, float& umax, float& vmax, float& umin, float& vmin)
{
           //maximale/minimale t&u, und die Differenz
           float uumax, vvmax, uumin, vvmin, uudelta, vvdelta;

           for(int j=0;j<4;j++)
           {
                      //je nach Ausrichtung des Objektes
                      switch(m.m_alignement)
                      {
                      case 'y':
                                 m.m_vertex[j].u1=m.m_vertex[j].x; ;
                                 m.m_vertex[j].v1=m.m_vertex[j].z;
                                 break;
                      case 'x':
                                 m.m_vertex[j].u1=m.m_vertex[j].z;
                                 m.m_vertex[j].v1=m.m_vertex[j].y;
                                 break;
                      case 'z':
                                 m.m_vertex[j].u1=m.m_vertex[j].x;
                                 m.m_vertex[j].v1=m.m_vertex[j].y;
                                 break;
                      }
           }

           //Startpunkt für die BBox Berechnung
           uumin=m.m_vertex[0].u1;
           uumax=m.m_vertex[0].u1;
           vvmin=m.m_vertex[0].v1;
           vvmax=m.m_vertex[0].v1;

           for(j=0;j<4;j++)
           {
                      //simple BBox Erstellung
                      if (m.m_vertex[j].u1<uumin)
                                 uumin=m.m_vertex[j].u1;
                      if(m_vertex[j].v1<vvmin)
                                 vvmin=m.m_vertex[j].v1;

                      if (m.m_vertex[j].u1>uumax)
                                 uumax=m.m_vertex[j].u1;
                      if (m.m_vertex[j].v1>vvmax)
                                 vvmax=m.m_vertex[j].v1;
           }

           //Differenz berechnen
           uudelta=uumax-uumin;
           vvdelta=vvmax-vvmin;

           for(j=0;j<4;j++)
           {
                      //minimalen Wert subtrahieren...
                      m.m_vertex[j].u1 -= uumin;
                      m.m_vertex[j].v1 -= vvmin;
                      // ...und durch die Breite der BBox dividieren
                      m.m_vertex[j].u1 /= uudelta;
                      m.m_vertex[j].v1 /= vvdelta;
           }

           //Werte speichern um sie an die aufzurufende Funktion zurückzugeben

           umin=uumin;
           umax=uumax;
           vmin=vvmin;
           vmax=vvmax;
}

 

Sooooooo, jetzt fehlt noch eine Funktion die für CalcLightmaps benötigt wird...
FindZeroUV
Deren Aufgabe ist so primitiv, dass ich mich beinahe schäme sie hier abzudrucken :
Sie liefert einfach nur den Vertex einer Lightmap zurück, wo die Texturkoordinaten (0,0) sind...

CVector CLightmapSystem::FindZeroUV (CLightmap& m)
{
           for (int l=0;l<4;l++)
           {
                      //Juhu ! Vertex gefunden... TOLL !

                      if (m.m_vertex[l].u1==0.0f && m.m_vertex[l].v1==0.0f)
                                 return CVector(m.m_vertex[l]);
           }

           //ohoh, wenn das passiert liegt wohl ein Fehler in SetupLightmapUV vor ...
           assert (0);
           return CVector (-999999,-999999,-999999);
}

 

Jetzt können wir mit der interessanten Funktion anfangen ! CalcLightmaps !!

BOOL CLightmapSystem::CalcLightmaps ()
{
           //berechne Licht für _alle_ Lighmaps

           for (int l=0;l<m_lightmaps.size();l++)
           {
                      //berechne die Ausrichtung des Polygons
                      m_lightmaps[l].CalcAlignement ();

                      //erstelle die Lightmap Textur m.h. einer meiner Helfer Funktionen
                      //die Lightmaptexturgröße ist m_detail*m_detail Pixel !
                      m_lightmaps[l].m_lightmap=g_textureMan.CreateNewTexture (m_detail,m_detail);
          
                      //"Lock" auf Textur anwenden. Nun können wir direkt in die Textur reinschreiben
                      //und Pixel verändern. VORSICHT : Im "gelockten" Zustand darf man die Textur
                      //nicht zum rendern verwenden
                      //Außerdem sollten noch auf Fehlerwerte gecheckt werden

                      g_smartTextureMan.Get(m_lightmaps[l].m_lightmap).Lock ();

                      //Oh Mann, das sind ganz schön viele Variablen :-)
                      //Ich werde sie jetzt nicht alle erklären, da das viel zu viel wäre, Erklärung gibt's bei
                      //Verwendung
                     
CSmartVector ZeroPosition,UVVector, Vect1, Vect2, norm, edge1, edge2, newedge1, newedge2, base, lumel;
                      float x,y,z,factor,vfactor,umin, vmin, umax,vmax;

                      //In ZeroPosition kommt nun die Position des Vertex mit den t&u Koordinaten 0,0
                      ZeroPosition=FindZeroUV (m_lightmaps[l]);
                      //In norm wird nun die Normale des Polygons übertragen
                      norm=m_lightmaps[l].m_plane.normal;
                      //die t&u Koordinaten für die Lightmap werden erstellt und in ?min und ?max
                      //kommen nun die BoundingBox für die Texturkoordinaten (Planar Mapping)
                     
SetupLightmapUV(m_lightmaps[l],umax,vmax,umin,vmin);

                      //Nun müssen wir die sog. "Eckvektoren" (Edgevectors") für unser Polygon ermitteln
                      //Warum ? Wir brauchen sie um später ausrechnen zu können wie weit ein Pixel
                      //auf der Lightmap von einem Punkt in 3D entfernt ist. Ein Punkt auf der Lightmap
                      //heißt übrigens Lumel (das Analog zum Texel auf einer normalen Textur)
                      //Was jetzt kommt ist wirklich schwer, und muss auch nicht unbedingt verstanden werden *g*
                     
switch (m_lightmaps[l].m_alignement)
                      {
                      case 'y': //XZ Ebene
                                //Stelle die Ebengleichung (Plane equation) um
                                //Ax + By + Cz + D = 0
                                //so dass du y rauskriegst (na, in Mathe gut aufgepasst :-)
                                //y = - ( Ax + Cz + D ) / B
                               
y= - (norm.x*umin+norm.z*vmin+m_lightmaps[l].m_plane.d)/norm.y;
                                UVVector = CVector(umin,y,vmin);
                                y= - (norm.x*umax+norm.z*vmin+m_lightmaps[l].m_plane.d)/norm.y;
                                Vect1 = CVector(umax,y,vmin);
                                 y= - (norm.x*umin+norm.z*vmax+m_lightmaps[l].m_plane.d)/norm.y;
                                 Vect2 = CVector(umin,y,vmax);
                                 break;
                      case 'x: //YZ Ebene
                                x= - (norm.z*umin+norm.y*vmin+m_lightmaps[l].m_plane.d)/norm.x;
                                UVVector = CVector(x, vmin,umin);
                                x= - (norm.z*umax+norm.y*vmin+m_lightmaps[l].m_plane.d)/norm.x;
                                Vect1 = CVector(x,vmin,umax);
                                x= - (norm.z*umin+norm.y*vmax+m_lightmaps[l].m_plane.d)/norm.x;
                                Vect2 = CVector(x,vmax,umin);
                                 break;
                      case 'z': //XY Ebene
                                 z= - (norm.x*umin+norm.y*vmin+m_lightmaps[l].m_plane.d)/norm.z;
                                 UVVector = CVector(umin,vmin,z);
                                 z= - (norm.x*umax+norm.y*vmin+m_lightmaps[l].m_plane.d)/norm.z;
                                 Vect1 = CVector(umax,vmin,z);
                                 z= - (norm.x*umin+norm.y*vmax+m_lightmaps[l].m_plane.d)/norm.z;
                                 Vect2 = CVector(umin,vmax,z);
                                 break;
                      }//switch

                      //das sind die Eckvektoren *g*       
                      edge1 = Vect1 - UVVector;
                      edge2 = Vect2 - UVVector;

                      //hier speichern wir temporär die Farbwerte
                      float r,g,b;
                      //Juhu ! Hier beginnt die eigentliche Lichtberechnung.
                      //wir "loopen" durch jeden Lumel und berechnen sein Licht...
                      //das ist fast so einfach wie ...... Pfannkuchen essen *lol*

                      for(int iX = 0; iX < m_detail; iX++)
                      {
                                 for(int iY = 0; iY < m_detail; iY++)
                                 {
                                           //ermittle die aktuellen u&v Koordinaten für die Lightmap

                                           ufactor = ((float)iX / m_detail);
                                           vfactor = ((float)iY / m_detail);
                                           //damit skalieren wir dann die Eckvektoren damit wir.....

                                           newedge1 = edge1 * ufactor;
                                           newedge2 = edge2 * vfactor;

                                           //.....die Position eines Lumels berechnen können

                                          lumel = UVVector + newedge1 + newedge2;
                                           //enthält gleich die Distanz von einem Lumel zu einer Lichtquelle
                                           float dist;
                                           //hier setzen wir die Farbwerte auf die Ambientcolor.
                                           //Warum ? Nun laut unserer Definition kann kein Pixel/Lumel dunkler als das
                                           //ambient light werden

                                           r=m_r; g=m_g; b=m_b;

                                           //Nun werden wir den Lumel beleuchten. Dafür loopen wir durch _alle_ Lichter in unserer
                                           //3D Welt.
                                           //Dann gucken wir ob der Lumel innerhalb des Radiusses eines Lichtes ist.

                                           for (int c=0;c<m_pointLights.size();c++)
                                          {
                                                    //Berechne die Distanz von dem Lumel zur Lichtquelle. Hierfür verwenden wir einfache
                                                    //Vektormathematik

                                                    dist=lumel.Distance (m_pointLights[c].m_pos);

                                                    if (dist<m_pointLights[c].m_radius)
                                                    {
                                                                //Jepp, der Lumel wird beleuchtet
                                                                //Was hier gemacht wird, kann man einfacher zeigen als erklären :
                                                                //dist=190, m_radius=200;
                                                                //dist=(200-190)/190 = 0.05

                                                                //und dabei wird noch auf "Division by 0" gecheckt, cool oda
                                                                if (dist==0.0f)
                                                                        dist=1.0f ;
                                                                else
                                                                        dist = (m_pointLights[c].m_radius-dist)/dist;

                                                                //nun wird zu dem Farbwert die Farbe des Lichtes addiert.
                                                                //dist skaliert sozusagen die Stärke der Lampenfarbe.
                                                                //Warum die Farbe zu r/g/b addiert wird ???
                                                                //nun, es kann ja sein das der Lumel von 2 Lichtquellen beleuchtet wird.
                                                                //durch die Addition stellen wir sicher, dass wirklich BEIDE Lampen berücksichtig
                                                                //werden

                                                                 g += (dist*m_pointLights[c].m_g);
                                                                 r += (dist*m_pointLights[c].m_r);
                                                                 b += (dist*m_pointLights[c].m_b);

                                                                //Dies sind 3 wirklich wichtige Zeilen für die Lichtberechnung ...
                                                                //255 ist bekanntlicherweise die hellste Farbe die ein Pixel haben kann.
                                                                //Wenn der Lumel nun von , sagen wir, 2 Lampen beleuchtet wird, kann es
                                                                //sein dass eben dieser Wert überstiegen wird.
                                                                //Das Ergebnis ist dass das finale Licht scheinbar zufällige Farben annimmt...

                                                                 if (r>255) r=255;
                                                                 if (g>255) g=255;
                                                                 if (b>255) b=255;
                                                   }//dist < radius
                                           }//for pointlight.size

                                           //speichere den Pixel auf der Lightmap
                   
                        g_textureMan.Get(m_lightmaps[l].m_lightmap).SetPixel (iX,iY,r,g,b);

                               }//for int y
                    }//for int x
     
                   //"Unlock" die Textur, so dass wir wieder normal mit ihr arbeiten können
                    g_textureMan.Get(m_lightmaps[l].m_lightmap).UnLock ()
           }//for lightmaps.size

         //alles OK !
         return 1;
}

 

Puhh, geschafft !!! Nun haben wir es geschafft ! Die Funktion loop durch alle Polygone (CLightmap), lockt die Textur,
berechnet die Eckvektoren, loopt dann durch alle Pixel der Textur, berechnet die Position eines Lumels m.H. der Eckvektoren,
loopt dann nochmal durch alle Lichter und addiert die Lichtwerte , die dann anschließend in die Lightmaptextur geschrieben werden.

Gar nicht so schwer wie es scheint ?? Naja, wenn nicht alles klar ist einfach alles nochmal durchlesen und sich die einzelnen
Schritte nochmal klarmachen...

Nun gehts eigentlich nur noch darum die Lightmap zu rendern.
Pfannkuchen2 *g*

void CLightmapSystem::Render ()
{
         //Dies sind die Befehle die wir brauchen um Multitexturing zu aktivieren !
         //jetzt wird auch mein vorher gebrauchter Ausdruck "Die Textur wird mit der Lightmap multipliziert"
         //klar ! Der erste Befehl aktiviert das sog. "multiplikative Alphablending". Damit werden die Pixel der
         //Textur und der Lightmap miteinander multipliziert !

         g_d3d.m_lpD3DDevice->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_MODULATE);
         g_d3d.m_lpD3DDevice->SetTextureStageState( 1, D3DTSS_COLORARG1, D3DTA_TEXTURE );


          for (int l=0;l<m_lightmaps.size();l++)
         {
                  //Man beachte die 0: Die normale Textur wird als erstes gesetzt
                  g_d3d.SetTexture (g_textureMan.GetTexture (m_lightmaps[l].m_texture),0);
                  //setze die Lightmap. Man beachte die 1 : Die Lightmap wird danach verwendet
                  g_d3d.SetTexture (g_textureMan.GetTexture (m_lightmaps[l].m_lightmap),1);

                  //Setze den richtigen Vertex shader für D3D
                  g_d3d.SetVertexShader (FVF);

                   //render

                   g_d3d.m_lpD3DDevice->DrawPrimitiveUP (
                                     D3DPT_TRIANGLESTRIP,                   //Typ
                                     2,                                                            //2 Primitives
                                     (BYTE**)&m_lightmaps[l].m_vertex[0], //Pointer auf Vertice
                                     sizeof(m_lightmaps[l].m_vertex[0]));       //Größe der Strukur in Bytes

          }

          //schalte die Texturen wieder aus, damit in anderen Funktionen keine hässlichen Reste übrigbleiben ;-)
          g_smartD3D.SetTexture (NULL,0);
          g_smartD3D.SetTexture (NULL,1);
}

 

So, das ist auch schon alles was man zum Erstellen und Rendern von Lightmaps benötigt !
Ach ja, nochwas ! Man muss unbedingt das D3D Light deaktivieren, sonst kriegt man nix auf den Bildschirm :

g_d3d.m_lpD3DDevice->SetRenderState(D3DRS_LIGHTING,FALSE);

 

Kurze Erklärung noch zu den Eckvektoren, die in CalcLightmaps verwendet werden :



Wie man sieht, kann man sich die Eckvektoren fast als BoundingBox des Polygons vorstellen.
Mit ihnen kann man dann die Position eines Lumels auf der Lightmap genau festlegen

 

Erweiterte Lightmaptechniken

In diesem Abschnitt werde ich komplexere Möglichkeiten vorstellen die durch die Verwendung von
Lightmaps entstehen. Diese Ideen werden jedoch nur kurz angesprochen und auch nicht im Demoprojekt
verwendet ...

 

1) Ein/Ausschaltbare Lightmaps

In Half-life werden sie sehr oft verwendet. Ein Beispiel ist ein Licht das man per Schalter ein und ausschalten
kann.
Natürlich wäre es sehr ineffektiv alle Lightmaps die von dem Licht beinflusst werden neu zu zeichnen.
Statt dessen kann man eine einfache Alternative verwenden :
Es werden für alle Polygone ZWEI Lightmaps erstellt : Einmal wenn das Licht
aus ist, und einmal -naja- wenn es eingeschaltet wurde. Diese Berechnungen werden natürlich auch VOR dem Start
des Programmes berechnet.
Beim Renderprozess wird dann gecheckt ob das Licht an ist :
if (light.enabled) SetTexture (lichtEIN) else SetTexture (lichtAUS);

2) Flickernde Lichter

Sind im Prinzip nur eine Erweiterungen der "Switchable Lightmaps" : Es werden nur nicht 2 sondern , z.B. 5 Lightmaps
erstellt, dabei wird allerdings jeweils die Lichtfarbe verändert....

3) Dynamische Lightmaps

Es klingt ja eigentlich wie ein Widerspruch : Oben habe ich -mehrfach- betont dass Lightmapberechnungen sehr langsam sind
und möglichst vor dem Programmstart berechnet werden sollten.
Jedoch gibt es bei (fast) jedem Spiel dynamische Lichter : Sei es eine Explosion oder ein Laserstrahl
der auf seinem Weg die Umgebung erhellt.
Wie macht man das ? Nun, man erstellt ein Pointlight mit einem _sehr_ kleinem Radius der nur wenige Objekte
beleuchtet und berechnet ihn dann "on the fly". Dynamische Lightmaps sollten auch _sehr_ klein sein (8*8 Pixel) da
sie von der CPU doch enorm viel Leistung verlangen.

4) Taschenlampen

Taschenlampen sind ja normalerweise ein sog. "gerichtetes Licht" (neudeutsch auch Spotlight genannt)
Anstatt wie Pointlights in alle Richtungen strahlen Spotlights das Licht in Form eines Zylinders aus.
Es ist deshalb auch wesentlich schwerer zu berechnen
Spiele wie Half-Life oder Quake3 verwenden einen einfachen Trick um die Berechnung zu vereinfachen.
Ein "Ray" wird in Blickrichtung ausgesendet. Dort wo der Strahl auftrifft wird ein einfaches Pointlight
mit kleinem Radius eingefügt
Folgende Zeichnung sollte meine Erklärung etwas erleichtern


 

5) Schatten

Schatten sind eines der schönsten Features man m.h. von Lightmap sehr einfach programmieren kann.
Dazu muss man einfach einen simplen Test machen : Ist es möglich dass die Lichtquelle den Lumel sieht ? (Tipp :
Ray -> Polygonintersection)
Wenn ja, wird ganz normal weitergemacht, sonst wird der Lumel einfach nicht berechnet.


6) Eerweiterte Beleuchtungstechniken

Wer jemals Half-Life-Level erstellt hat,wird sich wundern wie lange die Programme benötigen um ein Level
zu beleuchten. Mitunter kann es mehrere Stunden dauern bis alle Lightmaps in einem großem Level berechnet worden
sind.
Unsere Methode arbeitet jedoch wesentlich schneller. Wenige Sekunden und die Berechnung ist abgeschlossen.
Wer denkt dass die Leute bei Valve nicht programmieren können liegt falsch ;-)
Sie benutzen nur eine andere Beleuchtungsmethode, die sich "Radiosity" nennt. Die Idee ist folgende :
Halte mal einen Apfel direkt vor ein weißes Blatt Papier !
Wenn genügend Licht vorhanden ist, wirst du sehen dass der Apfel nicht nur Schatten wirft, sondern auch etwas von
seinem rot (oder grün, je nach dem :-) auf das Blatt wirft.
Das ist (vereinfacht) Radiosity.
Diese Technik macht auch das Ambient Light überflüssig. Eine Stelle die im Schatten
liegt ist ja nicht vollkommen "lichtfrei", die ganze Umgebung reflektiert Licht in gewissen Maße und erhellt selbst Stellen
die nicht direkt von einer Lichtquelle beschienen werden.
Auf gamedev.net gibts einen netten Artikel über dieses Thema.


7) Optimierung

Warum sollte man für ein sehr großes Polygon eine 32*32px große Lightmap erstellen, und für ein
Polygon, das sehr klein ist, eine Lightmap der selben Größe verwenden ? Irgendwie ist das Verschwendung, oder ?

 

Links

www.c-plusplus.de - sehr gute Seite mit vielen Links & nettem Forum
www.gamedev.net - Viele schöne Artikel über Gameprogramming
www.flipcode.com - Fast so gut wie gamedev ;-)
www.zfx.info - Eine der Besten Deutschen Seiten mit Tutorials
www.idsoftware.com - Der Klassiker ;-)

 

Letzte Worte

So, das wars auch schon was ich über Lightmaps weitergeben möchte.
Ich hoffe der Artikel war verständlich...

Ich möchte mich noch bei Tobias Johansson (http://polygone.flipcode.com/) für sein gutes Lightmaptutorial&Demo
bedanken. Ein großteil meines Codes basiert auf seiner Arbeit. Ohne ihn wäre dieses Tutorial in seiner jetztigen
Form nicht möglich gewesen!

Wenn Fragen auftreten, was unklar ist oder sonst irgendwelche Fragen auftreten : Ich freue mich über jede Mail

 

Viel Spaß ;-)



Informationen: " ); ?> " ); ?> " ); ?> " ); ?>
WWW :$wwwtitel
Data:Projektdateien
Mail:$author ($email)
Nick:$nick