Androidアプリ開発 OpenGL シェーディング
ライティングをすることで、3DCGらしい画像を生成することができるようになった。細かくポリゴンを作っていくことで、「リアルな物体」を画面に描くことができる。すごく細かくすれば、曲線もなめらかに再現できるはず。
しかしである。細かくするにも限界がある。メモリ容量とか、体力とか。
実は、少ないデータ量でなめらかな曲面となっているように見せる方法がある。「スムースシェーディング」「グローシェーディング」と呼ばれるものである。これに対して、今までやってきたのは、「フラットシェーディング」である。
フラットシェーディングでは、面の法線と光源、視線の関係から、面の色を「一様」に計算して表示していた。面に対して法線データはひとつだけとなっていた。
スムースシェーディングでは、頂点ごとに法線データが必要となる。各頂点の法線と光源、視線の関係から色を計算し、間を補完して描画する。
前置きはこれくらいにして、やってみるべし。
まずは、法線データを頂点ごとに作成しなければならない。これは、既に作成している頂点データのようにx,y,z座標が入ったバッファを作成することで準備できる。
public class Model { private FloatBuffer buffer; // 頂点用バッファ private FloatBuffer normalBuffer; // 法線用バッファ public Model() { float vertex[] = { -1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, }; ByteBuffer vb = ByteBuffer.allocateDirect(vertex.length * 4); vb.order(ByteOrder.nativeOrder()); buffer = vb.asFloatBuffer(); buffer.put(vertex); buffer.position(0); float normal[] = { 1.0f, 0.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, }; ByteBuffer nb = ByteBuffer.allocateDirect(normal.length * 4); nb.order(ByteOrder.nativeOrder()); normalBuffer = nb.asFloatBuffer(); normalBuffer.put(normal); normalBuffer.position(0); }
法線は単位ベクトルで指定しないといけないのだが、サンプルは計算するのがめんどうだったので、適当なベクトル。OpenGL側で単位ベクトルに計算し直してくれたようだ。
次に、描画時に法線データがバッファに入っているので、頂点データと同様に、OpenGLにバッファの場所を教えてやる。
glNormalPointerでバッファを指定するのだが、glVertexPointerと微妙に引数が異なるので注意。
ここで、引数の指定を誤ると、GPUに正しいバッファのアドレスが渡らず、Segmentation faultで落ちることになる。OpenGLのエンジン部分は、C/C++の世界であることに注意しなければならない。
public void draw(GL10 gl) { gl.glColor4f(1.0f, 0.0f, 0.0f, 1.0f); gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl.glVertexPointer(3, GL10.GL_FLOAT, 0, buffer); gl.glEnableClientState(GL10.GL_NORMAL_ARRAY); gl.glNormalPointer(GL10.GL_FLOAT, 0, normalBuffer); gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3); } }
次に、描画する際に、シェーディング方式を指定する。glShadeModelでGL_SMOOTHを指定してスムースシェーディングを行う。
フラットシェーディングとの違いがわかるように、左側の三角形をスムースシェーディングしてみる。
シャイニネスの値も両方で80に統一している。シェーディング方法以外はどちらも同じ条件になっている。
@Override public void onDrawFrame(GL10 gl) { gl.glClear(GL10.GL_COLOR_BUFFER_BIT); // ライティングをON gl.glEnable(GL10.GL_LIGHTING); // 光源を有効にして位置を設定 gl.glEnable(GL10.GL_LIGHT0); gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightpos, 0); gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, red, 0); gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, blue, 0); gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, yellow, 0); gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); gl.glPushMatrix(); // マトリックス記憶 gl.glTranslatef(1f, 0, -3f); gl.glRotatef(angle, 0, 1, 0); gl.glEnable(GL10.GL_CULL_FACE); gl.glCullFace(GL10.GL_BACK); gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, gray, 0); gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, gray, 0); gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, gray, 0); gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 80f); gl.glShadeModel(GL10.GL_FLAT); model.draw(gl); gl.glPopMatrix(); // マトリックスを戻す // ふたつめの描画 gl.glPushMatrix(); // マトリックス記憶 gl.glTranslatef(-1f, 0, -3f); gl.glRotatef(angle, 0, 1, 0); gl.glCullFace(GL10.GL_BACK); gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, gray, 0); gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, gray, 0); gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, gray, 0); gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 80f); // スムースシェーディング gl.glShadeModel(GL10.GL_SMOOTH); model.draw(gl); gl.glPopMatrix(); // マトリックスを戻す angle += 0.5; // 回転角度は最後に計算 }
スムースシェーディングの方は、ハイライトが丸い感じになっていることがわかる。
スムースシェーディングは、丸いものを少ないポリゴン数で描画できる、といったメリットがある。ただし、なめらかに見えているのは「色だけ」。形状は定義したポリゴンのままなので、あまりに荒いと角ができてしまう。
また、サンプルのように平らなものでもスムースシェーディングにより、金属的な材質感を出すことができる。
OpenGLの記事を最初から読みたいなら、以下からどうぞ。
Androidアプリ開発 OpenGLを使う GLSurfaceView
投稿者プロフィール

-
システムエンジニア
喋れる言語:日本語、C言語、SQL、JavaScript