OpenGL学习(十)-- 着色语言 GLSL 语法介绍

个人 OpenGL 专题学习目录,但愿和你们一块儿学习交流进步!html


1、简介

GLSLOpenGL Shading Language) 全称 OpenGL 着色语言,是用来在 OpenGL 中着色编程的语言,也即开发人员写的短小的自定义程序,他们是在图形卡的 GPU上执行的,代替了固定的渲染管线的一部分,使渲染管线中不一样层次具备可编程性。 GLSL 其使用 C 语言做为基础高阶着色语言,避免了使用汇编语言或硬件规格语言的复杂性。编程

2、变量命名

GLSL 的变量命名方式与 C 语言相似,可以使用字母,数字以及下划线,不能以数字开头。还须要注意的是,变量名不能以 gl_ 做为前缀,这个是 GLSL 保留的前缀,用于 GLSL 的内部变量。数组

3、数据类型

一、基本数据类型

类型 描述
void C 语言的 void 相似,表示空类型。做为函数的返回类型,表示这个函数不返回值。
bool 布尔类型truefalse,以及能够产生布尔型的表达式。
int 有符号整型
uint 无符号整形
float 浮点型

二、特殊类型--纹理采样类型

类型 描述
sampler1D 用于内建的纹理函数中引用指定的 1D纹理的句柄。只能够做为一致变量或者函数参数使用
sampler2D 二维纹理句柄
sampler3D 三维纹理句柄
samplerCube cube map 纹理句柄
sampler1DShadow 一维深度纹理句柄
sampler2DShadow 二维深度纹理句柄

三、聚合类型(向量和矩阵类型)

(1)向量类型

类型 描述
vec2,vec3,vec4 2份量、3份量和4份量浮点向量
ivec2,ivec3,ivec4 2份量、3份量和4份量整数向量
uvec2,uvec3,uvec4 2份量、3份量和4份量无符号整数向量
bvec2,vbec3,bvec4 2份量、3份量和4份量布尔向量

A、􏰴􏰜􏰷􏰸向量声明--4份量的float 类型向量

vec4 V1;
复制代码

B、􏰴􏰜􏰷􏰸声明向量并对其进行构造

vec4 V2 = vec4(1,2,3,4);
复制代码

C、􏰴􏰜􏰷􏰸向量运算

vec4 v;
vec4 vOldPos = vec4(1,2,3,4);
vec4 vOffset = vec4(1,2,3,4);
//注意:接下来假设全部参与运算的变量已定义并赋值。
v = vOldPos + vOffset;
v = vNewPos;
v += vec4(10,10,10,10);
v = vOldPos * vOffset;
v *= 5;
复制代码

D、􏰴􏰜􏰷􏰸向量元素的获取(成分选择)

向量中单独的成分能够经过 {x,y,z,w}, {r,g,b,a} 或者 {s,t,p,q} 的记法来表示。这些不一样的记法用于 顶点颜色纹理坐标。在成分选择中,你不能够混合使用这些记法。其中 {s,t,p,q} 中的 p 替换了纹理r 坐标,由于与颜色 r 重复了。下面是用法举例: 例若有向量 v1v2:less

vec3 v1 = {0.5, 0.35, 0.7};
vec4 v2 = {0.1, 0.2, 0.3, 0.4};
复制代码

能够经过 {x,y,z,w}, {r,g,b,a} 或者 {s,t,p,q} 来取出向量中的元素值。 经过 x,y,z,w函数

v2.x = 3.0f;
v2.xy = vec2(3.0f,4.0f);
v2.xyz = vec3(3,0f,4,0f,5.0f);
复制代码

经过 r,g,b,apost

v2.r = 3.0f;
v2.rgba = vec4(1.0f,1.0f,1.0f,1.0f);
复制代码

经过 s,t,q,r学习

v2.stqr = vec2(1.0f, 0.0f, 0.0f, 1.0f);
复制代码

错误示例:测试

float myQ = v1.q;// 出错,数组越界访问,q表明第四个元素
float myRY = v1.ry; // 不合法,混合使用记法
复制代码

向量还支持一次性对全部份量操做优化

v1.x = v2.x +5.0f; 
v1.y = v2.y +4.0f; 
v1.z = v2.z +3.0f;
v1.xyz = v2.xyz + vec3(5.0f,4.0f,3.0f);
复制代码

(2)矩阵类型

类型 描述
mat2 或 mat2x2 2x2的浮点数矩阵类型
mat3 或 mat3x3 3x3的浮点数矩阵类型
mat4 或 mat4x4 4x4的浮点数矩阵类型
mat2x3 2列3行的浮点矩阵(OpenGL的矩阵是列主顺序的)
mat2x4 2列4行的浮点矩阵
mat3x2 3列2行的浮点矩阵
mat3x4 3列4行的浮点矩阵
mat4x2 4列2行的浮点矩阵
mat4x3 4列3行的浮点矩阵

建立矩阵:ui

mat4 m1,m2,m3;
复制代码

构造单元矩阵:

mat4 m2 = mat4(1.0f,0.0f,0.0f,0.0f
                    0.0f,1.0f,0.0f,0.0f,
                    0.0f,0.0f,1.0f,0.0f,
                    0.0f,0.0f,0.0f,1.0f);
复制代码

或者

mat4 m4 = mat4(1.0f);
复制代码

四、数组

GLSL 中只可使用一维的数组。数组的类型能够是一切基本类型或者结构体。下面的几种数组声明是合法的:

float floatArray[4];
vec4 vecArray[2];
float a[4] = float[](1.0,2.0,3.0,4.0);
vec2 c[2] = vec2[2](vec2(1.0,2.0),vec2(3.0,4.0));
复制代码

数组类型内建了一个length()函数,能够返回数组的长度。

lightPositions.length() // 返回数组的长度
复制代码

五、结构体

结构体能够组合基本类型和数组来造成用户自定义的类型。在定义一个结构体的同时,你能够定义一个结构体实例。或者后面再定义。

struct fogStruct {
 vec4 color;
 float start;
 float end;
 vec3 points[3]; // 固定大小的数组是合法的
} fogVar;
复制代码

能够经过 = 为结构体赋值,或者使用 ==,!= 来判断两个结构体是否相等。

fogVar = fogStruct(vec4(1.0,0.0,0.0,1.0),0.5,2.0);
vec4 color = fogVar.color;
float start = fogVar.start;
复制代码

3、修饰符

一、变量存储限定符

限定符 描述
(默认的可省略)只是普通的本地变量,可读可写,外部不可见,外部不可访问
const 常量值必须在声明时初始化,它是只读的不可修改的
varying 顶点着色器的输出,主要负责在 vertexfragment 之间传递变量。例如颜色或者纹理坐标,(插值后的数据)做为片断着色器的只读输入数据。必须是全局范围声明的全局变量。能够是浮点数类型的标量,向量,矩阵。不能是数组或者结构体。
uniform 一致变量。在着色器执行期间一致变量的值是不变的。与 const 常量不一样的是,这个值在编译时期是未知的是由着色器外部初始化的。一致变量在顶点着色器和片断着色器之间是共享的。它也只能在全局范围进行声明。
attribute 表示只读的顶点数据,只用在顶点着色器中。数据来自当前的顶点状态或者顶点数组。它必须是全局范围声明的,不能再函数内部。一个 attribute 能够是浮点数类型的标量,向量,或者矩阵。不能够是数组或则结构体
centorid varying 在没有多重采样的状况下,与 varying 是同样的意思。在多重采样时,centorid varying 在光栅化的图形内部进行求值而不是在片断中心的固定位置求值。
invariant (不变量)用于表示顶点着色器的输出和任何匹配片断着色器的输入,在不一样的着色器中计算产生的值必须是一致的。全部的数据流和控制流,写入一个 invariant 变量的是一致的。编译器为了保证结果是彻底一致的,须要放弃那些可能会致使不一致值的潜在的优化。除非必要,不要使用这个修饰符。在多通道渲染中避免 z-fighting 可能会使用到。

二、函数参数限定符

GLSL 容许自定义函数,但参数默认是以值形式(in 限定符)传入的,也就是说任何变量在传入时都会被拷贝一份,若想以引用方式传参,须要增长函数参数限定符。

限定符 描述
in 用在函数的参数中,表示这个参数是输入的,在函数中改变这个值,并不会影响对调用的函数产生反作用。(至关于C语言的传值),这个是函数参数默认的修饰符
out 用在函数的参数中,表示该参数是输出参数,值是会改变的。
inout 用在函数的参数,表示这个参数便是输入参数也是输出参数。

其中使用 inout 方式传递的参数便与其余 OOP 语言中的引用传递相似,参数可读写,函数内对参数的修改会影响到传入参数自己。 eg:

vec4 getPosition(out vec4 p){ 
    p = vec4(0.,0.,0.,1.);
    return v4;
}

void doubleSize(inout float size){
    size= size * 3.0  ;
}
复制代码

3、GLSL 中的运算

⚠️注意 GLSL 中没有隐式转换,即使在多维向量中也没有,相似下面这样的的赋值都是错误的:

// ⚠️错误
int a = 2.0;
vec4 v4=vec4(1.0, 1.0, 2, 1.0);
复制代码

一、不一样类型 float 与 int 间的运算:

floatint 之间进行运算,须要进行一次显示转换,如下表达式都是正确的:

int a = int(2.0);
float a = float(2);

int a = int(2.0)*2 + 1;
float a = float(2)*6.0+2.3;
复制代码

二、float 与 vec(向量)、mat(矩阵) 的运算:

  • 逐份量运算 vec,mat 这些类型实际上是由 float 复合而成的,当它们与float 运算时,其实就是在每个份量上分别与 float 进行运算,这就是所谓的 逐份量运算GLSL 里,大部分涉及 vec,mat 的运算都是逐份量运算,但也并不全是。下文中就会讲到特例。 逐份量运算 是线性的,这就是说 vec 与 float 的运算结果是仍是 vec

