【问题标题】:GLSL Matrix/Inverse multiplication precisionGLSL 矩阵/逆乘法精度
【发布时间】:2018-06-10 01:48:40
【问题描述】:

我正在尝试使用 GPU 做一些工作以进行布料模拟,但我在使用不同的硬件时遇到了一些问题。我使用threejs作为框架,但我认为这与我遇到的问题无关。

基本上我要做的是上传一个矩阵和该矩阵的逆矩阵,以便将点从本地坐标转换为世界坐标,在世界坐标中进行一些数学运算(如碰撞检测),然后将它们转换回本地坐标。当我使用浮点纹理时,这在我的笔记本电脑上效果很好,但是我注意到在我的手机上有一些奇怪的伪影:

正确:

不正确:

在进行了一些调试后,我将其范围缩小到了两个问题。它们都与小数精度有关。由于约束(以及约束期间的精度问题)导致的顶点折叠以及使用矩阵乘法和逆矩阵时失去精度。

我认为问题与精度有关的原因是,如果我使用浮点纹理,它可以在我的计算机上运行,​​但如果我使用半浮点数,我也会遇到同样的问题。我的手机支持浮点纹理,这是我对为什么会在我的手机上发生这种情况感到困惑的原因之一。我缩小了问题的范围,因此禁用了所有布料模拟,如果我在计算机上运行具有半浮动纹理的应用程序,没有任何重力,但通过变换和反转平面会以奇怪的方式闪烁

如果禁用了转换和反转,那么它看起来很正常。

我不知道如何处理这个问题,或者我是否走在正确的道路上。我相信半浮点纹理的小数精度有限,但我不明白为什么这会导致我的问题,因为它应该只影响着色器的输出,而不影响着色器中的数学运算。

着色器的代码如下所示:

    '   vec2 cellSize  = 1.0 / res;',
    '   vec4 pos = texture2D(vertexPositions, vuv.xy );',


    '   vec2 newUV;',
    '   if(type == 0.0){',
        '   float px = floor(vuv.x * res.x );',
        '   float spacingx = px- (2.0 * floor(px/2.0));',
        '   float py = floor(vuv.y * res.y );',
        '   float spacingy = py- (2.0 * floor(py/2.0));',
        '   float total = spacingx + spacingy;',
        '   total = total- (2.0 * floor(total/2.0));',

        '   if(total == 0.0){',
        '       newUV = vuv + (direction * cellSize);',
        '   }',
        '   else{',
        '       newUV = vuv - (direction * cellSize);',
        '   }',
    '   }',
    '   if(type == 1.0){',
        '   float px = floor(vuv.x * res.x );',
        '   float spacingx = px- (2.0 * floor(px/2.0));',

        '   float total = spacingx;',


        '   if(total == 0.0){',
        '       newUV = vuv + (direction * cellSize);',
        '   }',
        '   else{',
        '       newUV = vuv - (direction * cellSize);',
        '   }',
    '   }',






    '   vec4 totalDisplacement = vec4(0.0);',

    '           if(newUV.x > 0.0 && newUV.x < 1.0 && newUV.y > 0.0 && newUV.y < 1.0){ ',
    '               vec4 posOld = texture2D(vertexPositionsStart, vuv);' ,
    '               vec4 posOld2 = texture2D(vertexPositionsStart, newUV);' ,

    '               float targetDistance = length(posOld - posOld2);',
    '               vec4 newPos =  texture2D(vertexPositions, newUV);',
    '               float dx = pos.x - newPos.x;',
    '               float dy = pos.y - newPos.y;',
    '               float dz = pos.z - newPos.z;',
    '               float distance = sqrt(dx * dx + dy * dy + dz * dz);',
    '               float difference = targetDistance- distance;',
    '               float percent = difference / distance / 2.0;',
    '               float offsetX = dx * percent * rigid;',
    '               float offsetY = dy * percent * rigid;',
    '               float offsetZ = dz * percent * rigid;',
    '               totalDisplacement.x += offsetX;',
    '               totalDisplacement.y += offsetY;',
    '               totalDisplacement.z += offsetZ;',
    '           }',
    '       }',
    '   }',

    '   pos += totalDisplacement;',
    '   if(  vuv.x  > 1.0 - cellSize.x  && topConstrain == 1 ){',
    '       pos =transformation *  texture2D(vertexPositionsStart, vuv.xy );',
    '   }',

    '   if(  vuv.x  < cellSize.x  && bottomConstrain == 1 ){',
    '       pos =transformation *  texture2D(vertexPositionsStart, vuv.xy );',
    '   }',

    '   if(  vuv.y  < cellSize.y  && leftConstrain == 1 ){',
    '       pos =transformation *  texture2D(vertexPositionsStart, vuv.xy );',
    '   }',


    '   if(  vuv.y  > 1.0 - cellSize.y && rightConstrain == 1 ){',
    '       pos =transformation *  texture2D(vertexPositionsStart, vuv.xy );',
    '   }',




    '   gl_FragColor = vec4( pos.xyz , 1.0 );',

