Mugichoko's blog

Mugichoko’s blog

プログラミングを中心としたメモ書き.

ドロネー三角形分割:三角形に所属する各点のIDを取得

目標

前回ランダムに生成した2次元点を入力として,ドロネー三角形分割を行い,結果を描画した. 今回は,そこで用いたFade2Dを利用して,各三角形にどの2次元点が使われたかを特定する方法を実装する.

mugichoko.hatenablog.com

実装環境

プログラム

決め手は,「Point2::setCustomIndex()」によって各点にあらかじめIDを割り振っておき,ドロネー三角形分割の後に「Point2::getCustomIndex()」で,各三角形のIDを取得するということ.

#include <iostream>
using namespace std;
#include <random>
#include <Fade_2D.h>
namespace fade = GEOM_FADE2D;

// Constants
const int PTS_NUM(100);
const int PTS_RANGE(600);
const string WND_NAME("Delaunay Fade 2D");


int main()
{
    // ----- Generate 2D points
    // Mersenne twister (random value generator)
    mt19937 mt;
    uniform_int_distribution<int> distribution(0, ::PTS_RANGE - 1);
    
    vector<fade::Point2> pts;
    for (int i = 0; i < ::PTS_NUM; ++i)
    {
        int x = distribution(mt);
        int y = distribution(mt);
        fade::Point2 p(x, y);
        p.setCustomIndex(i);
        pts.push_back(p);
    }

    // ----- Initialize Fade 2D
    fade::Fade_2D dt((unsigned int)pts.size());
    vector<fade::Point2 *> vDtVertPtr;
    dt.insert(pts, vDtVertPtr);

    // ----- Draw Delaunay triangles
    cv::Mat img = cv::Mat::zeros(::PTS_RANGE, ::PTS_RANGE, CV_8UC3);

    vector<fade::Triangle2 *> vDtTri;
    dt.getTrianglePointers(vDtTri);
    for (auto tri = vDtTri.begin(); tri != vDtTri.end(); ++tri)
    {
        fade::Point2 *pt[3] = { (*tri)->getCorner(0), (*tri)->getCorner(1), (*tri)->getCorner(2) };
        cout << "Point ID: " << pt[0]->getCustomIndex() << ", " << pt[1]->getCustomIndex() << ", " << pt[2]->getCustomIndex() << ", " << endl;
    }

    return 0;
}

実行結果

コマンドプロンプトに各三角形に含まれる点のIDが表示された.

f:id:Mugichoko:20170606120102j:plain

Python 3.6 + OpenCV 3.2 on Windows 10 64-bit

目標

PythonOpenCVを使ったプログラミングを,Visual Studioでできるようにする. 今回は,Visual StudioPythonはインストール済みとして話を進めます. 代わりに,私が経験した,PythonOpenCVのインストール時のトラブルについて紹介します.

実装環境

遭遇した問題

PythonOpenCVのインストールの仕方を参考にしてインストールしてみた. 例えば,以下のリンクが参考になる.

prpr.hatenablog.jp

この際,正しくインストールできたかどうかを確かめるために,以下のコマンドをコマンドプロンプトから実行しろ,という記事によく出くわす.

C:\Users\XXXX> python
>>> import cv2
>>> print(cv.2__version__)

しかし,この2行目のコードを実行すると…

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: DLL load failed: The specified module could not be found.

というエラーメッセージが表示される. どうやらDLLが見つからないようだ. この問題に関して,以下に列挙する,ネット上の種々の解決方法を試してみたが,全く効果がなかった.

解決策

解決策1:conda-forge

以下をコマンドプロンプトにコピペしてエンターキーで実行. conda-forgeを使ったPythonOpenCVのインストール用コマンドだ. しばらくしてインストールが終わる.

※この方法だと,ContribなしのOpenCVがインストールされると思われる.

conda install -c conda-forge opencv=3.2.0

参考

www.codesofinterest.com

解決策2:pipと.whil

ここにある,自分の環境にあった.whlファイルをダウンロードし,そのファイルを置いたパスで,コマンドプロンプトから以下を実行する.

※この方法だと,ContribありのOpenCVがインストールされる.

pip install opencv_python-3.1.0+contrib_opencl-cp35-cp35m-win_amd64.whl

私の場合,実装環境にある通りなので「opencv_python-3.2.0+contrib-cp36-cp36m-win_amd64.whl」を選択した.

参考

qiita.com

解決策3:Python3.5へのダウングレード(未確認)

下記のリンクにある回答によると,Python 3.5にダウングレードすれば,以下のコマンドでインストールできるようだ.

conda install -c https://conda.binstar.org/menpo opencv3

stackoverflow.com

テストコード

import numpy as np
import cv2

img = cv2.imread('me.jpg')
cv2.imshow('me', img)
cv2.waitKey()

実行結果

f:id:Mugichoko:20170531023511j:plain

