# GLSL ES 概述

GLSL ES 变成语言是在 OpenGL 着色器语言(GLSL)的基础上,删除和简化一部分功能后形成的。GLSL ES 的目标平台是消费电子产品或嵌入式设备;因此简化 GLSL ES 能够允许硬件厂商对这些设备的硬件进行简化,由此带来的好处是降低了硬件的功耗,以及更重要的,减少了性能开销。

# 基础

编写规范:

  • 程序是大小写敏感的;
  • 每一个语句都应该以一个英文分号结束;

# 执行次序

从 main() 函数开始执行,着色器程序必须有且仅有一个 main() 函数,而且该函数不能接收任何参数。main() 函数前面的 void 关键字表示这个函数不返回任何值。

# 注释

在着色器程序中,可以添加注释,而且注释的格式和 JavaScript 中的注释格式是相同的:

  • 单行注释:// 后直到换行处的所有字符为注释
int kep // 注释
  • 多行注释:/* 和 */ 之间的所有字符为注释

# 数据值类型(数值和布尔值)

GLSL 支持两种数据值类型

  • 数值类型:GLSL ES 支持整型数和浮点数,没有小数点的值被认为是整型数,而有小数点的值被称为浮点数。
  • 布尔值类型:包含 true 和 false

# 变量

组合规则:

  • 只包括 a-z、A-Z,0-9 和下划线。
  • 变量名的首字母不能是数字。
  • 不能以 gl_、webgl_ 或 webgl 开头,这些前缀是 OpenGL ES 保留字
  • 不能是下表所列出的关键字,也不能是那些保留字。但是,你的变量名的一部分可以是他们。

avatar

avatar

# GLSL ES 是强类型语言

GLSL ES 不像 JavaScript,他必须具体地指明变量的数据类型。

# 基本类型

GLSL ES 支持的基本类型如下:

类型 描述
float 单精度浮点数类型,该类型的变量表示一个单精度浮点数
int 整型数,该类型的变量表示一个整数
bool 布尔值,该类型的变量表示一个布尔值

下面代码是声明基本类型的变量例子:

float klimt; // 变量为一个浮点数
int utrillo; // 变量为一个整型数
bool doga; // 变量为一个布尔值

# 赋值和类型转换

使用等号可以进行赋值,两侧类型需要一致:

int i = 8;
float f1= 9; // 错误
float f2 = 9.0; // 没问题

要将一个整型数值赋值给浮点型变量,需要将整型数转换为浮点数,这个过程称为类型转换,比如,可以使用内置的函数 float() 来将整型数转换为浮点数:

int i = 8;
float f1= float(i); // 将 8 转换为 8.0 并赋值给 f1

GLSL ES 支持下面几种类型转换的内置函数: avatar

# 运算符

avatar

# 矢量和矩阵

GLSL ES 支持矢量和矩阵类型,这两种数据类型很适合用来处理计算机图形。矢量和矩阵类型的变量都包含多个元素,每个元素是一个数值(整型数、浮点数或布尔值)。矢量将这些元素排成一列,可以用来表示顶点坐标或颜色值等,而矩阵则将元素划分成行和列,可以用来表示变换矩阵。图给出了矢量和矩阵的例子。

avatar

GLSL ES 支持多种不同的矢量和矩阵类型,

类别 GLSL ES 数据类型 描述
矢量 vec2、vec3、vec4
ivec2、ivec3、ivec4
bvec2、bvec3、bvec4
具有 2、3、4 个浮点数元素的矢量
具有 2、3、4 个整行数元素的矢量
具有 2、3、4 个布尔值元素的矢量
矩阵 mat2、mat3、mat4 2 × 2、3 × 3、4 × 4 的浮点数元素的矩阵(分别具有4、9、16 个元素)

# 赋值和构造

同样适用等号来对矢量和矩阵进行赋值操作。通常我们使用与数据类型同名的内置构造函数来生成变量,对于 vec4 类型来说,就可以使用内置的 vec4() 函数。如下:

