30.06.2015 Aufrufe

Crazy Simulator - Prof. Dr. Henrik Tramberend - Beuth Hochschule ...

Crazy Simulator - Prof. Dr. Henrik Tramberend - Beuth Hochschule ...

Crazy Simulator - Prof. Dr. Henrik Tramberend - Beuth Hochschule ...

MEHR ANZEIGEN
WENIGER ANZEIGEN

Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.

YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.

BEUTH-HOCHSCHULE FÜR TECHNIK BERLIN<br />

<strong>Crazy</strong> <strong>Simulator</strong><br />

Graphics and Effects & Physical Simulation<br />

s750026 Marc Roßbach; s732633 Birgitt Grundmann<br />

13.07.2009


Inhaltsverzeichnis<br />

1 Spielidee .......................................................................................................................................... 3<br />

2 Softwarearchitektur ........................................................................................................................ 5<br />

3 Synchronisation der Grafik- und Physikobjekte .............................................................................. 6<br />

3.1 Feste Körper ............................................................................................................................ 6<br />

3.2 Elastische Körper ..................................................................................................................... 7<br />

4 Spielbausteine ................................................................................................................................. 8<br />

4.1 Halbröhren .............................................................................................................................. 8<br />

4.2 Jump-Pad ................................................................................................................................. 8<br />

4.3 Bretter-Kette ........................................................................................................................... 9<br />

5 Transformationstool ...................................................................................................................... 10<br />

6 Grafische Benutzeroberfläche ....................................................................................................... 11<br />

7 Shader ............................................................................................................................................ 11<br />

7.1 Rostiges Metall ...................................................................................................................... 11<br />

7.2 Gras und Felsen ..................................................................................................................... 12<br />

7.3 Spiegelndes Wasser ............................................................................................................... 14<br />

7.4 Weitere Shader ...................................................................................................................... 14<br />

8 Technologien und Tools ................................................................................................................ 15<br />

9 Probleme ....................................................................................................................................... 15


1 Spielidee<br />

Bei <strong>Crazy</strong>-<strong>Simulator</strong> handelt es sich um ein Strategiespiel. Sinn ist es, möglichst viele Bälle von der<br />

Ausgangsposition zum Ziel zu leiten. Dazu kann der Spieler zwischen vier unterschiedlichen<br />

Hilfsmitteln wählen.<br />

Die Spielumgebung ist eine Hügellandschaft. In der Ferne ist das Ziel in Form eines Brunnens zu<br />

erkennen (siehe Abbildung 1).<br />

Abbildung 1 <strong>Crazy</strong>-<strong>Simulator</strong> Ausgangssituation<br />

Wird nun auf den Start- Button geklickt, fällt aus dem Rohr am rechten Bildschirmrand ein Ball. Bei<br />

jedem weiteren Klick, fallen weitere Bälle in das Spielfeld (siehe Abbildung 2).<br />

Abbildung 2 Start-Button lässt Bälle in das Spielfeld fallen.


Über die Programmoberfläche hat man die Möglichkeit verschiedene Bauteile in das Spielfeld zu<br />

setzen, um den Ball in sein Ziel zu steuern (siehe Abbildung 3). Dabei spielt es keine Rolle wie viele<br />

Teile einer Sorte genutzt werden.<br />

Abbildung 3 Die vier Bauteile<br />

Gelingt es dem Spieler einen Ball im Brunnen (siehe Abbildung 4) zu versenken, erhöht sich der<br />

Punktezähler am linken unteren Bildschirmrand.<br />

Abbildung 4 Das Ziel<br />

Der Spieler kann sich mit den Tasten WSAD durch die Welt bewegen. Mit der linken Maustaste<br />

können Gegenstände angeklickt und transformiert werden. Die rechte Maustaste dient dem<br />

Kameraschwenken.


2 Softwarearchitektur<br />

Wie in Abbildung 5 zu sehen ist, besteht die Anwendung aus fünf Hauptkomponenten.<br />

1. InputState und InputManager verwalten die Eingaben von Maus und Tastatur.<br />

2. ComponentManager verwaltet alle Gegenstände des Spiels.<br />

3. PhysicsManager ist für die Physiksimulation zuständig (Bullet-Engine).<br />

4. GraphicsManager rendert die 3D-Szene (Horde3D-Engine).<br />

5. GuiManager verwaltet und rendert die GUI-Elemente (Horde3D-Engine).<br />

