人工知能に関する断創録

このブログでは人工知能のさまざまな分野について調査したことをまとめています(更新停止: 2019年12月31日)

立方体の描画

プリミティブの描画(2008/8/24)で使った四角形を6枚張り合わせて立方体を作ってみます。いよいよ3次元空間へ突入。

cube.py
f:id:aidiary:20100814103908p:plain

サンプルスクリプト

#!/usr/bin/env python
#coding:utf-8
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
import sys

def main():
    glutInit(sys.argv)
    glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE | GLUT_DEPTH)
    glutInitWindowSize(300, 300)  # ウィンドウサイズ
    glutInitWindowPosition(100, 100)  # ウィンドウ位置
    glutCreateWindow("立方体の描画")  # ウィンドウを表示
    glutDisplayFunc(display)  # 描画コールバック関数を登録
    glutReshapeFunc(reshape)  # リサイズコールバック関数の登録
    init(300, 300)
    glutMainLoop()

def init(width, height):
    """初期化"""
    glClearColor(0.0, 0.0, 0.0, 1.0)
    glEnable(GL_DEPTH_TEST)  # 隠面消去を有効に

    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(45.0, float(width)/float(height), 0.1, 100.0)  # 投影変換

def display():
    """描画処理"""
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()
    # 視野変換:カメラの位置と方向のセット
    gluLookAt(3.0, 2.0, 4.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)   # 右斜め上から撮影
    
    draw_cube()  # 立方体を描く
    
    glFlush()  # OpenGLコマンドの強制実行

def reshape(width, height):
    """画面サイズの変更時に呼び出されるコールバック関数"""
    glViewport(0, 0, width, height)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(45.0, float(width)/float(height), 0.1, 100.0)

def draw_cube():
    """立方体を描く"""
#    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)

    glBegin(GL_QUADS)
    # 上面(緑)
    glColor3f(0.0, 1.0, 0.0)
    glVertex3f(1.0, 1.0, -1.0)   # A
    glVertex3f(-1.0, 1.0, -1.0)  # B
    glVertex3f(-1.0, 1.0, 1.0)   # C
    glVertex3f(1.0, 1.0, 1.0)    # D
    # 下面(オレンジ)
    glColor3f(1, 0.5, 0)
    glVertex3f(1.0, -1.0, -1.0)  # E
    glVertex3f(-1.0, -1.0, -1.0) # F
    glVertex3f(-1.0, -1.0, 1.0)  # G
    glVertex3f(1.0, -1.0, 1.0)   # H
    # 前面(赤)
    glColor3f(1.0, 0.0, 0.0)
    glVertex3f(1.0, 1.0, 1.0)    # D
    glVertex3f(-1.0, 1.0, 1.0)   # C
    glVertex3f(-1.0, -1.0, 1.0)  # G
    glVertex3f(1.0, -1.0, 1.0)   # H
    # 背面(黄色)
    glColor3f(1.0, 1.0, 0.0)
    glVertex3f(1.0, 1.0, -1.0)   # A
    glVertex3f(-1.0, 1.0, -1.0)  # B
    glVertex3f(-1.0, -1.0, -1.0) # F
    glVertex3f(1.0, -1.0, -1.0)  # E
    # 左側面(青)
    glColor3f(0.0, 0.0, 1.0)
    glVertex3f(-1.0, 1.0, 1.0)   # C
    glVertex3f(-1.0, 1.0, -1.0)  # B
    glVertex3f(-1.0, -1.0, -1.0) # F
    glVertex3f(-1.0, -1.0, 1.0)  # G
    # 右側面(マゼンタ)
    glColor3f(1.0, 0.0, 1.0)
    glVertex3f(1.0, 1.0, 1.0)    # D
    glVertex3f(1.0, 1.0, -1.0)   # A
    glVertex3f(1.0, -1.0, -1.0)  # E
    glVertex3f(1.0, -1.0, 1.0)   # H
    glEnd()

if __name__ == "__main__":
    main()

立方体の描画

f:id:aidiary:20100814103909p:plain

OpenGLでは、上のようにX軸、Y軸、Z軸の3次元空間が表せます。右向きがX軸の正の方向、上向きがY軸の正の方向、そして手前向きがZ軸の正の方向になります。このようにX軸とY軸を配置したとき手前向きにZ軸の正の方向がくる座標系は右手系と呼ばれます。OpenGLは右手系です。

なぜ右手系と呼ばれるかと言うと・・・右手をパーにして薬指と小指を折ります。中指を半分くらいまで折って人指し指と直角になるようにしてください。親指をX軸の正の向き、人指し指をY軸の正の向きに合わせると中指がZ軸の正の向きになっています。左手でやるとZ軸の正の方向は逆方向になります。

f:id:aidiary:20100814103910p:plain

立方体はdraw_cube()関数の中で描画しています。display()からdraw_cube()を呼び出すと立方体が描けます。立方体は 6つの面から構成されるので四角形を6つ描きます。頂点と四角形の対応関係は上の図を見てください。立方体の外側がポリゴンの表になるように反時計回りに頂点を指定するのがポイントです。各面はいろいろな色で塗りつぶしています。

隠面消去

3次元空間ではあるオブジェクトの背後にあるオブジェクトは陰に隠れてしまい見えなくなります。これを隠面消去と呼びます。OpenGLで隠面消去を有効にするには、デプスバッファを使います。デプスは深さって意味なので画面の奥行きという感じでしょうか。追加しなければならないコードは以下の3つです。

glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE | GLUT_DEPTH)
glEnable(GL_DEPTH_TEST)  # 隠面消去を有効に

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

glutInitDisplayMode()でGLUT_DEPTHを指定し、glEnable()でGL_DEPTH_TESTを有効にします。あとglClear()で画面をクリアするときにGL_DEPTH_BUFFER_BITを指定してデプスバッファをクリアします。

カメラの移動

プログラム中の下のコードはカメラの位置をセットしています。

    # 視野変換:カメラの位置と方向のセット
    gluLookAt(3.0, 2.0, 4.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)   # 右斜め上から撮影

デフォルトではカメラは原点 (0.0, 0.0, 0.0) にあり、Z軸の負の向きを向いています。今回は、立方体を原点に配置しているためそのままでは立方体の中にカメラが入ってしまいます。カメラを移動する関数がgluLookAt()です。

引数は、最初の3つがカメラの座標、次の3つがカメラの向き、最後の3つがカメラの上方向を表すベクトルです。ここでは、カメラを (3.0, 3.0, 4.0) に置いて、立方体の中心である原点 (0.0, 0.0, 0.0) を狙って撮影しています。(3.0, 3.0, 4.0) なのでカメラは立方体の右斜め上の方にあります。カメラの上方向は (0.0, 1.0, 0.0)、つまりY軸の正の方向にしています。ふつうはこの方向です。ちょっとカメラの位置を変えてみます。

    gluLookAt(-4.0, 0, -4.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)  # 左斜め後ろから撮影

今度は、立方体の左斜め後ろから撮影しています。下のようにちゃんと立方体の後ろ側が写りますね!

f:id:aidiary:20100814103911p:plain

こういうカメラの移動は、視野変換と呼ばれています。視野変換するときは下のようにモデルビュー行列GL_MODELVIEWをglLoadIdentity()で初期化してからgluLookAt()を呼び出して設定します。

    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()
    # 視野変換:カメラの位置と方向のセット
    gluLookAt(3.0, 2.0, 4.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)   # 右斜め上から撮影

投影変換

OpenGLで3次元空間を表せるといってもやはりコンピュータのスクリーンに表示しているのだから最終的には2次元の絵になります。3次元の空間を2次元の平面に投影することを投影変換と呼びます。投影変換の設定は、init()の下の部分です。

    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(45.0, float(width)/float(height), 0.1, 100.0)  # 投影変換

投影変換は、gluPerspective()で設定します。第1引数の45.0度は視野角、第2引数はアスペクト比です。最後の2つは重要で、カメラからの距離が0.1から100.0の間の空間にあるオブジェクトが描画されることを意味しています。つまり、カメラからの距離が0.1より近いものや100.0より遠い場所にあるオブジェクトは画面に描画されません。gluPerspective()は、透視投影と呼ばれる遠近法(近いものは大きく、遠いものは小さく見える)を用いた投影方法です。遠近法を使うため2次元平面に描画されても3次元空間っぽく見えるわけです。投影変換を設定するときは、投影行列(GL_PROJECTION)をglLoadIdentity()で初期化してからgluPerspective()を呼び出して設定します。

リサイズコールバック関数

メイン関数でリサイズコールバック関数reshape()をglutReshapeFunc()で登録しています。

glutReshapeFunc(reshape)  # リサイズコールバック関数の登録

def reshape(width, height):
    """画面サイズの変更時に呼び出されるコールバック関数"""
    glViewport(0, 0, width, height)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(45.0, float(width)/float(height), 0.1, 100.0)

この関数は、ウィンドウのサイズを変更したときに自動的に呼ばれるコールバック関数です。ウィンドウサイズが変わると座標軸の長さが変わってしまうためウィンドウの幅widthと高さheightのアスペクト比に応じて投影変換をglutPerspective()で再設定しています。

f:id:aidiary:20100814103912p:plain
f:id:aidiary:20100814103913p:plain

上の図は、ウィンドウサイズを変えたときの様子です。リサイズコールバック関数を呼び出して、投影変換を再設定しないと左のように各座標軸の長さが変わってしまい、立方体が歪んでしまいます。ちゃんと再設定すると右のように歪みません。