コンピュータグラフィックスのレンダリングライブラリの一つである OpenGL を Python3 から利用するための Linux 環境を準備して、設定例およびサンプルコードを記載します。特にここでは Debian9 を利用します。
ModernGL インストール
Python バインディングのうち、ここでは ModernGL を介して OpenGL を利用することにします。ただし、必要となる Python は 3 系です。
Python3 および pip、ipython]、numpy、matplotlib、pil
sudo apt install python3
sudo apt install python3-pip
sudo apt install python3-ipython
sudo apt install python3-numpy
sudo apt install python3-matplotlib
sudo apt install python3-pil
ModernGL
以下のいずれかでインストールできます。
/usr/bin/python3 -m pip install ModernGL
/usr/bin/pip3 install ModernGL
動作確認
IPython 起動
/usr/bin/python3 -m IPython
以下の内容を実行します。
import moderngl
ctx = moderngl.create_standalone_context()
buf = ctx.buffer(b'Hello World!') # allocated on the GPU
buf.read()
出力例
b'Hello World!'
3D レンダリング Hello World
3D モデルから 2D 画像を生成する 3D レンダリングの簡単な例を記載します。IPython 等で以下の内容を実行します。
DISPLAY=:0 /usr/bin/python3 -m IPython
import moderngl
ctx = moderngl.create_standalone_context()
下記レンダリングパイプラインにおいて、頂点シェーダーへの入力となる、3 つの頂点の情報を準備します。
import numpy as np
vertices = np.array([
0.5, 0.5, 0.0,
-0.5, 0.5, 0.0,
-0.5, -0.5, 0.0
])
これらをバイトに変換して GPU 上にデータを格納し、Buffer (Vertex Buffer Object) を作成します。
vbo = ctx.buffer(vertices.astype('float32').tobytes())
ModernGL をインストールすると付属のバージョンの OpenGL がインストールされます。OpenGL には OpenGL Shading Language (GLSL) とよばれる、シェーダーを記述するための言語の処理系が実装されています。GLSL を用いて、以下の二つのシェーダーを作成します。これらは一連のレンダリングパイプラインで利用されます。
- Vertex Shader (頂点シェーダー)
- 入力された、3D モデルにおけるローカル座標系の頂点情報をワールド座標系に変換して、更にあるカメラの視点から見たときの座標系に変換して 2 次元情報に射影します。
- Fragment Shader (ピクセルシェーダー)
- 前工程のレンダリングパイプラインで生成されたフラグメントに色情報等を設定します。
GLSL version 330
を利用しています。簡単のため、入力された三次元頂点情報 vec3 をそのまま加工せずに返しています。その際、同次座標系で返す必要があるため 1.0 を追加して vec4 にしています。また、各フラグメントの色も簡単のため RGBA で青色を返しています。
prog = ctx.program(
vertex_shader='''
#version 330
in vec3 in_vert;
void main() {
gl_Position = vec4(in_vert, 1.0);
}
''',
fragment_shader='''
#version 330
out vec4 f_color;
void main() {
f_color = vec4(0.0, 0.0, 1.0, 1.0);
}
'''
)
頂点情報 Buffer をシェーダー prog に変数名を指定して渡します。結果として Vertex Array Object (vao) が得られます。
vao = ctx.simple_vertex_array(prog, vbo, 'in_vert')
レンダリング先となる 2D フレーム Frame Buffer Object を用意します。500x500 ピクセルとしてみます。
fbo = ctx.simple_framebuffer((500, 500))
fbo.use()
fbo.clear(0.0, 0.0, 0.0, 1.0)
レンダリングを実行します。結果が fbo に格納されます。
vao.render()
結果は RAW 画像であるため、Matplotlib や OpenCV からは直接読み込むことが困難です。pillow(PIL) の Image.frombytes を利用して NumPy の ndarray に変換することができます。
from PIL import Image
myimg = Image.frombytes('RGB', fbo.size, fbo.read(), 'raw', 'RGB', 0, -1)
以下のようにして表示できます。
from matplotlib import pyplot as plt
plt.imshow(myimg)
plt.show()
頂点毎に異なる色を設定
先程の Hello world プログラムでは各頂点をすべて青色にしました。色を変えるために頂点シェーダーへの引数を増やしてみます。
import moderngl
import numpy as np
from PIL import Image
from matplotlib import pyplot as plt
# OpenGL コンテキスト
ctx = moderngl.create_standalone_context()
# 頂点情報 (x,y,z,r,g,b)
vertices = np.array([
0.5, 0.5, 0.0, 1.0, 0.0, 0.0,
-0.5, 0.5, 0.0, 0.0, 1.0, 0.0,
-0.5, -0.5, 0.0, 0.0, 0.0, 1.0
])
vbo = ctx.buffer(vertices.astype('float32').tobytes())
# シェーダープログラム
prog = ctx.program(
vertex_shader='''
#version 330
in vec3 in_vert;
in vec3 in_color;
out vec3 v_color;
void main() {
v_color = in_color;
gl_Position = vec4(in_vert, 1.0);
}
''',
fragment_shader='''
#version 330
in vec3 v_color;
out vec4 f_color;
void main() {
f_color = vec4(v_color, 1.0);
}
'''
)
# 色情報の引数を増やしています
vao = ctx.simple_vertex_array(prog, vbo, 'in_vert', 'in_color')
# 2D フレームの作成
fbo = ctx.simple_framebuffer((500, 500))
fbo.use()
fbo.clear(0.0, 0.0, 0.0, 1.0)
# レンダリングの実行
vao.render()
# RAW 画像から ndarray に変換して描画
myimg = Image.frombytes('RGB', fbo.size, fbo.read(), 'raw', 'RGB', 0, -1)
plt.imshow(myimg)
plt.show()
二次元 XY 平面内での回転
GPU 内で利用する uniform 定数を宣言しています。
import moderngl
import numpy as np
from PIL import Image
from matplotlib import pyplot as plt
# OpenGL コンテキスト
ctx = moderngl.create_standalone_context()
# 頂点情報 (x,y,z,r,g,b)
vertices = np.array([
0.5, 0.5, 0.0, 1.0, 0.0, 0.0,
-0.5, 0.5, 0.0, 0.0, 1.0, 0.0,
-0.5, -0.5, 0.0, 0.0, 0.0, 1.0
])
vbo = ctx.buffer(vertices.astype('float32').tobytes())
# シェーダープログラム
prog = ctx.program(
vertex_shader='''
#version 330
in vec3 in_vert;
in vec3 in_color;
out vec3 v_color;
// uniform によって GPU 内で利用する定数を宣言できます。
// 引数ではなく属性値として CPU から値を設定します。
uniform vec3 scale;
uniform float rotation;
void main() {
// 二次元の回転行列
// mat3 の仕様上、行と列を転置したものを設定します。
mat3 rot = mat3(
cos(rotation), sin(rotation), 0.0,
-sin(rotation), cos(rotation), 0.0,
0.0, 0.0, 1.0
);
v_color = in_color;
gl_Position = vec4((rot * in_vert) * scale, 1.0);
}
''',
fragment_shader='''
#version 330
in vec3 v_color;
out vec4 f_color;
void main() {
f_color = vec4(v_color, 1.0);
}
'''
)
# prog 内で宣言した GPU 用の定数の値を設定します。
prog['scale'].value = (2.0, 2.0, 1.0)
prog['rotation'].value = np.deg2rad(90)
# 頂点情報 (位置、色) をシェーダーに渡します。
vao = ctx.simple_vertex_array(prog, vbo, 'in_vert', 'in_color')
# 2D フレームの作成
fbo = ctx.simple_framebuffer((500, 500))
fbo.use()
fbo.clear(0.0, 0.0, 0.0, 1.0)
# レンダリングの実行
vao.render()
# RAW 画像から ndarray に変換して描画
myimg = Image.frombytes('RGB', fbo.size, fbo.read(), 'raw', 'RGB', 0, -1)
plt.imshow(myimg)
plt.show()
反時計回りに 90 度回転させてから 2 倍に拡大しています。mat3 は column-major order で値を指定する必要があることに注意します。
オブジェクトファイルの利用
3D オブジェクトはメッシュによって表現できます。メッシュは頂点やフェイス等から成ります。通常フェイスには三角形を利用します。メッシュの情報を格納するファイルフォーマットとしては OBJ がよく用いられます。以下は簡単な例です。
sample.obj
v 0.5 0.5 0.0
v -0.5 0.5 0.0
v -0.5 -0.5 0.0
v 0.5 -0.5 0.0
f 1 2 3
f 3 4 1
v
は頂点のローカル座標です。f
では三角形フェイスで利用する頂点 v のインデックスを三つ指定します。今回のメッシュでは三角形フェイスが二つで正方形を表現しています。OBJ ファイルを読み込むために何らかのモジュールをインストールすると便利です。
/usr/bin/python3 -m pip install objloader
以下のようなレンダリング結果が得られます。
import moderngl
import numpy as np
from PIL import Image
from matplotlib import pyplot as plt
from objloader import Obj
# OpenGL コンテキスト
ctx = moderngl.create_standalone_context()
# 頂点情報 (x,y,z)
obj = Obj.open('./sample.obj')
vbo = ctx.buffer(obj.pack('vx vy vz'))
# シェーダープログラム
prog = ctx.program(
vertex_shader='''
#version 330
in vec3 in_vert;
void main() {
gl_Position = vec4(in_vert, 1.0);
}
''',
fragment_shader='''
#version 330
out vec4 f_color;
void main() {
f_color = vec4(0.0, 0.0, 1.0, 1.0);
}
'''
)
# Vertex Array Object の作成
vao = ctx.simple_vertex_array(prog, vbo, 'in_vert')
# 2D フレームの作成
fbo = ctx.simple_framebuffer((500, 500))
fbo.use()
fbo.clear(0.0, 0.0, 0.0, 1.0)
# レンダリングの実行
vao.render()
# RAW 画像から ndarray に変換して描画
myimg = Image.frombytes('RGB', fbo.size, fbo.read(), 'raw', 'RGB', 0, -1)
plt.imshow(myimg)
plt.show()
オブジェクトへのテクスチャの貼り付け
sample.obj
v 0.5 0.5 0.0
v -0.5 0.5 0.0
v -0.5 -0.5 0.0
v 0.5 -0.5 0.0
vt 0.0 0.0
vt 1.0 0.0
vt 1.0 1.0
vt 0.0 1.0
f 1/3 2/4 3/1
f 3/1 4/2 1/3
vt
はテクスチャ画像における座標です。フェイスでは /
区切りでどのテクスチャ座標を頂点座標と対応させるか指定します。
import moderngl
import numpy as np
from PIL import Image
from matplotlib import pyplot as plt
from objloader import Obj
# OpenGL コンテキスト
ctx = moderngl.create_standalone_context()
# 頂点座標 (x,y,z)、テクスチャ座標 (x,y)
obj = Obj.open('./sample.obj')
vbo = ctx.buffer(obj.pack('vx vy vz tx ty'))
# シェーダープログラム
prog = ctx.program(
vertex_shader='''
#version 330
in vec3 in_vert;
in vec2 in_text;
out vec2 v_text;
void main() {
v_text = in_text;
gl_Position = vec4(in_vert, 1.0);
}
''',
fragment_shader='''
#version 330
uniform sampler2D Texture;
in vec2 v_text;
out vec4 f_color;
void main() {
f_color = vec4(texture(Texture, v_text).rgb, 1.0);
}
''',
)
# Vertex Array Object の作成
vao = ctx.simple_vertex_array(prog, vbo, 'in_vert', 'in_text')
# テクスチャ画像の読み込み
textureimg = Image.open('./myimage.png').transpose(Image.FLIP_TOP_BOTTOM).convert('RGB')
texture = ctx.texture(textureimg.size, 3, textureimg.tobytes())
texture.build_mipmaps()
texture.use()
# 2D フレームの作成
fbo = ctx.simple_framebuffer((500, 500))
fbo.use()
fbo.clear(0.0, 0.0, 0.0, 1.0)
# レンダリングの実行
vao.render()
# RAW 画像から ndarray に変換して描画
myimg = Image.frombytes('RGB', fbo.size, fbo.read(), 'raw', 'RGB', 0, -1)
plt.imshow(myimg)
plt.show()
オブジェクトの回転
XY 平面内の回転等であれば簡単ですが、任意軸回りの回転を行うためには四元数 Quaternions が利用できることが知られています。
sample.obj
v 0.5 0.5 0.0
v -0.5 0.5 0.0
v -0.5 -0.5 0.0
v 0.5 -0.5 0.0
vt 0.0 0.0
vt 1.0 0.0
vt 1.0 1.0
vt 0.0 1.0
f 1/3 2/4 3/1
f 3/1 4/2 1/3
単位方向ベクトル (1/sqrt(2), 1/sqrt(2), 0)
回りに反時計回りに 60 度回転させる例は以下のようになります。OpenGL 側ではなく Python スクリプト内で処理していますが、内容としては OpenGL の glRotatef
等を利用した場合と同様です。
import moderngl
import numpy as np
from PIL import Image
from matplotlib import pyplot as plt
from objloader import Obj
# OpenGL コンテキスト
ctx = moderngl.create_standalone_context()
# 頂点座標 (x,y,z)、テクスチャ座標 (x,y)
obj = Obj.open('./sample.obj')
# 単位方向ベクトル、回転させる角度
v = np.array([1/np.sqrt(2), 1/np.sqrt(2), 0])
theta = np.deg2rad(60)
# Quaternion の計算
q = np.hstack((v * np.sin(theta/2), np.array([np.cos(theta/2)])))
# 長さは 1 です。
print(np.linalg.norm(q))
# 回転行列 3x3 を Quaternion から計算できます。
# Quaternion の生成時に cos を第一要素にしている場合は添字に注意してください。
R = np.array([
[1 - 2*(q[1]*q[1] + q[2]*q[2]), 2*(q[0]*q[1] - q[2]*q[3]), 2*(q[0]*q[2] + q[1]*q[3])],
[2*(q[0]*q[1] + q[2]*q[3]), 1 - 2*(q[0]*q[0] + q[2]*q[2]), 2*(q[1]*q[2] - q[0]*q[3])],
[2*(q[0]*q[2] - q[1]*q[3]), 2*(q[1]*q[2] + q[0]*q[3]), 1 - 2*(q[0]*q[0] + q[1]*q[1])]
])
# OBJ ファイルの読み込み結果を処理して頂点座標だけ抜き出します。回転行列を適用して再度結合します。
A,B,C = np.hsplit(obj.to_array(), 3)
vertices = np.hstack((R.dot(A.T).T, C[:,:2])).flatten()
vbo = ctx.buffer(vertices.astype('float32').tobytes())
# シェーダープログラム
prog = ctx.program(
vertex_shader='''
#version 330
in vec3 in_vert;
in vec2 in_text;
out vec2 v_text;
void main() {
v_text = in_text;
gl_Position = vec4(in_vert, 1.0);
}
''',
fragment_shader='''
#version 330
uniform sampler2D Texture;
in vec2 v_text;
out vec4 f_color;
void main() {
f_color = vec4(texture(Texture, v_text).rgb, 1.0);
}
''',
)
# Vertex Array Object の作成
vao = ctx.simple_vertex_array(prog, vbo, 'in_vert', 'in_text')
# テクスチャ画像の読み込み
textureimg = Image.open('./myimage.png').transpose(Image.FLIP_TOP_BOTTOM).convert('RGB')
texture = ctx.texture(textureimg.size, 3, textureimg.tobytes())
texture.build_mipmaps()
texture.use()
# 2D フレームの作成
fbo = ctx.simple_framebuffer((500, 500))
fbo.use()
fbo.clear(0.0, 0.0, 0.0, 1.0)
# レンダリングの実行
vao.render()
# RAW 画像から ndarray に変換して描画
myimg = Image.frombytes('RGB', fbo.size, fbo.read(), 'raw', 'RGB', 0, -1)
plt.imshow(myimg)
plt.show()
関連記事
- Python コードスニペット (条件分岐)if-elif-else sample.py #!/usr/bin/python # -*- coding: utf-8 -*- # コメント内であっても、ASCII外の文字が含まれる場合はエンコーディング情報が必須 x = 1 # 一行スタイル if x==0: print 'a' # 参考: and,or,notが使用可能 (&&,||はエラー) elif x==1: p...
- Python コードスニペット (リスト、タプル、ディクショナリ)リスト range 「0から10まで」といった範囲をリスト形式で生成します。 sample.py print range(10) # for(int i=0; i<10; ++i) ← C言語などのfor文と比較 print range(5,10) # for(int i=5; i<10; ++i) print range(5,10,2) # for(int i=5; i<10;...
- ZeroMQ (zmq) の Python サンプルコードZeroMQ を Python から利用する場合のサンプルコードを記載します。 Fixing the World To fix the world, we needed to do two things. One, to solve the general problem of "how to connect any code to any code, anywhere". Two, to wra...
- Matplotlib/SciPy/pandas/NumPy サンプルコードPython で数学的なことを試すときに利用される Matplotlib/SciPy/pandas/NumPy についてサンプルコードを記載します。 Matplotlib SciPy pandas [NumPy](https://www.numpy
- pytest の基本的な使い方pytest の基本的な使い方を記載します。 適宜参照するための公式ドキュメントページ Full pytest documentation API Reference インストール 適当なパッケージ