OpenGL Shading language学习总结

这篇文章是根据<OpenGL ES 2.0游戏开发(上卷):基础技术和典型案例 >中对应章节的总结,看了好几本OpenGL ES的入门图书,只有这本让我在入门的时候不那么迷茫,强烈推荐!非常遗憾,这本书没有电子正版,我只能购买一本正版图书,然后下载一个pdf版本在公交上阅读。


着色语言概述

OpenGL ES 着色语言是一种高级的图形编程语言,其源自于广泛使用的C语言,同时具有RenderMan以及其他着色语言的一些优良特性,易于被开发人员掌握。简单来说,OpenGL ES着色语言主要包括一下特性:
  • OpenGL ES 着色语言是一种高级的过程语言(注意,不是面向对象)
  • 对顶点着色器、片元着色器使用的是同样的语言,不做区分
  • 基于C/C++的语法以及流程控制
  • 完美支持向量和矩阵的各种操作
  • 通过类型限定符来管理输入和输出
  • 拥有大量的内置函数来提供丰富的功能

着色语言基础

一. 数据类型概述

总体来说,数据类型可以分为标量,向量,矩阵,采样器,结构体以及数组等几类。
1. 标量:标量也被称为“无向量”,其值只具有大小,并不具有方向。OpenGL ES 着色语言支持的标量类型有bool、int、float。
2. 向量:OpenGL ES 着色语言中,向量可以看做是同样类型的标量组成的,其基本类型也分为bool、int以及float 3种。每个向量可以由2个、3个或者4个相同的标量组成。向量在着色器代码的开发中有着十分重要的作用,可以很方便地存储以及操作颜色、位置、纹理坐标等不仅包含一个组成部分的量。访问向量中的各个分量不但可以采用“.”加上不同的分量名,还可以将向量看成一个数组,用下标来进行访问。
3. 矩阵:矩阵按尺寸可以分为2x2矩阵,3x3矩阵,以及4x4矩阵。OpenGL ES 着色语言中,矩阵是按列顺序组织的,也就是一个矩阵可以看作为几个列向量组成的。对于矩阵的访问,可以将矩阵作为列向量的数据来访问,如:matrix为一个mat4,可以使用matrix[2]来获取该矩阵的第3列,其为一个vec4。
4. 采样器:采样器是着色语言中不同于C语言的一种特殊的基本数据类型,其专门用来进行纹理采样的相关操作。一般情况下,一个采样器变量代表一幅或一套纹理贴图。
5. 结构体:OpenGL ES 着色语言还提供了类似于C语言中的用户自定义结构体,同样也是使用struct关键字进行声明。
6. 数组:在着色语言中,开发人员可以声明任何类型的数组。声明数组的方式主要有两种:
          (1)  在声明数组的同时,指定数组的大小。
          (2)  在声明数组的同事,也可以不指定数组的大小,但是必须符合下列两种情况之一。
                 (a) 引用数组之前,要再次使用第一种声明方式来声明数组。再次声明数组,指定大小之后,就不能再进行声明了。
                 (b)  代码中访问数组的下标都是编译时常量,这时编译器会自动创建适当大小的数组,使得数组尺寸足够存储编译器看到的最大索引值对应的元素。
7. 空类型:空类型使用void表示,仅用来声明不返回任何值的函数。

二. 数据类型的基本使用

1. 声明、作用域以及初始化
变量的声明以及作用域与C++语法类似,可以在任何需要的位置声明变量,同时其作用域也与C++类似,分为局部变量与全局变量。着色语言还有一点特殊的地方,在一些着色语言的实现中不可以在if语句中声明新的变量,这是为了简化变量在else子句上作用域的实现,目前大部分实现都是支持在if中声明变量的,但是不排除特殊情况。
着色器中变量的命名很自由,仅要求变量由字母、数字与下划线组成,且必须以字幕或者下划线开头。为了使得开发的代码简洁美观,有下列建议:
 (1)  由于系统中很多内建变量都是以“gl_”作为开头的,因此用户自定义的变量不允许使用“gl_”作为开头
 (2)  为自己的函数或变量取名时尽量采用有意义的拼写,除了一些局部变量外不要采用a、b、c这样的名称。