Abbildung 5 <strong>Crazy</strong>-<strong>Simulator</strong>: Softwarearchitektur (Klassendiagramm)<br />

Abgesehen vom InputManager ruft das Hauptprogramm (Main) in einer Schleife kontinuierlich die<br />

mainloop(…)-Methoden der Manager-Objekte auf (siehe Auflistung 1). Dabei wird jedem<br />

Manager die aktuelle Framerate übergeben, um beispielweise die Geschwindigkeit der<br />

Physiksimulation an die Framerate anpassen zu können. Ohne diese Anpassung würde die Simulation<br />

auf unterschiedlichen Systemen je nach Leistung der Hardware entweder langsamer oder schneller<br />

als gewünscht laufen.


int main(int argc, char** argv)<br />

{<br />

…<br />

// Game loop<br />

while( running )<br />

{<br />

…<br />

// Calc FPS<br />

++frames;<br />

if( frames >= 3 )<br />

{<br />

double t = glfwGetTime();<br />

fps = frames / (float)(t - t0);<br />

frames = 0;<br />

t0 = t;<br />

}<br />

componentManager->mainloop(fps);<br />

physicsManager->mainloop(fps);<br />

graphicsManager->mainloop(fps);<br />

guiManager->mainloop(fps);<br />

}<br />

glfwSwapBuffers();<br />

}<br />

// Quit<br />

glfwTerminate();<br />

return 0;<br />

Auflistung 1 Game-Loop (Main.cpp)<br />

3 Synchronisation der Grafik- und Physikobjekte<br />

Man unterscheidet bei der Bullet-Physikengine zwischen festen und elastischen Körpern. Für beide<br />

Körperarten werden unterschiedliche Synchronisationsverfahren verwendet.<br />

3.1 Feste Körper<br />

Starre Körper sind relativ einfach zu synchronisieren da im Grunde nur die Transformationsmatrix<br />

von dem Physikobjekt auf das Grafikobjekt übertragen werden muss. Bullet erlaubt es, eine Klasse zu<br />

definieren, die von btMotionState abgeleitet wird und später Synchronisation übernimmt. Dabei<br />

müssen folgende virtuelle Methoden überschrieben werden:<br />

virtual void getWorldTransform(btTransform &worldTrans) const;<br />

virtual void setWorldTransform(const btTransform &worldTrans);<br />

Es muss zudem dafür gesorgt werden, dass ein MotionState-Objekt eine Referenz auf das<br />

entsprechende Grafikobjekt besitzt.<br />

Abbildung 6 Synchronisationsarchitektur für feste Körper


Für die Simulation einer Kugel wird zunächst ein Objekt vom Typ btRigidBody erzeugt und mit<br />

einer Masse und einem Kollisionsobjekt (btSphereShape) versehen. Für die grafische<br />

Repräsentation wird ein MotionState-Objekt erzeugt was auf ein Horde-Objekt zeigt. Das<br />

MotionsState-Objekt wird ebenfalls an das RigidBody-Objekt gebunden (siehe Abbildung 6). Immer<br />

wenn sich im Laufe der Physiksimulation die Transformation des RigidBody ändert, wird die Methode<br />

setWorldTransform() (siehe Auflistung 2) des MotionState aufgerufen und die neue Transformation<br />

übergeben.<br />

void MotionState::setWorldTransform(const btTransform &worldTrans)<br />

{<br />

if(NULL == _hordeObj) return;<br />

float m[16];<br />

//Rotation<br />

btMatrix3x3 rm = worldTrans.getBasis();<br />

btVector3 col;<br />

col = rm.getColumn(0); m[0]=col.getX(); m[1]=col.getY(); m[2]=col.getZ();m[3]=0;<br />

col = rm.getColumn(1); m[4]=col.getX(); m[5]=col.getY(); m[6]=col.getZ();m[7]=0;<br />

col = rm.getColumn(2); m[8]=col.getX(); m[9]=col.getY(); m[10]=col.getZ();m[11]=0;<br />

//Translation<br />

btVector3 pos = worldTrans.getOrigin();<br />

m[12]=pos.x(); m[13]=pos.y(); m[14]=pos.z(); m[15]=1;<br />

}<br />

Horde3D::setNodeTransformMatrix(_hordeObj, m);<br />

3.2 Elastische Körper<br />

Auflistung 2 Synchronisationsmethode für feste Körper<br />

