注:現在、シミュレーターでこれをテストしています。しかし、アイデアとしては、たとえば iPhone 4s で許容できるパフォーマンスが得られるということです。(デバイスでテストする必要があることはわかっていますが、数日間デバイスを持っていません)。
私は、3x3、5x5、または 7x7 をサポートするフィルターと複数パスのオプションを使用して画像を畳み込むことができる畳み込みシェーダーを作成して遊んでいました。シェーダー自体は機能すると思います。しかし、私は次のことに気付きます:
- 単純なボックス フィルター 3x3、シングル パスでは、画像がほとんどぼやけません。そのため、ぼかしをより目立たせるには、3x3 2 パスまたは 5x5 のいずれかを行う必要があります。
- 最も単純なケース (3x3、1 パス) は、すでに十分に遅いため、たとえば 30 fps では使用できません。
これまでに2つのアプローチを試しました(これは、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 の両方が、クライアント/アプリ ランドで一度事前計算されてから、ユニフォームに設定されるというものです。そうすれば、シェーダー プログラムはいつでも使用できるようになります。一様配列の大きなサイズ (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 カーネルを 2 つの分離可能な 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 ガイドの元のシェーダーはより大きなステップを使用しており、よりぼかしています。
上記のすべてが述べられているので、私の質問は実際には2つあります。
- ステップ/テクセル オフセットを vec2(1/画像幅、1/画像高さ) として計算しています。前述のように、このオフセットでは、3x3 ボックス フィルターはほとんど目立ちません。これは正しいです?または、ステップの計算などを誤解していますか?
- 「一般的なケースでの畳み込み」アプローチをリアルタイムで十分に高速に実行するために他にできることはありますか? それとも、OpenGL の例のように単純化する必要がありますか?