Android OpenGL ES零基础系列(三):OpenGL ES的渲染管道及VertexShader与FragmentShader

前言

在前2篇文章中,我们都说到着色器,且在第二篇中正式说到,这着色器只能用在OpenGL ES2.x等可编程管道里面,而在OpenGL ES1.x是不能用的。但我们一直没有说这是为什么,两者有什么区别。那这篇我们就一起来学习下OpenGL ES中的渲染管道。

正文

管道,英文名叫Pipeline,相信用过FaceBook图片加载库的同学对这个管道并不陌生,因为SimpleImageDrawee里面也是用的管道来对图片进行的一个处理。由于其底层也是C,因此我可以大胆的猜想,FaceBook图片加载库的设计思路可能有参考OpenGL(这当然纯属臆想^_^)。

管道用正确的计算机语言来描述就是:

显卡执行的、从几何体到最终渲染图像的、数据传输处理计算的过程。

即是管道,那就得有先后顺序。整体是从上游流到下游。

在OpenGL ES1.x中,它是固定管道,整体是封闭的,中间的各道工艺按固定的流程顺序走。看下图:

从上图可以看出,这些工艺顺序是固定的。整个过程又可以分成三部分:处理顶点、处理片元、验证片元信息并存入内存。

Rasterizer:光栅化处理,当顶点处理完后,会交给rasterizer来进行光栅化处理,结果会把顶点的坐标信息转换成能在屏幕显示的像素信息即片元(fragments)。生成片元后,接下来就是对fragments片元的各种验证,即过滤掉无用的片元,裁剪掉不在视野内的片元,最终把有效片元存储入内存中。

这里对于Rasterizer光栅化,让我们一起来了解学习下:

Rasterizer/Rasterization:光栅化处理

这个词儿Adobe官方翻译成栅格化或者像素化。没错,就是把矢量图形转化成像素点儿的过程。我们屏幕上显示的画面都是由像素组成,而三维物体都是点线面构成的。要让点线面,变成能在屏幕上显示的像素,就需要Rasterize这个过程。就是从矢量的点线面的描述,变成像素的描述。(或:所顶点从世界坐标系转换为屏幕坐标系的片元)

如下图,这是一个放大了1200%的屏幕,前面是告诉计算机我有一个圆形,后面就是计算机把圆形转换成可以显示的像素点。这个过程就是Rasterize。

现在是一个多元化的社会,是一个讲个性化的社会,什么都想着个性化,OpenGL ES也不例外,它为个性化的需求提供了接口。如图一中的蓝色方块部分,就是可以高度定制化的地方,因此也就形成了OpenGL ES2.x等的可编程管道,在OpenGL ES里面有两个专用的词VertexShader(顶点着色器)、FragmentShader(片元着色器),分别对应图一中的Coordinate蓝色块和Texture等蓝色块。

下面就看下OpenGL ES2.0 可渲染管道图:

VertexShader:顶点着色器

顶点着色器,记得在前2篇中,我们有贴出2个着色器的脚本语句,再次贴出如下:

 /**
     * 顶点着色器的语句
     */
    private final String mVertexShader =
            "uniform mat4 uMVPMatrix;\n" +
                    "attribute vec4 aPosition;\n" +
                    "attribute vec2 aTextureCoord;\n" +
                    "varying vec2 vTextureCoord;\n" +
                    "void main() {\n" +
                    "  gl_Position = uMVPMatrix * aPosition;\n" +
                    "  vTextureCoord = aTextureCoord;\n" +
                    "}\n";

    /**
     * 片元着色器的语句
     */
    private final String mFragmentShader =
            "precision mediump float;\n" +
                    "varying vec2 vTextureCoord;\n" +
                    "uniform sampler2D sTexture;\n" +
                    "void main() {\n" +
                    "  gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
                    "}\n";

下面看下vertexShader语句中的关键字:

attribute :使用顶点数组封装每个顶点的数据,一般用于每个顶点都各不相同的变量,如顶点位置、颜色等

uniform : 顶点着色器使用的常量数据,不能被着色器修改,一般用于对同一组顶点组成的单个3D物体中所有顶点都相同的变量,如当前光源的位置。

sampler:这个是可选的,一种特殊的uniform,表示顶点着色器使用的纹理。

mat4: 表示4×4浮点数矩阵,该变量存储了组合模型视图和投影矩阵

vec4:表示包含了4个浮点数的向量

varying是用于从顶点着色器传递到片元着色器或FragmentShader传递到下一步的输出变量