【问题讨论】:

  • GLES 的精度要求远低于桌面 GL(尤其是使用 GLES2 时)。当您的着色器 ALU 仍然使用低得多的精度时,如果您使用完整的 fp32 纹理,这将无济于事。
  • 我明白了。所以你认为问题在于我手机的着色器 ALU 不支持足够的精度。我不明白为什么如果我在我的计算机上使用半浮动纹理仍然会发生这个问题。不过,这似乎是一个合理的解释
  • 尝试使用相对坐标系,这样变换后的顶点就不会离你的matrices origins太远。计算后转换回原始坐标系。这样,您将避免使用高幅度向量与矩阵相乘,这会产生精度问题。欲了解更多信息,请参阅ray and ellipsoid intersection accuracy improvement
  • 感谢 Spektre 的建议,我将使用一种方法,避免同时使用转换矩阵。我仍然在精度方面遇到一些问题(我相信这是精度。顶点在手机上缓慢地向中心移动,而在计算机上表现正常。虽然它们都应该支持 highp 浮点数
  • 所以最简单的方法是“尝试提高精度”。更好的方法是“找到更好的(数值稳定等)算法”。

标签: matrix three.js glsl webgl


【解决方案1】:

为确保您的着色器为浮点计算创建高精度变量,应将以下内容添加到顶点着色器的开头:

precision highp float;
precision highp int;

并且在片段着色器中,浮点变量声明应该如下声明:

precision highp float;

如果您在计算中使用先前存储为浮点数的计算结果的值,则浮点错误会被放大。否则称为中间值。

为了最大限度地减少这些错误,您应该限制在着色器中执行的中间计算的数量。例如,您可以完全扩展 newUV 的计算:

newUV = vuv + ( direction * ( 1.0 / res ) );

你也可以完全展开totalDisplacement的计算,一步一步,先代入offsetX,像这样:

totalDisplacement.x += ( dx * percent * rigid )

现在将每个变量 dxpercent 替换为上面的:

totalDisplacement.x += ( ( pos.x - newPos.x ) * ( difference / distance / 2.0 ) * rigid )

您现在可以看到等式可以进一步扩展,替换为difference,如下所示:

totalDisplacement.x += ( ( pos.x - newPos.x ) * ( ( targetDistance- distance ) / ( distance * 2.0 ) ) * rigid );

此时你可以做一些代数来简化和抵消一些变量(除以distance)。通过简化上面的等式,我们现在得到以下结果:

totalDisplacement.x += ( ( pos.x - newPos.x ) * ( ( targetDistance / ( distance * 2.0 ) - 0.5 ) * rigid );

最后我们可以用公式替换targetDistance,如下所示:

totalDisplacement.x += ( ( pos.x - newPos.x ) * ( ( length(posOld - posOld2) / ( distance * 2.0 ) - 0.5 ) * rigid );

分别为其他坐标:

totalDisplacement.y += ( ( pos.y - newPos.y ) * ( ( length(posOld - posOld2) / ( distance * 2.0 ) - 0.5 ) * rigid );
totalDisplacement.z += ( ( pos.z - newPos.z ) * ( ( length(posOld - posOld2) / ( distance * 2.0 ) - 0.5 ) * rigid );

显然你可以继续前进,即将posOldposOld2newPos 的值代入。

请注意,到目前为止,为了得到上面的等式,我们已经无需将 5 个中间值存储在浮点变量中。同样通过对等式的简化,(除以distance),distance 变量仅在计算中使用一次。将此与初始实现进行比较,distance 用于计算differencepercent。将所有这些组合在一个等式中可以简化和减少使用相同浮点值的次数。因此也减少了总浮点误差。这里的权衡是得到的方程不太可读。

如果您好奇,您还可以通过调用glGetShaderPrecisionFormat 来检查给定着色器编译器的精度级别:

int range[2], precision;
glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER, GL_HIGH_FLOAT, range, &precision);

如果您检查应用程序的desktopmobile 版本的结果,您将能够比较两个版本的精度差异。

【讨论】:

    猜你喜欢
    • 2017-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-05
    • 2016-01-10
    • 2014-06-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多