2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

PyOpenGLでモダンな表示を行う

Posted at

概要

約十年ぶりにOpenGLを触ることになり、glBegin()で三角形でも書いていけばいいかと思っていたら、そんな書き方はもう古いらしいことが分かりました(参考)。
さらに今回対象とするターゲットがPythonだったこともあり、簡単な図形の表示を行うにも一苦労したので、一通りの手順をまとめておこうと思います。

進め方

モダンなOpenGLへの変更点やモダンさの詳細は様々なWebページで解説がなされているので、ここでは実際に表示するプログラムを書くことに注力していきます。
具体的にはここで、作成されている立方体を描画するC++のプログラムをPython化することで処理の流れとPythonでの実装を見ていきます。

環境

  • ubuntu 20 on docker

  • apt

    apt install -y python3 python3-pip freeglut3 freeglut3-dev libglew-dev
    
  • requirements.txt

pip
numpy==1.20.2
PyOpenGL==3.1.5
PyOpenGL-accelerate==3.1.5
scipy==1.6.2

ソースコード

全体のソースコードはここに置いています。

main

mainは初期化とloopを呼び出すだけ。
RenderクラスのコンストラクタでGLUTの初期化を実行しています。

# メインプログラム
def main():
    render = Render(sys.argv[0])
    render.init()
    render.mainloop()

class Render

実際の処理を行うクラス。
メンバ関数はでもとのソースコードにほぼ対応しています。

  • init()
  • display()
  • mainloop()

init()

    def init(self):
        # 背景色
        glClearColor(1.0, 1.0, 1.0, 1.0)

        with open(self.vertShaderPath, "r") as f:
            vertShaderData = f.read()

        with open(self.fragShaderPath, "r") as f:
            fragShaderData = f.read()

        # シェーダオブジェクトの作成
        vertShader = glCreateShader(GL_VERTEX_SHADER)
        fragShader = glCreateShader(GL_FRAGMENT_SHADER)

        # シェーダのソースプログラムの読み込み
        glShaderSource(vertShader, vertShaderData)
        glShaderSource(fragShader, fragShaderData)
        
        # バーテックスシェーダのソースプログラムのコンパイル
        glCompileShader(vertShader)
        compiled = glGetShaderiv(vertShader, GL_COMPILE_STATUS)
        if compiled == GL_FALSE:
            print("in cimpiled error")
            strInfoLog = glGetShaderInfoLog(vertShader)
            print("Compilation failure for "  + " shader:\n" + str(strInfoLog))

        # フラグメントシェーダのソースプログラムのコンパイル
        glCompileShader(fragShader)
        compiled = glGetShaderiv(fragShader, GL_COMPILE_STATUS)
        if compiled == GL_FALSE:
            print("in cimpiled error")
            strInfoLog = glGetShaderInfoLog(fragShader)
            print("Compilation failure for "  + " shader:\n" + str(strInfoLog))

        # プログラムオブジェクトの作成
        self.gl2Program = glCreateProgram()

        # シェーダオブジェクトのシェーダプログラムへの登録
        glAttachShader(self.gl2Program, vertShader)
        glAttachShader(self.gl2Program, fragShader)

        # attribute 変数 position の index を 0 に指定する。
        glBindAttribLocation(self.gl2Program, 0, "position")

        # シェーダプログラムのリンク
        glLinkProgram(self.gl2Program)
        linked = glGetProgramiv(self.gl2Program, GL_LINK_STATUS)
        if linked == GL_FALSE:
            print("Link error.")
            strInfoLog = printProgramInfoLog(self.gl2Program)
            return

        # シェーダオブジェクトのデタッチ
        glDetachShader(self.gl2Program, vertShader)
        glDetachShader(self.gl2Program, fragShader)
        # シェーダオブジェクトの削除
        glDeleteShader(vertShader)
        glDeleteShader(fragShader)

今回の苦労したシェーダの読み込み。
リストなどを使用したらもう少しすっきりするとは思いますが、学習のために愚直に書いています。

        # 視野変換行列を求める
        temp0 = matrix.lookAt(4.0, 5.0, 6.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)

        # 透視投影変換行列を求める 
        temp1 = matrix.cameraMatrix(30.0, 1.0, 7.0, 11.0)

        # 視野変換行列と投影変換行列の積を projectionMatrix に入れる
        self.projectionMatrix = matrix.multiplyMatrix(temp0, temp1)

        # uniform 変数 projectionMatrix の場所を得る
        self.projectionMatrixLocation = glGetUniformLocation(self.gl2Program, "projectionMatrix")

ここは古いOpenGLに近いところ。
mtrixは行列計算をしている別ファイルを参照してください。

        # 頂点バッファオブジェクトを2つ作る
        self.vertbuffer = glGenBuffers(1)
        self.fragbuffer = glGenBuffers(1)

        # 頂点バッファオブジェクトに8頂点分のメモリ領域を確保する 
        glBindBuffer(GL_ARRAY_BUFFER, self.vertbuffer)
        glBufferData(GL_ARRAY_BUFFER, position, GL_STATIC_DRAW)

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.fragbuffer)
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, edge.astype(np.int32), GL_STATIC_DRAW)

        # 頂点バッファオブジェクトを解放する 
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)

        # 頂点バッファオブジェクトを解放する
        glBindBuffer(GL_ARRAY_BUFFER, 0)

モダンな書き方ですね。
glBufferData()などはC++のものと引数が若干異なっています。

display()

    def display(self):
        # 画面クリア
        glClear(GL_COLOR_BUFFER_BIT)

        # シェーダプログラムを適用する
        glUseProgram(self.gl2Program)

        # uniform 変数 projectionMatrix に行列を設定する
        glUniformMatrix4fv(self.projectionMatrixLocation, 1, GL_FALSE, self.projectionMatrix)

        # index が 0 の attribute 変数に頂点情報を対応付ける
        glEnableVertexAttribArray(0)

        # 頂点バッファオブジェクトとして vertbuffer を指定する
        glBindBuffer(GL_ARRAY_BUFFER, self.vertbuffer)

        # 頂点情報の格納場所と書式を指定する
        glVertexAttribPointer(0, 3, GL_DOUBLE, GL_FALSE, 0, None)

        # 頂点バッファオブジェクトの指標として fragbuffer を指定する
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.fragbuffer)

        # 図形を描く 
        glDrawElements(GL_LINES, 24, GL_UNSIGNED_INT, None)

        # 頂点バッファオブジェクトを解放する
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
        glBindBuffer(GL_ARRAY_BUFFER, 0)

        # index が 0 の attribute 変数の頂点情報との対応付けを解除する
        glDisableVertexAttribArray(0)

        glutSwapBuffers()

まとめ

モダンなOpenGLの実装をPythonでのPyOpenGLを使って学びました。
正直写経しただけで分かっていないところも多いのですが、全体の流れは把握できたと思います。
モダンなOpenGLでは、シェーダを使ってGPUを有効活用できるという大きな利点はありますが、その分今回のような簡単な図形を書いたりするために手間がかかるようになっている気がします。
近年では簡単な図形の描画もUnityなどのツールを使うことが多く、OpenGLを使う人向けに導入コストを下げることがコスト増になるのかもしれません。
考え方もモダンにしていかなくてはとつくづく思います。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?