计算机图形学课程设计报告
系 (院):
专业班级:
姓 名:
学 号:
指导教师:
设计时间:
设计地点:
(此处目录根据自己情况可以调整改动)
一、课程设计目的.............................................................................3
二、课程设计具体要求..................................................................3
三、需求分析与总体设计..................................................................4
四、详细设计与实现[含关键代码和实现界面].................................7
一、 课程设计目的
计算机图形学课程设计是验证、巩固和补充课堂讲授的理论知识的必要环节,通过上机实验,培养学生的自学能力、动手能力、综合运用知识解决实际问题的能力。要求学生运用计算机图形学理论与技术设计、编写、调试程序并撰写课程设计报告。
二、 课程设计具体要求
1.独立完成设计并撰写课程设计报告。
2.在规定时间将程序和设计报告用附件(信计111X班XXX 图形学课设报告.RAR)发送到274548837@qq.com,并上交纸质打印稿(A4纸10页左右)。
3. 课程设计报告内容包括:
(1)列出设计者姓名及本人详细信息、所用开发工具;
(2)程序的基本功能介绍;
(3)程序实现步骤和关键算法的理论介绍;
(4)关键源代码实现说明。(不要打印全部源程序!)
(5)程序运行界面截图(3幅左右)
(6)课设总结和自我评价。
4.《计算机图形学》课程的知识结构体系:
(1)课设为期两周:总学时为40学时,2学分
(2)学生必须完成二维线画图元和二维填充图元两个大功能。二维裁剪和二维图形变换至少实现两个内容。总共不少于10个算法。
(3)程序应做到:通用性、交互性、界面友好性!
三、需求分析与总体设计
1、Bresenham基本算法(含画圆与画线):
过各行各列象素中心构造一组虚拟网格线。按直线从起点到终点的顺序计算直线与各垂直网格线的交点,然后根据误差项的符号确定该列象素中与此交点最近的象素。
设直线方程为:
其中k=dy/dx。 因为直线的起始点在象素中心,所以误差项d的初值d0=0。
X下标每增加1,d的值相应递增直线的斜率值k,即d=d+k。一旦d≥1,
就把它减去1,这样保证d在0、1之间。
当d≥0.5时,最接近于当前象素的右上方象素( )
当d<0.5时,更接近于右方象素( )。
为方便计算,令e=d-0.5,e的初值为-0.5,增量为k。
当e≥0时,取当前象素(xi,yi)的右上方象素( );
而当e<0时,更接近于右方象素( )。
可以改用整数以避免除法。
3. 数值微分(DDA)法
已知过端点 的直线段L:y=kx+b,直线斜率为
从x的左端点 开始,向x右端点步进。步长=1(个象素),计算相应的y坐标y=kx+b;取象素点(x, round(y))作为当前点的坐标
4.中点画线法
当前象素点为(xp, yp)。下一个象素点为P1或P2。
设M=(xp+1, yp+0.5),为p1与p2之中点,Q为理想直线与x=xp+1垂线的交点。将Q与M的y坐标进行比较。
当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为下一个象素;
但这样做,每一个象素的计算量是4个加法,两个乘法。
d是xp, yp的线性函数,因此可采用增量计算,提高运算效率。
若当前象素处于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。
5.几何变换(平移与旋转)以一条直线段为例,完成目标的平移、绕任一点旋转。
7.多边形剪裁
基本思想是一次用窗口的一条边裁剪多边形,窗口的一条边以及延长线构成裁剪线,改线把平面分成两个部分:可见一侧,不可见一侧。用一条裁剪边多多边形进行裁剪,得到一个顶点序列,作为吓一条裁剪边处理过程的输入点。
对于每一条裁剪边,只是判断点在窗口的哪一测以及求线段与裁剪边的交点算法应随之改变。
仅用一条裁剪边时,逐次多边形裁剪框图
8. 多边形填充算法分析:
确定多边形所占有的最大扫描线数,得到多边形顶点的最小和最大y值(ymin和ymax),从y=ymin 到 y=ymax, 每次用一条扫描进行填充。对一条扫描线填充的过程可分为四个步骤: a.求交b.排序c.交点配对d.区间填色。在CGraphics类中的FillPlogon函数中实现多边形的填充算法。
9.直线段裁剪
10.圆的填充算法
用户界面:
1.Bresenham算法代码如下:
voidBresenhamline(int x1, int y1, int x2, int y2,CDC *pDC)
{ //对于所有直线均按照从左至右的方向绘制
int x,y,d,dx,dy,right,rightleft;
if(x1>x2){
int tempx,tempy;
tempx=x1;x1=x2;x2=tempx;
tempy=y1;y1=y2;y2=tempy;
}
//根据斜率的情况不同而绘制
if(y1==y2){//斜率为0的情况
for(x=x1;x<=x2;x++)
pDC->SetPixel(x,y1,2);
}
else if(x1==x2){//直线为垂直的情况
if(y1>y2){ //使直线按从下往上画
int tempy=y1;
y1=y2;y2=tempy;
}
for(y=y1;y<=y2;y++)
pDC->SetPixel(x1,y,2);
}
else{
dy=y2-y1;
dx=x2-x1;
if(abs(dy)==abs(dx)){////斜率为1或-1时
x=x1;y=y1;
if(dy<0){//斜率为1
for(;y>=y2;y--){
x++;
pDC->SetPixel(x,y,2);
}
}//斜率为1
else{//斜率为-1
for(;y<=y2;y++){
x++;
pDC->SetPixel(x,y,2);
}
}//斜率为-1
}
elseif(abs(dy)<abs(dx)){//斜率的绝对值小于1时
if(dy>0&&dx>0){//斜率为正时
right=-2*dy;
rightleft=2*dx-2*dy;
d=dx-2*dy;
x=x1;y=y1;
while(x<=x2){
pDC->SetPixel(x,y,2);
x++;
if(d<0){
y++;
d=d+rightleft;
}else{
d=d+right;
}
}
}//斜率为正时
else{//斜率为负时
right=2*dy;
rightleft=2*dy-2*dx;
d=2*dy-dx;
x=x1;y=y1;
while(x<=x2){
pDC->SetPixel(x,y,2);
x++;
if(d<0){
y++;
d=d+rightleft;
}else{
d=d+right;
}
}
}//斜率为负时
}//斜率的绝对值小于1时
else{////斜率的绝对值大于1时
if(dy>0&&dx>0){//斜率为正时
right=2*dx;
rightleft=2*dx-2*dy;
d=2*dx-dy;
x=x1;y=y1;
while(y<=y2){
pDC->SetPixel(x,y,2);
y++;
if(d>=0){
x++;
d=d+rightleft;
}else{
d=d+right;
}
}
}//斜率为正时
else{//斜率为负时
right=-2*dx;
rightleft=-2*dx-2*dy;
d=-2*dx-dy;
x=x1;y=y1;
while(y>=y2){
pDC->SetPixel(x,y,2);
y--;
if(d<0){
x++;
d=d+rightleft;
}else{
d=d+right;
}
}
}//斜率为负时
}//斜率的绝对值大于1时
}//斜率的所有情况
}
voidCBresenhamView::OnDraw(CDC* pDC)
{
CBresenhamDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
Bresenhamline(10,10,500,50,pDC);
// TODO: add draw code for native datahere
}
1) 新建工程文件
2) 打开下resenhamView.cpp
3) 在// CBresenhamView drawing代码后
添加以上代码。
4) 运行成功
5) 更改绘制坐标 Bresenhamline(10,10,500,50,pDC) 可以改变直线斜率,还能通过直线的连接绘制多边形。如:
6) //绘制多边形
7) Bresenhamline(10,10,500,10,pDC);
8) Bresenhamline(10,10,10,100,pDC);
9) Bresenhamline(10,100,600,120,pDC);
10) Bresenhamline(500,10,600,120,pDC);
11)
2.数值微分(DDA)法
DDA画线法:
voidDDALine(CDC *pdc,int x0,int x1,int y0,int y1,int color)
{
float k=1.0*(y1-y0)/(x1-x0);
int x=0,int y=y0;
for(x=0;x<=x1;x++)
{
pdc->SetPixel(x,y,color);
y=y+k;
}
}
voidCMy3View::On2()
{
// TODO: Add your command handler codehere
int x0,y0,x1,y1,color;
x0=111;
y0=111;
x1=138;
y1=138;
color=RGB(0,0,255);
CClientDC dc(this);
DDALine(&dc,x0,x1,y0,y1,color)
Midpoint中点画线法:
voidMidpoint_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);
}
}
voidCMy2View::On1()
{
// TODO: Add your command handler codehere
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);
}
bresenham画圆源程序
voidBresenhan_circle()
;int x,y=180,color=1,xcenter,ycenter;x=0;
int d=3-2*180;
while(x<y)
{
if(d<0){d=d+4*x+6;x=x+1;}
else{d=d+4*(x-y)+10;x=x+1;y=y-1;}
pdc->SetPixel(0+x,0+y,COLOR);
pdc->SetPixel(0+x,0-y,COLOR);
pdc->SetPixel(0-x,0+y,COLOR);
pdc->SetPixel(0-x,0-y,COLOR);
pdc->SetPixel(0+y,0+x,COLOR);
pdc->SetPixel(0+y,0-x,COLOR);
pdc->SetPixel(0-y,0+x,COLOR);
pdc->SetPixel(0-y,0-x,COLOR);
}
几何变换
1. 目标的平移的源程序
xa1=xa+dx;
ya1=ya+dy;
xb1=xb+dx;
yb1=yb+dy;
2. 绕任意点旋转的源程序
xa1=cos(angle/57.1)*xa-sin(angle/57.1)*ya+x-x*cos(angle/57.1)+y*sin(angle/57.1);ya1=sin(angle/57.1)*xa+cos(angle/57.1)*ya+y-y*cos(angle/57.1)-x*sin(angle/57.1);
xb1=cos(angle/57.1)*xb-sin(angle/57.1)*yb+x-x*cos(angle/57.1)+y*sin(angle/57.1);yb1=sin(angle/57.1)*xb+cos(angle/57.1)*yb+y-y*cos(angle/57.1)-x*sin(angle/57.1);
多边形填充
//在指定的pDC设备中,填充多边形
boolCGraphics::FillPloyon(CDC* pDC)
{
if(PointCount < 3)
return false;
//若多边形小于三个点则返回
int minX = Point[0].x , minY = Point[0].y;
int maxX = Point[0].x , maxY = Point[0].y;
for(int i=1;i<PointCount;i++)
{
if(minX > Point[i].x)
minX = Point[i].x;
if(minY > Point[i].y)
minY = Point[i].y;
if(maxX < Point[i].x)
maxX = Point[i].x;
if(maxY < Point[i].y)
maxY = Point[i].y;
}
//获取多边形中所有坐标点的最大值和最小值,作为扫描线循环的范围
CUIntArray myArray;
int x,y;
for(y=minY;y<maxY;y++)
{
//扫描线从minY开始到maxY
for(i=0;i<PointCount;i++)
{
//对每条边进行循环
CPoint PointCross;
int beforeI=BeforeIndex(i),afterI=AfterIndex(i);
//判断是否跟线段相交
if(InterCross(Point[beforeI],Point[i],CPoint(minX,y),CPoint(maxX,y),PointCross))
{
//若是存在交点,则进行相应的判断,即判断x的坐标取两次、一次还是不取
if(PointCross==Point[i])
{
if((Point[beforeI].y >PointCross.y) && (Point[afterI].y > PointCross.y))
{
myArray.Add(PointCross.x);
myArray.Add(PointCross.x);
//边顶点的y值大于交点的y值,x坐标取两次
}
else
if((Point[beforeI].y -PointCross.y) * (Point[afterI].y - PointCross.y) < 0)
myArray.Add(PointCross.x); //边顶点的y值在交点的y值之间,即一个顶点的y值大于交点的y值,而另一个小于,相应的x坐标取一次
else
if(PointCross.y==Point[afterI].y)
myArray.Add(PointCross.x);
}
else
if(PointCross==Point[beforeI])
{
continue;
}
else
myArray.Add(PointCross.x);//当交点不在线段的顶点时,x坐标只取一次
}
}
int *scanLineX,num=myArray.GetSize();
scanLineX = new int[num];
for(i=0;i<num;i++)
scanLineX[i]=myArray.GetAt(i);//获取扫描线x值,以构成填充区间
myArray.RemoveAll();
SortArray(scanLineX,num);//对scanLine(扫描线x坐标进行排序)
for(i=0;i<num;i=i+2)
{
if(i+1>=num)
break;
for(x=scanLineX[i];x<scanLineX[i+1];x++)//x值配对填充
pDC->SetPixelV(CPoint(x,y),RGB(255,0,0));//将填充区间相应点的颜色设置成红色
}
Sleep(1);//CPU暂停1ms,以体现出多边形是以扫描线的方式,一条一条的填充的
delete scanLineX;
}
return true;
}
多边形生成图
裁剪图及填充图
多边形裁剪
boolCGraphics::CutRect(CRect rect)
{
CPoint rectPoint[4];
rectPoint[0].x = rect.left;
rectPoint[0].y = rect.top;
rectPoint[1].x = rect.right;
rectPoint[1].y = rect.top;
rectPoint[3].x = rect.left;
rectPoint[3].y = rect.bottom;
rectPoint[2].x = rect.right;
rectPoint[2].y = rect.bottom;
//获取裁减矩形的四个点的坐标,第一个点为左上,第二个点为右上,第三个点为右下,第四个点为左下
int i;
CArray<CPoint,CPoint&> myArray;//裁减后,保存的多边形的依次各点的坐标
for(int rectNum=0;rectNum<4;rectNum++)
{
//对裁减矩形的四条边进行循环
for(i=0;i<PointCount;i++)
{
//对每条边进行循环
CPoint PointCross;
intbeforeI=BeforeIndex(i),afterI=AfterIndex(i);
int afterrectNum = ((rectNum ==3)?0:rectNum+1);
//判断是否跟线段相交
if(InterCross(Point[beforeI],Point[i],rectPoint[rectNum],rectPoint[afterrectNum],PointCross))
{
if(PointCross==Point[i])
{
myArray.Add(Point[i]);//交点在线段上,直接添加点坐标在保存多边形的数组中
}
else
if(PointCross==Point[beforeI])
{
if(IsInSquareRgn(rect,Point[i],rectNum))
myArray.Add(Point[i]);//判断是否可视,若是,则添加点坐标
}
else
{
myArray.Add(PointCross);//跟线段相交,但交点不在顶点上,添加交点坐标
if(IsInSquareRgn(rect,Point[i],rectNum))
myArray.Add(Point[i]);//判断是否可视,若是,则添加点坐标
}
}
else
if(IsInSquareRgn(rect,Point[i],rectNum))
myArray.Add(Point[i]);//线段不相交,但需判断是否可视,若是,则添加点坐标
}
PointCount=myArray.GetSize();
if(Point)
delete Point;
Point = new CPoint[PointCount];
for(i=0;i<PointCount;i++)
Point[i]=myArray.GetAt(i);//重新赋予点坐标的值
myArray.RemoveAll();
}
return true;
}
直线段裁剪:(单独用的函数)
#define LEFT1
#define RIGHT2
#defineBOTTOM 4
#define TOP 8
#define XL 150
#define XR 350
#define YB 200
#define YT 300
#include<math.h>
#include <graphics.h>
#include<stdio.h>
encode(x,y,code)
float x,y;
int *code;
{
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;
return *code;
}
void M_lieCLip(float x1,float y1,floatx2,float y2,float *x,float *y)
{
int code1,code2,code;
float t=1,xx,yy;
encode(x1,y1,&code1) ;
encode(x2,y2,&code2);
if (code1==0) { *x=x1; *y=y1;return;}
while(code1&code2==0)
{
L1:xx=(x1+x2)/2;
yy=(y1+y2)/2;
encode(x,y,&code);
if(abs((x2-xx)*(x2-xx)+(y2-yy)*(y2-yy))<t)
{*x=xx;*y=yy;return;}
if(code&code1!=0){x2=xx;y2=yy;}
else{x1=xx;y1=yy;}
}
}
void main()
{
float x1,y1,x2,y2,xx,yy,xxx,yyy,t;
int gdriver=DETECT,gmode;
initgraph(&gdriver,&gmode,"");
setcolor(4);
line(XL,YT,XR,YT); line(XL,YB,XR,YB);
line(XL,YT,XL,YB); line(XR,YT,XR,YB);
x1=50;y1=150;x2=400;y2=300;
setcolor(7);
line(x1,y1,x2,y2);
xx=0;yy=0;xxx=0;yyy=0;
M_lieCLip(x1,y1,x2,y2,&xx,&yy);
M_lieCLip(x2,y2,xx,yy,&xxx,&yyy);
setcolor(11);
line(xx,yy,xxx,yyy);
getch();
closegraph();
}
圆的填充算法
#include"stdlib.h"
#include"math.h"
#include<gl/glut.h>
//按坐标画点
void draw(GLintxCoord, GLint yCoord)
{
glBegin(GL_POINTS);//以点的形式
glVertex2i(xCoord,yCoord);//在(xCoord, yCoord)坐标下画点
glEnd();
glFlush();//强制刷新
}
void Circle(GLintx,GLint y)
{
int a=abs(x);//将x的绝对值赋给a
int b=abs(y);//将y的绝对值赋给b
int c=a*-1;//使c=a的相反数
int d=b*-1;//使d=b的相反数
draw(x, y); draw(y, x);
draw(-x, y); draw(y, -x);
draw(x, -y); draw(-y, x);
draw(-x, -y); draw(-y, -x);//按照圆的对称性以圆心为对称点将四个象限的圆周画出
for(inti=c;i<=a;i++)
{
for(int j=d;j<=b;j++)
{
draw(i,j);
}
}//以a,b,c,d为边界用点填充该圆
}
//主函数
voidBresenhamCircle(GLint r)
{
int d, d1, d2, direct;
GLint x,y;
x=0;
y=r;
d = 2*(1-r);
while(y>=0)
{
Circle(x,y);
if(d < 0)
{
d1 = 2* (d+ y) -1;
if(d1 <=0)
direct = 1;
else
direct = 2;
}
else
{
if( d > 0)
{
d2 = 2*(d-x)-1;
if(d2 <= 0)
direct = 2;
else
direct = 3;
}
else
direct = 2;
}
switch(direct)
{
case 1:
x++;
d+=2*x + 1;
break;
case 2:
x++; y--;
d+=2*(x-y+1) + 1;
break;
case 3:
y--;
d+=-2*y + 1;
break;
}
}
}
voidRenderScene(void)
{
BresenhamCircle(50);//主函数调用
}
//当窗口大小改变时由GLUT函数调用
voidChangeSize(GLsizei width, GLsizei Height)
{
GLfloat aspectRatio;
if (Height == 0)
{
Height = 1;
}
glViewport(0, 0, width,Height);//指定视口矩形左下角
glMatrixMode(GL_PROJECTION);//指定当前矩阵,对投影矩阵应用随后的矩阵操glLoadIdentity();// 装载单位矩阵
aspectRatio =(GLfloat)width / (GLfloat) Height;
if (width <= Height)
{
glOrtho(-100.0, 100.0,-100.0 / aspectRatio, 100.0 / aspectRatio, 1.0, -1.0);
}
else
{
glOrtho(-100.0 *aspectRatio, 100.0 * aspectRatio, -100.0, 100.0, 1.0, -1.0);
}
glMatrixMode(GL_MODELVIEW);//指定当前矩阵,对模型视景矩阵堆栈应用随后的矩阵操作glLoadIdentity();// 装载单位矩阵
}
//主程序入口
voidmain(void)
{
glutInitDisplayMode(GLUT_SINGLE| GLUT_RGB);//设置初始显示模式,指定单缓存窗口,指定 RGB 颜色模式的窗口
glutCreateWindow("圆");//创建窗口,窗口名称为“圆”
glutDisplayFunc(RenderScene);//进行画图
glutReshapeFunc(ChangeSize);//重画回调函数
glutMainLoop();//进入GLUT事件处理循环,让所有的与“事件”有关的函数调用无限循环
}
指导老师意见:
成绩: 教师签名:
年 月 日