[c++][SDL]图形学菜鸡的软渲染之路3-矩阵与各种变换

矩阵变换

矩阵的调用顺序和作用可以参考之前的博客:
渲染流程概述

线性代数相关数学知识可以参考
https://www.bilibili.com/video/av6731067/?p=13

我们的模型矩阵,视图矩阵,投影矩阵大小都是4x4的。
为了让一个三维的顶点坐标可以4x4的矩阵进行运算,必须先让他变成四维的,及齐次化,增加维度w。一个齐次化坐标(x, y, z, w),和世界坐标(x,y,z)的关系是: ( x w , y w , z w ) = ( x , y , z ) \begin{pmatrix} {x \over w}, {y \over w}, {z \over w} \\ \end{pmatrix} =\begin{pmatrix} x,y,z \\ \end{pmatrix} (wx,wy,wz)=(x,y,z)

1.模型矩阵

这里所有矩阵的使用方法都是矩阵左乘一个向量

平移矩阵

在三维空间中,一个顶点(x, y, z)平移(a, b, c)的结果是(x + a, y + b, z + c)这一变换用矩阵表达为:
[ 1 0 0 a 0 1 0 b 0 0 1 c 0 0 0 1 ] ∗ [ x y z 1 ] = [ x + a y + b z + c 1 ] \begin{bmatrix} 1 & 0 & 0 & a \\ 0 & 1 & 0 & b \\ 0 & 0 & 1 & c \\ 0 & 0 & 0 & 1 \end{bmatrix} * {\begin{bmatrix} x\\ y\\ z\\ 1 \end{bmatrix}}={\begin{bmatrix} x+a\\ y+b\\ z+c\\ 1 \end{bmatrix}} 100001000010abc1xyz1=x+ay+bz+c1

Matrix translate(double x, double y, double z) {
	Matrix t;
	t = Matrix::identity();
	t[0][3] = x;
	t[1][3] = y;
	t[2][3] = z;
	return t;
};

缩放矩阵

在三维空间中,一个顶点(x, y, z)以坐标原点(0,0,0)为中心,各个方向缩放(a, b, c)的结果是(x * a, y * b, z * c)这一变换用矩阵表达为:
[ a 0 0 0 0 b 0 0 0 0 c 0 0 0 0 1 ] ∗ [ x y z 1 ] = [ x ∗ a y ∗ b z ∗ c 1 ] \begin{bmatrix} a & 0 & 0 & 0 \\ 0 & b & 0 & 0 \\ 0 & 0 & c & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} * {\begin{bmatrix} x\\ y\\ z\\ 1 \end{bmatrix}}={\begin{bmatrix} x*a\\ y*b\\ z*c\\ 1 \end{bmatrix}} a0000b0000c00001xyz1=xaybzc1

Matrix scale(double x, double y, double z) {
	Matrix t;
	t= Matrix::identity();
	t[0][0] = x;
	t[1][1] = y;
	t[2][2] = z;
	return t;
};

旋转矩阵

Reference:
旋转变换(一)旋转矩阵
常用三角函数公式

二维情况

在这里插入图片描述
如图所示点v 绕 原点旋转θ 角,设v到原点距离为r,
则v的坐标为 ( r ∗ c o s ( ϕ ) , r ∗ s i n ( ϕ ) ) (r*cos(ϕ),r*sin(ϕ)) (rcos(ϕ),rsin(ϕ))

旋转后v‘坐标为 ( r ∗ c o s ( θ + ϕ ) , r ∗ s i n ( θ + ϕ ) ) (r*cos(θ+ϕ),r*sin(θ+ϕ)) (rcos(θ+ϕ),rsin(θ+ϕ))

通过两角和公式展开得:v‘坐标为

( r ∗ c o s θ c o s ϕ − r ∗ s i n θ s i n ϕ , r ∗ s i n θ c o s ϕ + r ∗ c o s θ s i n ϕ ) (r*cosθcosϕ−r*sinθsinϕ ,r*sinθcosϕ+r*cosθsinϕ ) (rcosθcosϕrsinθsinϕ,rsinθcosϕ+rcosθsinϕ)

代入v点表达式得: ( x ∗ c o s ϕ − y ∗ s i n ϕ , y ∗ c o s ϕ + x ∗ s i n ϕ ) (x*cosϕ−y*sinϕ ,y*cosϕ+x*sinϕ ) (xcosϕysinϕ,ycosϕ+xsinϕ)


x ′ = x c o s θ − y s i n θ x′=xcosθ−ysinθ x=xcosθysinθ

y ′ = x s i n θ + y c o s θ y′=xsinθ+ycosθ y=xsinθ+ycosθ

用矩阵表达为:
[ x ′ y ′ ] = [ c o s θ − s i n θ s i n θ c o s θ ] ∗ [ x y ] \begin{bmatrix}x' \\y' \\\end{bmatrix} = \begin{bmatrix} cosθ&−sinθ \\ sinθ&cosθ \\ \end{bmatrix} * \begin{bmatrix}x \\y \\\end{bmatrix} [xy]=[cosθsinθsinθcosθ][xy]

绕任意点的二维旋转

我们可以三部把绕任意点的情况转换为绕原点旋转的情况:

  1. 首先将旋转点移动到原点处
  2. 执行绕原点的旋转
  3. 再将旋转点移回到原来的位置
    在这里插入图片描述

平移矩阵我们前面已经介绍过了,现在设点绕t(x,y)旋转θ度,则复合旋转矩阵为:
[ 1 0 t . x 0 1 t . y 0 0 1 ] ∗ [ c o s θ − s i n θ 0 s i n θ c o s θ 0 0 0 1 ] ∗ [ 1 0 − t . x 0 1 − t . y 0 0 1 ] \begin{bmatrix} 1&0&t.x \\ 0&1&t.y \\ 0&0&1 \end{bmatrix}* \begin{bmatrix} cosθ&−sinθ&0 \\ sinθ&cosθ&0 \\ 0&0&1\end{bmatrix} * \begin{bmatrix} 1&0&-t.x \\ 0&1&-t.y \\ 0&0&1 \end{bmatrix} 100010t.xt.y1cosθsinθ0sinθcosθ0001100010t.xt.y1