Da bei elastischen Körpern die Position jedes einzelnen Vertex synchronisiert werden muss, können<br />

hierbei keine MotionState-Objekte eingesetzt werden. In Bullet sind alle elastischen Körper vom Typ<br />

btSoftBody. Um aus einem Horde-Objekt einen elastischen Körper zu erzeugen, wird die<br />

Geometrie in ihre <strong>Dr</strong>eiecke und Punkte zerlegt. Mit der Funktion btSoftBodyHelpers::<br />

CreateFromTriMesh(…) kann aus den <strong>Dr</strong>eiecken ein Soft-Body erzeugt werden. Für die spätere<br />

Synchronisation wird jeder Soft-Body mit einem Pointer (UserPointer) auf das Grafikobjekt<br />

versehen. Im Laufe der Simulation werden alle Soft-Bodies über eine Schleife durchiteriert und<br />

Vertex für Vertex mit dem Grafikobjekt synchronisiert (siehe Auflistung 3).<br />

void PhysicsManager::mainloop(float fps)<br />

{<br />

_dynamicsWorld->stepSimulation(1.f/fps,0);<br />

//SoftBodies<br />

btSoftBodyArray& sbs=_dynamicsWorld->getSoftBodyArray();<br />

for(int ib=0;ibm_nodes[i].m_x;<br />

}<br />

}<br />

Component* comp = ((UserPointer*)psb->getUserPointer())->_component;<br />

if(comp)<br />

{<br />

comp->updateVertexPos(psb->getUserPointer(), i, v[0], v[1], v[2]);<br />

}<br />

…<br />

Auflistung 3 Synchronisation der Soft-Bodies in der Hauptschleife der Physiksimulation


4 Spielbausteine<br />

Um die unterschiedlichen Möglichkeiten der Bullet-Engine in das Spiel zu integrieren, wurden vier<br />

unterschiedliche Spielbausteine implementiert.<br />

4.1 Halbröhren<br />

Die einfachsten Bausteine sind die Halbröhren in denen die Bälle rollen können. Es handelt sich dabei<br />

um Rigid-Bodies mit konkaven Kollisionsobjekten (btGImpactMeshShape). Um diese<br />

Kollisionsobjekte zu erzeugen, musste Geometrie ebenfalls in ein Array aus <strong>Dr</strong>eiecke zerlegt werden<br />

(siehe Abbildung 7). Die Masse der Halbröhren wurde auf null gesetzt, wodurch sie in der<br />

Physikengine zu statischen Objekten werden.<br />

4.2 Jump-Pad<br />

Abbildung 7 Geometrie der Halbröhren<br />

Das Jump-Pad besteht aus drei Teilen (siehe Abbildung 8). Der äußere Ring ist statisch und kann<br />

angeklickt und verschoben werden. Zwischen dem Ring ist ein Soft-Body (Pad) aufgespannt bei dem<br />

die äußersten Vertices statisch sind damit er nicht aus dem Ring fällt. Wenn ein Ball auf das Jump-Pad<br />

fällt, soll auf ihn ein Impuls ausgeübt werden, damit er wieder wegspringt. In der aktuellen Version<br />

von Bullet können allerding noch keine Kollisionen ausgelesen werden, an denen Soft-Bodies<br />

beteiligt sind. Darum befindet sich in der Mitte ein unsichtbarer statischer Körper der für die<br />

Kollisionserkennung mit dem Ball verwendet wird. Damit der Ball den Trigger auch erreichen kann,<br />

wurde in Bullet die Kollision von festen mit elastischen Körpern deaktiviert.<br />

Abbildung 8 Jump-Pad: Schwarz=statisch; Rot=elastisch; Blau=statischer Trigger


Auflistung 4 zeigt wie Kollisionspaare aus Bullet ausgelesen werden können. Über die UserPointer<br />

werden die Callback-Methoden der zugehören Bausteine (Component) aufgerufen und die<br />

UserPointer der an der Kollision beteiligten Bausteine übergeben.<br />

…<br />

//Kollisionen an Komponenten melden<br />

int numManifolds = _dynamicsWorld->getDispatcher()->getNumManifolds();<br />

for (int i=0;igetDispatcher()->getManifoldByIndexInternal(i);<br />

btCollisionObject* obA = static_cast(contactManifold->getBody0());<br />

btCollisionObject* obB = static_cast(contactManifold->getBody1());<br />

int numContacts = contactManifold->getNumContacts();<br />

if(obA->getUserPointer() && obB->getUserPointer())<br />

{<br />

UserPointer* A = (UserPointer*)obA->getUserPointer();<br />

UserPointer* B = (UserPointer*)obB->getUserPointer();<br />

}<br />

…<br />

}<br />

