实例渲染Instanced Rendering

实例渲染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的标准流程进行的,如果有疑问可以参照书中源代码 。

附:简单修改后的百人方队 
这里写图片描述

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值