= [ c o s θ − s i n θ t . x s i n θ c o s θ t . y 0 0 1 ] ∗ [ 1 0 − t . x 0 1 − t . y 0 0 1 ] = \begin{bmatrix} cosθ&−sinθ&t.x \\ sinθ&cosθ&t.y \\ 0&0&1\end{bmatrix} * \begin{bmatrix} 1&0&-t.x \\ 0&1&-t.y \\ 0&0&1 \end{bmatrix} =cosθsinθ0sinθcosθ0t.xt.y1100010t.xt.y1

= [ c o s θ − s i n θ − t . x ∗ c o s θ + t . y ∗ s i n θ + t . x s i n θ c o s θ − t . x ∗ s i n θ − t . y ∗ c o s θ + t . y 0 0 1 ] = \begin{bmatrix} cosθ&−sinθ&-t.x*cosθ+t.y*sinθ+t.x \\ sinθ&cosθ&-t.x*sinθ-t.y*cosθ+t.y \\ 0&0&1\end{bmatrix} =cosθsinθ0sinθcosθ0t.xcosθ+t.ysinθ+t.xt.xsinθt.ycosθ+t.y1

= [ c o s θ − s i n θ ( 1 − c o s θ ) ∗ t . x + t . y ∗ s i n θ s i n θ c o s θ ( 1 − s i n θ ) ∗ t . y − t . x ∗ s i n θ 0 0 1 ] = \begin{bmatrix} cosθ&−sinθ&(1-cosθ)*t.x+t.y*sinθ\\ sinθ&cosθ&(1-sinθ)*t.y-t.x*sinθ \\ 0&0&1\end{bmatrix} =cosθsinθ0sinθcosθ0(1cosθ)t.x+t.ysinθ(1sinθ)t.yt.xsinθ1

三维基本旋转

1.绕坐标轴旋转

这里我们旋转方向用右手坐标系定义
在这里插入图片描述
绕z轴旋转其实就是在xoy面上进行旋转,与我们的二维情况完全相同,可以直接套用我们刚刚推出的二维旋转公式。
[ x ′ y ′ z ′ 1 ] = [ c o s θ − s i n θ 0 0 s i n θ c o s θ 0 0 0 0 1 0 0 0 0 1 ] ∗ [ x y z 1 ] {\begin{bmatrix} x'\\ y'\\ z'\\ 1 \end{bmatrix}}= \begin{bmatrix} cosθ & −sinθ & 0 & 0\\ sinθ & cosθ & 0 & 0 \\ 0 &0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} * {\begin{bmatrix} x\\ y\\ z\\ 1 \end{bmatrix}} xyz1=cosθsinθ00sinθcosθ0000100001xyz1
绕x轴旋转为在yoz面上旋转,y轴类似二维情况的x轴,z轴为二维情况的y轴,符合右手方向,同样可以套用我们之前推出的公式:
[ x ′ y ′ z ′ 1 ] = [ 1 0 0 0 0 c o s θ − s i n θ 0 0 s i n θ c o s θ 0 0 0 0 1 ] ∗ [ x y z 1 ] {\begin{bmatrix} x'\\ y'\\ z'\\ 1 \end{bmatrix}}= \begin{bmatrix} 1 & 0 & 0 & 0\\ 0 & cosθ & −sinθ & 0 \\ 0 & sinθ & cosθ & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} * {\begin{bmatrix} x\\ y\\ z\\ 1 \end{bmatrix}} xyz1=10000cosθsinθ00sinθcosθ00001xyz1
绕y轴旋转为在zox面上旋转,z轴类似二维情况的x轴,x轴为二维情况的y轴,即:
x′=zsinθ+xcosθ
y′=y
z′=zcosθ−xsinθ
可以得到矩阵
[ x ′ y ′ z ′ 1 ] = [ c o s θ 0 s i n θ 0 0 1 0 0 − s i n θ 0 c o s θ 0 0 0 0 1 ] ∗ [ x y z 1 ] {\begin{bmatrix} x'\\ y'\\ z'\\ 1 \end{bmatrix}}= \begin{bmatrix} cosθ & 0& sinθ & 0\\ 0 & 1& 0& 0 \\ -sinθ & 0& cosθ & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} * {\begin{bmatrix} x\\ y\\ z\\ 1 \end{bmatrix}} xyz1=cosθ0sinθ00100sinθ0cosθ00001xyz1

2.绕任意轴旋转
推导方法一:

类似与二维时绕任意点旋转,我们同样可以把绕任意轴的旋转分解成绕三个坐标轴分别旋转。假设现在有一个点P要绕向量u旋转θ角度,得到点Q,求Q的步骤如下:

  1. 将旋转轴u绕x轴旋转至xoz平面
  2. 将旋转轴u绕y轴旋转至于z轴重合
  3. 绕z轴旋转θ角
  4. 执行步骤2的逆过程
  5. 执行步骤1的逆过程
    过程:
    在这里插入图片描述
    u为旋转轴,作点P在yoz平面的投影点q,q的坐标是(0, b, c),原点o与q点的连线oq和z轴的夹角α就是u绕x轴旋转的角度。通过这次旋转使得u向量旋转到xoz平面(图中的or向量)【步骤1】
    过r点作z轴的垂线,or与z轴的夹角为β, 这个角度就是绕Y轴旋转的角度,通过这次旋转使得u向量旋转到与z轴重合【步骤2】

步骤一绕x轴旋转α度的旋转矩阵为:
[ 1 0 0 0 0 c o s α − s i n α 0 0 s i n α c o s α 0 0 0 0 1 ] \begin{bmatrix} 1 & 0 & 0 & 0\\ 0 & cosα & −sinα & 0 \\ 0 & sinα & cosα & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} 10000cosαsinα00sinαcosα00001
由图片可以得到, c o s α = c b 2 + c 2 cosα = {c \over \sqrt{b^2+c^2}} cosα=b2+c2 c

s i n α = b b 2 + c 2 sinα = {b \over \sqrt{b^2+c^2}} sinα=b2+c2 b

记第一步旋转矩阵Rx(α) ,代入数据得:
[ 1 0 0 0 0 c ( b 2 + c 2 ) − b ( b 2 + c 2 ) 0 0 b ( b 2 + c 2 ) c ( b 2 + c 2 ) 0 0 0 0 1 ] \begin{bmatrix} 1 & 0 & 0 & 0\\ 0 & {c \over \sqrt{(b^2+c^2)}} & −{b \over \sqrt{(b^2+c^2)}} & 0 \\ 0 & {b \over \sqrt{(b^2+c^2)}} & {c \over \sqrt{(b^2+c^2)}} & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} 10000(b2+c2) c(b2+c2) b00(b2+c2) b(b2+c2) c00001
逆变换Rx(-α)为:
[ 1 0 0 0 0 c o s α s i n α 0 0 − s i n α c o s α 0 0 0 0 1 ] = [ 1 0 0 0 0 c ( b 2 + c 2 ) b ( b 2 + c 2 ) 0 0 − b ( b 2 + c 2 ) c ( b 2 + c 2 ) 0 0 0 0 1 ] \begin{bmatrix} 1 & 0 & 0 & 0\\ 0 & cosα & sinα & 0 \\ 0 & -sinα & cosα & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} = \begin{bmatrix} 1 & 0 & 0 & 0\\ 0 & {c \over \sqrt{(b^2+c^2)}} & {b \over \sqrt{(b^2+c^2)}} & 0 \\ 0 & -{b \over \sqrt{(b^2+c^2)}} & {c \over \sqrt{(b^2+c^2)}} & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} 10000cosαsinα00sinαcosα00001=10000(b2+c2) c(b2+c2) b00(b2+c2) b(b2+c2) c00001

