重要性采样概述
计算积分需要利用蒙特卡洛方法去近似,蒙特卡洛方法我这里就不讲了,有兴趣的可以看看我的知乎:蒙特卡洛方法的简单总结 - 知乎
重要性采样的目的就是加快收敛速度,所以选择pdf比较重要。
所以对于环境光采样,我们需要知道环境光每个方向上的概率密度。
环境光贴图
环境光贴图能用整个球的所有方向去采样,球面方向能转成极坐标θ[0, π]和φ[0, 2π]表示,所以我们用经纬图(long_latitude_map)来做环境光贴图。纬度可以用θ,经度用φ,例如地球仪的贴图展开:
采样long_latitude_map可以把方向转成θ和φ再转成uv来做采样:
float2 DirectionToPolar(float3 direction)
{
float3 normalizedCoords = normalize(direction);
float latitude = acos(normalizedCoords.y);
float longitude = atan2(normalizedCoords.z, normalizedCoords.x);
float2 sphereCoords = float2(longitude, latitude) * float2(0.5 / PI, 1.0 / PI);
return float2(0.5, 1.0) - sphereCoords;
}
float3 SampleEnviromentLight(float2 uv)
{
if (enviromentTextureMask == 1)
{
return _LatitudeLongitudeMap.SampleLevel(_LatitudeLongitudeMap_linear_repeat_sampler, uv, 0).rgb;
}
return enviromentColor;
}
在某些引擎中,uv的方向和经纬方向不一定是相同的,例如Unity,uv是从左下角开始。
而沿着贴图u的方向,也不一定是从0到2π,例如在左手左边系下,沿着φ方向是2π到0,见下图:
下面是左手坐标下的俯视图,表面了u和φ的关系:
如果我们把u从φ = π开始,沿着u的方向φ的范围就是[-π, π]了。
所以映射到uv的公式是:
环境光贴图重要性采样
构建分布
由于环境光贴图是一个离散的二维像素组成,没有通用函数,所以分布也是一个二维的分布。
但分布仅仅是用像素的亮度来决定吗?
我们可以看地球仪的展开图,极点坐标展开后,足足占了一整行像素,也就是说采样的频率和第下面赤道附近的频率一样,这样明显不符合分布,所以我们需要乘以一个sinθ来改变他们的分布,也就是说维度越高的,概率会越低。
我这里手画了一个图方便理解:
所以最后的分布构建是:亮度乘以sinθ。
for (int v = 0; v < height; ++v)
{
float vp = (float)v / (float)height;
float sinTheta = Mathf.Sin(Mathf.PI * (float)(v + 0.5f) / (float)height);
for (int u = 0; u < width; ++u)
{
float y = pixels[u + v * width].Y();
float distribution = y;
if (distribution == 0)
distribution = float.Epsilon;
distributions[u + v * width] = distribution * sinTheta;
}
}
概率密度函数转换推导
前面说了这么多,我们最后的目的是采样环境光贴图的uv来构建光照方向,然后计算出p(ω)来做蒙特卡洛计算,ω可以通过θ和φ构造。
然而我们的分布是通过θ和φ构造的,所以必须做一个分布的转换满足(θ, φ) -> (u, v)。
以下参考pbrtv3。
首先构建映射函数:
根据概率密度的转换函数:
这里设x是(u, v),y是(θ, φ),Jg(u,v) = 2π²,最后得出分布函数的转换:
p(ω) = p(θ, φ) / sinθ,代入最后得到:
代码如下:
float3 ImportanceSampleEnviromentLight(float2 u, out float pdf, out float3 wi)
{
float mapPdf = 0;
pdf = 0;
wi = 0;
float2 uv = Sample2DContinuous(u, mapPdf);
if (mapPdf == 0)
return float3(0, 0, 0);
float theta = (1.0 - uv.y) * PI;
float phi = (0.5 - uv.x) * 2 * PI;
float cosTheta = cos(theta);
float sinTheta = sin(theta);
float sinPhi = sin(phi);
float cosPhi = cos(phi);
//left hand coordinate and y is up
float x = sinTheta * cosPhi;
float y = cosTheta;
float z = sinTheta * sinPhi;
wi = float3(x, y, z);
// Compute PDF for sampled infinite light direction
pdf = mapPdf / (2 * PI * PI * sinTheta);
if (sinTheta == 0)
{
pdf = 0;
return 0;
}
return _LatitudeLongitudeMap.SampleLevel(_LatitudeLongitudeMap_linear_repeat_sampler, uv, 0) * 100;
}
效果对比
64spp,importance sampling envmap
64spp,uniform sampling envmap