2. 变量初始化的规则
(1) 常用的初始化方式:变量可以在声明的时候初始化
(2) 用const限定符修饰的变量必须在声明的时候进行初始化
(3) 属性变量、一致变量以及易变变量在声明的时候一定不能进行初始化

三. 运算符

OpenGL Shading language学习总结_第1张图片

四. 类型转换

OpenGL ES 着色语言没有提供类型的自动提升功能,并且对类型的匹配要求十分严格。同时OpenGL ES着色语言也没有提供数据类型的强制转换功能,只能使用构造函数来完成类型转换。例如,float f0 =1; 在着色语言中这么声明浮点数是会产生编译错误的,这也是一个初学者常犯的错误。

五. 限定符

限定符在使用时应该放在变量类型之前,且使用attribute, uniform以及varying限定修饰符的变量必须为全局变量
OpenGL Shading language学习总结_第2张图片

1. attribute限定符
     attribute限定符顾名思义为属性限定符,其修饰的变量用来接收渲染管线传递进顶点着色器当前待处理顶点的各种属性值。这些属性值每个顶点各自拥有独立的副本,用于描述顶点的各项特征,如顶点坐标、法向量、颜色、纹理坐标等。
     用attribute限定符修饰的变量其值是由宿主程序批量传入渲染管线的,管线进行基本处理后再传递给顶点着色器。数据中有多少顶点,管线就调用多少次顶点着色器,每次将一个顶点的各种属性数据传递给顶点着色器中对应的attribute变量。因此,顶点着色器每次执行将完成对一个顶点各项属性数据的处理。
     attribute限定符只能用于顶点着色器中,不能在片元着色器中使用。且attribute限定符只能用来修饰浮点数标量、浮点数向量以及矩阵变量,不能用来修饰其他类型的变量。

2. uniform限定符
     uniform 为一致变量限定符,一致变量指的是对于同一组顶点组成的单个3D物体中所有顶点相同的量。uniform 变量可以用在顶点着色器或片元着色器中,其支持用来修饰所有的基本数据类型。与属性变量类似,一致变量的值也是从宿主程序中传入的。

3. varying限定符
     要想将顶点着色器中的信息传入到片元着色器中,则必须使用varying限定符。用varying 限定符修饰的全局变量又称为易变变量,易变变量可以看成是顶点着色器以及片元着色器之间的动态接口,方便顶点着色器与片元着色器之间信息的传递。
     光栅化后产生了多少个片元,就会插值计算出多少套易变变量。同时,渲染管线就会调用多少次片元着色器,可以看出,一般情况下对一个3D物体的渲染中,片元着色器执行的次数会大大超过顶点着色器。因此GPU硬件中配置的片元着色器硬件的数量往往多于顶点着色器硬件数量,通过这些硬件单元的并发执行,提高渲染速度。
     对于顶点着色器而言,一般是既声明易变变量,又对易变变量进行赋值以及传递给片元着色器,而片元着色器中声明易变变量用于接收顶点着色器传过来的值即可,是不可以对易变变量赋值的。

4. const 限定符
     用const限定符修饰的变量其值是不可以变的,也就是常量,又称为编译时常量。变量时常量在声明的时候必须进行初始化,同时这些常量在着色器外部是完全不可见的。

六. 流程控制

OpenGL ES 着色语言中提供了3种流程控制方式,分别由if-else条件语句、while(do-while)循环语句与for循环语句实现。

七. 函数的声明与使用

<返回类型>  函数名称  ([<参数序列>])  {/*函数体*/}
从上面语法中可以看出,声明函数要包含4个组成部分,分别是”返回类型”、“函数名称”、“参数序列”、“函数体”。返回类型根据需要可以是除了采样器之外的任何类型。参数序列中的参数除了可以指定指定类型之外,还可以指定用途,通过”in”, “out”, “inout”等参数用途修饰符进行修饰。

八. 片元着色器中浮点变量精度的指定

