阅读:1483回复:0
3D图形编程指南4再续 - 2D图元光栅处理
其思想是通过保持颜色强度值或多边形每个顶点上的值来集成光栅处理和光线强度计算,并且在计算像素直线的同时线性地插入这些值,以在多边形内部为每个像素找到颜色。(参见图3.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/3.1/Image327.gif"></TD></TR> <TR align=middle> <TD colSpan=3><FONT color=#cccccc><B>图3.14 颜色强度插值</B></FONT></TD></TR></TABLE> 换句话说,假定同屏幕空间坐标<I>(x,y)</I>一起,每个顶点有一个<I>I</I>,即颜色强度数值的某个排序,我们可以原始的扫描线方法继续进行处理,在进行的过程中内插照明强度。通过沿着每条边内插颜色强度在多边形左或右边界获得数值(图3.14中的<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image328.gif">)。然后,当开始渲染任何一条水平线的时候,我们能够进一步地在这条线的起点和端点内插强度,在<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image328.gif">之间为像素寻找颜色i。 这种算法一个很明显的用于为平多边形进行光栅处理的边扫描函数的意义是,顶点现在有更多的信息同它们联系在一起。而且,取决于使用的光线方案,单个的颜色可能用几个值来表达(注意,RGB模式用三个值,分别用于红、绿、蓝三种颜色)。在扫描函数中蕴含了一种可能的设计, 它可能被选择用于构建N维边通用效果扫描器。实际上,我们极为经常地要遇到至少两个其它的情形,即我们要跨越多边形内插其它与光线强度不同的信息。通用的构建方式可以使扫描函数有能力处理多重目的。 在程序清单3.10中的函数片段在计算除了x和y的值之外,使用了前向差分,仍然是通过Bresenham迭代来计算 <TABLE cellSpacing=0 cellPadding=2 align=center border=0> <TR align=middle> <TD colSpan=3><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image329.gif"></TD></TR> <TR align=middle> <TD colSpan=3><FONT color=#cccccc><B>程序清单3.10 边扫描例程(部分)</B></FONT></TD></TR></TABLE> 一旦像素直线组成的给定多边形被恢复,光栅处理函数更进一步地跨过每条扫描线内插强度值,举例来说,使用简单的前向差分。与扫描函数相类似,小数可以以定点形式保留下来。在程序清单3.11中的代码片段演示了带有明暗处理的多边形光栅处理的像素直线填充循环 <TABLE cellSpacing=0 cellPadding=2 align=center border=0> <TR align=middle> <TD colSpan=3><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image330.gif"></TD></TR> <TR align=middle> <TD colSpan=3><FONT color=#cccccc><B>程序清单3.11 渲染带有明暗处理的多边形</B></FONT></TD></TR></TABLE> 这里只是一个很简单的说明。它提及到了强度能够同一个冗余维一样被处理得很恰当。实际上,我们在屏幕上考虑明暗处理化的多边形时,用到了两个空间维以及一个颜色维。但是在这个3D空间中所有的顶点都属于同一个平面吗?如果不是,我们要依赖于我们从哪个顶点开始内插来处理不同的明暗值。在实践中,当我们旋转一个带有上面提到的问题的明暗处理多边形的时候,在明暗面上有可见的不连续,根据多边形的方位这个不连续也在变化。一种解决方案是限制多边形仅仅为三角形。在3D空间中,三个不共线点属于同一个平面,因此,对方位不受约束的多边形来说,线性插值将给出光滑的外观。 <B>3.5、渲染纹理多边形 </B> 在多边形上应用纹理是为了改善合成场景的视觉真实度。实质上,纹理同多边形区域的参数(<I>u,v</I>)的颜色相关联。一种纹理方式是程序纹理,它是由一些函数(程序)为任意<I>(u,v)</I>坐标计算颜色。另外一种,可能是普通一些的方法,把纹理存储为位图。在这种情况下,在2维数组中精确地为每对<I>(u,v)</I> 存储了一个颜色。通过指定多边形每个顶点的纹理坐标把纹理同多边形关联起来。(参见图3.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/3.1/Image331.gif"></TD></TR> <TR align=middle> <TD colSpan=3><FONT color=#cccccc><B>图3.15 多边形和它的纹理</B></FONT></TD></TR></TABLE> 我们也可以扩展为插值明暗处理计算颜色强度的插值技术来进行纹理映射。如果我们只是在多边形的每个顶点保持纹理U和V, 可以在边扫描期间顺着边内插这两个值,并且沿着水平线,完成纹理<I>(U,V)</I>输出到屏幕上的像素的过程。(参见图3.16) <TABLE cellSpacing=0 cellPadding=2 align=center border=0> <TR align=middle> <TD colSpan=3><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image332.gif"></TD></TR> <TR align=middle> <TD colSpan=3><FONT color=#cccccc><B>图3.16 内插纹理坐标</B></FONT></TD></TR></TABLE> 然而,这种方式工作得并不彻底。或者,为了更精确,当使用透视投影寻找多边形在屏幕上的图像的时候,它不能工作。其原因是:透视变换不是线性的,因此在纹理映射期间纹理坐标跨越多边形的改变也不是线性的,不能使用线性插值得到正确的结果。(参见图3.17) 或许有人会问,为什么这种方法能为内插明暗处理工作:毕竟,我们一直在使用透视变换?其答案是,它实际上也没有工作,但是用于明暗处理的忽视了透视效果的可视对象在大多数情况下已经非常合适了。然而,对纹理渲染来说忽视它是不行的。它处理了人眼察觉到的可视对象的强度。在横越多边形时,相对较小的可视对象迹象的数量由明暗处理引入。在纹理多边形中,存在着相当大数量的可视对象迹象的数量。在后者情形下,不仅仅是真实性没有直接反映出来,还有可能在形式上彻底地被忽视了。 <TABLE cellSpacing=0 cellPadding=2 align=center border=0> <TR align=middle> <TD colSpan=3><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image333.gif"></TD></TR> <TR align=middle> <TD colSpan=3><FONT color=#cccccc><B>图3.17 线性纹理映射(注意,有不自然的扭曲)</B></FONT></TD></TR></TABLE> 为了在线性纹理映射中确认透视失真的效果,只要考虑下面的情况:一个矩形多边形显示在屏幕上,这样其中一条边比相对的那条更靠近我们,后者几乎消失在无穷远处(参见图3.18)。在纹理图中哪一条线对应屏幕上的像素线?显然,纹理坐标可以沿着AD、AB和DC边插值。然而,在这个例子中,BC边极其地小。因此,没有像素线能够与BC边在纹理图中相对应的贴切地映射。(参见图3.18) <TABLE cellSpacing=0 cellPadding=2 align=center border=0> <TR align=middle> <TD colSpan=3><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image334.gif"></TD></TR> <TR align=middle> <TD colSpan=3><FONT color=#cccccc><B>图3.18 用于透视投影多边形的线性纹理映射</B></FONT></TD></TR></TABLE> 在某些这类极端的情形下,屏幕上的纹理多边形缺乏与实际的纹理中相对应的部分,其它的部分则不自然地连结进来产生了失真。(参见图3.17) 然而,有的时候这种方式的纹理渲染看起来要好一些,当透视变幻的效果微不足道的时候它是很完美的:所有的点与观察者的距离粗略相同,或者距离视平面非常地远。在图3.17中,注意到,前景多边形的纹理看起来不真实,前景多边形的纹理在透视效果比较大的情况下出现了不自然的扭曲失真。实现线性纹理映射有个优点:比起我们后面要讨论的非线性映射,它的花费要小得多。对大多数应用来说,可能需要同时实现这两种方式。在多边形的位置上作简单的分析,如果情形合适,用线性纹理映射进行处理,只是在要紧的位置上使用非线性纹理映射,即,多边形太靠近投影面以及透视失真度比较大。 至于非线性纹理映射(参见图3.19),首先,直接的解决方案是在透视投影前分割多边形,这样更多的顶点精确地在屏幕和纹理之间映射,且线性纹理映射限制为较小的多边形,在这些多边形上失真度看起来较小。 <TABLE cellSpacing=0 cellPadding=2 align=center border=0> <TR align=middle> <TD colSpan=3><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image335.gif"></TD></TR> <TR align=middle> <TD colSpan=3><FONT color=#cccccc><B>图3.19 透视纹理映射</B></FONT></TD></TR></TABLE> 然而,这种解决方案增加了多边形的数量,因此不是很吸引人。为了找到一个不同的不需要细分多边形的方法,让我们来试着描述在变换阶段中多边形和它的纹理发生了些什么变化。(参见图3.20) <TABLE cellSpacing=0 cellPadding=2 align=center border=0> <TR align=middle> <TD colSpan=3><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image336.gif"></TD></TR> <TR align=middle> <TD colSpan=3><FONT color=#cccccc><B>图3.20 纹理多边形变换阶段</B></FONT></TD></TR></TABLE> 考虑一下上面描绘的三个空间:纹理空间、视空间(在投影变换之前,观察者位于坐标起始点的那个空间),最后,是屏幕上的内容 — 屏幕或图象空间。第一个空间是多边形局部。在世界中对象的位置和方位以及观察者的位置和方位决定仿射变换必须把多边形变换到视空间(第五章有这方面所需的详细细节)。屏幕空间包含了依赖于透视变换焦点距离的多边形透视图象。 我们已经看到,从世界空间变换到视空间必须进行仿射3D变换。这种变换以4×4矩阵表示。因此,点<I>T(u,v)</I>变换到视空间点<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image337.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/3.1/Image338.gif"></TD></TR></TABLE> 这里的<I>[T]</I>是4×4矩阵。假定我们知道在视空间中向量U和V映射为纹理空间的单位长度向量<I>(1,0)</I>和<I>(0,1)</I>,这些向量描述了纹理的主轴如何经过仿射变换而成为有方向的(oriented)。再假设我们知道纹理空间原点映射为视空间中的点O。知道了这些,我们就可以把三个点的纹理映射表达为以纹理空间中的原点和沿着两个主轴的单位向量表示的形式。 <TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0> <TR align=middle> <TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image339.gif"></TD></TR> <TR align=middle> <TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image340.gif"></TD></TR> <TR align=middle> <TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image341.gif"></TD></TR></TABLE> 知道了这些映射,并且记住纹理只有两个维,我们就可以再次得到变换矩阵<I>[T]</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/3.1/Image342.gif"></TD></TR></TABLE> 知道了变换,我们就能够表达个别的映射纹理坐标<I>T(u,v)</I>到视坐标<I>V(x,y,z)</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/3.1/Image343.gif"></TD></TR></TABLE> 更进一步地,视空间中的点<I>V(x,y,z)</I>经过透视变换后,变换为屏幕上的点<I>S(i,j)</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/3.1/Image344.gif"></TD></TR> <TR align=middle> <TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image345.gif"></TD></TR></TABLE> 为了实现映射,我们需要纹理坐标<I>(u,v)</I>作为屏幕坐标<I>(i,j)的</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/3.1/Image346.gif"></TD></TR> <TR align=middle> <TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image347.gif"></TD></TR></TABLE> 进一步,通过以纹理空间形式替换x,y和z,我们得到: <TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0> <TR align=middle> <TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image348.gif"></TD></TR> <TR align=middle> <TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image349.gif"></TD></TR></TABLE> 通过以i,j表达u,v,我们得到: <TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0> <TR align=middle> <TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image350.gif"></TD></TR> <TR align=middle> <TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image351.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/3.1/Image352.gif"></TD></TR> <TR align=middle> <TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image353.gif"></TD></TR></TABLE> 这两个公式使我们能使用描述纹理方位的两个向量以及描述纹理空间原点映射到视空间的点为任意屏幕坐标计算纹理坐标。这三个附加的参数必须在应用公式前获得。因此,每个多边形处理纹理时,最初都同描述纹理方位的两个向量和描述纹理空间原点的点关联在一起。在我们在多边形顶点上执行变换的同时,也要对这三个参数应用同样的变换。唯一的例外是,我们不必对顶点应用平移变换,因为它们对此不受影响。作为结果,我们也得到了开始纹理计算必须的视屏幕参数。尽管这种方式实现起来相对简单,但是附加到每个多边形上的数据对维护来说也是个大问题。而且,对线性纹理映射来说,我们需要每个顶点上的纹理坐标,因此,如果我们想使用上面的任一种方法渲染多边形,多余的信息会变得很大。在第五章,我们继续回到这个问题上来,提出一种策略,使我们能够在顶点上使用纹理坐标,并重新获得需要的透视纹理映射的信息。 如果我们检查公式,我们能够看到一些子表达式对整个多边形来说是常数,一些对每条扫描线来说是常数。因此,正确的计算排列能给出实际的性能。但是,即使考虑到这些,还是得在每个顶点上使用几次昂贵的除法。可能,最简单的优化计算的方式是沿着水平线细分。我们每隔N个点计算真正的纹理映射,然后在其中线性内插计算值,以直接获得点的映射。(参见图3.21) <TABLE cellSpacing=0 cellPadding=2 align=center border=0> <TR align=middle> <TD colSpan=3><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image354.gif"></TD></TR> <TR align=middle> <TD colSpan=3><FONT color=#cccccc><B>图3.21 扫描线细分和线性逼近</B></FONT></TD></TR></TABLE> 正如我们从图3.21中看到的,我们只是在沿着像素的几个点上作为屏幕坐标<I>(i,j)</I>的函数来计算真正的纹理映射<I>(u,v) </I>。沿线其它的点从已知精确映射的邻近点纹理坐标的线性插值中得到<I>(u,v)</I>。尽管我们并没有得到精确的结果(仅仅是用如图3.21所示的线段逼近真实的映射路径),但是,我们引人注目地减少了涉及到的昂贵的计算。而且随着精密计算映射的点大量增加,其结果也是十分合理的。 在某些环境下可能(比如说以硬件实现)更吸引人的另一个可能的途径是,用多项式曲线逼近纹理映射路径。如图3.21所示,以足够多的线段逼近(这个可以认为是一阶多项式曲线)需要大量的细分。通过应用更高阶的多项式我们能期望得到对实际的纹理映射路径更好的逼近。二次曲线(二阶多项式)就是一个很好的选择,因为真正的映射路径是相对光滑的。因此,我们能够通过求参数曲线的值尝试沿着某些扫描线找到纹理坐标<I>(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/3.1/Image355.gif"></TD></TR></TABLE> 这里的x是描述沿着扫描线位置的参数(参见图3.22) <TABLE cellSpacing=0 cellPadding=2 align=center border=0> <TR align=middle> <TD colSpan=3><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image356.gif"></TD></TR> <TR align=middle> <TD colSpan=3><FONT color=#cccccc><B>图3.22 二次曲线逼近</B></FONT></TD></TR></TABLE> 显然,为了找到上述表达式的6个系数,我们需要有6个已知左侧值的不同方程。如果我们有三个已知纹理映射精确的点(参见图3.22)就可以做到这一点。我们能够通过使用前面考虑的逆映射的技术找到这样的点。假定我们知道当前扫描线起点、中点和终点的映射。 假定参数x沿着扫描线在区间<I>[0,1]</I>变化,这样,在起点<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image357.gif">,在中点<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image358.gif">,在终点<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image359.gif">。这样,我们就进一步构造了6个方程,如下: 对 <IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image360.gif"> <IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image361.gif"> <IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image362.gif"> 对 <IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image363.gif"> <IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image364.gif"> <IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image365.gif"> 对 <IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image366.gif"> <IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image367.gif"> <IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image368.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/3.1/Image369.gif"> <IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image370.gif"> <IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image371.gif"></TD></TR> <TR align=middle> <TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image372.gif"> <IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image373.gif"> <IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image374.gif"></TD></TR></TABLE> 有了这些以后,我们能够通过求二次曲线的值沿着扫描线为任意像素找到逼近的纹理映射。当然,直接求值涉及到了几个代价昂贵的乘法。幸运的是,既然沿着扫描线的像素以迭代的方式处理,我们可以使用在前面的直线光栅处理中考虑到的前向差分的迭代技术,以极为有效的方式求二次曲线的值。正如我们看到的,如果我们知道前一个点的值和前向差分,就能够计算某些点上的函数值,对u纹理坐标来说,就是: <TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0> <TR align=middle> <TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image375.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/3.1/Image376.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/3.1/Image377.gif"></TD></TR></TABLE> 当然,对<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image378.gif">和<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image379.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/3.1/Image380.gif"> <IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image381.gif"></TD></TR></TABLE> 甚至有可能使用更高阶的多项式(例如三次曲线)用于扫描线纹理映射路径的逼近。系数求导和连续求值与上面一样,只是需要更多的点、更多的等式和更高次的前向差分。在实践上不使用阶数高于三次的多项式,因为它们给出的改善并不值得注意,但附加的开销变得大了起来。 我们也可以沿着常数Z直线渲染,来代替沿着扫描线渲染多边形。 后面这种方法沿着多边形Z是常数的面上的每条线进行,其优势也正来自此。透视变换是线性的,因此映射路径,比如说对应的直线也是线性的(透视变换是<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image382.gif">,我们也可以把它看成<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image383.gif">,这里<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image384.gif">,如果z是常数,则该式是线性的)。当然,它也有缺点,即,常数Z直线一般不是屏幕上的水平线。这样光栅处理可能出现问题,因为在直接向下的执行方式下,相邻的像素直线间可能有间隔。 <B>3.6、反走样</B> 正如我们以前已经注意到的,由于我们或显或隐的简化,存在一些固有的不理想性。举例来说,当绘制多边形的时候,部分地被多边形的解析区覆盖的像素仍然被标绘。这就产生了当我们使用直接向下的光栅处理算法的时候常见的“梯状”锯齿线和锯齿边效果。另一个例子,即,当从纹理图中拾取一个颜色值的时候,我们只考虑一个纹理单元。问题是,在距离观察者较远的距离上,由于透视缩短效果,不是一个单独的颜色而是一个纹理区域对应屏幕上的单个像素。一个关于这个古老问题的流行例子是国际象棋棋盘远离观察者移动。在某些点上,矩形的透视变得比屏幕上的像素小。自然地,举例来说,如果黑和白方块投影到同一个像素上,像素看起来有些灰色的色调。不幸地是,简化的纹理映射没有考虑这个区域,这样取决于周围的情况,要么是黑要么是白被绘制上去。这样,图象看起来非常地不自然,在纹理规则的情况下,比如说棋盘,实际上可能绘制成了不寻常的Moiré样式。 所有这些例子演示了由光栅图形离散的本性带来的走样(失真)问题。这样就产生了很奇特的反走样技术来减轻这个问题。这种技术从极其简单的到极度复杂的都有。这些技术可以粗略地分为两类,第一类以绘制例程合并到一起,第二类是在渲染图形的时候进行后期处理来改善品质。 举例来说,纹理映射器能够考虑实际的像素区域,在纹理中寻找哪个区域被映射上,进一步地平均颜色得到光滑图象。但这种技术开销比较大。通常用于替换它的方法叫做纹理细化(<I>MIP mapping</I>)。其思路是预先以不同的比例计算纹理,然后对远离观察者的多边形使用较小的纹理。既然它保证了在纹理和屏幕像素间小区域的不同,它也就有较小的走样问题。仅仅有相对较小的关联开销,但结果图象却显示出了相当大的改善。 第二类技术在渲染的图象上执行过滤,这样结果图象就更光滑了。原始图象通常是过采样(<I>supersampled</I>)的,换句话说,就是在更高的分辨率下渲染。过滤处理从原始图象中取出一组像素,计算它们强度的加权和。其结果放到过滤过的图象位图中。图3.23显示了过滤过程。在这个例子中,过滤器是赋值过加权的3×3矩阵,用于计算结果像素的强度,然后放到过滤过的位图中。 <TABLE cellSpacing=0 cellPadding=2 align=center border=0> <TR align=middle> <TD colSpan=3><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image385.gif"></TD></TR> <TR align=middle> <TD colSpan=3><FONT color=#cccccc><B>图3.23 过滤</B></FONT></TD></TR></TABLE> 过滤器通常从解析函数的变化中产生,其中最简单和最普通的是门函数(Box)、三角函数和高斯函数(Gaussian)。这些函数关联了跨过几个像素我们想要过滤的区域内点的加权。(参见图3.24) <TABLE cellSpacing=0 cellPadding=2 align=center border=0> <TR align=middle> <TD colSpan=3><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image386.gif"></TD></TR> <TR align=middle> <TD colSpan=3><FONT color=#cccccc><B>图3.24 过滤函数</B></FONT></TD></TR></TABLE> 代替解析过滤器的另一个途径是,在某个邻域内采样一些随机点。这种方法被称为随机采样。使用这种方法得到的结果看起来更好,因为这种方法趋向于在过滤过的图象中留下可视噪声和不规则图案。人类的视觉系统似乎有能力忽视可视噪声,夸大规则图案。 作为过滤结果,多边形的尖锐边和其他由单纯的光栅处理算法导入的失真变得光滑了一些。尽管通常来说彻底地去除采样过疏导致的问题是不可能的,但图象中一般的表现能够使用过滤方法得到相当大的改善。这种技术的限制是它使用了过采样。图象必须在较高分辨率下计算,这个受到可用内存资源和处理大图象的处理时间的限制。 另外,在几个例子中看到的空间走样,在运动中显示对象的交互计算机图形应用可能会遇到瞬间走样(<I>temporal aliasing</I>)。在电影中出现的同样的例子是旋转的车轮由于车轮高速旋转在摄像机的采样速度下,看起来反转了旋转方向。在计算机图形应用中尝试解决这个问题的一个方法是导入运动模糊(<I>motion blur</I>)。同减轻空间走样时使用的过滤策略相类似,在这种情况下,我们能在图象位图内计算像素的强度,这个可以通过在几个连续帧中寻找这个像素的加权和,及时在不同的运动中捕捉对象。当然,除了用于减小瞬间走样问题,这种运动模糊效果通常也用于以其自身改善交互图形应用的真实性。 这种技术的使用及其依赖于应用程序的目的和可用资源。顶尖的渲染应用可能使用非常复杂的技术用于改善视觉真实性,然而较低的应用,或者受性能约束的时候,可能就要为速度而牺牲真实性。 <B>小结</B> <FONT color=#993300><FONT face=楷体_GB2312><FONT> 总的说来,光栅处理例程用于在光栅显示设备上绘制几何图元。对光栅化复杂几何对象来说是非常昂贵的,我们通常以简单图元(例如线段或多边形)的集合来表达他们,这些简单图元容易光栅化。为了改善多边形的外观,依赖于可用的资源,可以加入明暗处理和纹理映射改善视觉真实性。 </FONT></FONT><FONT face=楷体_GB2312>光栅图形的离散本性经常使走样问题变得各种各样,在绘制算法或后期处理中的反走样技术尝试减轻这些问题</FONT></FONT><FONT face=楷体_GB2312>。<FONT face=楷体_GB2312><FONT face=楷体_GB2312> </FONT></FONT></FONT> |
|
|