New程序媛OpenGL全解析之—GLSL语法基础
大家好!
本期丹丹带给大家的是GLSL语法基础
本期因为用文字即可描述清楚所以没有相关的视频文件。
大家也可以直接在bi站首页搜索:New程序媛 ,即可看到往期相应视频
OpenGL从3.1版本开始,固定功能管线从核心模式中去除,所以要求我们必须使用着色器来完成工作。而无论是 OpenGL还是其他图形API的着色器,通常都是通过一种特殊的编程语言去编写的。对于 OpenGL来说,我们使用的是 GLSL(Opengl Shading Langua)它是在OpenGL2.0版本左右发布的(在之前它属于扩展功能)。它与 OpenGL的发展是同时进行的,与每个新版本的 OpenGL一起更新。GLSL是一种专门为图形开发设计的编程语言,但它与“C”语言非常类似,也有C++的影子。
4.3版本中的图形管线有4个处理阶段,还有一个通用计算阶段,每个阶段都需要由一个专门的着色器进行控制。
1)顶点着色阶段( vertex shading stage)
2)细分着色阶段( tessellation shading stage)
3)几何着色阶段( eometry shading stage)
4)片元着色阶段(Fragment shading stage)
5)计算着色阶段( Compute shading stage)
1-4就是四个处理阶段,而5就是计算着色阶段,它与前四个阶段不同,它并不是图形管线的一部分,而是在程序中相对独立的一个阶段。计算着色阶段处理的并不是顶点和片元这类图形数据,而是计算应用程序给定范围的内容。
如下是一个典型的着色器:
#version version_numberin
type in_variable_name;
in type in_variable_name;
out type out_variable_name;
uniform type uniform_name;
int main()
{
// 处理输入并进行一些图形操作
...
// 输出处理过的结果到输出变量
out_variable_name = weird_stuff_we_processed;
}
OpenGL定义了in变量将数据拷贝到着色器中,out变量将着色器的内容拷贝出去,这些变量的值在执行着色器的时候更新,而另一类变量,uiform变量,是从应用程序中接收数据的,他们不会随着顶点或者片元的变化而发生任何改变。
首先让我们看看OpenGL变量:
命名规则:由字母数字以及下划线构成,不能以为数字或下划线开始,变量名也不可包含连续的下划线,因为这些名称是GLSL保留使用的。
基本数据类型:
float:IEEE 32位浮点数
double:IEEE 64位浮点数
int: 有符号二进制补码的32位整数
uint:无符号的32位整数
bool:布尔
支持的隐式转换:
int-->uint
float-->int uint
double-->int uint float
注意:所有变量都必须在声明的同时进行初始化。
聚合数据类型(向量和矩阵):
向量
vec2,vec3,vec4 | 2分量、3分量和4分量浮点向量 |
ivec2,ivec3,ivec4 | 2分量、3分量和4分量整数向量 |
uvec2,uvec3,uvec4 | 2分量、3分量和4分量无符号整数向量 |
bvec2,vbec3,bvec4 | 2分量、3分量和4分量布尔向量 |
矩阵
mat2,mat2x2 | 两行两列 |
mat3,mat3x3 | 三行三列 |
mat4,mat4x4 | 四行四列 |
mat2x3 | 三行两列 |
mat2x4 | 四行两列 |
mat3x2 | 两行三列 |
mat3x4 | 四行三列 |
mat4x2 | 两行四列 |
mat4x3 | 三行四列 |
结构体:
结构将一组不同类型的数据组合成一个整体,方便数据的记录和传递;一个结构体定义后会自动创建一个新的类型,并隐式定义一个构造函数,通过.成员名来访问结构中的数据。
struct Particle( float lifetime, vec3 position, vec3 velocity);
Particle p = Particle(3.0, pos, vel);
数组:
GLSL中可以定义任何类型的数组,包括结构体,从0到n-1, 但是只能是一维的,不能是二维及以上的。
数组的声明:
可以指明大小,也可以不指明大小使用的时候指明大小,数组可以作为函数参数和返回值使用。
float coeff[3];// 等价于float[3] coeff;
int coeff[];
定义时候的初始化:
float coeff[3] = float[3](2.3, 1.0, 3.0);
可以用数组的下标来访问数组元素。
数组长度可以用默认内建.length()得到n大小;
for(int i = 0; i < coeff.length(); i++){
coeff[i] *= 1.0f;
}
接着我们来看看存储限定符:
1)const 常量,如果它初始化时用的是一个编译时常量,那么它本身也会成为编译时常量
2)in 设置变量为着色器阶段的输入变量
3)out 设置变量为着色器阶段的输出变量
4)uniform 设置变量为用户应用程序传递给着色器的数据,它对于给定的图元而言是一个常量。
在着色器运行之前, uniform修饰符可以指定一个在应用程序中设置好的变量,如上所说它不会在图元处理的过程中发生变化。 uniform变量在所有可用的着色阶段之间都是共享的,它必须定义为全局变量。任何类型的变量(包括结构体和数组)都可以设置为 uniform变量。着色器无法写入到 uniform量,也无法改变它的值。着色器中可以直接引用uniform变量,而应用程序中要改变,则需要通过glGetUniformLocation()函数获取该变量在列表中的索引,然后再通过glUniform*()或者glUniformMatrix*()系列函数设置变量的值。
如下:
GLint timeloc;/*着色器中的 uniform变量time的索引*/
GLfloat timeValue;/*程序运行时间*/
timeloc = glGetUniformLocation( program,"time");
glUniformlf(timeloc, timeValue );
5)buffer 设置应用程序共享的一块可读写的内存,这块内存也作为着色器中的存储缓存使用。
与uniform类似,在应用程序中共享一大块缓存给着色器,不过不同的是,它可以用着色器对它进行内容修改,对着色器来说它是可读写的。
6)shared 设置变量是本地工作组中共享的,它只能用于计算着色器中。之后再做介绍。
接着看看运算符:
优先级(越小越高) | 运算符 | 说明 | |
---|---|---|---|
1 | () | 成组操作:a*(b+c) | |
2 |
[] f () . ++ -- |
数组下标、方法参数、属性访问、自增/减 | |
3 | ++ -- + - ! | 自增/减前缀__++a --a__,正负号(一般正号不写)a ,-a,取反 | |
4 | * / | 乘除数学运算 | |
5 | + - | 加减数学运算 | |
7 | < > <= >= | 关系运算符 | |
8 | == != | 相等性运算符 | |
12 | && | 逻辑与 | |
13 | ^^ | 逻辑排他或(用处基本等于!=) | |
14 | || | 逻辑或 | |
15 | ? : | 三目运算符 | |
16 | = += -= *= /= | 赋值与复合赋值 | |
17 | , | 顺序分配运算 |
流程控制:
if () {}else{}
switch()
{case n:
语句;
break;
}
for(int i =m 0;i<10;++i)
{}
while(){ }
do{}while();
流程控制关键字:
break
continue
return
discard 丢弃当前的片元,终止着色器的执行,该语句只能用于片元着色器中。
函数:
声明语法:
returnType functionName([accesssModifier] type1 variable1, [accesssModifier] type2 variable2, ...)
{
return returnValue; // 除非没有返回值
}
函数参数可以是任何类型,如果是数组类型要指明长度。
函数返回值可以是内置的GLSL类型或自定义的结构体和数组,如果返回值为数组,则需要显示的指定大小,如果函数没有返回值便是void。
函数不能递归,原因是一些实现真正将编译链接后的着色器程序内嵌到GPU中运行,以支持没有堆栈支持的GPU进行渲染。
参数的限制符号:
in :将数据拷贝到函数中(如果没有指定修饰符,默认这种形式)
const in:将只读数据拷贝到函数中
out:从函数中获取数值(因此输入函数的值是未定义的)
inout:将数据拷贝到函数中,并且返回函数中修改的数据
GLSL无法保证在不同的着色器中,两个完全相同的计算式会得到完全一样的结果。与CPU端应用程序进行计算时的问题相同,即不同的优化方式可能会导致结果有非常细微的差异。这些细微的差异对于多通道的算法会产生问题,因为各个着色器阶段可能需要计算得到完全一致的结果。GLSL有两种方法来确保着色器之间的计算不变性,即invariant或者precise关键字。
invariant关键字:
可以设置任何着色器的输出变量。它可以确保如果两个着色器的输出变量使用了同样的表达式,并且表达式中的变量也是相同值,那么计算产生的结果也是相同的。
例如:
invariant gl_Position;
invariant centroid out vec3 Color:
precise关键字:
可以设置任何计算中的变量或者函数的返回值。
例如:
precise gl_Position;
precise out vec3 Location;
precise vec3 subdivide(vec3 p1, vec3 P2);
可以通过
#pragma STDGL invariant(all)来设置着色器中所有变量为invariant
GLSL中的预处理器
预处理器命令:
#define
#undef
#if
#ifdef
#ifndef
#else
#elif
#endif
以上都跟C语言的预处理器命令类似
#error text 强制编译器将text文字内容(直到第一个换行符为止)插入到着色器的信息日志当中
编译器的预处理
#pragma optimize(on) 优化开启(默认是开启的)
#pragma optimize(off)
#pragma debug(on) 开启或禁止调试(默认是禁止调试)
#pragma debug(off)
#pragma STDGL invariant(all) 开启多通道渲染不变性
宏定义:跟出演的宏定义类似,但不支持字符串替换以及预编译链接符
系统预定义的宏
__LINE__ 行号,默认为已经处理的所有换行符的个数加一,也可以通过#line命令修改
__FILE__ 当前处理的源字符串编号
__VERSION__ OpenGL着色语言版本的整数表示形式
以上就是丹丹整理的GLSL语法基础部分的内容,涉及到的具体应用,我们后面的推文中再给大家做详尽的介绍。
整理码文也是一项辛苦的工作,希望大家自习阅读后,对后期的继续学习有帮助
希望小伙伴们多多捧场,添加关注并介绍给身边的小伙伴哦丹丹也期待大家的意见和建议,欢迎小伙伴们积极留言