OpenGL——GLSL高级篇

OpenGL着色语言

接口块

从顶点着色器向片段着色器发送数据时,可以声明几个对应的输入/输出变量。将它们一个一个声明是着色器间发送数据最简单的方式了,但当程序变得更大时,希望发送的可能就不只是几个变量了,它还可能包括数组和结构体;为了帮助我们管理这些变量,GLSL为我们提供了一个叫做接口块(Interface Block)的东西,来方便我们组合这些变量。接口块的声明和struct的声明有点相像,不同的是,现在根据它是一个输入还是输出块(Block),使用in或out关键字来定义的。

//顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoords;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

out VS_OUT
{
    vec2 TexCoords;
} vs_out;

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);    
    vs_out.TexCoords = aTexCoords;
}  
//片元着色器
#version 330 core
out vec4 FragColor;

in VS_OUT
{
    vec2 TexCoords;
} fs_in;

uniform sampler2D texture;

void main()
{             
    FragColor = texture(texture, fs_in.TexCoords);   
}

Uniform缓冲对象

OpenGL提供了一个叫做Uniform缓冲对象(Uniform Buffer Object)的工具,它允许我们定义一系列在多个着色器中相同的全局Uniform变量。当使用Uniform缓冲对象的时候,我们只需要设置相关的uniform一次。当然,我们仍需要手动设置每个着色器中不同的uniform。并且创建和配置Uniform缓冲对象会有一点繁琐。

因为Uniform缓冲对象是一个缓冲,可以使用glGenBuffers来创建它,将它绑定到GL_UNIFORM_BUFFER缓冲目标,并将所有相关的uniform数据存入缓冲。

GLuint ubo;

glGenBuffers(1,&ubo);

glBindBuffer(GL_UNIFORM_BUFFER,ubo);

glBufferData(GL_UNIFORM_BUFFER,size,buffer,GL_STATIC_RAW);

//顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;

//将projection和view矩阵存储到Uniform块(Uniform Block)中:
//std140:定义的Uniform块对它的内容使用一个特定的内存布局。
layout (std140) uniform Matrices
{
    mat4 projection;
    mat4 view;
};

uniform mat4 model;

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}

Uniform块布局

uniform 块的内容是储存在一个缓冲对象中,它实际只是一块预留内存。因为这块内存并不会保存它具体保存的是什么类型的数据,我们需要告诉openGL内存的哪一部分对应着着色器中的哪一个uniform变量。

layout (std140) uniform ExampleBlock
{
    float value;
    vec3  vector;
    mat4  matrix;
    float values[3];
    bool  boolean;
    int   integer;
};

OpenGL——GLSL高级篇_第1张图片

1. shared

默认情况下,GLSL会使用一个叫做共享(shared)布局的uniform 内存布局,共享是因为一旦硬件定义了偏移量,他们在多个程序中共享并一致的;使用共享布局时,GLSL是可以为了优化而对uniform 变量的位置进行变动,只要变量的顺序保持不变;因为我们无法发知道uniform 变量的偏移量,可以使用glGetUniformIndices来获取指定名称uniform 变量的索引位置,使用glGetActiveUniformsiv 来获取指定索引位置的偏移量和大小:

void glGetUniformIndices(GLuint program, GLsizei uniformCount, const char** uniformNames,GLuint* uniformIndices);

返回uniformCount 个 uniform变量的索引位置;变量的名称通过uniformNames来指定,每个名称都以NULL结尾;

//顶点着色器 和 片元着色器共享的 ”Uniforms“ 的 uniform 块
...
"uniform Uniform {"
"    vec3 translation;"
"    flaot scale;"
"    vec4 rotation;"
"    bool enabled;"
"}"
...


//程序代码
.....

enum{
    Translation,
    Scale,
    Rotation,
    Enabled,
    NumUniforms
}

GLfloat scale = 0.5;
GLfloat translation[] = {0.1,0.1;0.0};
GLfloat rotation[] = {90,0.0,0.0,1.0};
GLBoolean enabled = GL_TRUE;

//对应着色器中的uniform 块中 uniform的变量名称
const char* names[NumUniforms] = {
    "translation",
    "scale",
    "rotation",
    "enabled"
}

GLuint indices[NumUniforms];
GLuint size[NumUniforms];
GLuint offset[NumUniforms];
GLuintt type[NumUniforms];

//获取所有uniform变量的索引
glGetUniformIndices(program,NumUniforms,names,indices);
//通过uniform 变量的索引来获取相应的偏移值、大小、类型等信息
glGetActiveUniformsiv(program,NumUniforms,indices,GL_UNIFORM_OFFSET,offset);
glGetActiveUniformsiv(program,NumUniforms,indices,GL_UNIFORM_SIZE,size);
glGetActiveUniformsiv(program,NumUniforms,indices,GL_UNIFORM_TYPE,type);


