[toc]
在做作业的时候,发现和很多其他同学遇到一样的问题:旋转了90°。在作业3的小牛的时候,倒立的图像实在是看不过去了,就打算回头来研究一下这个问题了
y=x2y=x2
参考[1][2],先说一下问题原因:代码里面传过来的近平面和远平面都是正数,但是在课程视频里面说的n, f都是负数,看范围也可以看出来是是要写成[f, n]。
看了一下参考[1][2]中一些同学说的方法,把正负取反,以及乘以一个变换矩阵,都觉得怪怪的,所以手动推导一遍投影矩阵,直接应用这个公式看看。
透视投影矩阵推导
V=⎛⎝⎜⎜⎜xyz1⎞⎠⎟⎟⎟V=(xyz1)
这个会导致空间中任意一个要归一化标准立方体的范围是[l,r]x[b,t]x[f,n]
简单思路就是,先把透视投影矩阵转换到正交投影矩阵
直接参考视频《Lecture 04 Transform Cont.》。先平移再缩放,这里因为是列向量表示法,所以是缩放矩阵x平移矩阵。
Mortho=⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢2r−l00002t−b00002n−f00001⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢100001000010−r+l2−t+b2−n+f21⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥Mortho=[2r−l00002t−b00002n−f00001][100−r+l2010−t+b2001−n+f20001]
对于3D空间中任意一点(x, y, z),其次坐标乘以任意一个非0的实数都表示的是3D空间中的同一个点。即(x, y, z, 1) == (xz, yz, z^2, z != 0),这里乘以特殊的z也是满足这个性质的。
- 透视投影视锥体上点投影到正交投影的立方体上
对于任意一点(x, y, z)投影到(x’, y’, z’)
- y->y’
可以很容易求出来(相似三角形)
y′=nzyy′=nzy
x′=nzxx′=nzx
⎛⎝⎜⎜⎜nx/zny/z?1⎞⎠⎟⎟⎟=⎛⎝⎜⎜⎜nxny?z⎞⎠⎟⎟⎟(nx/zny/z?1)=(nxny?z)
现在z不知道,接下来构建矩阵
Mpersp−>ortho⎛⎝⎜⎜⎜xyz1⎞⎠⎟⎟⎟=⎛⎝⎜⎜⎜nx/zny/z?z⎞⎠⎟⎟⎟=⎛⎝⎜⎜⎜nxny?z⎞⎠⎟⎟⎟Mpersp−>ortho(xyz1)=(nx/zny/z?z)=(nxny?z)
Mpersp−>ortho=⎡⎣⎢⎢⎢n0?00n?000?100?0⎤⎦⎥⎥⎥Mpersp−>ortho=[n0000n00????0010]
(1)对于近平面上的点,变换前后没有任何变化
(2)对于远平面上的中心点,变换前后没有变换
\(M_{persp->ortho}
⎛⎝⎜⎜⎜xyn1⎞⎠⎟⎟⎟(xyn1)
=
⎡⎣⎢⎢⎢n0?00n?000?100?0⎤⎦⎥⎥⎥[n0000n00????0010]
⎛⎝⎜⎜⎜xyn1⎞⎠⎟⎟⎟(xyn1)
=
⎛⎝⎜⎜⎜xyn1⎞⎠⎟⎟⎟(xyn1)
==
⎛⎝⎜⎜⎜nxnyn2n⎞⎠⎟⎟⎟(nxnyn2n)
\)
可知第三行前面两个和xy无关,但是可能和后面两个有关,设为A和B。所以矩阵可以写成如下形式:
\(M_{persp->ortho} =
⎡⎣⎢⎢⎢n0000n0000A100B0⎤⎦⎥⎥⎥[n0000n0000AB0010]
\)
可得第一个等式
An+B=n2An+B=n2
Mpersp−>ortho⎛⎝⎜⎜⎜00f1⎞⎠⎟⎟⎟=⎡⎣⎢⎢⎢n0000n0000A100B0⎤⎦⎥⎥⎥⎛⎝⎜⎜⎜00f1⎞⎠⎟⎟⎟=⎛⎝⎜⎜⎜00f1⎞⎠⎟⎟⎟==⎛⎝⎜⎜⎜00f2f⎞⎠⎟⎟⎟Mpersp−>ortho(00f1)=[n0000n0000AB0010](00f1)=(00f1)==(00f2f)
可得第二个等式
Af+B=f2Af+B=f2
根据两个等式可分别求得A和B:
A=n+fB=−nfA=n+fB=−nf
因为f < n < 0,所以:
tan(β/2)=ratio−ntan(α/2)=1−ntan(β/2)=ratio−ntan(α/2)=1−n
h=1ratio=wh=wh=1ratio=wh=w
参考《3D Game Programming with DirectX 11》这本书里面的推导的概念,因为主要是关心纵横比(aspect ratio),所以可以简化的把h设置为1.
Mortho=⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢2r−l00002t−b00002n−f00001⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢100001000010−r+l2−t+b2−n+f21⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥=⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢1ratio0000100002n−f00001⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎡⎣⎢⎢⎢⎢⎢10000100001000−n+f21⎤⎦⎥⎥⎥⎥⎥=⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢1ratio0000100002n−f000−n+fn−f1⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥Mortho=[2r−l00002t−b00002n−f00001][100−r+l2010−t+b2001−n+f20001]=[1ratio0000100002n−f00001][10000100001−n+f20001]=[1ratio0000100002n−f−n+fn−f0001]
Mpersp=MorthoMpersp−>ortho=⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢1ratio0000100002n−f000−n+fn−f1⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎡⎣⎢⎢⎢n0000n0000n+f100−nf0⎤⎦⎥⎥⎥=⎡⎣⎢⎢⎢⎢⎢⎢⎢nratio0000n0000n+fn−f100−2nfn−f0⎤⎦⎥⎥⎥⎥⎥⎥⎥=⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢−1tan(β/2)0000−rationtan(β/2)0000n+fn−f100−2nfn−f0⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥Mpersp=MorthoMpersp−>ortho=[1ratio0000100002n−f−n+fn−f0001][n0000n0000n+f−nf0010]=[nratio0000n0000n+fn−f−2nfn−f0010]=[−1tan(β/2)0000−rationtan(β/2)0000n+fn−f−2nfn−f0010]
这样结果虽然对了,但是其实是不合理的,因为传进来的f > n > 0是和这个推导不一致的。如果把-f, -n 代入到上面的矩阵,结果还是不对。代码里面应该还有哪个地方做了一次转换把这个问题给屏蔽掉了,后面继续看!
关于推导,看到上面就ok了,但是我在想左手和右手坐标系关系到底有多大,所以打算推一遍左手坐标系的
V=(x,y,z,1)V=(x,y,z,1)
- 摄像机朝向z轴正方向看,立方体表示为[l, r]x[b,t]x[n,f]
注意这里f > n > 0
Mortho=⎡⎣⎢⎢⎢⎢⎢100−r+l2010−t+b2001−n+f20001⎤⎦⎥⎥⎥⎥⎥⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢2r−l00002t−b00002f−n00001⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥=⎡⎣⎢⎢⎢⎢⎢10000100001−n+f20001⎤⎦⎥⎥⎥⎥⎥⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢1ratio0000100002f−n00001⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥=⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢1ratio0000100002f−n−n+ff−n0001⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥Mortho=[100001000010−r+l2−t+b2−n+f21][2r−l00002t−b00002f−n00001]=[10000100001000−n+f21][1ratio0000100002f−n00001]=[1ratio0000100002f−n000−n+ff−n1]
求出来的A和B跟右手坐标系是一样的结果:
A=n+fB=−nfA=n+fB=−nf
从这里可以看出来一点就是透视到正交的变换是跟坐标系无关的
Mpersp=Mpersp−>orthoMortho=⎡⎣⎢⎢⎢n0000n0000n+f100−nf0⎤⎦⎥⎥⎥⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢1ratio0000100002f−n−n+ff−n0001⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥=⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢nratio0000n0000n+ff−n2nff−n0010⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥=⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢1tan(β/2)0000rationtan(β/2)0000n+ff−n2nff−n0010⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥Mpersp=Mpersp−>orthoMortho=[n0000n0000n+f−nf0010][1ratio0000100002f−n000−n+ff−n1]=[nratio0000n0000n+ff−n1002nff−n0]=[1tan(β/2)0000rationtan(β/2)0000n+ff−n1002nff−n0]
可以看到这里和《3D数学基础图形与游戏开发》这本书上的是一样的
疑问:《3D Game Programming with DirectX 11》不一样是为什么?
书上推导出来的矩阵形式如下:
⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢1rtan(α/2)00001tan(α/2)0000ff−n−nff−n0010⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥[1rtan(α/2)00001tan(α/2)0000ff−n100−nff−n0]
参考书上5.6.3.4章节
原因主要是DX里面要求的立方体在z轴方向的范围是[0,1],而不是这里的[-1,1]。所以,把上面的正交投影矩阵改一下即可
Mortho=⎡⎣⎢⎢⎢⎢⎢100−r+l2010−t+b2001−n0001⎤⎦⎥⎥⎥⎥⎥⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢2r−l00002t−b00001f−n00001⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥=⎡⎣⎢⎢⎢10000100001−n0001⎤⎦⎥⎥⎥⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢1ratio0000100001f−n00001⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥=⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢1ratio0000100001f−n−nf−n0001⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥Mortho=[100001000010−r+l2−t+b2−n1][2r−l00002t−b00001f−n00001]=[10000100001000−n1][1ratio0000100001f−n00001]=[1ratio0000100001f−n000−nf−n1]
Mpersp=Mpersp−>orthoMortho=⎡⎣⎢⎢⎢n0000n0000n+f100−nf0⎤⎦⎥⎥⎥⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢1ratio0000100001f−n−nf−n0001⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥=⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢1tan(β/2)0000rationtan(β/2)0000ff−n−nff−n0010⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥Mpersp=Mpersp−>orthoMortho=[n0000n0000n+f−nf0010][1ratio0000100001f−n000−nf−n1]=[1tan(β/2)0000rationtan(β/2)0000ff−n100−nff−n0]
这样就和书上一致了。
[1]https://zhuanlan.zhihu.com/p/509902950
[2]https://games-cn.org/forums/forum/graphics-intro/page/3/
[3]https://www.bilibili.com/video/BV1X7411F744?p=4&vd_source=c10ae5c27bbde8ef3af23889645a0d8b
[4]《3D Game Programming with DirectX 11》
[5]《3D数学基础图形与游戏开发》