OpenGL のフレームバッファには複数のカラーバッファを割り当てることができます。フレームバッファはカラーバッファの他にデプスバッファとステンシルバッファを持ちます。これらバッファはすべてレンダーバッファとよばれるメモリ領域です。フラグメントシェーダで out
として得られる出力はカラーバッファに格納されます。一つのフラグメントシェーダに out
を複数設定すると複数のカラーバッファに結果を格納できます。ライブラリを用いずに直接 OpenGL API を利用するサンプルコードを記載します。描画部分のみを IPython で検証するためのソースコードはこちらです。
wget https://gist.githubusercontent.com/harubot/b4a4b17346b91fde8105fa11b9d3edb6/raw/d1656d5365cff617372719b1c61fc53d28493cb5/opengl-setup.py
DISPLAY=:0 python opengl-setup.py
レンダーバッファの準備
作成 glGenRenderbuffers
GLuint = c_uint
GLsizei = c_int
glGenRenderbuffers = loadGl('glGenRenderbuffers', None, GLsizei, POINTER(GLuint))
n = 2
renderbuffers = zeros(n, dtype=GLuint)
glGenRenderbuffers(GLsizei(n), renderbuffers.ctypes.data_as(POINTER(GLuint)))
if not glGetError() == GL_NO_ERROR:
raise Exception('glGenRenderbuffers failed')
バインド glBindRenderbuffer
GL_RENDERBUFFER = 0x8D41
glBindRenderbuffer = loadGl('glBindRenderbuffer', None, GLenum, GLuint)
glBindRenderbuffer(GL_RENDERBUFFER, GLuint(renderbuffers[0]))
#glBindRenderbuffer(GL_RENDERBUFFER, GLuint(renderbuffers[1]))
if not glGetError() == GL_NO_ERROR:
raise Exception('glBindRenderbuffer failed')
メモリ領域を確保 glRenderbufferStorage
GL_RGB8 = 0x8051
GL_RGBA8 = 0x8058
GL_RGB32F = 0x8815
GL_RGBA32F = 0x8814
width,height = 100,100
glRenderbufferStorage = loadGl('glRenderbufferStorage', None, GLenum, GLenum, GLsizei, GLsizei)
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, GLsizei(width), GLsizei(height))
if not glGetError() == GL_NO_ERROR:
raise Exception('glRenderbufferStorage failed')
バインドを解除
二つのレンダーバッファについて上記設定を繰り返します。
glBindRenderbuffer(GL_RENDERBUFFER, GLuint(0))
if not glGetError() == GL_NO_ERROR:
raise Exception('glBindRenderbuffer failed')
フレームバッファの準備
作成 glGenFramebuffers
n = 1
ids = zeros(n, dtype=GLuint)
glGenFramebuffers = loadGl('glGenFramebuffers', None, GLsizei, POINTER(GLuint))
glGenFramebuffers(GLsizei(n), ids.ctypes.data_as(POINTER(GLuint)))
if not glGetError() == GL_NO_ERROR:
raise Exception('glGenFramebuffers failed')
バインド glBindFramebuffer
GL_FRAMEBUFFER = 0x8D40
glBindFramebuffer = loadGl('glBindFramebuffer', None, GLenum, GLuint)
glBindFramebuffer(GL_FRAMEBUFFER, ids[0])
if not glGetError() == GL_NO_ERROR:
raise Exception('glBindFramebuffer failed')
レンダーバッファをカラーバッファとしてアタッチ glFramebufferRenderbuffer
GL_COLOR_ATTACHMENT0 = 0x8CE0
GL_COLOR_ATTACHMENT1 = 0x8CE1
GL_COLOR_ATTACHMENT2 = 0x8CE2
GL_COLOR_ATTACHMENT3 = 0x8CE3
GL_COLOR_ATTACHMENT4 = 0x8CE4
glFramebufferRenderbuffer = loadGl('glFramebufferRenderbuffer', None, GLenum, GLenum, GLenum, GLuint)
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffers[0])
#glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_RENDERBUFFER, renderbuffers[1])
if not glGetError() == GL_NO_ERROR:
raise Exception('glFramebufferRenderbuffer failed')
描画するカラーバッファとしてアタッチしたレンダーバッファを指定 glDrawBuffers
from numpy import array
bufs = array([GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1], dtype=GLenum)
glDrawBuffers = loadGl('glDrawBuffers', None, GLsizei, POINTER(GLenum))
glDrawBuffers(GLsizei(2), bufs.ctypes.data_as(POINTER(GLenum)))
if not glGetError() == GL_NO_ERROR:
raise Exception('glDrawBuffers failed')
フレームバッファの状態を検証 glCheckFramebufferStatus
GL_FRAMEBUFFER_COMPLETE = 0x8CD5
glCheckFramebufferStatus = loadGl('glCheckFramebufferStatus', GLenum, GLenum)
status = glCheckFramebufferStatus(GL_FRAMEBUFFER)
if not glGetError() == GL_NO_ERROR:
raise Exception('glCheckFramebufferStatus failed')
if not status == GL_FRAMEBUFFER_COMPLETE:
raise Exception('framebuffer not completed')
補足
フレームバッファを使用し終わったら glDeleteFramebuffers と glDeleteRenderbuffers で削除します。
シェーダプログラムの準備
シェーダオブジェクトの作成 glCreateShader
頂点シェーダとピクセルシェーダを作成します。
GL_FRAGMENT_SHADER = 0x8B30
GL_VERTEX_SHADER = 0x8B31
glCreateShader = loadGl('glCreateShader', GLuint, GLenum)
vobj = glCreateShader(GL_VERTEX_SHADER)
if not glGetError() == GL_NO_ERROR:
raise Exception('glCreateShader failed')
fobj = glCreateShader(GL_FRAGMENT_SHADER)
if not glGetError() == GL_NO_ERROR:
raise Exception('glCreateShader failed')
シェーダオブジェクト内にソースコードを設定 glShaderSource
glShaderSource
でシェーダオブジェクトにソースコードを設定します。
POINTER(POINTER(GLchar))
ソースコードを各行に分けて、文字列 (char の配列) の配列として設定できます。vcount = 1
以下の例では各行に分けずに改行文字を含む一行を一つの配列として設定しています。POINTER(GLint)
第四引数を None にすると各ソースコード文字列は NULL 終端しているとして処理されます。各行の長さを配列として渡すことで NULL 終端していない場合にも対応できます。
GLSL バージョン 1.3 を利用しています。頂点シェーダの gl_Position
は組込み変数でレンダリングパイプラインの次のステージへの入力となります。フラグメントシェーダの out
は上から順にカラーバッファの GL_COLOR_ATTACHMENT0
と GL_COLOR_ATTACHMENT1
に対応します。シェーダプログラムは 3D モデルのレンダリング以外にも利用可能です。カメラから各頂点までの距離情報をカラーバッファに格納すれば距離画像を生成できます。
from ctypes import c_char
GLchar = c_char
GLint = c_int
glShaderSource = loadGl('glShaderSource', None, GLuint, GLsizei, POINTER(POINTER(GLchar)), POINTER(GLint))
vcount = 1
vstring = """#version 130
in vec4 position;
void main() {
gl_Position = position;
}
"""
glShaderSource(vobj, vcount,
byref(array(vstring, dtype=GLchar).ctypes.data_as(POINTER(GLchar))),
byref(GLint(len(vstring))))
if not glGetError() == GL_NO_ERROR:
raise Exception('glShaderSource failed')
fcount = 1
fstring = """#version 130
out vec4 fragment1;
out vec4 fragment2;
void main() {
fragment1 = vec4(1.0, 0.0, 0.0, 1.0);
fragment2 = vec4(0.0, 1.0, 1.0, 1.0);
}
"""
glShaderSource(fobj, fcount,
byref(array(fstring, dtype=GLchar).ctypes.data_as(POINTER(GLchar))),
byref(GLint(len(fstring))))
if not glGetError() == GL_NO_ERROR:
raise Exception('glShaderSource failed')
ソースコードのコンパイル glCompileShader
glCompileShader = loadGl('glCompileShader', None, GLuint)
glCompileShader(vobj)
if not glGetError() == GL_NO_ERROR:
raise Exception('glCompileShader failed')
glCompileShader(fobj)
if not glGetError() == GL_NO_ERROR:
raise Exception('glCompileShader failed')
コンパイルが成功したかどうかの確認 glGetShaderiv、glGetShaderInfoLog
GL_FALSE = 0
GL_TRUE = 1
GL_COMPILE_STATUS = 0x8B81
GL_INFO_LOG_LENGTH = 0x8B84
glGetShaderiv = loadGl('glGetShaderiv', None, GLuint, GLenum, POINTER(GLint))
glGetShaderInfoLog = loadGl('glGetShaderInfoLog', None, GLuint, GLsizei, POINTER(GLsizei), POINTER(GLchar))
コンパイルが成功したかどうかの確認
params = GLint(0)
glGetShaderiv(GLuint(vobj), GL_COMPILE_STATUS, byref(params))
#glGetShaderiv(GLuint(fobj), GL_COMPILE_STATUS, byref(params))
if not glGetError() == GL_NO_ERROR:
raise Exception('glGetShaderiv failed')
print(params.value == GL_TRUE)
print(params.value == GL_FALSE)
コンパイルが失敗した原因を確認
params = GLint(0)
glGetShaderiv(GLuint(vobj), GL_INFO_LOG_LENGTH, byref(params))
#glGetShaderiv(GLuint(fobj), GL_INFO_LOG_LENGTH, byref(params))
if not glGetError() == GL_NO_ERROR:
raise Exception('glGetShaderiv failed')
infoLog = zeros(params.value, dtype=uint8)
length = GLsizei(0)
glGetShaderInfoLog(vobj, params.value, byref(length), infoLog.ctypes.data_as(POINTER(GLchar)))
#glGetShaderInfoLog(fobj, params.value, byref(length), infoLog.ctypes.data_as(POINTER(GLchar)))
if not glGetError() == GL_NO_ERROR:
raise Exception('glGetShaderInfoLog failed')
print(infoLog.tostring())
Mesa および GLSL バージョン
例えば GLSL のバージョン指定に不備があった場合は以下のようなエラーが出ます。
In [17]: infoLog.tostring()
Out[17]: '0:1(10): error: GLSL 3.30 is not supported. Supported versions are: 1.10, 1.20, 1.30, 1.00 ES, and 3.00 ES\n\x00'
バージョン情報は glxinfo
でも調べられます。
sudo apt install mesa-utils
DISPLAY=:0 glxinfo | egrep '^([a-zA-Z]+)'
OpenGL version string: 3.0 Mesa 13.0.6
OpenGL shading language version string: 1.30
Mesa が利用する GPU ドライバがサポートしているバージョンであれば環境変数で指定できます。
MESA_GL_VERSION_OVERRIDE=2.1 MESA_GLSL_VERSION_OVERRIDE=120 DISPLAY=:0 glxinfo | egrep '^([a-zA-Z]+)'
OpenGL version string: 2.1 Mesa 13.0.6
OpenGL shading language version string: 1.20
Docker コンテナ内などハードウェアドライバが利用できない場合はソフトウェアドライバが利用されます。
DISPLAY=:0 glxinfo | egrep '^([a-zA-Z]+)'
OpenGL renderer string: Gallium 0.4 on llvmpipe (LLVM 3.9, 256 bits)
ソフトウェアドライバには llvmpipe の他にも swrast
などがあります。
$ dpkg -S /usr/lib/x86_64-linux-gnu/dri/swrast_dri.so
libgl1-mesa-dri:amd64: /usr/lib/x86_64-linux-gnu/dri/swrast_dri.so
プログラムオブジェクトの作成 glCreateProgram
glCreateProgram = loadGl('glCreateProgram', GLuint)
program = glCreateProgram()
if not glGetError() == GL_NO_ERROR:
raise Exception('glCreateProgram failed')
プログラムオブジェクトへのシェーダオブジェクトのアタッチ glAttachShader
glAttachShader = loadGl('glAttachShader', None, GLuint, GLuint)
glAttachShader(program, vobj)
if not glGetError() == GL_NO_ERROR:
raise Exception('glAttachShader failed')
glAttachShader(program, fobj)
if not glGetError() == GL_NO_ERROR:
raise Exception('glAttachShader failed')
シェーダオブジェクトの削除 glDeleteShader
glDeleteShader
でシェーダオブジェクトに削除フラグを付与できます。実際に削除されるのはアタッチされたプログラムオブジェクトが削除される等で、シェーダオブジェクトがシェーダプログラムからデタッチされたときです。
glDeleteShader = loadGl('glDeleteShader', None, GLuint)
glDeleteShader(vobj)
if not glGetError() == GL_NO_ERROR:
raise Exception('glDeleteShader failed')
glDeleteShader(fobj)
if not glGetError() == GL_NO_ERROR:
raise Exception('glDeleteShader failed')
プログラムオブジェクトの変数設定およびリンク glBindAttribLocation、glBindFragDataLocation、glLinkProgram
glBindAttribLocation = loadGl('glBindAttribLocation', None, GLuint, GLuint, POINTER(GLchar))
glBindFragDataLocation = loadGl('glBindFragDataLocation', None, GLuint, GLuint, POINTER(GLchar))
glLinkProgram = loadGl('glLinkProgram', None, GLuint)
glBindAttribLocation(program, 0, array('position', dtype=GLchar).ctypes.data_as(POINTER(GLchar)))
if not glGetError() == GL_NO_ERROR:
raise Exception('glBindAttribLocation failed')
glBindFragDataLocation(program, 0, array('fragment1', dtype=GLchar).ctypes.data_as(POINTER(GLchar)))
if not glGetError() == GL_NO_ERROR:
raise Exception('glBindFragDataLocation failed')
glBindFragDataLocation(program, 1, array('fragment2', dtype=GLchar).ctypes.data_as(POINTER(GLchar)))
if not glGetError() == GL_NO_ERROR:
raise Exception('glBindFragDataLocation failed')
glLinkProgram(program)
if not glGetError() == GL_NO_ERROR:
raise Exception('glLinkProgram failed')
プログラムをインストール glUseProgram
glUseProgram = loadGl('glUseProgram', None, GLuint)
glUseProgram(program)
if not glGetError() == GL_NO_ERROR:
raise Exception('glUseProgram failed')
四角形の描画
頂点シェーダへの入力となる頂点バッファオブジェクト (VBO; Vertex Buffer Object) を用意して、四角形の頂点情報を格納します。頂点バッファオブジェクトには頂点属性 (attribute) との対応を設定します。今回の例では頂点属性は in vec4 position
一つです。頂点属性は位置の他に色、法線ベクトル、テクスチャ座標 (UV座標) など複数存在し得るため、頂点バッファオブジェクトを配列にして頂点配列オブジェクト (VAO; Vertex Array Object) として管理します。VAO は CPU から GPU に転送されてから描画時に GPU から利用されます。
頂点配列オブジェクトの作成 glGenVertexArrays
vaoList = zeros(1, dtype=GLuint)
glGenVertexArrays = loadGl('glGenVertexArrays', None, GLsizei, POINTER(GLuint))
glGenVertexArrays(1, vaoList.ctypes.data_as(POINTER(GLuint)))
if not glGetError() == GL_NO_ERROR:
raise Exception('glGenVertexArrays failed')
頂点配列オブジェクトのバインド glBindVertexArray
glBindVertexArray = loadGl('glBindVertexArray', None, GLuint)
glBindVertexArray(vaoList[0])
if not glGetError() == GL_NO_ERROR:
raise Exception('glBindVertexArray failed')
頂点バッファオブジェクトの作成 glGenBuffers
頂点バッファオブジェクトを一つ作成します。
glGenBuffers = loadGl('glGenBuffers', None, GLsizei, POINTER(GLuint))
vboList = zeros(1, dtype=GLuint)
glGenBuffers(1, vboList.ctypes.data_as(POINTER(GLuint)))
if not glGetError() == GL_NO_ERROR:
raise Exception('glGenBuffers failed')
頂点バッファオブジェクトのバインド glBindBuffer
GL_ARRAY_BUFFER = 0x8892
glBindBuffer = loadGl('glBindBuffer', None, GLenum, GLuint)
glBindBuffer(GL_ARRAY_BUFFER, vboList[0])
if not glGetError() == GL_NO_ERROR:
raise Exception('glBindBuffer failed')
頂点バッファオブジェクトにデータを設定 glBufferData
GLsizeiptr = c_uint
GLvoid_p = c_void_p
GL_STATIC_DRAW = 0x88E4
from numpy import float32
data = array([
[-0.5, -0.5, 0.0, 1.0],
[ 0.5, -0.5, 0.0, 1.0],
[ 0.5, 0.5, 0.0, 1.0],
[-0.5, 0.5, 0.0, 1.0]
], dtype=float32)
glBufferData = loadGl('glBufferData', None, GLenum, GLsizeiptr, GLvoid_p, GLenum)
glBufferData(GL_ARRAY_BUFFER,
GLsizeiptr(data.size * data.dtype.itemsize),
data.ctypes.data_as(GLvoid_p),
GL_STATIC_DRAW)
if not glGetError() == GL_NO_ERROR:
raise Exception('glBufferData failed')
頂点バッファオブジェクトと頂点属性の対応関係を設定 glVertexAttribPointer
GLboolean = c_uint
GL_FLOAT = 0x1406
glVertexAttribPointer = loadGl('glVertexAttribPointer', None, GLuint, GLint, GLenum, GLboolean, GLsizei, GLvoid_p)
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0)
if not glGetError() == GL_NO_ERROR:
raise Exception('glVertexAttribPointer failed')
glVertexAttribPointer
の引数について補足
0
position 頂点属性を指定しています。4
glBufferData
で設定した座標データは4次元です。GL_FLOAT
座標データの型です。0
glBufferData
で格納したデータに複数の頂点属性用のデータが入っている場合は変更します。0
glBufferData
で格納したデータに複数の頂点属性用のデータが入っている場合は変更します。
頂点属性を有効化 glEnableVertexAttribArray
glEnableVertexAttribArray = loadGl('glEnableVertexAttribArray', None, GLuint)
glEnableVertexAttribArray(0)
if not glGetError() == GL_NO_ERROR:
raise Exception('glEnableVertexAttribArray failed')
描画 glDrawArrays
背景を白色で塗り潰します。
GLfloat = c_float
glClearColor = loadGl('glClearColor', None, GLfloat, GLfloat, GLfloat, GLfloat)
glClearColor(GLfloat(1.0), GLfloat(1.0), GLfloat(1.0), GLfloat(1.0))
if not glGetError() == GL_NO_ERROR:
raise Exception('glClearColor failed')
GL_COLOR_BUFFER_BIT = 0x00004000
GL_DEPTH_BUFFER_BIT = 0x00000100
GLbitfield = c_uint
glClear = loadGl('glClear', None, GLbitfield)
glClear(GLbitfield(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT))
if not glGetError() == GL_NO_ERROR:
raise Exception('glClear failed')
有効化した頂点配列オブジェクト内の各頂点バッファにおいて、使用する要素は 0 から 4 つです。GL_LINE_LOOP
モードを利用すると4つの座標が順々につなげられて四角形を描けます。
GL_LINE_LOOP = 0x0002
GL_TRIANGLES = 0x0004
glDrawArrays = loadGl('glDrawArrays', None, GLenum, GLint, GLsizei)
glDrawArrays(GL_LINE_LOOP, 0, 4)
if not glGetError() == GL_NO_ERROR:
raise Exception('glDrawArrays failed')
補足
頂点バッファを使用し終わったら glDeleteVertexArrays と glDeleteBuffers で削除します。
フレームバッファのカラーバッファから読み出し
バインドされているフレームバッファがデフォルトのものではなく、GL_COLOR_ATTACHMENTi
を指定して読み出します。
glReadBuffer = loadGl('glReadBuffer', None, GLenum)
glReadBuffer(GL_COLOR_ATTACHMENT0)
#glReadBuffer(GL_COLOR_ATTACHMENT1)
if not glGetError() == GL_NO_ERROR:
raise Exception('glReadBuffer failed')
GLint = c_int
GLsizei = c_int
GLvoid_p = c_void_p
GL_RGB = 0x1907
GL_UNSIGNED_BYTE = 0x1401
glReadPixels = loadGl('glReadPixels', None, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, GLvoid_p)
x,y = 0,0
width,height = 100,100
format = GL_RGB
type = GL_UNSIGNED_BYTE
image = zeros((height, width, 3), dtype=uint8)
glReadPixels(GLint(x), GLint(y), GLsizei(width), GLsizei(height), format, type, image.ctypes.data_as(GLvoid_p))
if not glGetError() == GL_NO_ERROR:
raise Exception('glReadPixels failed')
表示して確認します。
import matplotlib.pyplot as plt
plt.imshow(image)
plt.show()
GL_COLOR_ATTACHMENT0
GL_COLOR_ATTACHMENT1
記事の執筆者にステッカーを贈る
有益な情報に対するお礼として、またはコメント欄における質問への返答に対するお礼として、 記事の読者は、執筆者に有料のステッカーを贈ることができます。
さらに詳しく →Feedbacks
ログインするとコメントを投稿できます。
関連記事
- 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 インストール 適当なパッケージ