GLvoid* buffer;
//获取uniform缓存的索引,并获取整个块的大小
GLuint uboIndex = glGetActiveUniformBlockiv(program,"Uniforms");
glGetActiveUniformBlockiv(program,uboIndex,GL_UNIFORM_BLOCK_DATA_SIZE,&uboSize);
buffer = malloc(uboSize);
//组装buffer
memcpy(buffer + offset[Scale],&scale, size[Scale]*4);

...

2. packed

当使用紧凑(Packed)布局时,是不能保证这个布局在每个程序中保持不变的(即非共享),因为它允许编译器去将uniform变量从uniform 块中优化掉,这在每个着色器中都可能是不同的。

3. std140

std140 布局声明了每个变量的偏移量都是由一系列规则所决定的,这显式的声明了每个变量类型的内存布局;由于显式提及,我们可以手动的计算出每个变量的偏移量。

每个变量都有一个基准对齐量,它等于一个变量在uniform 块中所占据的空间,这个基准对齐量是使用std140 布局的规则计算出来的,接下来,在计算它的对齐偏移量,它是一个变量从块起始位置的字节偏移量;一个变量的对齐字节偏移量必须等于基准对其量的倍数。

GLSL中每个每个变量,比如int,float,bool,都被定义为4字节,每4个字节用N来表示:

OpenGL——GLSL高级篇_第2张图片

layout (std140) uniform ExampleBlock
{
                     // 基准对齐量       // 对齐偏移量
    float value;     // 4               // 0 
    vec3 vector;     // 16              // 16  (必须是16的倍数,所以 4->16)
    mat4 matrix;     // 16              // 32  (列 0)
                     // 16              // 48  (列 1)
                     // 16              // 64  (列 2)
                     // 16              // 80  (列 3)
    float values[3]; // 16              // 96  (values[0])
                     // 16              // 112 (values[1])
                     // 16              // 128 (values[2])
    bool boolean;    // 4               // 144
    int integer;     // 4               // 148
}; 

使用示例见下文的 uniform 缓冲的使用

4. std430

std430 只适用于GLSL版本4.30或者更高版本。

OpenGL——GLSL高级篇_第3张图片

OpenGL——GLSL高级篇_第4张图片

5. Binding

从OpenGL 4.2版本起,你也可以添加一个布局标识符,显式地将Uniform块的绑定点储存在着色器中,这样就不用再调用glGetUniformBlockIndex和glUniformBlockBinding了。下面的代码显式地设置了Lights Uniform块的绑定点。

layout(std140, binding = 2) uniform Lights { ... };

Uniform缓冲的使用

1. 创建uniform缓冲对象

unsigned int uboExampleBlock;
glGenBuffers(1, &uboExampleBlock);
glBindBuffer(GL_UNIFORM_BUFFER, uboExampleBlock);
glBufferData(GL_UNIFORM_BUFFER, 152, NULL, GL_STATIC_DRAW); // 分配152字节的内存
glBindBuffer(GL_UNIFORM_BUFFER, 0);

2. 将Uniform 缓冲区 绑定到 绑定点

在OpenGL 上下文中,定义了绑定点(binding point),我们可以将一个uniform 缓冲链接到它;在创建uniform 缓冲之后,可以将它绑定到其中的一个绑定点上;同时将着色器中的uniform块绑定到相同的绑定点上,就将他们连接到一起了。

OpenGL——GLSL高级篇_第5张图片

使用如下方式将Uniform缓冲对象绑定到绑定点2上:

glBindBufferBase(GL_UNIFORM_BUFFER, 2, uboExampleBlock); 
// 或
glBindBufferRange(GL_UNIFORM_BUFFER, 2, uboExampleBlock, 0, 152);

3. 将uniform 块 绑定到绑定点

用如下方式将Lights Uniform 块 链接到绑定点2:

unsigned int lights_index = glGetUniformBlockIndex(shaderA.ID, "Lights");   
glUniformBlockBinding(shaderA.ID, lights_index, 2);

4. 向uniform缓冲添加数据

以使用glBufferSubData函数,用一个字节数组添加所有的数据,或者更新缓冲的一部分。要想更新uniform变量boolean,我们可以用以下方式更新Uniform缓冲对象:

glBindBuffer(GL_UNIFORM_BUFFER, uboExampleBlock); 
int b = true; // GLSL中的bool是4字节的,所以我们将它存为一个integer
glBufferSubData(GL_UNIFORM_BUFFER, 144, 4, &b); 
glBindBuffer(GL_UNIFORM_BUFFER, 0);

buffer 块

GLSL 中的buffer 块,或者对于应用程序而言,就是着色器的存储缓存对象,他的行为和uniform类似。

buffer 块 和 uniform 的区别:

1. 着色器可以写入buffer块,修改其中的内容并呈现给其他的着色器调用或者应用程序本身;

2. 可以在渲染之前再决定它的大小,而不是在编译和链接的时候。

TODO具体使用

GLSL的内建变量

顶点着色器内置变量

in int gl_VertexID;
in int gl_InstanceID;

