[OpenGL]Shader语言GLSL语言基础-限定符

存储限定符

在声明变量时,应根据需要使用存储限定符来修饰,类似 C 语言中的说明符。GLSL 中支持的存储限定符见下表:

限定符 描述
< none: default > 局部可读写变量,或者函数的参数
const 编译时常量,或只读的函数参数
attribute 由应用程序传输给顶点着色器的逐顶点的数据
uniform 在图元处理过程中其值保持不变,由应用程序传输给着色器
varying 由顶点着色器传输给片段着色器中的插值数据
  • 本地变量和函数参数只能使用 const 限定符,函数返回值和结构体成员不能使用限定符。
  • 数据不能从一个着色器程序传递给下一个阶段的着色器程序,这样会阻止同一个着色器程序在多个顶点或者片段中进行并行计算。
  • 不包含任何限定符或者包含 const 限定符的全局变量可以包含初始化器,这种情况下这些变量会在 main() 函数开始之后第一行代码之前被初始化,这些初始化值必须是常量表达式。
  • 没有任何限定符的全局变量如果没有在定义时初始化或者在程序中被初始化,则其值在进入 main() 函数之后是未定义的。
  • uniform、attribute 和 varying 限定符修饰的变量不能在初始化时被赋值,这些变量的值由 OpenGL ES 计算提供。

默认限定符

如果一个全局变量没有指定限定符,则该变量与应用程序或者其他正在运行的处理单元没有任何联系。不管是全局变量还是本地变量,它们总是在自己的处理单元被分配内存,因此可以对它们执行读和写操作。

const 限定符

任意基础类型的变量都可以声明为常量。常量表示这些变量中的值在着色器中不会发生变化,声明常量只需要在声明时加上限定符 const 即可,声明时必须赋初值。

const float zero = 0.0;
const float pi = 3.14159;
const vec4 red = vec4(1.0, 0.0, 0.0, 1.0);
const mat4 identity = mat4(1.0);
  • 常量声明过的值在代码中不能再改变,这一点和 C 语言或 C++ 一样。
  • 结构体成员不能被声明为常量,但是结构体变量可以被声明为常量,并且需要在初始化时使用构造器初始化其值。
  • 常量必须被初始化为一个常量表达式。数组或者包含数组的结构体不能被声明为常量(因为数组不能在定义时被初始化)。

attribute 限定符

attribute 变量只用于顶点着色器中,用来存储顶点着色器中每个顶点的输入(per-vertex inputs)。attribute 通常用来存储位置坐标、法向量、纹理坐标和颜色等。注意 attribute 是用来存储单个顶点的信息。如下是包含位置,色值 attribute 的顶点着色器示例:

// 顶点着色器 .vsh
attribute vec4 position;
attribute vec4 color;

varying vec4 colorVarying;

void main(void) {
    colorVarying = color;
    gl_Position = position;
}

着色器中的两个 attribute 变量 position 和 color 由应用程序加载数值。应用程序会创建一个顶点数组,其中包含了每个顶点的位置坐标和色值信息。可使用的最大 attribute 数量也是有上限的,可以使用 gl_MaxVertexAttribs 来获取,也可以使用内置函数 glGetIntegerv 来询问 GL_MAX_VERTEX_ATTRIBS。OpenGL ES 2.0 实现支持的最少 attribute 个数是8个。

uniform 限定符

uniform 是 GLSL 中的一种变量类型限定符,用于存储应用程序通过 GLSL 传递给着色器的只读值。uniform 可以用来存储着色器需要的各种数据,如变换矩阵、光参数和颜色等。传递给着色器的在所有的顶点着色器和片段着色器中保持不变的的任何参数,基本上都应该通过 uniform 来存储。uniform 变量在全局区声明,以下是 uniform 的一些示例:

uniform mat4 viewProjMatrix;
uniform mat4 viewMatrix;
uniform vec3 lightPosition;

需要注意的一点是,顶点着色器和片段着色器共享了 uniform 变量的命名空间。对于连接于同一个着色程序对象的顶点和片段着色器,它们共用同一组 uniform 变量,因此,如果在顶点着色器和片段着色器中都声明了 uniform 变量,二者的声明必须一致。当应用程序通过 API 加载了 uniform 变量时,该变量的值在顶点和片段着色器中都能够获取到

另一点需要注意的是,uniform 变量通常是存储在硬件中的”常量区”,这一区域是专门分配用来存储常量的,但是由于这一区域尺寸非常有限,因此着色程序中可以使用的 uniform 的个数也是有限的。可以通过读取内置变量 gl_MaxVertexUniformVectors 和 gl_MaxFragmentUniformVectors 来获得,也可以使用 glGetIntegerv 查询 GL_MAX_VERTEX_UNIFORM_VECTORS 或者 GL_MAX_FRAGMENT_UNIFORM_VECTORS 。OpenGL ES 2.0 的实现必须提供至少 128 个顶点 uniform 向量及 16 片段 uniform 向量

varying 限定符

**varying 存储的是顶点着色器的输出,同时作为片段着色器的输入,通常顶点着色器都会把需要传递给片段着色器的数据存储在一个或多个 varying 变量中。**这些变量在片段着色器中需要有相对应的声明且数据类型一致,然后在光栅化过程中进行插值计算。以下是一些 varying 变量的声明:

varying vec2 texCoord;
varying vec4 color;

