作者:idovelemon
日期:2018-03-17
来源:CSDN
主题:Prefilter Environment Map
前面的章节里面,我们讲述了如何通过brute force的方式去实现Specular的Image based Lighting。但是这种实现,在实际的游戏运行过程中消耗太大,实用价值不高。所以,本篇文章将给出对于这中brute force方式的优化处理,以加快Specular Image based Lighting的计算。这个处理,就是Unreal4引擎的实现方式。同时,添加对Albedo Map,Roughness Map,Metallic Map和Normal Map的应用,彻底展现下IBL的魅力。
首先,我们给出我们需要计算的渲染方程:
LD项的公式如下所示:
void DrawConvolutionCubeMapSpecularLD() {
// Setup shader
render::Device::SetShader(m_SpecularLDCubeMapProgram);
render::Device::SetShaderLayout(m_SpecularLDCubeMapProgram->GetShaderLayout());
// Setup texture
render::Device::ClearTexture();
render::Device::SetTexture(0, m_CubeMap, 0);
// Setup mesh
render::Device::SetVertexLayout(m_ScreenMesh->GetVertexLayout());
render::Device::SetVertexBuffer(m_ScreenMesh->GetVertexBuffer());
// Setup render state
render::Device::SetDepthTestEnable(true);
render::Device::SetCullFaceEnable(true);
render::Device::SetCullFaceMode(render::CULL_BACK);
int32_t miplevels = log(256) / log(2) + 1;
float roughnessStep = 1.0f / miplevels;
int32_t width = 256, height = 256;
for (int32_t j = 0; j < miplevels; j++) {
// Render Target
render::Device::SetRenderTarget(m_SpecularLDCubeMapRT[j]);
// View port
render::Device::SetViewport(0, 0, width, height);
width /= 2;
height /= 2;
for (int32_t i = 0; i < 6; i++) {
// Set Draw Color Buffer
render::Device::SetDrawColorBuffer(static_cast<render::DrawColorBuffer>(render::COLORBUF_COLOR_ATTACHMENT0 + i));
// Clear
render::Device::SetClearColor(0.0f, 0.0f, 0.0f);
render::Device::SetClearDepth(1.0f);
render::Device::Clear(render::CLEAR_COLOR | render::CLEAR_DEPTH);
// Setup uniform
render::Device::SetUniformSamplerCube(m_SpecularLDProgram_CubeMapLoc, 0);
render::Device::SetUniform1i(m_SpecularLDProgram_FaceIndexLoc, i);
render::Device::SetUniform1f(m_SpecularLDProgram_RoughnessLoc, j * roughnessStep);
// Draw
render::Device::Draw(render::PT_TRIANGLES, 0, m_ScreenMesh->GetVertexNum());
}
}
}
下面的GLSL代码展示了如何进行积分预算和Importance Sampling:
vec3 convolution_cube_map(samplerCube cube, int faceIndex, vec2 uv) {
// Calculate tangent space base vector
vec3 n = calc_normal(faceIndex, uv);
n = normalize(n);
vec3 v = n;
vec3 r = n;
// Convolution
uint sampler = 1024u;
vec3 color = vec3(0.0, 0.0, 0.0);
float weight = 0.0;
for (uint i = 0u; i < sampler; i++) {
vec2 xi = hammersley(i, sampler);
vec3 h = importance_sampling_ggx(xi, glb_Roughness, n);
vec3 l = 2.0 * dot(v, h) * h - v;
float ndotl = max(0, dot(n, l));
if (ndotl > 0.0) {
color = color + filtering_cube_map(glb_CubeMap, l).xyz * ndotl;
weight = weight + ndotl;
}
}
color = color / weight;
return color;
}
DFG项的公式如下:
void DrawConvolutionCubeMapSpecularDFG() {
// Setup shader
render::Device::SetShader(m_SpecularDFGCubeMapProgram);
render::Device::SetShaderLayout(m_SpecularDFGCubeMapProgram->GetShaderLayout());
// Setup texture
render::Device::ClearTexture();
// Setup mesh
render::Device::SetVertexLayout(m_ScreenMesh->GetVertexLayout());
render::Device::SetVertexBuffer(m_ScreenMesh->GetVertexBuffer());
// Setup render state
render::Device::SetDepthTestEnable(true);
render::Device::SetCullFaceEnable(true);
render::Device::SetCullFaceMode(render::CULL_BACK);
// Render Target
render::Device::SetRenderTarget(m_SpecularDFGCubeMapRT);
// View port
render::Device::SetViewport(0, 0, 128, 128);
// Set Draw Color Buffer
render::Device::SetDrawColorBuffer(static_cast<render::DrawColorBuffer>(render::COLORBUF_COLOR_ATTACHMENT0));
// Clear
render::Device::SetClearColor(0.0f, 0.0f, 0.0f);
render::Device::SetClearDepth(1.0f);
render::Device::Clear(render::CLEAR_COLOR | render::CLEAR_DEPTH);
// Draw
render::Device::Draw(render::PT_TRIANGLES, 0, m_ScreenMesh->GetVertexNum());
}
GLSL代码:
vec3 convolution_cube_map(vec2 uv) {
vec3 n = vec3(0.0, 0.0, 1.0);
float roughness = uv.y;
float ndotv = uv.x;
vec3 v = vec3(0.0, 0.0, 0.0);
v.x = sqrt(1.0 - ndotv * ndotv);
v.z = ndotv;
float scalar = 0.0;
float bias = 0.0;
// Convolution
uint sampler = 1024u;
for (uint i = 0u; i < sampler; i++) {
vec2 xi = hammersley(i, sampler);
vec3 h = importance_sampling_ggx(xi, roughness, n);
vec3 l = 2.0 * dot(v, h) * h - v;
float ndotl = max(0.0, l.z);
float ndoth = max(0.0, h.z);
float vdoth = max(0.0, dot(v, h));
if (ndotl > 0.0) {
float G = calc_Geometry_Smith_IBL(n, v, l, roughness);
float G_vis = G * vdoth / (ndotv * ndoth);
float Fc = pow(1.0 - vdoth, 5.0);
scalar = scalar + G_vis * (1.0 - Fc);
bias = bias + G_vis * Fc;
}
}
vec3 color = vec3(scalar, bias, 0.0);
color = color / sampler;
return color;
}
当我们成功的实现了如上两个步骤之后,我们就可以在实际渲染的时候计算IBL了,如下GLSL代码完成光照计算:
vec3 calc_ibl(vec3 n, vec3 v, vec3 albedo, float roughness, float metalic) {
vec3 F0 = mix(vec3(0.04, 0.04, 0.04), albedo, metalic);
vec3 F = calc_fresnel_roughness(n, v, F0, roughness);
// Diffuse part
vec3 T = vec3(1.0, 1.0, 1.0) - F;
vec3 kD = T * (1.0 - metalic);
vec3 irradiance = filtering_cube_map(glb_IrradianceMap, n);
vec3 diffuse = kD * albedo * irradiance;
// Specular part
float ndotv = max(0.0, dot(n, v));
vec3 r = 2.0 * ndotv * n - v;
vec3 ld = filtering_cube_map_lod(glb_PerfilterEnvMap, r, roughness * 9.0);
vec2 dfg = textureLod(glb_IntegrateBRDFMap, vec2(ndotv, roughness), 0.0).xy;
vec3 specular = ld * (F0 * dfg.x + dfg.y);
return diffuse + specular;
}
其中glb_PrefilterEnvMap表示的就是LD项,而glb_IntegrateBRDFMap表示的就是DFG项。通过预计算,这里的处理是不是变得十分的简单,只要获取结果,然后简单处理下就可以了。不使用材质贴图的情况,得到如下的结果:
如果你和我一样用的OpenGL,那么你可能会遇到如下图所显示的走样问题:
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // For cubemap seamless filtering
如果你的库里面,不存在这个选项,那么你要更新你的OpenGL。我这里使用的是OpenGL4.5和glew2.0.0版本。
前面的Demo,我都是使用参数来控制物体的材质,为了提供更加真实的效果,需要使用材质贴图。所以我额外的写了一个demo,来展示不同材质在PBS下的效果,这些效果可以在Github项目的首页中看到,也可以在CSDN GraphicsLab学习项目的首页看到。所有的材质贴图,都是从这里下载。
到了这里,我们的PBS基础功能已经实现完毕。之后可能会把这些demo特性集成到渲染器里面去。同时在了解了这个基础之后,就需要开始实现Light Probe以此来高效的在场景中使用IBL的功能,将又会有一大波功能和知识需要掌握,期待吧!
所有关于PBS的代码,都已经上传到了Github,这里简要的说明下各个项目的作用:
glb_pbs:用来展示PBS直接光照效果的demo,无贴图材质
glb_ibl_diffuse:用来展示IBL中diffuse部分的demo,无贴图材质
glb_ibl_specular_bruteforce:使用bruteforce的方式实现IBL中的specular部分,无贴图材质
glb_ibl_specular_epic:使用epic的split approximation方式实现IBL中的specular部分,无贴图
glb_pbs_texture:完整的PBS实现,具有直接光照,IBL光照,材质贴图
[1]s2013_epic_pbs_notes_v2
[2]learnopengl.com
[3]moving frostbite to pbr