阅读《计算机图形学编程(使用OpenGL和C++)》6 -同一个场景渲染相同的对象

渲染同一个场景,多个相同对象的情况,比如说24个立方体,可以利用循环变量来计算立方体的旋转和平移参数,以便每次绘制立方体时,都会构建不同的模型矩阵。

void display(GLFWwindow* window, double currentTime)
{
    ...
    // 构建透视矩阵
    glfwGetFramebufferSize(window, &width, &height);
    aspect = (float)width / (float)height;
    pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f); // 1.0472 radians = 60 degrees
                                                             
    // 构建视图矩阵、模型矩阵和视图-模型矩阵
    vMat = glm::translate(glm::mat4(1.0f), glm::vec3(-cameraX, -cameraY, -cameraZ));

    for (int i=0; i<24; i++)
    {
        tf = currentTime + i; // tf =="time factor(时间因子)" float类型
        // 使用当前时间来计算x, y和z的不同变换
        tMat = glm::translate(glm::mat4(1.0f),
            glm::vec3(sin(0.35f*tf)*8.0f, cos(0.52f*tf)*8.0f, sin(0.7f*tf)*8.0f));
        // 用1.75来调整旋转速度
        rMat = glm::rotate(glm::mat4(1.0f), 1.75f*tf, glm::vec3(0.0f, 1.0f, 0.0f));
        rMat = glm::rotate(rMat, 1.75f*tf, glm::vec3(1.0f, 0.0f, 0.0f));
        rMat = glm::rotate(rMat, 1.75f*tf, glm::vec3(0.0f, 0.0f, 1.0f));

        mMat = tMat * rMat;
        mvMat = vMat * mMat;
        // 将透视矩阵和MV矩阵复制给相应的统一变量
        glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat)); // GLM函数调用value_ptr()返回对矩阵数据的引用
        glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));

        // 将VBO关联给顶点着色器中相应的顶点属性
        glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);                    // 标记第0个缓冲区为“活跃”
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);    // 将第0个属性关联到缓冲区
        glEnableVertexAttribArray(0);                            // 启用第0个顶点属性

        // 调整OpenGL设置,绘制模型
        glEnable(GL_DEPTH_TEST);
        glDepthFunc(GL_LEQUAL);
        glDrawArrays(GL_TRIANGLES, 0, 36);                        // 执行该语句,第0个VBO中的数据将被传输给拥有位置0的layout修饰符的顶点属性中。这会将立方体的顶点数据发送到着色器。
    }
}

实例化提供只用一个调用就告诉显卡渲染一个对象的多个副本。将程序中的 glDrawArrays() 调用改为 glDrawArraysInstanced() ,这样就可以要求OpenGL 绘制多个副本。

顶点着色器可以访问内置变量gl_InstanceID,这是一个整数,指向当前正在处理对象的第几个实例。

为了使用实例化,需要将构建不同模型矩阵的计算[上方在display()中的循环内实现]移动到顶点着色器中。由于GLSL不提供平移或旋转函数,并且我们无法从着色器内部调用GLM,我们需要在着色器内自己编写工具函数。我们还需要将“时间因子”通过统一变量传递给顶点着色器。我们还需要将模型和视图矩阵传递到单独的统一变量中,因为对每个立方体的模型矩阵都需要进行旋转计算。

顶点着色器 vertShader.glsl

#version 460
layout (location = 0) in vec3 position;
uniform mat4 m_matrix; // 这些是分开的模型和视图矩阵
uniform mat4 v_matrix;
uniform mat4 proj_matrix;
uniform float tf; // 用于动画和放置立方体的时间因子

out vec4 varyingColor;

mat4 buildRotateX(float rad); // 矩阵变换工具函数的声明
mat4 buildRotateY(float rad); // GLSL要求函数先声明后调用
mat4 buildRotateZ(float rad);
mat4 buildTranslate(float x, float y, float z);

void main(void)
{ 
    float i = gl_InstanceID + tf; // 取值基于时间因子,但是对每个立方体示例也都是不同的
    float a = sin(203.0 * i/8000.0) * 403.0; // 这些是用来平移的x、y、z
    float b = sin(301.0 * i/4001.0) * 401.0; 
    float c = sin(400.0 * i/6003.0) * 405.0;

    // 构建旋转和平移矩阵,将会应用于当前立方体的模型矩阵
    mat4 localRotX = buildRotateX(1000*i);
    mat4 localRotY = buildRotateY(1000*i);
    mat4 localRotZ = buildRotateZ(1000*i);
    mat4 localTrans = buildTranslate(a, b, c);

    // 构建模型矩阵,然后是模型-视图矩阵
    mat4 newM_matrix = m_matrix * localTrans * localRotX * localRotY * localRotZ;
    mat4 mv_matrix = v_matrix * newM_matrix;

   gl_Position = proj_matrix * mv_matrix * vec4(position, 1.0);
   varyingColor = vec4(position, 1.0) * 0.5 + vec4(0.5, 0.5, 0.5, 0.5);
}
// 构建平移矩阵的工具函数
mat4 buildTranslate(float x, float y, float z){
mat4 trans = mat4(1.0, 0.0, 0.0, 0.0,
                  0.0, 1.0, 0.0, 0.0,
                  0.0, 0.0, 1.0, 0.0,
                  x, y, z, 1.0);
return trans;
}