((Component*)A->_component)->collisionCallback(A,B);<br />

((Component*)B->_component)->collisionCallback(B,A);<br />

Auflistung 4 Collision-Callback für feste Körper in der Hauptschleife der Physiksimulation<br />

Weil das Jump-Pad aus mehreren Teilen besteht verfügt es auch über mehrere UserPointer die alle<br />

auf denselben Baustein zeigen. Damit später trotzdem noch zwischen den einzelnen Teilen<br />

unterschieden werden kann, kann jeder UserPointer mit einer Zusatzinformation versehen werden.<br />

In diesem Fall wurde dem Trigger der Integerwert 2 zugeordnet (Ring=0, Pad=1). Auflistung 5 zeigt<br />

die Callbackmethode des Jump-Pad. In der Bedingung wird geprüft, ob das kollidierte Objekt vom Typ<br />

CompBall ist, und ob es den Trigger berührt hat. Wenn das der Fall ist, wird auf den Ball mit<br />

applyImpuse(…) ein Impuls ausgeübt. Damit sich auch das Pad entsprechend bewegt, wird auf<br />

dieses mit addForce(…) ebenfalls eine Kraft in die gleiche Richtung ausgeübt.<br />

void CompJumpPad::collisionCallback(void* ownUserPointer, void* otherUserPointer)<br />

{<br />

UserPointer* ownUP = (UserPointer*)ownUserPointer;<br />

Component* ownComp = ownUP->_component;<br />

UserPointer* otherUP = (UserPointer*)otherUserPointer;<br />

Component* otherComp = otherUP->_component;<br />

if(typeid(*otherComp) == typeid(CompBall) && ownUP->_data == 2) //Trigger hat die 2<br />

{<br />

…<br />

Vec3f dir = Vec3f(0,calcImpulsePower(800, _fps),0);<br />

dir = RotM*dir;<br />

}<br />

}<br />

otherComp->applyImpulse(dir[0],dir[1],dir[2]);<br />

_softBody->addForce(btVector3(dir[0],dir[1],dir[2]));<br />

Auflistung 5 Collision-Callback-Methode des Jump-Pad<br />

4.3 Bretter-Kette<br />

Diese Komponente besteht aus insgesamt fünf Einzelteilen (siehe Abbildung 9). Das oberste Brett ist<br />

Statisch und dient dazu, die dynamischen Teile auf Position zu halten. Die beiden Seile sind Soft-<br />

Bodies, die über die Funktion btSoftBodyHelpers::CreateRope(…) erzeugt wurden. Die<br />

obersten Vertices der Seile sind ebenfalls statisch. Die beiden dynamischen Bretter wurden mit<br />

appendAnchor(…) mit einzelne Vertices der Seile verbunden.


Abbildung 9 Bretter-Kette: Schwarz=statisch;<br />

Rot=elastisch; Grün=dynamisch<br />

5 Transformationstool<br />

Um die statischen Objekte in der Welt positionieren zu können, kann das Transformationstool<br />

verwendet werden. Neben Translation ist damit auch eine Rotation um drei Achsen möglich.<br />

Realisiert wurde die Rotationsfunktion mittels Rotationsebenen (siehe Abbildung 10).<br />

Abbildung 10 Transformationstool: Rotationsfunktion<br />

Wenn mit der Maus geklickt wird, wird zunächst über die Pickingfunktion von Horde3D ermittelt,<br />

welchen Rotationsring der Benutzer ausgewählt hat. Jeder Ring liegt in einer eigenen Ebene und das<br />

Programm berechnet den Schnittpunkt des Pickingstrahls mit dieser Ebene. Der Vektor v wird<br />

zwischen dem Mittelpunkt des Transformationstools und dem geklickten Schnittpunkt aufgespannt.<br />

Wird der Cursor nun bei gedrückter Maustaste bewegt, wird ein weiterer Schnittpunkt mit der<br />

Rotationsebene berechnet und es kann der Vektor w bestimmt werden. Über das Skalarprodukt<br />

beider Vektoren kann nun der Rotationswinkel berechnet werden. Die Rotationsrichtung ergibt sich<br />

aus dem Kreuzprodukt beider Vektoren.


