本稿では、「ModernGL」が提供する、カメラのサンプルを実行して挙動を確認する。ウィンドウに引き続き、プロジェクトでは基本となるカメラの扱いについて入門する気持ちで、サンプルコードを眺めてみると良いだろう。これまでのウィンドウの作成やグリッドの作成を利用して、かんたんにシーンを用意した後に、それらに組み合わせてカメラを用意して挙動を確認する。
サンプルコード
GitHubはこちら
'''
Added a simple camera class to an existing example.
The camera class is built using following tutorials:
https://learnopengl.com/Getting-started/Camera
http://in2gpu.com/2016/03/14/opengl-fps-camera-quaternion/
Controls:
Move:
Forward - W
Backwards - S
Strafe:
Up - up arrow
Down - down arrow
Left - A
Right - D
Rotate:
Left - Q
Right - E
Zoom:
In - X
Out - Z
adopted by: Alex Zakrividoroga
'''
import numpy as np
from pyrr import Matrix44, Quaternion, Vector3, vector
import moderngl
from ported._example import Example
class Camera():
def __init__(self, ratio):
self._zoom_step = 0.1
self._move_vertically = 0.1
self._move_horizontally = 0.1
self._rotate_horizontally = 0.1
self._rotate_vertically = 0.1
self._field_of_view_degrees = 60.0
self._z_near = 0.1
self._z_far = 100
self._ratio = ratio
self.build_projection()
self._camera_position = Vector3([0.0, 0.0, -40.0])
self._camera_front = Vector3([0.0, 0.0, 1.0])
self._camera_up = Vector3([0.0, 1.0, 0.0])
self._cameras_target = (self._camera_position + self._camera_front)
self.build_look_at()
def zoom_in(self):
self._field_of_view_degrees = self._field_of_view_degrees - self._zoom_step
self.build_projection()
def zoom_out(self):
self._field_of_view_degrees = self._field_of_view_degrees + self._zoom_step
self.build_projection()
def move_forward(self):
self._camera_position = self._camera_position + self._camera_front * self._move_horizontally
self.build_look_at()
def move_backwards(self):
self._camera_position = self._camera_position - self._camera_front * self._move_horizontally
self.build_look_at()
def strafe_left(self):
self._camera_position = self._camera_position - vector.normalize(self._camera_front ^ self._camera_up) * self._move_horizontally
self.build_look_at()
def strafe_right(self):
self._camera_position = self._camera_position + vector.normalize(self._camera_front ^ self._camera_up) * self._move_horizontally
self.build_look_at()
def strafe_up(self):
self._camera_position = self._camera_position + self._camera_up * self._move_vertically
self.build_look_at()
def strafe_down(self):
self._camera_position = self._camera_position - self._camera_up * self._move_vertically
self.build_look_at()
def rotate_left(self):
rotation = Quaternion.from_y_rotation(2 * float(self._rotate_horizontally) * np.pi / 180)
self._camera_front = rotation * self._camera_front
self.build_look_at()
def rotate_right(self):
rotation = Quaternion.from_y_rotation(-2 * float(self._rotate_horizontally) * np.pi / 180)
self._camera_front = rotation * self._camera_front
self.build_look_at()
def build_look_at(self):
self._cameras_target = (self._camera_position + self._camera_front)
self.mat_lookat = Matrix44.look_at(
self._camera_position,
self._cameras_target,
self._camera_up)
def build_projection(self):
self.mat_projection = Matrix44.perspective_projection(
self._field_of_view_degrees,
self._ratio,
self._z_near,
self._z_far)
def grid(size, steps):
u = np.repeat(np.linspace(-size, size, steps), 2)
v = np.tile([-size, size], steps)
w = np.zeros(steps * 2)
return np.concatenate([np.dstack([u, v, w]), np.dstack([v, u, w])])
class PerspectiveProjection(Example):
gl_version = (3, 3)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.prog = self.ctx.program(
vertex_shader='''
#version 330
uniform mat4 Mvp;
in vec3 in_vert;
void main() {
gl_Position = Mvp * vec4(in_vert, 1.0);
}
''',
fragment_shader='''
#version 330
out vec4 f_color;
void main() {
f_color = vec4(0.1, 0.1, 0.1, 1.0);
}
''',
)
self.camera = Camera(self.aspect_ratio)
self.mvp = self.prog['Mvp']
self.vbo = self.ctx.buffer(grid(15, 10).astype('f4'))
self.vao = self.ctx.simple_vertex_array(self.prog, self.vbo, 'in_vert')
self.states = {
self.wnd.keys.W: False, # forward
self.wnd.keys.S: False, # backwards
self.wnd.keys.UP: False, # strafe Up
self.wnd.keys.DOWN: False, # strafe Down
self.wnd.keys.A: False, # strafe left
self.wnd.keys.D: False, # strafe right
self.wnd.keys.Q: False, # rotate left
self.wnd.keys.E: False, # rotare right
self.wnd.keys.Z: False, # zoom in
self.wnd.keys.X: False, # zoom out
}
def move_camera(self):
if self.states.get(self.wnd.keys.W):
self.camera.move_forward()
if self.states.get(self.wnd.keys.S):
self.camera.move_backwards()
if self.states.get(self.wnd.keys.UP):
self.camera.strafe_up()
if self.states.get(self.wnd.keys.DOWN):
self.camera.strafe_down()
if self.states.get(self.wnd.keys.A):
self.camera.strafe_left()
if self.states.get(self.wnd.keys.D):
self.camera.strafe_right()
if self.states.get(self.wnd.keys.Q):
self.camera.rotate_left()
if self.states.get(self.wnd.keys.E):
self.camera.rotate_right()
if self.states.get(self.wnd.keys.Z):
self.camera.zoom_in()
if self.states.get(self.wnd.keys.X):
self.camera.zoom_out()
def key_event(self, key, action, modifiers):
if key not in self.states:
print(key, action)
return
if action == self.wnd.keys.ACTION_PRESS:
self.states[key] = True
else:
self.states[key] = False
def render(self, time, frame_time):
self.move_camera()
self.ctx.clear(1.0, 1.0, 1.0)
self.ctx.enable(moderngl.DEPTH_TEST)
self.mvp.write((self.camera.mat_projection * self.camera.mat_lookat).astype('f4'))
self.vao.render(moderngl.LINES)
if __name__ == '__main__':
PerspectiveProjection.run()
実行結果
コードのコメントにあるように、動作は非常にシンプルで、移動はA,D,S,Wと矢印キーの上下、回転はQ,E、ズームはX,Zでカメラを操作できる。