在前面的入门中,都是一个main 函数,没有菜单,没有对话框,如果要的话,我就想到与mfc 的组合。mfc 有菜单,对话框,opengl做图形显示。
我先是参考 cv::namedWindow, GLFWwindow以及其他程序嵌入到MFC中的教程
1: 运行效果
2:下载glfw, 并用cmake 建立编译环境,然后做如下一些小的修改。
在前面入门中看到,glfw中窗口的创建, 就是使用函数glfwCreateWindow. 在VS中, 找到glfwCreateWindow函数的定义位置, 是在 glfw3.h文件中, 新加入一个函数glfwCreateWindowEx声明, 如下:
* @reentrancy This function must not be called from a callback.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref window_creation
* @sa glfwDestroyWindow
*
* @since Added in version 3.0. Replaces `glfwOpenWindow`.
*
* @ingroup window
*/
GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, const char* title, GLFWmonitor* monitor, GLFWwindow* share);
GLFWAPI GLFWwindow* glfwCreateWindowEx(int width, int height, const char* title, GLFWmonitor* monitor, GLFWwindow* share,int hParent);
在原本glfwCreateWindow函数的参数列表中新加入了参数int hParent. 新加入的参数, 本应该是HWND类型, 但该类型定义于Windows.h中, 本着尽可能少的改动代码, 以int代替了HWND类型。
现在打开win32_platform.h文件, 找到其中struct _GLFWwindowWin32定义所在的位置, 新加入HWND handleParent, 用来保存父窗口的句柄作为参数传递给创建窗口的函数. 如下图所示:
// Win32-specific per-window data
//
typedef struct _GLFWwindowWin32
{
HWND handle;
HICON bigIcon;
HICON smallIcon;
GLFWbool cursorTracked;
GLFWbool iconified;
// The last received cursor position, regardless of source
int lastCursorPosX, lastCursorPosY;
HWND handleParent;
} _GLFWwindowWin32;
修改好参数结构体之后, 现在定位glfwCreateWindow函数的定义, 定义于文件window.c中. 复制glfwCreateWindow函数的定义, 粘贴在glfwCreateWindow函数的定义的下方, 更改函数名为glfwCreateWindowEx并加入参数int hParent. 在该函数的实现中找到_glfwPlatformCreateWindow函数的调用地方, 在其前方加入下述代码:
window->win32.handleParent = (HWND)hParent; //----------liwenz-------------
效果如下:
// Save the currently current context so it can be restored later
previous = _glfwPlatformGetCurrentContext();
if (ctxconfig.client != GLFW_NO_API)
glfwMakeContextCurrent(NULL);
window->win32.handleParent = (HWND)hParent; //----------liwenz-------------
// Open the actual window and create its context
if (!_glfwPlatformCreateWindow(window, &wndconfig, &ctxconfig, &fbconfig))
{
glfwMakeContextCurrent((GLFWwindow*) previous);
glfwDestroyWindow((GLFWwindow*) window);
return NULL;
}
现在, 沿着_glfwPlatformCreateWindow函数的函数调用一直找到API CreateWindowExW函数的调用地方, 位于win32_window.c文件定义的static int createWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconfig)函数中被调用. 在 CreateWindowExW函数前加入下述代码, 并将CreateWindowExW函数的倒数第四个参数改成window->win32.handleParent.
//--------------------liwenz
if (NULL != window->win32.handleParent) {
exStyle = 0;
style = WS_CHILDWINDOW | (wndconfig->visible ? WS_VISIBLE : 0);
}
截图如下:
wideTitle = _glfwCreateWideStringFromUTF8Win32(wndconfig->title);
if (!wideTitle)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Win32: Failed to convert window title to UTF-16");
return GLFW_FALSE;
}
//--------------------liwenz
if (NULL != window->win32.handleParent) {
exStyle = 0;
style = WS_CHILDWINDOW | (wndconfig->visible ? WS_VISIBLE : 0);
}
window->win32.handle = CreateWindowExW(exStyle,
_GLFW_WNDCLASSNAME,
wideTitle,
style,
xpos, ypos,
fullWidth, fullHeight,
window->win32.handleParent, // No parent window liwenz before NULL
NULL, // No window menu
GetModuleHandleW(NULL),
NULL);
free(wideTitle);
修改好了之后, 对代码进行编译, 还是运行前面示例进行验证. 仍然可以得到前面原始代码所展示的效果. 说明我们代码的修改没有对原本性能产生破坏。
3: 建立一个简单的mfc SDI 工程
按照以前教程 现代opengl 设计入门 准备第一个工程 添加include lib 目录,以及lib 的添加。
修改都在类CopenglmfcView里实现,添加
在openglmfcView.h 文件头加
#include
#include
在类中添加4个事件的响应函数,openglmfcView.h 中如下显示
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnDestroy();
afx_msg void OnSize(UINT nType, int cx, int cy);
在openglmfcView.cpp 中除了上面4个函数外,另要修改OnDraw
函数的代码就是分割我们前面例子,比如 现代opengl 设计入门,画图第一个三角形 这个最简单了。初始化代码放在 OnCreate, 结束释放代码放 OnDestroy,着色循环代码放OnDraw,framebuffer_size_callback的代码放OnSize。OnEraseBkgnd 只是取消 return CView::OnEraseBkgnd(pDC); 代之以 return 1;
return 1;
//return CView::OnEraseBkgnd(pDC);
在代码中增加类变量,代替代码间的沟通,因为循环和初始间需要共用。取消回调函数设置部分。
public:
GLFWwindow* m_window;
int m_shaderProgram;
unsigned int m_VAO,m_VBO;
最后代码如下:
OnDraw:
void CopenglmfcView::OnDraw(CDC* /*pDC*/)
{
CopenglmfcDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// TODO: add draw code for native data here
// render
// ------
glClearColor(0.2f, 0.3f, 0.5f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// draw our first triangle
glUseProgram(m_shaderProgram);
glBindVertexArray(m_VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized
glDrawArrays(GL_TRIANGLES, 0, 3);
// glBindVertexArray(0); // no need to unbind it every time
// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
// -------------------------------------------------------------------------------
glfwSwapBuffers(m_window);
glfwPollEvents();
InvalidateRect(NULL,FALSE); //???
}
OnCreate
int CopenglmfcView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
// TODO: Add your specialized creation code here
CClientDC* m_pDC = new CClientDC(this);
//Failure to Get DC
if( m_pDC == NULL )
return FALSE;
CWnd* pwndParent = this->GetParent();
CRect rc;
pwndParent->GetWindowRect(&rc);
int width,height;
width=1362;
height=702;
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
m_window = glfwCreateWindowEx(width, height, "Simple example", NULL, NULL, (int)GetParent()->m_hWnd);
if(m_window==NULL)
{
MessageBox("windows fail");
}
glfwMakeContextCurrent(m_window);
// glad: load all OpenGL function pointers
// ---------------------------------------
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
//std::cout << "Failed to initialize GLAD" << std::endl;
MessageBox("glad fail");
return -1;
}
const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
// build and compile our shader program
// ------------------------------------
// vertex shader
int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
// check for shader compile errors
int success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
MessageBox("ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" );
}
// fragment shader
int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
// check for shader compile errors
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
MessageBox("ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n");
}
// link shaders
m_shaderProgram = glCreateProgram();
glAttachShader(m_shaderProgram, vertexShader);
glAttachShader(m_shaderProgram, fragmentShader);
glLinkProgram(m_shaderProgram);
// check for linking errors
glGetProgramiv(m_shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(m_shaderProgram, 512, NULL, infoLog);
MessageBox("ERROR::SHADER::PROGRAM::LINKING_FAILED\n" );
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
// set up vertex data (and buffer(s)) and configure vertex attributes
// ------------------------------------------------------------------
float vertices[] = {
-0.5f, -0.5f, 0.0f, // left
0.5f, -0.5f, 0.0f, // right
0.0f, 0.5f, 0.0f // top
};
//unsigned int VBO, VAO;
glGenVertexArrays(1, &m_VAO);
glGenBuffers(1, &m_VBO);
// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
glBindVertexArray(m_VAO);
glBindBuffer(GL_ARRAY_BUFFER, m_VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind
glBindBuffer(GL_ARRAY_BUFFER, 0);
// You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other
// VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.
glBindVertexArray(0);
return 0;
}
其他都比较少:
BOOL CopenglmfcView::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
return 1;
//return CView::OnEraseBkgnd(pDC);
}
void CopenglmfcView::OnDestroy()
{
CView::OnDestroy();
// TODO: Add your message handler code here
// optional: de-allocate all resources once they've outlived their purpose:
// ------------------------------------------------------------------------
glDeleteVertexArrays(1, &m_VAO);
glDeleteBuffers(1, &m_VBO);
// glfw: terminate, clearing all previously allocated GLFW resources.
// ------------------------------------------------------------------
glfwTerminate();
}
void CopenglmfcView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
if ( 0 >= cx || 0 >= cy || nType == SIZE_MINIMIZED )
return;
glfwMakeContextCurrent(m_window);
glViewport( 0, 0, cx, cy );
InvalidateRect(NULL,FALSE);
}
4:调试时遇到的问题
glad.c 需要添加到工程,否则编译到链接时有说缺少链接函数。加了glad.c 说是如下错
glad.c : fatal error C1853: 'Debug\openglmfc.pch' precompiled header file is from a previous version of the compiler, or the precompiled header is C++ and you are using it from C (or vice versa)
这个选中glad.c ,鼠标右键,选择 properties, 然后C/C++->Precompiled Headers, 选择 Not Using Precompiles Headers, 然后就可以编译链接好了。如下图:
5:存在的问题
初始时设置 width, height 小于实际屏幕尺寸时,画图和清屏在拉大时外框并没有跟着变化。 height 方向三角形尺寸有异样,尺寸小的时候显示不完全,一直到最大或设置尺寸才正常。如下图开始设置 width=800, 我的屏尺寸为1300多,左右的问题。上下方向,三角形并不在正中央,尺寸越小,越偏下,甚至看不到。