天天看點

【Unity Shader】結合Projector和Rendertexture實作實時陰影

之前已在簡書上發過,這裡重新整理了一下,并附上實作代碼。

在unity中實作陰影的文章網上看了不少,包括常用的shadowMap,或直接投射Rendertexture等。比如unity中實作shadowmap,可以通過投射燈光空間的深度圖,并在投射物體上進行深度比較,判斷是否處于陰影的範圍,以此來渲染陰影。将深度圖投射到接受陰影的物體上的效果如圖所示:

【Unity Shader】結合Projector和Rendertexture實作實時陰影

你所需要做的就是在燈光空間渲染一張深度紋理,并投射到接受陰影的物體上,并和接受陰影的物體上對應像素位置的深度(燈光空間)進行比較,來确定目前像素是否處于陰影即可,此外還要考慮深度圖的精度以及以此會造成的ZFighting等,當然這并不是本文讨論的重點。

這邊我主要介紹一種直接投射燈光空間錄影機的Rendertexture來實作陰影的方法,并将在稍後将其和projector結合。當然同時熟悉這兩項技術的開發者應該已經清楚,使用projector實作陰影意味着你将會消耗額外的drawcall,實際上被投射projector并且未在shader中使用"IgnoreProjector"="true"的物體都會在自身shader渲染完(也可能是渲染前,具體看自身渲染隊列和projector的shader的渲染隊列的先後順序)後再次使用projector的shader渲染一次,這表示當被projector投射且未标記忽略投影機或不處于投影機忽略的層的物體會再次使用projector的material渲染,是以如果将projector應用在某些場景,比如擁有複雜的場景元素,且大部分是單獨的物體而不是合并的,會占用較多drawcall,當然如果projector隻影響一小部分元素,比如projector隻影響單獨模型的地面,則可以不必擔心drawcall的問題。

首先比較一下這種技術和shadowmap技術,實際上個人感覺很大程度上兩者的技術其實差不多,都需要用到螢幕投影,隻不過shadowmap投射的是深度圖(深度緩沖),而本文介紹的是直接投射螢幕紋理(幀緩沖),是以投射的紋理是帶Alpha通道的。

【Unity Shader】結合Projector和Rendertexture實作實時陰影

和shadowmap不同的是,燈光空間的錄影機應該隻看到投射陰影的物體:

【Unity Shader】結合Projector和Rendertexture實作實時陰影

此時投射後的效果大緻如圖所示:

【Unity Shader】結合Projector和Rendertexture實作實時陰影

需要注意的是,正如剛剛所講,投射的Rendertexture是帶alpha通道的,這意味着被投射物體的貼圖的Alpha值會存入這張Rendertexture,如圖是燈光空間的錄影機看到的投射陰影的物體:

【Unity Shader】結合Projector和Rendertexture實作實時陰影

理論上此時這個錄影機投射的rt應該也是這樣的效果(除了背景部分會透明),但是如果投射陰影的物體使用的紋理帶有Alpha通道,盡管物體可能并未使用開啟blend的shader,比如上圖中的物體時預設的Diffuse,但如果使用的紋理格式是帶Alpha通道的,比如RGBA Compressed DXT5并且這張紋理本身也具有Alpha通道資訊,則也會渲染到RT中,如下圖是使用了帶Alpha通道的紋理得到的Rendertexture,其身體使用RGBA格式,而頭部紋理則不具有Alpha通道資訊:

【Unity Shader】結合Projector和Rendertexture實作實時陰影

可以看到實際渲染得到的rt也會記錄紋理的Alpha。将其紋理格式改成RGB Compressed DXT1就不會渲染出半透明的RT了。

當然使用這種方式投射rendertexture必然造成的一個問題是,由于沒有投射接受陰影的平面,導緻一旦投射陰影的物體穿透接受陰影的物體時會造成陰影的穿幫:

【Unity Shader】結合Projector和Rendertexture實作實時陰影

接受陰影物體Shader主要實作代碼: 

【Unity Shader】結合Projector和Rendertexture實作實時陰影

其中viewMatrix為燈光空間錄影機的worldToCameraMatrix,projMatrix為燈光空間錄影機的投影矩陣。

當然使用這種方式實作陰影的不足之處在于需要明确的知道投射陰影的物體和接受陰影的物體。

接下來将嘗試将其與Projector結合,注意之前已經讨論過,使用projector意味着額外的drawcall,尤其是場景中物件很多且全部都是分離的物體時,不建議使用這種方式。當然如果場景中隻有極少部分物體需要接受陰影,比如隻有主要地形,則不妨可以嘗試使用這種方式,因為使用projector,你可以很友善的在shader中加入IgnoreProjector标簽來忽略投影機的作用,或者直接在projector上修改projector影響的層,并且不需要為接受陰影的物體單獨編寫shader或腳本,隻需要一個projector即可。

從unity标準資源包中的projector shader我們大緻可以了解,projector shader中需要兩個4階矩陣,          分别為_Projector和_ProjectorClip,其中後者主要用于近遠裁面的淡入淡出,并不是必須的。而前者的_Projector,注意這個矩陣應該差別于錄影機的projection矩陣(盡管錄影機和projector在很多參數上很相似),原因是官方的projector shader中直接通過:o.uvShadow = mul (_Projector, vertex);計算得到投影紋理坐标,這意味着_Projector矩陣應該同時實作将vertex轉換到世界空間,再轉換到projector的局部空間,最後轉換到projector的投影空間的功能,是以其性質應該類似UNITY_MATRIX_MVP矩陣,是以使用projector實作投射rendertexture的效果,隻需在projector位置建立一個錄影機,并複制projector的參數,并将這個錄影機的rendertexture傳遞給projector的material,具體shader實作如下:

【Unity Shader】結合Projector和Rendertexture實作實時陰影

其中_FadeTex是一張表示陰影衰減的貼圖,其r、g通道效果如下:

【Unity Shader】結合Projector和Rendertexture實作實時陰影
【Unity Shader】結合Projector和Rendertexture實作實時陰影

需要注意第二個通道的紋理使用_ProjectorClip,用于實作近裁面到遠裁面的衰減效果,而第一個通道的紋理則用于将rt周圍進行遮罩,如果不使用則當投射陰影的物體剛好到達或超出projector錄影機時,由于我的rt的wrapmode為clamp,會産生這種效果:

【Unity Shader】結合Projector和Rendertexture實作實時陰影

最後實作的陰影如下:

【Unity Shader】結合Projector和Rendertexture實作實時陰影

另外由于投射的是帶Alpha通道的Rendertexture,意味着可以友善的對其使用模糊shader完成模糊效果,這裡是我自己編寫的模糊腳本效果圖:

【Unity Shader】結合Projector和Rendertexture實作實時陰影
【Unity Shader】結合Projector和Rendertexture實作實時陰影

最後附上工程

連結:http://pan.baidu.com/s/1mgTkDhY 密碼:ggh8

之前的簡書文章位址:http://www.jianshu.com/p/1fade1e8c309

原創文章,如需轉載請注明出處。

繼續閱讀