コンピュータグラフィックスのレンダリングライブラリの一つである OpenGL を Python3 から利用するための Linux 環境を準備して、設定例およびサンプルコードを記載します。特にここでは Debian9 を利用します。
Python バインディングのうち、ここでは ModernGL を介して OpenGL を利用することにします。ただし、必要となる Python は 3 系です。
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
以下のいずれかでインストールできます。
/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 モデルから 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 を用いて、以下の二つのシェーダーを作成します。これらは一連のレンダリングパイプラインで利用されます。
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()
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()