顶点着色器和片段着色器中都会有 varying 变量的声明,由于 varying 是顶点着色器的输出且是片段着色器的输入,所以两处声明必须一致。与 uniform 和 attribute 相同,varying 也有数量的限制,可以使用 gl_MaxVaryingVectors 获取或使用 glGetIntegerv 查询 GL_MAX_VARYING_VECTORS 来获取。OpenGL ES 2.0 实现中的 varying 变量最小支持数为 8。

回顾下最初那个着色器对应的 varying 声明:

// 顶点着色器 .vsh
attribute vec4 position;
attribute vec4 color;

varying vec4 colorVarying;

void main(void) {
    colorVarying = color;
    gl_Position = position;
}

// 片段着色器 .fsh
varying lowp vec4 colorVarying;

void main(void) {
    gl_FragColor = colorVarying;
}

invariant 限定符

invariant 可以作用于顶点着色器输出的任何一个 varying 变量。

当着色器被编译时,编译器会对其进行优化,这种优化操作可能引起指令重排序(instruction reordering),指令重排序可能引起的结果是当两个着色器进行相同的计算时无法保证得到相同的结果。
例如,在两个顶点着色器中,变量 gl_Position 使用相同的表达式赋值,并且当着色程序运行时,在表达式中传入相等的变量值,则两个着色器中 gl_Position 的值无法保证相等,这是因为两个着色器是分别单独编译的。这将会引起 multi-pass 算法的几何不一致问题。
通常情况下,不同着色器之间的这种值的差异是允许存在的。如果要避免这种差异,则可以将变量声明为invariant,可以单独指定某个变量或进行全局设置。

使用 invariant 限定符可以使输出的变量保持不变。invariant 限定符可以作用于之前已声明的变量使其具有不变性,也可以在声明变量时直接作为声明的一部分,可参考以下两段示例代码:

varying mediump vec3 Color;
// 使已存在的 color 变量不可变
invariant Color;

invariant varying mediump vec3 Color;

以上是仅有的使用 invariant 限定符情境。如果在声明时使用 invariant 限定符,则必须保证其放在存储限定符(varying)之前。
只有以下变量可以声明为 invariant:

  • 由顶点着色器输出的内置的特殊变量
  • 由顶点着色器输出的 varying 变量
  • 向片段着色器输入的内置的特殊变量
  • 向片段着色器输入的 varying 变量
  • 由片段着色器输出的内置的特殊变量

参数限定符

GLSL 提供了一种特殊的限定符用来定义某个变量的值是否可以被函数修改,详见下表:

限定符 描述
in 默认使用的缺省限定符,指明参数传递的是值,并且函数不会修改传入的值(C 语言中值传递)
inout 指明参数传入的是引用,如果在函数中对参数的值进行了修改,当函数结束后参数的值也会修改(C 语言中引用传递)
out 参数的值不会传入函数,但是在函数内部修改其值,函数结束后其值会被修改

使用的方式如下边的代码:

vec4 myFunc(inout float myFloat, // inout parameter
            out vec4 myVec4, 	 // out parameter
            mat4 myMat4); 		 // in parameter (default)

以下是一个示例函数,函数定义用来计算基础的漫反射光照:

vec4 diffuse(vec3 normal, vec3 light, vec4 baseColor) {
	return baseColor * dot(normal, light);
}

精度限定符

OpenGL ES 与 OpenGL 之间的一个区别就是在 GLSL 中引入了精度限定符。精度限定符可使着色器的编写者明确定义着色器变量计算时使用的精度,变量可以选择被声明为低、中或高精度。精度限定符可告知编译器使其在计算时缩小变量潜在的精度变化范围,当使用低精度时,OpenGL ES 的实现可以更快速和低功耗地运行着色器,效率的提高来自于精度的舍弃,如果精度选择不合理,着色器运行的结果会很失真。

OpenGL ES 对各硬件并未强制要求多种精度的支持。其实现可以使用高精度完成所有的计算并且忽略掉精度限定符,然而某些情况下使用低精度的实现会更有优势,精度限定符可以指定整型或浮点型变量的精度,如 lowp,mediump,及 highp,如下:

限定符 描述
highp 满足顶点着色语言的最低要求。对片段着色语言是可选项
mediump 满足片段着色语言的最低要求,其对于范围和精度的要求必须不低于lowp并且不高于highp
lowp 范围和精度可低于mediump,但仍可以表示所有颜色通道的所有颜色值

具体用法参考以下示例:

highp vec4 position;
varying lowp vec4 color;
mediump float specularExp;

除了精度限定符,还可以指定默认使用的精度。如果某个变量没有使用精度限定符指定使用何种精度,则会使用该变量类型的默认精度。默认精度限定符放在着色器代码起始位置,以下是一些用例:

precision highp float;
precision mediump int;

精度修饰符声明了底层实现存储这些变量时,必须要使用的最小范围和精度。实现可能会使用比要求更大的范围和精度,但绝对不会比要求少。以下是精度修饰符要求的最低范围和精度:

- 浮点数范围 浮点数大小范围 浮点数精度范围 整数范围
highp (-2^62 , 2^62) (2^-62 ,2^62) 相对:2^-16 (-2^16 , 2^16)
mediump (-2^14 , 2^14) (2^-14 ,2^14) 相对:2^-10 (-2^10 , 2^10)
lowp (-2, 2) (2^-8 ,2) 绝对:2^-8 (-2^8 , 2^8)
发布了449 篇原创文章 · 获赞 180 · 访问量 87万+

猜你喜欢

转载自blog.csdn.net/ouyangshima/article/details/96484152
今日推荐