行列関連の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の仕様がそうなっているからだが、これってなんか理由があるのだろうか。OpenGLとFortranが関係あったりするのか、それとも最適化のためか。OpenGLなんて基本C言語なんだから、むしろDirectXみたいに行ベクトルで話をして行順で配列運用した方がよくないかと思うのだが。
書いてて思い出したが、
glMultTransposeMatrix
なんてのもあるので列ベクトルで話をして行順で運用する手もあるにはある。ていうかそうしよう。
遅かったりするんだろうか。