阅读:1367回复:0
3D图形编程指南4 - 2D图元光栅处理
<b>目 录
</b> 3.1 <a href="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/3.htm#3.1" target="_blank" ><FONT color=#000000>光栅化点</FONT></A> 3.2 <a href="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/3.htm#3.2" target="_blank" ><FONT color=#000000>光栅化线段</FONT></A> 3.3 <a href="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/3.htm#3.3" target="_blank" ><FONT color=#000000>光栅化多边形</FONT></A> 3.3.1 <a href="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/3.htm#3.3.1" target="_blank" ><FONT color=#000000>光栅化凸多边形</FONT></A> 3.3.2 <a href="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/3.htm#3.3.2" target="_blank" ><FONT color=#000000>光栅化凹多边形</FONT></A> 3.4 <a href="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/3.htm#3.4" target="_blank" ><FONT color=#000000>内插渲染明暗处理的多边形</FONT></A> 3.5 <a href="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/3.htm#3.5" target="_blank" ><FONT color=#000000>渲染纹理多边形</FONT></A> 3.6 <a href="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/3.htm#3.6" target="_blank" ><FONT color=#000000>反走样</FONT></A> <B> </B> <HR align=center width="95%" noShade SIZE=1> <B> 引言 </B> 在前一章我们已经讨论了如何实现不同的坐标变换。这种技术使我们能够描述空间中虚拟对象的各种不同的位置以及通过投影使其对观察者可见。 投影变换把对象图元坐标从3D空间映射到视屏的2D空间。这些图元必须在2D空间中绘制,使其对观察者可见。既然我们考虑的是由一系列整齐地镶嵌的小方块组成的光栅图形,因此我们要确定这种几何图元的模拟描述如何能够被正确地转换为一系列离散像素。这个过程被称之为光栅处理(rasterization)。 一些图元比另外一些更容易被光栅化。线段和多边形面片首当其冲。在许许多多实际的应用中,绘图模块只支持基本图元的渲染,例如上面提到的这两种。在某些对象的描述需要更复杂的图元光栅处理的情况下,比如说三次曲线或者双三次曲面片,可以通过逼近技术得到。曲线通常以线段逼近,曲面表面则以多边形逼近。不太一般地,有一些例程可以直接光栅化复杂图元,尤其是曲线。 在这一章中,我们讨论如何光栅化简单的几何图元:点、线段和多边形。 <B> 3.1、光栅化点</B> 点的光栅处理比较简单,非常便捷。它只包括把一些指定的位图内存单元设置上颜色数值。但是,不同之处在于,进行光栅处理的图象位图本身就是离散的,而我们要模拟的2D空间中却包含着连续的实数排列。如果我们要在两个整数像素坐标中标绘一个坐标为实数的点,就必须考虑如何处理这种不明确性。可能,把它逼近为最接近它的整数是一个相当合理和非常普通的选择。然而,数字图像的离散本性对光栅图象就来说是固有的误差之源,同时也带来了不完整性。在本章的其它部分还要对此进行讨论。现在,让我们忽略舍入误差,假定点的坐标被作为整数标绘,点的区域等于像素的区域(在这一小节中提到的点实际上是意味着点的附近,数学意义上的点不能被处理)。 让我们回忆一下,位图的第一个位置通常是左上角的像素。典型的内存单元排列描述了像素的连续扫描线,也确定了在这种情况下便于使用的参考系。Y轴的方向是从上到下,X轴的方向是从左到右。(参见图3.1) <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/Image260.gif"></TD></TR> <TR align=middle> <TD colSpan=3><FONT color=#cccccc><B>图3.1 图象位图布局</B></FONT></TD></TR></TABLE> 假定每个像素由内存的一个单元代表(字节或双字), 输出位图的大小是<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image261.gif">×<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image262.gif">,像素<I>(x,y)</I>在图象位图中的地址就应该是<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image263.gif">。在内存中从位图起始位置算起的某个偏移处设定指定的颜色值,最后的结果是,在blit位图后,点出现在屏幕坐标<I>(x,y)</I>上(参见程序清单3.1) <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/Image264.gif"></TD></TR> <TR align=middle> <TD colSpan=3><FONT color=#cccccc><B>程序清单 3.1 点的光栅处理</B></FONT></TD></TR></TABLE><B> 3.2、光栅化线段</B> 线的光栅处理算法在许多3D应用中是非常重要的。除了渲染线段外,几乎同样的算法也用于其它目的,比如说多边形光栅处理中扫描边的例程等。正因为它很普通,有许多原因要求这个算法要有效率。 当然,线段光栅处理的目的在于在给定的线上找到所有内插的像素,或者更一般地,找到非常接近直线轨迹的像素。一条线段通常由两个端点的坐标给定:<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image265.gif">和<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image266.gif">(参见图3.2) <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/Image267.gif"></TD></TR> <TR align=middle> <TD colSpan=3><FONT color=#cccccc><B>图3.2 在线段上寻找点</B></FONT></TD></TR></TABLE> 在显示了一条线段的图3.2中,我们可以发现两个相似的三角形。一个由<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image268.gif">和<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image269.gif">形成,另一个由<I>x</I>和<I>y</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/Image270.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/Image271.gif"> 以及 <IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image272.gif"></TD></TR></TABLE> 既然我们要沿着线的轨迹找到所有的点,我们就必须获得在区间<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image273.gif">中的所有的<I>X</I>,并计算相应的<I>Y</I>。然而这里有点问题:我们只有<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image274.gif">个计算点,但是, 如果Y的范围太大的话,我们找到的点在屏幕上可能不是连续的(参见图3.3)。在退化为垂直线的情况下,<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image275.gif">可能等于0,使得上面的等式不能成立。在这些情况下,我们必须从一个大区间<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image276.gif">中取出Y坐标,计算相应的X坐标(参见图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/3.1/Image277.gif"></TD></TR></TABLE> <TABLE cellSpacing=0 cellPadding=2 align=center border=0> <TR align=middle> <TD colSpan=3><B><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image278.gif"></B></TD></TR> <TR align=middle> <TD colSpan=3><FONT color=#cccccc><B>图3.3 计算X的Y函数与计算Y的X函数</B></FONT></TD></TR></TABLE> 考虑这种方法的性能,我们发现计算单个点时,使用了一个乘法和一个除法。在实践中,这样的开销是非常大的。一个更好的技术是使用前向差分来迭代计算多点坐标。这种技术通常用于光栅化各种多项式曲线,线段可以被认为是在一个常量范围中的这种曲线。这种技术应用了一个简单的事实,在某些点<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image279.gif">的函数值等于它在点x上的值与函数在<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image280.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/Image281.gif"></TD></TR></TABLE> 尽管通常<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image282.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/Image283.gif"></TD></TR></TABLE> 前向差分<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image282.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/Image284.gif"></TD></TR></TABLE> 这就意味着我们能够计算<I>y(x+1)</I>的值,即,对下一个离散的x(假定<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image285.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/Image286.gif"></TD></TR></TABLE> 这就是计算每个点时要用到小数(浮点或定点)加法,当然,它同原始的方法相比较已经有了相当的改善。前向差分技术同样也可用于复杂曲线。在更复杂的情况中(比如在后面几章中要看到的),函数的前向差分可能不是常数。然而,由于其本身就是多项式函数,所以可以通过求高阶差分得到。 要注意到这种算法涉及到了小数(分数),或者是浮点或者是定点。潜在地,它也涉及到整数变换或舍入,因为位图毕竟是离散的。在性能方面,它在只使用整数运算的时候比较完美。我们要考虑的另一种迭代方式不涉及除法或小数。这要归功于J.E. Bresenham。 这种方法是在<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image287.gif">和<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image288.gif">区间中找到一个较大的范围,沿着这个区间内的点,当在一个较小的范围内增加的时候,变化很显著。让我们考虑一下在线段光栅处理的某个阶段发生了些什么。假定<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image289.gif">较大(<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image290.gif">),在给定的线段上<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image291.gif">和<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image292.gif"> <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/Image293.gif"></TD></TR> <TR align=middle> <TD colSpan=3><FONT color=#cccccc><B>图3.4 线通过像素栅格</B></FONT></TD></TR></TABLE> 图3.4显示了这么一种情况,我们已经在坐标<I>(x,y)</I>处渲染了像素<I>P(x,y)</I>(前一个),现在要确定下一个在哪里,是像素<I>L(x+1,y)</I>(较低的)还是像素<I>H(x+1,y+1)</I>(较高的)。既然实际的线将要通过<I>P</I>和<I>L</I>两个点之间,我们必须标绘中心最接近交叉点<I>I(x+1,y(x))</I>(这里的x=x+1)的那个点。这可以通过比较以交叉点分隔的h段和l段的长度来度量。回想起在原始的线绘制方法,可以得到: <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/Image294.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/Image295.gif"></TD></TR> <TR align=middle> <TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image296.gif"></TD></TR></TABLE> 现在,我们感兴趣的h和l的比较结果,可以通过判断差分<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image297.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/Image298.gif"></TD></TR></TABLE> 如果<I>l-h>0</I> 意味着<I>l>h</I>,交叉点l更接近于H。H点被标绘。否则,如果<I>l<FONT face=Symbol>£</FONT> h</I>,L被标绘。在等式的两端都乘以<FONT face=Symbol>D</FONT> <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/3.1/Image299.gif"></TD></TR></TABLE><I></I> 既然<FONT face=Symbol>D</FONT> <I>X</I> 假定为比0大,<I>(l-h)</I>和<FONT face=Symbol>D</FONT> <I>X(l-h)</I>的符号就是相同的。我们用<I>d</I>来表示<FONT face=Symbol>D</FONT> <I>X(l-h)</I> 并找出在i次和i+1次迭代的的过程中<I>d</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/Image300.gif"></TD></TR> <TR align=middle> <TD><IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image301.gif"></TD></TR></TABLE> 我们也能够找出d的初始值。在第一个点上,既然<I>x=0</I>和<I>y=0</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/Image302.gif"></TD></TR></TABLE> 既然d的符号决定了哪一个点移到下一个位置(<I>H</I> or <I>L</I>),我们要找出在i次迭代中d 的值,假定我们在前面的计算中知道了i-1次迭代中d的值: <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/Image303.gif"></TD></TR></TABLE> 如果在前一次迭代期间,我们决定在H标绘点,我们有:<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image304.gif">和<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image305.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/Image306.gif"></TD></TR></TABLE> 另一方面,当较低的点L在前一次迭代中被标绘的时候,我们有<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image307.gif">和<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image308.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/Image309.gif"></TD></TR></TABLE> 这个导出看起来有点复杂,但是一旦清楚了在循环前和循环中要执行哪一步,实现的算法非常普通。在迭代部分,我们已经找到了d的初值。此外,在循环内,取决于d的符号,我们要么加上<I>2<FONT face=Symbol>D</FONT> Y-2<FONT face=Symbol>D</FONT> X</I>,要么加上<I>2<FONT face=Symbol>D</FONT> Y</I>。既然这两个值在整个循环期间是常量,我们就能够在实际的工作开始之前计算它们。 在上面导出过程中,我们假定<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image291.gif">以及<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image292.gif">,然而,在实际中我们不必限制这些,一个方法是在不使用上面的假设时把递增改变为递减。对计算内循环来说,在选择了点L的时候我们必须紧记,坐标只有一个有着较大的递增(或递减)范围;当H被标绘的时候,X和Y都改变了, 这时在两个范围都出现同时递进的情况。程序清单3.2给出了一种可能的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/Image310.gif"></TD></TR> <TR align=middle> <TD colSpan=3><FONT color=#cccccc><B>程序清单3.2 直线光栅处理</B></FONT></TD></TR></TABLE> 这种算法根本不涉及除法或小数值。它使用了乘2的乘法,但几乎所有的现代编译器都会把它优化为左移一位 — 一种非常廉价的计算。 让我们试着分析一下程序清单3.2中出现的例程的性能。首先,我们对内循环中的代码感兴趣,因为大部分线段的这部分函数都要被执行多次。我们能立即看出其中有整数加法函数<I>G_dot</I>。然而,这个函数包含了至少一个乘法<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image311.gif">,这是我们付出极大努力要避免的运算。 让我们回想一下目的图象位图所表达的。如果我们仅仅渲染像素并想标绘另一个挨着它的像素,位图中这两个像素的地址有多大的不同呢?(参见图3.5) <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/Image312.gif"></TD></TR> <TR align=middle> <TD colSpan=3><FONT color=#cccccc><B>图3.5 图象位图中的相邻像素</B></FONT></TD></TR></TABLE> 在图3.5中看得很清楚,如果我们想沿着X轴水平递进,像素B的地址等于像素A的地址加1。另一方面,像素A和C隔开了<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image313.gif">像素 — 位图的宽度。因此,为了沿着Y轴垂直递进,我们就要在A的地址上增加<IMG src="mk:@MSITStore:E:\3D图形编程指南.chm::/3D图形编程指南/image/3.1/Image313.gif">得到C的地址。使用这种策略,可以修改例程来替换坐标<I>(x,y)</I>,直接计算图象位图中的像素地址。程序清单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/Image314.gif"></TD></TR> <TR align=middle> <TD colSpan=3><FONT color=#cccccc><B>程序清单3.3 优化的直线光栅内循环</B></FONT></TD></TR></TABLE> 从程序清单3.3中,我们能够看出在循环体中的代码没有留下任何开销大的运算。 基于同样原理的增量算法也用于各种各样的曲线中,特别是圆。这种算法有时侯,特别在2D应用中,有助于给出相当好的性能。然而,曲线也可以用线段逼近,这种方法尽管可能不引人注意,但它在实践中有着广泛的应用。 <B></B> <B>3.3、光栅化多边形 </B> 按照定义,多边形是任何边由线段形成的封闭的平面图形。为了表现虚拟对象,我们只对非自交或简单多边形感兴趣,这种多边形只在多边形的顶点处有交点。光栅处理的目的是给封闭区域内的每个像素分配一些指定的颜色。大多数多边形光栅处理方法是使用各类扫描线(<I>scan-line</I>)方法。这种方法是让扫描线沿着多边形垂直移动,在每个不同的高度上与像素直线相交叉。一旦找到了所有的像素线,剩下的是在某个时刻只绘制它们一次。这些直线必须被水平化是因为在大多数普通的图象位图中,像素形成水平线将在内存中占有连续的位置,这样在某些情况下比垂直线渲染的更快。 多边形分为凸多边形和凹多边形。按照定义,在多边形内部任意两点的连线不经过多边形的边界,则是凸多边形,反之则为凹多边形。(参见图3.6) <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/Image315.gif"></TD></TR> <TR align=middle> <TD colSpan=3><FONT color=#cccccc><B>图3.6 凹多边形和凸多边形</B></FONT></TD></TR></TABLE> |
|
|