如何在单个MFC对话框窗口中创建两个独立的OpenGL视图
如何在单个MFC对话框窗口中创建两个独立的OpenGL视图
嗨,既然你已经能搞定单个OpenGL视图的创建了,要扩展成两个独立的其实核心就是给每个视图单独维护一套OpenGL上下文和渲染资源,这样它们就能各自渲染不同的场景、用不同的shader和VAO而互不干扰。下面我给你一步步拆解具体实现:
1. 先准备UI元素
在你的MFC对话框编辑器里,拖两个Picture Control控件,分别给它们设置独一无二的ID,比如IDC_OPENGL_VIEW1和IDC_OPENGL_VIEW2,这就是两个视图的载体。
2. 封装独立的视图数据
因为要区分两个视图的资源,不能再用单个的m_pDC这类成员变量了,我们可以写个结构体来统一存放每个视图的所有相关资源:
struct OpenGLViewData { CWnd* pWnd; // 对应的控件窗口 CDC* pDC; // 设备上下文 HGLRC hRC; // OpenGL渲染上下文 GLuint vao; // 该视图的VAO GLuint shaderProgram;// 该视图的shader程序 }; // 在对话框类里声明两个实例 OpenGLViewData m_view1, m_view2;
3. 编写通用的视图初始化函数
把你原来的setRenderingContext()改成可以针对单个视图初始化的通用函数,这样两个视图都能复用这个逻辑:
BOOL CMYAPPDlg::InitOpenGLView(OpenGLViewData& view, UINT ctrlID) { // 获取对应的控件窗口 view.pWnd = GetDlgItem(ctrlID); if (!view.pWnd) return FALSE; // 获取控件的DC view.pDC = view.pWnd->GetDC(); if (!view.pDC) return FALSE; // 定义像素格式描述符,和你原来的逻辑一致 static PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, 32, // 32位颜色深度 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, // 24位深度缓冲 8, // 8位模板缓冲 0, PFD_MAIN_PLANE, 0, 0, 0, 0 }; // 选择并设置像素格式 int pixelFormat = ChoosePixelFormat(view.pDC->m_hDC, &pfd); if (!pixelFormat) return FALSE; if (!SetPixelFormat(view.pDC->m_hDC, pixelFormat, &pfd)) return FALSE; // 给当前视图创建独立的渲染上下文 view.hRC = wglCreateContext(view.pDC->m_hDC); if (!view.hRC) return FALSE; // 激活当前视图的上下文,初始化它的专属资源 wglMakeCurrent(view.pDC->m_hDC, view.hRC); // 这里写该视图的VAO创建、shader编译链接逻辑 // 比如初始化第一个视图的VAO和shader,第二个视图这里的代码可以不同(或者写成参数化的函数) glGenVertexArrays(1, &view.vao); glBindVertexArray(view.vao); // 绑定VBO、设置顶点属性指针、编译/链接shader到view.shaderProgram... // 暂时释放上下文,避免干扰其他视图的初始化 wglMakeCurrent(NULL, NULL); return TRUE; }
然后在对话框的OnInitDialog()里调用两次这个函数,初始化两个视图:
BOOL CMYAPPDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // 其他初始化代码... // 初始化两个OpenGL视图 if (!InitOpenGLView(m_view1, IDC_OPENGL_VIEW1) || !InitOpenGLView(m_view2, IDC_OPENGL_VIEW2)) { AfxMessageBox(L"OpenGL视图初始化失败!"); return FALSE; } // 启动定时器触发渲染,比如每30ms渲染一次 SetTimer(1, 30, NULL); return TRUE; }
4. 实现独立的渲染逻辑
每次渲染时,必须先切换到目标视图的上下文,再进行渲染操作,这样才能保证资源是独立的。比如在定时器响应函数里实现:
void CMYAPPDlg::OnTimer(UINT_PTR nIDEvent) { if (nIDEvent == 1) { // ===== 渲染第一个视图 ===== wglMakeCurrent(m_view1.pDC->m_hDC, m_view1.hRC); // 清除缓冲,用第一个视图的背景色 glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 使用第一个视图的shader和VAO glUseProgram(m_view1.shaderProgram); glBindVertexArray(m_view1.vao); // 绘制第一个场景,比如画三角形 glDrawArrays(GL_TRIANGLES, 0, 3); // 双缓冲交换 SwapBuffers(m_view1.pDC->m_hDC); // 释放上下文 wglMakeCurrent(NULL, NULL); // ===== 渲染第二个视图 ===== wglMakeCurrent(m_view2.pDC->m_hDC, m_view2.hRC); // 用第二个视图的背景色 glClearColor(0.1f, 0.4f, 0.6f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 使用第二个视图的shader和VAO glUseProgram(m_view2.shaderProgram); glBindVertexArray(m_view2.vao); // 绘制第二个场景,比如画四边形 glDrawArrays(GL_QUADS, 0, 4); SwapBuffers(m_view2.pDC->m_hDC); wglMakeCurrent(NULL, NULL); } CDialogEx::OnTimer(nIDEvent); }
5. 资源清理不能忘
对话框销毁时,要分别清理每个视图的OpenGL资源和上下文,避免内存泄漏:
void CMYAPPDlg::OnDestroy() { // 清理第一个视图 wglMakeCurrent(m_view1.pDC->m_hDC, m_view1.hRC); glDeleteVertexArrays(1, &m_view1.vao); glDeleteProgram(m_view1.shaderProgram); wglMakeCurrent(NULL, NULL); wglDeleteContext(m_view1.hRC); m_view1.pWnd->ReleaseDC(m_view1.pDC); // 清理第二个视图 wglMakeCurrent(m_view2.pDC->m_hDC, m_view2.hRC); glDeleteVertexArrays(1, &m_view2.vao); glDeleteProgram(m_view2.shaderProgram); wglMakeCurrent(NULL, NULL); wglDeleteContext(m_view2.hRC); m_view2.pWnd->ReleaseDC(m_view2.pDC); CDialogEx::OnDestroy(); }
关键注意事项
- 上下文切换是核心:每次操作某个视图的OpenGL资源前,必须用
wglMakeCurrent切换到它的上下文,操作完最好释放(切换到NULL),避免和另一个视图的操作冲突。 - 资源完全独立:两个视图的VAO、shader、VBO等资源都要分开创建,绝对不能共用,这样才能保证场景完全独立。
- 如果代码量变大,建议把每个OpenGL视图封装成自定义控件类(从CWnd派生),这样代码更整洁,后期维护也方便。
内容来源于stack exchange
相关产品推荐
相关产品推荐