在完成步骤1之后,向量u被变换到了r的位置,我们继续步骤2的操作,绕y轴旋转负的β角(注意:这里的β是负的,因为我们按照右手方向进行旋转),经过这次变换之后向量u与z轴完全重合,由于这一步也是执行的一次绕Y轴的基本旋转,旋转矩阵(记作 Ry(−β))为:
[ c o s β 0 s i n β 0 0 1 0 0 − s i n β 0 c o s β 0 0 0 0 1 ] \begin{bmatrix} cosβ & 0& sinβ & 0\\ 0 & 1& 0& 0 \\ -sinβ & 0& cosβ & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} cosβ0sinβ00100sinβ0cosβ00001

其中 c o s β = ( b 2 + c 2 ) ( a 2 + b 2 + c 2 ) cosβ = {\sqrt{(b^2+c^2)} \over \sqrt{(a^2+b^2+c^2)}} cosβ=(a2+b2+c2) (b2+c2)

s i n β = a ( a 2 + b 2 + c 2 ) sinβ = {a \over \sqrt{(a^2+b^2+c^2)}} sinβ=(a2+b2+c2) a

记第二步旋转矩阵Ry(-β) ,代入数据得:
[ c o s ( − β ) 0 s i n ( − β ) 0 0 1 0 0 − s i n ( − β ) 0 c o s ( − β ) 0 0 0 0 1 ] = [ c o s β 0 − s i n β 0 0 1 0 0 s i n β 0 c o s β 0 0 0 0 1 ] \begin{bmatrix} cos(-β) & 0& sin(-β) & 0\\ 0 & 1& 0& 0 \\ -sin(-β) & 0& cos(-β) & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}= \begin{bmatrix} cosβ & 0& -sinβ & 0\\ 0 & 1& 0& 0 \\ sinβ & 0& cosβ & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} cos(β)0sin(β)00100sin(β)0cos(β)00001=cosβ0sinβ00100sinβ0cosβ00001

= [ ( b 2 + c 2 ) ( a 2 + b 2 + c 2 ) 0 − a a 2 + b 2 + c 2 0 0 1 0 0 a ( a 2 + b 2 + c 2 ) 0 ( b 2 + c 2 ) ( a 2 + b 2 + c 2 ) 0 0 0 0 1 ] = \begin{bmatrix} {\sqrt{(b^2+c^2)} \over \sqrt{(a^2+b^2+c^2)}} & 0& - {a \over \sqrt{a^2+b^2+c^2}} & 0\\ 0 & 1& 0& 0 \\ {a \over \sqrt{(a^2+b^2+c^2)}} & 0& {\sqrt{(b^2+c^2)} \over \sqrt{(a^2+b^2+c^2)}} & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} =(a2+b2+c2) (b2+c2) 0(a2+b2+c2) a00100a2+b2+c2 a0(a2+b2+c2) (b2+c2) 00001
逆变换Ry(β)为:
[ c o s β 0 s i n β 0 0 1 0 0 − s i n β 0 c o s β 0 0 0 0 1 ] = [ ( b 2 + c 2 ) ( a 2 + b 2 + c 2 ) 0 a ( a 2 + b 2 + c 2 ) 0 0 1 0 0 − a ( a 2 + b 2 + c 2 ) 0 ( b 2 + c 2 ) ( a 2 + b 2 + c 2 ) 0 0 0 0 1 ] \begin{bmatrix} cosβ & 0& sinβ & 0\\ 0 & 1& 0& 0 \\ -sinβ & 0& cosβ & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}= \begin{bmatrix} {\sqrt{(b^2+c^2)} \over \sqrt{(a^2+b^2+c^2)}} & 0& {a \over \sqrt{(a^2+b^2+c^2)}} & 0\\ 0 & 1& 0& 0 \\ -{a \over \sqrt{(a^2+b^2+c^2)}} & 0& {\sqrt{(b^2+c^2)} \over \sqrt{(a^2+b^2+c^2)}} & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} cosβ0sinβ00100sinβ0cosβ00001=(a2+b2+c2) (b2+c2) 0(a2+b2+c2) a00100(a2+b2+c2) a0(a2+b2+c2) (b2+c2) 00001
最终得到 绕任意轴u旋转的旋转矩阵是【因为使用的列向量,因此执行的是左乘(从右往左)】:
M R = R x ( − α ) ∗ R y ( β ) ∗ R z ( θ ) ∗ R y ( − β ) ∗ R x ( α ) = MR=Rx(−α) * Ry(β) * Rz(θ) * Ry(−β) * Rx(α)= MR=Rx(α)Ry(β)Rz(θ)Ry(β)Rx(α)=在这里插入图片描述
其中(u, v, w)对应上文(a,b,c),如果旋转轴向量时单位化的,矩阵可以化简为:
在这里插入图片描述

