HUNT THE ALP 2008


Steuerung:

WSAD bzw. Cursortasten ... bewegen


Ziel:

Der erste, der 1000 Punkte erreicht, hat gewonnen. Punkteschlüssel siehe Doku "Gamelogik". Viel Spaß beim Kistensammeln und Alp jagen :-)



Effektliste:

Terminologie: Immer wenn von Koordinaten die Rede ist, beziehen sich diese auf das right-handed coordinate frame, also insbesondere ist die y-Achse "die Höhe".


## Shadow Maps	3	Direktionale oder omnidirektionale Shadow Maps mit Self Shadowing auf den Objekten.

Es wurde die nicht-erweiterte "klassische" Shadowmapping Technik implementiert. Als Quelle dienten hier vor allem die Folien aus Echtzeitgraphik[8] und auch [3a], [3b]. Es gibt eine globale Lichtquelle, "die Sonne".

Zuerst wird die Kamera auf die Position der Lichtquelle gesetzt. Da nicht das gesamte Gelände mit einer shadowmap "abgedeckt" werden kann, ohne zu große Kompromisse bei der Schattenqualität einzugehen, wird die Kamera immer in einer konstanten Entfernung zum Spieler mitbewegt. Zusätzlich wird der FOV der Schattenkamera im Vergleich zur "Hauptkamera" vergrößert (um zusätzlich eine größere Fläche abzudecken). Eigentlich sollte die Sonne in der Szene ja direktionales Licht sein - trotzdem wird eine perspektivische Kamera verwendet - je näher man an den Rand der shadowmap gelangt, desto stärker "verzerrt" erscheint der Schatten dann auch.

Von dieser neuen Kamera(-position) aus wird nun jedes Objekt der Szene in ein FBO gerendert, welches potentiell einen Schatten werfen können soll. Dieses FBO stellt enthält dann die gewünschte shadowmap.

Was nützt uns jetzt diese diskretisierte Information über die Szene und wie können wir darauf zugreifen? Einfach weiterlesen :-)

Jetzt wird die gesamte Szene "normal" von der Hauptkamera aus gerendert. Dabei wird jedoch die vorher generierte und in der shadowmap gespeicherte Information verwendet:

Die eyespace vertex positions (modelview matrix * object space vertex position) werden mittels der inversen view matrix der "Hauptkamera" zuerst in den worldspace zurücktransformiert und dann mit der view- und projection matrix der Lichtquellenkamera / "der Lichtquelle" in deren postperspective space (postperspective space: nach perspektivischer Transformation, vor perspektivischer Division durch die homogene Koordinate). Da im postperspective space die späteren screen coordinates im Interval [-1,+1]x[-1,+1] liegen, eine 2D Textur (in diesem Fall eben die shadowmap) aber mit [0,1]x[0,1] adressiert wird, muß zusätzlich noch mit 0.5 skaliert und um 0.5 translated werden.

Man erhält nun einen Vektor (s', t', r', q') im vertex shader; die GPU reicht diese Koordinaten jedoch perspektivisch korrekt an den fragmentshader weiter - d.h. falls der Inputvertex die postperspektivischen Koordinaten (x, y, z, w) hat, kommt im fragment shader folgendes an: (s, t, r, q) = (s'/w, t'/w, r'/w, q'/w)  (siehe [8])

Das mapping jeder fragment position auf dem screen in die shadowmap ist somit bewerkstelligt! Jetzt reicht es, einen shadowmap lookup mit den ermittelten Koordinaten durchzuführen. Dabei verwendet man von den transformierten Koordinaten s/q, t/q um die shadowmap auszulesen und der ausgelesene Wert wird mit r/q verglichen. Ist der transformierte Punkt weiter von der Lichtquelle entfernt, als der näheste Occluder zur Lichtkamera (<==> Schattenentscheidung entspricht Sichtbarkeitsentscheidung!) - dies ist der Fall, falls "r/q > shadowmap[s/q, t/q]" - wird dieses Fragment einfach ein wenig dünkler dargestellt, da es ja bzgl. dieser Lichtquelle im Schatten liegt.

Anm.1: in GLSL wird statt ".strq" ".stpq" verwendet, da in ".rgba" der r-qualifier schon als "1.Strukturelement" vorkommt und damit eine Mehrdeutigkeit vorliegen würde... kann sein, daß in dieser Doku mal statt 'r' 'p' steht - nur damit klar ist, daß eigentlich in beiden Fällen dieselbe Koordinate gemeint ist.

Anm.2: man benötigt die 2.Division (durch q), da man ja in den real-space der Lichtkamera kommen will/muß, man sich aber vorerst in deren homogeneous space wieder findet (eben q nicht notwendigerweise == 1.0). Dies ist informell auch so einzusehen, da man ja auch 2 perspektivische Transformationen durchführt - nämlich eine von der Hauptkamera aus und die zweite von der Lichtkamera aus...

