三维模型上的标量场指的是三维模型上面的每个顶点都有一个标量值,从而构成了整体的标量场。如果能够以颜色代表这个标量值的大小,那么整体的标量场就能够方便的观察出来。一般来说,hls颜色模型的色相能够代表从红到蓝的渐进过度,因此可以用hsl转rgb的方法生成一维纹理。然后归一化标量场,将标量值作为一维的纹理坐标,从而显示出整个标量场。如何加载任意格式图片作为纹理,以及其它关于OpenGL纹理的内容,可以参考我其它文章。
但是这样比较麻烦。首先,需要颜色空间转换,还有使用的是一维纹理。其实,有更方便的做法。直接找一张代表从红到蓝的渐进过度纹理图片,加载这张图片生成纹理,使用二维纹理坐标,固定一维的坐标,如s,变化t。
对应的二维纹理图和实现效果:


这里需要注意一个问题,就是极限纹理坐标的跳变。比如说,纹理坐标很小接近0,可能就会直接跳到1去了,插值出来的颜色就会从红直接变成蓝,或者从蓝直接变成红,明显是不对的。如图所示,


处理这个问题的方法,有两种,一种是修改纹理坐标的模型为GL_MIRRORED_REPEAT的而不是一般的GL_REPEAT,第二个是将纹理坐标的范围压缩到大于0小于1之间。求的最大和最小标量值后,往外扩展下,再进行标量值的归一化。可以用下面的代码,处理。
float fDis = fMax - fMin;
fMin = fMin - fDis 0.001;
fMax = fMax + fDis
0.001;
这样正确的显示如下,


现在可以看到,本来是带蓝色中心的红带,变成了全部是红色的带了。

这篇文章是在我的另一篇文章“继承mfc的cwnd类渲染opengl”的基础上改进的。
AntTweakBar是一个条状的菜单库,类似于内嵌于渲染窗口的属性条,可以和OpenGL渲染窗口融为一体,效果很好看。以前见过有人用这个库,效果比较好看就关注了下。在网上找相关的资料,发现只有人在glut下使用。还有人因为在MFC下使用不成功,认为和MFC不兼容。其实,这个库只要给了渲染引擎就行了,然后把一些事情传给它就能交互了。
AntTweakBar提供的都是简单的C接口,所以非常方便和已有的界面框架整合,无论你用的是OpenGL还是DX渲染,无论你用的界面框架是glut还是glfw或者sdl等等都行。官方网站也提供了整合步骤
我现在介绍下,在我的框架里面的整合步骤。
第一步,在OnCreate函数里面调用TwInit(TW_OPENGL, NULL);初始化。
第二步,在OnCreate函数里面创建bar并且绑定变量。如,

1
2
3
4
5
*myBar;
myBar = TwNewBar("Test");
TwDefine(" Test refresh=0.5 color='96 216 224' alpha=0 text=dark");
static int nTest = 0;
TwAddVarRW(myBar, "Test", TW_TYPE_INT32, nTest, "test");

第三步,在OnSize里面调用TwWindowSize(cx, cy);
第四步,修改PreTranslateMessage实现为,

1
2
3
4
5
if (TwEventWin(pMsg->hwnd, pMsg->message, pMsg->wParam, pMsg->lParam))
{
return 1;
}
return CWnd::PreTranslateMessage(pMsg);

第五步,在RenderScene函数最后添加TwDraw(),但是得保证在SwapBuffers(m_hDC)之前添加这句。
第六步,在OnDestroy()中调用TwTerminate()释放资源。
综上所述,整合还是非常简单的。
另外,AntTweakBar提供TwDefine函数用于设置颜色,透明度等参数,可以调整效果,非常方便。还有一个需要注意的是,TwAddVarRW绑定的变量不能是局部变量,否则会出错。当你绑定的变量值变化时候,就是你和AntTweakBar交互的时候了。总之,非常方便使用吧。
最后,我要说的是,AntTweakBar自己会使用自身的光标,它的光标会覆盖MFC窗口的光标。如果,我们坚持使用MFC的光标的话,该怎么办了。只能禁止掉AntTweakBar的光标了。方法是,修改它的源码,重新编译成库。我们把TwMgr.cpp的针对windows系统的SetCursor函数内部实现注视掉就行了。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
void CTwMgr::SetCursor(CTwMgr::CCursor _Cursor)
{
/*if( m_CursorsCreated )
{
CURSORINFO ci;
memset(ci, 0, sizeof(ci));
ci.cbSize = sizeof(ci);
BOOL ok = ::GetCursorInfo(ci);
if( ok (ci.flags CURSOR_SHOWING) )
::SetCursor(_Cursor);
}*/
}

以下是实现效果,

有些时候,我们想知道鼠标点中了哪个物体或者哪个部分,更详细的是最靠近模型的哪个顶点或者哪条线,或者哪个面。这些选取问题有不同的解决办法。如果只是针对图元的选取,可以直接用OpenGL的选取模式实现。但是有的时候,情况更加复杂,比如我们想通过鼠标在模型上面绘制一条线,然后对模型进行剖分等。这就需要把鼠标点变换到三维顶点,再进一步的操作。
我在这里贴出2个我自己使用的函数,针对鼠标点投影视角空间和反投影。
投影:``` stylus
void CMesh::ScreenToModel(v2f point, v3f point3d)
{
float fWinX, fWinY, fWinZ;
int nX, nY;
GLdouble fX, fY, fZ;

    nX = point[0];
    nY = point[1];
    glReadBuffer(GL_BACK);
    glReadPixels(nX, nY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, fWinZ);
    if (fabs(fWinZ - 1.0) > 1e-8)
    {
        fWinX = nX;
        fWinY = nY;
        gluUnProject(fWinX, fWinY, fWinZ, m_pMatMV, m_pMatProj, m_pViewport,
            fX, fY, fZ);
        point3d[0] = fX;
        point3d[1] = fY;
        point3d[2] = fZ;
    }
    else
    {
        point3d[0] = point3d[1] = 0.0;
        point3d[2] = 1.0;
    }

}

1
2
3
4
5
6
7
8
9
10
11
使用该函数必须注意的是,point必须已经转换为**视口点**,即point[1] = window_height - point[1] - 1。
反投影:``` stylus
void CMesh::ModelToScreen(v3f point3d, v2f point)
{
double fWinX, fWinY, fWinZ;
gluProject(point3d[0], point3d[1], point3d[2], m_pMatMV, m_pMatProj, m_pViewport,
fWinX, fWinY, fWinZ);

point[0] = (float)fWinX;
point[1] = (float)fWinY;
}

反投影得到的2维点同样是基于左下角为原点的视口点,要转换为鼠标点的话还得变换y值,即point[1] = window_height - point[1] - 1。
至于其它的实现方法,当然可以自己直接操作投影矩阵和模型矩阵,视口信息,进行转换,不过也是重复造轮子,但对于理解原理有帮助。
鼠标点投影到三维之后,就可以找到最近的模型点,或者通过射线之类求相交面。

我前段时间写了一篇关于MFC界面框架下,OpenGL全屏抗锯齿的文章。原以为这份代码可以方便的使用了,没想到今天就出问题了。这也许不能算在代码上的bug,或许是硬件的原因。今天我换了台电脑尝试了下,出现了很奇怪的现象,调试了很久才找到原因是开启了多重采样。这个调试过程也是让我非常痛苦的,写图形学程序对调试的经验和功底要求真的不浅啊。
我只能猜测是硬件的原因了。在学校里的时候,实验室的机器上运行得好好的,但是在这台电脑上就不行。这台电脑确实有点不一样,是妹子的mac,被我装了windows7在用着。估计是硬件不一样吧。再说我的多重采样是参照nehe的实现方法,也许实现方法不太兼容了。
有必要展示下这个奇葩的结果
从图片可以看出,本应该被遮挡的部分显示出来了。这个也不像完全是透明混合的效果,也不是深度测试的原因。总之是很奇怪的结果,我也调试过深度测试和混合,都没有用。最后,经过一番调试才知道是因为开启多重采样的原因。
只能把多重采样去掉,效果才显示正常了,并且程序的其它部分才正常,比如鼠标点和模型求交等。
在这里,不能不再次感叹调试的力量。我今天是实在没办法了,只能把以前版本的代码下载下来,替换不同的文件才找到bug的所在。因为当工程里面代码量太大的时候,注释掉某些部分,vs已经不一定会正确生成结果了,这种事情只能全部重新生成,才能保证代码真的更新了。
调试功底真的很重要。比如文件替换来排查错误的所在,打log,调试状态查看内存等。没有强悍的调试手段,很多事情真的是继续不下去的。

标题的意思是使用OpenGL生成特定的图片。比如说,把OpenGL渲染出来的特定结果保存出来,或者生成特殊的纹理图片。
第一步,保存OpenGL环境。这个使用glPushAttrib(GL_ALL_ATTRIB_BITS)实现。
第二步,设置视口。既然我们要生成指定大小的图片,那我们就把视口设置为该图片的大小。glViewport(0, 0, nWidth, nHeight);
第三步,设置投影矩阵。
第四步,设置模型视图矩阵。
第五步,进行相关绘制。
第六步,调用glReadPixels保存绘制内容。(这一步也可以放到后面的任意位置)
第七步,恢复模型视图矩阵。
第八步,恢复投影矩阵。
第九步,恢复OpenGL环境。
大致的代码,可以用下面这个函数描述。只需要替换绘制的代码就可以改成你需要的形式了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void CDrawedTexture::InterpolateData(int m_nWidth, int m_nHeight)
{
glPushAttrib(GL_ALL_ATTRIB_BITS);

glViewport(0, 0, nWidth, nHeight);
glClearColor(0.0, 0.0, 0.0, 1.0);
glDisable(GL_TEXTURE_2D);

glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
gluOrtho2D(0.0, 1.0, 0.0, 1.0);

glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();

glClear(GL_COLOR_BUFFER_BIT);

//绘制三角形
glEnable(GL_TEXTURE_2D);

for (int i = 0; i < m_faces.size(); ++i)
{
m_faces[i].v[0].pTexture->Bind2d();
glBegin(GL_TRIANGLES);
for (int j = 0; j < 3; ++j)
{
//设置纹理坐标
glTexCoord2f(m_faces[i].v[j].oldTexPos[0], m_faces[i].v[j].oldTexPos[1]);
//设置坐标
glVertex2f(m_faces[i].v[j].newTexPos[0], m_faces[i].v[j].newTexPos[1]);
}
glEnd();
}

glReadPixels(0, 0, nWidth, nHeight, GL_RGBA, GL_UNSIGNED_BYTE, m_img.raw());

glPopMatrix();

glMatrixMode(GL_PROJECTION);
glPopMatrix();

glPopAttrib();

m_img.save("插值纹理.bmp");
}

注意请使用双缓存区,而且绝对不能调用交换缓存区函数。原因是生成图片是幕后工作,交换缓存区会破坏屏幕当前的显示。这里还有个必须很注意的地方,就是glReadPixels的参数一定要正确。比如像素的格式,还有数据的类型。如果你的颜色缓存区是rgba的,那么你一定得使用GL_RGBA而不是GL_RGB,还有一般不能使用GL_BYTE,而是要使用GL_UNSIGNED_BYTE,否则会造成数据截断。
我这段代码的意图是将笔画绘制所经过的三维部分投影到二维图片上面保存起来。三维模型上面贴了纹理。
当然还可以用FBO实现和本文类似的功能。
如果实现所谓的渲染到纹理,我们只需要把渲染到后缓存区的数据提取出来,绑定到纹理就行了。

第一步,在CMainFrame中定义NOTIFYICONDATA结构m_notify。

第二步,在OnCreate中添加下面的代码。代码的解释参见注释。

1
2
3
4
5
6
7
8
m_notify.cbSize = sizeof(NOTIFYICONDATA);//结构体大小
m_notify.hWnd = m_hWnd;//对应窗口
m_notify.uID = IDR_MAINFRAME;//托盘id
m_notify.hIcon = LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_MAINFRAME));//图标
strcpy(m_notify.szTip, "ConuterPerDay");//提示字符
m_notify.uCallbackMessage = WM_USER_NOTIFYICON;//处理消息
m_notify.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; //有效标志
Shell_NotifyIcon(NIM_ADD, m_notify);//添加托盘

需要注意的是,m_notify.uFlags必须设置正确,如果不正确,那么有些功能是不会有效的,还有m_notify.cbSize也是必须设置的。
第三步,在MainFrame.h中添加宏定义#define WM_USER_NOTIFYICON (WM_USER + 100),再定义对应的消息处理函数,afx_msg LRESULT OnNotifyMsg(WPARAM wparam, LPARAM lparam);注意该函数的格式。这是普通的消息处理函数,消息的参数必须带全。再在源文件中的BEGIN_MESSAGE_MAP和END_MESSAGE_MAP()之间添加消息映射ON_MESSAGE(WM_USER_NOTIFYICON, OnNotifyMsg)。
第四步,实现OnNotifyMsg。我的OnNotifyMsg实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// CMainFrame 消息处理程序
LRESULT CMainFrame::OnNotifyMsg(WPARAM wparam, LPARAM lparam)//wParam接收的是图标的ID,而lParam接收的是鼠标的行为
{
if (wparam != IDR_MAINFRAME) return 1;

CPoint pos;
CMenu menu;
switch (lparam)
{
case WM_RBUTTONUP://右键起来时弹出快捷菜单,这里只有一个“关闭”
GetCursorPos(pos);
menu.CreatePopupMenu();//声明一个弹出式菜单
//增加菜单项“关闭”,点击则发送消息WM_DESTROY给主窗口(已
//隐藏),将程序结束。
menu.AppendMenu(MF_STRING, WM_USER_EXIT, "关闭");
//确定弹出式菜单的位置
SetForegroundWindow();
menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, pos.x, pos.y, this);
menu.DestroyMenu();
break;

case WM_LBUTTONDBLCLK://双击左键的处理
if (IsWindowVisible())
{
ShowWindow(SW_HIDE);
}
else
{
ShowWindow(SW_SHOW);//简单的显示主窗口完事儿
}
break;
}

return 0;
}

值得注意的是menu.AppendMenu(MF_STRING, WM_USER_EXIT, “关闭”);,我用了自定义的消息WM_USER_EXIT而不是WM_DESTROY。刚开始我参照别人的代码,使用WM_DESTROY,结果弹出菜单一直是灰色的。我换成WM_CLOSE也是一样的效果。没办法,我就试了下自定义消息。刚开始我采样的是普通类型的消息映射处理该菜单消息,菜单也是灰色的。直到最后我用ON_COMMAND映射菜单消息,菜单才能够点击
第四步,在MainFrame.h中添加宏定义#define WM_USER_EXIT (WM_USER + 101),再定义对应的消息处理函数,aafx_msg void OnExit();注意该函数的格式,与第三步不同,因为这是命令消息。再在源文件中的BEGIN_MESSAGE_MAP和END_MESSAGE_MAP()之间添加消息映射ON_COMMAND(WM_USER_EXIT, OnExit)。第四步是使弹出菜单能够点击的关键。我在OnExit直接调用DestroyWindow退出程序。
第五步,重载WM_CLOSE,在其中调用ShowWindow(SW_HIDE);。这样点击关闭按钮则进入托盘状态,而点击最小化按钮在任务栏上有程序的图标。
第六步,重载WM_DESTROY,程序退出时候删除托盘。对应代码如下:

1
2
3
4
5
6
7
8
9
10
11
void CMainFrame::OnDestroy()
{
CFrameWndEx::OnDestroy();

NOTIFYICONDATA tnid;
tnid.cbSize = sizeof(NOTIFYICONDATA);
tnid.hWnd = m_hWnd;
tnid.uID = IDR_MAINFRAME;
//用NIM_DELETE删除图标
Shell_NotifyIcon(NIM_DELETE, tnid);
}

至此,实现了一个SDI框架下,完整的托盘程序。
我写这篇文章的目的是为了说明ON_MESSAGE和ON_COMMAND的区别。ON_COMMAND对应的普通的windows消息,ON_COMMAND则对应的是菜单和按钮之类的命令消息,需要区别对待。也就是处理不同类型的消息,需要使用不同类型的消息处理函数。如果消息处理函数类型不正确,有可能出现问题。比如,弹出菜单一直是灰色。

在这里我只写我遇到过的一种情况。因为以前没有发现原因,好几个月的我的代码渲染速度都很慢,以至于最后被老师狠狠得说了一顿。最后,没办法了,我才通过仔细对比,一步步排查,发现了最终的原因。
说到渲染速度优化,大家肯定会说用显示列表啊。这个我知道,书上都有。我尝试过显示列表,但是还是解决不了我的问题。再一个,假如你写的是一个模型编辑的软件,也就是渲染数据会发生变化的,那么用显示列表好像不太方便。当然,肯定也能使用显示列表,数据改变的时候重新生成一次显示列表呗。
我用的界面框架是MFC,刚开始我以为是用MFC的原因导致渲染速度大幅度下降。但是,我对比过别人的MFC渲染程序,发现不存这样的事情。毕竟windows下的glut肯定是用windows api实现的,MFC只是多了层封装而已。那么原因在哪里了。
大家都知道我们在贴纹理的时候,需要在每个顶点处指定纹理坐标,但是这样做渲染速度也很快。假如把纹理换成颜色怎么样了。假设不同的顶点有各自的颜色,这种情况下,我们必须开启光照,然后不断设置不同顶点的材质。那么,这个速度和设置纹理坐标的速度相比怎么样了?
结果是慢得不行,基本上50w左右的顶点,连旋转都延迟了。谁让我以前都使用以k为数量级的模型了,一直没面对这个问题。
也许是纹理的实现比光照的实现要快很多。毕竟贴纹理得到最终像素的颜色,差不多就是双线性差值之类的方法。当然纹理的具体实现肯定要复杂很多,内容多很多。但是,光照的实现需要更复杂的计算公式。但是这就是真正的原因么?
我只是在一直切换顶点的材质而已,为什么比切换顶点的纹理坐标慢了至少10倍了。我针对这种情况修改了代码,将材质切换进行了判断,使材质切换大大减少了,渲染速度果断达到了要求,可以顺畅渲染100w以上的数据。
现在,我只能经验性的知道不能频繁切换材质,但是能频繁切换纹理坐标。具体原因,我就不深入探究了。也许得从渲染片段的顺序之类的讨论起吧。
另外谈一下优化代码速度的关键,别磨磨蹭蹭想其它的原因了,直接去找一直在哪里循环执行的东西吧。优化循环或者循环被调用的东西吧。

其实OpenGL的全屏抗锯齿就是开启多重采样。在使用glut的时候,很方便就能实现。只要启用多重采样缓冲就行了。但是,在MFC窗口中的实现就很麻烦了。
首先,在MFC窗口中渲染OpenGL就需要比较麻烦的设置。所以,需要在设置OpenGL渲染环境的时候进一步处理。具体的来看下面一段代码吧。
这段代码就是MFC窗口创建的时候,设置OpenGL渲染环境的代码。
对于没有启用多重采样的情况,思路是先获取DC,然后调用ChoosePixelFormat获取最佳像素格式,再调用SetPixelFormat设置像素格式,接着调用wglCreateContext创建OpenGL渲染环境,最后调用wglMakeCurrent设置OpenGL渲染环境。
如果启用了多重采样,代码逻辑就更加复杂了。最恶心的一点是窗口必须创建2次。第一次按照常规思路设置好OpenGL渲染环境,然后调用函数InitMultisample判断是否支持多重采样,如果支持则销毁窗口,重新创建一次。第二次的思路是按照多重采样初始化得到的像素格式设置好OpenGL渲染环境的像素格式,其余的设置和常规的一样。具体参见下面的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
BOOL COglWnd::SetupGLContext()
{
DWORD dwFlags = (PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL);

PIXELFORMATDESCRIPTOR pfd =
{
sizeof(PIXELFORMATDESCRIPTOR), // Structure size,
1, // Structure version number
dwFlags, // Property flags
PFD_TYPE_RGBA, // RGBA mode
24, // 24-bit color
0, 0, 0, 0, 0, 0, // 8-bit each color
0, 0, 0, 0, 0, 0, 0, // No alpha or accum. buffer,
16, // 32-bit z-buffer
0, 0, // No stencil or aux buffer
PFD_MAIN_PLANE, // Mainn layer type
0, // Reserved
0, 0, 0, // Unsupported.
};

BOOL bOk = false;
m_hDC = GetDC()->m_hDC;
int PixelFormat;

static bool bFirst = true;
if (bFirst)
{
bFirst = false;

PixelFormat = ChoosePixelFormat(m_hDC, pfd);
bOk = SetPixelFormat(m_hDC, PixelFormat, pfd);
if (!bOk)
{
MessageBox(_T("GL set pixel format fail!"), _T("Error"), MB_OK);
return bOk;
}

m_hGLRC = wglCreateContext(m_hDC);
bOk = wglMakeCurrent(m_hDC, m_hGLRC);
if (!bOk)
{
MessageBox(_T("Set up GL render context fail!"), _T("Error"), MB_OK);
return bOk;
}

InitMultisample(m_hWnd);
if (arbMultisampleSupported)
{
DestroyWindow();
CreateEx(0, m_className, "OglWnd", WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
m_rect, m_parent, 0);
return bOk;
}
}
else
{
PixelFormat = arbMultisampleFormat;//第二次创建
bOk = SetPixelFormat(m_hDC, PixelFormat, pfd);
if (!bOk)
{
MessageBox(_T("GL set pixel format fail!"), _T("Error"), MB_OK);
return bOk;
}

m_hGLRC = wglCreateContext(m_hDC);
bOk = wglMakeCurrent(m_hDC, m_hGLRC);
if (!bOk)
{
MessageBox(_T("Set up GL render context fail!"), _T("Error"), MB_OK);
return bOk;
}
}

return bOk;
}

那么函数InitMultisample是怎么来的了。NeHe刚好有一节课是讲这个的,我是从那里下载得到的。该函数代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// InitMultisample: Used To Query The Multisample Frequencies
bool InitMultisample(HWND hWnd)
{
// See If The String Exists In WGL!
if (!WGLisExtensionSupported("WGL_ARB_multisample"))
{
arbMultisampleSupported=false;
return false;
}

// Get Our Pixel Format
PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");
if (!wglChoosePixelFormatARB)
{
arbMultisampleSupported=false;
return false;
}

// Get Our Current Device Context
HDC hDC = GetDC(hWnd);

int pixelFormat;
int valid;
UINT numFormats;
float fAttributes[] = {0,0};

// These Attributes Are The Bits We Want To Test For In Our Sample
// Everything Is Pretty Standard, The Only One We Want To
// Really Focus On Is The SAMPLE BUFFERS ARB And WGL SAMPLES
// These Two Are Going To Do The Main Testing For Whether Or Not
// We Support Multisampling On This Hardware.
int iAttributes[] =
{
WGL_DRAW_TO_WINDOW_ARB,GL_TRUE,
WGL_SUPPORT_OPENGL_ARB,GL_TRUE,
WGL_ACCELERATION_ARB,WGL_FULL_ACCELERATION_ARB,
WGL_COLOR_BITS_ARB,24,
WGL_ALPHA_BITS_ARB,8,
WGL_DEPTH_BITS_ARB,16,
WGL_STENCIL_BITS_ARB,0,
WGL_DOUBLE_BUFFER_ARB,GL_TRUE,
WGL_SAMPLE_BUFFERS_ARB,GL_TRUE,
WGL_SAMPLES_ARB,16,//16设置了抗锯齿的质量
0,0
};

// First We Check To See If We Can Get A Pixel Format For 4 Samples
valid = wglChoosePixelFormatARB(hDC,iAttributes,fAttributes,1,pixelFormat,numFormats);

// If We Returned True, And Our Format Count Is Greater Than 1
if (valid numFormats >= 1)
{
arbMultisampleSupported = true;
arbMultisampleFormat = pixelFormat;
return arbMultisampleSupported;
}

// Our Pixel Format With 4 Samples Failed, Test For 2 Samples
iAttributes[19] = 2;
valid = wglChoosePixelFormatARB(hDC,iAttributes,fAttributes,1,pixelFormat,numFormats);
if (valid numFormats >= 1)
{
arbMultisampleSupported = true;
arbMultisampleFormat = pixelFormat;
return arbMultisampleSupported;
}

// Return The Valid Format
return arbMultisampleSupported;
}

该函数的使用还是很简单的,基本上是修改了2个全局变量,arbMultisampleSupported判断是否支持多重采样,arbMultisampleFormat则是对应的像素格式。我在代码中注释了一行:WGL_SAMPLES_ARB,16,//16设置了抗锯齿的质量。这里貌似可以修改数字的大小,据说一般显卡都支持到16,16的情况下,抗锯齿很好了,4的话效果一般般。这个函数相关的完整代码可以从NeHe下载。
至于完整代码,可以参考我上一篇文章,然后将这一篇文章的代码加进去即可。
未抗锯齿的效果:
抗锯齿的效果:

我以前使用过glut的窗口环境,以及在CView中渲染OpenGL的类。但是这些都不怎么方便。首先,在windows下,MFC比glut,glui作为界面框架要方便很多,其次,CView类没有单独的CWnd类方便,自定义的窗口类可以嵌入到任意窗口内部,这样就可以脱离框架,方便移植了。
我的思路是实现一个COglWnd类。该类可以负责构建OpenGL渲染环境,并且调用模型类渲染,另外负责响应鼠标旋转放缩移动模型等,也可以添加一些额外的操作。
该类需要提供的外部接口,创建窗口和渲染模型。
COglWnd使用方法:在容器类(比如CView)的OnCreate函数内调用COglWnd的Create函数创建窗口,在容器类的OnSize函数类内调用COglWnd类的SetWindowPos移动窗口位置。
COglWnd的实现:
1.构建OpenGL渲染环境。这个必须在COglWnd的OnCreate函数类实现。具体实现我写在了SetupGLContext函数内部。该函数主要是依次调用了ChoosePixelFormat,SetPixelFormat,wglCreateContext,wglMakeCurrent函数而已。基本上就是设置下OpenGL的一些参数,然后构造下渲染环境的意思,非常好理解。具体可以google。
2.渲染。这里需要屏蔽OnEraseBkgnd函数,还有OnPaint内调用实际的渲染函数RenderScene而已。这里需要注意的是,我们屏蔽了CWnd的背景擦除函数,这样能够避免一定程度的闪烁。还有,当我们需要更新显示的时候,可以直接调用RenderScene而不是调用MFC的Invalidate函数。这样可以略去MFC的消息传递等。
3.鼠标响应。这里旋转我使用了轨迹球类。其余的直接调用OpenGL函数即可实现。
下面是我的COglWnd类,内部使用了一个vertex模版类存储数据和渲染球类来实现鼠标响应。上面所述,均可参照我的实现细节。我这个类就是嵌入在CView内作为子窗口使用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#include "libyx/vertex.h"
#include "libyx/trackball.h"
#include "Mesh/TriMesh.h"
#include "Mesh/Texture.h"
#include <vector>
// COglWnd

namespace LibYX
{

class COglWnd : public CWnd
{
DECLARE_DYNAMIC(COglWnd)

public:
COglWnd();
virtual ~COglWnd();
enum CURSOR_TYPE
{
CURSOR_HAND = 0, CURSOR_PEN = 1
};

enum MOUSE_STATE
{
MOUSE_NONE = 0,
MOUSE_LEFTBUTTON_DOWN = 1, MOUSE_LEFTBUTTON_UP = 2,
MOUSE_MIDDLEBUTTON_DOWN = 3, MOUSE_MIDDLEBUTTON_UP = 4,
MOUSE_RIGHTBUTTON_DOWN = 5, MOUSE_RIGHTBUTTON_UP = 6
};

//外部接口
public:
void Create(CRect rect, CWnd *parent);
CString LoadModel();
CString SaveModel();
bool ChooseDrawColor(v4f color);
bool ChooseStrokeColor(v4f color);
CString ChooseDrawTexture();
bool ChooseBackColor(v4f color);
void RenderScene();//渲染函数

public:
void SetCursorType(int nType) { m_cursorType = nType; }

//消息处理函数
protected:
DECLARE_MESSAGE_MAP()
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg void OnDestroy();
afx_msg void OnPaint();
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
afx_msg void OnMButtonDown(UINT nFlags, CPoint point);
afx_msg void OnMButtonUp(UINT nFlags, CPoint point);
afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
afx_msg void OnRButtonUp(UINT nFlags, CPoint point);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt);
afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);

//OpenGL函数
private:
BOOL SetupGLContext();
BOOL DetachGLContext();

private:
void SetGLViewFrustum(int left, int top, int right, int bottom, float DepthMin, float DepthMax, float Fov);
void SetGLViewFrustum(int left, int top, int right, int bottom);
void SetGLFOV(float Fov);
void ResizeGLViewport(int width, int height);
void SetEye(v3f pos, v3f dest, v3f up);

private:
void InitColor();
void InitLight();

private:
void InitialScene();
void ReleaseScene();

private:
void RenderBackGround();

public:
CTriMesh m_mesh;
CTexture m_backTexture;

private:
CRect m_rect;
CWnd* m_parent;
int m_nWinWidth;
int m_nWinHeight;
HDC m_hDC;
HGLRC m_hGLRC;
float m_fGLDepthMin;
float m_fGLDepthMax;
float m_fGLFov;
BOOL m_bGLSetup;

private:
v4f m_backColor;
v4f m_diffuseLight;
v4f m_specularLight;
v4f m_ambientLight;
v4f m_emissionLight;
v4f m_gloabalAmbientLight;

private:
v3f m_eye;
v3f m_dest;
v3f m_up;
Trackball m_trackball;

private:
int m_cursorType;
int m_mouseState;
};
};

GlWnd.cpp : 实现文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
#include "stdafx.h"
#include "OglWnd.h"
#include "../Resource.h"
#include <gl/glew.h>

#pragma comment(lib, "opengl.lib")
#pragma comment(lib, "glu.lib")
#pragma comment(lib, "glew32.lib")

// CGlWnd
namespace LibYX
{

IMPLEMENT_DYNAMIC(COglWnd, CWnd)

COglWnd::COglWnd()
{
m_bGLSetup = false;
m_fGLDepthMin = 0.0001f;
m_fGLDepthMax = 100.0f;
m_fGLFov = 60.0f;
m_cursorType = CURSOR_HAND;
m_mouseState = MOUSE_NONE;
}

COglWnd::~COglWnd()
{

}

void COglWnd::Create(CRect rect, CWnd *parent)
{
if (m_bGLSetup) return;

ASSERT(rect);
ASSERT(parent);
m_rect = rect;
m_parent = parent;

CString className = AfxRegisterWndClass(
CS_HREDRAW | CS_VREDRAW | CS_OWNDC, NULL, (HBRUSH)GetStockObject(BLACK_BRUSH), NULL);

CreateEx(0, className, "OglWnd", WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
rect, parent, 0);
m_mesh.Wnd(this);
}

CString COglWnd::LoadModel()
{
char filefilter[] = _T("M Files (*.m) |*.m|Wavefront Files (*.obj) |*.obj|");
CFileDialog dlg(TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
filefilter, NULL, 0);
CString pathname;

if (IDOK != dlg.DoModal())
return pathname;

pathname = dlg.GetPathName();
if (pathname.IsEmpty() == TRUE)
{
AfxMessageBox("输入模型文件名为空!");
return pathname;
}

CString ext = dlg.GetFileExt();
if (ext == "obj" || ext == "OBJ")
{
m_mesh.LoadOBJModel(pathname);
}
else if (ext == "m" || ext == "M")
{
m_mesh.LoadMModel(pathname);
}
RenderScene();

return pathname;
}

CString COglWnd::SaveModel()
{
CString pathname;
if (m_mesh.HasModelLoad() == FALSE)
{
return pathname;
}

char defExt[] = _T(".obj");
char filefilter[] = _T("Wavefront Files (*.obj) |*.obj|m Files (*.m) |*.m||");
CFileDialog dlg(FALSE, defExt, m_mesh.GetModelName().c_str(), OFN_HIDEREADONLY, filefilter, NULL, 0);

if (IDOK != dlg.DoModal())
return pathname;

CString ext = dlg.GetFileExt();

if (ext == _T("obj") || ext == _T("OBJ"))
{
pathname = dlg.GetPathName();
if (pathname.IsEmpty())
{
AfxMessageBox("输入模型文件名为空!");
}
else
{
m_mesh.SaveOBJModel(pathname);
}
}
else if (ext == _T("m") || ext == _T("M"))// Save model without material)
{
pathname = dlg.GetPathName();
if (pathname.IsEmpty())
{
AfxMessageBox("输入模型文件名为空!");
}
else
{
m_mesh.SaveMModel(pathname);
}
}

return pathname;
}

bool COglWnd::ChooseDrawColor(v4f color)
{
CColorDialog colorDialog;
COLORREF colorRef;

if (colorDialog.DoModal() == IDOK)//如果按下颜色对话框的OK键
{
colorRef = colorDialog.GetColor();
color[0] = GetRValue(colorRef);
color[1] = GetGValue(colorRef);
color[2] = GetBValue(colorRef);

m_mesh.DrawColor(color);
return true;
}

return false;
}

bool COglWnd::ChooseStrokeColor(v4f color)
{
CColorDialog colorDialog;
COLORREF colorRef;

if (colorDialog.DoModal() == IDOK)//如果按下颜色对话框的OK键
{
colorRef = colorDialog.GetColor();
color[0] = GetRValue(colorRef);
color[1] = GetGValue(colorRef);
color[2] = GetBValue(colorRef);
m_mesh.StrokeColor(color);
RenderScene();
return true;
}

return false;
}

bool COglWnd::ChooseBackColor(v4f color)
{
CColorDialog colorDialog;
COLORREF colorRef;

if (colorDialog.DoModal() == IDOK)//如果按下颜色对话框的OK键
{
colorRef = colorDialog.GetColor();
m_backColor[0] = GetRValue(colorRef);
m_backColor[1] = GetGValue(colorRef);
m_backColor[2] = GetBValue(colorRef);
color = m_backColor;
glClearColor(m_backColor[0], m_backColor[1], m_backColor[2], 1.0f);
RenderScene();
return true;
}

return false;
}

CString COglWnd::ChooseDrawTexture()
{
char filefilter[] = _T("Jpg Files (*.jpg)|*jpg|Bmp Files (*.bmp)|*.bmp|\
Png Files (*.png)|*.png|Gif Files (*.gif)|*.gif|Tga Files (*.tga)|*.tga|");
CFileDialog dlg(TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
filefilter, NULL, 0);
CString strPathName;

if (dlg.DoModal() == IDOK)
{
strPathName = dlg.GetPathName();
m_mesh.SetTexture(strPathName);
}

return strPathName;
}

//////////////////////////////////////////////////////////////////////////
//以下是OpenGL相关的函数
BOOL COglWnd::SetupGLContext()
{
DWORD dwFlags = (PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL);

PIXELFORMATDESCRIPTOR pfd =
{
sizeof(PIXELFORMATDESCRIPTOR), // Structure size,
1, // Structure version number
dwFlags, // Property flags
PFD_TYPE_RGBA, // RGBA mode
24, // 24-bit color
0, 0, 0, 0, 0, 0, // 8-bit each color
0, 0, 0, 0, 0, 0, 0, // No alpha or accum. buffer,
32, // 32-bit z-buffer
0, 0, // No stencil or aux buffer
PFD_MAIN_PLANE, // Mainn layer type
0, // Reserved
0, 0, 0, // Unsupported.
};

m_hDC = GetDC()->m_hDC;

int PixelFormat = ChoosePixelFormat(m_hDC, pfd);
BOOL bOk = false;
bOk = SetPixelFormat(m_hDC, PixelFormat, pfd);
if (!bOk)
{
MessageBox(_T("GL set pixel format fail!"), _T("Error"), MB_OK);
return bOk;
}
m_hGLRC = wglCreateContext(m_hDC);
bOk = wglMakeCurrent(m_hDC, m_hGLRC);
if (!bOk)
{
MessageBox(_T("Set up GL render context fail!"), _T("Error"), MB_OK);
return bOk;
}

return bOk;
}

BOOL COglWnd::DetachGLContext()
{
wglMakeCurrent(m_hDC, NULL);
wglDeleteContext(m_hGLRC);
::ReleaseDC(this->m_hWnd, m_hDC);

return TRUE;
}

void COglWnd::SetGLViewFrustum(GLint left, GLint top, GLint right, GLint bottom,
GLfloat DepthMin, GLfloat DepthMax, GLfloat Fov)
{
m_fGLDepthMin = DepthMin;
m_fGLDepthMax = DepthMax;
m_fGLFov = Fov;

SetGLViewFrustum(left, top, right, bottom);
}

void COglWnd::SetGLViewFrustum(GLint left, GLint top, GLint right, GLint bottom)
{
glViewport(left, top, (right - left), (bottom - top));

float AspectRatio = (float)(right - left) / (float)(bottom - top);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

gluPerspective(m_fGLFov, AspectRatio, m_fGLDepthMin, m_fGLDepthMax);
}

void COglWnd::SetGLFOV(GLfloat Fov)
{
m_fGLFov = Fov;

int vp[4];
glGetIntegerv(GL_VIEWPORT, vp);
float AspectRatio = (float)(vp[2]) / (float)(vp[3]);

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

gluPerspective(m_fGLFov, AspectRatio, m_fGLDepthMin, m_fGLDepthMax);
}

void COglWnd::SetEye(v3f pos, v3f desti, v3f up)
{
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(pos[0], pos[1], pos[2], desti[0], desti[1], desti[2], up[0], up[1], up[2]);
}

void COglWnd::ResizeGLViewport(GLint width, GLint height)
{
glViewport(0, 0, width, height);

float AspectRatio = (float)width / (float)height;

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

gluPerspective(m_fGLFov, AspectRatio, m_fGLDepthMin, m_fGLDepthMax);
}

void COglWnd::InitialScene()
{
if (glewInit() != GLEW_OK)
{
return;
}

m_eye[0] = m_eye[1] = 0.0;
m_eye[2] = 6.0f;
m_dest[0] = m_dest[1] = m_dest[2] = 0.0;
m_up[0] = m_up[2] = 0.0;
m_up[1] = 1.0;
SetEye(m_eye, m_dest, m_up);

InitColor();
InitLight();

glCullFace(GL_BACK);
glEnable(GL_CULL_FACE);
glShadeModel(GL_SMOOTH);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glClearColor(m_backColor[0], m_backColor[1], m_backColor[2], 1.0f);
glDrawBuffer(GL_BACK);

//glEnable(GL_ALPHA_TEST);
//glAlphaFunc(GL_GREATER, 0.9);//0.5可以换成任何在0~1之间的数

//m_backTexture.LoadFromFile("OglWnd\\blue_back.jpg");
//m_backTexture.Init2DTex();

m_trackball.setBallSpeed(10.0);
m_mesh.InitData();
m_mesh.DrawMouseStroke(true);
//m_mesh.LoadOBJModel("MeshData\\bunny_480k.obj");
//m_mesh.LoadModel("MeshData\\plane.obj");
m_mesh.LoadMModel("MeshData\\bunny_80k.m");
//m_mesh.LoadMModel("MeshData\\bunny_5k.m");
}

void COglWnd::InitColor()
{
//m_backColor[0] = 0.15f;
//m_backColor[1] = 0.20f;
//m_backColor[2] = 0.267f;
m_backColor[0] = m_backColor[1] = m_backColor[2] = 0.0;
}

void COglWnd::InitLight()
{
v4f lightOnePos(V4F(0.0f, 0.0f, 20.0f, 0.0f));

m_gloabalAmbientLight[0] = m_gloabalAmbientLight[1] = m_gloabalAmbientLight[2]
= m_gloabalAmbientLight[3] = 0.2f;
m_diffuseLight[0] = m_diffuseLight[1] = m_diffuseLight[2] = m_diffuseLight[3] = 1.0;
m_diffuseLight[3] = 1.0;
m_specularLight[0] = m_specularLight[1] = m_specularLight[2] = m_specularLight[3] = 1.0;
m_specularLight[3] = 1.0;
m_ambientLight[0] = m_ambientLight[1] = m_ambientLight[2] = m_ambientLight[3] = 0.2;
m_emissionLight[0] = m_emissionLight[1] = m_emissionLight[2] = m_emissionLight[3] = 0.1;

glEnable(GL_LIGHT0);
glEnable(GL_LIGHTING);

glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR);//纹理映射之后再应用镜面光高亮的效果
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, (float*)m_gloabalAmbientLight);

glLightfv(GL_LIGHT0, GL_POSITION, (float*)lightOnePos);
glLightfv(GL_LIGHT0, GL_DIFFUSE, (float*)m_diffuseLight);
glLightfv(GL_LIGHT0, GL_SPECULAR, (float*)m_specularLight);
}

void COglWnd::RenderBackGround()
{
glPushAttrib(GL_ALL_ATTRIB_BITS);
glDisable(GL_DEPTH_TEST);

glEnable(GL_TEXTURE_2D);
m_backTexture.Bind2d();

glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
gluOrtho2D(0.0, 1.0, 0.0, 1.0);

glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glBegin(GL_QUADS);
glTexCoord2f(0.0, 1.0);
glVertex2f(0.0, 0.0);
glTexCoord2f(1.0, 1.0);
glVertex2f(1.0, 0.0);
glTexCoord2f(1.0, 0.0);
glVertex2f(1.0, 1.0);
glTexCoord2f(0.0, 0.0);
glVertex2f(0.0, 1.0);
glEnd();
glPopMatrix();

glMatrixMode(GL_PROJECTION);
glPopMatrix();

glPopAttrib();
}

void COglWnd::RenderScene()
{
if (m_hGLRC != wglGetCurrentContext())
{
wglMakeCurrent(m_hDC, m_hGLRC);
}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear Screen And Depth Buffer

//RenderBackGround();
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glMultMatrixf((float*)(m_trackball.matrix().transpose()));

m_mesh.DrawScene();

glPopMatrix();
SwapBuffers(m_hDC);
}

void COglWnd::ReleaseScene()
{
m_mesh.DestroyData();
DetachGLContext();
}

BEGIN_MESSAGE_MAP(COglWnd, CWnd)
ON_WM_DESTROY()
ON_WM_PAINT()
ON_WM_CREATE()
ON_WM_SIZE()
ON_WM_ERASEBKGND()
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_MBUTTONDOWN()
ON_WM_MBUTTONUP()
ON_WM_RBUTTONDOWN()
ON_WM_RBUTTONUP()
ON_WM_MOUSEMOVE()
ON_WM_MOUSEWHEEL()
ON_WM_SETCURSOR()
ON_WM_MBUTTONDOWN()
ON_WM_MBUTTONUP()
ON_WM_RBUTTONDOWN()
ON_WM_RBUTTONUP()
END_MESSAGE_MAP()

//以下是消息处理函数
int COglWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CWnd::OnCreate(lpCreateStruct) == -1)
return -1;

if (!SetupGLContext())
{
AfxMessageBox("初始化OpenGL窗口设置失败!");
return -1;
}

InitialScene();

m_bGLSetup = TRUE;

return 0;
}

// COglWnd 消息处理程序

void COglWnd::OnSize(UINT nType, int cx, int cy)
{
CWnd::OnSize(nType, cx, cy);

// TODO: 在此处添加消息处理程序代码
m_nWinWidth = cx;
m_nWinHeight = cy;
m_mesh.WindowWidth(m_nWinWidth);
m_mesh.WindowHeight(m_nWinHeight);
m_trackball.init(cx, cy);

if (m_bGLSetup)
{
wglMakeCurrent(m_hDC, m_hGLRC);
ResizeGLViewport(cx, cy);
}
}

BOOL COglWnd::OnEraseBkgnd(CDC* pDC)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
return TRUE;
//return CWnd::OnEraseBkgnd(pDC);
}

void COglWnd::OnDestroy()
{
CWnd::OnDestroy();

ReleaseScene();
}

void COglWnd::OnPaint()
{
CPaintDC dc(this); // device context for painting

if (m_bGLSetup)
{
RenderScene();
}
}

BOOL COglWnd::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
if (m_cursorType == CURSOR_HAND)
{
SetCursor(LoadCursor(AfxGetApp()->m_hInstance, MAKEINTRESOURCE(IDC_CURSOR_HAND)));
}
else if (m_cursorType == CURSOR_PEN)
{
SetCursor(LoadCursor(AfxGetApp()->m_hInstance, MAKEINTRESOURCE(IDC_CURSOR_PEN)));
}

return TRUE;
//return CWnd::OnSetCursor(pWnd, nHitTest, message);
}

void COglWnd::OnLButtonDown(UINT nFlags, CPoint point)
{
m_mouseState = MOUSE_LEFTBUTTON_DOWN;
if (m_cursorType == CURSOR_HAND)//如果是浏览模式
{
m_trackball.buttonPressed(Trackball::LEFT_BUTTON, point.x, point.y);
m_mesh.DrawMouseStroke(false);
}
else if (m_cursorType == CURSOR_PEN)//如果是绘制模式
{
m_mesh.DrawMouseStroke(true);
m_mesh.ClearMouseStroke();//清除原鼠标点集合
//添加新鼠标点
m_mesh.AddMousePoint(V2F(point.x, m_nWinHeight - point.y - 1));
}

CWnd::OnLButtonDown(nFlags, point);
}

void COglWnd::OnLButtonUp(UINT nFlags, CPoint point)
{
m_mouseState = MOUSE_LEFTBUTTON_UP;
if (m_cursorType == CURSOR_HAND)
{
m_trackball.buttonReleased(Trackball::LEFT_BUTTON);
}
else if (m_cursorType == CURSOR_PEN)
{
//添加新鼠标点
m_mesh.AddMousePoint(V2F(point.x, m_nWinHeight - point.y - 1));
m_mesh.DrawStroke();//绘制笔画
}
RenderScene();

CWnd::OnLButtonUp(nFlags, point);
}

void COglWnd::OnMButtonDown(UINT nFlags, CPoint point)
{
m_mouseState = MOUSE_MIDDLEBUTTON_DOWN;
if (m_cursorType == CURSOR_HAND)
{
m_mesh.DrawMouseStroke(false);
m_trackball.buttonPressed(Trackball::MIDDLE_BUTTON, point.x, point.y);
}

CWnd::OnMButtonDown(nFlags, point);
}

void COglWnd::OnMButtonUp(UINT nFlags, CPoint point)
{
m_mouseState = MOUSE_MIDDLEBUTTON_UP;
if (m_cursorType == CURSOR_HAND)
{
m_trackball.buttonReleased(Trackball::MIDDLE_BUTTON);
}

CWnd::OnMButtonUp(nFlags, point);
}

void COglWnd::OnRButtonDown(UINT nFlags, CPoint point)
{
m_mouseState = MOUSE_RIGHTBUTTON_DOWN;
if (m_cursorType == CURSOR_HAND)
{
m_mesh.DrawMouseStroke(false);
m_trackball.buttonPressed(Trackball::RIGHT_BUTTON, point.x, point.y);
}

CWnd::OnRButtonDown(nFlags, point);
}

void COglWnd::OnRButtonUp(UINT nFlags, CPoint point)
{
m_mouseState = MOUSE_RIGHTBUTTON_UP;
if (m_cursorType == CURSOR_HAND)
{
m_trackball.buttonReleased(Trackball::RIGHT_BUTTON);
}

CWnd::OnRButtonUp(nFlags, point);
}

void COglWnd::OnMouseMove(UINT nFlags, CPoint point)
{
SetFocus();

if (m_cursorType == CURSOR_HAND)
{
if (m_mouseState == MOUSE_LEFTBUTTON_DOWN
|| m_mouseState == MOUSE_MIDDLEBUTTON_DOWN
|| m_mouseState == MOUSE_RIGHTBUTTON_DOWN)
{
m_trackball.update(point.x, point.y);
RenderScene();
}
}
else if (m_cursorType == CURSOR_PEN)
{
if (m_mouseState == MOUSE_LEFTBUTTON_DOWN)
{
//添加新鼠标点
m_mesh.AddMousePoint(V2F(point.x, m_nWinHeight - point.y - 1));
RenderScene();//更新显示
}
}

CWnd::OnMouseMove(nFlags, point);
}

//中键滚动放缩模型
BOOL COglWnd::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
if (m_cursorType == CURSOR_HAND m_mesh.HasModelLoad())
{
float z = m_eye[2];
z += zDelta / 1000.f;
z = z < m_fGLDepthMin ? m_fGLDepthMin : z;
SetEye(m_eye, m_dest, m_up);

RenderScene();
}

return CWnd::OnMouseWheel(nFlags, zDelta, pt);
}
};

效果如下图所示的中间渲染窗口:

说实话,这几天为了CMFCToolBarComboBoxButton上的莫名其妙自动切换选择的bug烦死了,浪费了很多时间。也是过其它替代方法,发现都实现不了需要的界面效果。
我也不知道为什么自己对界面效果这么纠结。伤不起啊。本来我就没用过vs2008更新版本的MFC,也没打算用这个。由于上一次做的东西用到了切分视图,但是发现FormView自适应控件大小的实现非常麻烦,老是出现些不爽的bug,而且添加新的控件还得编辑界面,非常不爽。所以,打算使用新版本MFC里面的属性窗口。
刚好发现vs2010的项目导航可以生成这样的工程,果断试验之。虽然过程无比艰辛,总算可以使用这些自适应的DockPane窗口了。而且还发现这样的结构非常合适添加子窗口,以后的项目都可以采用这样的界面了,只要平台在windows下。
但是,美中最不足的是CFrameWndEx(使用CDockablePane必须使用扩展的框架窗口),只能使用恶心的CMFCToolBar,因为CFrameWndEx的菜单和工具栏都是可以DockPane的子类。我只能说CMFCToolBar的使用太TMD的讲究了,一个不注意效果就不对。因为使用这个东西的人少,网上资料也很少。只能一直google,还有查看类定义的源码,慢慢尝试了。
现在,我这里说的只是添加组合框。
首先,在资源编辑器中新建工具栏IDR_BUILD,留下一个空位,如ID_BUILD_CHOOSE。如下图,第二个就是我预留的ID_BUILD_CHOOSE的空位。
然后,在CMainFrame::OnCreate中创建两个工具栏,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
   if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER
| CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC)
|| !m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("未能创建工具栏\n");
return -1; // 未能创建
}

if (!m_wndToolBarBuild.Create(this, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_TOOLTIPS | CBRS_FLYBY
| CBRS_HIDE_INPLACE | CBRS_SIZE_DYNAMIC| CBRS_GRIPPER | CBRS_BORDER_3D, IDC_MFCTOOLBAR_BUILD)
|| !m_wndToolBarBuild.LoadToolBar(IDR_BUILD))
{
TRACE0("未能创建build工具栏\n");
return -1; // 未能创建
}

if (!m_wndStatusBar.Create(this))
{
TRACE0("未能创建状态栏\n");
return -1; // 未能创建
}
m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT));

m_wndMenuBar.EnableDocking(CBRS_ALIGN_ANY);
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
m_wndToolBarBuild.EnableDocking(CBRS_ALIGN_ANY);

EnableDocking(CBRS_ALIGN_ANY);
DockPane(m_wndMenuBar);
DockPane(m_wndToolBarBuild);
DockPaneLeftOf(m_wndToolBar, m_wndToolBarBuild);

注意,创建第二个工具栏的时候需要使用Create而不是CreateEx,如果使用CreateEx就不能使用后面的ID参数。
现在需要实现三个消息。

1
2
3
ON_REGISTERED_MESSAGE(AFX_WM_RESETTOOLBAR, CMainFrame::OnToolbarReset)
ON_COMMAND(ID_BUILD_CHOOSE, CMainFrame::OnClickChoose)
ON_CBN_SELCHANGE(ID_BUILD_CHOOSE, CMainFrame::OnSelChangeClick)

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
LRESULT CMainFrame::OnToolbarReset(WPARAM wParam, LPARAM lParam)
{
UINT uiToolBarId = (UINT)wParam;

switch (uiToolBarId)
{
case IDR_MAINFRAME:
break;

case IDR_BUILD:
{
CMFCToolBarComboBoxButton comboButton(ID_BUILD_CHOOSE, GetCmdMgr()->GetCmdImage(ID_BUILD_CHOOSE, FALSE), CBS_DROPDOWN);
comboButton.EnableWindow(TRUE);
comboButton.SetCenterVert();
comboButton.SetFlatMode();
comboButton.AddItem(_T("Example Stack"));
comboButton.AddItem(_T("Upsample Pyramid"));
comboButton.AddItem(_T("Jitter Pyramid"));
comboButton.AddItem(_T("Correct Pyramid"));
comboButton.SelectItem(0);
m_wndToolBarBuild.ReplaceButton(ID_BUILD_CHOOSE, comboButton);
}
break;

default:
break;
}

return 0;
}

void CMainFrame::OnClickChoose()
{

}

void CMainFrame::OnSelChangeClick()
{
CMFCToolBarComboBoxButton* pCombo = CMFCToolBarComboBoxButton::GetByCmd(ID_BUILD_CHOOSE, TRUE);

int nIndex = pCombo->GetCurSel();
theAppState.ts.m_nDisplayMode = nIndex;
theAppState.ts.UpdateDisplayWnd();
}

OnToolbarReset中实现的是添加组合框,根据MSDN的文档,应该采用这样的方法,当然也可以采用CMFCToolBar的protected方法InsertButton,不过需要把这个方法改成public的,这个就需要修改类定义的源码。必须响应ID_BUILD_CHOOSE的点击消息,否则组合框是灰的,无法点击。
OnSelChangeClick响应的是组合框选择变化的消息,这个消息的实现必须用 CMFCToolBarComboBoxButton::GetByCmd获得组合框的指针,否则得不到正确的效果。
最后要讲的是我碰到的一个bug,恶心了我一周了。我在工具栏上面定义了向前和向后操作,如果我一直点击向前或者向后按钮,组合框会自动切换选择到最后一项。我调试了很久,发现跟我的算法实现代码一点关系都没有,我什么都不做,操作界面就会出现这个bug。最后,我偶然发现修改CMFCToolBarComboBoxButton comboButton(ID_BUILD_CHOOSE, GetCmdMgr()->GetCmdImage(ID_BUILD_CHOOSE, FALSE), CBS_DROPDOWNLIST);的最后一个参数为CBS_DROPDOWN就不会出现这样的bug。只是有一个不爽的地方,那就是点击组合框的时候,会出现光标而已。勉强接受了吧。