【Unity Shaders】學習筆記——SurfaceShader(四)用紋理改善漫反射
轉載請注明出處:http://www.cnblogs.com/-867259206/p/5603368.html
寫作本系列文章時使用的是Unity5.3。
寫代碼之前:
-
當然啦,如果Unity都沒安裝的話肯定不會來學Unity Shaders吧?
-
閱讀本系列文章之前你需要有一些編程的概念。
-
在VS里面,Unity Shaders是沒有語法高亮顯示和智能提示的,VS黨可以參考一下這篇文章使代碼高亮顯示,也可以下載
shaderlabvs
或NShader
之類的插件使代碼高亮顯示。 -
這是針對小白的Unity Shaders的基礎知識,如果你已經有了基礎或者你是大神,那么這些文章不適合你。
-
由於作者水平的局限,文中或許會有謬誤之處,懇請指出。
上一篇里我們做的HalfLambert的效果是這樣的:
我們看到,亮部到暗部沒有自然的過渡,暗部直接塗成深灰。就像初學者畫的素描,直接將暗部塗黑,沒有變化。
這是因為這種光照計算太簡單了,現實生活中,暗部因為漫反射的存在不會像畫面上那么暗。但如果要引入光照計算的話,那太復雜了,我們可以用紋理來改善它。
因為我們需要一張紋理貼圖,先在Properties里聲明紋理:
_RampTex ("Ramp Texture", 2D) = "white"{}
然后再CGPROGRAM里聲明一個同名變量:
sampler2D _RampTex;
最后只要稍微改改HalfLambert光照模型:
// add this line
float3 ramp = tex2D(_RampTex, float2(hLambert,hLambert)).rgb;
......
// modify this line
col.rgb = s.Albedo * _LightColor0.rgb * ramp;
將修改后的材質賦予小球,貼上這張貼圖:
看看效果:
光影效果是不是真實了許多?
這是因為我們將這張貼圖根據反射光強映射到小球上,改變了小球反射的顏色。
現在我們來做一個實驗,跟着我來:
修改這句:
// float hLambert = 0.5 * difLight + 0.5;
float hLambert = difLight;
也就是不要使用半蘭伯特光照。看看結果:
變成了這樣子。再繼續修改這句:
float hLambert = 0.9 * difLight;
映射在球上的貼圖移動了一點點對不對?
然后繼續修改,把0.9改為0.8,觀察效果,把0.8改為0.7,觀察效果,把0.7改為0.6,觀察效果……
我們看到,貼圖一點點地往上移,你們發現了什么嗎?在我們把數值一點點改小的過程中,小球光照最亮的點的值由1慢慢變小,對應地,最亮的點由白色漸漸變成了藍灰色,當把數值調為0.1的時候,變成了橙黃色。而那張貼圖,最左側是黃色,中間是藍灰色,右側是藍白色,如果用坐標表示紋理的話,最左側應該是0,中間是0.5,右側是1.所以紋理的映射關系你清楚了嗎?當小球最亮點的值是0.5的時候,它映射的就是貼圖中央的部分,也就呈現藍灰色。所以你們應該清楚這張貼圖是如何映射在小球身上從而改善光影效果的了吧。經過半蘭伯特光照的改善,原來光照為0的地方變成了0.5,所以映射的是紋理的中間部分,呈漸變的藍灰色,原來光照為-1的地方變成了0,映射的是紋理左側的橙色部分,如果你仔細觀察,可以看到在小球接近地面的地方顏色有些泛黃,也就是紋理左側的顏色。
學過素描的人知道,明暗交界線的地方是最暗的,背光面因為反光的原因,會亮一點,這樣畫出來的陰影就不會一片死黑,而是通透,接近真實。看下圖:
所以貼圖的中部顏色最深,是暗灰,模擬的是明暗交界處的顏色(也就是原先光照為0的地方經過改善變成了0.5),右側的漸變模擬的是亮部的顏色,左側的漸變模擬的是暗部因為反光而變亮一些的顏色。
這樣的效果還是不能使我們滿意,陰影的效果還是不夠柔和。
假的BRDF
BRDF是bidirectional reflectance distribution function的簡寫,意思是雙向反射分布函數。
之前我們都只考慮了入射光的方向,這樣我們只能得到一個線性的結果。實際上,一個表面上的一點,由於觀察方向不同,看到的效果是不同的。因為不同的角度,光線反射進眼里的強弱不同,顏色也不同。所以我們要引進一個新的變量,viewDir,它是人眼看向物體的方向。也就是說對於表面上某個點的顏色的計算,我們要考慮兩個方向,光照方向和觀察方向。可以簡單地理解為入射光在不透明的物體表面同時反射到觀察方向和出射光兩個方向。
好了,直接上代碼,不一句句地修改了:
inline float4 LightingHalfLambert (SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten){
float difLight = dot (s.Normal, lightDir);
float rimLight = dot (s.Normal, viewDir);
float dif_hLambert = 0.5 * difLight + 0.5;
float rim_hLambert = 0.5 * rimLight + 0.5;
float3 ramp = tex2D(_RampTex, float2(dif_hLambert, rim_hLambert)).rgb;
float4 col;
col.rgb = s.Albedo * _LightColor0.rgb * ramp;
col.a = s.Albedo;
return col;
}
在光照模型函數里我們新增了一個變量half3 viewDir
,它就是觀察方向,在函數里面,我們定義了一個新的變量float rimLight
,同樣對它與法線方向點積,然后用HalfLambert優化,最后將dif_hLambert
和rim_hLambert
作為uv坐標傳入tex2D
函數。
將新的材質賦予小球,貼上這張貼圖:
我們來看看效果:
你們可以將Slider的數值調大一點,然后給球一個顏色,看看效果,再和原先的材質比較一下。
看看對比,左邊是上一個只考慮光照方向的材質,右邊是BRDF材質。左邊很簡單地區分了亮面和暗面,好像直接畫了條圓弧區分了明暗交界線,而右側的明暗過渡更為柔和和富於變化。
我們先來分析一下紋理的映射關系:
紋理的左下角坐標是(0,0),右上角坐標是(1,1),左上角坐標是(0,1),右下角坐標是(1,0)。
左下角對應着光照最弱且離人視線最遠的地方,也就是小球接近地面的那個區域,對應紋理的黑色;右上角對應着光照最強且離人視線最近的地方,就是大概在小球高光附近的區域,對應紋理顏色最白的地方;左上角對應着光照最弱但離人視線最近的地方,也就是沒上色的效果圖里有點泛紅的區域;右下角對應着光照最強但離人視線最遠的地方,也就是小球靠近光源的邊緣區域,我們看到沒上色的圖里它有些泛藍。
實際上,圖的右上角到左下角對應的就是光照方向,左上角到右下角對應的就是觀察方向:
觀察方向和平面法向量點積的結果如圖:
意思就是說,離人視線越近的地方越亮,越遠的地方越暗。
這和素描關系也是一樣的,只考慮光照方向畫出來的陰影就像上一個shader那樣,不夠立體,這是因為只考慮光照方向的體積,同時考慮的視線方向,小球才會更有立體感。
之所以用小球來演示shader是因為小球更容易看出光影的關系,素描第一課都是畫球嘛。
其實shader和畫畫確實是一樣的,只不過一個是用筆在紙上畫出想要的結果,一個是用代碼“畫出”想要的結果~
附:代碼清單
Shader "Custom/RampDiffuse" {
Properties {
_EmissiveColor ("Emissive Color", Color) = (1,1,1,1)
_AmbientColor ("Ambient Color", Color) = (1,1,1,1)
_MySliderValue ("This is a Slider", Range(0,10)) = 2.5
_RampTex ("Ramp Texture", 2D) = "white"{}
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf HalfLambert
float4 _EmissiveColor;
float4 _AmbientColor;
float _MySliderValue;
sampler2D _RampTex;
struct Input {
float2 uv_MainTex;
};
void surf (Input IN, inout SurfaceOutput o){
float4 c;
c = pow((_EmissiveColor + _AmbientColor), _MySliderValue);
o.Albedo = c.rgb;
o.Alpha = c.a;
}
inline float4 LightingHalfLambert (SurfaceOutput s, fixed3 lightDir, fixed atten){
float difLight = dot (s.Normal, lightDir);
float hLambert = 0.5 * difLight + 0.5;
float3 ramp = tex2D(_RampTex, float2(hLambert,hLambert)).rgb;
float4 col;
col.rgb = s.Albedo * _LightColor0.rgb * ramp;
col.a = s.Alpha;
return col;
}
ENDCG
}
FallBack "Diffuse"
}