实例渲染Instanced Rendering
0.前言
在此之前红宝书已经介绍了不少关于缓存和绘制的命令,并给出了用同样的顶点绘制几个三角形的例程,但是此前的例程实现起来非常麻烦,每个三角形要指定一次变换矩阵,假如还要更换颜色的话还要增加相应语句。如果想绘制的物体成千上万,这么一一指定管理起来比较混乱,因此,OpenGL给出了一个新的命令类型:实例渲染——Instanced Rendering。
1.函数介绍:
函数原形:
void glDrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primCount);
函数说明:
绘制多个指定类型的原语
mode:绘制的原语类型
first,count:内部调用glDrawArrays()时的参数
primCount:绘制重复的次数
此函数内部有一个gl_InstanceID 计数器用来自增表示每个实例,实例每次都被渲染器当作一个新的实例。
例如一个模型实例有120个三角型,想绘制10个类似的此种模型,那么调用将会类似于:
glDrawArraysInstanced(GL_TRIANGLES, 0, 120, 10);
同样的,OpenGL提供了一些列的其他同类绘制命令:
void glDrawElementsInstanced(
GLenum mode,
GLsizei count,
GLenum type,
const void* indices,
GLsizei primCount);
void glDrawElementsInstancedBaseVertex(
GLenum mode,
GLsizei count,
GLenum type,
const void* indices,
GLsizei instanceCount,
GLuint baseVertex);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
目前看来,这几个命令与此前的绘制命令并无太大的差别,极其类似多重绘制命令,下面这个函数让他们产生了本质的区别:
函数原形:
void glVertexAttribDivisor(GLuint index, GLuint divisor);
函数说明:
指出index对应的索引数据在提供给着色器的时候,每隔多少个实例变换一次,如果设置为0则按照此前普通的形式,每个顶点提供一个新的数据。如果不是0,则每隔几个实例而不是顶点提供新的数据。
index:数组的索引
divisor:表示每多少个实例提供一次数据。
2.例3.9-3.12
书中给出了具体讲解的例子,这次例程错误比较少,下面首先给出运行结果然后给出注释的程序,此后给出分析讲解以及对书中程序的一些使用解释:
运行效果1(100个实例):
运行效果2(1000个实例,且加大了景深):
代码+注释:
#include "vapp.h"
#include "vutils.h"
#include "vmath.h"
#include "vbm.h"
#include <stdio.h>
using namespace vmath;
BEGIN_APP_DECLARATION(InstancingExample)
//这里用宏定义创建了一个类,继承的 VermillionApplication类具体可参见书中下载的代码
//定义了几个功能
virtual void Initialize(const char * title);
virtual void Display(bool auto_redraw);
virtual void Finalize(void);
virtual void Reshape(int width, int height);
//成员变量
float aspect;
//几个缓存的索引
GLuint color_buffer;
GLuint model_matrix_buffer;
//渲染器程序标号
GLuint render_prog;
//几个数据的索引
GLint model_matrix_loc;
GLint view_matrix_loc;
GLint projection_matrix_loc;
//产生了一个类,用于管理书中提供的模型
VBObject object;
//END_APP_DECLARATION()实际上就应为'};',不知为何在本例缺失
};//END_APP_DECLARATION()
//这里利用宏定义执行了很多操作,包括生成了main函数,后面会讲解
DEFINE_APP(InstancingExample, "Instancing Example")
//定义了绘制多少个实例
#define INSTANCE_COUNT 100
void InstancingExample::Initialize(const char * title)
{
int n;
base::Initialize(title);
// 下面是产生着色器的程序,中文注释,如果不能通过编译请删除中文注释
render_prog = glCreateProgram();
// 顶点渲染器:
static const char render_vs[] =
"#version 330\n"
"\n"
"//位置和法向数据按照正常行驶提供\n"
"layout (location = 0) in vec4 position;\n"
"layout (location = 1) in vec3 normal;\n"
"\n"
"//颜色每个实例提供一个\n"
"layout (location = 2) in vec4 color;\n"
"\n"
"//模型矩阵按照每个实例提供一个\n"
"//注意矩阵是4个连续的数组构成\n"
"//因此mat4实际占用了索引3456.\n"
"layout (location = 3) in mat4 model_matrix;\n"
"\n"
"//观视矩阵和投影矩阵在整个绘制过程中保持不变\n"
"uniform mat4 view_matrix;\n"
"uniform mat4 projection_matrix;\n"
"\n"
"// 输出到偏远着色器的变量(与片元着色器中定义对应)\n"
"out VERTEX\n"
"{\n"
" vec3 normal;\n"
" vec4 color;\n"
"} vertex;\n"
"\n"
"// 下面是主程序\n"
"void main(void)\n"
"{\n"
" // 利用观视矩阵和每个实例对应的模型矩阵创建模型观视矩阵\n"
" mat4 model_view_matrix = view_matrix * model_matrix;\n"
"\n"
" // 利用模型观视矩阵和投影矩阵变换模型顶点坐标.\n"
" gl_Position = projection_matrix * (model_view_matrix * position);\n"
" // 变换法向量\n"
" vertex.normal = mat3(model_view_matrix) * normal;\n"
" // 将颜色传给片元着色器,注意颜色每个实例变换一次\n"
" vertex.color = color;\n"
"}\n";
//片元着色器程序
static const char render_fs[] =
"#version 330\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"
"//这里利用顶点的法向量简单的实现了一个方向光的计算\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";
// 编译链接程序
vglAttachShaderSource(render_prog, GL_VERTEX_SHADER, render_vs);
vglAttachShaderSource(render_prog, GL_FRAGMENT_SHADER, render_fs);
glLinkProgram(render_prog);
glUseProgram(render_prog);
//获得投影矩阵和观视矩阵的uniform索引
view_matrix_loc = glGetUniformLocation(render_prog, "view_matrix");
projection_matrix_loc = glGetUniformLocation(render_prog, "projection_matrix");
//这里读取模型到object类,根据!!!自!己!的!电!脑!的!位!置!!!更改,且注意最好用分隔符"/",否则会把"\"当作特殊符号使用,如果运行起来屏幕一片白,很有可能是这里有问题。同时书中程序也提供了不少其他的模型,可以换着看看。
object.LoadFromVBM("D:/work/ComputerGraphic/oglpg-8th-edition/media/armadillo_low.vbm", 0, 1, 2);
// 将读出来的定点数组绑定到缓存
object.BindVertexArray();
// 获取render_prog中顶点属性的索引位置,注意这里其实并没有必要去获得这些位置,因为我们已经在程序中定义过了位置,我们既可以直接使用此前定义的数字layout (location = xxx),也可以使用这里查找到的数字,两者等同。 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");
// 将数据绑定到缓存
/*
// 在这里看不到绑定操作因为VBM类帮我们完成了这部分工作.
glBindBuffer(GL_ARRAY_BUFFER, position_buffer);
glVertexAttribPointer(position_loc, 4, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(position_loc);
glBindBuffer(GL_ARRAY_BUFFER, normal_buffer);
glVertexAttribPointer(normal_loc, 3, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(normal_loc);
*/
// 产生INSTANCE_COUNT个颜色
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);
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);
//在模型矩阵上执行同样的工作,但注意每个mat4模型矩阵实际上是4个位置同时操作
glGenBuffers(1, &model_matrix_buffer);
glBindBuffer(GL_ARRAY_BUFFER, model_matrix_buffer);
glBufferData(GL_ARRAY_BUFFER, INSTANCE_COUNT * sizeof(mat4), NULL, GL_DYNAMIC_DRAW);
for (int i = 0; i < 4; i++)
{
// Set up the vertex attribute
glVertexAttribPointer(matrix_loc + i, // Location
4, GL_FLOAT, GL_FALSE, // vec4
sizeof(mat4), // Stride
(void *)(sizeof(vec4) * i)); // Start offset
glEnableVertexAttribArray(matrix_loc + i);
glVertexAttribDivisor(matrix_loc + i, 1);
}
// 初始化结束,将顶点数组绑定解除
glBindVertexArray(0);
}
static inline int min(int a, int b)
{
return a < b ? a : b;
}
void InstancingExample::Display(bool auto_redraw)
{
float t = float(GetTickCount() & 0x3FFF) / float(0x3FFF);
static float q = 0.0f;
static const vec3 X(1.0f, 0.0f, 0.0f);
static const vec3 Y(0.0f, 1.0f, 0.0f);
static const vec3 Z(0.0f, 0.0f, 1.0f);
int n;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
// 启用model_matrix_buffer,计算并填充其中的模型矩阵
glBindBuffer(GL_ARRAY_BUFFER, model_matrix_buffer);
//利用直接映射操作模型矩阵
mat4 * matrices = (mat4 *)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
//计算并填充INSTANCE_COUNT个模型矩阵
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] = rotate(a + t * 360.0f, 1.0f, 0.0f, 0.0f) *
rotate(b + t * 360.0f, 0.0f, 1.0f, 0.0f) *
rotate(c + t * 360.0f, 0.0f, 0.0f, 1.0f) *
translate(10.0f + a, 40.0f + b, 50.0f + c);
}
//填充完毕,释放矩阵
glUnmapBuffer(GL_ARRAY_BUFFER);
// 激活渲染程序
glUseProgram(render_prog);
// 设置投影和观视矩阵
mat4 view_matrix(translate(0.0f, 0.0f, -1500.0f) * rotate(t * 360.0f * 2.0f, 0.0f, 1.0f, 0.0f));
mat4 projection_matrix(frustum(-1.0f, 1.0f, -aspect, aspect, 1.0f, 5000.0f));
glUniformMatrix4fv(view_matrix_loc, 1, GL_FALSE, view_matrix);
glUniformMatrix4fv(projection_matrix_loc, 1, GL_FALSE, projection_matrix);
// 命令object绘制INSTANCE_COUNT个实例
object.Render(0, INSTANCE_COUNT);
lookat(vec3(0.0f, 0.0f, 0.0f), vec3(1.0f, 0.0f, 0.0f), vec3(0.0f, 1.0f, 0.0f));
base::Display();
}
void InstancingExample::Finalize(void)
{
glUseProgram(0);
glDeleteProgram(render_prog);
glDeleteBuffers(1, &color_buffer);
glDeleteBuffers(1, &model_matrix_buffer);
}
void InstancingExample::Reshape(int width, int height)
{
glViewport(0, 0 , width, height);
aspect = float(height) / float(width);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
程序的一些讲解:
文中用到了很多矩阵变换,这些变换可以参考计算机图形学的相关书籍,并不在OpenGL的讲解范围之内。
如果运行到
#ifdef _DEBUG
if (glDebugMessageCallbackARB != NULL)
glDebugMessageCallbackARB(DebugOutputCallback, this);
#endif
- 1
- 2
- 3
- 4
- 5
产生错误,直接用”//”取消这两句就好,只是用来产生提示信息的语句。
object类绘制命令其实本程序调用的是
glDrawArraysInstanced(GL_TRIANGLES, m_frame[frame_index].first, m_frame[frame_index].count, instances);命令,其中first,count为从文件中读出的一个模型的相关参数,而instances这里等于INSTANCE_COUNT定义的数字。
在程序中很多工作都在VBM类中完成了,但实际上还是按照OpenGL的标准流程进行的,如果有疑问可以参照书中源代码 。
附:简单修改后的百人方队