我的原始代码如下:
Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio,
float zNear, float zFar)
{
Eigen::Matrix4f projection = Eigen::Matrix4f::Identity();
eye_fov = eye_fov/180.0*MY_PI;
// M(persp) = M(ortho) * M(persp->ortho)
// 参数列表中已有n和f,先求r, l, t, b
float yTop = std::abs(zNear) * std::tan(eye_fov/2.0);
float yButtom = - yTop;
float xRight = yTop * aspect_ratio;
float xLeft = - xRight;
// 先求 M(ortho)
Eigen::Matrix4f mOrthoScale;
mOrthoScale << 1.0/(xRight), 0, 0, 0,
0, 1.0/(yTop), 0, 0,
0, 0, 2.0/(zNear-zFar), 0,
0, 0, 0, 1;
Eigen::Matrix4f mOrthoTranslate;
mOrthoTranslate << 1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, -(zNear+zFar)/2.0,
0, 0, 0, 1;
// Eigen::Matrix4f mOrtho = mOrthoScale * mOrthoTranslate;
// 再求 M(persp->ortho)
Eigen::Matrix4f mPerspToOrtho;
mPerspToOrtho << zNear, 0, 0, 0,
0, zNear, 0, 0,
0, 0, zNear+zFar, -1.0*zNear*zFar,
0, 0, 1, 0;
// 汇总得到 M(persp)
projection = mOrthoScale * mOrthoTranslate * mPerspToOrtho;
return projection;
}
代码完全按照闫老师的PPT写的,把矩阵中的一些能简单算出来的参数稍微化简了。运行效果如下:
画出来的三角形是上下颠倒的,根据简单实验,修改三角形三个顶点坐标,使用非对称三角形,还可以发现运行结果是上下左右颠倒的,不过这些都不是重点。
直接说我的想法,是因为作业代码框架中给出的三角形(坐标分别是(2,0,-7), (0,2,-7), (-2,0,-7),做完view变换后的坐标,以摄像机为原点)不在作业给出的要投影的平截锥体(Frustum)内(作业中的Frustum:视场角为45°,宽高比为1,n为+0.1,f为+50):作业中三角形在z=-7平面上,而Frustum在z=+0.1~z=+50内,显然三角形不在Frustum内。我的理解是,我们做透视投影、视口变换的目的是把Frustum内的东西先整到Frustum前面那个小平面上,然后再把这个小平面通过视口变换显示到屏幕上。对于Frustum之外的东西,我认为理想情况下是不应该显示出来,因为摄影机就应该只能拍到Frustum内的东西;但实际给出的实现(也就是这些矩阵),确确实实能显示好Frustum内的东西,但对其外的东西,行为是不固定的!
作业中给出的三角形就是Frustum之外的东西,最后显示的结果就是不确定的,无论是上下颠倒还是左右颠倒都没有意义,因为大前提(在Frustum内)已经不满足了。
好了,上面是我的想法,按照这个想法,只要把作业中三角形的坐标改了,把三角形放到Frustum中,就应该可以正常显示了。三角形做完view变换后的坐标简单改成(2,0,7), (0,2,7), (-2,0,7)(只是把z坐标取反),那么原坐标应该为(2,0,12), (0,2,12), (-2,0,12)。也就是把下面这行:
std::vector<Eigen::Vector3f> pos{{2, 0, -2}, {0, 2, -2}, {-2, 0, -2}};
z坐标改成:
std::vector<Eigen::Vector3f> pos{{2, 0, 12}, {0, 2, 12}, {-2, 0, 12}};
运行看看效果:
成了。
再举一个例子。
还是上面说的那样,上课给出的矩阵只管把Frustum内的东西显示出来,其外的东西到底是也显示、还是显示错误、还是直接不显示,是不确定的!上面三角形的上下左右颠倒只是一种情况,再举一个显示一部分的例子:三角形view变换后的坐标(4,4,25), (-4,4,25), (0,12,25),这是z=25平面上的一个等腰三角形,它的下部分在Frustum内,上面一个角不在Frustum内。把代码改成:
// z=25是做了摄像机变换(view变换)之后的结果,z=30是原坐标
std::vector<Eigen::Vector3f> pos{{4, 4, 30}, {-4, 4, 30}, {0, 12, 30}};
按照我的想法,运行出来应该只显示下面在Frustum中的部分。运行验证:
确实是这样。
我把代码整了个git仓库:https://gitee.com/sheep-fish/games101-assignment1,在init提交之后就只有两次提交,都是只改了三角形坐标,然后运行出上面的几张截图。
个人水平有限,上面的仅是我的猜想,所谓“终极解释”单单为了吸人眼球,我只是有这么一个想法,然后做了上面的一点验证,就发出来了。如果我的想法是错的,我深感抱歉,还望大佬指正。