[OpenGL]
- LWJGLを使ってPMDファイルの読み込みを試す
PMDファイルのデータ型
- PMDの読み込みとか に書いたとおり
OpenGLESによる描画
シェーダー
- シェーダーとは?
- Wikipedia - シェーダー ... シェーダー(英: shader)とは、3次元コンピュータグラフィックスにおいて、シェーディング(陰影処理)を行うコンピュータプログラムのこと。
- シェーダは、描画処理の前半を担う 頂点シェーダ と後半部分の フラグメントシェーダ に分けられる
- 頂点シェーダーでは、頂点を1つ受け取ってフラグメントシェーダに何か渡す処理を書く
- フラグメントシェーダでは、色情報を出力する
- シェーダーについてはMMPの中を見れば結構いいかもしれない
頂点リストと面頂点リスト
- MMDのVertex部分は頂点データ(x,y,z)を格納するために用いられるDirectXのVertex Bufferに相等する
- モデルがもつすべての頂点を保持する
- MMDのFaceVertexはDirectXのIndex Bufferに相等する。MikuMikuDanceのモデルではMeshデータはすべて三角形で定義されており、各三角形(面)ごとの頂点番号を3個一組で順に記録している
- 頂点リストの要素を3つずつ指示する、3点を結んで描画し3Dグラフィックにする
- 具体的なOpenGLでの描画方法
- 大まかに言うと、Vertex Array Object, Vertex Buffer Object, Vertex Buffer Object Indexの3つを用意する
- Vertex Array Objectが複数のVertex Buffer Object(VBO)を持つことができ、VBOにすべての頂点データ(x,y,z)を格納する
- Vertex Buffer Object Indexはすべての頂点データ(x,y,z)のindexを複数指定(3つ)して描画する面をOpenGLに指示する
- 大まかに言うと、Vertex Array Object, Vertex Buffer Object, Vertex Buffer Object Indexの3つを用意する
材質リスト
- 色要素を設定したVBOを作ってBindする
- いろいろ調べたのだが、シェーダーを使って面頂点に対して直接色塗りをすることはできないようだ
- なので、受け渡し方は VBO -> vertexシェーダー(頂点) -> fragmentシェーダー(面頂点) になる、めんどくせー
- 上記の関係から、色塗りは頂点ベースで行うことになる、頂点データ(x,y,z)の数だけ材質リストを用意する
- しかしPMDファイルは容量削減のため材質リストには適用される面頂点の枚数しか記録されていない
- よって
- 材質リストの面頂点数だけ材質のリストを作成する
- 面頂点と材質の関連づけをindexをもとに作成する
- 頂点リストの数だけ材質のリストを作成する(どの材質を使うかは、上記の関連付けを参照する)
テクスチャ
テクスチャ単体の実装
- テクスチャの描画は頂点リストに入っているUV座標:u, vを使用する
- テクスチャの貼り付け元となる画像ファイルは材質リストの中にあるので、他の材質の属性と同様の方法で読み取る
ただ、テクスチャは常に頂点の色に適用されるわけではないのでシェーダーの中で条件分岐させてテクスチャを使う・使わないを判定しなければいけない
- その実装方法についてはMMPのものがわかりやすい
if((UV[0]!=0 && UV[1]!=0)) { // UV座標:u, vのどちらも0でなければテクスチャ使用 } else { // そうでない場合は普通に材質リストの属性で設定されている色を計算 }
実際に、MikuMikuDanceのモデルデータ(PMD)構造について(その1)で配布されている頂点リストと材質リストを確認すると、材質リストの中でテクスチャファイルを設定されている面頂点に属する頂点リストの属性値はちゃんとUV座標が設定されている一方で、テクスチャが設定されていない頂点リストの属性値はUV座標:u, vのどちらかが0になっている。
複数テクスチャの実装
- テクスチャは1枚だけではなく複数枚設定できるので対応が必要
Array textures are a great way of managing collections of textures of the same size and format. They allow for using a set of textures without having to bind between them.
- 上記のようにあるので、テクスチャは同じ大きさとフォーマットでなければならない。
例としてメタル版ミクのテクスチャ情報
初音ミクmetal.pmd | width | height |
---|---|---|
metal.sph | 256 | 256 |
mikuhair.sph | 256 | 256 |
eyeM2.bmp | 106 | 106 |
これは大きさを合わせないとだめなのだろうか
- 一方で GLSL, Array of textures of differing size に見られるように
- 画像は異なるサイズでもいいという話もある、GLSLの仕様書を読まないとだめなのか
- シェーダー
#version 450 core layout (location = 0) out vec4 color; layout (location = 0) in vec2 tex0; uniform sampler2DArray texarray; // <-- sampler2D が Arrayになっている uniform uint diffuse_layer; // レイヤー情報をfloatに変換 // https://www.khronos.org/opengl/wiki/Array_Texture を参考に float layer2coord(uint capacity, uint layer) { // 疑似コード // d はテクスチャ配列の数 // layer はテクスチャ座標からの浮動小数点レイヤー // // actual_layer = max(0, min(d - 1, floor(layer + 0.5)) ) return max(0, min(float(capacity - 1), floor(float(layer) + 0.5))); } void main() { color = texture(texarray, vec3(tex0, layer2coord(3, diffuse_layer))); }
- sampler2DArrayはglUniform1fvで定義すればよさそう → と思ったが、各テクスチャを別の設定で動かしたいと現在は考えてないので不使用
- 画像ロードはglTextureStorage3D, glTextureSubImage3DとSTBによる画像読み込みだけでできてしまった
法線ベクトル
- 法線を設定したVBOを作ってBindする
- しっかり学ぶシェーダプログラミング【グーローシェーダ編】
- 法線を受け取ってシェーダー内で色を計算する
カメラ
- model, view, projectionを設定しシェーダー内で使用する
- チュートリアル3:行列
- model, view, projectionはそれぞれモデル行列、ビュー行列、射影行列を表す
- モデル行列
- 描画対象のモデルの座標からOpenGLのワールド座標への相対値
- ビュー行列
- OpenGLのワールド座標からカメラの座標への相対値 (思ってたのと逆だ…)
- 射影行列
- カメラの座標から映し出される(射影)ものへの相対値
- 正直なぜこのような変換ができるのかよく分からないのだが、数学者じゃないのでよしとする
上記の行列はシェーダー内でグローバルGLSL変数として渡される
- 頂点シェーダーでは頂点とModelViewProjection行列の積を使って、モデルが動いた後の位置を決めている
- LWJGL + JOMLのカメラを実装するのはダルいので、既存のライブラリを使いたい
- https://github.com/JOML-CI/joml-camera JOMLによるカメラの実装ライブラリ
- https://github.com/JOML-CI/joml-lwjgl3-demos 上記のデモ
OpenGLのカメラについて
- 透視投影
- 3次元の物体を見たとおりに2次元平面に描画する、~Perspective, ~Frustum
- 平行投影
- 視点が無限遠方にあると仮定して物体と視点を結ぶ投影方法、~Ortho
この辺りは別ページに書こうと思う OpenGL関連
GLFWによるGUI制御
- LWJGLではGLFWがGUI制御を行う
ウィンドウサイズを変更時
- glutではglutReshapeFuncにウィンドウサイズ変更時のコールバックを登録する
- GLFWではglfwSetWindowSizeCallbackを使う
- どうやらGLFWのコールバックは後勝ち設定のようなので1つのクラスにしか適用できない、コールバックを複数クラスに適用したい場合はコールバック関数を自分で複数クラスに適用するコードを書かなければいけないようだ