Anm.3: Speziell fürs selfshadowing wird ein "polygon offset" verwendet, wenn die shadowmap generiert wird. Der polygon offset "schiebt" die gesamte Szene ein wenig von der Kamera weg. Da die shadowmap "nur" eine diskretiesierte Information über die Szene darstellt, macht man einen inherenten Fehler mit dieser Methode. Dieser Fehler äußert sich dann in fälschlicherweisem selfshadowing ("surface acne") - einem sehr offensichtlichen und störenden aliasing Artefakt. Mit eben erwähntem polygon offset kann man diesem Problem ein wenig entgegenwirken, da man "sicherstellt", daß eine Oberfläche selber in der shadowmap weiter entfernt erscheint, als sie tatsächlich gerendert wird, wordurch sie nicht "in ihrem eigenen Schatten liegt", selbst wenn man mit der shadowmap diesen "kleinen" Fehler macht...

Anm.4: die shadowmap wird als bilinear zu interpolierende depth texture (im Prinzip/meist eine 24 bit luminance -d.h. nur ein Kanal = intensity- texture)  definiert. Im Shader kann man dann eine spezielle shadowmap texturelookup Funktion bemühen, die selbst schon die perspektivische Division (eben durch q) durchführt und zusätzlich 4 samples "auf einmal" aus der shadowmap ausliest (~HW PCF = percentage closer filtering). Somit werden die Schatten quasi ohne performance penalty etwas geblurred und damit auch schöner anzuschauen :-) 




## Spotlights/projizierte Texturen	2	Eine dynamische Textur die mit projektivem Texturing auf Geometrie aufgebracht wird.

Mal davon abgesehen, daß der shadowmap Algorithmus eigentlich ja bereits projektives Texturing ist (der lookup-Wert ist halt keine Farbe, sondern eine Tiefeninformation), werden auch 2 screenspace Texturen auf die Szene projiziert, nämlich für die Reflektion und Refraktion auf der Wasseroberfläche (mehr dazu bei der Beschreibung des Wassereffekts). Screenspace Texturen auf die Szene zu projizieren ist "etwas leichter", da sich Projektor und Hauptkamera bereits an derselben Stelle befinden und die Kameraparameter (camera coordinate frame, ... und im wesentlichen auch FOV). Die momentane fragmentposition wird also einfach nur mit dem 0.5 scale + translate transformiert, um die jeweilige Textur zu indizieren. Falls man später auch die Texturkoordinaten leicht verändert, um einen "verzerrenden Spiegelungseffekt" zu erzielen, ist es keine schlechte Idee beim rendering der Refraktions- und Reflektionstextur den FOV etwas zu erhöhen. Somit erhält man an den Rändern nicht "falsche" Infos aus der jeweiligen Map, auch wenn man schon Fragments "außerhalb des screens" benötigen würde - diese Ränder werden ja eben durch den etwas größeren FOV mit einer hohen Wahrscheinlichkeit abgedeckt sein (um einen guten Wert zu finden, bietet sich trial and error an...).

Auch die "dynamic boat wakes" werden mittels screenspace projective texturing realisiert. Zuerst werden quads, die auf der xz Ebene bei y=0 (und somit direkt "auf dem Wasser") liegen gerendert. Im fragment shader wird dann eine simple Wellensimulation mit der Möglichkeit zu Interferenzen ausgewertet (im Prinzip einfache Punktwellen, die sich "konzentrisch" ausbreiten). Die Grauwerte die sich ergeben landen dann in einem FBO, welches dann als projective texture dient. Später, wenn das Wasser generiert wird (siehe weiter unten in der Effektliste beim Punkt "Wasser"), wird einfach dieser Wert zur Wasserhöhe dazuaddiert (mittels "Werteshift" kann das Wasser durchaus auch tiefer werden nach so einer Addition).

Ein weiteres mal kommt projective texturing beim Wasseroberflächenshading zum Einsatz: Die oben generierte boat wake displacement texture wird zusätzlich dazu verwendet, eine world space normal map zu berechnen (partielle Ableitungen nach x und z, sowie ein crossproduct ergeben eine komplette normalmap) (wieder gilt: siehe weiter unten in der Effektliste beim Punkt "Wasser" und beim Punkt "normal/parallax mapping"). Wird also ein Oberflächenpunkt gezeichnet, wird zusätzlich mittels projective texturing in dieser boat wake dynamic water displacement normal texture nachgesehen. 

Die Möglichkeit, die Wasseroberfläche zusätzlich interaktiv zu verändern, wird zwar in [2] kurz erwähnt, jedoch gibt es weder Details zur Implementation, noch ist die Technik im ausführbaren Demo implementiert.


Um die jeweiligen projective texturing textures zu sehen, reicht es in Hunt the Alp auf 'i' zu drücken - enjoy!



## Levels of Detail für Terrain	

Um das Terrain zu generieren, wird einmal ein entsprechend großes VBO mit 2 index arrays (dazu später mehr) auf die GPU hochgeladen. Ausgehend von der Hauptkamera wird eine sampling Kamera so justiert, daß jedes von ihr sichtbare Pixel einen Schnitt mit der baseplane hat (xz Ebene, y=0) - es darf natürlich auch kein "back-firing" geben. 

