這篇文章并不是談代碼里怎么實現(xiàn),而是陰影效果的實現(xiàn)理念枉氮。先有概念上的理解志衍,才有代碼的實現(xiàn)。
現(xiàn)實里聊替,陰影是怎么出來的楼肪?是光朝某個方向照射,有一部分照在了前面的物體上惹悄,造成在后面的物體上亮度出現(xiàn)差別春叫。亮度低的地方就被我們認作是陰影。
可以說沒有陰影這回事泣港,只有光暂殖,只要物體表面的光是正確的,那么陰影就自然出來了当纱。
在剛開始接觸3D世界里的陰影的時候呛每,我在想,光照不是已經算出來來了嗎坡氯?那么陰影應該自動出現(xiàn)了晨横。可實際上在Unity里計算物體的光照時箫柳,我們是認為光能夠照到任何地方的手形。比如漫反射:
fixed3 diffuse = _LightColor0.rgb * saturate(dot(lightDir, i.worldNormal)) * albedo;
這里的_LightColor0
是光照,我們并沒有做任何的判斷悯恍,何時_LightColor0
有何時沒有库糠,拿來就用。
如果要按照現(xiàn)實同樣的邏輯來計算陰影涮毫,那么就要判斷光照何時沒有曼玩,也就是在當前的基礎上做減法鳞骤。但實際并不是這么做,而是做加法黍判。
理念1: 把陰影看做是一種特殊的光豫尽,在原來的物體表面覆蓋上去。
如果你用電腦畫過畫就會很容易理解顷帖,在畫圖時給一個部分加陰影的做法是:新開一個圖層美旧,放在原色圖層的上面,然后設置成正片疊底的模式贬墩,在這個圖層用灰色畫就可以達到陰影的效果榴嗅。正片疊底其實就是顏色疊加陶舞,在一個顏色上面疊加灰色嗽测,原顏色性質保持,就是變暗了肿孵,跟陰影效果符合停做。
用代碼解釋:
color x = (a,b,c); //rgb分量分別是a b c
color shadow = (0.9, 0.9, 0.9); //灰色作陰影,每個通道值是在[0,1]
color y = x * shadow = (a * 0.9, b * 0.9 , c * 0.9); //顏色混合效果就是各通道分量相乘
這樣做剩下的問題就是找到那些地方有陰影這種特殊的光,在計算物體顏色的時候舅巷,做一個乘法就搞定了河咽。
哪些地方有陰影呢库北?先看一束光爬舰,假設位置A和B在同一束光里,那么誰在后面誰就有陰影寒瓦∏橐伲可以想象一下烤串的樣子,只有第一個有光杂腰,后面的都在陰影里垃你。
所以,如果我可以建立一個數(shù)據(jù)表,把一個光源所有方向第一個被照到的位置記錄下來惜颇。那么對于新的位置A皆刺,就在這個數(shù)據(jù)表里找到同一束光第一個位置B,如果A比B近一些凌摄,那么A就變成了這束光第一個被照到的地方羡蛾,且A就沒陰影,反之就有A就在陰影里锨亏。
對于一個光源來說痴怨,我只要維持一個這樣的數(shù)據(jù)表,就可以把陰影的問題搞定了器予,這個數(shù)據(jù)表就是陰影映射紋理浪藻。紋理這類東西,就是你給一個xy坐標乾翔,它返回給你一個特定的值爱葵,在這里,這個xy坐標就是用來定位這束光的坐標反浓,而返回的值就是陰影的顏色萌丈,如 (0.9, 0.9, 0.9)。
這個邏輯其實跟深度紋理是類似的勾习。
理念2:使用陰影映射紋理
那么問題就轉化成了如何定位一束光浓瞪?
解決方案就是建立一個光源自身的坐標體系(即光源空間)懈玻,而這個坐標系的xy位置就是用來定位每一束光的巧婶。
關于光源空間怎么定義的,下面是我的猜測涂乌。
比如對于平行光艺栈,平行光沒有光源位置概念,只有方向湾盒。那么我把光照射方向作為Z軸湿右,與光照射方向垂直的面設為xy軸平面。那么是不是xy值就可以定位一束光的呢罚勾?我覺得是的毅人。兩個點如果xy值一樣,那么他們肯定在同一束光里尖殃,因為Z軸方向就是光的方向丈莺。
對于點光源,光是球形的發(fā)散出去的送丰。光的方向是變化的缔俄,那么xy軸該怎么搞?在3D圖形空間變換里有一部分,是把頂點從相機空間轉變到裁剪空間里去俐载,也就是把照相機的那個有視錐體轉變成xyz都是[-1,1]范圍的一個立方體蟹略。那么光源這里可以同樣采用這種變換手段,只是這時是360度的視角遏佣。
發(fā)散一下挖炬,假如光會拐彎,那么xy平面就取垂直于光線的一個平面贼急,這個平面可能是一個凹凸不平的面茅茂,把這個不平的面展開成平面就好了,就跟不平的模型表面的紋理坐標一樣太抓。這樣的截面只需要一個就可以空闲,比如就取包含光源本身的那么面。
對于一個點A走敌,如果不在這個截面里碴倾,就可以沿著光線的回流,直到找到同一束光線上的xy截面的點B掉丽,這個點B和A具有相同的xy值跌榔。
(突然隱約想到流體分析里是不是用了這樣的想法。)