不匹配的插值
Mismatching interpolations
看这两张图:
两张图片的红色和蓝色通道均为零,而绿色通道是该点的 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上计算的:
首先,顶点着色器被告知绘制三个顶点(但给出了 none,更多信息 here):
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 位,它们完全相同。
有谁知道为什么这两张图片不一样?
GPU 图像是正确的 -- 似乎您启用了 sRGB 处理(这很好!)。因此,您的 GPU 代码以线性方式进行插值,然后应用 sRGB 伽马压缩函数。在 OpenGL 中,此行为由 glEnable/Disable(GL_FRAMEBUFFER_SRGB)
控制。在 Vulkan 中是 configured when creating the swapchain.
您的 CPU 代码在 'wrong' 色彩空间中进行插值——即通过直接在 sRGB 值中计算,就好像它们是线性的,即使它们不是。
您可以按如下方式修复 CPU 代码:
float greenChannel = ...;
greenChannel = greenChannel <= 0.0031308f ? 12.92f*greenChannel : 1.055f*powf(greenChannel, 1/2.4f) - 0.055f;
看这两张图:
两张图片的红色和蓝色通道均为零,而绿色通道是该点的 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上计算的: 首先,顶点着色器被告知绘制三个顶点(但给出了 none,更多信息 here):
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 位,它们完全相同。
有谁知道为什么这两张图片不一样?
GPU 图像是正确的 -- 似乎您启用了 sRGB 处理(这很好!)。因此,您的 GPU 代码以线性方式进行插值,然后应用 sRGB 伽马压缩函数。在 OpenGL 中,此行为由 glEnable/Disable(GL_FRAMEBUFFER_SRGB)
控制。在 Vulkan 中是 configured when creating the swapchain.
您的 CPU 代码在 'wrong' 色彩空间中进行插值——即通过直接在 sRGB 值中计算,就好像它们是线性的,即使它们不是。
您可以按如下方式修复 CPU 代码:
float greenChannel = ...;
greenChannel = greenChannel <= 0.0031308f ? 12.92f*greenChannel : 1.055f*powf(greenChannel, 1/2.4f) - 0.055f;