OpenGL ES 2.0 着色器语言GLSL(二)

本文上接OpenGL ES 2.0 着色器语言GLSL(一),继续讲解GLSL的一些基本的概念。如无特殊说明,文中的GLSL均指OpenGL ES的着色语言,基于OpenGL ES 2.0。

着色器的预处理指令

GLSL中预处理指令的使用也跟C++的预处理指令相似。以下代码是宏及宏的条件判断:

1
2
3
4
5
6
7
8
#define
#undef
#if
#ifdef
#ifndef
#else
#elif
#endif

注意与C++中不同,宏不能带参数定义。使用#if#else#elif可以用来判断宏是否被定义过。以下是一些预先定义好的宏及它们的描述:

1
2
3
4
__LINE__ // Replaced with the current line number in a shader
__FILE__ // Always 0 in OpenGL ES 2.0
__VERSION__ // The OpenGL ES shading language version (e.g., 100)
GL_ES // This will be defined for ES shaders to a value of 1

在着色器编译过程中,#error指令会触发编译错误并向日志中写入内容。使用#pragma指令可以向编译器明确与实现相关的指令。还有一种与C++中不同的预处理指令是#version,对于OpenGL ES 2.0的着色器应将此值设置为100。#version指定了编译着色器的GLSL对应版本,可以在未来更新的版本中据此判断着色器的语言版本,以实用对应的版本来完成编译。这一标记需要写在代码的最开始位置。以下是一个示例:

1
#version 100 // OpenGL ES Shading Language v1.00

预处理指令中另一个非常重要的是#extension,用来控制是否启用某些扩展的功能。当供应商扩展GLSL时,会增加新的语言扩展明细,如GL_OES_texture_3D等。着色器必须告知编译器是否允许使用扩展或以怎样的行为方式出现,这就需要使用#extension指令来完成,以下是一些用例:

1
2
3
4
// Set behavior for an extension
#extension extension_name : behavior
// Set behavior for ALL extensions
#extension all : behavior

第一个参数应为扩展的名称或者“all”,“all”表示该行为方式适用于所有的扩展。

扩展的行为方式 描述
require 指明扩展是必须的,如果该扩展不被支持,预处理器会抛出错误。如果扩展参数为“all”则一定会抛出错误。
enable 指明扩展是启用的,如果该扩展不被支持,预处理器会发出警告。代码会按照扩展被启用的状态执行,如果扩展参数为“all”则一定会抛出错误。
warn 除非是因为该扩展被其它处于启用状态的扩展所需要,否则在使用该扩展时会发出警告。如果扩展参数为“all”则无论何时使用扩展都会抛出警告。除此之外,如果扩展不被支持,也会发出警告。
disable 指明扩展被禁用,如果使用该扩展会抛出错误。如果扩展参数为“all”(即默认设置),则不允许使用任何扩展。

例如,实现不支持3D纹理扩展,如果你希望处理器发出警告(此时着色器也会同样被执行,如同实现支持3D纹理扩展一样),应当在着色器顶部加入以下代码:

1
#extension GL_OES_texture_3D : enable

内置变量与内置函数

顶点着色器内置变量

顶点着色器中有变量gl_Position,此变量用于写入齐次顶点位置坐标。一个完整的顶点着色器的所有执行命令都应该向此变量写入值。写入的时机可以是着色器执行过程中的任意时间。当被写入之后也同样可以读取此变量的值。在处理顶点之后的图元组装、剪切(clipping)、剔除(culling)等对于图元的固定功能操作中将会使用此值。如果编译器发现gl_Position未写入或在写入之前有读取行为将会产生一条诊断信息,但并非所有的情况都能发现。如果执行顶点着色器而未写入gl_Position,则gl_Position的值将是未定义。
顶点着色器中有变量gl_PointSize,此变量用于为顶点着色器写入将要栅格化的点的大小,以像素为单位。
顶点着色器中的这些内置变量固有的声明类型如下:

1
2
highp vec4 gl_Position; // should be written to
mediump float gl_PointSize; // may be written to

这些变量如果未写入或在在写入之前读取,则取到的值是未定义值。如果被写入多次,则在后续步骤中使用的是最后一次写入的值。
这些内置变量拥有全局作用域。
OpenGL ES中没有内置的attribute名称。

片段着色器内置变量

