首页 > 代码库 > Real-Time Rendering-第四章 Transforms(5)

Real-Time Rendering-第四章 Transforms(5)

4.6 Projections

在进行真正的场景渲染之前,必须把场景中的所有相关对象都投影到某个平面上或某种简单的包围体内。完成投影之后,就开始执行裁和渲染操作(见第2.3节)。

到目前为止,本章中的变换操作都没有使用到向量的第四个元素分量,w<script type="math/tex" id="MathJax-Element-91">w</script>-分量。也就是说,点和向量在变换后依然为保持为原来的类型。另外,4×4<script type="math/tex" id="MathJax-Element-92">4\times4</script> 矩阵的最下面一行元素始终为 (0  0  0  1)<script type="math/tex" id="MathJax-Element-93">(0\ \ 0\ \ 0\ \ 1)</script>。Perspective projection matrices(透视投影矩阵)则是这两种属性的一个例外情况:矩阵最下面一行包含了向量和点的操纵数,并且总是需要执行homogenization(齐次化)操作(即,w<script type="math/tex" id="MathJax-Element-94">w</script> 分量通常不为 1<script type="math/tex" id="MathJax-Element-95">1</script>,因此需要执行一次除以 w<script type="math/tex" id="MathJax-Element-96">w</script> 的运算以获得非齐次的点)。而在本节首先会讨论的Orthographic projection(正交投影)是一种更简单的投影,也是很常用的。这种投影方式不会影响 w<script type="math/tex" id="MathJax-Element-97">w</script> 分量。

在本节中,我们假设负z<script type="math/tex" id="MathJax-Element-98">z</script>-轴表示观察者的观看方向,y轴表示向上的方向,x轴表示向右的方向。这是一种右手坐标系。在一些书本和软件中,比如DirectX,使用左手坐标系,其中观察者的趋向对应于正z<script type="math/tex" id="MathJax-Element-99">z</script>-轴。这两坐标系表示方法同等有效,并且产生的最终结果是一样的。

4.6.1 Orthographic Projection(正交投影)

正交投影的特点之一是平行线在投影后依然保持平行。如下所示,矩阵 Po<script type="math/tex" id="MathJax-Element-100">\mathbf{P}_o</script> 是一个简单的正交投影矩阵,该投影不会改变坐标点的 x<script type="math/tex" id="MathJax-Element-101">x</script>- 和 y<script type="math/tex" id="MathJax-Element-102">y</script>-分量,只是简单地把 z<script type="math/tex" id="MathJax-Element-103">z</script>-分量设置为零,即正交地投影到平面 z=0<script type="math/tex" id="MathJax-Element-104">z=0</script>:

Po=?????1000010000000001?????.(4.59)
<script type="math/tex; mode=display" id="MathJax-Element-105"> \begin{aligned} \mathbf{P}_o \quad & = \begin{pmatrix} 1 & 0 & 0 & 0 \0 & 1 & 0 & 0 \0 & 0 & 0 & 0 \0 & 0 & 0 & 1 \end{pmatrix}. \qquad\qquad\qquad\qquad\text{(4.59)} \end{aligned} </script>

这种投影的效果如图4.16所示。显然,矩阵 Po<script type="math/tex" id="MathJax-Element-106">\mathbf{P}_o</script>是不可逆的,因为行列式 |Po|=0<script type="math/tex" id="MathJax-Element-107">|\mathbf{P}_o|=0</script>。换句话说,该变换从三维降到二维,并且没有办法取回丢弃的维度。使用这种正交投影产生的观察问题是,它会把 z<script type="math/tex" id="MathJax-Element-108">z</script>分量值为正的坐标点和 z<script type="math/tex" id="MathJax-Element-109">z</script>分量值为负的坐标点都投影到投影平面上。这种方法通常用于将坐标点的 z<script type="math/tex" id="MathJax-Element-110">z</script>-值(以及x<script type="math/tex" id="MathJax-Element-111">x</script>-和y<script type="math/tex" id="MathJax-Element-112">y</script>-值)限制到特定的间隔范围内,比如说从 n<script type="math/tex" id="MathJax-Element-113">n</script>(near plane)到 f<script type="math/tex" id="MathJax-Element-114">f</script>(far plane)。这是下一个变换的目的。
技术分享
图4.16

