美文网首页
高级GLSL

高级GLSL

作者: 不决书 | 来源:发表于2023-09-19 20:23 被阅读0次

GLSL内建变量

gl_Position : 顶点着色器的输出向量
gl_FragColor : 片段着色器输出的颜色
gl_PointSize : 已点的方式渲染时,设置渲染出来的点的大小,单位是点的宽高(像素)
在顶点着色器中修改点大小的功能默认是禁用的,如果你需要启用它的话,你需要启用OpenGL的

  glEnable(GL_PROGRAM_POINT_SIZE);

gl_VertexID: 我们只能对它进行读取,正在绘制顶点的当前ID,当(使用glDrawElements)进行索引渲染的时候,这个变量会存储正在绘制顶点的当前索引。当(使用glDrawArrays)不使用索引进行绘制的时候,这个变量会储存从渲染调用开始的已处理顶点数量

  #version 300 es
  uniform int numVerts;
  uniform vec2 resolution;
 
  #define PI radians(180.0)
 
  void main() {
    float u = float(gl_VertexID) / float(numVerts);  // goes from 0 to 1
    float angle = u * PI * 2.0;                      // goes from 0 to 2PI
    float radius = 0.8;
 
    vec2 pos = vec2(cos(angle), sin(angle)) * radius;
 
    float aspect = resolution.y / resolution.x;
    vec2 scale = vec2(aspect, 1);
 
    gl_Position = vec4(pos * scale, 0, 1);
    gl_PointSize = 5.0;
  }

gl_FragCoord: 的x和y分量是片段的窗口空间(Window-space)坐标,其原点为窗口的左下角。我们已经使用glViewport设定了一个800x600的窗口了,所以片段窗口空间坐标的x分量将在0到800之间,y分量在0到600之间
gl_FrontFacing: 如果我们不(启用GL_FACE_CULL来)使用面剔除,那么gl_FrontFacing将会告诉我们当前片段是属于正向面的一部分还是背向面的一部分。

  #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_FrontFacing就没有意义了。

gl_FragDepth: 设置深度值,我们直接写入一个0.0到1.0之间的float值到输出变量就可以了:

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

如果着色器没有写入值到gl_FragDepth,它会自动取用gl_FragCoord.z的值。
然而,由我们自己设置深度值有一个很大的缺点,只要我们在片段着色器中对gl_FragDepth进行写入,OpenGL就会(像深度测试小节中讨论的那样)禁用所有的提前深度测试(Early Depth Testing)。它被禁用的原因是,OpenGL无法在片段着色器运行之前得知片段将拥有的深度值,因为片段着色器可能会完全修改这个深度值。
从OpenGL 4.2起,我们仍可以对两者进行一定的调和,在片段着色器的顶部使用深度条件(Depth Condition)重新声明gl_FragDepth变量:

  layout (depth_<condition>) out float gl_FragDepth;

condition可以为下面的值:

条件 描述
any 默认值。提前深度测试是禁用的,你会损失很多性能
greater 你只能让深度值比gl_FragCoord.z更大
less 你只能让深度值比gl_FragCoord.z更小
unchanged 如果你要写入gl_FragDepth,你将只能写入gl_FragCoord.z的值
  #version 420 core // 注意GLSL的版本!
  out vec4 FragColor;
  layout (depth_greater) out float gl_FragDepth;

  void main()
  {             
      FragColor = vec4(1.0);
      gl_FragDepth = gl_FragCoord.z + 0.1;
  }  

接口块(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 缓存对象(Uniform Buffer Object)

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

将projection和view矩阵存储到所谓的Uniform块(Uniform Block)中:

  #version 330 core
  layout (location = 0) in vec3 aPos;

// 定义Uniform block
  layout (std140) uniform Matrices
  {
      mat4 projection;
      mat4 view;
  };

  uniform mat4 model;

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

你现在可能会在想layout (std140)这个语句是什么意思。它的意思是说,当前定义的Uniform块对它的内容使用一个特定的内存布局。这个语句设置了Uniform块布局(Uniform Block Layout)。

Uniform块布局

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

默认情况下,GLSL会使用一个叫做共享(Shared)布局的Uniform内存布局,共享是因为一旦硬件定义了偏移量,它们在多个程序中是共享并一致的。使用共享布局时,GLSL是可以为了优化而对uniform变量的位置进行变动的,只要变量的顺序保持不变。因为我们无法知道每个uniform变量的偏移量,我们也就不知道如何准确地填充我们的Uniform缓冲了。我们能够使用像是glGetUniformIndices这样的函数来查询这个信息,但这超出本节的范围了。

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

每个变量都有一个基准对齐量(Base Alignment),它等于一个变量在Uniform块中所占据的空间(包括填充量(Padding)),这个基准对齐量是使用std140布局的规则计算出来的。接下来,对每个变量,我们再计算它的对齐偏移量(Aligned Offset),它是一个变量从块起始位置的字节偏移量。一个变量的对齐字节偏移量必须等于基准对齐量的倍数。

类型 布局规则
标量,比如int和bool 每个标量的基准对齐量为N。
向量 2N或者4N。这意味着vec3的基准对齐量为4N。
标量或向量的数组 每个元素的基准对齐量与vec4的相同。
矩阵 储存为列向量的数组,每个向量的基准对齐量与vec4的相同。
结构体 等于所有元素根据规则计算后的大小,但会填充到vec4大小的倍数。

引入的那个叫做ExampleBlock的Uniform块,并使用std140布局计算出每个成员的对齐偏移量:

  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块定义之前添加layout (std140)语句,我们告诉OpenGL这个Uniform块使用的是std140布局。除此之外还可以选择两个布局,但它们都需要我们在填充缓冲之前先查询每个偏移量。我们已经见过shared布局了,剩下的一个布局是packed。当使用紧凑(Packed)布局时,是不能保证这个布局在每个程序中保持不变的(即非共享),因为它允许编译器去将uniform变量从Uniform块中优化掉,这在每个着色器中都可能是不同的。

使用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);

现在,每当我们需要对缓冲更新或者插入数据,我们都会绑定到uboExampleBlock,并使用glBufferSubData来更新它的内存。


image.png

这可以通过调用glGetUniformBlockIndex来获取,它接受一个程序对象和Uniform块的名称。我们可以用以下方式将图示中的Lights Uniform块链接到绑定点2:

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

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

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

接下来,我们还需要绑定Uniform缓冲对象到相同的绑定点上,这可以使用glBindBufferBase或glBindBufferRange来完成。

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

BufferSubData函数,用一个字节数组添加所有的数据,或者更新缓冲的一部分。要想更新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);

FYI: https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/08%20Advanced%20GLSL/

相关文章

网友评论

      本文标题:高级GLSL

      本文链接:https://www.haomeiwen.com/subject/yjqgvdtx.html