OpenGL ES渲染管线最后的步骤会对片段着色器的输出进行处理。
如果没有使用过discard关键字,则片段着色器使用内置变量gl_FragColorgl_FragData来向渲染管线输出数据。在片段着色器中,并非必须要对gl_FragColorgl_FragData的值进行写入。这些变量可以多次写入值,这样管线中后续步骤使用的是最后一次赋的值。写入的值可以再次读取出,如果在写入之前读取则会得到未定义值。写入的gl_FragColor值定义了后续固定功能管线中使用的片段的颜色。而变量gl_FragData是一个数组,写入的数值gl_FragData[n]指定了后续固定功能管线中对应于数据n的片段数据。如果着色器为gl_FragColor静态赋值,则可不必为gl_FragData赋值,同样如果着色器为gl_FragData中任意元素静态赋值,则可不必为gl_FragColor赋值。每个着色器应为二者之一赋值,而非二者同时。(在着色器中,如果某个变量在该着色器完成预处理之后,不受运行时的流程控制语句影响,一定会被写入值,则称之为对该变量的静态赋值)。如果着色器执行了discard关键字,则该片段被丢弃,且gl_FragColorgl_FragData不再相关。
片段着色器中有一个只读变量gl_FragCoord,存储了片段的窗口相对坐标xyz1/w。该值是在顶点处理阶段之后对图元插值生成片段计算所得。z分量是深度值用来表示片段的深度。
片段着色器可以访问内置的只读变量gl_FrontFacing,如果片段属于正面向前(front-facing)的图元,则该变量的值为true。该变量可以选取顶点着色器计算出的两个颜色之一以模拟两面光照。
片段着色器有只读变量gl_PointCoordgl_PointCoord存储的是当前片段所在点图元的二维坐标。点的范围是0.0到1.0。如果当前的图元不是一个点,那么从gl_PointCoord读出的值是未定义的。
片段着色器中这些内置变量固有声明类型如下:

1
2
3
4
5
mediump vec4 gl_FragCoord;
bool gl_FrontFacing;
mediump vec4 gl_FragColor;
mediump vec4 gl_FragData[gl_MaxDrawBuffers];
mediump vec2 gl_PointCoord;

但是它们实际的行为并不像是无存储限定符,而是像上边描述的样子。
这些内置变量拥有全局作用域。

内置常量

以下是提供给顶点着色器或片段着色器的内置常量:

1
2
3
4
5
6
7
8
9
10
11
12
//
// Implementation dependent constants. The example values below
// are the minimum values allowed for these maximums.
//
const mediump int gl_MaxVertexAttribs = 8;
const mediump int gl_MaxVertexUniformVectors = 128;
const mediump int gl_MaxVaryingVectors = 8;
const mediump int gl_MaxVertexTextureImageUnits = 0;
const mediump int gl_MaxCombinedTextureImageUnits = 8;
const mediump int gl_MaxTextureImageUnits = 8;
const mediump int gl_MaxFragmentUniformVectors = 16;
const mediump int gl_MaxDrawBuffers = 1;

内置uniform状态参数

GLSL内置了以下uniform变量用于获取OpenGL ES处理状态。如果一种实现在片段着色器中不支持highp精度并且所列状态为highp,则实际在片段着色器中可使用的是mediump精度。

1
2
3
4
5
6
7
8
9
//
// Depth range in window coordinates
//
struct gl_DepthRangeParameters {
highp float near; // n
highp float far; // f
highp float diff; // f - n
};
uniform gl_DepthRangeParameters gl_DepthRange;

内置函数

在GLSL中还有很多内置的函数,如下边的例子是片段着色器中用来计算镜面光的代码。

1
2
3
float nDotL = dot(normal , light);
float rDotV = dot(viewDir, (2.0 * normal) * nDotL – light);
float specular = specularColor * pow(rDotV, specularPower);

在上边的代码中,使用内置函数 dot来计算两个矢量的点乘积,使用内置函数pow来完成标量的幂计算。
在编写着色程序时,GLSL中有大量的内置函数供使用。绝大多数的内置函数可用于多种着色器,也有一些只适用于一种特定的着色器。这些内置函数大致可分为以下三类:

  • 将一些必要的硬件功能显露成方便调用的函数,如访问纹理图。着色器无法用语言模拟这些函数。
  • 代表一系列琐碎的操作,虽然这些操作可以由用户直接编写完成,但是这些操作都很常用并且可能会有一些硬件支持。编译器处理表达式于汇编指令的映射是非常困难的事情。
  • 代表可获得图形硬件加速的操作,如三角函数属于这一分类。
    很多函数与一些常见的C语言库里的同名函数相似,但这些内置函数不仅支持标量输入,还可以支持矢量输入。应用程序中应当尽量使用这些内置函数而不是有相同计算的自定义代码,因为内置函数很可能是最优化的(如可能是硬件直接支持的)。用户函数可以重载内置函数,但不能将其重定义。
    在下边的内置函数中,函数的输入参数(及相对应的输出)可以是float、vec2、vec3或vec4,则使用genType来作为参数。在实际使用一个函数时,所有的参数类型及返回类型必须是一致的。对于mat也相似,其具体类型可以是mat2、mat3或mat4。
    参数和返回值的精度限定符不显示。对于纹理函数,返回类型的精度与采样器的类型相匹配。