vec4 position = new vec4(1.0, 2.0, 3.0, 4.0);

# 矢量构造函数

在 GLSL ES 中,矢量非常重要,所以 GLSL ES 提供了丰富灵活的方式来创建矢量如:

vec3 v3 = vec3(1.0, 0.0, 0.5); // 将 v3 设为(1.0, 0.0, 0.5)
vec2 v2 = vec2(v3); // 使用 v3 的前两个元素,将 v2 设为 (1.0, 0.0)
vec4 v4 = vec4(1.0); // 将 v4 设为 (1.0, 1.0, 1.0, 1.0)

如果构造函数接收了不止一个参数,但是参数的个数又比矢量的元素个数少,就会出错:

也可以将多个矢量组合成一个矢量:

vec4 v4 = vec4(v2, v4); // 将 v4 设为 (1.0, 0.0, 1.0, 1.0)

上面的填充规则是,先把第一个参数 v2 的所有元素填充进去,如果还未填满,就继续用第 2 个参数 v4 中的元素填充。

# 矩阵构造函数

矩阵构造函数的使用方式与矢量构造函数的使用方式类似。但是,要保证存储在矩阵中的元素是按照列主序排列的,如下结果例子:

  • 向矩阵构造函数中传入矩阵的每一个元素的数值类构造矩阵,注意传入值的顺序必须是列主序的。
vec4 m4 = mat4(1.0, 2.0, 3.0, 4.0,
               5.0, 6.0, 7.0, 8.0,
               9.0, 10.0, 11.0, 12.0,
               13.0, 14.0, 15.0, 16.0);

avatar

  • 向矩阵构造函数中传入一个或多个矢量,按照列主序使用矢量里的元素值来构造矩阵。
// 使用两个 vec2 对象来创建 mat2 对象
vec2 v2_1 = vec2(1.0, 3.0);
vec2 v2_2 = vec2(2.0, 4.0);
mat2 m2_1 = mat2(v2_1, v2_2); // 1.0, 2.0
                             // 3.0, 4.0
// 使用一个 vec4 对象来创建 mat2 对象
vec4 v4 = vec4(1.0, 3.0, 2.0, 4.0);
mat2 m2_2 = mat2(v4); // 1.0, 2.0
                     // 3.0, 4.0
  • 向矩阵构造函数中传入矢量和数值,按照列主序使用矢量里的元素和直接传入的数值类构造矩阵。
mat2 m2 = mat2(1.0, 3.0, v2_2); // 1.0, 2.0
                                // 3.0, 4.0
  • 向矩阵构造函数中传入单个数值,这样将生成一个对角线上元素都是该数值,其他元素为 0.0 的矩阵。
mat4 m4 = mat4(1.0); // 1.0, 0.0, 0.0, 0.0
                    // 0.0, 1.0, 0.0, 0.0
                    // 0.0, 0.0, 1.0, 0.0
                    // 0.0, 1.0, 0.0, 1.0

如果传入的数值数量大于 1,又没有达到矩阵元素的数量,就会出错。

# 访问元素

访问矢量或矩阵中的元素,可以使用 . 或者 [] 运算符。

# 运算符

在矢量变量名后接点运算符,然后接上分量名,就可以访问矢量的元素了。矢量的分量名如下:

类别 描述
x,y,z,w 用来获取顶点坐标分量
r,g,b,a 用来获取颜色分量
s,t,p,g 用来获取纹理坐标分量

由于矢量可以用来存储顶点的坐标、颜色和纹理坐标,所以 GLSL ES 支持以上三种分量名称以增强程序的可读性。事实上,任何矢量的 x、r 或 s 分量都会返回第1个分量 y、g、t 分量都返回第2个分量,等等。如果你愿意,你可以随意地交换使用它们。比如:

vec3 v3 = vec3(1,0,2.0,3.0); // 将V3设为(1,0,2.0,3.0)
float f:

f = v3,x; // 设 f 为1,0
f = v3.y; // 设 f 为2,0
f = v3.z; // 设 f 为3.0

f = v3.r // 设 f 为1,0
f = v3.s; // 设 f 为1.0

如你所见,在这些例子中 x、r 和 s 虽然名称不同,但访问的却都是第1个分量。如果试图访问超过矢量长度的分量,就会出错:

f = v3.w; // v3 变量中不存在的第4个元素,w 无法访问

将(同一个集合的)多个分量名共同置于点运算符后,就可以从矢量中同时抽取出多个分量。这个过程称作混合( swizzling)。在下面这个例子中,我们使用了 x、y、z 和 w,其他的集合也有相同的效果:

vec2 v2
v2 = v3.xy; // 设 v2 为(1,0, 2.0)
v2 = V3.yz; // 设 v2 为(2.0, 3,0) 可以省略任意分量
v2 = v3.xz; // 设 v2 为(1.0, 3.0) 可以跳过任意分量
v2 = v3.yx; // 设 v2 为(2.0, 2.0) 可以逆序
v2 = v3.xx; // 设 v2 为(1.0, 1.0) 可以重复任意分量

vec3 v3a;
v3a = v3.zyx; // 设 v3a 为(3.0, 2.0, 1.0) 可以使用所有分量

聚合分量名也可以用来作为赋值表达式 (=) 的左值:

vec4 = position = vec4(1.0, 2.0, 3.0, 4.0);
position.xw = vec2(5.0, 6.0); // position = (5.0, 2.0, 3.0, 6.0)

记住,此时的多个分量名必须属于同一个集合,比如说,你不能使用 v3.was

# [] 运算符

除了点运算符,可以通过 [] 运算符通过数组下标来访问矢量或矩阵的元素。矩阵中的元素仍然是可以按照列主序读取的。注意下标是从 0 开始的。

mat4 m4 = mat4(1.0, 2.0, 3.0, 4.0,
               5.0, 6.0, 7.0, 8.0,
               9.0, 10.0, 11.0, 12.0,
               13.0, 14.0, 15.0, 16.0);
vec4 v4 = m4[0]; // 获取 m4 矩阵的第一列,即 [1.0, 5.0, 9.0, 13.0]

使用两个 [],获取某列的某个元素:

float m23 = m4[1][2]; // 获取 m4 矩阵的第 2 列,第 3 个元素,即 (10.0)

可以同时使用 [] 运算符和分量名来访问矩阵中的元素:

float m23 = m4[2].y; // 获取 m4 矩阵的第 3 列,第 2 个元素,即 (7.0)

需要注意的是,[] 中的索引值必须是常量索引值,常量索引值定义如下:

  • 整型字面量:0 或 1;
  • 用 const 修饰的全局变量或局部变量,不包括函数参数;
  • 循环索引;
  • 由前述三条中的项组成的表达式;

例子:

const int index = 0 // const 关键字表示变量是只读的
vec4 v4a = m4[index] // 同 m4[0]
// const 组成的表达式
vec4 v4b = m4[index + 1]

# 运算符

下表显示了矢量和矩阵所支持的运算。注意,对于矢量和矩阵,只可以使用比较运算符中的 == 和 != ,不可以使用 > 、<、>=、<=,可以使用内置函数,比如 lessThan() 来比较矢量和矩阵的大小。

avatar

当运算赋值操作作用于矢量或矩阵时,实际上是逐分量地对矩阵或矢量的每一个元素进行独立的运算赋值。

# 矩阵右乘矢量

矩阵右乘矢量的结果是矢量,其中每个分量都是原矢量中的对应分量,乘上矩阵对应行的每个元素的积的加和。这里要把矢量当做 3 行 1 列的矩阵,矩阵的 3 × 3 的矩阵乘以 3 × 1 的矩阵,就是 3 × 1 矢量。

