OpenGL ES着色器语言之变量和数据类型(二)(官方文档第四章)
4.5精度和精度修饰符
4.5.1范围和精度
用于存储和展示浮点数、整数变量的范围和精度依赖于数值的源(varying,uniform,纹理查找,等等),是不是顶点或者片元着色器,还有其他一些底层实现的细节。最低存储需要通过精度修饰符来声明。典型地,精度操作必须要保留变量包含的精度存储。仅有的例外是需要大量复杂计算的内建函数,如atan(),返回值的精度低于声明的精度。
强烈建议顶点语言提供一种匹配IEEE单精度浮点数或更高精度的浮点数的浮点范围和精度。这就需要顶点语言提供浮点变量的范围至少是(-2^62, 2^62),精度至少是65536。
顶点语言必须提供一种至少16位,加上一个符号位的整数精度。
片元语言提供与顶点着色器相同的浮点数范围和精度是很有必要的,但不是必须的。这就需要片元语言提供的浮点数的范围至少是(-16384,+16384),精度至少是1024。
片元语言必须提供一种至少10为,加上一个符号位的整数精度。
4.5.2精度修饰符
任何浮点数或者整数声明前面都可以添加如下精度修饰符:
举例:
lowp float color; varying mediump vec2 Coord; lowp ivec2 foo(lowp mat3); highp mat4 m;
精度修饰符声明了底层实现存储这些变量必须要使用的最小范围和精度。实现可能会使用比要求更大的范围和精度,但绝对不会比要求少。
一下是精度修饰符要求的最低范围和精度:
Floating Point Magnitude Range是非零值量级的范围。对于Floating Point Precision,relative意思是任何度量的值的精度都是相对于这个值的。对于所有的精度级别,0必须被精确的表示出来。任何不能提供着色器存储变量所声明的精度的实现都会引起一个编译或链接错误。
对于高精度和中级精度,整型范围必须可以准确地转化成相应的相同精度修饰符所表示的float型。这样的话,highp int 可以被转换成highp float, mediump int 可以被转换成mediump float,但是lowp int 不能转换成相应的lowp float。
顶点语言要求编译和链接任何lowp, mediump和highp应用都不能出现错误。
片元语言要求编译和链接任何lowp, mediump应用都不能出现错误。但是highp支持是可选的。
字符常量和布尔型没有精度修饰符.当浮点数和整数构造器不含带有精度修饰符的参数时也不需要精度修饰符。
在这段文档中,操作包含运算符,内建函数和构造器,操作数包含函数参数和构造器参数。
对于精度没有定义的常量表达式或子表达式,评估的精度结果是所有操作数中的最高精度(mediump或者highp) 。带评估的常量表达式必须是固定不变的,并且在编译期进行。
另外,对于没有精度修饰符的操作数,精度将来自于其他操作数。如果所有的操作数都没有精度,那么接着看使用计算结果的其他表达式。这个操作是递归的,直到找到一个有精度的操作符为止。如果必要,这个操作也包含赋值运算的左值,初始化声明的变量,函数形参,函数返回值.如果这样依然不能决定精度,如果组成表达式的所有操作数都没有精度,如果结果没有被赋值,也没有当作参数传进函数,那么将使用默认或更大的类型.当这种情况出现在片元着色器中,默认的精度必须被定义.
比如:
uniform highp float h1;
highp float h2 = 2.3*4.7;操作和结果都是高精度
mediump float m;
m = 3.7*h1*h2;//所有操作都是高精度
h2 = m * h1;//操作是高精度
m = h2 - h1;//操作是高精度
h2 = m + m;//加法和结果都是mediump精度
void f(highp p);
f(3.3);//3.3将作为高精度值传入函数
4.5.3默认精度修饰符
precision precision-qualifier type;
precision可以用来确定默认精度修饰符。type可以是int或float或采样器类型,precision-qualifier可以是lowp, mediump, 或者highp。任何其他类型和修饰符都会引起错误。如果type是float类型,那么该精度(precision-qualifier)将适用于所有无精度修饰符的浮点数声明(标量,向量,矩阵)。如果type是int类型,那么该精度(precision-qualifier)将适用于所有无精度修饰符的整型数声明(标量,向量)。包括全局变量声明,函数返回值声明,函数参数声明,和本地变量声明等。没有声明精度修饰符的变量将使用和它最近的precision语句中的精度。
在顶点语言中有如下预定义的全局默认精度语句:
precision highp float;
precision highp int;
precision lowp sampler2D;
precision lowp samplerCube;
在片元语言中有如下预定义的全局默认精度语句:
precision mediump int;
precision lowp sampler2D;
precision lowp samplerCube;
片元语言没有默认的浮点数精度修饰符。因此,对于浮点数,浮点数向量和矩阵变量声明,要么声明必须包含一个精度修饰符,要不默认的精度修饰符在之前已经被声明过了。
4.5.4可用的精度修饰符
内建宏GL_FRAGMENT_PRECISION_HIGH在支持highp精度的片元语言中是定义过的,但在不支持的系统中是未定义的。一旦定义以后,在顶点和片元语言中都可以使用。
#defien GL_FRAGMENT_PRECISION_HIGH 1;
4.6变异和invariant修饰符
在这部分中,变异是指在不同的着色器中的相同语句返回不同的值的可能性.举个例子,两个顶点着色器都使用相同的表达式来设置gl_Position,并且当着色器执行时传进表达式的值也是一样的.完全有可能,由于两个着色器独立的编译环境,当着色器运行时赋给gl_Position的值不一定会相同.在这个例子中,会引起多路算法的几何对齐问题.
通常,着色器之间的这种变异是允许的.如果想避免这种变异的发生,变量可以使用invariant来声明.
4.6.1invariant修饰符
为确保一个特定的输出变量是不变的,可以使用invariant修饰符.它可以修饰之前已经定义过的变量,如:
invariant gl_Position;
也可以用在变量的声明当中:
invariant varying mediump vec3 Color;
仅如下变量可以声明为invariant:
(1)顶点着色器中内建的特定输出变量
(2)顶点着色器中输出varying变量
(3)片元着色器中特定的输入变量
(4)片元着色器中的输入varying变量
(5)片元着色器中内建的输出变量
invariant后面还可以跟一个用逗号隔开的之前声明的标识符列表.
为了确保两个着色器中特定的输出变量不发生变异.还应遵循以下规则:
(1)顶点和片元着色器中的输出变量都声明为invariant
(2)相同的值必须输入到赋给输出变量的表达式或控制流的所有着色器输入变量.
(3)输出变量上的任何纹理函数调用在使用纹理格式,纹理像素值和纹理过滤时都需要设置成相同的方式.
(4)所有的输入变量都以相同的方式操作.
初始时,默认的所有输出变量被允许变异.如果想强制所有输出变量都不可变,那么在着色器所有的变量声明之前使用
#pragma STDGL invariant(all)
4.6.2着色器中的不变体
当一个值被存到一个变量中,我们通常假设它是一个常量,除非显示的去更改它的值.然而,在优化处理期间,编译期可能会重新计算一个值而不是将它存到寄存器中.因为操作的精度没有被完全指定(如,低精度的操作会被转成中等精度或高精度),重新计算的值有可能就和原来的值不一致.
在着色器中变体是允许的.如果要避免变体,可以使用invariant修饰符或invariant pragma.
precision mediump;
vec4 col;
vec2 a = ...;
........
col = texture2D(tex, a);//此时a的值假设为a1
..............
col = texture2D(tex, a);//此时a的值假设为a2,但是有可能a1不等于a2
如果强制成常量,可以使用:
#pragma STDGL invariant(all)
例子二:
vec2 m = ...;
vec2 n = ...;
vec2 a = m + n;
vec2 b = m + n;//没法保证a和b完全相等
4.6.3常量表达式的不变体
常量表达式必须要保证是不变体.一个特定的表达式在相同的还是不同的着色器中都必须有相同的结果.这包括同一个表达式出现在同一个顶点和片元着色器中,或出现在不同的顶点和片元着色器中.
如果满足以下条件,常量表达式必须得出相同的值:
(1)表达式的输入值相同
(2)执行的操作相同并且顺序也相同
(3)所有操作均以相同的精度执行
4.6.4不变体和链接装置
在顶点和片元着色器中声明的不变体varying变量必须要匹配.对于内建的特定变量,当且仅当gl_Position被声明为invariant时,gl_FragCoord才可以被声明为invariant.同样的,当且仅当gl_PositionSize被声明为invariant时,gl_PointCoord才可以被声明为invariant.将gl_FrontFacing声明为invariant是错误的.gl_FrontFacing的不变体和gl_Position的不变体是一样的.
4.7修饰顺序
当需要使用多个修饰时,它们必须遵循严格的顺序:
(1)invariant-qualifier storage-qualifier precision-qualifier
(2)storage-qualifier parameter-qualifier precision-qualifier