GLSL #9: Zバッファの線形化

目標

前回(下記参照)までの実装を少し変更して,Compute Shaderを使って,3DCGを描画した際に生成されるZバッファの線形化を行うプログラムを作成する.

mugichoko.hatenablog.com

Zバッファの線形化とは?

Zバッファは,実際の奥行値から非線形変換が行われて0~1の大きさに正規化されており,実際の空間内の距離とは異なる.そこで,以下の式に基づいてZバッファを線形化(本来の奥行値)に戻す. 尚, Z'は出力, ZはZバッファの値, nは3DCGを描画する際に用いたNear平面までの距離, fはFar平面まで距離. 詳細は下記を参照のこと.

 Z' = (2.0 * n * f) / (f + n - (Z * 2.0 - 1.0) * (f - n))

learnopengl.com

実装環境

レンダリングの流れ

  1. パス1:四角形ポリゴンに向いたカメラが奥行方向に0.5~5.5の範囲で移動する
  2. パス2:Compute Shaderに1のZバッファを渡し,上記の式を適用する
  3. パス3:パス1の結果はFBOに書き込まれているので,バックバッファにコピーする
  4. パス4:Compute Shaderで書き込んだテクスチャのデータをメインメモリに読み込んで,その中心の画素の値をコマンドプロンプトに表示する

※つまり,コマンドプロンプトには0.5~5.5の値が表示されるはずである!

サンプルプログラム

前回までのプログラムをそのまま利用し,以下の様に書き換えます.ただし,ここにあるバグを修正した.

main.cpp

//
// main.cpp
//
#include <glm/glm.hpp>
#include <glm/ext.hpp>
#include "OpenGLWrapper/window.h"
#include "OpenGLWrapper/shaderUtil.h"
#include "OpenGLWrapper/modelTex.h"
#include "OpenGLWrapper/fbo.h"

class imCompShader : public gl::compShader
{
public:
    imCompShader(
        int w,
        int h,
        const string &compShaderName,
        GLint internalFormat = GL_RGBA8,
        GLenum format = GL_RGBA,
        GLenum type = GL_UNSIGNED_BYTE
    ) : gl::compShader(w, h, 1, internalFormat, format, type, compShaderName)
    {
    }

    void imCompShader::execute(
        GLuint inTexID,
        float near,
        float far
    )
    {
        glUseProgram(prog);
        {
            // Uniforms
            glUniform1f(glGetUniformLocation(prog, "zNear"), near);
            glUniform1f(glGetUniformLocation(prog, "zFar"), far);

            // Input texture
            glBindImageTexture(0, inTexID, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32F);
            // Output texture
            glBindImageTexture(1, texID, 0, GL_FALSE, 0, GL_WRITE_ONLY, internalFormat);

            glDispatchCompute(width / 32, height / 32, depth);
        }
        glUseProgram(0);
    }
};

class MyWindow : gl::window
{
private:
    gl::modelTex *myTex;
    gl::fbo *myFBO;
    imCompShader *myZBuffComp;

    const float near;
    const float far;

    void path1()
    {
        glEnable(GL_DEPTH_TEST);

        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, myFBO->getFBOID());
        {
            // Clear color and depth buffers
            glClearColor(0.3f, 0.5f, 0.8f, 0.0f);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

            // Draw
            glm::vec3 eye(0.0f, 0.0f, 0.5f + 5.0f * abs(sin(glfwGetTime())));
            glm::vec3 center(0.0f);
            glm::vec3 up(0.0f, 1.0f, 0.0f);
            glm::mat4 V = glm::lookAt(eye, center, up);
            glm::mat4 P = glm::perspective(3.14f / 4.0f, (float)width / height, near, far);
            myTex->setViewMat(V);
            myTex->setProjMat(P);
            myTex->render();
        }
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    }
    void path2()
    {
        myZBuffComp->execute(myFBO->getDepthTexID(), near, far);
    }
    void path3()
    {
        // Framebuffer to back buffer copy
        glBindFramebuffer(GL_READ_FRAMEBUFFER, myFBO->getFBOID());
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
        glBlitFramebuffer(
            0, 0, width, height, 0, 0, width, height,
            GL_COLOR_BUFFER_BIT, GL_NEAREST
        );
        glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
    }
    void path4()
    {
        float *pixels = new float[width * height];

        glBindTexture(GL_TEXTURE_2D, myZBuffComp->getTexID());
        glGetTexImage(GL_TEXTURE_2D, 0, myZBuffComp->getFormat(), myZBuffComp->getType(), pixels);

        cout << pixels[width * (height + 1) / 2] << endl;
        delete[] pixels;
    }

