注意:现在我正在模拟器中对此进行测试。但我的想法是,我得到了可接受的性能,比如iPhone 4s。 (我知道,我应该在设备上进行测试,但几天后我就没有设备了。)
我正在玩制作一个卷积着色器,可以使用支持3x3,5x5或7x7的滤镜对图像进行卷积,并可以选择多次通过。着色器本身就可以工作了。但我注意到以下几点:
到目前为止我尝试了两种方法(这是我为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;
}
);
}
这种方法的想法是,过滤器值和offsetCoordinates to fetch texels都在Client / App中预先计算一次,然后在制服中设置。然后,着色器程序将始终在任何时候使用它们。请注意,统一数组(49)的大尺寸是因为我可以做到7x7内核。
此方法每次传递需要.46s。
然后我尝试了以下方法:
- (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线性过滤器的理论定义(它不是矩阵并且不等于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指南中的原始着色器使用了更大的步骤,模糊了更多。
所以说到上述所有内容,我的问题实际上是双重的:
答案 0 :(得分:3)
如果您通过Instruments中的OpenGL ES分析工具或Xcode中的Frame Debugger运行它们,您可能会看到关于dependent texture reads的注释 - 您正在计算片段着色器中的texcoords,这意味着硬件无法获取纹素数据,直到它在评估着色器时达到这一点。如果已知纹素坐标进入片段着色器,硬件可以与其他任务并行预取texel数据,因此在片段着色器需要它时就可以使用了。
通过在顶点着色器中预先计算纹素坐标,可以大大加快速度。 Brad Larson在this answer中就类似的问题提供了一个很好的例子。
答案 1 :(得分:0)
我没有关于你的确切问题的答案,但你应该看看GPUImage框架 - 它实现了几个框模糊过滤器(见SO question) - 其中一个2遍9x9过滤器 - 您还可以看到本文针对不同方法的实时FPS:vImage VS GPUImage vs CoreImage