注意:现在我正在模拟器中测试这个。但我的想法是,我可以在 iPhone 4s 上获得可接受的性能。(我知道,我应该在设备上进行测试,但我将有几天没有设备)。
我正在制作一个卷积着色器,它允许使用支持 3x3、5x5 或 7x7 的过滤器和多通道选项对图像进行卷积。我猜着色器本身可以工作。但我注意到以下几点:
- 一个简单的 3x3 盒式过滤器,单通道,几乎不会模糊图像。所以为了得到更明显的模糊,我必须做 3x3 2-pass 或 5x5。
- 最简单的情况(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 指南中的原始着色器使用了更大的步长,从而模糊了更多。
综上所述,我的问题实际上有两个方面:
- 我将步长/纹素偏移量计算为 vec2(1 / 图像宽度,1 / 图像高度)。就像我说的那样,有了这个偏移量,3x3 盒式过滤器几乎看不到。它是否正确?还是我误解了步骤的计算或其他什么?
- 我还能做些什么来尝试让“一般情况下的卷积”方法能够以足够快的速度实时运行?或者我是否一定需要像 OpenGL 示例那样进行简化?