计算机图形学复习2

计算机图形学复习0
计算机图形学复习1
接着上次的内容更,本次内容讲述基本图形的生成算法。包括直线的生成算法,圆弧的生成算法

直线生成算法

首先你要考虑这么一个问题,显示图形是由像素点构成的,点是离散的点,如果线是刚好完全经过像素点,比如你画个x,y轴,那多简单是吧,那你画的不完全经过像素点,就涉及到了像素点的取舍问题,如何让你的直线在离散点的构成中更像一条直线,成像质量和你的离散点的分布有关,就是直线生成算法的意义了。看下面这个图你就明白了。
在这里插入图片描述
在光栅显示器上显示任何图形,都是具有一种或多种颜色的像素的集合;
基本原理:用增量法的原理来生成图形的,即x,y两个方向”走步”(离散)来绘图;

增量算法:在一个迭代算法中,如果每走一步x,y值是用前一步的值加上一个增量来获得,则称为增量算法。
也就是通过得到的x和y来确定下一个离散的像素点。常见的直线生成算法有数值微分法DDA,中点画线法,Bresenham算法。下面将对三种算法进行一个简单介绍。

数值微分法DDA

基本思想:
已知过端点P0(x0,y0),P1(x1,y1)的直线段L:y=kx+b
直线斜率为在这里插入图片描述
特殊符号不好表示,直接用图片了,用上一个点计算下一个点:
在这里插入图片描述
注意上述分析的算法仅适用于|k| ≤1的情形。在这种情况下, x每增加1,y最多增加1当 |k| >1时, 必须把x,y地位互换.
具体斜率不同的情况如下图:
在这里插入图片描述
根据上面的每次x变化多少,y就变化多少个k值,可以得如下算法:

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; x<=x1, x++)	/*x的步长为1*/		
	  {   drawpixel (x, int(y), color);		   
	     y=y+k;
       }
  }

如果使用上述的算法,那么进行直线段P0(0,0)–P1(5,2)的绘制,将是会得到如下图:因为下一个点是有上一个点来生成的,将上一个加的k给截掉了,k小于0.5,所以每次都截掉,就生成了下面这个错误的结果。
在这里插入图片描述
所以需要对算法进行调整,比如给加上一个调整因子,0.5,像素点的一半。

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; x<=x1, x++)	/*x的步长为1*/		
	  {   drawpixel (x, int(y+0.5), color);		   
	     y=y+k;
       }
  }

程序的实现code:

