行列関連のgl関数をなるべく使わないでやってみる

スキニングの都合上glRotatefやglTranslatefを使わずに変換を実装する必要があるので下準備にこれらをglMultMatrixfで置き換えるテストコードを書いてみた。
さらにOpenGL3に準拠しようと思うとglMultMatrixfすらdeprecatedなので計算した行列をvertex shaderのuniform変数に渡してやらないといかん。
↓deprecatedの山
http://pyopengl.sourceforge.net/documentation/manual-3.0/index.xhtml


あと行列クラスを自前で実装しようかと思ったのだが途中でnumpyを使えることを思い出した。
http://www.nasuinfo.or.jp/FreeSpace/kenji/sf/fastTour/pyLinear.htm


というわけでスキニングの地均しに自前行列でglRotate, glTranslate, gluPerspectiveを代替してみた。
gl関数を使った実装(before)

    def draw(self):
        # gl関数を使った変換と描画
        glViewport(0, 0, half_w, self.h)
        # projection
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        self.projection.apply()
        # view
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        self.view.apply()
        # draw
        self.drawScene()

# self.projection.apply部分
    def apply(self):
        glMatrixMode(GL_PROJECTION)
        gluPerspective(
                self.fovy,
                self.aspect,
                self.near,
                self.far)

# self.view.apply部分
    def apply(self):
        glMatrixMode(GL_MODELVIEW)
        # position
        glTranslate(-self.pos[0], -self.pos[1], -self.pos[2])
        # pitch
        glRotate(self.pitch, 1, 0, 0)
        # head
        glRotate(self.head, 0, 1, 0)

glMultMatrixを使った実装(after)

    def draw(self):
        # 自前で行列を制御する描画
        glViewport(half_w, 0, half_w, self.h)
        # projection
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        glMultMatrixf(self.projection.getMatrix())
        # view
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        glMultMatrixf(self.view.getMatrix())
        # draw
        self.drawScene()

# self.projection.apply部分
    def getMatrix(self):
        # gluPerspectiveの写経
        p=numpy.identity(4)
        f=1.0/math.tan(to_radian(self.fovy/2.0))
        p[0, 0]=f/self.aspect
        p[1, 1]=f
        p[2, 2]=(self.far+self.near)/(self.near-self.far)
        p[2, 3]=-1
        p[3, 2]=2*self.far*self.near/(self.near-self.far)
        p[3, 3]=0
        return p

# self.view.apply部分
    def getMatrix(self):
        # translate matrix
        t=numpy.identity(4)
        t[3, 0]=-self.pos[0]
        t[3, 1]=-self.pos[1]
        t[3, 2]=-self.pos[2]
        # pitch
        pitch_sin=math.sin(to_radian(self.pitch))
        pitch_cos=math.cos(to_radian(self.pitch))
        p=numpy.identity(4)
        p[1, 1]=pitch_cos
        p[1, 2]=pitch_sin
        p[2, 1]=-pitch_sin
        p[2, 2]=pitch_cos
        # head
        head_sin=math.sin(to_radian(self.head))
        head_cos=math.cos(to_radian(self.head))
        h=numpy.identity(4)
        h[2, 2]=head_cos
        h[2, 0]=head_sin
        h[0, 2]=-head_sin
        h[0, 0]=head_cos
        # numpyでは行列の積はdot
        return numpy.dot(numpy.dot(h, p), t)

これで等価になった。
projectionの方は書いてある通りに
http://pyopengl.sourceforge.net/documentation/manual-3.0/gluPerspective.xhtml
写経しただけなのでいいとして、モデルビューの方は行列の積に関して抑えておくべきことがあるのでメモ。


まずOpenGLでは、行列の議論する場合は列ベクトルで話をするという大前提がある。
ということはあるベクトルvに行列A, Bを順番に適用する時は
(B A) v
でなければならない。
今回書いたコードは視点の移動に関して、ヘッド回転、ピッチ回転、移動という仕様にしているので、
(Translate Pitch Head) v
という順番でかけなければならないのだがコード上は左右逆で

        return numpy.dot(numpy.dot(h, p), t)

としなければならなかった。
numpyに無理やり列順で行列を入れているため行列が転置されてこうなる。
これは、考えている内容をコーディングする時に転置したり左右逆転したりが入ってよろしくない。
そもそも列順でデータを入れなければならないのはOpenGLの仕様がそうなっているからだが、これってなんか理由があるのだろうか。OpenGLFortranが関係あったりするのか、それとも最適化のためか。OpenGLなんて基本C言語なんだから、むしろDirectXみたいに行ベクトルで話をして行順で配列運用した方がよくないかと思うのだが。
書いてて思い出したが、
glMultTransposeMatrix
なんてのもあるので列ベクトルで話をして行順で運用する手もあるにはある。ていうかそうしよう。
遅かったりするんだろうか。