ベクトル・行列・座標変換¶
本稿では、ベクトル・行列・座標の変換を PyOpenGL 利用プログラムでどのように実現するかについて記す。
基本方針¶
プログラムが採用するコンテキストバージョンによらず、ベクトル・行列を取り扱うのに
np.array
を積極的に用いる。ただし、単なるlist
やtuple
で済む場合は、もちろんそれで間に合わせる。ベクトルは shape が
(n,)
に等しいインスタンスで表現する。ここでn`
は 2 か 3 か 4になる。行列は shape が
(4, 4)
に等しいインスタンスで表現する。
プログラムは、行列の成分を「教科書の順序」で取り扱うものとする。つまり、普段は通常の
np.array
の形で保持しておく。そして、OpenGL のインターフェイスとアクセスする際に、内部配列のメモリレイアウトを NumPy 形式または OpenGL 形式 (column-major) に並び替えるものとする。3.0 以上のコンテキストバージョンでは、OpenGL の行列の機能が全滅しているので、必要に応じて対応する機能を自作する。
np.array
の使いこなし方については、NumPy 利用ノート で述べる。
OpenGL インターフェイスとアクセスする行列データについて¶
OpenGL は新旧共通で行列データを 16 要素の浮動小数点型数の配列として扱うことが圧倒的に多い。
旧式インターフェイスに行列データを設定する¶
主に PyOpenGL 関数 glLoadMatrix
および glMultMatrix
に関しての話となる。これらのインターフェイスに対して np.array
を行列として引き渡す方法が二通り考えられる。
ひとつは次の擬似コードのように np.array.transpose
を用いて、内部配列を
column-major に並び替えることによるものだ:
matrix = np.identity(4)
# Set matrix elements...
glMatrixMode(GL_MODELVIEW)
glLoadMatrixf(matrix.transpose())
もうひとつは、配列はそのままにして OpenGL の「転置バージョン」の行列関数、つまり
glLoadTransposeMatrix
および glMultTransposeMatrix
を利用するものだ:
matrix = np.identity(4)
# Set matrix elements...
glMatrixMode(GL_MODELVIEW)
glLoadTransposeMatrixf(matrix)
旧式インターフェイスから行列データを取得する¶
OpenGL 側から行列データを受け取る方法について述べる。いつものようにデータ取得には関数 glGetFloatv
か glGetDoublev
を用いるのだが、 PyOpenGL の場合はオリジナルとは異なり戻り値として配列を得ることに注意を要する。
そして、配列自体のメモリレイアウトの問題も設定時と同様に存在する。ここでもメソッド .transpose
を用いてかつシンボル GL_*_MATRIX
を用いるか、またはメソッド .transpose
を用いずにシンボル GL_TRANSPOSE_*_MATRIX
を用いるかの選択肢がある。
シェーダーオブジェクトに行列データを設定する¶
頂点シェーダーのコードをこのように書くとする:
#version 330 core
uniform mat4 camera;
uniform mat4 projection;
uniform mat4 rotation;
...
例えば
uniform mat4 camera;
という表現が「4 次正方行列camera
を扱う」ということを宣言するものだ。このオブジェクトの内容を設定するには、「外側」から OpenGL の関数を用いる必要がある。def init_transform(self): # ... location = glGetUniformLocation(self.program_manager.program_id, b"camera"), glUniformMatrix4fv( location, 1, GL_TRUE, self.camera_matrix)
関数
glUniformMatrix4fv
の呼び出しが、行列データをシェーダープログラムに渡す処理である。関数
glGetUniformLocation
にシェーダーにおける識別子を与えて、行列オブジェクトの「場所」を得る。アドレスのようなものだろう。GL_TRUE
は引数の行列を転置する、すなわち OpenGL 側に column-major 化してもらうことを意味する。self.camera_matrix
は別のところで設定済みのnp.array
オブジェクトという仮定である。
シェーダーオブジェクトから行列データを取得する¶
先の例に対応する、行列データの取得コードは次のようなものになる。受け取った後に
.transpose
をしないと、数学の教科書通りの並びにならない。
camera_matrix = np.empty(4, dtype=np.float32)
glGetUniformfv(
self.program_manager.program_id,
location,
camera_matrix)
assert np.allclose(camera_matrix.transpose(), self.camera_matrix)
座標変換を実装する¶
基本的には、変換を表現する行列を返す関数を実装し、 Python コードではそのオブジェクトを GLSL に渡したり、その場で頂点座標を変換したりという利用を想定する。
自作する変換処理は別モジュール transform.py
で定義することにする。このモジュールが提供するいずれの自作関数も、返す行列 m
が次の条件を満たすものとする。
m
は数学的には、同次座標系で表現された列ベクトルに左から作用する行列を表現する。m
はnp.array
オブジェクトである。m.shape == (4, 4)
m.dtype == np.float64
自作対象関数¶
OpenGL 3.0 で deprecated にされた行列関連の関数と、自作するかどうかの対応表を記す。ここで言う「自作」がどういう作業を意味するのかは、関数によって異なってくる。
関数 |
対応 |
---|---|
|
|
|
|
|
|
|
|
|
GLSL にこの概念がないので、対応しない。 |
|
|
|
|
|
平行投影は当分利用しないので、必要になるまで対応しない。 |
|
スタックの概念が不要なので対応しない。 |
|
スタックの概念が不要なので対応しない。 |
|
|
|
対応する。 |
|
対応する。 |
|
是非対応する。 |
|
平行投影は当分利用しないので、必要になるまで対応しない。 |
|
是非対応する。 |
|
当分利用しないので、必要になるまで対応しない。 |
|
当分利用しないので、必要になるまで対応しない。 |
|
当分利用しないので、必要になるまで対応しない。 |
そのモジュールでのインポート部一覧を示す。
from numpy.linalg import norm
いつものモジュール numpy
だけではなく、サブモジュールである numpy.linalg
の機能も用いる。
関数 gluLookAt
を真似る¶
OpenGL のリファレンスをそのまま実装すればよい。
まず、与えられたカメラの位置、観測目標点、カメラの姿勢から正規直交基底を計算する。それから、座標系原点を観測者の位置に移すような変換を加味する。
"""Imitate gluLookAt"""
eye_work = np.array(eye, copy=False, dtype=np.float64)
center_work = np.array(center, copy=False, dtype=np.float64)
up_work = np.array(up, copy=False, dtype=np.float64)
f = center_work - eye_work
f /= norm(f)
s = np.cross(f, up_work)
s /= norm(s)
u = np.cross(s, f)
mat = np.identity(4)
mat[0, :3] = s
mat[1, :3] = u
mat[2, :3] = -f
mat[0, 3] = np.dot(-eye_work, s)
mat[1, 3] = np.dot(-eye_work, u)
mat[2, 3] = np.dot(-eye_work, -f)
return mat
関数 gluPerspective
を真似る¶
OpenGL のリファレンスをそのまま実装すればよい。
コツは先述の基本方針に沿うべく -1 の成分が最下行にあるということだ。
"""Imitate gluPerspective"""
f = 1.0 / np.tan(fovy * (np.pi / 360))
delta = znear - zfar
mat = np.identity(4)
mat[0, 0] = f / aspect
mat[1, 1] = f
mat[2, 2] = (zfar + znear) / delta
mat[2, 3] = (2 * zfar * znear) / delta
mat[3, 2] = -1
mat[3, 3] = 0
return mat