OpenGL のフレームバッファには複数のカラーバッファを割り当てることができます。フレームバッファはカラーバッファの他にデプスバッファとステンシルバッファを持ちます。これらバッファはすべてレンダーバッファとよばれるメモリ領域です。フラグメントシェーダで out
として得られる出力はカラーバッファに格納されます。一つのフラグメントシェーダに out
を複数設定すると複数のカラーバッファに結果を格納できます。ライブラリを用いずに直接 OpenGL API を利用するサンプルコードを記載します。描画部分のみを IPython で検証するためのソースコードはこちらです。
wget https://gist.githubusercontent.com/harubot/b4a4b17346b91fde8105fa11b9d3edb6/raw/d1656d5365cff617372719b1c61fc53d28493cb5/opengl-setup.py
DISPLAY=:0 python opengl-setup.py
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')
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')
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')
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')
GL_FRAMEBUFFER = 0x8D40
glBindFramebuffer = loadGl('glBindFramebuffer', None, GLenum, GLuint)
glBindFramebuffer(GL_FRAMEBUFFER, ids[0])
if not glGetError() == GL_NO_ERROR:
raise Exception('glBindFramebuffer failed')
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')
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')
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 で削除します。
頂点シェーダとピクセルシェーダを作成します。
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
でシェーダオブジェクトにソースコードを設定します。
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 = 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')
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())
例えば 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 = loadGl('glCreateProgram', GLuint)
program = glCreateProgram()
if not glGetError() == GL_NO_ERROR:
raise Exception('glCreateProgram failed')
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 = 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 = 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 = 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 から利用されます。
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 = loadGl('glBindVertexArray', None, GLuint)
glBindVertexArray(vaoList[0])
if not glGetError() == GL_NO_ERROR:
raise Exception('glBindVertexArray failed')
頂点バッファオブジェクトを一つ作成します。
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')
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')
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')
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 = loadGl('glEnableVertexAttribArray', None, GLuint)
glEnableVertexAttribArray(0)
if not glGetError() == GL_NO_ERROR:
raise Exception('glEnableVertexAttribArray failed')