GLSL之变量类型

前言:

GLSL是OpenGL Shading Language的缩写,意为OpenGL着色器语言。在学习着色器语言之前,首先要了解OpenGL的渲染管线(网上很多资料),这里不作赘述,本文将会详细介绍glsl中的各种变量的类型以及变量基本操作。

一、使用glsl构建着色器

GLSL具备了C++和Java的很多语言特性,这些特性后面会提到。创建一个着色器程序很简单,如下所示:

#version 330 core

void main(){
    //在此编写着色器代码
}

这就是一个着色器的基础结构,但是它是一个空的,所以接下来就要开始介绍变量类型。

二、基础类型 

1.标量类型(scalars):最基础的数据类型,主要有五种

float : 32为浮点数,浮点数字面量必须包含一个小数点,最好添加一个“f”后缀;

double:64位浮点数,必须在后面添加“lF”或“LF”后缀;

int:有符号32位整数;

uint:无符号32为整数,要在后面添加“u”后缀;

bool:布尔值。

2.向量和矩阵(vectors and matrices): 向量和矩阵由标量类型聚合而成

vecn: 由单精度浮点类型组成的向量

dvecn: 由双精度浮点类型组成的向量

ivecn: 由有符号整型组成的向量

uvecn: 由无符号整型组成的向量

bvecn: 由布尔类型组成的向量

其中n为2,3,4。

向量的访问有两种方式:使用分量访问符或者数组访问方式,这里主要介绍一下分量访问符的使用。

分量访问符一共有三种类型,在一个变量中,一次只能使用一种类型的访问符,也就是不能“串着用”。

                  分量访问符                         符号描述
                    (x,y,z,w)         与位置相关的分量
                    (r,g,b,a)         与颜色相关的分量
                    (s,t,p,q)         与纹理坐标相关的分量

swizzling: 分量访问符的一种主要应用,可以使用分量访问符去访问向量各个位置的数据,如下:

vec2 p;
vec4 pos=p.xyxy;

它可以使用得到的分量进行“任意搭配”,这也是它被称作swizzle的原因。

矩阵:所有的矩阵类型都是浮点类型,要么是单精度,要么是双精度,(在OpenGL4.0以后,双精度的矩阵使用dmat来声明)。

matnxm:代表一个n列m行的矩阵,n和m都为2,3,4

matn:代表一个n行n列的矩阵,n为2,3,4

注意:OpenGL uses column-major matrices,OpenGL使用的是列主序(DX使用的是行主序),如果对列主序和行主序不清楚最好找点资料看一下,其主要影响的是数据在内存中的排列方式。

Opaque types:不透明类型,Opaque types represent some external object which the shader references in some fashion. 

不透明类型代表着色器以某种方式引用的一些外部对象,主要包括采样器、图像以及原子计数器,这些内容之后会详细介绍。

之前的标量类型以及向量矩阵都是透明的,它们的内部形式都是暴露出来的。

隐式类型转换(implicit conversion)

glsl比C++更加注重类型安全,因此它支持更少的隐式类型转换,如下所示。

所需类型 可以从这些类型隐式转换
uint int 
float int ,uint 
double int,uint,float

除此之外,其它所有的数值转换都需要提供显式的转换构造函数(explicit conversion construtor),每种构造函数可以传入多重其它类型的值进行显式转换,这也体现了glsl的另一特性:函数重载。

三、数组和结构体

数组:和C/C++中的使用是类似的,它支持任意类型的数组,包括结构体数组,数组数组(即二维数组)以及不透明类型的数组

同时,glsl与java类似,它有一个隐式的方法可以返回元素的个数:即取长度的方法length(),使用length可以获得一个编译时常量,前提是该数组在length方法前就已经确定。

结构体:结构体的定义是不支持匿名的,所以必须要指定名称,而且不可以在一个结构体的内部定义一个新结构体,但是可以使用结构体类型变量作为成员,它还可以包含不透明类型的变量,注意:结构体不能用于输入和输出变量

如果需要引用结构体中的某个元素,可以直接使用“.”符号。

四、构造函数和初始化

绝大多数的变量在声明的同时必须进行初始化,但是有一下三种情况时例外的:

第一种:被in和out限定符修饰的变量(关于OpenGL中的限定符之后会专门讲述);

第二种:所有不透明类型的变量

第三种:在接口块中声明的变量

1.标量的初始化是使用一个字面量,并且要根据不同的类型有选择的加上不同的后缀名。

2.构造函数:比较复杂的一些基本类型,类似于向量和矩阵,需要使用构造函数,所有构造函数的基本形式都源于

typename(value1,value2,...)

typename就是要构造的变量的名称,而value则可以为一个字面量,一个预定义变量或者一个构造函数。

3.向量构造函数

//vec3所有的三个值都从1.0构造而来
vec3(1.0)=vec3(1.0,1.0,1.0);
//vec4的构造函数值可以是字面量或其它构造函数
vec4(1.0,2.0,3.0,2.0)=vec3(1.0,vec2(2.0,3.0),2.0);

4.矩阵构造函数

如果一个对角矩阵使用一个单个的字面量的值来构造的话,这个值将用来初始化对角线的值,剩余的值都为0,因此mat4(1.0)就是一个4x4的单位矩阵。如果使用多个值来构造,它将会按照列主序进行初始化。

如果构造函数使用的是另一个矩阵A,那么就会将A的值拷贝到矩阵的对应位置上,剩余的则使用单位矩阵来进行填充。

mat3 matrixA=mat3(3.0);
//matrixB的对角线的前三个值为3.0,最后一个值为1.0
mat4 matrixB=mat4(matrixA);

5.数组构造函数

float array[3]=float[3](1.2,2.4,3.5);
float array[]=float[](1.2,2.4,3.5);

数组的大小不是必须指定的,它可以根据构造函数中的参数来确定。

6.结构体构造函数

struct Data{
    float first
    vec2 sec;
};
Data data1=Data(1.0,vec2(2.0,2.2));
Data dataArrays[3]=Data[3](Data(1.0,vec2(2.0,2.1)),
                           Data(2.0,vec2(3.0,2.3)),
                           Data(3.0,vec2(4.0,2.5)));

7.初始化列表(initializer lists)

考虑上面dataArrays的初始化操作,显得有些冗余,因为我们要进行三次构造函数的重复工作,如果使用初始化列表,

Data dataArrays[3]={{1.0,{2.0,2.1}},
                    {2.0,{3.0,2.3}},
                    {3.0,{4.0,2.5}}};

这样就不会重复使用Data的构造函数,而编译器则会依据Data的结构体类型来正确推断数据的类型,以此进行初始化工作。

初始化列表使用“{}”作为分界符。

五、算术操作符

操作符所对应的类型必须是相同的,并且对于向量和矩阵而言,操作符的操作对象的维度也必须相同。在glsl中,大部分操作符都是经过重载的,也就是它们可以用来处理多种类型的数据。

需要注意的是,如果要进行向量和矩阵之间的乘法或者矩阵与矩阵之间的乘法,要注意矩阵和向量的维度必须是匹配的,而且要注意操作数顺序,因为矩阵乘法不满足交换律。

注意:在glsl中,使用操作符进行两个向量的相乘,得到的结果是一个逐分量相乘的向量,如果想要进行点乘和叉乘,则需要使用相应的函数。

你可能感兴趣的:(OpenGL)