目錄
一、標(biāo)準(zhǔn)光照模型(Phong光照模型)
1、環(huán)境光
?2、自發(fā)光
3、漫反射
4、高光反射
(1)Phong模型
(2)Blinn模型
5、光照模型實(shí)現(xiàn)方法——逐頂點(diǎn)和逐像素
二、Unity Shader 漫反射光照模型的實(shí)現(xiàn)
1、實(shí)踐:逐頂點(diǎn)
2、實(shí)踐:逐像素
3、半蘭伯特模型
4、漫反射光照模型效果展示
三、Unity Shader 高光反射光照模型的實(shí)現(xiàn)
1、實(shí)踐:逐頂點(diǎn)
2、實(shí)踐:逐像素
3、Blinn-Phong 光照模型
4、高光反射光照模型效果展示
四、Unity 的內(nèi)置函數(shù)
一、標(biāo)準(zhǔn)光照模型(Phong光照模型)
1、環(huán)境光
? ? ? ? 在標(biāo)準(zhǔn)光照模型中,使用環(huán)境光來近似模擬間接光照。間接光照就是指,光線通常會在多個(gè)物體之間反射,最后進(jìn)入攝像機(jī),例如光線通過墻壁、鏡面、地板等將光源反射后的一種照明效果,而不是直接將光源投像到被照物。
? ? ? ? 環(huán)境光的計(jì)算很簡單,通常是一個(gè)全局變量,也就是場景中的所有物體都使用這個(gè)環(huán)境光。計(jì)算如下:
?2、自發(fā)光
? ? ? ? 光線可以直接由光源發(fā)射進(jìn)入攝像機(jī),不需要經(jīng)過任何物體反射。自發(fā)光的計(jì)算就是直接使用了該材質(zhì)的自發(fā)光顏色;計(jì)算如下:
? ? ? ? 通常在實(shí)時(shí)渲染的時(shí)候,自發(fā)光不會被當(dāng)成一個(gè)光源,也就不會對周圍物體造成影響。后續(xù)會實(shí)現(xiàn)可以對周圍環(huán)境影響的效果。
3、漫反射
? ? ? ? 漫反射光照是用于對那些被物體表面隨機(jī)散射到各個(gè)方向的輻射度進(jìn)行建模的。漫反射中,視角的位置不重要,因?yàn)榉瓷涫峭耆S機(jī)的。入射光線角度很重要。
? ? ? ? 漫反射符合蘭伯特定律:反射光線強(qiáng)度與表面法線和光源方向之間的夾角的余弦值成正比。 具體計(jì)算如下:
? ? ? ? n是表面法線,I是指向光源的單位矢量,是材質(zhì)的漫反射顏色,是光源的顏色。我們要防止法線和光源方向的點(diǎn)乘為負(fù)值,所以用取最大值函數(shù)截取到0,可以防止物體被從后面來的光源照亮。
4、高光反射
(1)Phong模型
? ? ? ? 這里的高光反射是一種經(jīng)驗(yàn)?zāi)P汀煌耆险鎸?shí)世界中的高光反射現(xiàn)象。用于計(jì)算沿著完全鏡面反射方向的光線,讓物體看起來更有光澤。
? ? ? ? 計(jì)算高光反射我們需要知道:表面法線、視角方向、光源方向、反射方向。我們只要知道前三個(gè)矢量(都進(jìn)行了歸一化),反射方向可以通過計(jì)算得到,計(jì)算公式如:
? ? ? ? r是反射方向、n是法線方向、I是光源方向。
? ? ? ? 高光反射部分計(jì)算公式如下:
? ? ? ? 是材質(zhì)的光澤度,也叫反光度,用于控制高光區(qū)域的“亮點(diǎn)”有多大,越大,亮點(diǎn)越小。是材質(zhì)的高光反射顏色,用于控制材質(zhì)對于高光反射的強(qiáng)度、顏色。是光源的顏色和強(qiáng)度。同樣這里也要防止v*r的結(jié)果為負(fù)值。
(2)Blinn模型
? ? ? ? Blinn模型在上面的Phong模型上進(jìn)行簡單的修改來得到類似的效果。他的思想是,避免計(jì)算反射方向 r 。為此Blinn引入了一個(gè)新的矢量 h ,他是通過對 v 和 I 的取平均后再歸一化得到的:
????????所以Blinn模型公式如下:
? ? ? ? 這兩種模型各有優(yōu)劣。如果攝像機(jī)和光源距離模型足夠遠(yuǎn)的話,Blinn會快于Phong,因?yàn)檫@個(gè)時(shí)候 v 和 I 都可以看成定值,h 將是一個(gè)常量。反之Phong可能更快。
5、光照模型實(shí)現(xiàn)方法——逐頂點(diǎn)和逐像素
? ? ? ? 實(shí)現(xiàn)這些基本光照模型有兩種方式,一種是在片元著色器中計(jì)算,被稱為逐像素光照(per-pixel lighting);另一種是在頂點(diǎn)著色器中計(jì)算,被稱為逐頂點(diǎn)光照(per-vertex lighting).
? ? ? ? 逐像素光照,以每個(gè)像素為基礎(chǔ),得到它的法線(可以是對頂點(diǎn)法線插值得到的,也可以是從法線紋理中采樣得到的),然后進(jìn)行光照模型計(jì)算,這種在面片之間對頂點(diǎn)法線進(jìn)行插值的技術(shù)稱為Phong著色,也被稱為Phong插值或法線插值技術(shù),這不同于Phong光照模型。
? ? ? ? 逐頂點(diǎn)光照,也稱為高洛德著色(Gouraud shading)。在逐頂點(diǎn)光照中,我們在每個(gè)頂點(diǎn)上計(jì)算光照,然后再渲染圖元內(nèi)部進(jìn)行線性插值,最后輸出成像素顏色。由于頂點(diǎn)數(shù)目一般遠(yuǎn)小于像素的數(shù)目,所以逐頂點(diǎn)光照的計(jì)算量一般小于逐像素光照。但是因?yàn)橹痦旤c(diǎn)光照依賴于線性插值得到像素光照,所以,如果光照模型有非線性計(jì)算時(shí)(如計(jì)算高光反射時(shí)),就會出現(xiàn)問題。此外,逐頂點(diǎn)光照會再渲染圖元內(nèi)部對頂點(diǎn)顏色線性插值,導(dǎo)致渲染圖元內(nèi)部的顏色總是暗與頂點(diǎn)處的最高顏色,在某些情況下回產(chǎn)生明顯的棱角現(xiàn)象。
二、Unity Shader 漫反射光照模型的實(shí)現(xiàn)
1、實(shí)踐:逐頂點(diǎn)
? ? ? ? 實(shí)現(xiàn)代碼如下,備注也寫在代碼注釋里面了。
Shader "MyShader/6_DiffuseVertex.Leve!Mat"
{
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
}
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//頭文件,可以使用unity內(nèi)置的一些變量
#include "Lighting.cginc"
//fixed精度值-2到2,color顏色屬性范圍0-1
fixed4 _Diffuse;
//頂點(diǎn)著色器輸入結(jié)構(gòu)體
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;//訪問頂點(diǎn)的法線
};
//頂點(diǎn)著色器輸出結(jié)構(gòu)體
struct v2f {
float4 pos : SV_POSITION;
fixed3 color : COLOR;
};
//逐頂點(diǎn)的漫反射光照
v2f vert(a2v v) {
v2f o;
// 頂點(diǎn)著色器最基本的任務(wù)就是把頂點(diǎn)位置從模型空間轉(zhuǎn)換到裁剪空間
o.pos = UnityObjectToClipPos(v.vertex);
// 獲得環(huán)境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
/* 把法線方向變換到世界坐標(biāo)
使用頂點(diǎn)變換矩陣的逆轉(zhuǎn)置矩陣,unity_WorldToObject可以得到模型空間到世界空間的
變換矩陣的逆矩陣。 調(diào)換在mul函數(shù)中的位置,得到和轉(zhuǎn)置矩陣相同的矩陣乘法,相當(dāng)于乘了逆轉(zhuǎn)置矩陣。
法線是一個(gè)三維矢量,所以只要截取3*3*/
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
// 獲得世界坐標(biāo)下的光照方向
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);//默認(rèn)場景中只有一個(gè)平行光源,多個(gè)光源就不能只用這個(gè)
// 漫反射計(jì)算公式 = 入射光的顏色和強(qiáng)度 * 材質(zhì)的漫反射系數(shù) * max(0,表面法線*光源方向)
//表面法線*光源方向,兩者要在同一個(gè)坐標(biāo)系才有意義,這里選擇的世界坐標(biāo)系,所有有了上面的worldNormal和worldLight--轉(zhuǎn)換為世界坐標(biāo)空間
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));//saturate(x)函數(shù)可以截取在[0,1]范圍內(nèi)。
o.color = ambient + diffuse;
return o;
}
//計(jì)算在頂點(diǎn)著色器中都已經(jīng)完成了,所以偏遠(yuǎn)著色器只要把頂點(diǎn)顏色輸出即可.
fixed4 frag(v2f i) : SV_Target {
return fixed4(i.color, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
2、實(shí)踐:逐像素
Shader "MyShader/DiffusePixe"
{
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
}
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//頭文件,可以使用unity內(nèi)置的一些變量
#include "Lighting.cginc"
//fixed精度值-2到2,color顏色屬性范圍0-1
fixed4 _Diffuse;
//頂點(diǎn)著色器輸入結(jié)構(gòu)體
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;//訪問頂點(diǎn)的法線
};
//頂點(diǎn)著色器輸出結(jié)構(gòu)體
struct v2f {
float4 pos : SV_POSITION;
fixed3 worldNormal : TEXCOORD0;
};
//頂點(diǎn)著色器不需要計(jì)算光照模型,只要把世界空間下的法線傳遞給片元著色器即可
v2f vert(a2v v) {
v2f o;
// 把頂點(diǎn)位置從模型空間轉(zhuǎn)換到裁剪空間
o.pos = UnityObjectToClipPos(v.vertex);
// 把法線方向從模型坐標(biāo)空間轉(zhuǎn)換到世界坐標(biāo)空間
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
return o;
}
//片元著色器計(jì)算漫反射光照模型
fixed4 frag(v2f i) : SV_Target {
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 獲得世界坐標(biāo)空間下的法線向量
fixed3 worldNormal = normalize(i.worldNormal);
// 獲得世界坐標(biāo)空間下的光照方向
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// 計(jì)算漫反射
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
fixed3 color = ambient + diffuse;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
3、半蘭伯特模型
? ? ? ? 上面兩個(gè)模型(也叫蘭伯特光照模型)有個(gè)問題就是,光照沒有照射到的地方是全黑的,沒有明暗變化,導(dǎo)致背面就像是有個(gè)平面一樣。為此 有一種改善技術(shù)被提出來,這就是半蘭伯特 (Half Lambert) 光照模型。
? ? ? ? 廣義半蘭伯特光照模型公式如下:
? ? ? ? 與原來的蘭伯特光照模型相比,半蘭伯特光照模型沒有使用max操作來防止n*I的點(diǎn)積為負(fù)值,而是進(jìn)行了一個(gè)α倍的縮放和β大小的偏移。一般情況下這兩個(gè)值都為0.5,即:
? ? ? ? 這樣就可以把[-1,1]映射到[0,1]范圍內(nèi)。對于模型的背光面,在原蘭伯特光照模型中只會映射到0值;在半蘭伯特光照模型中,背光面也會有明暗變化,不同的點(diǎn)積會映射到不同的值上。
? ? ? ? 所以在實(shí)現(xiàn)上也很簡單,只要把上面的代碼中計(jì)算漫反射的公式修改一下就可以了。以逐像素為例,只要修改片元著色器就可以了:
fixed4 frag(v2f i) : SV_Target {
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// 只有公式有所改變!
fixed halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;
fixed3 color = ambient + diffuse;
return fixed4(color, 1.0);
}
4、漫反射光照模型效果展示
三、Unity Shader 高光反射光照模型的實(shí)現(xiàn)
1、實(shí)踐:逐頂點(diǎn)
Shader "MyShader/6_SpecularVertex"
{
Properties
{
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
_Specular("Specular", Color) = (1, 1, 1, 1) //高光反射顏色
_Gloss ("Gloss", Range(8.0, 256)) = 20 //控制高光區(qū)域大小
}
SubShader
{
Pass
{
Tags {"LightMode" = "ForwardBase"} //LightMode是Pass標(biāo)簽的一種
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
fixed3 color : COLOR;
};
v2f vert(a2v v) {
v2f o;
// 頂點(diǎn) 模型坐標(biāo)空間到裁剪坐標(biāo)空間
o.pos = UnityObjectToClipPos(v.vertex);
// 獲得環(huán)境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 法向量 模型坐標(biāo)空間到世界坐標(biāo)空間
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
// 獲得世界坐標(biāo)空間下的光照方向
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// 計(jì)算漫反射
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
//reflect(i,n)函數(shù)用于計(jì)算反射方向,i是入射方向,n是法線方向。對i的要求是由光源指向交點(diǎn),所以下面worldLightDir加了負(fù)號
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
// 獲得世界坐標(biāo)空間下的視角方向
//_WorldSpaceCameraPos獲得相機(jī)位置,mul(unity_ObjectToWorld, v.vertex).xyz把頂點(diǎn)位置從模型空間轉(zhuǎn)換到世界空間。
//上述兩項(xiàng)相減得到世界空間下的視角方向。
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
// 計(jì)算高光反射
//基本光照模型,高光反射部分的基本公式:Cspecular = (Clight*Mspecular)max(0,v*r)^Mgloss
//Clight入射光線的顏色和強(qiáng)度、Mspecular材質(zhì)的高光反射系數(shù)、v視角方向、r反射方向、Mgloss控制高光區(qū)域大小
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
o.color = ambient + diffuse + specular;
return o;
}
fixed4 frag(v2f i) : SV_Target {
return fixed4(i.color, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
2、實(shí)踐:逐像素
Shader "Unity Shaders Book/Chapter 6/Specular Pixel-Level" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert(a2v v) {
v2f o;
// 頂點(diǎn) 模型坐標(biāo)->裁剪坐標(biāo)
o.pos = UnityObjectToClipPos(v.vertex);
// 法相 模型坐標(biāo)->世界坐標(biāo)
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
// 頂點(diǎn) 模型坐標(biāo)->世界坐標(biāo)
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
//按逐像素的方式處理光照可以得到更加平滑的高光效果
fixed4 frag(v2f i) : SV_Target {
// 獲得環(huán)境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// 計(jì)算漫反射
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
// 計(jì)算反射方向
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
// 獲得世界坐標(biāo)空間下的視角方向
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
// 計(jì)算高光反射
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
3、Blinn-Phong 光照模型
? ? ? ? 在前面以及介紹了這個(gè)Blinn高光反射的光照模型,Blinn模型沒有計(jì)算反射方向,而是引用了一個(gè)新的矢量h,計(jì)算如下:
????????所以Blinn模型公式如下:
? ? ? ? 實(shí)現(xiàn)Blinn模型,只要修改前面的逐像素的高光反射模型代碼的片元著色器
fixed4 frag(v2f i) : SV_Target {
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
// 計(jì)算h向量
fixed3 halfDir = normalize(worldLightDir + viewDir);
// 計(jì)算高光反射
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
4、高光反射光照模型效果展示
四、Unity 的內(nèi)置函數(shù)
函數(shù)名 | 描述 |
---|---|
float3 WorldSpaceViewDir(float4 objectPos) | 輸入模型空間的頂點(diǎn)位置,返回該點(diǎn)到攝像機(jī)的觀察方向 |
float3 UnityWorldSpaceViewDir(float4 worldPos) | 輸入世界空間頂點(diǎn)位置,返回世界空間該點(diǎn)到攝像機(jī)的觀察方向 |
float3 ObjectSpaceViewDir(float4 objectPos) | 輸入模型空間下的頂點(diǎn)位置,返回模型空間下的視角方向(觀察方向) |
float3 WorldSpaceLightDir(float4 objectPos) | (僅可用于前向渲染中)即 Pass的Tags{"LightModel" = "ForwardBase"}文章來源:http://www.zghlxwxcb.cn/news/detail-761405.html 輸入模型空間下的頂點(diǎn)坐標(biāo),返回世界空間下的光源方向(從該點(diǎn)到光源的向量)文章來源地址http://www.zghlxwxcb.cn/news/detail-761405.html |
float3 UnityWorldSpaceLightDir(float4 worldPos) | (僅可用于前向渲染中)輸入世界空間下的頂點(diǎn)坐標(biāo),返回世界空間下的光源方向 |
float3 ObjectSpaceLightDir(float4 objectPos) | (僅可用于前向渲染中)輸入模型空間下的頂點(diǎn)坐標(biāo),返回模型空間下的光源方向 |
float3 UnityObjectToWorldNormal(float3 objectNormal) | 輸入模型空間下的法線,返回世界空間下的法線 |
float3 UnityObjectToWorldDir(float3 objectDir) | 輸入一個(gè)模型空間下的三維矢量,返回世界空間下的三維矢量 |
float3 UnityWorldToObjectDir(float3 worldDir) | 輸入一個(gè)世界空間下的三維矢量,返回模型空間下的三維矢量 |
到了這里,關(guān)于Unity Shader入門精要 第六章——Unity中的基礎(chǔ)光照的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!