3

注意:现在我正在模拟器中测试这个。但我的想法是,我可以在 iPhone 4s 上获得可接受的性能。(我知道,我应该在设备上进行测试,但我将有几天没有设备)。

我正在制作一个卷积着色器,它允许使用支持 3x3、5x5 或 7x7 的过滤器和多通道选项对图像进行卷积。我猜着色器本身可以工作。但我注意到以下几点:

  1. 一个简单的 3x3 盒式过滤器,单通道,几乎不会模糊图像。所以为了得到更明显的模糊,我必须做 3x3 2-pass 或 5x5。
  2. 最简单的情况(3x3,1-pass)已经足够慢,以至于不能以 30 fps 的速度使用。

到目前为止,我尝试了两种方法(这是针对我为 iPhone 做的一些基于 OGLES2 的插件,这就是这些方法的原因):

- (NSString *)vertexShader
{

    return SHADER_STRING
    (
     attribute vec4 aPosition;
     attribute vec2 aTextureCoordinates0;

     varying vec2 vTextureCoordinates0;

     void main(void)
     {
         vTextureCoordinates0 = aTextureCoordinates0;
         gl_Position = aPosition;
     }

     );
}

- (NSString *)fragmentShader
{
    return SHADER_STRING
    (
     precision highp float;

     uniform sampler2D uTextureUnit0;
     uniform float uKernel[49];
     uniform int uKernelSize;
     uniform vec2 uTextureUnit0Offset[49];
     uniform vec2 uTextureUnit0Step;

     varying vec2 vTextureCoordinates0;

     void main(void)
     {
         vec4 outputFragment = texture2D(uTextureUnit0, vTextureCoordinates0 + uTextureUnit0Offset[0] * uTextureUnit0Step) * uKernel[0];
         for (int i = 0; i < uKernelSize; i++) {
             outputFragment += texture2D(uTextureUnit0, vTextureCoordinates0 + uTextureUnit0Offset[i] * uTextureUnit0Step) * uKernel[i];
         }

         gl_FragColor = outputFragment;
     }

     );
}

这种方法的想法是过滤器值和用于获取纹素的偏移坐标都在客户端/应用程序域中预先计算一次,然后在制服中设置。然后,着色器程序将始终使它们在使用时可用。请注意,统一数组(49)的大尺寸是因为我可能会做一个 7x7 内核。

这种方法每次通过需要 0.46 秒。

然后我尝试了以下方法:

- (NSString *)vertexShader
{

    return SHADER_STRING
    (
     // Default pass-thru vertex shader:
     attribute vec4 aPosition;
     attribute vec2 aTextureCoordinates0;

     varying highp vec2 vTextureCoordinates0;

     void main(void)
     {
         vTextureCoordinates0 = aTextureCoordinates0;
         gl_Position = aPosition;
     }

     );
}

- (NSString *)fragmentShader
{
    return SHADER_STRING
    (
     precision highp float;

     uniform sampler2D uTextureUnit0;
     uniform vec2 uTextureUnit0Step;
     uniform float uKernel[49];
     uniform float uKernelRadius;

     varying vec2 vTextureCoordinates0;

     void main(void)
     {
         vec4 outputFragment = vec4(0., 0., 0., 0.);
         int kRadius = int(uKernelRadius);
         int kSupport  = 2 * kRadius + 1;
         for (int t = -kRadius; t <= kRadius; t++) {
             for (int s = -kRadius; s <= kRadius; s++) {
                 int kernelIndex = (s + kRadius) + ((t + kRadius) * kSupport);
                 outputFragment += texture2D(uTextureUnit0, vTextureCoordinates0 + (vec2(s,t) * uTextureUnit0Step)) * uKernel[kernelIndex];
             }
         }

         gl_FragColor = outputFragment;
     }

     );
}

在这里,我仍然通过制服将预先计算的内核传递给片段着色器。但我现在计算着色器中的纹素偏移甚至内核索引。我希望这种方法会更慢,因为我不仅有 2 个 for 循环,而且我还在为每个片段做一堆额外的计算。

有趣的是,这种方法需要 0.42 秒。其实更快...

在这一点上,我唯一能想到的另一件事是将 2D 内核视为两个可分离的 1D 内核,从而将卷积制动为 2 次通过。还没试过。

只是为了比较,并且知道以下示例是框过滤的特定实现,它是 A - 几乎是硬编码和 B - 并不真正遵守经典 nxn 线性滤波器的理论定义(它不是矩阵并且不t 加起来为 1),我尝试了 OpenGL ES 2.0 Programming guide 中的这种方法:

- (NSString *)fragmentShader
{
    return SHADER_STRING
    (
     // Default pass-thru fragment shader:
     precision mediump float;

     // Input texture:
     uniform sampler2D uTextureUnit0;

     // Texel step:
     uniform vec2 uTextureUnit0Step;


     varying vec2 vTextureCoordinates0;

     void main() {
         vec4 sample0;
         vec4 sample1;
         vec4 sample2;
         vec4 sample3;
         float step = uTextureUnit0Step.x;
         sample0 = texture2D(uTextureUnit0, vec2(vTextureCoordinates0.x - step, vTextureCoordinates0.y - step));
         sample1 = texture2D(uTextureUnit0, vec2(vTextureCoordinates0.x + step, vTextureCoordinates0.y + step));
         sample2 = texture2D(uTextureUnit0, vec2(vTextureCoordinates0.x + step, vTextureCoordinates0.y - step));
         sample3 = texture2D(uTextureUnit0, vec2(vTextureCoordinates0.x - step, vTextureCoordinates0.y + step));
         gl_FragColor = (sample0 + sample1 + sample2 + sample3) / 4.0;
     }
     );
}

这种方法每次通过需要 0.06 秒。 请注意,以上是我的改编,其中我使步骤与我在实现中使用的纹素偏移几乎相同。使用这一步,结果与我的实现非常相似,但 OpenGL 指南中的原始着色器使用了更大的步长,从而模糊了更多。

综上所述,我的问题实际上有两个方面:

  1. 我将步长/纹素偏移量计算为 vec2(1 / 图像宽度,1 / 图像高度)。就像我说的那样,有了这个偏移量,3x3 盒式过滤器几乎看不到。它是否正确?还是我误解了步骤的计算或其他什么?
  2. 我还能做些什么来尝试让“一般情况下的卷积”方法能够以足够快的速度实时运行?或者我是否一定需要像 OpenGL 示例那样进行简化?
4

2 回答 2

3

如果您通过 Instruments 中的 OpenGL ES 分析工具或 Xcode 中的帧调试器运行它们,您可能会看到有关依赖纹理读取的注释——您正在片段着色器中计算 texcoords,这意味着硬件无法获取texel 数据,直到它到达评估着色器的那一点。如果已知 texel 坐标进入片段着色器,硬件可以与其他任务并行预取您的 texel 数据,因此在片段着色器需要它时它已准备好。

您可以通过在顶点着色器中预先计算纹理坐标来大大加快速度。布拉德·拉森 (Brad Larson) 在回答类似问题时有一个很好的例子。

于 2013-09-23T20:30:07.140 回答
0

我没有关于您的确切问题的答案,但您应该看看GPUImage框架 - 它实现了几个框模糊过滤器(参见这个SO 问题) - 其中一个 2-pass 9x9 过滤器 - 您还可以看到这篇文章不同方法的实时 FPS:vImage VS GPUImage VS CoreImage

于 2013-09-23T10:03:02.357 回答