Crazy Simulator - Prof. Dr. Henrik Tramberend - Beuth Hochschule ...
Crazy Simulator - Prof. Dr. Henrik Tramberend - Beuth Hochschule ...
Crazy Simulator - Prof. Dr. Henrik Tramberend - Beuth Hochschule ...
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/