目標
前回(下記参照)までの実装を少し変更して,Compute Shaderを使って,3DCGを描画した際に生成されるZバッファの線形化を行うプログラムを作成する.
Zバッファの線形化とは?
Zバッファは,実際の奥行値から非線形変換が行われて0~1の大きさに正規化されており,実際の空間内の距離とは異なる.そこで,以下の式に基づいてZバッファを線形化(本来の奥行値)に戻す.
尚,は出力,
はZバッファの値,
は3DCGを描画する際に用いたNear平面までの距離,
はFar平面まで距離.
詳細は下記を参照のこと.
実装環境
- Windows 10 64bit
- Visual Studio 2015
- GLEW 3.2.1
- 設定方法はこちらを参照
- GLEW 1.13.0
- 設定方法はこちらを参照
- GLM
- 設定方法はこちらを参照
- DevIL
- 設定方法はこちらを参照
レンダリングの流れ
- パス1:四角形ポリゴンに向いたカメラが奥行方向に0.5~5.5の範囲で移動する
- パス2:Compute Shaderに1のZバッファを渡し,上記の式を適用する
- パス3:パス1の結果はFBOに書き込まれているので,バックバッファにコピーする
- パス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の位置に近づいた時.

カメラが5.5の位置に近づいた時.

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