GraphicsLab Project之Dynamic Environment Mapping

51 篇文章 7 订阅
19 篇文章 0 订阅

作者:i_dovelemon

日期:2017 - 03 - 27

来源:CSDN

主题:Environment Mapping, Cube Map



引言



        GLB库在此之前只支持了Diffuse,,Alpha和Normal贴图,最近希望能够支持Reflect 贴图,所以就添加了对Reflect贴图的支持。一般来说,反射贴图是使用Cube Map(Environment Map)来实现的。这个Cube Map编码了需要反射的环境,将它附加给物体,就能够通过shader进行Cube Mapping,实现简单的反射效果。如果想要实时的反射游戏中物体周围的环境,就需要在物体周围环境发生变化的时候,动态的产生这张Cube Map,以此来实现动态的实时反射效果。


立方体映射(Cube Mapping)



        那么,首先就来介绍下今天的主人公,立方体映射,即Cube Mapping。立方体映射的技术很早之前就提出了,它的主要思想是将环境编码到6个不同的贴图中去,这6个贴图构成了一个立方体的6个面,将他们合在一起的时候,刚好是一个封闭的立方体,如下图所示:



        由于要编码周围的环境需要6张贴图才行,但是以前的硬件没有办法对连续的6张纹理进行采样。直到Nvidia Gefore 256显卡出来之后,才有了硬件上对这种贴图进行采样的支持,也就是现在我们知道的Cube Map。现在的图形硬件基本都提供了对Cube Map的采样操作,所以我们就能够依靠图形硬件来实现我们需要的动态环境反射效果。

        当我们准备好了Cube Map之后,我们如何来为物体着色,从而实现反射效果了?这个很简单,由于Cube Map编码了周围的环境,当我们计算一个物体上某个点A的环境反射颜色时,我们只要根据该点的法线,以及该点A到相机的方向向量(即反射到我们人眼中的向量)来逆推出那个将要反射到我们眼中的纹素即可,如下图所示:





产生环境贴图



        由于我希望实现的是动态的实时反射,所以就需要动态产生环境贴图。主流的方案是以需要进行反射的物体为中心,然后分别对着世界坐标轴的+X,-X,+Y,-Y,+Z,-Z渲染场景6次,产生一张纹理贴图。除此之外在Real time rendering还提到了通过Geometry shader和Layer rendering技术来渲染场景一次产生环境贴图。不过后面一个方案目前还不是十分的稳定成熟,感兴趣的同学可以自行尝试下。

        由于渲染的场景需要是立方体的6个面,所以我们必须要重新设置投影矩阵,以90度视野和宽高比为1的画幅来进行渲染,这样才能够保证渲染出来的结果是正方形的。


集成到框架中



        最初设计的时候,我希望环境贴图的产生完全有框架来控制,可是最终发现很难决定到底以哪种方式实现。如果对每一个需要反射的物体都渲染一张贴图,明显开销太大;如果以区块划分,然后不同区块产生不同的贴图,这种方法又会有浪费,所以最终我决定,只在框架中集成烘培环境贴图的功能,具体要在什么位置烘焙,哪些物体需要用到哪种贴图,由上层应用来决定。所以,如下就是基本的框架。

        首先,我定义了数据结构,用来保存用户请求的环境贴图烘焙数据,如下所示:

struct EnvBaker {
    RenderTarget* target;
    Object* obj;
    int32_t tex_id;
    int32_t width;
    int32_t height;

    EnvBaker()
    : target(NULL)
    , obj(NULL)
    , tex_id(-1)
    , width(0)
    , height(0) {
    }
};

        并且提供了接口,用来请求烘焙和取消烘焙,如下所示:
int32_t RenderImp::RequestBakeEnvMap(int32_t width, int32_t height, Object* obj) {
    int32_t result = -1;

    RenderTarget* rt = RenderTarget::Create(width, height);
    texture::Texture* env_map = texture::Texture::CreateFloat16CubeTexture(width, height);
    if (rt != NULL && env_map != NULL && obj != NULL) {
        DrawColorBuffer attach_pos[6] = {
            COLORBUF_COLOR_ATTACHMENT0,
            COLORBUF_COLOR_ATTACHMENT1,
            COLORBUF_COLOR_ATTACHMENT2,
            COLORBUF_COLOR_ATTACHMENT3,
            COLORBUF_COLOR_ATTACHMENT4,
            COLORBUF_COLOR_ATTACHMENT5
        };
        rt->AttachCubeTexture(attach_pos, env_map);
        result = texture::Mgr::AddTexture(env_map);
        EnvBaker baker;
        baker.target = rt;
        baker.obj = obj;
        baker.tex_id = result;
        baker.width = width;
        baker.height = height;
        m_EnvBakers.push_back(baker);
    } else {
        GLB_SAFE_ASSERT(false);
    }

    return result;
}