uMVPMatrix * aPosition:通过4*4的变换矩阵变换位置后,输出给gl_Position。gl_Position是顶点着色器内置的输出变量。gl_FragColor:是片元着色器内置的输出变量。

PrimitiveAssembly:图元装配

图元即图形,在OpenGL中有几个基本图元:点,线,三角形,其它的复杂图元都是基于这些基本图元来绘成的。

在图元装配不为阶段,那些经过顶点着色器(VertexShader)处理过的顶点数组或缓冲区的数据(VertexArrays/Buff Objects),被组装到一个个独立的几何图形中(eg:点,线,三角形等)。

对装配好的每一个图元,都必须确保它在世界坐标系(即能显示在屏幕的可见区域)中,而对于不在世界坐标系中的图元,就必须进行裁剪,使其处在世界坐标系中才能流到下一道工序(光栅化处理)。

在这里注意下还有一个剔除操作(Cull),前提是这个功能的开关是打开的:GLES20.glEnable(GLES20.GL_CULL_FACE);,剔除的是图元的背影,阴影,背面等。

FragmentShader:片元着色器

片元着色器主要是对光栅化处理后生成的片元逐个进行处理。接收顶点着色器输出的值,需要传入的数据,以及它经过变换矩阵后输出值存储在哪里可以通过 下图一目了然:

gl_FragColor:是片元着色器内置的输出变量。

因为Rasterization光栅化处理后,图元只是在屏幕有了象素,却还没有进行颜色处理,还是看不到东西。

因此FragmentShader可以理解为:告诉电脑如何上色的——如何处理光、阴影、遮挡、环境等等。

Per-Fragment Operations:逐个片元操作阶段

在片元着色器对片元进行综合的处理,并最终为片元生成一个颜色值并存储在gl_FragColor变量后,接下来就是逐个对片元进行一系列的测试。在上面我们说到,在光栅化处理时,它由于是把顶点从世界坐标系转换到屏幕坐标系,因此在光栅处理后,每个片元在屏幕上都有个坐标(Xw,Yw).且存储在了帧缓冲区(FrameBuffer),包括片元着色器也是对(Xw,Yw)这个坐标的片元进行处理。

下图展示了Per-Fragment Operations的过程:

Pixel ownership test:像素所有权测试,它决定FrameBuffer中某一个(Xw, Yw)位置的像素是否属于当前 Opengl ES的context,比如:如果一个Opengl ES帧缓冲窗口被其他窗口遮住了,窗口系统将决定被遮住的像素不属于当前Opengl ES的context,因此也就不会被显示。

Scissor test:裁剪测试决定,判断某一个位置为(Xw, Yw)的片元是否位于裁剪矩形内,如果不在,则被丢弃。

Stencil Test / Depth tests:模板和深度测试,传入片元的模板和深度值,决定是否丢弃片元。

Blending:将FragmentShader新产生的片元颜色值和FrameBuffer中某一个位置为(Xw, Yw)的片元存储的颜色值进行混合。

Dithering:对于可用颜色较少的系统,可以以牺牲分辨率为代价,通过颜色值的抖动来增加可用颜色数量。抖动操作是和硬件相关的,OpenGL允许程序员所做的操作就只有打开或关闭抖动操作。实际上,若机器的分辨率已经相当高,激活抖动操作根本就没有任何意义。要激活或取消抖动,可以用glEnable(GL_DITHER)glDisable(GL_DITHER)函数。默认情况下,抖动是激活的。

参考资料

Android OpenGL ES 开发教程(3):OpenGL ES管道(Pipeline)

如何理解OpenGL中着色器,渲染管线,光栅化等概念?

OpenGL ES 2.0渲染管线

时间: 2024-08-07 16:35:43

Android OpenGL ES零基础系列(三):OpenGL ES的渲染管道及VertexShader与FragmentShader的相关文章

Android OpenGL ES零基础系列(一):理解GLSurfaceView,GLSurfaceView.Render的基本用法

转载请注明出处 前言 OpenGL ES是OpenGL的一个子集,是针对手机.PDA和游戏主机等嵌入式设备而设计的.该API由Khronos集团定义推广,Khronos是一个图形软硬件行业协会,该协会主要关注图形和多媒体方面的开放标准. 因此OpenGL ES作为第三方库被应用在android中. 到目前为止,OpenGL ES已经发展有了3个版本,OpenGL ES 1.0 , OpenGL ES 2.0 , OpenGL ES 3.0.其中OpenGL ES 1.0 是以OpenGL 1.3

