ios - OpenGL ES 2 中足够快的实时卷积着色器的最佳方法是什么?

标签 ios opengl-es-2.0 shader

注意:现在我正在模拟器中对此进行测试。但我的想法是,我可以在 iPhone 4s 中获得可接受的性能。 (我知道,我应该在设备上进行测试,但我要过几天才能拿到设备)。

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

  1. 一个简单的 3x3 盒式过滤器,单次通过,几乎不会使图像模糊。因此,为了获得更明显的模糊效果,我必须使用 3x3 2-pass 或 5x5。
  2. 最简单的情况(3x3,1 次通过)已经足够慢,无法以 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;
     }

     );
}

这里,我还是通过uniform把预计算好的kernel传给了fragment shader。但是我现在在着色器中计算纹素偏移量甚至内核索引。我预计这种方法会更慢,因为我不仅有 2 个 for 循环,而且我还为每个片段做了一堆额外的计算。

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

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

只是为了比较,请注意以下示例是框过滤的具体实现,A - 几乎是硬编码的,B - 并不真正遵守经典 nxn 线性滤波器的理论定义(它不是矩阵加起来不等于 1),我尝试了 OpenGL ES 2.0 编程指南中的这种方法:

- (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 示例那样进行简化?

最佳答案

如果您通过 Instruments 中的 OpenGL ES 分析工具或 Xcode 中的帧调试器运行它们,您可能会看到关于 dependent texture reads 的注释-- 你在片段着色器中计算纹理坐标,这意味着硬件在评估着色器之前无法获取纹理数据。如果进入片段着色器的纹素坐标已知,则硬件可以与其他任务并行预取纹素数据,因此在片段着色器需要它时它已准备就绪。

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

关于ios - OpenGL ES 2 中足够快的实时卷积着色器的最佳方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18939890/

相关文章:

objective-c - 分发配置文件有效,但开发配置文件显示未找到有效的签名实体

ios - UIImagepicker 上传到错误的 Firebase 存储

ios - 通过 UISlider 更改 UITextView 的字体大小

android - 删除容器之间的线( flutter/Dart )

opengl-es - 仅更新 OpenGL ES 2.0 中纹理的水平子区域

java - GLSL 中没有出现纹理的可能原因有哪些?

ios - iOS 上的 OpenGL ES 2.0 对象拾取

Android:性能问题多个纹理 Opengl ES

shader - .hlsl 和 .hlsli 有什么区别?

opengl - 为什么我无法知道 GLSL 中 OpenGL 灯光的状态?