intvec,mat 之间是不可运算的,由于 vecmat 中的每个份量都是 float 类型的,没法与 int 进行逐份量计算。

下面枚举了几种 floatvec,mat 运算的状况:

vec3 a  = vec3(1.0, 2.0, 3.0);
mat3 m  = mat3(1.0);
float s = 10.0;

vec3 b  = s * a; // vec3(10.0, 20.0, 30.0)
vec3 c  = a * s; // vec3(10.0, 20.0, 30.0)
mat3 m2 = s * m; // = mat3(10.0)
mat3 m3 = m * s; // = mat3(10.0)
复制代码

三、vec(向量) 与 vec(向量)运算:

两向量间的运算首先要保证操做数的阶数都相同,不然不能计算。例如: vec3*vec2vec4+vec3 等等都是不行的。

它们的计算方式是两操做数在同位置上的份量分别进行运算,其本质仍是逐份量进行的,这和上面所说的 float 类型的逐份量运算可能有一点点差别,相同的是 vecvec 运算结果仍是 vec,且阶数不变。

vec3 a = vec3(1.0, 2.0, 3.0);
vec3 b = vec3(0.1, 0.2, 0.3);
vec3 c = a + b; // = vec3(1.1, 2.2, 3.3);
vec3 d = a * b; // = vec3(0.1, 0.4, 0.9);
复制代码

vec-vec.png

四、vec(向量) 与 mat(矩阵):

要保证操做数的阶数相同,且 vecmat 间只存在乘法运算。 它们的计算方式和线性代数中的矩阵乘法相同,不是逐份量运算

vec2 v = vec2(10., 20.);
mat2 m = mat2(1., 2.,  3., 4.);
vec2 w = m * v; // = vec2(1. * 10. + 3. * 20., 2. * 10. + 4. * 20.)

vec2 v = vec2(10., 20.);
mat2 m = mat2(1., 2.,  3., 4.);
vec2 w = v * m; // = vec2(1. * 10. + 2. * 20., 3. * 10. + 4. * 20.)
复制代码

五、mat(矩阵) 与 mat(矩阵):

⚠️要保证操做数的阶数相同。

matmat 的运算中,除了乘法是线性代数中的矩阵乘法外,其他的运算仍为逐份量运算。简单说就是只有乘法是特殊的,其他都和 vecvec 运算相似。

mat2 a = mat2(1., 2.,  3., 4.);
mat2 b = mat2(10., 20.,  30., 40.);
mat2 c = a * b; // mat2(1.*10.+3.*20.,2.*10.+4.*20.,1.* 30.+3.*40.,2.* 30.+4.*40.);

mat2 d = a+b;// mat2(1.+10.,2.+20.,3.+30.,4.+40);
复制代码

4、类型转换

GLSL 能够经过构造函数进行显式转换,方法以下:

bool t= true;
bool f = false;

int a = int(t); // true转换为1或1.0
int a1 = int(f);// false转换为0或0.0

float b = float(t);
float b1 = float(f);

bool c = bool(0);// 0或0.0转换为false
bool c1 = bool(1);// 非0转换为true

bool d = bool(0.0);
bool d1 = bool(1.0);
复制代码

5、精度限定

GLSL 在进行光栅化着色的时候,会产生大量的浮点数运算,这些运算多是当前设备所不能承受的,因此 GLSL 提供了 3 种浮点数精度,咱们能够根据不一样的设备来使用合适的精度。 在变量前面加上 highpmediumplowp 便可完成对该变量的精度声明:

lowp float color;
varying mediump vec2 Coord;
lowp ivec2 foo(lowp mat3);
highp mat4 m;
复制代码

咱们通常在 片元着色器(fragment shader) 最开始的地方加上 precision mediump float; 便设定了默认的精度,这样全部没有显式代表精度的变量都会按照设定好的默认精度来处理。

6、控制语句

在语法上,GLSLC 很是类似, 也有 if else、for、while、do while,使用 continue 跳入下一次循环,break 结束循环。

for (l = 0; l < numLights; l++) {
    if (!lightExists[l]);
        continue;
    color += light[l];
}

while (i < num) {
    sum += color[i];
    i++;
}

do {
    color += light[lightNum];
    lightNum--;
} while (lightNum > 0)
复制代码

除了这些,GLSL 还多了一种特殊的控制语句 discard,它会当即跳出片元着色器,并不在向下任何语句。也就不执行后面的片断着色操做,片断也不会写入帧缓冲区。

if (true)
    discard;
复制代码

⚠️注意 GLSL 函数中没有递归!

以上的总结参考了并部分摘抄了如下文章,很是感谢如下做者的分享!:

一、做者吃代码的兔子窝的《初探 GLSL 着色器(一)》

三、做者吃代码的兔子窝的《初探 GLSL 着色器(二)》

四、做者jeffasd的《glsl语言基础》

转载请备注原文出处,不得用于商业传播——凡几多

相关文章
相关标签/搜索