6 Grafische Benutzeroberfläche<br />

Horde3D stellt kein eigenes GUI-Framework zur Verfügung. Aus diesem Grund wurde auf Grundlage<br />

der Horde-Overlays ein eigenes kleines Framework entwickelt.<br />

Abbildung 11 GUI des <strong>Crazy</strong>-<strong>Simulator</strong><br />

GUI-Elemente wie Hintergründe und Buttons bestehen im Wesentlichen aus PNG-Grafiken mit<br />

Alphakanal um auch runde und halbtransparente Elemente darstellen zu können. Um auch auf<br />

Mausevents reagieren zu können, wird in jedem Frame die Position des Cursors ausgelesen und<br />

berechnet, ob er sich über einem GUI-Element befindet. Im Falle eines Klicks wird von jedem GUI-<br />

Element über dem sich der Cursor befindet, die Methode mouseClickEvent() aufgerufen.<br />

Abbildung 11 zeigt die GUI des <strong>Crazy</strong> <strong>Simulator</strong>. Abgesehen von der Punkteanzeige, sind alle<br />

Elemente vom Typ GuiElement. Für die Punkteanzeige wurde die Funktion Horde3DUtils::<br />

showText(…) verwendet.<br />

7 Shader<br />

Für die Umgebung und die Spielkomponenten kommen die unterschiedlichsten Shader zum Einsatz.<br />

7.1 Rostiges Metall<br />

Für die Halbröhren wurde ein Shader entwickelt, der die Eigenschaften von rostigem Metall<br />

nachbildet. Dabei kommen vier Texturen zum Einsatz (siehe Abbildung 12). Über eine<br />

Graustufentextur wird bestimmt, ob entweder Metall (dunkle Bereiche) oder Rost (helle Bereiche) zu<br />

sehen ist. Um zusätzlich eine Oberflächenstruktur zu erzeugen, werden die Oberflächennormalen aus<br />

einer Normalmap ausgelesen, die aus der Rosttextur erzeugt wurde.<br />

Abbildung 12 Sampler des Shaders für rostiges Metall


…<br />

#ifdef _F06_RustyMetal<br />

uniform sampler2D rustMap;<br />

uniform sampler2D metalMap;<br />

uniform sampler2D rustyMetalMap;<br />

uniform sampler2D rustNormalMap;<br />

vec3 calcRustyMetal ( vec3 pos, vec3 normal, vec3 newCoords)<br />

{<br />

vec3 light = normalize( lightPos.xyz - pos );<br />

vec3 eye = normalize( viewer - pos );<br />

vec3 rust = texture2D( rustMap, newCoords.st ).rgb;<br />

vec3 metal = texture2D( metalMap, newCoords.st ).rgb;<br />

vec3 map = texture2D( rustyMetalMap, newCoords.st ).rgb;<br />

vec3 rustnormal = texture2D( rustNormalMap, newCoords.st ).rgb;<br />

//Metal<br />

vec3 bumpNormal = normalize(normal + (rustnormal-vec3(0.5,0.5,0.0))*0.3);<br />

float diffuse = max( dot(light , bumpNormal ), 0.0);<br />

float specular = max( dot ( reflect (-light , bumpNormal ), eye ), 0.0);<br />

vec3 metalPart = (1.0-map) * (1.0 * specular * metal + 0.7 * diffuse * metal);<br />

//Rust<br />

bumpNormal = normalize((rustnormal-vec3(0.5,0.5,0.0)));<br />

diffuse = max( dot(light , bumpNormal ), 0.0);<br />

specular = max( dot ( reflect (-light , bumpNormal ), eye ), 0.0);<br />

vec3 rustPart = map * (0.2 * specular * rust + 0.8 * diffuse * rust);<br />

}<br />

#endif<br />

…<br />

return lightColor * (metalPart + rustPart);<br />

Auflistung 6 Fragment-Shader für rostiges Metall<br />

Damit die Stellen mit dem glatten Metall stärker glänzen als die Roststellen, besitzt der Rost einen<br />

viel geringeren spekularen- und dafür einen höheren diffusen Reflexionsanteil als die Metaltextur<br />

(siehe Auflistung 6).<br />

7.2 Gras und Felsen<br />

Der Shader für die Landschaft ist ähnlich dem Vorherigen aufgebaut. Der einzige Unterschied ist, dass<br />

für die Verteilung keine Graustufentextur verwendet wird (siehe Abbildung 13).<br />