//构建并返回绕X轴的旋转矩阵
mat4 buildRotateX(float rad){
mat4 xrot = mat4(1.0, 0.0, 0.0, 0.0,
                   0.0, cos(rad), -sin(rad), 0.0,
                   0.0, sin(rad), cos(rad), 0.0,
                   0.0, 0.0, 0.0, 1.0);
  return xrot;
}

//构建并返回绕Y轴的旋转矩阵
mat4 buildRotateY(float rad){ 
mat4 yrot = mat4(cos(rad), 0.0, sin(rad), 0.0,
                   0.0, 1.0, 0.0, 0.0,
                   -sin(rad), 0.0, cos(rad), 0.0,
                   0.0, 0.0, 0.0, 1.0);
return yrot;
}

//构建并返回绕Z轴的旋转矩阵
mat4 buildRotateZ(float rad){ 
mat4 zrot = mat4(cos(rad), -sin(rad), 0.0, 0.0,
                   sin(rad), cos(rad), 0.0, 0.0,
                   0.0, 0.0, 1.0, 0.0,
                   0.0, 0.0, 0.0, 1.0);
return zrot;
}

在C++/OpenGL应用程序(display函数中)

void display(GLFWwindow* window, double currentTime)
{
    glClear(GL_DEPTH_BUFFER_BIT);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(renderingProgram);

    // 获取MV矩阵和投影矩阵的统一变量的引用
    tfLoc = glGetUniformLocation(renderingProgram, "tf");
    mLoc = glGetUniformLocation(renderingProgram, "m_matrix");
    vLoc = glGetUniformLocation(renderingProgram, "v_matrix"); 
    projLoc = glGetUniformLocation(renderingProgram, "proj_matrix");
    
    // 构建视图矩阵、模型矩阵
    vMat = glm::translate(glm::mat4(1.0f), glm::vec3(-cameraX, -cameraY, -cameraZ));
    mMat = glm::translate(glm::mat4(1.0f), glm::vec3(cubeLocX, cubeLocY, cubeLocZ));

    // 构建透视矩阵
    glfwGetFramebufferSize(window, &width, &height);
    aspect = (float)width / (float)height;
    pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f); // 1.0472 radians = 60 degrees

    // 为了获得时间因子信息
    timeFactor = ((float)currentTime);
    glUniform1f(tfLoc, (float)timeFactor);

    // 将透视矩阵和MV矩阵复制给相应的统一变量
    glUniformMatrix4fv(mLoc, 1, GL_FALSE, glm::value_ptr(mMat)); // GLM函数调用value_ptr()返回对矩阵数据的引用
    glUniformMatrix4fv(vLoc, 1, GL_FALSE, glm::value_ptr(vMat)); // 着色器需要视图矩阵的统一变量
    glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));
    
    // 将VBO关联给顶点着色器中相应的顶点属性
    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);                    // 标记第0个缓冲区为“活跃”
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);    // 将第0个属性关联到缓冲区
    glEnableVertexAttribArray(0);                            // 启用第0个顶点属性

    // 调整OpenGL设置,绘制模型
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glDrawArraysInstanced(GL_TRIANGLES, 0, 36, 100000);
}

实例化让我们可以极大地扩展对象的副本数量;在这个例子中,即使对于很普通的GPU,实现100000个立方体的动画仍然是可行的。对代码的更改主要是一些常量的修改,是为了将大量立方体进一步分散开

如:

顶点着色器

float a = sin(203.0 * i/8000.0) * 403.0;
float b = cos(301.0 * i/4001.0) * 401.0;
float c = sin(400.0 * i/6003.0) * 405.0;

C++/OpenGL应用程序

cameraZ = 420.0f; // 将摄像机沿着Z轴再移远一些,以看到更多的立方体
...
glDrawArraysInstanced(GL_TRIANGLES, 0, 36, 100000);

猜你喜欢

转载自blog.csdn.net/ttod/article/details/135346141