IBL

IBL是 Image Based Lighting的缩写,也就是基于图像的光照,  把包围整个场景的图像看作是一个大型光源, 对物体着色产生了影响。在图形渲染中通常指的SkyBox, 用于IBL光照的SkyBox通常是HDR格式(RGB-float16)的CubeMap, 因为真实世界的天空光照值不限制在1.0以内。

IBL和其他光源一样都遵循上一篇篇所讲的PBR方程式和BRDF,也就是说IBL对物质着色同时存在Specular Item和Diffuse Item.

下面先分析IBL的Diffuse Item

IBL-Diffuse

上面是光照方程式,同时包含Specular Item和Diffuse Item,我们先分离出diffuseItem

HDR CubeMap的每点都可看作一个小小光源, 对于某一个方向, 我们以该方向作为Z方向,进行正半球积分(负半球的光照由于方向问题不存在光照贡献),求解卷积(这个方向的光照微积分方程计算值累加),即是这个方向的Diffuse光照贡献值, 俗称漫反射辐射度(Diffuse Irradiance)。

这里卷积是通过累加逼近微积分值的一种常用手段。

流程

卷积预处理其实就是六个方向渲染Cube,  在每个方向上对HDR CubeMap进行光照卷积,计算每个方向的Diffuse 光照累加值,最后在渲染到一个CubeMap上。你可以简单理解为,一个HDR CubeMap都通过这样的手法生成对应一个相应的Diffuse Irradiance CubeMap

 GDirectxCore->TurnOnRenderSkyBoxDSS();GDirectxCore->TurnOnCullFront();renderCubeMap->ClearRenderTarget(1.0f, 1.0f, 1.0f, 1.0f);XMMATRIX projMatrix = cubeCamera->GetProjMatrix();for (int index = 0; index < 6; ++index){renderCubeMap->ClearDepthBuffer();renderCubeMap->SetRenderTarget(index);GShaderManager->cubeMapToIrradianceShader->SetMatrix("View", cubeCamera->GetViewMatrix(index));GShaderManager->cubeMapToIrradianceShader->SetMatrix("Proj", projMatrix);GShaderManager->cubeMapToIrradianceShader->SetTexture("HdrCubeMap", hdrCubeMap->GetTexture());GShaderManager->cubeMapToIrradianceShader->SetTextureSampler("ClampLinear", GTextureSamplerBilinearClamp);GShaderManager->cubeMapToIrradianceShader->Apply();cubeGameObject->RenderMesh();}//已经渲染了MipMap链上的Top等级,调用GenerateMips自动生成余下的Mip等级GDirectxCore->GenerateMips(renderCubeMap->GetSRV());GDirectxCore->RecoverDefaultDSS();GDirectxCore->RecoverDefualtRS();

CubeMalpToIradiance.fx

