投稿

8月, 2021の投稿を表示しています

ステップアップ OpenGL ES 2.0

イメージ
0. OpenGL スケルトン Android では、GLSurfaceView の枠組みの中で OpenGL を処理する形になる。 1. タイルを表示するサンプル 4 個の頂点座標データに基いて、たった一つの矩形のタイルを描くだけだが、この最初の一歩の段階で一挙にハードルが上がる。主には、シェーダーで定義した変数を通じて頂点データを入力するための手続。あとは Java 固有の話として、ダイレクトバッファの形でデータを OpenGL 側に入力するという点。 シェーダー Java 側では、シェーダーは String データに過ぎないが、OpenGL 内部での処理を GLSL で定義するもの。C 言語的なスクリプトであり、公式の クイックリファレンスカード によくまとまっている(詳細な リファレンス も公開されている)。gl_* が予約された変数で、この変数に意図した値を出力することで OpenGL 内の次の処理へと頂点データが渡ることになる。 このサンプルでは、a_Position を通じて Java 側から入力した値をそのまま次へリレーしている。またフラグメントシェーダーにおいては、u_Color を通じて Java 側からセットした値をそのまま各フラグメント(ピクセル)の色として渡している。ちなみに、u_Color は uniform なので、いわゆるグローバル変数的なものとして定義されており、4 つの全ての頂点間で共有されている。一方の a_Position の方は attribute であり、各頂点毎に属する値である(cf. クイックリファレンス p3 Qualifiers)。 シェーダーの使い方 各シェーダーをコンパイルし、バーテックスとフラグメントを組み合わせて一つのシェダープログラムとし、さらに今有効なプログラムとしてそれをセットする(シェーダープログラムは複数用意して使い分けることも可能なため)。 シェーダー変数の確保 シェーダープログラムから、OpenGL への入力に使用するシェーダー変数(のポインターインデックス)を得る。 頂点データの配列 Java 固有の話として、JavaVM 内部のメモリーヒープを使うわけにはいかないため、ダイレクトバッファ(ここでは FloatBuffer)を通じてデータを入出力する。

Android / Java と OpenGL ES 2.0 の要点

以下のような人(要するに僕自身のケースと同じ)を対象にしている: Android / Java のアプリ開発についての一般的な理解はある ImageView.onDraw や SurfaceView による動画ならそれほど苦労せずに理解できた しかし、OpenGL (ES 2.0) のことになると色んな解説やサンプルコードを見ても理解に苦しんで中々はかどらない なぜ OpenGL はわざわざああいうことをするようになっているのか? ImageView.onDraw や SurfaceView による動的グラフィックスなら理解しやすかった人にとって、動画とは、パラパラ漫画のようなものというほとんど無意識的な大前提があるわけだ。パラパラ漫画に限らず、映画やアニメなど、世の中の映像記録方式としては、こちらが圧倒的主流。ImageView.onDraw や SurfaceView の場合も、ループを回して、フレームバッファに次々とコマを描き込むだけ。 ところが、OpenGL はそれを許してくれないので、何か物凄く高度な、GPU のハードウェア的な性能を引き出すために、OpenGL の API を通じて描画する必要があるのかと、勘違いする。実際はそう(高度な GPU のハードウェア的な性能を引き出した上でレンダリングするのが目的の API 体系)ではないので、架空の妄想を相手に戦う羽目になり、決して勝利することができない戦いに挑む形になる。 OpenGL はそもそも、パラパラ漫画的なことをプログラマーが自らやることを想定していない(GPU 側の内部処理として行われる)。その一歩手前の部分(モデリング)を主にするのがプログラマーの仕事。モデルを基にしてラスタライズしてフレームバッファに落し込む処理は、GPU 側が(プログラマーが予め定義したフラグメントシェーダーに従って)内部的に行う形になっている。つまり、プログラマーがすべき仕事の領域がそもそも異っている点に気付かなければならない。 言ってみれば、フレームバッファ描き込みによるパラパラ漫画的動画はチカチカと瞬く GIF アニメみたいなもので、OpenGL はヌルヌル動く Flash アニメーションみたいなもの(Flash ももう過去の遺物だが……)。他にも、ペイント系グラフィック(ビットマップ

MVVM のバランス──DataBinding を使うのをやめた

以前の記事「 LVMC (a.k.a. MVVM: ViewModel + DataBinding) と Fat View 」では、使い方を覚えた DataBinding で調子に乗って、何でもかんでも処理を View-Controller 側に詰め込んでしまい、Fat View に陥いるという反省を経た。 結局、MVVM というより DataBinding がイマイチ良くなかった気がする。DataBinding を使おうとすると、どうしても無理してレイアウトで静的に記述しようとするために、その皺寄せを View-Controller 側なり、ViewModel 側なりに押し付けてしまうわけである。以前の記事の時は、DataBinding の皺寄せを、View-Controller 側に詰め込んだのを、ある程度 ViewModel にも分担させるべきだという反省だった。今回は実のところ、ViewModel 側に DataBinding の皺寄せを詰め込み過ぎたのである。 例えば、モデル側から画像のリスト(LiveData)を得てスライダー操作でその中から一つの画像を表示するとする。この場合、DataBinding と ImageView の立場からすると、ImageView は単に DataBinding で記述された静的なレイアウトから src を読んで表示するのが楽である。src がリストから抜き出されて変化するかどうかなどということは、ImageView 側は何も意識する必要がない。 となると、スライダー操作によってリストから一つの画像を抜き出す処理は全てバインドされた ViewModel 側に記述する必要がある。これがやってみると恐ろしく大変な作業で、抜き出される画像は、まず LiveData であるリストの変化を observe する必要があり、さらにスライダー操作によって抜き出すインデックスの変化をも observe する必要がある。こういった形でスパゲッティのように絡み合った observe / observant の LiveData で連鎖する回路を ViewModel 中に記述する羽目になる。 一度、上記のようなスタイルでアプリを構築したのだが、結局リファクタリングの際に見直して、普通に Activity / Fragment 側のイベン