OpenGL 系列 -- 渲染性能优化随记

张三丰:“无忌,我教你的还记得多少?”
张无忌:“回太师傅,我只记得一大半。”

张三丰:“那现在呢?”
张无忌:“已经剩下一小半了。”

张三丰:“那现在呢?”
张无忌:“我已经把所有的全忘记了。”

前置

知识科普部分对相关概念一带而过。但对新同学至关重要 – 避免在庞杂的书面知识上浪费时间。
技术选型部分对相关技术仅给出当前考量 – 方向是对的,剩下的就是工作量。
技术细节部分对相关技术细节的设计作详细阐述 – 浪费您的时间了。

知识科普

本文提及渲染时,统一默认为 OpenGL(或OpenGL ES) 和它的 GLSL。同时,文中尽量减少不必要的专业名词或术语。

什么是 OpenGL?

请从状态机的角度,去理解它。
请从客户机/服务器的角度,去理解它。

什么是 OpenGL?

如果从 OpenGL 以什么方式完成什么事情的角度,更愿意把它看作一个“建模大师” –(拿来用而已)

颜色

提及 RGB, 大多数人习以为常。
提及 BGR,OpenCV开发人员比较熟悉(默认颜色空间)。
至于其他的颜色空间,也只是表示方式不同。颜色处理时,99%的情况要转为 RGB 三成分。数据传输时,按需转为其他格式。

在解码时,要输出哪种格式?

这是数据源要关注的点。如果从渲染的角度判断,那就是:当前显卡驱动支持的最优格式。

对于渲染链路中的数据,要注意什么?

  • 尽量保持数据大小和数据格式不变,以减少格式转换带来的额外开销。
  • 尽量通过代码设计避免数据copy,以减少不必要的开销。
  • 尽量拥抱新知识,将耗时操作从 CPU 转移到 GPU。

光照

基础领域的研究成果,拿来用用而已(模拟现实是VR,增强现实是AR等)

对光源的划分

光源划分本身就是一个建模的过程。比如:太阳光定义为平行光;白炽灯定义为点光源。

对光源成分的划分

同样是一个建模的过程,比如:环境光,漫反射,高光反射等。

对光源成分建模

一堆计算公式而已,比如:Lambert模型, Half-Lambert模型, Phong模型,Blinne-Phong模型。建议忽略该部分,这是 TA 操心的事情。

坐标系

无论是一场 AR 直播,一个复杂的游戏场景,或者一幅图片,在生动的视觉效果背后都存在一个摸不着的虚拟世界。虚拟世界的完整描述,依赖各类坐标系(左右手):世界坐标系,模型坐标系,观察坐标系,裁剪坐标系。借助这些信息,渲染流水线得以完成各种变换(MVP)。

  • 世界坐标系描述了所有模型和光源位置
  • 光照计算需要在模型坐标系下进行
  • 视图坐标系定义了相机的位姿,直接影响画面的输出
  • 裁剪坐标系是经过透视除法后的坐标系,设计正交投影和透视投影。其中,透视投影矩阵是对近大远小等透视效果的建模。

模型

对人物或物体的建模,本质上就是描述其轮廓/包络/网格的过程。下面对一个模型文件进行分析:

准备环节

通过资源网站下载或者 blender 生成模型文件。为避免展开话题,我们限定模型文件格式为 .obj。

格式定义

mtllib 部分:忽略该部分(包含各种反射的参数信息,纹理文件信息等内容)
object 部分:必备参数(:v, :f),可选参数(:vt, :vn)。

  • 必备参数:顶点:v 和三角面:f 完整描述了物体的包络/网格。
  • 可选参数:忽略该部分(纹理顶点:vt 描述了贴图的映射信息,顶点法线:vn 用于光照计算)

应用分析

必备参数完整描述了模型的包络信息。可选参数算是辅助信息,比如:顶点法线用于光照计算(可在导入引擎时生成);纹理顶点相当于物体/人物贴图映射关系等等。

  • 对于 OpenGL 开发来说,控制如何绘制顶点是比较重要的一环。比如:
    • 需要初始化 VBO 信息,也即顶点:v
    • 需要初始化 EBO 信息,也即顶点索引:f
    • 可能需要初始化 VAO,也即元数据。它描述各个 buffer 的数据存储信息(VBO,EBO)
    • 可能需要初始化 PBO,用于释放 glTextureSubImage2D 操作的 cpu 占用
  • 对于引擎应用开发来说,更多的工作集中在 shader 及 shader 参数配置