Gute/typische VBO Grüßen bewegen sich in etwa im Bereich 256x256 bis 512x512 vertices.

Während in [2] diese Methode mit vertex texture fetches (im folgenden VTF) zum Einsatz kommt, wird in der Hunt the Alp engine das überBuffers Konzept von OpenGL verwendet (und zwar in der Form von "Render to Vertex Array/Buffer). Der Vorteil davon ist, daß erstens mipmapping der fragment shader unit verwendet werden kann (ist ja ein Ding der Unmöglichkeit im vertex shader, da erst screen space derivatives für die Wahl des mipmap levels ausgewertet werden), zweitens VTF (immer noch?) langsamer ist als ein texture fetch im fragment processor und drittens selbst ältere Karten wie GeForce5, die ja kein VTF können, mit dieser Technik zurecht kommen. 

Ausgehend von der sampling camera wird in ein float FBO die Schnittposition (ray-plane intersection, wobei für jeden pixel ein ray/eine ray equation "hergeleitet" werden kann) mit jedem Pixel gespeichert. 

Für die ray equation: ein screen-aligned quad wird gerendered, die texture coordinates "kombiniert" mit den Kameraachsen ergibt eine ray direction, wodurch eine Gerade hergeleitet werden kann.

Sobald man die xz Koordinaten kennt, kann man den vertex entlang y verschieben (displacement mapping). Der entsprechende y-Verschiebungswert kommt aus einer 16 bit luminance texture (diese texture ist die heightmap). Der automatisch ermittelte mipmap level (der streng genommen nur für ein komplett flaches Gelände - y konstant 0 - "korrekt" ist) reicht alleine nicht aus, um ein ausreichendes sampling zu erreichen, also werden zusätzliche samples aus den "lower mips" gelesen und mit in die Höhenberechnung einbezogen. Um dem "swimming effect" weiter entgegenzuwirken wird noch eine weitere Methode eingesetzt, jetzt aber vorerst noch zum mesh Generierungsproblem.... 

Okay, was nutzt mir also jetzt ein farbiges float FBO wenn ich doch eigentlich vertex positions / ein VBO brauche? Wieder soll der geduldige Leser belohnt werden ;-)

Der Begriff Render to vertex buffer (im folgenden R2VB) ist ja bereits gefallen. Der Vorgang funktioniert wie folgt: das generierte VBO, wird als PBO gebunden. Nun kann mittels glReadPixels() der Inhalt des FBOs ins PBO(und somit VBO!) kopiert werden. Der OpenGL Treiber sollte diese Operation alle "auf der GPU/innerhalb des VRAMs/auf der server side" durchführen können, wodurch es nicht wie sonst bei glReadPixels zu einem Tranfser von GPU-VRAM in CPU-RAM (und wieder zurück fürs "updated VBO") kommt...

Prinzipielle Vorteile der projected grid Methode: continuous LOD, watertight mesh, verglichen zu anderen LOD Techniken einfache Implementierung, view frustum culling "für umsonst", near equsized quads in screenspace und vermutlich noch einige mehr [wird in Hunt the Alp verwendet? - Muß ja gut sein! ;-)] 

Verbesserungen gegenüber [1], Auszug aus der Doku zur 2.Abgabe:

purpously slightly non-linear tesselation in screen space depending on camera-orientation
to avoid swimming effect in the distance; when aiming the camera to the ground, a uniform tesselation is blended with the distorted mapping, so that we have the best of the 2 extreme cases for the projector. In other words: when aiming at the horizon (we have to sample mostly distant parts of the terrain), more sample points are needed in the distance, so that the grid is distorted accordingly, in a non-linear way. On the other hand, when looking straight down, this distortion is not necessary any more and we would like to cover the ground with unifromly spaced sample points.
Additionally, the sampling camera "height-offset" is interpolated based on the viewdirection - when aiming at the horizon, it is better to have the sampling camera slightly higher than the main (=viewing) camera to leverage the problematic intersections at grazing angles. Again, when looking straight down, a sampling camera that is higher than the main camera unnecessarily scatters the sampling points over a larger area than the area that is seen from the main camera, so now it is better when the sampling camera's height is approximately at the same height as the main camera - this way the vertex efficiency is improved without introducing artifacts.  

While the basic algorithm was explained in [1], the only proposal to address the undesired swimming artifact was the lower LOD heightmap access based on the distance. The sampling camera height adjustments were touched in [2]. The distortion of the grid tesselation were developed "incrementally by trial and error".
The R2VB implementation was only mentioned in a pseudo-code snippet in [2], but without implementation details, since this feature wasn't available in HW back then. In [1] vertex texture fetches are needed to displace the raycasted projection points on the baseplane, whereas the presented method works on older cards without VTF, too :-)



