OpenGL ES 构建的三维空间,其中的三维实体由许多的三角形拼接构成。如下图左侧所示的三维实体圆锥,其由许多三角形按照一定规律拼接构成。而组成圆锥的每一个三角形,其任意一个顶点由三维空间中 x、y、z 三个坐标分量来定义。



对于我们日常使用的移动手持设备,手机屏幕窗口由不连续的有限的二维像素小格子构成的,每一个像素格子有x、y两个分量来定义。

因此在OpenGL ES绘制流程中,其主要工作是将三维空间中的坐标点(x,y,z)构成的三维图形,转化为手机屏幕上的二维像素点。

这个转化过程主要分为两个步骤:

  • 一个是顶点的变换;

    三维空间中的实体,经过各种变换(主要是通过矩阵相乘进行坐标系的变换),变为可投影在二维平面上的有限的像素点数据;
  • 二是渲染着色;

    根据传入的顶点颜色数据或图片纹理数据,将相应的颜色对应到响应的像素点。

在OpenGL ES中,其大概的绘制流程如下图所示:

这里将三维空间中构成三维实体的坐标信息,转换为手机屏幕上有限的二维像素的过程,便是由OpenGL ES渲染管线(Graphics Pipeline)来实现。

  • OpenGL ES Shading Language;
  • OpenGL ES 3.x 渲染管线;

一、着色语言版本

正文开始之前,需要先了解一下 OpenGL ES Shading Language 着色语言。

在《一文了解 OpenGL ES》中,我们了解到 OpenGL ES 3.x采用的是可编程渲染管线。

OpenGL ES 3.x将顶点着色器、片元着色器的编码权限开放给开发者。开发者需要使用OpenGL ES Shading Language(着色语言) 编码实现顶点着色器、片元着色器处理逻辑,从而渲染出自己想的展示效果。

对于OpenGL ES Shading Language,我们有必要先了解一下OpenGL ES 与 OpenGL ES Shading Language 的对应关系(了解对应的API版本,才能编写对应版本要求的Shading Language脚本)。

OpenGL ES Version GLSL Version
1.0 --
1.1 --
2.0 100
3.0 300
3.1 310
3.2 320
  • OpenGL ES 1.x为固定渲染管线,无Shading Language对应版本;
  • OpenGL ES 2.x开始才有可编程渲染管线,OpenGL ES 2.0对应的OpenGL ES Shading Language版本为1.00
  • OpenGL ES 3.x使用可编程渲染管线,OpenGL ES 3.0对应的OpenGL ES Shading Language版本为3.00

Shading Language 3.00 文档:

https://www.khronos.org/registry/OpenGL/specs/es/3.0/GLSL_ES_Specification_3.00.pdf

各OpenGL ES版本API文档:

https://www.khronos.org/registry/OpenGL/specs/es/

二、渲染管线

OpenGL ES中的渲染管线(OpenGL Rendering Pipeline)指的是一系列的变换与渲染过程,输入是需要渲染的3D物体的相关描述信息数据(例:顶点坐标、顶点颜色、顶点纹理等),经过渲染管线,输出一帧想要的图像。

渲染管线也称之为渲染流水线,由显示芯片(GPU)内部处理图形信号的并行处理单元组成,这些并行处理单元两两之间相互独立,根据显示芯片性能的不同处理单元的数量也存在很大的差异。将渲染工作通过显示芯片中多个相互独立的单元进行并行处理后,渲染的效率可以大大提高。

OpenGL ES 3.x详细的渲染管线如下图所示:

2.1 基本处理

该阶段设定三维空间中物体的顶点坐标、顶点对应的颜色、顶点的纹理坐标等数据,并且指定三维物体的绘制方式,如:点绘制、线段绘制或者三角形绘制等。

2.2 顶点缓冲区

顶点缓冲区在应用程序中是可选的,对于某些在整个场景中顶点数据基本不变的情况,可以在初始化阶段将顶点数据经基本处理后送入顶点缓冲区,在绘制每一帧想要的图像时就省去了顶点数据IO的步骤,直接从顶点缓冲区中获取顶点数据即可。相比于每次绘制时单独将顶点数据送入GPU的方式,可以在一定程度上节省GPU的 IO 带宽,提高渲染效率。

2.3 顶点着色器

顶点着色器是一个可编程的处理单元,功能为执行顶点的变换、光照、材质的应用与计算等顶点的相关操作,每个顶点执行一次。

