Site de Grégory Corgié - Tag - synthèse image2018-01-29T19:08:41+00:00Grégory Corgiéurn:md5:a09e75037597199fc2585937f1672c6aDotclear[MOLECULE VIEWER] - Indexation d'un mesh 3Durn:md5:db9ace73dee22e10035fecb3969d6d5b2009-04-07T22:08:00+01:00Gregory CorgieMolecule VieweropenGlsynthèse image
<h2>Intro</h2>
<p>Dans notre viewer, la molécule principale est composée d'un peu plus de 350 000 triangles, soit de plus d'un million de sommets. Réfléchissons à ce que cela nous donne dans les faits.</p>
<h2>Mode normal</h2>
<p>On appel par mode normal le mode consistant à constuire la molécule triangle par triangle via des glBegin() / glEnd()
Pour chaque vertex, on passe la <strong>position</strong> ( <em>3 floats</em> ), la <strong>couleur</strong> ( "3 floats" ) et la <strong>normale</strong> ( "3 floats" ) à la carte graphique :</p>
<ul>
<li>Donc un vertex équivaut à 9 floats.</li>
<li>Donc si l'on veut tracer notre molécule en la construisant triangle par triangle, on aura</li>
</ul>
<p><code>Nombre de float = Nombre de triangles * Nombre de sommet par triangle * Nombre de float utiles par sommet</code><br />
<code>Nombre de float = 350 000 * 3 * 9</code><br />
<code>Nombre de float = <ins>9 450 000 floats utiles</ins></code><br /></p>
<p>Ce mode de rendu est sous-optimal pour plusieurs raisons :</p>
<ol>
<li>Tout d'abord parce que dans le million de sommets, beaucoup vont être commun à plusieurs triangles et on va donc se retrouver à passer plusieurs fois les mêmes informations. <strong>Pas bien !</strong></li>
<li>Ensuite parce que le transfert des informations d'un vertex est en réalité un passage d'informations de l'application (CPU) vers la carte graphique (GPU). Il faut savoir que ces transferts sont significativement plus lent que les transferts d'information à partir de la mémoire de la carte graphique. On retiendra que <em><strong>la communication GPU-GPU est plus efficace que la communication CPU-GPU</strong></em></li>
</ol>
<h2>Mode indexé</h2>
<p>Le mode indexé permet principalement de pallier au premier problème, c'est à dire de limiter le nombre de passage d'information par vertex à la dénomination la plus simple possible, c'est-à-dire un identifiant de sommet. <strong><ins>Chaque sommet sera identifié par un entier</ins></strong></p>
<p>Prenons l'exemple d'un simple quad ABCD qui se décompose donc en 2 triangles.
<img src="http://gregory.corgie.free.fr/currentDotclear/public/.Indexation01_m.jpg" alt="Indexation01" style="display:block; margin:0 auto;" title="Indexation01, juin 2009" /></p>
<p>Dès lors, on se rend compte qu'au lieu de passer toutes les informations de chaque sommet ( à savoir position, normale et couleur dans notre exemple ), nous n'allons passer qu'un identifiant de sommet. Les identifiants 0 - 1 - 2 pour le premier triangle et 1 - 2 - 3 pour le second.
Alors oui tout cela est bien joli allez-vous me dire, mais ces informations comment sont-elles passées ?</p>
<h5>Via les fonction glVertexPointer, glNormalPointer, glColorPointer et glDrawElements.</h5>
<p>Ces 3 fonctions permettent de lire les informations directement à partir d'une adresse mémoire. Pour faire simple dans mon exemple, j'ai fabriqué 3 buffers distincts :</p>
<ul>
<li>Un buffer contenant de vecteurs 3D représentant les positions des différents vertex</li>
<li>Un buffer contenant des vecteurs 3D représentant les normales de différents vertex</li>
<li>Un buffer contenant les identifiants de sommets permettant de reconstruire la molécule</li>
</ul>
<p>ps: Je n'ai pas fait de buffer de couleur, car j'utilise une couleur constante pour tous les sommets</p>
<p>L'utilisation des fonctions donne donc quelque chose comme ça</p>
<p><code> glNormalPointer( GL_FLOAT, sizeof( CVector3 ), _geometrie._p_normbuf );</code> <br />
<code> glVertexPointer( 3, GL_FLOAT, sizeof( CVector3 ), _geometrie._p_vertbuf );</code> <br />
<code> glDrawElements( GL_TRIANGLES, index_to_render, GL_UNSIGNED_INT, _geometrie._p_indbuf ); </code><br /></p>
<h3>L'idée de base</h3>
<p>Très bien maintenant que l'on connait les appels OpenGL à faire, il va falloir construire nos tableaux de données à passer à ces magnifiques fonctions. Partons sur un exemple simple pour illustrer ce que l'on cherche à faire.
<br />
Prenons une forme composée de 4 triangles qui ont <strong>tous un sommet commun</strong>. Le but est donc à partir des 9 sommets fournis en entrée A - B - C - D - E - F - G - H - I - J - K - L, de constituer une géométrie composée d'identifiants de sommets, soit,
<img src="http://gregory.corgie.free.fr/currentDotclear/public/.Slide2_m.jpg" alt="Indexation02" style="display:block; margin:0 auto;" title="Indexation02, juin 2009" /></p>
<p>Maintenant que l'on sait ce que l'on souhaite obtenir, voyons à quoi va ressembler l'algorithme.</p>
<p><code> Initialisation du premier index libre à 0 </code> <br />
<code> Pour chaque sommet</code> <br />
<code> - Si le sommet n'est pas dans la liste des sommets répertoriés </code> <br />
<code> <del></del>> On l'ajoute à liste des sommets </code> <br />
<code> <del></del>> On ajoute au tableau d'index la valeur du premier index de sommet libre (qui sera donc associé au sommet courant) </code> <br />
<code> <del></del>> On incrémente la valeur du premier index libre </code> <br />
<code> - Sinon </code> <br />
<code> <del></del>> On récupère l'index du sommet correspondant à ce sommet dans la liste des sommets déjà répertoriés </code> <br />
<code> <del></del>> On ajoute au tableau d'index cette valeur </code> <br /></p>
<p><img src="http://gregory.corgie.free.fr/currentDotclear/public/.Slide3_m.jpg" alt="Indexation03" style="display:block; margin:0 auto;" title="Indexation03, juin 2009" />
<img src="http://gregory.corgie.free.fr/currentDotclear/public/.Slide4_m.jpg" alt="Indexation04" style="display:block; margin:0 auto;" title="Indexation04, juin 2009" /></p>
<h3>Méthode d'indexation naïve</h3>
<p>Comment implémenter <em> "Si le sommet n'est pas dans la liste des sommets répertoriés" </em> ? L'idée qui vient naïvement est de faire un tableau de coordonnées de sommets, et pour chaque sommet vérifier si ses coordonnées sont égales à celles d'un sommet stocké dans le tableau.</p>
<p>Voilà un algorithme tout bête qui marche parfaitement <strong>MAIS</strong> avec un faible nombre de sommets. En effet, pour chaque sommet traité, on doit parcourir (dans le pire de cas) toute la liste des sommets. Dans le cas de l'insertion du 10ième sommet, on aura au pire des cas 10 tests à faire. Pour le 100ième, 100 tests etc... La complexité de cet algorithme est une complexité quadratique O(n^2) ( n sommets à traiter * n sommets à comparer ).</p>
<p>Dans l'exemple de la molécules la géométrie est composée de 9 millions de sommets !!! Pour être honnête, mon application n'a jamais dépassée le stade de l'indexation de la géométrie complète et après 35 minutes de calculs, un magnifique message Windows est venu m'indiquer qu'il n'y avait plus assez de mémoire disponible sur mon système...</p>
<h3>Methode d'indexation très rapide</h3>
<p>L'algorithme précédent est inutilisable à grande échelle à cause du nombre de comparaisons à exécuter par sommet. On va donc accélérer ça en utilisant une <strong>map de la STL</strong>.</p>
<h4>Qu'est qu'une map ?</h4>
<p>TODO</p>
<h4>Comment l'appliquer à notre cas ?</h4>
<p>TODO</p> http://gregory.corgie.free.fr/currentDotclear/index.php?post/2009/04/07/%5BMOLECULE-VIEWER%5D-Indexation-d-un-mesh-3D#comment-formhttp://gregory.corgie.free.fr/currentDotclear/index.php?feed/atom/comments/44[SCENE 3D] - Partie 4 - Les sourcesurn:md5:188d98d974fc517ce7f1366e20f578d32008-02-17T22:10:00+00:00Grégory CorgiéScene 3D OpenGlopenGlsynthèse image
<h3>L'exe</h3>
<p>Le travail étant toujours en cours, je ne souhaite pas déposer les sources maintenant. Cependant, je mets à disposition l'exe et les ressources. N'oubliez pas d'installer le SDK Cg et surtout <strong>faites moi des retours</strong> pour me dire l'application marche sur votre PC.</p>
<p>La scène <a href="http://gregory.corgie.free.fr/currentDotclear/index.php?post/2008/02/17/../../dotclear/images/scene3D/Scene_3D.rar">ici !!!</a>.</p>
<h3>Installation nécessaire</h3>
<p>Possédant une carte NVidia, j'ai naturellement opté pour le Cg, d'autant plus que le SDK Cg est très complet (nombreux exemples) et qu'il contient aussi les librairies OpenGl très souvent utilisées mais non natives (glut32.dll, glut.h, glext.h). Ceci évite d'aller chercher ces fichiers sur le net et ils sont certifiés compatibles par NVidia.</p>
<p>Voila les liens pour les différentes plate-formes ( dans l'idéal, j'aimerai rendre mon exemple compatible Mac et Linux )</p>
<h4>Cg Toolkit install</h4>
<ul>
<li><a href="http://gregory.corgie.free.fr/currentDotclear/index.php?post/2008/02/17/../../Ressources/CgToolkit/Cg-2.0_Dec2007_Setup.exe" hreflang="fr">Cg Toolkit 2.0 Version Windows</a></li>
<li><a href="http://gregory.corgie.free.fr/currentDotclear/index.php?post/2008/02/17/../../Ressources/CgToolkit/Cg-2.0_Dec2007.dmg" hreflang="fr">Cg Toolkit 2.0 Version Mac</a></li>
<li><a href="http://gregory.corgie.free.fr/currentDotclear/index.php?post/2008/02/17/../../Ressources/CgToolkit/Cg-2.0_Dec2007_x86.tar.gz" hreflang="fr">Cg Toolkit 2.0 Version Linux</a></li>
</ul>
<p>Bien evidemment pour executer des shaders, il faut une carte graphique qui possède des unités de shaders. A partir de la GeForce 6800 chez NVidia je crois...</p>
<p><img src="http://gregory.corgie.free.fr/currentDotclear/public/.Scene3D01_m.jpg" alt="Scene3D01.png" style="display:block; margin:0 auto;" title="Scene3D01.png, janv. 2009" /></p> http://gregory.corgie.free.fr/currentDotclear/index.php?post/2008/02/17/40-scene-3d-les-sources#comment-formhttp://gregory.corgie.free.fr/currentDotclear/index.php?feed/atom/comments/36[SCENE 3D] - Partie 3 - Intégration d'un shader dans une scène OpenGlurn:md5:161284cea8a7ece150c7f5718f1d8fb92008-02-04T21:29:00+00:00Grégory CorgiéScene 3D OpenGlopenGlsynthèse image
<p>Voilà un mini tutorial concernant l'intégration d'un shader au milieu de notre scène.</p>
<h3>Installation nécessaire</h3>
<p>Possédant une carte NVidia, j'ai naturellement opté pour le Cg, d'autant plus que le SDK Cg est très complet (nombreux exemples) et qu'il contient aussi les librairies OpenGl très souvent utilisées mais non natives (glut32.dll, glut.h, glext.h). Ceci évite d'aller chercher ces fichiers sur le net et ils sont certifiés compatibles par NVidia.</p>
<p>Voila les liens pour les différentes plate-formes ( dans l'idéal, j'aimerai rendre mon exemple compatible Mac et Linux )</p>
<h4>Cg Toolkit install</h4>
<ul>
<li><a href="http://gregory.corgie.free.fr/currentDotclear/index.php?post/2008/02/04/../../Ressources/CgToolkit/Cg-2.0_Dec2007_Setup.exe" hreflang="fr">Cg Toolkit 2.0 Version Windows</a></li>
<li><a href="http://gregory.corgie.free.fr/currentDotclear/index.php?post/2008/02/04/../../Ressources/CgToolkit/Cg-2.0_Dec2007.dmg" hreflang="fr">Cg Toolkit 2.0 Version Mac</a></li>
<li><a href="http://gregory.corgie.free.fr/currentDotclear/index.php?post/2008/02/04/../../Ressources/CgToolkit/Cg-2.0_Dec2007_x86.tar.gz" hreflang="fr">Cg Toolkit 2.0 Version Linux</a></li>
</ul>
<p>Bien evidemment pour executer des shaders, il faut une carte graphique qui possède des unités de shaders. A partir de la GeForce 6800 chez NVidia je crois...</p>
<h3>Exemple de programme shader</h3>
<p>On part du principe que l'on dispose déjà de notre programme à exécuter au shader. Généralement, on trouve beaucoup de tutorials expliquant comment coder un shader mais très peu sur comment l'exécuter à partir d'openGl. Alors voici à quoi ressemble un shader, ici nous disposons d'un vertex shader et d'un pixel shader.</p>
<h5>Voici un exemple de déclaration de structures utilisables aux unités de shaders</h5>
<ul>
<li>appdata : Données d'entrées fournies par l'application (programme OpenGl) et lues en entrées du Vertex Shader. Typiquement, on retrouve la position du vertex dans l'espace, une couleur, une normale...</li>
<li>VSOut : Données en sortie de l'unité de Vertex Shader et lues en entrée de l'unité de Pixel Shader. On retrouve ici, les coordonnées de textures, les couleurs interpolées ...</li>
<li>PSOut : On retrouve ici <strong>obligatoirement</strong> une couleur. Il s'agit de la couleur finale du pixel à l'écran.</li>
</ul>
<blockquote><p><code> // Structure d'entrée </code><br />
<code> struct appdata </code><br />
<code> { </code><br />
<code> float4 pos : POSITION; </code><br />
<code> float4 col : COLOR0; </code><br />
<code> ... </code><br />
<code> }; </code><br />
<code> </code><br />
<code> // Structure de sortie / entrée pixel </code><br />
<code> struct VSout </code><br />
<code> {</code><br />
<code> float4 pos : POSITION;</code><br />
<code> float4 col : COLOR0;</code><br />
<code> ...</code><br />
<code> };</code><br />
<code> </code><br />
<code> // Structure au pixel shader </code><br />
<code> struct PSout</code><br />
<code> {</code><br />
<code> float4 col : COLOR0;</code><br />
<code> };</code><br /></p></blockquote>
<h5>Déclaration du programme à executer au Vertex Shader</h5>
<p>Ici on ne fait rien de plus que de transmettre la position et la couleur fournie en entrée.</p>
<blockquote><p><code> //</code><br />
<code> // Fonction vertex </code><br />
<code> // <del></del><del></del><del></del><del></del><del></del><del></del><del></del><del></del><del></del><del></del><del></del><del></del><del></del><del></del><del></del><del></del><del></del><del></del></code><br />
<code> </code><br />
<code> VSout vxShader(appdata IN, </code><br />
<code>uniform float4x4 cgWater_mViewProj,</code><br />
<code>uniform float4x4 cgWater_mViewI,</code><br />
<code>uniform float4 cgWater_LightVec )</code><br />
<code> {</code><br />
<code> VSout OUT;</code><br />
<code> </code><br />
<code> OUT.pos = IN.pos;</code><br />
<code> OUT.col = IN.col;</code><br />
<code> ...</code><br />
<code> return OUT;</code><br />
<code> }</code><br /></p></blockquote>
<h5>Déclaration du programme à executer au Pixel Shader.</h5>
<p>Dans ce cas, le programme attribue la couleur rouge à tous les pixels de la géométrie actuellement rendue.</p>
<blockquote><p><code> // </code><br />
<code> // Fonction pixel</code><br />
<code> // </code><br />
<code> </code><br />
<code> PSout pxShader ( VSout IN,</code><br />
<code> const uniform sampler2D cgWater_TexMap,</code><br />
<code> const uniform sampler2D cgWater_TexMapNM )</code><br />
<code> {</code><br />
<code> PSout OUT;</code><br />
<code> </code><br />
<code> OUT.col.rgb = float3(1.0,0.0,0.0);</code><br />
<code> OUT.col.a = 1.0; </code><br />
<code> </code><br />
<code> return OUT;</code><br />
<code> }</code><br /></p></blockquote>
<h3>Déclaration du shader dans notre programme OpenGl</h3>
<p>Plusieurs étapes sont nécessaires pour faire le lien entre un shader et notre programme. Voilà les étapes à suivre :</p>
<h4>Déclaration des variables nécessaires</h4>
<blockquote><p><code> CGcontext cgWaterContext;</code><br />
<code> CGprogram cgWaterVertexProgram;</code><br />
<code> CGprofile cgWaterVertexProfile;</code><br />
<code> CGprogram cgWaterPixelProgram;</code><br />
<code> CGprofile cgWaterPixelProfile;</code><br /></p></blockquote>
<h4>Initialisation</h4>
<ol>
<li>Déclaration du context</li>
</ol>
<p>Tous les shaders s'inscrivent dans un contexte. La déclaration du contexte se fait via la méthode (qui retourne NULL en cas d'échec)</p>
<blockquote><p><code> cgWaterContext = cgCreateContext(); </code><br /></p></blockquote>
<ol>
<li>Récupération des profils</li>
</ol>
<blockquote><p><code> cgWaterVertexProfile = cgGLGetLatestProfile( CG_GL_VERTEX );</code><br />
<code> cgWaterPixelProfile = cgGLGetLatestProfile( CG_GL_FRAGMENT );</code><br />
<code> cgGLSetOptimalOptions( cgWaterVertexProfile );</code><br />
<code> cgGLSetOptimalOptions( cgWaterPixelProfile );</code><br /></p></blockquote>
<ol>
<li>Récupération des shaders compilés</li>
</ol>
<blockquote><p><code> cgWaterVertexProgram = cgCreateProgramFromFile(cgWaterContext, CG_SOURCE, "Ressources/Shaders/water.cg", cgWaterVertexProfile, "vxShader", 0);</code><br />
<code> cgWaterPixelProgram = cgCreateProgramFromFile(cgWaterContext, CG_SOURCE, "Ressources/Shaders/water.cg", cgWaterPixelProfile, "pxShader", 0);</code><br /></p></blockquote>
<ol>
<li>Chargement des programmes en mémoire</li>
</ol>
<blockquote><p><code> cgGLLoadProgram(cgWaterVertexProgram);</code><br />
<code> cgGLLoadProgram(cgWaterPixelProgram); </code><br /></p></blockquote>
<p>A ce niveau, nous avons initialisé tout ce qu'il faut.</p>
<ol>
<li>Récupération des variables passées au shader</li>
</ol>
<p>Déclaration des variables</p>
<blockquote><p><code> CGparameter cgWater_mViewProj; </code><br />
<code> CGparameter cgWater_mViewI;</code><br />
<code> CGparameter cgWater_light;</code><br />
<code> CGparameter cgWater_tex;</code><br />
<code> CGparameter cgWater_texNM;</code><br /></p></blockquote>
<p>Récupération</p>
<blockquote><p><code> cgWater_mViewProj = cgGetNamedParameter(cgWaterVertexProgram, "cgWater_mViewProj");</code><br />
<code> cgWater_mViewI = cgGetNamedParameter(cgWaterVertexProgram, "cgWater_mViewI");</code><br />
<code> cgWater_light = cgGetNamedParameter(cgWaterVertexProgram, "cgWater_LightVec");</code><br />
<code> cgWater_tex = cgGetNamedParameter(cgWaterPixelProgram, "cgWater_TexMap");</code><br />
<code> cgWater_texNM = cgGetNamedParameter(cgWaterPixelProgram, "cgWater_TexMapNM");</code><br /></p></blockquote>
<h4>Pour assigner des valeurs aux paramètres passés aux shaders</h4>
<ul>
<li>Les matrices <br /></li>
</ul>
<p>On peut récupérer très facilement toutes les matrices nécessaires via différents flags (voir la doc Cg). Par exemple,</p>
<blockquote><p><code> cgGLSetStateMatrixParameter(cgWater_mViewProj,CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);</code><br />
<code> cgGLSetStateMatrixParameter(cgWater_mViewI, CG_GL_MODELVIEW_MATRIX, CG_GL_MATRIX_INVERSE );</code><br /></p></blockquote>
<ul>
<li>Les paramètres classiques<br /></li>
</ul>
<p>Rien de particulier à signaler, on peut passer toute valeur définie dans le programme</p>
<blockquote><p><code> cgSetParameter4f( cgWater_light, s_light_pos.x, s_light_pos.y, s_light_pos.z, 1.f ); </code><br /></p></blockquote>
<ul>
<li>Les textures<br /></li>
</ul>
<p>A faire <strong>une seule fois</strong> dans la boucle d'initialisation après avoir chargé les textures en mémoire vidéo (avec glBindTexture). Le second paramètre correspond à l'identifiant de texture retourné.</p>
<blockquote><p><code> cgGLSetTextureParameter( cgWater_tex, s_Sea.GetReflectTexId() );</code><br />
<code> cgGLSetTextureParameter( cgWater_texNM, s_Sea.GetNormalMapTexId() );</code><br /></p></blockquote>
<p><em>Remarque:</em> Il est possible d'utiliser les extensions ARB d'openGL et d'attribuer un identifiant de texture à chaque couche de texture. Ensuite, il suffit de faire le lien au niveau du shader et d'indiquer en face de chaque déclaration de sampler le registre associé. Exemple :</p>
<blockquote><p>><code> // Samplers (A placer après la déclaration des structures utilisées aux shaders appdata, VSOut, PSOUT)</code><br />
><code> uniform sampler2D tex_map : register(s0);</code><br />
><code> uniform sampler2D tex_mapNM : register(s1);</code><br /></p></blockquote>
<h4>L'éxecution (dans la boucle d'affichage)</h4>
<ul>
<li>Activation des profiles</li>
</ul>
<blockquote><p><code> cgGLEnableProfile(cgWaterVertexProfile);</code><br />
<code> cgGLEnableProfile(cgWaterPixelProfile);</code><br /></p></blockquote>
<ul>
<li>Activation des programmes</li>
</ul>
<blockquote><p><code> cgGLBindProgram(cgWaterVertexProgram);</code><br />
<code> cgGLBindProgram(cgWaterPixelProgram);</code><br /></p></blockquote>
<ul>
<li>Activation des textures si nécessaire</li>
</ul>
<blockquote><p><code> cgGLEnableTextureParameter(cgWater_tex);</code><br />
<code> cgGLEnableTextureParameter(cgWater_texNM);</code><br /></p></blockquote>
<ul>
<li>Affichage de la géométrie</li>
</ul>
<blockquote><p><code> glBegin( GL_QUADS );</code><br />
<code> glTexCoord2f( 0.f, 0.f); glVertex3f(x-size, y+size, z-size);</code><br />
<code> glTexCoord2f( 0.f, 1.f); glVertex3f(x+size, y+size, z-size);</code><br />
<code> glTexCoord2f( 1.f, 1.f); glVertex3f(x+size, y+size, z+size);</code><br />
<code> glTexCoord2f( 1.f, 0.f); glVertex3f(x-size, y+size, z+size);</code><br />
<code> glEnd();</code><br /></p></blockquote>
<ul>
<li>Désactivation des textures</li>
</ul>
<blockquote><p><code> cgGLDisableTextureParameter(cgWater_tex);</code><br />
<code> cgGLDisableTextureParameter(cgWater_texNM);</code><br /></p></blockquote>
<ul>
<li>Désactivation des profiles</li>
</ul>
<blockquote><p><code> cgGLDisableProfile(cgWaterVertexProfile);</code><br />
<code> cgGLDisableProfile(cgWaterPixelProfile);</code><br /></p></blockquote>
<ul>
<li>Désactivation des variables</li>
</ul>
<blockquote><p><code> cgGLDisableClientState(cgWater_mViewProj);</code><br />
<code> cgGLDisableClientState(cgWater_mViewI);</code><br />
<code> cgGLDisableClientState(cgWater_light);</code><br /></p></blockquote>
<p>Voilà, tout est fait. Normalement à ce niveau le shader sera exécuté lors du rendu de la géométrie à la place du Fixed Functions Pipeline
<br /></p> http://gregory.corgie.free.fr/currentDotclear/index.php?post/2008/02/04/38-integration-d-un-shader-dans-une-scene-opengl#comment-formhttp://gregory.corgie.free.fr/currentDotclear/index.php?feed/atom/comments/34