クラス AppBase
の実装¶
単純な PyOpenGL のプログラムは、どれも構造と手続きが似通ったものになる。このノートではサンプルプログラムを多数書くつもりでいるので、それらに共通しているプログラミング要素を、オブジェクト指向プログラミング的着想によってクラスとしてカプセル化しておく。
ただし、歴史的な事情によりそのようなクラスを単一のクラスで表現し切ることは無理があるようだ。 OpenGL 3.0 以前と以後とで、プログラミングの方法論がまるで別物なので、以前、以後に対応するベースクラスをそれぞれ設計することになる。
本稿では、それらのベースクラスのベースクラス AppBase
を設計する。
クラス AppBase
¶
先にクラス全景を次に示す。
#!/usr/bin/env python
"""appbase.py: Define class AppBase.
Reference:
* rndblnch / opengl-programmable <bitbucket.org/rndblnch/opengl-programmable>
* Simple Demo for GLSL <www.lighthouse3d.com>
* OpenGLBook.com <openglbook.com>
"""
# pylint: disable=unused-argument, no-self-use, invalid-name
import sys
from abc import (ABCMeta, abstractmethod)
import OpenGL.GL as GL
import OpenGL.GLUT as GLUT
from Quaternion import Quat
from viewnavigation import (ViewRotate, ViewZoom)
CONTEXT_VERSION = (3, 1)
WINDOW_TITLE = b"PyOpenGL Demo"
WINDOW_SX, WINDOW_SY = (320, 240)
WINDOW_X, WINDOW_Y = (100, 100)
PERSPECTIVE_FOVY = 45.0
PERSPECTIVE_NEAR = 1.0
PERSPECTIVE_FAR = 100.0
CAMERA_EYE = (0., 10., 0.)
CAMERA_CENTER = (0., 0., 0.)
CAMERA_UP = (0., 1., 0.)
# pylint: disable=too-many-instance-attributes
class AppBase(metaclass=ABCMeta):
"""This class represents skeleton of OpenGL programs."""
def __init__(self, **kwargs):
"""Initialize an instance of class AppBase."""
self.frame_count = 0
# parameters for GLUT
self.context_version = kwargs.get(
'context_version', CONTEXT_VERSION)
self.window_title = kwargs.get(
'window_title', WINDOW_TITLE)
self.window_size = kwargs.get(
'window_size', (WINDOW_SX, WINDOW_SY))
self.window_position = kwargs.get(
'window_position', (WINDOW_X, WINDOW_Y))
self.mouse_event_handlers = []
# paramters for gluPerspective/perspective
self.fovy = kwargs.get('perspective_fovy', PERSPECTIVE_FOVY)
self.znear = kwargs.get('perspective_near', PERSPECTIVE_NEAR)
self.zfar = kwargs.get('perspective_far', PERSPECTIVE_FAR)
# parameters for gluLookAt/lookat
self.camera_eye = kwargs.get('camera_eye', CAMERA_EYE)
self.camera_center = kwargs.get('camera_center', CAMERA_CENTER)
self.camera_up = kwargs.get('camera_up', CAMERA_UP)
# parameters for glMultMatrix
self.quat = Quat([0., 0., 0., 1.])
self.program_manager = None
def run(self, args):
"""Run a program."""
self.init_glut(args)
self.init_program()
self.init_gl()
self.init_mouse_event_handlers()
self.init_texture()
self.init_object()
self.init_transform()
return GLUT.glutMainLoop()
def init_glut(self, args):
"""Initialize the GLUT state."""
# Initialize GLUT.
GLUT.glutInit(args)
GLUT.glutInitContextVersion(
self.context_version[0], self.context_version[1])
GLUT.glutInitContextFlags(GLUT.GLUT_FORWARD_COMPATIBLE)
GLUT.glutInitContextProfile(GLUT.GLUT_CORE_PROFILE)
GLUT.glutSetOption(
GLUT.GLUT_ACTION_ON_WINDOW_CLOSE,
GLUT.GLUT_ACTION_GLUTMAINLOOP_RETURNS)
# Initialize and create the main window.
GLUT.glutInitDisplayMode(
GLUT.GLUT_DOUBLE | GLUT.GLUT_RGBA | GLUT.GLUT_DEPTH)
GLUT.glutInitWindowSize(
self.window_size[0], self.window_size[1])
GLUT.glutInitWindowPosition(
self.window_position[0], self.window_position[1])
GLUT.glutCreateWindow(self.window_title)
GLUT.glutDisplayFunc(self.render)
GLUT.glutIdleFunc(self.idle)
GLUT.glutReshapeFunc(self.resize)
GLUT.glutKeyboardFunc(self.keyboard)
GLUT.glutTimerFunc(0, self.timer, 0)
GLUT.glutMouseFunc(self.mouse)
GLUT.glutMotionFunc(self.motion)
GLUT.glutCloseFunc(self.cleanup)
aspects = [('Vendor', GL.GL_VENDOR),
('Renderer', GL.GL_RENDERER),
('Version', GL.GL_VERSION),]
if self.context_version[0] > 1:
aspects.append(('GLSL', GL.GL_SHADING_LANGUAGE_VERSION))
for i in aspects:
print('{}: {}'.format(i[0],
GL.glGetString(i[1]).decode()),
file=sys.stderr, flush=True)
@abstractmethod
def init_program(self):
"""Setup shaders."""
pass
@abstractmethod
def get_shader_sources(self):
"""Return shader sources."""
return None
def init_mouse_event_handlers(self):
"""Initialize view nagivation"""
self.mouse_event_handlers.append(
ViewRotate(self, GLUT.GLUT_LEFT_BUTTON))
self.mouse_event_handlers.append(
ViewZoom(self, GLUT.GLUT_RIGHT_BUTTON))
def init_gl(self):
"""Initialize the OpenGL state."""
GL.glEnable(GL.GL_DEPTH_TEST)
GL.glClearColor(0.0, 0.0, 0.0, 1.0)
def init_object(self):
"""Override this method."""
pass
def init_texture(self):
"""Initialize textures."""
pass
def init_transform(self):
"""Initialize VM transforms."""
pass
def resize(self, width, height):
"""The reshape callback function."""
if height == 0:
return
GL.glViewport(0, 0, width, height)
self.update_projection(self.fovy, width, height)
def update_projection(self, fovy, width, height):
"""Set the projection matrix."""
pass
def update_rotation(self, quat=None):
"""Update the model transform."""
pass
def render(self):
"""The display callback function."""
self.frame_count += 1
GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
self.do_render()
GLUT.glutSwapBuffers()
def timer(self, value):
"""The timer callback function."""
if value != 0:
caption = '{}: {} frames per secound @ {} x {}'.format(
self.window_title.decode(),
self.frame_count * 4,
GLUT.glutGet(GLUT.GLUT_WINDOW_WIDTH),
GLUT.glutGet(GLUT.GLUT_WINDOW_HEIGHT))
GLUT.glutSetWindowTitle(caption)
self.frame_count = 0
GLUT.glutTimerFunc(250, self.timer, 1) # a quarter of a second
def idle(self):
"""The idle callback function."""
GLUT.glutPostRedisplay()
def keyboard(self, key, x, y):
"""The new keyboard callback function."""
if ord(key) == 0o33:
print('Exit', file=sys.stderr, flush=True)
sys.exit()
GLUT.glutPostRedisplay()
def mouse(self, button, state, x, y):
"""The mouse callback function."""
for handler in self.mouse_event_handlers:
handler.mouse(button, state, x, y)
def motion(self, x, y):
"""The motion callback function."""
for handler in self.mouse_event_handlers:
handler.motion(x, y)
def cleanup(self):
"""The clean up callback function."""
pass
def do_render(self):
"""Override this method."""
pass
if __name__ == "__main__":
pass
このクラスの目的は次のとおりだ:
新しいサンプルコードを書くときは、このクラスのサブクラスを定義する形にすることで、コードの重複を積極的に少なくする。特にメソッドのオーバーライドにより、描画や操作の「差分」の実装だけで済むようにする。
サブクラスでコンテキストバージョンを指定することで、そのアプリケーションが deprecated features を採用するかどうかを明示する。
コンテキスト情報の出力や FPS の表示等、全サンプルコードが利用しがちな機能を提供する。
このクラスの利用方法は後述のページで見ていくことになるので、このページの残りでは、このクラス自体のポイントを記す。
機能概要¶
クラス AppBase
で実装する機能を述べる。
- コンテキストバージョン指定機能
OpenGL のコンテキストバージョンをコンストラクターで指定できる。サブクラスから指定することになる。
- ウィンドウタイトル設定機能
ウィンドウタイトルバーに表示するタイトル文字列を指定できる。ただし、アスキー文字のみのもよう。
- FPS リアルタイム表示機能
ウィンドウタイトルバーに FPS を表示する。0.25 秒ごとに更新。
- ウィンドウサイズ・位置の指定機能
ウィンドウの初期サイズ、初期表示位置をクラスのコンストラクターで指定できる。
- OpenGL サポートバージョン情報表示機能
ウィンドウ表示までに、次のような情報をコンソールに出力する。最後の GLSL に関する情報については、プログラムが利用する OpenGL のバージョンが 2.0 以上のときしか表示しないし、することができない。
Vendor: Intel Renderer: Intel(R) HD Graphics Version: 3.1.0 - Build 9.17.10.4101 GLSL: 1.40 - Intel Build 9.17.10.4101
- ウィンドウサイズ変更時ビューポート自動更新機能
ウィンドウサイズ変更時に可能な限り OpenGL のビューポートをウィンドウの全クライアント領域に更新する。
- マウスドラッグによるズーム・回転機能
左ボタンドラッグで描画モデルの回転、右ボタンドラッグでモデルにズームする。 回転とズーム も参照。
- 透視射影パラメーターの設定機能
関数
gluPerspective
またはそれと同等のパラメーターを任意に設定可能。- カメラパラメーターの設定機能
関数
gluLookAt
またはそれと同等のパラメーターを任意に設定可能。- GLSL オブジェクトの管理機能
プログラムオブジェクトをシェーダーオブジェクトの生成、破棄処理を管理する。詳細は シェーダープログラム管理クラス を参照。
- アプリケーション終了機能
Esc キーを押すと、アプリケーションを終了する。
利用方法¶
本クラスはオブジェクト化して利用するのではなく、ベースクラスとして利用する。サブクラスとしてアプリケーションを実装するサンプルのときに記す。