前言
回顾OpenGL在同一场景绘制不同模型的过程。
同一场景不同模型
在单个场景中绘制不同的简单模型,可以为每个模型分别使用单独的缓冲区,如果有需要(如涉及复杂光照或者对象由不同图元组成等情况),还可以设置不同的着色器程序进行渲染。这些简单模型之间没有内在联系,因此只需要通过模型矩阵改变其在场景中的位置,然后按“流程”输出即可。
一种更加复杂的情况是分层模型。分层模型由多个简单模型组装而成,且这些简单模型之间存在一定的内在联系。行星系统一个典型的例子——地球围着太阳转,月球围着地球转。分层模型中较难解决的部分是如何跟踪所有子模型的模型—视图矩阵并完美地协调它们。我们可以使用矩阵堆栈来处理此操作。
矩阵堆栈
矩阵堆栈:一堆变换的矩阵。在旧的固定功能管线中,可以使用OpenGL内置矩阵堆栈。新的功能管线中,可以使用stack来进行模拟。 |
矩阵堆栈中的第一个矩阵通常是视图矩阵。之后的矩阵是建立在其之上的,复杂程度越来越高的模型—视图矩阵,应用了更多的模型变换,可以直接应用,也可以先结合其它矩阵。
//以简单太阳系统模拟示例:太阳——地球——月球
……
glm::mat4 pMat,vMat,mMat;
stack<glm::mat4>mvStack;
……
mvStack.push(vMat); //基础视图矩阵
//--------------金字塔 == “太阳”,1号缓冲区--------------------
mvStack.push(mvStack.top());
mvStack.top() *= translate(mat4(1.0f), pyrLoc); //“太阳”位置
mvStack.push(mvStack.top());
mvStack.top() *= rotate(mat4(1.0f), (float)currentTime, vec3(0.0f, 1.0f, 0.0f)); //“太阳”自转
glUniformMatrix4fv(mvLoc, 1, GL_FALSE, value_ptr(mvStack.top())); //将“太阳”模型-视图矩阵传递给着色器
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
glDrawArrays(GL_TRIANGLES, 0, 18); //绘制“太阳”
mvStack.pop();
//--------------立方体 == “地球”,0号缓冲区-------------------------
mvStack.push(mvStack.top());
mvStack.top() *= translate(mat4(1.0f), vec3(sin((float)currentTime)*4.0f, 0.0f, cos((float)currentTime)*4.0f)); //地球公转
mvStack.push(mvStack.top());
mvStack.top() *= rotate(mat4(1.0f), (float)currentTime, vec3(0.0f, 1.0f, 0.0f)); //地球自转
mvStack.top() *= scale(mat4(1.0f), vec3(0.5f));
glUniformMatrix4fv(mvLoc, 1, GL_FALSE, 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();
//--------------小立方体 == "月球",0号缓冲区------------------------
mvStack.push(mvStack.top());
mvStack.top() *= translate(mat4(1.0f), vec3(0.0f, sin((float)currentTime)*2.0f, cos((float)currentTime)*2.0f)); //“月球”公转
mvStack.push(mvStack.top());
mvStack.top() *= rotate(mat4(1.0f), (float)currentTime, vec3(0.0f, 0.0f, 1.0f)); //“月球”自转
mvStack.top() *= scale(mat4(1.0f), vec3(0.25f));
glUniformMatrix4fv(mvLoc, 1, GL_FALSE, 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();mvStack.pop();
上述代码中,矩阵堆栈数据流动情况如下:
进栈记录 | 栈中数据流动 | mvStack |
---|---|---|
1 | 视图矩阵进栈 | 1 |
2 | "太阳"模型—视图矩阵进栈,添加模型平移(自身位置) | 1-2 |
3 | “太阳”模型—视图矩阵进栈,添加模型旋转(自转) | 1-2-3 |
pop | “太阳”模型—视图矩阵出栈 | 1-2 |
注意:此时栈顶是"太阳"的模型—视图矩阵 | ||
4 | “地球”模型—视图矩阵进栈,添加模型平移(相对于“太阳”的位置,公转) | 1-2-4 |
5 | “地球”模型—视图矩阵进栈,添加模型旋转、缩放(自转) | 1-2-4-5 |
pop | “地球”模型—视图矩阵出栈 | 1-2-4 |
注意:此时栈顶是"地球"的模型—视图矩阵 | ||
6 | “月球”模型—视图矩阵进栈,添加模型平移(相对于“地球“的位置,公转) | 1-2-4-6 |
7 | “月球”模型—视图矩阵进栈,添加模型旋转、缩放(自转) | 1-2-4-6-7 |
pop | “月球”模型—视图矩阵出栈 | 1-2-4-6 |
注意:此时栈中分别是"月球"、“地球”和太阳的模型—视图矩阵以及基础视图矩阵 | ||
pop | 依次出栈,直到栈空 | empty |
练习截图
“太阳系统模拟1” | “太阳系统模拟2” |
优化性能
随着场景复杂性的增加,我们必须关注性能,从而提高程序的运行速度。
- 尽量减少动态内存空间的分配
- 预先计算透视矩阵
- 启用背面剔除功能