GLSL Matrix/Inverse 乘法精度
GLSL Matrix/Inverse multiplication precision
我正在尝试使用 GPU 进行一些布料模拟工作,但我在使用不同硬件时遇到了一些问题。我正在使用 threejs 作为框架,但我认为这与我遇到的问题无关。
基本上我所做的是上传一个矩阵和该矩阵的逆矩阵,以便将点从本地坐标转换为世界坐标,在世界坐标中做一些数学运算(如碰撞检测),然后将它们转换回本地坐标.当我使用浮点纹理时,这在我的笔记本电脑上效果很好,但是我注意到 phone 上有一些奇怪的伪像:
正确:
不正确:
经过一些调试后,我将其缩小为两个问题。它们都与小数精度有关。由于约束(以及约束期间的精度问题)导致的顶点折叠以及使用矩阵乘法和逆矩阵时失去精度。
我认为问题与精度有关的原因是,如果我使用浮点纹理,它可以在我的计算机上运行,但如果我使用半浮点数,我会遇到同样的问题。我的 phone 支持浮点纹理,这是我对为什么我的 phone 会发生这种情况感到困惑的原因之一。我缩小了问题的范围,所以所有的布料模拟都被禁用了,如果我 运行 我的计算机上的应用程序有半浮动纹理,没有任何重力但有变换和反转平面以奇怪的方式闪烁
而如果禁用转换和逆则它看起来很正常。
我不知道如何处理这个问题,或者我是否正在走正确的道路。我相信半浮点纹理的小数精度有限,但我不明白为什么这会导致我的问题,因为它应该只影响着色器的输出,而不是着色器中进行的数学运算。
着色器的代码如下所示:
' 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 );',
为确保您的着色器为浮点计算创建高精度变量,应将以下内容添加到顶点着色器的开头:
precision highp float;
precision highp int;
而在片元着色器中,浮点型变量声明应如下声明:
precision highp float;
如果您在计算中使用以前存储为浮点数的计算结果的值,浮点数错误会被放大。否则称为中间值。
为了尽量减少这些错误,您应该限制在着色器中执行的中间计算的数量。例如,您可以完全扩展 newUV
:
的计算
newUV = vuv + ( direction * ( 1.0 / res ) );
您还可以完全展开 totalDisplacement
的计算,一步一步,首先像这样替换 offsetX
:
totalDisplacement.x += ( dx * percent * rigid )
现在将每个变量dx
和percent
代入上面:
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 );
显然您可以继续前进,即代入 posOld
、posOld2
和 newPos
的值。
请注意,到目前为止,为了得到上面的等式,我们已经消除了在浮点变量中存储 5 个中间值的需要。同样通过方程的简化,(除以distance
),distance
变量只在计算中使用一次。与最初的实现相比,distance
用于计算 difference
和 percent
。将所有这些组合在一个方程式中可以简化并减少使用相同浮点值的次数。因此也减少了总的浮点误差。这里的权衡是得到的方程式人类可读性较差。
如果您好奇,还可以通过调用 glGetShaderPrecisionFormat
:
检查给定着色器编译器的精度级别
int range[2], precision;
glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER, GL_HIGH_FLOAT, range, &precision);
我正在尝试使用 GPU 进行一些布料模拟工作,但我在使用不同硬件时遇到了一些问题。我正在使用 threejs 作为框架,但我认为这与我遇到的问题无关。
基本上我所做的是上传一个矩阵和该矩阵的逆矩阵,以便将点从本地坐标转换为世界坐标,在世界坐标中做一些数学运算(如碰撞检测),然后将它们转换回本地坐标.当我使用浮点纹理时,这在我的笔记本电脑上效果很好,但是我注意到 phone 上有一些奇怪的伪像:
正确:
不正确:
经过一些调试后,我将其缩小为两个问题。它们都与小数精度有关。由于约束(以及约束期间的精度问题)导致的顶点折叠以及使用矩阵乘法和逆矩阵时失去精度。
我认为问题与精度有关的原因是,如果我使用浮点纹理,它可以在我的计算机上运行,但如果我使用半浮点数,我会遇到同样的问题。我的 phone 支持浮点纹理,这是我对为什么我的 phone 会发生这种情况感到困惑的原因之一。我缩小了问题的范围,所以所有的布料模拟都被禁用了,如果我 运行 我的计算机上的应用程序有半浮动纹理,没有任何重力但有变换和反转平面以奇怪的方式闪烁
而如果禁用转换和逆则它看起来很正常。
我不知道如何处理这个问题,或者我是否正在走正确的道路。我相信半浮点纹理的小数精度有限,但我不明白为什么这会导致我的问题,因为它应该只影响着色器的输出,而不是着色器中进行的数学运算。
着色器的代码如下所示:
' 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 );',
为确保您的着色器为浮点计算创建高精度变量,应将以下内容添加到顶点着色器的开头:
precision highp float;
precision highp int;
而在片元着色器中,浮点型变量声明应如下声明:
precision highp float;
如果您在计算中使用以前存储为浮点数的计算结果的值,浮点数错误会被放大。否则称为中间值。
为了尽量减少这些错误,您应该限制在着色器中执行的中间计算的数量。例如,您可以完全扩展 newUV
:
newUV = vuv + ( direction * ( 1.0 / res ) );
您还可以完全展开 totalDisplacement
的计算,一步一步,首先像这样替换 offsetX
:
totalDisplacement.x += ( dx * percent * rigid )
现在将每个变量dx
和percent
代入上面:
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 );
显然您可以继续前进,即代入 posOld
、posOld2
和 newPos
的值。
请注意,到目前为止,为了得到上面的等式,我们已经消除了在浮点变量中存储 5 个中间值的需要。同样通过方程的简化,(除以distance
),distance
变量只在计算中使用一次。与最初的实现相比,distance
用于计算 difference
和 percent
。将所有这些组合在一个方程式中可以简化并减少使用相同浮点值的次数。因此也减少了总的浮点误差。这里的权衡是得到的方程式人类可读性较差。
如果您好奇,还可以通过调用 glGetShaderPrecisionFormat
:
int range[2], precision;
glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER, GL_HIGH_FLOAT, range, &precision);