Hello OpenGL ES! 启航!

零、前言

今天开始以我的视角分享 OpenGL ES 知识,主要包括以下几个小点:

  1. 分享理论知识和相应的 API 使用,以及 EGL 环境搭建等,并结合例子进行展示。
  2. 在 Android、iOS、鸿蒙、mac 和 windows 多个平台上运行同一套底层代码,达到一处编写多端使用。
  3. 分享期间会不断的完善 EglBox 项目,使其满足日常开发,在需要用到 GL 时,可以开箱即用。
  4. 基于 OpenGL ES 封装一些好玩的项目,例如:相机、音视频播放、裁剪工具等。

话不多说,开始属于 “OpenGL ES” 的 Hello World 吧!

一、OpenGL ES 能做什么?

1-1、2D 图形界面

基于 OpenGL ES 可以开发我们自己的 2D 图形界面,例如:

  1. nanovg( https://github.com/memononen/nanovg ),一个小型的图形渲染库。

  2. skia( https://github.com/google/skia ),google 开发的 2D 图形库,在 Flutter 早期版本中作为主要的渲染引擎,底层便有使用到 OpenGL ES 进行渲染。

Impeller Availability:https://docs.google.com/spreadsheets/d/1AebMvprRkxP-D6ndx920lbvDBbhg-sNNRJ64XY2P2t0/edit?gid=0#gid=0

后面分享的文章会介绍如何在 Flutter 上使用 OpenGL ES 渲染,让我们一起期待吧。

1-2、渲染 3D 效果

可以在移动设备上渲染 3D 效果,例如:

  1. 3D 胶卷渲染,最近的胶卷相机 APP 很多,在自己的 App 中加入胶卷的渲染可以丰富体验。

  2. 3D 字体渲染,结合 freetype 可以渲染不同字体的 3D 效果。

  3. 3D 场景渲染



这些都会在后续的文章中进行分享,一起期待吧

二、OpenGL ES 是什么?

OpenGL ES( OpenGL for Embedded Systems )是 OpenGL 的剪裁版本,专门针对嵌入式设备而设计,去除了许多不必要的特性以优化性能和内存占用。

它是一种跨平台的图形 API,不属于特定操作系统,可以在 Android、iOS、鸿蒙(HarmonyOS) 等多个移动平台以及智能电视、可穿戴设备、汽车信息娱乐系统等嵌入式系统上使用。

三、OpenGL ES 的版本

目前使用的 OpenGL ES 版本有 2.0 和 3.x (包括 3.0、3.1、3.2 ),他们之间从渲染表现和代码使用都有些许不同,接下来会一一阐述。

至于如何创建 2.0 和 3.x 的渲染环境(我们称之为 EGL ,全称 Embedded Graphics Library ),后面会单独一篇文章进行分享。

3-1、OpenGL ES 3.x 和 OpenGL ES 2.0

两者均支持 自定义渲染管线都需要 GPU 硬件支持 ,不可以用软件模拟,所以开发 OpenGL ES 应用建议使用真机运行,避免一些不必要的问题。

现在市面上的机型基本上有 GPU 硬件,并不用担心 GPU 硬件的缺失。

3-2、OpenGL ES 3.x 相较于 OpenGL ES 2.0 的区别

  1. OpenGL ES 3.x 性能更好;
  2. OpenGL ES 3.x 光影效果更加真实,画质更加逼真、细腻;
  3. OpenGL ES 3.x 渲染技术有更多的缓冲区对象,增加了 GLSL ES 3.x 着色语言和计算着色器( Compute Shaders )的支持;

3-3、OpenGL ES 3.x 版本

  1. OpenGL ES 3.0 是基于 OpenGL 3.x 规范的子集。
  2. OpenGL ES 3.1 是基于 OpenGL 4.x 规范的子集。
  3. OpenGL ES 3.1 向下兼容,可以在原来的 OpenGL ES 2.0 和 OpenGL ES 3.0 的基础上增加新特性。
  4. OpenGL ES 3.0 对应 Android 4.3 版本以上, OpenGL ES 3.1 对应 Android 5.0 版本以上。

四、OpenGL ES 渲染管线

4-1、什么是渲染管线

OpenGL ES 渲染管线由 GPU 内部 处理图形信号的并行处理单元 组成,这些并行处理单元相互独立而且同时处理。

相较于 CPU 串行处理 而言,会大大提升渲染效率。越高端的 GPU 并行处理单元数量会更多,效率会更好。

4-2、渲染管线的作用

管线会将输入的描述数据(例如:顶点数据、颜色、矩阵),经过处理后,输出一帧图像,呈现给用户。

多帧图像以一定的顺序和时间间隔,最终形成我们所看到的连贯动画。

4-3、OpenGL ES 渲染管线处理流程

OpenGL ES 2.0 和 OpenGL ES 3.x 在流程上是一样的,但 “顶点着色器” 和 “片元着色器” 的 glsl 写法有区别,在 4-3-2 和 4-3-5 中进行分享。

这里的概念会较多,如果是初学者只需要有一个大致了解,后续的文章会进行深入分享,最后再回来看这张图会有不一样的想法。

4-3-1、输入装配(Input Assembly)

主要将以下数据从 CPU 传到 GPU:

1、顶点数据,例如:顶点位置、法线、纹理坐标、颜色等;
2、绘制方式,图元类型(点、线、面);

4-3-2、顶点着色器(Vertex Shader)

对 “输入装配” 传入的每个顶点都会执行一次 “顶点着色器” 代码。 会对每个顶点进行以下处理:

  1. 模型变换、视图变换、投影变换等操作;
  2. 顶点属性计算,例如:颜色、法线方向变换、纹理坐标变换等;

顶点着色器在 OpenGL ES 2.0 的工作原理:

  • attribute 变量:每个顶点需要的属性,例如顶点的位置、颜色、法向量等信息。
  • uniform 变量: 对于同一组顶点组成的单个物体中所有顶点都相同的变量。例如:投影矩阵、摄像机位置、光源位置等。
  • varying 变量: 从顶点着色器计算产生并传递到片元着色器的数据变量。
  • 内建变量:
    gl_Position: 经过变换矩阵变换、投影后顶点的最终位置。
    gl_FrontFacing: 片元所在面的朝向。
    gl_PointSize: 点的大小。

值得注意:

当我们在顶点着色器中给 varying 变量赋值后,这些值并不会直接传递给片元着色器。实际上,图形渲染管线会先执行光栅化处理。在光栅化阶段,渲染管线会根据以下信息为每个片元计算出 varying 变量的值:

  • 图元各个顶点的 varying 变量值(顶点着色器的输出);
  • 当前片元在图元内的相对位置;

通过这种插值机制,片元着色器最终接收到的 varying 变量值是根据片元在图元中的位置,由相邻顶点的值按比例计算得出的结果。确保了图形表面的颜色、纹理坐标等属性能够平滑过渡。

varying 变量的插值处理过程:

此处以颜色为例,其他属性也是一样的计算规则。

顶点着色器在 OpenGL 3.0 的工作原理

  • in 变量: 等同于 OpenGL ES 2.0 中的 attribute 。每个顶点需要的属性变量,例如顶点的位置、颜色、法向量等信息。
  • uniform 变量: 和 OpenGL 2.0 中的 uniform 一样。对于同一组顶点组成的单个物体中所有顶点都相同的变量。例如:投影矩阵、摄像机位置、光源位置等。
  • out 变量: 从顶点着色器计算产生并传递到片元着色器的数据变量。可以通过以下写法控制进行插值或是不插值。
    smooth out(默认值): 等同于 OpenGL ES 2.0 中的 varying ,会进行插值,插值规则完全相同。
    flat out : 不会进行插值,使用图元中的最后一个顶点(例如三角形的第三个顶点)的值,所以此模式下图元每个片元的值都相同。
  • 内建输出变量:
    gl_Position: 是经过变换矩阵变换、投影后的顶点的最终位置。和 OpenGL ES 2.0 中的 gl_Position 完全相同。
    gl_PointSize: 点的大小。和 OpenGL ES 2.0 中的 gl_PointSize 完全相同。
  • 内建输入变量:
    gl_VertexID: 存储当前被处理顶点的整数索引,类型是 int 。
    gl_InstanceID: 实例化渲染中的实例索引,从 0 开始,每绘制一个新的实例就递增 1 。
4-3-3、图元装配(Primitive Assembly)

图元装配有两个步骤:图元组装图元处理

(1)图元组装:将顶点数据按照设置的绘制方式进行结合成完整的图元。

  • 点绘制时,则一个点为一个图元。
  • 线绘制时,则两个顶点结合为一个图元。
  • 三角形绘制时,则将三个顶点结合为一个图元。

(2)图元处理:会经历三个过程

  1. 裁剪:判断图元是否在视景体中,有三种可能:
  • 图元完全在视景体中,则整个图元保留;
  • 图元完全不在视景体中,则整个图元抛弃;
  • 图元部分在视景体中,则会进行增添顶点,保证视景体中的部分保留,超出的部分则抛弃;
  1. 透视除法:将裁剪空间坐标(x, y, z, w)转换为归一化设备坐标 (NDC,Normalized Device Coordinates) ,会进行 (x/w, y/w, z/w) 计算,让坐标影射到 [-1, 1] 区间
  2. 视口变换:根据 glViewport 设置的视口大小,将 NDC 坐标映射为窗口坐标。

一图胜千言

4-3-4、光栅化(Rasterization)

因为移动设备是通过像素来进行展示,所以需要将连续数学量表示的几何图元进行离散为一个个片元(此时不是像素)。“光栅化” 则是将连续数学量分解为离散化片元的这个动作

值得注意的是,此过程会根据顶点数据(例如:顶点坐标、纹理坐标、法向量、颜色等)进行插值计算得到每个片元的属性。

片元和像素的区别

图元的处理是并发的,所以片元的产生顺序并不固定的,产生的片元都会暂时送入对应的帧缓冲位置,当出现同位置的片元时,靠近观察点的片元覆盖远的片元(具体的处理会在深度测试阶段进行),所以片元不一定是像素,只能说是候补像素

4-3-5、片元着色器

光栅化得到的每个片元都会进行一次片元着色器的运算。 主要功能是计算片元的最终颜色和其他属性。

片元着色器在 OpenGL ES 2.0 的处理原理:

  • varying 变量: 从顶点着色器传递到片元着色器的值。系统会在顶点着色器后的光栅化阶段自动插值产生,并不是顶点着色器直接产出的值。
  • 内建变量:
    gl_FragColor: 片元的最终颜色。一般在片元着色器的最后会对其赋值。

片元着色器在 OpenGL ES 3.0 的处理原理:

  • in 变量: 和 OpenGL ES 2.0 的 varying 一样,只是换了名称。
  • out 变量: 由片元着色器写入计算完成的片元颜色值的变量。值得注意的是,OpenGL ES 2.0 中的 gl_FragColor 在 OpenGL ES 3.0 中被移除了。 需要输出片元颜色时,则定义一个 out vec4 类型的变量即可,变量名称没有规定。
  • 内建输入变量:
    gl_FrontFacing: 用于确定当前片元所属的三角形是正面还是背面。是一个布尔类型的变量。如果值为 true ,表示是正面;如果值为 false ,表示是背面。
    gl_FragCoord: 获取当前片元在帧缓冲中的屏幕坐标位置。是一个 vec4 类型的变量。gl_FragCoord.x 和 gl_FragCoord.y 表示片元在帧缓冲的二维屏幕坐标的位置。gl_FragCoord.z 表示片元的深度值,即深度坐标。而 gl_FragCoord.w 则表示透视除法的缩放因子( 1.0 / gl_FragCoord.w )。
    gl_PointCoord: 是一个 vec2 类型的变量,表示当前片元在点精灵上的纹理坐标位置。gl_PointCoord.x 和 gl_PointCoord.y 的取值范围是从 0.0 到 1.0 ,表示纹理坐标的范围。
  • 内建输出变量:
    gl_FragDepth: 设置片元的深度值,一个浮点数变量。

性能的优化点:

尽量减少片元着色器的运算量,将复杂的运算尽可能由顶点着色器来处理,因为顶点着色器的运行次数远远小于片元着色器的执行次数

4-3-6、片元级测试与操作(Per-fragment Operations)

此阶段会对片元按以下顺序执行操作:

  1. 裁剪测试(Scissor Test)
  2. 模版测试(Stencil Test)
  3. 深度测试(Depth Test)
  4. 混合(Blending)
  5. 抖动
剪裁测试

开启裁剪测试之后,后续的绘制操作只会在裁剪区域中,而不再是整个视口区域。

模板测试

根据模板缓冲中存储的数值以及预先设置的比较条件,决定是否允许当前片元写入帧缓冲。如果不允许写入则片元会被抛弃。一般用在湖面倒影、镜像等场景。

深度测试

这个阶段比较片元的远近,会保留近的片元,丢弃远的片元。

混合

将新片元的颜色与帧缓冲中已有颜色进行 “加权” 或 “数学操作” 后,写回帧缓冲中。可以做透明度等效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值