Как лучше всего написать воксельный движок на C с учетом производительности
Я - арматура в OpenGl, и поэтому я стараюсь изучать только современные OpenGl 4.x. После того, как я закончил базовые уроки (например, вращающиеся кубы), я решил попробовать создать программу на основе вокселей, работающую исключительно с кубами. Цели этой программы заключались в том, чтобы быть быстрым, использовать ограниченную мощность ЦП и память и быть динамичным, чтобы размер карты мог изменяться, и блоки будут отображаться только в том случае, если в массиве указано, что блок заполнен.
У меня есть один VBO с вершинами и индексами куба, построенного из треугольников. Вначале, если функция рендеринга, я говорю OpenGl, что шейдеры должны использовать, а затем связываю VBO, как только это завершится, я выполняю этот цикл
Draw Cube Loop:
//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, чтобы уменьшить накладные расходы?
PS Я не ищу книгу, инструмент или сторонний ресурс в качестве ответа. Я использовал выборку задних поверхностей и тест глубины OpenGL, чтобы попытаться улучшить скорость, однако они не принесли существенной разницы, это все еще занимает ~50 мс. для рендеринга кадра, и это слишком много для сетки вокселей 32*32*32.
Вот скриншот того, что я делаю:
А вот ссылка на полный код:
2 ответа
Это потому, что вы делаете это неправильно. Ты звонишь 32^3
раз какая-то функция DrawCube
это слишком большие накладные расходы (особенно если это меняет матрицы). Скорее всего, это займет гораздо больше времени, чем сам рендеринг. Вы должны передать все рендеринг сразу, если это возможно, например, как массив текстур или VBO со всеми кубами.
Вы должны делать все вещи внутри шейдеров (даже кубы...).
Вы не указали, какую технику вы хотите использовать для рендеринга вашего объема. Здесь есть много вариантов, некоторые из которых обычно используются:
- трассировка лучей
- Поперечное сечение
- Подповерхностное рассеяние
Ваши кубики прозрачные или сплошные? Если твердо, почему вы делаете 32^3
кубики вместо только видимых ~32^2
? Есть способы, как выбрать только видимые кубы перед рендерингом...
Лучше всего было бы использовать трассировку лучей и рендеринг внутри шейдерного фрагмента (без кубовых сеток только внутри кубического теста). Но для начала проще реализовать VBO со всеми кубами внутри сетки. Вы также можете иметь только точки в VBO и испускать кубы в последнем геометрическом шейдере....
Вот некоторая коллекция моих связанных с ними QA, которые могут помочь с каждой из техник...
трассировка лучей
- LED Cube: рисование трехмерной сферы в C/C++ игнорирует GL 1.0 и фокусируется на
sphere()
функция - Атмосферное рассеяние в GLSL (аналитическая объемная трассировка лучей)
- raytrace через трехмерную сетку Я бы использовал это, просто удалите сетку и пересечение с помощью простого преобразования координат куба, чтобы получить координаты куба в вашей матрице будет намного быстрее...
- SSS подповерхностное рассеяние это для полупрозрачного материала
Объемная трассировка лучей на порядок проще, чем сетчатая трассировка лучей.
Поперечное сечение
Это также намного проще для объема и в 3D...
Если вам нужна начальная точка для GLSL, взгляните на это:
[Edit1] Пример GLSL
Что ж, мне удалось разрушить очень упрощенный пример объемной трассировки лучей GLSL без преломлений и отражений. Идея состоит в том, чтобы навести луч для каждого пикселя камеры в вершинном шейдере и проверить, какая ячейка сетки объема и сторона воксельного куба попали внутрь фрагментного шейдера. Чтобы передать объем, который я использовал GL_TEXTURE_3D
без мипмапов и с GL_NEAREST
за s,t,r
, Вот как это выглядит:
Я инкапсулировал код на стороне процессора в этот код 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, перенесенная в 3D.
Я использовал свой собственный движок и VCL, поэтому вам нужно перенести его в вашу среду (AnsiString
загрузка / компиляция / связывание строк и шейдеров и list<>
) для получения дополнительной информации см. простую ссылку GL... Также я смешиваю старый GL 1.0 и основной GLSL материал, который не рекомендуется (я хотел, чтобы он был как можно проще), поэтому вы должны конвертировать один Quad
в VBO.
glsl_draw()
требует, чтобы шейдеры были связаны и связаны уже где ShaderProgram
это идентификатор шейдеров.
Объем сопоставлен с (0.0,0.0,0.0)
в (1.0,1.0,1.0)
, Камера в виде прямой матрицы tm_eye
, reper
класс - только моя матрица преобразования 4x4, держащая оба прямых rep
и обратный inv
матрица что-то вроде GLM.
Разрешение громкости устанавливается в gl_init()
жестко 32x32x32
так что просто поменяй строчку i=32
к тому, что вам нужно.
Код не оптимизирован и не подвергается тщательному тестированию, но выглядит так, как будто он работает. Времена на скриншоте говорят о многом, поскольку во время выполнения возникают огромные накладные расходы, так как я представляю это как часть более крупного приложения. Только tim
значение более или менее надежно, но не сильно изменяется при больших разрешениях (вероятно, до тех пор, пока какое-то узкое место не будет достигнуто, например, размер памяти или разрешение экрана в зависимости от частоты кадров). Вот снимок экрана всего приложения (так что у вас есть представление о том, что еще работает):
Если вы выполняете отдельные вызовы отрисовки и запускаете выполнение шейдера для каждого конкретного куба, это будет огромной потерей производительности. Я бы определенно рекомендовал создание экземпляров - таким образом, ваш код может иметь один вызов отрисовки, и все кубы будут обработаны.
Посмотрите документацию для glDrawElementsInstanced, однако этот подход также означает, что у вас должен быть "буфер" матриц, по одному для каждого воксельного куба, и вам нужно будет обращаться к каждой в шейдере, используя gl_InstanceID для индексации в правильной матрице.
Что касается буфера глубины, то при рендеринге будет сохранена экономия, если матрицы кубов будут каким-либо образом отсортированы спереди назад к камере, так что есть преимущество в производительности при неудачном тесте глубины на раннем этапе z для любого возможного фрагмента, который находится за уже Воксельный куб.