public:
    MyWindow(
        int w,
        int h,
        float near,
        float far,
        const string &name,
        const string &texFileName,
        const string &vertShaderName,
        const string &fragShaderName,
        const string &compShaderName
    ) : window(w, h, name), near(near), far(far)
    {
        myTex = new gl::modelTex(texFileName, vertShaderName, fragShaderName);
        myFBO = new gl::fbo(w, h, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE);
        myZBuffComp = new imCompShader(w, h, compShaderName, GL_R32F, GL_RED, GL_FLOAT);
    }
    ~MyWindow()
    {
        if (myTex) delete myTex;
        if (myFBO) delete myFBO;
        if (myZBuffComp) delete myZBuffComp;
    }

    void render()
    {
        // Rendering loop
        while (!glfwWindowShouldClose(wnd))
        {
            path1();
            path2();
            path3();
            path4();
            
            // Swap front and back buffers
            glfwSwapBuffers(wnd);
            // Poll for and process events
            glfwPollEvents();
        }
    }
};

int main()
{
    MyWindow wnd(
        512, 512, 0.1f, 100.0f,
        "GLFW 3 Window",
        "../data/me.jpg",
        "../shader/modelTex.vert",
        "../shader/modelTex.frag",
        "../shader/zbuff.comp"
    );
    wnd.render();

    return 0;
}

zbuff.comp

//
// zbuff.comp
//
#version 430

layout (binding = 0, r32f) readonly uniform image2D dataIn;
layout (binding = 1, r32f) writeonly uniform image2D dataOut;

layout (local_size_x = 32, local_size_y = 32) in;

uniform float zNear;
uniform float zFar;

void main(void)
{
    ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
    vec4 val = imageLoad(dataIn, pos);

    float z = (2.0 * zNear * zFar) / (zFar + zNear - (val.r * 2.0 - 1.0) * (zFar - zNear));

    imageStore(
        dataOut,
        pos,
        vec4(z, 0.0, 0.0, 0.0)
        );
}

結果

カメラが0.5の位置に近づいた時. f:id:Mugichoko:20170524172755j:plain

カメラが5.5の位置に近づいた時. f:id:Mugichoko:20170524172750j:plain

尚,0.5,5.5といった固定値を設定した場合にも,それぞれ0.5,5.5とコマンドプロンプトに表示された. ちなみに,線形化をしない場合,つまり「zbuff.comp」内で「float z = val.r;」とする場合,0.9辺りを前後した.

GLSLのCompute ShaderでZバッファを読み込む際のトラブルと解決方法

問題

GLSLのCompute ShaderでZバッファを読み込みたかったのだが,下記のリンクと同じ問題に遭遇した.

stackoverflow.com

簡単には,Zバッファを可視化したところ,ワイヤーフレームが強調された用な結果が出力されてしまう問題だ. 結果的に,私の場合は単なる記述ミス(バグ)だった.

問題に遭遇した手順

  1. カラーバッファとZバッファをアタッチしたFBOに3DCGを描画
  2. 1のZバッファ (GL_DEPTH_COMPONENT) をCompute Shaderに読み込み何らかの処理を実行し,テクスチャとして出力
    • 今回は,非線形に保存されているZバッファ値を線形に直す処理を行った(正に,上記のリンクにある内容と同じ!)
  3. 2のテクスチャをバックバッファに描画
  4. 3DCGのポリゴンの面にワイヤーフレームが加わったようなZバッファが描画される
    • 本来はワイヤフレームがない結果がほしい f:id:Mugichoko:20170517171951j:plain

実装環境

解決方法

FBOでZバッファを用意する時,internalFormatを「GL_DEPTH_COMPONENT」ではなく「GL_DEPTH_COMPONENT32F」とする.

glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, w, h, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0);

↓ 修正後

glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, w, h, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0);

修正結果が以下の通り.ワイヤーフレームのような画素が消えた.

f:id:Mugichoko:20170517172221j:plain

所感

よく分からないのは,最初に挙げたリンクでは既に今回の修正に相当する記述になっているのに同じ問題が起きることだ.

GLSL #8: Compute Shaderを使った画像処理

目標

前回(下記参照)までの実装を組み合わせて用いることで,3DCGを描画した結果に対してCompute Shaderを使って画像処理(Sobelフィルタによるエッジ検出)を行うプログラムを作成する.

mugichoko.hatenablog.com

実装環境

レンダリングの流れ

  1. パス1:第6回のパス1と同じ
  2. パス2:Compute Shaderに1の描画結果を渡し,Sobelフィルタをかける

サンプルプログラム

シェーダプログラムに関しては,ほとんどがこちらの丸コピです. 前回までに作成した以下のプログラムは変更せずそのまま利用します.

  • window.h
  • window.cpp
  • model.h
  • model.cpp
  • modelTex.h
  • modelTex.cpp
  • shaderUtil.h
    • execute関数を仮想関数としていたが,処理によって入出力が変わるため,削除した
  • shaderUtil.cpp

main.cpp