用于执行正交投影变换的矩阵通常使用六元组 (l,r,b,t,n,f)<script type="math/tex" id="MathJax-Element-115">(l,r,b,t,n,f)</script> 表示,分别表示left,right,bottom,top,near以及far平面。这个矩阵本质上是缩放和平移AABB(轴对齐包围盒;详见第16.2节中的定义),通过把6个平面放置到以原点为中心的轴对齐立方体的每一面中形成的。AABB的最小拐角是 (l,b,n)<script type="math/tex" id="MathJax-Element-116">(l,b,n)</script>,最大拐角为 (r,t,f)<script type="math/tex" id="MathJax-Element-117">(r,t,f)</script>。需要重点注意的是 n>f<script type="math/tex" id="MathJax-Element-118">n>f</script>,因为我们是沿着该空间体的负 z<script type="math/tex" id="MathJax-Element-119">z</script>-轴方向观察。但是根据常识我们一般会认为near值应该是比far更小的数值。在OpenGL中观察方向也是朝向负 z<script type="math/tex" id="MathJax-Element-120">z</script>-轴,但是在调用glOrtho函数创建正交投影矩阵时会把输入的near值表示为小于far值,然后在内部计算时对这两个值取反。另一种思考方式是OpenGL的near和far值是沿着观察方向(负 z<script type="math/tex" id="MathJax-Element-121">z</script>-轴)的(正)距离值,而不是 z<script type="math/tex" id="MathJax-Element-122">z</script> 视点坐标值。

在OpenGL中,轴对齐立方体的最小拐角为 (?1,?1,?1)<script type="math/tex" id="MathJax-Element-123">(-1,-1,-1)</script>,最大拐角为 (1,1,1)<script type="math/tex" id="MathJax-Element-124">(1,1,1)</script>;而在DirectX中,对应的边界为 (?1,?1,0)<script type="math/tex" id="MathJax-Element-125">(-1,-1,0)</script> 到(1,1,1)<script type="math/tex" id="MathJax-Element-126">(1,1,1)</script>。该立方体称为canonical view volume(规范视图体),里面的坐标称为normalized device coordinates(NDC规范化设备坐标)。变换过程如图4.17所示。之所以要变换到canonical view volume内,是因为在这种坐标下裁剪操作更高效。
技术分享
图4.16

变换到canonical view volume之后,就根据该立方体对要渲染的几何图形的顶点进行裁剪。最后通过把单位正方形映射到屏幕,渲染位于立方体范围内的几何图形。这种正交变换矩阵如下所示:

Po=S(s)T(t)=???????????2r?l00002t?b00002f?n0001??????????????????????100001000010?r+l2?t+b2?f+n21???????????.=???????????2r?l00002t?b00002f?n0?r+lr?l?t+bt?b?f+nf?n1???????????.(4.60)
<script type="math/tex; mode=display" id="MathJax-Element-127"> \begin{aligned} \mathbf{P}_o \quad = \mathbf{S}(\mathbf{s})\mathbf{T}(\mathbf{t}) & = \begin{pmatrix} \frac{2}{r-l} & 0 & 0 &0 \0 & \frac{2}{t-b} & 0 & 0 \0 & 0 & \frac{2}{f-n} & 0 \0 & 0 & 0 & 1 \end{pmatrix} \begin{pmatrix} 1 & 0 & 0 & -\frac{r+l}{2} \0 & 1 & 0 & -\frac{t+b}{2} \0 & 0 & 1 & -\frac{f+n}{2} \0 & 0 & 0 & 1 \end{pmatrix}. \\ & = \begin{pmatrix} \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}{f-n} & -\frac{f+n}{f-n} \0 & 0 & 0 & 1 \end{pmatrix}. \qquad\qquad\qquad\qquad\text{(4.60)} \end{aligned} </script>

如该公式所示:矩阵 Po<script type="math/tex" id="MathJax-Element-128">\mathbf{P}_o</script> 可以写成一个平移矩阵 T(t)<script type="math/tex" id="MathJax-Element-129">\mathbf{T}(\mathbf{t})</script>,以及一个缩放矩阵 S(s)<script type="math/tex" id="MathJax-Element-130">\mathbf{S}(\mathbf{s})</script> 的串联,其中 s=(2/(r?l),2(t?b),2/(f?n))<script type="math/tex" id="MathJax-Element-131">s=(2/(r-l),2(t-b),2/(f-n))</script>,t=(?(r+l)/2),?(t+b)/2,?(f+n)/2)<script type="math/tex" id="MathJax-Element-132">t=(-(r+l)/2),-(t+b)/2,-(f+n)/2)</script>。该矩阵是可逆的,即 P?1o=T(?t)S((r?l)/2,(t?b)/2,(f?n)/2)<script type="math/tex" id="MathJax-Element-133">\mathbf{P}_o^{-1}=\mathbf{T}(-\mathbf{t})\mathbf{S}((r-l)/2,(t-b)/2,(f-n)/2)</script>。

