如何修复着色器创建的形状中的“闪烁”伪影?

问题描述 投票:0回答:1

我目前正在 OpenGL 中编写一个程序,其中我仅使用片段着色器渲染场景的一部分,而不是使用纹理(这使得它无法使用 mipmap 来解决)。所讨论的部分是网格,它是场景的一部分,因此会经常移动和缩放。然而,问题在于,每当相机处于 45° 的倍数角度时,就会出现一些闪烁伪影,尤其是在平移视图时。

这是一个例子:

Uneven grid lines

质量不是很好,但你仍然可以看到网格线完全不均匀,而它们应该以相同的宽度渲染。

这是生成网格线的代码部分:

if (uDrawGrid) {
    // Texture coordinates.
    float x = fTexCoordinate.x;
    float y = fTexCoordinate.y;
    // Rounding the coordinates to 3 decimals, 
    // so we have the targetX and targetY of the grid, for this fragment.
    float targetX = round(x * 1000.0) / 1000.0;
    float targetY = round(y * 1000.0) / 1000.0;
    // Factor is basically the width of the grid lines, 
    // inversely proportional to the size of the underlying texture.
    vec2 factor = vec2(0.05 / uTexSize.x, 0.05 / uTexSize.y);
    // dx and dy are the distances from the current position to the target, plus a small offset.
    float dx = abs(x - targetX + factor.x / 20.0);
    float dy = abs(y - targetY + factor.y / 20.0);
    // Checking if the distances are smaller than the grid line widths.
    if (dx <= factor.x || dy <= factor.y) {
        // Blending with grid color being (0.5, 0.5, 0.5, 0.5)
        color.a = 1.0;
        color.r = 0.25 + topColor.r * 0.5;
        color.g = 0.25 + topColor.g * 0.5;
        color.b = 0.25 + topColor.b * 0.5;
        return;
    }
}

备注:

  1. uDrawGrid
    是一个布尔值,只是为了检查是否应该绘制网格。
  2. uTexSize
    是一个 vec2 变量,包含绘制网格的底层纹理的大小。
  3. 混合部分只是在下面的任何内容上绘制网格线。
  4. precision highp float;
    precision highp int;
    已设置在着色器的顶部。
  5. 只要相机角度不那么“均匀”,例如 13.59°,这种伪像就不会发生
问题: 我怎样才能避免这种影响的发生?我是否应该使用某种数学公式(替换我原来的),以便始终以相同数量的宽度像素绘制线条?

编辑: 在图像中,我的意思是不均匀的网格线是较小的。角落里的大十字是背景纹理的一部分,圆圈是 Android 上的点击。

opengl graphics glsl
1个回答
0
投票
我希望有人能在我年轻的时候教我这样的图形:

您已将网格的颜色定义为 uv 坐标的函数。 这很酷。但为了渲染这个函数,从技术上讲,每个像素应该被着色为函数在像素区域上的“平均值”。从数学上讲,这是函数在像素面积上的积分除以像素面积。

您有四种选择:

忽略该问题,并对每个片段对参数函数进行一次点采样。享受你闪闪发光的文物。

    使用全屏抗锯齿 (FSAA) 或多样本抗锯齿 (MSAA) 让光栅化器多次计算片段并对结果进行平均。性能损失与 MSAA 因子成正比。
  1. 让着色器在每个片段的多个位置对参数函数进行采样,并通过采样 N 次手动计算平均值。性能损失与 N 成正比。
  2. 直接计算参数函数在像素区域上的定积分。最好的选择,如果你会数学的话。
  3. 推广到一维,网格线的参数函数是一个“不对称方波”,其不定积分可以很容易地用两部分来表征。当方波高时它上升,当方波低时它稳定。从图形上看,它看起来像这样:

此函数必须集成在当前片段正在渲染的像素区域上。在图中,这是从 a 到 b 的范围。值得注意的是,当网格线相对于像素变小时,像素可能完全或部分包围任意数量网格线的任何部分。 要计算像素的强度,您需要精确计算积分两次,一次是在像素覆盖范围的最小值和最大值处,然后减去这些值并除以像素的面积以获得最能代表像素的强度整个像素的平均强度。

在 Desmos 中像这样可视化网格函数的积分,使得编写计算着色器的代码变得相当直观。

在我的示例中,我将网格线的宽度表示为线之间间距的一部分。你的做法有点不同,但我认为我的形式更容易整合。

这是一个适合您的实现。

// Integral of the line's cross-section intensity function vec2 g(vec2 uv, float portion) { vec2 c = vec2(1.0, 1.0) * portion; vec2 t = floor(mod(-uv - c / 2.0, 1.0) + c); vec2 whole = floor(uv + c / 2.0); vec2 frac = (uv + c / 2.0) - whole; vec2 rising = (frac + whole * c); vec2 level = (c / 2.0 + whole * c) + c / 2.0; return mix(level, rising, t); } float linearToSRGB(float linear) { if (linear <= 0.0031308) { return 12.92 * linear; } else { return 1.055 * pow(linear, 1.0/2.2) - 0.055; } return linear; } void mainImage( out vec4 fragColor, in vec2 fragCoord ) { // You can choose gnarly numbers here for gridSize and lineWidth float gridSize = 13.13; float lineWidth = 0.55; float fraction = lineWidth / gridSize; vec2 uv = fragCoord / gridSize; vec2 pixelSize = vec2(1.0, 1.0) / gridSize; vec2 average = (g(uv + pixelSize, fraction) - g(uv, fraction)) / pixelSize; float linear = max(average.x, average.y); vec3 srgb = vec3(1.0, 1.0, 1.0) * linearToSRGB(linear); fragColor = vec4(srgb.rgb, 1.0); }

© www.soinside.com 2019 - 2024. All rights reserved.