使用OpenGL 来做三维测量与重建

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

使用OpenGL来做三维测量与重建
1 前言:
在计算机图形图像处理中,由于OpenGL库带来的便利,使得其取得了越来越广泛的应用.本文旨在通过一个小的应用实例,介绍了OpenGL的基本编程思路, 希望这些知识和经验能有助于OpenGL的推广。

2 OpenGL简介:
OpenGL(Open Graphics Library)是独立于操作系统和硬件环境的三维图形软件库. 由于其开放性和高度的可重用性,目前已成为业界标准. 很多优秀的软件都是以它为基础开发出来的,象著名的产品有动画制作软件3DMAX ,Soft Image,VR软件和GIS软件等等。

OpenGL认可开发人员对真实世界中的二维图形和三维几何物体的描述,而且将这些几何形体绘制到三维图形加速卡的帧缓存中,可用一个简单的工作流程图来描述OpenGL的工作原理。

图2 OpenGL工作流程
2.1 OpenGL的主要功能有:
1)几何建模:在OpenGL中提供了绘制点,线,多边形等基本形体的函数,还提供了绘制复杂三维曲线,曲面(如Bezier,Nurbs等)和三维形体(如球,锥体和多面体等)的函数.由于OpenGL是以顶点为图元,由点构成线,由线及其拓扑结构构成多边形.所以应用这些建模函数,可构造出几乎所有的三维模型。

2)坐标变换:包括取景变换,模型变换,投影变换和视区变换。

3)颜色模式设置:RGBA模式和颜色索引模式。

4)光照和材质设置:可设置四种光,即辐射光,环境光,镜面光和漫反射光.材质用模型表面的反射特性表示。

5)图像功能:提供像素拷贝和读写操作的函数,还提供了反走样,融合和雾化等,以增强图像效果
6)纹理映射: OpenGL的纹理映射功能可十分逼真地再现物体表面的细节。

7)实时动画:利用OpenGL的双缓存(Double Buffer)技术可获得平滑逼真的动画效果。

8)交互技术:方便的三维图形交互接口(选择,拾取,反馈),可进行人机交互操作。

2.2 Win32下OpenGL的运行机制:
由于OpenGL的作用机制是客户(Client)服务器(Server)机制,由客户(用OpenGL绘制图形的应用程序)向服务器(OpenGL内核)发送OpenGL命令,服务器则负责解释这些命令。

在多数情况下,客户和服务器在同一机器上运行,当然,也可以在网络环境下使用,所以OpenGL具有网络透明性,与在Win32下的图形设备接口(GDI)把图形函数库封装在动态的连接库GDI32.DLL内一样,OpenGL的图形库也被封装在一个动态连接库opengl32.dll内。

从客户应用程序发布的对OpenGL函数的调用首先被opengl32.dll处理,然后传送给服务器后,被Winsrv.dll进一步处理,再传给Win32设备驱动接口(DDI),最后把处理过的图形命令送给视频显示驱动程序。

6) OpenGL结构共有4个,列举如下: GLYPHMETRICSFLOAT LAYERPLANEDESCRIPTOR PIXELFORMATDESCRIPTOR POINTFLOAT
3 VC环境下的实现:
背景:对三维物体进行可视化编辑,即增删改点,面操作.模型为用VRML表示的一个人头像.流程:数据准备à初始化绘制环境à模型的绘制à交互操作,点和面的编辑à导出数据
3.1 数据准备
1) 从指定的图象文件中测量出的特征的点数据及面数据,处理后存为input.dat文本文件,点由三维坐标表示,面则统一为三角型面片,由三个点索引组成,格式如下:
##Vertex//点数据,点的三维坐标;
#skin//特征名称;
0.0000 -31.9007 487.6600//点坐标
31.0000 -28.3430 470.4640
63.0000 -29.9017 455.9880
……
#facet//面数据,相关点的索引;
#skin//特征名称;
0 1 16//点的索引;
1 15 16
16 15 13
……
其中特征分为(skin,tongue,teeth,ear,lip,eye,mouth,nose,eyebrow);
2)从input.dat文件中读入点和面数据,存入程序结构中(PointNode,FaceNode),同时建立图元(点和面)的显示列表,为绘制模型做好准备。