其工作过程为将原始的顶点几何信息(顶点坐标、颜色、纹理)及其他属性传送到顶点着色器中,经过自定义的顶点着色程序处理产生变化后的顶点位置信息,将变化后的顶点位置信息传递给后续图元装配阶段,对应的顶点纹理、颜色等信息则经光栅化后传递到片元着色器。

顶点着色器替代了原有固定管线(OpenGL 1.x采用固定渲染管线)的顶点变换、光照计算,采用 着色语言进行开发(OpenGL ES Shading Language) ,开发人员可以根据自己的需求采用着色语言自行开发顶点变换、光照等功能,大大增加了程序的灵活性。

不同于OpenGL ES 2.0中,顶点着色器输入为attribute(属性变量),输出为varying(易变变量)。

在OpenGL ES3.0中,顶点着色器的输入主要为待处理顶点相应的in(输入属性)、uniform(统一变量)、采样器以及临时变量,输出主要为经过顶点着色器后生成的out(输出属性)及一些内建输出变量。

  • uniform(统一变量):

    指的是对于同一组顶点组成的三维物体其所有顶点属性都相同的属性量,一般为场景中当前的光源位置、当前的摄像机位置、投影系列矩阵等,,可以使用 uniform (统一变量) 传入顶点着色器。
  • in(输入属性)

    指的是三维空间中物体每个顶点各自不同的信息所属的变量,一般为顶点的位置、颜色、法向量等,每个顶点各自不同的信息都是以attribute变量的方式传入顶点着色器的。
  • out(输出属性)

    对于从顶点着色器传递到片元着色器的量,如 用于传递到片元着色器中的顶点颜色,可以使用out (输出属性)。
  • gl_Position (内建变量):

    内建变量为输出到OpenGL ES渲染管线的数据变量。

    顶点着色器从应用程序中获得原始的顶点位置数据,这些原始的顶点数据在顶点着色器中经过平移、旋转、缩放等数学变换后,生成新的顶点位置。新的顶点位置通过在顶点着色器中写入gl_Position传递到渲染管线的后继阶段继续处理。
  • gl_PointSize (内建变量):

    内建变量为输出到OpenGL ES渲染管线的数据变量。

    gl_PointSize的值一般只有在采用了点绘制方式之后才有意义。顶点着色器中可以计算一个点的大小(单位为像素),并将其赋值给gl_PointSize(float类型)以传递给渲染管线,如果没有明确赋值的话,默认值为1。

顶点着色器代码举例

着色器采用OpenGL ES Shading Language编写,为一种类C的编程语言,顶点着色器代码实现举例如下:

详细了解着色语言语法,可查看khronos官方:

OpenGL ES Shading Language 3.00:

https://www.khronos.org/registry/OpenGL/specs/es/3.0/GLSL_ES_Specification_3.00.pdf

// 顶点着色器程序举例如下:
#version 300 es // 为着色语言版本v3.00,必须出现在第一行
// 总变换矩阵
uniform mat4 uMVPMatrix;
// 顶点位置
in vec3 aPosition;
// 顶点颜色
in vec4 aColor;
// 用于传递给片元着色器的变量
out vec4 vColor; void main()
{
// 根据总变换矩阵计算此次绘制此顶点位置
gl_Position = uMVPMatrix * vec4(aPosition,1);
// 将接收的颜色传递给片元着色器
vColor = aColor;
}
  • uniform(统一变量)

    如:顶点着色器举例程序中的uniform mat4 uMVPMatrix; 总变换矩阵。
  • in(输入属性)

    如:顶点着色器举例程序中的顶点位置aPosition、顶点颜色aColor。
  • out(输出属性)

    如:顶点着色器举例程序中的out vec4 vColor; 顶点颜色。
  • 内建输出变量

    如:如输出到渲染管线的 gl_Position(顶点的最终位置)。

注:

输出属性(顶点颜色、纹理)在顶点着色器赋值后并不直接赋值到片元着色器中,而是经由光栅化阶段处理。将三维世界由顶点数据构成的物体离散为二维世界一个个像素点,片元着色器处理的数据是已经离散到二维显示平面上的一个个离散的像素点(每个像素点为一个片元)。而这个离散过程中位于两个顶点之间的像素顶数据(颜色值、纹理数据),大多在光栅化阶段差值计算产生。

上图中,展示的是一个自上而下从黑到白的一个渐变三角形。

在OpenGL ES的输入中,仅仅输入了三个顶点颜色值,顶点1 (0,0,0) 黑色、,顶点2 (1,1,1) 白色、顶点3 (1,1,1) 白色。而在三角形在光栅化阶段,投影到屏幕上之前,其他部分的颜色是由这三个输入顶点颜色值差值计算产生的,例如:三角形高二分之一处的点颜色差值计算的结果为 (0.5,0.5,0.5) 灰色。