void RenderImp::CancleBakeEnvMap(Object* obj) {
    if (obj != NULL) {
        if (!m_EnvBakers.empty()) {
            std::vector<EnvBaker>::iterator it = m_EnvBakers.begin();
            for (; it != m_EnvBakers.end(); ++it) {
                if (it->obj == obj) {
                    GLB_SAFE_DELETE(it->target);
                    it->tex_id = -1;
                    it->obj = NULL;
                    m_EnvBakers.erase(it);
                    break;
                }
            }
        }
    }
}

        这样当用户想要为某个模型实现反射效果的时候,只要请求烘焙一张环境贴图,然后将请求的贴图ID保存起来,就能够在后面当实际环境贴图渲染完毕之后拿来使用了,如下是一个使用该接口的典型场景:
        m_Cube = glb::scene::Scene::AddObject("ball.obj");
        obj = glb::scene::Scene::GetObjectById(m_Cube);
        obj->SetCullFaceEnable(true);
        obj->SetCullFaceMode(glb::render::CULL_BACK);
        obj->SetDepthTestEnable(true);
        obj->SetPos(Vector(0.0f, 0.0f, 0.0f));
        int32_t ref_tex = glb::render::Render::RequestBakeEnvMap(1024, 1024, obj);
        obj->SetTexWithId(glb::Model::MT_REFLECT, ref_tex);

        从上面的代码可以看出,我没有将模型使用反射贴图的操作绑定为必须使用烘焙的方式,如果你从某个文件中读取了静态的环境贴图,也能够通过SetTexWithId来为模型设置对应的反射贴图。


实现



        实现的代码很简单,如下所示:
