【问题标题】:Artifacts from linear filtering a floating point texture in the fragment shader在片段着色器中线性过滤浮点纹理的伪影
【发布时间】:2016-05-13 19:06:40
【问题描述】:

我正在使用取自this tutorial 的以下代码在 WebGL 的片段着色器中对浮点纹理执行线性过滤:

float fHeight = 512.0;
float fWidth = 1024.0;
float texelSizeX = 1.0/fWidth;
float texelSizeY = 1.0/fHeight;

float tex2DBiLinear( sampler2D textureSampler_i, vec2 texCoord_i )
{
    float p0q0 = texture2D(textureSampler_i, texCoord_i)[0];
    float p1q0 = texture2D(textureSampler_i, texCoord_i + vec2(texelSizeX, 0))[0];

    float p0q1 = texture2D(textureSampler_i, texCoord_i + vec2(0, texelSizeY))[0];
    float p1q1 = texture2D(textureSampler_i, texCoord_i + vec2(texelSizeX , texelSizeY))[0];

    float a = fract( texCoord_i.x * fWidth ); // Get Interpolation factor for X direction.
                    // Fraction near to valid data.

    float pInterp_q0 = mix( p0q0, p1q0, a ); // Interpolates top row in X direction.
    float pInterp_q1 = mix( p0q1, p1q1, a ); // Interpolates bottom row in X direction.

    float b = fract( texCoord_i.y * fHeight );// Get Interpolation factor for Y direction.
    return mix( pInterp_q0, pInterp_q1, b ); // Interpolate in Y direction.
}

在 Nvidia GPU 上看起来不错,但在其他两台配备 Intel 集成 GPU 的计算机上看起来像这样:

出现了不应该出现的较亮或较暗的线条。如果您放大它们,它们就会变得可见,并且随着您放大的次数越来越多,它们往往会变得更频繁。当非常接近放大时,它们会出现在我正在过滤的纹理的每个纹素的边缘。我尝试在片段着色器中更改精度语句,但这并没有解决它。

内置线性过滤适用于两个 GPU,但对于不支持使用 WebGL 对浮点纹理进行线性过滤的 GPU,我仍然需要手动过滤作为备用。

英特尔 GPU 来自台式机 Core i5-4460 和配备英特尔 HD 5500 GPU 的笔记本电脑。对于浮点值的所有精度,我从getShaderPrecisionFormat 得到 rangeMin 和 rangeMax 为 127,精度为 23。

对导致这些伪影的原因以及如何解决这些问题有任何想法吗?

编辑:

通过更多试验,我发现减少片段着色器中的纹素大小变量可以消除这些伪影:

float texelSizeX = 1.0/fWidth*0.998;
float texelSizeY = 1.0/fHeight*0.998;

乘以 0.999 是不够的,但是将纹素大小乘以 0.998 可以消除伪影。

这显然不是一个令人满意的修复,我仍然不知道是什么原因造成的,我现在可能在其他 GPU 或驱动程序上造成了伪影。所以我仍然有兴趣弄清楚这里的实际问题是什么。

【问题讨论】:

  • "我尝试在片段着色器中更改精度语句,但这并没有解决问题。" 如果您降低精度会发生什么NVIDIA 版本的?
  • @NicolBolas 我将不得不重新检查,但我认为它看起来仍然很好并且没有造成任何伪影。但我也不确定精度声明是否真的会导致 NVIDIA GPU 上的精度降低。
  • 另外,你能添加几个不同分辨率的英特尔版本吗?哦,还有一件事:你能验证这些线条的颜色是否总是相同的吗?
  • @NicolBolas 我添加了更多图像,颜色不一样。但是我现在也找到了一种解决方法,可以删除这些线条,尽管我仍然不明白这个问题。

标签: opengl-es glsl webgl fragment-shader


【解决方案1】:

我不清楚代码试图做什么。它不会重现 GPU 的双线性,因为那将使用以 texcoord 为中心的像素。

换句话说,正如实现的那样

vec4 c = tex2DBiLinear(someSampler, someTexcoord);

等同于LINEAR

vec4 c = texture2D(someSampler, someTexcoord);

texture2D 查看像素 someTexcoord +/- texelSize * .5tex2DBiLinear 查看像素 someTexcoordsomeTexcoord + texelSize

您没有提供足够的代码来解决您的问题。我猜源纹理的大小是 512x1024,但由于您没有发布该代码,我不知道您的源纹理是否与定义的大小匹配。您也没有发布目标的大小。您发布的顶部图像是 471x488。那是你的目标尺寸吗?您也没有发布您正在使用的纹理坐标以及操作它们的代码的代码。

猜测您的来源是 512x1024,您的目标是 471x488 我无法解决您的问题。

const fs = `
precision highp float;

uniform sampler2D tex;
varying vec2 v_texcoord;

float tex2DBiLinear( sampler2D textureSampler_i, vec2 texCoord_i )
{
float fHeight = 1024.0;
float fWidth = 512.0;
float texelSizeX = 1.0/fWidth;
float texelSizeY = 1.0/fHeight;

    float p0q0 = texture2D(textureSampler_i, texCoord_i)[0];
    float p1q0 = texture2D(textureSampler_i, texCoord_i + vec2(texelSizeX, 0))[0];

    float p0q1 = texture2D(textureSampler_i, texCoord_i + vec2(0, texelSizeY))[0];
    float p1q1 = texture2D(textureSampler_i, texCoord_i + vec2(texelSizeX , texelSizeY))[0];

    float a = fract( texCoord_i.x * fWidth ); // Get Interpolation factor for X direction.
                    // Fraction near to valid data.

    float pInterp_q0 = mix( p0q0, p1q0, a ); // Interpolates top row in X direction.
    float pInterp_q1 = mix( p0q1, p1q1, a ); // Interpolates bottom row in X direction.

    float b = fract( texCoord_i.y * fHeight );// Get Interpolation factor for Y direction.
    return mix( pInterp_q0, pInterp_q1, b ); // Interpolate in Y direction.
}

void main() {
  gl_FragColor = vec4(tex2DBiLinear(tex, v_texcoord), 0, 0, 1);
}
`;

const vs = `
attribute vec4 position;
attribute vec2 texcoord;
varying vec2 v_texcoord;
void main() {
  gl_Position = position;
  v_texcoord = texcoord;
}
`;

const gl = document.querySelector('canvas').getContext('webgl');
// compile shaders, link programs, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData for each array
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
  position: {
    numComponents: 2,
    data: [
      -1, -1,
       1, -1,
      -1,  1,
       1,  1,
    ],
  },
  texcoord: [
    0, 0,
    1, 0,
    0, 1,
    1, 1,
  ],
  indices: [
    0, 1, 2,
    2, 1, 3,
  ],
});


const ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = 512;
ctx.canvas.height = 1024;
const gradient = ctx.createRadialGradient(256, 512, 0, 256, 512, 700);

gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'cyan');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 512, 1024);

const tex = twgl.createTexture(gl, {
  src: ctx.canvas,
  minMag: gl.NEAREST,
  wrap: gl.CLAMP_TO_EDGE,
  auto: false,
});

gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas width="471" height="488"></canvas>

如果您认为问题与浮点纹理有关,我也不能在那里回购

const fs = `
precision highp float;

uniform sampler2D tex;
varying vec2 v_texcoord;

float tex2DBiLinear( sampler2D textureSampler_i, vec2 texCoord_i )
{
float fHeight = 1024.0;
float fWidth = 512.0;
float texelSizeX = 1.0/fWidth;
float texelSizeY = 1.0/fHeight;

    float p0q0 = texture2D(textureSampler_i, texCoord_i)[0];
    float p1q0 = texture2D(textureSampler_i, texCoord_i + vec2(texelSizeX, 0))[0];

    float p0q1 = texture2D(textureSampler_i, texCoord_i + vec2(0, texelSizeY))[0];
    float p1q1 = texture2D(textureSampler_i, texCoord_i + vec2(texelSizeX , texelSizeY))[0];

    float a = fract( texCoord_i.x * fWidth ); // Get Interpolation factor for X direction.
                    // Fraction near to valid data.

    float pInterp_q0 = mix( p0q0, p1q0, a ); // Interpolates top row in X direction.
    float pInterp_q1 = mix( p0q1, p1q1, a ); // Interpolates bottom row in X direction.

    float b = fract( texCoord_i.y * fHeight );// Get Interpolation factor for Y direction.
    return mix( pInterp_q0, pInterp_q1, b ); // Interpolate in Y direction.
}

void main() {
  gl_FragColor = vec4(tex2DBiLinear(tex, v_texcoord), 0, 0, 1);
}
`;

const vs = `
attribute vec4 position;
attribute vec2 texcoord;
varying vec2 v_texcoord;
void main() {
  gl_Position = position;
  v_texcoord = texcoord;
}
`;

const gl = document.querySelector('canvas').getContext('webgl');
const ext = gl.getExtension('OES_texture_float');
if (!ext) { alert('need OES_texture_float'); }
// compile shaders, link programs, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData for each array
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
  position: {
    numComponents: 2,
    data: [
      -1, -1,
       1, -1,
      -1,  1,
       1,  1,
    ],
  },
  texcoord: [
    0, 0,
    1, 0,
    0, 1,
    1, 1,
  ],
  indices: [
    0, 1, 2,
    2, 1, 3,
  ],
});


const ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = 512;
ctx.canvas.height = 1024;
const gradient = ctx.createRadialGradient(256, 512, 0, 256, 512, 700);

gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'cyan');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 512, 1024);

const tex = twgl.createTexture(gl, {
  src: ctx.canvas,
  type: gl.FLOAT,
  minMag: gl.NEAREST,
  wrap: gl.CLAMP_TO_EDGE,
  auto: false,
});

gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
const e = gl.getExtension('WEBGL_debug_renderer_info');
if (e) {
  console.log(gl.getParameter(e.UNMASKED_VENDOR_WEBGL));
  console.log(gl.getParameter(e.UNMASKED_RENDERER_WEBGL));
}
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas width="471" height="488"></canvas>

如果任何值关闭。如果您的源纹理大小与fWidthfHeigth 不匹配,或者如果您的纹理坐标不同或以某种方式调整,那么当然也许我可以回购。如果其中任何一个不同,那么我可以想象问题。

在 Intel Iris Pro 和 Intel HD Graphics 630 中进行了测试。还在 iPhone6+ 上进行了测试。请注意,您需要确保片段着色器在 precision highp float 中运行,但该设置可能只会影响移动 GPU。

【讨论】:

  • 不知道为什么有人在 3 年多之后赏金,今天的代码看起来与我原来的问题有点不同。函数本身似乎不是问题当然非常有趣,尽管我很确定纹理大小匹配,因为当前代码不再使用硬编码值。我无法访问旧计算机来重现相同的情况,我必须看看我今天是否可以使用不同的英特尔硬件和驱动程序版本再次重现。
  • 我添加了赏金。我在 iMac Radeon Pro 580 上看到了与原始帖子中描述的相同的问题。@gman,您对它在 texture2D 中如何工作的解释很有帮助,但它只是改变了工件,并没有让它消失。我看到使用各种纹理大小的问题,并且涉及的缩放越多似乎越明显。
  • 我不确定 Mad Scientist 想要做什么,但我需要在我的着色器中进行一些整数数学运算,因为插值会导致问题。在没有 interp 的情况下预处理纹理数据和绘制到屏幕外缓冲区在性能成本方面都太高了。我仍然希望最终结果看起来很平滑,所以我认为在着色器中进行双线性插值将是一个很好的解决方案。它工作得很好,除了这个奇怪的神器,就像上面描述的一样。疯狂科学家建议的解决方案确实有效,但我希望有另一种方法。
  • @whiskeyspider。只是为了确认一下,您在 iMac Radeon Pro 580 上的这个答案中看到了使用 sn-ps 的工件?
  • @gman 不,我无法使用您的 sn-ps 重新创建它,就像我可以使用自己的代码一样,所以它必须涉及其他内容。我将尝试整理一些演示该问题的示例代码。
【解决方案2】:

我们遇到了在特定纹理缩放时出现的几乎相同的问题。我们发现在这种情况下可以检测到出现伪影的位置:

vec2 imagePosCenterity = fract(uv * imageSize);
if (abs(imagePosCenterity.x-0.5) < 0.001 || abs(imagePosCenterity.y-0.5) < 0.001) {}

imageSize 是纹理的宽度和高度。

我们的解决方案如下所示:

vec4 texture2DLinear( sampler2D texSampler, vec2 uv) {
    vec2 pixelOff = vec2(0.5,0.5)/imageSize;

    vec2 imagePosCenterity = fract(uv * imageSize);
    if (abs(imagePosCenterity.x-0.5) < 0.001 || abs(imagePosCenterity.y-0.5) < 0.001) {
        pixelOff = pixelOff-vec2(0.00001,0.00001);
    }

    vec4 tl = texture2D(texSampler, uv + vec2(-pixelOff.x,-pixelOff.y));
    vec4 tr = texture2D(texSampler, uv + vec2(pixelOff.x,-pixelOff.y));
    vec4 bl = texture2D(texSampler, uv + vec2(-pixelOff.x,pixelOff.y));
    vec4 br = texture2D(texSampler, uv + vec2(pixelOff.x,pixelOff.y));
    vec2 f = fract( (uv.xy-pixelOff) * imageSize );
    vec4 tA = mix( tl, tr, f.x );
    vec4 tB = mix( bl, br, f.x );
    return mix( tA, tB, f.y );
}

这确实是一个肮脏的解决方案,但它确实有效。按照上面的建议更改 texelSize 只会将工件移动到另一个位置。我们只在有问题的位置上稍微改变了 texelSize。

为什么我们在 GLSL 着色器中使用线性纹理插值?这是因为我们需要使用每个像素 1 个样本,每个样本纹理 16 位,以及广泛的兼容设备。只有OES_texture_half_float_linear extension 才能做到这一点。通过我们的方法,可以在不使用扩展的情况下解决它。

【讨论】:

  • 谢谢,这真的很有帮助。我注意到了同样的事情,除了我的工件与纹素边缘对齐。用 1.0 替换 0.5,为我解决了这个问题。
猜你喜欢
  • 2018-08-29
  • 2013-09-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-26
  • 2019-01-23
  • 1970-01-01
相关资源
最近更新 更多