由于有这个需求,就是当在对话框设置一些数值的时候,可以在对话框上预览三维图像。
(1)生成一个基于对话框的程序,或者直接在单文档或者多文档上插入一个对话框,生成一个新的对话框类CGridingDlg,名字可以任取。
(2)配置好工程的OpenGL环境,不知道可以百度。
(3)在CGridingDlg的头文件中添加以下变量:
/*-----OpenGL绘图相关的变量-----------------------*/
//旋转角度
float xrof;
float yrof;
float zrof;
//Z轴移动变量
BOOL m_bZoomZ;
float m_fZoomZ;
GLfloat fNearPlane, fFarPlane;//透视投影的最近、最远的裁剪面距离
float cameraPos[3];//相机位置
float modelView[16];//模型矩阵
float translateSpeed;//鼠标滑轮平移速度
float rotateSpeed;//旋转速度
float walkSpeed;
float inertia;
GLfloat m_xCurAngle;//X方向上的旋转角度
GLfloat m_yCurAngle;//Y方向上的旋转角度
GLfloat m_zCurAngle;//Z方向上的旋转角度
CPoint m_MouseDownPoint;//鼠标点击的落点
BOOL bMouseWheelStop;//鼠标中键滚动
int buttonState;//鼠标状态
HGLRC m_hRC; //Rendering Context着色描述表
HDC m_pDC;//Device Context设备描述表
BOOL SetupPixelFormat(HDC hDC);//设置像素格式
BOOL InitializeOpenGL(HDC hDC);//初始化OpenGL
CRect m_oldRect;
void RenderScene(void);//绘图函数
/*------------------------------------------------*/
以上为在后面需要使用到变量
(4)重载对话框类CGridingDlg的虚函数OnInitDialog(),添加WM_TIMER消息,
添加函数SetupPixelFormat(HDC hDC)---->用于设置OpenGL的像素格式
BOOL CGriding::SetupPixelFormat(HDC hDC)
{
//PIXELFORMATDESCRIPTOR pixelDesc;
//pixelDesc.nSize = sizeof(PIXELFORMATDESCRIPTOR);
//pixelDesc.nVersion = 1;
//pixelDesc.dwFlags = PFD_DRAW_TO_WINDOW |
// PFD_SUPPORT_OPENGL |
// PFD_DOUBLEBUFFER |
// PFD_TYPE_RGBA;
//pixelDesc.iPixelType = PFD_TYPE_RGBA;
//pixelDesc.cColorBits = 32;
//pixelDesc.cRedBits = 0;
//pixelDesc.cRedShift = 0;
//pixelDesc.cGreenBits = 0;
//pixelDesc.cGreenShift = 0;
//pixelDesc.cBlueBits = 0;
//pixelDesc.cBlueShift = 0;
//pixelDesc.cAlphaBits = 0;
//pixelDesc.cAlphaShift = 0;
//pixelDesc.cAccumBits = 0;
//pixelDesc.cAccumRedBits = 0;
//pixelDesc.cAccumGreenBits = 0;
//pixelDesc.cAccumBlueBits = 0;
//pixelDesc.cAccumAlphaBits = 0;
//pixelDesc.cDepthBits = 0;
//pixelDesc.cStencilBits = 1;
//pixelDesc.cAuxBuffers = 0;
//pixelDesc.iLayerType = PFD_MAIN_PLANE;
//pixelDesc.bReserved = 0;
//pixelDesc.dwLayerMask = 0;
//pixelDesc.dwVisibleMask = 0;
//pixelDesc.dwDamageMask = 0;
//int PixelFormat = ChoosePixelFormat(hDC,&pixelDesc);
//if(PixelFormat==0) // Choose default
//{
// PixelFormat = 1;
// if(DescribePixelFormat(hDC,PixelFormat,
// sizeof(PIXELFORMATDESCRIPTOR),&pixelDesc)==0)
// {
// return FALSE;
// }
//}
//if(SetPixelFormat(hDC,PixelFormat,&pixelDesc)==FALSE)
//{
// return FALSE;
//}
//return TRUE;
//初始化象素格式以及选取合适的格式来创建RC
PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR), // pfd结构的大小
1, // 版本号
PFD_DRAW_TO_WINDOW | // 支持在窗口中绘图
PFD_SUPPORT_OPENGL | // 支持 OpenGL
PFD_DOUBLEBUFFER, // 双缓存模式
PFD_TYPE_RGBA, // RGBA 颜色模式
24, // 24 位颜色深度 ,color depth
0, 0, 0, 0, 0, 0, // 忽略颜色位
0, // 没有非透明度缓存
0, // 忽略移位位
0, // 无累加缓存
0, 0, 0, 0, // 忽略累加位
32, // 32 位深度缓存
0, // 无模板缓存
0, // 无辅助缓存
PFD_MAIN_PLANE, // 主层
0, // 保留
0, 0, 0 // 忽略层,可见性和损毁掩模
};
//在DC中选择合适的象素格式并返回索引号
int pixelformat;
pixelformat=::ChoosePixelFormat(m_pDC,&pfd);
if (pixelformat==0)
{
AfxMessageBox("选择像素格式失败!");
return FALSE;
}
//设置指定象素格式
if (::SetPixelFormat(m_pDC,pixelformat,&pfd)==FALSE)
{
AfxMessageBox("设置像素格式失败!");
return FALSE;
}
return TRUE;
}
上面是两种设置OpenGL像素格式的方法,其中注释的代码也可以用于设置像素格式,任用一种。
在对话框上面添加一个Picture控件,用于显示图形,设置其ID为IDC_STATIC_PictureRender
添加InitializeOpenGL()函数用于在对话框中初始化OpenGL。
BOOL CGriding::InitializeOpenGL(HDC hDC)
{
//首先把DC的象素格式调整为指定的格式,以便后面对DC的使用
SetupPixelFormat(hDC);
//根据DC来创建RC
m_hRC=::wglCreateContext(hDC);
if (m_hRC==NULL)
{
return FALSE;
}
//设置当前的RC,以后的画图操作都画在m_pDC指向的DC上
if ((::wglMakeCurrent(hDC,m_hRC))==FALSE)
{
return FALSE;
}
return TRUE;
//下面可以进行画图操作了
//初始化整个场景和OpenGL的状态变量
// OpenGL场景初始化(光照、雾化等〕
// GetClientRect(&m_oldRect);
GetDlgItem(IDC_STATIC_PictureRender)->GetClientRect(&m_oldRect);//获取图片控件的大小
//*--设置OpenGL初始状态模式---*/
///设置清屏颜色为白色
glClearColor(1.0f,1.0f,1.0f, 1.0f);
//清除颜色缓冲区
glClear(GL_COLOR_BUFFER_BIT);
glEnable( GL_COLOR_MATERIAL );//启动颜色材料模式,使得在光照下模型颜色有用-不加这一句,模型颜色全为黑白色
//设置深度缓存的清除值 ,Depth Buffer Setup
glClearDepth(1.0f);
glClear(GL_DEPTH_BUFFER_BIT);
//做深度测试,实现三维场景的消隐
glEnable(GL_DEPTH_TEST);
//深度测试函数,GL_LEQUAL:深度小或相等当前深度的时候也渲染
glDepthFunc(GL_LEQUAL);
//可以调用函数glHint()对图像质量和绘制速度之间的权衡作一些控制
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
/*表示把渲染的图像融合到目标区域。也就是说源的每一个像素的alpha
都等于自己的alpha,目标的每一个像素的alpha等于1减去该位置源像素
的alpha。 因此不论叠加多少次,亮度是不变的。*/
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
//glEnable(GL_TEXTURE_RECTANGLE_ARB);
glEnable(GL_NORMALIZE);//打开法线矢量自动归一化功能
GLfloat fAspect;
if (m_oldRect.bottom)
fAspect = (GLfloat)m_oldRect.right/m_oldRect.bottom;
else
fAspect = 1.0f;
fNearPlane = 0.1f;//透视投影近平面
fFarPlane = 1000.0f;//透视投影远平面
cameraPos[2] =-(fNearPlane + fFarPlane)/ 40.0f;
cameraPos[0]=-0;
cameraPos[1]=-0;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
//设置透视投影矩阵
gluPerspective(45, fAspect, fNearPlane, fFarPlane);
// glOrtho(0,10000000,0,10000000,-2000,10000000);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
SwapBuffers(m_pDC);
return TRUE;
}
在对话框类中的初始化对话框函数OnInitDialog(),初始化OpenGL;
BOOL CGriding::OnInitDialog()
{
CDialogEx::OnInitDialog();
// TODO: 在此添加额外的初始化
/*-------OpenGL绘图初始化相关---------------------*/
CWnd *wnd=GetDlgItem(IDC_STATIC_PictureRender);//获取图片控件的窗口指针
m_pDC=::GetDC(wnd->m_hWnd);//将绘图DC与图片窗口关联起来,如果这里是Wnd则绘图区域就设置为整个对//话框
InitializeOpenGL(m_pDC);//初始化OpenGL函数
///
SetTimer(1,1,0); //设置定时器
return TRUE; // return TRUE unless you set the focus to a control
// 异常: OCX 属性页应返回 FALSE
}
然后为该对话框类添加一个函数RenderScene(),用于三维模型的绘制
void CGriding::RenderScene(void)
{
///
//基本准备
glClearColor(1,1,1,1.0);//设置背景颜色为白色
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//清除颜色缓冲和深度缓冲
glEnable( GL_COLOR_MATERIAL );//启动颜色材料模式,使得在光照下模型颜色有用-不加这一句,模型颜色全为黑白色
//开始显示图形
//设置模型视景矩阵,包括了视点变换和模型变换矩阵
//重置模型矩阵
glMatrixMode(GL_MODELVIEW);//对模型视景矩阵堆栈应用随后的矩阵操作
glLoadIdentity();//将当前的用户坐标系的原点移到了屏幕中心:类似于一个复位操作
//keyboard();//响应键盘按键
//ShowLegend();//绘制图例,在固定地方,没有改变模型视景矩阵
/*GL_MODELVIEW矩阵在一个矩阵中包含view矩阵和model 矩阵
视点变换:gluLookAt()
模型变换:包括平移、旋转和缩放。
在程序中, 视点变换必须在模型变换之前完成, 但是投影变换和视口变换可以在绘图之前的任何时候指定。
函数glRotate( ) 旋转的是坐标系而不是物体,旋转操作遵循右手规则
如果矩阵模式是GL_MODELVIEW 或GL_PROJECTION 时,函数glRotate ( ) 调用后所有绘制的对象将被旋转。
glTranslate( ) 函数平移的是坐标系而不是物体。glTranslate( ) 函数可以作用于几何矩阵、投影矩阵和纹理坐标变换矩阵。
如果当前矩阵为几何矩阵, 函数的功能则是将物体坐标系的原点移到( x, y, z) 所指的位置,形成新的物体坐标系*/
glTranslatef(cameraPos[0], cameraPos[1], cameraPos[2]);//平移,用到后面的modelView
/*modelView的X(m0、m4、m8)、Y(m1、m5、m9)、Z(m2、m6、m10)存的是模型矩阵的X、Y、Z的方向
,默认为(1,0,0)、(0,1,0)、(0,0,1)。本实例中modelView的X、Y、Z的方向保持不变,值也
没有变化*/
glGetFloatv(GL_MODELVIEW_MATRIX, modelView);
glRotatef(m_xCurAngle, 1.0, 0.0, 0.0);
glRotatef(m_yCurAngle, 0.0, 1.0, 0.0);
glRotatef(m_zCurAngle, 0.0, 0.0, 1.0);
glPushMatrix();
/*---------绘图函数代码区,只需添加绘图代码即可----------------*/
//这里绘制了三个坐标轴线和一个立方体
glBegin( GL_LINES );
glColor3d(1.0, 0.0, 0.0); // X轴 红色
glVertex3d(0.0, 0.0, 0.0);
glVertex3d(1.0, 0.0, 0.0);
glColor3d(0.0, 1.0, 0.0); // Y轴 绿色
glVertex3d(0.0, 0.0, 0.0);
glVertex3d(0.0, 1.0, 0.0);
glColor3d(0.0, 0.0, 1.0); // Z轴 蓝色
glVertex3d(0.0, 0.0, 0.0);
glVertex3d(0.0, 0.0, 1.0);
glEnd();
glColor3f(1.0, 0.0, 0.0);
glutWireCube(1);
/*--------------------------------------------------------------*/
glPopMatrix();
glDisable(GL_LIGHTING);
glColor3f(1,0.5,0.5);
glRasterPos2f(2.0,2.0);
glEnable(GL_LIGHTING);
glFinish();
SwapBuffers(m_pDC);//交换缓冲区
}
然后在该类的定时器响应函数OnTime()将RenderScene()函数添加进去,每隔多少毫秒就调用RenderScene(),时间间隔设置的越少就可以形成连续绘制的效果
void CGriding::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
RenderScene();
CDialogEx::OnTimer(nIDEvent);
}
最后别忘了在该类的析构函数中释放绘图句柄,防止出现内存泄露
CGriding::~CGriding()
{
wglMakeCurrent(NULL, NULL) ;
wglDeleteContext(m_hRC); //删除绘图描述表
::ReleaseDC (m_hWnd, m_pDC) ; //释放设备描述表
}
这样,我们就可以在对话框中看见我们所画的图形了,如果想自己随便画什么模型,那么就需要自己再在RenderScene()函数中封装一个函数,将该函数放置于RenderScene()函数的绘图代码区就好了。
绘制的效果如下
但是上面中的模型并不能进行交互操作,所以须在CGridingDlg该类中添加鼠标左键,鼠标中键以及鼠标滑轮的消息响应函数,但先需要在该类的构造函数中初始化控制变量。
//旋转角度
xrof=-90.0f;
yrof=0.0f;
zrof=40.0f;
//Z轴移动
m_fZoomZ=0;
m_bZoomZ=FALSE;
translateSpeed = 0.0001f;//平移速度
// translateSpeed = 1.08f;//鼠标放大缩小时的缩放大小因子
// translateSpeed = 0.9f;//鼠标放大缩小时的缩放大小因子
rotateSpeed = 0.04f;//旋转速度
walkSpeed = 0.08f;
inertia = 0.1f;
m_xCurAngle = -90.0f;
m_yCurAngle = 0.0f;
m_zCurAngle = 0.0f;
bMouseWheelStop = TRUE;
buttonState = -1; //鼠标按键的状态
然后在其各种鼠标响应消息中添加以下代码,鼠标左键用于选择,中键用于移动,滑轮用于缩放
void CGriding::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_MouseDownPoint=CPoint(0,0);
/*ReleaseCapture():该函数从当前线程中的窗口释放鼠标捕获,并恢复通
常的鼠标输入处理。捕获鼠标的窗口接收所有的鼠标输入(无论光标的位置
在哪里),除非点击鼠标键时,光标热点在另一个线程的窗口中。*/
ReleaseCapture();
buttonState = -1;
///
CDialogEx::OnLButtonUp(nFlags, point);
}
void CGriding::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_MouseDownPoint=point;
/*SetCapture():该函数在属于当前线程的指定窗口里设置鼠标捕获。一旦窗口捕获了鼠标,
所有鼠标输入都针对该窗口,无论光标是否在窗口的边界内*/
SetCapture();
buttonState = GLUT_LEFT_BUTTON;
CDialogEx::OnLButtonDown(nFlags, point);
}
void CGriding::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
/*GetCapture():该函数取得捕获了鼠标的窗口(如果存在)的句柄。
在同一时刻,只有一个窗口能捕获鼠标;此时,该窗口接收鼠标的输入
,无论光标是否在其范围内。*/
if (GetCapture()==this)
{
float dx, dy;
dx = point.x - m_MouseDownPoint.x;
dy = point.y - m_MouseDownPoint.y;
if (buttonState == GLUT_LEFT_BUTTON)//如果是左键按下,就旋转物体
{
m_xCurAngle += dy/10;
m_yCurAngle += dx/10;
}
if (buttonState == GLUT_MIDDLE_BUTTON ||buttonState == GLUT_RIGHT_BUTTON)//如果是鼠标中键或者是右键按下,就平移物体
{
float v[3];
v[0] = dx*translateSpeed;
v[1] = -dy*translateSpeed;
v[2] = 0.0f;
float r[3];
r[0] = v[0]*modelView[0] + v[1]*modelView[1] + v[2]*modelView[2];
r[1] = v[0]*modelView[4] + v[1]*modelView[5] + v[2]*modelView[6];
r[2] = v[0]*modelView[8] + v[1]*modelView[9] + v[2]*modelView[10];
cameraPos[0] += r[0];
cameraPos[1] += r[1];
cameraPos[2] += r[2];
}
/*if (buttonState== GLUT_WHEEL_UP)
//if(nFlags & MK_MBUTTON)
{
// left+middle = zoom
float v[3];
v[0] = 0.0;
v[1] = 0.0;
v[2] = dy*translateSpeed;
float r[3];
r[0] = v[0]*modelView[0] + v[1]*modelView[1] + v[2]*modelView[2];
r[1] = v[0]*modelView[4] + v[1]*modelView[5] + v[2]*modelView[6];
r[2] = v[0]*modelView[8] + v[1]*modelView[9] + v[2]*modelView[10];
cameraPos[0] += r[0];
cameraPos[1] += r[1];
cameraPos[2] += r[2];
} */
m_MouseDownPoint=point;
InvalidateRect(NULL,FALSE);
}
CDialogEx::OnMouseMove(nFlags, point);
}
BOOL CGriding::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
//鼠标中键滑轮滚动进行放大缩小操作
float v[3];
v[0] = 0.0;
v[1] = 0.0;
v[2] = zDelta*translateSpeed;
float r[3];
r[0] = v[0]*modelView[0] + v[1]*modelView[1] + v[2]*modelView[2];
r[1] = v[0]*modelView[4] + v[1]*modelView[5] + v[2]*modelView[6];
r[2] = v[0]*modelView[8] + v[1]*modelView[9] + v[2]*modelView[10];
cameraPos[0] += r[0];
cameraPos[1] += r[1];
cameraPos[2] += r[2];
InvalidateRect(NULL,FALSE);
return CDialogEx::OnMouseWheel(nFlags, zDelta, pt);
}
void CGriding::OnMButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_MouseDownPoint=point;
SetCapture();
buttonState = GLUT_MIDDLE_BUTTON;
CDialogEx::OnMButtonDown(nFlags, point);
}
void CGriding::OnMButtonUp(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_MouseDownPoint=CPoint(0,0);
ReleaseCapture();
buttonState = -1;
CDialogEx::OnMButtonUp(nFlags, point);
}
这样我们就可以旋转、放大、缩放物体了
如果您觉得这篇博文有用,请访问我的个人站:http://www.stubbornhuang.com,更多博文干货等着您。