//
// main.cpp
//
#include <glm/glm.hpp>
#include <glm/ext.hpp>
#include "OpenGLWrapper/window.h"
#include "OpenGLWrapper/shaderUtil.h"
#include "OpenGLWrapper/modelTex.h"
#include "OpenGLWrapper/fbo.h"

class imCompShader : public gl::compShader
{
public:
    imCompShader(
        int w,
        int h,
        const string &compShaderName,
        GLint internalFormat = GL_RGBA8,
        GLenum format = GL_RGBA,
        GLenum type = GL_UNSIGNED_BYTE
    ) : gl::compShader(w, h, 1, internalFormat, format, type, compShaderName)
    {
    }

    void imCompShader::execute(
        GLuint inTexID
    )
    {
        glUseProgram(prog);
        {
            // Input texture
            glBindImageTexture(0, inTexID, 0, GL_FALSE, 0, GL_READ_ONLY, internalFormat);
            // Output texture
            glBindImageTexture(1, texID, 0, GL_FALSE, 0, GL_WRITE_ONLY, internalFormat);

            glDispatchCompute(width / 32, height / 32, depth);
        }
        glUseProgram(0);
    }
};

class MyWindow : gl::window
{
private:
    gl::modelTex *myTex;
    gl::fbo *myFBO;
    imCompShader *mySobelComp;


    void path1()
    {
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, myFBO->getFBOID());
        {
            // Clear color and depth buffers
            glClearColor(0.3f, 0.5f, 0.8f, 0.0f);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

            // Draw
            glm::vec3 eye(cos(glfwGetTime()), sin(glfwGetTime()), 1.5f);
            glm::vec3 center(0.0f);
            glm::vec3 up(0.0f, 1.0f, 0.0f);
            glm::mat4 V = glm::lookAt(eye, center, up);
            glm::mat4 P = glm::perspective(3.14f / 4.0f, (float)width / height, 0.1f, 100.0f);
            myTex->setViewMat(V);
            myTex->setProjMat(P);
            myTex->render();
        }
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    }
    void path2()
    {
        mySobelComp->execute(myFBO->getColorTexID());
        myTex->setViewMat(glm::mat4());
        myTex->setProjMat(glm::mat4());
        myTex->render(mySobelComp->getTexID());
    }

public:
    MyWindow(
        int w,
        int h,
        const string &name,
        const string &texFileName,
        const string &vertShaderName,
        const string &fragShaderName,
        const string &compShaderName
    ) : window(w, h, name)
    {
        myTex = new gl::modelTex(texFileName, vertShaderName, fragShaderName);
        myFBO = new gl::fbo(w, h, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE);
        mySobelComp = new imCompShader(w, h, compShaderName);
    }
    ~MyWindow()
    {
        if (myTex) delete myTex;
        if (myFBO) delete myFBO;
        if (mySobelComp) delete mySobelComp;
    }

    void render()
    {
        // Rendering loop
        while (!glfwWindowShouldClose(wnd))
        {
            path1();
            path2();
            
            // Swap front and back buffers
            glfwSwapBuffers(wnd);
            // Poll for and process events
            glfwPollEvents();
        }
    }
};

int main()
{
    MyWindow wnd(
        512, 512, "GLFW 3 Window",
        "../data/me.jpg",
        "../shader/modelTex.vert",
        "../shader/modelTex.frag",
        "../shader/sobel.comp"
    );
    wnd.render();

    return 0;
}

sample.comp

//
// sobel.comp
//
#version 430

layout (binding = 0, rgba8) readonly uniform image2D dataIn;
layout (binding = 1) writeonly uniform image2D dataOut;

layout (local_size_x = 32, local_size_y = 32) in;

void main(void)
{
    ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
    vec3 px00 = imageLoad(dataIn, pos + ivec2(-1, 1)).rgb;
    vec3 px01 = imageLoad(dataIn, pos + ivec2( 0, 1)).rgb;
    vec3 px02 = imageLoad(dataIn, pos + ivec2( 1, 1)).rgb;
    vec3 px10 = imageLoad(dataIn, pos + ivec2(-1, 0)).rgb;
    vec3 px12 = imageLoad(dataIn, pos + ivec2( 1, 0)).rgb;
    vec3 px20 = imageLoad(dataIn, pos + ivec2(-1,-1)).rgb;
    vec3 px21 = imageLoad(dataIn, pos + ivec2( 0,-1)).rgb;
    vec3 px22 = imageLoad(dataIn, pos + ivec2( 1,-1)).rgb;

    // Sobel
    vec3 hori = -px00 - 2.0 * px10 -px20 + px02 + 2.0 * px12 + px22;
    vec3 vert = -px00 - 2.0 * px01 -px02 + px20 + 2.0 * px21 + px22;

    imageStore(
        dataOut,
        pos,
        vec4(sqrt(hori * hori + vert * vert), 0.0)
        );
}

結果

第6回で得た出力結果に対してエッジ検出をかけた結果が出力されました!目標達成です.

