ロボットシミュレータ自作物語 - Day 1: RustでOpenGL入門 & 3D CGの基礎

たいそうご無沙汰しております、ぼくたち宇宙人のお時間です。15ヶ月ぶりくらいみたいですね。

さて、4月から専門分野をガラっと変えてヒューマノイドロボットの運動制御について研究する修士の学生になるにあたり、春休み中に素振りとしてロボットシミュレータを自作してみようと思います(学部の卒業も無事に決まりましたしね)。需要があるのか不明ですが解説付きで開発日誌をつけてみようと思いますので三日坊主にならないことを祈ってくださいお付き合いくださいませ。

TL; DR

gliumの公式チュートリアルを丁寧に写経すべし。

今日の目標

とりあえず3D CGをある程度自由に描けるようになりたいので、OpenGL(Wikipedia)で簡単なモデルを表示できるようにしたいと思います。 ただし、OpenGLAPIを生で喋るのはちょっとつらいので避けたいところです。 そこで、今もっとも興味を持っている言語であるRustにもついでに入門してしまうことにして、OpenGL wrapperであるglium*1を使って開発していきます。

環境構築 & コーディング & コーディング & コーディング

RustおよびCargo(Rustのパッケージマネージャ)を適宜な方法でインストールしてください。OpenGLGPUのドライバと一緒にすでにインストールされている可能性が高いです。というか手元のArch Linuxではそうだったので手動で入れるやりかたがわかりません。

あとは上記チュートリアル参照。写経大事。

成果物

f:id:iTakeshi:20170218231952p:plain

OpenGL基礎の基礎

※脚注※ 当初はこの節以降で3D CGの基礎数学についても解説を試みる予定でしたが、参考資料を探している間に傑作を発見してしまいましたのでそちらに譲りたいと思います。6回構成でとても丁寧に解説されており読みやすいです → けんごのお屋敷。以下、5分で概要を知りたい方向けです。

3D CGの三要素(私が勝手に命名しました)は「モデル、光源、カメラ」です。仮想3D空間に粘土細工(モデル)を配置し、仮想光源で光を当てて、仮想空間内に設置したカメラで撮影したものが、私たちがテレビやモニタで見ている絵になります。アニメーションである場合はフレームごとにこの計算を繰り返します。

OpenGLに限らず多くの3Dグラフィックスライブラリでは、CPUが計算したモデルの現在位置をGPUが受け取ってから画面に描画するまでの一連の流れを「パイプライン」と呼んでいくつかのステップに分割し、順序よく仮想3D空間を2次元に落とし込んでいきます。各ステップには各々のライブラリに固有の機能を実現するものなどもありますが、最も原始的で絶対に必要になるものは Vertex shaderによる頂点位置の計算ラスタライズFragment shaderによる各ピクセルの色の計算 の3ステップです。順に見ていきましょう。

Vertex shaderによる頂点位置の計算

CGにおいて「滑らかな表面」というものは存在しません。滑らかに見えているものは、無数の小さな三角形の集合体です。三角形は「三次元空間内に任意の相異なる3点を取ると、かならずその3点を含む平面がただ一つ求まる」「3つの頂点の位置が与えられた場合、三角形の各辺の長さおよび3つの内角がすべて一意に求まる」などの便利な性質があるため、3D CGではあらゆるモデルを三角形に分解して表現します。例えばイルカならこんな感じ↓ f:id:iTakeshi:20170218232626p:plain

https://ja.wikipedia.org/wiki/%E3%83%9D%E3%83%AA%E3%82%B4%E3%83%B3%E3%83%A1%E3%83%83%E3%82%B7%E3%83%A5#/media/File:Dolphin_triangle_mesh.png - パブリックドメイン

Vertex shaderは、光も影もない灰色の世界でモデルをカメラで撮影したとき、モデルを構成する無数の小さな三角形のそれぞれの頂点の位置(三次元)が写真の上でどのような座標(二次元)にあるのかを計算します。

上で「CPUがモデルの現在位置を計算」と簡単に書きましたが、これはあくまでも「モデルの姿勢を規定するのに必要なパラメータを計算」するだけです。例えば下図のような単純なロボットアームであれば腕の長さと \theta_1 \theta_2の角度さえわかればモデルの形は決まってしまうので、CPUが計算するのはこれらの値だけであとはGPUに投げてしまって良いということになります*2f:id:iTakeshi:20170218222423p:plain

Vertex shaderは、腕の長さとθ1とθ2の角度をもとにロボットアームのモデルを空間内に配置し、そのモデルを構成するすべての三角形のすべての頂点の位置をまず三次元で計算します。その後、仮想空間にカメラを設置し、そのカメラでとった写真の上の座標に三次元の位置を対応させます。

※このとき、遠近法に基づき遠くのものを小さく、手前のものを大きく変形する必要がありますが、上で紹介した「けんごの部屋」の記事ではこの部分が解説されていませんので、この記事を補足としてリンクしておきます。

ラスタライズ

さて、Vertex shaderで各頂点の二次元座標がわかりましたが、まだモニタに表示することはできません。Vertex shaderで計算した座標は -1 \le x, y \le 1浮動小数点(画面の左端が x = -1)で表されており、これを例えば800x600pxのモニタのピクセルと対応させる必要があり、この工程をラスタライズと呼びます。ラスタライズはOpenGLが自動で行うため、プログラマがあえて命令する必要はありません(というか普通カスタマイズできません)。

Fragment shaderによる色の計算

ラスタライズによってモニタ上の各ピクセルがモデルのどの三角形と対応しているかが決定されましたが、まだ世界は灰色のままです。Fragment shaderでは、三角形そのものに指定されている色情報、またはテクスチャ(モデルの表面にはりつける質感画像)によりピクセルの色を計算します。さらに、三次元空間内に光源を仮定し、モデルの光のあたり具合によって色を明るくしたり影をつけたりします*3

ロボットシミュレータにおいてはロボットの「動き」そのものが大事でありあまり見た目にはこだわらないので、このプロジェクトではfragment shaderによる処理は最低限のものになるでしょう。

Day 2 に向けて

現状はただティーポットが画面上に浮いているだけなので、矢印キーでモデルを回したりカメラ位置を変えたりできるようにしていくつもりです。またBlenderを使ってロボットの3Dモデルを作り始めます。

*1:RustのOpenGL wrapperとしてはもうひとつglutinというのがありますが、これはGLFW相当のやや低いレイヤを扱うライブラリであり、glutinのREADMEにも「もう一段階高レベルな抽象化レイヤを導入すべきやで」との記載があります。gliumとglutinの作者は同一人物ですので、つまるところすなおにgliumを使えということでしょう

*2:物理演算をする場合などはもっとCPU側でいろいろな計算(衝突判定etc)をしますがこの記事の範囲外なので省略

*3:この説明には誤りを含みます。厳密には光源情報もVertex shaderで前処理されている必要があり、Fragment shaderではそこで加工された情報をもとにライティングを行います。