Ad "2 index arrays": Für die Reflektion und Refraktion wird zwar dasselbe VBO verwendet, jedoch wird durch den Einsatz eines anderen Indexarrays nur jeder n-te vertex gezeichnet (im jetzigen release: n=2). Das Gelände wird dadurch zwar nicht mehr so detailgetreu wiedergegeben, jedoch fällt dies in der Reflektion/Refraktion ohnedies kaum auf. Auf jeden Fall spart man einige Polygone und kann dadurch die performance ohne großen Aufwand erhöhen.






## Environment Mapping	1	Sphere- oder Cube Mapping auf beweglichen Objekten. Wenn die Environment Map jeden Frame upgedatet wird bekommt ihr 1 Extrapunkt

Die cubemap skybox wird in die screenspace reflection- & refraction maps gerendert und somit "indirekt" im lookup verwendet.



## Per Pixel Lighting	2	Phong-Beleuchtung im Fragment Shader.
Beleuchtungsmodell basierend auf [7] auf den MD2 Modellen.
Auch das Gelände wird per pixel gelighted wobei auch hier aufgrund der Speicherplatzeffizienz keine vorberechnete normalmap zum Einsatz kommt, sondern wieder mittels gradient Berechnung die surface normal on the fly approximiert wird.


## Normal/Parallax Mapping	3	Normal Mapping, bei dem ihr den Tangent Space selber berechnet. Dieser Effekt zählt nur wenn er auf komplexen Objekten aufgebracht ist und nicht auf axis-aligned Quads oder Boxen! In diesem Effekt ist Per Pixel Lighting bereits inkludiert.

Bei den dynamic boat wakes wird on the fly eine world space normal map berechnet und diese kommt beim shading der Wasseroberfläche (zusätzlich) zum Einsatz. Genaueres steht bereits bei Punkt "spotlights / projective texturing".


## Kaustiken	1	Ein Lichtbrechungseffekt unter Wasser, der als animierte Texturschicht über die normale Oberflächentextur geblendet wird. Wenn ihr die Kaustiken-Textur selbst dynamisch jeden Frame neu generiert bekommt ihr 1 Extrapunkt

Das Wellenmodell wird beim Zeichnen der refraction map ausgewertet, und abhängig von der Wellenhöhe wird ein Intensitätswert on the fly generiert. Dieser Wert wird zusätzlich noch nichtlinear mit der Wasserhöhe verringert (somit nimmt die Kausikintensität mit zunehmender Wassertiefe ab). Je größer der Intensitätswert, desto "weißer" wird nun der "Meeresboden" gezeichnet.



## Wasser	1	Sich dynamisch ändernde Wellen-Geometrie und eine Texturanimation.

"animated watersurface with geometric displacement"

Weil das geometric displacement der Wasseroberfläche vergleichsweise klein ist (etwa im Vergleich zum Gelände), reicht es, ein kleineres VBO zu verwenden. Gute/typische Werte sind 64x64 bis 128x128.

In einer frühen Version wurde das Wasser genau wie das Gelände generiert (durch ray-plane intersections im fragment shader). Mittlerweile wurde die unglaubliche ;-) Hunt the Alp engine jedoch erweitert - ähnlich wie in [2] wird nun mit Matrizen gerechnet... was das bringt? Man kann mit der "normalen" Pipeline zusätzliche perturbations (in Geometrie!!!) ins WasserFBO/VBO rendern (der aufmerksame Leser hat diese Info bereits im Punkt "projective texturing" erfahren)!!! Wenn man jedoch die Projektion "manuell" mit oben erwähnten ray-plane intersections durchführt, muß man dabei sicherstellen, wirklich jeden "(scaling) Faktor" mit einzubeziehen, der während der transformation pipeline auftritt (screen ratios, near- und farplanes, etc.)...
Anders als in [2] wird jedoch keine explizite "invert matrix" Funktion verwendet - ja, oft ist man halt ein wenig faul - andererseits macht die Not erfinderisch: es folgen Auszüge aus EZG.cpp sowie der Doku von der 2.Abgabe welche das Prinzip genau erklären...


height values are read from a 3D texture (z-coord used for animation -> "Daumenkino")
rendered in a similar way as the terrain (R2VB), only this time not through raycasts, but by projecting only the 4 corner points of the screen-aligned grid (in clipspace) onto the baseplane (xz plane with y=0 in the standard OpenGL right handed coordinate system)
this yields 4 world-space intersection points with a homogeneous component that is in general != 1.0

