www.codeworx.org/opengl-tutorials/Tutorial 44: 3D-Linseneffekt mit Occlusion und Frustum Culling

Willkommen zu diesem 44. Nehe-Tut. Es wird gezeigt wie sich ein sehr realistisch wirkender Linseneffekt (sieht man öfter in 3D-Weltraumspielen oder Ego-Shootern, wenn der Spieler in Lichtquellen guckt.) mit OpenGL und einem (nagut zwei ;) Sichtbarkeitstest erzeugen läßt. Diese Effekte haben oft eins gemeinsam, sie scheinen um die Bildmitte zu rotieren, also nur um eine einzige Achse. Man könnte auf die Idee kommen die Z-Koordinate ganz zu vernachläßigen, wird aber bei dem Test, ob die Kamera gerade in eine Lichtquelle guckt ein ziemliches Problem bekommen... Mit einem "echten" 3D-Linseneffekt läßt sich das umgehen. Die glCamera-Klasse muss dazu erweitert werden. Zuerst müssen Funktionen geschrieben werden, die prüfen ob sich ein bestimmter Punkt oder eine Kugel im Sichtbereich der Kamera befindet. Dann müssen noch einige Texturen für den Effekt an sich geladen werden.

Ich muss vorher noch mitteilen das in der Kameraklasse noch ein Bug (in SetPrespective) steckt, der mit dem unteren Code verbessert werden muss:

void glCamera::SetPrespective()
{
   GLfloat Matrix[16];
   glVector v;
   // Going To Use glRotate To Calculate Our Direction Vector
   glRotatef(m_HeadingDegrees, 0.0f, 1.0f, 0.0f);
   glRotatef(m_PitchDegrees, 1.0f, 0.0f, 0.0f);
   // Get The Resulting Matrix From OpenGL It Will Have Our
   // Direction Vector In The 3rd Row
   glGetFloatv(GL_MODELVIEW_MATRIX, Matrix);
   // Get The Direction Vector From The Matrix. Element 10 Must
   // Be Inverted!
   m_DirectionVector.i = Matrix[8];
   m_DirectionVector.j = Matrix[9];
   m_DirectionVector.k = -Matrix[10];
   // Ok Erase The Results Of The Last Computation
   glLoadIdentity();
   // Rotate The Scene To Get The Right Orientation
   glRotatef(m_PitchDegrees, 1.0f, 0.0f, 0.0f);
   glRotatef(m_HeadingDegrees, 0.0f, 1.0f, 0.0f);
   // Scale The Direction By Our Speed
   v = m_DirectionVector;
   v *= m_ForwardVelocity;
   // Increment Our Position By The Vector
   m_Position.x += v.i;
   m_Position.y += v.j;
   m_Position.z += v.k;
   // Translate To Our New Position
   glTranslatef(-m_Position.x, -m_Position.y, -m_Position.z);
}

Jetzt aber an die eigentliche Arbeit. Es werden 4 verschiedene Texturen geladen, Big Glow, Streaks, Glow und Halo. Diese sollen sich beim Blick ins Licht, auf verschiedenen Bahnen und entgegen der Kameradrehung um die Bildmitte bewegen. Big Glow stellt die eigentliche "Lichtquelle" dar, die Beleuchtungsfunktionen von OpenGL kommen nicht zu Einsatz (da wäre auch (noch) nichts zum Beleuchten da :). Die anderen Texturen vervollständigen das Ganze.


Big Glow

Streaks
  .

Glow

Halo

Ein Linseneffekt macht nur dann wirklich Sinn, wenn er zu sehen ist sobald der Betrachter in die Lichtquelle guckt, danach aber wieder verschwindet. Dazu muss das Sichtvolumen der Kamera berechnet werden um zu prüfen ob das Licht momentan zu sehen ist oder nicht. UpdateFrustrum() errechnet das aus 6 Ebenen bestehende Sichtvolumen aus der Projection- und der Modelview Matrix, die man direkt von OpenGL abfragen kann. Neuere 3D-Grafikkarten, die die viel besprochenen OpenGL-Extensions unterstützen, bieten eine Alternative. Dort lassen sich beliebige Punkte unkomplizierter auf ihre Sichtbarkeit prüfen mit Hilfe von GL_HP_occlusion_test oder GL_NV_occlusion_query. In diesem Tutorial wird der altmodische, aber auch auf alter Hardware noch funktionierende Weg beschrieben.

