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

同一个场景渲染不同的对象,一种简单的方法是为每个模型使用单独的缓冲区。每个模型都需要自己的模型矩阵,这样我们就需要为我们渲染的每个模型生成一个新的模型-视图矩阵。还需要为每个模型单独调用glDrawArrays()。因此,我们需要修改init()和display()函数。

让我们继续添加一个简单的金字塔,这样我们的场景就包括一个立方体和一个金字塔。

顶点和片段着色器代码被省略了,他们和之前4节最后展示的代码一样。

main.cpp

void setupVertices(void)
{    
    // 立方体
    float cubePositions[108] = {
        -1.0f,  1.0f, -1.0f, -1.0f, -1.0f, -1.0f,  1.0f, -1.0f, -1.0f,
         1.0f, -1.0f, -1.0f,  1.0f,  1.0f, -1.0f, -1.0f,  1.0f, -1.0f,
         1.0f, -1.0f, -1.0f,  1.0f, -1.0f,  1.0f,  1.0f,  1.0f, -1.0f,
         1.0f, -1.0f,  1.0f,  1.0f,  1.0f,  1.0f,  1.0f,  1.0f, -1.0f,
         1.0f, -1.0f,  1.0f, -1.0f, -1.0f,  1.0f,  1.0f,  1.0f,  1.0f,
        -1.0f, -1.0f,  1.0f, -1.0f,  1.0f,  1.0f,  1.0f,  1.0f,  1.0f,
        -1.0f, -1.0f,  1.0f, -1.0f, -1.0f, -1.0f, -1.0f,  1.0f,  1.0f,
        -1.0f, -1.0f, -1.0f, -1.0f,  1.0f, -1.0f, -1.0f,  1.0f,  1.0f,
        -1.0f, -1.0f,  1.0f,  1.0f, -1.0f,  1.0f,  1.0f, -1.0f, -1.0f,
         1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f,  1.0f,
        -1.0f,  1.0f, -1.0f,  1.0f,  1.0f, -1.0f,  1.0f,  1.0f,  1.0f,
         1.0f,  1.0f,  1.0f, -1.0f,  1.0f,  1.0f, -1.0f,  1.0f, -1.0f,
    };
    // 金字塔有18个顶点,由6个三角形组成(侧面4个,底面2个)
    float pyramidOsitions[54] = {
        -1.0f, -1.0f,  1.0f,  1.0f, -1.0f,  1.0f,  0.0f,  1.0f,  0.0f, // 前面
         1.0f, -1.0f,  1.0f,  1.0f, -1.0f, -1.0f,  0.0f,  1.0f,  0.0f, // 右面
         1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f,  0.0f,  1.0f,  0.0f, // 后面
        -1.0f, -1.0f, -1.0f, -1.0f, -1.0f,  1.0f,  0.0f,  1.0f,  0.0f, // 左面
        -1.0f, -1.0f, -1.0f,  1.0f, -1.0f,  1.0f, -1.0f, -1.0f,  1.0f, // 底面 - 左前一半
         1.0f, -1.0f,  1.0f, -1.0f, -1.0f, -1.0f,  1.0f, -1.0f, -1.0f, // 底面 - 右后一半
    };
    glGenVertexArrays(1, vao); // 创建一个vao,并返回它的整数型ID存进数组vao中
    glBindVertexArray(vao[0]); // 激活vao
    glGenBuffers(numVBOs, vbo);// 创建两个vbo,并返回它们的整数型ID存进数组vbo中

    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); // 激活vbo第0个缓冲区
    glBufferData(GL_ARRAY_BUFFER, sizeof(cubePositions), cubePositions, GL_STATIC_DRAW); // 将包含顶点数据的数组复制进活跃缓冲区(这里是第0个VBO)

    glBindBuffer(GL_ARRAY_BUFFER, vbo[1]); // 激活vbo第0个缓冲区
    glBufferData(GL_ARRAY_BUFFER, sizeof(pyramidOsitions), pyramidOsitions, GL_STATIC_DRAW); // 将包含顶点数据的数组复制进活跃缓冲区(这里是第1个VBO)
}

void init(GLFWwindow* window) {
    renderingProgram = Utils::createShaderProgram("vertShader.glsl", "fragShader.glsl");
    cameraX = 0.0f;  cameraY = 0.0f;    cameraZ = 8.0f;
    cubeLocX = 0.0f; cubeLocY = -2.0f; cubeLocZ = 0.0f; // 沿Y轴下移以展示透视
    pyrLocX = 2.0f; pyrLocY = 2.0f; pyrLocZ = 0.0f;
    setupVertices();
}


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

    // 获取MV矩阵和投影矩阵的统一变量的引用
    mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix");
    projLoc = glGetUniformLocation(renderingProgram, "proj_matrix");
    
    // 构建透视矩阵
    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));

    // 绘制立方体(使用0号缓冲区)
    mMat = glm::translate(glm::mat4(1.0f), glm::vec3(cubeLocX, cubeLocY, cubeLocZ));
    mvMat = vMat * mMat;

    glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat)); // 着色器需要视图矩阵的统一变量
    glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));

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

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

    // 绘制金字塔(使用1号缓冲区)
    mMat = glm::translate(glm::mat4(1.0f), glm::vec3(pyrLocX, pyrLocY, pyrLocZ));
    mvMat = vMat * mMat;

    glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat)); // 着色器需要视图矩阵的统一变量
    glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));

    glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);                    // 标记第1个缓冲区为“活跃”
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);    // 将第0个属性关联到缓冲区
    glEnableVertexAttribArray(0);                            // 启用第0个顶点属性

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glDrawArrays(GL_TRIANGLES, 0, 18);                        // 执行该语句,第1个VBO中的数据将被传输给拥有位置0的layout修饰符的顶点属性中。这会将金字塔的顶点数据发送到着色器。
    
}

