Comment écrire au mieux un moteur de voxel en C tout en gardant à l’esprit les performances

Je suis une armature dans OpenGl et pour cette raison, je cherche à apprendre uniquement les versions OpenGl modernes. Une fois les tutoriels de base terminés (rotation des cubes, par exemple), j’ai décidé de créer un programme basé sur voxel et traitant uniquement des cubes. Les objectives de ce programme étaient d’être rapide, d’utiliser une mémoire et une puissance de calcul limitées, et d’être dynamic afin que la taille de la carte puisse changer et que des blocs ne soient dessinés que si le bloc est rempli dans le tableau.

J’ai un VBO avec les sumts et les index d’un cube construit avec des sortingangles. Au début, si la fonction de rendu, je dis à OpenGl les shaders à utiliser, puis lie le VBO une fois terminé, j’exécute cette boucle

Dessiner une boucle de cube:

//The letter_max are the dimensions of the masortingx created to store the voxel status in // The method I use for getting and setting ensortinges in the map are very efficient so I have not included it in this example for(int z = -(z_max / 2); z < z_max - (z_max / 2); z++) { for(int y = -(y_max / 2); y < y_max - (y_max / 2); y++) { for(int x = -(x_max / 2); x < x_max - (x_max / 2); x++) { DrawCube(x, y, z); } } } 

Cube.c

 #include "include/Project.h" void CreateCube() { const Vertex VERTICES[8] = { { { -.5f, -.5f, .5f, 1 }, { 0, 0, 1, 1 } }, { { -.5f, .5f, .5f, 1 }, { 1, 0, 0, 1 } }, { { .5f, .5f, .5f, 1 }, { 0, 1, 0, 1 } }, { { .5f, -.5f, .5f, 1 }, { 1, 1, 0, 1 } }, { { -.5f, -.5f, -.5f, 1 }, { 1, 1, 1, 1 } }, { { -.5f, .5f, -.5f, 1 }, { 1, 0, 0, 1 } }, { { .5f, .5f, -.5f, 1 }, { 1, 0, 1, 1 } }, { { .5f, -.5f, -.5f, 1 }, { 0, 0, 1, 1 } } }; const GLuint INDICES[36] = { 0,2,1, 0,3,2, 4,3,0, 4,7,3, 4,1,5, 4,0,1, 3,6,2, 3,7,6, 1,6,5, 1,2,6, 7,5,6, 7,4,5 }; ShaderIds[0] = glCreateProgram(); ExitOnGLError("ERROR: Could not create the shader program"); { ShaderIds[1] = LoadShader("FragmentShader.glsl", GL_FRAGMENT_SHADER); ShaderIds[2] = LoadShader("VertexShader.glsl", GL_VERTEX_SHADER); glAttachShader(ShaderIds[0], ShaderIds[1]); glAttachShader(ShaderIds[0], ShaderIds[2]); } glLinkProgram(ShaderIds[0]); ExitOnGLError("ERROR: Could not link the shader program"); ModelMasortingxUniformLocation = glGetUniformLocation(ShaderIds[0], "ModelMasortingx"); ViewMasortingxUniformLocation = glGetUniformLocation(ShaderIds[0], "ViewMasortingx"); ProjectionMasortingxUniformLocation = glGetUniformLocation(ShaderIds[0], "ProjectionMasortingx"); ExitOnGLError("ERROR: Could not get shader uniform locations"); glGenVertexArrays(1, &BufferIds[0]); ExitOnGLError("ERROR: Could not generate the VAO"); glBindVertexArray(BufferIds[0]); ExitOnGLError("ERROR: Could not bind the VAO"); glEnableVertexAtsortingbArray(0); glEnableVertexAtsortingbArray(1); ExitOnGLError("ERROR: Could not enable vertex atsortingbutes"); glGenBuffers(2, &BufferIds[1]); ExitOnGLError("ERROR: Could not generate the buffer objects"); glBindBuffer(GL_ARRAY_BUFFER, BufferIds[1]); glBufferData(GL_ARRAY_BUFFER, sizeof(VERTICES), VERTICES, GL_STATIC_DRAW); ExitOnGLError("ERROR: Could not bind the VBO to the VAO"); glVertexAtsortingbPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(VERTICES[0]), (GLvoid*)0); glVertexAtsortingbPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(VERTICES[0]), (GLvoid*)sizeof(VERTICES[0].Position)); ExitOnGLError("ERROR: Could not set VAO atsortingbutes"); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, BufferIds[2]); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(INDICES), INDICES, GL_STATIC_DRAW); ExitOnGLError("ERROR: Could not bind the IBO to the VAO"); glBindVertexArray(0); } void DestroyCube() { glDetachShader(ShaderIds[0], ShaderIds[1]); glDetachShader(ShaderIds[0], ShaderIds[2]); glDeleteShader(ShaderIds[1]); glDeleteShader(ShaderIds[2]); glDeleteProgram(ShaderIds[0]); ExitOnGLError("ERROR: Could not destroy the shaders"); glDeleteBuffers(2, &BufferIds[1]); glDeleteVertexArrays(1, &BufferIds[0]); ExitOnGLError("ERROR: Could not destroy the buffer objects"); } void DrawCube(float x, float y, float z) { ModelMasortingx = IDENTITY_MATRIX; TranslateMasortingx(&ModelMasortingx, x, y, z); TranslateMasortingx(&ModelMasortingx, MainCamera.x, MainCamera.y, MainCamera.z); glUniformMasortingx4fv(ModelMasortingxUniformLocation, 1, GL_FALSE, ModelMasortingx.m); glUniformMasortingx4fv(ViewMasortingxUniformLocation, 1, GL_FALSE, ViewMasortingx.m); ExitOnGLError("ERROR: Could not set the shader uniforms"); glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, (GLvoid*)0); ExitOnGLError("ERROR: Could not draw the cube"); } 