void glCamera::UpdateFrustum()
{
   GLfloatclip[16];
   GLfloatproj[16];
   GLfloatmodl[16];
   GLfloatt;
   // Get The Current PROJECTION Matrix From OpenGL
   glGetFloatv( GL_PROJECTION_MATRIX, proj );
   // Get The Current MODELVIEW Matrix From OpenGL
   glGetFloatv( GL_MODELVIEW_MATRIX, modl );
   // Combine The Two Matrices (Multiply Projection By Modelview)
   clip[ 0] = modl[ 0] * proj[ 0] + modl[ 1] * proj[ 4] + modl[ 2] * proj[ 8] +    modl[ 3] * proj[12];
   clip[ 1] = modl[ 0] * proj[ 1] + modl[ 1] * proj[ 5] + modl[ 2] * proj[ 9] +    modl[ 3] * proj[13];
   clip[ 2] = modl[ 0] * proj[ 2] + modl[ 1] * proj[ 6] + modl[ 2] * proj[10] +    modl[ 3] * proj[14];
   clip[ 3] = modl[ 0] * proj[ 3] + modl[ 1] * proj[ 7] + modl[ 2] * proj[11] +    modl[ 3] * proj[15];
   clip[ 4] = modl[ 4] * proj[ 0] + modl[ 5] * proj[ 4] + modl[ 6] * proj[ 8]    + modl[ 7] * proj[12];
   clip[ 5] = modl[ 4] * proj[ 1] + modl[ 5] * proj[ 5] + modl[ 6] * proj[ 9] +    modl[ 7] * proj[13];
   clip[ 6] = modl[ 4] * proj[ 2] + modl[ 5] * proj[ 6] + modl[ 6] * proj[10] +    modl[ 7] * proj[14];
   clip[ 7] = modl[ 4] * proj[ 3] + modl[ 5] * proj[ 7] + modl[ 6] * proj[11] +    modl[ 7] * proj[15];
   clip[ 8] = modl[ 8] * proj[ 0] + modl[ 9] * proj[ 4] + modl[10] * proj[ 8]    + modl[11] * proj[12];
   clip[ 9] = modl[ 8] * proj[ 1] + modl[ 9] * proj[ 5] + modl[10] * proj[ 9] +    modl[11] * proj[13];
   clip[10] = modl[ 8] * proj[ 2] + modl[ 9] * proj[ 6] + modl[10] * proj[10] +    modl[11] * proj[14];
   clip[11] = modl[ 8] * proj[ 3] + modl[ 9] * proj[ 7] + modl[10] * proj[11] +    modl[11] * proj[15];
   clip[12] = modl[12] * proj[ 0] + modl[13] * proj[ 4] + modl[14] * proj[ 8]    + modl[15] * proj[12];
   clip[13] = modl[12] * proj[ 1] + modl[13] * proj[ 5] + modl[14] * proj[ 9] +    modl[15] * proj[13];
   clip[14] = modl[12] * proj[ 2] + modl[13] * proj[ 6] + modl[14] * proj[10] +    modl[15] * proj[14];
   clip[15] = modl[12] * proj[ 3] + modl[13] * proj[ 7] + modl[14] * proj[11] +    modl[15] * proj[15];
   // Extract The Numbers For The RIGHT Plane
   m_Frustum[0][0] = clip[ 3] - clip[ 0];
   m_Frustum[0][1] = clip[ 7] - clip[ 4];
   m_Frustum[0][2] = clip[11] - clip[ 8];
   m_Frustum[0][3] = clip[15] - clip[12];
   // Normalize The Result
   t = GLfloat(sqrt( m_Frustum[0][0] * m_Frustum[0][0] + m_Frustum[0][1] * m_Frustum[0][1]    
   + m_Frustum[0][2] * m_Frustum[0][2] ));

   m_Frustum[0][0] /= t;
   m_Frustum[0][1] /= t;
   m_Frustum[0][2] /= t;
   m_Frustum[0][3] /= t;
   // Extract The Numbers For The LEFT Plane
   m_Frustum[1][0] = clip[ 3] + clip[ 0];
   m_Frustum[1][1] = clip[ 7] + clip[ 4];
   m_Frustum[1][2] = clip[11] + clip[ 8];
   m_Frustum[1][3] = clip[15] + clip[12];
   // Normalize The Result
   t = GLfloat(sqrt( m_Frustum[1][0] * m_Frustum[1][0] + m_Frustum[1][1] * m_Frustum[1][1]    
   + m_Frustum[1][2] * m_Frustum[1][2] ));
   m_Frustum[1][0] /= t;
   m_Frustum[1][1] /= t;
   m_Frustum[1][2] /= t;
   m_Frustum[1][3] /= t;
   // Extract The BOTTOM Plane
   m_Frustum[2][0] = clip[ 3] + clip[ 1];
   m_Frustum[2][1] = clip[ 7] + clip[ 5];
   m_Frustum[2][2] = clip[11] + clip[ 9];
   m_Frustum[2][3] = clip[15] + clip[13];
   // Normalize The Result
   t = GLfloat(sqrt( m_Frustum[2][0] * m_Frustum[2][0] + m_Frustum[2][1] * m_Frustum[2][1]    
   + m_Frustum[2][2] * m_Frustum[2][2] ));
   m_Frustum[2][0] /= t;
   m_Frustum[2][1] /= t;
   m_Frustum[2][2] /= t;
   m_Frustum[2][3] /= t;
   // Extract The TOP Plane
   m_Frustum[3][0] = clip[ 3] - clip[ 1];
   m_Frustum[3][1] = clip[ 7] - clip[ 5];
   m_Frustum[3][2] = clip[11] - clip[ 9];
   m_Frustum[3][3] = clip[15] - clip[13];
   // Normalize The Result
   t = GLfloat(sqrt( m_Frustum[3][0] * m_Frustum[3][0] + m_Frustum[3][1] * m_Frustum[3][1]   
   + m_Frustum[3][2] * m_Frustum[3][2] ));
   m_Frustum[3][0] /= t;
   m_Frustum[3][1] /= t;
   m_Frustum[3][2] /= t;
   m_Frustum[3][3] /= t;
   // Extract The FAR Plane
   m_Frustum[4][0] = clip[ 3] - clip[ 2];
   m_Frustum[4][1] = clip[ 7] - clip[ 6];
   m_Frustum[4][2] = clip[11] - clip[10];
   m_Frustum[4][3] = clip[15] - clip[14];
   // Normalize The Result
   t = GLfloat(sqrt( m_Frustum[4][0] * m_Frustum[4][0] + m_Frustum[4][1] * m_Frustum[4][1]   
   + m_Frustum[4][2] * m_Frustum[4][2] ));
   m_Frustum[4][0] /= t;
   m_Frustum[4][1] /= t;
   m_Frustum[4][2] /= t;
   m_Frustum[4][3] /= t;
   // Extract The NEAR Plane
   m_Frustum[5][0] = clip[ 3] + clip[ 2];
   m_Frustum[5][1] = clip[ 7] + clip[ 6];
   m_Frustum[5][2] = clip[11] + clip[10];
   m_Frustum[5][3] = clip[15] + clip[14];
   // Normalize The Result
   t = GLfloat(sqrt( m_Frustum[5][0] * m_Frustum[5][0] + m_Frustum[5][1] * m_Frustum[5][1]   
   + m_Frustum[5][2] * m_Frustum[5][2] ));
   m_Frustum[5][0] /= t;
   m_Frustum[5][1] /= t;
   m_Frustum[5][2] /= t;
   m_Frustum[5][3] /= t;
}