TextureCube HdrCubeMap:register(t0);
SamplerState ClampLinear:register(s0);static const float PI = 3.1415926;cbuffer CBMatrix:register(b0)
{matrix View;matrix Proj;
};struct VertexIn
{float3 Pos:POSITION;float3 Color:COLOR;float3 Normal:NORMAL;float3 Tangent:TANGENT;float2 Tex:TEXCOORD;
};struct VertexOut
{float4 Pos:SV_POSITION;float3 SkyPos:TEXCOORD0;
};VertexOut VS(VertexIn ina)
{VertexOut outa;outa.SkyPos = ina.Pos;outa.Pos = float4(ina.Pos, 1.0f);outa.Pos = mul(outa.Pos, View);outa.Pos = mul(outa.Pos, Proj);return outa;
}float4 PS(VertexOut outa) : SV_Target
{float3 N = normalize(outa.SkyPos);float3 irradiance = float3(0.0, 0.0, 0.0);float3 up = float3(0.0, 1.0, 0.0);float3 right = cross(up, N);up = cross(N, right);float sampleDelta = 0.025;float nrSamples = 0.0;for (float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta){for (float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta){// spherical to cartesian (in tangent space)float3 tangentSample = float3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta));float3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N;irradiance += HdrCubeMap.Sample(ClampLinear, sampleVec).xyz * cos(theta) * sin(theta);nrSamples += 1.0;}}irradiance = PI * irradiance * (1.0 / float(nrSamples));return float4(irradiance, 1.0);
}
 float3 irradiance = IrradianceTex.Sample(clampLinearSample, worldNormal).xyz;float3 iblDiffuse = irradiance * albedo * kd / PI;

下面第一幅图背景是HDRCubeMap,第二幅图背景是IrradianceCubeMap

IBL-Specular

IBL和其他光源一样存在Specular Item的,看上面的光照方程公式

一样分离出Specular Item

先思考下是否可以利用Diffuse Item类似卷积办法预结算类似DiffuseIrradiance的CubeMap?

其实如果看了上面的微积分, 就发现了不对劲, BRDF的specular item并不存在相机方向ViewDir, 这个ViewDir可能是存在任意方向的,这意味着如果采用了上面的的卷积预结算,你除了像DiffuseIrradiance一样对每个入射光线方向进行卷积,还得对每个方向的ViewDir进行卷积,双重卷积的结果就是你得提前预计算无数个IrradianceCubeMap来满足第二维ViewDir信息,总体上这些问题导致整条BRDF Specular方程式很难直接进行卷积。

虚幻引擎的图形程序Brians Karis 提出了 split sum approximation来解决这些问题, split sum approximation 通过把BRDF的specular项划分成两部分可以单独进行卷积的部分,然后在最后的PBR Shader中进行结合计算。其中第一部分类似于上面DiffuseIrradiance, 也是对HDRCubeMap进行卷积, 这部分可称为PrefliterHDRCubeMap.  第二部分是把部分参数进行函数隐射到LUT查询表, 这部分可称为ConvolutedBRDF.

看下面的方程式分解:

split sum approximation 的微积分两部分如下所示:

Prefliter HDR CubeMap

这部分其实和求DifffuseIrradiance比较类似, 不过得注意的是IBL Specular和IBL Diffuse有些地方不太一样,IBL Diffuse的受光和粗糙度无关的,整个正半球都较为均匀的对同个方向进行了光照累积。但是IBL Specular不一样,specular项和表面的粗糙度息息相关。可以看看下图:

不同的粗糙表面形成的spcular lobe大小不一样的的,越粗糙的表面,spcular lobe越大,从光的可逆性来看,某个反射方向的specular光就是来源于更多入射光方向的贡献,造成卷积CubeMap出来的效果模糊,长得像Diffuse Irradiance CubeMap。而越平滑的表面,某个反射光方向只是来源于非常少数方向的光,造成卷积CubeMap出来的效果清晰,长得像CubeMap.

有个问题是进行预卷积HDR CubeMap的时候,Specular的相机方向ViewDir是不知道,epic games做出了简化,假设光的反射方向(采样方向)和ViewDir是一样的

效果上和正常的反射相比确实不如,但一定程度解决了相机方向的问题

这里我们用CubeMapMip来对应多个粗糙度下Prefliter的CubeMap

这里对于入射方向的随机性采用了蒙特卡洛积分,而specular lobe的大小(入射光方向限定范围)则采用了GGX重要性采样。

这里的蒙特卡洛积分采用了 Hammersley sequence

// ----------------------------------------------------------------------------
// http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
// efficient VanDerCorpus calculation.
float RadicalInverse_Vdc(uint bits)
{bits = (bits << 16u) | (bits >> 16u);bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);return float(bits) * 2.3283064365386963e-10; // / 0x100000000
}float2 Hammersley(uint i, uint N)
{return float2(float(i) / float(N), RadicalInverse_Vdc(i));
}

而重要性采样是基于GGX分布的

float3 ImportanceSampleGGX(float2 Xi, float3 N, float roughness)
{float a = roughness * roughness;float phi = 2.0 * PI * Xi.x;float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y));float sinTheta = sqrt(1.0 - cosTheta * cosTheta);float3 H;H.x = cos(phi) * sinTheta;H.y = sin(phi) * sinTheta;H.z = cosTheta;float3 up = abs(N.z) < 0.999 ? float3(0.0, 0.0, 1.0) : float3(1.0, 0.0, 0.0);float3 tangent = normalize(cross(up, N));float3 bitangent = cross(N, tangent);float3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z;return normalize(sampleVec);
}

