我的OpenGL学习进阶之旅OpenGL ES 着色语言 (上)

Posted 欧阳鹏

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我的OpenGL学习进阶之旅OpenGL ES 着色语言 (上)相关的知识,希望对你有一定的参考价值。

前言

通过前面几篇博客的介绍,我们了解了着色器是OpenGL ES 3.0 API的一个基础核心概念。

每个OpenGL ES 3.0程序都需要一个顶点着色器和一个片段着色器,以渲染有意义的图片。

考虑到着色器是API概念的核心,我希望在深入了解图形API的更多细节之前,掌握编写着色器的基础知识。

本篇博客的目标是理解着色器中的如下概念:

  • 变量和变量类型
  • 向量和矩阵的构造和选择
  • 常量
  • 结构和数组
  • 运算符、控制流和函数
  • 输入/输出变量、统一变量、统一变量块和布局限定符
  • 预处理器和指令
  • 统一变量和插值器打包
  • 精度限定符和不变性

一、OpenGL ES 着色语言基础知识

通过前面几篇博客的介绍,我们已经理解了着色器的作用的基本概念以及它融入管线的方式。

现在我们要关注的是着色器究竟是由什么组成。你可能已经察觉到,着色器的语法和C编程语言有很多相似之处。如果你能够理解C代码,理解着色器的语法就没有太大的难度。但是,两种语言之间当然有一些重大的区别,首先是版本规范和所支持的原生数据类型。

二、着色器版本规范

OpenGL ES 3.0 顶点着色器和片段着色器的第1行总是声明着色器的版本。

声明着色器版本通知着色器编译器预期在着色器中出现的语法和结构。编译器按照声明的着色器语言版本检查着色器语法。

采用如下语法声明着色器使用OpenGL ES 着色器语言 3.00版本:

#version 300 es

没有声明版本号的着色器被认定为使用OpenGL ES 着色语言的 1.00 版本。 OpenGL ES 着色语言的 1.00 版本用于OpenGL ES 2.0。 对于OpenGL ES 3.0,规范的作者决定匹配API和着色语言的版本号,这就是版本号从1.00跳到3.00的原因。

OpenGL ES着色语言3.0 增加了很多新概念,包括:

  • 非方矩阵
  • 全整数支持
  • 插值限定符
  • 统一变量块
  • 布局限定符
  • 新的内建函数
  • 全循环
  • 全分支支持
  • 无限的着色器指令长度。

PS: 关于如果第1行不是声明着色器版本,从而产生的编译错误,我之前的一篇博客有介绍,如下所示:

三、变量和变量类型

在计算机图形中,两个基本数据类型组成了变换的基础:向量和矩阵。

这两个数据类型在OpenGL ES 着色器语言中也是核心。

下表具体描述了着色器语言中存在的基于标量、向量和矩阵的数据类型。

3.1 标量类型

标量类型说明
float浮点
int整数
uint无符号整数
bool布尔值

3.2 向量类型

向量类型说明
float包含了1个浮点数的向量
vec2包含了2个浮点数的向量
vec3包含了3个浮点数的向量
vec4包含了4个浮点数的向量
int包含了1个整数的向量
ivec2包含了2个整数的向量
ivec3包含了3个整数的向量
ivec4包含了4个整数的向量
uint包含了1个无符号整数的向量
uvec2包含了2个无符号整数的向量
uvec3包含了3个无符号整数的向量
uvec4包含了4个无符号整数的向量
bool包含了1个布尔数的向量
bvec2包含了2个布尔数的向量
bvec3包含了3个布尔数的向量
bvec4包含了4个布尔数的向量

3.3 矩阵类型

矩阵类型说明
mat22x2浮点数矩阵
mat2x32x3浮点数矩阵
mat2x42x4浮点数矩阵
mat3x23x2浮点数矩阵
mat33x3浮点数矩阵
mat3x43x4浮点数矩阵
mat4x24x2浮点数矩阵
mat4x34x3浮点数矩阵
mat44x4浮点数矩阵

3.4 采样器

采样器是着色语言中不同于C语言的一种特殊的基本数据类型,其专门用来进行纹理采样的相关操作。一般情况下,一个采样器变量代表一幅或一套纹理贴图,其具体情况如下:

采样器说明
sampler2D用于访问二维纹理
smapler3D用于访问三维纹理
samplerCube用于访问立方贴图纹理

需要注意的是,与前面介绍的几种变量不同,采样器变量不能再着色器中初始化。一般情况下采样器变量都用uniform限定符来修饰,从宿主语言(如java)接受传递进着色器的值。

着色语言中的变量必须以某个类型声明。
例如,下面的声明描述如何声明一个标量、一个向量和一个矩阵:

float specularAtten; 	// A floating-point-based scalar
vec4 vPosition; 		// A floating-point-based 4-tuple vector 
mat4 mViewProjection; 	// A 4 x 4 matrix variable declaration
ivec2 vOffset; 		    // An integer-based 2-tuple vector