Bei diesem Biest von Funktion kann man sich gut vorstellen, warum solche Berechnungen auch als OpenGL-Extensions implemetiert werden. Obwohl die Mathematik dahinter nicht allzu kompliziert ist, umfasst die Funktion 190 Operationen, leider auch sehr zeitraubende Divisionen und 6 Wurzeln. Rausoptimieren lassen sich noch einige Multiplikationen, wenn die Projection Matrix nicht durch Rotation oder Translation verändert wird. Da die Funktion nach jeder Bewegung des Betrachters neu berechnet werden muss, lohnt sich das:

void glCamera::UpdateFrustumFaster()
{
   GLfloat clip[16];
   GLfloat proj[16];
   GLfloat modl[16];
   GLfloat t;
   // Get The Current PROJECTION Matrix From OpenGL
   glGetFloatv( GL_PROJECTION_MATRIX, proj );
   // Get The Current MODELVIEW Matrix From OpenGL
   glGetFloatv( GL_MODELVIEW_MATRIX, modl );
   // Combine The Two Matrices (Multiply Projection By Modelview) 
   // But Keep In Mind This Function Will Only Work If You Do NOT
   // Rotate Or Translate Your Projection Matrix
   clip[ 0] = modl[ 0] * proj[ 0];
   clip[ 1] = modl[ 1] * proj[ 5];
   clip[ 2] = modl[ 2] * proj[10] + modl[ 3] * proj[14];
   clip[ 3] = modl[ 2] * proj[11];
   clip[ 4] = modl[ 4] * proj[ 0];
   clip[ 5] = modl[ 5] * proj[ 5];
   clip[ 6] = modl[ 6] * proj[10] + modl[ 7] * proj[14];
   clip[ 7] = modl[ 6] * proj[11];
   clip[ 8] = modl[ 8] * proj[ 0];
   clip[ 9] = modl[ 9] * proj[ 5];
   clip[10] = modl[10] * proj[10] + modl[11] * proj[14];
   clip[11] = modl[10] * proj[11];
   clip[12] = modl[12] * proj[ 0];
   clip[13] = modl[13] * proj[ 5];
   clip[14] = modl[14] * proj[10] + modl[15] * proj[14];
   clip[15] = modl[14] * proj[11];
   // Extract The Numbers For The RIGHT Plane
   m_Frustum[0][0] = clip[ 3] - clip[ 0];
   m_Frustum[0][1] = clip[ 7] - clip[ 4];
   m_Frustum[0][2] = clip[11] - clip[ 8];
   m_Frustum[0][3] = clip[15] - clip[12];
   // Normalize The Result
   t = GLfloat(sqrt( m_Frustum[0][0] * m_Frustum[0][0] + m_Frustum[0][1] * m_Frustum[0][1]   
   + m_Frustum[0][2] * m_Frustum[0][2] ));
   m_Frustum[0][0] /= t;
   m_Frustum[0][1] /= t;
   m_Frustum[0][2] /= t;
   m_Frustum[0][3] /= t;
   // Extract The Numbers For The LEFT Plane
   m_Frustum[1][0] = clip[ 3] + clip[ 0];
   m_Frustum[1][1] = clip[ 7] + clip[ 4];
   m_Frustum[1][2] = clip[11] + clip[ 8];
   m_Frustum[1][3] = clip[15] + clip[12];
   // Normalize The Result
   t = GLfloat(sqrt( m_Frustum[1][0] * m_Frustum[1][0] + m_Frustum[1][1] * m_Frustum[1][1]  
   + m_Frustum[1][2] * m_Frustum[1][2] ));
   m_Frustum[1][0] /= t;
   m_Frustum[1][1] /= t;
   m_Frustum[1][2] /= t;
   m_Frustum[1][3] /= t;
   // Extract The BOTTOM Plane
   m_Frustum[2][0] = clip[ 3] + clip[ 1];
   m_Frustum[2][1] = clip[ 7] + clip[ 5];
   m_Frustum[2][2] = clip[11] + clip[ 9];
   m_Frustum[2][3] = clip[15] + clip[13];
   // Normalize The Result
   t = GLfloat(sqrt( m_Frustum[2][0] * m_Frustum[2][0] + m_Frustum[2][1] * m_Frustum[2][1]   
   + m_Frustum[2][2] * m_Frustum[2][2] ));
   m_Frustum[2][0] /= t;
   m_Frustum[2][1] /= t;
   m_Frustum[2][2] /= t;
   m_Frustum[2][3] /= t;
   // Extract The TOP Plane
   m_Frustum[3][0] = clip[ 3] - clip[ 1];
   m_Frustum[3][1] = clip[ 7] - clip[ 5];
   m_Frustum[3][2] = clip[11] - clip[ 9];
   m_Frustum[3][3] = clip[15] - clip[13];
   // Normalize The Result
   t = GLfloat(sqrt( m_Frustum[3][0] * m_Frustum[3][0] + m_Frustum[3][1] * m_Frustum[3][1]   
   + m_Frustum[3][2] * m_Frustum[3][2] ));
   m_Frustum[3][0] /= t;
   m_Frustum[3][1] /= t;
   m_Frustum[3][2] /= t;
   m_Frustum[3][3] /= t;
   // Extract The FAR Plane
   m_Frustum[4][0] = clip[ 3] - clip[ 2];
   m_Frustum[4][1] = clip[ 7] - clip[ 6];
   m_Frustum[4][2] = clip[11] - clip[10];
   m_Frustum[4][3] = clip[15] - clip[14];
   // Normalize The Result
   t = GLfloat(sqrt( m_Frustum[4][0] * m_Frustum[4][0] + m_Frustum[4][1] * m_Frustum[4][1]   
   + m_Frustum[4][2] * m_Frustum[4][2] ));
   m_Frustum[4][0] /= t;
   m_Frustum[4][1] /= t;
   m_Frustum[4][2] /= t;
   m_Frustum[4][3] /= t;
   // Extract The NEAR Plane
   m_Frustum[5][0] = clip[ 3] + clip[ 2];
   m_Frustum[5][1] = clip[ 7] + clip[ 6];
   m_Frustum[5][2] = clip[11] + clip[10];
   m_Frustum[5][3] = clip[15] + clip[14];
   // Normalize The Result
   t = GLfloat(sqrt( m_Frustum[5][0] * m_Frustum[5][0] + m_Frustum[5][1] * m_Frustum[5][1]  
   + m_Frustum[5][2] * m_Frustum[5][2] ));
   m_Frustum[5][0] /= t;
   m_Frustum[5][1] /= t;
   m_Frustum[5][2] /= t;
   m_Frustum[5][3] /= t;
}