v3b = m3a * v3a 
// v3b.x = m3a[0].x * v3a.x + m3a[1].x * v3a.y + m3a[2].x * v3a.z
// v3b.y = m3a[0].y * v3a.x + m3a[1].y * v3a.y + m3a[2].y * v3a.z
// v3b.z = m3a[0].z * v3a.x + m3a[1].z * v3a.y + m3a[2].z * v3a.z

# 矩阵左乘矢量

矩阵左乘也是可以,但是结果与右乘不同,可以将矢量想象成 1 × 3 的矩阵,乘以 3 × 3 的矩阵,就是 1 × 3 的矢量。

v3b = v3a * m3a  
// v3b.x = v3a.x * m3a[0].x + v3a.y * m3a[0].y + v3a.z * m3a[0].z
// v3b.y = v3a.x * m3a[1].x + v3a.y * m3a[1].y + v3a.z * m3a[1].z
// v3b.z = v3a.x * m3a[2].x + v3a.y * m3a[2].y + v3a.z * m3a[2].z

# 矩阵与矩阵相乘

就是线性代数中的矩阵相乘:

m3c = m3a * m3b  
// m3c[0].x = m3a[0].x * m3b[0].x + m3a[1].x * m3b[0].y + m3a[2].x * m3b[0].z
// m3c[1].x = m3a[0].y * m3b[0].x + m3a[1].x * m3b[0].y + m3a[2].x * m3b[0].z
// m3c[2].x = m3a[0].x * m3b[0].x + m3a[1].x * m3b[0].y + m3a[2].x * m3b[0].z

// m3c[0].y = m3a[0].y * m3b[0].x + m3a[1].y * m3b[0].y + m3a[2].y * m3b[0].z
// m3c[1].y = m3a[0].y * m3b[0].x + m3a[1].y * m3b[0].y + m3a[2].y * m3b[0].z
// m3c[2].y = m3a[0].y * m3b[0].x + m3a[1].y * m3b[0].y + m3a[2].y * m3b[0].z

// m3c[0].z = m3a[0].z * m3b[0].x + m3a[1].z * m3b[0].y + m3a[2].z * m3b[0].z
// m3c[1].z = m3a[0].z * m3b[0].x + m3a[1].z * m3b[0].y + m3a[2].z * m3b[0].z
// m3c[2].z = m3a[0].z * m3b[0].x + m3a[1].z * m3b[0].y + m3a[2].z * m3b[0].z

# 结构体

GLSL ES 支持用于自定义的类型,也就是结构体。使用关键字 struct,将已存在的类型聚合在一起,就可以定义为结构体:

// 定义了结构体类型 light
struct light {
  vec4 color;
  vec3 position;
}
// 声明了 light 类型的变量 l1 和 l2
light l1, l2;

此外,为了方便,可以在同一条语句中定义结构体并声明改结构体类型的变量,如下所示:

// 定义了结构体类型 light
struct light {
  vec4 color; // 光的颜色
  vec3 position; // 光源位置
} l1; // 声明了 light 类型的变量 l1

# 赋值和构造

结构体有标准的构造函数,与结构体名称一致,如前面定义的结构体函数,使用方法如下:

l1 = light(vec4(0.0, 1.0, 0.0, 1.0), vec3(8.0, 3.0, 0.0))

# 访问成员

跟 JavaScript 对象访问属性是一样的。如下:

vec4 color = l1.color
vec3 position = l1.position

# 运算符

结构体的成员可以参与其自身类型支持的任何运算,但是结构体本身只支持两种运算:赋值(=)和比较(== 和 !=):

运算符 运算 描述
= 赋值 赋值和比较运算符不适用于含有数组与纹理成员的结构体
==,!= 比较 赋值和比较运算符不适用于含有数组与纹理成员的结构体

当且仅当两个结构体变量所对应的所有成员都相等时,== 运算符才会返回 true。

# 数组

GLSL ES 支持数组类型,并且只支持一维数组,没有与 JavaScript 一样有数组的一些操作方法;创建数组的方法也很简单,不需要 new 运算符。如下:

float floatArray[4] // 声明含有 4 个浮点数元素的数组
vec4 vec4Array[2] // 声明含有 2 个 vec4 对象的数组

[] 里面是数组的长度,数组的长度必须是大于 0 的整型常量表达式。

int size = 4
vec4 vec4Array[size] // 错误,如果第一行为 const int size = 4,则不会报错

数组元素可以通过索引值来访问,索引值是从 0 开始的。只有整型常量表达式和 uniform 变量,可以被用作数组的索引值。数组不能在声明时被一次性地初始化,而必须显示地对每个元素进行初始化。如下:

vec4Array[0] = vec4(4.0, 3.0, 6.0, 1.0)
vec4Array[1] = vec4(4.0, 3.0, 6.0, 1.0)

数组只支持 [] 运算,但数组的元素可以参与其自身类型支持的任意运算:

// 将 floatArry 的第 2 个元素乘以 3.14
float f = floatArray[1] * 3.14

# 取样器(纹理)

GLSL ES 支持一种内置的类型:取样器;我们必须通过该类型变量访问纹理。有两种基本的取样器类型:sampler2D 和 samplerCube。取样器变量只能是 uniform 变量,或者需要访问纹理的函数,如 texture2D() 函数。比如:

uniform sampler2D u_Sampler;

下表是纹理查询相关函数: avatar

此外,唯一能赋值给取样器变量的就是纹理单元编号,而且必须使用 WebGL 方法 gl.uniformli() 来进行赋值。比如我们前面在纹理的例子中,使用 gl.uniformli(u_Sample, 0) 将纹理单元编号 0 传给着色器。

除了 =、== 和 !=,取样器变量不可以作为操作数参与运算。

取样器类型变量受到着色器支持的纹理单元的最大数量限制,如下表,mediump 是一个精度限定字。

着色器 表示最大数量的内置常量 最小数量
顶点着色去 const mediump int gl_MaxVertexTextureImageUnits 0
片元着色器 const mediump int gl_MaxTextureImageUnits 8

# 运算符优先级

如下表: avatar

# 程序流程控制:分支和循环

着色器中的分支和循环跟 JavaScript 中的几乎一样。

# if 语句和 if-else 语句

例子:

if (distance < 0.5) {
  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0) // 红色
} else {
  gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0) // 绿色
}

# for 语句

例子:

for (int i = 0; i < 3; i++) {
  sum += i
}

需要注意的是,循环变量,也就是上面的 i 只能在舒适化表达式中定义,也就是 for 的第一个参数。for 语句有下面一些限制:

  • 只允许有一个循环变量,循环变量只能是 int 或 float 类型;
  • 循环表达式必须是以下的形式:i++, i--, i+= 常量表达式或 i-= 常量表达式
  • 条件表达式必须是循环变量与整型变量的比较;
  • 在循环体内,循环变量 i 不可被赋值。

# continue、break 和 discard 语句

只能在 for 语句中使用 continue 和 break,通常将他们与 if 语句搭配使用;

  • continue 终止包含该语句的最内层循环和执行循环表达式(递增 / 递减循环变量),然后执行下一次循环。
  • break 终止包含该语句的最内存循环,并不再继续执行循环。
for(int i = 0; i < 10; i ++) {
  if (i == 8) {
    continue; // 跳过循环体后面的代码,继续下次循环
  }
  // 当 i == 8 时,不会执行这里
}

break 例子:

for(int i = 0; i < 10; i ++) {
  if (i == 8) {
    break; // 跳出 for 循环
  }
  // 当 i > 8 时,不会执行这里
}
// 当 i = 8 时,执行这里

discard 他只能在片元着色器中使用,表示放弃当前片元直接处理下一个片元。

# 函数

如果函数没有返回值,函数不需要有 return 语句,并且返回类型必须是 void。也可以将自己定义的结构体类型指定为返回类型,但是结构体的成员中不能有数组。

例子:实现将 RGBA 颜色值转换为亮度值