A matrix is needed that transforms from clipspace to worldspace [doesn't yield a point yet, but an entire line ... so stay tuned - there is a solution ;-) ]


explanation taken from the implementation (see EZG.cpp):

Calculate intersection points with the baseplane
by back-projecting the clipspace corner-points
along the auxiliary camera's parameters (viewMatrix & projectionMatrix).
The auxiliary camera is the projector for the
projected grid



     * In the following:
     *
     * P ... perspective matrix
     * M ... model matrix
     * V ... view matrix
     * MV = V * M ... modelview matrix
     *
     * 
     * V is composed as follows:
     *
     * CamRot * CamTranslate    
     *     and is applied to as: V * Vert_world
     *
     * CamRot is made up of "negative rotation angles" that orient the cam
     *  in other words: the frame has orthonormal axes <CamX, CamY, CamZ>.
     *  If we ignore camera translation and look at rotation only, then
     *  the matrix with these vectors as columns transforms from 
     *  eyespace to worldspace. Since the basisvectors that make up
     *  the matrix are orthogonal to each other, the matrix's inverse is
     *  equal to its transpose [and transforms from worldspace to eyespace]!
     *  To conclude:   CamRot^(-1) == CamRot^T
     *
     *  This means that, based on the Rotation-part of the current viewMat, 
     *  simply use glMultTransposeMatrix(viewMat) to achieve the desired effect.
     *  
     * CamTranslate is glTranslatef(-CamPos)
     *
     * 
     *    mapping from worldspace to clipspace:
     *
     *        Vert_clip = P * MV * Vert_obj
     *                  = P * V * (M * Vert_obj)
     *                  = P * V * Vert_world
     *                  = P * CamRot * CamTranslate * Vert_world
     *
     *
     *    mapping from clipspace to worldspace:
     *
     *        Vert_world = V^(-1) * P^(-1) * Vert_clip
     *                   = CamTranslate^(-1) * CamRot^(-1) * P^(-1) * Vert_clip
     *                   = clip2worldMatrix * Vert_clip
     *
     *
     * with CamTranslate^(-1):
     *   glTranslatef(+CamPos)
     *
     * Since we don't know a priori, which coordinates a point on the baseplane
     * (xz-plane, y=0, y-axis=plane_normal= <0,1,0>) will have from the current view
     * (neither in worldspace, nor in clipspace), we transform each 
     * clipspace-viewfrustum-cornerpoint twice by "clip2worldMatrix", 
     * once with a depth-value
     * (= z-coordinate again z is from clipspace) of z=-1.0, and once with z=1.0 
     *
     * [ nearplane <==> z=-1.0 ]
     *
     * Then, with these 2 points, perform intersection with baseplane ->
     * yields intersection point along this line of sight in homogeneous coordinates
     
By decompositing the rather complex matrix into simpler matrices as described above, the lazy programmer doesn't even need to write a full-fledged (and expensive) matrix-invert function ;-)

We pass the 4 homogeneous intersection points as uniforms to the fragment shader, where the points are interpolated bilinearly (also interpolate the 4th component!) based on the texture coords of the screen-aligned quad that is rendered when the fragment shader is bound. When we now divide the interpolated points xyz components through the interpolated point's 4th component, we end up with the real-space intersection point just as if we had back-projected the according point in the screen-aligned grid (e.g. by a raycast / ray-plane-intersection).

Now we have a "1:1 fixed-pipeline conform mapping" to perform render to texture in separate (even fixed function pipeline is possible, since the actual "pipeline transformation matrices" are used) render passes and then, by projective texture mapping, altering the waterheights (=> dynamic boat wakes) when sampling the wateranimation displacement texture

Auf jeden Fall hat man wieder alle nötigen Positionen in einem FBO, welches man ganz analog zum terrain in ein VBO "kopiert/einliest".

Die Animation wird durch eine 3D Textur bewerkstelligt. Jeder slice entspricht einer "Momentaufnahme" der Animation. Die Texures ist seamless in xyz (xy ist das "örtliche" wrap around und z entspricht dem "zeitlichen" wrap around - Achtung, dies widerspricht der oben eingeführten Konvention laut welcher y die "Höhenkoordinate" sein sollte...). Die Textur besteht aus Grauwerten, die wie ein heightfield interpretiert werden. Die dynamic boat wakes können den Höhenwert zusätzlich beeinflussen.


Die Wellendisplacement Textur wird auch für die surface normals herangezogen. Während das geometric displacment mit nur einem lookup bestimmt wird, wird die surface normal zusätzlich mit lower- und higher frequencies kombiniert (d.h. basierend auf den "displacement lookup" Koordinaten werden diese einmal "gestretched" und einmal "verkeinert") - dieser Zusatzaufwand macht sich bezahlt bei der Lichtberechnung (simpler phong Term, siehe[7]) und den lookups in die "local (screen-space) reflection- & refraction maps"! Auch beim reflection- & refraction map lookup wird basierend auf der surface normal die Texturkoordinate leicht verschoben. Dies ergibt einen realistischeren Eindruck, da das Wasser ansonsten wie ein Spiegel aussehen würde.

Die blending weights für die reflection- & refraction map kommen aus einer Fresnelterm Approximation. In der Reflektion kommt einerseits die "lokale" Reflektion von der "Umgebung" und andererseits die "globale" Reflektion von der cubemap-skybox zum Zug. Im Prinzip ist dies ein bump mapped environment mapping...


Jetzt ist hier außerdem schon die ganze Zeit die Rede von local reflection- & refraction maps. Wie generiert man die eigentlich?

Per fragment culling:
Da das Wasser keine planare Fläche ist, bekommt man bei Verwendung einer cliplane unschöne reflection- refraction leakages an den seams wo sich Wasser und Terrain treffen. Deshalb wird abhängig von der Wellenhöhe im fragment shader auf fragment level "discarded".


wie wird reflection map generiert?
Für die reflection map wird das Gelände gespiegelt um die Wasserebene gezeichnet.


XXXXXXXXXXXXXX

For further information, XXX (fragment level clipping involved in rendering the reflection- and refraction maps, deep watercolor simulation, caustics, ...).

XXXXXXXXXXXXXX


## Partikelsystem	1	Rauch, Feuer, Explosionen...

Das einzige, dafür recht ausgereifte Partikelsystem sind die Dynamic boat wakes die bereits beschrieben wurden. 



## Planare Spiegelungen	2	Die Szene wird gespiegelt mit Stencil Buffer oder in eine Textur gerendert und dann auf eine planare Oberfläche aufgebracht. Dieser Effekt lässt sich beispielsweise für Spiegel verwenden, und lässt sich auch sehr gut mit Wasser kombinieren.

Siehe unter dem Punkt Wasser - es wird "sogar" per fragment culling verwendet, nicht "nur" mit einer clip plane.


Parallax Mapping + Normal Mapping
Auf dem Gelände kann man mittels 'b' parallax mapping einschalten/ausschalten (2 versch. bump scales).
Mittels des Gradienten wird ein lokales Koordinatensystem erstellt, welches gleichzeitig der Tangentspace ist. In einer heightmap wird dann ein Wert gelesen, der die texcoord entlang des view vectors anpaßt (Berechnung im Tangentspace => vorher viewvector in TS transformieren -> Parallaxe berechnen -> Parallaxe zurücktransformieren. An der nun angepaßten texcoord wird nun nochmals die surface normal mittels Gradient berechnet - wieder ergibt sich ein TS-frame und man kann den normal vector aus der normalmap aus dem TS in den WS transformieren und dort für die Lichtberechnung verwenden. Zusätzlich wird die normal aus der normalmap nur verwendet, wenn die Makrostruktur (die "Normale" aus dem Terrain) überhaupt zur Lichtquelle gerichtet ist.

Hitzeflimmern + Depth Of Field
Die Szene wird zuerst in ein FBO gerendert, wobei im Alphakanal die Entfernungen zw. [0;1] kodiert sind. Dieser Wert wird im postprocessing verwendet, um einen stärkeren Blur zu generieren (für DOF sim; "stärkerer" Blur durch lineare "Aufspreizung" des Filterkernels abh. von der Tiefe im Alphakanal) und auch um einen wieder linear davon abhängigen texcoord-shift durchzuführen für einen Hitzeflimmer Effekt. Die Flimmeranimation kommt aus der 3D Wasseranimationstextur ;-)


Zusätzliche Schmankerl:
-) realtime texture synthesis for terrain texturing:

4 layers (2 grass layers, 1 stone layer, 1 sand layer where each texture tiles seamlessly and they all have a rather low resolution compared to the size of complete terrain) are combined based on height and slope
on-the-fly normal calculation.

specular highlights -> gloss map (based on the stone-texture and sand-texture layer, since grass is not specular!) + perturbed normals

detail texture map for additional high frequency detail


<<<<>>>>
NOTE: the engine handles rendering of the complete world ("at least" land+water) and even dynamic modifications to the watersurface (based on later object movement) on the GPU - this leaves the CPU idle for physics, AI, sound and (other) gamelogic! 
<<<<>>>>




Modelle und Animationen:

Unserer Welt besteht aus Objekte die statisch und/oder dynamisch sind.
Alle Objekte sind von uns modelliert, texturiert und animiert worden.
Die Texturgrößen liegen zwischen 512x512 und 1024x1024 Pixeln. 

Der "main character" handsome-Teddy ist mit einem Biped verbunden (Zweibeiner aus 3d-Studio Max). Zusätzlich ist ein Physics Controller auf dem "Biped" angwandt damit die Meshstruktur ohne Verzehrungen animiert werden kann. Eine Laufbewegung haben wir für den Bär erstellt.  

Ein weiterer wichtiger Charakter ist unser Alp. Der Alp, welcher auf  einem alten Piratenschiff lebt und Diamanten verschießt, wurde mit dem Bend-Modifier (3DS-Max) animiert (Flügelschläge).

Weitere Gegner sind die evil-Teddies die alle versuchen uns die Diamanten wegzuschnappen. Diese haben eine andere Textur als der "handsome" Bär.
Animation und Mesh entsprechen dem "handsome" Bär.

Die Diamanten haben mehrere Animationsphasen, die je nach Zustand im Spiel unterschiedlich abgespielt werden können.

Zum Schluß haben wir noch die Ente (Robinson Duck), die "lazy boy trees", Apfelbäume und die Pilze. Die Animationen wurden mit dem Bend-, Twist- und Strechmodifier erstellt. "Lasst uns alle miteinander tanzen" :-)

Tools und Exporter:

Die Modelle und Animation wurden in 3D-Studio und Blender erzeugt. Die Texturen haben wir in Photoshop und Gimp gezeichnet.