2.4 变换反馈

变化反馈能够将 “顶点着色器” 的计算结果输出,也就是可以将大量的向量、矩阵等运算交给GPU来运行,在一定程度上节省CPU的运算量。变化反馈的计算结果,可以输出到一个缓冲区中,该缓冲区中的内容还可作为另一个 “顶点着色器” 程序的输入数据。

2.5 图元装配

在这个阶段,主要有两个任务,一个是图元组装,另一个是图元处理。

  • 图元组装是顶点数据被结合成完整的图元。

    例如:一个单独的点就是一个图元,它只需要一个点;一条直线也是一个图元,为两个点构成的图元;三角形则需要三个顶点构成一个图元。

    图元组装时会有效的收集足够的顶点以组成一个单独的图元,方便进行后续处理。
  • 图元处理最重要的是剪裁,其任务是消除位于显示区域之外的部分几何图元。

    图元处理阶段:

    如果图元完全位于视椎体内,则传递图元进行后面的处理;

    如果有一部分位于视椎体内,则剪裁该图元(剪裁图元可能会额外增加顶点数据)。

    如果其完全位于视椎体外,则丢弃该图元,以节省GPU性能。

2.6 光栅化

光栅化就是将三维空间中连续的数学图形,栅格化为二维显示平面上一个个像素点的过程。

将由三维顶点信息组成的几何图元,栅格化为帧缓冲区中由无数像素点构成的二维图像。这里,最终生成的帧缓冲区中每一个像素点都被看做一个片元。

例如,一条直线可能在屏幕上包含了5个像素,而光栅化就是将这条直线转化为5个片元,每个片元由顶点坐标、顶点颜色、顶点纹理坐标以及顶点的深度等信息组成。

光栅化实际是由以下两个过程组成:

  • 将三维空间中的几何图元,投影到二维平面上:

  • 将投影到二维平面上的几何图元,栅格化为帧缓冲区中一个个像素:

2.7 片元着色器

片元着色器是用于处理片元相关数据的可编程单元,可以执行纹理的采样、颜色的汇总、计算雾颜色等操作,每片元执行一次(每个像素执行一次)。

片元着色器可编程单元替换了OpenGL ES 1.x固定渲染管线阶段中纹理颜色求和、雾以及Alpha测试等阶段,被其替代的功能将需要由开发人员用着色器语言编码完成。

顶点着色器每顶点执行一次,片元着色器每片元[像素]执行一次。

一般情况下片元的数量远远大于构成三维物体顶点的数量(例如:一个三角形由三个顶点数据构成,光栅化到屏幕上时,却有成百上千个像素点构成),因此片元着色器的执行次数明显大于顶点着色器的执行次数,开发中为提高运行效率,应尽量减小片元着色器的运算量,将一些复杂运算放在顶点着色器中执行。

  • out(输出属性)

    从顶点着色器传递到片元着色器的数据变量,如顶点着色器所介绍,由系统在顶点着色器后的光栅化阶段自动插值产生。out属性变量个数随需求而定,并没有硬性的变量数量上限限制。
  • out vec4 fragColor

    片元着色器用 fragColor 向OpenGL ES渲染管线写入计算完成的片元颜色值,此颜色值将进入渲染管线的后继阶段继续处理。

    不同于OpenGL ES2.0的最终片元颜色输出到内建变量gl_FragColor ,OpenGL ES 3.0最终片元的颜色输出到输出属性fragColor。

片元着色器代码举例

着色器采用OpenGL ES Shading Language编写,为一种类C的编程语言,片元着色器代码实现举例如下:

详细了解着色语言语法,可查看khronos官方:

OpenGL ES Shading Language 3.00:

https://www.khronos.org/registry/OpenGL/specs/es/3.0/GLSL_ES_Specification_3.00.pdf

// 片元着色器举例(每个片元[像素]执行一次)
#version 300 es // 为着色语言版本v3.00,必须出现在第一行
precision mediump float;
// 接收从顶点着色器过来的参数(具体数值由光栅化阶段差值产生)
out vec4 vColor;
// 输出到的片元颜色
out vec4 fragColor void main()
{
//片元的最终赋值
fragColor = vColor;
}
  • varying(易变变量)

    例,接收从顶点着色器过来的颜色参数 varying vec4 vColor
  • out vec4 fragColor

    不同于OpenGL ES2.0的最终片元颜色输出到内建变量gl_FragColor ,OpenGL ES 3.0最终片元的颜色输出到输出属性fragColor。

    例,片元着色器具体程序中的 fragColor = vColor。