float luma(vec4 color) {
  float r = color.r;
  float g = color.g;
  float b = color.b;
  return 0.2126 * r + 0.7126 * g + 0.0722 * b;
}

attribute vec4 a_Color;
void main() {
  float brightness = luma(a_Color)
}

不能递归调用。

# 规范声明

如果函数定义在其调用之后,需要先声明函数的规范,会犯声明会预先告诉 webgl 系统函数的参数、参数类型、返回值等等。如下:

float luma(vec4) // 规范声明
attribute vec4 a_Color;
void main() {
  float brightness = luma(a_Color) // 在定义之前就被调用了
}

float luma(vec4 color) {
  float r = color.r;
  float g = color.g;
  float b = color.b;
  return 0.2126 * r + 0.7126 * g + 0.0722 * b;
}

# 参数限定词

在 GLSL ES 中,可以为函数参数指定限定词,以控制参数的行为。可以将函数参数定义为:(1) 传递给函数的,(2) 将要在函数中被赋值的,(3) 既是传递给函数的,也是将要在函数中被赋值的。

avatar

例子:

void luma(in vec3 color, out float brightness) {
  brightness = 0.2126 * color.r
}
// 调用函数
luma(color, brightness) // 函数结果存储在 brightness 中
// 和 brightness = luma(color) 效果一样

修改之后,函数本身不返回值,所以函数的返回类型设置为 void,in 限定词是参数默认的限定词,可以省略。

# 内置函数

如下表:

avatar

函数中定义的变量只能在函数中使用,为局部变量,在函数外定义的变量就是全局变量。

# 存储限定字

使用 attribute、varying 和 uniform 限定字来修改变量,

avatar avatar

# const 变量

在声明 const 变量时,需要将 const 写在类型之前,声明同时必须对它进行初始化,声明之后就不能修改他的值。

const int lightspeed = 233343

# Attribute 变量

attribute 变量只能出现在顶点着色器中,只能被声明为全局变量,被用来表示逐顶点的信息。表示逐顶点的含义是,比如线段有两个顶点,这两个坐标就会传递给 attribute 变量。而线段上的其他点,比如中点,虽然也被绘制出来了,但他不是顶点,并没有传递给 attribute 变量。

attribute 变量的类型只能是 float、vec2、vec3、vec4、mat2、mat3 和 mat4,如下:

attribute vec4 a_Color;
attribute float a_PointSize;

顶点着色器中能够容纳的 attribute 变量的最大数目与设备有关,可以通过访问内置的全局变量来获取该值,但是,不管设置配置如何,支持 webgl 的环境都支持至少 8 个 attribute 变量。

变量类别 内置全局变量(表示最大数量) 最小值
attribute 变量 const mediump int gl_MaxVertexAttribs 8
uniform 变量(顶点着色器) const mediump int gl_MaxVertexUniformVectors 128
uniform 变量(片元着色器) const mediump int gl_MaxFragmentUniformVectors 16
varying 变量 const mediump int gl_MaxVaryingVectors 8

# uniform 变量

uniform 变量可以用在顶点着色器和片元着色器中,且必须是全局变量。uniform 变量是只读的,他可以是除了数组或结构体之外的任意类型。如果顶点着色器和片元着色器中声明了同名的 uniform 变量,那么它就会被两种着色器共享。uniform 包含了一致的数据,JavaScript 应该向其传递此类数据。比如变换矩阵就不是逐顶点的,而是所有顶点共用的,所以在着色器中是 uniform 变量。

uniform mat4 u_ViewMatrix;

# varying 变量

varying 变量必须是全局变量,他的任务是从顶点着色器向片元着色器传输数据。两种着色器必须声明同名、同类型的 varying 变量。

varying vec2 v_TexCoord;

