图形学讲义1. 计算机图形学 一 . 教学目的 计算机图形学是一门理论性、实用性强的学科且具有非常广泛的应用领域,是计算机科学与技术专业重要课程之一。介绍计算机图形学的主要教学目的是使我们掌握计算机图形学的基本理论及其图形绘制程序设计的相关方法技术 ,培养计算机科学与技术的应用能力。为图形学的进一步的应用研究打下良好的理论基础与图形软件设计基础。 2. 二 . 教学路线与教学内容 计 算 机 图 形 学 二维图形生成算法原理 图 形 变 换 二维图形变换 三维图形变换 图形裁剪 三 维 图 形 设 计 三维形体表示 几何投影变换 三维图形消隐 真实感图形生成技术 图 形 系 统 硬件 软件 基本 图形 程序 设计 图形程序设计语言 简单图形程序设计 与分析 4. 第一章 绪 论 1 、 1 计算机图形学与图像处理 1 计算机图形学 ( 1 )计算机图形学: 是真实物体或虚构物体的图形综合技术。 综合技术包括: 图形数据结构 算法与程序设计 图形处理 变换与消隐技术 图形显示输出处理技术 ( 2 )计算机图形学主要研究问题 1 )如何在计算机中构造一个客观世界——几何(模型)的描 述 、创建和处理 2 )如何将计算机中的虚拟世界用最形象的方式、静态或动态 地展现出来 ( 2 )计算机图形学的处理过程 12. CRT 偏转灵敏度 CRT 偏转灵敏度示反映信号所能产生的偏转角度的 大小。解决灵敏度的途径 1 。增长电子枪的长度 2 。增加电子束的速度 彩色 CRT 彩色的产生一般是由三种基色( R 、 G 、 B) 。由此构 成三个电子枪,即为三基色电子枪,三种基色作用于同一 点而产生混合的彩色,而构成彩色的 CRT 。 目前采用的方法有: 1 。射线穿透法:是根据电子射线穿透屏幕上的荧光 层的深浅来形成彩色(广泛用于随机扫描显示器)。 2 。影孔板法:十三个不同颜色的电子枪产生三个荧 光电发出三基色而构成(广泛用于光栅扫描显示器)。 14. (3) 彩色表技术 对于彩色显示器要分别控制三个原色,并且每个原色 有 256 种灰度级,即每个原色就要占帧缓存单元 8 位,三个原 色则占 24 位,若屏幕分辨率 800×600 象素点,则需要 800×600 占 24 位的单元。因此,要求帧缓存空间就很大,显然,其价格 就会有较大增加。为此,人们采用了彩色表技术。 如图所示 29. 2.4.1 TBrush 类 TBrush 类是 C ++ Builder 描述 Windows 的画刷所具有的填充特性 。 TBrush 类属性与方法 属性或方法 说 明 Color 指定 填充 的颜色 Style 指定 填充 式样 Bitmap 指定 填充图案 。 ~TBrush 释放 TBrush 。 TBrush 创建一个 TBrush 实例。 Assign 赋值另一个 TBrush 对象的属性。 37. TBitmap 类 TCanvas canvas ; Int Height ; Int Width ; Bool Empty ;指定位图对象是否包含一个位图 LoadFormFile(AnsiString filename); SaveToFile(const AnsiString Filename); 40. int L,W,H,R,Rmin,Tr,Ti,x0,y0; x0=100; y0=250; L=StrToInt(Long->Text); // 板长 W=StrToInt(Width->Text); // 板宽 Ti=StrToInt(Circle_H->Text); // 圆柱厚 H=StrToInt(Height->Text); // 圆柱高 R=StrToInt(Circle_R->Text); // 圆柱内径 Rmin=StrToInt(CirCle_min_R->Text); // 小圆孔内径 Tr=StrToInt(TR->Text); // 工件板倒圆尺寸 // 绘制工件主体部分 Canvas->RoundRect(x0,y0,x0+L,y0+W,Tr,Tr); Canvas->Arc(x0+Tr-Rmin,y0+Tr-Rmin,x0+Tr+Rmin,y0+Tr+Rmin,0,0,0,0); Canvas->Arc(x0-Tr-Rmin+L,y0+Tr-Rmin,x0-Tr+Rmin+L,y0+Tr+Rmin,0,0,0,0); Canvas->Arc(x0+Tr-Rmin,y0-Tr-Rmin+W,x0+Tr+Rmin,y0-Tr+Rmin+W,0,0,0,0); Canvas->Arc(x0-Tr-Rmin+L,y0-Tr-Rmin+W,x0-Tr+Rmin+L,y0-Tr+Rmin+W,0,0,0,0); Canvas->Arc(x0+L/2-R,y0+W/2-R,x0+L/2+R,y0+W/2+R,0,0,0,0); Canvas->Arc(x0+L/2-R-Ti,y0+W/2-R-Ti,x0+L/2+R+Ti,y0+W/2+R+Ti,0,0,0,0); Canvas->Rectangle(x0,y0-50,x0+L,y0-50+Tr); Canvas->Rectangle(x0+L/2-(R+Tr),y0-50,x0+L/2+(R+Tr),y0-50-H-Tr); 41. DrawLine(x0+L/2-R,y0-50,x0+L/2-R,y0-50-H-Tr,2); DrawLine(x0+L/2+R,y0-50,x0+L/2+R,y0-50-H-Tr,2); // »æÖÆÔ°ÖÐÐÄÏß DrawLine(x0,y0+W/2,x0+L,y0+W/2,3); DrawLine(x0+L/2,y0,x0+L/2,y0+W,3); DrawLine(x0,y0+Tr,x0+2*Tr,y0+Tr,0); DrawLine(x0+Tr,y0,x0+Tr,y0+2*Tr,0); DrawLine(x0+L-2*Tr,y0+Tr,x0+L,y0+Tr,0); DrawLine(x0+L-Tr,y0,x0+L-Tr,y0+2*Tr,0); DrawLine(x0,y0+W-Tr,x0+2*Tr,y0+W-Tr,0); DrawLine(x0+Tr,y0+W-2*Tr,x0+Tr,y0+W,0); DrawLine(x0+L-2*Tr,y0+W-Tr,x0+L,y0+W-Tr,0); DrawLine(x0+L-Tr,y0+W-2*Tr,x0+L-Tr,y0+W,0); void __fastcall TForm1::DrawLine(int xs, int ys, int xe, int ye, int style) { Canvas->Pen->Style=style; Canvas->MoveTo(xs,ys); Canvas->LineTo(xe,ye); } 45. 4. 程序设计步骤 第一步:选择“ File/New” 菜单创建一个工程项目文件 T301 。在创 建后的窗体菜单条上创建主菜单“平面曲线”和子菜单项“抛物线”。 第二步:选择“ File/New” 菜单的对话框的“ Dialogs” 标签,设计参 数输入对话框。保存文件名为“ ParDialog” 。 第三步:增加 T M ainForm 类的变量XL、YH、P、N说明。其 方法是在类专家管理器中,鼠标光标移动至“ T M ainForm” 上,按 下右键,选择“ NewField” ,弹出对话框。依照对话框的内容填写 “ FieldName” 和“ Type” ,并可选择成员类型,按“OK”键结束一次变量的添加。依次上述过程添加全部变量。 第四步:增加绘制抛物线的方法成员。其增加方法是:在类专家 管理器中,鼠标光标移动至“ T M ainForm” 上,按下右键,选择 “ N ew M athod” ,弹出对话框。依照对话框的内容填写,在 “ MethodName” 中填写 “ DrawCurve” ,在“ Arguments” 中填写参变 量类型和变量名(因本例因无参数,则不填写)及其其它内容的 选择。选择后,按“OK”键。代码窗中,即刻显示该方法名 ( MethodName )的成员函数的框架。 46. 2.5.1.2 直方图的绘制 1. 建立图形的数学模型 ⑴ 矩形条 x 坐标确定: 由图 3.12 假设 N 是直方图的组数 M, 每组中的对象特征个数 N, 设 dx 是绘制对象特征矩形条的宽度,组间距离 Ds 。由此可得出下式: Dx=XL/((M+A)*n) Ds=A*dx ( 3-8 ) Xij=I*(M*dx+ds)+j*dx+X0 Yscale=YH/Vmax Yij=Y0-vij*Yscale 其中: A :是组间距与矩形宽度的比值。 Vmax: 统计的最大值, vij: 统计值 XL :是绘图区域的宽度 , YH: 是绘图区域的高度 50. DDA(int x1,int y1,int x2, int y2) { int xinc,yinc,x,y,i; float k,dx,dy; dx=abs(x2-x1); dy=abs(y2-y1); k=dx; if (dy>dx) k=dy; xinc=int(dx/k); yinc=int(dy/k); x=x1; y=y1; Image1->Canvas->Pixels[x][y]=RGB(255,0,0); for(i=1;i<=k;i++) { x=x+xinc; y=y+yinc; Image1->Canvas->Pixels[x][y]=RGB(255,0,0); } } 51. 3. Bresenham 法 1) 基本思想:在某一计长方向上每次必变化 1 个单位,另一个方向上的变化通过计算得到。 2) 判别式 如图所示 , 依据直线的逼近的最近的原则,现在要决定下一个点是 T 还是 S 。右图可以看出: 如 s<t, 则为 S 。如 t>=s 则为 T 。由此, d=s-t 当 d>0 则下一个点是 T, 否则 下一个点是 S 。由直线方程 : y=dy/dx*x 可分别求得: s t P(x i-1 ,y j-1 ) S(x i ,y j-1 ) T(x i ,y j ) 53. 输入参数: xc,yc,a,b,ts,te 和椭圆的旋转角 q 和 dt n=(te-ts)/dt+0.5; a1=a*cos(ts*0.0174533); a2=cos(q*0.0174533); b1=b*sin(ts); b2=sin(q*0.0174533); N n==0 Y n=6.28319/dt+0.5 x=xc+a1*a2-b1*b2 y=yc+a1*b2+b1*a2; MoveTo(x,y); I=0;I<n;I++ t=I*dt; a1=a*cos(t) ; b1=b*sin(t); x=xc+a1*a2-b1*b2; y=yc+a1*b1+b1*a2; LineTo(x,y); 54. void __fastcall TForm1::BLine(int xs, int ys, int xe, int ye, TColor color) { int i,x,y,f; float xinc,yinc; dx=abs(xe-xs); dy=abs(ye-ys); x=xs; y=ys; if((xe-xs)>=0) s1=1; else s1=-1; if((ye-ys)>=0) s2=1; else s2=-1; if(dy>dx) {swap(&dx,&dy);flag=1;}else flag=0; f =2*dy-dx; for(i=1;i<=dx;i++) { 55. Canvas->Pixels[x][y]=color; if(f>=0) { if(flag==1) x=x+s1;else y=y+s2; f=f-2*dx; } if(flag==1) y=y+s2;else x=x+s1; f=f+2*dy; } } void __fastcall TForm1::swap(int *x, int *y) { int temp; temp=*x; *x=*y; *y=temp; } 58. 2. 角度 DDA 法产生椭圆或椭圆弧 1) 椭圆的参数方程: x=a*cost y=b*sint 由上面个参数方程可以看出,它只能画一个正的椭圆,若要画任意椭圆则可对其作旋转可得: x=a*cos(t)*cos(a)-b*sin(t)*sin(a) y=a*cos(t)*sin(a)+b*sin(t)*cos(a) 2) 画任意椭圆的程序设计数学模型 已知椭圆的角度增量: dt, 起始、 终止 弧的角度: ts 、 te 则: n=(te-ts)/dt+0.5 59. 输入参数: xc,yc,a,b,ts,te 和椭圆的旋转角 a 和 dt ts=ts*0.0174533 ; te=te*0.0174533 ; a=a*0.0174533; n=(te-ts)/dt+0.5; N n==0 Y n=6.28319/dt+0.5 I=0;I<n;I++ t=ts+I*dt; x=xc+a*cos(t)*cos(a)-b*sin(t)*sin(a); y=yc+ a*cos(t)* sin(a)+ b*sin(t)* cos(a); N i==0 Y LineTo(x,y); MoveTo(x,y) 60. 4. Bresenham 画圆算法 1 )基本思想:选择一个离开实际圆弧最近的点,即误差: |D(pi)|=|(xi*xi-yi*yi)-R*R| 最小。 由图所示 , 若 C 是 R 半径圆弧实际点,则 D(S) 和 D(T) 是离开实际圆的误差值。所以由 : di=|D(s)|-|D(T)| 的正负能够决定选择点 T 或 S 。 由于, di 是 R 的递减函数,当 R=Rs 时, di=0 。当 Rs>R 时,圆弧在 C 的上方, di<0 。当 Rs<R 时,圆弧在 C 的下方, di>0 。 故: di=D(s)+D(T) 当 di<0 ,取 S 点 , 当 di>0 ,取 T 点 , 若设圆 起点 x0=0 ,y0=R 则 d1=3-2R A B C D E 61. void __fastcall TForm1::Circle(int xc, int yc, int R, TColor color) { int x,y,d; x=0; y=R; d=3-2*R; while (x<y) { plot(xc,yc,x,y,color); // 绘制圆的对称点 if (d<0) d=d+4*x+6; else { d=d+4*(x-y)+10; y=y-1; } x=x+1; } if(x==y)plot(xc,yc,x,y,color); } 62. void __fastcall TForm1::plot(int xc, int yc, int x,int y,TColor color) { Canvas->Pixels[xc+x][yc+y]=color; Canvas->Pixels[xc+y][yc+x]=color; Canvas->Pixels[xc+y][yc-x]=color; Canvas->Pixels[xc+x][yc-y]=color; Canvas->Pixels[xc-x][yc-y]=color; Canvas->Pixels[xc-y][yc-x]=color; Canvas->Pixels[xc-y][yc+x]=color; Canvas->Pixels[xc-x][yc+y]=color; } // 绘制圆的激活函数句柄 void __fastcall TForm1::BCircle1Click(TObject *Sender) { Circle(100,100,50,RGB(255,0,0)); } 63. 4.2 二次曲线的参数拟合法 1. 二次曲线的一般参数方程 见 P72 (3-13) 1 )抛物线的参数拟合 当 e1=e2=0 时,参数方程为抛物线。其方程为 p73(3-16) 2) 参数的拟合条件 ( 如图所示) a. 当 t=0 时,曲线过 p0 点,且切于 p0p1; b. 当 t=1 时,曲线过 p2 点,且切于 p1p2; 3 )求系数 a,b,c 将 t=0 代入( 3-16 )得: C=p0 将 t=0 代入( 3-16 )得: a+b+c=p2 64. 将 t=0 代入( 3-16 )的求导后的表达式 Q’(t)=2at+b 得: b=p0’ =k(p1-p0) 将 t=0 代入( 3-16 )的求导后的表达式 Q’(t)=2at+b 得: 2a+b=p2’=l(p2-p1) 由上式科的方程组: c=p0 a+b+c=p2 b=k(p1-p0) 2a+b=l(p2-p1) 求解方程组 : a+b+c=l/2(p2-p1)+b/2=p2-p0 =l(p2-p1)+k(p1-p0)=2(p2-p0) 由于直线 p2p1 、 p1p0 、 p2p0 线性无关,所以 : l=k=2 。 65. 分别将 l=2,k=2 代入方程组,求得: c=p0 b=2(p1-p0) a=p2-2p1+p0 由于点是一个二维坐标,所以: cx=x0 cy=y0 bx=2(x1-x0) by=2(y1-y0) ax=x2-2x1+x0 ay=y2-2y1+y0 将代入方程( 3-16 )得: x(t)=(x2-2x1+x0 )*t*t+ 2(x1-x0) t+x0 y(t)=(y2-2y1+y0 )*t*t+ 2(y1-y0) t+y0 整理: x(t)=(1-2t+t*t)x0+(2t-2*t*t)x1+t*t*x2 y(t)=(1-2t+t*t)y0+(2t-2*t*t)y1+t*t*y2 66. 4) 抛物线的参数拟合的两个重要性质 a . 曲线在 t=1/2 处的切线平行于 p0p2 由 Q’(t)=2at+b 可知: y’ t /X’ t | 1/2 =(2a y t+b y )/ (2a x t+b x ) 将 ax=x2-2x1+x0 ay=y2-2y1+y0 bx=2(x1-x0) by=2(y1-y0) 得: y’ t | 1/2 = ( y2-y0)/(x2-x0) b. p m 点为 p1C 直线的中点 pm=Q(1/2)=(1-1+1/4)p0+(1-1/2)p1+1/4p2 =1/4p0+1/2p1+1/4p2 =1/2(p1+1/2(p0+p2)) 即: xm=1/2(x1+1/2(x0+x2)) ym= 1/2(y1+1/2(y0+y2)) 67. 由此:将 t=1/2 代入( 3-16 )则可得出 P74 的方程组求解方 程组得出( 3-19) 。由( 3-19 )可以得出结论: 由三个控制点 p0 、 p1 、 p2 和三个型值点 p0 、 pm 、 p2 所构成得抛物线等价的。例如: p0(50,50) 、 p1(100,200) 、 p2(200,50) 作为三种不同拟合的曲线比较(见程序 ) 。 依据这一特性,我们可以看出,利用这一特性采 用型值点更加具有实际意义。因为实际中往往获 得是型值点。 3. 双曲线的参数拟合(当 e1=1,e2=0 时 ) Q(t=0)=c=p0 Q(t=1)=(a+b+c)/2=p2 Q’(t=0)=b-c=k(p1-p0) Q’(t=1)=(3a+b-c)/4=l(p2-p1) 求解方程组求得 a 、 b 、 c ( 见 p77 ) 4. 椭圆的参数拟合(与上面的原理一致) 68. 4.3 自由曲线 一、基本概念 1. 自由曲线的定义:指的是形状比较复杂,不能用二次方程表示 的曲线。 2. 曲线的拟合: 完全通过给定的点 ( 型值点)来构造曲线的方法。 3. 曲线的插值:求给定型值点之间的曲线的点方法。 4. 曲线的逼近:求出几何形状上与给定型值点列的连线相近似的 曲线方法。 二、抛物线参数样条曲线 1. 基本思想: 由给定的 N 个 型值点 p1 、 p2 、…、 pn, 依 次对相邻的三个点 p i 、 p i+1 、 p i+2 及 p i+1 、 p i+2 、 p i+3 , i=1,2,……,n-2 。反复 使用抛物线算法拟合。 70. 由以上的表达式不难看出,曲线不通过两个端点。所以,必须在两 端点增加一个点。即: 由抛物线参数拟合可知: p’ 1= p 2 -p 0 和 p n ’=P n+1 -p n 则: p 0 =p 2 -p 1 ’ 和 p n+1 =p n ’+P n-1 故:我们可以通过给定 p’ 的值或者依据直线 p 1 p 2 的斜率外延 p 0 点。 3. 程序设计 输入给定点的坐标数据 p 1 ,p 2 ,……,p n-1 ,p n 计算 P 0 和 p n+1 坐标值 I=0; I<n-2; I++ dt=0.5/m; j=0; j<=m; j++ t=j*dt; 71. X=(4*t*t-t-4*t*t*t)x j +(1-10*t*t+12*t*t*t)x j+1 +(t+8*t*t-12*t*t*t)x j+2 +(4*t*t*t-2*t*t)x j+3 y=(4*t*t-t-4*t*t*t)y j +(1-10*t*t+12*t*t*t)y j+1 +(t+8*t*t-12*t*t*t)y j+2 +(4*t*t*t-2*t*t)y j+3 N I==0 Y LineTo(x,y); MoveTo(X,Y); 画制坐标系 72. 二、 Hermite 曲线 1 . 参数方程 将 (3-20) 的参数方程写成矩阵形式 (见 p80 的( 3-21 )) 由已知条件可求得 hermite 矩阵(见 p81 (3-26)) 由此可见 ,hermite 曲线是由给定曲线的两个端点 p 0 、 p 1 , 以及两端点 处的切线矢量 R 0 、 R 1 来描述的。即: Q(t)=T.M h .G 2. 调和函数 : F h (t)=T.M 则得出: 调和函数性质: ( 1) 仅与参数 t 有关,与初始条件无关 ( 2 )对于空间三个坐标值 (x,y,z) 是相同 ( 3 )在参数域边界,调和函数各分量仅一个起作用。 因此,不难看出 Hermite 曲线的调和函数是随参数 t 而变化的曲线。见图 3-18 73. 3 Hermite 曲线的切线矢量 由 显然对于单位矢量有 : 由此 , 决定 Hermite 曲线,需要知道切线的方向和切线长度。同时也可以看出:对于曲线的端点及端点的切线方向是一定,若给定不同的切线长度,将有不同形状的 Hermite 曲线。 因此,人们利用这一性质调整 k 0 和 K 1 实现曲线的交互设计。 74. 三、三次参数样条曲线 1. 基本思想:是将一阻离散的点列 p1,p2,…,pn ,应用分段的 Heimit 曲线构成光滑的曲线。如图所示。 H1 H2 连接点 显然,两端曲线光滑连接的必要条件是:曲线的连接处满足 连续。 即: 已知 Heimit 曲线的方程 : 其中 : 75. 1 、 Hermite 曲线的二阶导数 a. 一阶导数: Q’(t)=F’ h1 (t)p i + F’ h2 (t)p i+1 + F’ h3 (t)p’ i F’ h4 (t)p’ i+1 F’ h1 (t)=6*t*t-6*t F’ h2 (t)=-6*t*t+6*t F’ h2 (t)=3*t*t-4*t+1 F’ h2 (t)=3*t*t-2*t b. 二阶导数 : Q’’(t)=F’’ h1 (t)p i + F’’ h2 (t)p i+1 + F’’ h3 (t)p’ i F’’ h4 (t)p’ i+1 F’’ h1 (t)=12*t-6 F’’ h2 (t)=-12*t+6 F’’ h2 (t)=6*t-4 F’’ h2 (t)=6*t-2 76. 令: t=0 则 Q’’(0)=-6p 0 +6p 1 -4p 0 ’-2p 1 ’ Q’’(1)=6p 0 -6p 1 +2p 0 ’+4p 1 ’ 求解方程组得: p 0 ’=-p 0 +p 1 -1/6*(2p 0 ’’+p 1 ’’) p 1 ’=-p 0 +p 1 -1/6*(p 0 ’’+2p 1 ’’) 将上式代入( 3-29 ) 则得出 p85(3-34) 我们称 (3-34) 式为三次参数方程。 2. 连续的三次参数样条曲线。设有一组离散得点列 p1,p2,……,p n 依据 的连续条件,如取曲线 H i 和曲线 H i+1 , 在 p 处的二阶导数左右相等。即: H1 H2 p1 p2 p3 p4 pn 77. 在 p 处的左导数: Q i+1 ’’(t=0)=-6p i+1 +6p i+2 -4p’ i+1 -2p’ i+2 在 p 处的右导数: Q i ’’(t=1)=6p i -6p i+1 +2p’ I +4p’ i+1 因为 Q i+1 ’’(t=0)= Q i ’’(t=1) 所以 -6p i+1 +6p i+2 -4p’ i+1 -2p’ i+2 = 6p i -6p i+1 +2p’ I +4p’ i+1 整理 p i ’+4p’ i+1 +p’ i+2 =3(p i+2 -p i ) 称为切矢连续方程 3 、边界条件 ( 1 )自由端 p’’ 1 =p’’ n 将 t=0 或 t=1 得: -4p’ 1 -2p’ 2 =6p 2 -6p 1 即 2p’ 1 +p 2 =3(p 2 -p 1 ) p’ n-1 +2p’ n =3(p n -p n-1 ) ( 2 )夹持端 (3) 抛物线 p’’ 1 =p’’ 2 和 p’’ n-1 =p’’ n 78. 4. 三次参数样条曲线的程序设计 输入一组离散点列 p 1 , p 2 , p 3 , ……..pn 和 N 、 M 建立 A[][] 矩阵 I=1,2,…..n-3 选择边界条件 1 2 3 A[0][0]=2 A[0][0]=1 A[0][0]=1 A[0][1]=1 A[0][1]=1 C[0]=3(P 1 -P 0 ) C[0]=K1e 1 C[0]=2(P 1 -P 0 ) C[n-1]=3(P n-1 -P n-2 ) C[n-1]=K1e 1 C[0]=2(P n-1 -P n-2 ) A[n-1][n-2]=1 A[n-1][n-1]=1 A[n-1][n-2]=1 A[n-1][n-1]=2 A[n-1][n-1]=1 for (I=1;I<n-2;I++) C[I]=3*(p i+1 -p i ) 调用追赶法求解方程组,求得 p’[] for(I=0;I<n-1;I++) p’’ 1 =-6p i +6p i+1 -4p’[I]-2p’[I+1] p’’ 2 =6p i -6p i+1 +2p’[I]+4p’[I+1] 79. for(j=0;j<M;j++) t=j*dt t1=t*t; t2=t1*t; x=(1-t)p ix +tp i+1x +1/6(-t2+3t1-2t)p’’ ix +1/6(t2-t)p’’ I+1x y=(1-t)p iy +tp i+1y +1/6(-t2+3t1-2t)p’’ iy +1/6(t2-t)p’’ I+1y j==0 N Y LineTo(x,y) MoveTo(x,y) 绘制坐标系 80. 3.5 Bezier 曲线 1.Bezier 曲线的数学表达式 2. 三次 Bezier 曲线的数学表达式 当 n=3 时为三次多项式,则控制点为四个点 。由此有: 81. 写成矩阵得: 写成代数方程: x(t)= B 0,3 (t)x0+B 1,3 (t)x1+B 2,3 (t)x2+B 3,3 (t)x3 y(t)= B 0,3 (t)y0+B 1,3 (t)y1+B 2,3 (t)y2+B 3,3 (t)y3 82. 3.Berier 曲线的画图程序设计 输入给定点的坐标数据 p 1 ,p 2 ,……,p n-1 ,p n 和 N , M dt=1.0/(m+1); j=0; j<=m; j++ t=j*dt; t1=t*t; t2=t1*t; f1=1-3t+3*t1-t2; f2=3t-6t1+3t2; f3=3t1-3t2; f4=t2; x=f1*a[I][0]+f2*a[I+1][0]+f3*a[I+2][0]+f4*a[I+3][0] 84. 3.6 B 样条曲线 1.B 样条的数学表达式: l=0,1,…….,n n 为阶次数 2. 三次 B 样条的曲线方程 87. 输入控制点的数据: p 1 ,p 2 ,……..,p n ->a[n][2] 和 dt 求等分数 M=(int)(1.0/dt)+1 for I=0;I<N-3;I++ for(j=0;j<M;J++ t=j*dt; t1=t*t; t2=t1*t; A0=1.0/6*(-t2+3*t1-3*t+1) A1=1.0/6*(3*t2-6*t1+4) A2=1.0/6*(-3*t2+3*t1+3*t+1) A3=1.0/6*t2 x=A0*a[I][0]+A1*a[I+1][0]+A2*a[I+2][0]+A3*a[I+3][0] y=A0*a[I][1]+A1*a[I+1][1]+A2*a[I+2][1]+A3*a[I+3][1] N j==0 Y LineTo(x,y) MoveTo(x,y) 89. 4. B 样条曲线的性质 ( 1 )端点性质与连续性: 当 t=0 或 t=1 分别代入 Q(t) 方程得: Q(0)=1/3*((p i +p i+2 )/2+2p i+1 ) Q(1)=1/3*((p i+1 +p i+3 )/2+2p i+2 ) 三次 B 样条在连接处的一阶导数、二阶导数都是连续的。 ( 2 )局部性:改变一个控制点的位置,顶多影响四个曲线段。由此三次 B 样条具有改变控制点的位置就可以对 B 样条局部修改曲线。 ( 3 )扩展性 90. 4.4 区域填充 4.4.1 基本概念 1. 区域的定义: 1 )内定义区域:区域内部具同一种颜色 , 外部有一种颜色。 2 )边界定义区域:边界具有一种特定颜色,边界内具有不是新值(填 充色)。 3 )四连通区域:各像素在水平和垂直四个方向是连通的。 4 )八连通区域:各像素在八个方向是连通的。 2. 种子 填充算法 ( 1 )简单的种子 填充算法 a. 漫水法 : 是一种内定义区域填充算法 , 基本思想是首先在区域呢测试点 (x,y) 的像素值,判断是否是原始色,如果是则改变为新值。依次向四个方向或把各方向上扩展。 91. b. 边界 填充算法 1) 基本思想 : 与 漫水法 基本思想一致 , 不同的事 , 在则试 (x,y) 点的像素时 , 除该点与新值比较外 , 还需要判断该点是否是边界颜色值 . 2) 基本流程: (1 )种子像素要入栈 (2) 当堆栈非空时 。从对栈中推出一个像素,并将该像素置成所要的值 对于每个与当前像素邻接的四连通或八连通进行测试。若所测试的像素在区域内又无填充过,则将该像素入栈。 c. 扫描线种子 填充算法 种子点 种子点 92. 二、 扫描线转换填充算法 ( 1 )射线法 由图不难发现, a 射线与多边形的交点为两个,是偶数。 b 、 c 射线与多边形的交点为三个,是奇数。 依据该规律射线与多边形的交点数是偶数,则该点在区域外。射线与多边形的交点数是奇数,则该点在区域内。 d 射线与多边形的交点为两个,是偶数 , 但该点在区域内。对这种特殊情况,可以采用“左闭右开”或“上闭下开”的原则处理。 射线 a b c d 93. ( 2 )弧长法 1. 对于 a 点: 构成的三角形的角度的和等于 0 2. 对于 b 点: 构成的三角形的角度的和等于 360 a b A B C D E ( 3) 扫描线算法 1. 基本思想:利用一条扫描线上的像素存在着连贯性这一特征(区域连贯性、扫描线连贯性和多边形边的连贯性)。避免对像素逐点判断和反复求交的运算。 1 )区域连贯性:对于相邻的两个区域必有一 内部区 外部区 区域是在多边形内部,另一个区域在多边形外部。 扫描线 94. 2 )扫描线的连贯性:若扫描线 y=j 与多边形 p 相交,则交点数是偶数。且在扫描线上,只有区段 位于多边形 p 内。 3 )边的连贯性:若多边形 p 的边与扫描线 y=k1 至 y=k2 都相交,则边有下面关系: 扫描线 y=8 X i x i+1 m=(Y 2 -Y 1 )/(X 2 -X 1 ) 为多边形边的线段的斜率。 95. 2. 算法步骤: ( 1 )求交点 ( 2 )排序 ( 3 )填充 3. 扫描线的数据结构 ( 1 )边表( ET 表(见图 3-39 ) ET 表的基本元素: y: 边的上端点的 y 坐标。 x: 边的下端点的 x 坐标。 ( 2 )有效边表( AET 表):是纪录当前与扫描线相交的所有边(见图 3-40 )。 y: 边的上端点的 y 坐标。 x: 是表示边与扫描线的交点的 x 坐标。 AET 指针 Y x 1/m Y x 1/m 96. 按扫描线建立 ET 表 初始化 AET 表 ET 和 AET 空? 把 ET 中 y 的信息与 AET 合并 对 AET 表中按 x 排序 取一点对作像素填充 Y>ymax ? 删去 y>ymax 项的点对 X=x+1/m 扫描线完? Y=y+1 结束 4. 程序设计流程图 97. 第四章 图形的裁剪及几何变换 4.1.1 窗口视图变换 (1) 几个概念 用户域:是指用户定义设计对象的实数域,理论上用户域是连续的、无限的。 窗口区:是指用户对用户域感兴趣的一个矩形区域,其坐标系为用户坐标系(如空间直角坐标系)。 视图区:是指图形在屏幕上显示的矩形区域,坐标系设备坐标系。 ( 2 )窗口区和视图区之间的坐标变换 窗口区 视图区 Wxs,wys Wxe,wye Vxs,Vys Vxe,Vye (wx,wy) (Vx,Vy) 坐标映射 99. 4.2 二维图形的裁剪 4.2.1 二维线段的裁剪 1 矢量裁剪法 ( 1 ) . 基本思想:先从直线的一端为始点进行判断或进行求交运算,所得交点坐标 (xs,ys) ,然后把直线另一端作为始点,再进行判断或进行求交运算,所得交点坐标 (xe,ye), 连接端点 (xs,ys) 和 (xe,ye) 之间的直线。 ( 2 )算法实现步骤分析 1) 直线段在窗口外。即: XL XR YT YB 100. 2 直线参数求交法 设: L1: L2: 2) 直线完全在窗口内,即: 3 )直线段与窗口边相交。该类又可分为下面情况: ① 当 ② ③ 102. 3) 求直线端点编码程序设计 char __fastcall TForm1::Getflag(int xa,intya,int xb, int yb,char &c1,char &c2) { char c; c1=0; c2=0; if(xa<XL) c1=c1|1; else if(xa>XR) c1=c1|2; if(xb<XL) c2=c2|1; else if(xb>XR) c2=c2|2; if(ya<YB) c1=c1|4; else if(ya>YT) c1=c1|8; if(yb<YB) c2=c2|4; else if(yb>YT) c2=c2|8; c=c1&c2; Return c; } 103. 4) 直线与窗口边求交程序设计 void __fastcall TForm1::GetCrossing(int xa, int ya, int xb, int yb, char c, int & x, int & y) { char top=8,bottom=4,right=2,left=1; if ((c&bottom)==bottom) { x=xa-(ya-YB)*(xa-xb)/(ya-yb); y=YB; } else if ((c&top)==top){ x=xa-(ya-YT)*(xa-xb)/(ya-yb); y=YT; } else if( (c&left)==left) { x=XL; y=ya-(xa-XL)*(ya-yb)/(xa-xb); } else if((c&right)==right) { x=XR; y=ya-(xa-XR)*(ya-yb)/(xa-xb); }} 104. 5 ) . 编码裁剪法的主调程序设计流程图 初始化: done=0,accept=0 求线段两端点的编码 C1 、 C2 C1&C2=0? done=1; C1&&C2=0? accept=1;done=1; C1=0? 交换线段的端点 p1←p2 P1 在窗口的部位 dnoe: 是否作细分, 0 :作细分 1 :不作细分 accept: 是否接受 0 :不接受 1 :接受 106. 6 ) . 编码裁剪法的主调程序设计 void __fastcall TForm1::Button1Click(TObject *Sender) { int xa,ya,xb,yb,x,y,k; int done=0,accept=0; char c,c1,c2,cc; XL=10; XR=160; YB=20; YT=170; // 窗口区 Image1->Canvas->Rectangle(0,0,Image1->Width,Image1->Height); Image1->Canvas->Rectangle(XL,YB,XR,YT); Image2->Canvas->Rectangle(XL,YB,XR,YT); xa=StrToInt(Xa->Text); ya=StrToInt(Ya->Text); xb=StrToInt(Xb->Text); 109. 3. 中点分割裁剪法 ( 1 ) 基本思想: 当一条线断不能直接接受也不能直接 舍 弃时,则将其线段拆成一半,再并分别测试线段,通过二分法搜索方式直至线段被接受(如图所式)。 p1 p2 pm1 pm2 pm3 ( 2) 中点分割裁剪法优点 计算速度快 , 因为算法简单,算法只需作位移计算。 110. 中点裁剪算法的程序设计 ( 1 )程序设计的关键步骤分析 1 )直线段数据输入方式 文件输入方式,鼠标交互输入方式 2 )直线段的存储结构 线性表结构(链表存储结构,栈存储结构) 定义直线段的类结构: class Line { public: TPoint p0; TPoint p1; void drawLine(TImage *Image){ Image->Canvas->MoveTo(p0.x,p0.y); Image->Canvas->LineTo(p1.x,p1.y); } } ; 3 )中点裁剪算法 111. while(lines->Count>0) { pline=(Line*) lines->Last(); // 从栈中取出一条直线段 GetFlag(pline->p0,pline->p1,c1,c2); // 求直线段的端点编码 c=c1 & c2; if( c==0) { if(c1==0 && c2==0) { // 直线段在窗口内 pline->drawLine(Image2); lines->Delete(lines->Count-1); } else { // 直线段与窗口边有交点 dx=pline->p1.x-pline->p0.x; dy=pline->p1.y-pline->p0.y; s=sqrt(dx*dx+dy*dy); // 计算直线段的距离 112. if(s>1){ // 直线段可以再分 xm=pline->p0.x+((dx)>>1); ym=pline->p0.y+((dy)>>1); cline1=new Line(); cline2=new Line(); cline1->p0.x=pline->p0.x ; cline1->p0.y=pline->p0.y; cline1->p1.x=xm; cline1->p1.y=ym; cline2->p0.x=xm; cline2->p0.y=ym; cline2->p1.x=pline->p1.x; cline2->p1.y=pline->p1.y; lines->Delete(lines->Count-1); lines->Add(cline1); lines->Add(cline2); }else lines->Delete(lines->Count-1); // 直线段长度小于 1 从栈删除 } } else lines->Delete(lines->Count-1); // 从栈删除窗口外的直线段 } 114. 4. 多边形裁剪算法 1 . 多边形裁剪的原则 :多边形裁剪结果仍是一个或多个多边形。 2. 多边形裁剪重点考虑问题 :把多边形落在窗口边界上的交点正确地按序连接成裁剪后的多边形(其中包括决定窗口边界及拐角的取舍)。 3 逐边裁剪法 (1) 基本思想: 每次用窗口的一条变界对要裁剪的多边形进行裁剪,把落在窗口外部区域的图形去掉,保留窗口内部区域的图形,并把它作为下一次待裁剪的多边形。 (2) 算法实现步骤: 1). 指定多边形的顶点序列 P 2). 取一条窗口裁剪多边形的边 e 依次检查顶点序列种的每个顶点 p[I] 。并由下面的判断: a. 处于 e 边可见侧被列入新产生的顶点序列 Q[j] 中。 115. b. 处于 e 边不可见侧的顶点则被删除。 ( 3 )检查 p[I] 与 p[i+1] 是否处于同则,若不是则求交点,求得的交点列入新多边形顶点序列 Q[j] 中。如图所示: A B C D E F H 可见则 不可见则 取裁剪窗口的右边界为裁剪边 , 则有新顶点序列由红色的点组成。 依次再用底边、左边和顶边分别对作裁剪。最终获得裁剪后的多边形。 116. ( 3 )逐边裁剪法程序设计 1 )程序设计流程图 输入 j 点 p P 是第一点? sp 与 e 边相交? s←p 计算交点并输出至 Q 中 s←p S 处于 e 的可见侧? 输出 s 到 Q 中 j<m? I++ sp 与 e 相交 计算交点并 输出到 Q 中 窗口边 e 循环 i i<4? 绘制新的多边形 结束 p← 第一点 i++ y N y y y y y N N N N 117. (4) 判断点 p 的可见性的程序代码 bool __fastcall TForm1::GetViewFlag(int x, int y, int i) { bool flag; flag=0; switch (i) { case 0: if(x>=XL) flag=1; break; case 1: if(y>=YB) flag=1;break; case 2: if(x<=XR) flag=1; break; case 3: if(y<=YT) flag=1; break; } return flag; } 118. (5) 计算直线 sp 与窗口边的交点坐标 (x,y) 的程序代码: int __fastcall TForm1::GetCrossing(int x1, int y1, int x2, int y2, int i, int & x, int & y) { int c,a; c=0; x=0;y=0; switch(i){ case 0: a=(XL-x1)*(XL-x2); if(a<0) { x=XL; y=y1-(x1-XL)*(y1-y2)/(x1-x2); c=1; } else c=0; break; case 1: a=(YB-y1)*(YB-y2) if(a<0) { x=x1-(y1-YB)*(x1-x2)/(y1-y2); y=YB; c=1;} else c=0; break; case 2: a=(XR-x1)*(XR-x2); 119. if(a<0) { x=XR; y=y1-(x1-XR)*(y1-y2)/(x1-x2); c=1; } else c=0; break; case 3: a=(YT-y1)*(YT-y2); if(a<0) { x=x1-(y1-YT)*(x1-x2)/(y1-y2); y=YT; c=1; } else c=0; break; } return c; } ( 6 )逐边裁剪法主调程序代码: void __fastcall TForm1::Button1Click(TObject *Sender) { int p[20][2]={5,25,100,5,120,45,250,50,230,240,180,260,150,200,60,150}; int q[20][2],i,j,k,x1,x2,y1,y2,c,x,y,m; bool c1; TPoint p1[20]; 120. m=8; StringGrid1->Cells[0][0]=“ 序号 "; StringGrid1->Cells[1][0]="X"; StringGrid1->Cells[2][0]="Y"; for(i=0;i<8;i++){ StringGrid1->Cells[0][i+1]=IntToStr(i); StringGrid1->Cells[1][i+1]=IntToStr(p[i][0]); StringGrid1->Cells[2][i+1]=IntToStr(p[i][1]); p1[i]=TPoint(p[i][0],p[i][1]); } Image1->Canvas->Polygon(p1,7); Image1->Canvas->Brush->Style=bsClear; XL=20; YB=20; XR=220; YT=220; Image1->Canvas->Rectangle(XL,YB,XR,YT); 121. for(i=0;i<4;i++) { k=0; for(j=0;j<m;j++) { x2=p[j][0]; y2=p[j][1]; if(j!=0) { c=GetCrossing(x1,y1,x2,y2,i,x,y); if(c==1){ q[k][0]=x; q[k][1]=y; k++;} } x1=x2; y1=y2; c1=GetViewFlag(x2,y2,i); if(c1) { q[k][0]=x1; q[k][1]=y1; k++; } } x2=p[0][0]; y2=p[0][1]; c=GetCrossing(x1,y1,x2,y2,i,x,y); if(c==1) { q[k][0]=x; q[k][1]=y; k++; } 122. m=k; for(j=0;j<m;j++) { p[j][0]=q[j][0]; p[j][1]=q[j][1]; } } Image2->Canvas->Rectangle(XL,YB,XR,YT); Image2->Canvas->Brush->Style=0; Image2->Canvas->Brush->Color=RGB(255,0,0); StringGrid2->RowCount=m+1; StringGrid2->Cells[0][0]=“ 序号 "; StringGrid2->Cells[1][0]="X"; StringGrid2->Cells[2][0]="Y"; 125. 5. 双边裁剪法 主多边形 ps 裁剪多边形 pc 进入点 前交点 A 算法的基本思想与步骤 : 设用户的多边型为主多边形 ps ,而裁剪窗口为裁剪多边形 pc. 算法首先沿 ps 任一点出发,各跟踪检测 ps 的每一条边,当 ps 与 pc 的有效边相交时: ( 1 )若 ps 的边进入 pc ,则继续沿 ps 的边往下处理,并输出该线段。 ( 2 )若 ps 的边是从 pc 中出来,则从该点(称为前交点)开始,沿着窗口边向右检测 pc 的边,找到 ps 与 pc 最靠近的前交点的新交点。同时输出由前交点到新交点的线段。 ( 3 )返回到前交点,继续( 1 ) ~ (3) 的步骤。