シェーダプログラムでフレームバッファの複数カラーバッファを利用するサンプル (OpenGL、Python)
[最終更新] (2019/06/03 00:20:30)
最近の投稿
注目の記事

概要

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')

補足

フレームバッファを使用し終わったら glDeleteFramebuffersglDeleteRenderbuffers で削除します。

シェーダプログラムの準備

シェーダオブジェクトの作成 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_ATTACHMENT0GL_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')

コンパイルが成功したかどうかの確認 glGetShaderivglGetShaderInfoLog

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')

プログラムオブジェクトの変数設定およびリンク glBindAttribLocationglBindFragDataLocationglLinkProgram

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

この続きが気になる方は

シェーダプログラムでフレームバッファの複数カラーバッファを利用するサンプル (OpenGL、Python)

残り文字数は全体の約 13 %
tybot
100 円
関連ページ
    概要 立方体を二つ配置して回転させてみます。ライブラリを用いずに OpenGL API を直接利用します。描画部分のみを IPython で検証するためのソースコードはこちらです。 wget https://gist.githubusercontent.com/harubot/df886254396a449038ee542ed317f7b3/raw/92216e02d0210b9d8177056
    概要 コンピュータグラフィックスのレンダリングライブラリの一つ OpenGL はプラットフォームに依存しない仕様となっています。プラットフォームの一つに X11 があります。プラットフォームに依存する仕様は EGL (Embedded-System Graphics Library) にまとめられています。EGL は OpenGL とネイティブプラットフォームの間のインタフェースとして機能します