Games202(2·第4章,第5章)
第4章
优化性能
如何优化pcf以及pcss的性能呢,在区域计算时,随机采样指定数目的像素,而不是区域内的所有像素,但是这样会导致噪声,
另一种方法不再计算区域内的每个像素(比如8*8=64),而是通过转为正太分布的pdf,估计概率,这就是vssm
vssm (variance soft shadow mapping)方差软阴影映射
vssm正是为了解决软阴影实现的性能问题,那么为了映射为正太分布,我们需要求出曲线的均值,和方差,然后求出pdf中满足一定区域的概率
求均值:
- mipmap:它只能在正方形内查询,并且它是不准的
- SAT(summed area tables):可以在长方形查询,结果是准确的
- sat算法是为了解决快速区域查询的,它的思想是前缀和表
- 为了理解,我们首先看一维,比如我要快速获取原数组的和,可以通过新建一个前缀和数组(遍历n次,每次累加的值存储到新数组中),在区域查询时直接从新数组获取ab的索引,两者相减即可
- 对于2维同样,不过ij的值需要:(K为区域的半径)
- sum = P[i + K + 1][j + K + 1] - P[i - K][j + K + 1] - P[i + K + 1][j - K] + P[i - K][j - K]
- 对于查询时需要:[i + K, j + K] - [i - K, j + K] - [i + K, j - K] + [i - K, j - K]
求方差: 我们利用了公式:x平方值的期望 - x期望值的平方 = 方差
因为shadowmap中存储的depth就是x,区域的均值就是期望,因此只需要在计算shadowmap时,额外生成一张shadow map,它保存的是depth^2即可
求概率
CDF : 累积分布函数,是pdf的积分 == 函数的面积
但是也可以通过切比雪夫不等式 在知道 期望 和 方差 时候 近似求得x > t(必须在均值的右边)时的概率
正太分布
对于连续性随机变量X(样本到实数映射的函数),其中ab区间的概率 == ab区间的面积
正态分布又称高斯分布函数:通常会呈现钟形曲线,中间区域概率高,两侧的概率低,且不想左右偏移:
- 平均值 = 中位数 = 众数
- 沿中线对称
- 50% 的值小于平均值,50%的值大于平均值
正太分布由几个概念组成:
- 均值μ:决定了正态分布曲线的中心位置,也是分布的期望值
- 标准差σ:决定了正态分布曲线的形状,标准差越大,曲线越分散;标准差越小,曲线越集中
- 方差σ^2:方差是标准差的平方,表示数据离散程度的度量
第5章
SDF(Signed Distance Function)
函数返回空间中任何一点,到某个物体的最小距离,sdf(p)= d,其中返回值有正负,负数代表在物体内部,正数则表示在物体外部(是一个矢量)
应用:
Ray Marching光线步进:
如同光栅化/ray tracing一样,它也是渲染的一种方式,类似光线追踪,都是找到光线在scene中的交点,但不同的是,它是按照步进的方式,而不是一次性的和场景求交
那么每次走多远呢?为了保证不会超过物体,每次走sdf的距离,可以看图,从p0点以sdf为半径画圆,沿着光线方向走sdf的距离到达p1点,……逐渐步进
直到距离物体足够接近求交,或者步进超过了指定次数,还距离物体非常远,停止步进
软阴影
我们知道不同的shading point阴影的过滤程度不同(和遮挡物和被投射物体的距离有关),我们前面对pcss做了优化vssm,但是我们一般不会使用vssm,还有其他方式生成软阴影,我们将利用sdf
以着色点p为起点和光源连线,以sdf(p)为半径画圆,在以p为起点和圆做切线,这里的连线和切线的夹角就是safe angle
safe angle越小,阴影越黑,越趋近于硬阴影;afe angle越小,阴影越黑,越趋近于硬阴影;
可以想象随着遮挡物和被投射物体的距离越近,sdf越小,那么safe angle越小,阴影越趋近于硬阴影,完全符合软阴影的性质
但是如何求出这个safe angle呢?
- 我们可以求出圆的半径 = sdf(p),可以求出p-0,那么利用反正弦函数arcsin(正弦:sin(theta) = float, arcsin(float) = theta)就可以轻松求出夹角
- 但是arcsin非常消耗性能,因此用另一种方法,其中k越小,半影区域越大,越接近软阴影效果.K越大,半影区域越小,越接近硬阴影效果.
Global Illuminace
全局光照(直接+间接(环境光))
Microfacet model 计算直接光照
Microfacet model是pbr模型之一
每个光源都要对p点(Blinnphong求得贡献 * brdf)仅计算一次贡献(光源会向四周发生无数光线,但只有一条wi->p方向的光线会照射到p点,产生直接光照贡献)后求和
IBL(image-based lighing)基于图像的照明
间接/环境光照可以通过ibl的方法求得,ibl将周围环境都作为次级光源,因此它不考虑可视项
我们将使用渲染方程来求得环境光,因为有大量的次级光源,因此用积分采样半球方向(蒙特卡洛),并且如果要使用基于物理的模型,那么就既使用积分又基于物理,所以用渲染方程是非常合适的(它既包含积分又包含物理)
分解渲染方程
为了简化计算,通常将渲染方程分解为漫反射和镜面反射两部分
环境光漫反射部分
为了加快运行时计算的速度,采用预计算
- 预计算环境贴图(通常用cubemap/sh存储)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//离散采样
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 球坐标转换笛卡尔坐标
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++;
}
}
//求平均 并*brdf
irradiance = PI * irradiance * (1.0 / float(nrSamples));
展开
- 预计算辐照度图(通常用cubemap/sh存储,记录以每个可能的 法线半球朝向 离散采样wi(根据球坐标) 求和平均 的辐照度),这样就可以直接根据某个法线朝向直接采样L的结果(texture())
- 其中公式末尾多出的sin,因为每个采样近似代表了半球上的一小块区域,天顶角 θ 变高,半球的离散采样区域变小,我们使用 sinθ 来平衡区域贡献度
- 漫反射部分,brdf项为常数(和视线方向无关)可以直接拆出积分,辐照度图也就直接 * brdf项,也就是现在的辐照度图比较特殊,存储的是 周围的间接光 累积的 反射率的 漫反射部分的 积分
- 根据p点法线朝向直接采样获得漫反射部分积分结果
环境光镜面部分
- 由于镜面部分的brdf非常数,不可以拆出积分,因此根据微积分的不等式性质,两个函数乘积的积分 近似为两个函数积分的乘积
求逆累积分布函数,其中u是[0,1]区间内的随机数,逆累积分布函数可以将均匀分布的随机数映射到目标分布(GGX 分布)上。它会根据 GGX 分布的概率密度,自动地在概率高的区域生成更多的采样点,在概率低的区域生成较少的采样点
或者根据这个,e1代表xi_x,e2代表xi_y
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness)
{
//将随机数xi 映射到 球坐标的方位角 ϕ 和极角 θ
float a = roughness*roughness;
float phi = 2.0 * PI * Xi.x;//方位角 ϕ:Xi.x 乘以 2π,可以将其映射到 [0,2π]的范围
//极角的余弦值 cosθ_GGX:
//这里需要 根据目标概率 GGX 分布积分,得到累积分布函数(CDF),对cdf反演,得到逆累积分布函数(Inverse CDF)
float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y));
float sinTheta = sqrt(1.0 - cosTheta*cosTheta);//极角的正弦值 sinθ_GGX: 根据sin^2θ + cos^2θ = 1 公式
// 从球坐标转换为笛卡尔坐标
vec3 H;
H.x = cos(phi) * sinTheta;
H.y = sin(phi) * sinTheta;
H.z = cosTheta;
// 从切线空间转换到世界空间
vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
vec3 tangent = normalize(cross(up, N));
vec3 bitangent = cross(N, tangent);
vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z;
return normalize(sampleVec);//返回生成的采样方向
}
展开
- 根据GGX重要性采样生成有偏采样方向
- 镜面反射依赖于表面的粗糙度,反射光线可能比较松散,也可能比较紧密,但是一定会围绕着反射向量r的镜面波瓣区域(a越高,波瓣越大),除非表面极度粗糙,因此采样时按照反射方向选取采样向量是有意义的
- 在这之前会利用低差异序列生成样本向量,然后根据D项(法线分布项)实现重要性采样
- 这次使用预计算滤波环境贴图,而非预计算辐照度图,即根据粗糙度设置不同的mipmap级别
- 外循环maxMipLevels次,内循环6次生成以a级别的mipmap cubemap
- 根据ImportanceSampleGGX生成的采样方向是H,已知V,根据反射定律计算wi方向
- 内部不再使用球坐标无偏采样,使用GGX重要性采样有偏采样,最后依旧累加求均值
- 对于预计算BRDF部分参数很多,但我们也可以简化后预计算,结果称为BRDF积分贴图
- 由于镜面部分依赖于视角方向,所以怎样预计算呢?可以假设视角方向v == 输出采样方向ωo,这样就不需要关心视角方向了
- 就可以在给定粗糙度、光线 ωi(GGX) , 法线 n(已知) , 夹角 n⋅ωi (已知)的情况下,预计算 BRDF
- n⋅ωi作为横坐标,以粗糙度作为纵坐标
- 采样方向的权重
- 合并:漫反射部分(L * F) + 镜面部分(L * F)