片元着色器中使用浮点数相关类型的变量时与顶点着色器中有所不同,在顶点着色器中直接声明使用即可,而在片元着色器中必须指定精度,若不指定精度可能会引起编译错误。精度有3种,lowp、mediump以及highp。一般情况下,使用mediump即可。
如果在开发中同一个片元着色器中浮点相关类型的变量都选用同一种精度,则可以指定整个着色器中浮点相关类型的默认精度, precision <精度> <类型>; 
1)  精度可以选择lowp、mediump以及highp 3种之一
2)  类型一般为float,这不单表示为浮点标量类型float指定了精度,还表示对浮点类型相关的向量、矩阵也指定了默认精度。


特殊的内建变量

     着色器代码的开发中会用到很多变量,其中大多数变量可能是由开发人员根据需求自定义的,但着色器中也提供了一些用来满足特定需求的内建变量。这些内建变量不需要声明即可使用,一般用来实现渲染管线固定功能部分与自定义顶点或片元着色器之间的信息交互。内建变量根据信息传递的方向可以分为两类,输入与输出变量。输入变量负责将渲染管线中固定功能部分产生的信息传递进着色器,输出变量负责将着色器产生的信息传递给渲染管线中固定功能部分。

一. 顶点着色器中的内建变量

顶点着色器中的内建变量主要是输出变量,包括gl_Position、gl_PointSize等。
1. gl_Position
顶点着色器从应用程序中获得原始的顶点位置数据,这些原始的顶点数据在顶点着色器中经过平移、旋转、缩放等数学变换后,生成新的顶点位置。新的顶点位置通过在顶点着色器中写入gl_Position传递到渲染管线的后继阶段继续处理。gl_Position的类型是vec4,写入的顶点位置数据也必须与其类型一致。几乎在所有的顶点着色器中都必须对gl_Position写入适当的值,否则后继阶段的处理结果将是不确定的。
2. gl_PointSize
顶点着色器中可以计算一个点的大小(单位为像素),并将其赋值给 gl_PointSize(标量 float类型) 以传递给渲染管线。如果没有明确赋值的话,就是采用默认值1了。

 二. 片元着色器中的内建变量

1. 内建输入变量
片元着色器中的内建输入变量主要有gl_FragCoord以及gl_FrontFacing。这两个内建变量都是只读的,由渲染管线中片元着色器之前的阶段生成。
(1)  gl_FragCoord
内建变量 gl_FragCoord (vec4类型) 中含有当前片元相对于窗口位置的坐标值x、y、z以及1/w。
(2)  gl_FrontFacing
gl_FrontFacing是一个布尔型的内建变量,通过读取该内建变量的值可以判断正在处理的片元是否属于在光栅化阶段生成此片元的对应图元的正面。如果属于正面,gl_FrontFacing的值为true,反之为false。期一般用于开发双面光照功能相关的应用程序中。

2. 内建输出变量
片元着色器中的内建输出变量主要有 gl_FragColor 与 gl_FragData,在片元着色器中根据具体情况需要给这两个内建变量写入值。实际开发中,对这两个内建变量赋值时应该根据具体情况仅选用其中的一个,而不应该同时对这两个都进行赋值。若执行了discard操作则两个内建变量都不需要写入值了。大部分的情况下,都是给 gl_FragColor 内建变量赋值了。
(1)  gl_FragColor
gl_FragColor (vec4类型) 内建变量用来由片元着色器写入计算完成的片元颜色值,此颜色值将送入渲染管线的后继阶段进行处理。
(2)  gl_FragData
gl_FragData 内建变量本身是一个vec4类型的数组,写入时要给出下标,如”gl_FragData[0]”。通过其写入的信息将供渲染管线中的后续过程使用。


着色语言的内置函数

内置函数按照设计目的可以分为3个类别:
1. 提供独特硬件功能的访问接口,如纹理采样系列的函数,这些函数用户是无法自己开发的。着色语言通过提供特定内置函数对这些硬件进行封装,建立了用户调用这些硬件功能的接口。
2. 简单的数学函数,如abs,floor等。
3. 一些复杂的函数,如三角函数等。



你可能感兴趣的:(OpenGL Shading language学习总结)