hyde
路人甲
路人甲
  • 注册日期2003-09-24
  • 发帖数555
  • QQ
  • 铜币1457枚
  • 威望0点
  • 贡献值0点
  • 银元0个
阅读:1251回复:0

3D图形编程指南3续 -3D变换

楼主#
更多 发布于:2004-04-27 13:54
一般地,这种近似在从<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image120.gif">到<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image121.gif">(-45<FONT face=Symbol>°</FONT> to 45<FONT face=Symbol>°</FONT>)的范围内工作的十分好。对<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image122.gif">来说, 类似的近似公式是<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image123.gif">。(参见图2.14)


<TABLE cellSpacing=0 cellPadding=2 align=center border=0>

<TR align=middle>
<TD colSpan=3><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image124.gif"></TD></TR>
<TR align=middle>
<TD colSpan=3><FONT color=#cccccc><B>图2.14 近似的 cos(x)</B></FONT></TD></TR></TABLE><B>
</B>  很明显,我们同样要知道在窄区间之外的三角函数值。为了做到这一点,我们要么增大边界,要么就利用<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image125.gif">和<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image126.gif">的周期性。在<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image127.gif">(360<FONT face=Symbol>°</FONT>)的周期内函数值重复,因此这就足以使我们能够在从0到<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image127.gif">的区间内为全部无穷多的自变量计算函数值。
   从图2.13和图2.14中, 不难看出,<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image125.gif">看起来是水平移动的<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image126.gif">。实际上,<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image128.gif">。因此, 在<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image129.gif">到<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image130.gif">区间内的<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image131.gif">,可以作为<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image132.gif">来计算,级数的形式是:<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image133.gif">,同样,在<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image134.gif">到<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image135.gif">区间内的值也就是<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image136.gif">可以被表示为:<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image137.gif">。(参见图2.13)按照这种方式,我们就能够在0到<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image138.gif">(360<FONT face=Symbol>°</FONT> )的范围内计算任意值。
  知道了如何计算三角函数以后,再让我们看看能不能在其它方面做一些优化工作。举例来说,既然在许多情况下的应用对角度的测量是相当粗略的,那么只考虑整数角度很有意义。在这种情况下,我们不需要整个实数范围内的sin(x)、cos(x)值,把函数区间限制在离散的360个整数中。我们在进行任何旋转之前,只对全部的360个角度进行一次计算。这些做完之后,sin(x)或cos(x)值可以通过一个简单的查询表来得到,这样就不必进行相对复杂的级数计算。
  而且,在一些应用中,我们不需要全部的360个角度。这个测量度不是很方便。稍微更有效一些的办法是把整个圆周分割为256个伪角度。通过这个办法,我们只需定义一个unsigned char(一个字节)来存储一个角度。一旦我们越过了255,值会自动地回绕为0。这种处理节省了在使用360度时可能需要的条件声明。
  为3D旋转变换编写代码是非常直截了当的。在程序清单2.3中完成的函数构建了一个旋转系数集。注意,浮点变量:<I>T_mx1, T_mx2</I>等,假定为全局的。


<TABLE cellSpacing=0 cellPadding=2 align=center border=0>

<TR align=middle>
<TD colSpan=3><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image139.gif"></TD></TR>
<TR align=middle>
<TD colSpan=3><FONT color=#cccccc><B>程序清单2.3 计算旋转因子</B></FONT></TD></TR></TABLE>
  另一个函数:T_rotation,在程序清单2.4中给出, 使用了在T_set_rotation函数中计算出的因子来执行旋转变换。


<TABLE cellSpacing=0 cellPadding=2 align=center border=0>

<TR align=middle>
<TD colSpan=3><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image140.gif"></TD></TR>
<TR align=middle>
<TD colSpan=3><FONT color=#cccccc><B>程序清单2.4 旋转变换</B></FONT></TD></TR></TABLE>
<B>2.6、矩阵形式表达的变换</B>

  矩阵是一个数值表:


<TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0>

<TR align=middle>
<TD> <IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image141.gif"></TD></TR></TABLE>
  矩阵特例是行向量和列向量:


<TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0>

<TR align=middle>
<TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image142.gif" align=absMiddle>  <IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image143.gif"></TD></TR></TABLE>
  行向量乘以列向量得到一个标量值:


<TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0>

<TR align=middle>
<TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image144.gif"></TD></TR></TABLE>
  一个矩阵能够乘上其它的矩阵。得到的矩阵的每一个元素是第一个矩阵的行元素和第二个矩阵的列元素分别相乘的结果。矩阵要能够相乘,第一个矩阵的行向量和第二个矩阵的列向量必须有相同的维数。这样得到的矩阵的维数就是第一个矩阵的行×第二个矩阵的列。矩阵乘法可以用下面的方法计算:


<TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0>

<TR align=middle>
<TD>  <IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image145.gif"></TD></TR></TABLE>
  既然点是由三个坐标指定的,因此我们对一个行向量乘以一个3×3的矩阵尤其有兴趣:


<TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0>

<TR align=middle>
<TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image146.gif"></TD></TR></TABLE>
  如果我们仔细地看上面的表达式, 就能够知道在上一节中推出的9个旋转因子可以很方便地在3×3矩阵中应用
。我们可以把3D旋转变换认为是一个向量乘以一个“旋转”矩阵得到的结果向量。
  同样,我们也可以通过矩阵形式表达旋转和缩放变换。3阶矩阵形式的缩放矩阵可以写成:


<TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0>

<TR align=middle>
<TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image147.gif"></TD></TR></TABLE>
  矩阵形式表达的平移变换有一些困难。通常的矩阵乘法不考虑我们在这种情况下所需要的加法。但是,我们可以使用4×4矩阵以及调整坐标向量来正确地表达平移变换。平移有时候也被称作“仿射变换”,而旋转和缩放是“线性变换”。普通的仿射变换可以被认为是线性变换(在3D空间中由3×3矩阵指定)和平移(由附加的矩阵维指定)的组合。
  下面的4×4矩阵表达了平移变换:


<TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0>

<TR align=middle>
<TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image148.gif"></TD></TR></TABLE>
  如果我们在形式上选择矩阵表示变换,并打算使用平移的话,其它的变换也能别表示为4×4矩阵。这时候要为旋转和缩放矩阵增加一个新的维,为了保持操作的正确性,增加的新元素中除了在对角线上的元素外,都必须等于零,对角线上的元素设为1。
  根据矩阵乘法规则,也有可能右乘一个列向量,方法如下:


<TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0>

<TR align=middle>
<TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image149.gif"></TD></TR></TABLE>
  这种乘法也在形式上用于表达变换。在行向量和列向量上没有什么本质的区别,仅仅是个人的习惯问题决定使用第一种还是第二种。实际上, 行向量是列向量的转置矩阵,反之亦然。(一个矩阵的元素<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image150.gif">在转置矩阵中是<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image151.gif">):


<TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0>

<TR align=middle>
<TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image152.gif"></TD></TR></TABLE>
  除了数学上的完美外,这种方式在矩阵处理上也有很多优势。它考虑到了给定的变换表达式,并且对有很多连续变换的情况给出了计算捷径:


<TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0>

<TR align=middle>
<TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image153.gif"></TD></TR></TABLE>
  上式中的[<I>A</I>]和[<I>B</I>]是变换矩阵,[<I>X</I>]是要变换的向量。既然矩阵乘法符合下面的结合律:


<TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0>

<TR align=middle>
<TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image154.gif"></TD></TR></TABLE>
  我们把上面的式子表示为:


<TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0>

<TR align=middle>
<TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image155.gif"></TD></TR></TABLE>
  这样就可以先得到“级联”矩阵<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image156.gif">,接下来,坐标向量的每个变换就可以按照下面的形式计算:


<TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0>

<TR align=middle>
<TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image157.gif"></TD></TR></TABLE>
  应该注意到,每个坐标轴的旋转表达为3×3的矩阵形式,我们可以使用级联矩阵的计算导出3D旋转的公式。在种意义上,其本质上做的是找出系数的表达式。我们从每个旋转的表达式开始,后面的导出基本是模仿矩阵乘法。


<TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0>

<TR align=middle>
<TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image158.gif"></TD></TR></TABLE>
  如果我们随着级联矩阵的构成追踪表达式的形成过程,我们看到的是与2.5.2节非常类似的结果。
  我们已经演示了旋转变换不能交换。这就是说,不同的旋转变换顺序可以得到不同的结果。这在矩阵乘法中有很明显的反应。不象传统的整数或实数乘法,它们符合交换律<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image159.gif">,而矩阵乘法不符和交换律<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image160.gif">。
  <B>考虑</B>:


<TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0>

<TR align=middle>
<TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image161.gif"></TD></TR></TABLE>
  而下面式子中的<I>a,b,c,d,i,j,u,v </I>与上式中的大多不相同:


<TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0>

<TR align=middle>
<TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image162.gif"></TD></TR></TABLE>
  举例来说,我们设上面式中<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image163.gif">,<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image164.gif">而其它的值为0,这样我们得到:


<TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0>

<TR align=middle>
<TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image165.gif"></TD></TR></TABLE>
  在有很多连续变换的情况下,矩阵形式是非常有效的。举例来说,在有多个连续变换的时候,我们计算级联矩阵,并用它来变换多点,这么做减少了许多个乘法,导致速度更快。
  但是,我们必须注意到,矩阵表达式泛化了变换。3×3矩阵与三阶向量的乘法中有9个标量乘法。另一方面,由两个公式所表达的围绕着一个坐标轴的个别旋转变换则只有4个(在这里,5个矩阵元素是0,但是,不能防止处理器在它上的循环开销)。
  解决这种问题的一个通用办法是,当我们提前知道了要执行的变换,并且表达式中多是稀疏矩阵时,我们可以预先计算公式中的级联矩阵系数,而不是通过每个单独的矩阵乘法来计算这个矩阵。在3D旋转中,直接计算系数需要16个乘法, 但两个矩阵乘法需要<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image166.gif">个乘法(每个元素需要3个乘法,一共有9个元素,要乘两次),另外,这再一次归功于在这种情况下矩阵中的大多数元素是0。
  矩阵表现形式对表达变换提供了一个非常统一的结构。当应用程序在运行中需要任意的变换集时,可以直接以矩阵的形式表达每个变换。另一方面,当有限数量的变换以及其顺序已经提前预知的时候,我们可以利用更特殊的方法的优势,比如说直接地预计算系数。
