本記事では、openFrameworksでglmで利用するにあたっての気づきと注意をまとめます。現在のリリース候補版 (0.10.0) でようやくglmのデフォルトの組み込みが実装されましたが、2016年の中頃にはすでにgithubのMaster branchで利用されはじめ、リファレンスの中にmigration guideも存在していました。
これまでも、oFにおいてglmを扱うためのaddonがありましたし、そもそも、外部依存のないC++ライブラリなので、インクルードは容易です。それでも、oFのコミッタ達がglmをコアに組み込む決断をしたということは色々な意味があるように思います。
glm自体の説明については、こちらが詳しいです。
glm を使うモチベーション
OpenGLにおける3DCGプログラミングを支援するために開発されているために、3DCGに関する便利な機能が多くあるほか、GLSLと記法が同じようになるためにプログラミングの際の心理的な障壁が少なくなります。
GLSLと記法が同じようになる
根本の違いは、GLM・GLSL → 列ベクトル、oFのMathクラス群 → 行ベクトルを採用しているために掛ける順番が異なることです。
using namespace glm;
// glslっぽい書き方
vec3 vertex(1.0f); // 全要素1.0のベクトル
vec3 n = normalize(vertex); // 正規化
float len = length(vertex); // 長さ
vec4 pos = vec4(vertex, 1.f); // コンストラクタが柔軟
vec2 seed = pos.ga; //swizzle演算子が使える
// oFのMathライブラリは変換行列は後ろから掛ける。3次元ベクトルと4次元正方行列の掛け算ができる
ofVec3f v = ofVec3f(1.f,1.f,1.f) * ofMatrix4x4();
// glmは前から掛ける。4次元ベクトルにしないと4次元正方行列と積算できない
vec4 v = camera.getModelViewMatrix() * vec4(node.getPosition(), 1.f);
ただし、oFのMathライブラリで実現できていた、3次元ベクトルと4次元正方行列の積算ができなくなります。本来、数学的には間違っているのでより正しい仕様ですが、あると便利ではあったかもしれません。(頻繁に使ってました…)
便利な記法がたくさんある
特に行列の座標変換系の関数が多く用意されています。一覧はこちら。
// 単位行列
glm::mat4 m(1.0f);
// 座標変換
glm::mat4 s = glm::scale(glm::vec3(1.f, 1.f, 2.f));
glm::mat4 r = glm::rotate(PI, glm::vec3(1.f, 0.f, 0.f));
glm::mat4 t = glm::translate(glm::vec3(100.f, 200.f, 0.f));
glm::mat4 transform = t * r * s;
// 透視投影変換行列
glm::mat4 projection = glm::perspective(fovy, width, height, near, far);
// 平行投影変換行列
projection = glm::ortho(left, right, top, bottom, near, far);
// 法線の変換行列
glm::mat4 normalMatrix = glm::inverse(glm::transpose(modelViewMatrix));
平行投影の行列が一発で出せたり、逆行列や転置行列も素直な感じに書けます。
互換性
glmのクラスからoFのクラスの作成
基本的に、oFが用意されるクラス群には、glmとの互換機能が実装されています。そのため、glmで計算した結果をoFのMathライブラリに渡して計算したりもできます。
// glmクラスのインスタンスを渡しての初期化ができる
ofVec3f v(glm::vec3(1.f));
ofMatrix4x4 mat(glm::mat3(1.f));
// 代入ができる
v = glm::vec3(1.f);
mat = glm::mat4(1.f);
座標や行列のgetter系は全てglmのクラスで返却される
ofNode::getPosition()
やofMesh::getVertex()
、ofCamera::getModelViewMatrix()
といった座標や行列を得る関数群は、全て、glmの型で返却されるように0.10.0では変わっています。
基本的には上記の互換機能があるので、以外とコンパイルできてしまいますが、計算の順序や次元あわせの厳格さからくるコンパイルエラー等に注意が必要です。以下に例を示します。
// ノードの場所を取得して、x軸45度回転した座標を得たい
ofMatrix4x4 mat;
mat.glRotate(45, 1,0,0); // x軸に45度の回転行列
ofVec3f v = node.getPosition() * mat;
// 0.9.8ではコンパイルでき、計算もそれらしくおこなわれるが、0.10.0ではコンパイルできない
// node.getPosition()の返却値はglm::vec3型であり、ofMatrix4x4型との"*"演算子が定義されていない
// 後ろからかけるのマズイ。
これを以下のように書き換える必要があります。
using namespace glm;
mat4 mat = rotate(ofDegToRad(45), vec3(1.f, 0.f, 0.f));
vec3 v = (mat * vec4(node.getPosition(), 1.f)).xyz;
以上です。