球谐光照

523 阅读6分钟

环境贴图最主要的技术实现方式有三种:立方体贴图、IBL和球谐光照。

之前已经说过立方体贴图和IBL,今天重点来聊一聊球谐光照。

在立方体贴图IBL的基本设置里,Convolution Type属性 除了设置为Specular(Glossy Reflection)之外,还可以设置为Diffuse(Irradiance)。Specular 是IBL用于模拟间接光照高光反射的,而Diffuse是模拟间接光照漫反射的。

间接光照漫反射(下文只称漫反射)的实现方式有两种:**一种是IBL,另外一种就是球谐光照。**先来看看IBL实现漫反射的方式。

IBL漫反射

漫反射区别于高光反射,它的颜色变化不会随视角的转动而发生变化,使用IBL实现漫反射,首先要将Convolution Type属性设置为Diffuse(Irradiance)。

Pasted image 20230414100043.png

高光反射的颜色会随着视角的变化而变化,而漫反射不会,在计算颜色时只和法线方向有关,因此,IBL漫反射的计算公式是

// 漫反射颜色的计算
float4 uv_ibl = float4(normal_dir, mip_level);
half4 color_cubemap = texCUBElod(_CubeMap, uv_ibl);
half3 env_color = DecodeHDR(color_cubemap, _CubeMap_HDR);

大家可以对比一下IBL高光反射计算的方式,看看二者的区别:

half4 colorCubeMap = UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0,reflect_dir,mip_Level);
half3 env_color = DecodeHDR(colorCubeMap,unity_SpecCube0_HDR);

在贴图尺寸设置上,如果只是用来实现漫反射效果,贴图的尺寸不需要太大,一般设置512*512就足够了。

漫反射效果,忽略AO,物体各处的光照强度基本一致,没有太多的强弱之分,在视觉上也呈现出温和的效果。

Pasted image 20230414102155.png

球谐光照

球谐光照最主要的应用就是用于计算间接光照(也叫环境光),这里的间接光照是指skybox所发出的光。

IBL也是计算间接光照,skybox发出的环境光有什么特点呢?它最大的特点skybox无限大,如此一来我们可以忽略掉位置信息而只考虑法线信息。也就是说,不管我们的模型多么大,在计算模型上某一个具体点的时候我们认为该点处于天空球中心。

不过IBL计算间接光照最大的缺点就是纹理采样。采用IBL的方法,我们需要存储一张立方体贴图,纹理采样的带宽对于电脑来说可能不算什么,不过对于手机而言就实在是有点奢侈了。

球谐光照可以说是为了弥补IBL的缺点而诞生的一种更加性能优化的计算方式。

这里不探讨球谐光照是如何实现的,因为其中涉及的数学模型计算实在是太复杂了。我们只关注在unity中如何实现球谐光照。

我们先要明白球谐光照的一个最基本原理,它是通过从立方体贴图中计算提取出七个球谐光照系数,然后通过一些复杂的数学计算生成光照颜色,最后赋予给物体。

这些系数的格式如下所示,仅供参考:

Pasted image 20230415103640.png

获取到这些系数后,然后把它传递到shader里面参与计算,计算的核心代码如下所示:


	float4 normalForSH = float4(normal_dir, 1.0);
	//SHEvalLinearL0L1
	half3 x;
	x.r = dot(custom_SHAr, normalForSH);
	x.g = dot(custom_SHAg, normalForSH);
	x.b = dot(custom_SHAb, normalForSH);

	//SHEvalLinearL2
	half3 x1, x2;
	// 4 of the quadratic (L2) polynomials
	half4 vB = normalForSH.xyzz * normalForSH.yzzx;
	x1.r = dot(custom_SHBr, vB);
	x1.g = dot(custom_SHBg, vB);
	x1.b = dot(custom_SHBb, vB);

	// Final (5th) quadratic (L2) polynomial
	half vC = normalForSH.x*normalForSH.x - normalForSH.y*normalForSH.y;
	x2 = custom_SHC.rgb * vC;

	float3 sh = max(float3(0.0, 0.0, 0.0), (x + x1 + x2));
	sh = pow(sh, 1.0 / 2.2);

	half3 env_color = sh;

光照探针

掌握了了球谐光照的基本原理,我们再来说说光照探针。

球谐光照是光照探针的使用基础,如果手动实现球谐光照,就要像上面一样又是计算光照系数,又是引入立方体贴图,还要在shader中进行大量复杂的计算。这无疑非常困难。

但unity已经给开发者封装好了这一系列操作,它就是光照探针!我们来看看光照探针是如何使用的。

全局光照探针

如果场景中没有放置光照探针,unity会默认使用一个全局光照探针,它是在Lightint面板中设置的。

不过,在此之前要先烘焙立方体贴图,这和使用反射探针的操作是一样的。

Pasted image 20230415110813.png

Environment Light是设置光照探针烘焙贴图的相关属性

Environment Reflections是设置反射探针烘焙贴图的相关属性

设置完成后,点击Generate Lighting按钮,就会在场景Scene文件夹下生成一张带有光照信息的立方体贴图。

第二歩、漫反射的计算。球谐光照的shader计算很复杂,但是使用unity内置的球谐光照计算方法ShadeSH9(),计算就会变得非常简单,不过在此之前,还需要设置LightMode、引入相关文件,具体如下:


Tags{"LightMode"="ForwardBase"}
CGPROGRAM
#pragma multi_compile_fwdbase
#include "AutoLight.cginc"
#include "UnityCG.cginc"
half3 env_color = ShadeSH9(float4(normal_dir,1.0));

打开Frame Debugger,在渲染流程中可以查看到球谐光照的系数信息

Pasted image 20230415112230.png

手动计算球谐光照,需要开发者自己在参数面板上设置球谐系数值,这些系数值的计算是一个非常复杂的过程,往往需要借助一些第三方工具。而使用全局光照探针和unity内置的球谐光照计算的函数,就免去了上述一系列复杂的操作。

对比一下手动计算球谐光照和使用光照探针计算球谐光照的效果,二者之间没有太大的差别。

Pasted image 20230415111749.png

局部光照探针

局部光照探针需要开发者手动添加,我们来看看该如何操作?

1、添加LightProbeGroup

Pasted image 20230415113533.png

2、进入edit 模式,排列布置探针小球的采样点位置

Pasted image 20230415113853.png

Pasted image 20230415114248.png

3、添加光源,光源模式设置为Baked

Pasted image 20230415114700.png

Pasted image 20230415115508.png

4、在Lighting面板中勾选“Baked Global Illumination”,然后点击“Generate Lighting”

Pasted image 20230415115838.png

5、最后看看效果,当物体移动时,物体表面的光照也会时刻发生变化。

ezgif-5-70a9c0e37b.gif

为什么会这样呢?这是因为烘焙贴图后,光照探针组的每一个光照小球,都带有球谐值。我们可以通过下面的设置,直接查看光照小球的信息。

Pasted image 20230415120853.png

点击物体模型,带线条的小球就是对物体光照产生影响的光照探针。当物体移动时,它附近的光照小球可以将携带的球谐信息进行插值混合运算,生成新的球谐系数,最后再赋予给物体的shader,从而照亮物体。

Pasted image 20230415160020.png