实 验 报 告
课程名称:计算机图形学
学 院:信息科学与工程学院
专 业:数字媒体技术班 级:20##级
姓 名:李义学 号:201301052015
20##年12月19日
山东科技大学教务处制
目录:
实验一:环境设置
…………………………………………… 3
实验二:直线的生成算法
……………………………………………4
实验三:圆的生成算法
……………………………………………6
实验四:扫描填充算法
……………………………………………7
实验五:裁剪算法
……………………………………………11
实验六:Bezier曲线
……………………………………………14
实验一:环境设置
一、实验目的和要求:
1、了解和使用TurboC提供的基本图形函数
2、像素点的生成
3、掌握Turbo C 进行图形程序设计的基本方法;.
4熟悉Visual C++实验环境
二、实验内容:
1.Turbo C集程序编辑、编译、连接、调试为一体具有速度快、效率高、功能强等优点。Turbo C是基于DOS平台的C编译系统,占用系统资源少,提供的界面直观、易用。程序的编译、连接、调试、运行、环境设置等工作都在同一界面上进行,从而用户使用非常方便。用户在使用Turbo C之前,必须将Turbo C系统安装在用户的磁盘上,建立一个Turbo C的使用环境。运行Turbo C有两种方式:1)由DOS平台进入Turbo C,2) 由Windows平台进入Turbo C.
一个新建立的文件,也可以是一个已存在的文件。编辑好源文件存盘后,下面的工作就是对源文件(可以是单个文件,也可是多个文件)进行编译、连接和运行。在Turbo C中,对源程序进行编译、连接和运行可以分三步单独进行,也可以将编译和连接同时进行,然后运行;或者将编译、连接和运行一次完成。
说明:
(1)在操作完成后应选Project下的Clear Project,清空当前有效的项目文件(用Project name 指定的),否则系统在以后的编译中仍编译该项目文件,而不是当前编辑屏幕中的源程序。
(2)多文件编译中因出错停止编译的方式用户需要用Project的子菜单Break make on进行设置(图 14)。
Errors 表示编译完一个文件后,有“错误”信息就停止编译。
Warnings 表示编译完一个文件后,有“警告”以上信息就停止编译。
Fatal errors 表示编译完所有文件后,停止编译。
Link 进行连接之前停止编译。
图 14
当程序在编译、连接中没有错误,但运行结果不正确的情况下,我们通常用以下两种方法调试程序。1)跟踪进入方法,2) 设置断点方法。设置Turbo C 环境参数是指出包含文件和库文件存放的位置以及输出文件(.OBJ和.EXE)准备存放的位置。设置Turbo C 环境参数通过主菜单Options中的选项完成,如图21。
图 21
2 熟悉Visual C++实验环境
(1)启动Developer Studio,了解初始化界面由哪些部分组成
查看各菜单项,看看都有哪些子菜单和命令
(2) 将鼠标放置于各工具条图标上,系统会自动显示该图标代表的命令含义,了解一下都有哪些命令。
(3) 在任意工具条上单击鼠标右键,弹出式菜单上将显示所有可用的工具条,选择其中没有对号(√)的项,看看有什么效果,再选择有对号的项看有什么效果。
(4) 将鼠标移动到任意工具条上,将鼠标放到图标间隙,按下鼠标左键不放,移动鼠标到屏幕中间,看有什么现象发生,再将它拖回到原来位置观察有什么现象发生。
(5) 将鼠标移动到下边的输出窗口,按鼠标右键,弹出一个菜单,选择其中的菜单项”Hide”,选择菜单View|Output,重新显示该窗口,看窗口是不是又显示出来了。
(6) 学习使用帮助系统。在Visual C++5.0中,在工作区窗口的InfoView中选择内容,双击它,在文档区显示具体的帮助信息。在Visual C++6.0,选择菜单Help|Contents,启动MSDN联机帮助系统,学习使用该帮助系统。
选File|Exit退出Developer Studio。
(2)像素是组成图形的最小单位,像素的大小可以通过设置不同的显示方式来改变。该实验实现了简单圆的生成
(3)基于Turbo C的开发环境实现人造卫星运动动画。
实验二:直线的生成算法
一、实验目的
1、通过用中点画线法绘制直线,加深对算法原理的理解。
2、对程序以及函数的细节有更深入的把握。
二、实验要求
实现一个直线的中点生成算法的程序
三、实验原理
当前像素点为(xp,yp),下一个像素点有两个可选择点P1(xp+1,yp+1)或P2(xp+1,yp+1)。若M=(xp+1,yp+0.5)为P1与P2之中点,Q为理想直线 x=xp+1垂线的交点。当M在Q的下方时,则P2 应为下一个象素点;M在Q的上方,应取P1为下一点。
构造判别式:
d=F(M)=F(xp+1,yp+0.5) =a(xp+1)+b(yp+0.5)+c
其中a=y0-y1, b=x1-x0, c=x0y1-x1y0。
当d<0,M在L(Q点)下方,取右上方P2为下一个象素;
当d>0,M在L(Q点)上方,取右方P1为下一个象素;
当d=0,选P1或P2均可, 约定取P1为下一个象素;
若当前象素处于d>=0情况,则取正右方象素P1 (xp+1, yp), 要判下一个象素位置,应计算
d1 = F(xp+2, yp+0.5)=a(xp+2)+b(yp+0.5)=d+a; 增量为a
若d<0时,则取右上方象素P2 (xp+1, yp+1)。要判断再下一象素,则要计算
d2 = F(xp+2, yp+1.5)=a(xp+2)+b(yp+1.5)+c=d+a+b ;增量为a+b
1、画线从(x0, y0)开始,d的初值
d0 = F(x0+1, y0+0.5)=F(x0, y0)+a+0.5b =a+0.5b。
可以用2d代替d来摆脱小数,提高效率。
四、主要代码
void Midpoint_Line(CDC *pdc,int x0,int y0,int x1,int y1,int color)
{
int a,b,d,d1,d2,x,y;
a=y0-y1,b=x1-x0,d=2*a+b;
d1=2*a,d2=2*(a+b);
x=x0,y=y0;
pdc->SetPixel(x,y,color);
while(x<x1)
{
if(d<0)
{
x++;
y++;
d+=d1;
}
else
{
x++;
d+=d2;
}
pdc->SetPixel(x,y,color);
}
}
void CMy2View::On1()
{
// TODO: Add your command handler code here
int x0,x1,y0,y1,color;
x0=111;
y0=111;
x1=138;
y1=138;
color=RGB(255,0,0);
CClientDC dc(this);
Midpoint_Line(&dc,x0,y0,x1,y1,color);
}
五、实验心得
写代码时一定要细心,不能漏写、写错,另外,要注意分号的使用。
实验三:圆的生成算法
一、实验目的
1、通过用中点画圆法绘制圆,加深对算法原理的理解。
2、对程序以及函数的细节有更深入的把握。
二、实验要求
实现一个圆的中点生成算法的程序
三、实验原理
如果我们构造函数 F(x,y)=x2+y2-R2,则对于圆上的点有F(x,y)=0,对于圆外的点有F(x,y)>0,对于圆内的点F(x,y)<0 。与中点画线法一样,构造判别式:
d=F(M)=F(xp+1,yp-0.5)=(xp+1)2+(yp-0.5)2-R2
若 d<0,则应取P1为下一象素,而且再下一象素的判别式为:
d=F(xp+2,yp-0.5)=(xp+2)2+(yp-0.5)2-R2=d+2xp+3
若d≥0,则应取P2为下一象素,而且下一象素的判别式为
d=F(xp+2,yp-1.5)=(xp+2)2+(yp-1.5)2-R2=d+2(xp-yp)+5
我们这里讨论的第一个象素是(0,R),判别式d的初始值为:
d0=F(1,R-0.5)=1.25-R
四、主要代码
void CDrawCircleView::OnMidPoint()
{
CDC *pDC=GetDC();
int xc=200,yc=300,r=50,c=0;
int x,y; float d;
x=0;y=r;d=1.25-r;
pDC->SetPixel((xc+x),(yc+y),c);
pDC->SetPixel((xc-x),(yc+y),c);
pDC->SetPixel((xc+x),(yc-y),c);
pDC->SetPixel((xc-x),(yc-y),c);
pDC->SetPixel((xc+y),(yc+x),c);
pDC->SetPixel((xc-y),(yc+x),c);
pDC->SetPixel((xc+y),(yc-x),c);
pDC->SetPixel((xc-y),(yc-x),c);
while(x<=y)
{
if(d<0)
d+=2*x+3;
else
{
d+=2*(x-y)+5;
y--;
}
x++;
pDC->SetPixel((xc+x),(yc+y),c);
pDC->SetPixel((xc-x),(yc+y),c);
pDC->SetPixel((xc+x),(yc-y),c);
pDC->SetPixel((xc-x),(yc-y),c);
pDC->SetPixel((xc+y),(yc+x),c);
pDC->SetPixel((xc-y),(yc+x),c);
pDC->SetPixel((xc+y),(yc-x),c);
pDC->SetPixel((xc-y),(yc-x),c);
Sleep(1);
}
ReleaseDC(pDC);
}
五、实验心得
在程序调试中有很多错误,最后发现是没有弄清函数的主调关系,最后查阅了资料,调试好了程序。
实验四:扫描填充算法
一、实验目的
1、掌握多边形扫描填充算法的思路
2、对程序以及函数的细节有更深入的把握。
二、实验要求
用VC编程实现扫描线填充算法
三、实验原理
对于给定的一个多变形,用一组水平的扫描线进行扫描,对每一条扫描线均可求出与多边形边的交点,这些交点将扫描线分割成落在多边形内部的线段和落在多边形外部的线段,并且两者相间排列,将落在多边形内部的线段上的所有像素点赋以给定的填充色彩。这样不需要检验每一个像素点,而只考虑与多边形相交的交点分割后的扫描线段。
首先将有效多边形边界存储在一个有序边表中:有序边表每一个元素对应一条扫描线并按照扫描线从低到高排列;多边形的每条边,按照较低端点的y1值,存储在边表y1扫描线对应元素指向的链表中,链表中的节点记录了边的高端y2值、低端的x1值和1/m,这样就可以知道扫描线有没有和这条边相交(是否在y1和y2之间),初始交点(y1,x1),中间交点(y1+k,x1+k/m)。另外,所谓有效边是指去掉水平边、和为了解决交点计数问题而缩短了的多边形边。
然后从多边形底部到顶部处理扫描线,对每条当前的扫描线,建立活化边表(和当前扫描线有交点的多边形边):活化边表可以是一个链表,其中的节点按照和扫描线交点从左到右排列。初始的活化边表可以直接从有序边表的对应扫描线链表中生成(需要排序),以后每次扫描线向上移动可以经过以下几个步骤更新活化边表:
(1) 检查活化边表每个节点的上端y值,如果小于扫描线的y值,说明这条边已经扫描完毕,应该从活化边表中删除;否则,更新节点对应边和扫描线交点的x坐标值,这只要执行x+=1/m就可以了。
(2) 加入新和扫描线相交的多边形边,即把有序边表中和新扫描线对应的链表中的节点加入到活化边表中来。
(3) 根据节点的x值重新排序活化边表中的节点。
最后根据扫描线连贯性,填充扫描线在多边形内线段的像素,即根据活化边表中节点的顺序和x值,确定需要染色的点。
四、主要代码
void CFillView::Bubble(int n){
for(int i = 0; i < n-1; i++)
for(int j = 0; j < n-1-i; j++){
if(((BubblePoints[j].py==BubblePoints[j+1].py)&&(BubblePoints[j].px > BubblePoints[j+1].px))||(BubblePoints[j].py > BubblePoints[j+1].py)){
Point tmp = BubblePoints[j];
BubblePoints[j] = BubblePoints[j+1];
BubblePoints[j+1] = tmp;
}
}
}
int CFillView::Table(int n){
int i,j,ym,l,r;
ym = BubblePoints[0].py;
int k = 0,g = 0;
for(i = 0; i < n; i++){
if(BubblePoints[i].py != ym){
DDD[k] = g;
k++;
g = 0;
}
if(BubblePoints[i].id == 0){
l = n - 1;
r = 1;
}
else if(BubblePoints[i].id == n-1){
l = n - 2;
r = 0;
}
else{
l = BubblePoints[i].id - 1;
r = BubblePoints[i].id + 1;
}
if(inputPoints[l].py > BubblePoints[i].py){
Array[k][g].dx = (double)(inputPoints[l].px - BubblePoints[i].px) / (inputPoints[l].py - BubblePoints[i].py);
Array[k][g].x_ = BubblePoints[i].px;
Array[k][g].ymax = inputPoints[l].py;
Array[k][g].ypre = BubblePoints[i].py;
g++;
}
if(inputPoints[r].py > BubblePoints[i].py){
Array[k][g].dx = (double)(inputPoints[r].px - BubblePoints[i].px) (inputPoints[r].py - BubblePoints[i].py);
Array[k][g].x_ = BubblePoints[i].px;
Array[k][g].ymax = inputPoints[r].py;
Array[k][g].ypre = BubblePoints[i].py;
g++;
}
}
return k;
}
void CFillView::bubble(int n,double arr[10]){
for(int i = 0; i < n-1; i++)
for(int j = 0; j < n-1-i;j++)
if(arr[j] > arr[j+1]){
double tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
void CFillView::OnDraw(CDC* pDC)
{
CFillDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
int n = 7;
double arr[10];
Input(n);
Bubble(n);
int a = Table(n);
int i,j,k,p = 0;
int ymin = BubblePoints[0].py;
int ymax = BubblePoints[n-1].py;
for(i = ymin; i < ymax; i++){
p = 0;
for(j = 0; j < a ; j++){
for(k = 0; k < DDD[j]; k++){
if(i > Array[j][k].ypre && i < Array[j][k].ymax){
Array[j][k].x_ += Array[j][k].dx;
arr[p++] = Array[j][k].x_ ;
}
}
}
bubble(p,arr);
bool canDraw = true;
k = 0;
int j = (int)arr[k];
while(j<arr[p-1]){
if(canDraw){
pDC->SetPixel(j++,i,RGB(255,0,0));
if(j > arr[k+1]){
canDraw = !canDraw;
k++;
}
}
else{
j++;
if(j > arr[k+1]){
canDraw = !canDraw;
k++;
}
}
}
}
}
五、实验心得
在算法实现过程中遇到了一些困难,有些问题考虑的不太全面,花费了很时间去调试,另一个就是在细节方面处理得不够好。
实验五:裁剪算法
一、实验目的
1、掌握Cohen-Sutherland裁剪算法与中点分割裁剪算法
2、对程序以及函数的细节有更深入的把握。
二、实验要求
用VC编程实现Cohen-Sutherland或中点分割裁剪算法
三、实验原理
1、Cohen-Sutherland裁剪算法
对于每条线段P1P2分为三种情况处理:
(1)若P1P2完全在窗口内,则显示该线段P1P2。
(2)若P1P2明显在窗口外,则丢弃该线段。
(3)若线段不满足(1)或(2)的条件,则在交点处把线段分为两段。其中一段完全在窗口外,可弃之。然后对另一段重复上述处理。
为快速判断,采用如下编码方法:
将窗口边线两边沿长,得到九个区域,每一个区域都用一个四位二进制数标识,直线的端点都按其所处区域赋予相应的区域码,用来标识出端点相对于裁剪矩形边界的位置。
将区域码的各位从右到左编号,则坐标区
域与各位的关系为:
上 下 右 左 X X X X
任何位赋值为1,代表端点落在相应的位置上,否则该位为0。若端点在剪取矩形内,区域码为0000。如果端点落在矩形的左下角,则区域码为0101。
若P1P2完全在窗口内code1=0,且code2=0,则“取”;若P1P2明显在窗口外code1&code2≠0,则“弃”; 在交点处把线段分为两段。其中一段完全在窗口外,可弃之。然后对另一段重复上述处理。
3、中点分割裁剪算法
从P0点出发找出离P0最近的可见点,和从P1点出发找出离P1最近的可见点。这两个可见点的连线就是原线段的可见部分。 与Cohen-Sutherland算法一样首先对线段端点进行编码,并把线段与窗口的关系分为三种情况,对前两种情况,进行一样的处理;对于第三种情况,用中点分割的方法求出线段与窗口的交点。A、B分别为距P0 、 P1最近的可见点,Pm为P0P1中点。
从P0出发找距离P0最近可见点采用中点分割方法
先求出P0P1的中点Pm,若P0Pm不是显然不可见的,并且P0P1在窗口中有可见部分,则距P0最近的可见点一定落在P0Pm上,所以用P0Pm代替P0P1;否则取PmP1代替P0P1。再对新的P0P1求中点Pm。重复上述过程,直到PmP1长度小于给定的控制常数为止,此时Pm收敛于交点。从P1出发找距离P1最近可见点采用上面类似方法。
四、主要代码
void Encode (int x,int y,int *code,int XL,int XR,int YB,int YT)
{
//请将此程序补充完整
int c=0;
if(x<XL) c=c|LEFT;
else if(x>XR) c=c|RIGHT;
if(y<YB) c=c|BOTTOM;
else if(y>YT) c=c|TOP;
(*code)=c;
}
//编码裁剪算法:
void C_S_Line(POINT &p1,POINT &p2,int XL,int XR,int YB,int YT)
{
//请将此程序补充完整
int x1,x2,y1,y2,x,y,code1,code2,code;
x1=p1.x; x2=p2.x; y1=p1.y; y2=p2.y;
Encode(x1,y1,&code1,XL,XR,YB,YT);
Encode(x2,y2,&code2,XL,XR,YB,YT);
while(code1!=0||code2!=0)
{
if((code1&code2)!=0) return;
code=code1;
if(code1==0) code=code2;
if((LEFT&code)!=0)
{x=XL;y=y1+(y2-y1)*(XL-x1)/(x2-x1);}
else if((RIGHT&code)!=0)
{x=XR;y=y1+(y2-y1)*(XR-x1)/(x2-x1);}
if((BOTTOM&code)!=0)
{y=YB;x=x1+(x2-x1)*(YB-y1)/(y2-y1);}
else if((TOP&code)!=0)
{y=YT;x=x1+(x2-x1)*(YT-y1)/(y2-y1);}
if(code==code1)
{x1=x;y1=y;Encode(x,y,&code1,XL,XR,YB,YT);}
else
{x2=x;y2=y;Encode(x,y,&code2,XL,XR,YB,YT);}
}
p1.x=x1;p1.y=y1;p2.x=x2;p2.y=y2;
}
int IsInArea(POINT point,int XL,int XR,int YB,int YT)
{
//请将此程序补充完整
if(point.x>=XL && point.x<=XR && point.y>YB && point.y<YT) return 1;
else return 0;
}
int NotIntersect(POINT begin,POINT end,int XL,int XR,int YB,int YT)
{
//请将此程序补充完整
int maxx,maxy,minx,miny;
maxx=(begin.x>end.x)?begin.x:end.x;
minx=(begin.x<end.x)?begin.x:end.x;
maxy=(begin.y>end.y)?begin.y:end.y;
miny=(begin.y<end.y)?begin.y:end.y;
if(maxx<XL|| minx>XR||maxy<YB||miny>YT) return 1;
else return 0;
}
//中点裁剪算法:
POINT ClipMid(POINT begin,POINT end,int XL,int XR,int YB,int YT)
{
//请将此程序补充完整
POINT mid,temp;
if(IsInArea(begin,XL,XR,YB,YT)) temp=begin;
else if(NotIntersect(begin,end,XL,XR,YB,YT)) temp=begin;
else
{
mid.x=(begin.x+end.x)/2;mid.y=(begin.y+end.y)/2;
if(abs(mid.x-end.x)<=1&& abs(mid.y-end.y)<=1) temp=mid;
else
{
if(NotIntersect(begin,mid,XL,XR,YB,YT))
temp=ClipMid(mid,end,XL,XR,YB,YT);
else
temp=ClipMid(begin,mid,XL,XR,YB,YT);
}
}
return temp;
}
五、实验心得
通过这次试验使我了解到如何运用计算机程序对窗口进行剪裁,了解到编码剪裁算法直观方便,速度较快,中点分割剪裁算法不用进行乘除运算,剪裁效率高。
实验六:Bezier曲线
一、实验目的
掌握Bezier曲线的求值、升阶算法及Bezier曲线绘制方法。
二、实验要求
用VC编程绘制Bezier曲线。
三、实验原理
Bezier曲线是一种广泛应用于外形设计的参数曲线,它通过对一些特定点的控制来控制曲线的形状,我们称这些点为控制顶点。现在我们来给出Bezier曲线的数学表达式。
给定空间n+1个点的位置矢量Pi(i=0,1,2,…,n),则Bezier曲线可定义为:
其中:Pi构成该Bezier曲线的特征多边形,Bi,n(t)是n次Bernstein基函数:
四、主要代码
void CBezierView::RatioPoint(double t, double x1, double y1, double x2, double y2, double &xx, double &yy){
xx = x1 + (x2 - x1) * t;
yy = y1 + (y2 - y1) * t;
void CBezierView::Init_Put(){
arr_Points[0].px = 100.0;
arr_Points[0].py = 100.0;
arr_Points[1].px = 200.0;
arr_Points[1].py = 200.0;
arr_Points[2].px = 300.0;
arr_Points[2].py = 200.0;
arr_Points[3].px = 400.0;
arr_Points[3].py = 500.0;
}
void CBezierView::OnDraw(CDC* pDC)
{
CBezierDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
//输入需要的点
Init_Put();
//画出多边形
pDC->MoveTo(arr_Points[0].px,arr_Points[0].py);
pDC->LineTo(arr_Points[1].px,arr_Points[1].py);
pDC->MoveTo(arr_Points[1].px,arr_Points[1].py);
pDC->LineTo(arr_Points[2].px,arr_Points[2].py);
pDC->MoveTo(arr_Points[2].px,arr_Points[2].py);
pDC->LineTo(arr_Points[3].px,arr_Points[3].py);
double xx1,yy1;
for(double t = 0;t <= 1;t+=0.001){
int len = N;
for(int i = 0; i < N; i++){
tmp[i].px = arr_Points[i].px;
tmp[i].py = arr_Points[i].py;
}
while(len!=1){
len--;
for(int i = 0; i < len; i++){
RatioPoint(t, tmp[i].px , tmp[i].py , tmp[i+1].px , tmp[i+1].py , xx1, yy1);
tmp[i].px = xx1;
tmp[i].py = yy1;
}
}
pDC->SetPixel(tmp[0].px,tmp[0].py,RGB(255,0,0));
}
}
五、实验心得
Bezier曲线理解起来有点难,写算法的时候也有点费劲。