2.8 剪裁测试

如果程序中启用了剪裁测试,OpenGL ES 会检查每个片元在帧缓冲中对应的位置,若对应位置在剪裁窗口中则将此片元送入下一阶段,否则丢弃此片元。。

2.9 深度测试和模板测试

  • 深度测试
  • 模板测试

a、深度测试

深度测试是在片元处理过程中通过测试一个片元在视椎体的深度坐标,来判断它是否被更小深度坐标的片断遮挡而决定是否真去绘制它(开启深度测试有助于节省GPU性能);没有深度测试,物体展现与否就会取决于绘制顺序而不是摆放的坐标。

// 在Android中打开深度测试
GLES30.glEnable(GLES20.GL_DEPTH_TEST);

b、模板测试

模板测试的主要功能为将绘制区域限定在一定的范围内,一般用在湖面倒影、镜像等场合。

模板测试涉及到一个词模板缓冲区(Stencil Buffer)。在模板缓冲中,通常每个模板值(Stencil Value)是8位的,所以每个片元一共能有256种不同的模板值。

使用时,我们可以先将模板缓冲区刷成个某个固定的值,然后进行图像绘制,完成绘制后模板缓冲区中的值可能会被污染[污染],此时开发者就可以选择丢弃或是保留这些被污染的片段,从而构造湖面倒影或者镜像。

2.10 颜色缓冲混合

经过前面的几个阶段,已经产生了候选片元,而候选片元最终要进入帧缓冲成为像素。

在候选片元进入帧缓冲区成为像素的过程中:

  • 若未开启了Alpha混合,则当前片元成为屏幕的像素,覆盖掉屏幕上的原有像素;
  • 若开启了混合,则根据Alpha值将当前像素与候选片元进行颜色混合得出最终的颜色。

2.11 抖动

抖动是一种简单的操作,是一种在色彩空间较小的设备上展示较大色彩空间的图像的一种方法。

例如:在一个RGB_565的设备上展示RGB_888的图像,展示时如果简单进行数据截断位,会造成色彩的失真和生硬。抖动使用一个矩阵,来调整一个像素周围的像素的值,来使人眼产生错觉,而模拟出原来的色彩。

这种技巧,对于只支持8位或者16位颜色信息的显示系统中非常有用。

// 开启抖动
GLES30.glEnable(GLES20.GL_DITHER);

2.12 帧缓冲

OpenGL ES的物体绘制并不是直接回执在屏幕上的,而是预先在帧缓冲区中进行绘制,每一绘制完成一帧再将绘制的结果交换到屏幕上。因此,每次绘制新的一帧数据时都需要清理缓冲区中的相关数据,否则可能产生错误的显示效果。

三、参考

OpenGL ES Shading Language 3.00:

https://www.khronos.org/registry/OpenGL/specs/es/3.0/GLSL_ES_Specification_3.00.pdf

OpenGL ES 3.0 API:

https://www.khronos.org/registry/OpenGL-Refpages/es3.0/

gl_pipeline:

http://www.songho.ca/opengl/gl_pipeline.html

learnopengl:

https://learnopengl-cn.github.io/01%20Getting%20started/01%20OpenGL/

GLSL详解:

https://zhuanlan.zhihu.com/p/349296191

OpenGL ES 变换反馈:

https://www.shangmayuan.com/a/4a48e882e765411b8597e12c.html

= THE END =