最后和上面的Diffuse Irradiance一样进行RenderToCubeMap, 不过得考虑roughness分为5个等级进行渲染到TargetCubeMap对应的Mip中。

 const int MaxMipLevel = 5;const int MaxCubeMapFaces = 6;ID3D11RenderTargetView* rtvs[MaxCubeMapFaces];const float color[4] = { 0.0, 0.0, 0.0, 1.0f };XMMATRIX projMatrix = cubeCamera->GetProjMatrix();//第一,填充2D纹理形容结构体,并创建2D渲染目标纹理//Texture2DD3D11_TEXTURE2D_DESC cubeMapTextureDesc;ZeroMemory(&cubeMapTextureDesc, sizeof(cubeMapTextureDesc));cubeMapTextureDesc.Format = DXGI_FORMAT_R16G16B16A16_FLOAT;cubeMapTextureDesc.Width = textureWidth;cubeMapTextureDesc.Height = textureHeight;cubeMapTextureDesc.MipLevels = 0;cubeMapTextureDesc.ArraySize = MaxCubeMapFaces;cubeMapTextureDesc.Usage = D3D11_USAGE_DEFAULT;cubeMapTextureDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;cubeMapTextureDesc.CPUAccessFlags = 0;cubeMapTextureDesc.MiscFlags = D3D11_RESOURCE_MISC_TEXTURECUBE | D3D11_RESOURCE_MISC_GENERATE_MIPS;cubeMapTextureDesc.SampleDesc.Count = 1;cubeMapTextureDesc.SampleDesc.Quality = 0;HR(g_pDevice->CreateTexture2D(&cubeMapTextureDesc, NULL, &cubeMapTexture));//SRVD3D11_SHADER_RESOURCE_VIEW_DESC shaderResourceViewDesc;shaderResourceViewDesc.Format = cubeMapTextureDesc.Format;shaderResourceViewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE;shaderResourceViewDesc.Texture2D.MostDetailedMip = 0;shaderResourceViewDesc.Texture2D.MipLevels = MaxMipLevel; //-1会生成到1X1像素的MipMapHR(g_pDevice->CreateShaderResourceView(cubeMapTexture, &shaderResourceViewDesc, &srv));GDirectxCore->TurnOnRenderSkyBoxDSS();GDirectxCore->TurnOnCullFront();for (int mip = 0; mip < MaxMipLevel; ++mip){D3D11_RENDER_TARGET_VIEW_DESC envMapRTVDesc;ZeroMemory(&envMapRTVDesc, sizeof(envMapRTVDesc));envMapRTVDesc.Format = cubeMapTextureDesc.Format;envMapRTVDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY;envMapRTVDesc.Texture2D.MipSlice = mip;envMapRTVDesc.Texture2DArray.ArraySize = 1;int mipWidth = textureWidth * pow(0.5, mip);int mipHeight = textureHeight * pow(0.5, mip);D3D11_VIEWPORT envMapviewport;envMapviewport.Width = mipWidth;envMapviewport.Height = mipHeight;envMapviewport.MinDepth = 0.0f;envMapviewport.MaxDepth = 1.0f;envMapviewport.TopLeftX = 0.0f;envMapviewport.TopLeftY = 0.0f;float roughness =(float)mip / (float)(MaxMipLevel - 1);for (int index = 0; index < MaxCubeMapFaces; ++index){envMapRTVDesc.Texture2DArray.FirstArraySlice = index;g_pDevice->CreateRenderTargetView(cubeMapTexture, &envMapRTVDesc, &rtvs[index]);g_pDeviceContext->ClearRenderTargetView(rtvs[index], color);g_pDeviceContext->OMSetRenderTargets(1, &rtvs[index], 0);g_pDeviceContext->RSSetViewports(1, &envMapviewport);GShaderManager->prefilterCubeMapShader->SetMatrix("View", cubeCamera->GetViewMatrix(index));GShaderManager->prefilterCubeMapShader->SetMatrix("Proj", projMatrix);GShaderManager->prefilterCubeMapShader->SetFloat("Roughness", roughness);GShaderManager->prefilterCubeMapShader->SetTexture("HdrCubeMap", hdrCubeMap->GetTexture());GShaderManager->prefilterCubeMapShader->SetTextureSampler("TrilinearFliterClamp", GTrilinearFliterClamp);GShaderManager->prefilterCubeMapShader->Apply();cubeGameObject->RenderMesh();}for (int index = 0; index < MaxCubeMapFaces; ++index){ReleaseCOM(rtvs[index]);}}GDirectxCore->RecoverDefaultDSS();GDirectxCore->RecoverDefualtRS();

