GLSL
变量类型与声明
GLSL常见的基本数据类型为:
类型 | 描述 |
---|---|
float | IEEE32位浮点数 |
double | IEEE64位浮点数 |
int | 有符号二进制补码的32位整数 |
uint | 无符号的32位整数 |
bool | 布尔值 |
表格中的类型都是透明类型,即内部形式是暴露出来的,与之对应的是不透明类型,包括采样器(sampler)、图像(image)、原子计数器(atomic counter)。
注意,GLSL中的所有变量都必须在声明时就进行初始化。
GLSL中,变量隐式强制转换是很严格的,通常能直接进行强制转换的类型为:
所需类型 | 可以从这些类型隐式转换 |
---|---|
uint | int |
float | int、uint |
double | int、uint、float |
不过可以通过转换构造函数来显示强制转换,如int()、float()、double()、uint()、bool()以及它们的矢量vec和矩阵mat。
对于复合类型,GLSL的支持为:
基本类型 | 2D矢量 | 3D矢量 | 4D矢量 | 矩阵类型 |
---|---|---|---|---|
float | vec2 | vec3 | vec4 | mat(2,3,4)...mat(4x2,4x3,4x4) |
double | dvec2 | dvec3 | dvec4 | 上行加前缀d |
int | ivec2 | ivec3 | ivec4 | 无 |
uint | uvec2 | uvec3 | uvec4 | 无 |
bool | bvec2 | bvec3 | bvec4 | 无 |
对于复合类型,GLSL可以使用分量访问,支持分量:
符号访问符 | 符号描述 |
---|---|
x、y、z、w | 位置相关 |
r、g、b、a | 颜色相关 |
s、t、p、q | 纹理坐标相关 |
也可以使用数组形式访问,使用[ ]和[ ][ ]。
GLSL可以声明结构体:
struct Particle{
float lifetime;
vec3 position;
vec3 velocity;
};
用法和C语言类似。
GLSL还支持任意类型的数组,其用法和C语言类似。比如:
float coeff[3];
float[3] coeff;
int indices[];
还可以使用构造函数:
float coeff[3] = float[3](2.38, 3.14, 42.0)
数组有一个内置方法length(),即数组长度,该方法矢量和矩阵也可以使用,分别对应矢量分量数和矩阵列数。
对于变量,GLSL有一些限制符:
类型修饰符 | 描述 |
---|---|
const | 变量定义为只读形式 |
in | 设置为输入变量 |
out | 设置为输出变量 |
uniform | 设置为统一变量,通过应用程序传递给着色器 |
buffer | 设置应用程序共享的一块可读写的内存,这块内存也可作为着色器中的存储缓存 |
shared | 设置变量是本地工作组中共享的,只能用于计算着色器 |
对于uniform变量,在应用程序中使用下面的方法传递值:
GLint glGetUniformLocation(GLuint program, const char* name);
该方法返回程序中着色器变量name对应的索引值。
然后使用下面的方法根据位置值传递变量:
void glUniform{1234}{fdi ui}(GLint location, TYPE value);
void glUniform{1234}{fdi ui}(GLint location, GLsizei count, const TYPE* values);
void glUniformMatrix{234}{fd}v(GLint location, GLsizei count, GLboolean tranpose,
const GLfloat* values);
void glUniformMatrix{2x3, 2x4, 3x2,3x4,4x2,4x3}{fd}v(GLint location, GLsizei count,
GLboolean tranpose, const GLfloat* values);
流控制与循环
GLSL可以使用if-else和switch,这点和C语言一致。
GLSL支持C语言形式的for、while和do..while循环。
除此之外,GLSL还支持一些控制语句:
- break
- continue
- return
- discard:丢弃当前的片元,终止着色器的执行,该语句只在片元着色器中有效。
函数
GLSL的函数语法和C语言类似:
returnType functionName([accessMidifier] type1 variable1,
[accessMidifier] type2 variable2,
...){... return returnValue;}
函数名可以是任何名字,但不能使用数字、连续下滑线和gl_开头。
注意,使用函数前,必须声明函数原型或者直接给出函数体。
函数的参数可以使用下面的访问修饰符:
访问修饰符 | 描述 |
---|---|
in | 将数据拷贝到函数中,这是默认的 |
const in | 将只读数据拷贝到函数中 |
out | 从函数中获取数值 |
inout | 将数据拷贝到函数中,并且返回函数中修改的数据 |
计算不变性
GLSL无法保证在不同的着色器中,两个完全相同的计算式会得到完全一样的结果。GLSL有两种方法来确保着色器之的间计算不变性,即invariant或precise关键字。
invariant限制符可以设置任何着色器的输出变量,可以确保如果两个着色器的输出变量使用了同样的表达式,并且表达式中的变量也是相同值。调试过程中,还可以将所有变量都设置为invariant,可在顶点着色器中这么写:
#pragma STDGL invariant(all)
precise限制符可以设置任何计算中的变量或者函数的返回值,它可以增加计算的可复用型,这通常在细分着色器中使用。
预处理命令
GLSL的预处理命令包括:
预处理命令 | 描述 |
---|---|
#define,#undef | 控制常量和宏的定义,与C语言类似 |
#if,#ifdef,#ifndef,#else,#elif,#endif | 代码的条件编译,与C语言类似。只能使用整数表达式或者#define定义的值 |
#error text | 强制编译器将text内容(直到第一个换行符)插入到着色器的信息日志中 |
#pragma options | 控制编译器的特定选项 |
#extension options | 设置编译器支持特定GLSL扩展功能 |
#version number | 设置当前使用的GLSL版本名称 |
#line options | 设置诊断行号 |
对于宏定义,GLSL和C语言类似,只是不支持字符串资环以及预编译连接符,可以定义单一的值,也可以带参数:
#define NUM_ELEMENTS 10
#define LPos(n) gl_LightSource[(n)].position
GLSL还有一些预先定义好的宏,可以使用#error输出:
-
__LINE__
:行号,默认为已处理的换行符个数加一 -
__FILE__
:当前处理的源字符串编号 -
__VERSION__
:OpenGL着色语言版本的整数表示形式
#pragma
可以定义一些编译器选项:
#pragma optimize(on)\(off) //编译器优化选项 ,默认开启
#pragma debug(on)\(off) //编译器调试选项,默认关闭
数据块
类似于结构体声明,GLSL可以将uniform,in,out等变量声明在一个块中:
uniform Matrices{
mat4 Model;
mat4 View;
mat4 Projection;
}(name);
注意,只能在uniform块中声明透明数据类型。
uniform块可以使用不同的布局方式,有以下布局限制符:
- binding = N:设置缓存的绑定方式
- shared:设置uniform块是多个程序间共享的,这是默认方式
- packed:设置uniform块占用最小的内存空间,禁止程序间共享
- std140:使用标准布局方式来设置uniform块或buffer块
- std430:使用标准布局方式来设置uniform块
- offset=N:强制设置成员变量位于缓存的N字节偏移处
- align = N:强制设置成员变量的偏移位置是N的倍数
- row_major:使用行主序的方式来存储uniform块中的矩阵
- column_major:使用列主序的方式来存储uniform块中的矩阵,这是默认地
可以这么使用限制符声明块:
layout(shared, row_major) uniform{...};
多个限制符用逗号隔开。
对于uniform块,应用程序可以这么与变量进行关联:
GLuint glGetUniformBlockIndex(GLuint program, const char* uniformBlockName);
该方法会返回某一uniform块的索引值。
如果要初始化uniform块对应的缓存对象,那么就需要使用glBindBuffer()将缓存对象绑定到GL_UNIFORM_BUFFER上。接着使用glGetActiceUniformBlockiv(),将参数设为GL_UNIFORM_BLOCK_DATA_SIZE来得到编译器分配的块的大小。接着可以用下面的方法将缓存对象与索引对应的块相关联:
void glBindBufferRange(GLenum target, GLuint index, GLuint buffer, GLintptr offset,
GLsizeiptr size);
void glBindBufferBase(GLenum target, GLuint index, GLuint buffer);
我们还可以在调用glLinkProgram前调用下面的函数来显式地控制一个uniform块的绑定方式:
GLuint glUniformBlockBinding(GLuint program, GLuint uniformBlockIndex,
GLuint uniformBlockBinding);
buffer块声明如下:
buffer BufferObject{
int mode;
vec4 points[];
};
它与uniform块类似。不过,着色器可以写入buffer块,修改其中的内容并呈现给其它的着色器调用或者应用程序本身。其次,可以在渲染之前再决定它的大小,而不是编译链接的时候。注意,只支持std430布局。
in、out块类似:
out Lighting{
};
in Lighting{
};
名称在着色器间必须匹配。
着色器编译
着色器对象创建和编译的过程为:
- 创建一个着色器对象
- 将着色器对象关联到着色器程序
- 链接着色器程序
- 判断着色器的链接过程是否成功完成
- 使用着色器来处理顶点和片元
创建着色器对象:
GLuint glCreateShader(GLenum type);
type为几种shader中的一种。
接着将着色器源代码关联到对象上:
void glShaderSource(GLuint shader, GLsizei count, const GLchar** string, const GLint* length);
string用来表示源代码字符串。
编译着色器源代码:
void glCompileShader(GLuint shader);
然后可以使用glGetShaderiv(),参数设为GL_COMPILE_STATUS来判断编译是否正确。然后可以使用下面的方法来获得日志信息:
void glGetShaderInfoLog(GLuint shader, GLsizei bufSize, GLsizei* length, char* infoLog);
创建完必要的着色器对象后,通常是一个顶点+一个片元,就创建程序:
GLuint glCreateProgram(void);
然后关联到着色器对象上:
void glAttachShader(GLuint program, GLuint shader);
想要移除着色器对象,使用:
void glDetachShader(GLuint program, GLuint shader);
之后可以连接对象生成程序:
void glLingProgram(GLuint program);
可以使用glGetProgramiv(),参数设为GL_LINK_STATUS来判断链接是否成功,接着可以使用下面的方法获得日志信息:
void glGetProgramInfoLog(GLuint program, GLsizei bufSize, GLsizei* length, char* infoLog);
最后调用该程序来运行着色器代码:
void glUseProgram(GLuint program);
在着色器对象完成任务后,可以删除它:
void glDeleteShader(GLuint shader);
不再使用程序后,也可以删除:
void glDeleteProgram(GLuint program);
我们可以使用下面的方法来判断着色器对象和程序是否存在:
GLboolean gllsShader(GLuint shader);
GLboolean gllsProgram(GLuint program);
网友评论