推导方法二:

除了将旋转分解到三个方向,我们还能直接构建新的坐标系进行旋转,再将新坐标系的坐标转换到世界坐标系里。

矩阵变换:沿任意轴旋转及其推导

代码:

Matrix rotate(Vec3f &axis,double theta) {
	Matrix t;
	axis.normalize();

	theta = theta * PI / 180.0;
	double cosTheta = std::cos(theta);
	double sinTheta = std::sin(theta);

	//std::cout << "sinAngle:" << sinTheta << std::endl;
	//std::cout << "cosAngle:" << cosTheta << std::endl;
	double u = axis[0];
	double v = axis[1];
	double w = axis[2];

	t[0][0] = cosTheta + (u * u) * (1. - cosTheta);
	t[0][1] = u * v * (1. - cosTheta) + w * sinTheta;
	t[0][2] = u * w * (1.- cosTheta) - v * sinTheta;
	t[0][3] = 0;

	t[1][0] = u * v * (1. - cosTheta) - w * sinTheta;
	t[1][1] = cosTheta + v * v * (1 - cosTheta);
	t[1][2] = w * v * (1. - cosTheta) + u * sinTheta;
	t[1][3] = 0;

	t[2][0] = u * w * (1. - cosTheta) + v * sinTheta;
	t[2][1] = v * w * (1. - cosTheta) - u * sinTheta;
	t[2][2] = cosTheta + w * w * (1. - cosTheta);
	t[2][3] = 0;

	t[3][0] = 0;
	t[3][1] = 0;
	t[3][2] = 0;
	t[3][3] = 1;

	return t;
};

问题:
模型矩阵的第四行是(0, 0, 0, 1),它的运算结果是让新向量的w等于原来的w,那么为什么模型矩阵的大小不是3x4而是4x4呢?更近一步,为什么我们要用矩阵来表达这一变换,而不是直接让两个向量相加得到最终结果?

1.为什么使用4x4矩阵
假设我们的模型有n个顶点。在整个流程里,模型的每一个顶点都要乘以模型矩阵,视图矩阵,投影矩阵得到最终的坐标。这样的话我们就要进行3*n次矩阵乘法。但是同一个模型的这三个矩阵在这一帧画面里都是相同的,又因为矩阵乘法的符合结合律,所以我们可以先让模型矩阵,视图矩阵,投影矩阵相乘得到MVP复合矩阵,再让每一个顶点乘以复合矩阵,此时计算量就降低到n+2次。
故使用复合矩阵可以大大提高我们的计算效率,虽然模型矩阵和视图矩阵的第四维都不对w进行操作,但是透视矩阵会。所以我们要让矩阵的大小符合计算规则,即所有矩阵大小都是4x4。让他们可以互相相乘。
我们用4x4矩阵表示平移也是同样的目的。

2.视图矩阵

Reference:计算机图形学视图矩阵推导过程

视图矩阵的作用时将世界坐标系的点转换到摄像机坐标系,首先我们先来构建摄像机坐标系。
构建摄像机坐标系需要3个参数,摄像机位置,摄像机看向的目标,摄像机的上方向。摄像机位置,摄像机看向的目标做差可以得到摄像机的视线方向,加上我们指定的上方向,可以保证摄像机不会绕视线方向旋转。这三个参数就能唯一指定一个摄像机。
具体构建坐标系过程为:
假设视点或camera的局部坐标系为UVN,UVN分别指向右方、上方和后方从而构成右手坐标系,视点则局部坐标系处于坐标原点。
1、首先我们来求得视线方向 N = camPos – looka,并把N归一化。
在这里插入图片描述
2、up和N差积得到右向量U, U= up X N,归一化U。
在这里插入图片描述

3、然后N和U差积得到垂直于U,N平面的上向量V,V = U x N
在这里插入图片描述
此时我们已经构建好了摄像机坐标系的基向量。也就得到了摄像机坐标系到世界坐标系的过度矩阵R。
R = [ U . x V . x N . x 0 U . y V . y N . y 0 U . z V . z N . z 0 0 0 0 1 ] R= \begin{bmatrix} U.x & V.x & N.x & 0 \\ U.y & V.y & N.y & 0 \\ U.z & V.z &N.z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} R=U.xU.yU.z0V.xV.yV.z0N.xN.yN.z00001
我们的目标是计算世界坐标系中的物体在摄像机坐标系下的坐标,将世界坐标系平移至于相机坐标系重合,此时就可以利用过度矩阵进行坐标变换。
T = [ 1 0 0 c a m P o s . x 0 1 0 c a m P o s . y 0 0 1 c a m P o s . z 0 0 0 1 ] T= \begin{bmatrix} 1 & 0 & 0 & camPos.x \\ 0 & 1 & 0 & camPos.y \\ 0 & 0 &1 & camPos.z \\ 0 & 0 & 0 & 1 \end{bmatrix} T=100001000010camPos.xcamPos.ycamPos.z1

这样这个旋转R和平移T矩阵的组合矩阵M=T∗R,就是将相机坐标系中坐 标变换到世界坐标系中坐标的变换矩阵,那么所求的视图变换矩阵(世界坐标系中坐标转换到相机坐标系中坐标的矩阵)即为这个矩阵的逆: v i e w = M − 1 = ( T ∗ R ) − 1 = R − 1 ∗ T − 1 view=M^{−1}=(T*R)^{−1}=R^{−1}*T^{−1} view=M1=(TR)1=R1T1

= [ U . x U . y U . z 0 V . x V . y V . z 0 N . x N . y N . z 0 0 0 0 1 ] ∗ [ 1 0 0 − c a m P o s . x 0 1 0 − c a m P o s . y 0 0 1 − c a m P o s . z 0 0 0 1 ] =\begin{bmatrix} U.x & U.y & U.z &0 \\ V.x &V.y & V.z & 0 \\ N.x & N.y &N.z& 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} *\begin{bmatrix} 1 & 0 & 0 & -camPos.x \\ 0 & 1 & 0 & -camPos.y \\ 0 & 0 &1 & -camPos.z \\ 0 & 0 & 0 & 1 \end{bmatrix} =U.xV.xN.x0U.yV.yN.y0U.zV.zN.z00001100001000010camPos.xcamPos.ycamPos.z1

= [ U . x U . y U . z − U ∗ c a m P o s V . x V . y V . z − V ∗ c a m P o s N . x N . y N . z − N ∗ c a m P o s 0 0 0 1 ] =\begin{bmatrix} U.x & U.y & U.z &-U*camPos \\ V.x &V.y & V.z & -V*camPos \\ N.x & N.y &N.z& -N*camPos \\ 0 & 0 & 0 & 1 \end{bmatrix} =U.xV.xN.x0U.yV.yN.y0U.zV.zN.z0UcamPosVcamPosNcamPos1
代码:

Matrix 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;
	Minv= Matrix::identity();
	for (int i = 0; i < 3; i++) {
		Minv[0][i] = x[i];
		Minv[1][i] = y[i];
		Minv[2][i] = z[i];
	}
	Minv[0][3] = -1*(x * eye);
	Minv[1][3] = -1*(y * eye);
	Minv[2][3] = -1*(z * eye);

	return Minv;
}

3.投影矩阵

Reference:
OpenGL学习脚印: 投影矩阵和视口变换矩阵(math-projection and viewport matrix)
AirGuanZ:透视投影矩阵推导
YEMI:透视投影矩阵推导
醉里挑灯看剑:投影矩阵推导

投影矩阵主要分为两种,正交投影和透视投影。
在这里插入图片描述
两者都会将可见范围(对于透视投影是四棱台,正交投影是长方体)中的点映射到一个立方体中。

透视投影

指定透视投影的视锥需要6个参数,投影平面左边的位置(left),投影平面右边的位置(right),投影平面上边(top),投影平面下边(bottom),投影平面距摄像机的距离(near),最远平面距离摄像机的距离(far)。下面会用到这些参数的英文首字母简写。
我们的目标是最终将视图坐标(eye)通过投影矩阵变换为裁切坐标(clip),再通过透视除法将裁切坐标(clip)变成规范化设备坐标系(NDC)坐标,他们的转换关系如下
[ x c h i p y c h i p z c h i p w c h i p ] = P ∗ [ x e y e y e y e z e y e w e y e ] \begin{bmatrix} x_{chip}\\ y_{chip}\\ z_{chip}\\ w_{chip}\\ \end{bmatrix} =P*\begin{bmatrix} x_{eye}\\ y_{eye}\\ z_{eye}\\ w_{eye}\\ \end{bmatrix} xchipychipzchipwchip=Pxeyeyeyezeyeweye

[ x n d c y n d c z n d c ] = [ x c h i p / w c h i p y c h i p / w c h i p z c h i p / w c h i p ] (0) \tag{0}\begin{bmatrix} x_{ndc}\\ y_{ndc}\\ z_{ndc}\\ \end{bmatrix} =\begin{bmatrix} x_{chip} / w_{chip}\\ y_{chip} / w_{chip}\\ z_{chip} / w_{chip}\\ \end{bmatrix} xndcyndczndc=xchip/wchipychip/wchipzchip/wchip(0)
左边是裁切坐标系示意图,右边是ndc示意图
在这里插入图片描述
我们假设视锥内有一点 P ( x e y e , y e y e , z e y e ) P(x_{eye}, y_{eye}, z_{eye}) P(xeye,yeye,zeye)

他投影到投影平面的点为 P ( x p r o j e c t , y p r o j e c t , − n e a r ) P(x_{project}, y_{project}, -near) P(xproject,yproject,near)
在这里插入图片描述
通过俯视图,我们通过相似三角西得到如下关系
在这里插入图片描述

x p r o j e c t x e y e = − n e a r z e y e {x_{project} \over {x_{eye}}} = {{-near} \over {z_{eye}}} xeyexproject=zeyenear

即:
x p r o j e c t = − n e a r ∗ x e y e z e y e (1) \tag{1} x_{project} = {{-near * x_{eye}} \over {z_{eye}}} xproject=zeyenearxeye(1)

同理通过侧视图我们可以得到y的值:
y p r o j e c t = − n e a r ∗ y e y e z e y e (2) \tag{2} y_{project} = {{-near * y_{eye}} \over {z_{eye}}} yproject=zeyenearyeye(2)

