OpenGL(碎梦花事)
(参考《实战OpenGL三维可视化系统开发与源码精解》,吕希奎,周小平著)
一、OpenGL像素格式
1、像素格式设置
像素格式是OpenGL窗口的重要属性,它包括是否使用双缓冲,颜色位数和类型以及深度位数等。像素格式可由Windows系统定义的所谓像素格式描述子结构来定义(PIXELFORMATDESCRIPTOR),该结构定义在windows.h中(与BMP头文件BITMAPINFOHEADER相似,在windows.h中,结构体不需要单独定义)。
在该结构中包含有26个属性信息,其形式为:
typedef structtagPIXELFORMATDESCRIPTOR
{
WORD nSize; //该结构所占内存空间。
WORD nVersion;//版本号,当前为。
DWORD dwFlags;
/* PFD_DRAW_TO_BITMAP 支持内存中绘制位图
PFD_DRAW_TO_WINDOW 支持屏幕绘图
PFD_DOUBLEBUFFER 支持双缓冲
PFD_CENERIC_FORMAT指定选择GDI支持的像素格式
PFD_NEED_PALETTE 指定需要逻辑调色板
PFD_NEED_SYSTEM_PALETTE 指定需要硬件调色板
PFD_STEREONT 不支持
PFD_SUPPORT_OPENGL支持OpenGL
PFD_SUPPORT_GDI支持GDI,此时不可使用PFD_DOUBLEBUFFER
*/
BYTE iPixelType;//像素颜色模式,可选项为PFD_TYPE_RGBA或PFD_TYPE_INDEX,分别对应于RGBA模式和颜色索引模式。
BYTE cColorBits;//指定颜色的位数。
BYTE cRedBits; //采用RGBA模式时,红色组分占用位数
BYTE cRedShift;//采用RGBA模式时,红色组分占偏移量
BYTE cGreenBits;//采用RGBA模式时,绿色组分占用位数
BYTE cGreenShift;//采用RGBA模式时,绿色组分偏移量
BYTE cBlueBits; //采用RGBA模式时,蓝色组分占用位数
BYTE cBlueShift;//采用RGBA模式时,蓝色组分偏移量
BYTE cAlphaBits;//采用RGBA模式时,Alpha组分占用位数。
BYTE cAlphaShift;//采用RGBA模式时,Alpha组分偏移量。
BYTE cAccumBits;//指定累积缓冲区表示一个像素所用位数。
BYTE cAccumRedBits;//定累积缓冲区表示红色组分占用位数。
BYTE cAccumGreenBits;//指定累积缓冲区表示绿色组分占用位数。
BYTE cAccumBlueBits;//指定累积缓冲区表示蓝色组分占用位数。
BYTE cAccumAlphaBits;//指定累积缓冲区表示Alpha组分占用位数
BYTE cDepthBits;//指定深度缓冲区表示一个像素所用位数。
BYTE cStencilBits;//指定模板缓冲区表示一个像素所用位数。
BYTE cAuxBuffers;//指定辅助缓冲区,Windows9x、NT不支持
BYTE iLayerType;//Windows9x、NT下只能是PFD_MAIN_PLANE。
BYTE bReserved; //0
DWORD dwLayerMask;//指定覆盖层的屏蔽,Windows9x、NT不支持
DWORD dwVisibleMask;//Windows9x、NT不支持
DWORD dwDamageMask;//Windows9x、NT不支持
} PIXELFORMATDESCRIPTOR;
Windows提供了四个像素格式管理函数,分别介绍如下:
(1)int ChoosePixelFormat(HDChdc,PIXELFORMATDESCRIPTOR *ppdf)
该函数比较传过来的像素格式描述和OpenGL支持的像素格式,返回一个最佳匹配的像素格式索引。该索引值可传给SetPixelFormat为DC设置像素格式。返回值为0表示失败。
在比较像素格式时,匹配优先级顺序为像素格式描述子结构中的下述各域:
dwFlags->cColorBits->cAlphaBits->cAccumBits->cDepthBits->cStencilBits->cAuxBuffers->iLayerType硬件支持的像素格式优先。
(2)int DescribePixelFormat(HDC hdc,int iPixelFormat, UINT nBytes,LPPIXELFORMATDESCRIPTOR*ppfd)
该函数用格式索引iPixelFormat说明的像素格式来填写由ppfd所指向的像素格式描述子结构,利用该函数可以枚举像素格式。
(3)int GetPixelFormat(HDC hdc)
该函数用于获取hdc的格式索引。
(4) BOOL SetPixelFormat(HDC hdc, intiPixelFormat, LPPIXELFORMATDESCRIPTOR*ppfd)
该函数用格式索引iPixelFormat来设置hdc的像素格式。在使用该函数之前应该调用ChoosePixelFormat来获取像素格式索引。另外,OpenGL窗口风格必须包含WS_CLIPCHILDREN和WS_CLIPSIBLINGS类型,否则设置失败。
注意:
应该注意的是ChoosePixelFormat函数并不一定返回一个最佳的像素格式值,可以利用DescribePixelFormat来枚举系统所支持的所有像素格式。OpenGL的通常支持24种不同的像素格式,如果系统安装了OpenGL硬件加速器,它可能会支持其它的像素格式。
2、着色表描述(Rendering Context,RC)
任何一个Windows程序都必须处理设备描述表(Device Context),他告诉Windows怎样在一个窗口中显示图形信息一个设备描述表(DC)说明了笔和画刷的颜色、绘制模式、调色盘信息、映射模式、以及其他Windows必须知道的怎样显示图像的属性。与其他Windows程序一样,OpenGL应用程序应必须应用DC。不过我们将其称为着色描述表(Rendering Context,RC),有他通知Windows在窗口中绘制图形。每一个OpenGL都被两接到一个着色描述表上。着色描述表将所有的OpenGL命令连接到的设备描述表(Device Context,DC)上,应用程序必须在绘图之前调用专用函数wglCreateContext()创建自己的着色描述表,调用wlgMakeCurrent()使其当前化,退出OpenGL时使着色表非当前化。
3、设置像素格式
设置像素格式,首先要填充PIXELFORMATDESCRIPTOR结构,然后设置像素格式。
BOOL CMyDEM3DView::bSetupPixelFormat()
{
//填充PIXELFORMATDESCRIPTOR结构
static PIXELFORMATDESCRIPTORpfd =
{
sizeof(PIXELFORMATDESCRIPTOR), //像素格式描述符的大小
1, // 版本号
PFD_DRAW_TO_WINDOW | // 格式必须支持窗口
PFD_SUPPORT_OPENGL | // 格式必须支持OpenGL
PFD_DOUBLEBUFFER, // 必须支持双缓冲
PFD_TYPE_RGBA, // 申请RGBA 格式
24, // 选定色彩深度
0, 0, 0, 0, 0, 0, // 忽略的色彩位
0, // 无Alpha缓存
0, // 忽略Shift Bit
0, // 无聚集缓存
0, 0, 0, 0, // 忽略聚集位
32, // 32位Z-缓存(深度缓存)
0, // 无模板缓存
0, // 无辅助缓存
PFD_MAIN_PLANE, // 主绘图层
0, // 保留
0, 0, 0 // 忽略层遮罩
};
//设置像素格式
int pixelformat;
// Windows能否找到相应的象素格式?
if ( (pixelformat =ChoosePixelFormat(m_pDC->GetSafeHdc(), &pfd)) == 0 )
{
MessageBox("选择像素格式失败!");
return FALSE;
}
// 能否设置象素格式?
if (SetPixelFormat(m_pDC->GetSafeHdc(),pixelformat, &pfd) ==FALSE)
{
MessageBox("设置像素格式出错!");
return FALSE;
}
return TRUE;
}
注意:
一个应用程序只为一个窗口设置一次像素格式,并且一旦设置,在程序执行过程中就不能更改,而且,在创建着色描述表之前必须设置像素格式,否则着色描述表就不知道它该选择什么样的像素属性。
4、创建着色描述表
一般在视图类(Cview)的消息OnCreate()函数中创建图形操作描述表。
有两种方法创建着色描述表:
(1)通过响应Windows消息WM_CREATE来创建并当前化一个着色描述表,通过相应Windows消息WM_DESTORY来删除它
(2)创建和删除与(1)相同,但是只在每次应用OpenGL进行绘制前立即当前化,并且一旦此次绘制完成立即非当前化。
方法(1)只当前化一次着色描述表,减少了许多开销,加快了程序的执行,缺点是必须在整个应用过程中保留一个DC。方法(2)易于实现,但需要更多执行时间。
方法(1)实现:
int OnCreate(LPCREATESTRUCTlpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
//TODO: Add your specialized creation codehere
Init();
return 0;
}
void Init()
{
PIXELFORMATDESCRIPTOR pfd;
HGLRC hrc;
m_pDC = newCClientDC(this); //创建DC
ASSERT(m_pDC !=NULL);
if (!bSetupPixelFormat()) //设置像素格式
return;
hrc = wglCreateContext(m_pDC->GetSafeHdc());//用DC去创建一个RC
wglMakeCurrent(m_pDC->GetSafeHdc(),hrc);//RC与当前DC相关联
}
void CMyDEM3DView::OnDestroy()
{
HGLRC hrc;
hrc = wglGetCurrentContext();
wglMakeCurrent(NULL, NULL); //使当前着色表不再是当前的
if (hrc)
wglDeleteContext(hrc);//删除RC
if (m_pDC)
delete m_pDC; //删除当前View拥有的DC
CView::OnDestroy();
}
wglMakeCurrent(m_pDC->GetSafeHdc(),hrc)是着色描述表当前化,wglMakeCurrent()有两个参数,一是DC的句柄,二是着色描述表句柄。如果成功返回GL_TRUE,否则,返回GL_FALSE。
wglMakeCurrent(NULL,NULL)和wglMakeCurrent(m_pDC->m_hDC,NULL)是当前着色描述表不再是当前的。两者是完全一样的,只要第二个参数为NULL,则一定可以使当前着色描述表非当前化,且第一个参数自动被忽略。
二、基于单文档OpenGL框架(VS2008)
着色表采用方法(1)的模式,即RC和DC只在初始化时(Init)绑定一次,在OnDestroy时使RC非当前化并删除RC和DC。
该程序适用于单文档,只含有一个RC与DC,具有很高的时间效率。
该程序在OnSize中没有加入防止形变得内容。
1、配置OpenGL环境
配置OpenGL环境,添加.lib类库
2、添加头文件
在stdafx.h中添加头文件(放到其他文件中也可以)
#include <gl\gl.h>
#include <gl\glu.h>
3、添加变量
public:
CClientDC *m_pDC; //DeviceContext设备上下文
HGLRC m_hRC; //RenderingContext着色上下文
4、设定OpenGL风格
在PreCreateWindow中添加cs.style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
5、初始化
int OnCreate(LPCREATESTRUCTlpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
Init(); //初始化
return 0;
}
BOOL bSetupPixelFormat()
{
//填充PIXELFORMATDESCRIPTOR结构
staticPIXELFORMATDESCRIPTORpfd =
{
sizeof(PIXELFORMATDESCRIPTOR), //像素格式描述符的大小
1, //版本号
PFD_DRAW_TO_WINDOW | //格式必须支持窗口
PFD_SUPPORT_OPENGL | //格式必须支持OpenGL
PFD_DOUBLEBUFFER, //必须支持双缓冲
PFD_TYPE_RGBA, //申请RGBA格式
24, //选定色彩深度
0, 0, 0, 0,0, 0, //忽略的色彩位
0, //无Alpha缓存
0, //忽略ShiftBit
0, //无聚集缓存
0, 0, 0, 0, //忽略聚集位
32, // 32位Z-缓存(深度缓存)
0, //无模板缓存
0, //无辅助缓存
PFD_MAIN_PLANE, //主绘图层
0, //保留
0, 0, 0 //忽略层遮罩
};
//设置像素格式
int pixelformat;
// Windows能否找到相应的象素格式了?
if ( (pixelformat =ChoosePixelFormat(m_pDC->GetSafeHdc(),&pfd)) ==0 )
{
MessageBox("选择像素格式失败!");
returnFALSE;
}
// 能否设置象素格式?
if (SetPixelFormat(m_pDC->GetSafeHdc(),pixelformat,&pfd) ==FALSE)
{
MessageBox("设置像素格式出错!");
returnFALSE;
}
returnTRUE;
}
/*初始化*/
void Init()
{
PIXELFORMATDESCRIPTOR pfd;
m_pDC =new CClientDC(this); //创建DC
ASSERT(m_pDC !=NULL);
if (!bSetupPixelFormat()) //设置像素格式
return;
m_hRC =wglCreateContext(m_pDC->GetSafeHdc());//用DC去创建一个RC
wglMakeCurrent(m_pDC->GetSafeHdc(),m_hRC);//RC与当前DC相关联
}
6、OnSize()窗口改变
void CLidar428LabView::OnSize(UINTnType,intcx,intcy)
{
CView::OnSize(nType,cx,cy);
// TODO: Add yourmessage handler code here
if(cy > 0)
{
glMatrixMode(GL_PROJECTION); //选用投影矩阵
glLoadIdentity(); //重置当前指定的矩阵为单位矩阵
glViewport(0, 0,cx,cy); //根据窗口的实时变化重绘窗口
}
}
7、响应WM_ERASEBKGND消息
BOOL OnEraseBkgnd(CDC*pDC)
{
// TODO: Add your message handler code here and/or calldefault
//returnCView::OnEraseBkgnd(pDC);
return TRUE;
}
当需要重新设置窗口背景时,产生WM_ERASEBKGND消息,处理该消息的默认操作是用当前背景色填充整个窗口。处理方法:注释掉原有的return语句,改为return true;使该函数不执行操作,仅返回true。
8、画图
在OnDraw()中调用绘图函数。
void CLidar428LabView::OnDraw(CDC*pDC)
{
CLidar428LabDoc*pDoc =GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// TODO: 在此处为本机数据添加绘制代码
DrawScene(); //绘图
}
void DrawScene()
{
glClearColor(0.6f,0.6f,1.0f,1.0f);//设置清屏颜色
glClear(GL_COLOR_BUFFER_BIT |GL_DEPTH_BUFFER_BIT); //清除颜色缓冲区和深度缓冲区
glPushMatrix();//矩阵堆栈函数,和glPopMatrix()相对应
glPointSize(10.0);//设置点的大小
glBegin(GL_LINES);//说明几何图元为“点”,和glEnd()相对应
glColor3f(1.0,0.0,0.0);//绘制红色的点
glVertex3f(-0.7,-0.7,0.0);
glVertex3f(0.7,-0.7,0.0);
glVertex3f(0.7,-0.7,0.0);
glVertex3f(0.0,0.0,0.0);
glVertex3f(0.0,0.0,0.0);
glVertex3f(0.7,0.7,0.0);
glVertex3f(0.7,0.7,0.0);
glVertex3f(-0.7,0.7,0.0);
glVertex3f(-0.7,0.7,0.0);
glVertex3f(-0.7,-0.7,0.0);
glEnd();
glPopMatrix();
glFinish();
SwapBuffers(wglGetCurrentDC());//双缓冲
}
红色部分是画图内容,可以随意更改。
9、响应WM_DESTROY消息
取消DC和RC的关联,并且删除DC与RC。
void CLidar428LabView::OnDestroy()
{
CView::OnDestroy();
// TODO: Add your message handler code here
//m_hRC = wglGetCurrentContext();
wglMakeCurrent(NULL,NULL); //取消DC和RC的关联
if(m_hRC)
wglDeleteContext(m_hRC); //删除RC
if(m_pDC)
delete m_pDC; //删除DC
}
图像 glVertex3f()参数只能小于1,大于1会被裁剪掉。
注:不能添加GLUT32.lib,因为glut32是与窗口有关的库,而我们使用的是MFC单文档,否则会起冲突!