在着色器运行时,片段着色器的输入纹理是否会更改?

时间:2013-02-11 08:11:51

标签: ios opengl-es glsl fragment-shader gpuimage

我正在尝试使用我们自己的Atkinson dithering algorithmBrad Larson框架在GLSL中的片段着色器中实现GPUImage。 (这可能是那些不可能完成的事情之一,但我还不知道如何确定这一点,所以我只是继续这样做。)

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

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

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);
}

2 个答案:

答案 0 :(得分:2)

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

对于潜在的着色器友好抖动实现,请参阅问题"Floyd–Steinberg dithering alternatives for pixel shader."

你通常也不能写入和读取相同的纹理,虽然Apple确实在iOS 6.0中添加了一些扩展,让你可以写入帧缓冲区并在相同的渲染过程中读取该写入的值。

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

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

最后,为了回答您的标题问题,通常您无法在读取时修改纹理,但iOS纹理缓存机制有时允许您在着色器正在通过场景时覆盖纹理值。这通常会导致严重的撕裂伪影。

答案 1 :(得分:1)

@GarrettAlbright对于1-Bit Camera应用程序,我最终只使用原始内存指针和(相当)紧密优化的C代码迭代图像数据。我研究了NEON内在函数和Accelerate框架,但是任何并行都真的搞砸了这种算法,所以我没有使用它。

我还想到了这个想法,首先要对GPU上的错误分布进行足够好的近似,然后在另一个传递中进行阈值处理,但是我从来没有得到任何东西,只是来自那些实验的相当难看的噪声抖动。 some papers周围有关于在GPU上接近扩散抖动的其他方法。