youtu.be

GLSL #7: Compute Shader

目標

こちらの内容を参考に,とりあえずCompute Shaderが動作することを確認する. 尚,今回実装する内容は,前回(下記参照)までの実装に基づいております.

mugichoko.hatenablog.com

実装環境

レンダリングの流れ

  1. Compute Shaderを実行し,テクスチャに結果を書き込む
  2. 1の結果をmodelTexクラスを用いて画面に描画

サンプルプログラム

シェーダプログラムに関しては,ほとんどがこちらの丸コピです. 前回までに作成した以下のプログラムは変更せずそのまま利用します.

  • window.h
  • window.cpp
  • model.h
  • model.cpp
  • modelTex.h
  • modelTex.cpp

shaderUtil.h

前回から,以下を追加. 追加内容は,Compute Shaderをコンパイルするためのインタフェース「initializeProgram」とCompute Shader実装時のインタフェースとなる「compShader」クラス.

//
// shaderUtil.h
//

//
// 前回までの記述
//

namespace gl
{
    // Interface for compute shader
    void initializeProgram(
        GLuint &theProgram,
        const string &compShaderFileName
    );
    
    // ----- Compute shader handlar
    class compShader
    {
    protected:
        GLuint prog;
        GLuint texID;

        const GLint internalFormat;
        const GLsizei width;
        const GLsizei height;
        const GLuint depth;
        const GLenum format;
        const GLenum type;
    public:
        compShader(
            GLsizei width,
            GLsizei height,
            GLuint dpeth,
            GLint internalFormat,
            GLenum format,
            GLenum type,
            const string compShaderName
        );
        ~compShader();

        GLuint getTexID();
        GLint getInternalFormat();
        GLsizei getWidth();
        GLsizei getHeight();
        GLenum getFormat();
        GLenum getType();
        GLuint getDepth();

        virtual void execute() = 0;
    };
}

shaderUtil.cpp

ヘッダファイルに合わせて,前回から,以下を追加.

//
// shaderUtil.cpp
//
#include "shaderUtil.h"

//
// 前回までの記述
//

namespace gl
{
    void initializeProgram(
        GLuint &theProgram,
        const string &compShaderFileName
    )
    {
        try
        {
            cout << "[Shader Utility] Making a shader program..." << endl;

            vector<GLuint> shaderList;

            // ----- Load shaders
            stringstream ssComputeShader;
            readFile(compShaderFileName, ssComputeShader);

            // ----- Compile shaders
            shaderList.push_back(createShader(GL_COMPUTE_SHADER, ssComputeShader.str()));

            // ----- Link the shaders to the program
            theProgram = createProgram(shaderList);

            // ----- Delete
            std::for_each(shaderList.begin(), shaderList.end(), glDeleteShader);

            cout << "[Shader Utility] ... done!" << endl;
        }
        catch (string e)
        {
            cerr << "Error: " << e << endl;
            exit(EXIT_FAILURE);
        }
    }
    
    // ----- Compute shader handlar
    compShader::compShader(
        GLsizei width,
        GLsizei height,
        GLuint dpeth,
        GLint internalFormat,
        GLenum format,
        GLenum type,
        const string compShaderName
    ) : internalFormat(internalFormat), width(width), height(height), depth(dpeth), format(format), type(type)
    {
        // ----- Generate data texture
        glGenTextures(1, &texID);
        glBindTexture(GL_TEXTURE_2D, texID);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, NULL);

        // ----- Generate a compute shader program
        initializeProgram(prog, compShaderName);
    }
    compShader::~compShader()
    {
        glDeleteProgram(prog);
    }

    GLuint compShader::getTexID()
    {
        return texID;
    }
    GLint compShader::getInternalFormat()
    {
        return internalFormat;
    }
    GLsizei compShader::getWidth()
    {
        return width;
    }
    GLsizei compShader::getHeight()
    {
        return height;
    }
    GLenum compShader::getFormat()
    {
        return format;
    }
    GLenum compShader::getType()
    {
        return type;
    }
    GLuint compShader::getDepth()
    {
        return depth;
    }
}

main.cpp

//
// main.cpp
//
#include <glm/glm.hpp>
#include <glm/ext.hpp>
#include "OpenGLWrapper/window.h"
#include "OpenGLWrapper/shaderUtil.h"
#include "OpenGLWrapper/modelTex.h"

class MyCompShader : public gl::compShader
{
public:
    MyCompShader(
        int w,
        int h,
        const string &compShaderName
    ) : gl::compShader(w, h, 1, GL_RGBA32F, GL_RGBA, GL_FLOAT, compShaderName)
    {
    }

