games101作业1

题目

给定三维下三个点 v0(2.0, 0.0, −2.0), v1(0.0, 2.0, −2.0), v2(−2.0, 0.0, −2.0), 你需要将这三个点的坐标变换为屏幕坐标并在屏幕上绘制出对应的线框三角形 (在代码框架中,我们已经提供了 draw_triangle 函数,所以你只需要去构建变换矩阵即可)。简而言之,我们需要进行模型、视图、投影、视口等变换来将三角形显示在屏幕上。在提供的代码框架中,我们留下了模型变换和投影变换的部分给你去完成。

题解

本次作业需要实现代码框架中的两个接口:

Eigen::Matrix4f get_model_matrix(float rotation_angle);
Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio,float zNear, float zFar);
1. 旋转

第一个接口相对比较简单,直接返回旋转矩阵即可。注意:需要将角度转为弧度。
绕Z轴旋转矩阵如下:
R z ( α ) = ( cos ⁡ α − sin ⁡ α 0 0 sin ⁡ α cos ⁡ α 0 0 0 0 1 0 0 0 0 1 ) \mathbf{R}_z(\alpha)=\left(\begin{array}{cccc} \cos \alpha & -\sin \alpha & 0 & 0 \\ \sin \alpha & \cos \alpha & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{array}\right) Rz(α)= cosαsinα00sinαcosα0000100001
课程中还讲到了绕X 轴的旋转矩阵
R x ( α ) = ( 1 0 0 0 0 cos ⁡ α − sin ⁡ α 0 0 sin ⁡ α cos ⁡ α 0 0 0 0 1 ) \mathbf{R}_x(\alpha)=\left(\begin{array}{cccc} 1 & 0 & 0 & 0 \\ 0 & \cos \alpha & -\sin \alpha & 0 \\ 0 & \sin \alpha & \cos \alpha & 0 \\ 0 & 0 & 0 & 1 \end{array}\right) Rx(α)= 10000cosαsinα00sinαcosα00001
绕Y轴的旋转矩阵( 注意这里的sin符号和其他两种情况不同,是因为右手坐标系,从+x->+z旋转,Y轴和拇指方向相反)
R y ( α ) = ( cos ⁡ α 0 sin ⁡ α 0 0 1 0 0 − sin ⁡ α 0 cos ⁡ α 0 0 0 0 1 ) \mathbf{R}_y(\alpha)=\left(\begin{array}{cccc} \cos \alpha & 0 & \sin \alpha & 0 \\ 0 & 1 & 0 & 0 \\ -\sin \alpha & 0 & \cos \alpha & 0 \\ 0 & 0 & 0 & 1 \end{array}\right) Ry(α)= cosα0sinα00100sinα0cosα00001
角度转为弧度:
α = θ π 180 \alpha=\theta \frac{\pi}{180} α=θ180π

2. 正交投影

根据课程中的推导过程:其中 z ∈ [ − 1 , 1 ] z \in[-1,1] z[1,1],采用右手坐标系。观察变换完成后,将物体投影到相机坐标系中。然后在相机坐标系中进行投影变换:正交投影或者透视投影。正交投影的基本思想是:将长方体视窗体平移到原点,然后进行缩放,使得: x , y , z ∈ [ − 1 , 1 ] x,y,z \in[-1,1] x,y,z[1,1]
假设长方体视窗体的左右上下前后六个面的坐标分别为: l , r , t , b , n , f l,r,t,b,n,f l,r,t,b,n,f. 正交投影矩阵如下:
M ortho  = [ 2 r − l 0 0 0 0 2 t − b 0 0 0 0 2 n − f 0 0 0 0 1 ] [ 1 0 0 − r + l 2 0 1 0 − t + b 2 0 0 1 − n + f 2 0 0 0 1 ] = [ 2 r − l 0 0 − r + l r − l 0 2 t − b 0 − t + b t − b 0 0 2 n − f − n + f n − f 0 0 0 1 ] M_{\text {ortho }}=\left[\begin{array}{cccc} \frac{2}{r-l} & 0 & 0 & 0 \\ 0 & \frac{2}{t-b} & 0 & 0 \\ 0 & 0 & \frac{2}{n-f} & 0 \\ 0 & 0 & 0 & 1 \end{array}\right]\left[\begin{array}{cccc} 1 & 0 & 0 & -\frac{r+l}{2} \\ 0 & 1 & 0 & -\frac{t+b}{2} \\ 0 & 0 & 1 & -\frac{n+f}{2} \\ 0 & 0 & 0 & 1 \end{array}\right]=\left[\begin{array}{cccc} \frac{2}{r-l} & 0 & 0 & -\frac{r+l}{r-l} \\ 0 & \frac{2}{t-b} & 0 & -\frac{t+b}{t-b} \\ 0 & 0 & \frac{2}{n-f} & -\frac{n+f}{n-f} \\ 0 & 0 & 0 & 1 \end{array}\right] Mortho = rl20000tb20000nf200001 1000010000102r+l2t+b2n+f1 = rl20000tb20000nf20rlr+ltbt+bnfn+f1