void  DDAline(int  x0, int  y0, int  x1, int  y1,int c)
{   int  dx,dy,steps,k;   float  x,y, delta_x, delta_y;
     dx=x1-x0;    dy=y1-y0;    x=x0;  y=y0;
     if(abs(dx)>abs(dy)) 
            stepa=abs(dx);
     else  steps=abs(dy);
    delta_x=(float)(dx)/steps;
    delta_y=(float)(dy)/ steps;
     for(k=0;k<= steps;k++)
     {   putpixel((int)(x+0.5),(int)(y+0.5),255);
          x+= delta_x;   y+= delta_y;
      }

此时再对直线段P0(0,0)–P1(5,2)进行绘制:
在这里插入图片描述
得到的结果好多了。
在这里插入图片描述
算法的优缺点:
这种方法直观,一看就明白,但效率太低. 因为在此算法中,y、k必须是float,每一步需要一次浮点乘法和一次舍入运算, 不利于硬件实现。

中点画线法

下面介绍一种中点画线法。
假定直线斜率0<K<1,且已确定点亮象素点P(xp,yp),则下一个与直线最接近的像素只能是P1点或P2点.设M为中点, Q为交点.现需确定下一个点亮的象素.
在这里插入图片描述
也就是需要判断这个交点是在中点的上面还是下面。

M在Q的下方-> P2离直线更近->取P2
M在Q的上方-> P1离直线更近->取P1
M与Q重合,P1、P2任取一点

问题:如何判断M与Q点的关系?
过(x0,y0),(x1, y1)的直线段L的方程式为
F(x, y)=ax+by+c=0.欲判断中点M在Q点的上方还
是下方,只要把M代入F(x,y),并判断它的符号即可 .

其中,a=y0-y1, b=x1-x0, c=x0y1-x1y0
在这里插入图片描述
构造判别式:d=F(M)=F(xp+1,yp+0.5) =a(xp+1)+b(yp+0.5)+c

当d<0,M在直线(Q点)下方,取右上方P2,即x++,y++;
当d>0,M在直线(Q点)上方,取右方P1,x++,y不变;
当d=0,选P1或P2均可,约定取P1,x++,y不变;

那么每绘制一个点前,计算一个新的d,根据d的符号判断下一个待绘制的点。
能否采用增量算法求解d呢?

1.若d>0 ,M在直线上方,取P1;此时再下一个象素的判别式为
d1=F(xp+2, yp+0.5)=a(xp+2)+b(yp+0.5)+c
= a(xp +1)+b(yp +0.5)+c +a =d+a;
增量为a。

2.若d<0 -> M在直线下方 -> 取P2;此时再下一个象素的判别式为
d2= F(xp+2, yp+1.5)=a(xp+2)+b(yp+1.5)+c
= a(xp +1)+b(yp +0.5)+c +a +b =d+a+b ;
增量为a+b。

画线从(x0, y0)开始,d的初值
d0=F(x0+1, y0+0.5)= a(x0 +1)+b(y0 +0.5)+c
= F(x0, y0)+a+0.5b = a+0.5b
由于只用d 的符号作判断,为了只包含整数运算,
可以用2d代替d来摆脱小数,提高效率。这样增量也要*2.

算法code:

void Midpoint Line (int x0,int y0,int x1, int y1,int color)
     {   int a, b, d1, d2, d, x, y;
    a=y0-y1, b=x1-x0, d=2*a+b;
    d1=2*a, d2=2* (a+b);
    x=x0, y=y0;
    drawpixel(x, y, color);
    while (x<x1)
    { if (d<0)       {x++; y++; d+=d2; }
     else       {x++; d+=d1;}
     drawpixel (x, y, color);
     }  /* while */
 } /* mid PointLine */

用中点画线法P0(0,0) P1(5,2)

a=y0-y1=-2 , b=x1-x0=5
d0=2a+b=1 ,d1=2a=-4 ,d2=2(a+b)=6

d是采用增量变化的。
在这里插入图片描述
在这里插入图片描述

Bresenham算法

它计算机图形学领域中使用最广泛的直线生成技术。该算法适合于光栅图形显示器、数字化仪设计等设备
Bresenham算法最早应用于绘图仪上,后来被移植到显示器上。
它是通过每列像素中确定与理想直线最近的像素来进行直线的扫描转换的。
原理:Bresenham算法与DDA算法类似,但不再采用四舍五入的办法;与中点画线法类似,由符号来决定下一个像素点的位置。若K在0—1之间,在x方向上每增加一个单位长度,y方向是否增加一个单位长度是根据计算误差项的符号P确定的。
在这里插入图片描述

假定直线斜率,0<k<1, 起点坐标为(x1,y1),终点坐标(x2,y2);
下一个点亮象素可能是:(x1+1,y1) 或 (x1+1,y1+1)。这可以通过d值的大小来确定:

直线的方程为:y=kx+b
其中b=y1-kx1,k=(y2-y1)/(x2-x1)

考虑斜率0<k<1时的扫描转换情况
如图:
在这里插入图片描述
设斜线在( xk,yk)已经确定了一个点,下一步是xk+1处的点,y如何确定?

设点Q为斜线段与xk+1直线的交点,d1、d2分别为点Q距上下平行线yk, yk+1距离。
设斜线y=kx+b,点Q的x轴坐标为xk+1,则点Q的y轴坐标为:y=k(xk+1)+ b
则可求出d1、d2的长度:
d1=k(xk+1)+b -yk
d2=(yk+1)–[k(xk+1)+b ]
则d1、d2的差值为
在这里插入图片描述
上式有除法,两边同乘dx,在0<k<1时,dx>0
记:P=d1-d2, P=2dy(xk+1)-2ykdx+(2b-1)dx。

也就是说当:
P>0 时, 取上点,即xk+1=xk+1,yk+1=yk+1
P<0 时, 取下点, 即xk+1=xk+1,yk+1=yk
P=0 时, 始终取上(或下)点

每一步都要计算新的d1,d2,再判断P的符号?能否用增量呢?即P’=P+增量,这样就可以减少运行量了。

1.P>0的情况
新的d1’, d2’,P’。
d1=k(xk+2)+b-(yk+1),
d2=(yk+2)-[k(xk+2)+b]
P’= d1-d2=P+ 2(dy-dx)
增量为: 2(dy-dx)
在这里插入图片描述
2.P<0的情况
新的d1’, d2’,P’
d1=k(xk+2)+b- yk
d2=(yk+1)-[k(xk+2)+b]
P’= d1-d2=P+ 2dy
增量为: 2dy
在这里插入图片描述
那么初始值p0是多少呢?
d1=k(x0+1)+b-y0
d2=y0+1-[k(x0+1)+b]
P0= 2dy-dx

由上述内容,可以得到算法的基本思想:
在这里插入图片描述
bresenham法画P0(0,0) P1(5,2)
dx = x2-x1=5, dy=y2-y1=2
p0=2dy-dx=-1,2dy-2dx = -6,2dy=4.
在这里插入图片描述
在这里插入图片描述

Bresenham算法的优点是:
1、不必计算直线之斜率,因此不做除法;
2、不用浮点数,只用整数;
3、只做整数加减法和乘2运算,而乘2运算可以用硬件移位实现。
Bresenham 算法速度很快,并适于用硬件实现。

直线属性

线型、线宽和颜色
线型可用一个布尔值的数组来存放。一个18位整数可以存放18个布尔值,如果用这样的整数存放线型定义时,线型必须以18个象素为周期进行重复。
在这里插入图片描述
缺口解决方法
在这里插入图片描述
在这里插入图片描述
小总结:三种算法都用增量,方便计算1.DDA算法,算斜率,每次增量都是斜率。2.中点画线法,a=y0-y1,b=x1-x0,初始d0 = 2a+b,d1=2a,d2=2(a+b)
3.bresenham算法,dx=x2-x1,dy=y2-y1,初始p0=2dy-dx,再更加pi的大小进行+2dy-dx或者+2dy。
自己把我下面这个给画出来就理解了。

请自行完成下面三个例题。

1【计算题】利用DDA直线生成算法,绘制从P1(0,0)P2(6,4)的直线,请写出经过哪些像素点?
正确答案:
(0,0)(1,1)(2,1)(3,2)(4,3)(5,3)(6,4)
2【计算题】利用中点画线算法,绘制从P1(0,0)P2(6,4)的直线,请写出经过哪些像素点?
正确答案:
(0,0)(1,1)(2,1)(3,2)(4,3)(5,3)(6,4)
3【计算题】利用Bresenham直线生成算法,绘制从P1(0,0)P2(6,4)的直线,请写出经过哪些像素点?
正确答案:
(0,0)(1,1)(2,1)(3,2)(4,3)(5,3)(6,4)

终于TM的直线生成的常见算法给整完了。接下来开始圆弧的生成算法。

圆弧生成算法

圆具有对称特性。圆心位于原点的圆有四条对称轴:x=0,y=0,x=y和x=-y直线。若已知圆弧上一点(x,y),可以得到其关于四条对称轴的其它7个点,这种性质称为八对称性,因此只需要绘制出1/8圆,其它的按对称即可。以绘制1b象限为例。
在这里插入图片描述
常用的圆弧生成算法有:中点画圆法、Bresenham画圆法、生成圆弧的正负法、圆弧的多边形逼近法等。
其中,中点画圆法和Bresenham画圆法与中点画线法和Bresenham画线法的思想基本相同。

中点画圆法

利用圆的对称性,只须讨论1/8圆。

在这里插入图片描述
P为当前点亮象素,那么,下一个点亮的象素可能是P1(Xp+1,Yp)或P2(Xp +1,Yp -1)。
在这里插入图片描述
d = F(M) = F(xp + 1, yp - 0.5)
=(xp + 1)^2 + (yp - 0.5)^ 2 - R^2

  1. 若d<0, 则P1 为下一个象素,那么再下一个象素的判别式为:
    d1 = F(xp + 2, yp - 0.5)
    = (xp + 2)^2 + (yp - 0.5) ^2- R^2 = d + 2xp +3
    即d 的增量为 2xp +3.
  2. 若d>=0, 则P2 为下一个象素,那么再下一个象素的判别式为:d1 = F(xp + 2, yp - 1.5)
    = (xp + 2)^2 + (yp - 1.5) ^2 - R^2
    = d + (2xp + 3)+(-2 yp + 2)
    即d 的增量为 2 (xp - yp) +5

d的初值d0 = F(1, R-0.5) = 1 + (R-0.5)^2 - R^2 = 1.25 - R

若d<0,d=d0+2xp+3
否则,d=d0+ 2 (xp - yp) +5
根据以上code如下:

 MidpointCircle(int r, int color)
 {
          int x,y;
          float d;
          x=0; y=r; d=1.25-r;
          drawpixel(x,y,color);
          while(x<y){
                            if(d<0){ d+ = 2*x+3; x++ }
                            else{d+ = 2*(x-y) + 5; x++;y--; }
                             }
 }

为了进一步提高算法的效率,可以将上面的算法中的浮点数改写成整数,将乘法运算改成加法运算,即仅用整数实现中点画圆法。

消除浮点运算
令e= d-0.25
初始值e0= 1.25-R-0.25=1-R  为整数
判别式为d<0   ---> e<-0.25   等价于e<0

Bresenham画圆算法

在这里插入图片描述

现在从A点开始向右下方逐点来寻找弧AB要用的点. 如图中点Pi-1是已选中的一个表示圆弧上的点,根据弧AB的走向,下一个点应该从Hi或者Li中选择.显然应选离AB最近的点作为显示弧AB的点.
在这里插入图片描述
那么如何计算di?
在这里插入图片描述

在这里插入图片描述
当di<0时,取Hi ,yi=yi-1,则
di+1 = di + 4xi-1 + 6

当di ≥ 0时,取Li , yi=yi-1-1,则
di+1 = di + 4(xi-1-yi-1) + 10

易知 x0=0,y0=R, x1=x0+1,

因此 : d0=12 + y02 + 12 +(y0 - 1)2 - 2R2
= 3 - 2y0 = 3 - 2R

由上述内容易写算法code:

 Bresenhamcircle(int r, int color)
  {
  int x, y, d; 
        x=0; y=r; d=32*r;
  while(x<y)
       {
      drawpixel(x,y,color);
      if (d<0) {d=d+4*x+6;x++}
              else{ d= d+4*(x-y)+10;x++;y-- ; }
        }
     }

生成圆弧的正负法

设要显示的圆的圆心在(xc, yc),半径为R.令F(x,y)=(x-xc)^2 + (y-yc)^2 -R^2,
F(x,y)=0 即是圆的方程
在这里插入图片描述
利用F(x,y)作判断点与曲线之间关系。
当点(x,y)在圆内时,有F(x,y)<0;
当点(x,y)在圆外时,有F(x,y)>0;
在这里插入图片描述
即求得Pi点后选择下一个象素点Pi+1的规则为:
当F(xi,yi) ≤0 取xi+1 = xi+1,yi+1 = yi;
当F(xi,yi) >0 取xi+1 = xi, yi+1 = yi - 1;
这样用于表示圆弧的点均在圆弧附近,且使F(xi,yi) 时正时负,故称正负法。

快速计算的关键是F(xi,yi) 的计算,能否采用增量算法?
若F(xi,yi) 已知,计算F(xi+1,yi+1) 可分两种情况:

  1. F(xi,yi)≤0 --> xi+1 = xi+1,yi+1 = yi;
    –> F(xi+1,yi+1)= (xi+1 )2 +(yi+1 )2 -R2
    –> = (xi+1)2+ yi2 -R2 = F(xi,yi) +2xi +1
  2. F(xi,yi)>0 --> xi+1 = xi,yi+1 = yi -1;
    –> F(xi+1,yi+1)= (xi+1 )2 +(yi+1 )2 -R2
    –> = xi2+(yi -1)2-R2 = F(xi,yi) - 2yi +1
    这样就得到了增量的值。
    code:
void pnarc(int r,xc,yc);
int x,y,f;
{
    x=xc; y=yc+r; f=0;
    while (y>yc)
    {
            plot(x,y);
            if ( f>0)
                {  f=f-2*(y-yc)+1; y=y-1;}
           else
               {f=f+2*(x-xc)+1; x=x+1; }         
    }
    if(y==yc) plot(x,y)
    }

圆弧的多边形逼近法

画曲为直的思想。
两种方法:
正内接多边形迫近法
等面积正多边形迫近法
在这里插入图片描述

特点:
多边形边数足够多时接近圆
误差控制边数
在这里插入图片描述
1、正内接多边形迫近法
圆:x2 + y2 = R2 ---------圆心坐标(0,0)
内接多边形顶点集:
在这里插入图片描述
在这里插入图片描述
一个圆再怎么逼近毕竟也还是直线的,那么多少边较为合适呢?
给定最大逼近误差(最大 距离)d , 如何确定多边形的边数n或a?
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
2.2、等面积正多边形逼近法
面积:
正多边形的面积等于圆的面积
内接正多边形的面积小于圆的面积
步骤:
求多边形边长,从而求所有顶点坐标值
由逼近误差值,确定边所对应的圆心角α

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
由逼近误差值d,确定边所对应的圆心角α
在这里插入图片描述
在这里插入图片描述

椭圆的扫描转换算法

椭圆方程 x^2 / a^2 + y^2/ b^2 = 1
隐函数形式:F(x,y)= b^2 x^2+ a^2 y^2- a^ 2 b^2=0
椭圆的4对称性,只考虑第一象限椭圆弧生成
在这里插入图片描述

分上下两部分,以弧上斜率为-1的点(法向两个分量相等的点)作为分界点。
椭圆上一点(x,y)处的法向:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
从而弧上斜率为-1的点满足:法向量的两个分量相等,即
2b^2 x = 2a^2 y
可求得斜率为-1的点坐标:
在这里插入图片描述
在这里插入图片描述
在分界点的上部分,法向量的y分量更大;在下部分,法向量的x分量更大. 若在当前中点,法向量
在这里插入图片描述

的y分量比x分量大,即
在这里插入图片描述
而在下一个中点,不等号改变方向,则说明椭圆弧从上部分转入下部分。

椭圆的中点画法

与圆弧中点算法类似:确定一个象素后,接着在两个候选象素的中点计算一个判别式的值,由判别式的符号确定更近的点。
考虑上方的椭圆弧段:斜率满足-1≤k≤0, 下一像素可能是中点的上方或下方。
在这里插入图片描述
在这里插入图片描述
初始条件
椭圆弧起点为(0,b),第一个中点为(1,b-0.5),d1判别式 的初始条件为:
在这里插入图片描述
由于上部分和下部分的算法不同, 因而在每一步的迭代过程中,必须通过计算法向量的两个分量来确定是否从上部分转入下部分.

转入下方的椭圆弧,斜率-1≤ 1/k≤0, y递减一个像素, x至多递增一个像素. 下一象素可能是一正下方或右下方,x和y角色互换,此时判别式要初始化。
在这里插入图片描述
下部分也可以:从 x轴的 x=a, y=0
点逆时钟方向开始计算
在这里插入图片描述
code:

MidpointEllipe(int a, int b, int color)
{  int x,y;  float d1,d2;
    x = 0; y = b;
    d1 = b*b +a*a*(-b+0.25);
    SetPixel(x,y,color);
    while( b*b*(x+1) < a*a*(y-0.5))
    {
       {  
          if (d1<0)
	        d1 +=b*b*(2*x+3); x++;  }
	     else { 
             d1 +=(b*b*(2*x+3)+a*a*(-2*y+2));
	        x++;  y--;	
       } 
		SetPixel(x,y,color);
     } //上部分
        d2 = sqr(b*(x+0.5))  +  sqr(a*(y-1))sqr(a*b);
   while(y >0)
   {  if (d2 <0)
       { 
          d2 +=b*b*(2*x+2)+a*a*(-2*y+3);
          x++;  y--}
      else 
      { d2 += a*a*(-2*y+3);  y--; }
      SetPixel(x,y,color); 
   } //下部分
}

这一章的内容属实有点多,今天就分享到这里,主要重点是增加法的使用,直线生成算法在考试用十分常见,一定要掌握。

©️2020 CSDN 皮肤主题: 黑客帝国 设计师:上身试试 返回首页