    void MyCompShader::execute()
    {
        static float frame(0.0f);

        glUseProgram(prog);
        {
            glBindImageTexture(0, texID, 0, GL_FALSE, 0, GL_WRITE_ONLY, internalFormat);

            glUniform1f(glGetUniformLocation(prog, "roll"), (float)frame * 0.01f);
            glDispatchCompute(width / 32, height / 32, depth);
        }
        glUseProgram(0);

        frame += 0.5f;
        if (frame >= FLT_MAX - 1.0f)
        {
            frame = 0.0f;
        }
    }
};

class MyWindow : gl::window
{
private:
    gl::modelTex *myTex;
    MyCompShader *myComp;

public:
    MyWindow(
        int w,
        int h,
        const string &name,
        const string &texFileName,
        const string &vertShaderName,
        const string &fragShaderName,
        const string &compShaderName
    ): window(w, h, name)
    {
        myTex = new gl::modelTex(w, h, vertShaderName, fragShaderName);
        myComp = new MyCompShader(w, h, compShaderName);
    }
    ~MyWindow()
    {
        if (myTex) delete myTex;
        if (myComp) delete myComp;
    }

    void render()
    {
        // Rendering loop
        while (!glfwWindowShouldClose(wnd))
        {
            myComp->execute();
            glClearColor(0.3f, 0.5f, 0.8f, 0.0f);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            myTex->render(myComp->getTexID());

            // Swap front and back buffers
            glfwSwapBuffers(wnd);
            // Poll for and process events
            glfwPollEvents();
        }
    }
};

int main()
{
    MyWindow wnd(
        512, 512, "GLFW 3 Window",
        "../data/me.jpg",
        "../shader/modelTex.vert",
        "../shader/modelTex.frag",
        "../shader/sample.comp"
    );
    wnd.render();

    return 0;
}

sample.comp

//
// sample.comp
//
#version 430

uniform float roll;
layout (binding = 0) writeonly uniform image2D data;

layout (local_size_x = 32, local_size_y = 32) in;

void main(void)
{
    ivec2 storePos = ivec2(gl_GlobalInvocationID.xy);
    float localCoef = length(vec2(256, 256) - vec2(ivec2(gl_GlobalInvocationID.xy)));
    float val = 0.5 + 0.5 * sin(localCoef * 0.05 + roll);
    imageStore(
        data,
        storePos,
        vec4(val, val, val, 0)
        );
}

結果

動作しました!バッチリです.

youtu.be

GLSL #6: Frame Buffer Object (FBO)

目標

前回(下記参照)は,テクスチャマッピングを行うプログラムを作成しました. 今回は,そのプログラムをベースに,FBOに描画した結果を四角形ポリゴンにテクスチャとして利用する方法を実装します.

mugichoko.hatenablog.com

実装環境

レンダリングの流れと結果

今回は分かりやすさのために,レンダリングの流れと結果を先に示す.

  1. パス1:FBO(テクスチャ)にテクスチャ付き3Dポリゴンを描画
    • この際,カメラを3Dポリゴン (modelTex) の中心を注視して,円を描くように動かす
    • つまり,以下の動画を毎フレームFBOに書き出す youtu.be
  2. パス2:FBOが保持するテクスチャのIDをmodelTexクラスに渡し,今度は,バックバッファに描画
    • この際,カメラは3Dポリゴン (modelTex) から少し離れた位置に置く youtu.be

サンプルプログラム

前回までに作成した以下のプログラムは変更せずそのまま利用します.

  • window.h
  • window.cpp
  • shaderUtil.h
  • shaderUtil.cpp
  • model.h
  • model.cpp
  • modelTex.vert
  • modelTex.frag

modelTex.h

前回から少し変更. 具体的には,画像ををファイルから読み込まずとも,別処理で作成されたテクスチャを受け取って描画できるようにした.

//
// modelTex.h
//
#pragma once
#include "model.h"
#include <IL/il.h>

namespace gl
{
    class modelTex : public model
    {
    private:
        static const GLfloat vertData[40];
        static const GLshort idxData[6];
        static const int numVert;
        static const int offsetTexUVData;

        GLuint texID;

        GLuint MVPMatLoc;
        GLuint vao; // Vertex array object
        GLuint vbo; // Vertex buffer object
        GLuint ibo; // Index buffer object

        void init(
            int w,
            int h,
            const string &vertShaderName,
            const string &fragShaderName,
            GLint internalFormat,
            GLenum format,
            GLenum type
        );

    public:
        modelTex(
            const string &texFileName,
            const string &vertShaderName,
            const string &fragShaderName,
            GLint internalFormat = GL_RGBA,
            GLenum format = GL_RGBA,
            GLenum type = GL_UNSIGNED_BYTE
        );
        modelTex(
            int w,
            int h,
            const string &vertShaderName,
            const string &fragShaderName,
            GLint internalFormat = GL_RGBA,
            GLenum format = GL_RGBA,
            GLenum type = GL_UNSIGNED_BYTE
        );
        ~modelTex();

        void render();
        void render(
            GLint texID
        );
    };
}

modelTex.cpp