hadoop零基础系列之一:虚拟机下的Linux集群构建

经过了近两年的hadoop学习和使用,有必要把hadoop的学习进行一个总结,最好的方式就是以博客的方式来总结,既重新梳理以前的学习也可以和同行沟通交流,从今天开始将陆续推出hadoop零基础系列的文章, 当然总结过程中会参考相关方面的资料书,有些例子会直接来源与网络和书籍,我会在文中列出引用 考虑到初学者都是在单机的环境进行学习,所以我们采用虚拟机的方式来构建Linux集群,本篇我们先把Linux集群给构建起来,主机系统本人采用的系统是win7 旗舰版 1.虚拟机软件VMware 采用的VMw

搜索引擎ElasticSearchV5.4.2系列三之ES使用

相关博文: 搜索引擎ElasticSearchV5.4.2系列一之ES介绍 搜索引擎ElasticSearchV5.4.2系列二之ElasticSearchV5.4.2+klanaV5.4.2+x-packV5.4.2安装 搜索引擎ElasticSearchV5.4.2系列三之ES使用 1.启动ES ,Kibana cd elasticsearch-6.0.0-alpha2/bin ./elasticsearch cd kibana-5.4.2-linux-x86_64 ./bin/kibana

Android属性动画Property Animation系列三之LayoutTransition(布局容器动画)

在上一篇中我们学习了属性动画的ObjectAnimator使用,不了解的可以看看 Android属性动画Property Animation系列一之ObjectAnimator.这一篇我们来学点新的东西.做项目的时候应该碰到这种问题:根据不同条件显示或者隐藏一个控件或者布局,我们能想到的第一个方法就是 调用View.setVisibility()方法.虽然实现了显示隐藏效果,但是总感觉这样的显示隐藏过程很僵硬,让人不是很舒服,那么有没有办法能让这种显示隐藏有个过渡的动画效果呢?答案是肯定的,不言

Android近场通信---NFC基础(三)(转)

转自 http://blog.csdn.net/think_soft/article/details/8180203 过滤NFC的Intent 要在你想要处理被扫描到的NFC标签时启动你的应用程序,可以在你的应用程序的Android清单中针对一种.两种或全部三种类型的NFC的Intent来过滤.但是,通常想要在应用程序启动时控制最常用的ACTION_NDEF_DISCOVERED类型的Intent.在没有过滤ACTION_NDEF_DISCOVERED类型的Intent的应用程序,或数据负载不是

salesforce lightning零基础学习(三) 表达式的!(绑定表达式)与 #(非绑定表达式)

在salesforce的classic中,我们使用{!expresion}在前台页面展示信息,在lightning中,上一篇我们也提及了,如果展示attribute的值,可以使用{!v.expresion}展示信息. lightning在component中解析动态值的时候,会将{!} 这个里面的变量进行动态的解析以及展示.当然这个变量可以是基础类型,自定义类型,数组集合等等,当然如果表达式为空字符串或者是空格则不会解析.偶尔特殊的要求为需要输出'{!}'这个字符串,官方文档说可以使用<aura

Salesforce零基础(三)简单的数据增删改查页面的构建(Ajax样式)

VisualForce封装了很多的标签用来进行页面设计 下面以一个单一的表进行数据增删改查.表结构如图1所示.通过图可以看出GOODS表自己定义的参数主要包括以下: GoodsName__c,GoodsType__c,GoodsBrand__c,GoodsDescribe__c,GoodsPrice__c. 图1 VF每个页面都是以<apex:page>标签起始</apex:page>结束,每个VF页面都有一个Controller用来控制其业务逻辑.本篇例子中主要用到的控件包括如下

Java零基础系列005——条件控制

public class Control { public static void main(String[] args) { //java里常用的条件控制语句有if-esle,switch语句. boolean tr = true; boolean fa = false; //if语句后面的括号里装的是逻辑,其结果是boolean类型,当括号中结果为true时,程序运行大括号里的语句,否者运行else里面的. System.out.println("*******************if-

Java零基础系列002——命名、变量类型、类型转换、JDK中二进制整数和数字分隔符新特性

public class BasicDataType { public static void main(String[] args) { /* * 标示符:用于给变量.方法.类命名,必须以字母.下划线.$符号开头.以字母.下划线.数字.$符号组合,且不能为关键字 * * * */ System.out.println("--------------标示符------------"); int $,_,a;//正确的命名 int 彭驰=12;//由于Java内部采用Unicode编码方