在计算机图形学中,经过投影变换之后通常使用左手坐标系—即对于视口来说,x<script type="math/tex" id="MathJax-Element-134">x</script>-轴表示向右的方向,y<script type="math/tex" id="MathJax-Element-135">y</script>-轴表示向上的方向,z<script type="math/tex" id="MathJax-Element-136">z</script>-轴表示指向视口里面的方向。由于我们用于定义AABB的方式是far值小于near值,因此正交变换将始终包括一次镜像变换操作。使用这种方式,我们可以把原始AABBs看成与目标包围体,即canonical view volume的大小一致。于是AABB的坐标 (?1,?1,1)<script type="math/tex" id="MathJax-Element-137">(-1,-1,1)</script> 对应于 (l,b,n)<script type="math/tex" id="MathJax-Element-138">(l,b,n)</script>,(1,1,?1)<script type="math/tex" id="MathJax-Element-139">(1,1,-1)</script> 对应于 (r,t,f)<script type="math/tex" id="MathJax-Element-140">(r,t,f)</script>。由此可以得到公式4.60

Po=?????1000010000?100001?????.(4.61)
<script type="math/tex; mode=display" id="MathJax-Element-141"> \begin{aligned} \mathbf{P}_o \quad & = \begin{pmatrix} 1 & 0 & 0 & 0 \0 & 1 & 0 & 0 \0 & 0 & -1 & 0 \0 & 0 & 0 & 1 \end{pmatrix}. \qquad\qquad\qquad\qquad\text{(4.61)} \end{aligned} </script>

这是一种镜像矩阵。正是通过这种镜像从右手观察坐标系(观察方向为负 z<script type="math/tex" id="MathJax-Element-142">z</script>-轴)转换到左手normalized device coordinates(NDC)。

DirectX将 z<script type="math/tex" id="MathJax-Element-143">z</script>-深度值映射到范围 [0,1]<script type="math/tex" id="MathJax-Element-144">[0,1]</script>,而不是OpenGL中使用的[?1,1]<script type="math/tex" id="MathJax-Element-145">[-1,1]</script>。在使用正交矩阵执行变换之后,通过应用一个简单的缩放和平移矩阵就可以实现这种转换,该矩阵为

Mst=?????10000100000.50000.51?????.(4.62)
<script type="math/tex; mode=display" id="MathJax-Element-146"> \begin{aligned} \mathbf{M}_{st} \quad & = \begin{pmatrix} 1 & 0 & 0 & 0 \0 & 1 & 0 & 0 \0 & 0 & 0.5 & 0.5 \0 & 0 & 0 & 1 \end{pmatrix}. \qquad\qquad\qquad\qquad\text{(4.62)} \end{aligned} </script>

因此在DirectX中使用的正交矩阵为

Po[0,1]=???????????2r?l00002t?b00001f?n0?r+lr?l?t+bt?b?nf?n1???????????.(4.63)
<script type="math/tex; mode=display" id="MathJax-Element-147"> \begin{aligned} \mathbf{P}_{o[0,1]} \quad & = \begin{pmatrix} \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{1}{f-n} & -\frac{n}{f-n} \0 & 0 & 0 & 1 \end{pmatrix}. \qquad\qquad\qquad\qquad\text{(4.63)} \end{aligned} </script>

通常使用矩阵的转置进行计算,因为在DirectXk中,通常使用行优先矩阵的表示方法。

4.6.2 Perspective Projection

比正交投影更有趣的一种变换是透视投影,在大多数计算机图形学应用程序中都会使用这种变换。在这种情况下,平行线在投影之后通常不再是平行的;相反,可能会在无穷远处聚集到单个点。透视投影更接近于我们感知真实世界的情况,即距离观察者越远的物体看起来越小。

首先,我们将提出一种有关透视投影矩阵的具有指导意义的推导过程,该投影矩阵把物体投影到平面 z=?d,d>0<script type="math/tex" id="MathJax-Element-9951">z=-d,d>0</script> 上。从world space的推导到进一步简化对world-to-view变换的理解,最后推导出OpenGL中所使用的更规范的矩阵。

