(Python OpenGL)【5】平移 PyOpenGL

时间:2021-09-20 18:15:18

原文:http://ogldev.atspace.co.uk/www/tutorial06/tutorial06.html  (英文)

下面是我翻译过来的:

背景

在本教程中,我们开始着眼于各种转换,它们以3D形式呈现对象,并使其能够在屏幕上显示,同时保持场景中的深度错觉。这样做的常用方法是使用矩阵表示每个转换,将它们逐个相乘,然后将顶点位置乘以最终产品。每个教程都将致力于检查一个转换。

在这里,我们看看翻译变换,它负责沿着任意长度和方向的矢量移动对象。假设您想要将左侧图片中的三角形移动到右侧的位置:

(Python OpenGL)【5】平移 PyOpenGL

一种方法是将偏移矢量(在本例中为-1,1)作为统一变量提供给着色器,并将其简单地添加到每个处理过的顶点的位置。但是,这打破了将一组矩阵乘以一个以获得单一全面转换的方法。此外,稍后您会看到,翻译通常不是第一个翻译,所以您需要将位置乘以代表翻译前转换的矩阵,然后将矩阵中的位置和最终的多位加上表示翻译后转换的矩阵。这太尴尬了。更好的方法是找到一个表示翻译并参与所有矩阵乘法的矩阵。但是,你能找到一个矩阵,当乘以点(0,0)时,左边三角形的左下角顶点,给出结果(1,1)?事实是,你不能用2D矩阵来做它(你不能用(0,0,0)的3D矩阵来做)。一般来说,我们可以说我们需要的是给定点P(x,y,z)和向量V(v1,v2,v3)的矩阵M给出M * P = P1(x + v1,y + v2 ,z + v3)。简而言之,这意味着矩阵M将P转换成位置P + V。在P1中,我们可以看到每个分量是来自P的分量和V的对应分量的和。每个求和方程的左侧由单位矩阵提供:z)和向量V(v1,v2,v3)提供M * P = P1(x + v1,y + v2,z + v3)。简而言之,这意味着矩阵M将P转换成位置P + V。在P1中,我们可以看到每个分量是来自P的分量和V的对应分量的和。每个求和方程的左侧由单位矩阵提供:z)和向量V(v1,v2,v3)提供M * P = P1(x + v1,y + v2,z + v3)。简而言之,这意味着矩阵M将P转换成位置P + V。在P1中,我们可以看到每个分量是来自P的分量和V的对应分量的和。每个求和方程的左侧由单位矩阵提供:
I * P = P(x,y,z)。因此,看起来我们应该从单位矩阵开始,找出将在每个分量(... + V1,... + V2,... + V3)中完成总和方程右侧的变化。我们来看看身份矩阵是怎样的:

(Python OpenGL)【5】平移 PyOpenGL

我们想修改单位矩阵,结果是:

(Python OpenGL)【5】平移 PyOpenGL

如果我们坚持使用3x3矩阵,真的不是一个简单的方法,但如果我们改变为4x4矩阵,我们可以做到以下几点:

(Python OpenGL)【5】平移 PyOpenGL

使用像这样的4矢量来表示3矢量被称为同质坐标,并且对3D图形非常流行和有用。第四个组件被称为'w'。实际上,我们在前面教程中看到的内部着色器符号gl_Position是一个4向量,而w分量对于从3D到2D进行投影具有非常重要的作用。常用符号是对于向量使用w = 1,对于向量使用w = 0。原因是点可以翻译,但矢量不能。您可以更改矢量的长度或其方向,但所有具有相同长度/方向的矢量都被视为相等,无论其“起始位置”如何。所以你可以简单地使用所有矢量的原点。设置w = 0并将平移矩阵乘以该向量将导致相同的向量。

源walkthru

struct Matrix4f {
    float m[4][4];
};

我们在math_3d.h中添加了一个4x4矩阵定义。这将用于我们从现在开始的大多数转换矩阵。

GLuint gWorldLocation;

