《计算机图形学》实验指导书(正式版)
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
《计算机图形学》实验指导
谢晓玲
华东理工大学
信息学院计算机系
2010年8月
目录
实验1 OpenGL应用的创建 (2)
实验2 橡皮筋技术的实现 (17)
实验3 基本变换 (24)
实验4 拾取 (41)
实验5 三维观察的实现 (54)
实验1 OpenGL应用的创建
一、实验目的
1、了解C++.NET开发基于窗口技术的应用程序的步骤;
2、了解OpenGL绘图的步骤;
3、显示一个三角形图形。
二、使用的工具软件及环境
C++.NET、OpenGL
三、实验内容
1、构造一个单文档的Windows应用程序
2、定义一个填充图案;
3、通过菜单,交互控制填充开关;
4、显示一个填充的三角形图形。
四、实验指导
1、基本要素
(1)C++.NET程序设计框架
C++.NET提供了一套应用程序框架,应用程序框架是指用于生成一般的应用程序所必须的各种面向对象的软件组建的集合。
C++程序设计的特点之一就是大量使用类库来进行功能扩展。
类库是一个可以在应用程序中使用的相互关联的C++类的集合。
一些类库是随编译器一起提供的,一些是由其他软件公司销售的,还有一些是由用户自己开发的。
应用程序框架是一种类库的超集,它用来定义程序的结构,将其他的类库,例如文档类、视图类及用户自定义类等,嵌入到应用程序框架中,以完成用户预期的功能。
通过定制,C++.NET 可以自动生成一套程序代码,以单文档多视风格的应用程序为例,自动生成的源代码主要包含应用程序类、主框架类、文档类、视口类。
以MyDemo为工程名,C++.NET自动生成的类如下:
A.class CMyDemoApp: public CWinApp
CMyDemoApp的对象就代表了一个应用程序。
该程序定义了一个单独的全局CMyApp对象theApp:
CMyDemoApp theApp;
其基类决定了theApp的行为,包括程序的启动、初始化和运行等。
B.class CMainFrame : public CFrameWnd
它代表了应用程序的主框架窗口,它负责创建和显示具体的窗口结构,并负责消息的分发。
C.class CMyDemoDoc : public CDocument
从CDocument派生应用程序的文档类,可以添加成员变量用于保存数据,可以重写成员函数Serialize()实现文档数据的串行化。
D.class CMyDemoView : public CView
从CView派生应用程序的视图界面,响应用户的操作。
(2)文档-视图结构
文档类和视图类共同形成了“文档-视图”结构。
文档类用于管理数据,视图类用于将数据显示出来,视图是提供文档与用户交互的一个窗口。
视图类的好处在于将数据从用户对数据的
观察中分离了出来,从而允许同一数据可以有多种视图,而这也是符合客观世界中人们观察事物过程的客观要求的。
文档类的任务是对数据的管理和维护,相当于数据容器,数据通常被保存在文档类的成员变量中,通过文档类的串行化过程将数据保存到文件或数据库中。
视图类在文档和用户之间起中介作用,视图可以直接或间接访问文档类中存放数据的成员变量,并显示在屏幕上;可以接受用户的输入,并反馈给文档类。
文档-视图结构的工作机制是视图调用成员函数GetDocument()获得指向相关联的文档对象的指针,再通过该指针调用文档类的成员获得数据,由屏幕显示给用户;用户通过视图交互读写数据,再通过相关联的文档类的成员传递数据,最后通过串行化保存到介质中。
a)文档类的方法
CDocument是所有的文档类的基类,为文档与其他对象(视图、应用程序对象以及框架窗口等)交互的实现提供一个框架。
它提供的方法分一般方法(表1_1)和虚拟方法(表1_2)(允许应用程序重写),可以通过Afxwin.h头文件了解该类的基本功能。
表1_1 CDocument的一般方法
GetTitle() 获得文档标题
GetPathName()获得文件的路径
GetDocTemplate()获得文档模板的指针
AddView() 向文档视图列表中添加视图
RemoveView() 从文档视图列表中删除视图
UpdateAllViews() 通知相关的视图,文档已被修改,它们应该重画
DisconnectViews() 使文档与视图分离
表1_2 CDocument的虚拟方法
SetTitle()设置文档标题
SetPathName()设置文件的路径
IsModified()确定文档最后一次保存后是否被修改过
SetModifiedFlag()设置文档最后一次保存后是否被修改过
OnNewDocument() 由MFC调用建立新文档,在此可以进一步初始化
OnOpenDocument()由MFC调用打开文档
OnSaveDocument()由MFC调用保存文档
OnCloseDocument()由MFC调用关闭文档
GetFile()获得CFile类的指针
ReleaseFile()释放文件
SaveModified() 查询文件的修改状态并保存修改的文件
GetFirstViewPosition() 获得文档视图列表的头位置
GetNextView() 获得下一个文档视图
DeleteContents() 在未撤销文档对象时删除文档数据
CanCloseFrame() 确定文档的框架窗口是否被允许关闭
b)视图类的方法
CView视图类是从CWnd类派生的,所以CView视图类及其派生类都具有CWnd类的所有功能,如创建、移动、显示、隐藏窗口,并且接受任何Windows消息,而CDocument类不行。
CView视图类提供的方法分一般方法(表1_3)和虚拟方法(表1_4)(允许应用程序重写),可以通过Afxwin.h头文件了解该类的基本功能。
表1_3 CView的一般方法
GetDocument() 获得与视图相关的文档的标题
DoPreparePrinting()显示Print对话框,创建打印机设备环境
表1_4 CView的虚拟方法
IsSelected()确定是否选中
OnScroll()当用户滚动时,CView进行响应
OnInitialUpdate ()在视图第一次与文档连接时由MFC调用
OnDraw()显示文档的内容,实现屏幕显示、打印及打印预览。
OnUpdate() 通知视图它所关联的文档进行了修改而需进行响应
OnPrepareDC()在调用OnDraw()前允许由MFC调用修改设备描述表当屏幕发生变化或焦点的变化需要视图重绘以实现正确显示时,OnDraw()被自动调用;而文档数据发生变化时,OnDraw()函数不会被自动调用,需调用与视图关联的文档类的成员函数OnUpdateAllViews(),该函数使视图做出响应调用OnDraw()函数。
c)视图类的派生
在很多情况下,应用程序中使用CV iew的派生类而非CV iew作为本应用程序中视图类的基类,在创建应用程序时,应根据需求选择合适的视图派生类。
●CScrollView类,可以实现视图的滚动显示,可以利用其成员函数SetScrollSize()设置
滚动尺寸和坐标映射模式,但在绘图和接受用户输入时需要对坐标进行变换。
●CTreeView类,支持树型控件功能的实现,以TreeCtrl界面为视图界面,通过成员函
数GetTreeCtrl可以获得CTreeCtrl的引用。
●CListView类,类似CTreeV iew类,以ListCtrl界面为视图界面,通过成员函数GetListCtrl
可以获得CListCtrl的引用。
●CEditView类,利用CEdit接收用户输入,实现类似编辑控件的功能,通过成员函数
GetEditCtrl可以获得CEdit的引用。
●CRichEditView类,作为Rich Text Edit的视图类,提供可以按照格式显示文本的能力,
在使用时需要CRichEditDoc的支持。
●CFormView类,提供用户在资源文件中定义界面的能力,并可以将子窗口和变量进行
绑定,通过UpdateData()函数让数据在变量和子窗口之间进行交换。
UpdateData(true); //控件的value->成员变量
UpdateData(false); //控件的value<-成员变量
(3)windows图形环境介绍
C++.NET所编写的Windows的应用程序通常在视图类OnDraw函数中添加绘图代码来完成图形生成。
OnDraw函数是CView类的虚拟成员函数,它在CView类的派生类中被重新定义,在接到WM_PAINT消息后就会通过消息映射函数OnPaint调用它。
WM_PAINT消息是在某个视图窗口需要重画或刷新其显示内容时发出的。
如果程序的数据被改变,则可以调用视图的Invalidate成员函数,并最终调用OnDraw函数来完成绘图。
(4)设备上下文(Device Context, DC)
图形的输出设备有显示器、打印机、绘图仪等,为了实现图形输出与设备无关,Windows 应用程序使用图形设备接口(GDI)进行图形编程。
GDI给Windows提供全部绘图函数,这些函数会自动参考被称为设备上下文DC(Device Context)的数据结构,而Windows则自动将设备上下文映射到具体的物理设备上实现图形输出。
DC也称为设备描述表,是GDI中的重要的组成部分,是一种数据结构,它定义了一系列图形对象以及图形对象的属性和图形输出的图形模式。
图形对象包括画线的画笔、用于填充图形的画刷、位图和调色板等。
DC表示物理设备的逻辑形式,Windows和MFC类库提供了四种类型的设备上下文:A.显示设备上下文Display DC:将显示信息输出到视频显示器。
B.打印设备上下文Printer DC:将显示信息输出到打印机。
C.内存设备上下文Memory DC:为特定的设备保存位图图像。
D.信息设备上下文Information DC:用于访问默认设备数据信息的设备上下文。
(5)绘图句柄(Handler Device Context,HDC)
设备上下文不能被应用程序直接存取,只能通过调用句柄(HDC)来间接地存取设备上下文及其属性。
MFC类库提供了不同类型的设备上下文的类,每一个类都封装了代表Windows设备上下文的句柄(HDC)和函数。
A.CDC类:
是设备上下文的基类,其它的设备上下文类都是CDC的派生类。
CDC类非常庞大的,包含170多个成员函数和数据成员。
利用它可以访问整个显示设备和其它输出设备。
B.CPaintDC类:
是OnPaint()函数使用的设备上下文类,代表了窗口的绘图画面。
C.CClientDC类:
是窗口客户区的设备上下文类,代表了客户区窗口的绘图画面。
D.CWindowDC类:
类是整个窗口区域的设备上下文类,整个窗口区域即包括客户区又包括非客户区,即允许用户在显示器屏幕的任何地方绘图,包括窗口边框、标题区域。
E.CMetaFileDC类:
用于创建一个Windows图元文件的设备上下文。
Windows图元文件包含了一系列GDI绘图命令。
(6)绘图上下文(Rendering Context,RC)
OpenGL的所有绘图操作是针对RC的。
但RC不能直接完成绘图,必须与特定的DC相联系。
在绘图时,OpenGL将绘图参数传递给RC,RC再将其经过一定的变化后,传递给与它联系的DC,完成绘图操作。
Windows下的窗口和DC都支持像素格式(PIXELFORMA T)属性,和RC有位图结构上的一致。
因此,只要在创建RC时,与一个DC建立联系,OpenGL的函数就可以通过RC对应的DC画到相应的显示设备上。
OpenGL的绘图上下文的类型为HGLRC。
A.初始化RC的步骤:
①创建像素格式(PIXELFORMA TDESCRIPTOR)的DC ;
CClientDC dc(this);
int pf=ChoosePixelFormat(dc.m_hDC,&pfd);//为DC选择像素格式
BOOL rt =SetPixelFormat(dc.m_hDC,pf,&pfd);//为DC设置像素格式
②创建符合DC的RC
hglrc=wglCreateContext (dc.m_hDC );//创建符合DC的RC
B.使用RC的步骤:
①获得DC
HWND hWnd=GetSafeHwnd();//获得窗口句柄
HDC hDC=::GetDC(hWnd);//获得窗口对应的DC的句柄
②建立RC与DC联系
wglMakeCurrent(dc.m_hDC ,hglrc);
C.释放RC
①获得当前RC的句柄
wglGetCurrentContext();
②释放RC使用的DC
wglMakeCurrent(NULL,NULL);//将RC置为空,并且与DC的联系断开
③删除RC
wglDeleteContext(hglrc);//删除RC
D.OpenGL程序框架
●程序初始化:清屏、状态设置
●场景的绘制
●窗口大小变化时场景的重绘
(7)涉及绘画的OpenGL函数
●设置视图
void glViewport(GLint x,GLint y, GLsizei width, GLsizei height);
这里:x,y是视图左下角, 像素. 缺省为(0,0);
width, height是视图的宽度和高度
规范化坐标(x nd, y nd)与窗口坐标(x w, y w)的转换:
x w=( x nd+1)(width/2)+x
y w=(y nd +1)( height/2)+y
●设置当前的矩阵模式
void glMatrixMode( GLenum mode);
这里:mode
GL_MODELVIEW用于几何变换的模视矩阵模式.
GL_PROJECTION 用于坐标投影的投影矩阵模式.
GL_TEXTURE 用于纹理投影的纹理矩阵模式
●设置视点
在GL_MODELVIEW模式下设置视点,默认视点为:eye(0,0,0),center(0,0,-1),up(0,1,0)
void gluLookAt (
GLdouble eyex, GLdouble eyey, GLdouble eyez, //视点位置
GLdouble centerx,GLdouble centery,GLdouble centerz, //观察点
GLdouble upx,GLdouble upy,GLdouble upz //视点向上的方向
);
●设置单位矩阵
void glLoadIdentity(void);
●压入、弹出当前矩阵堆栈
void glPushMatrix(void);void glPopMatrix(void);
●交换缓冲区
BOOL SwapBuffers( HDC hdc );
●设置背景颜色
void glClearColor(GLclampf red,GLclampf green,GLclampf blue,GLclampf
alpha);
●初始化缓冲区为设定的值
void glClear(GLbitfield mask );
这里:mode
GL_COLOR_BUFFER_BIT 用glClearColor()设置的颜色值,填充初始化颜色缓冲区GL_DEPTH_BUFFER_BIT 用glClearDepth()设置的值,填充初始化深度缓冲区
GL_ACCUM_BUFFER_BIT 初始化累加缓冲区
GL_STENCIL_BUFFER_BIT 初始化模板缓冲区
例如:glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
●设置当前色
void glColor3b(GLbyte red,GLbyte green,GLbyte blue );
●定义一组顶点集,用于绘制图元
void glBegin(GLenum mode);void glEnd(void);
这里:mode
●
void glVertex2d( GLdouble x, GLdouble y);
void glVertex3f(GLfloat x, GLfloat y,GLfloat z);
●强制执行
void glFlush( void);
(8)图案填充开、关
glEnable(GL_POLYGON_STIPPLE);//启动多边形点画式样
glDisable(GL_POLYGON_STIPPLE); //关闭多边形点画式样
(9)置多边形点画式样
void glPolygonStipple(const GLubyte *mask);
这里mask:指向32x32的点画式样数组
2、实验步骤
步骤1:通过向导,创建项目MyDemo。
步骤2:设置项目的属性
a)设置单文档
b)取掉打印和打印预览
c )生成若干个类
应用程序向导将自动生成四个类:
1) CMyDemoApp 应用程序类;
2) CMyDemoDoc 文档类;
3) CMyDemoView 视图类;
4) CmainFrame 主框架类。
步骤3:添加OpenGL 静态库
步骤4:视图类(CMyDemoView)添加成员变量和成员函数
public:
HGLRC hglrc;//绘图上下文句柄
public:
void DrawScene(void);//在此函数内完成绘图操作
opengl32.lib
glu32.lib
glaux.lib
a )添加绘图上下文句柄成员变量:HGLRC hglrc;
b )添加成员函数:void DrawScene(void); 步骤5:为视图类(CMyDemoView)添加消息响应函数
OnCreate()、OnDestroy()、OnPaint()、
OnSize()
绘图上下文句柄
步骤6:修改框架类(CMainFrame)中的PreCreateWindow()函数,定义窗口的大小、标题等BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{ if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
// TODO: 在此处通过修改 CREATESTRUCT cs 来修改窗口类或样式
cs.cx=500;
cs.cy =400;
cs.lpszName="谢老师的绘图例子";
cs.style &=~FWS_ADDTOTITLE ;
return TRUE;
}
步骤7:修改视图类(CMyDemoView)的头文件MyDemoView.h
#include <gl\gl.h> // 对应库文件Opengl32.lib
#include <gl\glu.h> // 对应库文件Glu32.lib
#include <gl\glaux.h> // 对应库文件Glaux.lib
步骤8:修改视图类(CMyDemoView)的OnCreate()函数
定义像素存储格式,并创建一个OpenGL操作所必须的绘图上下文RC(Rendering Context)。
使用一个PIXELFORMA TDESCRIPTOR结构来指定像素格式,使用wglCreateContext()函数创建绘图上下文RC。
int CMyDemoView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
// TODO: 在此添加您专用的创建代码
PIXELFORMATDESCRIPTOR pfd={
sizeof(PIXELFORMATDESCRIPTOR), //像素的尺寸
1, //版本号
PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER ,
//pfd必须支持窗口绘制|支持OpenGL|支持双缓存
PFD_TYPE_RGBA , //像素格式为RGBA
24, //颜色深度
0,0,0,0,0,0, //忽略颜色位数
0, //无Alpha缓存
0, //忽略偏移位
0, //无累计移位
0,0,0,0, //忽略累计缓存位
32, //深度缓存位
0, //无模板缓存
0, //无辅助缓存
PFD_MAIN_PLANE , //主要控制层
0, //保留位
0,0,0 //忽略层遮罩
};
CClientDC dc(this);
int pf=ChoosePixelFormat(dc.m_hDC,&pfd);//为DC选择像素格式
BOOL rt =SetPixelFormat(dc.m_hDC,pf,&pfd);//为DC设置像素格式
hglrc=wglCreateContext (dc.m_hDC );//创建符合DC的RC
return 0;
}
步骤9:修改视图类(CMyDemoView)的OnDestroy()函数
在OnDestroy成员中需要释放OnCreate成员中RC所占用的资源,命令wglDeleteContext 可以完成这个工作,但在释放RC之前,还需要使用命令wglMakeCurrent()断开RC与设备描述表DC的连接。
void CMyDemoView::OnDestroy()
{
// TODO: 在此处添加消息处理程序代码
CView::OnDestroy();
if(wglGetCurrentContext()!=NULL)//获得当前RC的句柄
wglMakeCurrent(NULL,NULL);//释放RC使用的DC
if(hglrc!=NULL)//若RC不为空
{
wglDeleteContext(hglrc);//删除RC
hglrc =NULL;
}
}
步骤10:修改视图类(CMyDemoView)的OnSize()函数
当视图尺寸变化时,应及时将新的客户区尺寸通知OpenGL,方能够正确在窗口客户区域显示二维场景,通过命令glViewPort完成这项工作。
void CDemoGenericView::OnSize(UINT nType, int cx, int cy)
{
// TODO: 在此处添加消息处理程序代码
GLsizei w=cx;
GLsizei h=cy ;
if(!h) return;
HWND hWnd=GetSafeHwnd();//获得窗口句柄
HDC hDC=::GetDC(hWnd);//获得窗口对应的DC的句柄
wglMakeCurrent(hDC ,hglrc);//使得RC与当前线程联系起来
glViewport(0,0,w,h);//设置一个视图,与窗口大小对应
glMatrixMode (GL_PROJECTION ); //投影模式
glLoadIdentity ();//单位投影矩阵
glMatrixMode (GL_MODELVIEW ); //模视模式
glLoadIdentity ();//单位模视矩阵
wglMakeCurrent (NULL,NULL);//将RC(hglrc)与DC的联系断开
}
步骤11:修改视图类(CMyDemoView)的OnPaint()函数,调用DrawScene()函数,显示三角形图形
void CMyDemoView::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: 在此处添加消息处理程序代码
DrawScene();//显示一个三角形图形
}
void CMyDemoView:: DrawScene(void)
{
//初始化屏幕
HWND hWnd=GetSafeHwnd();//获得窗口句柄
HDC hDC=::GetDC(hWnd);//获得窗口对应的DC的句柄
wglMakeCurrent(hDC ,hglrc);//使得RC与当前线程联系起来
glClearColor(1.0f,1.0f, 1.0f, 1.0f);//清除颜色缓冲区,并初始化为白
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );//将颜色缓冲区设置为当前颜色
//**********************************************************
//在此处添加代码进行绘图操作
gluLookAt(0.0,0.0,0.0,0.0,0.0,-1.0,0.0,1.0,0.0);//设置视点
glColor3f(1.0f,1.0f,0.0f);//设置颜色缓冲区为白色
glBegin(GL_TRIANGLES);
glColor3ub(255, 0, 0); // 定义上顶点为红色
glVertex3f(0.0f, 1.0f, 0.0f); // 绘制上顶点
glColor3ub(0, 255, 0); // 定义左下顶点为绿色
glVertex3f( -1.0f, -1.0f, 0.0f);// 绘制左下顶点
glColor3ub(255, 255, 0); // 定义右下顶点为黄色
glVertex3f(1.0f, -1.0f, 0.0f); // 绘制右下顶点
glEnd();
//**********************************************************
glFlush();//强制执行
wglMakeCurrent(NULL ,NULL);//将RC(hglrc)与DC的联系断开
SwapBuffers(hDC);//交换前后缓冲区
}
步骤12:定义菜单、视图类(CMyDemoView)添加成员变量isPattern
步骤13:为视图类(CMyDemoView)添加菜单事件处理函数OnPattern()和OnUpdatePattern()
步骤14:在MyDemoView.cpp全局区定义填充图案
步骤15:修改DrawScene(),填充三角形
3、实验效果
//定义填充图案 GLubyte pattern[]={ 0x00,0x01,0x80,0x00,0x00,0x03,0xc0,0x00, 0x00,0x07,0xe0,0x00,0x00,0x0f,0xf0,0x00, 0x00,0x1f,0xf8,0x00,0x00,0x3f,0xfc,0x00, 0x00,0x7f,0xfe,0x00,0x00,0xff,0xff,0x00, 0x01,0xff,0xff,0x80,0x03,0xff,0xff,0xc0, 0x07,0xff,0xff,0xe0,0x0f,0xff,0xff,0xf0, 0x1f,0xff,0xff,0xf8,0x3f,0xff,0xff,0xfc, 0x7f,0xff,0xff,0xfe,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0x7f,0xff,0xff,0xfe, 0x3f,0xff,0xff,0xfc,0x1f,0xff,0xff,0xf8, 0x0f,0xff,0xff,0xf0,0x07,0xff,0xff,0xe0, 0x03,0xff,0xff,0xc0,0x01,0xff,0xff,0x80, 0x00,0xff,0xff,0x00,0x00,0x7f,0xfe,0x00, 0x00,0x3f,0xfc,0x00,0x00,0x1f,0xf8,0x00, 0x00,0x0f,0xf0,0x00,0x00,0x07,0xe0,0x00,
0x00,0x03,0xc0,0x00,0x00,0x01,0x80,0x00};
4、实验思考
(1) 定义点划(- - - - - - - - - -)的图案,则32x32的点画式样数组是什么? (2) 简述如何将绘图参数传递给RC ,再传递给DC ,以完成绘图操作。
实验2 橡皮筋技术的实现
一、实验目的
1.通过键盘输入,控制折线或多边形显示;
2.通过鼠标操作,实现橡皮筋显示;
3.输出提示字符串。
二、使用的工具软件及环境
C++.NET、OpenGL
三、实验内容
1、定义PointNode结构和链表数据结构,以及添加、清除等操作;
2、键盘输入”C”(或”c”)或其他字符,控制isPolygon为true或false,显示多边形或折线。
3、通过鼠标单击,确定折线或多边形的顶点坐标点;
4、通过鼠标移动,实现折线或多边形的橡皮筋显示。
5、通过鼠标右键,结束显示
6、输出提示字符串。
四、实验指导
1、基本要素
(1)获得设备对象
CDC* pDC=GetDC();//获得设备对象
(2)利用设备对象输出文本
CPen newpen(PS_SOLID,1,RGB(255,0,0));//定义画实线的红笔
CPen *old=pDC->SelectObject(&newpen);//保存旧的笔,替换新的笔
pDC->Rectangle(CRect(15,winHeight -80,winWidth-15,winHeight-10));//窗口
pDC->TextOut(20,winHeight-70,"单击鼠标左键,确定顶点的位置;单击右键,结束显示");
pDC->TextOut(20,winHeight-50,"移动鼠标,橡皮筋显示");
pDC->TextOut(20,winHeight-30,"键盘输入C或c,显示多边形;输入其他字符,显示折线");
pDC->SelectObject(old);//恢复旧的笔
(3)判断键盘输入
键盘消息处理:void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
鼠标消息处理:void OnLButtonDown(UINT nFlags, CPoint point)
void OnRButtonDown(UINT nFlags, CPoint point)
void OnMouseMove(UINT nFlags, CPoint point)
其中:nChar,键盘输入字符的ASCII码;
point,鼠标位置
nFlags和位屏蔽的位与,可以判断鼠标或键盘输入时是否按下辅助键。
例如:if(nFlags & MK_SHIFT) //鼠标或键盘输入时按下Shift键
2、实验步骤
步骤1:在stdafx.h中定义结构PointNode
步骤2:在视图类(CMyDemoView)中添加一些成员变量
步骤3:为视图类(CMyDemoView)添加以下成员函数:
addPoint()、clearPoint()、showPrompt()、DrawScene()
a)添加成员函数
b)addPoint()成员函数
c)clearPoint()成员函数
d)showPrompt()成员函数
e )DrawScene()成员函数
步骤4:添加视图类(CMyDemoView)的以下消息处理:
OnCreate()、OnSize()、OnPain()、OnKeyDown()、OnLButtonDown()、OnRButtonDown()、OnMouseMove()
a)定义消息所对应的事件过程OnKeyDown()、OnLButtonDown()
b)OnSize()消息处理
c)OnPaint()消息处理
d)OnKeyDown()消息处理
e)OnLButtonDown()消息处理
d)OnRButtonDown()、OnMouseMove()消息处理3、实验效果
4、实验思考
(1)修改程序,实现2点定矩形的橡皮筋显示。
(2)辅助键Shift 控制矩形是正方形或长方形。
实验3 基本变换
一、实验目的
1、通过对话框获得基本变换的参数;
2、通过键盘调整坐标系的显示,观察在正交变换、透视变换下的投影图;
3、以三角形、水壶实体为例,完成缩放或旋转或平移的操作;
二、使用的工具软件及环境
C++.NET、OpenGL
三、实验内容
1、添加对话框,并产生类;
2、通过对话框,获得基本变换的参数和投影变换;
3、在正交投影或透视投影下,通过键盘调整坐标系的显示,以水壶实体为例,完成缩放
或旋转或平移的操作。
四、实验指导
1、基本要素
(1)通过分屏对象(CSplitterWnd)显示用户自定义的对话框和视图
AfxGetApp();
获得应用对象的指针,通过应用对象的成员变量m_pMainWnd,可获得框架指针。
分屏对象的方法:
●BOOL CreateStatic(CWnd* pParentWnd,int nRows, int nCols, DWORD dwStyle, UINT nID);
将框架拆分为行、列组成的面板,这里dwStyle可以是WS_CHILD|WS_VISIBLE
●BOOL CreateView(int row, int col,CRuntimeClass* pViewClass,
SIZE sizeInit, CCreateContext* pContext);
设置某行、某列的面板大小、所对应的视图
●CWnd* GetPane(int row, int col);
获得某行、某列的面板所对应的视图指针
(2)旋转变换
void glRotated(GLdouble angle,GLdouble x,GLdouble y,GLdouble z);
这里angle:表示对象从标点(x,y,z)到原点的方向,以逆时针旋转的角度(度为单位)(3)比例变换
void glScaled(GLdouble x,GLdouble y,GLdouble z);
这里x,y,z是3个轴的缩放比例因子。
(4)平移变换
void glTranslated(GLdouble x,GLdouble y,GLdouble z);
这里x,y,z是3个轴的平移量。
(5)设置投影变换
●平行投影
void glOrtho(GLdouble left,GLdouble right,GLdouble bottom,
GLdouble top,GLdouble zNear,GLdouble zFar);
透视投影
void gluPerspective(GLdouble fovy,GLdouble aspect,
GLdouble zNear,GLdouble zFar);
void gluPerspective(GLdouble left,GLdouble right,GLdouble bottom,
GLdouble top,GLdouble near,GLdouble far);
近平面距离-zNear
远平面距离-zFar
2、实验步骤
步骤1:添加对话框IDD_DIAOLOG1,并设置其属性style为child。
步骤2:编辑对话框
步骤3:为对话框IDD_DIAOLOG1定义新类MyDemoControl ,其基类是CFormView 。
在MyDemoControl.h文件的开始处添加如下代码:
#include "MainFrm.h"
#include "MyDemoView.h"
#include "MyDemoDoc.h"
#define INFOBAR_SIZE 250//控制面板的宽度
步骤4:为主框架类(CMainFrame)添加成员变量,并在CMainFrame的构造函数中初始化。
步骤5:在MainFrame.cpp文件的开始处添加如下代码:
#include "MyDemoControl.h"
#include "MyDemoView.h"
步骤6:为主框架类(CMainFrame)重写虚函数OnCreateClient()和PreCreateWindow()。
a) PreCreateWindow ()虚函数
b) OnCreateClient()虚函数
步骤7:为主框架类(CMainFrame)添加消息处理函数OnSize()。
步骤8:在MyDemoView.h文件的开始处添加如下代码:
#include <gl\gl.h> // 对应库文件Opengl32.lib
#include <gl\glu.h> // 对应库文件Glu32.lib
#include <gl\glaux.h> // 对应库文件Glaux.lib
class CMyDemoDoc;
步骤9:为视图类(CMyDemoView)添加成员变量,并修改视图类(CMyDemoView)的构造函数,对成员变量初始化。
步骤10:修改视图类(CMyDemoView)的消息处理。
a)为视图类(CMyDemoView)添加消息处理函数
b)OnPaint()、OnSize()、OnKeyDown()消息处理函数
其他消息处理函数(OnCreate()、OnDestroy()、OnPattern()、OnUpdatePattern())见实验1。
步骤11:为文档类(CMyDemoDoc)添加成员变量,并修改视图类(CMyDemoDoc)的构造函数,对成员变量初始化。
步骤12:为对话框类(MyDemoControl)添加成员函数GetMyDemoView ()。
步骤13:为对话框(IDD_DIAOLOG1)的各控件定义控件成员变量、值成员变量和事件处理(表3_1)。
表3_1 对话框(IDD_DIAOLOG1)的各控件定义成员变量
步骤13:为对话框(MyDemoControl)的正交复选控件(IDC_CHECK4)添加消息处理函数OnBnClickedCheckrdOrtho()。
为对话框(MyDemoControl)的缩放关联控件(IDC_CHECK1)添加消息处理函数OnBnClickedCheck1()、OnBnClickedCheckRotation()、OnBnClickedCheckTranslate()。
OnBnClickedCheckRotation()、OnBnClickedCheckTranslate()的代码类似。
步骤14:修改对话框类(MyDemoControl)的消息处理函数OnHScroll()。
步骤15:为对话框类(MyDemoControl)修改虚成员函数OnInitialUpdate(),完成控件属性初始化。
步骤16:为视图类(CMyDemoView)添加成员函数。
a) 为视图类(CMyDemoView)添加成员函数
b) SetContext()、SwapGLBuffers()成员函数
c) DrawCoordinate()成员函数
d) showPrompt()成员函数
e) DrawTriAngle()成员函数
f) GeometryTransform()成员函数
g) DrawScene ()成员函数
3、实验效果
a)在透视投影下,近大远小
b )在正交投影下,大小不变
图3.1 几何变换 4、实验思考
(1)简述如何将框架中的左面板的对话框输入的参数,传递给右面板的视图并触发重绘事件。
(2)简述正交投影、透视投影的特点。
实验4 拾取
一、实验目的
在选择模式下,通过鼠标拾取图元,选中的图元开始自转,并按照控制面板上的参数完成几何变换。
二、使用的工具软件及环境
C++.NET、OpenGL
三、实验内容
1、定义TeapotNode结构和链表数据结构,保存3个水壶的几何参数;
2、在选择模式下,通过鼠标确定拾选区,并获得选中的图元的ID;
3、在实验3的基础上,单击鼠标,启动定时器,再单击鼠标,终止定时器;
4、定义定时器,每个500毫秒发出WM_TIMER消息,触发OnTime()消息处理,使得选中的图
元开始自转;
5、根据选中的图元的ID,从TeapotNode链表中获得该图元的几何参数,并更新控制面板;
6、选中的图元按照控制面板上的参数完成几何变换。
四、实验指导
1、基本要素
(1)三种不同的模式
在OpenGL中有三种不同的渲染模式——绘图模式(缺省模式),选择模式和反馈模式。
在选择和反馈模式下,屏幕将保持冻结(不进行图形绘制),绘图信息将返回给应用程序,而不是像在绘图模式下那样发送到帧缓冲区。
切换模式的方法:
GLint glRenderMode(GLenum mode);切换模式, 当退出选择或反馈模式时,OpenGL
会把命中记录写入选择数组,清空名称堆栈。
这里mode: GL_RENDER(默认)、GL_SELECT和GL_FEEDBACK;
返回值:返回选择或反馈数组中的值的个数,负值表示溢出。
(2)选择模式
在选择模式下,OpenGL自动得出窗口的指定区域内绘制的是哪些图元,并对图元作可见与非可见的判断,若图元处于可视区域内,则向选择缓冲区添加一条命中记录。
当渲染模式切换回图形绘制,可以通过名称堆栈,获取选中物体的名字信息,并读取选择缓冲区中的命中记录。
选择缓冲区是一个整型数组,由若干条命中记录构成,每条命中记录由四部分数据构成:
a.命中发生时,名字堆栈中名字的数量;
b.可视区域内图元所有顶点的最小、最大z值,该值进行了缩放,取值范围在0.0~1.0
之间;
c.名字堆栈的名字,最底部名称在名称堆栈的最顶层。
选择模式的方法:
●void glSelectBuffer(GLsize size,GLuint *buffer);定义选择数组
(3)反馈模式
OpenGL执行一般的绘制计算,计算的结果不直接在屏幕上绘制图元,而是返回绘图信息(图元类型、图元的顶点、颜色、…),这些信息送回到由标记组成的反馈数组中。
反馈模式的方法:
●void glFeedbackBuffer( GLsizei size, GLenum type, GLfloat *buffer); 建立反馈缓冲
区。
这里type:GL_2D, GL_3D, GL_3D_COLOR, GL_3D_COLOR_TEXTURE, and
GL_4D_COLOR_TEXTURE.
●void glPassThrough(GLfloat token);向反馈数组插入用户定义标记。
(4)定义拾选区域
在选择模式,使用gluPickMatrix函数创建一个新的矩阵,用于描述新的可视区域,如图所示。
其中,P点为当前鼠标选中位置,须事先将其由屏幕坐标转换成OpenGL坐标值,视景体最远最近面的位置可由函数直接定义。
图4.1 新的可视区域
定义拾取区的方法:
●void gluPickMatrix(GLdouble x, GLdouble y,GLdouble width, GLdouble
height,Glint viewport[4]);定义拾取区域
这里x,y:拾取区域的中心(通常为光标位置);
width,height:拾取区域的宽、高;
viewport[4]:当前视区的边界,可以通过以下函数获得。
glGetIntegerv(GL_VIEWPORT, Glint *viewport);
(5)名称堆栈
名字堆栈允许在绘图图元或图元组时,使用一个无符号的整数进行标识(在选择模式下),每当一个图元被命名时,就用glPushName函数将一个无符号整数(即,图元的名字)压入到名字堆栈,或者用glLoadName函数替换当前位于名字堆栈顶部的名字。
对于由多个图元构成的图形,构成图形的所有图元共享同一个名称,每个名称只产生一个命中。
名称堆栈的方法:
●void glInitNames(void);初始化名称堆栈
●void glPushName(GLuint name); 将name压入名称堆栈
●void glPopName(void); 从名称堆栈弹出名称
●void glLoadName(GLuint name);用name替换栈顶元素
2、实验步骤。