Raspberry Pi で OpenGL ES2 (1) (2012/09/09)

Raspberry Pi メモ (04)のTODOにも書いたように、Raspberry Pi には OpenGL ES2 が使える GPU(VideoCore4) が搭載されていて、3次元グラフィックのアニメーションが実行できます。標準のOSイメージである Raspbian には OpenGL ES2 のプログラムを作成して、実行する環境が最初から すべてそろっています。Raspberry Pi の CPU自体は高速とは言えないものですが、GPUは結構速そうです。せっかくなので使ってみましょう。

上の動画は Raspberry Pi で 36頂点の立方体を 50個回転させているところです。1秒間に約50回書き換えできているため十分スムーズにアニメーションさせることができます。これを実行している時のCPUの負荷は10%程度です。とても35ドルのコンピュータで実行しているとは思えないほど高速です。

OpenGL は20年前にリリースされた Linux、Mac、iOS、Windowsとマルチプラットフォームで使用できる3DCGのアプリケーションプログラミングインタフェース(API) です。その後も進化を続けているため、WindowsのDirectXのように多くのバージョンがあります。グラフィックチップの違いやOS環境によって使うことのできるバージョンやAPIの拡張機能に差があって、アマチュアがグラフィックスプログラミングの共通の場として試してみるにはハードルが高いAPI でした。 今後の普及の度合いに依存しますが、Raspberry Pi は OpenGL ES2でグラフィックスプログラミングを試す最適な環境ではないかと思います。また、WebGL もOpenGL ES2を採用しているため言語はJavaScriptになりますが、ほぼ同じAPIで相互の移植は比較的容易です。

OpenGL ES2 と OpenGL の違い

OpenGL ES2 はこれまでの OpenGL と比べると使い方が大きく異なっています。固定機能と呼ばれる、行列演算、色、法線ベクトル、カメラ、照明などに関する機能はOpenGL ES2では使えません。したがって、OpenGL と OpenGL ES2 は全く別物と考えたほうが良いかもしれません。

  // 次のような固定機能は OpenGL ES2 では使えない
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glFrustum(-1.0, 1.0, -1.0, 1.0, 2.0, 200.0); // 射影変換
  glBegin(GL_TRIANGLES);                       // 図形の指定
    glColor3f(0.5, 0.8, 0.7);                  // 色の指定
    glVertex3f(-1.0, 0.0, 0.0);                // 頂点の指定
    glVertex3f( 1.0, 0.0, 0.0);
    glVertex3f( 0.0, 1.0, 0.0);
  glEnd();

では、OpenGL ES2で行列演算、色、法線ベクトル、カメラ、照明などの機能をどのように実現するか? 答えは簡単で「全部自分で作る」ということです。WebGL でも多くのライブラリが作られているように OpenGL ES2 でもライブラリが作られているかも知れませんが、せっかくなので「自分で作る」路線で行きたいと思います。しばらくの間、「硬派な OpenGL ES2 プログラミング」としてチュートリアルを書いてみようと思います。

3次元グラフィックスのアニメーションもある瞬間には静止画であり、その静止画も画面上の個々のピクセルに色を指定しているだけです。 絵の特定の位置に存在する物体の面の色、その面のテクスチャ、照明の当たり具合と視点の位置といった多くの情報を元に画面上のピクセルの色が決まります。OpenGL ES2では面の色や角度、照明や視点(カメラ)の状態といった設定は、OpenGLの関数(glXXX)で指定するのではなく、シェーダと呼ばれるプログラムを作成する事で行われます。非常に自由度がある(なんでもできる)反面、シェーダプログラムの作成には3Dグラフィックスの理論に関する基礎的な知識が必要になります。一方で、OpenGLの関数(glXXX)は、シェーダのソースをコンパイルしたり、シェーダにデータを与える命令しかなく、シェーダプログラム以外は一定のパターンさえ覚えておけばいいので難易度はむしろ低いかもしれません。

シェーダ

シェーダ(shader)は2000年頃の DirectX8 で登場した手法です。照明などで変化する物体の色をライブラリに最初から用意されている機能(固定機能)ではなく、ゲームなどグラフィックを利用するアプリケーションの開発者自身でプログラムできる付加的な機能で、「プログラムシェーダ」などと呼ばれていました。最近ではDirectX でも OpenGL でも膨らみ続ける固定機能をばっさり捨てて、高速なGPU上で実行するシェーダですべてを行うようになってきています。シェーダを作成するにはプログラミング言語Cに似たOpenGL Shader Language (GLSL) を使います。

OpenGL ES2ではバーテックスシェーダ(頂点シェーダ) とフラグメントシェーダ(ピクセルシェーダ) の2種類を作成する事になります。 バーテックスシェーダは頂点毎に呼び出されて処理を行い、結果的に画面上の頂点の位置を出力します。フラグメントシェーダは頂点で囲まれた範囲のピクセル毎に呼び出され、その位置の色を出力します。頂点と頂点の間は、頂点が持っている色、法線ベクトル、テクスチャ座標などの値を頂点間で自動的に補完してフラグメントシェーダに渡します。

triangle

この図のように頂点の位置とその他の情報を頂点シェーダに渡すと、それらの情報は補間計算されてフラグメントシェーダに渡され、フラグメントシェーダが1つのピクセルの色を決めます。

頂点シェーダの例

gl_Position に頂点の座標を出力します。下の例ではユニフォーム変数 uProjMatrix に3次元の位置を2次元に変換する射影変換行列、uMVMatrix に物体とカメラの相対位置を計算するモデルビュー行列を設定しているつもりです。

  attribute vec3 aPosition;
  varying   vec3 vPosition;  
  uniform   mat4 uProjMatrix;
  uniform   mat4 uMVMatrix;
  
  void main(void) { 
    gl_Position = uProjMatrix * uMVMatrix * vec4(aPosition, 1.0);
    vPosition = vec3(uMVMatrix * vec4(aPosition, 1.0));
  }

フラグメントシェーダの例

gl_FragColor に代入された値が最終的に画面に出力される色になります。下の例では uColorに渡された色をそのまま出力しています。頂点位置から補間されたvPositionは使っていません。

  precision highp float;
  varying vec3   vPosition;
  uniform vec4   uColor;
  
  void main(void) {
    gl_FragColor = vec4(uColor.rgb, 1);
  }

このような感じで解説を進めていこうと思いますが、待ちきれない人には参考書籍として「Open GL ES 2.0 プログラミングガイド」をおすすめします。また、Raspberry Pi の公式SDカードイメージにも /opt/vc/src/hello_pi 以下のディレクトリにサンプルソースが用意されています。


「Raspberry Pi 特有の初期化」 に続く...