1、2019/4/2,计算机图形学演示稿,1,第三章 基本光栅图形基础*,物体的形状和颜色可用象素矩阵或直线线段和多边形填充区域等基本几何结构来描述。点和直线段是最简单的几何成分,其它可供构造图形的输出图元有圆及其它圆锥曲线、二次曲面、样条曲线和曲面、多边形填充区域以及字符串等。而二维图形的生成是三维图形生成的基础,研究计算机生成图形需先从二维图形的生成开始。下面将讨论一些基本二维图元生成技术和算法,以光栅图形系统的扫描转换方法为基础。,2019/4/2,计算机图形学演示稿,2,本章内容,?直线段的扫描转换算法DDA算法Bresenham画线算法 ?区域填充扫描线填充种子填充 边填充,2019/4
2、/2,计算机图形学演示稿,3,直线段的扫描转换算法,直线的扫描转换: 确定最佳逼近于该直线的一组象素,并且按扫描线顺序,对这些象素进行写操作。 常用算法: 数值微分法(DDA) Bresenham算法。,2019/4/2,计算机图形学演示稿,4,直线是点的集合,在几何学中直线被定义为两个点之间的最短距离。也就是说一条直线是指所有在它上面的点的集合。直线是一维的,即它们具有长度但没有面积。直线可以向一个方向及其相反的方向无限伸长,这不是计算机图形学中所需要的,在图形学中研究的对象是直线段。已知线段的起点坐标(x1,y1),终点坐标(x2,y2),这两点就确定了一条线段。一般来讲,任何图形输出设备
3、都能准确地画出水平线X和垂直线Y,或对角线,但要画出一条准确斜线不是件容易的事。在光栅系统中,线段通过象素绘制,水平和垂直方向的台阶大小受象素的间隔限制。这就是说,必须在离散位置上对线段取样,并且在每个取样位置上决定距线段最近的象素,画一条直线实际上就计算出来一系列与该线靠近的象素。,2019/4/2,计算机图形学演示稿,5,A,B,光栅化的直线,A,B,C,D,2019/4/2,计算机图形学演示稿,6,数值微分法(),假定直线的起点、终点分别为:(x0,y0), (x1,y1),且都为整数。,2019/4/2,计算机图形学演示稿,7,数值微分(DDA)法,基本思想 已知过端点P0 (x0,
4、y0), P1(x1, y1)的直线段L y=kx+b 直线斜率为这种方法直观,但效率太低,因为每一步需要一次浮点乘法和一次舍入运算。,2019/4/2,计算机图形学演示稿,8,数值微分(DDA)法,计算yi+1= kxi+1+b = kxi+b+kx= yi+kx 当x =1; yi+1 = yi+k 即:当x每递增1,y递增k(即直线斜率); 注意上述分析的算法仅适用于k 1的情形。在这种情况下,x每增加1,y最多增加1。 当 k 1时,必须把x,y地位互换,2019/4/2,计算机图形学演示稿,9,2019/4/2,计算机图形学演示稿,10,数值微分(DDA)法,增量算法:在一个迭代算法
5、中,如果每一步的x、y值是用前一步的值加上一个增量来获得,则称为增量算法。 DDA算法就是一个增量算法。,2019/4/2,计算机图形学演示稿,11,数值微分(DDA)法,void DDALine(int x0,int y0,int x1,int y1,int color) int x; float dx, dy, y, k; dx = x1-x0; dy=y1-y0; k=dy/dx; y=y0; for (x=x0; xx1, x+) drawpixel (x, int(y+0.5), color); y=y+k;,2019/4/2,计算机图形学演示稿,12,数值微分(DDA)法,例:画直
6、线段P0(0,0)-P1(5,2) x int(y+0.5) y+0.5 0 0 0+0.5 1 0 0.4+0.5 2 1 0.8+0.5 3 1 1.2+0.5 4 2 1.6+0.5 5 2 2.0+0.5,2019/4/2,计算机图形学演示稿,13,数值微分(DDA)法,缺点: 在此算法中,y、k必须是float,且每一步都必须对y进行舍入取整,不利于硬件实现。,2019/4/2,计算机图形学演示稿,14,Bresenham画线算法,在直线生成的算法中Bresenham算法是最有效的算法之一。令 k=y/x,就0k1的情况来说明Bresenham算法。由DDA算法可知: yi+1=yi
7、+k (1)由于k不一定是整数,由此式求出的yi也不一定是整数,因此要用坐标为(xi,yir)的象素来表示直线上的点,其中yir表示最靠近yi的整数。,2019/4/2,计算机图形学演示稿,15,Bresenham画线算法,设图中xi列上已用(xi,yir)作为表示直线的点,又设B点是直线上的点,其坐标为(xi+1,yi+1),显然下一个表示直线的点( xi+1,yi+1,r)只能从图中的C或者D点中去选。设A为CD边的中点。 若B在A点上面则应取D点作为( xi+1,yi+1,r),否则应取C点。,xi,Xi+1,Yi,r,Yi+1,r,C,D,B,A,(x)的几何意义,为能确定B在A点上面
8、或下面,令 (xi+1)=yi+1-yi,r-0.5 (2)若B在A的下面,则有(xi+1)0。由图可知yi+1,r=yi,r+1,若(xi+1)0 (3)yi+1,r=yi,r, 若(xi+1)0,2019/4/2,计算机图形学演示稿,16,Bresenham画线算法,由式(2)和式(3)可得到(xi+2)=yi+2 - yi+1,r - 0.5=yi+1 + k - yi+1,r - 0.5 (4)yi+1 - yir -0.5 + k - 1,当(xi+1)0yi+1 - yir -0.5 + k, 当(xi+1)0(xi+2)= (xi+1) + k -1 ,当(xi+1)0(xi+2
9、)= (xi+1) + k , 当(xi+1)0,2019/4/2,计算机图形学演示稿,17,程序如下: BresenhamLine(x0,y0,x1,y1,color)int x0,y0,x1,y1,color;int x,y,dx,dy;float k,e; dx = x1-x0;dy = y1-y0;k = dy/dx; e = -0.5; x=x0; y=y0; for( i=0; i= 0) y+;e = e - 1; ,Bresenham画线算法,2019/4/2,计算机图形学演示稿,18,将e乘以2x记为E2xe,则E同e有相同的符号,取代e判断E的符号确定象素点的过程仍然正确。
10、此时上述算法中各误差项的表示式做如下变动: 初始误差项: E02xe0-x; 积累误差 ek+1ekm修改为:Ek+12xek+12x(eky/x)2xek2yEk2y; 如果选取上面的象素点,积累误差还要减去1,修改为: Ek+12x(ek+11)E k+12x,2019/4/2,计算机图形学演示稿,19,程序如下: BresenhamLine(x0,y0,x1,y1,color)int x0,y0,x1,y1,color;int x,y,dx,dy;float k;int e;dx = x1-x0;dy = y1-y0;k = dy/dx; e = -dx;x=x0; y=y0;for(
11、i=0; i= 0) y+;e = e - 2*dx;,2019/4/2,计算机图形学演示稿,20,Bresenham画线法,例:用bresenhanm画线法P0(0,0) P1(5,2) dy=y1-y0=2 dx=x1-x0=5 e0=-dx=-5 i xi yi e 1 0 0 -5 2 1 0 -1 3 2 1 3 4 3 1 -3 5 4 2 1 6 5 2 -5,2019/4/2,计算机图形学演示稿,21,区域填充一个区域是指一组相邻而又相连的象素,且具有同样的属性。根据边或轮廓线的描述,生成实区域的过程称为区域填充。,2019/4/2,计算机图形学演示稿,22,多边形的表示方法
12、顶点表示点阵表示顶点表示:用多边形顶点的序列来刻划多边形。直观、几何意义强、占内存少;不能直接用于面着色。 点阵表示:用位于多边形内的象素的集合来刻划多边形。失去了许多重要的几何信息;便于运用帧缓冲存储器表示图形,易于面着色。,2019/4/2,计算机图形学演示稿,23,区域填充算法可分为两大类:一是种子填充算法;二是扫描转换填充算法。种子填充算法首先假定封闭轮廓线内某点是已知的,然后算法开始搜索与种子点相邻且位于轮廓线内的点。种子填充算法只适用于光栅扫描设备。扫描转换填充算法则是按扫描线的顺序确定某一点是否位于多边形或轮廓线范围之内。这些算法一般从轮廓线的顶部开始进行到它的底部。区域填充的边
13、界可以是直线也可以是曲线。,2019/4/2,计算机图形学演示稿,24,多边形的扫描转换,多边形的扫描转换:把多边形的顶点表示转换为点阵表示,也就是从多边形的给定边界出发,求出位于其内部的各个象素,并给帧缓冲器内的各个对应元素设置相应的灰度和颜色,通常称这种转换为多边形的扫描转换。 几种方法:逐点判断法;扫描线算法;边填充法;栅栏填充法;边界标志法。,2019/4/2,计算机图形学演示稿,25,扫描线算法,扫描线算法 目标:利用相邻像素之间的连贯性,提高算法效率 处理对象:非自交多边形 (边与边之间除了顶点外无其它交点),2019/4/2,计算机图形学演示稿,26,右图所示是一简单多边形,各条
14、扫描线与多边形有不同的交点个数,交点将扫描线分成了不同的区域。求出扫描线与多边形的交点后,将各条扫描线上的交点按X方向从小到大排序后两两配对。每对交点所确定的区间均取填充的光强或色彩,其它区间则取背景光强或色彩,即可完成填充。,2019/4/2,计算机图形学演示稿,27,步骤:(1)求交:计算扫描线与多边形各边的交点; (2)排序:把所有交点按x值递增顺序排序; (3)配对:第一个与第二个,第三个与第四个等等;每对交点代表扫描线与多边形的一个相交区间, (4)着色:把相交区间内的象素置成多边形颜色,把相交区间外的象素置成背景色。,2019/4/2,计算机图形学演示稿,28,2019/4/2,计
15、算机图形学演示稿,29,扫描线算法,交点的取整规则 要求:使生成的像素全部位于多边形之内 用于线画图元扫描转换的四舍五入原则导致部分像素位于多边形之外,从而不可用假定非水平边与扫描线y=e 相交,交点的横坐标为x, 规则如下,2019/4/2,计算机图形学演示稿,30,扫描线算法,规则1:X为小数,即交点落于扫描线上两个相邻像素之间(a)交点位于左边之上,向右取整(b)交点位于右边之上,向左取整,2019/4/2,计算机图形学演示稿,31,规则2:边界上象素的取舍问题,避免填充扩大化。 解决方法:边界象素:规定落在右上边界的象素不予填充。具体实现时,只要对扫描线与多边形的相交区间左闭右开,扫描
16、线算法,2019/4/2,计算机图形学演示稿,32,规则3:扫描线与多边形的顶点相交时,交点的取舍,保证交点正确配对。 解决方法:检查两相邻边在扫描线的哪一侧。只要检查顶点的两条边的另外两个端点的Y值,两个Y值中大于交点Y值的个数是0,1,2,来决定取0,1,2个交点。,扫描线算法,2019/4/2,计算机图形学演示稿,33,数据结构的实现,为了提高效率,在处理一条扫描线时,仅对与它相交的多边形的边进行求交运算。我们把与当前扫描线相交的边称为活性边,并把它们按与扫描线交点x坐标递增的顺序存放在一个链表中,称此链表为活性边表(AET)。,2019/4/2,计算机图形学演示稿,34,(a)扫描线6
17、的活性边表,(b)扫描线7的活性边表,2019/4/2,计算机图形学演示稿,35,假定当前扫描线与多边形某一条边的交点的横坐标为x,则下一条扫描线与该边的交点不必要重计算,只要加一个增量x即可,下面,我们推导这个结论。,2019/4/2,计算机图形学演示稿,36,设该边的直线方程为:ax+by+c=0, y=yi时,x=xi;则当y=yi+1时, Xi+1=xi-b/a,另外使用增量法计算时,我们需要知道一条边何时不再与下一条扫描线相交,以便及时把它从活性边表中删除出去。综上所述,活性边表的结点应为对应边保存如下内容:第1项存当前扫描线与边的交点坐标x值;第2项存从当前扫描线到下一条扫描线间x
18、的增量Dx;第3项存该边所交的最高扫描线号ymax。,2019/4/2,计算机图形学演示稿,37,为了方便活性边表的建立与更新,我们为每一条扫描线建立一个新边表(NET),存放在该扫描线第一次出现的边。也就是说,若某边的较低端点为ymin,则该边就放在扫描线ymin的新边表中。,上图所示各条扫描线的新边表NET,2019/4/2,计算机图形学演示稿,38,算法过程: void polyfill (polygon, color) int color;多边形 polygon; for (各条扫描线i ) 初始化新边表头指针NET i;把y min = i 的边放进边表NET i; y = 最低扫描
19、线号;初始化活性边表AET为空;for (各条扫描线i ) 把新边表NETi中的边结点用插入排序法插入AET表,使之按x坐标递增顺序排列;遍历AET表,把配对交点区间(左闭右开)上的象素(x, y),用drawpixel (x, y, color) 改写象素颜色值;遍历AET表,把y max= i 的结点从AET表中删除,并把y max i结点的x值递增D x;若允许多边形的边自相交,则用冒泡排序法对AET表重新排序; /* polyfill */,2019/4/2,计算机图形学演示稿,39,边填充法另一种扫描转换方法即所谓的边填充算法。其基本思想是对每条扫描线和每条多边形的交点(xk,yk)
20、,将该扫描线上交点右方的所有象素取补。对多边形的每条边作此处理,就可以完成多边形区域填充。边填充算法描述如下: 1. 取多边形的一条边; 2. 求出每一条扫描线与该边的交点坐标(xk,yk);3. 将(xk,yk)右边的全部象素取补;4还有没处理的多边形边时转1,否则结束。,2019/4/2,计算机图形学演示稿,40,下图示出了边填充算法的实现过程。,2019/4/2,计算机图形学演示稿,41,上述算法的缺点是对于复杂的图形,一些象素可能被访问多次,一种改进的办法是引入栅栏。通过多边形设一栅栏,每次只对交点与栅栏之间的象素 点取补,可使访问象素的次数减少。下图示出了这一算法的原理。,2019/
21、4/2,计算机图形学演示稿,42,种子填充算法*以上讨论的填充多边形的算法都是按扫描线顺序进行的。种子填充算法则来用完全不同的方法。种子填充算法假设在多边形或区域内部至少有一个象素是已知的。然后设法找到区域内所有其它象素,并对它们进行填充。,2019/4/2,计算机图形学演示稿,43,区域指已经表示成点阵形式的填充图形,它是象素的集合。,2019/4/2,计算机图形学演示稿,44,表示方法:内点表示、边界表示 内点表示 枚举处区域内部的所有像素 内部的所有像素着同一个颜色 边界像素着与内部像素不同的 颜色 边界表示 枚举出边界上所有的像素 边界上的所有像素着同一颜色 内部像素着与边界像素不同的
22、颜色,2019/4/2,计算机图形学演示稿,45,区域填充要求区域是连通的 连通性4连通、8连通 4连通:8连通,2019/4/2,计算机图形学演示稿,46,对4连接算法,应用边界定义区域,可使用堆栈以建立简单的种子填充算法。使用的堆栈的种子填充算法如下:1 种子象素压入堆栈;2 当堆栈非空时做(1)栈顶象素出栈;(2)将出栈象素置成填充颜色;(3)检查每个与当前象素邻接的4连接象素,若其中有象素不为边界且没有设置成填充颜色,将该象素压入堆栈;(4)转2,2019/4/2,计算机图形学演示稿,47,算法可用伪语言描述如下: 算法:seed(x,y) 作为种子BV (BoundaryValue)
23、 边界值NV (NewValue) 填充值beginpixel(x, y)=seed(x,y)push pixel(x,y)while (stack not empty)pop pixel(x,y)if pixel(x,y)NVthen putpixel(x,y,NV)pixel(x,y)=NVendifif pixel(x+1,y)NV and pixel(x+1,y)BVthen push pixel(x+1,y)endifif pixel(x,y+1)NV and pixel(x,y+1)BVthen push pixel(x,y+1)endifif pixel(x-1,y)NV and
24、 pixel(x-1,y)BVthen push pixel(x-1,y)endifif pixel(x,y-1)NV and pixel(x,y-1)BVthen push pixel(x,y-1)endifendwhileend,2019/4/2,计算机图形学演示稿,48,种子填充算法例子,如右图所示,种子的坐标为(3,2),以四连通顺时针左起点为例,其进出栈顺序为: 种子(3,2)进栈,初始栈:(3,2),(1) 种子(3,2)出栈,四个相邻点进栈;栈:(2,2),(3,3),(4,2),(3,1) (2) 点(3,1)出栈,相邻点(2,1),(4,1)进栈;栈:(2,2),(3,3),
25、(4,2),(2,1),(4,1) (3)点(4,1)出栈,相邻点(4,2)进栈;栈:(2,2),(3,3),(4,2),(2,1),(4,2) (4)点(4,2)出栈,相邻点进栈(无);栈:(2,2),(3,3),(4,2),(2,1),2019/4/2,计算机图形学演示稿,49,(5)点(2,1)出栈,相邻点(2,2)进栈;栈:(2,2),(3,3),(4,2),(2,2) (6) 点(2,2)出栈,相邻点(1,2),(2,3)进栈;栈:(2,2),(3,3),(4,2),(1,2),(2,3) (7) 点(2,3)出栈,相邻点(3,3)进栈;栈:(2,2),(3,3),(4,2),(1,
26、2),(3,3) (8) 点(3,3)出栈,相邻点进栈(无);栈:(2,2),(3,3),(4,2),(1,2) (9) 点(1,2)出栈,相邻点进栈(无);栈:(2,2),(3,3),(4,2) (10)点(4,2)出栈,相邻点进栈(无);栈:(2,2),(3,3) (11)点(3,3)出栈,相邻点进栈(无);栈:(2,2) (12)点(2,2)出栈,相邻点进栈(无); (13)栈空结束。,2019/4/2,计算机图形学演示稿,50,种子填充算法主要实现子程序实例,void seed(x1,y1,nv,bv) /nv:填充颜色,bv:边界颜色int x1,y1,nv,bv;int a1999
27、9,b19999;int i,k,x,y,mx,my;unsigned c;a0=x1;b0=y1;k=0;putpixel(x1,y1,nv);,2019/4/2,计算机图形学演示稿,51,while(k=0),2019/4/2,计算机图形学演示稿,52,for(i=1;i5;i+)x=mx;y=my;if (i=1) /右x+;else if (i=2) /上y+;elseif (i=3) /左x-;elsey-; /下,2019/4/2,计算机图形学演示稿,53,c=getpixel(x,y);if (c!=nv) ,种子填充算法演示,2019/4/2,计算机图形学演示稿,54,上面的算
28、法是一种深度优先搜索算法,采用堆栈实现。也可以改为广度优先搜索,采用队列实现。,扫描线种子填充算法 种子填充算法的缺点是可能把太多的象素压入堆栈,有些象素甚至会入栈多次,降低算法的效率,存储空间需求大。解决的一种方法是对每一条扫描线实行种子算法。在任意不间断的扫描线象素段中,只取一个种子象素。象素段是指区域内相邻象素在水平方向的组合,它的两端以具有边界值的象素为界,其中间不包括具有新值的象素。,2019/4/2,计算机图形学演示稿,55,对于区域内的每一象素段,我们可以只保留其最右(或左)端的象素作为种子象素。因此,区域中每一个末被填充的部分,至少有一个象素段是保持在栈里的。扫描线种子填充算法
29、适用于边界定义的区域。区域可以是凸的,也可以是凹的,还可以包含一个或多个孔 。 定义的区域。区域可以是凸的,也可以是凹的,还可以包含一个或多个孔。算法叙述如下:1 种子象素入栈;2 当堆栈非空时做(1)栈顶象素出栈;,2019/4/2,计算机图形学演示稿,56,(2)沿扫描线对出栈象素的左右象素进行填充,直到遇到边界象素为止,即每出栈一个象素,便对包含该象素的整个区间进行填充;(3)上述区间内最左最右的象素分别记为xLeft、xRight;(4)在区间xLeft,xRight中检查与当前扫描线相邻的上下两条扫描线的有关象素是否全为边界象素或为已填充的象素,若存在非边界未填充的象素,则把每一区间
30、的最右象素取作种子象素入栈;(5)转2此算法可以有效地解决简单种子填充算法存在的堆栈可能过深的问题。,2019/4/2,计算机图形学演示稿,57,扫描线种子填充算法主要实现子程序实例,void push(int x,int y) pointstop0=x;pointstop1=y;top+; void pop() x=pointstop-10;y=pointstop-11;top-; void Scanline_seed_fill_draw(HDC hdc) int i;COLORREF BoundColor;POINT triangle=300,275,280,325,320,325,300
31、,275;POINT fourline=250,340,300,375,350,340,300,350,250,340;BoundColor=RGB(255,0,0);SetColor(BoundColor,hdc);,2019/4/2,计算机图形学演示稿,58,Ellipse(hdc,180,200,420,400); Ellipse(hdc,230,230,270,310); Ellipse(hdc,330,230,370,310); Ellipse(hdc,245,264,255,296); Ellipse(hdc,345,264,355,296); for(i=0;i3;i+)line
32、(hdc,trianglei,trianglei+1); for(i=0;i4;i+)line(hdc,fourlinei,fourlinei+1); Scanline_seed_fill(hdc,300,300,RGB(0,0,255),BoundColor);Scanline_seed_fill(hdc,250,280,RGB(0,255,0),BoundColor);Scanline_seed_fill(hdc,350,280,RGB(0,255,0),BoundColor);Scanline_seed_fill(hdc,300,360,RGB(0,255,255),BoundColor
33、);Scanline_seed_fill(hdc,235,270,RGB(0,0,0),BoundColor);Scanline_seed_fill(hdc,335,270,RGB(0,0,0),BoundColor); void Scanline_seed_fill(HDC hdc,int seed_x,int seed_y,COLORREF FillColor,COLORREF BoundColor) int active_x,active_y,Left_x,Right_x,flag,Nextspan_x;COLORREF rgbColor;push(seed_x,seed_y);whil
34、e(top!=bottom)pop();SetPixel(hdc,x,y,FillColor);,2019/4/2,计算机图形学演示稿,59,/填充扫描线右半部分active_x=x+1;while(GetPixel(hdc,active_x,y)!=BoundColor)SetPixel(hdc,active_x,y,FillColor);active_x+;Sleep(1);Right_x=active_x-1; /填充扫描线左半部分active_x=x-1;while(GetPixel(hdc,active_x,y)!=BoundColor)SetPixel(hdc,active_x,y
35、,FillColor);active_x-;Sleep(1);Left_x=active_x+1;active_x=Left_x; /确定下一条扫描线的种子y=y+1;while(active_xRight_x)flag=0;while(GetPixel(hdc,active_x,y)!=FillColor) &(GetPixel(hdc,active_x,y)!= BoundColor) & (active_xRight_x),2019/4/2,计算机图形学演示稿,60,if(flag=0)flag=1;active_x+;if(flag=1)if(active_x=Right_x),2019/4/2,计算机图形学演示稿,61,while(active_xRight_x)flag=0;while(GetPixel(hdc,active_x,y)!=BoundColor) ,扫描线种子填充算法演示,