3.透视投影

透视投影可以看作是将视锥体的大端进行压缩成长方体视窗体。透视投影矩阵可以写成如下形式:
M persp  = M ortho  M persp  →  ortho  M_{\text {persp }}=M_{\text {ortho }} M_{\text {persp } \rightarrow \text { ortho }} Mpersp =Mortho Mpersp  ortho 
压缩过程需要满足以下两个条件:
1.所有近平面的坐标不发生改变
2.远平面的z坐标不发生改变。
教程中根据上述条件可以推出
M persp  →  ortho  = ( n 0 0 0 0 n 0 0 0 0 n + f − n f 0 0 1 0 ) M_{\text {persp } \rightarrow \text { ortho }}=\left(\begin{array}{cccc} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & n+f & -nf \\ 0 & 0 & 1 & 0 \end{array}\right) Mpersp  ortho = n0000n0000n+f100nf0
所以投影矩阵如下:
M persp  = M ortho  M persp  →  ortho  = [ 2 r − l 0 0 − r + l r − l 0 2 t − b 0 − t + b t − b 0 0 2 n − f − n + f n − f 0 0 0 1 ] [ n 0 0 0 0 n 0 0 0 0 n + f − n f 0 0 1 0 ] = [ 2 n r − l 0 r + l l − r 0 0 2 n t − b t + b b − t 0 0 0 n + f n − f − 2 n f n − f 0 0 1 0 ] M_{\text {persp }}=M_{\text {ortho }} M_{\text {persp } \rightarrow \text { ortho }}= \left[\begin{array}{cccc} \frac{2}{r-l} & 0 & 0 & -\frac{r+l}{r-l} \\ 0 & \frac{2}{t-b} & 0 & -\frac{t+b}{t-b} \\ 0 & 0 & \frac{2}{n-f} & -\frac{n+f}{n-f} \\ 0 & 0 & 0 & 1 \end{array}\right] \left[\begin{array}{cccc} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & n+f & -nf \\ 0 & 0 & 1 & 0 \end{array}\right]= \left[\begin{array}{cccc} \frac{2n}{r-l} & 0 & \frac{r+l}{l-r} & 0 \\ 0 & \frac{2n}{t-b} & \frac{t+b}{b-t} & 0 \\ 0 & 0 & \frac{n+f}{n-f} & -\frac{2nf}{n-f} \\ 0 & 0 & 1 & 0 \end{array}\right] Mpersp =Mortho Mpersp  ortho = rl20000tb20000nf20rlr+ltbt+bnfn+f1 n0000n0000n+f100nf0 = rl2n0000tb2n00lrr+lbtt+bnfn+f100nf2nf0
此时投影矩阵就算推导完毕,但是投影接口的参数是:张角 f o v fov fov,纵横比 a s p e c t aspect aspect,近平面到原点的距离 n e a r near near,远平面到原点的距离 f a r far far
下面将矩阵中的参数都转为接口中的入参:
一般情况下,长方体视窗体是轴对称,故有 l = − r , b = − t l=-r,b=-t l=r,b=t,由于从原点看向 − z -z z方向看去,所以 n = − n e a r , f = − f a r n=-near,f=-far n=near,f=far
w = r − l , h = t − b , t a n ( f o v 2 ) = h / 2 n e a r , a s p e c t = w h , w=r-l,\\ h=t-b,\\ tan(\frac{fov}{2})=\frac{h/2}{near},aspect=\frac{w}{h}, w=rl,h=tb,tan(2fov)=nearh/2,aspect=hw,