Abbildung 13 Sampler des Shaders für Gras und Felsen<br />

Ob die Gras- oder die Felsentextur verwendet wird, hängt davon ab, wie groß der Y-Wert des<br />

jeweiligen Punktes ist. Abbildung 14 illustriert die generierte Verteilungsmap für diesen Shader ohne<br />

Texturen.


Abbildung 14 Verteilung von Gras und Felsen<br />

Damit an der Grenze von Gras zu Felsen keine harte Kante entsteht, werden im Grenzbereich beide<br />

Texturen mit der smoothstep()-Funktion ineinander übergeblendet (siehe Auflistung 7).<br />

…<br />

#ifdef _F07_Ground<br />

uniform sampler2D grasMap;<br />

uniform sampler2D rockMap;<br />

uniform sampler2D grasNormalMap;<br />

uniform sampler2D rockNormalMap;<br />

vec3 calcGround ( vec3 pos, vec3 normal, vec3 newCoords)<br />

{<br />

…<br />

vec3 gras = texture2D( grasMap, newCoords.st ).rgb;<br />

vec3 rock = texture2D( rockMap, newCoords.st ).rgb;<br />

vec3 grasnormal = texture2D( grasNormalMap, newCoords.st ).rgb;<br />

vec3 rocknormal = texture2D( rockNormalMap, newCoords.st ).rgb;<br />

float map = pos.y/24; //Abhängig von maximaler Höhe der Berge<br />

float border = 0.4; //Grenzgebiet<br />

float borderSize = 0.3; //Breite des Grenzstreifen<br />

vec3 bumpNormal = normalize(normal - (grasnormal-vec3(0.5,0.5,0.0))*0.2);<br />

float diffuse = max( dot(light , bumpNormal ), 0.0);<br />

float specular = max( dot ( reflect (-light , bumpNormal ), eye ), 0.0);<br />

vec3 grasPart = 0.2 * specular * gras + diffuse * gras;<br />

bumpNormal = normalize(normal + (rocknormal-vec3(0.5,0.5,0.0))*0.9);<br />

diffuse = max( dot(light , bumpNormal ), 0.0);<br />

specular = max( dot ( reflect (-light , bumpNormal ), eye ), 0.0);<br />

vec3 rockPart = 0.1 * specular * rock + diffuse * rock;<br />

if(map(border+borderSize/2))<br />

return lightColor * rockPart;<br />

}<br />

#endif<br />

…<br />

float fade = smoothstep(1.0,0.0,(map-border+borderSize/2)/borderSize);<br />

return lightColor * (fade*grasPart+(1.0-fade)*rockPart);<br />

Auflistung 7 Fragment-Shader für Gras und Felsen


7.3 Spiegelndes Wasser<br />

Als ein optisches Highlight wurde noch der Wassershader von <strong>Prof</strong>. <strong>Dr</strong>. <strong>Tramberend</strong> eingebaut (siehe<br />

Abbildung 15). Problematisch hat sich dabei das Rendern in eine Textur erwiesen, da die Horde-<br />

Engine hier vermutlich noch einige Fehler aufweist. Das Problem konnte jedoch durch den Einsatz<br />

direkter OpenGL-Befehlen gelöst werden (siehe Auflistung 8).<br />

Abbildung 15 Spiegelndes Wasser<br />

…<br />

ResHandle _mirrorMat = Horde3D::findResource(ResourceTypes::Material, "models/plane/mirror.material.xml");<br />

ResHandle _mirroredImage = Horde3D::createTexture2D("mirrortex",<br />

ResourceFlags::NoTexCompression |<br />

ResourceFlags::NoTexMipmaps,<br />

mapWidth, mapHeight, false);<br />

unsigned char* _data = new unsigned char[mapWidth*mapHeight*4];<br />

…<br />

glReadBuffer(GL_BACK);<br />

glReadPixels(0, 0, mapWidth, mapHeight, GL_RGBA, GL_UNSIGNED_BYTE, _data);<br />

Horde3D::updateResourceData(_mirroredImage, TextureResParams::PixelData, _data, mapWidth*mapHeight*4);<br />

Horde3D::setMaterialSampler(_mirrorMat, "mirrorMap", _mirroredImage);<br />

…<br />

Auflistung 8 Horde3D Render-To-Texture Workaround<br />

Der Trick besteht darin, mit der Hordekamera nicht direkt in die Textur sondern zunächst ganz<br />

