OpenGL ES 的精度问题

1,494 阅读2分钟
原文链接: zhuanlan.zhihu.com

随机函数

我将一些效果从 Windows 移植到手机上。某个效果 Windows 跟手机差别悬殊。耐心裁剪 glsl 代码,定位到此随机函数。

float rand(vec2 co) {
    return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453123);
}

参考这里。Random / noise functions for GLSL


正确噪音图(Shadertoy)

上述函数很常见,生成 [0, 1] 之间的随机数。在 shadertoy 粘贴下面代码

float rand(vec2 co) {
    return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453123);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = fragCoord/iResolution.xy;
    float r = rand(uv);
    fragColor = vec4(r, r, r, 1.0);
}

可生成噪音图:

正确噪音图

错误噪音图(iPhone 7+)

在 iPhone 7+ 运行类似代码

precision mediump float;
varying vec2 vTexCoord;

float rand(vec2 co) {
    return fract(sin(dot(co.xy,vec2(12.9898,78.233))) * 43758.5453123);
}

void main()
{
    vec2 uv = vTexCoord;
    float r = rand(uv);
    gl_FragColor = vec4(r, r, r, 1.0);
}

生成了错误的噪音图,有一半数据是黑色(黑色代表 0),原因是精度不够。

错误噪音图

精度

移植时,要注意精度。OpenGL 精度比 OpenGL ES 精度高得多。如下例子

precision mediump float;
varying vec2 vTexCoord;

void main()
{
    float r = sin(1.0);
    float k = abs(r - 0.8414709848079);
    if (k < 0.0005) {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
    } else {
        gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
    }
}

Windows 上运行,精度足够,显示红色。iPhone 7+ 上运行,精度不够,显示绿色。就算将 mediump 改成 highp,iPhone 7+ 照样显示绿色。

经测试,GLES 的数字在 4 位整数(十进制),4 位小数(十进制)之间,是相对安全的,当然范围越小越准确。


修正 1

添加 highp 标识

float rand(highp vec2 co) {
    return fract(sin(dot(co.xy,vec2(12.9898,78.233))) * 43758.5453123);
}

得到噪音图:

噪音图(修正1)

修正 2

假如不想指定 highp 精度,就不能乘以太大的数字,将原始数字 43758.5453123 换成 758.545。

float rand(vec2 co) {
    return fract(sin(dot(co.xy,vec2(12.9898,78.233))) * 758.545);
}

得到噪音图:

噪音图(修正2)

虽然还不够随机,但比修正前一半黑线要好,某些场合够用。


移植时其它注意事项

  • GLES 需显式写成浮点数,如 float k = 1;编译错误,需写成 float k = 1.0;
  • 注意精度,数值不能过大或者过小。如上述随机函数,想效果更加一致,应老老实实载入噪音纹理,取其像素作为随机值。
  • 假如使用 kEAGLRenderingAPIOpenGLES2 创建 EAGLContext,纹理 WrapMode 为 GL_REPEAT 会有限制,高宽必须是 2 的 n 次方(如 2、4、8、16、32、64 这些数字),不然会黑屏。kEAGLRenderingAPIOpenGLES3 没有这限制,但某些机型不支持 ES3,比如 iPhone 5C。
  • GLES 不支持某些函数,如 fwidth。