Immernoch recht umfangreich, aber es sind jetzt nur noch 102 Operationen. Es wurden überflüßige Multiplikationen entfernt, deren Ergebnis unter der obrigen Bedingung normalerweise 0 ist.

Mit der unteren Funktion läßt sich jetzt sehr einfach berechnen, ob ein bestimmter Punkt momentan sichtbar ist, was gerade bei großen 3D-Szenen (Spiele!) mit sehr vielen Polygonen ungemeine Vorteile bietet. Es ist dort sinnvoll, weil noch bevor OpenGL mit unnötigen Daten überschwemmt wird, geprüft werden kann ob die zugehörigen Polygone überhaupt sichtbar sind.
In der glCamera-Klasse befindet sich noch eine weitere Funktion "SphereInFrustum()" die eine Kugel auf Sichtbarkeit prüft. Diese sollte sich ohne größere Erläuterungen (bei Bedarf) nutzen lassen.

BOOL glCamera::PointInFrustum(glPoint p)
{
   int i;
   // The Idea Behind This Algorithum Is That If The Point
   // Is Inside All 6 Clipping Planes Then It Is Inside Our
   // Viewing Volume So We Can Return True.

   for(i = 0; i < 6; i++)
   {
      if(m_Frustum[i][0] * p.x + m_Frustum[i][1] * p.y + m_Frustum[i][2] 
         * p.z + m_Frustum[i][3] <= 0)
      {
         return(FALSE);
      }
   }

   return(TRUE);
}

Bis jetzt wird nur abgefragt ob ein Punkt "potentiell" sichtbar ist, also im Sichtvolumen liegt. Wichtig ist auch, ob er nicht durch ein anderes Objekt verdeckt wird. OpenGL erledigt diesen Teil der Arbeit durch die Projektion eines beliebigen 3D-Punktes auf die Sichtebene des Betrachters. Dabei muss im Z-Buffer (also dem Teil des Grafikspeichers, der Tiefeninformationen enthält) geprüft werden ob sich vor dem Licht (das den Linseneffekt ja erzeugen soll) schon irgendetwas befindet.

