跳转至

6.3 GLSL

GLSL

你已经看到了一些用GLSL编写的简短简单的着色器程序示例。实际上,着色器程序通常相当简短,但并不总是那么简单。为了理解我们将在本书的其余部分中使用的更复杂的着色器,你需要更多地了解GLSL。本节旨在简要介绍该语言的主要特点。这是一个相当技术性的章节。你应该阅读它以熟悉GLSL,并在需要时将其作为参考。

对于WebGL 1.0,着色器必须用GLSL ES 1.00版本编写。WebGL 2.0可以使用1.00版本或3.00版本,但某些WebGL 2.0的功能只有在用GLSL ES 3.00编写着色器时才可用。尽管两个版本的GLSL非常相似,但存在重大差异和不兼容性。除非另有说明,这里的讨论适用于两个版本。

着色器程序中的顶点着色器和片段着色器必须使用相同版本的GLSL编写。GLSL ES 3.00着色器程序必须以以下行开始:

#version 300 es

这必须是着色器源代码的非常第一行。它甚至不能由空行或注释先行。不以这行开始的着色器是1.00版本的着色器。1.00版本的着色器不包括版本号的声明。

You have seen a few short, simple examples of shader programs written in GLSL. In fact, shader programs are often fairly short, but they are not always so simple. To understand the more complex shaders that we will be using in the rest of this book, you will need to know more about GLSL. This section aims to give a short introduction to the major features of the language. This is a rather technical section. You should read it to get some familiarity with GLSL, and then use it as a reference when needed.

For WebGL 1.0, shaders must be written in version 1.00 of GLSL ES. WebGL 2.0 can use either version 1.00 or version 3.00, but some features of WebGL 2.0 are only available when shaders are written in GLSL ES 3.00. Although the two versions of GLSL are very similar, there are major differences and incompatibilities. Unless otherwise noted, the discussion here applies to both versions.

The vertex shader and the fragment shader in a shader program must be written using the same version of GLSL. A GLSL ES 3.00 shader program must begin with the line

#version 300 es

This must be the very first line of the shader source code. It cannot even be preceded by blank lines or comments. A shader program that does not start with this line is a version 1.00 shader. A version 1.00 shader does not include a declaration of the version number.

6.3.1 基本类型

Basic Types

在GLSL中,变量在使用前必须声明。GLSL是一种严格类型化的语言,每个变量在声明时都会被赋予一个类型。

GLSL有内置类型来表示标量(即单个值)、向量和矩阵。标量类型有floatintbool。3.00版本增加了无符号整型,uint。GPU可能不会在硬件层面支持整数或布尔值,因此intbool类型实际上是用浮点值表示的。

vec2vec3vec4类型分别表示两个、三个和四个floats的向量。还有表示ints的向量类型(ivec2ivec3ivec4)和布尔值的向量类型(bvec2、bvec3和bvec4)——以及在3.00版本中,表示无符号整数的向量类型(uvec2、uvec3和uvec4)。GLSL对于引用向量分量的符号非常灵活。访问它们的一种是使用数组符号。例如,如果v是一个四分量向量,那么它的分量可以作为v[0]v[1]v[2]v[3]访问。但它们也可以使用点符号作为v.xv.yv.zv.w访问。分量名称x、y、z和w适用于保存坐标的向量。然而,向量也可以用于表示颜色,v的分量也可以称为v.rv.gv.bv.a。最后,它们可以称为v.sv.tv.pv.q——适用于纹理坐标的名称。

此外,GLSL允许你在点后面使用多个分量名称,如v.rgb或v.zx甚至v.yyy。名称可以按任何顺序排列,允许重复。这称为swizzlingv.zx是swizzler的一个例子。v.zx符号可以在表达式中作为一个两分量向量使用。例如,如果vvec4(1.0,2.0,3.0,4.0),那么v.zx等同于vec2(3.0,1.0),而v.yyy就像vec3(2.0,2.0,2.0)。只要它们不包含重复的分量,swizzlers甚至可以用在赋值的左侧。例如,

vec4 coords = vec4(1.0, 2.0, 3.0, 4.0);
vec3 point = vec3(5.0, 6.0, 7.0);
coords.yzw = coords.wyz;  // 现在,coords是(1.0, 4.0, 2.0, 3.0)
point.xy = coords.xx;     // 现在,point是(1.0, 1.0, 7.0)

vec2(1.0, 2.0)这样的符号被称为“构造函数”,尽管它不是Java或C++中的意义上的构造函数,因为GLSL不是面向对象的,也没有new运算符。GLSL中的构造函数由类型名称后跟括号中的表达式列表组成,它表示由类型名称指定的类型的值。可以使用任何类型名称,包括标量类型。该值由括号中的表达式的值构建。一个表达式可以为构建的值贡献多个值;我们已经在像

vec2 v = vec2( 1.0, 2.0 );
vec4 w = vec4( v, v );  // w是( 1.0, 2.0, 1.0, 2.0 )

注意,表达式可以是swizzlers:

vec3 v = vec3( 1.0, 2.0, 3.0 );
vec3 w = vec3( v.zx, 4.0 );  // w是( 3.0, 1.0, 4.0 )

最后一个参数的额外值将被丢弃。这使得可以使用构造函数来缩短向量。然而,拥有一些根本没有为结果贡献值的额外参数是不合法的:

vec4 rgba = vec4( 0.1, 0.2, 0.3, 0.4 );
vec3 rgb = vec3( rgba );  // 从rgba中取3个项目;rgb是(0.1, 0.2, 0.3)
float r = float( rgba );  // r是0.1
vec2 v = vec2( rgb, rgba );    // 错误:没有使用rgba中的值。

作为一种特殊情况,当从一个单一的标量值构造向量时,向量的所有分量都将被设置为该值:

vec4 black = vec4( 1.0 );  // black是( 1.0, 1.0, 1.0, 1.0 )

当构造内置类型时,如果需要,将应用类型转换。就转换而言,布尔值true/false转换为数值零和一;反过来,零转换为false,任何其他数值转换为true。据我所知,构造函数是GLSL中唯一的自动类型转换的上下文。例如,你需要使用构造函数将int值赋给float变量,直接将int加到float是非法的:

int k = 1;
float x = float(k);  // 好的;"x = k"将是类型不匹配错误
x = x + 1.0;         // 好的
x = x + 1;           // 错误:不能添加不同类型的值。

内置的矩阵类型是mat2mat3mat4。它们分别表示二乘二、三乘三和四乘四的浮点数矩阵。(没有整数或布尔值的矩阵,但有一些额外的矩阵类型用于表示非方阵。)矩阵的元素可以使用数组符号访问,如M[2][1]。如果使用单一索引,如M[2],结果是向量。例如,如果Mmat4类型,那么M[2]vec4。GLSL中的数组,像OpenGL中的一样,使用列主序。这意味着M[2]是M中的第二列,而不是第二行(就像在Java中一样),M[2][1]是第二列和第一行的元素。

矩阵可以从适当数量的值构造,这些值可以作为标量、向量或矩阵提供。例如,mat3可以从九个float或三个vec3参数构造:

