WebGL 精度问题 float double

1,165 阅读2分钟

原文

float与double

  • cpp中数字默认是double,要float的话,3.14f可以这样
  • JavaScript 中的Number类型精度为double类型,而一般GPU的图形接口api(如opengl、DirectX等)中浮点数精度为float,所以数据在cpu传入GPU做渲染过程中会有精度损失,从而导致了渲染中的闪动问题。
  • float为单精度浮点数,double是双精度浮点数
  • 半精度有效数字
    • 16位浮点数 1+8+7
    • 2个有效数字 | 符号位 | 指数位 | 尾数部分 | | --- | --- | --- | | 1 | 8 | 7 |
  • float类型的具体位数描述:
    • 32位浮点数 1+8+23
    • 指数: 2的7次方即-127~128
    • 尾数: 2^23 = 8388608
    • 即float的精度为6~7位有效数字 | 符号位 | 指数位 | 尾数部分 | | --- | --- | --- | | 1 | 8 | 23 |
  • double类型的具体位数描述:
    • 64位浮点数 1+11+52
    • 指数: 2的10次方即-1023~1024
    • 尾数: 2^52 = 4503599627370496
    • 即float的精度为15~16位有效数字 | 符号位 | 指数位 | 尾数部分 | | --- | --- | --- | | 1 | 11 | 52 |
  • 浮点数,浮点数的小数点是浮动的,小数点后面的小数位数不固定

shader精度

  • 顶点着色器默认精度
precision highp float;
precision highp int;
precision lowp sampler2D;
precision lowp samplerCube;
  • 片元着色器默认精度
precision mediump int;
precision lowp sampler2D;
precision lowp samplerCube;

问题重现

var vertices = [
5000000.5, 0.5, 0.5, 5000000.5, 0.5, -0.5, 5000000.5, -0.5, 0.5, 
5000000.5, -0.5, -0.5, 4999999.5, 0.5, -0.5, 4999999.5, 0.5, 0.5, 
4999999.5, -0.5, -0.5, 4999999.5, -0.5, 0.5, 4999999.5, 0.5, -0.5, 
5000000.5, 0.5, -0.5, 4999999.5, 0.5, 0.5, 5000000.5, 0.5, 0.5, 
4999999.5, -0.5, 0.5, 5000000.5, -0.5, 0.5, 4999999.5, -0.5, -0.5, 
5000000.5, -0.5, -0.5, 4999999.5, 0.5, 0.5, 5000000.5, 0.5, 0.5,
4999999.5, -0.5, 0.5, 5000000.5, -0.5, 0.5, 5000000.5, 0.5, -0.5, 
4999999.5, 0.5, -0.5, 5000000.5, -0.5, -0.5, 4999999.5, -0.5, -0.5]
  • float里有效数字最多7位,存在有效数字为8位,导致精度的丢失,从而导致组成的立方体会不规则。

精度问题的解决方案

  • RTC方案
    • RTC(Relative To Center)的方案将模型的顶点信息,转化为相对于某个中心点的坐标,
var vertices = [
0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5
...
]
  • 使用2个float传高精度的数据
    • 这种方式保证精度的同时也会增加数据量,造成数据带宽瓶颈,具体要视情况而定。
function doubleToTwoFloats(value) {
    var high;
    var low;
    if (value >= 0) {
        tempHigh = Math.floor(value / 65536) * 65536;
        high = tempHigh;
        low = value - tempHigh;
    } else {
        tempHigh = Math.floor(-value / 65536) * 65536;
        high = -tempHigh;
        low = value + tempHigh;
    }
    return [high, low];
}
uniform vec3 uViewerHigh;
uniform vec3 uViewerLow;

void main(void)
{
    vec3 highDifference = vec3(gl_VertexHigh - uViewerHigh);
    vec3 lowDifference = vec3(gl_VertexLow.xyz - uViewerLow);
    gl_Position = gl_ModelViewProjectionMatrix *
         vec4(highDifference + lowDifference, 1.0);
}