如何在C中最好地编写体素引擎并考虑到性能

我是OpenGl中的一个电枢,因此我只想学习现代OpenGl 4.x的东西。 一旦我完成了基本教程(例如旋转立方体),我决定尝试创建一个基于体素的程序,仅处理立方体。 这个程序的目标是快速,使用有限的CPU功率和内存,并且是动态的,因此地图大小可以改变,只有在数组中它表示块被填充时才会绘制块。

我有一个VBO,其中包含由三角形构成的立方体的顶点和索引。 一开始,如果渲染函数我告诉OpenGl要使用的着色器,然后在完成后绑定VBO我执行此循环

绘制立方体循环:

//The letter_max are the dimensions of the matrix created to store the voxel status in // The method I use for getting and setting entries 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"); ModelMatrixUniformLocation = glGetUniformLocation(ShaderIds[0], "ModelMatrix"); ViewMatrixUniformLocation = glGetUniformLocation(ShaderIds[0], "ViewMatrix"); ProjectionMatrixUniformLocation = glGetUniformLocation(ShaderIds[0], "ProjectionMatrix"); 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"); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); ExitOnGLError("ERROR: Could not enable vertex attributes"); 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"); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(VERTICES[0]), (GLvoid*)0); glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(VERTICES[0]), (GLvoid*)sizeof(VERTICES[0].Position)); ExitOnGLError("ERROR: Could not set VAO attributes"); 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) { ModelMatrix = IDENTITY_MATRIX; TranslateMatrix(&ModelMatrix, x, y, z); TranslateMatrix(&ModelMatrix, MainCamera.x, MainCamera.y, MainCamera.z); glUniformMatrix4fv(ModelMatrixUniformLocation, 1, GL_FALSE, ModelMatrix.m); glUniformMatrix4fv(ViewMatrixUniformLocation, 1, GL_FALSE, ViewMatrix.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"); } 

顶点着色器仅处理顶点的旋转和变换,片段着色器仅处理颜色,它们运行起来并不昂贵,因此它们不是瓶颈。

如何改进此代码以提高效率并充分利用现代OpenGLfunction来降低开销?

PS我不是在寻找一本书或一个工具或一个非现场资源作为答案我使用背面剔除和OpenGL深度测试试图提高速度但是它们没有产生显着的差异它仍然需要~50ms渲染帧,这对于32 * 32 * 32的体素网格来说太多了。

这是我正在做的截图:

IMG

这里链接到完整代码:

  • GitHUB体素查看器

那是因为你以错误的方式做到了这一点。 你正在调用一些函数DrawCube 32^3次,这是一个太大的开销(特别是如果它改变了矩阵)。 这比渲染本身花费的时间更多。 如果可能,您应该立即传递所有渲染内容,例如作为纹理数组或包含所有立方体的VBO

你应该在着色器(甚至是立方体……)中做所有的事情。

您没有指定要用于渲染卷的技术。 这里有很多选项,通常用于:

  • 光线追踪
  • 横截面
  • 次表面散射

你的立方体是透明还是坚固? 如果坚固你为什么渲染32^3立方体而不是只有可见~32^2 ? 有几种方法可以在渲染之前仅选择可见的立方体…

我最好的选择是在片段着色器中使用光线跟踪和渲染(在立方体测试中没有立方体网格物体)。 但对于初学者来说,更容易实现的是使用VBO,其中所有立方体都是网格。 你也可以在VBO中只有点,并在几何着色器后面发射立方体….

这里有一些我相关的QAs集合可以帮助解决每种技术……

光线追踪

  • LED立方体:用C / C ++绘制3D球体忽略GL 1.0的东西并专注于sphere()函数
  • GLSL中的大气散射(分析体积光线追踪)
  • 通过3D网格光线追踪我会用这个只是删除网格和交叉点的东西与简单的立方坐标变换,以获得矩阵中的立方坐标将更快…
  • SSS子表面散射这是半透明的东西

体积光线跟踪器比网格光线跟踪更简单。

横截面

  • 4D横截面

这对于音量和3D来说也更简单……

如果您需要GLSL的一些起点,请看一下:

  • C ++中简单完整的GL + VAO / VBO + GLSL +着色器示例

[Edit1] GLSL示例

好吧,我设法完成了GLSL体积光线追踪的简化示例,没有折射或reflection。 我们的想法是在顶点着色器中为相机的每个像素投射光线,并测试它在片段着色器中击中的体素网格单元和体素立方体的一侧。 为了传递卷,我使用GL_TEXTURE_3D没有GL_TEXTURE_3D和用于s,t,r GL_NEAREST 。 这是它的样子:

截图

我将CPU端代码封装到此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]; AnsiString 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 //--------------------------------------------------------------------------- 

卷启动和使用如下:

 // [globals] volume vol; // [On init] // here init OpenGL and extentions (GLEW) // load/compile/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 strings 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,... 

vol.glsl_draw()渲染东西......不要忘记在关闭app之前调用gl_exit

这里的顶点着色器:

 //------------------------------------------------------------------ #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); } //------------------------------------------------------------------ 

和碎片:

 //------------------------------------------------------------------ #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; } //--------------------------------------------------------------------------- 

正如您所看到的,它与我上面链接的Mesh Raytracer非常相似(它是从它完成的)。 光线跟踪器就是这种移植到3D的 Doom技术 。

我使用自己的引擎和VCL,因此您需要将它移植到您的环境( AnsiString字符串和着色器加载/编译/链接和list<> )以获取更多信息,请参阅简单的GL ...链接。 另外我混合旧的GL 1.0和核心GLSL的东西,这是不推荐的(我想保持它尽可能简单)所以你应该将单个Quad转换为VBO

glsl_draw()要求着色器已链接并绑定,其中ShaderProgram是着色器的id。

卷从(0.0,0.0,0.0)映射到(1.0,1.0,1.0) 。 相机采用直接矩阵tm_eyeforms。 reper类只是我的4x4变换矩阵,包含直接rep和逆inv矩阵,就像GLM一样。

体积分辨率在gl_init()设置为硬编码为32x32x32因此只需将线i=32更改为您需要的线。

代码未经优化或经过严格测试,但看起来很有效。 屏幕截图中的时间并不能说明运行时期间存在巨大的开销,因为我将此作为较大应用程序的一部分。 只有tim值或多或少可靠,但是在更大的分辨率下没有太大变化(可能直到某些瓶颈被打到像内存大小或屏幕分辨率与帧率一样)这里有整个应用程序的截图(所以你知道还有什么在跑):

IDE

如果您正在进行单独的绘制调用并为每个特定的多维数据集调用着色器执行,这将是一个巨大的性能损失。 我肯定会推荐实例化 – 这样你的代码可以有一个绘图调用,所有的立方体都会被渲染。

查找glDrawElementsInstanced的文档,但是这种方法也意味着你必须有一个矩阵的“缓冲区”,每个体素立方体一个,并且必须使用gl_InstanceID访问着色器中的每一个以索引到正确的矩阵。

关于深度缓冲区,如果立方体矩阵以某种方式从摄像机前后排序,那么渲染将会节省,因此早期z深度测试的性能优势对于已经存在的任何可能的片段都会失败渲染的体素立方体。