什么是 OpenGL?

请忘掉上述回复。对于常规 opengl 开发,它跟普通的库没有区别。只需要了解相关接口即可。

  • 非必要,不要引入 MVP。比如:视频帧渲染,如果默认 MVP 都是单位阵,试试有什么效果?
  • 非必要,不要接触光照,那是 TA 操心的内容。再者,视频帧渲染跟光照没有半毛钱关系。
  • 没必要接触模型。对于视频帧或者图片渲染来说,模型就是四个顶点(两个三角面/一个四边面)。

技术选型

QT 技术栈

qt quick

不存在渲染性能问题,qt quick 默认采用 gpu 渲染。在禁用 gpu (qt::aa_usesoftwareopengl)的情况下,才会 fallback。

qt qwidget

qwidget 是采用 gpu 渲染。如果想利用 gpu 的优势,需要借助 qopenglwidget 控件,并且需要大量的工作

  • oepngl 版本控制: 兼容用户的显卡驱动版本
    • pbo 技术: gles 4.2, opengl 3.3 才得以支持
    • gl_polygon 枚举:旧版本的参数在新版本已被舍弃
    • shader 指令: gles 中 inout 参数需要提供精度信息
  • fallback 操作:对于禁用 gpu 的场景,需要额外提供软渲作为兜底
  • opengl 渲染:需要同时支持 opengl 和 gles
  • 软渲:用于 fallback 场景

QT 渲染属性

qt::aa_usedefault (qt 没有该枚举,用于占位)

默认情况下, qt 会渲染当前显卡驱动的 opengl 版本,比如:4.6

qt::aa_useopengles

当指定该渲染属性时,qt 会选择显卡驱动支持的 gles 版本,比如:2.0

qt::aa_usesoftwareopengl

当指定该渲染属性时,当前应用中的所有 ui 渲染都会 fallback

QT 渲染方式

paintevent(*) with qpainter

qwidget 提供的渲染接口,也即上文的软渲。由于 qopenglwidget 是继承至 qwidget,所以需要实现该接口用于 fallback 场景

paintgl() with qpainter

提供了灵活支持,可以使用一些非 opengl 指令。但该操作本身会占用 cpu

paintgl() with opengl commands

即是 opengl 渲染~

OpenGL & Shader

opengl 与着色器 shader 是什么关系呢?

对于 opengl 来说,glsl 算是一个辅助工具。借用《红宝书第七版》的描述: glsl 可以进一步发掘 OpenGL 的现代硬件实现的计算威力。 如果没有shader, 某些计算只能在 cpu 中进行。

不使用 shader, opengl 可以完成常规渲染操作吗?

对于引擎应用开发来说,不行。引擎应用开发唯一能操作的就是shader,他们无需关心 opengl 底层接口。
对于非引擎应用开发,shader 是一个可选项。多重纹理渲染那的场景:

  • 当纹理大小完全匹配时,单次 draw call 即可完成
  • 当纹理大小不完全匹配且某些纹理需要等比缩放(cover 模式)
    • opengl 实现: 使用独立的顶点和纹理映射,多次 draw call 即可完成(配合 blend separate)
    • shader 实现: 多次 draw call 比较麻烦;单次 draw call 时,需要借助 cpu 完成对各个纹理进行缩放和平移

注:draw call 是引擎渲染中的术语。对于 opengl 开发,就是一次顶点接口调用,如:glDrawElements。

阅读推荐

《OpenGL 红宝书第七版》:建议精读。后续接触新知识时,会更容易理解它们的由来。
《OpenGL 红宝书第九版》:大部分内容趋于同质化。只需了解最新的纹理及多重纹理内容即可。
《OpenGL ES 3.0 编程指南》:工具书,方便查询接口。

技术细节

截至前两部分,本文基本上已经完成。代码是廉价的,偷懒不想再作详细介绍~~ bye

  • 1282 error: 参数错误。 opengl 不同版本会舍弃一些参数;
  • 1281 error: 接口报错,比如:pbo 映射内存时,没有进行 bind 操作;
  • glteximage 接口:比较耗时哦。尽量合理利用;
  • ffmpeg 解码:尽量借助帧间隔(sleep)完成对一阵纹理的提交,避免多余的内存 copy。

OpenGL 系列 -- 渲染性能优化随记
https://jalencui.com/2025/02/15/Opengl-Practice-1/
Author
Jalen Cui
Posted on
February 15, 2025
Licensed under