版权声明:Davidwang原创文章,严禁用于任何商业途径,授权后方可转载。
阴影在现实生活中扮演着非常重要的角色,通过阴影我们能直观的感受到光源位置、光源强弱、物体离地面高度、
物体轮廓等,在大脑中构建环境空间信息。阴影的产生与光源密切相关,阴影的产生也与环境光密切相关。阴影还影响人对空间环境的判断,是构建立体空间信息的重要参考因素。
与真实世界一样,在数字世界中阴影的生成也需要光源,同时,在AR中阴影生成与 VR 相比有很大不同,VR是纯数字世界,在其环境中肯定能找到接受阴影的对象,但 AR 中却不一定有这样的对象,如将一个AR虚拟物体放置在真实桌面上,这时虚拟物体投射的阴影没有接受物体(桌面并不是数字世界中的对象,无法直接接受来自虚拟物体的阴影),因此就不能生成阴影。为使虚拟物体产生阴影,我们的思路是在虚拟物体下方放置一个接受阴影的对象,这个对象需要能接受阴影但又不能有任何材质表现,即除了阴影部分,其他地方需要透明,这样才不会遮挡现实世界中的物体。
(一) ShadowMap技术原理
在Unity中,实现实时阴影混合了多种阴影生成算法,根据要求可以是shadowmap、screen space shadowmap、Cascaded Shadow等,shadowmap按技术又可以分为Standard Shadow Mapping、PCF、PSM、LISPSM、VSM、CSM / PSSM等等。之所以分这么多阴影生成算法,最主要的目的是平衡需求与复杂度。阴影生成的理论本身并不复杂,有光线照射的地方就是阳面(没有阴影),没有被光照射的地方就是阴面(有阴影),但现实环境光照非常复杂,阴影千差万别,有在太阳光直射下棱角特别分明的阴影,有在只有环境光照下界线特别不分明的超软阴影,也有介于这两者之间的阴影,目前没有一种统一的算法可以满足各种阴影生成要求,因此发展了各类阴影算法处理在特定情况下阴影的生成问题。
ShadowMap技术需要进行两次渲染,第一次从灯光的视角渲染一张RenderTexture,将深度值写入到纹理中,这张深度纹理称之为深度图,第二次从正常的摄像机视角渲染场景,在渲染场景时需要将当前像素到灯光的距离与第一次渲染后的深度图中对应的深度信息作比较,如果距离比深度图中取出的值大就说明该点处于阴影中,如下图所示。
在第一次渲染中,从灯光视角出发,渲染的是灯光位置可见的像素,记录这些深度信息到深度图中;第二次从摄像机视角渲染场景,这次渲染的是摄像机位置可见的像素,如E点,计算E点到灯光的距离L2,将L2与深度图中对应的深度值作比较,从图中可以看到,在第一次渲染时,与E点对应的深度信息是L1,并且L1<L2,这说明从灯光的视角看,E点被其他点挡住了,因此E点不能被灯光照射到,即E点处于阴影中(特别需要注意的是距离比较一定要在同一坐标空间中才有意义)。
(二)使用实时阴影
如前所述,阴影生成一方面需要有光源,另一方面还需要有一个接受并显示阴影的载体。本节中我们将采用Unity内置的阴影解决方案生成AR实时阴影,光源采用Directional Light,使用一个Plane做阴影授受对象。
首先,我们制作一个接受阴影且透明的Plane,在Project窗口中新建一个Shader,命名为ARShadow,编写以下cg代码,该shader的功能就是显示阴影。
Shader "Davidwang/MobileARShadow"
{
SubShader{
Pass {
Tags { "LightMode" = "ForwardBase" "RenderType" = "Opaque" "Queue" = "Geometry+1" "ForceNoShadowCasting" = "True" }
LOD 150
Blend Zero SrcColor
ZWrite On
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#pragma multi_compile_fwdbase
#include "AutoLight.cginc"
struct v2f
{
float4 pos : SV_POSITION;
LIGHTING_COORDS(0,1)
};
v2f vert(appdata_base v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
TRANSFER_VERTEX_TO_FRAGMENT(o);
return o;
}
fixed4 frag(v2f i) : COLOR {
float attenuation = LIGHT_ATTENUATION(i);
return fixed4(1.0,1.0,1.0,1.0) * attenuation;
}
ENDCG
}
}
Fallback "VertexLit"
}
然后新建一个材质,亦命名为ARShadow,选择shader为刚才变编写的ARShadow.shader。在Hierarchy窗口中新建一个Plane,将其Scale改小一点,修改为(0.1,0.1,0.1),然后将ARShadow材质赋给它,并制作成Prefab,命名为ARPlane,删除Hierarchy窗口中的Plane,到此,接受阴影的平面制作完成。
在Hierarchy窗口选择Directional Light,然后在Inspector窗口中进行设置。先测试一下硬阴影,将Shadow Type设置为Hard Shadows,其余参数设置如下图所示,详细参数后文会说明。
在Unity菜单栏依次选择 Edit ->Project Settings,打开Project Settings对话框,选择Quality选项卡,点击其右侧Android下的黑色小三角图标,在下拉菜单中选择Very High或者Ultra,然后选择Shadows为Hard Shadows Only,选择Shadow Projection为Close Fit,如下图所示。
在Project窗口中新建一个C#脚本,命名为AppController.cs,编写如下代码。
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
[RequireComponent(typeof(ARRaycastManager))]
public class AppControler : MonoBehaviour
{
public GameObject spawnPrefab;
public GameObject ARPlane;
static List<ARRaycastHit> Hits;
private ARRaycastManager mRaycastManager;
private GameObject spawnedObject = null;
private float mARCoreAngle = 180f;
private void Start()
{
Hits = new List<ARRaycastHit>();
mRaycastManager = GetComponent<ARRaycastManager>();
}
void Update()
{
if (Input.touchCount == 0)
return;
var touch = Input.GetTouch(0);
if (mRaycastManager.Raycast(touch.position, Hits, TrackableType.PlaneWithinPolygon | TrackableType.PlaneWithinBounds))
{
var hitPose = Hits[0].pose;
if (spawnedObject == null)
{
spawnedObject = Instantiate(spawnPrefab, hitPose.position, hitPose.rotation);
spawnedObject.transform.Rotate(Vector3.up, mARCoreAngle);
var p = Instantiate(ARPlane, hitPose.position, hitPose.rotation);
p.transform.parent = spawnedObject.transform;
}
else
{
spawnedObject.transform.position = hitPose.position;
}
}
}
}
该脚本的主要功能在检测到的平面上放置虚拟对象,同时实例化了一个接受阴影的平面,并设置该平面为虚拟对象的子对象。将该脚本挂载在场景中的AR Session Origin对象上,并将虚拟物体与前面制作好的ARPlane赋给相应属性,如下图所示。
运行后效果如下图所示。
从上图中可以看到,这时生成的阴影锯齿非常严重,基本无法使用,其性能消耗基本保持在20ms-30ms之间,如下图所示。
接下来我们测试软阴影,将Directional Light的Shadow Type设置为Soft Shadows,其余参数设置不变。然后在Unity菜单栏依次选择 Edit ->Project Settings,打开Project Settings对话框,选择Quality选项卡,设置选择Shadow Projection为Stable Fit,效果如下。
在使用Soft Shadow并选择Shadow Projection为Stable Fit后,阴影的质量有了比较大的改观,也柔和了很多,但阴影与虚拟对象贴合得不紧密,同时性能消耗升到40ms-50ms之间。
保持Directional Light属性Shadow Type为Soft Shadows,设置选择Shadow Projection为Close Fit,效果如下。
在选择Shadow Projection为Close Fit后,阴影与虚拟对象贴合得非常好,整体效果也还不错,但边缘过渡还是比较生硬,性能消耗升到60ms-80ms之间。对比可以发现,Shadow Type 与 Shadow Projection选项对阴影质量影响很大,Stable Fit对Shadow map使用的是球形渐隐,Close Fit使用的是线性渐隐,Close Fit通常用于渲染较高分辨率的阴影,但在相机移动时阴影会轻微摆动,Stable Fit渲染的阴影分辨率较低,不过相机移动时不会发生摆动。另外也可以看出,阴影生成对性能影响还是比较大的,高分辨率的实时阴影可能导致性能消耗成倍增加。
(三)阴影参数详解
Unity阴影系统非常强大复杂,在Directional Light属性中,与阴影相关的属性详细信息如下表所示:
属性 | 描述 |
---|---|
Strength | 阴影强度,值越大阴影越浓,越小阴影越淡,范围为[0,1],默认值为1。 |
Resolution | 阴影分辨率,Use Quality Settings为使用Project Settings中的设置值,通常会选择该选项,因为Project Settings中可设置的参数更丰富,按分辨率从低到高依次为Low Resolution,Medium Resolution,High Resolution,Very High Resolution,分辨率越高,阴影边缘的锯齿效应就越平滑越不明显。 |
Bias | 偏移量,范围为[0,2],默认值为0.05,阴影与模型的位置关系,值越大阴影与模型偏移越大,其与Normal Bias一并用来防止自阴影(Shadow acne)和阴影偏离(peter-panning)。 |
Normal Bias | 法向偏移量,范围为[0,3],默认值为0.4,建议取值范围[0.3,0.7],该属性实际是用来调整表面坡度偏移量,坡度越大偏移量应该越大,但过大的偏移理又会导致阴影偏离(peter-panning),因此固定的偏移量需要法向偏移量来修正。 |
Near Plane | 近平面,产生阴影的最小距离,范围为[0.1,10],调整该值可以调整近阴影裁减平面。 |
在Project Settings对话框中,选择Quality选项卡,这里可以设置整体的质量表现,从左到右依次为台式电脑、iOS、Android、webGL,从上到下质量越来越高,在效果越来越好的同时性能消耗也会同步增加。
在Project Settings对话框中与阴影相关的设置如下图所示。
各属性详细说明见下表。
属性 | 描述 |
---|---|
Shadowmask Mode | 阴影遮罩模式,只在光源为Mix模式才有用,Shadowmask表示所有静态物体都使用烘焙阴影,Distance Shadowmask与Shadow Distance配合使用,在Shadow Distance范围内使用实时阴影,在范围外使用烘焙阴影。因此,Distance Shadowmask性能消耗比Shadowmask要高。 |
Shadows | 阴影模式,分为Disable Shadows,Hard Shadows Only,Hard and Soft Shadows,分别为无阴影,硬阴影,软阴影。 |
Shadow Resolution | 阴影分辨率,按分辨率从低到高依次为Low Resolution,Medium Resolution,High Resolution,Very High Resolution,分辨率越高,阴影边缘的锯齿效应就越平滑越不明显。 |
Shadow Projection | 阴影投影,Directional Light的阴影投影方式。Close Fit渲染高分辨率的阴影,Stable Fit渲染低分辨率的阴影。 |
Shadow Distance | 阴影可见距离,为提高性能,在此距离内渲染阴影,在此距离外的阴影不渲染。 |
Shadow Near Plane Offset | 近平面阴影偏移,用于处理近平面处大三角形导致的阴影扭曲问题。 |
Shadow Cascades | 层叠阴影,分为No Cascades,Two Cascades,Four Cascades,更高的层叠阴影带来更好的阴影边缘柔和效果,层叠阴影对性能消耗非常大。 |
Cascade Splits | 层叠切片,用于调整层叠阴影叠加效果。 |
Unity自带的阴影系统实现了shadowmap、screen space shadowmap、Cascaded Shadow等阴影效果,也可以控制阴影偏移量与法向偏移量,能普适于常见应用。