void RenderImp::DrawEnvMapCore() {
    // Save original proj-view matrix information
    PerspectiveProj old_proj = m_Perspective[Render::PRIMARY_PERS];
    camera::CameraBase* old_camera = NULL;
    scene::Scene::GetCurCamera()->Clone(&old_camera);
    Vector old_sky_object_pos = scene::Scene::GetSkyObject()->GetPos();

    // New perspective matrix
    m_Perspective[Render::PRIMARY_PERS].znear = 0.1f;
    m_Perspective[Render::PRIMARY_PERS].zfar = 1000.0f;
    m_Perspective[Render::PRIMARY_PERS].aspect = 1.0f;
    m_Perspective[Render::PRIMARY_PERS].fov = 90.0f;
    m_Perspective[Render::PRIMARY_PERS].m.MakeProjectionMatrix(1.0f, 90.0f, 0.1f, 1000.0f);

    int32_t size = m_EnvBakers.size();
    for (int32_t i = 0; i < size; i++) {
        Object* ref_obj = m_EnvBakers[i].obj;
        Vector ref_pos = ref_obj->GetPos();

        scene::Scene::GetSkyObject()->SetPos(ref_pos);
        scene::Scene::GetSkyObject()->Update();

        // 6 View matrix(+X,-X,+Y,-Y,+Z,-Z)
        Matrix views[6];
        views[0].MakeViewMatrix(ref_pos, ref_pos + Vector(1.0f, 0.0f, 0.0f));
        views[1].MakeViewMatrix(ref_pos, ref_pos + Vector(-1.0f, 0.0f, 0.0f));
        views[2].MakeViewMatrix(ref_pos, Vector(1.0f, 0.0f, 0.0f), Vector(0.0f, 0.0f, 1.0f), Vector(0.0f, -1.0f, 0.0f));
        views[3].MakeViewMatrix(ref_pos, Vector(1.0f, 0.0f, 0.0f), Vector(0.0f, 0.0f, -1.0f), Vector(0.0f, 1.0f, 0.0f));
        views[4].MakeViewMatrix(ref_pos, ref_pos + Vector(0.0f, 0.0f, 1.0f));
        views[5].MakeViewMatrix(ref_pos, ref_pos + Vector(0.0f, 0.0f, -1.0f));

        // Render Target
        render::Device::SetRenderTarget(m_EnvBakers[i].target);

        // View port
        render::Device::SetViewport(0, 0, m_EnvBakers[i].width, m_EnvBakers[i].height);

        for (int32_t j = 0; j < 6; j++) {
            scene::Scene::GetCurCamera()->SetViewMatrix(views[j]);

            // Draw Buffer
            render::Device::SetDrawColorBuffer(static_cast<render::DrawColorBuffer>(render::COLORBUF_COLOR_ATTACHMENT0 + j));

            // Clear
            render::Device::SetClearColor(1.0f, 1.0f, 1.0f);
            render::Device::SetClearDepth(1.0f);
            render::Device::Clear(CLEAR_COLOR | CLEAR_DEPTH);

            for (int32_t k = 0; k < static_cast<int32_t>(m_ShaderGroups.size()); k++) {
                std::vector<Object*> objs = m_ShaderGroups[k].GetObjects();
                shader::Descriptor desc = m_ShaderGroups[k].GetShaderDesc();
                shader::Program* program = m_ShaderGroups[k].GetShaderProgram();
                std::vector<uniform::UniformEntry>& uniforms = program->GetUniforms();

                // Shader
                render::Device::SetShader(program);
                render::Device::SetShaderLayout(program->GetShaderLayout());

                // Scene uniforms
                for (int32_t l = 0; l < static_cast<int32_t>(uniforms.size()); l++) {
                    uniform::UniformEntry entry = uniforms[l];
                    if (entry.flag) {
                        // TODO: for now, id is the index of the uniform picker table
                        uniform::Wrapper uniform_wrapper = uniform::kUniformPickers[entry.id].picker(NULL);
                        SetUniform(entry.location, uniform_wrapper);
                    }
                }

                // Objects
                for (int32_t l = 0; l < static_cast<int32_t>(objs.size()); l++) {
                    Object* obj = objs[l];
                    if (obj == ref_obj) {
                        continue;
                    }

                    // Textures
                    if (obj->GetModel()->HasDiffuseTexture()) {
                        render::Device::SetTexture(render::TS_DIFFUSE, texture::Mgr::GetTextureById(obj->GetModel()->GetTexId(Model::MT_DIFFUSE)), 0);
                    }
                    if (obj->GetModel()->HasAlphaTexture()) {
                        render::Device::SetTexture(render::TS_ALPHA, texture::Mgr::GetTextureById(obj->GetModel()->GetTexId(Model::MT_ALPHA)), 1);
                    }
                    if (obj->GetModel()->HasNormalTexture()) {
                        render::Device::SetTexture(render::TS_NORMAL, texture::Mgr::GetTextureById(obj->GetModel()->GetTexId(Model::MT_NORMAL)), 2);
                    }
                    if (obj->GetModel()->IsAcceptShadow()) {
                        render::Device::SetTexture(render::TS_SHADOW, texture::Mgr::GetTextureById(m_ShadowMap), 3);
                    }
                    if (obj->GetModel()->IsUseAO()) {
                        render::Device::SetTexture(render::TS_AO_MAP, texture::Mgr::GetTextureById(m_AOMap), 4);
                    }

                    // Object Uniform
                    for (int32_t m = 0; m < static_cast<int32_t>(uniforms.size()); m++) {
                        uniform::UniformEntry entry = uniforms[m];
                        if (!entry.flag) {
                            // TODO: for now, id is the index of the uniform picker table
                            uniform::Wrapper uniform_wrapper = uniform::kUniformPickers[entry.id].picker(obj);
                            SetUniform(entry.location, uniform_wrapper);
                        }
                    }

                    // Vertex Buffer
                    int32_t mesh_id = obj->GetModel()->GetMeshId();
                    VertexLayout layout = mesh::Mgr::GetMeshById(mesh_id)->GetVertexLayout();
                    int32_t num = mesh::Mgr::GetMeshById(mesh_id)->GetVertexNum();
                    render::Device::SetVertexBuffer(mesh::Mgr::GetMeshById(mesh_id)->GetVertexBuffer());
                    render::Device::SetVertexLayout(layout);

                    if (obj->IsCullFaceEnable()) {
                        render::Device::SetCullFaceEnable(true);
                        render::Device::SetCullFaceMode(obj->GetCullFaceMode());
                    } else {
                        render::Device::SetCullFaceEnable(false);
                    }

                    if (obj->IsDepthTestEnable()) {
                        render::Device::SetDepthTestEnable(true);
                    } else {
                        render::Device::SetDepthTestEnable(false);
                    }

                    if (obj->IsAlphaBlendEnable()) {
                        render::Device::SetAlphaBlendEnable(true);
                        render::Device::SetAlphaBlendFunc(render::FACTOR_SRC, obj->GetAlphaBlendFunc(render::FACTOR_SRC));
                        render::Device::SetAlphaBlendFunc(render::FACTOR_DST, obj->GetAlphaBlendFunc(render::FACTOR_DST));
                    } else {
                        render::Device::SetAlphaBlendEnable(false);
                    }

                    // Draw
                    render::Device::Draw(render::PT_TRIANGLES, 0, num);
                }
            }
        }

        // Reset render target
        render::Device::SetRenderTarget(0);
    }

    // Restore original proj-view matrix information
    m_Perspective[Render::PRIMARY_PERS] = old_proj;
    scene::Scene::GetCurCamera()->Restore(&old_camera);
    scene::Scene::GetSkyObject()->SetPos(old_sky_object_pos);
    scene::Scene::GetSkyObject()->Update();
    render::Device::SetViewport(0, 0, m_Width, m_Height);
}

        为保存的baker数据中,每一个需要渲染的环境贴图渲染6次场景,最终得到一个环境贴图。

        当环境贴图渲染完毕之后,我们就可以在最终的渲染中,开启反射贴图选项,使用对应的shader,来进行反射计算,如下代码表示了反射计算的过程:
		#ifdef GLB_ENABLE_REFLECT_TEX
			vec3 refl = reflect(-view_vec, normal);
			refl = normalize(refl);
			oColor.xyz = textureCube(glb_ReflectTex, refl).xyz;  // Just for environment mapping now, please fix this
		#endif		


        下面是通过此种技术渲染的简单场景:



        上图即是渲染的结果。读者可能发现上图中反射的环境图并不是连续成一个整体的,具体原因我们接下来仔细分析。