技术分享
图4.18

假设相机(视点)位于坐标原点,并且我们想要将点 p<script type="math/tex" id="MathJax-Element-9952">\mathbf{p}</script> 投影到平面 z=?d,d>0<script type="math/tex" id="MathJax-Element-9953">z=-d,d>0</script> 上,产生一个新的点 q=(qx,qy,?d)<script type="math/tex" id="MathJax-Element-9954">\mathbf{q}=(q_x,q_y,-d)</script>。这种情形如图4.18所示。从该图中显示的相似三角形,由以下推导公式可以得到 q<script type="math/tex" id="MathJax-Element-9955">\mathbf{q}</script> 的 x<script type="math/tex" id="MathJax-Element-9956">x</script>-分量:

qxpx=?dpz?qx=?dpxpz.(4.64)
<script type="math/tex; mode=display" id="MathJax-Element-9957"> \begin{aligned} &&\frac{q_x}{p_x} = \frac{-d}{p_z} \iff q_x=-d\frac{p_x}{p_z} . &&&&\text{(4.64)} \end{aligned} </script>

q<script type="math/tex" id="MathJax-Element-9958">\mathbf{q}</script> 的其他分量对应的表达式为 qy=?dpy/pz<script type="math/tex" id="MathJax-Element-9959">q_y=-dp_y/p_z</script>(计算过程与 qx<script type="math/tex" id="MathJax-Element-9960">q_x</script> 类似),qz=?d<script type="math/tex" id="MathJax-Element-9961">q_z = -d</script>。与上述公式一起,可以推导出透视投影矩阵 Pp<script type="math/tex" id="MathJax-Element-9962">\mathbf{P}_p</script>,如下所示:

Pp=?????10000100001?1/d0000?????.(4.65)
<script type="math/tex; mode=display" id="MathJax-Element-9963"> \begin{aligned} \mathbf{P}_p = \begin{pmatrix} 1 & 0 & 0 & 0 \0 & 1 & 0 & 0 \0 & 0 & 1 & 0 \0 & 0 & -1/d & 0 \end{pmatrix}. \qquad\qquad\qquad\qquad\text{(4.65)} \end{aligned} </script>

通过公式4.66的简单验证可以确认该矩阵产生的透视投影的正确性:

q=Ppp=?????10000100001?1/d0000???????????pxpypz1??????=??????pxpypz?pz/d??????????????dpx/pz?dpy/pz?d1??????.(4.66)
<script type="math/tex; mode=display" id="MathJax-Element-9964"> \qquad\begin{aligned} \mathbf{q} = \mathbf{P}_p\mathbf{p} = \begin{pmatrix} 1 & 0 & 0 & 0 \0 & 1 & 0 & 0 \0 & 0 & 1 & 0 \0 & 0 & -1/d & 0 \end{pmatrix} \begin{pmatrix} p_x \\ p_y \\ p_z \\ 1 \end{pmatrix} = \begin{pmatrix} p_x \\ p_y \\ p_z \\ -p_z/d \end{pmatrix} \Rightarrow \begin{pmatrix} -dp_x/p_z \\ -dp_y/p_z \\ -d \\ 1 \end{pmatrix}.\qquad\qquad\text{(4.66)} \end{aligned} </script>

最后一步是基于整个向量除以 w<script type="math/tex" id="MathJax-Element-9965">w</script>-分量(在这种情况下该分量值为 ?pz/d<script type="math/tex" id="MathJax-Element-9966">-p_z/d</script>)的事实,以便于在最后位置得到值1。由此生成的 z<script type="math/tex" id="MathJax-Element-9967">z</script>值总是为 ?d<script type="math/tex" id="MathJax-Element-9968">-d</script>,因为这是我们要投影的平面。

直观地说,很容易理解为什么齐次坐标允许投影。齐次化过程的一种几何解释是该操作把点 (px,py,pz)<script type="math/tex" id="MathJax-Element-9969">(p_x,p_y,p_z)</script> 投影到平面 w=1<script type="math/tex" id="MathJax-Element-9970">w = 1</script> 上。