结果如下:

矩阵堆栈

矩阵堆栈是一堆变换矩阵。它使得变换可以构建在其他变换之上(或者从其他变换中被移除)。

可以使用C++标准模板库(STL)的stack类。

“*=”运算符在mat4中被重载,因此它可以用于连接矩阵。

我们不再通过创建mat4的实例来构建变换,而是使用push()命令在堆栈顶部创建新的矩阵。然后再根据需要将期望的变换应用于堆栈顶部的新创建的矩阵。

模仿太阳系运行的动画效果。

将视图矩阵放入堆栈中,位于视图矩阵正上方的矩阵将是太阳的MV矩阵。在它之上的矩阵将是地球的MV矩阵,由太阳的MV矩阵的副本和应用于其之上的地球模型矩阵变换组成。也就是说,地球的MV矩阵是通过将行星的变换结合到太阳的变换中而建立的。同样,月球的MV矩阵位于行星的MV矩阵之上,并通过将月球的模型矩阵变换应用于紧邻其下方的行星的MV矩阵来构建。

在渲染月球之后,可以通过从堆栈中“弹出”第一个月球的矩阵(将堆栈的顶部恢复到行星的模型-视图矩阵),然后重复第二个月球的过程,来渲染第二个“月球”。

基本方法如下。

(1)我们声明我们的堆栈,给它起名为“mvStack”。

(2)当相对于父对象创建新对象时,调用“mvStack.push(mvStack.top())”。

(3)应用新对象所需的变换,也就是将所需的变换乘以它。

(4)完成对象或子对象的绘制后,调用“mvStack.pop()”从矩阵堆栈顶部移除其模型-视图矩阵。

main.cpp

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

    // 获取MV矩阵和投影矩阵的统一变量的引用
    mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix");
    projLoc = glGetUniformLocation(renderingProgram, "proj_matrix");
    
    // 构建透视矩阵
    glfwGetFramebufferSize(window, &width, &height);
    aspect = (float)width / (float)height;
    pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f); // 1.0472 radians = 60 degrees
    glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));

    // 将视图矩阵推入堆栈
    vMat = glm::translate(glm::mat4(1.0f), glm::vec3(-cameraX, -cameraY, -cameraZ));
    mvStack.push(vMat);

    // 金字塔 == 太阳
    mvStack.push(mvStack.top());
    mvStack.top() *= glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 0.0f)); // 太阳位置
    mvStack.push(mvStack.top());
    mvStack.top() *= glm::rotate(glm::mat4(1.0f), (float)currentTime, glm::vec3(1.0f, 0.0f, 0.0f));

    // 太阳旋转
    glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvStack.top())); 
    glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(0);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_LEQUAL);
    glDrawArrays(GL_TRIANGLES, 0, 18);    // 绘制太阳
    mvStack.pop();  // 从堆栈中移除太阳的轴旋转

    // 立方体 == 行星
    mvStack.push(mvStack.top());
    mvStack.top() *= glm::translate(glm::mat4(1.0f), glm::vec3(sin((float)currentTime)*4.0, 0.0f, cos((float)currentTime)*4.0));
    mvStack.push(mvStack.top());
    mvStack.top() *= glm::rotate(glm::mat4(1.0f), (float)currentTime, glm::vec3(0.0, 1.0, 0.0));

    // 行星旋转
    glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvStack.top()));
    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(0);
    glDrawArrays(GL_TRIANGLES, 0, 36); // 绘制行星
    mvStack.pop();  // 从堆栈中移除行星的轴旋转

    // 小立方体 == 月球
    mvStack.push(mvStack.top());
    mvStack.top() *= glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, sin((float)currentTime)*2.0, cos((float)currentTime)*2.0));
    mvStack.top() *= glm::rotate(glm::mat4(1.0f), (float)currentTime, glm::vec3(0.0f, 0.0f, 1.0f));

    // 月球旋转
    mvStack.top() *= glm::scale(glm::mat4(1.0f), glm::vec3(0.25f, 0.25, 0.25)); // 让月球小一些
    glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvStack.top()));
    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(0);
    glDrawArrays(GL_TRIANGLES, 0, 36); // 绘制行星

    // 从堆栈中移除月球缩放、旋转、位置矩阵,行星位置矩阵,太阳位置矩阵,和视图矩阵
    mvStack.pop();
    mvStack.pop();
    mvStack.pop();
    mvStack.pop();    
}

着色器代码不变

效果如下:

猜你喜欢

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