Le vertex shader ne gère que la rotation et la transformation des sumts et le fragment shader ne traite que les couleurs pour lesquelles ils ne coûtent pas cher, ils ne constituent donc pas le goulot d’étranglement.

Comment ce code peut-il être amélioré pour obtenir un rendu plus efficace et tirer pleinement parti des fonctionnalités modernes d’OpenGL afin de réduire les frais généraux?

Post-scriptum Je ne cherche pas un livre, un outil ou une ressource hors site comme réponse. J’ai utilisé l’abattage au verso et le test de profondeur OpenGL pour essayer d’améliorer la vitesse, mais ils n’ont pas fait de grande différence, mais ils prennent toujours environ 50 ms. rendre un cadre et c’est trop pour une grid de voxels de 32 * 32 * 32.

Voici une capture d’écran de ce que je fais:

img

Et ici, lien vers le code complet:

  • GitHUB Voxel Viewer

C’est parce que vous faites cela de la mauvaise façon. Vous appelez 32^3 fois une fonction DrawCube qui représente une surcharge trop importante (surtout s’il modifie les masortingces). Cela prend beaucoup plus de temps que le rendu lui-même. Si possible, transmettez tous les éléments de rendu en même temps, par exemple sous forme de tableau de texture ou de VBO avec tous les cubes.

Vous devriez faire tout ce qui est dans les shaders (même les cubes …).

Vous n’avez pas spécifié la technique à utiliser pour le rendu de votre volume. Il y a beaucoup d’options ici, certaines sont habituellement utilisées:

  • tracé laser
  • La Coupe transversale
  • Diffusion sous la surface

Vos cubes sont-ils transparents ou solides? Si solide, pourquoi restituez-vous 32^3 cubes au lieu de la valeur visible ~32^2 ? Il existe des moyens de sélectionner uniquement les cubes visibles avant le rendu …

Mon meilleur choix serait d’utiliser le traçage de rayons et le rendu dans fragment shader (pas de maillage de cube juste dans le test de cube). Mais pour commencer, le plus simple à implémenter serait d’utiliser VBO avec tous les cubes à l’intérieur sous forme de maillage. Vous pouvez également n’avoir que des points dans le VBO et émettre des cubes dans la géomésortinge suivante: shader ….

Voici une collection de QA liés qui pourraient aider avec chacune des techniques …

tracé laser

  • LED Cube: dessiner une sphère 3D en C / C ++ ignore le système GL 1.0 et se concentre sur la fonction sphere()
  • Diffusion atmosphérique dans les GLSL (tracé analytique en volume)
  • raytrace à travers un maillage 3D Je voudrais utiliser ceci pour supprimer le maillage et l’intersection avec une simple transformation de coordonnées de cube afin d’obtenir les coordonnées de cube dans votre masortingce, ce sera beaucoup plus rapide …
  • La diffusion SSS sous la surface est destinée aux objects semi-transparents

Le traceur de rayons de volume est plus simple en amplitude que le lancer de rayons

La Coupe transversale

  • Section transversale 4D

C’est aussi une magnitude plus simple pour le volume et en 3D

Si vous avez besoin d’un sharepoint départ pour GLSL, jetez un œil à ceci:

  • exemple complet simple GL + VAO / VBO + GLSL + shaders en C ++

