实例化(instancing)或者多实例渲染(instancd rendering)是一种连续执行多条相同渲染命令的方法。并且每个命令的所产生的渲染结果都会有轻微的差异。是一种非常有效的,实用少量api调用来渲染大量几何体的方法。OpenGL提供多种机制,允许着色器对不同渲染实例赋予不同的顶点属性。
几个简单的多实例渲染命令:
1、void glDrawArraysInstanced( GLenum mode, GLint first, GLsizei count, GLsizei primCount )
该函数是glDrawArrays()的多实例版本,参数完全等价,只是多了个primCount,该参数用于设置渲染实例个数。
2、void glDrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, void* indices, GLsizei primcount )
该函数是glDrawElements()的多实例版本,同样只是多了个primCount参数而已,同样是用于设置渲染实例个数。
3、void glDrawElementsInstancedBaseVertex( GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instanceCount, GLuint baseVertex )
该函数是glDrawElementsBaseVertex()的多实例版本,instanceCount表示渲染的实例数目。
多实例渲染顶点属性控制:
1、void glVertexAttribDivisor( GLenum index, GLuint divisor )
index对应于着色器中输入变量的location。divisor:表示顶点属性的更新频率,每隔多少个实例将重新设置实例的该属性,例如设置为1,那么每个实例的属性都不一样,设置为2则每两个实例相同,3则每三个实例改变属性。而该属性的属性数组大小将为(instance/divisor),instance为之前设置的渲染实例数(primCount),假设在多实例渲染中改变实例的颜色,设divisor为2,instance为100,颜色数组至少为100/2
= 50组rgba数据,才能保证每个实例都有自己的颜色值,不然将是黑漆漆的。最后如果divisor设置为0,将代表是非实例化,渲染的结果是,所有实例都是黑漆漆的,可能这个黑漆漆的结果也不是必然的,我猜想的是这时候着色器的输入变量vec4 color为默认的(0.0,0.0,0.0,1.0)并没有设置它的值,所以是黑色的。
顶点着色器分析:
#version 410
//输入变量position,顶点坐标
layout (location = 0) in vec4 position;
//normal顶点法线计算
layout (location = 1) in vec3 normal;
//顶点颜色
layout (location = 2) in vec4 color;
//特别注意,这里设置的mat4类型的输入变量,location为3,但是一个mat4类型会占据连续的4个位置
//因此model_matrix占据了3,4,5,6四个索引位置。
layout (location = 3) in mat4 model_matrix;
//在程序渲染过程是常量的视图矩阵,和投影矩阵(just这个程序是常量)
uniform mat4 view_matrix;
uniform mat4 projection_matrix;
//输入变量,一个简单的结构体,法线,和颜色。
out VERTEX
{
vec3 normal;
vec4 color;
} vertex;
void main(void)
{
//计算模型视图矩阵
mat4 model_view_matrix = view_matrix * model_matrix;
//计算顶点坐标
gl_Position = projection_matrix * (model_view_matrix * position);
//计算法线和颜色,并输出
vertex.normal = mat3(model_view_matrix) * normal;
vertex.color = color;
}
片元着色器分析:
#version 410
//片元着色器的输出
layout (location = 0) out vec4 color;
//和顶点着色器几乎一样的片元着色器输入,in/out必须是匹配的。
in VERTEX
{
vec3 normal;
vec4 color;
} vertex;
void main(void)
{
//结合法线计算最终的颜色
color = vertex.color * (0.1 + abs(vertex.normal.z)) + vec4(0.8, 0.9, 0.7, 1.0) * pow(abs(vertex.normal.z), 40.0);
}
应用程序代码:
代码并没有太多的分析,主要是用了前面几个函数,进行多实例渲染的设置,代码在关键的地方都有或多或少的注释。在这里把尽可能把代码贴全。代码中用到了vbm格式的模型数据,大概是书者自己定义的一种格式,在源代码中vbm管理类历经变迁,新版本还不能读旧版本的数据。
在这里顺便提供第八版源代码的下载地址:http://www.opengl-redbook.com/ 应该可以轻易下到。感谢作者们的辛勤劳动
#include "instancing_1.h" #include "vutils.h" #include "vmath.h" #include "vbm_ch3_intancing1.h" namespace instancing1 { float shade_aspect = 800 / 600; GLuint color_buffer; GLuint model_matrix_buffer; GLuint render_prog; GLuint model_matrix_loc; GLuint view_matrix_loc; GLuint projection_matrix_loc; VBObject object; #define INSTANCE_COUNT 100 GLenum gl_err = 0; static const GLubyte* errorStr = NULL; }; using namespace instancing1; void instancing1Init() { int n; //创建着色器程序 render_prog = glCreateProgram(); //着色器字符串 static const char render_vs[] = "#version 410\n" "\n" "// 'position' and 'normal' are regular vertex attributes\n" "layout (location = 0) in vec4 position;\n" "layout (location = 1) in vec3 normal;\n" "\n" "// Color is a per-instance attribute\n" "layout (location = 2) in vec4 color;\n" "\n" "// model_matrix will be used as a per-instance transformation\n" "// matrix. Note that a mat4 consumes 4 consecutive locations, so\n" "// this will actually sit in locations, 3, 4, 5, and 6.\n" "layout (location = 3) in mat4 model_matrix;\n" "\n" "// The view matrix and the projection matrix are constant across a draw\n" "uniform mat4 view_matrix;\n" "uniform mat4 projection_matrix;\n" "\n" "// The output of the vertex shader (matched to the fragment shader)\n" "out VERTEX\n" "{\n" " vec3 normal;\n" " vec4 color;\n" "} vertex;\n" "\n" "// Ok, go!\n" "void main(void)\n" "{\n" " // Construct a model-view matrix from the uniform view matrix\n" " // and the per-instance model matrix.\n" " mat4 model_view_matrix = view_matrix * model_matrix;\n" "\n" " // Transform position by the model-view matrix, then by the\n" " // projection matrix.\n" " gl_Position = projection_matrix * (model_view_matrix * position);\n" " // Transform the normal by the upper-left-3x3-submatrix of the\n" " // model-view matrix\n" " vertex.normal = mat3(model_view_matrix) * normal;\n" " // Pass the per-instance color through to the fragment shader.\n" " vertex.color = color;\n" "}\n"; static const char render_fs[] = "#version 410\n" "\n" "layout (location = 0) out vec4 color;\n" "\n" "in VERTEX\n" "{\n" " vec3 normal;\n" " vec4 color;\n" "} vertex;\n" "\n" "void main(void)\n" "{\n" " color = vertex.color * (0.1 + abs(vertex.normal.z)) + vec4(0.8, 0.9, 0.7, 1.0) * pow(abs(vertex.normal.z), 40.0);\n" "}\n"; //shader创建,编译,装载 vglAttachShaderSource( render_prog, GL_VERTEX_SHADER, render_vs ); vglAttachShaderSource( render_prog, GL_FRAGMENT_SHADER, render_fs ); //连接着色器成为可用程序 glLinkProgram(render_prog); //激活着色器 glUseProgram(render_prog); //获取视图矩阵位置 view_matrix_loc = glGetUniformLocation(render_prog, "view_matrix"); //获取投影矩阵位置 projection_matrix_loc = glGetUniformLocation(render_prog, "projection_matrix"); //加载vbm模型文件 //@pram 1路径、2顶点location 3法线location、4纹理location(颜色) object.LoadFromVBM("../8edlib/media/armadillo_low.vbm", 0, 1, 2); //绑定顶点数组 object.BindVertexArray(); //获取顶点着色器in变量location int position_loc = glGetAttribLocation( render_prog, "position" ); int normal_loc = glGetAttribLocation( render_prog, "normal" ); int color_loc = glGetAttribLocation( render_prog, "color" ); int matrix_loc = glGetAttribLocation( render_prog, "model_matrix"); //每个实例的颜色数组 vmath::vec4 colors[INSTANCE_COUNT]; for (n = 0; n < INSTANCE_COUNT; n++ ) { float a = float(n) / 4.0f; float b = float(n) / 5.0f; float c = float(n) / 6.0f; colors[n][0] = 0.5f + 0.25f * (sinf(a + 1.0f) + 1.0f ); colors[n][1] = 0.5f + 0.25f * (sinf(b + 2.0f) + 1.0f ); colors[n][2] = 0.5f + 0.25f * (sinf(c + 3.0f) + 1.0f ); colors[n][3] = 1.0f; } //缓存对象 glGenBuffers(1, &color_buffer); glBindBuffer(GL_ARRAY_BUFFER, color_buffer);//3个工作。 glBufferData( GL_ARRAY_BUFFER, sizeof(colors), colors, GL_DYNAMIC_DRAW );//动态改变用于绘制的数据 glVertexAttribPointer( color_loc, 4, GL_FLOAT, GL_FALSE, 0, NULL ); glEnableVertexAttribArray( color_loc ); //!!!启用顶点属性多实例属性,每个实例读取一次颜色值 glVertexAttribDivisor( color_loc, 1); //模型矩阵数据 glGenBuffers(1, &model_matrix_buffer ); glBindBuffer( GL_ARRAY_BUFFER, model_matrix_buffer ); //NULL保留内存 glBufferData( GL_ARRAY_BUFFER, INSTANCE_COUNT * sizeof(vmath::mat4), NULL, GL_DYNAMIC_DRAW ); for (int i = 0; i < 4; i++ ) { glVertexAttribPointer(matrix_loc + i, 4, GL_FLOAT, GL_FALSE, sizeof(vmath::mat4), (void *)(sizeof(vmath::vec4)* i)); glEnableVertexAttribArray( matrix_loc + i ); glVertexAttribDivisor( matrix_loc + i, 1 ); } //解除vao绑定,至于为什么要这句呢,我的理解是:这是个好习惯,用完之后吧OpenGL还原默认状态。 glBindVertexArray(0); } static inline int min( int a, int b ) { return a < b ? a : b; } void instancing1Display() { float t = float(GetTickCount() & 0x3FFF) / float(0x3FFF); int n; //清屏 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); // glEnable( GL_CULL_FACE ); glEnable( GL_DEPTH_TEST ); glDepthFunc( GL_LEQUAL ); //绑定vbo,修改数据 glBindBuffer( GL_ARRAY_BUFFER, model_matrix_buffer ); //获取当前vbo的OpenGL指针 vmath::mat4* matrices = (vmath::mat4*)glMapBuffer( GL_ARRAY_BUFFER, GL_WRITE_ONLY ); for (n = 0; n < INSTANCE_COUNT; n++ ) { float a = 50.0f * float(n) / 4.0f; float b = 50.0f * float(n) / 5.0f; float c = 50.0f * float(n) / 6.0f; matrices[n] = vmath::rotate(a + t * 360.0f, 1.0f, 0.0f, 0.0f) * vmath::rotate(a + t * 360.0f, 0.0f, 1.0f, 0.0f) * vmath::rotate(a + t * 360.0f, 0.0f, 0.0f, 1.0f) * vmath::translate(10.0f + a, 40.0f + b, 50.0f + c);//平移,再旋转 } //数据操作完毕,解除映射!必须 glUnmapBuffer( GL_ARRAY_BUFFER ); //确认激活需要的着色器程序 glUseProgram( render_prog ); //生成视图矩阵和投影矩阵 vmath::mat4 view_matrix(vmath::translate(0.0f, 0.0f, -1500.0f) * vmath::rotate(t * 360.0f * 2.0f, 0.0f, 1.0f, 0.0f)); vmath::mat4 projection_matrix(vmath::frustum(-1.0f, 1.0f, -shade_aspect, shade_aspect, 1.0f, 5000.0f)); //设置视图矩阵和投影矩阵 glUniformMatrix4fv( view_matrix_loc, 1, GL_FALSE, view_matrix );//这次你就没写错参数,能不能不坑爹,之前我都以为自己智商出问题了。 glUniformMatrix4fv( projection_matrix_loc, 1, GL_FALSE, projection_matrix ); //渲染多个实例 object.Render( 0, INSTANCE_COUNT ); //能不能不坑爹。。后面的vbm居然不兼容前面的vbm..... GLenum error = glGetError();// const GLubyte* errorStr = gluErrorString(error); //这个几个意思。。。lookat只是生成了一个矩阵而已。。。 //vmath::lookat( vmath::vec3(0.0f, 0.0f, 0.0f), vmath::vec3(1.0f, 0.0f, 0.0f), vmath::vec3(0.0f, 1.0f, 0.0f) ); //摄像机?????????/ } void instancing1Update(float) { }
另一个多实例渲染实例,纹理打包,着色器内置变量gl_InstanceID使用:
实例计数器gl_InstanceID:
当前实例的索引值可以再顶点着色器中通过内置变量gl_InstanceID变量获得。该变量被声明为一个整数,初始为0,每个实例被渲染之后,他会加1.他总是存在于顶点着色器中,即使当前没有启用多实例特性,此时他的值保持0.gl_InstanceID可以作为uniform数组的索引使用,也可以作为纹理查找的参数,或者作为某个分析函数的输入,等等。
新实例分析:
新示例实现了上一个实例的相同画面,只是程序的实现方式不一样。在这里用到了纹理缓存对象,对于我又是一个未使用过的特性,在接受新东西的时候总不会觉得乏味。
...................虽然不觉得乏味,但还是会困,明天还得上班QAQ,To Be Continue~~~~~