现代OpenGL渲染管线严重依赖着色器来处理出入的数据,如果不使用着色器,那么OpenGL可以处理的事情可能只有清除窗口了,可见着色器对OpenGL的重要性。在3.0版本(含3.0)以前,如果用到了兼容模式环境,OpenGL还包含一个固定渲染管线,可以在不使用着色器的情况下处理几何与像素数据。自从3.1版本开始,固定渲染管线从核心模式中去除,因此必须使用着色器来完成工作。
GLSL是一种专门为图形开发设计的语言,你会发现它与“C”语言非常类似,当然也具备C++和java的许多特性,它也被OpenGL中所有阶段所支持。
一个着色器程序和C语言类似,都是从main()函数开始的,一个典型的着色器是这样的
#version 330 core
Void main()
{
//编写代码
}
1.在程序的起始位置需使用#version类声明当前使用的版本。
2.Main函数不需要返回值,返回为void。
3.可以用“//” 单行注释也可以用“/* */” 多行注释。
GLSL是一种强类型语言,所有的变量必须事先声明,并给出变量的类型,语法与C语言相似。下表为GLSL的基本数据类型。
类型 |
描述 |
float |
32位浮点值 |
double |
32位浮点值 |
int |
有符号的32位整数 |
uint |
无符号的32位整数 |
bool |
布尔值 |
以上的基本数据类型是都是”透明”的,他们的内部形式都是暴露出来的,与之对于的是”不透明”类型,他们的内部形式没有暴露出来,例如采样器(sampler),图像(image),以及原子计数器(atomic)。他们声明的变量相当于一个不透明的句柄。
在任何函数定义之外声明的变量都拥有全局作用域,对着色器中所有函数都是可见的。
在一组大括号之内声明的变量只能在大括号范围内使用
循环的迭代自变量只能在循环内起最作用。
所有的变量必须在声明的同时进行初始化,例如
int i = 100‘
foat f = 2.0;
bool b = true;
向量与矩阵类型 |
||||
基本类型 |
2D向量 |
3D向量 |
4D向量 |
矩阵类型 |
float |
vec2 |
vec3 |
vec4 |
mat2 mat3 mat4 |
mat2*2 mat2*3 mat2*4 |
||||
Mat3*2 mat3*3 mat3*4 |
||||
Mat4*2 mat4*3 mat4*4 |
||||
double |
dvec2 |
dvec3 |
dvec4 |
dmat2 dmat3 dmat4 |
dmat2*2 dmat2*3 dmat2*4 |
||||
Mat3*2 dmat3*3 dmat3*4 |
||||
Mat4*2 dmat4*3 dmat4*4 |
||||
int |
ivec2 |
ivec3 |
ivec4 |
-- |
uint |
uvec2 |
uvec3 |
uvec4 |
-- |
bool |
bvec2 |
bvec3 |
bvec4 |
-- |
1.矩阵类型需要给出两个纬度的信息,例如mat4*3,其中一个值表示列数,一个值表示行数。
2.向量的初始化与标量类似
Vec3 v = vec3(0.0, 4.0, 1.0);
类型间可以互相转化
ivec3 steps = ivec3(v);
3.矩阵的构建方式与此相同,例如
通过指定每一个元素来初始化
mat3 M = mat3(1, 2, 3,
4, 5, 6,
7, 8, 9)
通过向量来初始化
vec3 v1 = vec3(1,2,3);
vec3 v2 = vec3(4,5,6);
vec3 v3 = vec3(7,8,9);
mat3 M = mat3(v1,v2,v3);
还可以
vec2 v1 = vec2 (1,2);
vec2 v2 = vec2 (4,5);
vec2 v3 = vec2 (7,8);
mat3 M = mat3(v1, 3, v2, 6, v3, 9);
4,访问向量和矩阵中的元素
向量可以使用分量的名称和下表来访问元素
vec3 color = vec3(1,2,3);
float rad = color.r;或 float rad = color[0];
矩阵元素的访问可以使用数组标记访问
mat4 m = mat4(2.0);
vec4 zVec = m[2];
float yScale = m[1][1];
struct particle{
float lifetime;
vec3 position;
vec3 velocity;
};
GLSL还支持任意类型的数组,包括结构体数组
GLSL4.2和更早的版本不允许创建数组类型的数组。
float coeff[3];//有3个float元素的数组
float[3] coeff;//有3个float元素的数组
int indices[];//为定义数组,可以从新声明它的纬数
float coeff[3] = float[3]2.38, 3.14, 42.0();//用构造函数构造数组
coeff.lenhth();//返回元素的个数
类型修饰符 |
|
类型修饰符 |
描述 |
const |
将变量定义为只读形式 |
in |
设置这个变量为着色阶段的输入变量 |
out |
设置这个变量为着色阶段的输出变量 |
uniform |
设置这个变量为用户应用程序传递给着色器的数据,对于图元而言它是个常量 |
buffer |
设置应用程序共享的一块可读写的内存 |
shared |
设置变量是本地工作组中共享的。它只能用于计算着色器中 |
in存储限制符
用于修饰用于定义着色器的输入变量,这类输入可以是顶点属性,也可以是前一个着色器的输出变量
out存储限制符
用于修饰用于定义着色器的输出变量,例如顶点着色器输出坐标,或者片段着色器输出最终的片段颜色
uniform存储限制符
uniform修饰符可以设置一个全局变量,该变量数据是单向传递的,用户应用程序可以修改该变量,着色器只能读取该变量,无法写入到该变量,也无法改变它的值。
在用户程序中想要写入数据到uniform变量,通常需要两个函数
GLint glGetUniformLocation(GLuint program, const char* name); 返回值:返回着色器程序中uniform变量对应的索引值。 参 数:program着色器程序Id name uniform变量名 |
void glUniform1f (GLint location, GLfloat value ); 返回值:空。 参 数:location色器程序中uniform变量对应的索引值 value写入的值 |
使用方法通常是这样的
1.在着色器程序中定义一个uniform变量
uniform float temp;
2.在用户程序中修改f的值,首先获取该uniform变量在着色器中对应的索引值,
GLint id = glGetUniformLocation(1, "temp");
3.接着修改它的值
glUniform1f(id, 4);
buffer存储限制符
如果需要在应用程序中共享一大块缓冲给着色器,那么buffer变量是一个好的选择,这块缓冲对于着色器来说是可读可写的。
shared存储限制符只能用于计算着色器中,它可以建立本地工作组内共享的内存。
参数限制符
在GLSL中函数可以在运行后返回和修改数据,但它与"C" 不同,并没有指针或引用的概念,不过函数的参数可以指定一个参数限制符,来表明如何处理数据。
函数参数的访问修饰符 |
|
访问修饰符 |
描述 |
int |
将数据拷贝到函数中(如果没有指定,则默认这种形式) |
const in |
将只读数据拷贝到函数中 |
out |
从函数中获取数值(输入函数的值是为定义的) |
inout |
将数据拷贝到函数中,并且返回函数中修改的数据 |
uniform块
着色器与应用程序之间或者着色器个阶段之间共享的变量可以组织为变量块的形式,uniform可以使用uniform块,in, out, buffer可以使用各自对应的块。例如
uniform b{//限定符可以用in, out, buffer
vec3 v;//变量列表
bool b;
}
注:uniform块中只能包含透明类型的变量,而且uniform块必须在全局作用域内声明。
uniform块的布局控制
在uniform块中可以使用不同的限制符来设置变量的布局方式。限制符如下表
uniform的布局限制符 |
|
布局限制符 |
描述 |
shared |
在多个程序间共享(默认的布局方式) |
packed |
占用最小的内存空间,但会禁止程序间共享这个块 |
std140 |
使用标准布局方式来设置uniform块或着色器存储的buffer块 |
std430 |
使用标准布局方式来设置buffer块 |
row_major |
使用行主序的方式来存储uniform块中的矩阵 |
column_major |
使用列主序的方式来存储uniform块中的矩阵(默认的顺序) |
如果需要共享一个uniform块,并且使用行主序的方式来存储数据,如下所示:
layout (shared, row_major) uniform{ };
如果需要对所有的uniform块布局,可以使用下面的语句
layout (shared, row_major) uniform;
应用程序访问uniform块
1.在着色器中定义uniform块
uniform Uniforms{
vec3 trans;
float scale;
vec4 rotation;
bool enabled;
}
2.在应用程序中据获取Uniforms的缓冲索引,并判断其大小,使用函数glGetUniformBlockIndex和glGetActiveUniformBlockiv。
函数原型:
GLuint glGetUniformBlockIndex(GLuint program, const GLchar *uniformBlockName); 返回值:返回Uniforms的缓冲索引 参 数:program 着色器程序ID uniformBlockName Uniforms块名 |
void glGetActiveUniformBlockiv(GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint *params) 返回值:void 参 数:program 着色器程序ID uniformBlockIndex Uniforms的缓冲索引 pname 输出的类型 params 输出参数,输出Uniforms块的大小 |
GLvoid* buffer;//缓冲区
GLint uboSize;//Uniforms块大小
GLint uboIndex;//Uniforms缓冲索引
uboIndex = glGetUniformBlockIndex(ID , "Uniforms");
glGetActiveUniformBlockiv(ID, uboIndex , "Uniforms", &uboSize);
buffer = malloc(uboSize);
到了这一步我们已经开辟了一片和Uniforms块大小相同的缓冲区,下面就是给他填充数据
3.给应用程序中的buffer赋值
//准备buffer中的值
vec3 trans[] = {1.0, 2.0, 3.0}
float scale = 0.5;
vec4 rotation = {1.0, 2.0, 3.0, 4.0}
bool enabled = True;
//查询对应的属性判断向缓冲中写入数值的位置
GLuint indices[4];
GLuint size[4];
GLuint offset[4];
GLuint type[4];
void glGetUniformIndices(GLuint program,GLsizeiUniformCount, const char**UniformNames, GLuint * uniformIndices); 返回值:void 参 数:program 着色器程序ID UniformCount Uniforms块的变量个数 UniformNames Uniforms块中的变量名 uniformIndices Uniforms的缓冲索引 |
void glGetActiveUniformsiv(GLuint program, GLsizei uniformCount, const GLuint *uniformIndices, GLenum pname, GLint *params) 返回值:void 参 数:program 着色器程序ID uniformCount Uniforms块的变量个数 uniformIndices Uniforms的缓冲索引 pname 输出类型 params 输出函参数 |
4.将变量值拷贝到buffer中
memcpy(buffer + offset[Scale], &scale, size[Scale] * TypeSize(type[Scale]));
memcpy(buffer + offset[trans], &scale, size[trans] * TypeSize(type[trans]));
memcpy(buffer + offset[rotation], &scale, size[rotation] * TypeSize(type[rotation]));
memcpy(buffer + offset[enabled], &scale, size[enabled] * TypeSize(type[enabled]));
注:现在只是把数值按Uniforms块中的格式存储到buffer,数据还在应用程序内存中,下一不就要把数据放到着色器中了
5.将buffer放入到着色器中
//创建uniform缓存对象
GLuint ubo;
glGenBuffers(1, &ubo);
glBindBuffer(GL_UNIFORM, uboSize, buffer, GL_STATIC_RAW);
//将缓存对象与块关联
void glBindBufferBase(GLenum target, GLuint index, GLuint buffer) 返回值:void 参 数:target 关联的目标 index Uniforms的缓冲索引 buffer ubo缓存对象 |
glBindBufferBase(GL_UNIFORM_BUFFER, uboIndex, ubo);
参见:《OpenGL编程指南》第八版第2章