首页 > 解决方案 > 插值不匹配

问题描述

看这两张图:

在此处输入图像描述 在此处输入图像描述

两张图片上的红色和蓝色通道都为零,而绿色通道是该点的 x 坐标。x 坐标在 (-1, 1) 范围内。

第一张图像是在 CPU 上计算的:

double screenHeight = 750;
double screenWidth = 750;
double viewportWidth = 2.0;

for (int i = 0; i < screenHeight; ++i) {
    for (int j = 0; j < screenWidth; ++j) {
        float greenChannel = (double)j / screenWidth * viewportWidth;
    }
}

第二个是在 GPU 上计算的:首先,顶点着色器被告知要绘制三个顶点(但没有给出,更多信息在这里):

layout (location = 0) out vec2 outUV;

void main() {
    outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
    gl_Position = vec4(vec2(outUV) * 2.0f + -1.0f, 0.0f, 1.0f);
}

这基本上将重心坐标发送到每个片段。它们被插在三个值之间。然后片段着色器进行了以下计算:

layout (location = 0) in vec2 outUV;
layout(location = 0) out vec4 outColor;

void main() {
    double viewportWidth = 2.0;
    double c = (outUv.x - 0.5) * viewportWidth;
    outColor = vec4(vec3(0.0, float(c), 0.0), 1.0);
}

这首先移动 x 坐标,使其现在位于 (-0.5, -0.5) 范围内,然后将其缩放到 (-1, 1) 范围内。

如果您再次查看这些图像,您会发现它们并不相同。CPU 在中心附近更加平滑,而在 GPU 上渲染的过渡更加突然。你知道是什么原因造成的吗?看起来这可能是由于 GPU 某处的精度损失所致。我认为插值的浮点数不够精确,所以我为 c 尝试了不同的方法:

double screenWidth = 750;
double viewportWidth = 2.0;
double c = (gl_FragCoord.xy - 0.5 * screenWidth )/ screenWidth  * viewportWidth;

这使用 gl_FragCoord 和屏幕的分辨率,而不是依赖于插值。它给了我与第一次尝试无法区分的结果,所以我猜这不是插值。可以肯定的是,我用两种方法计算了 c 并进行了比较。直到小数点后 7 位,它们都完全相同。

有谁知道为什么这两个图像不一样?

标签: openglinterpolationvulkan

解决方案


GPU 图像是正确的——似乎您启用了 sRGB 处理(这很好!)。因此,您的 GPU 代码以线性方式进行插值,然后应用 sRGB 伽玛压缩函数。在 OpenGL 中,此行为由glEnable/Disable(GL_FRAMEBUFFER_SRGB). 在 Vulkan 中,它是在创建交换链时配置的

您的 CPU 代码在“错误的”色彩空间中进行插值——即直接在 sRGB 值中进行计算,就好像它们是线性的一样,即使它们不是线性的。

您可以按如下方式修复 CPU 代码:

float greenChannel = ...;
greenChannel = greenChannel <= 0.0031308f ? 12.92f*greenChannel : 1.055f*powf(greenChannel, 1/2.4f) - 0.055f;

推荐阅读