OpenGL 第五个实例 glsl语言的应用

2021-10-20  本文已影响0人  Alexander

glsl是一个以C语言为基础的高阶着色器语言, 通过编写glsl提供开发和对绘图管线更多的直接控制,不再需要使用汇编语言或者其他硬件规格的语言.

GLSL中提供了许多内建的函数,来方便我们的使用。可以在官方手册中查找相关的函数

GLSL官方手册

![OpenGL流程] image.png

常用修饰符

常用的内置变量

例如: 下面的顶点着色器


attribute vec4 position;
attrubute vec2 textCoordinate;

uniform mat4 rotateMatrix;
varying lowp vec2 varTextCoord;

vodi main() {
    
    varTextCoord = textCoordinate;
    vec4 vPods = position;
    vPods = vPods * rotateMatrix;
    gl_Position = vPods;
}

GLSL 语言

GLSL中提供了许多内建的函数,来方便我们的使用。可以在官方手册中查找相关的函数GLSL官方手册

注释

单行注释://
多行注释:/**/

变量

GLSL的变量命名方式和C语言一样,变量名称可以使用数字、下划线、字母组成。但是不能以数字开头。需要注意的是GLSL的变量并不能以gl_作为前缀,原因是其官方保留了这个前缀作为GLSL的内部变量。

基本类型

GLSL中除了整型、布尔类型、浮点类型外,它还引入了一些着色器用到的数据基本类型,他们同样能作为结构体累不的类型,具体看下图。

数据类型 类型描述
void 跟C语言的void类似,表示空类型,也可作为函数的返回值,表示的是无返回值返回
bool 布尔类型,返回的是true或者false的表达式
int 整型
folat 浮点类型
bvecn 表示包含n个bool类型的分量,eg:bvec2、bvec3、bvec4
ivecn 表示包含n个int类型的分量,eg:ivec2、ivec3、ivec4
mat2、mat2x2 2x2的浮点矩阵类型
mat3、mat3x3 3x3的浮点矩阵类型
mat4x4 4x4的浮点矩阵类型
matnxm nxm的浮点矩阵类型,eg:mat2x3(2列3行的矩阵)、mat3x4(3列4行的浮点矩阵)。OpenGL的矩阵是列主顺序的
sampler1D 用于内奸的纹理函数中引用指定的1D纹理句柄,只可以作为一致变量或者函数中使用
sampler2D 2维纹理句柄
sampler3D 3维纹理句柄
samplerCube cube map纹理句柄
sampler1DShadow 一维深度纹理句柄
sampler2DShadow 二维深度纹理句柄

结构体

结构体可以组合基本数据类型和数组来自定义结构体,在定义一个结构体的同时,也可以定义一个结构体实例给它,或者后面再定义也是可以的。

结构体中至少要包含一个成员变量,固定大小的数组也是可以为成员的,GLSL语言中是不允许结构体嵌套的,但是如果结构体已经预先声明的话,也是可以嵌套到定义的结构体中。

struct velocity {
    
    vec3 direction;
}velo;

数组

在GLSL中只能定义一维数组,数组的类型可以是GLSL中的任何基本数据类型,我们也可以在定义数组的是时候指定显示大小的数组,这个数组可以是参数也可以是返回值。

    vec4 lightPositions[8];
    vec4 lightPos[] = lightPositions;

获取数组长度大小可以用length,例如:lightPositions.length // 打印结果为8

修饰符

修饰符 修饰符描述
const 常量值修饰符,必须在声明时初始化,只读属性,不可写
attribute 表示只读的顶点数据,只用在顶点着色器中,数据来自当前的顶点状态或者顶点数组,它修饰的属性必须是全局范围声明的属性,也就是说不能在函数内部,一个attribute修饰的属性可以是浮点类型的标量、向量、或者矩阵。重点是:attribute不能修饰数组和结构体
uniform 一致变量,在着色器执行期间一致变量的值是不变的,与const常量不同的是,这个值在编译时期是未知的且有着色器外部进行初始化,一致变量在顶点着色器和片段着色器之间数据是共享的,所以它修饰的属性只能是全局范围进行声明的属性值。
varying 顶点着色器的输出,例如纹理坐标和颜色,(插值后的数据)作为片段着色器的只读输入数据,同样必须是全局范文声明的全局变量,可以是浮点类型的标量、向量、举证,但同样不能修饰数组和结构体
centoridvarying 在没有多重采样的情况下,与varying的特性是一样的,而在多重采样时,centoridvarying在光栅化的图形内部进行求值,而不是在片段中心的固定位置求值。
invariant 用于表示定点着色器的输出和任何匹配片段着色器的输入,在不同的着色器中计算产生的值必须是一致的,所有的控制流和数据流,写入一个invariant变量也是一致的。编译器为了保证结果是完全一致,需要放弃那些可能会导致不一致值的潜在优化,所以,除非必要,不要使用这个修饰符,在多通道渲染中必变z-fighting时可能会被用到。
in 用在函数的参数中,目的是表示函数时输入的,在函数中改变这个值,并不会影响对调用函数,相当于是C语言中的传值,是一个默认修饰符。
out 用在函数中,修饰的参数表示是输出参数,值是会改变的
inout 用在函数的参数,表示这个蚕食既是输入参数也是输出参数

内置变量

内置变量可以与固定的函数功能进行交互,使用签不需要声明

顶点着色器可用的内置变量。
内置变量名称 类型 描述
gl_Color vec4 输入属性, 表示顶点的主颜色
gl_SeconddaryColor vec4 输入属性,表示的是顶点的辅助颜色
gl_Normal vec3 输入属性,表示顶点的法线值
gl_Vertex vec4 输入属性,表示物体空间的顶点位置
gl_MultiTexColor vec4 输入属性,表示的是顶点的第n个纹理的坐标
gl_FogCoord float 输入属性,表示顶点的雾坐标
gl_Position vec4 输出属性,变换后的顶点位置,用于后面的裁剪等操作,所有的顶点着色器都必须写这个值
gl_ClipVertex vec4 输出坐标,用于用户裁剪平面的裁剪
gl_PointSize float 点的大小
gl_FrontColor vec4 正面的主颜色的varying输出
gl_BackColor vec4 背面主颜色的varying的输出
gl_FrontSecondaryColor vec4 正面的辅助颜色的varying的输出
gl_BackSecondaryColor vec4 背面的辅助颜色的varying
gl_TexCoord[] vec4 纹理坐标的数组varying输出
gl_FogFragCoord float 雾坐标的varying输出
片段着色器的内置变量
内置变量名称 类型 描述
gl_Color vec4 包含主颜色的插值,只读输入属性
gl_SecondarycOLOR vec4 包含辅助颜色的插值,只读输入属性
gl_TexCoord[] vec4 包含纹理坐标数组的插值,只读输入属性
gl_FogFragCoord float 包含了雾坐标的插值,只读输入属性
gl_FragCoord float 只读输入属性,表示窗口的x、y、z和1/w
gl_FrontFaciong bool 只读输入属性,如果是窗口正面图元的一部分,则这个值为true
gl_PointCoord vec2 点精灵的二维空间坐标范围在(0.0, 0.0)到(1.0, 1.0)之间,仅用于点图元和点精灵开启的情况下。
gl_FragData[] vec4 使用glDrawBuffers输出的数据数组。不能与gl_FragColor结合使用。
gl_FragColor vec4 输出的颜色用于随后的像素操作
gl_FragDepth float 输出的深度用于随后的像素操作,如果这个值没有被写,则使用固定功能管线的深度值代替

表达式

GLSL语言的操作符与C语言相似,优先级如下表从高到底排列

操作符(优先级: ) 描述
() 用于表达式组合,函数的调用,构造
[] 数组下标,向量或矩阵选择器
. 结构体和向量的成员选择(点语法)
++ - 前缀或后缀的自增自减的操作符
"+" "-" ! 一元操作符,表示正、负、逻辑非(有双引号是因为对应操作符在markdown中是特使符号)
*/ 乘、除操作符
”+“、”-“ 二元操作符, 表示加减操作
<> <= >= == != 小于,大于,小于等于, 大于等于,等于,不等于 判断符
&& 逻辑与
?: 条件判断符
= += –= *= /= 赋值操作符
, 表示序列

数组访问

GLSL数组下表同样也是从0开始, 其范围是在[0, length-1],若数组越界,就会被系统定义为行为未定义, 当然,如果着色器的编译器在编译时识别数组越界,就会提示编译失败。

vec4 myColor, ambient, didduse[6], specular[6];

myColor = ambient + didduse[4] + specular[4];

构造函数

构造函数可以用于初始化包含多个成员的变量、数组、结构体,构造函数也可以使用在表达式中


vec3 myNormal = vec3(1.0, 1.0, 1.0);
greenTint = myColor + vec3(0.0, 1.0, 0.0);
ivec4 myColor = ivec4(255);

成分选择

向量中单独的成分是可以通过{x,y,z,w}、{r,g,b,a}或者{s,t,p,q}的记法来表示,这些不同的记法用于顶点、颜色、纹理坐标。在成分选择中还可以使用特殊方式,比如重复使用向量中的元素、颠倒顺序等, 但是如果是在赋值时,就不能重复使用。

// 调换顺序
vec3 yxz = myVec.yxz;

// 重复使用
vec4 mySstt = myVec.sstt;

// 在赋值时, 可以自主选择想要的顺序,但是不能重复成分
vec4 myColor = {0.0, 1.0, 2.0, 1.0};
myColor.x = -0.1;
myColor.yz = vec2(3.0, 5.0);
myColor.wx = vec2(1.0, 3.0);

