现代 OpenGL:VBO、GLM 和矩阵堆栈
Posted
技术标签:
【中文标题】现代 OpenGL:VBO、GLM 和矩阵堆栈【英文标题】:Modern OpenGL: VBO, GLM and Matrix Stacks 【发布时间】:2012-01-21 07:51:12 【问题描述】:在搜索和阅读有关 Modern OpenGL 以升级我现有的项目后,我有点困惑,因为我的 3D 框架基于 OpenGL 2.1。
所以,据我所知...
我们需要从顶点、索引、法线、颜色、uvs 等生成我们的 Vertex-Buffer-Objects。
然后我们可以使用 GLM 进行矩阵变换,我们只使用 VBO 来创建或操作网格,最后我们像这样将所有内容传递给 GLSL 顶点着色器...
glm::mat4 MVP = projection * view * model;
glUniformMatrix4fv(glGetUniformLocation(shaderProgramID, "MVP"), 1, GL_FALSE, glm::value_ptr(MVP)); //or &MVP[0][0]
// uniform mat4 MVP;
// in vec3 Vertex;
// void main(void)
//
// gl_Position = MVP * vec4(Vertex, 1.0); //instead of ftransform();
//
问题:我们如何在没有 pushMatrix/popMatrix 的情况下进行分层转换? (或者也许我们通过使用我们的 VBO 来进行分层转换,这可能吗?)
如果不可能,那么如何使用 GLM 和 C++
假设我需要这样的东西:
> Set identity
> Translate to X, Y, Z
> Draw Mesh 1
> Rotate 0.5 by X axis
> Draw Mesh 2
> Scale down to 0.1
> Draw Mesh 3
【问题讨论】:
@BЈовић:OpenGL Mathematics (GLM),用于矩阵/向量数学的仅标头 C++ 库。 【参考方案1】:如果您的渲染已经使用例如函数递归进行分层,那么您已经有了矩阵堆栈!
void renderMesh(Matrix transform, Mesh mesh)
// here call glDrawElements/glDrawArrays and send transform matrix to MVP uniform
mesh->draw(transform);
// now render all the sub-meshes, then will be transformed relative to current mesh
for (int i=0; i<mesh->subMeshCount(); i++)
Matrix subMeshTransform = mesh->getSubMeshTransform(i);
Mesh subMesh = mesh->getSubMesh();
renderMesh(subMeshTransform * transform, subMesh);
// somwhere in main function
...
Matrix projection = Matrix::perspective(...);
Matrix view = camera->getViewMatrix();
Matrix transform = view * projectIon;
renderMesh(transform, rootMesh);
【讨论】:
【参考方案2】:我们需要从顶点、索引、法线、颜色、uvs 等生成我们的 Vertex-Buffer-Objects。
实际上没有必要使用 VBO,客户端顶点数组也可以。但是强烈建议使用 VBO,因为它使驱动程序的生活更轻松,从长远来看,您也可以作为必须处理数据的人。代码开销可以忽略不计(与生成和上传纹理数据差不多),性能只会提高。
然后我们可以使用 GLM 进行矩阵变换,我们只使用 VBO 来创建或操作网格,最后我们像这样将所有内容传递给 GLSL 顶点着色器...
您不仅限于 GLM。任何矩阵数学库都可以。如果您正在寻找可以在 C99 中使用的东西,请查看我的(仍然不完整)linmath.h
https://github.com/datenwolf/linmath.h,它只是一个带有 static inline
函数的头文件。如果代码重复对性能有负面影响(代码大小会造成 L1 缓存压力),我还没有进行基准测试。
问题:我们如何在没有 pushMatrix/popMatrix 的情况下进行分层转换? (或者也许我们通过使用我们的 VBO 来进行分层转换,这可能吗?)
VBO 与此无关。给大多数老式 OpenGL 用户带来麻烦的是那些矩阵堆栈函数,它们使 OpenGL 看起来有点像场景图。但事实并非如此。
如果您忘记了旧 OpenGL 的矩阵堆栈,那么如何进行层次转换就变得很明显了:在层次结构中的每个分支处复制转换矩阵并对其进行操作。您会得到一个层次结构的转换树,在每个节点上存储相应的矩阵。然后将这些矩阵作为制服传递给顶点着色器;或者如果您正在绘制一个只有一个变换的刚性对象,则只有一个矩阵。多个矩阵,你通常只需要像这样的角色的骨骼动画这样的可变形对象
worldtransform ->
pelvis ->
left upper leg -> left lower leg -> left foot
right upper leg -> right lower leg -> right foot
torso ->
neck -> head ->
left eye
right eye
facial deformation // this is a whole chapter of it's own
left upper arm -> left lower arm -> left hand
right upper arm -> right lower arm -> right hand
每次您在这样的层次结构中遇到->
时,您都会复制矩阵并继续处理该矩阵。当回退到树的更高级别时,您会再次从该矩阵开始工作。
【讨论】:
"面部变形" //这是它自己的一整章 => 包括下巴:)【参考方案3】:是的,如果您需要分层转换,那么您必须自己完成。但是,如果您只需要一个堆栈,那么这几乎是微不足道的。只需将堆栈中的最后一个条目与您要应用的下一个矩阵相乘,然后将结果压入堆栈。
[关于您编辑的问题]:您根本不需要堆栈,也没有分层转换。只需一个矩阵并应用您的平移、绘制、将其与旋转矩阵相乘、绘制、乘以缩放、绘制。
【讨论】:
呃?如果你有初始矩阵 M、平移 T、旋转 R 和缩放 S,那么你的第一个矩阵是 M=T,第二个矩阵是 M*=R = (TR == 先平移,旋转第二个),第三个是 M*=S = (TR*S == 所有矩阵。) 对于机械臂,您也可以从顶部开始并在向下移动时发出绘图调用树。 “堆栈”是 std::stack 还是递归场景遍历例程的一部分都没有关系。 如果你不做 M*=S,而只是使用 MS(复制!),那么原始矩阵不会改变。你的堆栈看起来像这样:[M],然后你压入 T(导致堆栈 [M,MT]),然后你弹出([M]),然后再次压入 R([M,M *R])。我不明白你的问题? 停止编辑您的 cmets 的含义。我说你可以通过堆栈来实现它,或者你可以在递归绘图函数中复制你的矩阵(隐式创建一个堆栈。) [M, ..] 是一个堆栈,如果你想要这个,就用它。但是你也可以在没有任何 std::stack 的情况下轻松逃脱。 让我们continue this discussion in chat【参考方案4】:关于 VAO 和 VBO 性能我不同意 VBO 更快,我建议查看此链接
http://www.openglsuperbible.com/2013/12/09/vertex-array-performance/
从上面的结果可以看出,至少对于我们的小样本集,VAO 在所有实现上都更快。这是有道理的——调用 glBindVertexArray 时验证的参数少于 glBindBuffer 或 glVertexAttribPointer。即使只有一个顶点属性,使用 VAO 开关调用 OpenGL 的次数也比使用全局 VAO 显式更新的次数少一半。除了明显的“更少的 API 调用意味着更快的执行”关系之外,VAO 是 OpenGL 驱动程序可以存储编程底层 GPU 所需信息的地方。无论哪种方式,发送到 GPU 的状态更改总量都是相同的。
【讨论】:
这与问题有什么关系?以上是关于现代 OpenGL:VBO、GLM 和矩阵堆栈的主要内容,如果未能解决你的问题,请参考以下文章
为 3D Cup 创建圆柱体基础(现代 OpenGL、GLM)