varying 变量的类型只能是 float、vec2、vec3、vec4、mat2、mat3 和 mat4;顶点着色器中赋给 varying 变量的值并不是直接传给了片元着色器的 varying 变量,这其中发生了光栅化的过程:根据绘制的图形,对前者(顶点着色器 varying 变量)进行内插,然后再传递给片元着色器的 varying 变量,这是因为 varying 变量需要被内插。所以需要限制他的数据类型。

# 精度限定字

精度限定字目的是帮助着色器程序调高运行效率,消减内存开支。精度限定字用来表示每种数据具有的精度。简而言之,高精度的程序需要更大的开销(包括更大的内存和更久的计算时间),而低精度的程序需要的开销则小的多。使用精度限定字,能够精细地控制程序在效果和性能间的平衡。精度限定字是可选的,可以使用下面这个适中的默认值:

精度限定 : 片元着色器中 float 没有默认精度,所以需要手动提前指定;

#ifdef GL_ES
precision mediump float;
#endif

在低精度下,webgl 程序的运行结果会比较粗糙或不明确。

avatar

在某些 webgl 环境中,片元着色器可能不支持 highp 精度,数值范围和精度实际上也是与系统环境相关的,可以使用 gl.getShaderPrecisionFormat() 检查。

mediump float sizel; // 中精度的浮点型变量
highp vec4 position; // 具有高精度的浮点型元素 vec4 变量
lowp vec4 color; // 具有低精度的浮点型元素 vec4 变量

为每个变量都声明精度很繁琐,可以使用关键字 precision 来声明着色器的默认精度,下面这行代码必须在顶点着色器或片元着色器的顶部:

格式如下:precision 精度限定字 类型名称;

这句代码表示,在着色器中,某种类型的变量其默认精度由精度限定字指定。也就是说,接下来不以精度限定字修饰的该类型变量,其精度就是默认精度。比如:

precision mediump float; // 所有浮点数默认为中精度
precision highp int; // 所有整型数默认为高精度

上面代码表示,所有 float 类型以及相关的 vec2 和 mat3 的变量都是中精度,所有整型变量都是高精度的。

对于大部分的类型,着色器已经实现了默认的精度,只有片元着色器中的 float 类型没有默认精度,如下:

avatar

如果不在片元着色器中限定 float 类型的精度,就会导致编译错误。

# 预处理指令

GLSL ES 支持预处理指令,预处理指令用来在真正编译之前对代码进行预处理,都以 # 开始,如下:

# ifdef GL_ES
precision mediump float;
#endif

这段代码检查了是否已经定义了 GL_ES 宏,如果是,那就执行 #ifdef 和 #endif 之间的部分。

下面是我们在 GLSL ES 中可能用到的三种预处理指令:

#if 表达式
If 如果条件表达式为真,执行这里
#endif

#ifdef 自定义宏
如果定义了宏,执行这里
#endif

#ifndef 自定义宏
如果定义了宏,执行这里
#endif

可以使用 #define 指定进行宏定义。

#define 宏名 宏内容

可以使用 #undef 指令解除宏定义

#undef 宏名

可以使用 #else 指令配合 #ifdef,

#define NUM 100
#if NUM == 100
如果宏 NUM 为 100,执行这里
#else
否则,执行这里
#endif

宏的名称可以任意起,不过不要和预定的内置宏名称相同即可:

描述
GL_ES 在 opengl es 2.0 中定义为 1
GL_FRAGMENT_PRECISION_HIGH 片元着色器支持 highp 精度

可以这样使用宏来进行精度限定:

#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float; // 支持高精度,限定浮点型为高精度
#else
precision mediump float; // 不支持高精度,限定浮点数为中精度
#endif
#endif

可以使用 #version 来指定着色器使用的 GLSL ES 版本,可以接受的版本包括 100(GLSL ES 1.00)和 101(GL_SL ES 1.01),如果不使用 #version 指令,着色器将默认 版本为 1.00,指定 1.01 版本的代码如下:

#version 101

#version 指令必须在着色器的顶部,在他之前只能有注释和空白。

评 论:

更新: 3/4/2021, 1:00:21 AM