变量可以在声明时或者声明以后初始化。初始化通过使用构造器进行,构造器也用于类型转换。

四、变量构造器

OpenGL ES 着色语言在类型转换方面有非常严格的规则。也就是说,变量只能赋值为相同类型的其他变量或者与相同类型的变量进行运算。

在语言中不允许隐含类型转换的原因是,这样可以避免着色器作者遇到可能导致难以跟踪的缺陷的意外转换。

为了应付类型转换,语言中有一些可用的构造器。你可以使用构造器初始化变量,并作为不同类型变量之间的转换收到。

变量可以在声明(或者以后在着色器中)时使用构造器初始化。每种内建变量类型都有一组相关的构造器。

4.1 如何使用构造器初始化和转换标量值

我们先来看看如何使用构造器初始化和转换标量值。

float myFloat = 1.0;
float myFloat2 = 1;   //ERROR: invalid type conversion
bool myBool = true;
int myInt = 0;
int myInt2 = 0.0 ;    //ERROR: invalid type conversion
myFloat = float(myBool);  // Convert from bool -> float
myFloat = float(myInt);   // Convert from int  -> float
myBool  = bool(myInt);    // Convert from int  -> bool

4.2 如何使用构造器转换和初始化向量数据类型

类似地,构造器可以用于转换和初始化向量数据类型。 向量构造器的参数将被转换为与被构造的向量相同的基本类型(float、int 或 bool)。

向量构造器的参数传递有两种基本方法

  • 如果只为向量构造器提供一个标量参数,则该值用于设置向量的所有值。
  • 如果提供了多个标量或者向量参数,则向量的值从左到右使用这些参数设置。 如果提供了多个标量参数,那么在向量中必须有至少和参数中一样多的分量。

下面是构造向量的一些例子:

vec4 myVec4 = vec4(1.0);   			// myVec4 = 1.0,1.0,1.0,1.0
	
vec3 myVec3 = vec3(1.0,0.0,0.5);    // myVec3 = 1.0,0.0,0.5
vec3 temp   = vec3(myVec3);         // temp = myVec3 

vec2 myVec2 = vec2(myVec3);         // myVec2 =  myVec3.x, myVec3.y

myVec4 = vec4(myVec2,temp);         // myVec4 =  myVec2.x, myVec23.y, temp.x, temp.y

4.3 如何使用构造器转换和初始化矩阵数据类型

对于矩阵的构造,着色器语言很灵活。下面是构造矩阵的一些基本规则:

  • 如果只为矩阵构造器提供一个标量参数,则该值被放在矩阵的对角线上。例如,mat4(1.0) 将创建一个 4 x 4 的单位矩阵。
  • 矩阵可以从多个向量参数构造。例如,mat2 可以从两个 vec2 构造。
  • 矩阵可以从多个标量参数构造。每个参数代表矩阵中的一个值,从左到右使用。

矩阵的构造比刚才说明的基本规则更灵活,只要在矩阵初始化时提供足够多的分量、矩阵基本上可以从任何标量和向量的组合构造。

OpenGL ES中的矩阵以列优先顺序存储。使用矩阵构造器时,参数按列填充矩阵。

下面例子中的注释说明了矩阵构造参数如何映射到列中。

mat3 myMat3 = mat3( 1.0,0.0,0.0,     // First  column
					0.0,1.0,0.0, 	 // Second column
					0.0,1.0,1.0); 	 // Third  column

五、向量和矩阵分量

向量的单独分量可以用两种方式访问:使用 “.” 运算符或者通过数组下标“[]”

根据组成向量的分量数量,每个分量可以通过使用下面三个组合访问。

  • x, y , z , w
    数学上的向量
  • r, g , b , a
    颜色
  • s, t , p , q
    纹理坐标

三种不同的命名方案的原因是向量可以互换地用于标识 数学上的向量、颜色和纹理坐标。

x, r, s 分量总是引用向量的第一个分量。