normal in den Framebuffer zu rendern. Anschließend kann mit glReadPixels(…) der<br />

Framebuffer wieder ausgelesen und in die Textur geschrieben werden. Wenn das geschehen ist, wird<br />

die Szene noch einmal für den Betrachter aus der richtigen Position gerendert.<br />

7.4 Weitere Shader<br />

Für die meisten anderen Oberflächen wurde das Parallax-Mapping eingesetzt, was bereits in dem<br />

Model-Shader von Horde enthalten ist. Lediglich die verwendeten Texturen und ihre Normalmaps<br />

wurden entsprechend ausgetauscht.


8 Technologien und Tools<br />

Die entwickelte Anwendung nutzt die Horde3D-Grafikengine 1 für das OpenGL-Rendering und die<br />

Bullet-Physikengine 2 . Als Mathematikbibliothek für Matrizen und Vektorrechnungen wurde die<br />

Graphics Math Template Library (GMTL 3 ) eingesetzt.<br />

Als Entwicklungsumgebung ist Visual Studio 2008 (C++) 4 zum Einsatz gekommen. Für die Erstellung<br />

der Landschaft wurde mit Terragen 5 eine Highmap erzeugt, die dann mit 3ds Max 2009 6 in eine<br />

Geometrie konvertiert wurde. 3ds Max ist auch für die Modellierung sämtlicher anderen 3D-Objekte<br />

verwendet worden. Der Export aus 3ds Max erfolgte über das Collada-Format was über das<br />

ColladaMax-Plugin 7 generiert werden konnte. Anschließend mussten die Collada-Dateien mit der<br />

ColladaConv.exe der Horde-Engine in ein Horde-Format konvertiert werden.<br />

Die für die Shaderprogrammierung benötigten Normalmaps wurden mit dem Tool <strong>Crazy</strong>Bump 8<br />

erzeugt. Das Tool ist in der Lage, auch ohne Tiefeninformationen aus den Helligkeitswerten einer<br />

Textur realistische Normalmaps zu erzeugen.<br />

Für die Bearbeitung der Skybox (DDS-Format) wurde das Tool CubeMapGen von AMD verwendet.<br />

9 Probleme<br />

1. Statische Objekte in Bullet können nachträglich nicht mehr korrekt transformiert werden.<br />

Lösung: Objekt aus Physiksimulation entfernen Objekt transformieren Objekt wieder in<br />

Simulation einfügen.<br />

2. SoftBodies in Bullet kollidieren nicht mit konkaven Objekten.<br />

3. Keine Möglichkeit für ein Collision-Callback von SoftBodies gefunden.<br />

4. Stärke des Impulses des Jump-Pad ist abhängig von der Framerate. Je höher die Framerate,<br />

desto schwächer der Impuls. Die Beziehung ist jedoch nicht wirklich umgekehrt proportional.<br />

Bisherige Näherung für Impulsstärke: power = power/(100-sqrt(fps))<br />

5. Clippingplanes funktionieren nicht auf Nvidia-Karten (getestet auf GTX280 und GTX260 unter<br />

Vista x64). Auch Far- und Near Clipping Plane der Kamera funktionieren nicht. Auf ATI-Karten<br />

(getestet auf HD4870 unter Vista x64) arbeitet Clipping fehlerfrei.<br />

6. Übergabe von Uniform-Variablen an Shader funktioniert nicht.<br />

Lösung: Shadercontext explizit in der Renderpipeline aufrufen.<br />

7. Render-To-Texture funktioniert nicht über Horde-API.<br />

Lösung: siehe Shader: Spiegelndes Wasser<br />

8. Die aktuelle Version der Horde-Engine unterstützt keine dynamische Geometrie.<br />

Lösung: Die Vertices elastischer Körper wurden daher in Horde von vielen kleinen Kugeln<br />

repräsentiert (siehe Jump-Pad).<br />

1 http://www.horde3d.org/<br />

2 http://www.bulletphysics.com/<br />

3 http://ggt.sourceforge.net/<br />

4 http://www.microsoft.com/germany/express/download/default.aspx<br />

5 http://www.planetside.co.uk/terragen/productmain.shtml<br />

6 http://www.autodesk.de/adsk/servlet/index?siteID=403786&id=12340933<br />

7 https://collada.org/mediawiki/index.php/ColladaMax_NextGen<br />

8 http://www.crazybump.com/

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!