这是一个实用且性能足够好的着色器,可以在移动设备上进行模糊处理吗?

Is this a practical and enough performant shader for doing blur on mobile device

我正在尝试使用 GLSL 着色器在我的移动设备游戏中实现模糊效果。我以前没有任何编写着色器的经验。而且我不明白我的着色器是否足够好。其实我从一个教程中复制了GLSL代码,我不知道这个教程是为了生动的演示还是可以在实践中使用。这是使用高斯权重 (http://www.cocos2d-x.org/wiki/User_Tutorial-RenderTexture_Plus_Blur) 的两遍模糊着色器的代码:

#ifdef GL_ES                                                                      
precision mediump float;
#endif                                                                            

varying vec4 v_fragmentColor;                                                     
varying vec2 v_texCoord;                                                          

uniform vec2 pixelSize;
uniform vec2 direction;
uniform int radius;
uniform float weights[64];

void main() 
{
    gl_FragColor = texture2D(CC_Texture0, v_texCoord)*weights[0];
    for (int i = 1; i < radius; i++) {
        vec2 offset = vec2(float(i)*pixelSize.x*direction.x, float(i)*pixelSize.y*direction.y);
        gl_FragColor += texture2D(CC_Texture0, v_texCoord + offset)*weights[i];
        gl_FragColor += texture2D(CC_Texture0, v_texCoord - offset)*weights[i];
    }
}   

我 运行 这个着色器在每个帧更新(一秒内 60 次)并且我的游戏帧率只有一次通过 iPhone 5S 下降到 22 FPS(不是一个坏设备) .我认为这非常非常奇怪。似乎没有太多的指导。为什么这么重?

P.S。模糊半径为50,步长为1.

要加快速度,您可以:

  1. 将半径设置为常量以允许着色器编译器展开循环
  2. 预计算像素大小 * 方向
  3. 减小半径,我认为 50 对移动设备来说太大了

你的着色器很重的主要原因:

1:这两个计算:v_texCoord + offsetv_texCoord - offset。因为 uv 坐标是在片段着色器中计算的,所以必须当场从内存中加载纹理数据,从而导致缓存未命中。

What is a dependent texture read?

2:radius 太大了。

制作方法faster/better:

1:尽可能在vertex shader中计算。理想情况下,如果您在顶点着色器中计算所有 UV,GPU 可以在调用片段着色器之前移动缓存中的纹理内存,从而显着提高性能。

2:减少 Radius 以容纳比方说 8-16 texture2D 次调用。这可能不会给您预期的结果,为了解决这个问题,您可以使用 2 个纹理,将纹理 A 模糊到 B 中,然后再次将 B 模糊到纹理 A 中,依此类推,根据需要进行混合。这会产生很好的效果,我记得crisys 1用它来做运动模糊,但我找不到论文。

3:消除那64个制服,将所有数据硬编码在着色器中。我知道这不是很好,但你会获得一些额外的性能。

4:如果仔细计算 UV 坐标,您可以充分利用纹理插值。基本上从不在它的中心采样一个像素,总是在像素之间采样,硬件会做和附近 4 个像素的平均值:

https://en.wikipedia.org/wiki/Bilinear_filtering

5:这一行:precision mediump float; 是否所有内容都必须是 mediump?我建议删除它并尽可能多地使用 lowp 进行一些测试。

编辑: 对于您的着色器,这是您需要执行的简化版本:

顶点着色器:

attribute highp vec4 Position;
attribute mediump vec2 texture0UV;

varying mediump vec2 v_texCoord0;
varying mediump vec2 v_texCoord1;
varying mediump vec2 v_texCoord2;
varying mediump vec2 v_texCoord3;
varying mediump vec2 v_texCoord5;

uniform mediump vec2 texture_size;

void main() 
{
    gl_Position = Position;
    vec2 pixel_size = vec2(1.0) / texture_size;
    vec2 offset;


    v_texCoord0 = texture0UV;
    v_texCoord1 = texture0UV + vec2(-1.0,0.0) / texture_size + pixel_size * 0.5;
    v_texCoord2 = texture0UV + vec2(0.0,-1.0) / texture_size + pixel_size * 0.5;
    v_texCoord3 = texture0UV + vec2(1.0,0.0) / texture_size  - pixel_size * 0.5;
    v_texCoord4 = texture0UV + vec2(0.0,1.0) / texture_size  - pixel_size * 0.5;
}

需要最后一个操作 pixel_size * 0.5 才能最大程度地利用线性插值。在这个例子中,你选择的采样位置是微不足道的,但是关于你应该如何选择采样位置的整个讨论超出了这个问题的范围。

片段着色器:

varying mediump vec2 v_texCoord0;
varying mediump vec2 v_texCoord1;
varying mediump vec2 v_texCoord2;
varying mediump vec2 v_texCoord3;
varying mediump vec2 v_texCoord5;

uniform lowp sampler2D CC_Texture0;

void main() 
{
    mediump vec4 final_color = vec4(0.0);

    final_color += texture2D(CC_Texture0,v_texCoord0);
    final_color += texture2D(CC_Texture0,v_texCoord1);
    final_color += texture2D(CC_Texture0,v_texCoord2);
    final_color += texture2D(CC_Texture0,v_texCoord3);
    final_color += texture2D(CC_Texture0,v_texCoord4);

    gl_FragColor = final_color / 5.0;//weights have to go, use fixed values instead, in this case it's 1/5 for each sample
}

为了看起来不错,您需要多次模糊纹理,即使模糊纹理 2 次,您也应该看到明显的差异。