Unity局部高效實時陰影

無意間看到一篇文章棍郎,說是Unity5 demo中為了實現(xiàn)角色的良好陰影钦讳,單獨給角色設計了一個角色陰影系統(tǒng)。而且使用的是比較老的技術掐暮,但效果很好蝎抽。其實在很多時候,我們需要的并不是萬能的陰影光照系統(tǒng),而是局部能做到效果就行樟结。

萬能的好處在于任何情況都能看上去合理养交,但是相對的,性能開銷也大瓢宦,同時為了兼顧各種情況碎连,只能做各種效果的折中,所以我們看到了現(xiàn)在移動平臺上驮履,要么就是沒有實時陰影鱼辙,要么就是充滿鋸齒的實時陰影,要么就是使用2D貼圖來模擬實時陰影玫镐。
用2D貼圖來模擬的效果毫無疑問是最好的倒戏,但問題在于成本太高,很多小團隊資金有限恐似,很難專門為每一個角色都讓美術畫一大堆陰影貼圖杜跷。而這也毫無疑問會增加游戲的大小。
我主要思考的是矫夷,在某種條件下葱椭,是否可以實現(xiàn)局部的良好的陰影。比如角色展臺口四,毫無疑問只會出現(xiàn)一個角色孵运,那么這個情況下,毫無疑問我們需要的是一個完美的陰影蔓彩≈伪浚或者說某一些游戲,視角固定赤嚼,而且能看到的范圍很小旷赖,那么是否只針對這個部分去實現(xiàn)好的陰影系統(tǒng)「洌或者一個很小的室內(nèi)等孵,我們也需要一個好的角色陰影。
ok蹂空,那么開始思考方案俯萌,首先我們應該只需要一個平行光的陰影。一般來說需要獲得這個位置看過去的深度圖上枕。我首先在這個位置上放了一個正交攝像機咐熙,注意如果你想讓角色有陰影,那么必須讓角色處在這個正交攝像機的范圍內(nèi)辨萍,那么現(xiàn)在第一個問題來了棋恼,如何保證角色在正交攝像機的范圍內(nèi)?
方法如下:首先你要獲得你主攝像機內(nèi)的所有的需要陰影的物體,然后將這些物體轉(zhuǎn)化到正交攝像機的坐標中爪飘,計算出這些物體的最大范圍义起,并得出正交矩陣賦值給正交攝像機。(代碼借鑒了http://game.ceeger.com/forum/read.php?tid=22738&fid=2师崎,對這個樓主深表感謝)
這里要注意并扇,Unity計算出來的Z是負值,但OpenGL是正的抡诞,官方說明如下:
Matrix that transforms from world to camera space.
Use this to calculate the camera space position of objects or to provide customcamera's location that is not based on the transform.
Note that camera space matches OpenGL convention: camera's forward is the negativeZ axis. This is different from Unity's convention, where forward is the positive Zaxis.
If you change this matrix, the camera no longer updates its rendering based on its Transform.This lasts until you call ResetWorldToCameraMatrix.

#pragma strict
// Offsets camera's rendering from the transform's position.
public var offset: Vector3 = new Vector3(0, 1, 0);
var camera: Camera;
function Start() {
camera = GetComponent.();
}
function LateUpdate() {
var camoffset: Vector3 = new Vector3(-offset.x, -offset.y, offset.z);
var m: Matrix4x4 = Matrix4x4.TRS(camoffset, Quaternion.identity, new Vector3(1, 1, -1));
camera.worldToCameraMatrix = m * transform.worldToLocalMatrix;
}
不過實際使用過程中穷蛹,我們也許并不需要正確的矩陣賦值,因為你需要的是保證所有的物體在攝像機范圍內(nèi)昼汗,只需要知道AABB盒肴熏,然后把相機設置在AABB盒的中心,同時增加Size即可顷窒。
public ListCharactorList;
void CreateCameraProjecterMatrix()
{
Vector3 v3MaxPosition = -Vector3.one * 500000.0f;
Vector3 v3MinPosition = Vector3.one * 500000.0f;
for (int vertId = 0; vertId < CharactorList.Count; ++vertId)
{
// Light view space
Vector3 v3Position = camera1.worldToCameraMatrix.MultiplyPoint3x4(CharactorList[vertId].position);
if (v3Position.x > v3MaxPosition.x)
{
v3MaxPosition.x = v3Position.x;
}
if (v3Position.y > v3MaxPosition.y)
{
v3MaxPosition.y = v3Position.y;
}
if (v3Position.z > v3MaxPosition.z)
{
v3MaxPosition.z = v3Position.z;
}
if (v3Position.x < v3MinPosition.x)
{
v3MinPosition.x = v3Position.x;
}
if (v3Position.y < v3MinPosition.y)
{
v3MinPosition.y = v3Position.y;
}
if (v3Position.z < v3MinPosition.z)
{
v3MinPosition.z = v3Position.z;
}
}
Vector3 off = v3MaxPosition - v3MinPosition;
Vector3 sizeOff = off;
sizeOff.z = 0;
float dis = sizeOff.magnitude;
//CreateOrthogonalProjectMatrix (ref m_projMatrix, v3MaxPosition, v3MinPosition);
//Debug.Log (v3MaxPosition.ToString() + v3MinPosition.ToString());
//Matrix4x4 m = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(1, 1, -1));
//camera1.projectionMatrix = m * m_projMatrix;
camera1.orthographicSize = dis / 1.8f;
camera1.farClipPlane = off.z + 50;
}
void CreateViewMatrix(ref Matrix4x4 viewMatrix,Vector3 look,Vector3 up,Vector3 right,Vector3 pos)
{
look.Normalize ();
up.Normalize ();
right.Normalize ();
float x = -Vector3.Dot (right,pos);
float y = -Vector3.Dot (up,pos);
float z = -Vector3.Dot (look,pos);
viewMatrix.m00 = right.x; viewMatrix.m10 = up.x; viewMatrix.m20 = look.x; viewMatrix.m30 = 0.0f;
viewMatrix.m01 = right.y; viewMatrix.m11 = up.y; viewMatrix.m21 = look.y; viewMatrix.m31 = 0.0f;
viewMatrix.m02 = right.z; viewMatrix.m12 = up.z; viewMatrix.m22 = look.z; viewMatrix.m32 = 0.0f;
viewMatrix.m03 = x; viewMatrix.m13 = y; viewMatrix.m23 = z; viewMatrix.m33 = 1.0f;
}
void CreateOrthogonalProjectMatrix(ref Matrix4x4 projectMatrix,Vector3 v3MaxInViewSpace, Vector3 v3MinInViewSpace)
{
float scaleX, scaleY, scaleZ;
float offsetX, offsetY, offsetZ;
scaleX = 2.0f / (v3MaxInViewSpace.x - v3MinInViewSpace.x);
scaleY = 2.0f / (v3MaxInViewSpace.y - v3MinInViewSpace.y);
offsetX = -0.5f * (v3MaxInViewSpace.x + v3MinInViewSpace.x) * scaleX;
offsetY = -0.5f * (v3MaxInViewSpace.y + v3MinInViewSpace.y) * scaleY;
scaleZ = 1.0f / (v3MaxInViewSpace.z - v3MinInViewSpace.z);
offsetZ = -v3MinInViewSpace.z * scaleZ;
//列矩陣
projectMatrix.m00 = scaleX; projectMatrix.m01 = 0.0f; projectMatrix.m02 = 0.0f; projectMatrix.m03 = offsetX;
projectMatrix.m10 = 0.0f; projectMatrix.m11 = scaleY; projectMatrix.m12 = 0.0f; projectMatrix.m13 = offsetY;
projectMatrix.m20 = 0.0f; projectMatrix.m21 = 0.0f; projectMatrix.m22 = scaleZ; projectMatrix.m23 = offsetZ;
projectMatrix.m30 = 0.0f; projectMatrix.m31 = 0.0f; projectMatrix.m32 = 0.0f; projectMatrix.m33 = 1.0f;
}

你看 所有角色都被包括在內(nèi)了蛙吏。當然具體適合的值你可以自己調(diào)整,這樣我們就解決了第一個問題鞋吉。 如果你的應用場景在室內(nèi)鸦做,你可以無視第一個問題,直接手動設置一個
最合適的值就行了谓着。
第二部泼诱,就是我們需要獲得物體的剪影。就是說將物體的外輪廓給檢錄下來赊锚。當然復雜點就是獲得物體的深度圖治筒。剪影獲得很簡單,我們看下深度圖如何獲得舷蒲。因為在移動平臺上不支持自動生成深度圖耸袜,所以我打算自己使用片段著色器獲得。

Shader "depthShader" {
Properties {
}
SubShader {
//Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
Pass {
CGPROGRAM
// Upgrade NOTE: excluded shader from DX11 and Xbox360; has structs without semantics (struct v2f members pos1)
#pragma exclude_renderers d3d11 xbox360
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D_float _CameraDepthTexture;
struct appdata {
float4 vertex : POSITION;
};
struct v2f {
half4 pos : SV_POSITION;
float2 depth;
};
v2f vert (appdata_base v) {
v2f o;
o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
o.depth = o.pos.zw;
return o;
}
fixed4 frag(v2f i) : COLOR
{
float d = i.depth.x/i.depth.y;
float flag = 0;
if(d < 0)
{
d = abs(d);
flag = 1;
}
float3 kEncodeMul = float3(1.0, 255.0, 65025.0);
float kEncodeBit = 1.0/255.0;
float3 enc = kEncodeMul * d;
enc = frac (enc);
enc -= enc.yzz * kEncodeBit;
return fixed4(flag, enc);
}
ENDCG
}
}
}

本來只要存儲z就好了牲平,不過很遺憾的是堤框,有些平臺的z竟然是負值,負值存儲成像素是無意義的纵柿。所以我用r存儲正負值蜈抓,gba保存數(shù)值,但這樣子性能確實會下降藐窄,畢竟在片段著色器里资昧,考慮到后面需要照顧陰影質(zhì)量,我覺得不使用深度圖荆忍,而使用剪影。
剪影就簡單了,連著色器都不用自己寫刹枉,直接用攝像機渲染的黑圖即可叽唱。這里我將攝像機設置成普通渲染模式,手動調(diào)用render,將貼圖放到rendertexture中微宝。

camera1 = GameObject.Find ("Camera").camera;
//camera1.hideFlags = HideFlags.HideAndDontSave;
camera1.enabled = false;
//camera1.projectionMatrix = camera.projectionMatrix;
int textureSize = 1024;
shadowTexture = new RenderTexture(textureSize , textureSize, 16, RenderTextureFormat.ARGB32);
shadowTexture.name = "shadowTexture" + GetInstanceID();
shadowTexture.isPowerOfTwo = true;
shadowTexture.hideFlags = HideFlags.DontSave;
camera1.targetTexture = shadowTexture;

這樣就可以看到如下貼圖:


1024大小棺亭,內(nèi)存6M 還算可以接受。當然蟋软,因為深度沒有用镶摘,可以取消,這樣會變成4M岳守,其他格式可能會更小凄敢,但手機上不一定支持,所以暫時先這樣吧湿痢。
第三個問題涝缝,就是怎么把這些東西投射到地上變成影子。
首先投射到地上已經(jīng)有現(xiàn)成的Projector組件了譬重,所以問題變成了坐標計算拒逮。
我們先把Projector的位置確定一下,Projector應該和主攝像機放在同一個地方臀规,同時有同樣的參數(shù)設置:

proj = GameObject.Find ("GameObject").GetComponent();
proj.nearClipPlane = camera.nearClipPlane;
proj.farClipPlane = camera.farClipPlane;
proj.fieldOfView = camera.fieldOfView;

這樣就保證了視角內(nèi)的物體都會出現(xiàn)陰影滩援。然后就是坐標計算了,我們想一下塔嬉,假設世界坐標中的點a狠怨,那么我們計算出它在正交攝像機中的坐標,然后根據(jù)坐標取出投影貼圖中的點邑遏,那么假如這個點是全黑的(看你設置的是啥值了)佣赖,那么就是不在陰影區(qū)的,假如不是记盒,那么說明是陰影憎蛤。
有了這個方案,我們就開始計算吧纪吮。首先我們可以輕易獲得物體的坐標俩檬,問題在于怎么獲得它在正交攝像機中的坐標,這個就需要使用正交攝像機的矩陣獲得:

matVP = GL.GetGPUProjectionMatrix (camera1.projectionMatrix, true) * camera1.worldToCameraMatrix;
proj.material.SetMatrix("ShadowMatrix", matVP);

注意碾盟,我將這個計算出來的矩陣賦值給了一個材質(zhì)棚辽,這個材質(zhì)就是Projector使用的,因為是它需要根據(jù)坐標判斷是否有陰影冰肴。
這樣似乎接下來就可以直接寫出著色器了:

v2f vert (appdata_base v)
{
v2f o;
o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
float4x4 matWVP = mul (ShadowMatrix, _Object2World);
o.uvShadow = mul(matWVP, v.vertex);
return o;
}

注意屈藐,頂點著色器不僅要計算出pos榔组,同時還要獲得正交相機中的坐標,也就是uvShadow.
然后就可以在片段著色器中處理了:

half2 uv = i.uvShadow.xy / i.uvShadow.w * 0.5 + 0.5;
#if UNITY_UV_STARTS_AT_TOP
uv.y = 1 - uv.y;
#endif
fixed4 res = fixed4(0, 0, 0, 0);
fixed4 texS = tex2D(_ShadowTex, uv);
if(texS.a > 0)
{
res.a = 0.5;
}

就這么幾行联逻,陰影就出現(xiàn)了:


但效果似乎不那么盡如人意搓扯,讓我們看下u3d自帶的高品質(zhì)的陰影效果:


其實已經(jīng)很接近了呢,不過鑒于我開頭宣稱要高質(zhì)量陰影包归,所以我打算繼續(xù)優(yōu)化邊緣锨推,因為你可以看到邊緣部分的鋸齒,當然我們可以單純增加貼圖大小公壤,但是假如到2048,那么就要占據(jù)16M的內(nèi)存了换可,所以我暫時打算用另外一種做法,pcf厦幅。就是通過采樣沾鳄,將邊緣像素模糊化。

texS = tex2D(_ShadowTex, uv + half2(-0.94201624/pad, -0.39906216/pad));
if(texS.a > 0)
{
res.a += _Strength;
}
texS = tex2D(_ShadowTex, uv + half2(0.94558609/pad, -0.76890725/pad));
if(texS.a > 0)
{
res.a += _Strength;
}
texS = tex2D(_ShadowTex, uv + half2(-0.094184101/pad, -0.92938870/pad));
if(texS.a > 0)
{
res.a += _Strength;
}
texS = tex2D(_ShadowTex, uv + half2(0.34495938/pad, 0.29387760/pad));
if(texS.a > 0)
{
res.a += _Strength;
}

經(jīng)過采樣后效果如下:


最后 是時候做一次全面比較了慨削,在電腦上我自己寫的完爆自帶的洞渔,因為我的電腦的顯卡很渣,但手機顯卡好一些缚态,所以手機上的結果可能不太一樣磁椒,放到手機上,開啟最強模式玫芦,看看到底性能和效果對比吧浆熔。找了一臺兩年前的1000塊錢的華為:
u3d 高質(zhì)量陰影:
近距離效果:


再看我自己實現(xiàn)的效果:
2048最強模式下:
近距離:


不過幀數(shù)下降了不少,再看看1024的吧桥帆。


效果也是要好上不少吧医增。
后來做了一些優(yōu)化,主要是動態(tài)調(diào)整正交攝像機的位置老虫, 這樣就能夠在遠距離時候使用模糊陰影叶骨,近距離使用高清陰影了, 在我們自己的項目中使用祈匙, 效果十分可以忽刽。
優(yōu)化代碼:

public class VisibleMesh:MonoBehaviour
{
public Transform tr;
void OnWillRenderObject()
{
if(Camera.current == Camera.main)
{
if(SceneShadow.inst.CharactorList.Contains(tr))
{
return;
}
SceneShadow.inst.CharactorList.Add (tr);
}
}
}
Vector3 off = v3MaxPosition - v3MinPosition;
Vector3 pos = (v3MaxPosition + v3MinPosition) / 2;
pos = camera1.cameraToWorldMatrix.MultiplyPoint3x4 (pos);
camera1.transform.position = pos - camera1.transform.forward * (off.z + 10);
Vector3 sizeOff = off;
sizeOff.z = 0;
float dis = sizeOff.x;
if(sizeOff.y > dis)
{
dis = sizeOff.y;
}
camera1.orthographicSize = dis / 2;
camera1.farClipPlane = off.z + 30;
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市夺欲,隨后出現(xiàn)的幾起案子跪帝,更是在濱河造成了極大的恐慌,老刑警劉巖些阅,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伞剑,死亡現(xiàn)場離奇詭異,居然都是意外死亡市埋,警方通過查閱死者的電腦和手機黎泣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門恕刘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人聘裁,你說我怎么就攤上這事雪营」В” “怎么了衡便?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長洋访。 經(jīng)常有香客問我镣陕,道長,這世上最難降的妖魔是什么姻政? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任呆抑,我火速辦了婚禮,結果婚禮上汁展,老公的妹妹穿的比我還像新娘鹊碍。我一直安慰自己,他們只是感情好食绿,可當我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布侈咕。 她就那樣靜靜地躺著,像睡著了一般器紧。 火紅的嫁衣襯著肌膚如雪耀销。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天铲汪,我揣著相機與錄音熊尉,去河邊找鬼。 笑死掌腰,一個胖子當著我的面吹牛狰住,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播齿梁,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼催植,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了士飒?” 一聲冷哼從身側響起查邢,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎酵幕,沒想到半個月后扰藕,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡芳撒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年邓深,在試婚紗的時候發(fā)現(xiàn)自己被綠了未桥。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡芥备,死狀恐怖冬耿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情萌壳,我是刑警寧澤亦镶,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站袱瓮,受9級特大地震影響缤骨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜尺借,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一绊起、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧燎斩,春花似錦虱歪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至谨读,卻和暖如春局装,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背劳殖。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工铐尚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人哆姻。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓宣增,卻偏偏與公主長得像,于是被迫代替她去往敵國和親矛缨。 傳聞我的和親對象是個殘疾皇子爹脾,可洞房花燭夜當晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內(nèi)容

  • 111. [動畫系統(tǒng)]如何將其他類型的動畫轉(zhuǎn)換成關鍵幀動畫? 動畫->點緩存->關鍵幀 112. [動畫]Unit...
    胤醚貔貅閱讀 12,979評論 3 90
  • 最先執(zhí)行的方法是: 1箕昭、(激活時的初始化代碼)Awake灵妨,2、Start落竹、3泌霍、Update【FixUpdate、L...
    困卡閱讀 4,169評論 0 8
  • 這個是我剛剛整理出的Unity面試題述召,為了幫助大家面試朱转,同時幫助大家更好地復習Unity知識點蟹地,如果大家發(fā)現(xiàn)有什么...
    編程小火雞閱讀 3,887評論 2 35
  • 可以復食后,這食欲大增藤为,根本停不下來怪与,想必有體重漲回去的趨勢……
    恩煦閱讀 162評論 1 0
  • 我在網(wǎng)上搜“人不可貌相”分别,點擊圖片,第一個出來的居然是馬云的照片窿吩。 我是一個以貌取人的人茎杂,頗有些自戀错览,大概頭腦深處...
    妙語如珠閱讀 274評論 0 0