[Edit1] Exemple GLSL

Eh bien, je parviens à créer un exemple très simplifié de tracé de rayon volumésortingque GLSL sans réfractions ni reflections. L’idée est de projeter un rayon pour chaque pixel de la caméra dans le vertex shader et de tester la cellule de la grid de volume et le côté du cube de voxel touchés dans le fragment shader . Pour transmettre le volume, j’ai utilisé GL_TEXTURE_3D sans mipmaps et avec GL_NEAREST pour s,t,r . C’est à ça que ça ressemble:

capture d'écran

J’ai encapsulé le code côté CPU dans ce code C ++ / VCL :

 //--------------------------------------------------------------------------- //--- GLSL Raytrace system ver: 1.000 --------------------------------------- //--------------------------------------------------------------------------- #ifndef _raytrace_volume_h #define _raytrace_volume_h //--------------------------------------------------------------------------- const GLuint _empty_voxel=0x00000000; class volume { public: bool _init; // has been initiated ? GLuint txrvol; // volume texture at GPU side GLuint size,size2,size3;// volume size [voxel] and its powers GLuint ***data,*pdata; // volume 3D texture at CPU side reper eye; float aspect,focal_length; volume() { _init=false; txrvol=-1; size=0; data=NULL; aspect=1.0; focal_length=1.0; } volume(volume& a) { *this=a; } ~volume() { gl_exit(); } volume* operator = (const volume *a) { *this=*a; return this; } //volume* operator = (const volume &a) { ...copy... return this; } // init/exit void gl_init(); void gl_exit(); // render void gl_draw(); // for debug void glsl_draw(GLint ShaderProgram,List &log); // geometry void beg(); void end(); void add_box(int x,int y,int z,int rx,int ry,int rz,GLuint col); void add_sphere(int x,int y,int z,int r,GLuint col); }; //--------------------------------------------------------------------------- void volume::gl_init() { if (_init) return; _init=true; int x,y,z; GLint i; glGetIntegerv(GL_MAX_TEXTURE_SIZE,&i); size=i; i=32; if (size>i) size=i; // force 32x32x32 resolution size2=size*size; size3=size*size2; pdata =new GLuint [size3]; data =new GLuint**[size]; for (z=0;z &log) { GLint ix,i; GLfloat n[16]; AnsiSsortingng nam; const int txru_vol=0; // uniforms nam="aspect"; ix=glGetUniformLocation(ShaderProgram,nam.c_str()); if (ix<0) log.add(nam); else glUniform1f(ix,aspect); nam="focal_length"; ix=glGetUniformLocation(ShaderProgram,nam.c_str()); if (ix<0) log.add(nam); else glUniform1f(ix,focal_length); nam="vol_siz"; ix=glGetUniformLocation(ShaderProgram,nam.c_str()); if (ix<0) log.add(nam); else glUniform1i(ix,size); nam="vol_txr"; ix=glGetUniformLocation(ShaderProgram,nam.c_str()); if (ix<0) log.add(nam); else glUniform1i(ix,txru_vol); nam="tm_eye"; ix=glGetUniformLocation(ShaderProgram,nam.c_str()); if (ix<0) log.add(nam); else{ eye.use_rep(); for (int i=0;i<16;i++) n[i]=eye.rep[i]; glUniformMatrix4fv(ix,1,false,n); } glActiveTexture(GL_TEXTURE0+txru_vol); glEnable(GL_TEXTURE_3D); glBindTexture(GL_TEXTURE_3D,txrvol); // this should be a VBO glColor4f(1.0,1.0,1.0,1.0); glBegin(GL_QUADS); glVertex2f(-1.0,-1.0); glVertex2f(-1.0,+1.0); glVertex2f(+1.0,+1.0); glVertex2f(+1.0,-1.0); glEnd(); glActiveTexture(GL_TEXTURE0+txru_vol); glBindTexture(GL_TEXTURE_3D,0); glDisable(GL_TEXTURE_3D); } //--------------------------------------------------------------------------- void volume::beg() { if (!_init) return; for (int i=0;i=size) x1=size; y1=y0+ry; y0-=ry; if (y0<0) y0=0; if (y1>=size) y1=size; z1=z0+rz; z0-=rz; if (z0<0) z0=0; if (z1>=size) z1=size; for (z=z0;z<=z1;z++) for (y=y0;y<=y1;y++) for (x=x0;x<=x1;x++) data[z][y][x]=col; } //--------------------------------------------------------------------------- void volume::add_sphere(int cx,int cy,int cz,int r,GLuint col) { if (!_init) return; int x0,y0,z0,x1,y1,z1,x,y,z,xx,yy,zz,rr=r*r; x0=cx-r; x1=cx+r; if (x0<0) x0=0; if (x1>=size) x1=size; y0=cy-r; y1=cy+r; if (y0<0) y0=0; if (y1>=size) y1=size; z0=cz-r; z1=cz+r; if (z0<0) z0=0; if (z1>=size) z1=size; for (z=z0;z<=z1;z++) for (zz=z-cz,zz*=zz,y=y0;y<=y1;y++) for (yy=y-cy,yy*=yy,x=x0;x<=x1;x++) { xx=x-cx;xx*=xx; if (xx+yy+zz<=rr) data[z][y][x]=col; } } //--------------------------------------------------------------------------- #endif //--------------------------------------------------------------------------- 