ヘッダファイルに合わせて,前回から少し変更.

//
// modelTex.cpp
//
#include "modelTex.h"

namespace gl
{
    const GLfloat modelTex::vertData[40] =
    {
        // Vertex position
        +1.0f, +1.0f, 0.0f,      // #0: Upper right
        -1.0f, +1.0f, 0.0f,      // #1: Upper left
        -1.0f, -1.0f, 0.0f,      // #2: Lower left
        +1.0f, -1.0f, 0.0f,      // #3: Lower right
        // Texture uv position
        1.0f, 1.0f,               // #0: Upper right
        0.0f, 1.0f,               // #1: Upper left
        0.0f, 0.0f,               // #2: Lower left
        1.0f, 0.0f                // #3: Lower right
    };
    const GLshort modelTex::idxData[6] =
    {
        0, 1, 2,
        0, 2, 3
    };
    const int modelTex::numVert(4);
    const int modelTex::offsetTexUVData(sizeof(float) * 3 * numVert);

    void modelTex::init(
        int w,
        int h,
        const string &vertShaderName,
        const string &fragShaderName,
        GLint internalFormat,
        GLenum format,
        GLenum type
    )
    {
        // ----- Initialize shader programs
        initializeProgram(prog, vertShaderName, fragShaderName);

        // ----- Generate buffers
        // VBO
        glGenBuffers(1, &vbo);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertData), vertData, GL_STATIC_DRAW);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        // IBO
        glGenBuffers(1, &ibo);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(idxData), idxData, GL_STATIC_DRAW);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

        // ----- Get a uiform location
        MVPMatLoc = glGetUniformLocation(prog, "MVPMat");

        // ----- VAO
        glGenVertexArrays(1, &vao);
        glBindVertexArray(vao);

        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glEnableVertexAttribArray(0);
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);  // 3 elements per vertex
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (void*)offsetTexUVData); // 2 elements per color
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);

        glBindVertexArray(0);

        // ----- Currently the texture is empty
        glGenTextures(1, &texID);
        glBindTexture(GL_TEXTURE_2D, texID);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, w, h, 0, format, type, NULL);
    }

    modelTex::modelTex(
        const string &texFileName,
        const string &vertShaderName,
        const string &fragShaderName,
        GLint internalFormat,
        GLenum format,
        GLenum type
    )
    {
        // ----- Load an image and create a texture of the image
        ilInit();
        
        ILuint imgID;
        ilGenImages(1, &imgID);

        ilBindImage(imgID);
        ilEnable(IL_ORIGIN_SET);
        ilOriginFunc(IL_ORIGIN_LOWER_LEFT);

        try {
            ILboolean isLoaded = ilLoadImage((ILstring)texFileName.c_str());
            if (isLoaded)
            {
                cout << "Loaded " << texFileName << endl;

                init(
                    ilGetInteger(IL_IMAGE_WIDTH), ilGetInteger(IL_IMAGE_HEIGHT),
                    vertShaderName, fragShaderName,
                    internalFormat, format, type
                );

                ilConvertImage(IL_RGBA, IL_UNSIGNED_BYTE);

                glBindTexture(GL_TEXTURE_2D, texID);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, ilGetInteger(IL_IMAGE_WIDTH),
                    ilGetInteger(IL_IMAGE_HEIGHT), 0, format, type,
                    ilGetData());
            }
            else
            {
                throw "Could not load image: " + texFileName;
            }
        }
        catch (string e)
        {
            cerr << "Error: " << e.c_str() << endl;
            exit(EXIT_FAILURE);
        }
    }
    modelTex::modelTex(
        int w,
        int h,
        const string &vertShaderName,
        const string &fragShaderName,
        GLint internalFormat,
        GLenum format,
        GLenum type
    )
    {
        init(w, h, vertShaderName, fragShaderName, internalFormat, format, type);
    }
    modelTex::~modelTex()
    {
        glDeleteVertexArrays(1, &vao);
        glDeleteTextures(1, &texID);
        glDeleteBuffers(1, &vbo);
        glDeleteBuffers(1, &ibo);
        glDeleteBuffers(1, &MVPMatLoc);
    }

    void modelTex::render()
    {
        render(-1);
    }
    void modelTex::render(
        GLint texID
    )
    {
        glUseProgram(prog);
        {
            // Send matrix data to GPU
            glUniformMatrix4fv(MVPMatLoc, 1, GL_FALSE, glm::value_ptr(projMat * viewMat * modelMat));
            
            // Texture
            if (texID >= 0)
            {
                glBindTexture(GL_TEXTURE_2D, texID);
            }
            else
            {
                glBindTexture(GL_TEXTURE_2D, this->texID);
            }

            // VAO
            glBindVertexArray(vao);
            glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
            glBindVertexArray(0);
        }
        glUseProgram(0);
    }
}

fbo.h

FBOを扱うクラスを記述したヘッダファイル.

