2

我正在尝试使用我们自己的Brad LarsonGPUImage框架在 GLSL 的片段着色器中实现阿特金森抖动算法。(这可能是不可能的事情之一,但我还没有足够的知识来确定这一点,所以我还是继续做下去。)

Atkinson 算法将灰度图像抖动为在原始 Macintosh 上看到的纯黑色和白色。基本上,我需要调查我的像素周围的几个像素,并确定每个像素与纯黑色或纯白色的距离有多远,并使用它来计算累积的“误差”;该错误值加上给定像素的原始值决定了它应该是黑色还是白色。问题是,据我所知,误差值(几乎?)总是零或不知不觉地接近它。我在想可能发生的是我正在采样的纹理与我正在写入的纹理相同,因此错误最终为零(或接近它),因为我的大多数/所有像素' m 采样已经是黑色或白色。

这是正确的,还是我从中采样和写入的纹理不同?如果是前者,有没有办法避免这种情况?如果是后者,那么您是否能够发现此代码的其他错误?'因为我很难过,也许不知道如何正确调试它。

varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;

uniform highp vec3 dimensions;

void main()
{
    highp vec2 relevantPixels[6];

    relevantPixels[0] = vec2(textureCoordinate.x, textureCoordinate.y - 2.0);
    relevantPixels[1] = vec2(textureCoordinate.x - 1.0, textureCoordinate.y - 1.0);
    relevantPixels[2] = vec2(textureCoordinate.x, textureCoordinate.y - 1.0);
    relevantPixels[3] = vec2(textureCoordinate.x + 1.0, textureCoordinate.y - 1.0);
    relevantPixels[4] = vec2(textureCoordinate.x - 2.0, textureCoordinate.y);
    relevantPixels[5] = vec2(textureCoordinate.x - 1.0, textureCoordinate.y);

    highp float err = 0.0;

    for (mediump int i = 0; i < 6; i++) {
        highp vec2 relevantPixel = relevantPixels[i];
        // @todo Make sure we're not sampling a pixel out of scope. For now this
        // doesn't seem to be a failure (?!).
        lowp vec4 pointColor = texture2D(inputImageTexture, relevantPixel);
        err += ((pointColor.r - step(.5, pointColor.r)) / 8.0);
    }

    lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
    lowp float hue = step(.5, textureColor.r + err);

    gl_FragColor = vec4(hue, hue, hue, 1.0);
}
4

2 回答 2

2

这里有一些问题,但最大的问题是 Atkinson 抖动无法在片段着色器中以有效的方式执行。这种抖动是一个顺序过程,取决于它上面和后面的片段的结果。片段着色器只能写入 OpenGL ES 中的一个片段,而不是您指向的 Python 实现中需要的相邻片段。

有关潜在的着色器友好抖动实现,请参阅问题“像素着色器的 Floyd–Steinberg 抖动替代方案”。

您通常也不能写入和读取相同的纹理,尽管 Apple 确实在 iOS 6.0 中添加了一些扩展,允许您写入帧缓冲区并在同一渲染通道中读取写入的值。

至于为什么您会看到奇怪的错误结果,GPUImage 过滤器中的坐标系被标准化为 0.0 - 1.0 的范围。当您尝试通过添加 1.0 来偏移纹理坐标时,您正在读取纹理的末尾(然后默认情况下将其限制为边缘的值)。这就是为什么你看到我在其他需要从相邻像素采样的过滤器中使用 texelWidth 和 texelHeight 值作为制服。这些计算为整个图像宽度和高度的一部分。

我也不建议在片段着色器中进行纹理坐标计算,因为这会导致读取依赖纹理并真正减慢渲染速度。如果可以,将其移至顶点着色器。

最后,要回答您的标题问题,通常您无法在读取纹理时对其进行修改,但 iOS 纹理缓存机制有时允许您在着色器通过场景工作时覆盖纹理值。这通常会导致不良的撕裂伪影。

于 2013-02-11T22:23:18.387 回答
1

@GarrettAlbright 对于 1-Bit Camera 应用程序,我最终使用原始内存指针和(相当)严格优化的 C 代码简单地迭代图像数据。我研究了 NEON intrisics 和 Accelerate 框架,但任何并行性确实会破坏这种性质的算法,所以我没有使用它。

I also toyed around with the idea to do a decent enough aproximation of the error distribution on the GPU first, and then do the thresholding in another pass, but I never got anything but a rather ugly noise dither from those experiments. There are some papers around covering other ways of approaching diffusion dithering on the GPU.

于 2013-05-07T12:26:52.283 回答