不同的命名约定只是为了方便。但是,不能在访问向量时混用使用命名约定(换言之,不能采用 .xgr这样的引用方法,因为一次只能使用一种命名约定。

5.1 使用 “.” 运算符

使用 “.” 运算符时,可以在操作中重新排列向量的分量,下面是一个例子。

vec3 myVec3 = vec3(0.0, 1.0, 2.0);  // myVec3 = 0.0, 1.0, 2.0
vec3 temp;

temp = myVec3.xyz;				    // temp = 0.0, 1.0, 2.0
temp = myVec3.xxx;					// temp = 0.0, 0.0, 0.0
temp = myVec3.zyx; 					// temp = 2.0, 1.0, 0.0

5.2 使用数组下标“[]”

除了使用 “.” 运算符时,向量还可以使用数组下标“[]”运算符访问。

在数组下标中,元素[0] 对应于 x , 元素[1] 对应于 y ,等等。

矩阵被看成由一些向量组成。例如,mat2可以看做两个vec2, mat3可以看做3个vec3,等等。

对于矩阵,单独的列可以用数组下标“[]”运算符选择,然后每个向量可以通过向量访问行为来访问。

下面展示一些访问矩阵的例子:

mat4 myMat4 = mat4(1.0);        //Initialize diagonal to 1.0 (identity)
vec4 col0 = myMat4[0];          // Get col0 vector out of the matrix
float m1_1 = myMat4[1][1];      // Get element at [1][1] in matrix
float m2_2 = myMat4[2].z;       // Get element at [2][2] in matrix

六、常量

  • 可以将任何基本类型声明为常数常量。
  • 常数变量是着色器中不变的值。
  • 声明常量时,在声明中加入const限定符。
  • 常数变量必须在声明时初始化

下面是const声明的一些例子:

const float zero = 0.0;
const float pi = 3.14159;
const vec4 red = vec4(1.0, 0.0, 0.0, 1.0);
const mat4 identity = mat4(1.0);

正如C或者C++那样,声明为const的变量是只读的,不能在源代码中修改。

七、结构

除了使用语言中提供的基本类型之外,还可以和C语言一样将变量聚合成结构。

OpenGL ES着色语言中声明结构的语法如下例所示:

struct fogStruct

	vec4 color;
	float start;
	float end;
 fogVar;


上述定义的结果是一个名为fogStruct的新用户类型 和 一个名为fogVar的新变量。

结构可以用构造器初始化。在定义新的结构类型之后,也用与类型相同的名称定义一个新的结构构造器。
结构中的类型和构造器中的类型必须是一对一的。

例如,上述结构可以用如下构造语法初始化:

struct fogStruct

	vec4 color;
	float start;
	float end;
 fogVar;

fogVar = fogStruct(vec4(0.0 ,1.0, 0.0, 0.0),    // color
					0.5,						// start
					2.0);					    // end


结构的构造器基于类型名称,以每个分量作为参数。

访问结构元素的方法和C中的结构的相同,如下例所示:

vec4  color = fogVar.color;
float start = fogVar.start;
float end   = fogVar.end;

八、数组

除了结构之外,OpenGL ES着色语言也支持数组。
数组的语法和C语言相类似,索引从0开始。

下面的代码块展示了一些创建数组的例子:

float floatArray[4];
vec4 vecArray[2];


数组可以用数组初始化构造器初始化,如下面的代码所示:

float a[4] = float[](1.0,2.0,3.0,4.0);
float b[4] = float[4](1.0,2.0,3.0,4.0);
vec2 c[2]  = vec2[2](vec2(1.0, vec2(2.0)));


为数组构造器提供的大小是可选的。数组构造器中的参数数量必须对于数组的大小。

九、运算符

下表列出了OpenGL ES 着色器语言提供的运算符。


大部分运算符的表现和C中一样。OpenGL ES着色语言对运算符有严格的类型规则。
也就是说,运算符只能出现在有相同基本类型的变量之间

对于二元运算符(*/+-),变量的基本类型必须是浮点或者整数。
而且,像乘这样的运算符可以在浮点、向量和矩阵之间进行运算。

下面是一些例子:

float myFloat;
vec4 myVec4;
mat4 myMat4;

// Multipies each component of myVec4 by a scalar myFloat
myVec4 = myVec4 * myFloat;

// Multipies each component of myVec4 together (e.g., myVec4 ^ 2)
myVec4 = myVec4 * myVec4;

// Does a matrix * vector multiply of  myMat4 * myVec4
myVec4 = myMat4 * myVec4;

// Does a matrix * matrix multiply of  myMat4 * myMat4
myMat4 = myMat4 * myMat4;

// Multipies each matrix component by the scalar myFloat
myMat4 = myMat4 * myFloat;


除了 ==!= 之外,比较运算符( < , <= , >, >=) 只能用于标量值。要比较向量,可以使用内建函数,逐个分量进行比较。

未完待续

后面的内容下一篇博客 【我的OpenGL学习进阶之旅】OpenGL ES 着色语言 (下) 再介绍!

以上是关于我的OpenGL学习进阶之旅OpenGL ES 着色语言 (上)的主要内容,如果未能解决你的问题,请参考以下文章

我的OpenGL学习进阶之旅学习OpenGL ES 3.0 的实战 Awsome Demo (中)

我的OpenGL学习进阶之旅学习OpenGL ES 3.0 的实战 Awsome Demo (中)

我的OpenGL学习进阶之旅学习OpenGL ES 3.0 的实战 Awsome Demo (上)

我的OpenGL学习进阶之旅学习OpenGL ES 3.0 的实战 Awsome Demo (上)

我的OpenGL学习进阶之旅 OpenGL ES 实现不停旋转图片,并保证图片不变形的效果

我的OpenGL学习进阶之旅 OpenGL ES 实现不停旋转图片,并保证图片不变形的效果