//
// fbo.h
//
#pragma once

#include <iostream>
using namespace std;
#include <GL/glew.h>
#include <GLFW/glfw3.h>

namespace gl
{
    class fbo
    {
    private:
        const int width;
        const int height;
        GLuint texColorID;
        GLuint texDepthID;
        GLuint fboID;

    public:
        fbo(
            int w,
            int h,
            GLint internalFormat = GL_RGBA,
            GLenum format = GL_RGBA,
            GLenum type = GL_UNSIGNED_BYTE
        );
        ~fbo();

        int getWidth();
        int getHeight();
        GLuint getColorTexID();
        GLuint getDepthTexID();
        GLuint getFBOID();
    };
}

fbo.cpp

FBOを扱うクラスを記述したソースファイル.

//
// fbo.cpp
//
#include "fbo.h"

namespace gl
{
    fbo::fbo(
        int w,
        int h,
        GLint internalFormat,
        GLenum format,
        GLenum type
    ) : width(w), height(h)
    {
        // ----- Create a texture
        // Color buffer
        glGenTextures(1, &texColorID);
        glBindTexture(GL_TEXTURE_2D, texColorID);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, w, h, 0, format, type, 0);
        glBindTexture(GL_TEXTURE_2D, 0);
        // Z buffer
        glGenTextures(1, &texDepthID);
        glBindTexture(GL_TEXTURE_2D, texDepthID);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, w, h, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0);
        glBindTexture(GL_TEXTURE_2D, 0);

        // ----- Create a framebuffer object (FBO)
        glGenFramebuffers(1, &fboID);
        glBindFramebuffer(GL_FRAMEBUFFER, fboID);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texColorID, 0);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texDepthID, 0);

        // ----- Get back to backbuffer
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }
    fbo::~fbo()
    {
        glDeleteTextures(1, &texColorID);
        glDeleteTextures(1, &texDepthID);
        glDeleteFramebuffers(1, &fboID);
    }

    int fbo::getWidth()
    {
        return width;
    }
    int fbo::getHeight()
    {
        return height;
    }
    GLuint fbo::getColorTexID()
    {
        return texColorID;
    }
    GLuint fbo::getDepthTexID()
    {
        return texDepthID;
    }
    GLuint fbo::getFBOID()
    {
        return fboID;
    }
}

main.cpp

//
// main.cpp
//
#include <glm/glm.hpp>
#include <glm/ext.hpp>
#include "OpenGLWrapper/window.h"
#include "OpenGLWrapper/shaderUtil.h"
#include "OpenGLWrapper/modelTex.h"
#include "OpenGLWrapper/fbo.h"

class MyWindow : gl::window
{
private:
    gl::modelTex *myTex;
    gl::fbo *myFBO;

public:
    MyWindow(
        int w,
        int h,
        const string &name,
        const string &texFileName,
        const string &vertShaderName,
        const string &fragShaderName
    ): window(w, h, name)
    {
        myTex = new gl::modelTex(texFileName, vertShaderName, fragShaderName);
        myFBO = new gl::fbo(w, h, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE);
    }
    ~MyWindow()
    {
        if (myTex) delete myTex;
        if (myFBO) delete myFBO;
    }

    void path1()
    {
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, myFBO->getFBOID());
        {
            // Clear color and depth buffers
            glClearColor(0.3f, 0.5f, 0.8f, 0.0f);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

            // Draw
            glm::vec3 eye(cos(glfwGetTime()), sin(glfwGetTime()), 1.5f);
            glm::vec3 center(0.0f);
            glm::vec3 up(0.0f, 1.0f, 0.0f);
            glm::mat4 V = glm::lookAt(eye, center, up);
            glm::mat4 P = glm::perspective(3.14f / 4.0f, (float)width / height, 0.1f, 100.0f);
            myTex->setViewMat(V);
            myTex->setProjMat(P);
            myTex->render();
        }
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    }
    void path2()
    {
        // Clear color and depth buffers
        glClearColor(0.8f, 0.5f, 0.3f, 0.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glm::vec3 eye(-1.0f, 1.0f, 2.5f);
        glm::vec3 center(0.0f);
        glm::vec3 up(0.0f, 1.0f, 0.0f);
        glm::mat4 V = glm::lookAt(eye, center, up);
        myTex->setViewMat(V);
        myTex->render(myFBO->getColorTexID());
    }
    void render()
    {
        // Rendering loop
        while (!glfwWindowShouldClose(wnd))
        {
            path1();
            path2();

            // Swap front and back buffers
            glfwSwapBuffers(wnd);
            // Poll for and process events
            glfwPollEvents();
        }
    }
};

int main()
{
    MyWindow wnd(
        640, 480, "GLFW 3 Window",
        "../data/me.jpg",
        "../shader/modelTex.vert",
        "../shader/modelTex.frag"
    );
    wnd.render();

    return 0;
}