モーダルを閉じる工作HardwareHub ロゴ画像

工作HardwareHubは、ロボット工作や電子工作に関する情報やモノが行き交うコミュニティサイトです。さらに詳しく

利用規約プライバシーポリシー に同意したうえでログインしてください。

目次目次を開く/閉じる

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

モーダルを閉じる

ステッカーを選択してください

お支払い手続きへ
モーダルを閉じる

お支払い内容をご確認ください

購入商品
」ステッカーの表示権
メッセージ
料金
(税込)
決済方法
GooglePayマーク
決済プラットフォーム
確認事項

利用規約をご確認のうえお支払いください

※カード情報はGoogleアカウント内に保存されます。本サイトやStripeには保存されません

※記事の執筆者は購入者のユーザー名を知ることができます

※購入後のキャンセルはできません

作成日作成日
2019/05/03
最終更新最終更新
2023/12/17
記事区分記事区分
一般公開

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

背景を白色で塗り潰します。

glClearColor

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

glClear

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

補足

頂点バッファを使用し終わったら glDeleteVertexArraysglDeleteBuffers で削除します。

フレームバッファのカラーバッファから読み出し

バインドされているフレームバッファがデフォルトのものではなく、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

Likeボタン(off)0
詳細設定を開く/閉じる
アカウント プロフィール画像

SPA構築が得意

記事の執筆者にステッカーを贈る

有益な情報に対するお礼として、またはコメント欄における質問への返答に対するお礼として、 記事の読者は、執筆者に有料のステッカーを贈ることができます。

>>さらに詳しくステッカーを贈る
ステッカーを贈る コンセプト画像

Feedbacks

Feedbacks コンセプト画像

    ログインするとコメントを投稿できます。

    ログインする

    関連記事