typedef struct pt_node
{
char pZT;//点的状态,(’V’--有效;’I’--无效),为后续编辑作准备;
double pt[3];//点的三维坐标;
}PointNode;
typedef struct fe_node
{
char fZT;//面的状态(’V’--有效;’I’--无效);
int Pt1Index;//面片中第一个Vertex的点索引;
int Pt2Index;//面片中第二个Vertex的点索引;
int Pt3Index;//面片中第三个Vertex的点索引;
}FaceNode;
void CDisplayView::DrawTriangle(int &i, GLdouble pt1[], GLdouble pt2[], GLdouble pt3[]) {//创建三角行面片的显示列表;
CalculateNormal(pt1,pt2,pt3,dNormal);//首先计算该面的法向量;
glNewList(i,GL_COMPILE);//建立第i个显示列表;
glBegin(GL_TRIANGLES);
glNormal3dv(dNormal);//指定该面的法向量为dNormal;
glVertex3dv(pt1);
glVertex3dv(pt2);
glVertex3dv(pt3);
glEnd();
glEndList();
i++;
}
void CDisplayView::DrawPoint(int &n, GLdouble pt[])
{//创建点的显示列表;
glNewList(n,GL_COMPILE);
glBegin(GL_POINTS);
glVertex3dv(pt);
glEnd();
glEndList();
n++;
}
3.2初始化绘制环境
1)填充PIXELFORMATDESCRIPTOR 结构。

PIXELFORMATDESCRIPTOR pixelDesc = { ……};//按需要填充适当的值;
2)设置像素格式
m_GLPixelIndex = ChoosePixelFormat(hDC,&pixelDesc);
//确定设备上下文(DC)所支持的像素格式中,与给定像素格式描述符最为匹配的像素格式索引值;
if(m_GLPixelIndex ==0)
{
MessageBox(“ChoosePixelFormat failed!”);
return FALSE;//执行失败!
}
if(!SetPixelFormat(hDC,m_GLPixelIndex,&pixelDesc)) //将设备上下文(DC)中的像素格式return FALSE;//执行失败!设置为索引值m_GLPixelIndex所
指定的格式;
3)创建着色描述表
HWND hWnd = GetSafeHwnd();//得到Windows窗口的窗口句柄;
HDC hDC =::GetDC(hWnd);//得到设备上下文(DC)句柄;
if(MyFunction_SetWindowPixelFormat(hDC)==FALSE)//测试设置像素格式是否正确;
return FALSE;//测试失败;
hglrc = wglCreateContext(hDC);//建立hDC所关联的OpenGL绘图上下文(RC);
if(hglrc == NULL)
return FALSE;//创建失败;
if(wglMakeCurrent(hDC,hglrc)==FALSE)//将一个指定的绘图描述表变为正在调用线程return FALSE;//失败;的当前绘图描述表。

4)对灯光,材质等环境相关参数进行初始化
int CDisplayView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
……
glPolygonMode(GL_FRONT,GL_FILL);//多边型的正面为填充模式;
glPolygonMode(GL_BACK,GL_FILL);//多边形的背面为填充模式;
glShadeModel(GL_SMOOTH);//明暗模式为平滑型;
glEnable(GL_DEPTH_TEST);//使能深度消隐;
glEnable(GL_NORMALIZE);//使能法向量单位化;
glFrontFace(GL_CW);//设置朝向正面的多边型的顶点环绕方向为顺时针方向;
GLfloat ambientProperties[] ={0.15f,0.15f,0.15f,1.0f};//设置环境光分量; GLfloat diffuseProperties[] ={0.7f,0.7f,0.7f,1.0f};//设置漫反射光分量;
GLfloat specularProperties[] ={0.7f,0.7f,0.7f,1.0f};//设置镜面反射光分量;
GLfloat position[] = {1.0,1.0,-5.0,0.0};//设置光源位置;
glClearDepth(1.0f);//设置深度缓冲的值为1;
//把各光分量的值赋给光源0;
glLightfv(GL_LIGHT0,GL_AMBIENT,ambientProperties);
glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuseProperties);
glLightfv(GL_LIGHT0,GL_SPECULAR,specularProperties);
glLightfv(GL_LIGHT0,GL_POSITION,position);
glEnable(GL_LIGHTING);//使能照明效果;
glEnable(GL_LIGHT0);//使能灯光0;
}
5)场景布置
为了将物体放置在场景中合适的位置,需要使用一些OpenGL的变换操作。

OpenGL变换有基本变换和投影变换两种,基本变换是对三维物体进行平移、旋转、缩放等。

投影变换则把物体从三维投影到二维。

视区则决定物体的投影图像在显示设备中的尺寸和位置。

OpenGL中的多数变换均对应于相应的变换矩阵,可以说就是实现将物体的各个顶点通过各种变换矩阵的作用映射到屏幕的过程。

根据变化的类型,分为三种矩阵模式,GL_MODELVIEW对应于基本变换,GL_PROJECTION对应于投影变换,GL_TEXTURE对应于纹理矩阵类型。

具体应用见小节1,小节2。

1.对几何造型初始化
void CDisplayView::InitGeometry()
{//x,y,z方向的默认旋转角度;
m_xRotate = 0.0f;
m_yRotate = 0.0f;
m_zRotate = 0.0f;
//x,y,z方向默认的平移大小;
m_xTrans = 0.0f;
m_yTrans = -0.5f;
m_zTrans = -3.5f;
//x,y,z方向默认的缩放大小;
m_xScaling = 1.0f;
m_yScaling = 1.0f;
m_zScaling = 1.0f;
}
2.设置透视投影变换和视区
void DisplayView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
GLint w=cx;
GLint h=cy;
glViewport(0,0,w,h);//设置视区;
glMatrixMode(GL_PROJECTION);//表明下面的矩阵操作是关于投影变换矩阵的;
glLoadIdentity();//单位阵作为当前矩阵;
gluPerspective(60.0,h/w,1.0f,128.0);//设置透视投影矩阵;
glMatrixMode(GL_MODELVIEW);//表明下面的矩阵操作是关于模型-取景变换矩阵的
glLoadIdentity();//同上;
glPushMatrix();//把当前的变换矩阵压栈;
glTranslatef(m_xTrans,m_yTrans,m_zTrans);//x,y,z方向按给定实参值进行平移;
glRotatef(m_xRotate,1.0f,0.0f,0.0f);//x轴旋转m_xRotate度;
glRotatef(m_yRotate,0.0f,1.0f,0.0f); //y轴旋转m_yRotate度;
glRotatef(m_zRotate,0.0f,0.0f,1.0f); //z轴旋转m_zRotate度;
glScalef( m_xScaling,m_yScaling,m_zScaling ); //x,y,z方向按给定实参值进行缩放;
glPopMatrix();//原来的变换矩阵出栈;
glDrawBuffer(GL_BACK);//绘制操作在后台缓存;
}
3.3 模型的绘制
由于侧重模型的编辑操作,在绘制时并未计算顶点的法向量,仅仅求出了各个面片的法向量。

void CDisplayView::OnPaint()
{
CPaintDC dc(this);
glClearColor(0.0f,0.5f,0.5f,1.0f);//将显示窗口颜色设置为蓝绿色;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//用该色清除颜色,深度缓存;
……
DrawScene();//绘制模型,即调用显示列表;
……
SwapBuffers(dc.m_ps.hdc);//当前设备的前台缓存和后台缓存交换;
}
void CDisplayView::Drawscene()
{
for( int i = 0;i< TotalNum;i++ )
glCallList(i);//调用显示列表,该数据已在2.4.1节准备完毕;
}
3.4点和面的编辑
为了能够编辑点和面,必须首先在显示窗口中获取相关数据,即拾取到相应的点和面,然后才能对其进行正确的编辑操作。

一般的绘图程序通常只是在屏幕上绘制场景中物体的二维或三维静态图像,用户不能对其中的图形进行有效的交互。

为了方便人机交互,往往需要通过鼠标等设备来选定观察体积中的某个或某几个图元,以便对其进行进一步的操作。

这种在选择模式下确定选中物体的操作称为拾取操作。

选择模式下的拾取操作通过将一个特殊的拾取矩阵与投影矩阵相乘,从而将绘图操作限制在视口中鼠标附近的一个小区域内。

通过鼠标点击来激活选择模式,这样鼠标附近绘制的物体就会被选中,即确定了鼠标附近所绘制的物体名称。

1)拾取数据
实现如下:
void CDisplayView::OnLButtonDown(UINT nFlags, CPoint point)
{//拾取操作有鼠标的LBUTTONDOWN消息触发;
……
glGetIntegerv(GL_VIEWPORT,viewport);//得到当前的视口;
w=viewport[2];
h=viewport[3];
glSelectBuffer(512,buf);//指定返回的选中纪录所使用的数组;
glRenderMode(GL_SELECT);//进入选择模式;
glInitNames();//初始化名称栈;
glPushName(-1);//将-1压入栈;
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
gluPickMatrix( (GLdouble)x,(GLdouble)(viewport[3]-y),3.0,3.0,viewport);//定义拾取矩阵;
gluPerspective(60.0,h/w,1.0f,128.0);// 设置透视投影矩阵;
glMatrixMode(GL_MODELVIEW);
……
/*绘制场景并命名*/
glLoadName(i); //给说绘制的物体命名
glCallList(i);//调用显示列表,绘制图元(i);
……
hits=glRenderMode(GL_RENDER);//退出选择模式
if( hits )//处理命中信息
{
ResultName = GetSelectObject(buf,hits);//该函数返回命中图元的名称;
…… //主要处理当一次点击发生多次命中,只返回Z坐标值最小的那个图元
/*改变该图元的显示,重新绘制*/
glPointSize(1.2f);
glLineWidth(1.1f);
glColor3f( 1.0f,0.0f,0.0f);
绘制该图元;
……
}
}
2)编辑操作
2.1)增加,删除,修改一个点
根据用户输入的坐标值,将该点显示出来,观察其位置,确定之后保存在点的数据结构中,状态记为有效(V),此为增加操作。

删除则只改其状态,记为(I),重新绘制即可。

修改转化为删除和增加操作。

2.2)增加,删除,修改一个面
面的增,删,改操作同上。

3.5 导出数据
将最终满意的点,面数据以文件方式输出,格式与3.1节中使用的格式相同。

主界面如下:。

相关文档
最新文档