PreFilterHdrCubeMap.fx

float3 N = normalize(outa.SkyPos);float3 R = N;float3 V = R;const uint SAMPLE_COUNT = 1024;float3 perfilteredColor = float3(0.0, 0.0, 0.0);float totalWeight = 0.0;for (uint i = 0; i < SAMPLE_COUNT; ++i){// generates a sample vector that's biased towards the preferred alignment direction (importance sampling).float2 Xi = Hammersley(i, SAMPLE_COUNT);float3 H = ImportanceSampleGGX(Xi, N, Roughness);float3 L = normalize(2.0 * dot(V, H) * H - V);float NdotL = max(dot(N, L), 0.0);if (NdotL > 0.0){float D = DistributionGGX(N, H, Roughness);float NdotH = max(dot(N, H), 0.0);float HdotV = max(dot(H, V), 0.0);float pdf = D * NdotH / (4.0 * HdotV) + 0.0001;float resolution = 512.0; // resolution of source cubemap (per face)float saTexel = 4.0 * PI / (6.0 * resolution * resolution);float saSample = 1.0 / (float(SAMPLE_COUNT) * pdf + 0.0001);float mipLevel = Roughness == 0.0 ? 0.0 : 0.5 * log2(saSample / saTexel);perfilteredColor += HdrCubeMap.SampleLevel(TrilinearFliterClamp, L, mipLevel).rgb * NdotL;totalWeight += NdotL;}}perfilteredColor /= totalWeight;return float4(perfilteredColor, 1.0);

ConvolutedBRDF

这里就是对上面公式第二部分的卷积了

这里进行一定的化简(下面的F(wo, h)为菲涅尔函数)

因为 fr(p, wi, wo) 反射函数已经包含了F项

可以舍去分母F(wo, h),得到下面:

对这条简化的BRDF公式进行卷积计算,得到一张2D贴图LUT, 以NdotV和roughness作为 U,V轴,计算了IBL specular BRDF简化公式的值。

ConvolutedBRDFShader.fx, 这里得注意IBL的BRDF的几何遮挡函数k系数和点光源,平行光是不一样的

float GeometrySchlickGGXForIBL(float NdotV, float roughness)
{// note that we use a different k for IBLfloat a = roughness;float k = (a * a) / 2.0;float nom = NdotV;float denom = NdotV * (1.0 - k) + k;return nom / denom;
}float GeometrySmithForIBL(float3 N, float3 V, float3 L, float roughness)
{float NdotV = max(dot(N, V), 0.0);float NdotL = max(dot(N, L), 0.0);float ggx2 = GeometrySchlickGGXForIBL(NdotV, roughness);float ggx1 = GeometrySchlickGGXForIBL(NdotL, roughness);return ggx1 * ggx2;
}float2 IntegrateBRDF(float NDotV, float roughness)
{float3 V;V.x = sqrt(1.0 - NDotV * NDotV);V.y = 0.0;V.z = NDotV;float A = 0.0;float B = 0.0;float3 N = float3(0.0, 0.0, 1.0);const uint SAMPLE_COUNT = 1024;for (uint i = 0; i < SAMPLE_COUNT; ++i){// generates a sample vector that's biased towards the// preferred alignment direction (importance sampling).float2 Xi = Hammersley(i, SAMPLE_COUNT);float3 H = ImportanceSampleGGX(Xi, N, roughness);float3 L = normalize(2.0 * dot(V, H) * H - V);float NdotL = max(L.z, 0.0);float NdotH = max(H.z, 0.0);float VdotH = max(dot(V, H), 0.0);if (NdotL > 0){float G = GeometrySmithForIBL(N, V, L, roughness);float G_Vis = (G * VdotH) / (NdotH * NDotV);//V = Wofloat FC = pow(1.0 - VdotH, 5.0);A += (1.0 - FC) * G_Vis;B += FC * G_Vis;}}A /= float(SAMPLE_COUNT);B /= float(SAMPLE_COUNT);return float2(A, B);
}struct VertexIn
{float3 Pos:POSITION;float2 Tex:TEXCOORD;
};struct VertexOut
{float4 Pos:SV_POSITION;float2 Tex:TEXCOORD0;
};VertexOut VS(VertexIn ina)
{VertexOut outa;outa.Pos = float4(ina.Pos.xy, 1.0, 1.0);outa.Tex = ina.Tex;return outa;
}float4 PS(VertexOut outa) : SV_Target
{float2 color2 =  IntegrateBRDF(outa.Tex.x, outa.Tex.y);return float4(color2, 0.0, 0.0);
}

最终PrefliterCubeMap和ConvolutedBrdf结合在一起,如下:

 const float MAX_REF_LOD = 4.0;float3 prefliterColor = PrefliterCubeMap.SampleLevel(TrilinearFliterClamp, R, MAX_REF_LOD * roughness).rgb;float2 brdf = BrdfLut.Sample(clampLinearSample, float2(nDotv, roughness)).xy;float3 iblSpecular = prefliterColor * (ks * brdf.x + brdf.y);

最终的渲染结果

项目代码链接

https://github.com/2047241149/SDEngine

资料参考

[1]  https://learnopengl.com/PBR/IBL/Diffuse-irradiance

[2]  https://learnopengl.com/PBR/IBL/Specular-IBL

[3] https://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_slides.pdf

[4] https://matheowis.github.io/HDRI-to-CubeMap/

[5] https://github.com/shadercoder/PhysicallyBasedRendering

Directx11进阶教程PBR(3)之IBL相关推荐

  1. Directx11进阶教程PBR(2)之BRDF

    PBR单位 电磁辐射的测量是辐射度学.有很多的辐射量单位来描述物体表面接受的光照,但是图形渲染经常用RGB三色谱来作为单位, 也就是float3. BRDF基本属性 普通认为, 光照shading是在 ...

  2. javascript进阶教程第一章案例实战

    javascript进阶教程第一章案例实战 一.学习任务 通过几个案例练习回顾学过的知识 通过练习积累JS的使用技巧 二.实例 练习1:删除确认提示框 实例描述: 防止用户小心单击了"删除& ...

  3. JSP WEB开发入门基础到高手进阶教程002

    JSP WEB开发入门基础到高手进阶教程 -------开发入门 JSP与微软的Active Server Pages 兼容,但它是使用类似HTML的卷标以及Java程序代码段而不是VBScript. ...

  4. duilib进阶教程 -- 在MFC中使用duilib (1)

    由于入门教程的反响还不错,因此Alberl就以直播的形式来写<进阶教程>啦,本教程的前提: 1.请先阅读<仿迅雷播放器教程> 2.要有一定的duilib基础,如果还没,请先阅读 ...

  5. 爬虫进阶教程:极验(GEETEST)验证码破解教程

    原文链接及原作者:爬虫进阶教程:极验(GEETEST)验证码破解教程 | Jack Cui 一.前言 爬虫最大的敌人之一是什么?没错,验证码![Geetest]作为提供验证码服务的行家,市场占有率还是 ...

  6. 《Web前端开发精品课 HTML与CSS进阶教程》——1.4 id和class

    本节书摘来自异步社区<Web前端开发精品课 HTML与CSS进阶教程>一书中的第1章,第1.4节,作者: 莫振杰 更多章节内容可以访问云栖社区"异步社区"公众号查看. ...

  7. STM32 进阶教程 20 - 串口+DMA实现OneWire总线

    前言 One-wire总线使用一根并联总线完成对于多个设备的访问,通过上拉的OD门实现多设备的读写操作,通过ID区别设备,通过CRC5完成数据校验.常见对于one-wire总线的操作代码主要使用包含基 ...

  8. STM32 进阶教程 19 - IQmath数学库的使用

    前言 STM32 M3 系列是不带浮点运算单元的,小数运算都是采用定点转浮点试式实现的,本节给大家介绍一个很好用的定点转浮点数学运算库,IQmath是德州仪器 (TI) 的一个高度优化的高精度数学函数 ...

  9. STM32 进阶教程 18 – ADC间断模式

    前言 STM32 的ADC拥有连续扫描模式,也有间断模式,间断模式较扫描模式需要更多的触发事件才能完成所有的通道转换操作,在实际工程应用中,可以利用间断模式实现一些特殊应用.关于间断模式,在STM32 ...

最新文章

  1. PHP中的static静态变量的使用方法详解
  2. python观察日志(part25)--创建numpy数组
  3. 无限“递归”的python程序
  4. .NET Core微服务开发网篇-ocelot
  5. php 到精通 书,PHP从入门到精通——读书笔记(第20章:Zend Framwork框架)
  6. python tkinter获取屏幕大小_用 Python 制作关不掉的端午安康弹窗
  7. 【收藏】一份最新的、全面的NLP文本分类综述
  8. ERP实施过程中的沟通管理研究
  9. UnityWebPlayer缓存清理工具
  10. 使用显卡+hashcat破解握手包
  11. php根据参数跳转到指定网址,根据访问的域名跳转到指定目录的代码
  12. 逻辑函数的公式化减法
  13. 提取文件名+复制+改名+批量创建文件程序(Excel VBA版)
  14. 服务器bios上传文件,巧改BIOS设置 让文件服务器性能提升10%
  15. java编写分数加减法_JAVA 分数加减法
  16. 使用cookiecutter创建pyramid项目
  17. 学Java对一个专科生,对一个穷人来说,是一条好的出路吗?
  18. UVA10494 - If We Were a Child Again
  19. 编程语言排行榜2020年3月 TIOBE编程语言排行榜2020年最新版
  20. S3C6410开发板adc驱动代码分析及测试代码分析

热门文章

  1. 【单片机笔记】基于2G、4G通信的物联网数据方案及扫码支付方案
  2. android gps locationCb 数据
  3. [转贴]电脑使用者的眼睛保护须知
  4. 分布式存储Ceph中的逻辑结构Pool和PG
  5. 常见DB2锁等待解决流程
  6. win10wifi开关自动弹回_win10wlan开关自动弹回
  7. 计算机网络(6)体系结构:计算机网络协议、接口、服务的概念
  8. linux网络hack用法之onlink路由
  9. Adobe2023全家桶win及Mac系统安装包下载及安装教程ps、pr、ai、ae安装包下载
  10. Android Wifi --自动连接指定SSID(各种加密方式均可)