PBR渲染
参考Shader学习 (18)PBR光照模型效果分析手动复现 - 九猫的文章 - 知乎
https://zhuanlan.zhihu.com/p/364932774
PBR效果可以拆为两大部分,直接光照和间接光照。在直接光照和间接光照的效果中,都能再分为漫反射和镜面反射两种计算。
直接光照计算的是灯光在第一次接触到物体的时候产生的计算结果。而间接光照计算的就是光线从物体上被反射出去之后的效果。
直接光照
漫反射部分单独拿出来就是很常见的Lambert模型,一个简单的从亮到暗的过渡。
再说直接光照的镜面反射,拆分为几个更基础的效果:镜面高光、几何遮蔽、菲涅尔。
间接光照
漫反射的间接光使用球谐光照来实现,而镜面反射的间接光照由CubeMap来实现。
漫反射间接光照:(环境中其他物体表面反射的光线照射到当前物体上产生的漫反射效果")
球谐光照的的作用是为非金属的材质添加间接光照,它只受到金属度这一个参数的影响,当物体是金属时,不受到漫反射间接光照(球谐光照)的影响。
镜面反射间接光照:
CubeMap就是一张全景HDR图,CubeMap在制作或者说拍摄的时候,有球面映射的(较多)和立方体映射的。
镜面反射的显示收到两个参数的影响:Metallic和Roughness 。
金属物体反射的光线会带有金属本身的颜色,而非金属物体不论本身是什么颜色,它们反射的光线都是光线原本的颜色。金属物体的镜面反射的强度更高
粗糙度控制CubeMap的Mip等级,Mip等级越高,贴图就越模糊。越粗糙的物体反射的光线越少,而越光滑的物体镜面反射就越强。(贴图的MipMap技术在各个游戏和影视中的应用非常广泛,简单来说就是把一张贴图缩小为多个等级的更小分辨率的图片保存下来,游戏运行时根据物体和摄像机的距离读取不同分辨率的图片。)
在相同粗糙度的时候,金属材质颜色更暗一些,金属吸收的能量更多
能量守恒:在PBR中,光线照射到物体表面时,会被分为两部分:一部分被反射(镜面反射),另一部分进入物体内部发生散射(漫反射)。总反射光不能超过入射光,即满足能量守恒。
金属与非金属的区别:
- 非金属(电介质):光线进入物体内部,经过多次散射后部分光线重新射出表面形成漫反射,只有少部分光线在表面直接反射(镜面反射)。因此,非金属的漫反射较强,镜面反射较弱。
- 金属(导体):光线进入金属表面后,由于金属内部的自由电子会迅速吸收光线(转化为热能),所以几乎没有光线能够重新射出表面(即没有漫反射),大部分光线被反射(镜面反射)。因此,金属的镜面反射很强,漫反射几乎没有。
颜色与明暗:
- 非金属:漫反射的颜色由物体表面的色素(即Albedo颜色)决定,所以看起来颜色较亮(有漫反射贡献)。
- 金属:由于没有漫反射,只有镜面反射,而镜面反射的颜色通常由入射光颜色和金属本身的颜色共同决定(即Fresnel项中的F0是金属的颜色)。但是,金属会吸收大部分进入内部的光线,导致其整体看起来更暗(因为只有反射部分可见,而反射只发生在特定方向,其他方向则较暗)。
代码解读
Li(p,ωi)代表的是物体表面某一点p的位置上在ωi方向上观察到的所有光线的总和。也可以理解为光源的颜色。
base_color:点云的“固有色”。
Lo:出射光(最终pbr颜色:这个点在当前光照、视角、材质等条件下,真实应该呈现出来的颜色。)
fdiffuse:直接光照漫反射 BRDF
fspecular:直接光照镜面反射 BRDF
n⋅ωi:余弦项(法线与入射光方向的点积),光线入射角θi的余弦值
直接光照镜面反射 | D/G/F三项完整实现 | f_s=GGX_specular函数 |
直接光照漫反射 | Lambert + 能量守恒 | f_d = base_color/π |
间接光照镜面反射 | 环境贴图采样 | global_incident_lights |
间接光照漫反射 | 球谐函数 | local_incident_lights |
几何遮蔽(间接光) | 预计算可见性 | incident_visibility |
间接光照 | 物体接收的总入射光,公式中的Li(p,wi) | incident_lights = local_incident_lights + global_incident_lights* incident_visibility |
def rendering_equation(base_color, roughness, normals, viewdirs,incidents, direct_light_env_light=None,visibility_precompute=None, incident_dirs_precompute=None, incident_areas_precompute=None):#brdf_color, extra_results = rendering_equation(# base_color, roughness, normal.detach(), viewdirs, incidents,# direct_light_env_light, visibility_precompute=pc._visibility_tracing, # incident_dirs_precompute=pc._incident_dirs, incident_areas_precompute=pc._incident_areas)incident_dirs, incident_areas = incident_dirs_precompute, incident_areas_precompute#之前计算的采样方向和面积权重,# (num_pts, num_sample, 3), (num_pts, num_sample, 1)deg = int(np.sqrt(incidents.shape[1]) - 1)global_incident_lights = direct_light_env_light.direct_light(incident_dirs)#间接光照镜面反射,对应方向的环境光辐射亮度(RGB值:包含颜色和光强)local_incident_lights = eval_sh(deg, incidents.transpose(1, 2).view(-1, 1, 3, (deg + 1) ** 2), incident_dirs).clamp_min(0)#间接光照漫反射(球谐函数计算)incident_visibility = visibility_precompute#每个高斯体、每个采样方向的高斯的可见性global_incident_lights = global_incident_lights * incident_visibilityincident_lights = local_incident_lights + global_incident_lights#间接光照,物体接收的总入射光,(num_pts, num_sample, 3)n_d_i = (normals[:, None] * incident_dirs).sum(-1, keepdim=True).clamp(min=0)#法向量与采样方向的点积,(num_pts, num_sample, 1)f_d = base_color[:, None] / np.pif_s = GGX_specular(normals, viewdirs, incident_dirs, roughness, fresnel=0.04)transport = incident_lights * incident_areas * n_d_i # (num_pts, num_sample, 3)specular = ((f_s) * transport).mean(dim=-2)#镜面反射分量pbr = ((f_d + f_s) * transport).mean(dim=-2)diffuse_light = transport.mean(dim=-2) #入射辐照度(Irradiance):描述表面接收的总光能量,尚未考虑材质如何反射或散射这部分能量。
张量与维度
见张量与维度-CSDN博客