Le volume est lancé et utilisé comme ceci:

 // [globals] volume vol; // [On init] // here init OpenGL and extentions (GLEW) // load/comstack/link shaders // init of volume data vol.gl_init(); vol.beg(); vol.add_sphere(16,16,16,10,0x00FF8040); vol.add_sphere(23,16,16,8,0x004080FF); vol.add_box(16,24,16,2,6,2,0x0060FF60); vol.add_box(10,10,20,3,3,3,0x00FF2020); vol.add_box(20,10,10,3,3,3,0x002020FF); vol.end(); // this copies the CPU side volume array to 3D texture // [on render] // clear screen what ever // bind shader vol.glsl_draw(shader,log); // log is list of ssortingngs I use for errors you can ignore/remove it from code // unbind shader // add HUD or what ever // refresh buffers // [on exit] vol.gl_exit(); // free what ever you need to like GL,... 

le vol.glsl_draw() rend le tout ... N'oubliez pas d'appeler gl_exit avant la fermeture de l'application.

Voici le vertex shader:

 //------------------------------------------------------------------ #version 420 core //------------------------------------------------------------------ uniform float aspect; uniform float focal_length; uniform mat4x4 tm_eye; layout(location=0) in vec2 pos; out smooth vec3 ray_pos; // ray start position out smooth vec3 ray_dir; // ray start direction //------------------------------------------------------------------ void main(void) { vec4 p; // perspective projection p=tm_eye*vec4(pos.x/aspect,pos.y,0.0,1.0); ray_pos=p.xyz; p-=tm_eye*vec4(0.0,0.0,-focal_length,1.0); ray_dir=normalize(p.xyz); gl_Position=vec4(pos,0.0,1.0); } //------------------------------------------------------------------ 

Et fragment:

 //------------------------------------------------------------------ #version 420 core //------------------------------------------------------------------ // Ray tracer ver: 1.000 //------------------------------------------------------------------ in smooth vec3 ray_pos; // ray start position in smooth vec3 ray_dir; // ray start direction uniform int vol_siz; // square texture x,y resolution size uniform sampler3D vol_txr; // scene mesh data texture out layout(location=0) vec4 frag_col; //--------------------------------------------------------------------------- void main(void) { const vec3 light_dir=normalize(vec3(0.1,0.1,-1.0)); const float light_amb=0.1; const float light_dif=0.5; const vec4 back_col=vec4(0.1,0.1,0.1,1.0); // background color const float _zero=1e-6; const vec4 _empty_voxel=vec4(0.0,0.0,0.0,0.0); vec4 col=back_col,c; const float n=vol_siz; const float _n=1.0/n; vec3 p,dp,dq,dir=normalize(ray_dir),nor=vec3(0.0,0.0,0.0),nnor=nor; float l=1e20,ll,dl; // Ray trace #define castray\ for (ll=length(p-ray_pos),dl=length(dp),p-=0.0*dp;;)\ {\ if (ll>l) break;\ if ((dp.x<-_zero)&&(px<0.0)) break;\ if ((dp.x>+_zero)&&(px>1.0)) break;\ if ((dp.y<-_zero)&&(py<0.0)) break;\ if ((dp.y>+_zero)&&(py>1.0)) break;\ if ((dp.z<-_zero)&&(pz<0.0)) break;\ if ((dp.z>+_zero)&&(pz>1.0)) break;\ if ((px>=0.0)&&(px<=1.0)\ &&(py>=0.0)&&(py<=1.0)\ &&(pz>=0.0)&&(pz<=1.0))\ {\ c=texture(vol_txr,p);\ if (c!=_empty_voxel){ col=c; l=ll; nor=nnor; break; }\ }\ p+=dp; ll+=dl;\ } // YZ plane voxels hits if (abs(dir.x)>_zero) { // compute start position aligned grid p=ray_pos; if (dir.x<0.0) { p+=dir*(((floor(px*n)-_zero)*_n)-ray_pos.x)/dir.x; nnor=vec3(+1.0,0.0,0.0); } if (dir.x>0.0) { p+=dir*((( ceil(px*n)+_zero)*_n)-ray_pos.x)/dir.x; nnor=vec3(-1.0,0.0,0.0); } // single voxel step dp=dir/abs(dir.x*n); // Ray trace castray; } // ZX plane voxels hits if (abs(dir.y)>_zero) { // compute start position aligned grid p=ray_pos; if (dir.y<0.0) { p+=dir*(((floor(py*n)-_zero)*_n)-ray_pos.y)/dir.y; nnor=vec3(0.0,+1.0,0.0); } if (dir.y>0.0) { p+=dir*((( ceil(py*n)+_zero)*_n)-ray_pos.y)/dir.y; nnor=vec3(0.0,-1.0,0.0); } // single voxel step dp=dir/abs(dir.y*n); // Ray trace castray; } // XY plane voxels hits if (abs(dir.z)>_zero) { // compute start position aligned grid p=ray_pos; if (dir.z<0.0) { p+=dir*(((floor(pz*n)-_zero)*_n)-ray_pos.z)/dir.z; nnor=vec3(0.0,0.0,+1.0); } if (dir.z>0.0) { p+=dir*((( ceil(pz*n)+_zero)*_n)-ray_pos.z)/dir.z; nnor=vec3(0.0,0.0,-1.0); } // single voxel step dp=dir/abs(dir.z*n); // Ray trace castray; } // final color and lighting output if (col!=back_col) col.rgb*=light_amb+light_dif*max(0.0,dot(light_dir,nor)); frag_col=col; } //--------------------------------------------------------------------------- 

