OpenGL 模型、视图、投影矩阵

5

我正在尝试理解使用矩阵的OpenGL相机。

我编写了一个简单的着色器,代码如下:

#version 330 core

layout (location = 0) in vec3 a_pos;
layout (location = 1) in vec4 a_col;

uniform mat4 u_mvp_mat;
uniform mat4 u_mod_mat;
uniform mat4 u_view_mat;
uniform mat4 u_proj_mat;

out vec4 f_color;

void main()
{
    vec4 v = u_mvp_mat * vec4(0.0, 0.0, 1.0, 1.0);
    gl_Position =   u_mvp_mat * vec4(a_pos, 1.0);
    //gl_Position =   u_proj_mat * u_view_mat * u_mod_mat * vec4(a_pos, 1.0);
    f_color = a_col;
}

这段文字有点啰嗦,因为我正在测试将模型矩阵、视图矩阵或投影矩阵传递到 GPU 上进行计算,或者在 CPU 上进行计算并传递 mvp 矩阵,然后只需执行 mvp * position 矩阵乘法。

我知道后一种方法可以提高性能,但是目前绘制一个四边形时我并没有看到任何性能问题。

现在,我使用此代码从我的着色器中获取位置,并创建模型视图和投影矩阵。

pos_loc = get_attrib_location(ce_get_default_shader(), "a_pos");
col_loc = get_attrib_location(ce_get_default_shader(), "a_col");
mvp_matrix_loc = get_uniform_location(ce_get_default_shader(), "u_mvp_mat");
model_mat_loc = get_uniform_location(ce_get_default_shader(), "u_mod_mat");
view_mat_loc = get_uniform_location(ce_get_default_shader(), "u_view_mat");
proj_matrix_loc =
    get_uniform_location(ce_get_default_shader(), "u_proj_mat");

float h_w = (float)ce_get_width() * 0.5f;  //width = 320
float h_h = (float)ce_get_height() * 0.5f; //height = 480

model_mat = mat4_identity();
view_mat = mat4_identity();
proj_mat = mat4_identity();

point3* eye = point3_new(0, 0, 0);
point3* center = point3_new(0, 0, -1);
vec3* up = vec3_new(0, 1, 0);

mat4_look_at(view_mat, eye, center, up);
mat4_translate(view_mat, h_w, h_h, -20);

mat4_ortho(proj_mat, 0, ce_get_width(), 0, ce_get_height(), 1, 100);

mat4_scale(model_mat, 30, 30, 1);

mvp_mat = mat4_identity();

在此之后,我设置了我的VAO和VBO,然后准备进行渲染。

glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(ce_get_default_shader()->shader_program);
glBindVertexArray(vao);

mvp_mat = mat4_multi(mvp_mat, view_mat, model_mat);
mvp_mat = mat4_multi(mvp_mat, proj_mat, mvp_mat);

glUniformMatrix4fv(mvp_matrix_loc, 1, GL_FALSE, mat4_get_data(mvp_mat));

glUniformMatrix4fv(model_mat_loc, 1, GL_FALSE, mat4_get_data(model_mat));
glUniformMatrix4fv(view_mat_loc, 1, GL_FALSE, mat4_get_data(view_mat));
glUniformMatrix4fv(proj_matrix_loc, 1, GL_FALSE, mat4_get_data(proj_mat));

glDrawElements(GL_TRIANGLES, quad->vertex_count, GL_UNSIGNED_SHORT, 0);
glBindVertexArray(0);

假设所有矩阵运算都是正确的,我想将视图矩阵和投影矩阵提取到一个相机结构体中,并将模型矩阵提取到一个精灵结构体中,这样可以避免所有这些矩阵计算,使使用更加简单。
矩阵乘法顺序为:
Projection * View * Model * Vector

因此,相机将保存投影和视图矩阵,而精灵则保存模型矩阵。

在将数据发送到GPU之前,进行所有相机和精灵变换,然后进行矩阵乘法。

如果我记得正确,矩阵乘法不是可交换的,因此执行 view * projection * model 将会导致错误的矩阵结果。

伪代码

glClearxxx(....);
glUseProgram(..);
glBindVertexArray(..);

mvp_mat = mat4_identity();
proj_mat = camera_get_proj_mat();
view_mat = camera_get_view_mat();
mod_mat  = sprite_get_transform_mat();

mat4_multi(mvp_mat, view_mat, mod_mat); //mvp holds model * view
mat4_multi(mvp_mat, proj_mat, mvp_mat); //mvp holds proj * model * view

glUniformMatrix4fv(mvp_mat, 1, GL_FALSE, mat4_get_data(mvp_mat));

glDrawElements(...);
glBindVertexArray(0);

这是一种可扩展的高效实现方式吗?

2个回答

