版權聲明: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等陰影效果,也可以控制陰影偏移量與法向偏移量,能普適於常見應用。