一文详解 OpenGL ES 3.x 渲染管线的更多相关文章

  1. 一文详解 OpenGL ES 纹理颜色混合

    在OpenGL中绘制的时候,有时候想使新画的颜色和已经有的颜色按照一定的方式进行混合.例如:想使物体拥有半透明的效果,或者绘制叠加光亮的效果,这时候就需要用到OpenGLES混合. 如上图所示,为石头 ...

  2. 详解 OpenGL ES 2.x 渲染流程

    khronos官方对OpenGL ES的描述如下: OpenGL ES is a royalty-free, cross-platform API for rendering advanced 2D ...

  3. 一文详解Hexo+Github小白建站

    作者:玩世不恭的Coder时间:2020-03-08说明:本文为原创文章,未经允许不可转载,转载前请联系作者 一文详解Hexo+Github小白建站 前言 GitHub是一个面向开源及私有软件项目的托 ...

  4. 一文详解 Linux 系统常用监控工一文详解 Linux 系统常用监控工具(top,htop,iotop,iftop)具(top,htop,iotop,iftop)

    一文详解 Linux 系统常用监控工具(top,htop,iotop,iftop)     概 述 本文主要记录一下 Linux 系统上一些常用的系统监控工具,非常好用.正所谓磨刀不误砍柴工,花点时间 ...

  5. 一文详解 WebSocket 网络协议

    WebSocket 协议运行在TCP协议之上,与Http协议同属于应用层网络数据传输协议.WebSocket相比于Http协议最大的特点是:允许服务端主动向客户端推送数据(从而解决Http 1.1协议 ...

  6. 1.3w字,一文详解死锁!

    死锁(Dead Lock)指的是两个或两个以上的运算单元(进程.线程或协程),都在等待对方停止执行,以取得系统资源,但是没有一方提前退出,就称为死锁. 1.死锁演示 死锁的形成分为两个方面,一个是使用 ...

  7. 一文详解 纹理采样与Mipmap纹理——构建山地渲染效果

    在开发一些相对较大的场景时,例如:一片铺满相同草地纹理的丘陵地形,如果不采用一些技术手段,就会出现远处的丘陵较近处的丘陵相比更加的清晰的视觉效果,而这种效果与真实世界中近处的物体清晰远处物体模糊的效果 ...

  8. 一文详解Redis键过期策略

    摘要:Redis采用的过期策略:惰性删除+定期删除. 本文分享自华为云社区<Redis键过期策略详解>,作者:JavaEdge. 1 设置带过期时间的 key # 时间复杂度:O(1),最 ...

  9. OpenGL ES 2.0 渲染管线 学习笔记

    图中展示整个OpenGL ES 2.0可编程管线 图中Vertex Shader和Fragment Shader 是可编程管线: Vertex Array/Buffer objects 顶点数据来源, ...

随机推荐

  1. 如何做好 NodeJS 框架选型?

    作为一个有一定工作经验的工程师,工作中经常会遇到技术选型的问题.比如当我们在工作中需要使用到 NodeJS 时,第一个要解决的问题就是如何选择一个合适的框架. 不同的框架有不同的特点,如果我们仅仅从框 ...

  2. Spring Boot 如何热加载jar实现动态插件?

    一.背景 动态插件化编程是一件很酷的事情,能实现业务功能的 解耦 便于维护,另外也可以提升 可扩展性 随时可以在不停服务器的情况下扩展功能,也具有非常好的 开放性 除了自己的研发人员可以开发功能之外, ...

  3. 【HMS Core 6.0全球上线】华为钥匙环服务,打造跨应用跨形态无缝登录体验

    华为钥匙环服务(Keyring),是HMS Core在安全领域开放的全新服务,为全球开发者提供用户认证凭据(以下简称"凭据")本地存储和跨应用.跨形态共享能力,帮助您在安卓应用.快 ...

  4. 初次认识指针:C语言*p、p以及&p的区别,*p和**p的区别?

    https://blog.csdn.net/weixin_43115440/article/details/93475460 先要理解地址和数据,你可以想象有很多盒子,每个盒子有对应的号码,那个号码叫 ...

  5. 3 Implementation: The Big Picture 实现:蓝图

    三.Implementation: The Big Picture 实现:蓝图 3.1 Layering of a .NET Solution .Net解决方案的分层 The picture belo ...

  6. kafka应用讲解及应用场景(三)

    一. 验证 1.进入bin目录 cd bin 2.ls查看脚本 会发现下面有很多脚本文件,由于我是要创建一个topic所有直接打开kafka-topics.sh脚本查看命令 打开脚本后发现里面有很多命 ...

  7. TypeError: Restaurant() takes no arguments

    1. 错误描述 TypeError: Restaurant() takes no arguments 2. 原因:在编写__init__时,pycharm会自动添加关键字,有时会直接写称整型int, ...

  8. 【UE4】 补丁Patch 与 DLC

    概述 UE4 中主要使用 Project Launcher 来进行补丁和DLC的制作 补丁与 DLC 都需要基于某个版本而制作 补丁 与 DLC 最后以 Pak 形式表现, 补丁的 pak 可以重命名 ...

  9. Flink sql 之 join 与 StreamPhysicalJoinRule (源码解析)

    源码分析基于flink1.14 Join是flink中最常用的操作之一,但是如果滥用的话会有很多的性能问题,了解一下Flink源码的实现原理是非常有必要的 本文的join主要是指flink sql的R ...

  10. 架构师之路-https底层原理

    引子 先说说我对架构师的理解.从业务能力上,需要的是发现问题和解决问题的能力:从团队建设上,需要的是能培养团队的业务能力:从项目管理上,把控好整个项目和软件产品的全生命周期. 我搜索了一下架构师的培训 ...