因为我们需要Z值保存深度信息后续使用,所以这里先设Z为f(z),那么我们当前的投影坐标就是:
P ′ = ( − n e a r ∗ x e y e z e y e , − n e a r ∗ y e y e z e y e , f ( z ) ) P' = ({{-near * x_{eye}} \over {z_{eye}}},{{-near * y_{eye}} \over {z_{eye}}},f(z)) P=(zeyenearxeye,zeyenearyeye,f(z))
P’x和P‘y都不是关于Px,Py的线性函数,不能通过矩阵变换得到,我们这里进行等价的齐次变化
P ′ = ( n e a r ∗ x e y e , n e a r ∗ y e y e , − z e y e ∗ f ( z ) , − z e y e ) P' = (near * x_{eye}, near * y_{eye},-z_{eye}*f(z),-z_{eye}) P=(nearxeye,nearyeye,zeyef(z),zeye)

现在我们的运算写成矩阵形式为:
[ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ] ∗ [ x e y e y e y e z e y e w e y e ] = [ n e a r ∗ x e y e n e a r ∗ y e y e f ( z ) − z e y e ] \begin{bmatrix} ?&?&?&?\\ ?&?&?&?\\ ?&?&?&?\\ ?&?&?&?\\ \end{bmatrix} *\begin{bmatrix} x_{eye}\\ y_{eye}\\ z_{eye}\\ w_{eye}\\ \end{bmatrix} =\begin{bmatrix} near * x_{eye}\\ near * y_{eye}\\ f(z)\\ -z_{eye}\\ \end{bmatrix} ????????????????xeyeyeyezeyeweye=nearxeyenearyeyef(z)zeye
因为 结果的w维已经确定为-Zeye,我们可以推出矩阵最后一行为:
[ ? ? ? ? ? ? ? ? ? ? ? ? 0 0 − 1 0 ] ∗ [ x e y e y e y e z e y e w e y e ] = [ n e a r ∗ x e y e n e a r ∗ y e y e f ( z ) − z e y e ] \begin{bmatrix} ?&?&?&?\\ ?&?&?&?\\ ?&?&?&?\\ 0&0&-1&0\\ \end{bmatrix} *\begin{bmatrix} x_{eye}\\ y_{eye}\\ z_{eye}\\ w_{eye}\\ \end{bmatrix} =\begin{bmatrix} near * x_{eye}\\ near * y_{eye}\\ f(z)\\ -z_{eye}\\ \end{bmatrix} ???0???0???1???0xeyeyeyezeyeweye=nearxeyenearyeyef(z)zeye
接下来我们将投影平面上的坐标转线性映射到(-1,1)空间。
其中Xp的映射关系如下图所示:
在这里插入图片描述
设这条直线方程为
X n d c = A ∗ X p r o j e c t + B X_{ndc} = A * X_{project} + B Xndc=AXproject+B
我们已知直线上两点(l,-1), (r,1);
带入直线得:
{ − 1 = A ∗ l + B 1 = A ∗ r + B ⇒ { A = 2 r − l B = l + r l − r \begin{cases} -1 = A * l +B\\ 1 = A * r +B\\ \end{cases}⇒\begin{cases} A= {2 \over {r-l}}\\ B={ {l+r}\over{l-r}}\\ \end{cases} {1=Al+B1=Ar+B{A=rl2B=lrl+r


X n d c = 2 r − l ∗ X p r o j e c t + l + r l − r (3) \tag{3} X_{ndc} = {2 \over {r-l}} * X_{project} + { {l+r}\over{l-r}} Xndc=rl2Xproject+lrl+r(3)

将(1)带入(3)得:
X n d c = 2 r − l ∗ − n e a r ∗ x e y e z e y e + l + r l − r X_{ndc} = {2 \over {r-l}} * {{-near * x_{eye}} \over {z_{eye}}} + { {l+r}\over{l-r}} Xndc=rl2zeyenearxeye+lrl+r

同理可得
Y n d c = 2 t − b ∗ − n e a r ∗ y e y e z e y e + t + b b − t Y_{ndc} = {2 \over {t-b}} * {{-near * y_{eye}} \over {z_{eye}}} + { {t+b}\over{b-t}} Yndc=tb2zeyenearyeye+btt+b

根据ndc坐标和clip坐标的关系(0)得到
X c l i p = 2 ∗ n r − l ∗ x e y e − l + r l − r ∗ z e y e X_{clip} = {2*n \over {r-l}} * { x_{eye}} - { {l+r}\over{l-r}}*z_{eye} Xclip=rl2nxeyelrl+rzeye

Y c l i p = 2 ∗ n t − b ∗ y e y e − t + b b − t ∗ z e y e Y_{clip} ={2*n \over {t-b}} * { y_{eye}} - { {t+b}\over{b-t}}*z_{eye} Yclip=tb2nyeyebtt+bzeye

由此得到矩阵的前两行为:
[ 2 ∗ n r − l 0 − l + r l − r 0 0 2 ∗ n t − b − t + b b − t 0 ? ? ? ? 0 0 − 1 0 ] ∗ [ x e y e y e y e z e y e w e y e ] = [ X c l i p Y c l i p f ( z ) − z e y e ] \begin{bmatrix} {2*n \over {r-l}}&0&- { {l+r}\over{l-r}}&0\\ 0&{2*n \over {t-b}}&- { {t+b}\over{b-t}}&0\\ ?&?&?&?\\ 0&0&-1&0\\ \end{bmatrix} *\begin{bmatrix} x_{eye}\\ y_{eye}\\ z_{eye}\\ w_{eye}\\ \end{bmatrix} =\begin{bmatrix} X_{clip}\\ Y_{clip}\\ f(z)\\ -z_{eye}\\ \end{bmatrix} rl2n0?00tb2n?0lrl+rbtt+b?100?0xeyeyeyezeyeweye=XclipYclipf(z)zeye
由于Ze投影到平面时结果都为−n,因此寻找zn与之前的x,y分量不太一样。我们知道zn与x,y分量无关,因此上述矩阵P可以书写为:
[ 2 ∗ n r − l 0 − l + r l − r 0 0 2 ∗ n t − b − t + b b − t 0 0 0 A B 0 0 − 1 0 ] ∗ [ x e y e y e y e z e y e w e y e ] = [ X c l i p Y c l i p f ( z ) − z e y e ] \begin{bmatrix} {2*n \over {r-l}}&0&- { {l+r}\over{l-r}}&0\\ 0&{2*n \over {t-b}}&- { {t+b}\over{b-t}}&0\\ 0&0&A&B\\ 0&0&-1&0\\ \end{bmatrix} *\begin{bmatrix} x_{eye}\\ y_{eye}\\ z_{eye}\\ w_{eye}\\ \end{bmatrix} =\begin{bmatrix} X_{clip}\\ Y_{clip}\\ f(z)\\ -z_{eye}\\ \end{bmatrix} rl2n0000tb2n00lrl+rbtt+bA100B0xeyeyeyezeyeweye=XclipYclipf(z)zeye
则有
Z n d c = A ∗ z e y e + B ∗ w e y e − z e y e Z_{ndc} = {{A*z_{eye} + B*w_{eye}}\over{-z_{eye}}} Zndc=zeyeAzeye+Bweye

又因为w_{eye} = 1,故可以化简得:
Z n d c = A ∗ z e y e + B − z e y e (4) \tag{4} Z_{ndc} = {{A*z_{eye} + B}\over{-z_{eye}}} Zndc=zeyeAzeye+B(4)

我们已知Zndc与Zeye之间的对应关系(-n,-1)(-f,1)带入(4)得:

{ − 1 = − n A + B n 1 = − f A + B f ⇒ { A = − f + n f − n B = − 2 n f f − n \begin{cases} -1 = {{-nA + B}\over{n}}\\ 1 = {{-fA + B}\over{f}}\\ \end{cases} ⇒\begin{cases} A= -{{f+n} \over {f-n}}\\ B={ {-2nf}\over{f-n}}\\ \end{cases} {1=nnA+B1=ffA+B{A=fnf+nB=fn2nf

将AB带入4得到
Z n d c = − ( f + n ) f − n ∗ z e y e + − 2 n f f − n − z e y e Z_{ndc} = {{{{-(f+n)} \over {f-n}}*z_{eye} + { {-2nf}\over{f-n}}}\over{-z_{eye}}} Zndc=zeyefn(f+n)zeye+fn2nf

我们的投影矩阵为:
[ 2 ∗ n r − l 0 − l + r l − r 0 0 2 ∗ n t − b − t + b b − t 0 0 0 − ( f + n ) f − n − 2 n f f − n 0 0 − 1 0 ] (透视投影矩阵) \tag{透视投影矩阵} \begin{bmatrix} {2*n \over {r-l}}&0&- { {l+r}\over{l-r}}&0\\ 0&{2*n \over {t-b}}&- { {t+b}\over{b-t}}&0\\ 0&0&{{-(f+n)} \over {f-n}}&{{-2nf}\over{f-n}}\\ 0&0&-1&0\\ \end{bmatrix} rl2n0000tb2n00lrl+rbtt+bfn(f+n)100fn2nf0()
如果我们的视锥是对称的,满足r = -l,t = -b,则投影矩阵可以化简为
[ n r 0 0 0 0 n t 0 0 0 0 − ( f + n ) f − n − 2 n f f − n 0 0 − 1 0 ] (简化的透视投影矩阵) \tag{简化的透视投影矩阵} \begin{bmatrix} {n \over {r}}&0&0&0\\ 0&{n \over {t}}&0&0\\ 0&0&{{-(f+n)} \over {f-n}}&{ {-2nf}\over{f-n}}\\ 0&0&-1&0\\ \end{bmatrix} rn0000tn0000fn(f+n)100fn2nf0()

通过fov计算投影矩阵

另外一种经常使用 的方式是通过视角(Fov),宽高比(Aspect)来指定透视投影
在这里插入图片描述

在这里插入图片描述
我们需要给定fov,Aspect,near,far来指定视锥。
通过fov和near,我们可以求得平面高度为
h 2 = n e a r ∗ t a n ( θ 2 ) {h \over 2} = near * tan({θ\over 2}) 2h=neartan(2θ)在这里插入图片描述
通过平面高度和宽高比,我们可以求得投影平面宽度
w 2 = h 2 ∗ a s p e c t = n e a r ∗ t a n ( θ 2 ) ∗ a s p e c t {w \over 2} = {h \over 2}*aspect = near * tan({θ\over 2})*aspect 2w=2haspect=neartan(2θ)aspect

宽高与之前的 l, r, t, b 的对应关系为:
r = w 2 = n e a r ∗ t a n ( θ 2 ) ∗ a s p e c t l = − r t = h 2 = n e a r ∗ t a n ( θ 2 ) b = − t r = {w \over 2} = near * tan({θ\over 2})*aspect\\ l = -r\\ t = {h \over 2} = near * tan({θ\over 2})\\ b = -t\\ r=2w=neartan(2θ)aspectl=rt=2h=neartan(2θ)b=t
带入之前的简化投影矩阵得到
[ 1 t a n ( θ 2 ) ∗ a s p e c t 0 0 0 0 1 t a n ( θ 2 ) 0 0 0 0 − ( f + n ) f − n − 2 n f f − n 0 0 − 1 0 ] (fov透视投影矩阵) \tag{fov透视投影矩阵} \begin{bmatrix} {1 \over {tan({θ\over 2})∗aspect}}&0&0&0\\ 0&{1 \over { tan({θ\over 2})}}&0&0\\ 0&0&{{-(f+n)} \over {f-n}}&{ {-2nf}\over{f-n}}\\ 0&0&-1&0\\ \end{bmatrix} tan(2θ)aspect10000tan(2θ)10000fn(f+n)100fn2nf0(fov)

Matrix setFrustum(double fovy, double aspect, double near, double far)
{
	double z_range = far - near;
	Matrix matrix;
	matrix= Matrix::identity();
	assert(fovy > 0 && aspect > 0);
	assert(near > 0 && far > 0 && z_range > 0);
	matrix[1][1] = 1 / (float)tan(fovy / 2);
	matrix[0][0] = matrix[1][1] / aspect;
	matrix[2][2] = -(near + far) / z_range;
	matrix[2][3] = -2 * near * far / z_range;
	matrix[3][2] = -1;
	matrix[3][3] = 0;

	return matrix;
}

Zfighting问题

回过头去看透视投影部分,zn与ze的关系式
Z n d c = − ( f + n ) f − n ∗ z e y e + − 2 n f f − n − z e y e Z_{ndc} = {{{{-(f+n)} \over {f-n}}*z_{eye} + { {-2nf}\over{f-n}}}\over{-z_{eye}}} Zndc=zeyefn(f+n)zeye+fn2nf
这是一个非线性关系函数,函数关系如图。
在这里插入图片描述
从图我们可以看到,在近裁剪平面附近Zn值变化比较大,精确度较好;而在远裁剪平面附近,有一段距离内,Zn近乎持平,精确度不好。当增大远近裁剪平面的范围[−n,−f]后,如右边图所示,我们看到在远裁剪平面附近,不同相机坐标Ze对应的Zn相同,精确度低的现象更为明显,这种深度的精确度引起的问题称之为zFighting。要尽量减小[-n,-f]的范围,以减轻zFighting问题。

正交投影

在这里插入图片描述
对于正交投影,有
x p = x e , y p = y e x_p=x_e,y_p=y_e xp=xe,yp=ye

因而可以直接利用Xe与Xn的映射关系:[l,−1],[r,1],
利用Ye和Yn的映射关系:[b,−1],[t,1],
以及Ze和Zn的映射关系:[−n,−1],[−f,1]。
与透视矩阵推导类似,我们直接将映射关系[l,−1],[r,1]带入直线。
x n d c = A ∗ x e y e + B x_{ndc}=A*x_{eye}+B xndc=Axeye+B

解得:
x n d c = 2 r − l ∗ x e y e + r + l r − l x_{ndc}={2\over{r-l}}*x_{eye}+{{r+l}\over{r-l}} xndc=rl2xeye+rlr+l

同理可以得出
y n d c = 2 t − b ∗ y e y e + t + b t − b y_{ndc}={2\over{t-b}}*y_{eye}+{{t+b}\over{t-b}} yndc=tb2yeye+tbt+b

z n d c = − 2 f − n ∗ z e y e + f + n f − n z_{ndc}={-2\over{f-n}}*z_{eye}+{{f+n}\over{f-n}} zndc=fn2zeye+fnf+n

对于正交坐标,因为xy维度没有变量z参与,所以可以不进行齐次化。故ndc坐标和裁切坐标相同。我们通过上面三个式子得到正交投影矩阵
[ 2 r − l 0 0 − r + l r − l 0 2 t − b 0 − t + b t − b 0 0 − 2 f − n − f + n f − n 0 0 0 1 ] (正交透视投影矩阵) \tag{正交透视投影矩阵} \begin{bmatrix} {2 \over {r-l}}&0&0&-{{r+l}\over{r-l}}\\ 0& {2 \over {t-b}} &0&-{{t+b}\over{t-b}}\\ 0&0&{{-2} \over {f-n}}&-{ {f+n}\over{f-n}}\\ 0&0&0&1\\ \end{bmatrix} rl20000tb20000fn20rlr+ltbt+bfnf+n1()
如果视锥对称(r = −l, t = −b),我们同样可以进行简化得到:
[ 1 r 0 0 0 0 1 t 0 0 0 0 − 2 f − n − f + n f − n 0 0 0 1 ] (简化正交透视投影矩阵) \tag{简化正交透视投影矩阵} \begin{bmatrix} {1 \over r}&0&0&0\\ 0& {1 \over t} &0&0\\ 0&0&{{-2} \over {f-n}}&-{ {f+n}\over{f-n}}\\ 0&0&0&1\\ \end{bmatrix} r10000t10000fn2000fnf+n1()

4.视窗变换

在这里插入图片描述
视窗变换需要指定4个参数,窗口的左下角位置(sx,sy),窗口宽度width,窗口宽度height
在ndc坐标系中,我们的所有坐标范围都在[-1,1]中,视窗变换的目的是让x坐标变换到[sx,sx+width],y坐标变换到[sy,sy+height],z坐标变换到[0,1]即:
x s c r e e n = s x + x ( x n d c + 1 ) ∗ 0.5 ∗ w i d t h y s c r e e n = s y + ( y n d c + 1 ) ∗ 0.5 ∗ h e i g h t z s c r e e n = ( z n d c + 1 ) ∗ 0.5 x_{screen} = s_x+x(x_{ndc}+1)*0.5*width\\ y_{screen} = s_y+(y_{ndc}+1)*0.5*height\\ z_{screen} = (z_{ndc}+1)*0.5 xscreen=sx+x(xndc+1)0.5widthyscreen=sy+(yndc+1)0.5heightzscreen=(zndc+1)0.5

写为矩阵模式为:
[ w i d t h 2 0 0 s x + w i d t h 2 0 h e i g h t 2 0 s y + h e i g h t 2 0 0 1 2 1 2 0 0 0 1 ] (视口变换矩阵) \tag{视口变换矩阵} \begin{bmatrix} {width \over 2}&0&0&{s_x +{width \over 2} }\\ 0& {height \over 2} &0&{s_y + {height \over 2}}\\ 0&0&{1 \over 2}&{ 1\over2}\\ 0&0&0&1\\ \end{bmatrix} 2width00002height0000210sx+2widthsy+2height211()

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值