// 不合法的使用
myColor.zz = vec2(2.0, 3.0);

我们可以使用下标来访问向量矩阵中的元素,如果越界,那么当前行为就会被定义为未定义的行为事件。

循环

同C语言和C++语言一样,GLSL语言也提供了for、while、do/while的循环方式,continue则继续下一次循环,break这停止并结束当前循环。

for (l = 0, l < macCount, l++) {
    if (!arr[l])
    continue;
    a += arr[l];
}
while(i < maxCount) {
    sum += ary[i];
    i++;
}

控制流

if (a > 0) {
a = c;
} else {
a =b;
}

discard

在片段着色器中一种特殊的控制流即:discard。在片段着色器中使用discard会退出片段着色器,不会再继续执行片段着色器,片段也不会写入帧缓冲区中。

if (color.a < 0.9)
discard; // 退出当前片段着色器,不会再执行之后的的相关渲染工作

main

每个shader中必须要有一个main()函数,main
函数中的void参数是可选的,但如果返回值是void时,那么就是必须的。

void main(void) {
    ....
}

GLSL中的函数,必须是全局范围定义和声明的,不能在函数定义中国声明或者定义函数,函数必须要有返回值类型,但参数是可选的,参数可以是intoutinoutconst等。


// 声明函数

// 函数声明
bool isAnyNegative(const vec4 v) {
    
    bool isNegative = isAnyNegative(gl_Color);
}

// 定义函数
bool isAnyNegative(const vec4 v) {
    
    if (v.x < 0.0 || v.y < 0.0 || v.z < 0.0 || v.w < 0.0) {
        return true;
    } else {
        return false;
    }
}

在GLSL中函数名称是可以重名的,只需要参数个数或者类型不同即可

> float min(float a, float b) {
  return a+b;
}

vec3 min(vec3 v1, vec3 v2) {
  return v1+v2;
}

运算符

运算符(优先级: ) 运算符 说明
1 () 聚组:a*(b+c)
2 []、()、++、-- []是数组下标;()是函数调用:fun(arg1, arg2);. 则是链式访问,比如myStruct.a;++或者--则表示自增和自减。
3 ++、--、+、—、! ++自增,--自减,正负号(一般正号不写)a ,-a;!a表示取反
4 *、/ 乘或除运算符
5 +、- 加减数学运算符
6 < > <= >= 关系运算符
7 == != 相等性运算符
8 && 逻辑与
9 ^^ 逻辑排他或(用处基本等于!=)
10 II 逻辑或
11 ? : 三目运算符
12 = += -= *= /= 赋值与复合赋值
13 , 顺序分配符

glsl中是没有隐式类型转换的,所有在glsl中要求的是任何表达式的左右值类型必须是一致的。

总结

在图形管道中GLSL是可直接执行OpenGL的着色语言的,着色器中最常用的两种着色器:顶点着色器(该着色器是将形状转换到真实的3D坐标系中)和片元着色器(主要是计算最终渲染的颜色和其他属性)。

顶点着色器回顾

顶点着色器的操作是3D空间的坐标并且每个顶点都会调用一次这个函数,其目的是设置gl_Position变量,这个变量是一个特殊的全局内置变量,是用来存储当前顶点的位置;


void main() {

    gl_Position = ...;
}

这个void main()函数时定义全局的gl_Position变量的标准方式,所有在这个函数中代码都会被着色器执行,比如:将3D空间中的位置投射在2D屏幕上时,这些信息do偶会被保存在计算结果的变量中。

顶点着色器的内置变量:
gl_Position:顶点着色器的裁剪空间输出位置向量,只要是要在屏幕上显示,就需要在顶点着色器红设置gl_Position
gl_pointSize : 比如它其中一个图元是 GL_POINTS,说明每一个顶点都是一个图元,都会被渲染成一个点,我们可以同OpenGL中的glPointSize函数来设置渲染出来点的大小,也可以在顶点着色器中修改这个值。换句话说,它能对每个顶点设置不同的值,用来设置像素的size,是个float类型。
gl_VertexIDgl_Positiongl_PointSize都是输出变量。因为他们的值是作为顶点着色器的输出被外界读取,我们可以对他们进行写入,从而改变结果,而gl_VertexID就可以用来进行读取操作。

片段着色器回顾

片段(或纹理)着色器在计算时定义了一个像素的rgba颜色值,每个像素只会调用一次片段着色器,这个着色器的作用是设置gl_FragCoord,这个变量同样属于内置变量,它的z分量等于对应片段着色器的深度值。

gl_FragCoord的x分量和y分量记录的是当前片段在屏幕空间桌表系统下的x、y坐标。

void main() {
    
    // 计算的结果就包含了rgba的颜色信息
    gl_FragCoord = ...;
}
上一篇下一篇

猜你喜欢

热点阅读