OpenGL.Shader:10-阴影实现 - FBO生成深度位图

OpenGL.Shader:10-阴影实现 - FBO深度位图

 

感觉有段时间没出文章了,N多原因导致。主要是近期有点忙,新文章的内容也有点多有点难,需要时间充分准备。

首先我们来复习一下FBO,我们之前在OpenGL.ES在Android上的简单实践:23-水印录制 中有介绍过什么是FBO,当时我们是用于离屏渲染,借助其自定义的缓冲区颜色空间解决透明冲突的问题。这里就不在重复论述什么是FBO了,需要强调一点的是FBO只是一个容器,容器能挂载很多种类的渲染对象。  这次我们就需要使用另一种新的渲染对象——深度缓存。

废话不说,直接上FrameBufferObject.hpp

#pragma once
#ifndef FRAME_BUFFER_OBJECT_HPP
#define FRAME_BUFFER_OBJECT_HPP

#define FBO_NONE  0x0000
#define FBO_RGBA  0x0001
#define FBO_DEPTH 0x0002

#include 
#include "zzr_common.h"

class   FrameBufferObject
{

public:
    unsigned int   _width;
    unsigned int   _height;
    unsigned int   _fboID;
    unsigned int   _rgbaTexId;
    unsigned int   _depthTexId;
    unsigned int   _depthRboId;
    unsigned int   _type;

public:
    FrameBufferObject()
    {
        _width  = 0;
        _height = 0;
        _fboID  = 0;
        _rgbaTexId = 0;
        _depthTexId = 0;
        _depthRboId = 0;
        _type   = FBO_NONE;
    }

    int getDepthTexId() {
        return _depthTexId;
    }
    int getRgbaTexId() {
        return _rgbaTexId;
    }
    bool    setup(int w, int h, int type)
    {
        _type = static_cast(type);
        _width = static_cast(w);
        _height = static_cast(h);

        glGenFramebuffers(1, &_fboID);
        glBindFramebuffer(GL_FRAMEBUFFER, _fboID);

        if(_type == FBO_DEPTH) {
            createDepthTexture();
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, _depthTexId, 0);
        }
        if(_type == FBO_RGBA) {
            createRgbaTexture();
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _rgbaTexId, 0);
            createDepthRenderBuffer();
            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthRboId);
        }

        glBindFramebuffer(GL_FRAMEBUFFER, 0);

        GLenum err = glGetError();
        while(err!=GL_NO_ERROR) {
            LOGE("fbo.setup.glTexImage2D : 0x%08x\n", err);
            err = glGetError();
        }
        return  true;
    }

    void    begin()
    {
        GLenum err = glGetError();
        while(err!=GL_NO_ERROR) {
            LOGE("fbo.begin : 0x%08x\n", err);
            err = glGetError();
        }
        glBindFramebuffer(GL_FRAMEBUFFER, _fboID);

        if(_type == FBO_DEPTH) {
            glDrawBuffers(0, GL_NONE);
            glReadBuffer(GL_NONE);
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, _depthTexId, 0);
        }
        if(_type == FBO_RGBA) {
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _rgbaTexId, 0);
            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthRboId);
        }

        GLenum fbo_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
        if(fbo_status!=GL_FRAMEBUFFER_COMPLETE) {
            LOGE("glCheckFramebufferStatus check err : 0x%08x\n", fbo_status);
        }
    }

    void    end()
    {
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }


private:
    void    createDepthTexture()
    {
        glGenTextures(1, &_depthTexId);
        glBindTexture(GL_TEXTURE_2D, _depthTexId);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, _width, _height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, 0);
    }

    void    createRgbaTexture()
    {
        glGenTextures(1, &_rgbaTexId);
        glBindTexture(GL_TEXTURE_2D, _rgbaTexId);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, _width, _height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
    }

    void    createDepthRenderBuffer()
    {
        glGenRenderbuffers( 1, &_depthRboId );
        glBindRenderbuffer( GL_RENDERBUFFER, _depthRboId );
        glRenderbufferStorage( GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, _width, _height );
    }
};
#endif // FRAME_BUFFER_OBJECT_HPP

基本代码如上,根据常用情况,我把FBO分成了两种类型,FBO_DEPTH(深度纹理模式) 和 FBO_RGBA(颜色纹理模式),从字面上理解,FBO_RGBA就是之前介绍的那种用途。区别在于这次追加了深度附着点(GL_DEPTH_ATTACHMENT)的RenderBuffer,这是因为我们这次是处在3D模型之中,深度信息不能忽略,在我们向FBO输出之后,需要有缓冲区保留这些深度信息,这样的fbo才能完整保留我们所需要的所有信息。

不理解的同学,我尝试把depth——renderbuffer相关代码注释。效果如下图示(先画正方体后画地板):

这就是没深度测试数据的效果,可想而知这个depth——renderbuffer是多么重要。在这里简单介绍一下RenderBuffer渲染对象,它和Texture纹理对象其实是同一类型的东西,都是OpenGL里面用于保存输出的一种容器对象,Texture纹理对象更为灵活,能传递到shader当中使用,但是有时候纹理并不是万能,就像当前例子,需要存储颜色信息的同时还需要存储深度信息,此时Renderbuffer就排上用场了。

 

那么什么是深度位图呢?我们看上面代码,在FBO_DEPTH模式下,createDepthTexture函数创建的深度位图当中,与createRgbaTexture唯一区别就在于glTexImage2D的参数,别少看这些参数,我还真在这些参数当中踩了不是坑,而这些坑搜索了全网包括google都没有明确的答复,在此就说下自己的认知,如果有前辈能正确理解的请留言指导。

void    createDepthTexture()
{
        glGenTextures(1, &_depthTexId);
        glBindTexture(GL_TEXTURE_2D, _depthTexId);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, _width, _height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, 0);
        //LOGE("createDepthTexture check err 4 : 0x%08x\n", glGetError());
}

在OpenGL的Wiki当中(https://www.khronos.org/opengl/wiki/GLAPI/glTexImage2D),glTexImage2D的参数说明,其中第三个参数internalFormat说明如下:

internalFormat
Specifies the number of color components in the texture. Must be one of base internal formats given in Table 1, one of the sized internal formats given in Table 2, or one of the compressed internal formats given in Table 3, below.

然后就是指向下面三个Table,仔细阅读三个表格,很重要的一点就是表格2、3其实都是指向表格1的 Base Internal Formats,在移动端的GL ES裁剪之下,只保留最终的GL_DEPTH_COMPONENT,其余GL_DEPTH_COMPONENT16 / GL_DEPTH_COMPONENT24 / GL_DEPTH_COMPONENT32F都不在适用,就算当前GPU支持多少位深(通过glGetIntegerv(GL_DEPTH_BITS, &depth_bits)查询),EGL配置多少位深(EglCore初始化),一律都使用GL_DEPTH_COMPONENT

然后倒数第二个参数Type,就是根据EGL设置的位深,判别使用什么,8位 = GL_UNSIGNED_BYTE,16位 = GL_UNSIGNED_SHORT;

好了,经过正确的配置depth_texture之后我们就可以保存输出的深度测试到纹理当中,并把当其一张纹理输出到屏幕显示。

大家留意左上角的深度位图,一开始视角上我们是紧贴地板,越靠前,深度值越接近0。随后看见正方体的轮廓,正方体后方的地板也有深度的表现。(认真看,灰色很浅)然后放出其他部分的代码,便于理解。

void ShadowFBORender::renderOnDraw(double elpasedInMilliSec)
{
    if (mEglCore == NULL || mWindowSurface == NULL) {
        LOGW("Skipping drawFrame after shutdown");
        return;
    }
    mWindowSurface->makeCurrent();

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    glViewport(0,0, mViewWidth, mViewHeight);
    lightCube.render(mCamera3D);
    land.render(mCamera3D);

    renderDepthFBO();

    mWindowSurface->swapBuffers();
}

void ShadowFBORender::renderDepthFBO() {
    depthFBO.begin();
    {
        glEnable(GL_DEPTH_TEST);
        glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
        glClearDepthf(1.0f);
        lightCube.render(mCamera3D);
        land.render(mCamera3D);
    }
    depthFBO.end();

    pip.setTextureId(depthFBO.getDepthTexId()); // 深度纹理
    //pip.setTextureId(depthFBO.getRgbaTexId());// 彩色纹理
    pip.render();
}

pip->PIPicture是利用正交投影在屏幕画出texture的一个类对象,很简单很简单。参考代码 https://github.com/MrZhaozhirong/NativeCppApp -> ShadowFBORender.cpp / FrameBufferObject.hpp / PIPicture.hpp

下一章我们一起探讨如何利用深度位图,来实现光照阴影。

你可能感兴趣的:(OpenGL.Shader)