Als Fileformat für die Game-Engine verwenden wir das MD2-Fileformat.
Wir haben dafür den Exporter von "herby - www.planetquake.com" zum exportiern aus dem 3D-Studio verwendet. Wir mußten diesen Exporter anpassen da er keine Animationcycles unterstützte.


Physik:

Für die Spiel-Physik haben wir uns für eine bereits bestehende "Lib", nämlich Ageia,  entschieden (Version 2.8.0).

Welche Physik Komponenten verweden wir (grob): "Meshe-Tiles" , "Plane", "Static Bounding Boxes",  "Dynamic Bounding Boxes", "Rays" und "Kinematic Actors"

Das Terrain wird aus einer "heightmap - 16-Bit Integer", um feine Abstufungen der Höhenunterschiede zu erreichen, erzeugt.
		
Ageia implementiert bereits eine Datenstruktur "NxHeightField", die dafür vorgesehen sein sollte, um Terrains aus "heightmaps" zu erzeugen. Das Problem war aber, daß diese für große "heightmaps"  2048x2048 und größere ungeeignet war. Wir haben im Zuge userer Experimente starke Performance einbusen bemerkt. Diese Struktur dürfte speziell für den PhysX-Beschleuniger optimiert sein.

Um trotzdem schnelle "Collisions" und "Response" Berechnugen durchführen zukönnen 
haben wir "Tile-Meshes" aus dem "heightfields" erzeugt. Dies war ein großer Performancegewinn. 
Die Physik-Engine berechnet mehrere Phasen für die "Collision Detection". Unteranderem wird in der ersten Phase "Broadphase - hier wird ein axis-aligned bounding boxes (AABB) Verfahren verwendet" getestet, so wird im optimal Fall nur in wenigen Tiles getestet welches für unser großes Terrain einen Performancegewinn ist.

Wir haben uns dann dafür entschlossen  einzelne "Mesh-Tiles" zu generieren, welche vom Typ NxMesh (Ageia) sind. 
Die einzelnen Mesh-Tiles haben wir dann so in die Datenstruktur von Ageia integriert, daß sie "seamless" zu einander passen. Das war notwendig, da an den Kanten zwischen den einzelnen "tiles" die Physik nicht korrekt funktionieren würde. 

Weiters werden dann die einzelnen Mesh-Tiles "gecooked", das bedeuted das die Mesh-Datenstrukturen in ein Zugriff- und Speicher effizientes Format (für Ageia) konvertiert wird. Diese werden dann in Files abgespeichert um Sie bei bedarf laden zu können.

Für unser Wasser verwenden wir eine Ebene für die Kollisionstests (NxPlane).

Beides, die Kollisionsobjekte die das Terrain und das Wasser beschreiben, bekommen eigene Oberfacheneigenschaften um das Fahrverhalten des Spielers noch interresanter zu gestalten.

Das Hovercraft des Spielers besitzt als Kollisonsobjekt einen Quarder. Um nun ein halbwegs realistisches Fahrverhalten zu berechnen gingen wir so vor:   

Wir schießen von jedem Eckpunkt der Bodenfläches des Quaders einen Strahl um einen Kollisionstest mit der Welt bzw. dem Wasser zu berechnen. Weiters besitzt jeder Strahl eine fixe Länge somit wird dann bei einem Test mit der Umgeben eine entgegenwirkende Kraft berechnet die quadratisch, abhängig von der Kollisionsdistanz, abnimmt. So hat man das Gefühl das der Spieler über dem Boden schweben bleibt. Durch diese Verfahren würde der Spieler noch nicht stabil über dem Boden schweben. Deshalb berechnen wir Torsionskräfte entsprechend der Rotationsachsen (X, Z) damit das Spielerobjekt stabilisiert bzw. gedämpft wird. Zum Schuß wird je nach Tastatureingabe ein Kraft einsprechend der Eingaberichtung  erzeugt. Bei keiner eingabe verzögert das Fahrzeug entsprechend der letzten Fahrtrichtungeingabe.

Die statischen Objekte werden entsprechend der Surface-Normal ausgerichtet (Gradientenbestimmung).

Der Alp ist bei uns ein kinematikes Objekt welches in die physikalischen bestimmenden Eigenschaften der Welt nicht eingebunden wird (z.B Gravitation).

Gamelogik:

In unserem Spiel werden vom Alp (Feid) Diamanten weggeschossen, dieser fliegt mit seinem Piratenschiff über die Insel. Der Spieler (handsome Teddy) und die feindlichen AI-Gegner (evil Teddies) müssen jetzt so schnell wie möglich eine gewisse Anzahl von Punkten erreichen um das Spiel zu gewinnen. Der Wert eines Diamanten erhöht sich je nach dem wie viele der Spieler an einem Diamanten interressiert sind. Eine Wertänderung macht sich dadurch bemerkbar, daß der Diamant sein Form ändert. Wenn einer der Spieler die maximale Punkteanzahl erreicht hat, hat er das Spiel gewonnen. 