h = 2 ∗ n e a r ∗ t a n ( f o v 2 ) w = h ∗ a s p e c t = 2 ∗ n e a r ∗ t a n ( f o v 2 ) ∗ a s p e c t h=2*near *tan(\frac{fov}{2})\\ w=h*aspect=2*near *tan(\frac{fov}{2})*aspect h=2neartan(2fov)w=haspect=2neartan(2fov)aspect
化简后
M persp  = [ − 1 a s p e c t ∗ t a n ( f o v 2 ) 0 0 0 0 − 1 t a n ( f o v 2 ) 0 0 0 0 n e a r + f a r n e a r − f a r 2 ∗ n e a r ∗ f a r n e a r − f a r 0 0 1 0 ] M_{\text {persp }}=\left[\begin{array}{cccc} -\frac{1}{aspect*tan(\frac{fov}{2})} & 0 & 0 & 0 \\ 0 & -\frac{1}{tan(\frac{fov}{2})} & 0 & 0 \\ 0 & 0 & \frac{near+far}{near-far} & \frac{2*near*far}{near-far} \\ 0 & 0 & 1 & 0 \end{array}\right] Mpersp = aspecttan(2fov)10000tan(2fov)10000nearfarnear+far100nearfar2nearfar0
这个结果和glm库中实现不同

	template<typename T>
	GLM_FUNC_QUALIFIER mat<4, 4, T, defaultp> perspectiveRH_NO(T fovy, T aspect, T zNear, T zFar)
	{
		assert(abs(aspect - std::numeric_limits<T>::epsilon()) > static_cast<T>(0));

		T const tanHalfFovy = tan(fovy / static_cast<T>(2));

		mat<4, 4, T, defaultp> Result(static_cast<T>(0));
		Result[0][0] = static_cast<T>(1) / (aspect * tanHalfFovy);
		Result[1][1] = static_cast<T>(1) / (tanHalfFovy);
		Result[2][2] = - (zFar + zNear) / (zFar - zNear);
		Result[2][3] = - static_cast<T>(1);
		Result[3][2] = - (static_cast<T>(2) * zFar * zNear) / (zFar - zNear);
		return Result;
	}

都是右手坐标系,为什么不同呢?博客对此进行了解释。主要由于glm 是基于 n , f n,f n,f都是正值进行推导的,同时,glm的透视投影中还进行了NDC坐标转换,而NDC坐标系是左手坐标系。

结果

在这里插入图片描述

代码

constexpr double MY_PI = 3.1415926;

inline double DEG2RAD(double deg) { return deg * MY_PI / 180; }
Eigen::Matrix4f get_model_matrix(float rotation_angle)
{
    Eigen::Matrix4f model = Eigen::Matrix4f::Identity();

    double rad = DEG2RAD(rotation_angle);
    model << cos(rad), -sin(rad), 0, 0,
        sin(rad), cos(rad), 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1;
    return model;
}
Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio,
                                      float zNear, float zFar)
{
    Eigen::Matrix4f projection = Eigen::Matrix4f::Identity();

    double rad = DEG2RAD(eye_fov/2);

    projection << -1/(aspect_ratio * tan(rad)),0, 0, 0,
        0, -1/tan(rad), 0, 0,
        0, 0, (zNear+zFar) /(zNear - zFar), 2*zNear*zFar/(zNear - zFar),
        0, 0, 1, 0;

    return projection;
}

如果不转到左手坐标系,在作业2中,结果不对,所以在作业2中使用如下的投影变化代码:


Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar)
{
    // TODO: Copy-paste your implementation from the previous assignment.
    Eigen::Matrix4f projection = Eigen::Matrix4f::Identity();

    double rad = DEG2RAD(eye_fov / 2);

    projection << 1 / (aspect_ratio * tan(rad)), 0, 0, 0,
        0, 1 / tan(rad), 0, 0,
        0, 0, (zNear + zFar) / (zNear - zFar), 2 * zNear * zFar / (zNear - zFar),
        0, 0, -1, 0;

    return projection;
}

参考文献
OpenGL NDC 左手还是右手?
Games101中的透视矩阵和glm::perspective的关系

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xhh-cy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值