クラス ModernApp の実装

本稿で採り上げるクラス ModernApp こそが、今後制作する PyOpenGL プログラムの「ベース」となるものだ。 OpenGL 3.0 以降の新機能と、古参かつ現役の機能とから何かを描画するためのクラスだ。

クラス ModernApp

まずクラス全景を示す。

#!/usr/bin/env python
"""glmodernapp.py: For OpenGL 3.x applications.

References:
  * rndblnch / opengl-programmable
    <http://bitbucket.org/rndblnch/opengl-programmable>
  * OpenGLBook.com
    <http://openglbook.com/chapter-4-entering-the-third-dimension.html>
  * Tutorials for modern OpenGL (3.3+)
    <http://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices/>
"""
# pylint: disable=unused-argument, no-self-use
import sys
import numpy as np
import OpenGL.GL as GL
from appbase import AppBase
from program_manager import ProgramManager
from transform import (lookat, perspective)

class ModernApp(AppBase):
    """The base class for classes that never use deprecated features
    of OpenGL.
    """

    def __init__(self, **kwargs):
        """Initialize an instance of class ModernApp."""

        super(ModernApp, self).__init__(**kwargs)

    def init_program(self):
        """Setup shaders."""

        shader_sources = self.get_shader_sources()
        if not shader_sources:
            return

        self.program_manager = ProgramManager()
        self.program_manager.setup(shader_sources)

    def init_transform(self):
        """Initialize VM transforms."""

        self.update_rotation()

        camera_matrix = lookat(
            self.camera_eye, self.camera_center, self.camera_up)

        GL.glUniformMatrix4fv(
            GL.glGetUniformLocation(
                self.program_manager.program_id, b"camera"),
            1, GL.GL_TRUE,
            camera_matrix)

    def get_shader_sources(self):
        """Return shader sources."""
        return {}

    def update_projection(self, fovy, width, height):
        """Update the projection matrix."""

        if not self.program_manager:
            return

        if self.program_manager.program_id:
            GL.glUniformMatrix4fv(
                GL.glGetUniformLocation(
                    self.program_manager.program_id, b"projection"),
                1, GL.GL_TRUE,
                perspective(
                    fovy, width / height, self.znear, self.zfar))

        self.fovy = fovy

    def update_rotation(self, quat=None):
        """Update the model transform."""

        if not self.program_manager or not self.program_manager.program_id:
            return

        rotation_matrix = np.identity(4)
        if quat is not None:
            self.quat = quat
            rotation_matrix[:3, :3] = quat.transform

        GL.glUniformMatrix4fv(
            GL.glGetUniformLocation(
                self.program_manager.program_id, b"rotation"),
            1, GL.GL_TRUE,
            rotation_matrix)

    def cleanup(self):
        """The clean up callback function."""

        if self.program_manager:
            self.program_manager.cleanup()

def main(args):
    """The main function."""

    app = ModernApp()
    app.run(sys.argv)

if __name__ == "__main__":
    sys.exit(main(sys.argv))

各メソッドはすべてベースクラス AppBase からのオーバーライドとなる。以下、主要メソッドの実装を解説する。

メソッド init_program

        """Setup shaders."""

        shader_sources = self.get_shader_sources()
        if not shader_sources:
            return

        self.program_manager = ProgramManager()
        self.program_manager.setup(shader_sources)

サブクラスでオーバーライドされているメソッド get_shader_sources を呼び出し、シェーダータイプとシェーダーコードのペアからなる辞書オブジェクトを受け取り、クラス ProgramManager のオブジェクトを生成し、それに初期化処理を委ねる。

詳細は シェーダープログラム管理クラス を参照。

メソッド init_transform

        """Initialize VM transforms."""

        self.update_rotation()

        camera_matrix = lookat(
            self.camera_eye, self.camera_center, self.camera_up)

        GL.glUniformMatrix4fv(
            GL.glGetUniformLocation(
                self.program_manager.program_id, b"camera"),
            1, GL.GL_TRUE,
            camera_matrix)

初回にシェーダーオブジェクトに行列データを送信する処理である。モデル回転用行列、カメラ用行列、投影用行列すべてを設定する。

  • 関数 lookat については ベクトル・行列・座標変換 参照。

  • GL.glUniformMatrix4fv の転置フラグを ON にすることで、OpenGL に行列の column-major 化をさせる。

言い忘れたが、頂点シェーダーでの各行列の名前は次のとおりとする:

#version 330 core

uniform mat4 camera;
uniform mat4 projection;
uniform mat4 rotation;

...

決め打ちなのはいかにも手抜きだが、当面これでいく。

メソッド update_projection

        """Update the projection matrix."""

        if not self.program_manager:
            return

        if self.program_manager.program_id:
            GL.glUniformMatrix4fv(
                GL.glGetUniformLocation(
                    self.program_manager.program_id, b"projection"),
                1, GL.GL_TRUE,
                perspective(
                    fovy, width / height, self.znear, self.zfar))

        self.fovy = fovy

直接呼び出すというよりは、ベースクラスでウィンドウサイズ変更時に呼び出すメソッドだ。

メソッド update_rotation

        """Update the model transform."""

        if not self.program_manager or not self.program_manager.program_id:
            return

        rotation_matrix = np.identity(4)
        if quat is not None:
            self.quat = quat
            rotation_matrix[:3, :3] = quat.transform

        GL.glUniformMatrix4fv(
            GL.glGetUniformLocation(
                self.program_manager.program_id, b"rotation"),
            1, GL.GL_TRUE,
            rotation_matrix)

前半部はほぼ クラス DeprecatedApp の実装 でのオーバーライドと同じだ。外部で計算した回転量を受け取って、直接メンバーデータ self.quat に保存しておく。後半はシェーダーオブジェクトへの行列送信である。

メソッド cleanup

        """The clean up callback function."""

        if self.program_manager:
            self.program_manager.cleanup()

シェーダー関連の後始末を行う。詳細は シェーダープログラム管理クラス を参照。

なお、サブクラスではこのメソッドをオーバーライドし、この処理に加えて、バッファーオブジェクトやテクスチャーオブジェクトの破棄をすることになる。