schnelle mehrfache Animationen



  • Shader sind ein sehr sehr interessantes aber auch hoch komplexes Thema.
    Ich würde es für dein Vorhaben nicht benutzen. Es ist mit Kanonen auf Spatzen geschossen. Und Animationen gab es auch schon bevor es VertexShader gab !
    Lern erstmal noch ein bisschen den richtigen Umgang mit OpenGL, da kann man noch eine ganze Menge herausholen !
    Hier mein Vorschlag den ich neulich nicht zuende schreiben konnte, da ich nun weiss das du über Software Transformierst.
    Jede T&L Karte kann auf Hardware transformieren. Also alle Karten die so momentan im Umlauf sind halt.
    Deshalb ein Vorschlag.
    1. Lies dir was an wie du ganze Arrays an Vertices auf einmal an die Grafikkarte schicken kannst. Transformiere immernoch in Software und schicke dann alle Daten GESAMMELT im Array an die Gk (pro Modell).
    Dies sollte dir einen ersten Eindruck geben wieviel die ganzen FunktionCalls zu glVertex3f gekostet haben als du jedes Vertex einzeln hochgeladen hast. (Diese Funktionen sind eigentlich nur für kleine Demos zum testen etc... nicht als praktikable Lösung gedacht)

    Danach verwirf das alles und unterteile dein Modell in alle Beweglichen Teile und lege einen VertexBuffer auf der Grafikkarte für jedes dieser Teile an.
    Also hast du dann Arm, Torso, Fuss, Hand, Kopf, etc... auf der Grafikkarte liegen.

    Dann gehst du durch die Liste deiner "virtuellen" Modells und errechnest die Animationsmatrix für das entsprechende Körperteil(so wie bisher in Software auch)
    Nur das du jetzt nicht in Software deine Vertices mit der Matrix multiplizierst sondern die TransformationsMatrix direkt an OpenGL auf die Grafikkarte schickst. Dann Renderst du den Körperteil.
    Dies machst du einfach für alle Körperteile aller Models.
    Dadurch wird die Matrixmultiplikation jedes einzelnen Vertex auf der Grafikkarte ausgeführt was natürlich viel viel schneller ist. Ausserdem musst du (siehe AGP) nicht jedesmal deine VertexDaten auf die Grafikkarte schicken(diese liegen dort untransformiert im Speicher) sondern nur die Transformationsbeschreibungen (Matrizen) für die einzelnen Körperteile. Und dies spart ordentlich Overhead über den AGP.
    Diese Variante lässt sich noch um einiges Optimieren indem du TeilTransformationen (Position des Modells, Arm bewegt auch Hand etc...) behälts und die letzte Transformationsmatrix auf der GK nur mit der neuen Mutliplizierst.

    3. Diese Variante ist dann nur wenn du alles vorher gut gemacht hast und es dir immernoch zu langsam seien sollte(was ich nicht recht glaube 😃 )
    Du kannst versuchen die BewegungsMatrizen auf dem Shader zu berechnen, so das du diese Berechnung auch von der Software auf die Hardware verlagerst. Ich weiss nicht genau wie dies funktioniert. Ich nehme an die setzt einige Werte für deine Vertices im Buffer auf feste Vectoren(wie der Arm gedreht werden kann, etc...) und schreibst dir dann einen Shader der durch Shaderparameter weiss an welcher Stelle der Animation das Modell gerade sein soll und daraus die Matrizen berechnet.
    Dadurch hast du dann nur noch ein paar Shadervariablen pro Model und Frame(bzw. Animationsabschnitt(muss nicht jedes Frame sein(->gilt auch für die anderen Varianten)) die über den AGP gehen müssen. Ausserdem sind dann sämtliche Berechnungen auf der Harfware, wofür sie schliesslich da ist.

    K, hoffe ich hab nicht zu viel scheisse geredet, hab mich verdammt lange nicht mit Grafik beschäftigt.



  • das hört sich doch schon sehr gut an. aber wie wird das dann wiederum in ogl realisiert?
    1. wie sende ich ein ganzes Paket von Vertices rüber? ich kenn nur glVertex3fv und der verlangt nur einen Vertex in nem array.
    2. wie lasse ich in ogl die Transformationsmtrix auf der graka errechnen ohne den in 3 angesprochenen VertexShader zu benutzen?
    3. alles im shader arbeiten lassen, das wird noch ein heiden spaß 🤡

    und nochmal die weitere Frage zu den vertex-shadern (damit sie nicht untergeht): ist es für das Programmieren egal, welche version der shader installiert ist? (änderungen der versionen sind logisch, sind sie aber auch abwärtskompatibel?) und von welchem Hersteller die graka ist? (ATI, nVidia) ?? oder muss ich mehrere versionen schreiben, sodass es auf ATI- und nVidia-Karten läuft ?

    heimschmiede



  • Ob ati oder nvidia ist egal, solange die karte die entsprechende shader version unterstütz. Die Karten unterstützen auch immer ältere Shader. (Die Shader werden kompiliert und dann vom Prozessor der Karte ausgeführt. Da diese Prozessoren normalerweise immer umfangreichere Operationen unterstützen ist die Abwärtskompatibilität so weit ich das sehe eigentlich fast garantiert).Die Transformations - matrizen kannst du dir nicht auf der GraKa berechnen lassen (wäre zumindest nicht sinnvoll, da du die Berechnung dann für jeden vertex wieder ausführen müßtest). Die Transformationen der vertices bei gegebenen Matrizen auf der GraKa auszuführen ist hingegen sehr sinnvoll, da deren Prozessor für solche Rechnungen optimiert ist.

    Zu Punkt 2 von Chaos Angle
    Diese Unterteilung ist nur dann sinnvoll, wenn du kein skinning benutzt, d.h. wenn deine vertices nicht von mehreren bones beeinflußt werden. Falls du dich trotzdem dafür entscheidest wäre es etwas schneller, wenn du dein gesamtes modell in einem vertex-buffer schreibst und dir nur die offsets der jeweiligen Teile merkst. -> Dadurch mußt du den Buffer seltener wechseln.

    Auf der folgenden Seite findest du einige Methoden, wie du deine Dreiecke schneller auf die Karte bekommst.

    http://nehe.gamedev.net/



  • zu finale matrizen-multiplikation der vertices auf der graka. ich meine das genau so, wie das gesagt hast mac_bu. nur die matrix-vektor-multiplikation auf der graka. aber wie mache ich diese finale multiplikation auf der graka? das dürfte doch nur mit nem vertex-shader funzen, oder? und wie bekomme ich dann die finale matrix auf die graka? (Quelltext)

    die nehes kenne ich größten teils, aber der nutzt ja auch hauptsächlich die Funktion glVertex3fv. ansonsten verwendet er vertex-buffer. bei welchem tut siehst du, wo er nen vertex-array am stück rüber zieht??

    heimschmiede



  • Entschuldige, ich meinte die VertexBuffer. Zu VertexList gibt es zwar auch etwas (Begin(List), dann die draw aufrufe, dann end) und anschließend diese Liste zeichnen. Das ist aber eigentlich nur für statische Objekte sinnvoll. Ich komme von DX, daher habe ich hier keine besonders tiefgehenden Kenntnisse.
    Unter DX gibt es den Aufruf SetVertexShaderConstant(...) mit dem sich eine begrenzte Anzahl an Konstanten (üblicherweise Vektoren) setzen läßt (?196 Vektoren?). Hiermit würdest du der Karte die Matrizen übermitteln. Anschließend kannst du auf diese Konstanten von deinem Shader aus zugreifen. Mit OGL dürfte es wohl ähnlich ablaufen. Anstelle von Matrizen könntest du in diese Konstanten auch Quaternione legen (+TranslationsVektoren). Dadurch könntest du auf mehr Bones zugreifen (Dürfte aber mehr Rechenaufwand sein). Solltest du Pro Modell mehr Bones haben, als sich in den ?196? Vektoren übermitteln lässt musst du dein Modell 'zerschneiden' d.h. in 2 Objekte unterteilen, die möglichst wenige gemeinsame Bones haben.
    Sieh dir mal die Teile auf der NVidia homepage an, da bekommst du denke ich ein ganz gutes Gefühl, wie das ganze funktioniert.

    Da fällt mir noch was ein:
    Schau dir mal die Funktionen
    glInterleavedArrays, glDrawArrays, glDrawElements, glArrayElement an.

    VertexBuffer wären aber wahrscheinlich trotzdem schneller (zumindest bei DX - ich weiß nicht in wie weit die ähnlich sind).

    Viel Spaß damit!



  • Also Nehe hat eigentlich alles was du für diese einfache Aufgabe brauchst.
    Ansonsten würde ich dir gerne das OpenGL ReadBook zu Herzen legen. Ist zwar schon etwas älter aber ein sehr gutes Buch um zu sehen was man alles wie machen machen.

    Hab grad mal etwas oberflächlich dort gesucht:
    glVertexPointer und glDrawElements oder ähnliche Sachen sollten dir bei Aufgabe 1. helfen.

    Die Matrizen auf der Grafikkarte rechnen lassen das tust du Teilweise schon.

    Wie bewegst und rotierst du deine Objekte denn. Ich glaube doch kaum in Software !!

    Du Benutzt die Methoden glRotate/Translate
    Also entweder benutzt du anschliessend diese um deine Körperteile zu bewegen, oder du übergibst gleich die Matrix mit den Befehlen glLoadIdentity oder glLoadMatrix/MultMatrix

    Damit schickst du die Matrizen zur Transformation an die GK, danach zeichnest du dein Objekt und die GK Multipliziert deine Vertices mit der Matrix (und noch einigen anderen Matrizen (Projektion, World, etc...)

    Wie gesagt, lass erstmal noch die Finger von Shadern. Sie sind sehr interessant, aber vorher brauchst du noch ein paar Grundkenntnisse was die generelle Kommunikation mit der GK angeht.



  • Ist schon richtig. Wenn du auf shader zurückgreifen würdest müßtest du in denen auch die beleuchtung deiner Objekte selbst berechnen. Lohnen könnte es sich trotzdem!

    Das mit den Matrizen war wohl ein Missverständis meinerseits.



  • das was ich bisher von den shadern gesehen habe, sieht sehr übel aus, deshalb wollt ich erst einmal auf andere methoden zurückgreifen.
    momentan berechne ich alle meine punkte in software. dh: an hand der keyframes und den absoluten und relativen matrizen werden die finalen matrizen erstellt, die dann die ganze tranformation enthalten. diese werden dann bei mir im code in software transformiert und dann erst in die graka verschifft. in der graka kommt dann noch das anderes gedönse, der vorher mit glRotate und glTranslate erstellt wird. das ist ja im prinzip auch nur ne matrix.
    ich denke, dass ich mal mit glDrawArrays rumexperimentiere um zu sehen, wie viel es mir wirklich bringt.
    dann danke ich mal allen für die guten antworten! ich werd wahrscheinlich mal wieder kommen, wenn ich irgendwo wieder probleme haben werde.
    aber wer noch weitere ideen ´hat, kann ja ruhig weiter posten 😉

    heimschmiede



  • Wie gesagt, DrawArrays wird sich auf jeden Fall lohnen. Aber noch viel mehr (oder genauso, egal, beides wird SEHR SEHR VIEL bringen) lohnt sich die Matrix Multiplikation auf der GK.
    Die Grafikkarte muss sowieso jedes Vertex mit der Matrix multiplizieren. Es ist also kein Mehraufwand.

    Teste einfach mal all deine Modelle OHNE Animation !
    Diese Geschwindigkeit sollte ungefähr die gleiche sein wie wenn du die Matrizen mit glLoadMatrix erst auf der GK mutltiplizierst.

    Viel Spass beim ausprobieren !



  • moinmoin,
    ich werde dieses thema wieder auffrischen müssen. es hat so lang gedauert, weil noch ein paar klausuren dazwischen lagen ....

    ich hab mir ja auch viel von den vertex-arrays versprochen, deshalb hab ich alles uaf vertex-arrays umgebaut und nachher wieder viele zeiten gemessen. leider sind die vertex-arrays kein bischen schneller als die massenweisen alten glVertex3f(v)-funktionen. auf die 100 models ist keine veränderung zu spüren. zusätzlich muss auch noch gesagt werden, dass man wegen der transformationen noch zusätzlichen speicherplatz reservieren muss. leider hat es sich nicht gelohnt. jetzt bin ich aber am überlegen, wie ich da noch was rausholen kann.
    ansatz: alle matrizen-multiplikationen auf der GK durchführen lassen. da im milkshape-model-format jeder vertex einem bone zugeordnet ist, ist es sinnvoll das gesamte format nach den bones zu sortieren, sonst könnte es passieren, dass bei jedem vertex ne neue matrix rübergeschoben werden muss, was eher kontraproduktiv wäre. also wären ale bones zusammen mit bone-eigener matrix, wo sich auch dann seperate vertex-buffer lohnen könnten. nachteil: es ist nicht vorher bekannt, ob alle dreiecke eines bones die selbe textur besitzen. es könnte also vorkommen, dass innerhalb des rendern eines bones mehrfach die textur geändert werden muss. -> ist aber ein eher kleinerer nachteil, weil man beim modellieren dieses problem beseitigen könnte.
    das milkshape-model ist nach meshes aufgebaut und sortiert. ein mesh besitzt maximal ein material und somit auch maximal eine textur.

    ist dieser ansatz vielversprechend? könnt ihr mir weitere nachteile oder auch weitere vorteile nennen?? denn dies ist mit ner menge arbeit verbunden, deshalb wollt ich lieber einmal nochmal nachfragen.

    heimschmiede


  • Mod

    klingt als würdest du sehr viel optimieren ohne dass du überhaupt weißt woran dein problem liegt. normalerweise ist das einzelne verschieben von vertices das hauptproblem und die umsteleung auf drawarray ein großer boost. wenn das bei dir nichts gebracht hat, gibt es ein anderes problem als die transformation bzw der vertextransfer.

    nur der neigierde wegen, stell das iechnen mal auf wireframe 😉

    rapso->greets();



  • Normalerweise sollte es schon einiges gebracht haben ...
    Also 2 Möglichkeiten:
    1. Du machst was falsch
    PS: is die häufigste
    2. Deine spezielle Situation hat ihren Bottleneck woanders ...
    -> nach Bottleneck suchen ...

    Aber so wie du es beschrieben hast sollte es eigentlich viel schneller werden.
    Mach einfach mal das was Rapso gesagt hat, stell das ganze auf Wireframe, dann siehst du ob die Fillrate dein Problem ist ... wenn ja solltest du über CullingAlgos nachdenken ..

    Ansonsten poste ansatzweise was du mit den VA's machst.
    (vielleicht füllst du sie ja jedes Frame oder so ...)


  • Mod

    ChaosAngel schrieb:

    (vielleicht füllst du sie ja jedes Frame oder so ...)

    er transformiert per software, das impliziert das füllen jedes frames. aber da er auch ohne transformationen miese performance hat.... macht er was falsch und das bottlenevk ist woanders :)... ich tippe ja mal auf software rendering 🙂



  • ich schick jetzt mal auszüge aus meinem Quelltext:

    KlassebStruktur:
    class VERTEX {
    public:
    	float location[3];
    	char boneID;
    };
    
    class TRIANGLE
    {
    public:
    	int VertexIndex[3];
    	float Normals[3][3];
    	float s[3];
    	float t[3];
    };
    
    class MESH
    {
    public:
    	int nTri;
    	int *TriangleIndex;
    	int MaterialIndex;
    };
    
    class KEYFRAME
    {
    public:
    	int JointIndex;
    	float time; //millisekunden
    	float parameter[3];
    };
    
    class JOINT
    {
    public:
    	float rotation[3];
    	float translation[3];
    	MATRIX absoluteMatrix; 
    	MATRIX relativeMatrix; 
    	int numRotationKeyframes;
    	int numTranslationKeyframes;
    	KEYFRAME *TranslationKeyframe;
    	KEYFRAME *RotationKeyframe;
    	int curRotationKeyframe;
    	int curTranslationKeyframe;
    	MATRIX finalMatrix; //final MATRIX
    	int parent;
    };
    
    class Milkmodel
    {
    public:
    	Milkmodel();
    	~Milkmodel();
    	int numVertex;
    	int numTriangle;
    	int numMaterial;
    	int numMesh;
    	int numJoints;
    	VERTEX *vertex;
    	TRIANGLE *triangle;
    	MATERIAL *material;
    	MESH *mesh;
    	JOINT *joint;
    	TIMER timer;
    	double totaltime;
    	bool loop;
    	float min[3];
    	float max[3];
    
    	GLfloat *vertexarr;
    	GLfloat *normalarr;
    	GLfloat *texcoordarr;
    
    	bool LoadMilkModel(char *filename, float scale, char* logfilename);
    	void DrawMilkModel(float scale); //scale ist nicht eingebunden
    	void DrawBoundingBox();
    	void SetupJoints();
    	void Animate();
    	void restart();
    private:
    	void AddMilkmodelLog(const char *message, ...);
    	char* LogFilename;
    
    };
    
    //und hier noch einen Auszug aus der DrawMilkModel-Funktion:
    [...]
    int arrindex=0;
    int mi, mj, mk;
    	for(mi=0;mi<numMesh;mi++) //Mesh
    	{
    			for(mj=0;mj<mesh[mi].nTri;mj++) //Dreiecke
    			{
    				int triangleIndex=mesh[mi].TriangleIndex[mj];
    				for(mk=0;mk<3;mk++) //Vertices
    				{
    
    					int index=triangle[triangleIndex].VertexIndex[mk];
    					if(vertex[index].boneID==-1)
    					{
    
    						memcpy(&vertexarr[arrindex*3],vertex[index].location,sizeof(float)*3);
    						memcpy(&normalarr[arrindex*3],triangle[triangleIndex].Normals[mk],sizeof(float)*3);
    					}
    					else
    					{
    						MATRIX &final=joint[vertex[index].boneID].finalMatrix;
    						float tempv[3];
    						memcpy(tempv,triangle[triangleIndex].Normals[mk],sizeof(float)*3); 
    						final.TransformNormalVect(tempv);
    
    						memcpy(&normalarr[arrindex*3],tempv,sizeof(float)*3);
    
    						memcpy(tempv,vertex[index].location,sizeof(float)*3);
    
    						final.TransformVect(tempv);
    
    						memcpy(&vertexarr[arrindex*3],tempv,sizeof(float)*3);
    
    					}
    
    					if(materialIndex>=0)
    					{
    
    						if(material[materialIndex].boolTexture)
    						{
    							float tc[2]; //tex coords
    							tc[0]=triangle[triangleIndex].s[mk];
    							tc[1]=triangle[triangleIndex].t[mk];
    							memcpy(&texcoordarr[arrindex*2],tc,sizeof(float)*2);
    
    						}
    
    					}
    
    					arrindex++;
    
    				}
    
    			}
    
    		}
    
    		//glEnd();
    
    		glEnableClientState(GL_VERTEX_ARRAY);
    		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    		glEnableClientState(GL_NORMAL_ARRAY);
    		glVertexPointer(3,GL_FLOAT,0,vertexarr);
    		glTexCoordPointer(2,GL_FLOAT,0,texcoordarr);
    
    		glNormalPointer(GL_FLOAT,0,normalarr);
    
    		glDrawArrays(GL_TRIANGLES,0,arrindex);
    		arrindex=0;
    
    	}
    

    sorry, wenn es ein wenig lang geworden ist, aber so sieht es aus. ich muss die Arrays jeden frame neu füllen, weil es in der natur einer animation liegt, dass sich dinge bewegen... 🙂

    was meint ihr mit bottlenevk ?? am meisten zeit frisst diese draw-funktion. die animations-funktion berechnet nur die finalen Matrizen der Bones (in Milkshape: Joints)

    sagt einfach laut los, was ich falsch mache, dafür bin ich ja hier hin gekommen!



  • hi

    tja, den klausurenstress kenn ich auch. Deswegen misch ich mich auch erst jetz ein. 🙂

    Ich hab bei meinem derzeitigen Projekt eine ähnliche Situation wie Heimschmiede und weiß auch noch nich genau, wie ich die Berechnungen auf die Graka krieg. Allerdings is die Problematik bei mir doch ne etwas andere, also erstma ne allgemeine Beschreibung:

    Ich schreibe auch (VC6, OpenGl) an ner Modelrenderklasse, die MS3D-Files lädt, derzeit noch ohne animation, is aber für ziemlich bald in Planung.

    Ich hab mich aber erstma auf Schattierung konzentriert: Mein Ansatz war die Beleuchtung der Pixelshader durch dynamische Texturierung zu simulieren. So werden also quadratische Texturen, die in der Mitte einen hellen Punkt haben und nach außen dunkler werden, so auf das Model gemappt, dass der helle Punkt immer entweder auf den Seiten direkt zum Licht (diffuse), zum helbvector (specular) oder zum vertex-eye-vector (glow-ähnlicher Effekt) steht (Vorlage war nehe's CellShading Tut).

    Das bringt denkt ich bei natürlich einigen Nachteilen gegenüber Pixelshadern trotzdem ne ganze Menge:
    - es werden keine Pixelshader für eine gute Beleuchtung gebraucht (läuft daher auch auf älteren Rechnern)
    - helligkeitsberechnungen sind nur pro vertex, nich pro pixel nötig - mehr speed, also auch besser für alte PCs
    - es lassen sich einige Effekte erzielen, die mit Pixelshadern eigentlich unmöglich sind, wenn die Texturen zB. nicht einfache Hellikeitsverläufe enthalten, sondern zB. ein Schachmuster oder sowas:

    |
     \  |  /
      \ | /
    ---+#+---
      / | \
     /  |  \
        |
    

    Das funzt alles auch bis auf ein paar kleine Fehler schon ziemlich gut.
    Falls interesse:

    http://cthulhu.homelinux.org/bsp.jpg
    http://cthulhu.homelinux.org/bsp.zip

    Die Addierung der unterschiedlichen Helligkeiten für zB. Diffuse und Specular hab ich zur zeit noch durch mehrere Passes gelöst, in Planung is aber Mutlitexture (mehr speed!! 😃 ). Außerdem läuft die Berechnung der uv-Koordinaten derzeit noch auf der CPU und die werden mit glTexCoord2f und glVertex3f übergeben. Das will ich natürlich ändern. Das halte ich aber bei für ziemlich stressig. Ich stell mir das ca. so vor:

    Für jedes Material jedes Bones müssten in einem ersten Pass alle Dreiecke (am besten durch VertexArrays?) mit den ShadingTexturen gezeichnet werden.
    Dafür müssten mit multitexture die entsprechenden Helligkeits-Texturen gebindet werden. Die erste wäre dann zB. die für diffuse, die zweite für specular. Dann sollte das Model der Graka übergeben werden, welche dann per Vertexshader die eigentliche Berechnung der uv-Koordinaten durchführt. Dazu müssen Vertexshader allerdings Multitexture verstehen und jede uv-Koordinate jeder Textureschicht ändern können. Können die das?

    Letztendlich würde dann dachte ich in nem zweiten Pass das Model mit glBlendFunc(GL_DST_COLOR,GL_SRC_COLOR) mit den eigentlichen Texturen gerendert.

    Haltet ihr das so für durchführbar/sinnvoll? Oder gibt es eine andere Möglichkeit diese Berechnungen von der Graka durchführen zu lassen, also ohne Vertexshader?

    Thx schonmal jetz

    [edit] ähm, ein Moderator möge das bitte ma besser in nen neuen thread schieben, thx[/edit]


Anmelden zum Antworten