3
这是一种性能良好且可扩展的方法吗?
是的,除非您有某种与常规完全不同的非常奇特的用例。
通常,您最后需要担心的是从相机中检索模型视图和投影矩阵的性能问题。这是因为这些矩阵通常每帧每个视口只需要获取一次。在扫描线光栅化基元并拉取矩阵之间可能会有数百万次迭代的其他工作,而从相机中获取矩阵只是一个简单的恒定时间操作。
因此,通常您只需使其尽可能方便即可。在我的情况下,我通过中央SDK中的函数指针抽象接口进行了所有操作,然后函数使用与相机相关的用户定义属性动态计算proj / mv / ti_mv矩阵。尽管如此,它从未出现过热点 - 它甚至根本不会在分析器中显示。
有更多昂贵的事情需要担心。可扩展性意味着规模 - 从相机中检索矩阵的复杂性不会扩展。要渲染的三角形、四边形、线条或其他基元的数量可能会扩展,片段着色器中处理的片段数量可以扩展。相机通常不会扩展,除非涉及视口的数量,并且没有人会需要使用一百万个视口。

我明白你关于可扩展性的观点,这确实有道理。不过还有一个问题。假设我按照你的建议让相机返回一个视图*投影矩阵,那么乘以模型矩阵会产生错误的结果,因为它可能是model * proj * view,这不是正确的矩阵乘法方式。我想可以将模型矩阵传递给相机并让其执行正确的操作,但这样会使精灵与相机非常接近,对吗? - user1610950
关于按正确顺序相乘矩阵,是的——你必须这样做。但我不太确定为什么这是一个问题——只需按所需顺序相乘即可获得正确的结果...或者我错过了一些使这个过程难以完成的东西吗? - user4842163
哦,我明白了——但我想这里存在着对“相机”的低级和高级关注点的混淆。如果您的相机与现实世界中的相机有任何相似之处,那么它除了定义其特征(如视场,在投影矩阵中捕获)的属性外,还应该具有在世界中位置和方向(在模型视图矩阵中捕获)。因此,一个适当的相机对象通常应该能够在所有时候存储生成模型视图矩阵所需的信息——您不应该必须传入该信息。 - user4842163
谢谢,这是我有点不确定的最后一点。我明白相机至少需要有2个矩阵,以及x、y、z轴的位置和旋转,还有比例和其他一些东西。感谢您的所有帮助! - user1610950
@Ike 你的答案很好。我认为这是一个高质量的问题,非常适合概括关于战略OpenGL设计和实现的原因和做法的基础知识。 - decltype_auto
显示剩余5条评论

3

我没有逐位检查,但你所做的看起来通常是可以的。

我想将视图和投影矩阵抽象成一个相机结构体

这是一个非常合适的想法;我几乎无法想象一个严肃的GL应用程序没有这样的抽象。

这是一种可扩展的高效方法吗?

可扩展性的一般限制是:

  • 漫反射和高光BRDFs(需要一个光源uniform,一个法线属性,如果模型缩放不均匀还需要计算一个法线矩阵),并且需要每像素照明以进行高质量渲染。

  • 同样适用于多个光源(例如太阳和近距离聚光灯)

  • 阴影贴图! 阴影贴图?(每个光源都需要一个吗?)

  • 透明度

  • 反射(镜子,玻璃,水)

  • 纹理

从列表中可以看出,仅凭 MVP uniform 和顶点坐标属性是不够的。

但仅仅拥有大量的uniform并不是性能最关键的点——看到你的代码,我相信你不会不必要地重新编译着色器,只在需要时更新uniforms,使用Uniform Buffer Objects等。

问题在于将数据插入这些uniform和VBO中。或者不是。


考虑到“Alice”人形网格在多个相关光源下的傍晚(水会有涟漪),通过城市广场奔跑的情况(这是网格变形+平移)并经过喷泉。
让我们考虑仅通过CPU和老派方法将所有数据收集起来,并准备好直接渲染到着色器中:
- Alice的网格被变形,因此需要更新她的VBO - Alice的网格将移动;因此,所有受影响的阴影贴图都需要更新(OK,这是可以的。它们由GPU上的阴影照明循环生成,但如果你做错了,你会推动大量的数据) - Alice在喷泉中的倒影将不断出现和消失 - Alice的头发会旋转 - CPU可能会非常繁忙,至少可以这么说
(实际上,后者非常困难,以至于您几乎看不到任何半逼真的实时长发动画,但惊人的是(不,实际上并不是如此),有许多马尾辫和短发剪裁)

我们还没有谈论到爱丽丝的着装; 希望她只是穿了一件T恤和一条牛仔裤(不是宽松的衬衫和裙子,这将需要折叠和碰撞计算)。

正如您可能已经猜到的那样,老派的方法不能带领我们走得太远,因此,在CPU和GPU操作之间需要找到适合的匹配点。

此外,应该在早期阶段考虑计算的并行化。有利的做法是使数据尽可能扁平,并将其分成尽可能大的块,以便只需将指针和大小放入gl-call中,然后可以毫不费力地处理这些数据而无需进行任何复制、重新排列、循环或其他工作。

这是我今天关于GL性能和可伸缩性的价值2美分。


你在这个回答中提到的问题实际上是我最初写这个问题的原因。虽然它似乎与我的当前实现相距甚远,但我宁愿避免自己陷入困境。有很多事情需要考虑,谢谢你,我已经点赞了这个答案。 - user1610950

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接