ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OpenGL Drawing项目解析:GDI与OpenGL混合绘图实战指南

OpenGL Drawing项目解析:GDI与OpenGL混合绘图实战指南 简介图形编程中选择合适的渲染接口往往决定项目效率与表现力。GDI作为Windows原生二维绘图接口简单易用适合UI与文字输出OpenGL则依托GPU实现高性能三维渲染亦可处理复杂二维场景。理解两者的原理差异有助于在Visual C工程中合理划分绘制任务例如用OpenGL渲染几何体用GDI标注界面元素。从环境配置、矩阵传递到线宽绘制图形开发者常需在性能与兼容性间权衡。基于OpenGL-Drawing项目实例可系统掌握现代着色器管线、VBO/VAO操作及GDI协作技巧为图像编辑器或数据可视化工具提供可靠的技术参照。 前阵子整理硬盘翻出一个叫 OpenGL-Drawing.rar 的压缩包解压后是一套 Visual C 写的图像编程示例。这类代码在很多老项目里都能见到里面既有 GDI 的绘制逻辑也有基于 OpenGL 的渲染实现用来做两个图形接口的对比学习很合适。说实在的如果你刚接触图像编程很容易被 GDI 和 OpenGL 这两套东西搞混GDI 是 Windows 自带的二维绘图接口OpenGL 则是跨平台的三维图形接口但也可以做二维。这份项目正好把两者放在了一起因此能很直观地看出各自的适用范围和协作方式。无论你是想复习 Visual C 图形编程还是想把一份老源码调通之后加入自己的渲染逻辑这篇文章都值得接着往下看。1. 项目拆解为什么一份绘图代码里同时出现 GDI 和 OpenGL1.1 从压缩包目录看项目的真实结构解压 OpenGL-Drawing.rar 后可以看到一套很典型的 Visual C 工程文件。通常会有 DrawGL.sln 或 DrawGL.vcxproj一个 MFC 对话框或单文档视图工程源码目录里会区分 GDIView.cpp、GLRenderer.cpp 这类文件如果代码写得规范还会有 res 目录放图标和位图资源或者一个 shaders 文件夹存放 .glsl 着色器源码。这种目录布局说明设计者从一开始就没想把两套绘制接口揉在一起。GDI 代码负责窗口背景、坐标轴刻度、文字标注和简单的二维交互OpenGL 代码负责真正需要硬件加速的三维模型或大数量图元渲染。把两个功能拆到不同类里既能避免 OnPaint 里既画 GDI 又调 OpenGL 导致的像素格式冲突也能让后续维护者一眼看出各自边界。很多初学者自己写图像程序时喜欢在一个窗口过程里混着调用所有绘图函数最后往往因为设备上下文缺少 OpenGL 像素格式而黑屏。这个项目实际上提供了一个很好的分层样本。1.2 GDI 和 OpenGL 各自负责什么GDIGraphics Device Interface是 Windows 自带的二维绘图体系核心是设备上下文DC。你调用 MoveToEx、LineTo、Ellipse 这些函数时绘图操作由 CPU 执行输出到窗口 DC 或内存位图 DC。优点非常明显API 简单、和窗口系统天然集成尤其适合文字排版和截图类应用。但缺点也很致命所有光栅化都由 CPU 完成场景复杂后帧率上不去也不支持三维透视变换。OpenGL 则完全不同。它把几何数据交给 GPU通过着色器在显卡上完成坐标变换、裁剪和光栅化。优点是性能高、支持三维透视、可编程管线灵活缺点是需要手动管理上下文、编译着色器、检查状态API 比 GDI 复杂一个量级。在图像编程场景里如果只是画几个矩形、写几行文字GDI 显然更省事如果要对上千个顶点做实时变换、叠加纹理、做混合效果OpenGL 就是绕不开的选择。1.3 混合绘制的核心权衡实际项目很少只用一种接口。最常见的组合是OpenGL 负责窗口显示区域的几何渲染GDI 负责工具栏、状态栏、坐标标注这类 UI 元素。两者可以共存但要注意时序。我在调类似工程时踩过不少坑最典型的是在 WM_PAINT 里先调用 OpenGL 渲染再调用 GDI 的 TextOut 输出文字结果文字被下一帧清掉或者出现明显的白闪。原因在于 OpenGL 的双缓冲交换发生在 GDI 绘制之前窗口 DC 的内容被整体覆盖。正确的做法是让两个渲染流程各自独立。比如在定时器或渲染循环里只做 OpenGL 绘制用单独的 WM_PAINT 处理 GDI 文字或者先绘制 GDI 内容到内存位图再上传成 OpenGL 纹理最终统一输出。从项目结构上看OpenGL-Drawing 这份代码如果按 GDIView 和 GLRenderer 两个类拆分就是已经考虑了这个问题值得模仿。1.4 性能对比什么时候用 GDI什么时候用 OpenGL如果你只是想更新一个 500x300 的小区域GDI 的优势反而更明显因为它省去了初始化 OpenGL 上下文和编译着色器的开销。比如画一个会动的进度条用 GDI 的矩形填充就够了完全没必要上 GPU。但如果你要同时绘制一万个三角形GDI 会立刻卡顿因为每次 GDI 调用都走一次 CPU 光栅化而且没有硬件深度测试。OpenGL 把批量顶点一次性提交给 GPU瓶颈往往只在数据上传和 draw call 数量上。所以我的经验是图元数量少于一千、不需要三维变换或像素级混合就选 GDI需要连续动画、实时交互或者大量粒子就直接 OpenGL。这个项目之所以把两者放一起大概也是想演示这种“按需选择”的工程思路。2. 环境准备Visual C 运行库与开发环境避坑指南2.1 老项目在 Visual Studio 里的迁移思路OpenGL-Drawing 这种命名风格很可能是 2005 到 2010 年之间的工程。用新版 Visual Studio 打开 .dsw 或 .vcproj 时会提示升级项目。升级后常见问题有两类一类是 MFC 库版本对不上需要把项目属性里的“使用 MFC”改成“在共享 DLL 中使用 MFC”另一类是字符集从多字节变成了 Unicode导致部分 API 参数类型报错比如 CreateWindowA 和 CreateWindowW 的冲突。我的操作顺序是先让 VS 自带的升级向导跑一遍然后把平台工具集改成当前 VS 版本再编译根据错误一个个处理。不要为了一个老工程专门装旧版 Visual C 6.0 或 VS2010现代编译器完全可以编译这些老代码只是要注意库版本和语言标准。遇到error C4996: GetVersionExA was declared deprecated这类警告可以用_CRT_SECURE_NO_WARNINGS临时屏蔽但不建议长期依赖它。2.2 解决 Visual C Redistributable 安装弹窗老工程在发行时经常会让用户安装“Microsoft Visual C Redistributable”运行库。程序一启动就弹窗提示缺少运行库往往不是代码问题而是本机运行库损坏或版本冲突。最常见的是 2008 x86 和 2015-2022 x64 混装后互相覆盖。我实测有效的方法是下载对应版本的 vc_redist.x86.exe 和 vc_redist.x64.exe先装 x86再装 x64两个都要装。如果遇到 0x80070666 错误码说明机器上已经存在一个相同或更高版本的运行库。不要硬装先去“控制面板”里看有没有“Microsoft Visual C 2015 Redistributable (x86)/(x64)”这类旧版本把它卸载干净再用官方卸载工具清理 MSI 残留最后重启安装最新版。这个过程听起来麻烦但比反复试错要快得多。安装 node.js 时提示“microsoft visual c 2022 x86 minimum runtime 安装包不存在”也是同一个思路直接去微软官网下载 vc_redist.x86.exe 覆盖安装即可。2.3 WSL 下 GPU 识别但 OpenGL 软件渲染的问题不少人在 WSL Ubuntu 里做 OpenGL 开发会看到 nvidia-smi 能正常识别 GPU但调用 OpenGL 时glGetString(GL_RENDERER)返回的是 llvmpipe也就是 CPU 软件模拟。这说明 Mesa 没有走硬件加速路径而是在用栈上的软件光栅化器。WSL2 下要想用上 GPU 的 OpenGL需要安装 WSL 专用的显卡驱动并且让 Mesa 的 D3D12 后端被正确激活。如果你只是想在 WSL 里跑通一个简单图形程序可以在命令行环境变量里加上LIBGL_ALWAYS_SOFTWARE1来强制软件渲染保证功能正确但如果要分析这项 OpenGL-Drawing 项目的实际渲染效果我建议直接在原生 Windows 里用 Visual Studio 编译运行省去驱动和显示服务器的各种坑。WSL 适合做编译和单元测试图形程序放 Windows 桌面跑是更实际的分工。2.4 在 Visual C 工程里正确配置 OpenGL 头文件和库在 Visual C 里调用 OpenGL传统做法是引入gl.h、glu.h并链接opengl32.lib和glu32.lib。这些库在 Windows SDK 里自带不需要额外下载。但如果你要使用现代 OpenGL3.0的扩展接口还需要一个扩展加载库比如 GLEW 或 GLAD。GLEW 的配置很直接下载 glew 后把 include 目录加到“常规 - 附加包含目录”把 lib 目录加到“附加库目录”预处理定义写GLEW_STATIC或GLEW_DYNAMIC再在代码里#include GL/glew.h并且在创建 OpenGL 上下文之后调用glewInit()。GLAD 则是现在更流行的选择因为它是按你需要的 OpenGL 版本在线生成的代码体积更小。使用 GLAD 时把生成的glad.c并进工程包含glad/glad.h头文件即可。我建议新项目直接用 GLAD老项目如果已经用了 GLEW也不要急着迁能跑就行。重点是确保 OpenGL 库的位数和程序一致32 位程序必须链接 x86 版本的库64 位程序链接 x64 版本否则链接阶段会出现 unresolved external symbol。3. 核心要点OpenGL 画线和矩阵传递的真问题3.1 GDI 画线有多简单OpenGL 画线有多折腾用 GDI 画一条从 (10, 10) 到 (200, 100) 的线段只需要这么几行HDC hdc GetDC(hwnd); HPEN pen CreatePen(PS_SOLID, 2, RGB(255, 0, 0)); HPEN oldPen (HPEN)SelectObject(hdc, pen); MoveToEx(hdc, 10, 10, nullptr); LineTo(hdc, 200, 100); SelectObject(hdc, oldPen); DeleteObject(pen); ReleaseDC(hwnd, hdc);OpenGL 固定管线里也有一个类似接口glLineWidth(2.0f); glBegin(GL_LINES); glVertex2f(10, 10); glVertex2f(200, 100); glEnd();看起来也不复杂。问题是glLineWidth在大多数现代驱动上只支持 1.0你传 2.0 或更粗时往往会被忽略。这几乎是所有从 GDI 转 OpenGL 的人都会踩的坑。原因在于 OpenGL 规范只要求实现必须支持 1.0线宽大于 1 是可选项到了 3.2 核心模式连glBegin/glEnd都彻底没有了。所以你要画一条带宽度的线必须自己想办法。3.2 线宽失效的替代方案最稳妥的做法是把它当成一个矩形来画。比如从点 A 到点 B线宽为 w先算出垂直于点 AB 的单位向量 n然后就能生成四个顶点A nw/2、A - nw/2、B nw/2、B - nw/2。再按两个三角形组织顶点渲染出来就是一条任意宽度的线段。这个方案的优点是完全不依赖驱动支持任何 OpenGL 版本都能用。另一种更高级的方案是用几何着色器输入一条线段输出一个三角形带。几何着色器可以拿到线段两端点的位置在 GPU 上实时生成矩形这样 CPU 端只需要提交顶点。不过几何着色器在移动设备和 WebGL 里支持有限如果你只是做 Windows 桌面程序可以放心用。还有一个折中方案是直接使用多边形偏移画线先把线画成很细的三角形条带再通过混合加一点抗锯齿效果。我在实际项目里最常用的还是 CPU 生成矩形顶点因为逻辑清楚、调试容易也方便控制虚线样式。3.3 glUniformMatrix4fv 的完整用法和列主序陷阱glUniformMatrix4fv经常被用来把自己的模型视图投影矩阵传给着色器。先获取 uniform 位置GLint loc glGetUniformLocation(program, mvp); glUniformMatrix4fv(loc, 1, GL_FALSE, glm::value_ptr(mvp));第二个参数是矩阵数量一般传 1第三个参数是是否需要转置必须搞明白背后的含义。OpenGL 的矩阵约定是列主序也就是内存里先存第一列而很多 C 数学库默认是行主序。如果直接把一个行主序矩阵传给 GL_FALSE渲染结果会变成转置后的效果模型看起来像是被压扁或镜像非常诡异。解决办法是统一使用 glm::mat4 这样的列主序矩阵或者把 transpose 传 GL_TRUE。还有一个容易被忽略的点glUniformMatrix4fv必须在glUseProgram之后调用否则会报GL_INVALID_OPERATION。即使你之前已经绑定过同一个 program只要中间切换过其他程序就要先重新使用。调试时我通常会把 uniform 的位置缓存在类成员变量里避免每帧都调用glGetUniformLocation这虽然是个很小的性能优化但也是一名工程师该有的习惯。3.4 线段绘制与折线的三角化细节如果画一条连续的折线而不是单独一条线段三角化时要特别注意相邻线段的重叠和接缝。最简单的做法是每一段都用独立的矩形但两段连接处会出现缺口或重叠。改进方法是把折线看作一个多边形条带遍历每个顶点计算当前段和下一段的法向量平均值再生成连续三角形。听起来复杂实际实现并不难但边界条件要考虑完整比如夹角太小导致法向量归零。我在处理 OpenGL-Drawing 里的标尺线时直接用GL_LINE_STRIP画了细线因为它是辅助线不需要太宽。真正要给用户展示的箭头、边框等元素才用三角形生成宽线。这样既保证了性能又避开了线宽失效的问题。类似的做法也适用于虚线与其用 OpenGL 的线型模式不如直接把线段顶点按 dash 长度切割每个虚线小段由两个三角形组成控制起来更自由。4. 实操过程从零搭一个 OpenGL Drawing 示例4.1 创建窗口与 OpenGL 上下文我选用 GLFW 来管理窗口和上下文因为它比直接用 Win32 API 简便得多。初始化代码如下glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); GLFWwindow* window glfwCreateWindow(800, 600, OpenGL Drawing, nullptr, nullptr); glfwMakeContextCurrent(window); gladLoadGL(glfwGetProcAddress);如果用原始的PIXELFORMATDESCRIPTOR也能创建上下文但代码会多出几十行还要自己处理 WM_CREATE 和 WM_SIZE。GLFW 不仅简化窗口创建还提供了键盘鼠标回调对这类绘图 Demo 来说体验好很多。要注意的是3.3 核心模式下不能用旧的glBegin/glEnd所有几何数据必须走 VBO/VAO。这一点在项目里选现代 OpenGL 时尤其重要。4.2 准备顶点数据与着色器这里我们绘制一个彩色三角形顺便把 GLSL 着色器的流程带上。顶点数据如下float vertices[] { -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f }; unsigned int vbo, vao; glGenVertexArrays(1, vao); glGenBuffers(1, vbo); glBindVertexArray(vao); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1);着色器至少需要一个顶点着色器和一个片段着色器。顶点着色器里我同时输出位置和颜色#version 330 core layout(location 0) in vec3 aPos; layout(location 1) in vec3 aColor; out vec3 vColor; void main() { gl_Position vec4(aPos, 1.0); vColor aColor; }片段着色器#version 330 core in vec3 vColor; out vec4 FragColor; void main() { FragColor vec4(vColor, 1.0); }编译完成后调用glLinkProgram把两个着色器链接成一个 program。编译和链接的错误日志用glGetShaderiv、glGetShaderInfoLog、glGetProgramiv、glGetProgramInfoLog来拿新手经常漏掉这一步结果画面黑屏还不知道代码哪里写错。4.3 设置投影矩阵与绘制循环如果要让图形具备透视效果最简单的方法是乘一个投影矩阵正好能用上之前讲的glUniformMatrix4fv。使用 glm 的代码如下glm::mat4 proj glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f); glm::mat4 view glm::lookAt(glm::vec3(0, 0, 3), glm::vec3(0, 0, 0), glm::vec3(0, 1, 0)); glm::mat4 mvp proj * view; glUseProgram(program); GLint loc glGetUniformLocation(program, mvp); glUniformMatrix4fv(loc, 1, GL_FALSE, glm::value_ptr(mvp));主循环while (!glfwWindowShouldClose(window)) { glClearColor(0.1f, 0.1f, 0.1f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(program); glBindVertexArray(vao); glDrawArrays(GL_TRIANGLES, 0, 3); glfwSwapBuffers(window); glfwPollEvents(); }注意glViewport需要在窗口大小变化时调用。我通常在 GLFW 的 framebuffer size 回调里写glViewport(0, 0, width, height)否则窗口从 800x600 拉大到全屏时画面仍然只画在左上角那一小块区域里。4.4 GDI 辅助标注与 OpenGL 场景的协作如果要在窗口右上角显示 GDI 绘制的 FPS 文字一个简单的实现是在 OpenGL 交换缓冲区前后分别拿 DCHDC dc GetDC(glfwGetWin32Window(window)); TextOut(dc, 10, 10, fpsText, lstrlen(fpsText)); ReleaseDC(glfwGetWin32Window(window), dc);但前面说过这个顺序很容易导致文字被清掉。更稳妥的方式是让 OpenGL 渲染到离屏 framebuffer然后用 GDI 把这个 framebuffer 的像素拷到窗口 DC 上。虽然多了一步像素拷贝但渲染结果和 GDI 标注完全解耦不会互相干扰。如果项目对性能要求不高这个方案实现简单逻辑也清楚。当然老派 MFC 程序里更常见的是“左右分栏”左侧一个视图类用 GDI 画辅助信息和工具栏右侧另一个视图类用 OpenGL 显示三维场景。这样完全避免了同窗口混用的问题维护起来也容易。OpenGL-Drawing 项目如果采用了 MFC 的 SplitterWnd大概就是这个思路。我强烈建议初学者先从这个方案入手。4.5 将绘制结果保存为图片图像编程里经常要把 OpenGL 渲染结果保存成图片。在 OpenGL 里保存截图需要读回帧缓冲区的像素数据int w, h; glfwGetFramebufferSize(window, w, h); std::vectorunsigned char pixels(w * h * 3); glPixelStorei(GL_PACK_ALIGNMENT, 1); glReadBuffer(GL_FRONT); glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, pixels.data());上面的代码读的是前置缓冲如果你一直用双缓冲最好在glfwSwapBuffers之后读或者读GL_BACK。另外要注意 glReadPixels 的数据是从左下角开始的保存成 BMP 或 PNG 时需要把图像反过来。这个坑我踩过很多次后来都会顺手写一个FlipImageVertical函数把行序反转后再写文件。如果你只是想在窗口里看效果不需要保存图片那这部分可以先跳过。5. 常见问题排查与经验记录5.1 黑屏、无输出时先查什么黑屏是第一大问题。我的排查顺序很固定先确认上下文创建成功再检查 glClearColor 是否生效如果连背景色都没变化大概率是上下文或者gladLoadGL调用时机不对。接着检查着色器编译和链接是否有错用日志函数把 error log 打出来。然后检查顶点属性有没有正确设置尤其是 stride 和偏移量写错了经常画出乱线或什么都没有。最后看 glDrawArrays 的图元类型和顶点数是不是匹配。比如你定义了 3 个顶点却调用glDrawArrays(GL_TRIANGLES, 0, 6)OpenGL 会额外读取一段未初始化的内存结果可能是黑屏或乱线。用glGetError()也能辅助定位常见错误码GL_INVALID_OPERATION、GL_INVALID_ENUM分别对应什么可以快速判断是 API 调用问题还是对象状态问题。把这些步骤形成肌肉记忆之后黑屏问题基本几分钟就能定位。5.2 Visual C 运行库 0x80070666 排查记录前面说过 0x80070666 是运行库安装的老问题。具体表现是安装 microsoft visual c 2015-2022 redistributable (x64) 时提示另一个版本已安装。我处理过一台机器控制面板里能看到 2015、2017、2019 多个版本混在一起安装程序互相不认。我的解决方法是先把“Microsoft Visual C 2015 Redistributable (x86)/(x64)”和“2015-2019”的版本卸载干净用官方卸载工具清理残留再重启安装 2015-2022 最新版。注意 x86 和 x64 必须分别装因为很多 32 位程序依赖 x86 运行库即使操作系统是 64 位的也必须保留 x86 版本。安装 node.js 时提示“microsoft visual c 2022 x86 minimum runtime 安装包不存在”也按照这个思路去微软官网下载 vc_redist.x86.exe 覆盖安装即可不要试图手动删系统文件。5.3 其他容易忽略的小坑除了 glLineWidth 和矩阵转置新手特别容易踩的还有几个点核心 profile 下不要再用glBegin/glEnd否则会报GL_INVALID_OPERATION程序不崩溃但画面什么都没有。显卡驱动更新后旧的 Shader 可能因为内置变量变化而报错比如gl_FragColor在 330 core 里被改为自己声明out vec4 FragColor。glGetUniformLocation返回 -1 是正常的如果 uniform 被编译器优化掉也是 -1不要用 0 来判断失败。在 MFC 中用 OpenGL务必要在PreCreateWindow或OnCreate里设置窗口样式支持 OpenGL并且在OnEraseBkgnd里返回 TRUE否则窗口会不停地擦背景导致画面闪烁。这些问题看起来小但一个没注意就可能让你卡半个晚上。尤其最后一个闪烁问题很多老工程里都能看到往往是被误判为 OpenGL 双缓冲没有生效。5.4 排查问题速查表现象可能原因处理办法黑屏上下文/着色器/顶点属性错误依次检查 glGetError、编译日志、属性指针线段很细或失效glLineWidth 超出支持范围用三角形对生成宽带线条图形被拉伸或翻转视口未设置或矩阵转置错误调整 glViewport检查 transpose 参数运行库弹窗版本损坏或冲突卸载后安装最新 vc_redist窗口闪烁MFC 背景擦除触发重写 OnEraseBkgnd返回 TRUEglUniformMatrix4fv 没效果程序未绑定或 uniform 名拼错确保 glUseProgram 后再调用这个速查表是我根据自己实际排错经验总结的很多看起来毫无头绪的问题最后都能归到这六类里。6. 这类绘图项目还能怎么扩展6.1 把固定管线升级到现代管线如果你从 OpenGL-Drawing.rar 里看到的是旧式glBegin/glEnd代码我建议把它升级为现代可编程管线即便短期内没有功能需求。升级思路是把所有几何数据放入 VBO/VAO把矩阵以 uniform 方式传入把光照计算搬到 shader 里。这个迁移过程能帮你把固定管线时代模糊的概念重新梳理一遍。很多人以为 OpenGL 3.3 和 OpenGL 1.1 只是 API 不同实际是渲染状态的思维模型完全不同值得花一两个晚上重写。在升级过程中你会逐渐理解为什么要手动管理 VAO、为什么要绑定 Buffer、为什么每次绘制都要设置属性指针。这些知识在 DirectX 或者 Vulkan 里一样通用。从工程角度看现代管线也让代码更容易扩展比如增加阴影、后处理、粒子系统都只需要在 shader 层增加功能不用改大量 CPU 端代码。6.2 从 OpenGL Drawing 到 2D 图像编辑器的思路这份项目名叫 OpenGL-Drawing本身就适合往图像编辑器方向扩展。你可以把每个图形抽象成 Drawable 对象包含位置、颜色、透明度、图元类型用一个 Command 模式管理画布操作支持绘制、撤销、重做。GDI 继续负责工具栏、属性面板OpenGL 负责画布的纹理上传和显示。这样既保留了 GDI 处理 UI 的便利又获得了 OpenGL 的高性能合成能力。实践时要注意纹理坐标和清理每次画一条线后如果直接更新整张纹理性能会很差。我建议把绘制结果维护在一张离线位图上当有局部变更时只更新位图里发生变化的那一小块区域再上传到 OpenGL 纹理。这样可以避免每画一笔就重传整张 1080p 纹理。如果后续要做像素级滤镜再把位图数据交给 OpenGL 的 compute shader 或 fragment shader 处理效率会高很多。6.3 我的一点个人经验如果你手头也有类似的老项目别急着扔掉对照源码一步步调通很有成就感。我在调这个 OpenGL-Drawing 工程时最大的体会是GDI 负责“所见即所得”OpenGL 负责“几何和性能”两者的边界一定要在设计初期划清楚。第二个体会是Visual C 运行库问题虽然看似和绘图无关但它往往比渲染 bug 更早碰到先把运行库、工具链稳定下来后面调试才清爽。最后不管用哪种接口画线条这件事永远绕不开线宽和风格提前准备一套“宽线绘制工具函数”以后会省很多事。本文还有配套的精品资源点击获取
返回列表