Partage
  • Partager sur Facebook
  • Partager sur Twitter

[OpenGL] Rendu lent

glDrawArraysInstancied()

    9 juillet 2020 à 23:04:21

    Bonsoir, je suis entrain de coder un petit programme en C++ avec OpenGL, je charge des décors à partir de fichier .obj venant de Blender et je voudrais modéliser une forêt, le problème se pose alors rapidement : J'ai modélisé les arbres avec Blender et son générateur de particules, il y en a pas moins de ... 20000. Pour éviter d'avoir un fichier .obj de taille surdimensionnée, j'ai laissé le sol dans le fichier et j'ai mis un seul modèle d'arbre dans un autre fichier .obj. Dans mon programme, j'appelle glDrawArrays() pour modéliser le sol puis en guise de solution provisoire, j'appelle la même fonction en boucle pour dessiner les arbres mais en ne dessinant que les arbres les plus proches de la caméra. Seulement voilà, je viens de découvrir qu'il existait une fonction parfaitement adaptée à mes besoins : glDrawArraysInstancied() qui permet de dessiner le même objet un certain nombre de fois, il faut juste envoyer des infos au vertex shader pour éviter que tous les objets soient dessinés au même endroit. Je viens donc de mettre cette idée en application mais j'ai eu une très mauvaise surprise en constatant que malgré l'utilisation de cette fonction, le programme était très lent, et pour seulement 100 arbres, on est loin des 20000 ... Voici le code du rendu des arbres :

    void OBJ2_DrawVBO_Instanced(OBJ2_VBO *vbo,vector<OBJ2_Vertex> &positions,const GLuint shader)
    {
        const int attributId=glGetAttribLocation(shader,"numtex");
    
        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
        glEnableClientState(GL_NORMAL_ARRAY);
        glEnableClientState(GL_COLOR_ARRAY);
        glEnableVertexAttribArray(attributId);
    
        glBindBuffer(GL_ARRAY_BUFFER,vbo->bufferVRAM);		//Bindage espace mémoire VRAM
    
        glVertexPointer(3,GL_FLOAT,0,BUFFER_OFFSET(0));
        glTexCoordPointer(2,GL_FLOAT,0,BUFFER_OFFSET(vbo->nbVertices*3*sizeof(float)));
        glNormalPointer(GL_FLOAT,0,BUFFER_OFFSET(vbo->nbVertices*3*sizeof(float)+vbo->nbVertices*2*sizeof(float)));
        glColorPointer(3,GL_FLOAT,0,BUFFER_OFFSET(vbo->nbVertices*3*sizeof(float)+vbo->nbVertices*2*sizeof(float)+vbo->nbVertices*3*sizeof(float)));
        glVertexAttribPointer(attributId,1,GL_FLOAT,0,0,BUFFER_OFFSET(vbo->nbVertices*3*sizeof(float)+vbo->nbVertices*2*sizeof(float)+vbo->nbVertices*3*sizeof(float)+vbo->nbVertices*3*sizeof(float)));		//Attributs de sommets (Pour les textures)
    
        if(vbo->textures.size()!=0)
        {
            int texShader[vbo->textures.size()];
    
            int compteurTex=0;
            while(1)		//Multi-texturing
            {
                glActiveTexture(GL_TEXTURE0+compteurTex);
                glBindTexture(GL_TEXTURE_2D,vbo->textures[compteurTex]);
    
                texShader[compteurTex]=compteurTex;
    
                compteurTex++;
                if(compteurTex==vbo->textures.size())
                    break;
            }
    
            glUniform1iv(glGetUniformLocation(shader,"tex"),vbo->textures.size(),texShader);		//Envoi tableau de textures au shader
        }
    
        int compteurPos=0;
    
        glUniform1i(glGetUniformLocation(shader,"instancied"),true);
    
        while(1)
        {
            ostringstream os;
            os << compteurPos;
    
            string index="positions["+os.str()+"]";
    
            glUniform3f(glGetUniformLocation(shader,index.c_str()),positions[compteurPos].X,positions[compteurPos].Y,positions[compteurPos].Z);		//Envoi des positions des arbres au shader
    
            compteurPos++;
            if(compteurPos==positions.size())
                break;
        }
    
        glDrawArraysInstanced(GL_TRIANGLES,0,vbo->nbVertices,positions.size());			//Rendu des arbres
    
        glUniform1i(glGetUniformLocation(shader,"instancied"),false);
    
        glActiveTexture(GL_TEXTURE0);
    
        glBindBuffer(GL_ARRAY_BUFFER,0);
    
        glDisableClientState(GL_VERTEX_ARRAY);
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
        glDisableClientState(GL_NORMAL_ARRAY);
        glDisableClientState(GL_COLOR_ARRAY);
        glDisableVertexAttribArray(attributId);
    }

    Voici mon vertex shader :

    #version 120
    
    attribute float numtex;
    
    out float numtex2;
    
    uniform vec3 positions[100];		//Positions des arbres
    uniform bool instancied=false;
     
    void main(void)
    {
    	if(instancied)
    	{
    		vec3 offset=positions[gl_InstanceID];		//Récupération position de l'instance
    		vec4 nouvellePosition;
    		
    		nouvellePosition.x=gl_Vertex.x+offset.x;		//Translation
    		nouvellePosition.y=gl_Vertex.y+offset.y;
    		nouvellePosition.z=gl_Vertex.z+offset.z;
    		nouvellePosition.w=gl_Vertex.w;
    		
    		gl_Position=gl_ModelViewProjectionMatrix*nouvellePosition;
    	}
    	
    	else
    		gl_Position=gl_ModelViewProjectionMatrix*gl_Vertex;
    	
    	gl_TexCoord[0]=gl_MultiTexCoord0;
    	
    	gl_FrontColor=gl_Color;
    	
    	numtex2=numtex;
    }

    Voilà ben je tombe des hauts, moi qui voyait en cette fonction une lueur d'espoir ... Bon il faut dire que mon modèle d'arbre possède pas moins de 66524 vertices, mais il faut ce qu'il faut pour avoir un minimum de réalisme. Pour vous dire, je suis à 12 FPS avec un appel de glDrawArrays() en boucle et exactement la même valeur avec un seul appel de glDrawArraysInstanced(), à croire que cette fonction ne sert pas à grand-chose finalement ...

    • Partager sur Facebook
    • Partager sur Twitter
      14 juillet 2020 à 17:12:27

      Bonjour,

      Je suis très loin d'être un expert en OpenGL mais vu le peu de personnes active s'y connaissant je vais quand même essayer de vous donner des pistes.

      • Avez vous essayer de n'envoyer que la position au shader et d'appeler draw 100 fois ? (apres verification cela ne change pas grand chose)
      • La position des objets en OpenGL sont généralement des matrices
      • J'avais lu quelque part que certaines version d'opengl sont limités à 2^16 vertices soit un peu moins que 66000 mais je ne pense pas que ce soit le cas ici
      • J'ai lu quelque part que glGenList et displayList permettent d'optimiser legerement l'affichage mais je n'ai jamais essayé
      • ps: les while(1) et break ne peuvent-ils pas être remplacés par un do while() ?

      La logique d'afficher plusieurs objets à l'écran avec une position différente suit très fortement le concept de particule, il pourrait être intéressant de regarder par là. Cependant les particules ont généralement un faible nombre de vertices donc le problème ne provient pas forcémment du nombre de mesh.

      Juste avant avoir publié ce message j'ai vérifié de mon coté sur un ancien projet et j'ai appelé draw 100 fois sur un objet avec un grand nombre de vertices (34 000). Cela m'a aussi fait chuter grandement mes fps bien que dans mon projet j'avais un systeme de vao, que la fonction d'affichage avait déja stocké les pointeurs vers le shader et que le shader était basique et avec matrice.

      Du coup je pense qu'il faut utiliser des fonctionnalités avancées et propre à OpenGL permettant d'optimiser tout cela mais cela n'avance malheureusement pas beaucoup :/.


      Des pistes seraient d'intégrer la notion de LOD et de culling mais je ne sais pas à quel point cela serait efficace.

      En esperant que cela puisse vous aider ne serait-ce qu'un peu,

      bonne chance

      • Partager sur Facebook
      • Partager sur Twitter
        15 juillet 2020 à 15:28:50

        Bonjour, merci pour votre réponse.

        - Appeler glDrawArrays() en boucle j'ai déjà tenté et apparemment ça donne exactement le même résultat que glDrawArraysInstanced() appelée une seule fois.

        - Euh oui, des vecteurs en fait donc si on veut des matrices à une colonne et 3 (4 en réalité ...) lignes.

        - J'ignore s'il y a une limite du nombre de vertices, ça doit dépendre de la carte graphique.

        - Les display listes sont effectivement un moyen d'optimiser les rendus mais devenu obsolète, il vaut mieux utiliser les VBO (Vertex Buffer Object), comme c'est le cas dans mon projet.

        - Oui, un do while ou même un while ou un for, mais j'ai gardé cette habitude de while(1) ... Ne m'en veuillez pas ^^

        Bref j'ai trouvé une solution consistant à afficher les arbres uniquement dans un rayon autour de la caméra et dans un rayon plus éloigné j'affiche des arbres en croix mais avec l'éclairage ça fait pas très jojo, en voit la structure en croix, j'hésite à utiliser un billboard.

        Merci pour votre réponse en tout cas.

        • Partager sur Facebook
        • Partager sur Twitter
          19 novembre 2020 à 10:46:31

          Bonjour,

          Déplacement vers un forum plus approprié

          Le sujet est déplacé de la section Mapping & modding vers la section Langage C++

          • Partager sur Facebook
          • Partager sur Twitter

          Moderateur forum || FAQ 3D || discord 3D francophone || OC Tweak script

            19 novembre 2020 à 13:04:43

            Salut !

            Il faut penser à l'instanciation hardware, qui te permet d'appeler glDrawXxxInstanced une seule fois pour tous les objets du même type (ici ton arbre) en ajoutant dans ton VAO un attribut d'instance contenant la matrice de transformation de l'instance courante.

            http://ogldev.org/www/tutorial33/tutorial33.html

            • Partager sur Facebook
            • Partager sur Twitter

            Si vous ne trouvez plus rien, cherchez autre chose.

              13 décembre 2020 à 21:01:32

              Re, je suis sur un autre projet mais le challenge est le même : Représenter un nombre important d'arbres de manière optimisée. J'arrive maintenant à faire fonctionner l'instanciation et je parviens à avoir quelque chose de fluide du moins lorsque le nombre d'instances reste en-dessous d'une certaine valeur. Au-delà le rendu devient lent, j'aimerais résoudre ce problème avec du frustum culling mais je me heurte à un mur : Il faudrait tester la position de chaque instance dans mon application ce qui risque d'être d'autant plus lent qu'il y a d'instances mais ce n'est pas le seul problème : Le tableau d'attributs représentant les positions des instances sont stockés dans un VBO et je compte agir sur le contenu de ce tableau en supprimant de ce dernier les positions des instances que la caméra ne voit pas, ce qui nécessite d'appeler glMapBuffer() et de travailler sur le tableau pointé qui est un tableau statique donc pour supprimer des éléments de ce tableau ça s'annonce compliqué et tou sauf optimisé. A cela s'ajoute que si j'appelle glMapBuffer() à chaque frame alors le VBO perd tout son intérêt. Bref je ne vois aucune solution pour appliquer le frustum culling à des instances, si vous avec des conseils je suis preneur.

              Code du rendu :

              void Map::renduArbres(Camera *cam,const int index)
              {
                  glUseProgram(shaderArbres);    //Activation shader
              
                  const int attributPos=glGetAttribLocation(shaderArbres,"offsetInstance");   //Id attribut
              
                  glEnableClientState(GL_VERTEX_ARRAY);
                  glEnableClientState(GL_TEXTURE_COORD_ARRAY);
                  glEnableClientState(GL_NORMAL_ARRAY);
                  glEnableVertexAttribArray(attributPos);
              
                  glBindBuffer(GL_ARRAY_BUFFER,zones[index].arbre.bufferVRAM);    //Bindage VBO
              
                  glVertexPointer(3,GL_FLOAT,0,BUFFER_OFFSET(0));
                  glTexCoordPointer(2,GL_FLOAT,0,BUFFER_OFFSET(zones[index].arbre.coordVertices.size()*sizeof(float)));
                  glNormalPointer(GL_FLOAT,0,BUFFER_OFFSET(zones[index].arbre.coordVertices.size()*sizeof(float)+zones[index].arbre.coordTex.size()*sizeof(float)));
                  glVertexAttribPointer(attributPos,3,GL_FLOAT,0,0,BUFFER_OFFSET(zones[index].arbre.coordVertices.size()*sizeof(float)+zones[index].arbre.coordTex.size()*sizeof(float)+zones[index].arbre.normales.size()*sizeof(float)));
              
                  glVertexAttribDivisor(attributPos,1);    //Config attribut par instance
              
                  glBindTexture(GL_TEXTURE_2D,zones[index].arbre.textures[0]);    //Bindage texture
              
                  glDrawArraysInstanced(GL_QUADS,0,zones[index].arbre.coordVertices.size()/3,zones[index].arbre.attributsPositions.size());    Rendu
              
                  glBindBuffer(GL_ARRAY_BUFFER,0);
              
                  glDisableClientState(GL_VERTEX_ARRAY);
                  glDisableClientState(GL_TEXTURE_COORD_ARRAY);
                  glDisableClientState(GL_NORMAL_ARRAY);
                  glDisableVertexAttribArray(attributPos);
              }

              Vertex shader :

              #version 120
              
              attribute vec3 offsetInstance;
               
              void main(void)
              {
              	vec4 vertex=gl_Vertex;
              	vec3 normale=gl_Normal;
              	
              	vertex.x+=offsetInstance.x;
              	vertex.y+=offsetInstance.y;
              	vertex.z+=offsetInstance.z;
              	
              	gl_Position=gl_ModelViewProjectionMatrix*vertex;
              	
              	gl_TexCoord[0]=gl_MultiTexCoord0;
              	
              	gl_FrontColor=vec4(1.0,1.0,1.0,1.0);
              }

              -
              Edité par KevinGL 13 décembre 2020 à 21:03:25

              • Partager sur Facebook
              • Partager sur Twitter

              [OpenGL] Rendu lent

              × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
              × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
              • Editeur
              • Markdown