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

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

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

工作HardwareHub ロゴ画像 (Laptop端末利用時)
工作HardwareHub ロゴ画像 (Mobile端末利用時)
目次目次を開く/閉じる

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

モーダルを閉じる

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

モーダルを閉じる

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

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

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

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

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

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

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

目次

    アカウント プロフィール画像 (サイドバー)

    SPA構築が得意

    0
    ステッカーを贈るとは?

    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

    0
    詳細設定を開く/閉じる
    アカウント プロフィール画像 (本文下)

    SPA構築が得意

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

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

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

    Feedbacks

    Feedbacks コンセプト画像

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

      関連記事