mat3 m1 = mat3( 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0 );
vec3 v = vec3( 1, 2, 3 );
mat3 m2 = mat3( v, v, v );

请记住,矩阵是以列主序填充的;也就是说,前三个数字进入第0列,接下来的三个进入第1列,最后三个进入第2列。

作为一种特殊情况,如果矩阵M从一个单一的标量值构造,那么该值将放入M的所有对角元素中(M[0][0]M[1][1]等)。非对角元素都设置为零。例如,mat4(1.0)构造了四乘四的单位矩阵。

唯一其他内置类型是所谓的“采样器类型”,用于访问纹理。采样器类型只能以有限的方式使用。它们不是数值类型,也不能转换为数值类型。它们将在下一部分中介绍。

Variables in GLSL must be declared before they are used. GLSL is a strictly typed language, and every variable is given a type when it is declared.

GLSL has built-in types to represent scalars (that is, single values), vectors, and matrices. The scalar types are float, int, and bool. Version 3.00 adds an unsigned integer type, uint. A GPU might not support integers or booleans on the hardware level, so it is possible that the int and bool types are actually represented as floating point values.

The types vec2, vec3, and vec4 represent vectors of two, three, and four floats. There are also types to represent vectors of ints (ivec2, ivec3, and ivec4) and bools (bvec2, bvec3, and bvec4) — and, in version 3.00, of unsigned integers (uvec2, uvec3, and uvec4). GLSL has very flexible notation for referring to the components of a vector. One way to access them is with array notation. For example, if v is a four-component vector, then its components can be accessed as v[0], v[1], v[2], and v[3]. But they can also be accessed using the dot notation as v.x, v.y, v.z, andv.w. The component names x, y, z, and w are appropriate for a vector that holds coordinates. However, vectors can also be used to represent colors, and the components of v can alternatively be referred to as v.r, v.g, v.b, and v.a. Finally, they can be referred to as v.s, v.t, v.p, and v.q — names appropriate for texture coordinates.

Furthermore, GLSL allows you to use multiple component names after the dot, as in v.rgb or v.zx or even v.yyy. The names can be in any order, and repetition is allowed. This is called swizzling, and v.zx is an example of a swizzler. The notation v.zx can be used in an expression as a two-component vector. For example, if v is vec4(1.0,2.0,3.0,4.0), then v.zx is equivalent to vec2(3.0,1.0), and v.yyy is like vec3(2.0,2.0,2.0). Swizzlers can even be used on the left-hand side of an assignment, as long as they don't contain repeated components. For example,

vec4 coords = vec4(1.0, 2.0, 3.0, 4.0);
vec3 point = vec3(5.0, 6.0, 7.0);
coords.yzw = coords.wyz;  // Now, coords is (1.0, 4.0, 2.0, 3.0)
point.xy = coords.xx;     // Now, point is (1.0, 1.0, 7.0)

A notation such as vec2(1.0, 2.0) is referred to as a "constructor," although it is not a constructor in the sense of Java or C++, since GLSL is not object-oriented, and there is no new operator. A constructor in GLSL consists of a type name followed by a list of expressions in parentheses, and it represents a value of the type specified by the type name. Any type name can be used, including the scalar types. The value is constructed from the values of the expressions in parentheses. An expression can contribute more than one value to the constructed value; we have already seen this in examples such as

vec2 v = vec2( 1.0, 2.0 );
vec4 w = vec4( v, v );  // w is ( 1.0, 2.0, 1.0, 2.0 )

Note that the expressions can be swizzlers:

vec3 v = vec3( 1.0, 2.0, 3.0 );
vec3 w = vec3( v.zx, 4.0 );  // w is ( 3.0, 1.0, 4.0 )

Extra values from the last parameter will be dropped. This makes is possible to use a constructor to shorten a vector. However, it is not legal to have extra parameters that contribute no values at all to the result:

vec4 rgba = vec4( 0.1, 0.2, 0.3, 0.4 );
vec3 rgb = vec3( rgba );  // takes 3 items from rgba; rgb is (0.1, 0.2, 0.3)
float r = float( rgba );  // r is 0.1
vec2 v = vec2( rgb, rgba );    // ERROR: No values from rgba are used.

As a special case, when a vector is constructed from a single scalar value, all components of the vector will be set equal to that value:

vec4 black = vec4( 1.0 );  // black is ( 1.0, 1.0, 1.0, 1.0 )

When constructing one of the built-in types, type conversion will be applied if necessary. For purposes of conversion, the boolean values true/false convert to the numeric values zero and one; in the other direction, zero converts to false and any other numeric value converts to true. As far as I know, constructors are the only context in which GLSL does automatic type conversion. For example, you need to use a constructor to assign an int value to a float variable, and it is illegal to add an int to a float:

int k = 1;
float x = float(k);  // OK; "x = k" would be a type mismatch error
x = x + 1.0;         // OK
x = x + 1;           // ERROR: Can't add values of different types.

The built-in matrix types are mat2, mat3, and mat4. They represent, respectively, two-by-two, three-by-three, and four-by-four matrices of floating point numbers. (There are no matrices of integers or booleans, but there are some additional matrix types for representing non-square matrices.) The elements of a matrix can be accessed using array notation, such as M[2][1]. If a single index is used, as in M[2], the result is a vector. For example, if M is of type mat4, then M[2] is a vec4. Arrays in GLSL, as in OpenGL, use column-major order. This means that M[2] is column number 2 in M rather than row number 2 (as it would be in Java), and M[2][1] is the element in column 2 and row 1.

A matrix can be constructed from the appropriate number of values, which can be provided as scalars, vectors or matrices. For example, a mat3 can be constructed from nine float or from three vec3 parameters:

mat3 m1 = mat3( 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0 );
vec3 v = vec3( 1, 2, 3 );
mat3 m2 = mat3( v, v, v );

Keep in mind that the matrix is filled in column-major order; that is, the first three numbers go into column 0, the next three into column 1, and the last three into column 2.

As a special case, if a matrix M is constructed from a single scalar value, then that value is put into all the diagonal elements of M (M[0][0], M[1][1], and so on). The non-diagonal elements are all set equal to zero. For example, mat4(1.0) constructs the four-by-four identity matrix.

The only other built-in types are the so-called "sampler types", which are used for accessing textures. The sampler types can be used only in limited ways. They are not numeric types and cannot be converted to or from numeric types. The will be covered in the next section.

6.3.2 数据结构

Data Structures

GLSL程序可以使用struct关键字定义新类型。语法与C语言相同,但有一些限制。一个结构由一系列命名成员组成,这些成员可以是不同类型的。成员的类型可以是任何内置类型、数组类型或之前定义的结构类型。例如:

struct LightProperties {
    vec4 position;
    vec3 color;
    float intensity;
};

这定义了一个名为LightProperties的类型。这个类型可以用来声明变量:

LightProperties light;

