我们经常会听到立方体贴图、经纬图、全景图这样的概念,它们描绘的都是一种能从四周观察到周围环境的高动态范围(HDR)的图片,统称为环境贴图。
环境贴图是一种用于模拟高度反射物体表面反映周围环境的技术,常见的环境贴图主要有三种:立方体贴图(CubeMap)、经纬度全景图(LatLongMap)以及球面环境图(SphereMap)。它们之间本质上没有什么区别,只不过排版布局形式不一样而已,下面是环境贴图常见的集中布局形式。
unity中常用的环境贴图是立方体贴图(CubeMap),它采用的是十字形的布局形式,
用一个简单的比喻来解释立方体贴图的形成:物体的中心位置架设一台全景相机,在场景中表现为一个长方体, 长方体的每个面都会拍摄它正前方的场景图像 , 这样就以相机为中心,就能获取到它上、下、前、后、左、右六个方向的图像, 这六张图片会被存储为一个立方体贴图(CubeMap),提供给具有反射材质的物体使用。
设置
unity中新建立方体贴图有两种方法:第一种是将hdr格式的全景图片导入项目中,设置图片的Texture Shape 为cube,这种方式是最方便快捷的。
第二种使用一种最古老的方式:create/Legacy/Cubemap,这种方式需要准备六张同一位置不同方向角度(前后、上下、左右)的图片,指定后unity会为自动生成一张立方体贴图。
采样
环境贴图的采样过程也很好理解,一束光照射到物体表面时会产生反射,反射光线会与立方体的其中一个面产生一个交点,只需要对这个交点进行采样,就能得到顶点的颜色像素值。
立方体贴图有六个面,首先需要判断顶点的反射光线与哪个面相交,这个判断方法非常简单:顶点坐标哪个分量的绝对值最大,顶点反射光就与哪个面相交。
比如顶点坐标V(-0.5,0.3,-0.1),分量绝对值最大是-5,因此这个顶点的反射光与-x指向的面相交。
知道了相交面,下一步就是求相交点,相交面标准化,它的其中一个面的分量值是确定的。比如,立方体贴图-x轴指向的面,它的x轴坐标是-1,那么只要让反射矢量的坐标乘以某个标量,让矢量的x分量也等于-1,矢量的yz分量就是交点的yz值。
举个例子反射矢量是(-0.5,0.3,-0.1),乘以2后等于(-1,0.6,-0.2),那么相交点的坐标就是(-1,0.6,-0.2)。
由于立方体贴图采样只关注方向而忽略了位置,因此它在平坦反射表面上的效果很不真实,相对的,它在曲面上可以取得较好的视觉效果。
代码
下面就来看看在代码中如何实现立方体贴图的采样。根据采样原理的理解,要获取到顶点的切线空间坐标、光源方向、法线方向和反射方向。具体代码如下:
half3 normal_dir = normalize(i.normal_world);
half3 normalData = UnpackNormal(tex2D(_NormalMap,i.uv));
//切线方向
half3 tangent_dir = normalize(i.tangent_world);
// 双切线方向
half3 binormal_dir = normalize(i.binormal_world);
// 法线方向
normal_dir = normalize(tangent_dir * normalData.x + binormal_dir * normalData.y + normal_dir*normalData.z);
// 观察方向
half3 view_dir = normalize(_WorldSpaceCameraPos.xyz - i.pos_world);
// 反射方向
half3 reflect_dir = reflect(-view_dir, normal_dir);
得到反射方向后,直接使用texCUBE方法,传入反射矢量,对贴图进行采样
// properties
_CubeMap("Cube Map", Cube) = "white"{}
//var
samplerCUBE _CubeMap;
float4 _CubeMap_HDR;
//frag
half4 colorCubeMap = texCUBE(_CubeMap,reflect_dir);
half3 env_color = DecodeHDR(colorCubeMap,_CubeMap_HDR);
通过以上两步,基本能实现对立方体贴图采样的功能。
立方体贴图拥有丰富的颜色细节,直接采样产生的效果可能会过曝,因此通常会配合Bloom 、ACES ToneMapping等后期技术来使用。
反射探针
讲了这么多立方体贴图底层技术的实现,但实际项目中可能并不需要我们这么做。在Unity项目中,我们可以为物体添加反射探针(ReflectionProbe)来实现物体的反射效果,反射探针就是根据立方体贴图的技术原理封装而成的。
默认情况下,Unity设置了一个全局反射探针,我们只需要在window-rendering-light这里点击"Generate Lighting",项目就会自动烘焙一张天空盒子的立方体贴图保存在场景目录下
可在Environment中调节全局反射探针的效果。
另外,也可以"右键/light/reflection Probe"中添加局部反射探针,点击Bake,此时反射探针会自动生成一个天空盒子的环境贴图。
但此时,反射探针并不能反射周围其他物体,如果要反射其他物体,那么其他物体需要勾选“reflection Probe Statics”,然后重新Bake
在优先级上,局部反射探针的优先级别要高于全局反射探针。