New程序媛OpenGL全解析之—GLSL语法基础

大家好!

New程序媛OpenGL全解析之—GLSL语法基础

本期丹丹带给大家的是GLSL语法基础New程序媛OpenGL全解析之—GLSL语法基础

本期因为用文字即可描述清楚所以没有相关的视频文件。

大家也可以直接在bi站首页搜索:New程序媛 ,即可看到往期相应视频New程序媛OpenGL全解析之—GLSL语法基础


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语法基础部分的内容,涉及到的具体应用,我们后面的推文中再给大家做详尽的介绍。


整理码文也是一项辛苦的工作,希望大家自习阅读后,对后期的继续学习有帮助New程序媛OpenGL全解析之—GLSL语法基础


希望小伙伴们多多捧场,添加关注并介绍给身边的小伙伴哦New程序媛OpenGL全解析之—GLSL语法基础丹丹也期待大家的意见和建议,欢迎小伙伴们积极留言New程序媛OpenGL全解析之—GLSL语法基础