跳转到内容

OpenGL编程/现代OpenGL教程 04

维基教科书,自由的教学读本

在本教程中,我们会涉足变换矩阵(transformation matrices)的世界,以便让我们可以平移、旋转以及缩放我们的三角形。

设置矩阵事宜

[编辑]
书架

自然科学 - 数学 - 生物学 - 工程学 - 计算机科学 - 医学 - 体育
人文科学 - 哲学 - 艺术 - 文学 - 语言 - 历史 - 社会科学 - 商业与经济学 - 杂项

一些在使用矩阵时要在意的地方(tidbits):

  • 变换是通过对4x4的矩阵进行逆序相乘达成的。M = M_translation * M_rotation 意味着首先旋转,然后平移。
  • 单位矩阵(identity matrix)是指那种什么都不做的矩阵——没有任何变换。
  • 为了变换一个顶点(vertex),我们使用矩阵对它进行相乘:v' = M * v
  • 4x4矩阵仅能被用在4x1向量上——该向量是由我们将1增加在第4维上后所获得的:(x, y, z, 1)。

为了完成这些乘法运算,我们需要一个数学函数库。着色器带有对矩阵运算内置、简易的支持,但是通常情况下我们需要从C代码中操纵矩阵。这样做也更加高效,因为着色器是为每个顶点执行的,所以说预先计算矩阵是更佳的做法。

该教程将使用OpenGL Mathematics (GLM) 库,它由C++写成。GLM有意识地使用和GLSL一样的约定,于是会更容易起步。它的文档也描述了对已声明不赞成使用的OpenGL 1.x和GLU函数——例如glRotateglFrustumgluLookAt——的替代选择,这将会对那些已经在使用它们的人有很大帮助。

替代项也存在,例如libSIMDx86(顺便一提,它也可以工作在非x86处理器上)。你也可以自己写矩阵代码,毕竟它也不是很长——可以参考Mesa 3D示例mesa-demos-8.0.1/src/egl/opengles2/tri.c中的代码。

GLM是一个纯头文件的库,所以你不需要修改Makefile,而只需要确保头文件被安装在一个标准路径中。 来安装GLM:

apt-get install libglm-dev  # Debian, Ubuntu
dnf install glm-devel  # Fedora

我们现在可以增加GLM头文件了:

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

使用3D的点

[编辑]

我们的变换矩阵是为3D顶点而设计的。即使我们现在处于2D,也可以利用Z=0来将三角形描述成3D点集。总之,我们会在下个教程中转移到3D物体上去 :)

在triangle.cpp中给OpenGL定义它(每个顶点3个元素):

struct attributes {
  GLfloat coord3d[3];
  GLfloat v_color[3];
};

然后,在init_resources()中

  struct attributes triangle_attributes[] = {
    {{ 0.0,  0.8, 0.0}, {1.0, 1.0, 0.0}},
    {{-0.8, -0.8, 0.0}, {0.0, 0.0, 1.0}},
    {{ 0.8, -0.8, 0.0}, {1.0, 0.0, 0.0}}
  };

...

  attribute_name = "coord3d";
  attribute_coord3d = glGetAttribLocation(program, attribute_name);
  if (attribute_coord3d == -1) {
    cerr << "Could not bind attribute " << attribute_name << endl;
    return false;
  }

修改render()中对顶点数组的初始化:

  glVertexAttribPointer(
    attribute_coord3d,   // attribute
    3,                   // number of elements per vertex, here (x,y,z)
    GL_FLOAT,            // the type of each element
    GL_FALSE,            // take our values as-is
    sizeof(struct attributes),  // next coord3d appears every 6 floats
    0                    // offset of first element
  );

相应地,替换掉'attribute_coord2d'的其他出现之处,并且告诉着色器使用新的坐标:

attribute vec3 coord3d;
[...]
void main(void) {
  gl_Position = vec4(coord3d, 1.0);

创建变换矩阵

[编辑]

GLM带有内置的函数以计算旋转、平移和缩放矩阵。 一起在logic()中增加变换矩阵,并且计算一个渐进的旋转及所搭的(combined)平移:

void logic() {
	float move = sinf(SDL_GetTicks() / 1000.0 * (2*3.14) / 5); // -1<->+1 every 5 seconds
	float angle = SDL_GetTicks() / 1000.0 * 45;  // 45° per second
	glm::vec3 axis_z(0, 0, 1);
	glm::mat4 m_transform = glm::translate(glm::mat4(1.0f), glm::vec3(move, 0.0, 0.0))
		* glm::rotate(glm::mat4(1.0f), glm::radians(angle), axis_z);
  [...]

glm4(1.0f)是单位矩阵,意味着我们在草稿状态已经开始进行变换。

传递变换矩阵

[编辑]

就像我们在前一个教程所看到的那样,我们会增加一个新的律态——借由glUniformMatrix4fv

  /* Global */
  #include <glm/gtc/type_ptr.hpp>
  GLint uniform_m_transform;
  /* init_resources() */
  uniform_name = "m_transform";
  uniform_m_transform = glGetUniformLocation(program, uniform_name);
  if (uniform_m_transform == -1) {
    cerr << "Could not bind uniform_fade " << uniform_name << endl;
    return false;
  }
  /* logic() */
  glUniformMatrix4fv(uniform_m_transform, 1, GL_FALSE, glm::value_ptr(m_transform));

如果你不是在使用GLM,传递指向一个GLfloat[16]数组的指针应当足够。就像这样:

  GLfloat matrix[16] = {...};
  glUniformMatrix4fv(uniform_m_transform, 1, GL_FALSE, matrix);

顶点着色器仅仅需要拿我们上面所见的那个矩阵去乘顶点:

uniform mat4 m_transform;
void main(void) {
  gl_Position = m_transform * vec4(coord3d, 1.0);
  [...]
Our triangle, transformed

注意到我们仍然有外观比例的问题(就像在一个16:9的显示器上全屏观看电视节目一样)。 我们会在下个教程中使用Model-View-Projection矩阵解决它。

进行试验!

[编辑]

还记得我们提到过要逆序运用矩阵么?在我们的例子中,首先是旋转,然后是平移。

可以试试用另一种方式去做:你会使该三角形在移动后再旋转,这意味着它会绕着原点旋转而不是绕着它自己的中心。