渲染结果不是整体的原因?



        首先,我们将球体四周的6个立方体,分别用带有上,下,左,右,前和后的贴图来表示,以便于我们区分反射的具体情况。如下图即是结果:




        仔细分析上图可以看到,左右前后的贴图上下颠倒了,并且前和后的位置颠倒了。通过调查发现这是OpenGL保存Cube Map的方式,具体可以看 OpenGL Specific中有关的介绍。

        那么,怎么解决这个问题了?最先想到的方法就是对每一个贴图产生的时候,对相机矩阵做一些处理。但是这样就绑定到了OpenGL这个API,在DX中实际上是不需要这样处理的。所以这里使用一个比较讨巧的方法。

        我们在绘制+X,-X,+Y,-Y,+Z,-Z场景的时候,他们和对应的Cube Map面的对应关系如下所示:

        +X <==> GL_CUBE_MAP_POSITIVE_X
        -X <==> GL_CUBE_MAP_NEGATIVE_X
        +Y <==> GL_CUBE_MAP_POSITIVE_Y
        -Y <==> GL_CUBE_MAP_NEGATIVE_Y
        +Z <==> GL_CUBE_MAP_POSITIVE_Z
        -Z <==> GL_CUBE_MAP_NEGATIVE_Z

        我们将这个对应关系,改为如下的方式:

        +X <==> GL_CUBE_MAP_POSITIVE_X
        -X <==> GL_CUBE_MAP_NEGATIVE_X
        +Y <==> GL_CUBE_MAP_NEGATIVE_Y
        -Y <==> GL_CUBE_MAP_POSITIVE_Y
        +Z <==> GL_CUBE_MAP_NEGATIVE_Z
        -Z <==> GL_CUBE_MAP_POSITIVE_Z

        通过这样的改变,我们就能够得到如下所示的图:




        可以看到,更改对应关系之后,整个反射的画面已经是一个连续的状态,但是还是存在一个问题,反射的图像上下颠倒了。这是因为我们向前面那样改变,本质上就是将反射的环境贴图上下颠倒,想要获得正常的结果,就需要在采样的时候,将纹理坐标绕X轴旋转180度,即:

		#ifdef GLB_ENABLE_REFLECT_TEX
			vec3 refl = reflect(-view_vec, normal);
			refl = normalize(refl);
			refl.yz = -refl.yz;
			oColor.xyz = textureCube(glb_ReflectTex, refl).xyz;  // Just for environment mapping now, please fix this
		#endif		

        此时你得到的结果就是:


总结



        将环境映射技术集成到框架中去之后,慢慢的就发现原来设计中有很多不足的地方,除此之外速度也变得很慢,过段时间可能集中精力优化整个框架,让程序能够更流畅的运行。


参考文献



[2]  https://www.khronos.org/opengl/wiki/Cubemap_Texture Cube Map Texture Opengl Wiki
[3]  http://www.3dcpptutorials.sk/index.php?id=24  3D C/C++ tutorials - OpenGL 2.1 - GLSL cube mapping
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值