与正交变换一样,也有一个对应的透视变换,用于把视锥体变换为canonical view volume,而不是投影到一个具体的平面上(这种变换是不可逆的)。其中视锥体假定为起始于 z=n<script type="math/tex" id="MathJax-Element-9971">z=n</script> 处,并在 z=f<script type="math/tex" id="MathJax-Element-9972">z=f</script> 处结束,其中 0>n>f<script type="math/tex" id="MathJax-Element-9973">0>n>f</script>。位于 z=n<script type="math/tex" id="MathJax-Element-9974">z=n</script> 处的矩形具有最小拐角值 (l,b,n)<script type="math/tex" id="MathJax-Element-9975">(l,b,n)</script>,以及最大拐角 (r,t,n)<script type="math/tex" id="MathJax-Element-9976">(r,t,n)</script>。如图4.19所示。
技术分享
图4.19

参数 (l,r,b,t,n,f)<script type="math/tex" id="MathJax-Element-9977">(l,r,b,t,n,f)</script> 确定了相机的视锥体。水平视域由左平面和右平面(由 l<script type="math/tex" id="MathJax-Element-9978">l</script> 和 r<script type="math/tex" id="MathJax-Element-9979">r</script> 确定)之间的角度决定。使用两样的方法,由顶平面和底平面(由 t<script type="math/tex" id="MathJax-Element-9980">t</script> 和 b<script type="math/tex" id="MathJax-Element-9981">b</script> 确定)之间的角度可以确定垂直视域。视域范围越大,通过相机能观察到的场景越多。通过设置 r?l<script type="math/tex" id="MathJax-Element-9982">r\neq-l</script> 或者 t?b<script type="math/tex" id="MathJax-Element-9983">t\neq-b</script> 可以创建非对称的平截头体。例如,非对称的平截头体可以用于表示立体视角(见第18.1.4节)以及CAVEs中[210]。

视域是提供给观察者一种场景感觉的重要因素。与计算机屏幕相比,人的眼睛本身具有物理上的视域范围。这种对应关系为

?=2arctan(w/(2d)).(4.64)
<script type="math/tex; mode=display" id="MathJax-Element-9984"> \begin{aligned} &&\phi = 2\arctan(w/(2d)) . &&&&\text{(4.64)} \end{aligned} </script>
其中 ?<script type="math/tex" id="MathJax-Element-9985">\phi</script> 表示视域,w<script type="math/tex" id="MathJax-Element-9986">w</script> 表示物体垂直于视线的宽度,d<script type="math/tex" id="MathJax-Element-9987">d</script> 是观察点到物体的距离。例如,一个21英寸的显示器大约是16英寸宽,而推荐的最小观察距离为25英寸[27],由此产生一个35度的物理视域。依次计算,在12英寸远处,视域为67度;在18英寸处,则是48度;在30英寸为30度。这个公式也可以用于从相机镜头尺寸转换到视域范围,比如一个标准的50mm镜头用在35mm相机上(具有36mm宽的框架尺寸)可以得到 ?=2arctan(36/(2?50))=39.6<script type="math/tex" id="MathJax-Element-9988">\phi=2\arctan(36/(2*50))= 39.6</script>度。

与物理设置相比,使用一个更窄的视域范围将会减少透视效果,因为场景中的观察者将会被放大。设置一个更宽的视域将使物体看起来变得扭曲(就像使用一个宽角度相机镜头),特别是靠近屏幕边缘的位置,并且会扩大附近物体的规模。但是,更宽的视域范围能够带给观察者一种物体变得更大,更令人印象深刻的感觉,并且具有向用户提供有关周围环境更多信息的优点。
感觉对象更大,更令人印象深刻,并有优势
向用户提供关于周围环境的更多信息。

使用公式4.68所示的透视变换矩阵可以把视锥体变换为一个单位立方体:

Po[0,1]=???????????2nr?l00002nt?b00?r+lr?l?t+bt?bf+nf?n100?2fnf?n0???????????.(4.68)
<script type="math/tex; mode=display" id="MathJax-Element-9989"> \begin{aligned} \mathbf{P}_{o[0,1]} \quad & = \begin{pmatrix} \frac{2n}{r-l} & 0 & -\frac{r+l}{r-l} & 0 \0 & \frac{2n}{t-b} & -\frac{t+b}{t-b} & 0 \0 & 0 & \frac{f+n}{f-n} & -\frac{2fn}{f-n} \0 & 0 & 1 & 0 \end{pmatrix}. \qquad\qquad\qquad\qquad\text{(4.68)} \end{aligned} </script>

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    Real-Time Rendering-第四章 Transforms(5)