// ########## New Stuff by rIO.Spinning Kids ##########
bool glCamera::IsOccluded(glPoint p)
{
   GLint viewport[4];// Space For Viewport Data
   GLdouble mvmatrix[16], projmatrix[16];// Space For Transform Matrix
   GLdouble winx, winy, winz;// Space For Returned Projected Coords
   GLdouble flareZ;// Here We Will Store The Transformed Flare Z
   GLfloat bufferZ;// Here We Will Store The Read Z From The Buffer
   glGetIntegerv (GL_VIEWPORT, viewport);// Get Actual Viewport
   glGetDoublev (GL_MODELVIEW_MATRIX, mvmatrix);// Get Actual Model View Matrix
   glGetDoublev (GL_PROJECTION_MATRIX, projmatrix);// Get Actual Projection Matrix
   // This Asks OGL To Guess The 2D Position Of A 3D Point Inside The Viewport
   gluProject(p.x, p.y, p.z, mvmatrix, projmatrix, viewport, &winx, &winy,    &winz);
   flareZ = winz;
   // We Read Back One Pixel From The Depth Buffer (Exactly Where Our Flare Should Be Drawn)
   glReadPixels(winx, winy,1,1,GL_DEPTH_COMPONENT, GL_FLOAT, &bufferZ);
   // If The Buffer Z Is Lower Than Our Flare Guessed Z Then Don't Draw
   // This Means There Is Something In Front Of Our Flare
   if (bufferZ < flareZ)
      return true;

   else
      return false;
}