Artificial Itelligence:

Die AI nutzt genauso wie die Physik die Ageia Engine. Es gibt zwei direkt Ai gesteuerte Objekttypen im Spiel: die gegnerischen Hovercrafts und den Alp. Der Alp wählt zufällig einen Punkt auf der Welt aus und steuert diesen an. Wenn er diesen Punkt in einer gewissen Umgebung erreicht hat (in einer Epsilonumgebung die als Ziel anerkannt wird) oder ein Timer abgelaufen ist, wird ein neuer Punkt ausgewählt und angesteuert. 

Der Alp verschießt immer wieder eine bestimmte Anzahl an Diamanten. Der zeitliche Abstand ist zufällig auf ein Interval verteilt. Die Diamanten werden je zur Hälfte nach links und rechts abgeschossen. Die genaue Kraft die auf die Diamanten wirkt, wird wieder zufällig auf einem Interval bestimmt. 

Der Alp bewegt sich horizontal so, dass er versucht auf einer bestimmten Y - Distanz zur Welt ausrichtet ist. Dabei bremst er seine Vorwärtsbewegung ab, wenn diese Distanz zu kleine wird. Auf diese Weise verhindert er, dass sich das Objekt in das Gelände bohrt wenn seine Steiggeschwindigkeit gering ist. Der Verzögerungfaktor hängt von der Distanz zum Boden ab.

Die Ai Gegener kennen grundsatzlich zwei Arten von Zielen. Erstens, wenn die Diamanten in der Welt existieren, dann wählen sie den mit der geringsten Distanz aus. Zweitens, der Alp ist ihr Ziel. Wenn sich die AI auf ihr Ziel ausrichtet, bremst sie, wenn der Winkel zwischen eigener Ausrichtung und dem Ziel zu groß wird. Auf diese Weise konnten wir die Zielsicherheit der Ai (gegenüber der ürsprunglichen gleichbleibenden Geschwindigkeit) stark erhöhen. 

Weiters versucht die Ai anderen Objekten (Pflanzen, Spieler andere Ai's) im Spiel auszuweichen. Die Ai erkennt Objekte die in ihrem Weg liegen durch collision boxes (Trigger) welche auf der AI montiert ist.

Ein weiteres Ai ähnliches Objekt ist der Diamant. Er hat ebenfalls eine collision box und einen Trigger um sein Objekt und registriert damit wenn es eingesammelt wird. Ein Fahrzeug, dass einen Diamant eingesammelt hat bekommt die Punkte dafür gutgeschrieben und der Diamant gibt sein graphisches Model an eine Verwaltungsstelle zurück, damit es wiederverwendet werden kann. Dadurch werden weniger VBOs erzeugt werden. 

Weiters bekommen Diamanten mitgeteilt, wenn eine Ai sie anvisiert. Dadurch entsteht ein Multiplier. Dieser ermöglicht es den Wert des Diamanten zu erhöhen. Um graphisch darzustellen, wie viel ein Object wert ist, haben wir beschlossen dessen graphische Darstelung zu ändern. Dafür haben die Diamanten mehrere Zustände, bei einem Zustandswechsel ändert dieser seinen Wert. Bei einem Zustandswechsel wird eine Animation abgespielt. Diese stellt das morphen von einer Objektform in die Andere dar. Falls zu viele Änderungen passieren, werden Teile der Animation übersprungen. Diamanten verschwinden, wenn sie nicht innerhalb einer bestimmten Zeit eingesammelt werden.

Audio:

Für die Audioeffekte wurde die FMOD Bibliothek verwendet. Einerseits wird ständig eine Hintergrundmusik abgepielt, wobei sich mehrere Lieder abwechseln. Weiters werden eventgesteuert Sounds abgespielt. Die Laustärke dieser Sounds ist entfernungsabhängig. Die Soundeffekte selbst wurden mit Hilfe von Audacity erstellt.

		



Quellen:

[1] Yotam Livny, Neta Sokolovsky, Tal Grinshpoun, Jihad El-Sana "Persistent Grid Mapping: A
GPU-based Framework for Interactive Terrain Rendering", Department of Computer Science,
Ben-Gurion University of the Negev, Beer-Sheva, Israel

[2] Claes Johanson, "Real-time water rendering, Introducing the projected grid concept",
Master of Science thesis March 2004, Lund University

[3a] Randi Rost, "OpenGL Shading Language, 2nd Edition", better know as the "Orange book", p.341

[3b] http://www.cs.cmu.edu/afs/cs/academic/class/15462/web.06s/asst/project3/shadowmap/index.html

[4] Kevin Hawkins, Dave Astle, "OpenGL game programming", Prima Tech, 2001

[5 - 6] Im Text nicht referenzierte Eingebungen aus dem "Nirvana" ;-)

[7] Hearn, Baker, "Computer Graphics with OpenGL, 3rd Edition"

[8] Michael Wimmer, et. al. Folien zur VO Echtzeitgraphik, www.cg.tuwien.ac.at/courses/...