todo:对天空盒的实现原理感觉理解的并不透彻,比如深度测试的细节等,回头有时间再深入钻研.
这一篇是对环境光照(漫反射)进行更精确的渲染,涉及到Monte Carlo积分,笔者数学功底不好,一开始在理解公式上浪费了些时间。
另外:求辐照度卷积,其实是只求天空盒正中间的一个点,各个方向的辐照度卷积值(漫反射),渲染物体时,方向相同的点采样的漫反射强度是一样的,参考Article - Physically Based Rendering 或者教程中所述,实际工程中,会选空间中4个Position来计算四分卷积图,其他的图从这四个Position对应的采样进行差值运算
一、背景:
1. 为什么要用IBL
对环境光照进行积分,在理论上是可行的,也更精确,而计算机运算是基于离散的值,只能说不断扩大采样,无限逼近理论值,这在工程领域是不可取的的,有巨大的计算资源消耗。
所以采用IBL的思路,把环境光照通过Monte Carlo积分的方式简化,再通过一张辐照度纹理存储实现预计算,真正渲染的时候,是通过对辐照度纹理取值。
learnOpengl这一章讲述的不太好,可以参考Article - Physically Based Rendering来理解。
2.蒙特卡洛积分
备注一个歧义点:
更新[1],再认真看一遍,教程上说的是采用黎曼和来求积分,为啥这两种积分的结果不一样呢?,可能是取平均值,除以积分总数,1/n1n2 并不属于黎曼和的范畴??但是为啥要求平均值呢?所有的光照是真实的照在物体上的,求平均值干啥?这里先过吧,卡了好几天了,不能再浪费时间了,项目快做不完了。
更新[2],这个是求间接环境光照,就是一种算法实践,求平均值,是为了逼近真实环境光的效果,如果不求平均,那太亮了
learnOpengl-IBL 教程中关于蒙特卡洛积分公式貌似写的有问题,π写在分母上了
![](https://img.haomeiwen.com/i3144284/a3b8d72355f964c0.png)
![](https://img.haomeiwen.com/i3144284/68d5949bf23f7731.png)
Article - Physically Based Rendering一文中阐述的不同:
![](https://img.haomeiwen.com/i3144284/e2e2729a41f736bd.png)
二、渲染环境贴图
为IBL实现打基础,这里也有不少细节,每一步都需要花时间吃透
![](https://img.haomeiwen.com/i3144284/5bcda2afa7e4be57.png)
涉及的知识点依次说明
1. 2D的环境贴图采样成立体贴图处理
uniform sampler2D equirectangularMap;
const vec2 invAtan = vec2(0.1591, 0.3183);
vec2 SampleSphericalMap(vec3 v)
{
vec2 uv = vec2(atan(v.z, v.x), asin(v.y));
uv *= invAtan;
uv += 0.5;
return uv;
}
void main()
{
vec2 uv = SampleSphericalMap(normalize(localPos)); // make sure to normalize localPos
vec3 color = texture(equirectangularMap, uv).rgb;
FragColor = vec4(color, 1.0);
}
代码说明:
![](https://img.haomeiwen.com/i3144284/de2546cd1f823e1d.png)
- 根据position求经纬度, asin(v.y),根据y坐标求角度,注意半径r已经归一化处理成了1,注意图中的角度的规则;atan(v.z, v.x)求经度,同样注意角度的取值规则,这里网上的很多帖子都一笔带过,笔者在这里竟然琢磨半天
- 求出来的角度是弧度值,asin取值范围[-π/2, π/2],atan取值范围[-π, π],需要归一化到[0,1]之间,因为环境贴图纹理是[0, 1]的取值范围。
这里注意invAtan是个常量值,用来归一化
invAtan = vec2(0.1591, 0.3183)
0.1591 = 1/6.28319(=>2PI)
0.3183 = 1/3.14159(=>PI)
invAtan参考stackflow:https://stackoverflow.com/questions/48494389/how-does-this-code-sample-from-a-spherical-map/48534536
asin atan取值参考 opengl文档 https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/
剩下的逻辑不复杂,绘制完天空盒后,再按照上一篇绘制球体和光照
完整代码参考:https://github.com/JoeyDeVries/LearnOpenGL/tree/master/src/6.pbr/2.1.1.ibl_irradiance_conversion
画图是个好习惯,梳理思路:
![](https://img.haomeiwen.com/i3144284/1a61a73441230171.png)
三、IBL
渲染环境光照卷积![](https://img.haomeiwen.com/i3144284/ee1efe61bc7f6c17.png)
![](https://img.haomeiwen.com/i3144284/571b96e6148b13c9.png)
求光照卷积这一段中TBN切面空间是怎么算的没懂,苦思不得求解
更新[1] 目前能想到的是,根据worldPos传进来的坐标(作为法线),更新半球方向,从Model坐标 -->转换到世界坐标。注意,球面上每一个方向的卷积积分的角度是不一样的。
vec3 irradiance = vec3(0.0);
vec3 up = vec3(0.0, 1.0, 0.0);
vec3 right = cross(up, normal);
up = cross(normal, 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)
vec3 tangentSample = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta));
// tangent space to world
vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N;
irradiance += texture(environmentMap, sampleVec).rgb * cos(theta) * sin(theta);
nrSamples++;
}
}
irradiance = PI * irradiance * (1.0 / float(nrSamples));
网友评论