<A>
</A><B>2.7、投影变换</B>
  我们模拟的世界是三维的。但是,展现这个世界的屏幕只有两维。把3D世界坐标映射到2D屏幕坐标的过程叫做投影。
  尽管有许多能够想到的线性或非线性的方式可以把3D空间映射到2D平面上,但我们最感兴趣的两种方式是平行(正交)投影和透视投影。在下面的部分我们讨论这两种变换。

  <B>2.7.1 平行投影</B>
  当我们去掉3D空间中的一个维数的时候,就得到了平行投影,这时候,在3D空间中的所有点,都在从3D空间映射到2D平面的平行线上,因此,称之为平行投影。(参看图2.15)


<TABLE cellSpacing=0 cellPadding=2 align=center border=0>

<TR align=middle>
<TD colSpan=3><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/2.1/Image167.gif"></TD></TR>
<TR align=middle>
<TD colSpan=3><FONT color=#cccccc><B>图2.15 平行投影</B></FONT></TD></TR></TABLE>
  我们可以在投影线与投影面交叉的角度的基础上更进一步地细分平行投影。如果角度是直角,称之为正交投影(<I>orthographic</I>),否则就叫做倾斜投影(<I>oblique</I>)。
  典型的,可能同工程绘图一样,通常基于多正交投影集(顶视,前视和侧视)。我们最感兴趣的是正交投影,这可能是因为它与我们如何看到距离我们同样远的对象相一致。
  如果我们选择的投影面平行于坐标系的<I>XY</I>平面,投影线将平行于Z轴,因此, 平行投影变换将去掉所有空间中的点的z坐标。对我们来说, 在大多数的时间里,对由观察者在世界中的位置和方位(通常指的是摄像机位置和方位)创建的投影感兴趣。在大多数这类情况中,投影面无需平行于坐标系的<I>XY</I>面。
  也有很多方式用来描述摄像机。我们可以指定在3D世界中的观察者(摄像机)点的坐标以及指定描述观察者在世界参考系中的方位的向量。或者,不用指定向量,我们可以通过3个角度<FONT face=Symbol>a</FONT><I>,<FONT face=Symbol> b</FONT> ,<FONT face=Symbol>g</FONT> </I>表达同样的内容,描述观察者的方位。任意投影变换都能以所有的这些摄像机表达方式,通过两个步骤表现出来。在这个过程中,首先是执行仿射变换,这一步是对空间进行变换以使投影面映射到<I>XY</I>面上,然后,第二步,执行上面讨论的简化的平行投影。
  很显然,在第一部中应用的仿射变换将要包括把观察者平移到世界的中心,并且根据观察者的方位旋转世界。在第五章我们会回到这个问题上来,讨论观察过程的细节和指出如何找出在第一步中要用到的用于以不同的方式描述的摄像机的仿射变换。
  正如我们刚刚看到的,对平行投影来说,基本的原理是去掉z坐标。但是,去掉了z坐标后,我们丢失了所有的原始3D空间深度信息。为了减少这种影响,我们应该考虑一下透视投影。尽管有这个缺点,平行投影还是在许多领域广泛地得到了应用,比如说在CAD中的应用。平行投影保留了图像中的平行线和对象的实际大小,这个重要性质比真实的观察更重要。

  <B>2.7.2 透视投影
  </B>透视投影创建的对象投影图像的大小依赖于对象与观察者的距离。在透视投影中显现这种效果并不困难。可能,最先与我们联系起来的是一个空荡荡且很直的街道,以及街道两旁消失在无穷远处的建筑物。透视投影使我们以场景现实主义的特点产生图像。毕竟,如果道路不是汇集到一点,建筑物不是在远方变得越来越小的时候,即便是同样的街道也看起来非常不自然。
  我们可以通过把观察者的眼睛仿真为一个点,而光线从所有对象反射回来汇聚到这个点上,来模拟透视投影变换。每条光线,在射到眼睛里之前,已经与观察者面前的平面相交叉。如果我们能够找到交叉横断面,并标绘那里的点,观察者的注意力会被欺骗,认为从标绘的点那里发射出的光线实际是来自与空间中其原始的位置。(参见图2.16)

喜欢0 评分0
夜落了,风静了,我喜欢一本书,一杯茶,一粒摇曳的烛光...
游客

返回顶部