out gl_PerVertex{
    vec4 gl_Position;
    float gl_PointSize;
    float gl_ClipDistance[];
}

gl_VertexID

整型变量gl_VertexID储存了正在绘制顶点的当前ID。当(使用glDrawElements)进行索引渲染的时候,这个变量会存储正在绘制顶点的当前索引。当(使用glDrawArrays)不使用索引进行绘制的时候,这个变量会储存从渲染调用开始的已处理顶点数量。

gl_InstanceID

顶点着色器的输入变量gl_InstanceID 保存在实例化绘图命令中的当前图元的实例数值;如果当前图元不来子实例化绘图命令,那么该值为0。

gl_Position

作为输出变量,gl_Position用来保存顶点位置的齐次坐标;该值用作图元装配、裁剪、筛选,以及其他同固定功能操作。

如果在顶点着色器中没有对g'l_Position 赋值,那么在定点处理阶段它的值是不确定的;作为输入变量,读取前着色器阶段写入的值作为gl_Position的值。

gl_PointSize

gl_PointSize输出变量,它是一个float变量,你可以使用它来设置点的宽高(像素)。在顶点着色器中修改点的大小的话,你就能对每个顶点设置不同的值了。

在顶点着色器中修改点大小的功能默认是禁用的,如果你需要启用它的话,你需要用图下方法启用:

glEnable(GL_PROGRAM_POINT_SIZE);

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);    
    gl_PointSize = gl_Position.z;    
}

gl_ClipDistance[]

 

几何着色器内置变量

in gl_PerVertex{
    vec4  gl_Position;
    float gl_PointSize;
    float gl_ClipDistance[];
}gl_in[];

in int gl_PrimitiveIDIn;
in int gl_InvocationID;

out gl_PerVertex{
    vec4  gl_Position;
    float gl_PointSize;
    float gl_ClipDistance[];
};

out int gl_PrimitiveID;
out int gl_Layer;
out int gl_ViewportIndex;

gl_PrimitiveIDIn

gl_InvocationID

gl_PrimitiveID

gl_Layer

gl_ViewportIndex

片元着色器内置变量

in vec4  gl_FragCoord;
in bool  gl_FrontFacing;
in float gl_ClipDistance[];
in vec2  gl_PointCoord;
in int   gl_PrimitiveID;
in int   gl_SampleID;
in vec2  gl_SamplePosition;
in int   gl_SampleMaskIn[];
in int   gl_Layer;
in int   gl_ViewPortIndex;

out float gl_FragDepth;
out int   gl_SampleMask[];

gl_FragCoord

输入片元着色器中的gl_FragCoord保存当前片元的窗口位置,另外gl_FragCoord的z分量等于对应片段的深度值;

gl_FrontFacing

OpenGL能够根据顶点的环绕顺序来决定一个面是正向还是背向面。如果我们不(启用GL_FACE_CULL来)使用面剔除,那么gl_FrontFacing将会告诉我们当前片段是属于正向面的一部分还是背向面的一部分。

gl_FrontFacing变量是一个bool,如果当前片段是正向面的一部分那么就是true,否则就是false。比如说,我们可以这样子创建一个立方体,在内部和外部使用不同的纹理:

#version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D frontTexture;
uniform sampler2D backTexture;

void main()
{             
    if(gl_FrontFacing)
        FragColor = texture(frontTexture, TexCoords);
    else
        FragColor = texture(backTexture, TexCoords);
}

gl_PointCoord

当点图形被使用时,gl_PointCoord 的2维坐标值表示当前片元的点图元中的位置;他们的取值范围在【0.0,1.0】;如果当前未使用点图元或者点图形没有使用时,gl_PointCoord 的值是不确定的。

gl_SampleID

gl_SampleID 时当前处理的样本数量,他的取值范围是【0,gl_NumSamples -1】.使用这个变量的片元着色器,而这个着色器则评估为每样本。

gl_SamplePosition

在多样本绘图缓冲中当前样本的位置,g'l_SamplePositon 的 x, y 部分包含当前样本的子像素坐标,取值【0.0,1.0】;如果在任何一个片元着色器中使用这个变量,则这个片元着色器评估为每样本。

gl_FragDepth

gl_FragDepth是片元着色器的的输出变量,可以使用它来在着色器内设置片段的深度值。要想设置深度值,我们直接写入一个0.0到1.0之间的float值到输出变量就可以了,如果着色器没有写入值到gl_FragDepth,它会自动取用gl_FragCoord.z的值。

gl_FragDepth = 0.0; // 这个片段现在的深度值为 0.0

当我们在片段着色器中对gl_FragDepth进行写入有一个很大的缺点:OpenGL会禁用所有的提前深度测试(Early Depth Testing)。它被禁用的原因是,OpenGL无法在片段着色器运行之前得知片段将拥有的深度值,因为片段着色器可能会完全修改这个深度值。

你可能感兴趣的:(OpenGL,OpenGL,ES,opengl,opengles,glsl)