1
2
3
4
uniform lowp sampler2D sampler;
highp vec2 coord;
...
lowp vec4 col = texture2D (sampler, coord); // texture2D returns lowp

其它内置函数的形式参数的精度限定符则无关。调用这些内置函数将会返回一个匹配输入参数的最高精度级的精度限定符。

角度和三角函数

函数参数是以弧度为单位的角度值。以下内置函数是按逐个分量进行操作,但按单个分量操作进行描述。

Syntax Description
genType radians (genType degrees) Converts degrees to radians
genType degrees (genType radians) Converts radians to degrees
genType sin (genType angle) The standard trigonometric sine function.
genType cos (genType angle) The standard trigonometric cosine function.
genType tan (genType angle) The standard trigonometric tangent.
genType asin (genType x) Arc sine. Returns an angle whose sine is x. The range of values returned by this function is[-π/2,π/2] .Results are undefined if ∣x∣ > 1.
genType acos (genType x) Arc cosine. Returns an angle whose cosine is x. The range of values returned by this function is [0, π]. Results are undefined if ∣x∣ > 1.
genType atan (genType y, genType x) Arc tangent. Returns an angle whose tangent is y/x. The signs of x and y are used to determine what quadrant the angle is in . The range of values returned by this function is [−π,π]. Results are undefined if x and y are both 0.
genType atan (genType y_over_x) Arc tangent. Returns an angle whose tangent is y_over_x. The range of values returned by this function is [−π/2,π/2]

指数函数

以下内置函数是按逐个分量进行操作,但按单个分量操作进行描述。

Syntax Description
genType pow (genType x, genType y) Returns x raised to the y power. Results are undefined if x < 0 .Results are undefined if x = 0 and y <= 0.
genType exp (genType x) Returns the natural exponentiation of x.
genType log (genType x) Returns the natural logarithm of x. Results are undefined if x <= 0.
genType exp2 (genType x) Returns 2 raised to the x power.
genType log2 (genType x) Returns the base 2 logarithm of x. Results are undefined if x <= 0.
genType sqrt (genType x) Returns square root of x. Results are undefined if x < 0.
genType inversesqrt (genType x) Returns 1/sqrt(x) . Results are undefined if x <= 0.

通用函数

以下内置函数是按逐个分量进行操作,但按单个分量操作进行描述。

Syntax Description
genType abs (genType x) Returns x if x >= 0, otherwise it returns –x.
genType sign (genType x) Returns 1.0 if x > 0, 0.0 if x = 0, or –1.0 if x < 0
genType floor (genType x) Returns a value equal to the nearest integer that is less than or equal to x
genType ceil (genType x) Returns a value equal to the nearest integer that is greater than or equal to x
genType fract (genType x) Returns x – floor (x)
genType mod (genType x, float y) Modulus (modulo). Returns x – y ∗ floor (x/y)
genType mod (genType x, genType y) Modulus. Returns x – y ∗ floor (x/y)
genType min (genType x, genType y)
genType min (genType x, float y)
Returns y if y < x, otherwise it returns x
genType max (genType x, genType y)
genType max (genType x, float y)
Returns y if x < y, otherwise it returns x.
genType clamp (genType x,genType minVal, genType maxVal)
genType clamp (genType x, float minVal,float maxVal)
Returns min (max (x, minVal), maxVal) Results are undefined if minVal > maxVal.
genType mix (genType x,genType y,genType a)
genType mix (genType x,genType y, float a)
Returns the linear blend of x and y: x(1-a)+ya
genType step (genType edge, genType x)
genType step (float edge, genType x)
Returns 0.0 if x < edge, otherwise it returns 1.0
genType smoothstep (genType edge0,genType edge1,genType x)
genType smoothstep (float edge0,float edge1,genType x)
Returns 0.0 if x <= edge0 and 1.0 if x >= edge1 and performs smooth Hermite interpolation between 0 and 1 when edge0 < x < edge1. This is useful in cases where you would want a threshold function with a smooth transition. This is equivalent to: genType t; t = clamp ((x – edge0) / (edge1 – edge0), 0, 1); return t * t * (3 – 2 * t); Results are undefined if edge0 >= edge1.

几何函数

以下内置函数是按逐个分量进行操作,但按单个分量操作进行描述。

Syntax Description
float length (genType x) Returns the length of vector x.
float distance (genType p0, genType p1) Returns the distance between p0 and p1.
float dot (genType x, genType y) Returns the dot product of x and y.
vec3 cross (vec3 x, vec3 y) Returns the cross product of x and y.
genType normalize (genType x) Returns a vector in the same direction as x but with a length of 1.
genType faceforward(genType N,genType I,genType Nref) If dot(Nref, I) < 0 return N, otherwise return –N.
genType reflect (genType I, genType N) For the incident vector I and surface orientation N,returns the reflection direction: I – 2 ∗ dot(N, I) ∗ N. N must already be normalized in order to achieve the desired result.
genType refract(genType I, genType N,float eta) For the incident vector I and surface normal N, and the ratio of indices of refraction eta, return the refraction vector. The result is computed by k = 1.0 - eta * eta * (1.0 - dot(N, I) * dot(N, I)); if (k < 0.0) return genType(0.0) else return eta * I - (eta * dot(N, I) + sqrt(k)) * N. The input parameters for the incident vector I and thesurface normal N must already be normalized to get the desired results.

矩阵函数

Syntax Description
mat matrixCompMult (mat x, mat y) Multiply matrix x by matrix y component-wise, i.e.,result[i][j] is the scalar product of x[i][j] and y[i][j]. Note: to get linear algebraic matrix multiplication, usethe multiply operator (*).

矢量关系函数

矢量之间的比较关系符号(<, <=, >, >=, ==, !=)被定义(或保留)比较产生一个标量的布尔型结果。使用下边的函数可以得到矢量结果。以下的”bvec”指代”bvec2”、”bvec3”或”bvec4”之一,”ivec”指代”ivec2”、”ivec3”或”ivec4”之一,”vec”指代”vec2”、”vec3”或”vec4”之一。输入参数和返回值各矢量的大小必须一致。

Syntax Description
bvec lessThan(vec x, vec y)
bvec lessThan(ivec x, ivec y)
Returns the component-wise compare of x < y.
bvec lessThanEqual(vec x, vec y)
bvec lessThanEqual(ivec x, ivec y)
Returns the component-wise compare of x <= y.
bvec greaterThan(vec x, vec y)
bvec greaterThan(ivec x, ivec y)
Returns the component-wise compare of x > y.
bvec greaterThanEqual(vec x, vec y)
bvec greaterThanEqual(ivec x, ivec y)
Returns the component-wise compare of x >= y.
bvec equal(vec x, vec y)
bvec equal(ivec x, ivec y)
bvec equal(bvec x, bvec y)
bvec notEqual(vec x, vec y)
bvec notEqual(ivec x, ivec y)
bvec notEqual(bvec x, bvec y)
Returns the component-wise compare of x == y; Returns the component-wise compare of x != y.
bool any(bvec x) Returns true if any component of x is true.
bool all(bvec x) Returns true only if all components of x are true.
bvec not(bvec x) Returns the component-wise logical complement of x.

纹理查找函数

顶点着色器和片段着色器中都可以使用纹理查找函数。但是在顶点着色器中不会计算细节层次(level of detail),所以二者的纹理查找函数略有不同。以下的函数可以通过采样器访问纹理。
只有在片段着色器中才可以使用包含偏移参数(bias)的函数。如果有偏移参数,则在要将其加到细节层次之后再获取纹理。如果没有提供偏移参数,则实现会自动选择细节层次:如果纹理没有mip-map,则直接使用纹理。如果有mip-map且在片段着色器中,则会使用实现计算出来的LOD来进行纹理查找。如果有mip-map且在顶点着色器中,则会使用基础纹理。以”LoD”为前缀的函数只能用在顶点着色器中。

Syntax Description
vec4 texture2D (sampler2D sampler,vec2 coord )
vec4 texture2D (sampler2D sampler,vec2 coord, float bias)
vec4 texture2DProj (sampler2D sampler,vec3 coord )
vec4 texture2DProj (sampler2D sampler,vec3 coord, float bias)
vec4 texture2DProj (sampler2D sampler,vec4 coord)
vec4 texture2DProj (sampler2D sampler,vec4 coord, float bias)
vec4 texture2DLod (sampler2D sampler,vec2 coord, float lod)
vec4 texture2DProjLod (sampler2D sampler,vec3 coord, float lod)
vec4 texture2DProjLod (sampler2D sampler,vec4 coord, float lod)
Use the texture coordinate coord to do a texture lookup in the 2D texture currently bound to sampler. For the projective(“Proj”) versions, the texture coordinate(coord.s, coord.t) is divided by the lastcomponent of coord. The third componentof coord is ignored for the vec4 coordvariant.
vec4 textureCube (samplerCube sampler,vec3 coord )
vec4 textureCube (samplerCube sampler,vec3 coord, float bias )
vec4 textureCubeLod (samplerCube sampler,vec3 coord, float lod)
Use the texture coordinate coord to do a texture lookup in the cube map texture currently bound to sampler. The direction of coord is used to select which face to do a 2-dimensional texture lookup in

REFERENCE

OpenGL ES Common Profile Specification
OpenGL ES 2.0 ProgrammingGuide
The OpenGL ES Shading Language
OpenGL编程指南(原书第8版)