注意:目前我在模拟器中进行测试。但我的想法是在iPhone 4s上获得可接受的性能。(我知道,我应该在设备上进行测试,但我还要等几天才能拿到设备)。
我在尝试制作一个卷积着色器,它可以允许用支持3x3、5x5或7x7的滤波器对图像进行卷积,并具有多次传递的选项。我想这个着色器本身是有效的。但我注意到了以下几点:
- 一个简单的盒式滤波器3x3,单次传递,只能轻微地模糊图像。所以为了获得更明显的模糊效果,我必须使用3x3 2次通行或5x5。
- 最简单的情况(即3x3,1次传递)已经足够慢,以至于无法在30fps的情况下使用。
到目前为止,我已经尝试了两种方法(这是我为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;
}
);
}
在这种方法中,过滤器值和提取纹理像素的偏移坐标在客户端/App领域预先计算,并设置为uniforms。然后,着色器程序会随时可用。请注意,uniform数组的大小(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将预计算的内核传递到片段着色器中。但是现在,我在着色器中计算像素偏移和内核索引。我期望这种方法会更慢,因为我不仅有两个循环,而且对于每个单独的片段,我还要进行一堆额外的计算。
有趣的是,这种方法只需要0.42秒。 实际上更快了...
此时,我能想到的唯一其他事情就是通过将2D内核视为两个可分离的1D内核,将卷积拆分为两个步骤。还没有尝试过。
仅供比较,并且知道以下示例是盒式滤波的特定实现,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指南中的原始着色器使用了更大的步长,会模糊更多。
因此,总的来说,我的问题实际上有两个:
- 我将步骤/像素偏移计算为vec2(1 /图像宽度,1 /图像高度)。使用此偏移量,如我所说,3x3的盒状滤波器几乎不可见。这是正确的吗?还是我误解了步骤的计算或其他什么东西?
- 除了像OpenGL示例那样简化之外,我是否可以尝试其他任何方法使“一般情况下的卷积”方法足够快以实时运行?还是我必须选择简化的方法?
texture2D
的 texcoord 参数是在片段着色器中进行的任何计算的结果,它都会很慢。这包括在顶点着色器中计算的偏移量。如果您在顶点着色器中完成所有的texcoord计算,就像我链接的答案中那样,您应该会看到大大提高的性能。 - rickster