我们使用这个句柄来访问着色器中的世界矩阵统一变量。我们将它命名为'世界',因为我们正在对物体做的事情是将其位置移动(翻译)到我们想要的虚拟“世界”坐标系中的位置。

Matrix4f World;
World.m[0][0] = 1.0f; World.m[0][1] = 0.0f; World.m[0][2] = 0.0f; World.m[0][3] = sinf(Scale);
World.m[1][0] = 0.0f; World.m[1][1] = 1.0f; World.m[1][2] = 0.0f; World.m[1][3] = 0.0f;
World.m[2][0] = 0.0f; World.m[2][1] = 0.0f; World.m[2][2] = 1.0f; World.m[2][3] = 0.0f;
World.m[3][0] = 0.0f; World.m[3][1] = 0.0f; World.m[3][2] = 0.0f; World.m[3][3] = 1.0f;

在渲染函数中,我们根据上面的解释准备一个4x4矩阵并将其排列。我们将v 2和v 3 设置为零,因此我们预计对象的Y和Z坐标不会发生变化,并且我们将v 1设置为正弦函数的结果。这会将X坐标转换为在-1和1之间很好地摆动的值。现在我们需要将该矩阵加载到着色器中。

glUniformMatrix4fv(gWorldLocation, 1, GL_TRUE, &World.m[0][0]);

这是将数据加载到统一着色器变量的glUniform *函数的另一个示例。该特定功能加载4x4矩阵,并且还有2x2,3x3,3x2,2x4,4x2,3x4和4x3版本。第一个参数是统一变量的位置(在使用glGetUniformLocation()着色器编译后检索)。第二个参数表示我们正在更新的矩阵的数量。我们对一个矩阵使用1,但我们也可以使用此函数在一次调用中更新乘法矩阵。第三个参数经常让新人感到困惑。它指示矩阵是按行 - 主还是列 - 主要顺序提供的。行 - 主要意味着从顶部开始一行一行地提供矩阵。列专业是相同的,但在列。关键是C / C ++默认是行主语言。这意味着当你使用值填充一个二维数组时,它们被放置在内存行中,并且在较低地址处使用“顶部”行。例如,请参阅以下数组:

int a[2][3];
a[0][0] = 1;
a[0][1] = 2;
a[0][2] = 3;
a[1][0] = 4;
a[1][1] = 5;
a[1][2] = 6;

在视觉上,阵列看起来像下面的矩阵:

1 2 3 
4 5 6 

内存布局如下:1 2 3 4 5 6(在较低地址处有1)。

所以我们对glUniformMatrix4fv()的第三个参数是GL_TRUE,因为我们按行优先顺序提供矩阵。我们也可以创建第三个参数GL_FALSE,但我们需要转置矩阵值(C / C ++内存布局将保持不变,但OpenGL会“认为”我们提供的前4个值实际上是矩阵列,等等并会相应地表现出来)。第四个参数只是内存中矩阵的起始地址。

其余的来源是着色器代码。

uniform mat4 gWorld;

这是一个4x4矩阵的统一变量。mat2和mat3也可用。

gl_Position = gWorld * vec4(Position, 1.0);

三角形顶点在顶点缓冲区中的位置是3个分量的向量,但我们同意我们需要第四个分量的值为1.有两种选择:将4个分量的顶点放置在顶点缓冲区中或添加第四个分量在顶点着色器中。第一个选项没有优势。每个顶点位置将为已知为始终为1的组件额外消耗4个字节。使用3分量向量并将w分量连接到着色器会更高效。在GLSL中,这是使用'vec4(位置,1.0)'完成的。我们将该矩阵乘以该向量,结果进入gl_Position。总而言之,在每一帧中,我们都会生成一个转换矩阵,它将X坐标转换为-1和1之间的第四个值。着色器将每个顶点的位置乘以该矩阵,从而导致组合对象向左和向右移动。在大多数情况下,其中一个三角形边将在顶点着色器和裁剪器将裁剪出该边后从标准化框中移出。我们只能看到标准化框内的区域。

代码: