GPUImageの例を使用したシェーダーの紹介





この記事では、デバイスのカメラからのビデオをリアルタイムで処理するiphone用アプリケーションの開発について説明します。 これを行うには、GPUImageフレームワークを使用し、OpenGL ESで独自のシェーダーを記述し、画像を処理するためのフィルターを把握します。





GPUImageフレームワーク



GPUImageはBrad Larsonによって作成され、BSD、GPUを使用して映画、ライブビデオ、画像にフィルターやその他の効果を適用できるライセンスの下で配布されるiOSライブラリです。



GPU対CPU



各iphoneにはCPUとGPUの2つのプロセッサが搭載されており、それぞれに長所と短所があります。

XcodeでCまたはObjective-Cを記述する場合、CPU上で排他的に実行される命令を作成します。 反対に、GPUは、たとえばグラフィックスのレンダリングなど、多くの小さな独立した操作に分割できる計算に特に適した特殊なチップです。 GPUの命令のタイプはCPUとは根本的に異なるため、OpenGL(より正確にはシェーダー言語GLSL)で別の言語でコードを記述します。

CPUとGPUでのビデオのレンダリングのパフォーマンスを比較すると、違いが非常に大きいことがわかります。



フレームレート:CPU対 GPU(より大きなFPSの方が優れています)



計算 GPU FPS CPU FPS
しきい値☓1 60.00 4.21 14.3
しきい値☓2 33.63 2.36 14.3
しきい値☓100 1.45 0.05 28.7




GPUImage vs Core Image



Core Image-ほぼリアルタイムで画像とビデオを処理するための標準フレームワーク。 iOS 5以降に登場し、このバージョンではそれほど多くのフィルターセットはありませんでしたが(ほとんどのタスクでは十分ですが)、iOS 6のリリースでフィルターの数が大幅に増加しました。 Core Imageでは、CPUとGPUの両方で処理することもできます。



コアイメージに対するGPUImageの主な利点:







GPUImageは、OpenGLの学習を開始するための優れた方法でもあります。多くの例、ドキュメント、およびターンキーソリューションがあるためです。 新しいフィルターを作成するなど、よりエキサイティングなことをすぐに実行して、すぐに結果を確認できます!



GPUImageの構造



GPUImageは本質的に、レンダリングパイプラインの周りのObjective-Cの抽象化です。 カメラ、ネットワーク、ディスクなどの外部ソースからの画像は、一連のフィルターを通過し、結果を画像(UIImage)、画面への直接レンダリング(GPUImageVIew経由)、または単なるデータストリームで提供することでダウンロードおよび変更されます。







別の言語では、GPUImage APIには何千ものカメラアプリケーションがあり、フィルターと正しい想像力の適切な組み合わせを待っています。

たとえば、 色レベルフィルタをビデオカメラの画像に適用して、さまざまなタイプの色覚異常をシミュレートし、リアルタイムで表示できます。

色レベル
GPUImageVideoCamera *videoCamera = [[GPUImageVideoCamera alloc] initWithSessionPreset:AVCaptureSessionPreset640x480 cameraPosition:AVCaptureDevicePositionBack]; videoCamera.outputImageOrientation = UIInterfaceOrientationPortrait; GPUImageFilter *filter = [[GPUImageLevelsFilter alloc] initWithFragmentShaderFromFile:@"CustomShader"]; [filter setRedMin:0.299 gamma:1.0 max:1.0 minOut:0.0 maxOut:1.0]; [filter setGreenMin:0.587 gamma:1.0 max:1.0 minOut:0.0 maxOut:1.0]; [filter setBlueMin:0.114 gamma:1.0 max:1.0 minOut:0.0 maxOut:1.0]; [videoCamera addTarget:filter]; GPUImageView *filteredVideoView = [[GPUImageView alloc] initWithFrame:self.view.bounds)]; [filter addTarget:filteredVideoView]; [self.view addSubView:filteredVideoView]; [videoCamera startCameraCapture]
      
      







真剣に、GPUImageの例としてバンドルされているフィルタープレゼンテーションアプリは、変更なしで約$ 3.99でApple Storeでホストできます。 また、Twitterの統合といくつかの効果音を追加することで、価格をわずか6.99ドルに引き上げることができます。



頂点シェーダーGPUImage



シェーダーは、グラフィックスパイプラインの1つのステージの動作を制御し、対応する入力データを処理するGPU用のプログラムです。

記事の主要部分で何が起こっているのかをよりよく理解するために、頂点頂点シェーダーに触れます。

画像を扱う場合、ほとんどの場合、2次元のオブジェクトを扱います。 画像は長方形の平面に表示されます。 これは、OpenGLに必要です。 すべてが3次元空間に存在します。 何かを描画したい場合は、最初に描画するサーフェスを作成する必要があります。 OpenGL ES 2.0は、三角形(および点と線だけでなく、長方形は)しか描画できないため、平面は2つの三角形から作成されます。

頂点シェーダーは、1つの頂点を処理するための小さなプログラムです。

これは、GPUImageの標準の頂点シェーダーの外観です。

頂点シェーダー
 attribute vec4 position; attribute vec4 inputTextureCoordinate; varying vec2 textureCoordinate; void main() { gl_Position = position; textureCoordinate = inputTextureCoordinate.xy; }
      
      





3種類の変数がシェーダーに渡されます:属性、可変、およびユニフォーム。 各頂点について、それ自体の属性が送信されます-空間内の位置、テクスチャ座標(図上でのテクスチャの表示方法)、色、法線など。

可変変数は、頂点シェーダーとフラグメントシェーダー間の関係を表します。 このタイプの変数は、頂点シェーダーで宣言および初期化され、フラグメントシェーダーに渡されます。 しかし、なぜなら フラグメントシェーダーは、図全体のポイントで動作します-これらの値は線形補間されます。 たとえば、図の左半分の頂点が白で、右半分が黒の場合、図の色は白から黒へのグラデーションを表します。

ユニフォーム-シェーダーが外部の世界(プログラム自体)と通信するには変数が必要です。 それらはすべての頂点とフラグメントで同じです。

GPUImageでは、2つの座標セットを渡します。これらは、プレーン自体の座標とテクスチャ座標です。 原則として、これを処理する必要はないため、詳細は説明しません。

開発したフィルターには頂点シェーダーを含めませんが、 GPUImageFilterクラスの標準シェーダーを使用します。



新しいGPUImageプロジェクトを作成します



iPhone用の新しいプロジェクトを作成します。 これを行うには、ファイル->プロジェクト→シングルビューアプリケーションを実行します。 ストーリーボードとARCを残すか、チェックを外すことができます。

次に、作成したプロジェクトにフレームワークを接続します(フレームワークがあるフォルダーを右クリック→ファイルを追加)。 次に、スクリーンショットに示されているフレームワークとライブラリのいくつかを追加する必要があります。





最後に、プロジェクトのビルド設定で、残りのリンカーフラグに-ObjCフラグを追加し、「ヘッダー検索パス」でGPUImageフレームワークを使用してフォルダーの場所を指定する必要があります。

これですべての準備が整い、独自のフィルターの作成を開始できます! ポーラーピクセルレートシェーダーを作成し、ツイストとポスタリゼーションの可能性を追加して拡張します(画面に表示される色の数を減らします)。



Polar Pixellateポスタライズフィルター



フィルターは、極座標系を使用して着信画像をピクセル化します。

最初に行うことは、GPUImageFilterから継承した新しいクラスを作成することです。 GPUImagePolarPixellatePosterizeFilterと呼びましょう。

GPUImageFilter
 #import "GPUImageFilter.h" @interface GPUImagePolarPixellateFilterPosertize : GPUImageFilter { GLint centerUniform, pixelSizeUniform; } // The center about which to apply the distortion, with a default of (0.5, 0.5) @property(readwrite, nonatomic) CGPoint center; // The amount of distortion to apply, from (-2.0, -2.0) to (2.0, 2.0), with a default of (0.05, 0.05) @property(readwrite, nonatomic) CGSize pixelSize; @end
      
      







このフィルターで2つの均一変数を渡します。 変数centerUniformは、極座標系の起点です-デフォルトでは、0.5、0.5、つまり画面の中心です。 OpenGLの座標系の範囲は0.0、0.0〜1.0、1.0で、左下隅が開始点です(テクスチャ座標について話していることに注意してください)。 pixellate値は、フィルターを適用した後の「ピクセル」の大きさを決定します。 極座標系を使用するため、値「x」は半径(「中心」からの距離)であり、他の値はラジアン単位の角度です。

GPUImageFilterはOpenGLのセットアップと必要なフレームバッファーの作成を担当しますが、シェーダー自体を記述し、必要な均一変数をそれに渡す必要があります。



最初のシェーダー



@実装行の前に次のコードを追加します。

コード
 NSString *const kGPUImagePolarPixellatePosterizeFragmentShaderString = SHADER_STRING ( varying highp vec2 textureCoordinate; uniform highp vec2 center; uniform highp vec2 pixelSize; uniform sampler2D inputImageTexture; void main() { highp vec2 normCoord = 2.0 * textureCoordinate - 1.0; highp vec2 normCenter = 2.0 * center - 1.0; normCoord -= normCenter; highp float r = length(normCoord); // to polar coords highp float phi = atan(normCoord.y, normCoord.x); // to polar coords r = r - mod(r, pixelSize.x) + 0.03; phi = phi - mod(phi, pixelSize.y); normCoord.x = r * cos(phi); normCoord.y = r * sin(phi); normCoord += normCenter; mediump vec2 textureCoordinateToUse = normCoord / 2.0 + 0.5; mediump vec4 color = texture2D(inputImageTexture, textureCoordinateToUse ); color = color - mod(color, 0.1); gl_FragColor = color; } );
      
      









シェーダーコードは、NSString文字列として表すために、マクロSHADER_STRING()で囲まれています。

GLSLのデータ型と操作に関するいくつかの言葉。 使用される主なデータ型は、int、float、vector(vec2、vec3、vec4)およびmatrix(mat2、mat3、mat4)です。 たとえば、vec2 + vec2を追加するなど、行列とベクトルに対して簡単な算術演算を実行できます。 さらに、ベクトルに数値(intまたはfloat)を掛ける演算が許可されます。たとえば、float * vec2 = vec2.x * float、vec2.y * float。 vec3を取得する場合は、vec4.xyzという呼び出しも使用できます。 サポートされているタイプの完全なリストはここにあります

ここで何が起こるか見てみましょう。 タイプvec2のさまざまなtextureCoordinateは、デフォルトの頂点シェーダーから取得されます。 均一— center変数とpixelSize変数—は、フィルタークラスから渡す変数です。 最後に、sampler2D型のinputImageTexture変数があります。 この均一変数はスーパークラスGPUImageFilterで設定され、処理する画像の2次元テクスチャを表します。

お気付きかもしれませんが、私たちは常にhighp分類器を使用しています。 これは、データ型の精度レベルについてGLSLに伝えるために行われます。 ご想像のとおり、精度が高いほど、データ型の精度は高くなります。 しかし、これは常に関連しているわけではありません-単純なレンダリングの場合、画面に合う精度が低くなり、計算を少し速く実行できます。 精度分類子は、lowp、mediump、highpです。 ここで 、精度と実際の制限について詳しく知ることができます

シェーダーには常にmain関数main()があります。 フラグメントシェーダーの結果は、処理中のフラグメントに設定される色です。 この場合、この色はnormCoord座標の元の画像から取得されます。 この値を使用して、極座標系の位置に応じてピクセル化を生成します。



最初に行うことは、座標系を極座標系に変換することです。 TextureCoordinate変数は、0.0、0.0〜1.0、1.0の間で定義されます。 一様変数Centerは同じ範囲で定義されています。 画面を極座標で記述するには、-1.0、-1.0から1.0、1.0の座標が必要です。 最初の2行はこの変換を実行します。 3行目は、中央のnormCoordから減算します。 つまり 座標系をnormCoordを中心とする新しいポイントに単純にシフトしました。 半径と角度phiの値を見つけた後、再びデカルト座標系に戻り、中心を以前の場所に移動します。 したがって、テクスチャの検索に必要な範囲0.0、0.0 1.0、1.0を取得します。 これを行うには、texture2D()関数を呼び出します。この関数は、2次元のテクスチャオブジェクト(この場合はinputImage)と座標(textureCoordinateToUse)の2つのパラメーターを入力として受け取ります。

最後に、赤、緑、青(およびアルファ、ただしアルファは常に1.0です)の色域を、各コンポーネントの256値(1680万色)から10(1000色)に減らします。

これはフラグメントシェーダーであり、非常に高速に動作します。 同じ目標を達成するためにプロセッサ(CPU)で同じ操作を行う必要がある場合、はるかに時間がかかります。 多くの場合、リアルタイムビデオフィルタリングはGPUで実行できますが、これはCPUを使用しては不可能です。



フィルターの開発を完了しています



シェーダーを作成した後、残っているのは均一変数のセッターだけです。 初期化中に、シェーダーのテキストをスーパークラスに渡し、統一変数へのポインターを定義し、いくつかのデフォルト値を設定します。

@implementationの後に次のコードを追加します。

コード
 @synthesize center = _center; @synthesize pixelSize = _pixelSize; #pragma mark - #pragma mark Initialization and teardown - (id)init { if (!(self = [super initWithFragmentShaderFromString:kGPUImagePolarPixellatePosterizeFragmentShaderString])) { return nil; } pixelSizeUniform = [filterProgram uniformIndex:@"pixelSize"]; centerUniform = [filterProgram uniformIndex:@"center"]; self.pixelSize = CGSizeMake(0.05, 0.05); self.center = CGPointMake(0.5, 0.5); return self; }
      
      







initWithFragmentShaderFromStringを呼び出すと、シェーダーは検証とコンパイルに適切なメソッドを通過するため、GPUで実行する準備が整います。 同じ方法で頂点シェーダーを送信したい場合、彼にとって、同じ操作をすべて実行するという同じ課題があります。

シェーダーに含まれるユニフォーム-変数ごとに[filterProgram uniformIndex:]を呼び出す必要があります。 このメソッドは、Glint型のポインターをUniform変数に返します。これを使用して、変数の値を設定できます。

最後に、初期化段階でいくつかのデフォルト値を設定して、ユーザーの介入なしにフィルターが機能するようにします。

最後に行う必要があるのは、均一変数のセッターとゲッターを設定することです。

コード
 - (void)setPixelSize:(CGSize)pixelSize { _pixelSize = pixelSize; [self setSize:pixelSize forUniform: pixelSizeUniform program:filterProgram]; } - (void)setCenter:(CGPoint)newValue; { _center = newValue; [self setPoint:newValue forUniform: centerUniform program:filterProgram]; }
      
      









アプリケーションを作成する



次に、シンプルなビデオアプリケーションを作成します。 テンプレートとして取得し、以前に構成したView Controllerクラスに移りましょう。 このファイルの次の行を変更します。

コード
 #import "JGViewController.h" #import "GPUImage.h" #import "GPUImagePolarPixellatePosterizeFilter.h" @interface JGViewController () { GPUImageVideoCamera *vc; GPUImagePolarPixellatePosterizeFilter *ppf; } @end @implementation JGViewController - (void)viewDidLoad { [super viewDidLoad]; vc = [[GPUImageVideoCamera alloc] initWithSessionPreset:AVCaptureSessionPreset640x480 cameraPosition:AVCaptureDevicePositionBack ]; vc.outputImageOrientation = UIInterfaceOrientationPortrait; ppf = [[GPUImagePolarPixellatePosterizeFilter alloc] init]; [vc addTarget:ppf]; GPUImageView *v = [[GPUImageView alloc] init]; [ppf addTarget:v]; self.view = v; [vc startCameraCapture]; } -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { CGPoint location = [[touches anyObject] locationInView:self.view]; CGSize pixelS = CGSizeMake(location.x / self.view.bounds.size.width * 0.5, location.y / self.view.bounds.size.height * 0.5); [ppf setPixelSize:pixelS]; } -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { CGPoint location = [[touches anyObject] locationInView:self.view]; CGSize pixelS = CGSizeMake(location.x / self.view.bounds.size.width * 0.5, location.y / self.view.bounds.size.height * 0.5); [ppf setPixelSize:pixelS]; }
      
      









GPUImageVideoCameraとGPUImagePolarPixellatePosterizeFilterフィルターの特定の解像度と場所でビデオカメラを作成します。

そして、GPUImageViewをビューコントローラーのメインビューとして設定します。

したがって、パイプラインは次のようになります。カメラからのビデオ-フィルターpikselizatsiiおよび低温殺菌(polarpixellateposterizefilter)-GPUImageView。これを使用して、電話でビデオを表示します。



この段階では、すでにアプリケーションを実行して、機能するフィルターを取得できます。 しかし、インタラクティブ性をさらに追加することをお勧めします! これを行うには、 touchesmovedメソッドtouchesbeganメソッドを使用します 。これらのメソッドは、クリックをキャプチャし、フィルターのpixelSize均一変数に影響を与えます。

最小の「ピクセル」を取得するには、画像の左上隅をタッチし、最大の場合は右下をタッチします。 これで、フィルターを自分で試して、まったく異なる結果を得ることができます。

おめでとうございます! 最初のシェーダーを作成しました!



シェーダーを使用した画像処理の他の例



画像の赤と緑のレベルを下げて、青を増やします。

コード
 lowp vec4 color = sampler2D(inputImageTexture, textureCoordinate); lowp vec4 alter = vec4(0.1, 0.5, 1.5, 1.0); gl_FragColor = color * alter;
      
      







輝度低下:

コード
 lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate); gl_FragColor = vec4((textureColor.rgb + vec3(-0.5)), textureColor.w);
      
      







人気の画像ぼかし:

コード
 mediump float texelWidthOffset = 0.01; mediump float texelHeightOffset = 0.01; vec2 firstOffset = vec2(1.5 * texelWidthOffset, 1.5 * texelHeightOffset); vec2 secondOffset = vec2(3.5 * texelWidthOffset, 3.5 * texelHeightOffset); mediump oneStepLeftTextureCoordinate = inputTextureCoordinate - firstOffset; mediump twoStepsLeftTextureCoordinate = inputTextureCoordinate - secondOffset; mediump oneStepRightTextureCoordinate = inputTextureCoordinate + firstOffset; mediump twoStepsRightTextureCoordinate = inputTextureCoordinate + secondOffset; mediump vec4 fragmentColor = texture2D(inputImageTexture, inputTextureCoordinate) * 0.2; fragmentColor += texture2D(inputImageTexture, oneStepLeftTextureCoordinate) * 0.2; fragmentColor += texture2D(inputImageTexture, oneStepRightTextureCoordinate) * 0.2; fragmentColor += texture2D(inputImageTexture, twoStepsLeftTextureCoordinate) * 0.2; fragmentColor += texture2D(inputImageTexture, twoStepsRightTextureCoordinate) * 0.2; gl_FragColor = fragmentColor;
      
      









GPUImageを使い始めるのは非常に簡単で、夢見ていたすべてを具現化するのは非常に強力です。 さらに、GPUImageは目を見張るほどの数のフィルター、カラー設定、ブレンドモード、そしてあなたが夢見ることができる(またはそれらの存在さえ知らなかった)視覚効果です。 エッジ認識、魚眼レンズ、その他のクールなものを含む最新のフィルターを使用して、多くの例を見つけることができます。

ソースコード

OpenGL ES 2.0(GLSL)およびグラフィックパイプラインデバイスの概要: onetwothree

この記事は、知識処理と翻訳による創造的な適応です: ソース1ソース2



All Articles