Ein anderes Problem ist die eigentliche Darstellung des Effektes. Man könnte auf die Idee kommen, die Texturen auf Vierecke im Raum zu mappen, allerdings würde sich der Effekt dadurch merklich verzerren, sobald man etwas seitlicher ins Licht guckt. Viel geeigneter sind Sprites, also 2D-Grafiken die flach auf der Sichtebene liegen und somit keine Z-Koordinate besitzen (Ein noch besserer Weg wären sogenannte Point Sprites, die allerdings auch "nur" als OpenGL-Extension zu haben sind, dort muss nur ein einziger Punkt übergeben werden und die Texturkoordinaten fallen weg, aber es muss diesmal ohne Extensions gehen). Damit die 4 Grafiken ständig in die Kamera zeigen, müssen die Kamerarotationen (X und Y) beim Zeichnen umgekehrt werden. Das klappt gut solange der Betrachter vor der Lichtquelle steht. Sollte er aber an der Lichtquelle vorbei gehen, funktioniert der Trick nicht mehr. Die schnellste Lösung ist, die Lichtquelle in Z-Richtung mit der Kamera mitzuverschieben. Dadurch erscheint die Lichtquelle sehr weit entfernt, was einen sonnenähnlichen Effekt erzeugt. Die dazu benötigten Vektoren und Punkte werden hier berechnet.

   GLfloat Length = 0.0f;
   // Draw The Flare Only If The Light Source Is In Our Line Of Sight
   if(SphereInFrustum(m_LightSourcePos, 1.0f) == TRUE)
   {
      vLightSourceToCamera = m_Position - m_LightSourcePos;
      // Lets Compute The Vector That Points To

      // The Camera From The Light Source.
      Length = vLightSourceToCamera.Magnitude();
      // Save The Length We Will Need It In A Minute
      ptIntersect = m_DirectionVector * Length;
      // Now Lets Find A Point Along The Cameras Direction

      // Vector That We Can Use As An Intersection Point
      // Lets Translate Down This Vector The Same Distance
      // That The Camera Is. Away From The Light Source.
      ptIntersect += m_Position;
      // Lets Compute The Vector That Points To The Intersect
      vLightSourceToIntersect = ptIntersect - m_LightSourcePos;


      // Point From The Light Source
      Length = vLightSourceToIntersect.Magnitude();
      // Save The Length We Will Need It Later

      vLightSourceToIntersect.Normalize();
      // Normalize The Vector So Its Unit Length


Zuerst muss der Abstand von Lichtquelle und Kamera ermittelt werden (vLightSourceToCamera). Als nächstes wird der Schnittpunkt entlang des Richtungsvektors der Kamera berechnet. Dabei muss der Abstand zwischen Schnittpunkt und Kamera genauso groß sein, wie der zwischen Lichtquelle und Kamera. Es wird nun noch der Vektor berechnet, an dem sich die Lage der einzelnen Grafiken orientiert (vLightSourceToIntersect). Die untere Skizze stellt das nocheinmal anschaulich dar:

Das war der schwierige Teil, jetzt müssen die Grafiken des Effekts nur noch wie auf einer Perlenschnur am Vektor platziert und ausgegeben werden.

   glEnable(GL_BLEND);
   glBlendFunc(GL_SRC_ALPHA, GL_ONE);
   glDisable(GL_DEPTH_TEST);
   glEnable(GL_TEXTURE_2D);
   // ########## New Stuff by rIO.Spinning Kids ##########
   if (!IsOccluded(m_LightSourcePos))// Check If The Center Of The Flare Is Occluded
   {
      // Render The Large Hazy Glow
      RenderBigGlow(0.60f, 0.60f, 0.8f, 1.0f, m_LightSourcePos, 16.0f);
      // Render The Streaks
      RenderStreaks(0.60f, 0.60f, 0.8f, 1.0f, m_LightSourcePos, 16.0f);
      // Render The Small Glow
      RenderGlow(0.8f, 0.8f, 1.0f, 0.5f, m_LightSourcePos, 3.5f);
      pt = vLightSourceToIntersect * (Length * 0.1f);// Lets Compute A Point That Is 20%
      pt += m_LightSourcePos;// Away From The Light Source In The
      // Direction Of The Intersection Point
      RenderGlow(0.9f, 0.6f, 0.4f, 0.5f, pt, 0.6f);// Render The Small Glow
      pt = vLightSourceToIntersect * (Length * 0.15f);// Lets Compute A Point That Is 30%
      pt += m_LightSourcePos;// Away From The Light Source In The
      // Direction Of The Intersection Point
      RenderHalo(0.8f, 0.5f, 0.6f, 0.5f, pt, 1.7f);// Render The Halo
      pt = vLightSourceToIntersect * (Length * 0.175f);// Lets Compute A Point That Is 35%
      pt += m_LightSourcePos;// Away From The Light Source In The
      // Direction Of The Intersection Point
      RenderHalo(0.9f, 0.2f, 0.1f, 0.5f, pt, 0.83f);// Render The Halo
      pt = vLightSourceToIntersect * (Length * 0.285f);// Lets Compute A Point That Is 57%
      pt += m_LightSourcePos;// Away From The Light Source In The
      // Direction Of The Intersection Point
      RenderHalo(0.7f, 0.7f, 0.4f, 0.5f, pt, 1.6f);// Render The Halo
      pt = vLightSourceToIntersect * (Length * 0.2755f);// Lets Compute A Point That Is 55.1%
      pt += m_LightSourcePos;// Away From The Light Source In The
      // Direction Of The Intersection Point
      RenderGlow(0.9f, 0.9f, 0.2f, 0.5f, pt, 0.8f);// Render The Small Glow
      pt = vLightSourceToIntersect * (Length * 0.4775f);// Lets Compute A Point That Is 95.5%
      pt += m_LightSourcePos;// Away From The Light Source In The
      // Direction Of The Intersection Point
      RenderGlow(0.93f, 0.82f, 0.73f, 0.5f, pt, 1.0f);// Render The Small Glow
      pt = vLightSourceToIntersect * (Length * 0.49f);// Lets Compute A Point That Is 98%
      pt += m_LightSourcePos;// Away From The Light Source In The
      // Direction Of The Intersection Point
      RenderHalo(0.7f, 0.6f, 0.5f, 0.5f, pt, 1.4f);// Render The Halo
      pt = vLightSourceToIntersect * (Length * 0.65f);// Lets Compute A Point That Is 130%
      pt += m_LightSourcePos;// Away From The Light Source In The
      // Direction Of The Intersection Point
      RenderGlow(0.7f, 0.8f, 0.3f, 0.5f, pt, 1.8f);// Render The Small Glow
      pt = vLightSourceToIntersect * (Length * 0.63f);// Lets Compute A Point That Is 126%
      pt += m_LightSourcePos;// Away From The Light Source In The
      // Direction Of The Intersection Point
      RenderGlow(0.4f, 0.3f, 0.2f, 0.5f, pt, 1.4f);// Render The Small Glow
      pt = vLightSourceToIntersect * (Length * 0.8f);// Lets Compute A Point That Is 160%
      pt += m_LightSourcePos;// Away From The Light Source In The
      // Direction Of The Intersection Point
      RenderHalo(0.7f, 0.5f, 0.5f, 0.5f, pt, 1.4f);// Render The Halo
      pt = vLightSourceToIntersect * (Length * 0.7825f);// Lets Compute A Point That Is 156.5%
      pt += m_LightSourcePos;// Away From The Light Source In The
      // Direction Of The Intersection Point
      RenderGlow(0.8f, 0.5f, 0.1f, 0.5f, pt, 0.6f);// Render The Small Glow
      pt = vLightSourceToIntersect * (Length * 1.0f);// Lets Compute A Point That Is 200%
      pt += m_LightSourcePos;// Away From The Light Source In The
      // Direction Of The Intersection Point
      RenderHalo(0.5f, 0.5f, 0.7f, 0.5f, pt, 1.7f);// Render The Halo
      pt = vLightSourceToIntersect * (Length * 0.975f);// Lets Compute A Point That Is 195%
      pt += m_LightSourcePos;// Away From The Light Source In The
      // Direction Of The Intersection Point
      RenderGlow(0.4f, 0.1f, 0.9f, 0.5f, pt, 2.0f);// Render The Small Glow
   }
   glDisable(GL_BLEND );
   glEnable(GL_DEPTH_TEST);
   glDisable(GL_TEXTURE_2D);

Jetzt noch die eigentlichen Ausgabefunktionen der einzelnen Objekte:

void glCamera::RenderHalo(GLfloat r, GLfloat g, GLfloat b, GLfloat a, glPoint p, GLfloat scale)
{
   glPoint q[4];
   // Basically We Are Just Going To Make A 2D Box
   // From Four Points We Don't Need A Z Coord Because
   // We Are Rotating The Camera By The Inverse So The 
   // Texture Mapped Quads Will Always Face Us.
   q[0].x = (p.x - scale);// Set The x Coordinate -scale Units From The Center    Point.
   q[0].y = (p.y - scale);// Set The y Coordinate -scale Units From The Center    Point.
   q[1].x = (p.x - scale);// Set The x Coordinate -scale Units From The Center    Point.
   q[1].y = (p.y + scale);// Set The y Coordinate scale Units From The Center Point.
   q[2].x = (p.x + scale);// Set The x Coordinate scale Units From The Center Point.
   q[2].y = (p.y - scale);// Set The y Coordinate -scale Units From The Center    Point.
   q[3].x = (p.x + scale);// Set The x Coordinate scale Units From The Center Point.
   q[3].y = (p.y + scale);// Set The y Coordinate scale Units From The Center Point.
   glPushMatrix();// Save The Model View Matrix
   glTranslatef(p.x, p.y, p.z);// Translate To Our Point
   glRotatef(-m_HeadingDegrees, 0.0f, 1.0f, 0.0f);
   glRotatef(-m_PitchDegrees, 1.0f, 0.0f, 0.0f);
   glBindTexture(GL_TEXTURE_2D, m_HaloTexture);// Bind To The Big Glow Texture
   glColor4f(r, g, b, a);// Set The Color Since The Texture Is A Gray Scale
   glBegin(GL_TRIANGLE_STRIP);// Draw The Big Glow On A Triangle Strip
   glTexCoord2f(0.0f, 0.0f);
   glVertex2f(q[0].x, q[0].y);
   glTexCoord2f(0.0f, 1.0f);
   glVertex2f(q[1].x, q[1].y);
   glTexCoord2f(1.0f, 0.0f);
   glVertex2f(q[2].x, q[2].y);
   glTexCoord2f(1.0f, 1.0f);
   glVertex2f(q[3].x, q[3].y);
   glEnd();
   glPopMatrix();// Restore The Model View Matrix
}
void glCamera::RenderGlow(GLfloat r, GLfloat g, GLfloat b, GLfloat a, glPoint p, GLfloat scale)
{
   glPoint q[4];
   // Basically We Are Just Going To Make A 2D Box
   // From Four Points We Don't Need A Z Coord Because
   // We Are Rotating The Camera By The Inverse So The 
   // Texture Mapped Quads Will Always Face Us.
   q[0].x = (p.x - scale);// Set The x Coordinate -scale Units From The Center    Point.
   q[0].y = (p.y - scale);// Set The y Coordinate -scale Units From The Center    Point.
   q[1].x = (p.x - scale);// Set The x Coordinate -scale Units From The Center    Point.
   q[1].y = (p.y + scale);// Set The y Coordinate scale Units From The Center Point.
   q[2].x = (p.x + scale);// Set The x Coordinate scale Units From The Center Point.
   q[2].y = (p.y - scale);// Set The y Coordinate -scale Units From The Center    Point.
   q[3].x = (p.x + scale);// Set The x Coordinate scale Units From The Center Point.
   q[3].y = (p.y + scale);// Set The y Coordinate scale Units From The Center Point.
   glPushMatrix();// Save The Model View Matrix
   glTranslatef(p.x, p.y, p.z);// Translate To Our Point
   glRotatef(-m_HeadingDegrees, 0.0f, 1.0f, 0.0f);
   glRotatef(-m_PitchDegrees, 1.0f, 0.0f, 0.0f);
   glBindTexture(GL_TEXTURE_2D, m_GlowTexture);// Bind To The Big Glow Texture
   glColor4f(r, g, b, a);// Set The Color Since The Texture Is A Gray Scale
   glBegin(GL_TRIANGLE_STRIP);// Draw The Big Glow On A Triangle Strip
   glTexCoord2f(0.0f, 0.0f);
   glVertex2f(q[0].x, q[0].y);
   glTexCoord2f(0.0f, 1.0f);
   glVertex2f(q[1].x, q[1].y);
   glTexCoord2f(1.0f, 0.0f);
   glVertex2f(q[2].x, q[2].y);
   glTexCoord2f(1.0f, 1.0f);
   glVertex2f(q[3].x, q[3].y);
   glEnd();
   glPopMatrix();// Restore The Model View Matrix
}
void glCamera::RenderBigGlow(GLfloat r, GLfloat g, GLfloat b, GLfloat a, glPoint p, GLfloat scale)
{
   glPoint q[4];
   // Basically We Are Just Going To Make A 2D Box
   // From Four Points We Don't Need A Z Coord Because
   // We Are Rotating The Camera By The Inverse So The 
   // Texture Mapped Quads Will Always Face Us.
   q[0].x = (p.x - scale);// Set The x Coordinate -scale Units From The Center    Point.
   q[0].y = (p.y - scale);// Set The y Coordinate -scale Units From The Center    Point.
   q[1].x = (p.x - scale);// Set The x Coordinate -scale Units From The Center    Point.
   q[1].y = (p.y + scale);// Set The y Coordinate scale Units From The Center Point.
   q[2].x = (p.x + scale);// Set The x Coordinate scale Units From The Center Point.
   q[2].y = (p.y - scale);// Set The y Coordinate -scale Units From The Center    Point.
   q[3].x = (p.x + scale);// Set The x Coordinate scale Units From The Center Point.
   q[3].y = (p.y + scale);// Set The y Coordinate scale Units From The Center Point.
   glPushMatrix();// Save The Model View Matrix
   glTranslatef(p.x, p.y, p.z);// Translate To Our Point
   glRotatef(-m_HeadingDegrees, 0.0f, 1.0f, 0.0f);
   glRotatef(-m_PitchDegrees, 1.0f, 0.0f, 0.0f);
   glBindTexture(GL_TEXTURE_2D, m_BigGlowTexture);// Bind To The Big Glow Texture

   glColor4f(r, g, b, a);// Set The Color Since The Texture Is A Gray Scale
   glBegin(GL_TRIANGLE_STRIP);// Draw The Big Glow On A Triangle Strip
   glTexCoord2f(0.0f, 0.0f);
   glVertex2f(q[0].x, q[0].y);
   glTexCoord2f(0.0f, 1.0f);
   glVertex2f(q[1].x, q[1].y);
   glTexCoord2f(1.0f, 0.0f);
   glVertex2f(q[2].x, q[2].y);
   glTexCoord2f(1.0f, 1.0f);
   glVertex2f(q[3].x, q[3].y);
   glEnd();
   glPopMatrix();// Restore The Model View Matrix
}
void glCamera::RenderStreaks(GLfloat r, GLfloat g, GLfloat b, GLfloat a, glPoint p, GLfloat scale)
{
   glPoint q[4];
   // Basically We Are Just Going To Make A 2D Box
   // From Four Points We Don't Need A Z Coord Because
   // We Are Rotating The Camera By The Inverse So The 
   // Texture Mapped Quads Will Always Face Us.
   q[0].x = (p.x - scale);// Set The x Coordinate -scale Units From The Center    Point.
   q[0].y = (p.y - scale);// Set The y Coordinate -scale Units From The Center    Point.
   q[1].x = (p.x - scale);// Set The x Coordinate -scale Units From The Center    Point.
   q[1].y = (p.y + scale);// Set The y Coordinate scale Units From The Center Point.
   q[2].x = (p.x + scale);// Set The x Coordinate scale Units From The Center Point.
   q[2].y = (p.y - scale);// Set The y Coordinate -scale Units From The Center    Point.
   q[3].x = (p.x + scale);// Set The x Coordinate scale Units From The Center Point.
   q[3].y = (p.y + scale);// Set The y Coordinate scale Units From The Center Point.
   glPushMatrix();// Save The Model View Matrix
   glTranslatef(p.x, p.y, p.z);// Translate To Our Point
   glRotatef(-m_HeadingDegrees, 0.0f, 1.0f, 0.0f);
   glRotatef(-m_PitchDegrees, 1.0f, 0.0f, 0.0f);
   glBindTexture(GL_TEXTURE_2D, m_StreakTexture);// Bind To The Big Glow Texture
   glColor4f(r, g, b, a);// Set The Color Since The Texture Is A Gray Scale
   glBegin(GL_TRIANGLE_STRIP);// Draw The Big Glow On A Triangle Strip
   glTexCoord2f(0.0f, 0.0f);
   glVertex2f(q[0].x, q[0].y);
   glTexCoord2f(0.0f, 1.0f);
   glVertex2f(q[1].x, q[1].y);
   glTexCoord2f(1.0f, 0.0f);
   glVertex2f(q[2].x, q[2].y);
   glTexCoord2f(1.0f, 1.0f);
   glVertex2f(q[3].x, q[3].y);
   glEnd();
   glPopMatrix();// Restore The Model View Matrix
}

Mit den W,A,S,D-Knöpfen kann die Kamera geschwänkt werden. Mit 1 und 2 werden zusätzliche Informationen angezeigt. Z bewegt die Kamera konstant nach vorne, C nach hinten und X stoppt jedgliche Bewegung.

Das wars schonwieder. Fragen, Wünsche, Kommentare sind wie immer willkommen. Ich bin natürlich nicht der erste der sich mit Linseneffekten auseinandergesetzt hat, unten finden sich ein paar sicher nützliche Links zum Thema. Vielen Dank an: Dave Steere, Cameron Tidwell, Bert Sammons und Brannon Martindale.

Hoffentlich hats Spaß gemacht, bis zum nächsten Tutorial.

http://www.gamedev.net/reference/articles/article874.asp
http://www.gamedev.net/reference/articles/article813.asp
http://www.opengl.org/developers/code/mjktips/lensflare/
http://www.markmorley.com/opengl/frustumculling.html
http://oss.sgi.com/projects/ogl-sample/registry/HP/occlusion_test.txt
http://oss.sgi.com/projects/ogl-sample/registry/NV/occlusion_query.txt

- Cheers

Vic Hollis

Jeff Molofee (NeHe) http://nehe.gamedev.net

Die Source Codes und Ausführbaren Dateien zu den Kursen liegen auf der Neon Helium Website

Übersetzt und leicht modifiziert von Hans-Jakob Schwer 22.01.2k4, www.codeworx.org