Comme vous pouvez le voir, il est très similaire au Mesh Raytracer I lié ci-dessus (il a été créé à partir de celui-ci). Le traceur de rayons est simplement cette technique Doom scope en 3D .

J'ai utilisé mon propre moteur et ma propre VCL , vous devez donc le porter dans votre environnement (chaînes AnsiSsortingng et chargement / compilation / liaison de shader et list<> ) pour plus d'informations, voir le lien GL ... simple. De plus, je mélange du vieux GL 1.0 et du core GLSL, ce qui n’est pas recommandé (je voulais le garder aussi simple que possible), vous devriez donc convertir le Quad simple en VBO .

le glsl_draw() exige que les shaders soient liés et déjà ShaderProgramShaderProgram est l'id des shaders.

Le volume est mappé de (0.0,0.0,0.0) à (1.0,1.0,1.0) . La caméra est sous forme de masortingce directe tm_eye . La classe reper est juste la mienne masortingce de transformation 4x4 contenant à la fois une rep directe et une masortingce inv inverse quelque chose comme GLM .

La résolution du volume est définie dans gl_init() en gl_init() dur sur 32x32x32 . 32x32x32 simplement la ligne i=32 selon vos besoins.

Le code n'est pas optimisé, ni testé de manière intensive, mais semble fonctionner. Le minutage de la capture d'écran n'en dit pas plus car il y a une surcharge énorme pendant l'exécution, car cela fait partie d'une application plus grande. Seule la valeur tim est plus ou moins fiable mais ne change pas beaucoup avec des résolutions plus grandes (probablement jusqu'à ce qu'un goulot d'étranglement soit atteint, comme la taille de la mémoire ou la résolution de l'écran par rapport à la fréquence d'images). Voici une capture d'écran de l'application entière (pour que vous ayez une idée est en cours d'exécution):

IDE

Si vous effectuez des appels de tirage séparés et appelez l’exécution du shader pour chaque cube spécifique qui va être une perte de performance énorme. Je recommanderais sans hésiter l’instanciation – de cette façon, votre code peut avoir un seul appel à dessiner et tous les cubes seront rendus.

Recherchez la documentation de glDrawElementsInstanced, mais cette approche signifie également que vous devez disposer d’un “tampon” de masortingces, un pour chaque cube de voxel, et que vous devrez accéder à chacun d’eux dans le shader à l’aide de gl_InstanceID pour indexer dans la masortingce correcte.

En ce qui concerne le tampon de profondeur, le rendu sera économique si les masortingces de cube sont en quelque sorte sortingées avant-arrière par la caméra, de sorte que le test de profondeur précoce z présente des avantages en termes de performances. Cube de voxel.