着色器基础

2020-08-08  本文已影响0人  Dragon_boy

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还支持一些控制语句:

函数

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输出:

#pragma可以定义一些编译器选项:

#pragma optimize(on)\(off)  //编译器优化选项 ,默认开启
#pragma debug(on)\(off)  //编译器调试选项,默认关闭

数据块

类似于结构体声明,GLSL可以将uniform,in,out等变量声明在一个块中:

uniform Matrices{
    mat4 Model;
    mat4 View;
    mat4 Projection;
}(name);

注意,只能在uniform块中声明透明数据类型。

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);
上一篇 下一篇

猜你喜欢

热点阅读