计算机图形学实验报告.docx
《计算机图形学实验报告.docx》由会员分享,可在线阅读,更多相关《计算机图形学实验报告.docx(45页珍藏版)》请在冰豆网上搜索。
计算机图形学实验报告
深圳大学实验报告
实验课程名称:
计算机图形学
实验项目名称:
图形学算法演示系统
学院:
专业:
报告人:
学号:
班级:
同组人:
指导教师:
实验时间:
实验报告提交时间:
教务处制
实验内容:
设计开发一个具有图形界面、交互性较好的图形学算法演示系统,并进行答辩演示。
具体要求如下:
系统应包含如下内容:
●基本图元绘制算法:
DDA绘直线、Bresenham绘直线、Bresenham绘圆
●多边形扫描转换算法和区域填充算法实现(扫描线算法为必做,基于求余运算的边缘填充和边标志算法为任选;基于种子的区域填充采用4连通区域的递归种子填充算法,或扫描线种子填充算法,要求种子点(x,y)可交互输入)。
●线段裁剪和多边形裁剪算法的动画演示实现。
(两种线段裁剪算法和H-S多边形逐边裁剪算法)多边形裁剪算法的动画演示要求先画出一个封闭的多边形,再画矩形的裁剪窗口,然后选择裁剪按钮(或命令),按下“上边裁剪”按钮(或执行“上边裁剪”命令),多边形相对裁剪窗口的上边进行裁剪,显示上边裁剪后的多边形,依此进行其它各边裁剪。
●用动画实现二维图形变换的各种算法,实现对指定形体的平移、旋转和缩放。
(包括类似自行车行走和绕固定点旋转的自旋转物体动画。
)
●简单三维图形系统:
凸多面体的建模、透视投影,隐藏面的消除及基本图形变换(平移、旋转、缩放)
●交互式Bezier曲线的输入绘制程序实现
⏹实用算法动态图形演示:
任意选择程序设计、数据结构和算法设计中的经典问题,如冒泡排序、汉诺塔、八皇后、背包问题、动态规划等等,动画策略自定
⏹光照效应:
三维图形的面着色;
⏹分形几何:
Koch雪花,L系统植物及其他有特色的图形学相关效果等。
实验说明:
方法、步骤
编程语言:
C++
IDE版本:
MicrosoftVisualC++6.0
实验方法:
使用MFC进行编程,参照实验指导书,一步一步操作,就可以基本完成整个实验的操作了。
1、建立程序框架:
创建MFC单文档应用程序
2、添加绘图菜单项
通过更改各个菜单选项属性,设置好ID号,并标明,如图所示
3、在创建的View中添加个消息函数
添加对应消息WM_CREAT的消息函数OnCreate()函数
添加对应鼠标消息的WM_LBUTTONDOWN的消息函数OnLButtonDown();
添加对应鼠标消息的WM_MOUSEMOVE的消息函数OnMouseMove();
添加对应鼠标消息的WM_RBUTTONDOWN的消息函数OnRButtonDown();
添加对应鼠标消息的WM_MOUSEWHEEL的消息函数OnMouseWheel();
4、声明变量和函数
5、添加各菜单项的消息映射函数
主要添加的响应有,鼠标右键、鼠标右键、鼠标滑轮、鼠标移动、键盘按键响应。
每个工程的建立大同小异,下面将不对工程的建立和设置进行描述和说明。
主要讨论程序的算法和实现。
具体的设置参见程序。
基本图元
一、实验内容
本实验程序实现的主要函数及其说明
关于绘制图形的算法的函数:
voidDDALine(CPointstart,CPointend,longcolor);//用DDA算法绘制直线
voidMiddleLine(CPointstart,CPointend,longcolor);//用中点算法绘制直线
voidBresenhamLine(CPointstart,CPointend,longcolor);//用Bresenham算法绘制直线
voidRectangle(CPointstart,CPointend,longcolor);//绘制多边形
voidBresenhamCircle(CPointcenter,CPointt,longcolor);//用Bresenham算法绘制圆
voidMiddleCircle(CPointcenter,CPointt,longcolor);//用中点算法绘制圆
voidMiddleEllipse(CPointstart,CPointend,longcolor);//用中点算法绘制椭圆
二、程序设计说明及源代码:
1、绘制直线的算法
1)、DDA算法
解:
这个算法代码是直接抄写实验书上面的呀,算法比较明显,通过计算x方向和y方向的增量依次画出各个点,而这个代码也写得比较精炼step的应用大大减少代码量。
代码如下:
voidCExercise1View:
:
DDALine(CPointstart,CPointend,longcolor)
{
CClientDCdc(this);
dc.SetPixel(start.x,start.y,color);
dc.SetPixel(end.x,end.y,color);
//dx和dy分别是x方向和y方向的增量
intdx=end.x-start.x,dy=end.y-start.y,steps,k;
floatxIncrement,yIncrement,x=start.x,y=start.y;
//选择增量大的方向作为每次前进的方向
if(fabs(dx)>fabs(dy))steps=fabs(dx);
elsesteps=fabs(dy);
//计算每个方向的前进增量
xIncrement=float(dx)/float(steps);
yIncrement=float(dy)/float(steps);
dc.SetPixel((int)(x+0.5),(int)(y+0.5),color);
for(k=0;k{
x+=xIncrement;
y+=yIncrement;
dc.SetPixel((int)(x+0.5),(int)(y+0.5),color);
}
}
2)、中点算法
解:
通过计算中点在直线的上方还是下方来决定选择点直线方程
当斜率在
时,
,这时考虑右上方和右下方的点:
时,取右下方的点,此时判别式为:
时,取右下方的点,此时判别式为:
当斜率在
时,
,这时考虑上左方和上右方的点:
时,取上左方的点,此时判别式为:
时,取上右方的点,此时判别式为:
斜率在
和
的情况类似上面的推导,这里就不进行推导。
代码考虑了斜率的所有情况。
代码如下:
voidCExercise1View:
:
MiddleLine(CPointstart,CPointend,longcolor)
{
CClientDCdc(this);
CPointTempPoint;
if(start.x>end.x){TempPoint=start;start=end;end=TempPoint;}
dc.SetPixel(start.x,start.y,color);
dc.SetPixel(end.x,end.y,color);
inta,b,d1,d2,d,x,y,flag=0,temp;
if(fabs(start.x-end.x)>fabs(start.y-end.y))flag=1;
x=start.x;y=start.y;
dc.SetPixel(x,y,color);
a=start.y-end.y;
b=end.x-start.x;
if(start.y>end.y)b=-b;
if(flag==0){temp=a;a=b;b=temp;}
d=2*a+b;
d1=2*a;d2=2*(a+b);
if(start.y<=end.y)
{
if(flag==1){
while(x{
if(d<0){x++;y++;d+=d2;}
else{x++;d+=d1;}
dc.SetPixel(x,y,color);
}
}else{
while(y{
if(d>0){y++;x++;d+=d2;}
else{y++;d+=d1;}
dc.SetPixel(x,y,color);
}
}
}
else
{
if(flag==1){
while(x{
if(d>0){x++;y--;d+=d2;}
else{x++;d+=d1;}
dc.SetPixel(x,y,color);
}
}else{
while(y>end.y){
if(d<0){y--;x++;d+=d2;}
else{y--;d+=d1;}
dc.SetPixel(x,y,color);
}
}
}
}
3)、Bresenham算法
解:
这个算法参照实验指导书只考虑斜率在
的情况,进行扩展,其实很简单,只要将x和y的位置交换下就可以了,再根据x和y的前进方向进行设置就可以了,程序很容易写完。
代码如下:
voidCExercise1View:
:
BresenhamLine(CPointstart,CPointend,longcolor)
{
CClientDCdc(this);
CPointTempPoint;
if(start.x>end.x){TempPoint=start;start=end;end=TempPoint;}
dc.SetPixel(start.x,start.y,color);
dc.SetPixel(end.x,end.y,color);
intdx=fabs(end.x-start.x),dy=fabs(end.y-start.y);
intd,twoDy,twoDyMinusDx;
intx,y,t=1,count;
if(start.y>end.y)t=-1;
x=start.x;y=start.y;
dc.SetPixel(x,y,color);
if(dx>=dy)
{
d=2*dy-dx;
twoDy=2*dy,twoDyMinusDx=2*(dy-dx);
while(x{
x++;
if(d<0)d+=twoDy;
else{y+=t;d+=twoDyMinusDx;}
dc.SetPixel(x,y,color);
}
}
else
{
d=2*dx-dy;
twoDy=2*dx,twoDyMinusDx=2*(dx-dy);
count=0;
while(count{
count++;
y+=t;
if(d<0)d+=twoDy;
else{x++;d+=twoDyMinusDx;}
dc.SetPixel(x,y,color);
}
}
}
2、多边形绘制
1)、三角形
三角形画法很简单只是用鼠标点三个点,然后将三个点连线就可以了。
这个难点在于交与方面和实现橡皮筋功能方面。
2)、矩形
矩形的画法也比教简单,只是选择两个点(XA,YA),(XB,XY),然后组合后画出对应的4条线段就可以了。
3)、多边形
通过鼠标点击的点依次画出各个点间的连线就可以了,最后结束的时候只要点击鼠标右键就可以了。
3、圆的绘制算法
1)、中点算法
解:
这个算法的原理和直线的中点算法原理一样,代码完全参照课本给的程序,修改下就可以使用了,也结合了实验指导书的例程。
voidCExercise1View:
:
MiddleCircle(CPointcenter,CPointt,longcolor)
{
CClientDCdc(this);
CPointtemp;
floatradius=sqrt((center.x-t.x)*(center.x-t.x)+(center.y-t.y)*(center.y-t.y));
intp=1-(int)radius;
temp.x=(int)radius;
temp.y=0;
circlePlotPoint(center,temp,color);
while(temp.y{
temp.y++;
if(p<0)p+=2*temp.y+3;
else{temp.x--;p+=2*(temp.y-temp.x)+5;}
circlePlotPoint(center,temp,color);
}
}
2)、Bresenham算法
解:
也是参照书上的例程,修改下,对这个算法的理解还不是很深刻。
voidCExercise1View:
:
BresenhamCircle(CPointcenter,CPointt,longcolor)
{
CClientDCdc(this);
CPointtemp;
intR=(int)sqrt((center.x-t.x)*(center.x-t.x)+(center.y-t.y)*(center.y-t.y));
intd=3-2*R;
temp.x=0;
temp.y=R;
circlePlotPoint(center,temp,color);
while(temp.x{
circlePlotPoint(center,temp,color);
if(d<0)d=d+4*temp.x+6;
else{d=d+4*(temp.x-temp.y)+10;temp.y--;}
temp.x++;
}
if(temp.x==temp.y)circlePlotPoint(center,temp,color);
}
4、椭圆绘制算法
解:
首先将椭圆分成两个区域,以切线斜率为-1作为分界。
由
及
算出分界点
,
初值为
,从(0,b)开始画到(x,y)到(a,0)
切线斜率
,
当
,
,当
,
切线斜率
,
当
,
,当
,
代码如下:
voidCExercise1View:
:
MiddleEllipse(CPointstart,CPointend,longcolor)
{
CClientDCdc(this);
CPointt,k;
CPointtemp;
temp=start;
inta=fabs(start.x-end.x),b=fabs(start.y-end.y);
if(a==0||b==0)return;
intaa=a*a,bb=b*b;
k.x=(int)((float)aa/sqrt((float)(aa+bb))+0.5);
k.y=(int)((float)aa/sqrt((float)(aa+bb))+0.5);
t.x=0;
t.y=b;
intd=4*(bb-aa*b)+aa;
ellipsePlotPoint(temp,t,color);
while(t.x<=k.x)
{
if(d<0)d+=4*bb*(2*t.x+3);
else{d+=4*(bb*(2*t.x+3)+2*aa*(1-t.y));t.y--;}
t.x++;
ellipsePlotPoint(temp,t,color);
}
while(t.y>0)
{
if(d>=0)d+=4*aa*(3-2*t.y);
else{d+=4*((2*bb*(t.x+1))+aa*(3-2*t.y));t.x++;}
t.y--;
ellipsePlotPoint(temp,t,color);
}
}
区域填充
一、实验内容
本实验程序实现的主要函数及其说明
关于绘制图形的算法的函数:
voidLineFill();//用扫描线算法填充多边形
voidSideNotFill();//用边取反算法填充多边形
voidSeedFill(CPointtemp);//用种子填充算法填充区域
voidLineSeedFill(CPointseed);//用扫描线种子填充算法填充区域
二、程序设计说明及源代码:
1、多边形填充算法
1)、扫描线填充算法
用到的数据结构:
桶ET和边的活性边表AEL
算法的描述如下:
for(y=ymin;i<=ymax;y++)
{
合并当前扫描线y的ET表;
将y桶中的每个记录按x项升序排列;
在当前y值下,将两两记录的x值之间的像素进行填充;
修改边记录x=x+1/m;(m为直线斜率的倒数)
}
这个算法主要的函数有
buildEdgelist(cnt,pts,edges);//建立边表
buildActivelist(scan,active,edges);//建立活性边
fillscan(scan,active);//填充
updateActivelist(scan,active);//更新
resortActivelist(active);//重排序
2)、边取反填充算法
对图像M作偶数次取反运算后还是M,而对图像作奇数次取反后的结果是~M,可以利用这个原理对多边形的每一条边向右填充逐位取反操作。
算法比较简单,但是要注意一点就是顶点的处理,顶点分为内点和外点。
对于每一个顶点,如果他相邻的两个顶点在它的同一侧,则这个点为外点,否则称为内点。
对于内点只需要向右填充一次,外点不需要向右进行填充。
为了增加向右填充的效率,在实验中先找出最右边的点,只需要填充到最右的这个点就可以了。
程序的代码如下:
voidCLabView:
:
SideNotFill()
{
CLabDoc*pdoc=GetDocument();
longi,n=pdoc->NodeCount;
memset(pdoc->PolygonNodeJudge,0,100*sizeof(pdoc->PolygonNodeJudge[0]));
moreLeft=pdoc->PolygonNode[0].x;
//判断第0个点是内点还是外点
if((pdoc->PolygonNode[n-1].y>pdoc->PolygonNode[0].y&&
pdoc->PolygonNode[1].yPolygonNode[0].y)||
(pdoc->PolygonNode[n-1].yPolygonNode[0].y&&
pdoc->PolygonNode[1].y>pdoc->PolygonNode[0].y)){
pdoc->PolygonNodeJudge[0]=1;
}
//判断最后一个点是内点还是外点
if((pdoc->PolygonNode[0].y>pdoc->PolygonNode[n-1].y&&
pdoc->PolygonNode[n-2].yPolygonNode[n-1].y)||
(pdoc->PolygonNode[0].yPolygonNode[n-1].y&&
pdoc->PolygonNode[n-2].y>pdoc->PolygonNode[n-1].y)){
pdoc->PolygonNodeJudge[n-1]=1;
}
//对其他点进行判断是内点还是外点
for(i=1;iif((pdoc->PolygonNode[i-1].y>pdoc->PolygonNode[i].y&&
pdoc->PolygonNode[i+1].yPolygonNode[i].y)||
(pdoc->PolygonNode[i-1].yPolygonNode[i].y&&
pdoc->PolygonNode[i+1].y>pdoc->PolygonNode[i].y)){
pdoc->PolygonNodeJudge[i]=1;
}
}
//找出多边形中“最右”的那个点的y值
for(i=1;iNodeCount;i++)
if(pdoc->PolygonNode[i].x>moreLeft)moreLeft=pdoc->PolygonNode[i].x;
moreLeft=moreLeft+10>m_rect.right?
m_rect.right:
moreLeft+10;
if(pdoc->PolygonNode[0].y!
=pdoc->PolygonNode[pdoc->NodeCount-1].y)
LineDDA2(pdoc->PolygonNodeJudge[0],pdoc->PolygonNode[0],pdoc->PolygonNode[pdoc->NodeCount-1],pdoc->PenColor);
for(i=1;iNodeCount;i++){
if(pdoc->PolygonNode[i].y!
=pdoc->PolygonNode[i-1].y)
LineDDA2(pdoc->PolygonNodeJudge[i],pdoc->PolygonNode[i],pdoc->PolygonNode[i-1],pdoc->PenColor);
}
}
2、区域填充算法
1)、扫描线种子填充算法
扫描线种子填充算法的基本思想是:
从给定的种子开始,填充当前扫描线上种子点所在的区间,然后确定与这一区间相邻的上下两条扫描线上需要填充的区间,从这些区间上各取一个点并依次保存下来,作为下次填充的种子点,反复进行,到填充完这个区间为止。
这个算法是参考课本的扫描线种子填充算法,经过补充和修改而成的。
核心代码如下:
voidCLabView:
:
LineSeedFill(CPointseed)
{
CClientDCdc(this);
CLabDoc*pdoc=GetDocument();
CPointstack[1024],t;
longslen=0,savex,xright,xleft,flag;
unsignedlongcolor=dc.GetPixel(seed);
//如果填充的区域的颜色与要填充的颜色一样,则要退出
if(color==pdoc->FillColor)return;
stack[slen++]=seed;//将种子放进栈中
w