7

TinyRenderer学习笔记05:移动摄像机

 2 years ago
source link: https://direct5dom.github.io/2022/09/23/TinyRenderer%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B005/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

OpenGL本身并没有”摄像机“这一概念,渲染器只能使用位于Z轴上的固定位置来绘制场景。

因为无法移动渲染位置,因此我们只能逆其道而行——移动场景。

因此,我们需要一个方法,来计算场景坐标的变化。

3D空间中的基底变化

在欧几里得空间中,坐标可以由一个原点和一组基来给出。

点P在框架(O,i,j,k)中可以表示为:

假设我们有另一个框架:(O,i′,j′,k′),我们该如何将给定的坐标转换为另一个。

由于(O,i,j,k)和(O,i′,j′,k′)都是3D空间的基,因此存在一个矩阵M,使得:

就像这样:

画图是个好习惯,这样我们可以很直观的看出:

将上面的公式代入:

于是我们得到了:

这就是我们所需要的转换公式。

创建自己的gluLookAt

假设,我们想用位于e点(eye)的相机绘制一个场景,相机应该指向c点(center),使得给定向量u(up)是在最终的渲染结果中保持垂直:

这意味着我们需要的是框架(c,x′,y′,z′)中的渲染结果,但是我们只能通过(O,x,y,z)进行渲染,于是我们需要计算坐标的转换。

这是计算4×4矩阵ModelView的代码:

Matrix lookat(overrightarrow3f eye, overrightarrow3f center, overrightarrow3f up) {
overrightarrow3f z = (eye-center).normalize();
overrightarrow3f x = (up^z).normalize();
overrightarrow3f y = (z^x).normalize();
Matrix Minv = Matrix::identity(4);
Matrix Tr = Matrix::identity(4);
Matrix ModelView = Matrix::identity(4);
for (int i=0; i<3; i++) {
Minv[0][i] = x[i];
Minv[1][i] = y[i];
Minv[2][i] = z[i];
Tr[i][3] = -eye[i];
}
ModelView = Minv*Tr;
return ModelView;
}

需要注意的是:

  • z′的计算 —— 由向量ce→给出(不要忘记归一化,这对之后有好处)。

  • x′的计算 —— 只需要通过计算u和z′之间的叉积。

  • y′的计算 —— 只需通过计算刚刚得到的x′和z′之间的叉积(ce→和u不一定是正交的,这个需要注意)。

  • 最后只需要将原点平移e点,我们的变换矩阵就准备好了。

ModelView实际上是OpenGL的术语。

在我们的代码中,出现过很多次这样的代码:

screen_coords[j] = overrightarrow2i((v.x+1.)*width/2., (v.y+1.)*height/2.);

这段代码的意思是,我们有一个点overrightarrow2f v,它属于正方形[-1,1]*[-1,1]。我想在给定的宽度和高度中绘制它。

(v.x+1.)在0和2之间变化,值(v.x+1.)/2在0和1之间变化。并且,(v.x+1.)*width/2扫描所有图像。

最后,我们有效的将双单体正方形映射到图像上。

现在我们摆脱上面那种代码,转而以矩阵的方式重写计算方式:

Matrix viewport(int x, int y, int w, int h) {
Matrix m = Matrix::identity(4);
m[0][3] = x+w/2.f;
m[1][3] = y+h/2.f;
m[2][3] = depth/2.f;

m[0][0] = w/2.f;
m[1][1] = h/2.f;
m[2][2] = depth/2.f;
return m;
}

此代码会创建如下矩阵:

这意味着双单体立方体[-1,1]*[-1,1]*[-1,1]映射到屏幕立方体[x,x+w]*[y,y+h]*[0,d]

注意,是“屏幕立方体”,而不是矩形。这是因为使用z-buffer进行深度计算。这里d是z-buffer的分辨率。

分辨率一般是255,因为调试时可以简单的转储z-buffer的黑白图像。

在OpenGL中,这个矩阵被称为视口矩阵——Viewport Matrix。

坐标变换链

让我们总结一下:我们的模型是在它自己的坐标(对象坐标 Object Coordinates)中创建的。它们被插入到以**世界坐标 (World Coordinates)**表示的场景中。

从一个到另一个的转换是使用矩阵模型进行的。然后,我们要在相机框架(Eye Coordinates)中表示它,这种变换叫做View

然后,我们使用L4中的投影矩阵 (Projection Matrix)对场景进行变形以创建透视变形,该矩阵将场景转换为所谓的剪辑坐标 (clip coordinates)

最后,我们绘制场景,将剪辑坐标转换为屏幕坐标 (Screen Coordinates)的矩阵被称为视口矩阵 (Viewport Matrix)

同样,如果我们从.obj文件中读取点v,然后将其绘制在屏幕上,他们会经历以下转换链:

Viewport * Projection * View * Model * v.
overrightarrow3f v = model->vert(face[j]);
screen_coords[j] = overrightarrow3f(ViewPort*Projection*ModelView*Matrix(v));

法线向量的变换

我们有一个向量n=(A,B,C)。我们知道,通过原点并以其法线位n的平面具有方程Ax+By+Cz=0。写成矩阵形式即:

(A,B,C)是一个向量,嵌入到4D时添加0列。

对于(x,y,z),嵌入4D时添加1,因为它是一个点。

让我们在两者之间插入一个单位矩阵(M的逆乘以M等于单位):

  • 右边的括号 - 用于对象的转换;
  • 左边的括号 - 用于对象的法线向量的转换。

标准一般写成:

  • 左边的括号 - 可以通过应用仿射映射的逆转置矩阵从旧法线来计算转换对象的法线。

请注意,如果我们的变换矩阵M是均匀缩放、旋转和平移(欧几里得空间的等距)的组合,则M等于它的逆转置,因为在这种情况下,逆和转置相互抵消。

但是因为我们矩阵包括透视变形,通常这个技巧没啥用。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK