标题的意思是使用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。只是有一个不爽的地方,那就是点击组合框的时候,会出现光标而已。勉强接受了吧。

这里讲的不是pca的原理,我还没仔细去研究过。我刚刚使用过opencv进行pca降维,效果成功过。所以只是记录下方式和注意的地方。当然也参考了网上很多的代码。网上的代码大多大同小异,我的代码当然也和别人的差不多。

首先,我用的是C版本的OpenCV。我不是嫌弃C++版本的,而是C版本的OpenCV本来已经够复杂了,我不想再跟踪C++版本的这些对象的内存了。我们需要使用到OpenCV的CvMat结构存储数据。

我们需要使用以下的CvMat。
CvMat m_pData;//原始样本集
CvMat
m_pMean;//平均值向量、
CvMat m_pEigVals;//协方差矩阵的特征值
CvMat
m_pEigVecs;//协方差矩阵的特征向量
CvMat m_pResult;//PCA降维的结果
CvMat
m_pQueryIn;//输入的一个数据
CvMat* m_pQueryOut;//用样本集的PCA信息降维后的一个数据

首先,我们创建数据m_pData = cvCreateMat(m_nRows, m_nDim, CV_64FC1);然后初始化数据。再创建m_pMean,m_pEigVals,m_pEigVecs。然后调用cvCalcPCA计算平均值向量和协方差矩阵的特征值和特征向量。最后一步是cvProjectPCA,在这个函数里面可以指定维度。我们就可以得到降维之后的数据。

现在再讲查询。首先创建一个数据。m_pQueryIn = cvCreateMat(1, m_nDim, CV_64FC1);m_pQueryOut = cvCreateMat(1, m_nUseDim, CV_64FC1);然后,我们初始化m_pQueryIn。再调用cvProjectPCA(m_pQueryIn, m_pMean, m_pEigVecs, m_pQueryOut);就可以得到降维之后的数据。

下面看下综合起来的代码。

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
if (m_pData)
{
cvReleaseMat(m_pData);
}
m_pData = cvCreateMat(m_nRows, m_nDim, CV_64FC1);
//在这里初始化样本集合
if (m_pMean)
{
cvReleaseMat(m_pMean);
}
m_pMean = cvCreateMat(1, m_nDim, CV_64FC1);
if (m_pEigVals)
{
cvReleaseMat(m_pEigVals);
}
m_pEigVals = cvCreateMat(1, std::min(m_nRows, m_nDim), CV_64FC1);

if (m_pEigVecs)
{
cvReleaseMat(m_pEigVecs);
}
m_pEigVecs = cvCreateMat(std::min(m_nRows, m_nDim), m_nDim, CV_64FC1);

cvCalcPCA(m_pData, m_pMean, m_pEigVals, m_pEigVecs, CV_PCA_DATA_AS_ROW);

if (m_pResult)
{
cvReleaseMat(m_pResult);
}
m_pResult = cvCreateMat(m_nRows, m_nUseDim, CV_64FC1);
cvProjectPCA(m_pData, m_pMean, m_pEigVecs, m_pResult);//现在m_pResult保存的就是降低维度之后的信息

//初始化查询需要使用的数据
if (m_pQueryIn)
{
cvReleaseMat(m_pQueryIn);
}
if (m_pQueryOut)
{
cvReleaseMat(m_pQueryOut);
}
m_pQueryIn = cvCreateMat(1, m_nDim, CV_64FC1);
m_pQueryOut = cvCreateMat(1, m_nUseDim, CV_64FC1);

//从m_nDim降到m_nUseDim维
double* CNeighborSet::Pca(double* pfIn)
{
m_pQueryIn->data.db = pfIn;
cvProjectPCA(m_pQueryIn, m_pMean, m_pEigVecs, m_pQueryOut);//现在pResult保存的是降低维度之后的信息
return m_pQueryOut->data.db;
}

现在还有一个问题,是确定我们到底能够降低到多少维度了。如果维度降低太多,就保持不了足够的信息,就会失真,达不到应用的效果。当然如果效果不对,可以慢慢增大维度,直到够用为止。
当然还有其它办法了。比如说,在代码中计算我们应该保留多少维度就能够保持90%的信息了。下面要给出的就是这个办法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
double fSum = 0.0;
double fSum0 = 0.0;
for (int i = 0; i < m_pEigVals->cols; ++i)
{
fSum += *(m_pEigVals->data.db + i);
}

double fTmp = fSum * m_fPcaRatio;
for (int i = 0; i < m_pEigVals->cols; ++i)
{
fSum0 += *(m_pEigVals->data.db + i);
if (fSum0 >= fTmp)
{
m_nUseDim = i + 1;
break;
}
}

事情的经过是这样的,我在实现一个基于OpenCv的CImage类。我需要实现它的拷贝构造函数,因为我要把它push_back到vector里面去。我过去的一直做法是在赋值操作符中实现真正的操作,在拷贝构造函数中调用。但是在这里却出现了内存错误。

好歹我还写过几年程序,那我就开始了艰辛的调试。经过调试我发现,我的程序一直只进入拷贝构造函数,然后进去赋值操作符,在赋值操作符中我释放了对象原有的内存。错误就出现在了这里。按照我这种写法的理解,push_back是先用默认构造函数构造对象加入vector,然后再用原有拷贝构造函数初始化内容,或者说用赋值操作符初始化内容。

事实上,完全不是这样的。push_back直接添加新的内存,然后对这块新的内存用拷贝构造函数初始化而已。既然这样的话,拷贝构造函数是不能够直接调用赋值操作符的。应该再重新写一个真正的赋值子函数,拷贝构造函数和赋值操作符都调用这个子函数。赋值操作符中多了一个先检查是否同一个对象,如果不是同一个则释放原有内存的过程而已。

具体的可以看,CImage的部分代码。

class CImage
{
public:
    CImage();
    ~CImage();
    CImage(const CImage yxImage);

    CImage operator =(const CImage yxImage);//深度拷贝

private:
    void CopyData(const CImage yxImage);
};
CImage::CImage(void)
{
    m_pImg = NULL;
    m_ppfData = NULL;
    m_pfData = NULL;
}

CImage::CImage(const char* pcszName) 
{
    m_pImg = NULL;
    m_ppfData = NULL;
    m_pfData = NULL;
    Load(pcszName);
}

CImage::~CImage(void)
{
    UnInit();
}

void CImage::CopyData(const CImage yxImage)
{
    m_pImg = cvCloneImage(yxImage.m_pImg);
    m_nRows = m_pImg->height;
    m_nCols = m_pImg->width * m_pImg->nChannels;
    int nLineSize = m_nCols * sizeof(double);
    m_ppfData = new double*[m_nRows];
    m_pfData = new double[m_nRows * m_nCols];
    for (int i = 0; i < m_pImg->height; ++i)
    {
        m_ppfData[i] = m_pfData[i * m_nCols];
        memcpy(m_ppfData[i], yxImage[i], nLineSize);
    }
}

CImage::CImage(const CImage yxImage)//复制构造函数
{
    //*this = yxImage;//不能用=来实现,否则在push_back时候会出现内存错误,因为vector不调用默认构造函数
     //而是先添加对象的内存,再用复制构造函数初始化数据
    CopyData(yxImage);
}

CImage CImage::operator=(const CImage yxImage)
{
    if (this == yxImage)
    {
        return *this;
    }

    UnInit();

    CopyData(yxImage);

    return *this;
}

昨天一直在尝试使用assimp导入obj模型,再把数据导入到cgal的Polyhedron_3中。结果发现,所有的模型都是平滑着色的。好奇怪的现象。。。我尝试了很多次修改着色模式,一点反应都没有。我去掉assimp显示就是对的。后面,怀疑到我在cgal中计算顶点法线的过程。果然是这里出问题了。计算顶点法线的过程中基本上只使用了一个邻接面。也就是cgal里面已经没有原有obj模型的拓扑性质了。所以,顶点法线基本上等于面法线了,效果就成了flat着色了。

这有什么办法了。难道在assimp中计算法线。即使这样,我能够得到正确的顶点法线,但是cgal里面已经没有原有模型的拓扑性质了。我的数据结构还是加载失败的。所以,我不能使用assimp。而且,assimp还不能导入.m模型。虽然,它能导入格式麻烦的obj模型已经很不错了。

下面是我用assimp加载模型,并且导入到cgal中的代码。

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
#include <CGAL/Polyhedron_incremental_builder_3.h>
#include "Enriched_polyhedron.h"
#include <vector>
#include <assimp/Importer.hpp> // C++ importer interface
#include <assimp/scene.h> // Output data structure
#include <assimp/postprocess.h> // Post processing flags

template <class HDS>
class Builder_obj : public CGAL::Modifier_base<HDS>
{
private:
typedef typename HDS::Vertex::Point Point;
typedef typename CGAL::Polyhedron_incremental_builder_3<HDS> Builder;
const char* pcszfileName;
const aiScene* scene;

public:
Builder_obj(const char* pFilename)
{
pcszfileName = pFilename;
}

~Builder_obj() {}

void operator()(HDS hds)
{
Builder builder(hds,true);
builder.begin_surface(3,1,6);

Assimp::Importer importer;
scene = importer.ReadFile(pcszfileName, aiProcessPreset_TargetRealtime_Quality);
if (!scene) {
fprintf (stderr, "ERROR: reading mesh %s\n", pcszfileName);
return;
}

for (unsigned int m_i = 0; m_i < scene->mNumMeshes; m_i++)
{
const aiMesh* mesh = scene->mMeshes[m_i];

for (unsigned int v_i = 0; v_i < mesh->mNumVertices; v_i++)
{
if (mesh->HasPositions())
{
const aiVector3D* vp = (mesh->mVertices[v_i]);
printf (" vp %i (%f,%f,%f)\n", v_i, vp->x, vp->y, vp->z);
builder.add_vertex(Point(vp->x, vp->y, vp->z));
}
}

for (unsigned int j = 0; j < mesh->mNumFaces; ++j)
{
const aiFace face = mesh->mFaces[j];
assert(face.mNumIndices == 3);

builder.begin_facet();
builder.add_vertex_to_facet(face.mIndices[0]);
builder.add_vertex_to_facet(face.mIndices[1]);
builder.add_vertex_to_facet(face.mIndices[2]);
builder.end_facet();
}

}

builder.end_surface();
}
};

template <class kernel, class items>
class Parser_obj
{
public:
typedef typename Enriched_Model::HalfedgeDS HalfedgeDS;
Parser_obj() {}
~Parser_obj() {}

public:
bool read(const char*pFilename,
Enriched_Model *pMesh)
{
CGAL_assertion(pMesh != NULL);
Builder_obj<HalfedgeDS> builder(pFilename);
pMesh->delegate(builder);
return true;
}
};

代码确实非常简洁,但是不能到达要求,又有什么办法了。如果谁有什么解决办法的,欢迎回复啊。。。所以,我打算自己实现一个适用于cgal的导入obj模型(我只导入顶点),.m模型,.off模型等的类吧,以方便以后扩充和重复使用吧。

最近用了点时间学习了QT,重新阅读了下OpenGL超级宝典第四版,并且基本阅读完了。坑爹的是看完了第四版居然在网上发现了已经可以下载第五版的中文版了。翻阅了下第五版的目录,发现作者已经把GPU编程融合到整本书里面去了。而不像第四版和第三版那样只是分出几张做些介绍而已。我以前在图书馆借了本OpenGL编程宝典第三版,居然只有第三版(PS:我堂堂大中南的图书馆也该更新下了,真不明白图书馆买那么多不需要的办公室软件书籍作甚,整个图书馆只能索引到3本qt相关的书籍),里面居然还有用汇编语言形式的GPU底层着色操作。这个在第四版就去掉了。第五版我没有仔细阅读。以后有时间的话,还是阅读下第五版的内容吧。接下来,也不打算继续看Qt的东西了,毕竟界面编程这种东西,看多了没意义。我毕竟用了几年的MFC,看qt这种封装的太好的东西跟看手册一样的,所以我还是不需要继续去背书了。。。
所以,接下来可能学习下着色语言的所谓橙皮书。也可能阅读英文原版的,毕竟我还没看过一本英文原版的书籍,也提高一下英语。还有要看看微分几何的书籍了。如果还有时间的话,看看数字图像处理,以及matlab的一些东西。
我还是回到标题吧。。。
前段时间,用MFC做了个交互式分割模型的程序,我用的CSplitWnd切分主窗口为三个视图。做这个界面的过程中,遇到最恶心的问题是,我的一个基于对话框的视图无法很好的自放缩子控件,也就是自动调整控件大小。不要跟我说在OnSize里面处理下就行了。不要告诉我再禁止掉滚动条。总之很难达到想要的结果。而且,代码会乱成一团,本来就不爽mfc框架生成的那么多代码。不要以为我在这里喷MFC,我毕竟是学MFC过来的,用了很多次。我以前的界面99%都是MFC做的。可以说是MFC养大了我,但是我慢慢发现我还是对MFC越来越不满了。
所以,我就去看Qt了。同学介绍有个豆子的空间里面关于qt的教程不错,我就把前面的大部分看了一遍,并且都把代码提取出来,全部整合到一个工程里面实验了一下。总之,发现qt还不错吧。至少对于前面的那个问题,qt里面非常好解决,因为它有水平布局和垂直布局的概念。
至于Qt和Opengl的结合,也非常简单。直接继承QGLWidget,实现几个函数就行了。qt5还提供了增强版本的3d类QGLView。这里我要说的是libQglviewer。这个是在qt上的一个增强opengl渲染C++库。封装了很多默认的功能,非常强大。cgal上面的qt框架的3d显示就是推荐的这个东西。
至于模型加载,我打算试试Assimp,用这个加载模型之后,提取出数据,再来初始化cgal的数据结构。这样就不用处理各种烦人的obj,m格式等等了。。。
估计下一个需要渲染的东西就是用这些东西实现了吧。