然后,变量light的成员被称为light.positionlight.colorlight.intensity。结构类型有构造函数,但它们的构造函数不支持类型转换:构造函数必须包含一个值列表,其类型与结构中相应成员的类型完全匹配。例如:

light = LightProperties( vec4(0.0, 0.0, 0.0, 1.0), vec3(1.0), 1.0 );

GLSL还支持数组。只允许一维数组。数组的基础类型可以是任何基本类型,也可以是结构类型。数组的大小必须在变量声明中指定为整型常量。例如:

int A[10];
vec3 palette[8];
LightProperties lights[3];

在1.00版本中,没有数组构造函数,也不能在声明时初始化数组。3.00版本确实有数组构造函数,并允许使用类型名称,如“int[10]”,表示一个包含10个整数的数组:

int[4] B; // B是一个包含4个int的数组;仅限GLSL ES 3.00!
B = int[4] (2, 3, 5, 7);  // 数组构造函数;仅限GLSL ES 3.00!

数组索引使用通常的语法,如A[0]palette[i+1]lights[3].color。在GLSL ES 1.00中,对于可以用作数组索引的表达式有一些严格的限制。除了一个例外,用作数组索引的表达式只能包含整型常量和for循环变量(即在for循环中用作循环控制变量的变量)。例如,表达式palette[i+1]只有在形式为for (int i = ....的for循环中才是合法的。唯一的例外是,在顶点着色器中的uniforms数组(且仅当数组不包含采样器时)可以使用任意索引表达式。请注意,这些限制在GLSL ES 3.00中不适用。

就像在C语言中一样,没有对数组索引越界错误进行检查。程序员需要确保数组索引是有效的。

A GLSL program can define new types using the struct keyword. The syntax is the same as in C, with some limitations. A struct is made up of a sequence of named members, which can be of different types. The type of a member can be any of the built-in types, an array type, or a previously defined struct type. For example,

struct LightProperties {
    vec4 position;
    vec3 color;
    float intensity;
};

This defines a type named LightProperties. The type can be used to declare variables:

LightProperties light;

The members of the variable light are then referred to as light.position, light.color, and light.intensity. Struct types have constructors, but their constructors do not support type conversion: The constructor must contain a list of values whose types exactly match the types of the corresponding members in the struct. For example,

light = LightProperties( vec4(0.0, 0.0, 0.0, 1.0), vec3(1.0), 1.0 );

GLSL also supports arrays. Only one-dimensional arrays are allowed. The base type of an array can be any of the basic types or it can be a struct type. The size of the array must be specified in the variable declaration as an integer constant. For example

int A[10];
vec3 palette[8];
LightProperties lights[3];

In version 1.00, there are no array constructors, and it is not possible to initialize an array as part of its declaration. Version 3.00 does have array constructors, and it allows type names such as "int[10], representing an array of 10 integers:

int[4] B; // B is an array of 4 ints; GLSL ES 3.00 only!
B = int[4] (2, 3, 5, 7);  // Array constructor; GLSL ES 3.00 only!

Array indexing uses the usual syntax, such as A[0] or palette[i+1] or lights[3].color. In GLSL ES 1.00, there are some strong limitations on the expressions that can be used as array indices. With one exception, an expression that is used as the index for an array can contain only integer constants and for loop variables (that is, variables that are used as loop control variables in for loops). For example, the expression palette[i+1] would only be legal inside a for of the form for (int i = .... The single exception is that arbitrary index expressions can be used for arrays of uniforms in a vertex shader (and then only if the array does not contain samplers). Note that these restrictions do not apply in GLSL ES 3.00.

Just as in C, there is no check for array index out of bounds errors. It is up to the programmer to make sure that array indices are valid.

6.3.3 预选赛

Qualifiers

变量声明可以通过各种限定符进行修改。你已经看到了限定符attributeuniformvarying的例子。这些被称为存储限定符。在3.00版本中,没有attributevarying限定符;相反,在顶点着色器中使用存储限定符in声明属性,在顶点着色器中使用out声明变化变量,在片段着色器中使用inuniform限定符在两个版本中都使用。只有全局变量,而不是函数定义中的局部变量,才能是属性、统一或变化变量。

attribute限定符只能在GLSL ES 1.00顶点着色器中使用,并且它只适用于内置浮点类型floatvec2vec3vec4mat2mat3mat4。(矩阵属性在JavaScript方面没有直接支持。矩阵属性必须被视为一组向量属性,每个矩阵列一个。列的属性位置是连续的整数,WebGL函数gl.getAttribLocation将返回第一列的位置。矩阵属性可能很少见,但对于实例化绘制可能很有用,我不会在这里详细介绍它们。)

在GLSL ES 3.00中,顶点着色器变量上的in限定符将其定义为属性变量,并且它可以应用于整数和无符号整数标量和向量,以及浮点类型。

同样在GLSL ES 3.00中,out限定符可以在片段着色器中的整数和浮点标量和向量上使用。在1.00版本中,片段着色器有预定义变量gl_FragColor,类型为vec4,用于指定像素的颜色。在3.00版本中,片段着色器可以有多个输出,输出不一定是颜色。因为输出类型不一定是vec4,所以不可能有一个预定义的输出变量。目前,我们只会使用一个代表颜色的片段着色器输出。所以,3.00版本的片段着色器将有一个类型为vec4的输出变量。(当我们在第7.4节讨论帧缓冲区时,我们将看到如何使用多个输出。)

顶点着色器和片段着色器都可以使用uniform变量。只要两个着色器中的类型相同,同一个变量就可以出现在两个着色器中。统一变量可以是任何类型,包括数组和结构类型。现在,JavaScript只有用于设置标量变量、向量或矩阵的统一值的函数。没有用于设置结构或数组值的函数。解决这个问题的方法是将结构或数组的每个组件视为单独的统一值。例如,考虑声明:

struct LightProperties {
    vec4 position;
    vec3 color;
    float intensity;
};
uniform LightProperties light[4];

变量light包含十二个基本值,类型为vec4vec3float。要在JavaScript中使用light统一变量,我们需要十二个变量来表示统一变量的12个组件的位置。当使用gl.getUniformLocation获取12个组件之一的位置时,你需要在GLSL程序中给出组件的完整名称。例如:gl.getUniformLocation(prog, "light[2].color")。很自然地将12个位置存储在与GLSL端的结构数组平行的JavaScript对象数组中。以下是创建结构并使用它初始化统一变量的典型JavaScript代码:

lightLocations = new Array(4);
for (i = 0; i < light.length; i++) {
    lightLocations[i] = {
        position: gl.getUniformLocation(prog, "light[" + i + "].position" ),
        color: gl.getUniformLocation(prog, "light[" + i + "].color" ),
        intensity: gl.getUniformLocation(prog, "light[" + i + "].intensity" )
    };
}

for (i = 0; i < light.length; i++) {
    gl.uniform4f( lightLocations[i].position, 0, 0, 0, 1 );
    gl.uniform3f( lightLocations[i].color, 1, 1, 1 );
    gl.uniform1f( lightLocations[i].intensity, 0 );    
}

对于矩阵统一着色器变量,用于设置统一值的JavaScript函数是gl.uniformMatrix2fv用于mat2gl.uniformMatrix3fv用于mat3,或gl.uniformMatrix4fv用于mat4。即使矩阵是二维的,值也存储在一维数组中。值以列主序的方式加载到数组中。例如,如果transform是着色器中的统一mat3,则JavaScript可以将其值设置为单位矩阵:

transformLoc = gl.getUniformLocation(prog, "transform");
gl.uniformMatrix3fv( transformLoc, false, [ 1,0,0, 0,1,0, 0,0,1 ] );

在1.00版本中,第二个参数必须是false。在3.00版本中,第二个参数可以是true,表示矩阵的条目以行主序而不是列主序提供。注意,uniformMatrix3fv中的3指的是矩阵的行数和列数,而不是数组的长度,数组的长度必须是9。(顺便说一下,对于统一变量的值,使用类型化数组而不是普通的JavaScript数组是可以的。)

变化变量应在顶点着色器和片段着色器中以相同的名称和类型声明。在1.00版本中,声明变化变量的存储限定符是varying,它只能用于内置浮点类型(floatvec2vec3vec4mat2mat3mat4)和这些类型的数组。

在3.00版本中,变化变量也可以是整数或无符号整数标量或向量。但有一个复杂情况,因为对整数值应用插值是没有意义的。所以,整型变化变量必须用额外的限定符flat声明,这意味着它不会被插值。相反,三角形或线段的第一个顶点的值将用于每个像素。(浮点变化变量也可以选择性地声明为flat。)例如:

flat in ivec3 A;  // 仅限GLSL ES 3.00片段着色器!

另一种可能的存储限定符是const,这意味着变量的值在初始化后不能更改。const变量的声明必须包括初始化。


变量声明也可以通过精度限定符进行修改。可能的精度限定符是highpmediumplowp。精度限定符为整型变量设置了最小值范围,或为浮点变量设置了最小值范围和最小小数位数。GLSL没有为精度限定符分配确切的含义,但规定了一些最低要求。例如,在1.00版本中,lowp整数必须能够表示至少在-28到28范围内的值;mediump整数,在-210到210范围内;highp整数,在-216到216范围内。对于3.00版本,highp变量始终使用32位,mediumplowp的要求更高。甚至可能所有值都是32位值,精度限定符没有实际效果。但是,嵌入式系统的GPU可能更有限。

精度限定符可用于任何变量声明,包括局部变量和函数参数。如果变量还有存储限定符,则存储限定符放在第一位。例如

lowp int n;
varying highp float v;
uniform mediump vec3 colors[3];

变化变量在顶点着色器和片段着色器中可以有不同的精度。顶点着色器中整数和浮点数的默认精度是highp。片段着色器不一定支持highp,尽管它们很可能会这样做,除非可能是在旧的移动硬件上。在片段着色器中,整数的默认精度是mediump,但浮点数没有默认精度。这意味着片段着色器中的每个浮点变量都必须显式分配精度。或者,可以使用语句为浮点数设置默认精度

precision mediump float;

这在上一节的每个片段着色器开头都使用了。当然,如果片段着色器确实支持highp,这会不必要地限制精度。你可以通过在片段着色器开头使用以下代码来避免这种情况:

#ifdef GL_FRAGMENT_PRECISION_HIGH
    precision highp float;
#else
    precision mediump float;
#endif

如果可用,则将默认精度设置为highp,如果不可用,则设置为mediump。以"#"开头的行是预处理器指令——GLSL的一个方面,我不想深入。


下一个限定符,invariant,更难以解释,它的用途也有限。不变性指的是当相同的表达式用于计算同一个变量的值(可能在不同的着色器中)时,分配给变量的值在两种情况下应该完全相同。这并不自动成立。例如,如果编译器在两个表达式中使用不同的优化或以不同的顺序评估操作数,得到的值可能会不同。变量上的不变性限定符将强制编译器对两个赋值语句使用完全相同的计算。这个限定符只能用在变化变量的声明上。它必须是声明中的第一个限定符。例如:

invariant varying mediump vec3 color;

不变性限定符也可以用于使内置变量(如 gl_Positiongl_FragCoord)不变,使用如下语句:

invariant gl_Position;

在多阶段算法中,不变性可能很重要,该算法连续应用两个或更多的着色器程序来计算图像。例如,当两个着色器使用相同的表达式计算同一个顶点的 gl_Position 时,确保它们得到相同的结果是很重要的。在着色器中使 gl_Position 不变将确保这一点。

最后,"布局"(layout)限定符仅在3.00版本中可用。它可以用来指定属性变量的整型ID,作为使用 JavaScript 函数 gl.getAttribLocation() 查询ID的替代。例如:

layout(location = 0) in vec3 a_coords; // 仅限 GLSL ES 3.00 顶点着色器!

GLSL ES 3.00 片段着色器中,如果有多个输出,也可以使用相同类型的布局限定符。在这种情况下,它指定了该变量应该使用的几个输出目标中的哪一个。

希望这些信息对你有所帮助。如果你有更多关于 GLSL 或其他编程语言的问题,请随时提问。

Variable declarations can be modified by various qualifiers. You have seen examples of the qualifiers attribute, uniform, and varying. These are called storage qualifiers. The qualifiers attribute and varying do not exist in version 3.00; instead, an attribute is declared in the vertex shader using the storage qualifier in, and a varying variable is declared using out in the vertex shader and in in the fragment shader. The uniform qualifier is used in both versions. Only global variables, not local variables in function definition, can be attribute, uniform, or varying variables.

The attribute qualifier can only be used in a GLSL ES 1.00 vertex shader, and it only applies to the built-in floating point types float, vec2, vec3, vec4, mat2, mat3, and mat4. (Matrix attributes are not supported directly on the JavaScript side. A matrix attribute has to be treated as a set of vector attributes, one for each column. The attribute locations for the columns are successive integers, and the WebGL function gl.getAttribLocation will return the location for the first column. Matrix attributes would be rare, though perhaps useful for instanced drawing, and I won't go into further detail about them here.)

In GLSL ES 3.00, the in qualifier on a vertex shader variable defines it to be an attribute variable, and it can be applied to integer and unsigned integer scalars and vectors, as well as to the floating point types.

Also in GLSL ES 3.00, the out qualifier can be used on integer and floating point scalars and vectors in the fragment shader. In version 1.00, a fragment shader has the predefined variable gl_FragColor of type vec4 to specify the color of the pixel. In version 3.00, a fragment shader can have multiple outputs, and the outputs are not necessarily colors. Because the output type does not have to be vec4, it is not possible to have a predefined output variable. For now, we will only use one fragment shader output representing a color. So, a version 3.00 fragment shader will have one out variable of type vec4. (When we discuss framebuffers in Section 7.4, we will see how multiple outputs can be used.)

Both the vertex shader and the fragment shader can use uniform variables. The same variable can occur in both shaders, as long as the types in the two shaders are the same. Uniform variables can be of any type, including array and structure types. Now, JavaScript only has functions for setting uniform values that are scalar variables, vectors, or matrices. There are no functions for setting the values of structs or arrays. The solution to this problem requires treating every component of a struct or array as a separate uniform value. For example, consider the declarations

struct LightProperties {
    vec4 position;
    vec3 color;
    float intensity;
};
uniform LightProperties light[4];

The variable light contains twelve basic values, which are of type vec4, vec3, or float. To work with the light uniform in JavaScript, we need twelve variables to represent the locations of the 12 components of the uniform variable. When using gl.getUniformLocation to get the location of one of the 12 components, you need to give the full name of the component in the GLSL program. For example: gl.getUniformLocation(prog, "light[2].color"). It is natural to store the 12 locations in an array of JavaScript objects that parallels the structure of the array of structs on the GLSL side. Here is typical JavaScript code to create the structure and use it to initialize the uniform variables:

lightLocations = new Array(4);
for (i = 0; i < light.length; i++) {
    lightLocations[i] = {
        position: gl.getUniformLocation(prog, "light[" + i + "].position" );
        color: gl.getUniformLocation(prog, "light[" + i + "].color" );
        intensity: gl.getUniformLocation(prog, "light[" + i + "].intensity" );
    };
}

for (i = 0; i < light.length; i++) {
    gl.uniform4f( lightLocations[i].position, 0, 0, 0, 1 );
    gl.uniform3f( lightLocations[i].color, 1, 1, 1 );
    gl.uniform1f( lightLocations[i].intensity, 0 );    
}

For uniform shader variables that are matrices, the JavaScript function that is used to set the value of the uniform is gl.uniformMatrix2fv for a mat2, gl.uniformMatrix3fv for a mat3, or gl.uniformMatrix4fv for a mat4. Even though the matrix is two-dimensional, the values are stored in a one dimensional array. The values are loaded into the array in column-major order. For example, if transform is a uniform mat3 in the shader, then JavaScript can set its value to be the identity matrix with

transformLoc = gl.getUniformLocation(prog, "transform");
gl.uniformMatrix3fv( transformLoc, false, [ 1,0,0, 0,1,0, 0,0,1 ] );

In Version 1.00, the second parameter must be false. In Version 3.00, the second parameter can be true to indicate that the entries of the matrix are provided in row-major rather than column-major order. Note that the 3 in uniformMatrix3fv refers to the number of rows and columns in the matrix, not to the length of the array, which must be 9. (By the way, it is OK to use a typed array rather than a normal JavaScript array for the value of a uniform.)

A varying variable should be declared with the same name and type in both the vertex shader and fragment shader. In version 1.00, the storage qualifier for declaring varying variables is varying, and it can only be used for the built-in floating point types (float, vec2, vec3, vec4, mat2, mat3, and mat4) and for arrays of those types.

In version 3.00, a varying variable can also be an integer or unsigned integer scalar or vector. But there is a complication because it doesn't make sense to apply interpolation to integer values. So, a varying variable of integer type must be declared with the additional qualifier flat, which means it will not be interpolated. Instead, the value from the first vertex of a triangle or line segment will be used for every pixel. (Floating point varying variables can also, optionally, be declared as flat.) For example:

flat in ivec3 A;  // GLSL ES 3.00 fragment shader only!

Another possible storage qualifier is const, which means that the value of the variable cannot be changed after it has been initialized. The declaration of a const variable must include initialization.


A variable declaration can also be modified by precision qualifiers. The possible precision qualifiers are highp, mediump, and lowp. A precision qualifier sets the minimum range of possible values for an integer variable or the minimum range of values and number of decimal places for a floating point variable. GLSL doesn't assign a definite meaning to the precision qualifiers, but mandates some minimum requirements. For example, in version 1.00, lowp integers must be able to represent values in at least the range −28 to 28; mediump integers, in the range −210 to 210; and highp integers, in the range −216 to 216. For version 3.00, highp variables always use 32 bits, and the requirements for mediump and lowp are higher. It is even possible that all values are 32-bit values and the precision qualifiers have no real effect. But GPUs in embedded systems can be more limited.

A precision qualifier can be used on any variable declaration, including local variables and function parameters. If the variable also has a storage qualifier, the storage qualifier comes first. For example

lowp int n;
varying highp float v;
uniform mediump vec3 colors[3];

A varying variable can have different precisions in the vertex and in the fragment shader. The default precision for integers and floats in the vertex shader is highp. Fragment shaders are not required to support highp, although it is likely that they do so, except perhaps on older mobile hardware. In the fragment shader, the default precision for integers is mediump, but floats do not have a default precision. This means that every floating point variable in the fragment shader has to be explicitly assigned a precision. Alternatively, it is possible to set a default precision for floats with the statement

precision mediump float;

This statement was used at the start of each of the fragment shaders in the previous section. Of course, if the fragment shader does support highp, this restricts the precision unnecessarily. You can avoid that by using this code at the start of the fragment shader:

#ifdef GL_FRAGMENT_PRECISION_HIGH
    precision highp float;
#else
    precision mediump float;
#endif

This sets the default precision to highp if it is available and to mediump if not. The lines starting with "#" are preprocessor directives—an aspect of GLSL that I don't want to get into.


The next qualifier, invariant, is even more difficult to explain, and it has only a limited use. Invariance refers to the requirement that when the same expression is used to compute the value of the same variable (possibly in different shaders), then the value that is assigned to the variable should be exactly the same in both cases. This is not automatically the case. For example, the values can be different if a compiler uses different optimizations or evaluates the operands in a different order in the two expressions. The invariant qualifier on the variable will force the compiler to use exactly the same calculations for the two assignment statements. The qualifier can only be used on declarations of varying variables. It must be the first qualifier in the declaration. For example,

invariant varying mediump vec3 color;

It can also be used to make the predefined variables such as gl_Position and gl_FragCoord invariant, using a statement such as

invariant gl_Position;

Invariance can be important in a multi-pass algorithm that applies two or more shader programs in succession to compute an image. It is important, for example, that both shaders get the same answer when they compute gl_Position for the same vertex, using the same expression in both vertex shaders. Making gl_Position invariant in the shaders will ensure that.


The last type of qualifier, a "layout" qualifier, is only available in version 3.00. It can be used to specify the integer ID of an attribute variable, as an alternative to using the JavaScript function gl.getAttribLocation() to query the ID. An example would be

layout(location = 0) in vec3 a_coords; // GLSL ES 3.00 vertex shader only!

The same kind of layout qualifier can be used on an out variable in a GLSL ES 3.00 fragment shader that has multiple outputs. In that case, it specifies which of several output destinations should be used for that variable.

6.3.4 表达式

Expressions

在GLSL中,表达式可以使用算术运算符+、-、、/、++和--,用于整数和浮点数值。在3.00版本中,还为整数类型提供了余数运算符%,以及左移和右移位运算符和位逻辑运算符。表达式中没有自动类型转换。如果xfloat类型,表达式x+1是非法的。你必须说x+1.0x+float(1)*。

算术运算符已经以各种方式扩展,以适应向量和矩阵运算。如果你使用来乘以一个矩阵和一个向量,无论是哪种顺序,它都会以线性代数的方式进行乘法,得到一个向量作为结果。操作数的类型必须以显而易见的方式匹配;例如,vec3只能与mat3相乘,结果是vec3。当用于两个相同大小的矩阵时,执行矩阵乘法。

如果对具有相同基本类型的向量和标量使用+、-、或/,则对向量的每个元素执行操作。例如,vec2(3.0,3.0) / 2.0是向量vec2(1.5,1.5)2ivec3(1,2,3)是向量ivec3(2,4,6)。当这些运算符应用于两个相同类型的向量时,对每对分量执行操作,结果是向量。例如,表达式的值

vec3( 1.0, 2.0, 3.0 ) + vec3( 4.2, -7.0, 1.7 )

是向量vec3(5.2,-5.0,4.7)。特别注意,通常的向量运算操作——向量的加法和减法、向量乘以标量、向量乘以矩阵——在GLSL中以自然的方式书写。

关系运算符<、>、<=和>=只能应用于整数和浮点标量,两个操作数的类型必须完全匹配。然而,等式运算符==和!=已扩展到适用于除采样器类型之外的所有内置类型。两个向量只有在相应的分量对都相等时才相等。矩阵也是如此。等式运算符不能用于数组,但它们适用于结构体,只要结构体不包含任何数组或采样器;同样,两个结构体中的每对成员必须相等,结构体才被视为相等。

GLSL有逻辑运算符!、&&、||和^^(最后一个是异或操作)。操作数必须是bool类型。

最后,还有赋值运算符=、+=、-=、*=和/=,具有通常的含义。


GLSL还有大量的预定义函数,我在这里无法全部讨论。我将在这里提到的所有函数都需要浮点值作为参数,即使该函数对整数值也有意义。

也许最有趣的是向量代数函数。见第3.5节了解这些运算的定义。这些函数有简单的公式,但它们作为函数提供是为了方便,并且因为它们可能在GPU中有高效的硬件实现。函数dot(x,y)计算两个长度相同的向量的点积x·y。返回值是floatcross(x,y)计算叉积x×y,参数和返回值类型为vec3length(x)是向量x的长度,distance(x,y)给出两个向量之间的距离;normalize(x)返回指向与x相同方向的单位向量。还有名为reflectrefract的函数,可以用来计算反射和折射光线的方向;我将在需要使用它们时介绍。

函数mix(x,y,t)计算x(1−t) + yt。如果t是一个在0.0到1.0范围内的float,那么返回值是x和y的线性混合或加权平均。这个函数可能用于例如两种颜色的alpha混合。函数clamp(x,low,high)x限制在lowhigh的范围内;返回值可以计算为min(max(x,low),high)。如果rgb*是一个表示颜色的向量,我们可以通过命令

rgb = clamp( rgb, 0.0, 1.0 );

确保向量的所有分量都在0到1的范围内。

如果st是float,且s < t,那么smoothstep(s,t,x)x小于s时返回0.0,在x大于t时返回1.0。对于xst之间的值,返回值从0.0到1.0平滑插值。以下是一个示例,可能在片段着色器中用于渲染启用透明度的gl.POINTS原语:

float dist = distance( gl_PointCoord, vec2(0.5) );
float alpha = 1.0 - smoothstep( 0.45, 0.5, dist );
if (alpha == 0.0) {
    discard; // 丢弃完全透明的像素
}
gl_FragColor = vec4( 1.0, 0.0, 0.0, alpha );

这将把点渲染成一个红色的圆盘,颜色从圆盘边缘的不透明到透明平滑过渡,随着dist从0.45增加到0.5。注意,对于函数mixclampsmoothstepxy参数可以是向量,也可以是float。在这种情况下,它们分别对向量的每个分量进行操作。

GLSL提供了通常的数学函数,包括sincostanasinacosatanlogexppowsqrtabsfloorceilminmax。(在3.00版本中,abs、min和max也适用于整数类型。)对于这些函数,参数可以是任何floatvec2vec3vec4类型。返回值是相同类型,并且分别对每个分量应用函数。例如,sqrt(vec3(16.0,9.0,4.0))的值是向量vec3(4.0,3.0,2.0)。对于minmax,还有一个第二个版本的函数,其中第一个参数是向量,第二个参数是float。对于这些版本,向量的每个分量都与float进行比较;例如,max(vec3(1.0,2.0,3.0),2.5)vec3(2.5,2.5,3.0)

函数mod(x,y)计算余数,当x除以y时。返回值计算为x − yfloor(x/y)。与minmax一样,y可以是向量或float。mod*函数可以用作%运算符的替代品,后者在GLSL ES 1.00中不受支持。

还有用于处理采样器变量的函数。我将在下一部分中讨论其中的一些。

Expressions in GLSL can use the arithmetic operators +, −, *, /, ++ and −− for integer and floating point values. In version 3.00, the remainder operator, %, as well as left and right shift and bitwise logical operators, are also available for integer types. There is no automatic type conversion in expressions. If x is of type float, the expression x+1 is illegal. You have to say x+1.0 or x+float(1).

The arithmetic operators have been extended in various ways to work with vectors and matrices. If you use * to multiply a matrix and a vector, in either order, it multiplies them in the linear algebra sense, giving a vector as the result. The types of the operands must match in the obvious way; for example, a vec3 can only be multiplied by a mat3, and the result is a vec3. When used with two matrices of the same size, * does matrix multiplication.

If +, −, *, or \/ is used on a vector and a scalar of the same basic type, then the operation is performed on each element of the vector. For example, vec2(3.0,3.0) / 2.0 is the vector vec2(1.5,1.5), and 2ivec3(1,2,3) is the vector ivec3(2,4,6)*. When one of these operators is applied to two vectors of the same type, the operation is applied to each pair of components, and the result is a vector. For example, the value of

vec3( 1.0, 2.0, 3.0 ) + vec3( 4.2, -7.0, 1.7 )

is the vector vec3(5.2,-5.0,4.7). Note in particular that the usual vector arithmetic operations—addition and subtraction of vectors, multiplication of a vector by a scalar, and multiplication of a vector by a matrix—are written in the natural way is GLSL.

The relational operators <, >, <=, and >= can only be applied to integer and floating point scalars, and the types of the two operands must match exactly. However, the equality operators == and != have been extended to work on all of the built-in types except sampler types. Two vectors are equal only if the corresponding pairs of components are all equal. The same is true for matrices. The equality operators cannot be used with arrays, but they do work for structs, as long as the structs don't contain any arrays or samplers; again, every pair of members in two structs must be equal for the structs to be considered equal.

GLSL has logical operators !, &&, ||, and ^^ (the last one being an exclusive or operation). The operands must be of type bool.

Finally, there are the assignment operators =, +=, −=, *=, and /=, with the usual meanings.


GLSL also has a large number of predefined functions, more than I can discuss here. All of the functions that I will mention here require floating-point values as parameters, even if the function would also make sense for integer values.

Most interesting, perhaps, are functions for vector algebra. See Section 3.5 for the definitions of these operations. These functions have simple formulas, but they are provided as functions for convenience and because they might have efficient hardware implementations in a GPU. The function dot(x,y) computes the dot product x·y of two vectors of the same length. The return value is a float; cross(x,y) computes the cross product x×y, where the parameters and return value are of type vec3; length(x) is the length of the vector x and distance(x,y) gives the distance between two vectors; normalize(x) returns a unit vector that points in the same direction as x. There are also functions named reflect and refract that can be used to compute the direction of reflected and refracted light rays; I will cover them when I need to use them.

The function mix(x,y,t) computes x*(1−t) + y*t. If t is a float in the range 0.0 to 1.0, then the return value is a linear mixture, or weighted average, of x and y. This function might be used, for example, to do alpha-blending of two colors. The function clamp(x,low,high) clamps x to the range low to high; the return value could be computed as min(max(x,low),high). If rgb is a vector representing a color, we could ensure that all of the components of the vector lie in the range 0 to 1 with the command

rgb = clamp( rgb, 0.0, 1.0 );

If s and t are floats, with s < t, then smoothstep(s,t,x) returns 0.0 for x less than s and returns 1.0 for x greater than t. For values of x between s and t, the return value is smoothly interpolated from 0.0 to 1.0. Here is an example that might be used in a fragment shader for rendering a gl.POINTS primitive, with transparency enabled:

float dist = distance( gl_PointCoord, vec2(0.5) );
float alpha = 1.0 - smoothstep( 0.45, 0.5, dist );
if (alpha == 0.0) {
    discard; // discard fully transparent pixels
}
gl_FragColor = vec4( 1.0, 0.0, 0.0, alpha );

This would render the point as a red disk, with the color fading smoothly from opaque to transparent around the edge of the disk, as dist increases from 0.45 to 0.5. Note that for the functions mix, clamp, and smoothstep, the x and y parameters can be vectors as well as floats. In that case, they operate on each component of the vector individually.

The usual mathematical functions are available in GLSL, including sin, cos, tan, asin, acos, atan, log, exp, pow, sqrt, abs, floor, ceil, min, and max. (In version 3.00, abs, min, and max also apply to integer types.) For these functions, the parameters can be any of the types float, vec2, vec3, or vec4. The return value is of the same type, and the function is applied to each component separately. For example, the value of sqrt(vec3(16.0,9.0,4.0)) is the vector vec3(4.0,3.0,2.0). For min and max, there is also a second version of the function in which the first parameter is a vector and the second parameter is a float. For those versions, each component of the vector is compared to the float; for example, max(vec3(1.0,2.0,3.0),2.5) is vec3(2.5,2.5,3.0).

The function mod(x,y) computes the modulus, or remainder, when x is divided by y. The return value is computed as x − yfloor(x/y). As with min and max, y can be either a vector or a float. The mod* function can be used as a substitute for the % operator, which is not supported in GLSL ES 1.00.

There are also functions for working with sampler variables. I will discuss some of them in the next section.

6.3.5 函数定义

Function Definitions

GLSL程序可以定义新函数,语法与C语言类似。与C语言不同,函数名称可以重载;也就是说,两个函数可以有相同的名称,只要它们有不同的参数数量或类型。在使用函数之前必须先声明它。像在C语言中一样,可以通过给出完整的定义或函数原型来声明它。

函数参数可以是任何类型。函数的返回类型可以是除数组类型之外的任何类型。结构体类型可以作为返回类型,只要结构体中不包含任何数组。当数组作为形式参数使用时,必须通过整型常量指定数组的长度。例如:

float arraySum10( float A[10] ) {
    float sum = 0.0;
    for ( int i = 0; i < 10; i++ ) {
        sum += A[i];
    }
    return sum;
}

函数参数可以通过限定符inoutinout进行修改。如果没有指定限定符,默认为in。限定符表示参数是用于函数的输入、函数的输出,还是两者兼有。对于输入参数,函数调用中实际参数的值会复制到函数定义中的形式参数,并且形式参数和实际参数之间没有进一步的交互。对于输出参数,当函数返回时,形式参数的值会复制到实际参数。对于inout参数,值会在两个方向上复制。这种类型的参数传递被称为“按值/返回调用”。注意,对于输出或inout参数,实际参数必须是可以赋值的东西,比如变量或swizzler(C、Java和JavaScript中的所有参数都是输入参数,但将指针作为参数传递可以具有类似于inout参数的效果。当然,GLSL没有指针)。例如:

void cumulativeSum( in float A[10], out float B[10]) {
    B[0] = A[0];
    for ( int i = 1; i < 10; i++ ) {
        B[i] = B[i-1] + A[i];
    }
}

请注意,GLSL中的函数不支持递归。

A GLSL program can define new functions, with a syntax similar to C. Unlike C, function names can be overloaded; that is, two functions can have the same name, as long as they have different numbers or types of parameters. A function must be declared before it is used. As in C, it can be declared by giving either a full definition or a function prototype.

Function parameters can be of any type. The return type for a function can be any type except for array types. A struct type can be a return type, as long as the structure does not include any arrays. When an array is used a formal parameter, the length of the array must be specified by an integer constant. For example,

float arraySum10( float A[10] ) {
float sum = 0.0;
for ( int i = 0; i < 10; i++ ) {
    sum += A[i];
}
return sum;
}

Function parameters can be modified by the qualifiers in, out, or inout. The default, if no qualifier is specified, is in. The qualifier indicates whether the parameter is used for input to the function, output from the function, or both. For input parameters, the value of the actual parameter in the function call is copied into the formal parameter in the function definition, and there is no further interaction between the formal and actual parameters. For output parameters, the value of the formal parameter is copied to the actual parameter when the function returns. For an inout parameter, the value is copied in both directions. This type of parameter passing is referred to as "call by value/return." Note that the actual parameter for an out or inout parameter must be something to which a value can be assigned, such as a variable or swizzler. (All parameters in C, Java, and JavaScript are input parameters, but passing a pointer as a parameter can have an effect similar to an inout parameter. GLSL, of course, has no pointers.) For example,

void cumulativeSum( in float A[10], out float B[10]) {
    B[0] = A[0];
    for ( int i = 1; i < 10; i++ ) {
        B[i] = B[i-1] + A[i];
    }
}

Note that recursion is not supported for functions in GLSL.

6.3.6 控制结构

Control Structures

在WebGL的GLSL ES 1.00中,唯一的控制结构是if语句和非常受限形式的for循环。没有whiledo..while循环,也没有switch语句。然而,GLSL ES 3.00支持所有这些。

if语句支持C语言的完整语法,包括elseelse if。在3.00版本中,所有控制结构的语法与C语言几乎相同。

在1.00版本着色器中的for循环,循环控制变量必须在循环中声明,并且必须是intfloat类型。循环控制变量的初始值必须是常量表达式(即,它可以包含运算符,但所有操作数必须是字面量常量或const变量)。循环内的代码不允许更改循环控制变量的值。结束循环的测试只能有形式var op expression,其中var是循环控制变量,op是关系或等式运算符之一,expression是常量表达式。最后,更新表达式必须具有var++var--var+=expressionvar-=expression的形式,其中var是循环控制变量,expression是常量表达式。当然,这是其他语言中for循环最典型的形式。一些合法的for循环的首行示例:

for (int i = 0; i < 10; i++)

for (float x = 1.0; x < 2.0; x += 0.1)

for (int k = 10; k != 0; k -= 1)

在3.00版本中,这些限制不适用。请注意,所有循环都可以包含breakcontinue语句。

The only control structures in GLSL ES 1.00 for WebGL are the if statement and a very restricted form of the for loop. There is no while or do..while loop, and there is no switch statement. However, all of these are supported in GLSL ES 3.00.

If statements are supported with the full syntax from C, including else and else if. In version 3.00, the syntax for all control structures is pretty much the same as in C.

In a for loop in a version 1.00 shader, the loop control variable must be declared in the loop, and it must be of type int or float. The initial value for the loop control variable must be a constant expression (that is, it can include operators, but all the operands must be literal constants or const variables) The code inside the loop is not allowed to change the value of the loop control variable. The test for ending the loop can only have the form var op expression, where var is the loop control variable, the op is one of the relational or equality operators, and the expression is a constant expression. Finally, the update expression must have one of the forms var++, var--, var+=expression, or var-=expression, where var is the loop control variable, and expression is a constant expression. Of course, this is the most typical form for for loops in other languages. Some examples of legal first lines for for loops:

for (int i = 0; i < 10; i++)

for (float x = 1.0; x < 2.0; x += 0.1)

for (int k = 10; k != 0; k -= 1)

In version 3.00, these restrictions do not apply. Note that all loops can include break and continue statements.

6.3.7 限制

Limits

WebGL对WebGL及其GLSL程序使用的某些资源设置了限制,例如属性变量的数量或纹理图像的大小。这些限制在许多情况下是由于GPU的硬件限制,它们取决于程序运行的设备以及该设备上WebGL的实现。移动设备(如平板电脑和手机)的硬件限制可能较低,但现代平板电脑和手机拥有相当令人印象深刻的GPU。尽管这些限制可能有所不同,但WebGL规定了所有实现都必须满足的一组最低要求。我将给出WebGL 1.0的最低值。WebGL 2.0的最低值更高。

例如,任何WebGL实现都必须允许在顶点着色器中至少有8个属性。特定实现的实际限制可能更多,但不能更少。实际限制在GLSL程序中作为预定义常量gl_MaxVertexAttribs的值提供。更方便的是,在JavaScript方面,它作为表达式的值提供

gl.getParameter( gl.MAX_VERTEX_ATTRIBS )

类型为floatvec2vec3vec4的属性变量都计为对限制的一个属性。对于矩阵值属性,每个列在限制方面都计为单独的属性。

类似地,变化变量有限制,并且在顶点和片段着色器中对统一变量有单独的限制。(限制是针对四个分量的“向量”的数量。可以将单独的变量打包到一个向量中,但使用的打包不必是最优的。属性变量不执行打包。)限制必须满足

gl_MaxVertexAttribs >= 8;
gl_MaxVertexUniformVectors >= 128;
gl_MaxFragmentUniformVectors >= 16;
gl_MaxVaryingVectors >= 8;

GLSL中还有纹理单元的数量限制,这基本上意味着可以同时使用的纹理数量。这些限制必须满足

gl_MaxTextureImageUnits >= 8;         // 片段着色器的限制
gl_MaxVertexTextureImageUnits >= 0;   // 顶点着色器的限制
gl_MaxCombinedTextureImageUnits >= 8; // 两个着色器的总限制

纹理通常在片段着色器中使用,但有时在顶点着色器中也可能有用。但请注意,gl_MaxVertexTextureImageUnits可以是零,这意味着实现不需要允许在顶点着色器中使用纹理单元。(这种可能性仅适用于WebGL 1.0。)

还有其他事情的限制,包括视口大小、纹理图像大小、线原语的线宽和POINTS原语的点大小。所有限制都可以使用gl.getParameter()从JavaScript方面查询。

以下演示显示了您正在查看此页面的设备上资源限制的实际值。演示显示了WebGL 1.0图形上下文的限制。您可以使用它来检查您希望您的WebGL程序运行的各种设备的功能。通常,实际限制将明显大于所需的最小值值。

WebGL puts limits on certain resources that are used by WebGL and its GLSL programs, such as the number of attribute variables or the size of a texture image. The limits are due in many cases to hardware limits in the GPU, and they depend on the device on which the program is running, and on the implementation of WebGL on that device. The hardware limits can be lower on mobile devices such as tablets and phones, but modern tablets and phones have pretty impressive GPUs. Although the limits can vary, WebGL imposes a set of minimum requirements that all implementations must satisfy. I will give the minimums for WebGL 1.0. The minimums for WebGL 2.0 are greater.

For example, any WebGL implementation must allow at least 8 attributes in a vertex shader. The actual limit for a particular implementation might be more, but cannot be less. The actual limit is available in a GLSL program as the value of a predefined constant, gl_MaxVertexAttribs. More conveniently, it is available on the JavaScript side as the value of the expression

gl.getParameter( gl.MAX_VERTEX_ATTRIBS )

Attribute variables of type float, vec2, vec3, and vec4 all count as one attribute against the limit. For a matrix-valued attribute, each column counts as a separate attribute as far as the limit goes.

Similarly, there are limits on varying variables, and there are separate limits on uniform variables in the vertex and fragment shaders. (The limits are on the number of four-component "vectors." There can be some packing of separate variables into a single vector, but the packing that is used does not have to be optimal. No packing is done for attribute variables.) The limits must satisfy

gl_MaxVertexAttribs >= 8;
gl_MaxVertexUniformVectors >= 128;
gl_MaxFragmentUniformVectors >= 16;
gl_MaxVaryingVectors >= 8;

There are also limits in GLSL on the number of texture units, which means essentially the number of textures that can be used simultaneously. These limits must satisfy

gl_MaxTextureImageUnits >= 8;         // limit for fragment shader
gl_MaxVertexTextureImageUnits >= 0;   // limit for vertex shader
gl_MaxCombinedTextureImageUnits >= 8; // total limit for both shaders

Textures are usually used in fragment shaders, but they can sometimes be useful in vertex shaders. Note however, that gl_MaxVertexTextureImageUnits can be zero, which means that implementations are not required to allow texture units to be used in vertex shaders. (This possibility is for WebGL 1.0 only.)

There are also limits on other things, including viewport size, texture image size, line width for line primitives, and point size for the POINTS primitive. All of the limits can be queried from the JavaScript side using gl.getParameter().

The following demo shows the actual values of the resource limits on the device on which you are viewing this page. The demo shows the limits for a WebGL 1.0 graphics context. You can use it to check the capabilities of various devices on which you want your WebGL programs to run. In general, the actual limits will be significantly larger than the required minimum values.