再谈透视投影

这是第三次将目光放到变换矩阵上。之前经历过初次提交修正,不过,剧本果然越来越精彩。

首先是终于理解了三个矩阵。我是因为搞不清 HLSL 中莫名其妙的向量和矩阵的左右关系才去搜索 MUL 指令的,但是偶然从一篇讲解文章中理解了三个变换的意义:

首先明白观察矩阵的目的是:将一个世界空间的坐标转换到观察坐系中。即将一个由X,Y,Z轴构成的世界空间的坐标点调整到由摄相机的上向量、观察方向、右向量的空间中。在这里,摄相机的右向量(上向量与观察方向的叉乘)等同于世界坐标系中的X轴。上向量等同于Y轴,观察方向等同于Z轴。

……

于是,我们可以知道,乘以观察矩阵,就相当于是把一个点以原点为起点,自己为终点,构造一个向量,然后求出自己在由摄相机的各个轴构上的投影。最后再根据摄相机位置移回原点的过程。

我一直用“MVP矩阵”的方法来记忆世界、观察、投影三个矩阵的顺序(MVP 嘛,不管是 Most Valuable Professional 还是 Most Valuable Player,随便啦),但是不理解为什么就该这么乘。现在我理解了:

  1. M(model),是将各个顶点从模型坐标系(局部坐标系)变换到世界坐标系(全局坐标系)的;
  2. V(view),是将上一步的顶点从世界坐标系(以世界坐标系中的原点为原点)变换到观察者(摄像机)坐标系(以世界坐标系中的摄像机坐标为原点)的;
  3. P(projection),是将上一步的顶点从三维世界空间中,按投影方式(常用:透视投影、正交投影)投影到一个屏幕长方体(X 和 Y 是二维屏幕平面,Z 是深度关系)中的。

结合观察矩阵的 XYZ 意义,很容易理解投影矩阵的生成。所以这就弄清楚了。

接下来是 DX 中渲染的问题:近剪裁面,零,还是非零。

在我的一个测试场景中,我期望的渲染结果是这样的:

正常遮挡关系

然而出来的都是这样:

错误的遮挡关系

对此我百思不得其解。我明明启用了 Z Buffer,为什么会出现奇怪的先后绘制关系呢?在学长的建议下,我将变换后的顶点输出调试(后来发现这个变换也是错误的,左乘和右乘的问题),看不出什么说明原因的东西。然后我就开始调代码,先是将视图矩阵换成固定摄像机,接着将投影矩阵从我的临时方案换成大多数情况下能用的 SharpDX 的原始代码——自然要改近剪裁平面距离。在距离设为 1 之后渲染结果突然正常了!我想是不是矩阵计算发生了错误——但是我已经通过测试,在距离大于零时和原始代码结果应该是一样的。想想有可能又是我的代码出了错误,我吓出一身冷汗,毕竟之前因为同一个地方的问题提交过一次有缺陷的PR,如果这次再是我的锅,我就名誉扫地了。最终我认为不是我的过错,以及想起了我说过的,在原来用固定管线的时候遮挡关系就出错了这个描述。

在我担忧又毁了一个PR的时候,我看到邮箱里关于上次PR的评论收到了回应。仍然是 Alexandre Mutel (@xoofx,SharpDX 目前的主力维护者,感叹一下好脾气):

I’m not sure what do you expect from using a znear 0, as the result projection matrix column ending with zeros is perfectly normal and valid, that’s the formula. It is a degenerated case, but nobody expect to set znear = 0, so it doesn’t make sense at all. Not sure why you seem to be so annoyed by this. It is like complaining that dividing by 0 is not valid.

If you want to use infinite projection matrix, you can use a different formula as described in Tightening the Precision of Perspective Rendering, and as you can note, that’s only the far plane that is discarded, not the near plane that need to be here in order to perform proper “clipping/clamping” of the zvalue when z approach the near plane.

我读完立刻产生了一种被白了一眼的感觉。好吧,我花样作死,图形学太烂。

分享到 评论