从纹理中使用片段着色器进行边缘/轮廓检测

3
我正在尝试在WebGL中显示纹理的清晰轮廓。 我将一张纹理传递给我的片段着色器,然后使用本地导数来显示轮廓/轮廓,但是它不像我期望的那样平滑。
如果直接打印纹理而不进行处理,效果与预期相同:
vec2 texc = vec2(((vProjectedCoords.x / vProjectedCoords.w) + 1.0 ) / 2.0,
            ((vProjectedCoords.y / vProjectedCoords.w) + 1.0 ) / 2.0 );
vec4 color = texture2D(uTextureFilled, texc);
gl_FragColor = color;

enter image description here

使用本地导数,会错过一些边缘:

vec2 texc = vec2(((vProjectedCoords.x / vProjectedCoords.w) + 1.0 ) / 2.0,
            ((vProjectedCoords.y / vProjectedCoords.w) + 1.0 ) / 2.0 );
vec4 color = texture2D(uTextureFilled, texc);
float maxColor = length(color.rgb);
gl_FragColor.r = abs(dFdx(maxColor));
gl_FragColor.g = abs(dFdy(maxColor));
gl_FragColor.a = 1.;

enter image description here

1个回答

6

理论上,你的代码是正确的。

但在实践中,大多数GPU会在2x2像素块上计算导数。因此,对于这样一个块的所有4个像素,dFdX和dFdY的值将是相同的。(详细解释在这里

这会导致某种类型的混叠,并且你将随机地错过一些形状轮廓的像素(当黑色到形状颜色的转换发生在2x2块的边界时会发生这种情况)。

要解决这个问题并获得真正的每像素导数,你可以自己计算它,这看起来像这样:

// get tex coordinates
vec2 texc = vec2(((vProjectedCoords.x / vProjectedCoords.w) + 1.0 ) / 2.0,
                 ((vProjectedCoords.y / vProjectedCoords.w) + 1.0 ) / 2.0 );

// compute the U & V step needed to read neighbor pixels
// for that you need to pass the texture dimensions to the shader, 
// so let's say those are texWidth and texHeight
float step_u = 1.0 / texWidth;
float step_v = 1.0 / texHeight;

// read current pixel
vec4 centerPixel = texture2D(uTextureFilled, texc);

// read nearest right pixel & nearest bottom pixel
vec4 rightPixel  = texture2D(uTextureFilled, texc + vec2(step_u, 0.0));
vec4 bottomPixel = texture2D(uTextureFilled, texc + vec2(0.0, step_v));

// now manually compute the derivatives
float _dFdX = length(rightPixel - centerPixel) / step_u;
float _dFdY = length(bottomPixel - centerPixel) / step_v;

// display
gl_FragColor.r = _dFdX;
gl_FragColor.g = _dFdY;
gl_FragColor.a = 1.;

几个重要的事项:

  • 纹理不应使用mipmaps
  • 纹理最小和最大过滤应设置为GL_NEAREST
  • 纹理夹取模式应设置为clamp(而不是repeat)

这里有一个ShaderToy示例,演示了这一点:

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接