我是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");
}
顶点着色器只处理顶点的旋转和变换,片段着色器只处理它们运行起来不贵的颜色,因此它们不是瓶颈。
如何改进此代码以提高效率并充分利用现代OpenGL功能来降低开销?
P.S。 我不是在寻找一本书或一个工具或一个非现场资源作为答案我已经使用了背面剔除和OpenGL深度测试来尝试提高速度但是它们还没有产生显着的差异它仍然需要〜渲染帧的时间为50ms,对于32 * 32 * 32的体素网格来说太多了。
这是我正在做的截图:
这里链接到完整代码:
答案 0 :(得分:5)
那是因为你以错误的方式做到了这一点。你正在调用32^3
次函数DrawCube
,这是一个太大的开销(特别是如果它改变了矩阵)。这比渲染本身花费的时间更多。您应该尽可能一次传递所有渲染内容,例如作为纹理数组或 VBO 包含所有立方体。
你应该在着色器中做所有的事情(甚至是立方体......)。
您没有指定要用于渲染卷的技术。这里有很多选项可供选择:
您的立方体是透明还是实心?如果为实体,为什么要渲染32^3
多维数据集而不是仅显示可见~32^2
?有很多方法可以在渲染之前只选择可见的立方体......
我最好的选择是使用光线追踪并在片段着色器内渲染(在立方体测试中没有立方体网格物体)。但对于初学者来说,更容易实现的是使用 VBO 将所有立方体作为网格。您还可以在 VBO 中获得点,并在几何着色器后面发出多维数据集....
这里有一些相关的QAs集合,可以帮助解决每种技术......
光线追踪
sphere()
功能体积光线跟踪器比网格光线跟踪更简单。
横截面
对于音量和 3D ......
,这也是一个更简单的幅度如果你需要一些 GLSL 的起点,请看一下:
[Edit1] GLSL示例
好吧,我设法突破了非常简化的 GLSL 体积光线追踪示例,没有折射或反射。我们的想法是在顶点着色器中为相机的每个像素投射光线,并测试它在片段着色器内击中的体素网格单元和体素立方体的一侧。为了传递我使用GL_TEXTURE_3D
而不使用mipmap而GL_NEAREST
用于s,t,r
的卷。这就是它的样子:
我将 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<AnsiString> &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<size;z++){ data[z] =new GLuint* [size];
for (y=0;y<size;y++){ data[z][y]=pdata+(z*size2)+(y*size); }}
glGenTextures(1,&txrvol);
}
//---------------------------------------------------------------------------
void volume::gl_exit()
{
if (!_init) return; _init=false;
int x,y,z;
glDeleteTextures(1,&txrvol);
size=0; size2=0; size3=0;
for (z=0;z<size;z++){ if (data[z]) delete[] data[z]; }
if (data ) delete[] data; data =NULL;
if (pdata ) delete[] pdata; pdata=NULL;
}
//---------------------------------------------------------------------------
void volume::gl_draw()
{
int x,y,z;
float xx,yy,zz,voxel_size=1.0/float(size);
reper rep;
double v0[3],v1[3],v2[3],p[3],n[3],q[3],r,sz=0.5;
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glPerspective(2.0*atanxy(focal_length,1.0)*rad,1.0,0.1,100.0);
glScalef(aspect,1.0,1.0);
// glGetDoublev(GL_PROJECTION_MATRIX,per);
glScalef(1.0,1.0,-1.0);
glMatrixMode(GL_MODELVIEW);
glPushMatrix(); rep=eye;
rep.lpos_set(vector_ld(0.0,0.0,-focal_length));
rep.use_inv(); glLoadMatrixd(rep.inv);
glBegin(GL_POINTS);
for (zz=-0.0,z=0;z<size;z++,zz+=voxel_size)
for (yy=-0.0,y=0;y<size;y++,yy+=voxel_size)
for (xx=-0.0,x=0;x<size;x++,xx+=voxel_size)
if (data[z][y][x]!=_empty_voxel)
{
glColor4ubv((BYTE*)(&data[z][y][x]));
glVertex3f(xx,yy,zz);
}
glEnd();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
}
//---------------------------------------------------------------------------
void volume::glsl_draw(GLint ShaderProgram,List<AnsiString> &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<size3;i++) pdata[i]=_empty_voxel;
}
//---------------------------------------------------------------------------
void volume::end()
{
if (!_init) return;
int z;
// volume texture init
glEnable(GL_TEXTURE_3D);
glBindTexture(GL_TEXTURE_3D,txrvol);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R,GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_MODULATE);
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, size, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, pdata);
glDisable(GL_TEXTURE_3D);
}
//---------------------------------------------------------------------------
void volume::add_box(int x0,int y0,int z0,int rx,int ry,int rz,GLuint col)
{
if (!_init) return;
int x1,y1,z1,x,y,z;
x1=x0+rx; x0-=rx; if (x0<0) x0=0; if (x1>=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()
呈现内容......不要忘记在关闭应用之前致电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)&&(p.x<0.0)) break;\
if ((dp.x>+_zero)&&(p.x>1.0)) break;\
if ((dp.y<-_zero)&&(p.y<0.0)) break;\
if ((dp.y>+_zero)&&(p.y>1.0)) break;\
if ((dp.z<-_zero)&&(p.z<0.0)) break;\
if ((dp.z>+_zero)&&(p.z>1.0)) break;\
if ((p.x>=0.0)&&(p.x<=1.0)\
&&(p.y>=0.0)&&(p.y<=1.0)\
&&(p.z>=0.0)&&(p.z<=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(p.x*n)-_zero)*_n)-ray_pos.x)/dir.x; nnor=vec3(+1.0,0.0,0.0); }
if (dir.x>0.0) { p+=dir*((( ceil(p.x*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(p.y*n)-_zero)*_n)-ray_pos.y)/dir.y; nnor=vec3(0.0,+1.0,0.0); }
if (dir.y>0.0) { p+=dir*((( ceil(p.y*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(p.z*n)-_zero)*_n)-ray_pos.z)/dir.z; nnor=vec3(0.0,0.0,+1.0); }
if (dir.z>0.0) { p+=dir*((( ceil(p.z*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非常相似(它是从它完成的)。光线跟踪器就是这个Doom technique移植到 3D 。
我使用自己的引擎和 VCL ,因此您需要将其移植到您的环境(AnsiString
字符串和着色器加载/编译/链接和list<>
)以获取更多信息看到简单的 GL ...链接。我还混合了旧的 GL 1.0 和核心的 GLSL 这些不推荐的东西(我希望尽可能简单),所以你应该转换单{{1} } VBO 。
Quad
要求着色器已链接并绑定,其中glsl_draw()
是着色器的ID。
该卷已从ShaderProgram
映射到(0.0,0.0,0.0)
。相机采用直接矩阵(1.0,1.0,1.0)
的形式。 tm_eye
类只是我的4x4变换矩阵,包含直接reper
和反rep
矩阵,类似 GLM 。
音量分辨率设置为inv
硬编码为gl_init()
,因此只需将行32x32x32
更改为您需要的内容。
代码未经优化或经过严格测试,但看起来很有效。屏幕截图中的时间并不能说明运行时期间存在巨大的开销,因为我将此作为较大应用程序的一部分。只有i=32
值或多或少可靠,但在更大的分辨率下没有太大的变化(可能直到某些瓶颈被击中,如内存大小或屏幕分辨率与帧速率)这里是整个应用程序的截图(所以你有知道还在运行什么):
答案 1 :(得分:2)
如果您正在进行单独的绘制调用并为每个特定的多维数据集调用着色器执行,这将是一个巨大的性能损失。我肯定会推荐实例化 - 这样你的代码就可以进行一次绘制调用,并且所有的立方体都将被渲染。
查找glDrawElementsInstanced的文档,但是这种方法也意味着你必须有一个矩阵的“缓冲区”,每个体素立方体一个,并且必须使用gl_InstanceID访问着色器中的每一个以索引到正确的基质
关于深度缓冲区,如果立方体矩阵以某种方式从摄像机前后排序,那么渲染将会节省,因此早期z深度测试的性能优势对于任何可能的片段而言都是失败的在已经渲染的体素立方体后面。