可用的数据类型只有4种:有符号整数,无符号整数,浮点数,布尔值。 OpenGL着色语言中没有指针和字符串或字符。返回值可以为void。
所有4种基本数据类型都可以存储在二维、三维或者四维向量中:
OpenGL着色语言向量数据类型
vec2,vec3,vec4
2分量、3分量和4分量浮点向量
ivec2,ivec3,ivec4
2分量、3分量和4分量整数向量
uvec2,uvec3,uvec4
2分量、3分量和4分量无符号整数向量
bvec2,vbec3,bvec4
2分量、3分量和4分量布尔向量
矩阵类型只支持浮点数
OpenGL着色语言矩阵数据类型
mat2,mat2x2
两行两列
mat3,mat3x3
三行三列
mat4,mat4x4
四行四列
mat2x3
三行两列
mat2x4
四行两列
mat3x2
两行三列
mat3x4
四行三列
mat4x2
两行四列
mat4x3
三行四列
限定符用于将变量标记为输入变量、输出变量或常量。
变量存储限定符
const
一个编译时常量,或者说是一个队函数来说为只读的参数
in
一个从以前的截断传递过来的变量
in
centroid 一个从以前的截断传递过来的变量,使用质心插值
out
传递到下一个处理阶段或者在一个函数中指定一个返回值
out
centroid 传递到下一个处理阶段,使用质心插值
inout
一个读/写变量,只能用于局部函数参数
uniform
一个从客户端代码传递过来的变量,在顶点之间不做改变
属性是每个顶点位置、表面法线和纹理坐标等都需要的,而统一值则用于为整个图元批次向保持不变的(统一(uniform)的)着色器传递数据。 创建一个统一值只需在变量声明开始时放置一个uniform关键词:
main 函数
最终的像素颜色取决于预设的全局变量 gl_FragColor。
vec4 四分量浮点向量 vec4(1.0,0.0,1.0,1.0), 四个变元分别响应红,绿,蓝和透明度通道
vec3 三分量浮点向量
vec2 二分量浮点向量
它们的数据类型通常为:float, vec2, vec3, vec4, mat2, mat3, mat4, sampler2D and samplerCube。
uniform 值需要数值类型前后一致。且在 shader 的开头,在设定精度之后,就对其进行定义。
就像 GLSL 有个默认输出值 vec4 gl_FragColor 一样,它也有一个默认输入值( vec4 gl_FragCoord )。
gl_FragCoord存储了活动线程正在处理的像素或屏幕碎片的坐标。有了它我们就知道了屏幕上的哪一个线程正在运转。
为什么我们不叫 gl_FragCoord uniform (统一值)呢?因为每个像素的坐标都不同,所以我们把它叫做 varying
(变化值)。
pow()
- 求x的y次幂
exp()
- 以自然常数e为底的指数函数
log()
- 对数函数
sqrt()
- 平方根函数
abs()
- 绝对值
ceil()
- 向正无穷取整
floor()
- 向负无穷取整
fract()
- 只选取小数部分
clamp(x,0.0,1.0)
把 x 的值限制在 0.0 到 1.0
Step 和 Smoothstep
step()
- 插值函数需要输入两个参数。第一个是极限或阀值,第二个是我们想要检测或通过的值。对任何小于阀值的值,返回 0.0,大于阀值,则返回 1.0。
smoothstep()
- 当给定一个范围的上下限和一个数值,这个函数会在已有的范围内给出插值。前两个参数规定转换的开始和结束点,第三个是给出一个值用来插值。
在GLSL中,有个十分有用的函数:mix()
mix()
函数:以百分比混合两个值。
百分比的取值范围:0~1
fract()
函数: 返回小数点后的数。
void
空类型,即不返回任何值
bool
布尔类型 true,false
int
带符号的整数 signed integer
float
带符号的浮点数 floating scalar
vec2, vec3, vec4
n维浮点数向量 n-component floating point vector
bvec2, bvec3, bvec4
n维布尔向量 Boolean vector
ivec2, ivec3, ivec4
n维整数向量 signed integer vector
mat2, mat3, mat4
2x2, 3x3, 4x4 浮点数矩阵 float matrix
sampler2D
2D纹理 a 2D texture
samplerCube
盒纹理 cube mapped texture
结构
struct type-name{} 类似c语言中的 结构体
数组
float foo[3] glsl只支持1维数组,数组可以是结构体的成员
glsl中的向量(vec2,vec3,vec4)往往有特殊的含义,比如可能代表了一个空间坐标(x,y,z,w),或者代表了一个颜色(r,g,b,a),再或者代表一个纹理坐标(s,t,p,q) 所以glsl提供了一些更人性化的分量访问方式.
vector.xyzw
其中xyzw 可以任意组合
vector.rgba
其中rgba 可以任意组合
vector.stpq
其中rgba 可以任意组合
1
()
聚组:a*(b+c)
N/A
2
[] () . ++ --
数组下标__[],方法参数__fun(arg1,arg2,arg3),属性访问__a.b__,自增/减后缀__a++ a--__
L - R
3
++ -- + - !
自增/减前缀__++a --a__,正负号(一般正号不写)a ,-a,取反__!false__
R - L
4
* /
乘除数学运算
L - R
5
+ -
加减数学运算
L - R
7
< > <= >=
关系运算符
L - R
8
== !=
相等性运算符
L - R
12
&&
逻辑与
L - R
13
^^
逻辑排他或(用处基本等于!=)
L - R
14
||
逻辑或
L - R
15
? :
三目运算符
L - R
16
= += -= *= /=
赋值与复合赋值
L - R
17
,
顺序分配运算
L - R
ps 左值与右值:
glsl中,没有隐式类型转换,原则上glsl要求任何表达式左右两侧(l-value),(r-value)的类型必须一致 也就是说以下表达式都是错误的:
下面来分别说说可能遇到的情况:
1.float
与 int
:
float与float , int与int之间是可以直接运算的,但float与int不行.它们需要进行一次显示转换.即要么把float转成int: int(1.0) ,要么把int转成float: float(1) ,以下表达式都是正确的:
2.float
与 vec(向量)
mat(矩阵)
:
vec,mat这些类型其实是由float复合而成的,当它们与float运算时,其实就是在每一个分量上分别与float进行运算,这就是所谓的逐分量
运算.glsl里 大部分涉及vec,mat的运算都是逐分量
运算,但也并不全是. 下文中就会讲到特例.
逐分量
运算是线性的,这就是说 vec 与 float 的运算结果是还是 vec.
int 与 vec,mat之间是不可运算的, 因为vec和mat中的每一个分量都是 float 类型的. 无法与int进行逐分量计算.
下面枚举了几种 float 与 vec,mat 运算的情况
3. vec(向量)
与 vec(向量)
:
两向量间的运算首先要保证操作数的阶数都相同.否则不能计算.例如: vec3*vec2 vec4+vec3 等等都是不行的.
它们的计算方式是两操作数在同位置上的分量分别进行运算,其本质还是逐分量进行的,这和上面所说的float类型的 逐分量运算可能有一点点差异,相同的是 vec 与 vec 运算结果还是 vec, 且阶数不变.
3. vec(向量)
与 mat(矩阵)
:
要保证操作数的阶数相同,且vec与mat间只存在乘法运算.
它们的计算方式和线性代数中的矩阵乘法相同,不是逐分量运算.
向量与矩阵的乘法规则如下:
4. mat(矩阵)
与 mat(矩阵)
:
要保证操作数的阶数相同.
在mat与mat的运算中, 除了乘法是线性代数中的矩阵乘法外.其余的运算任为逐分量运算.简单说就是只有乘法是特殊的,其余都和vec与vec运算类似.
矩阵乘法规则如下:
none
(默认的可省略)本地变量,可读可写,函数的输入参数既是这种类型
const
声明变量或函数的参数为只读类型
attribute
只能存在于vertex shader中,一般用于保存顶点或法线数据,它可以在数据缓冲区中读取数据
uniform
在运行时shader无法改变uniform变量, 一般用来放置程序传递给shader的变换矩阵,材质,光照参数等等.
varying
主要负责在vertex 和 fragment 之间传递变量
const:
和C语言类似,被const限定符修饰的变量初始化后不可变,除了局部变量,函数参数也可以使用const修饰符.但要注意的是结构变量可以用const修饰, 但结构中的字段不行.
const变量必须在声明时就初始化 const vec3 v3 = vec3(0.,0.,0.)
局部变量只能使用const限定符.
函数参数只能使用const限定符.
attribute:
attribute变量是全局
且只读
的,它只能在vertex shader中使用,只能与浮点数,向量或矩阵变量组合, 一般attribute变量用来放置程序传递来的模型顶点,法线,颜色,纹理等数据它可以访问数据缓冲区 (还记得__gl.vertexAttribPointer__这个函数吧)
uniform:
uniform变量是全局
且只读
的,在整个shader执行完毕前其值不会改变,他可以和任意基本类型变量组合, 一般我们使用uniform变量来放置外部程序传递来的环境数据(如点光源位置,模型的变换矩阵等等) 这些数据在运行中显然是不需要被改变的.
varying:
varying类型变量是 vertex shader 与 fragment shader 之间的信使,一般我们在 vertex shader 中修改它然后在fragment shader使用它,但不能在 fragment shader中修改它.
要注意全局变量限制符只能为 const、attribute、uniform和varying中的一个.不可复合.
函数的参数默认是以拷贝的形式传递的,也就是值传递,任何传递给函数参数的变量,其值都会被复制一份,然后再交给函数内部进行处理. 我们可以为参数添加限定符来达到传递引用的目的,glsl中提供的参数限定符如下:
< none: default >
默认使用 in 限定符
in
复制到函数中在函数中可读写
out
返回时从函数中复制出来
inout
复制到函数中并在返回时复制出来
in
是函数参数的默认限定符,最终真正传入函数形参的其实是实参的一份拷贝.在函数中,修改in修饰的形参不会影响到实参变量本身.
out
它的作用是向函数外部传递新值,out模式下传递进来的参数是write-only的(可写不可读).就像是一个"坑位",坑位中的值需要函数给他赋予. 在函数中,修改out修饰的形参会影响到实参本身.
inout
inout下,形参可以被理解为是一个带值的"坑位",及可读也可写,在函数中,修改inout修饰的形参会影响到实参本身.
glsl允许在程序的最外部声明函数.函数不能嵌套,不能递归调用,且必须声明返回值类型(无返回值时声明为void) 在其他方面glsl函数与c函数非常类似.
glsl中变量可以在声明的时候初始化,float pSize = 10.0
也可以先声明然后等需要的时候在进行赋值.
聚合类型对象如(向量,矩阵,数组,结构) 需要使用其构造函数来进行初始化. vec4 color = vec4(0.0, 1.0, 0.0, 1.0);
glsl可以使用构造函数进行显式类型转换,各值如下:
glsl在进行光栅化着色的时候,会产生大量的浮点数运算,这些运算可能是当前设备所不能承受的,所以glsl提供了3种浮点数精度,我们可以根据不同的设备来使用合适的精度.
在变量前面加上 highp
mediump
lowp
即可完成对该变量的精度声明.
我们一般在片元着色器(fragment shader)最开始的地方加上 precision mediump float;
便设定了默认的精度.这样所有没有显式表明精度的变量 都会按照设定好的默认精度来处理.
如何确定精度:
变量的精度首先是由精度限定符决定的,如果没有精度限定符,则要寻找其右侧表达式中,已经确定精度的变量,一旦找到,那么整个表达式都将在该精度下运行.如果找到多个, 则选择精度较高的那种,如果一个都找不到,则使用默认或更大的精度类型.
invariant关键字:
由于shader在编译时会进行一些内部优化,可能会导致同样的运算在不同shader里结果不一定精确相等.这会引起一些问题,尤其是vertx shader向fragmeng shader传值的时候. 所以我们需要使用invariant
关键字来显式要求计算结果必须精确一致. 当然我们也可使用 #pragma STDGL invariant(all)
来命令所有输出变量必须精确一致, 但这样会限制编译器优化程度,降低性能.
限定符的顺序:
当需要用到多个限定符的时候要遵循以下顺序:
1.在一般变量中: invariant > storage > precision
2.在参数中: storage > parameter > precision
我们来举例说明:
以 # 开头的是预编译指令,常用的有:
比如 #version 100 他的意思是规定当前shader使用 GLSL ES 1.00标准进行编译,如果使用这条预编译指令,则他必须出现在程序的最开始位置.
内置的宏:
__LINE__
: 当前源码中的行号.
__VERSION__
: 一个整数,指示当前的glsl版本 比如 100 ps: 100 = v1.00
GL_ES
: 如果当前是在 OPGL ES 环境中运行则 GL_ES 被设置成1,一般用来检查当前环境是不是 OPENGL ES.
GL_FRAGMENT_PRECISION_HIGH
: 如果当前系统glsl的片元着色器支持高浮点精度,则设置为1.一般用于检查着色器精度.
实例:
1.如何通过判断系统环境,来选择合适的精度:
2.自定义宏:
glsl程序使用一些特殊的内置变量与硬件进行沟通.他们大致分成两种 一种是 input
类型,他负责向硬件(渲染管线)发送数据. 另一种是output
类型,负责向程序回传数据,以便编程时需要.
在 vertex Shader 中:
output 类型的内置变量:
highp vec4 gl_Position
;
gl_Position 放置顶点坐标信息
vec4
mediump float gl_PointSize
;
gl_PointSize 需要绘制点的大小,(只在gl.POINTS模式下有效)
float
在 fragment Shader 中:
input 类型的内置变量:
mediump vec4 gl_FragCoord
;
片元在framebuffer画面的相对位置
vec4
bool gl_FrontFacing
;
标志当前图元是不是正面图元的一部分
bool
mediump vec2 gl_PointCoord
;
经过插值计算后的纹理坐标,点的范围是0.0到1.0
vec2
output 类型的内置变量:
mediump vec4 gl_FragColor
;
设置当前片点的颜色
vec4 RGBA color
mediump vec4 gl_FragData[n]
设置当前片点的颜色,使用glDrawBuffers数据数组
vec4 RGBA color
glsl提供了一些内置的常量,用来说明当前系统的一些特性. 有时我们需要针对这些特性,对shader程序进行优化,让程序兼容度更好.
在 vertex Shader 中:
1.const mediump int gl_MaxVertexAttribs
>=8
gl_MaxVertexAttribs 表示在vertex shader(顶点着色器)中可用的最大attributes数.这个值的大小取决于 OpenGL ES 在某设备上的具体实现, 不过最低不能小于 8 个.
2.const mediump int gl_MaxVertexUniformVectors
>= 128
gl_MaxVertexUniformVectors 表示在vertex shader(顶点着色器)中可用的最大uniform vectors数. 这个值的大小取决于 OpenGL ES 在某设备上的具体实现, 不过最低不能小于 128 个.
3.const mediump int gl_MaxVaryingVectors
>= 8
gl_MaxVaryingVectors 表示在vertex shader(顶点着色器)中可用的最大varying vectors数. 这个值的大小取决于 OpenGL ES 在某设备上的具体实现, 不过最低不能小于 8 个.
4.const mediump int gl_MaxVertexTextureImageUnits
>= 0
gl_MaxVaryingVectors 表示在vertex shader(顶点着色器)中可用的最大纹理单元数(贴图). 这个值的大小取决于 OpenGL ES 在某设备上的具体实现, 甚至可以一个都没有(无法获取顶点纹理)
5.const mediump int gl_MaxCombinedTextureImageUnits
>= 8
gl_MaxVaryingVectors 表示在 vertex Shader和fragment Shader总共最多支持多少个纹理单元. 这个值的大小取决于 OpenGL ES 在某设备上的具体实现, 不过最低不能小于 8 个.
在 fragment Shader 中:
1.const mediump int gl_MaxTextureImageUnits
>= 8
gl_MaxVaryingVectors 表示在 fragment Shader(片元着色器)中能访问的最大纹理单元数,这个值的大小取决于 OpenGL ES 在某设备上的具体实现, 不过最低不能小于 8 个.
2.const mediump int gl_MaxFragmentUniformVectors
>= 16
gl_MaxFragmentUniformVectors 表示在 fragment Shader(片元着色器)中可用的最大uniform vectors数,这个值的大小取决于 OpenGL ES 在某设备上的具体实现, 不过最低不能小于 16 个.
3.const mediump int gl_MaxDrawBuffers
= 1
gl_MaxDrawBuffers 表示可用的drawBuffers数,在OpenGL ES 2.0中这个值为1, 在将来的版本可能会有所变化.
glsl中还有一种内置的uniform状态变量, gl_DepthRange
它用来表明全局深度范围.
结构如下:
除了 gl_DepthRange 外的所有uniform状态常量都已在glsl 1.30 中废弃
.
glsl的流控制和c语言非常相似,这里不必再做过多说明,唯一不同的是片段着色器中有一种特殊的控制流discard
. 使用discard会退出片段着色器,不执行后面的片段着色操作。片段也不会写入帧缓冲区。
glsl提供了非常丰富的函数库,供我们使用,这些功能都是非常有用且会经常用到的. 这些函数按功能区分大改可以分成7类:
通用函数:
下文中的 类型 T可以是 float, vec2, vec3, vec4,且可以逐分量操作.
T abs(T x)
返回x的绝对值
T sign(T x)
比较x与0的值,大于,等于,小于 分别返回 1.0 ,0.0,-1.0
T floor(T x)
返回<=x的最大整数
T ceil(T x)
返回>=等于x的最小整数
T fract(T x)
获取x的小数部分
T mod(T x, T y) T mod(T x, float y)
取x,y的余数
T min(T x, T y) T min(T x, float y)
取x,y的最小值
T max(T x, T y) T max(T x, float y)
取x,y的最大值
T clamp(T x, T minVal, T maxVal) T clamp(T x, float minVal,float maxVal)
min(max(x, minVal), maxVal),返回值被限定在 minVal,maxVal之间
T mix(T x, T y, T a) T mix(T x, T y, float a)
取x,y的线性混合,x*(1-a)+y*a
T step(T edge, T x) T step(float edge, T x)
如果 x<edge 返回 0.0 否则返回1.0
T smoothstep(T edge0, T edge1, T x) T smoothstep(float edge0,float edge1, T x)
如果x<edge0 返回 0.0 如果x>edge1返回1.0, 否则返回Hermite插值
角度&三角函数:
下文中的 类型 T可以是 float, vec2, vec3, vec4,且可以逐分量操作.
T radians(T degrees)
角度转弧度
T degrees(T radians)
弧度转角度
T sin(T angle)
正弦函数,角度是弧度
T cos(T angle)
余弦函数,角度是弧度
T tan(T angle)
正切函数,角度是弧度
T asin(T x)
反正弦函数,返回值是弧度
T acos(T x)
反余弦函数,返回值是弧度
T atan(T y, T x) T atan(T y_over_x)
反正切函数,返回值是弧度
指数函数:
下文中的 类型 T可以是 float, vec2, vec3, vec4,且可以逐分量操作.
T pow(T x, T y)
返回x的y次幂 xy
T exp(T x)
返回x的自然指数幂 ex
T log(T x)
返回x的自然对数 ln
T exp2(T x)
返回2的x次幂 2x
T log2(T x)
返回2为底的对数 log2
T sqrt(T x)
开根号 √x
T inversesqrt(T x)
先开根号,在取倒数,就是 1/√x
几何函数:
下文中的 类型 T可以是 float, vec2, vec3, vec4,且可以逐分量操作.
float length(T x)
返回矢量x的长度
float distance(T p0, T p1)
返回p0 p1两点的距离
float dot(T x, T y)
返回x y的点积
vec3 cross(vec3 x, vec3 y)
返回x y的叉积
T normalize(T x)
对x进行归一化,保持向量方向不变但长度变为1
T faceforward(T N, T I, T Nref)
根据 矢量 N 与Nref 调整法向量
T reflect(T I, T N)
返回 I - 2 * dot(N,I) * N, 结果是入射矢量 I 关于法向量N的 镜面反射矢量
T refract(T I, T N, float eta)
返回入射矢量I关于法向量N的折射矢量,折射率为eta
矩阵函数:
mat可以为任意类型矩阵.
mat matrixCompMult(mat x, mat y)
将矩阵 x 和 y的元素逐分量相乘
向量函数:
下文中的 类型 T可以是 vec2, vec3, vec4, 且可以逐分量操作.
bvec指的是由bool类型组成的一个向量:
bvec lessThan(T x, T y)
逐分量比较x < y,将结果写入bvec对应位置
bvec lessThanEqual(T x, T y)
逐分量比较 x <= y,将结果写入bvec对应位置
bvec greaterThan(T x, T y)
逐分量比较 x > y,将结果写入bvec对应位置
bvec greaterThanEqual(T x, T y)
逐分量比较 x >= y,将结果写入bvec对应位置
bvec equal(T x, T y) bvec equal(bvec x, bvec y)
逐分量比较 x == y,将结果写入bvec对应位置
bvec notEqual(T x, T y) bvec notEqual(bvec x, bvec y)
逐分量比较 x!= y,将结果写入bvec对应位置
bool any(bvec x)
如果x的任意一个分量是true,则结果为true
bool all(bvec x)
如果x的所有分量是true,则结果为true
bvec not(bvec x)
bool矢量的逐分量取反
纹理查询函数:
图像纹理有两种 一种是平面2d纹理,另一种是盒纹理,针对不同的纹理类型有不同访问方法.
纹理查询的最终目的是从sampler中提取指定坐标的颜色信息. 函数中带有Cube字样的是指 需要传入盒状纹理. 带有Proj字样的是指带投影的版本.
以下函数只在vertex shader中可用:
以下函数只在fragment shader中可用:
在 vertex shader 与 fragment shader 中都可用:
下面的shader如果你可以一眼看懂,说明你已经对glsl语言基本掌握了.
Vertex Shader:
Fragment Shader: