移动相机

前言:图形学第五弹,移动相机!这一章难度比较大,为了静下心来看这个文章,我把大神的文章整个翻译了一遍。

3D 空间的基本变化

在欧几里得坐标系中,坐标可以被「原点」和「基坐标」表示。(x, y, z) 在坐标系 (O, i, j, k) 意味着什么呢?意味着,向量 OP 可以按以下的方法表示

\overrightarrow{OP}=\overrightarrow{i}x+\overrightarrow{j}y+\overrightarrow{k}z=\begin{bmatrix} \overrightarrow{i} &\overrightarrow{j} & \overrightarrow{k} \end{bmatrix}\begin{bmatrix} x\\ y\\ z \end{bmatrix}​

现在有另一个坐标系了(O', i', j', k')​ 我们如何转换坐标呢?新的坐标系可以由旧坐标系表示

\begin{bmatrix} \overrightarrow{i}' &\overrightarrow{j}' & \overrightarrow{k}' \end{bmatrix} = \begin{bmatrix} \overrightarrow{i} &\overrightarrow{j} & \overrightarrow{k} \end{bmatrix} × M

一图解决所有问题

所以我们重新表示一下 OP

用之前的公式代替

同样给我们了新的公式去转换坐标

让我们创建我们自己的 gluLookAt

我们的玩具渲染器只能让相机在 Z 轴画图像,移动相机没有问题,只需要移动整个场景,让我们的相机不移动

让我们以这种方式解决问题:我们想要用位于e(眼睛)的相机绘制一个场景,相机应该指向点c(中心),使得给定的向量u(向上)是在最终渲染中是垂直的

画个图表示一下

这个图表示,我们可以在新的坐标系中画图,(c, x', y', z') 但是我们给的模型是坐标系(O, x, y, z)。没有问题!我们所需要的就是去计算坐标的转换,这里是 C++ 代码计算必要的 4×4 模型场景矩阵

void lookat(Vec3f eye, Vec3f center, Vec3f up) {
    Vec3f z = (eye-center).normalize();
    Vec3f x = cross(up,z).normalize();
    Vec3f y = cross(z,x).normalize();
    Matrix Minv = Matrix::identity();
    Matrix Tr   = Matrix::identity();
    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] = -center[i];
    }
    ModelView = Minv*Tr;
}

注意向量 \overrightarrow{ce} 给出了 z' (不要忘记去正则化它, 这在后面会有用)我们如何计算 x' ? 简单通过 \overrightarrow{u}\overrightarrow{z'} 叉乘得到,然后我们计算 y' 使它与计算的 x', z' 正交。(让我提醒你一下,我们问题的设定 \overrightarrow{ce}\overrightarrow{u} 不一定是正交)。最后一步是将原点转换为中心 c,我们的转换矩阵就绪。现在只需在模型框架中获得坐标(x,y,z,1)的任意点,将其乘以矩阵 ModelView,我们就可以获得相机框架中的坐标!顺便说一下,ModelView这个名字来自 OpenGL 术语。

Viewport

如果你从一开始就按照这个课程,你应该记得像这样的奇怪的代码:

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

这是什么意思?这意味着我有一个点 Vec2f v,[-1,1] * [ - 1,1] 它属于我想在图像(宽度,高度)尺寸的图像中绘制它。值(v.x 1)在 0 和 2 之间变化,(v.x 1)/ 2在0和1之间变化,并且(v.x 1)* width / 2扫描所有图像。

但是现在我们正在摆脱这些丑陋的构造,我想重写矩阵形式的所有计算。让我们考虑以下 C++ 代码:

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;
}

这个 code 创建了这样一个矩阵

这意味着原来 [-1,1] * [ - 1,1] * [ - 1,1] 被映射到屏幕立方体 [x,xw] * [y,yh] * [0,d ]。这是立方体,而不是矩形。这是因为 z-buffer 的深度计算。这里 d 是 z 缓冲区的分辨率。我喜欢它等于255,因为简单的倾斜 z-buffer 的黑白图像用于调试。 在 OpenGL 术语中,该矩阵称为 ViewPort 矩阵。

Chain of coordinate transformations 坐标一系列的变换

所以,让我们总结一下。我们的模型是在他们自己的坐标系中创建的,它们要被插入以世界坐标表示的场景中。使用矩阵模型进行从一个坐标系到另一个坐标系的转换。然后我们想要在相机框架(眼睛坐标系中)表示它们,转换这个过程称为 View。得到新的坐标以后,使用透视投影。矩阵转换到场景中被叫做 Clip Coordinate。最后,我们画出这个场景,这个矩阵变化的过程 Clip Coordinate 到 Screen Coordinates 被叫做 ViewPort

再一次如果我们从 .obj 文件读取一个点 v,然后画它在屏幕上。经历了以下步骤

Viewport * Projection * View * Model * v

Transformation of normal vectors 法向量的转换

这里有一个广泛知道的事实

  • 如果我们有一个平面和它的法向量,这个平面经过一系列仿射变换,那么法向量同样也会经过相同的仿射变换。等价于原始映射矩阵的逆矩阵的转换。

什么什么什么?!我认识了很多了解这个事实的程序员,但对他们来说仍然是一个黑魔法。事实上,它并不复杂。拿一支铅笔绘制一个 2D 三角形 (0, 0) , (0, 1), (1, 0) 和一个法向量 n,n 等于 (1, 1)。然后让我们将所有 y 坐标拉伸 2 倍,保持 x 坐标不变。因此,我们的三角形变为(0, 0) (0, 2)(1, 0)。如果我们以相同的方式变换向量 n,它变为 (1, 2) 并且它不再与三角形的变换边正交。

因此,为了消除所有的黑魔法迷雾,我们需要理解一件简单的事情:我们不需要简单地变换法向量(因为它们可能变得不正常),我们需要计算(新)法向量到变换后的模型。

回到 3D,我们有一个向量n = (A,B,C)。我们知道穿过原点并且 n为法线的平面具有方程 Ax + By + Cz = 0​。让我们以矩阵形式编写它(在齐次坐标中执行):

回想一下(A,B,C) -是一个向量,所以我们在嵌入 4D 时用0增加它,而(x,y,z)用1增加,因为它是一个点。

让我们在其间插入一个单位矩阵

可以写成


左括号告诉我们通过应用仿射映射的逆转置矩阵,可以从旧法线计算变换对象的法线。

原文

Lesson 5: Moving the camera

结果

代码

GitHub

最后实现渲染器的代码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 如果不熟悉线性代数的概念,要去学习自然科学,现在看来就和文盲差不多。”,然而“按照现行的国际标准,线性代数是通过公...
    Drafei阅读 1,601评论 0 3
  • 理解矩阵一:转载自:http://blog.csdn.net/myan/article/details/64751...
    jiandanjinxin阅读 1,562评论 1 15
  • 前言 最近翻阅关于从2D视频或者图片中重构3D姿态的文章及其源码,发现都有关于摄像机参数的求解,查找了相关资料,做...
    予汐阅读 6,301评论 0 3
  • 昨天和静静去买了火锅的材料,买了好多火锅丸子,一盒肥牛,还有好多蔬菜。火锅底料是德庄的特辣牛油火锅,7.9一小袋,...
    露霭阅读 202评论 0 0
  • 雨,已经不记得下了几天。 天亮的时候在下,天黑的时候在下。云黑灰混杂的压在远...
    许生花阅读 369评论 0 3