Mugichoko's blog

Mugichoko’s blog

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

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~n~f) / (f + n - (2~Z - 1) ~ (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辺りを前後した.