簡(jiǎn)介:
介紹了Unity Shader入門精要中初級(jí)篇包含的所有代碼,通過(guò)詳細(xì)拆解代碼,一步一步揭曉Shader的原理。
第5章 開(kāi)始Unity Shader學(xué)習(xí)之旅
5.2.1 頂點(diǎn)/片元著色器的基本結(jié)構(gòu)
Shader "MyShaderName" { //著色器名字
Properties {
// 屬性
}
SubShader {
// 針對(duì)顯卡A的SubShader
Pass {
// 設(shè)置渲染狀態(tài)和標(biāo)簽
// 開(kāi)始CG代碼片段
CGPROGRAM
// 該代碼片段的編譯指令,例如:
#pragma vertex vert
#pragma fragment frag
// CG代碼寫(xiě)在這里
ENDCG //結(jié)束CG代碼
// 其他設(shè)置
}
// 其他需要的Pass
}
SubShader {
// 針對(duì)顯卡B的SubShader
}
// 上述SubShader都失敗后用于回調(diào)的Unity Shader
Fallback "VertexLit"
}
說(shuō)人話:
Shader "MyShaderName" {}
?第一行是著色器的名字,用大括號(hào){} 包裹后續(xù)所有的Shader代碼,關(guān)鍵字 Shader。
Properties {
// 屬性
}
?緊接著是Shader的屬性,用大括號(hào){} 包裹后續(xù)所有的屬性代碼,關(guān)鍵字 Properties。
SubShader {}
?代表子著色器,每個(gè)Shader都會(huì)包含至少一個(gè)SubShader,Unity顯示物體時(shí),回去檢測(cè)這些SubShader,選擇一個(gè)能夠在當(dāng)前顯卡允許的SubShader。
Pass{}
?Pass命令包含渲染狀態(tài)設(shè)置命令的列表,可以有多個(gè)Pass。
CGPROGRAM
//CG代碼
ENDCG
?CG代碼被包含在其中。
#pragma vertex vert
#pragma fragment frag
?該代碼片段的編譯指令,告訴計(jì)算機(jī)哪個(gè)是頂點(diǎn)著色器(vertex),哪個(gè)是片元著色器(fragment)。vert和frag通常分別是它們的名字,也可以更改。
Fallback "VertexLit"
? 上述SubShader都失敗后用于回調(diào)的Unity Shader
最簡(jiǎn)單的頂點(diǎn)/片元著色器
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
// 定義一個(gè)名為 "Simple Shader" 的著色器
Shader "Unity Shaders Book/Chapter 5/Simple Shader" {
// 定義屬性
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1) // 顏色屬性
}
// 定義子著色器
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert // 頂點(diǎn)著色器入口點(diǎn)
#pragma fragment frag // 片段著色器入口點(diǎn)
uniform fixed4 _Color; // 顏色變量
// 定義輸入結(jié)構(gòu)體
struct a2v {
float4 vertex : POSITION; // 頂點(diǎn)位置
float3 normal : NORMAL; // 法線
float4 texcoord : TEXCOORD0; // 紋理坐標(biāo)
};
// 定義輸出結(jié)構(gòu)體
struct v2f {
float4 pos : SV_POSITION; // 頂點(diǎn)位置
fixed3 color : COLOR0; // 顏色
};
// 頂點(diǎn)著色器函數(shù)
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); // 將頂點(diǎn)位置轉(zhuǎn)換為裁剪空間
o.color = v.normal * 0.5 + fixed3(0.5, 0.5, 0.5); // 計(jì)算顏色
return o;
}
// 片元著色器函數(shù)
fixed4 frag(v2f i) : SV_Target {
fixed3 c = i.color;
c *= _Color.rgb; // 使用顏色屬性調(diào)整顏色
return fixed4(c, 1.0); // 返回最終顏色
}
ENDCG
}
}
}
說(shuō)人話:
Shader "Unity Shaders Book/Chapter 5/Simple Shader" {}
? 第一行表示Shader的名字,”/“用來(lái)分隔層次結(jié)構(gòu)。
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1) // 顏色屬性
}
?Properties語(yǔ)義塊中,聲明了一個(gè)屬性_Color,它的類型是Color,初始值是(1.0,1.0,1.0,1.0),對(duì)應(yīng)白色。為了在CG代碼中可以訪問(wèn)它,我們還需要在CG代碼片段中提前定義一個(gè)新的變量,這個(gè)變量的名稱和類型必須與Properties語(yǔ)義塊中的屬性定義相匹配。
?在片元著色器中使用了該屬性。
ShaderLab屬性類型 | CG變量類型 |
---|---|
Color,Vector | float4,half4,fixed4 |
Range,Float | float,half,fixed |
2D | sampler2D |
Cube | samplerCube |
3D | sampler3D |
SubShader { Pass {} }
?定義子著色器和Pass語(yǔ)句塊。Pass命令包含渲染狀態(tài)設(shè)置命令的列表。
CGPROGRAM
ENDCG
?CG代碼被包含在其中。所以 CGPROGRAM通常在Pass語(yǔ)句塊的開(kāi)頭, ENDCG在Pass語(yǔ)句塊結(jié)尾。
#pragma vertex vert
#pragma fragment frag
?它們將告訴Unity,哪個(gè)函數(shù)包含了頂點(diǎn)著色器的代碼,哪個(gè)函數(shù)包含了片元著色器的代碼。更通用的編譯指令表示如下: #pragma vertex name
#pragma fragment name
?其中name就是我們指定的函數(shù)名,這兩個(gè)函數(shù)的名字不一定是vert和frag,它們可以是任意自定義的合法函數(shù)名,但我們一般使用vert和frag來(lái)定義這兩個(gè)函數(shù),因?yàn)樗鼈兒苤庇^。
uniform fixed4 _Color; // 顏色變量
?屬性中有_Color在Pass中也要聲明Pass。uniform可省略,fixed4表示是一個(gè)四維矢量。
// 定義輸入結(jié)構(gòu)體
struct a2v {
float4 vertex : POSITION; // 頂點(diǎn)位置
float3 normal : NORMAL; // 法線
float4 texcoord : TEXCOORD0; // 紋理坐標(biāo)
};
?vertex包含這個(gè)頂點(diǎn)的位置,這是通過(guò)POSITION語(yǔ)義指定的,返回值是一個(gè)float4類型變量,它是該頂點(diǎn)在裁剪空間中的位置。這些語(yǔ)義將告訴系統(tǒng)用戶需要哪些輸入值,以及用戶的輸出是什么。
?POSITON語(yǔ)義告訴Unity,用模型空間頂點(diǎn)坐標(biāo)填充vertex變量。
?NORMAL告訴Unity,用模型空間頂點(diǎn)坐標(biāo)填充normal變量。
?TECCOORD0告訴Unity,用模型的第一套紋理坐標(biāo)填充texcoord。
?a表示應(yīng)用(application), v表示頂點(diǎn)著色器(vertex shader), a2v的意思就是把數(shù)據(jù)從應(yīng)用階段傳遞到頂點(diǎn)著色器中。
// 定義輸出結(jié)構(gòu)體
struct v2f {
float4 pos : SV_POSITION; // 頂點(diǎn)位置
fixed3 color : COLOR0; // 顏色
};
?SV_POSITION將告訴Unity,pos里包含了頂點(diǎn)在裁剪空間的位置信息。
?COLORO語(yǔ)義可以用來(lái)存儲(chǔ)顏色信息。
// 頂點(diǎn)著色器函數(shù)
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); // 將頂點(diǎn)位置轉(zhuǎn)換為裁剪空間
o.color = v.normal * 0.5 + fixed3(0.5, 0.5, 0.5); // 計(jì)算顏色
return o;
}
?聲明輸出結(jié)構(gòu)體 v2f o。 參數(shù)a2v v是輸入結(jié)構(gòu)體。
?vert函數(shù)的返回值是v2f類型的。傳入?yún)?shù)a2v v用于進(jìn)行頂點(diǎn)和片元的信息傳遞。
?o.pos = UnityObjectToClipPos(v.vertex) 將頂點(diǎn)位置轉(zhuǎn)換為裁剪空間。
?v.normal包含頂點(diǎn)的法線方向,其分量范圍在[-1.0,1.0],將法線歸一化后,法線有可能是正向和反向,所以分量范圍是[-1.0,1.0]。
?o.color = v.normal * 0.5 + fixed3(0.5, 0.5, 0.5); 把分量范圍映射到[0.0,1.0] ,也就是將范圍[-1.0,1.0]乘以0.5在加上0.5,映射到[0.0,1.0] 。
?return o 頂點(diǎn)著色器最重要的是返回裁剪空間中的頂點(diǎn)信息。受到結(jié)構(gòu)體v2f中的語(yǔ)義控制
float4 pos : SV_POSITION; // 頂點(diǎn)位置 fixed3 color : COLOR0; // 顏色 SV表示系統(tǒng)數(shù)值語(yǔ)義用SV_POSITION語(yǔ)義去修飾頂點(diǎn)著色器的輸出變量pos,那么就表示pos包含了可用于光柵化的變換后的頂點(diǎn)坐標(biāo)(即齊次裁剪空間中的坐標(biāo))。通常SV表示輸出。
語(yǔ)義 | 描述 |
---|---|
POSITION | 模型空間中的頂點(diǎn)位置,通常是float4類型 |
NORMAL | 頂點(diǎn)法線,通常是float3類型 |
TANGENT | 頂點(diǎn)切線,通常是float4類型 |
TEXCOORDn,TEXCOORD0,TEXCOORD1 | 該頂點(diǎn)的紋理坐標(biāo),TEXCOORD0表示第一組紋理坐標(biāo)。 |
COLOR | 頂點(diǎn)顏色,通常是fixed4或float4類型 |
// 片元著色器函數(shù)
fixed4 frag(v2f i) : SV_Target {
fixed3 c = i.color;
c *= _Color.rgb; // 使用顏色屬性調(diào)整顏色
return fixed4(c, 1.0); // 返回最終顏色
}
?參數(shù) v2f i,輸入結(jié)構(gòu)體 i。同時(shí)也是頂點(diǎn)著色器的輸出結(jié)構(gòu)體。
?SV_Target也是HLSL中的一個(gè)系統(tǒng)語(yǔ)義,它等同于告訴渲染器,把用戶的輸出顏色存儲(chǔ)到一個(gè)渲染目標(biāo)(render target)中,這里將輸出到默認(rèn)的幀緩存中。
?fixed3 c = i.color 用fixed類型的變量c獲得輸出結(jié)構(gòu)體的顏色。
?c *= _Color.rgb 使用顏色屬性調(diào)整顏色。rgb表示紅綠藍(lán),是fixed 3類型的。
?return fixed4(c, 1.0) ,返回值是一個(gè)fixded4類型變量。受到SV_Target語(yǔ)義的控制,表示渲染的顏色。
第6章 Unity中的基礎(chǔ)光照
6.4.1 逐頂點(diǎn)漫反射光照
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 6/Diffuse Vertex-Level" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
}
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
fixed3 color : COLOR;
};
v2f vert(a2v v) {
v2f o;
// Transform the vertex from object space to projection space
o.pos = UnityObjectToClipPos(v.vertex);
// Get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// Transform the normal from object space to world space
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
// Get the light direction in world space
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
// Compute diffuse term
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));
o.color = ambient + diffuse;
return o;
}
fixed4 frag(v2f i) : SV_Target {
return fixed4(i.color, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
說(shuō)人話:
Shader "Unity Shaders Book/Chapter 6/Diffuse Vertex-Level" {}
?為這個(gè)Shader起一個(gè)名字。
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
}
?為了得到并且控制材質(zhì)的漫反射顏色,我們首先在Shader的Properties語(yǔ)義塊中聲明了一個(gè)Color類型的屬性,并把它的初始值設(shè)為白色。
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
}
}
?在SubShader語(yǔ)義塊中定義了一個(gè)Pass語(yǔ)義塊。這是因?yàn)轫旤c(diǎn)/片元著色器的代碼需要寫(xiě)在Pass語(yǔ)義塊,而非SubShader語(yǔ)義塊中。而且,我們?cè)赑ass的第一行指明了該P(yáng)ass的光照模式。
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
?使用CGPROGRAM和ENDCG來(lái)包圍CG代碼片,以定義最重要的頂點(diǎn)著色器和片元著色器代碼。首先,我們使用#pragma指令來(lái)告訴Unity,我們定義的頂點(diǎn)著色器和片元著色器叫什么名字。在本例中,它們的名字分別是vert和frag。
#include "Lighting.cginc"
?為了使用Unity內(nèi)置的一些變量,如后面要講到的_LightColor0,還需要包含進(jìn)Unity的內(nèi)置文件Lighting.cginc。
fixed4 _Diffuse;
?為了在Shader中使用Properties語(yǔ)義塊中聲明的屬性,我們需要定義一個(gè)和該屬性類型相匹配的變量:
?由于顏色屬性的范圍在0到1之間,因此我們可以使用fixed精度的變量來(lái)存儲(chǔ)它。
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
fixed3 color : COLOR;
};
?然后,我們定義了頂點(diǎn)著色器的輸入和輸出結(jié)構(gòu)體(輸出結(jié)構(gòu)體同時(shí)也是片元著色器的輸入結(jié)構(gòu)體)。
v2f vert(a2v v) {
v2f o;
// Transform the vertex from object space to projection space
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
// Get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// Transform the normal fram object space to world space
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)_World2Object));
// Get the light direction in world space
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
// Compute diffuse term
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,
worldLight));
o.color = ambient + diffuse;
return o;
}
?在第一行,我們首先定義了返回值o。我們已經(jīng)重復(fù)過(guò)很多次,頂點(diǎn)著色器最基本的任務(wù)就是把頂點(diǎn)位置從模型空間轉(zhuǎn)換到裁剪空間中。
? o.pos = mul(UNITY_MATRIX_MVP, v.vertex) 因此我們需要使用Unity內(nèi)置的模型世界投影矩陣UNITY_MATRIX_MVP來(lái)完成這樣的坐標(biāo)變換?;蛘呤褂胦.pos = UnityObjectToClipPos(v.vertex) 將頂點(diǎn)位置轉(zhuǎn)換為裁剪空間
? fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz 接下來(lái),我們通過(guò)Unity的內(nèi)置變量UNITY_LIGHTMODEL_AMBIENT得到了環(huán)境光部分。
?fixed3 worldNormal = normalize(mul(v.normal, (float3x3)World2Object)) 因?yàn)閍2v得到的頂點(diǎn)法線是位于模型空間下的,World2Object是模型到空間的逆矩陣。通過(guò)模型空間法線和模型到空間的逆矩陣相乘將法線轉(zhuǎn)換到世界空間中。法線是一個(gè)三維矢量,因此我們只需要截取_World2Object的前三行前三列即可。
?在得到了世界空間中的法線和光源方向后,我們需要對(duì)它們進(jìn)行歸一化操作。
?fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz) 獲得指向光源的單位矢量。
? 漫反射計(jì)算公式 clight:入射光線顏色和強(qiáng)度 mdiffuse:材質(zhì)的漫反射系數(shù) n:表面法線 I:光源方向
?fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,
worldLight)) 在得到它們點(diǎn)積的結(jié)果后,我們需要防止這個(gè)結(jié)果為負(fù)值。為此,我們使用了saturate函數(shù)。saturate函數(shù)是CG提供的一種函數(shù),它的作用是可以把參數(shù)截取到[0, 1]的范圍內(nèi)。最后,再與光源的顏色和強(qiáng)度以及材質(zhì)的漫反射顏色相乘即可得到最終的漫反射光照部分。
6.4.2 逐像素漫反射光照
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 6/Diffuse Pixel-Level" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
}
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
};
v2f vert(a2v v) {
v2f o;
// Transform the vertex from object space to projection space
o.pos = UnityObjectToClipPos(v.vertex);
// Transform the normal from object space to world space
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
return o;
}
fixed4 frag(v2f i) : SV_Target {
// Get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// Get the normal in world space
fixed3 worldNormal = normalize(i.worldNormal);
// Get the light direction in world space
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// Compute diffuse term
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
fixed3 color = ambient + diffuse;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
說(shuō)人話:
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
};
?修改頂點(diǎn)著色器的輸出結(jié)構(gòu)體v2f。從fixed3 color : COLOR修改為float3 worldNormal : TEXCOORD0。
v2f vert(a2v v) {
v2f o;
// Transform the vertex from object space to projection space
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
// Transform the normal fram object space to world space
o.worldNormal = mul(v.normal, (float3x3)_World2Object);
return o;
}
?頂點(diǎn)著色器不需要計(jì)算光照模型,只需要把世界空間下的法線傳遞給片元著色器即可。在片元著色器中計(jì)算光照模型。只有頂點(diǎn)著色器可以得到頂點(diǎn)的法線以及頂點(diǎn)相關(guān)的位置信息,片元著色器得不到頂點(diǎn),所以需要頂點(diǎn)著色器中進(jìn)行計(jì)算然后傳遞。
?v2f o 聲明輸出結(jié)構(gòu)體。
?o.pos = mul(UNITY_MATRIX_MVP, v.vertex) 計(jì)算世界空間下頂點(diǎn)位置。
? o.worldNormal = mul(v.normal, (float3x3)_World2Object) 計(jì)算頂點(diǎn)世界法線位置。
fixed4 frag(v2f i) : SV_Target {
// Get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// Get the normal in world space
fixed3 worldNormal = normalize(i.worldNormal);
// Get the light direction in world space
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// Compute diffuse term
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,
worldLightDir));
fixed3 color = ambient + diffuse;
return fixed4(color, 1.0);
}
?片元著色器需要計(jì)算漫反射光照模型。
?fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz 通過(guò)內(nèi)置函數(shù)獲得自發(fā)光。
?fixed3 worldNormal = normalize(i.worldNormal) 歸一化世界法線。
?fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz) 歸一化世界光源方向。
? fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,
worldLightDir)) 根據(jù)光照模型計(jì)算漫反射。
?fixed3 color = ambient + diffuse 得到自發(fā)光和漫反射的顏色。
?逐頂點(diǎn)和逐像素的區(qū)別:
逐頂點(diǎn)光照是在 頂點(diǎn)shader的時(shí)候計(jì)算頂點(diǎn)光照顏色,然后到片元著色的時(shí)候通過(guò)插值,得到片元的光照計(jì)算的結(jié)果。逐像素光照是在片元著色的時(shí)候做光照計(jì)算,逐像素光照性能開(kāi)銷大,但是效果好,逐頂點(diǎn)光照,性能開(kāi)銷小,效果比逐像素差。
6.5.1 逐頂點(diǎn)高光反射光照
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 6/Specular Vertex-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;
fixed3 color : COLOR;
};
v2f vert(a2v v) {
v2f o;
// Transform the vertex from object space to projection space
o.pos = UnityObjectToClipPos(v.vertex);
// Get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// Transform the normal from object space to world space
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
// Get the light direction in world space
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// Compute diffuse term
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
// Get the reflect direction in world space
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
// Get the view direction in world space
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
// Compute specular term
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"
}
?高光反射計(jì)算公式
clight:入射光線的顏色和強(qiáng)度 mspecluar:材質(zhì)的高光反射系數(shù) v:視角方向 r:反射方向
?反射方向r由表面法線n和光源方向I計(jì)算得到。可推導(dǎo)而成
?CG提供了計(jì)算反射方向的函數(shù)reflect 函數(shù):reflect(i, n)
參數(shù):i,入射方向;n,法線方向??梢允莊loat、float2、float3等類型。
描述:當(dāng)給定入射方向i和法線方向n時(shí),reflect函數(shù)可以返回反射方向。圖6.9給出了參數(shù)和返回值之間的關(guān)系。
說(shuō)人話:
Shader "Unity Shaders Book/Chapter 6/Specular Vertex-Level" { }
?首先,我們需要為這個(gè)Shader起一個(gè)名字。
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
?為了在材質(zhì)面板中能夠方便地控制高光反射屬性,我們?cè)赟hader的Properties語(yǔ)義塊中聲明了三個(gè)屬性。其中,新添加的Specular用于控制材質(zhì)的高光反射顏色,而Gloss用于控制高光區(qū)域的大小。
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
}
}
?我們?cè)赟ubShader語(yǔ)義塊中定義了一個(gè)Pass語(yǔ)義塊。這是因?yàn)轫旤c(diǎn)/片元著色器的代碼需要寫(xiě)在Pass語(yǔ)義塊,而非SubShader語(yǔ)義塊中。而且,我們?cè)赑ass的第一行指明了該P(yáng)ass的光照模式。
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
?使用CGPROGRAM和ENDCG來(lái)包圍CG代碼片,以定義最重要的頂點(diǎn)著色器和片元著色器代碼。首先,我們使用#pragma指令來(lái)告訴Unity,我們定義的頂點(diǎn)著色器和片元著色器叫什么名字。在本例中,它們的名字分別是vert和frag。
#include "Lighting.cginc"
?為了使用Unity內(nèi)置的一些變量,如_LightColor0,還需要包含進(jìn)Unity的內(nèi)置文件Lighting.cginc。
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
?為了在Shader中使用Properties語(yǔ)義塊中聲明的屬性,我們需要定義和這些屬性類型相匹配的變量
?由于顏色屬性的范圍在0到1之間,因此對(duì)于_Diffuse和_Specular屬性我們可以使用fixed精度的變量來(lái)存儲(chǔ)它。而_Gloss的范圍很大,因此我們使用float精度來(lái)存儲(chǔ)。
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
fixed3 color : COLOR;
};
?然后,我們定義了頂點(diǎn)著色器的輸入和輸出結(jié)構(gòu)體(輸出結(jié)構(gòu)體同時(shí)也是片元著色器的輸入結(jié)構(gòu)體)。
v2f vert(a2v v) {
v2f o;
// Transform the vertex from object space to projection space
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
// Get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// Transform the normal fram object space to world space
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)_World2Object));
// Get the light direction in world space
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// Compute diffuse term
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
// Get the reflect direction in world space
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
// Get the view direction in world space
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(_Object2World, v.vertex).xyz);
// Compute specular term
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
o.color = ambient + diffuse + specular;
return o;
}
?在頂點(diǎn)著色器中,我們計(jì)算了包含高光反射的光照模型。
?漫反射部分的計(jì)算和6.4節(jié)中的代碼完全一致。
? fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal)) 我們首先計(jì)算了入射光線方向關(guān)于表面法線的反射方向reflectDir。由于CG的reflect函數(shù)的入射方向要求是由光源指向交點(diǎn)處的,因此我們需要對(duì)worldLightDir取反后再傳給reflect函數(shù)。worldLightDir是交點(diǎn)處指向光源。
?fixed3 viewDir = normalize(WorldSpaceCameraPos.xyz - mul(Object2World, v.vertex).xyz) 然后,我們通過(guò)_WorldSpaceCameraPos得到了世界空間中的攝像機(jī)位置,再通過(guò)和頂點(diǎn)世界空間位置相減即可得到世界空間下的視角方向,這是單位視角方向。
? fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss) 得到了所有的4個(gè)參數(shù),代入公式即可得到高光反射的光照部分。最后,再和環(huán)境光、漫反射光相加存儲(chǔ)到最后的顏色中。
fixed4 frag(v2f i) : SV_Target {
return fixed4(i.color, 1.0);
}
?片元著色器的代碼非常簡(jiǎn)單,我們只需要直接返回頂點(diǎn)顏色即可。
Fallback "Specular"
?)最后,我們需要把這個(gè)Unity Shader的回調(diào)Shader設(shè)置為內(nèi)置的Specular。
6.5.2 逐像素高光反射光照
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
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;
// Transform the vertex from object space to projection space
o.pos = UnityObjectToClipPos(v.vertex);
// Transform the normal from object space to world space
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
// Transform the vertex from object spacet to world space
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target {
// Get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// Compute diffuse term
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
// Get the reflect direction in world space
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
// Get the view direction in world space
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
// Compute specular term
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
說(shuō)人話:
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
?修改頂點(diǎn)著色器的輸出結(jié)構(gòu)體v2f。修改為世界法線和世界坐標(biāo)。語(yǔ)義為第一組紋理坐標(biāo)和第二組紋理坐標(biāo)。
v2f vert(a2v v) {
v2f o;
// Transform the vertex from object space to projection space
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
// Transform the normal fram object space to world space
o.worldNormal = mul(v.normal, (float3x3)_World2Object);
// Transform the vertex from object space to world space
o.worldPos = mul(_Object2World, v.vertex).xyz;
return o;
}
?頂點(diǎn)著色器只需要計(jì)算世界空間下的法線方向和頂點(diǎn)坐標(biāo),并把它們傳遞給片元著色器即可。
fixed4 frag(v2f i) : SV_Target {
// Get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// Compute diffuse term
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
// Get the reflect direction in world space
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
// Get the view direction in world space
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
// Compute specular term
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
?片元著色器需要計(jì)算關(guān)鍵的光照模型。
6.5.3 Blinn-Phong 光照模型
?Blinn模型沒(méi)有使用反射方向,而是引入一個(gè)新的矢量h,它是通過(guò)對(duì)視角方向v和光照方向I相加后再歸一化得到的。即
而B(niǎo)linn模型計(jì)算高光反射的公式如下:
將逐像素高光反射SHader中的片元著色器中對(duì)高光反射部分的計(jì)算代碼進(jìn)行修改。
fixed4 frag(v2f i) : SV_Target {
...
// Get the view direction in world space
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
// Get the half direction in world space
fixed3 halfDir = normalize(worldLightDir + viewDir);
// Compute specular term
fixed3 specular=_LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
?fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz) 得到攝像機(jī)視角。
?fixed3 halfDir = normalize(worldLightDir + viewDir) 得到新的矢量h。通過(guò)將世界光照方向和攝像機(jī)視角方向歸一化得到的。
? fixed3 specular=_LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss) 按照Blinn-Phong 光照模型公式進(jìn)行計(jì)算得到高光反射。
6.6.0 Unity 內(nèi)置的函數(shù)
UnityCG.cginc中一些常用的幫助函數(shù):
函數(shù)名 | 描述 |
---|---|
float3 WorldSpaceViewDir (float4 v) | 輸入一個(gè)模型空間中的頂點(diǎn)位置,返回世界空間中從該點(diǎn)到攝像機(jī)的觀察方向,內(nèi)部實(shí)現(xiàn)使用UnityWorldSpaceViewDir函數(shù) |
float3 UnityWorldSpaceViewDir (float4 v) | 輸入一個(gè)世界空間中的頂點(diǎn)位置,返回世界空間中從該點(diǎn)到攝像機(jī)的觀察方向 |
float3 ObjSpaceViewDir (float4 v) | 輸入一個(gè)模型空間中的頂點(diǎn)位置,返回模型空間中從該點(diǎn)到攝像機(jī)的觀察方向 |
float3 WorldSpaceLightDir (float4 v) | 僅可用于前向渲染中,輸入一個(gè)模型空間中的頂點(diǎn)位置,返回世界空間中從該點(diǎn)到光源的光照方向。內(nèi)部實(shí)現(xiàn)使用了UnityWorldSpaceLightDir函數(shù),沒(méi)有被歸一化 |
float3 UnityWorldSpaceLightDir (float4 v) | 僅可用于前向渲染中,輸入一個(gè)世界空間中的頂點(diǎn)位置,返回世界空間中從該點(diǎn)到光源的光照方向。沒(méi)有被歸一化 |
float3 ObjSpaceLightDir (float4 v) | 僅可用于前向渲染中,輸入一個(gè)模型空間中的頂點(diǎn)位置,返回模型空間中從該點(diǎn)到光源的光照方向。沒(méi)有被歸一化 |
float3 UnityObjectToWorldNormal (float3 norm) | 把法線方向從模型空間轉(zhuǎn)換到世界空間中 |
float3 UnityObjectToWorldDir (in float3 dir) | 把方向矢量從模型空間轉(zhuǎn)換到世界空間中 |
float3 UnityWorldToWorldDir (float3 dir) | 把方向矢量從世界空間轉(zhuǎn)換到模型空間中 |
?需要注意的是,這些函數(shù)都沒(méi)有保證得到的方向矢量是單位矢量,因此,我們需要在使用前把它們歸一化。
6.6.1 內(nèi)置函數(shù)來(lái)改寫(xiě)B(tài)linn-Phong光照模型的Unity Shader
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 6/Blinn-Phong Use Built-in Functions" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(1.0, 500)) = 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;
float4 worldPos : TEXCOORD1;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
// Use the build-in funtion to compute the normal in world space
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.worldNormal);
// Use the build-in funtion to compute the light direction in world space
// Remember to normalize the result
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
// Use the build-in funtion to compute the view direction in world space
// Remember to normalize the result
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
說(shuō)人話:
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
// Use the build-in funtion to compute the normal in world space
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
return o;
}
?o.pos = UnityObjectToClipPos(v.vertex) 把頂點(diǎn)位置從模型空間轉(zhuǎn)換到裁剪空間。
o.worldNormal = UnityObjectToWorldNormal(v.normal) 把法線方向從模型空間轉(zhuǎn)換到世界空間中。
fixed4 frag(v2f i) : SV_Target {
...
fixed3 worldNormal = normalize(i.worldNormal);
// Use the build-in function to compute the light direction in world space
// Remember to normalize the result
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
...
// Use the build-in function to compute the view direction in world space
// Remember to normalize the result
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
...
}
?在片元著色器中,我們使用內(nèi)置的UnityWorldSpaceLightDir函數(shù)和UnityWorldSpaceView Dir函數(shù)來(lái)分別計(jì)算世界空間的光照方向和視角方向。
? fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)) 輸入一個(gè)世界空間的頂點(diǎn)位置,返回世界空間中從該點(diǎn)到光源的光照方向,沒(méi)有被歸一化。
?fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)) 輸入一個(gè)世界空間中的頂點(diǎn)位置,返回世界空間中從該點(diǎn)到攝像機(jī)的觀察方向。沒(méi)有被歸一化。
第7章 基礎(chǔ)紋理
7.1.1 單張紋理Shader
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 7/Single Texture" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_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 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
// Or just call the built-in function
// o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
// Use the texture to sample the diffuse color
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
說(shuō)人話:
Shader "Unity Shaders Book/Chapter 7/Single Texture" {
?首先,我們需要為這個(gè)Unity Shader起一個(gè)名字。
Properties {
_Color ("Color Tint", Color) = (1,1,1,1)
_MainTex ("Main Tex", 2D) = "white" {}
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
?為了使用紋理,我們需要在Properties語(yǔ)義塊中添加一個(gè)紋理屬性。
?MainTex的紋理,在3.3.2節(jié)中,我們已經(jīng)知道2D是紋理屬性的聲明方式。我們使用一個(gè)字符串后跟一個(gè)花括號(hào)作為它的初始值,“white”是內(nèi)置紋理的名字,也就是一個(gè)全白的紋理。為了控制物體的整體色調(diào),我們還聲明了一個(gè)Color屬性。
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
}
}
?然后,我們?cè)赟ubShader語(yǔ)義塊中定義了一個(gè)Pass語(yǔ)義塊。而且,我們?cè)赑ass的第一行指明了該P(yáng)ass的光照模式。
?LightMode標(biāo)簽是Pass標(biāo)簽中的一種,它用于定義該P(yáng)ass在Unity的光照流水線中的角色。
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
?接著,我們使用CGPROGRAM和ENDCG來(lái)包圍住CG代碼片,以定義最重要的頂點(diǎn)著色器和片元著色器代碼。首先,我們使用#pragma指令來(lái)告訴Unity,我們定義的頂點(diǎn)著色器和片元著色器叫什么名字。在本例中,它們的名字分別是vert和frag。
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Specular;
float _Gloss;
?我們需要在CG代碼片中聲明和上述屬性類型相匹配的變量,以便和材質(zhì)面板中的屬性建立聯(lián)系。
?與其他屬性類型不同的是,我們還需要為紋理類型的屬性聲明一個(gè)float4類型的變量MainTex_ST。其中,MainTex_ST的名字不是任意起的。在Unity中,我們需要使用紋理名ST的方式來(lái)聲明某個(gè)紋理的屬性。其中,ST是縮放(scale)和平移(translation)的縮寫(xiě)。MainTex_ST可以讓我們得到該紋理的縮放和平移(偏移)值,MainTex_ST.xy存儲(chǔ)的是縮放值,而MainTex_ST.zw存儲(chǔ)的是偏移值。這些值可以在材質(zhì)面板的紋理屬性中調(diào)節(jié)。
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
?接下來(lái),我們需要定義頂點(diǎn)著色器的輸入和輸出結(jié)構(gòu)體。
?在上面的代碼中,我們首先在a2v結(jié)構(gòu)體中使用TEXCOORD0語(yǔ)義聲明了一個(gè)新的變量texcoord,這樣Unity就會(huì)將模型的第一組紋理坐標(biāo)存儲(chǔ)到該變量中。然后,我們?cè)趘2f結(jié)構(gòu)體中添加了用于存儲(chǔ)紋理坐標(biāo)的變量uv,以便在片元著色器中使用該坐標(biāo)進(jìn)行紋理采樣。
v2f vert(a2v v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(_Object2World, v.vertex).xyz;
o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
// Or just call the built-in function
// o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
?然后,我們定義了頂點(diǎn)著色器。
? o.uv = v.texcoord.xy * MainTex_ST.xy + MainTex_ST.zw 在頂點(diǎn)著色器中,我們使用紋理的屬性值MainTex_ST來(lái)對(duì)頂點(diǎn)紋理坐標(biāo)(v.texcoord.xy )進(jìn)行變換,得到最終的紋理坐標(biāo)。計(jì)算過(guò)程是,首先使用縮放屬性MainTex_ST.xy對(duì)頂點(diǎn)紋理坐標(biāo)(v.texcoord.xy ))進(jìn)行縮放,然后再使用偏移屬性MainTex_ST.zw對(duì)結(jié)果進(jìn)行偏移。Unity提供了一個(gè)內(nèi)置宏TRANSFORM_TEX來(lái)幫我們計(jì)算上述過(guò)程。TRANSFORM_TEX是在UnityCG.cginc中定義的。也就是o.uv = TRANSFORM_TEX(v.texcoord, _MainTex)。
// Transforms 2D UV by scale/bias property
#define TRANSFORM_TEX(tex, name) (tex.xy * name##_ST.xy + name##_ST.zw)
它接受兩個(gè)參數(shù),第一個(gè)參數(shù)是頂點(diǎn)紋理坐標(biāo),第二個(gè)參數(shù)是紋理名,在它的實(shí)現(xiàn)中,將利用紋理名_ST的方式來(lái)計(jì)算變換后的紋理坐標(biāo)。
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
// Use the texture to sample the diffuse color
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal,
halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
?我們還需要實(shí)現(xiàn)片元著色器,并在計(jì)算漫反射時(shí)使用紋理中的紋素值。
?上面的代碼首先計(jì)算了世界空間下的法線方向和光照方向。然后,使用CG的tex2D函數(shù)對(duì)紋理進(jìn)行采樣。它的第一個(gè)參數(shù)是需要被采樣的紋理,第二個(gè)參數(shù)是一個(gè)float2類型的紋理坐標(biāo),它將返回計(jì)算得到的紋素值。我們使用采樣結(jié)果和顏色屬性_Color的乘積來(lái)作為材質(zhì)的反射率albedo,并把它和環(huán)境光照相乘得到環(huán)境光部分。隨后,我們使用albedo來(lái)計(jì)算漫反射光照的結(jié)果,并和環(huán)境光照、高光反射光照相加后返回。
? fixed3 worldNormal = normalize(i.worldNormal); 計(jì)算世界空間下法線方向
?fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));計(jì)算世界空間下光照方向
? fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb; 使用CG的tex2D函數(shù)對(duì)紋理進(jìn)行采樣。使用采樣結(jié)果和顏色屬性Color的乘積來(lái)作為材質(zhì)的反射率albedo。
?fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; 并把它和環(huán)境光照相乘得到環(huán)境光部分
? fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));隨后,我們使用albedo來(lái)計(jì)算漫反射光照的結(jié)果
?return fixed4(ambient + diffuse + specular, 1.0); 并和環(huán)境光照、高光反射光照相加后返回。
Fallback "Specular"
?最后,我們?yōu)樵揝hader設(shè)置了合適的Fallback。
7.2.1 高度紋理
7.2.2 法線紋理
由于法線方向的分量范圍在[-1, 1],而像素的分量范圍為[0, 1],因此我們需要做一個(gè)映射,通常使用的映射就是
這就要求,我們?cè)赟hader中對(duì)法線紋理進(jìn)行紋理采樣后,還需要對(duì)結(jié)果進(jìn)行一次反映射的過(guò)程,以得到原先的法線方向。反映射的過(guò)程實(shí)際就是使用上面映射函數(shù)的逆函數(shù):
normal=pixel ×2-1
1,在切線空間下計(jì)算
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 7/Normal Map In Tangent Space" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_BumpMap ("Normal Map", 2D) = "bump" {}
_BumpScale ("Bump Scale", Float) = 1.0
_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 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float3 lightDir: TEXCOORD1;
float3 viewDir : TEXCOORD2;
};
// Unity doesn't support the 'inverse' function in native shader
// so we write one by our own
// Note: this function is just a demonstration, not too confident on the math or the speed
// Reference: http://answers.unity3d.com/questions/218333/shader-inversefloat4x4-function.html
float4x4 inverse(float4x4 input) {
#define minor(a,b,c) determinant(float3x3(input.a, input.b, input.c))
float4x4 cofactors = float4x4(
minor(_22_23_24, _32_33_34, _42_43_44),
-minor(_21_23_24, _31_33_34, _41_43_44),
minor(_21_22_24, _31_32_34, _41_42_44),
-minor(_21_22_23, _31_32_33, _41_42_43),
-minor(_12_13_14, _32_33_34, _42_43_44),
minor(_11_13_14, _31_33_34, _41_43_44),
-minor(_11_12_14, _31_32_34, _41_42_44),
minor(_11_12_13, _31_32_33, _41_42_43),
minor(_12_13_14, _22_23_24, _42_43_44),
-minor(_11_13_14, _21_23_24, _41_43_44),
minor(_11_12_14, _21_22_24, _41_42_44),
-minor(_11_12_13, _21_22_23, _41_42_43),
-minor(_12_13_14, _22_23_24, _32_33_34),
minor(_11_13_14, _21_23_24, _31_33_34),
-minor(_11_12_14, _21_22_24, _31_32_34),
minor(_11_12_13, _21_22_23, _31_32_33)
);
#undef minor
return transpose(cofactors) / determinant(input);
}
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
///
/// Note that the code below can handle both uniform and non-uniform scales
///
// Construct a matrix that transforms a point/vector from tangent space to world space
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
/*
float4x4 tangentToWorld = float4x4(worldTangent.x, worldBinormal.x, worldNormal.x, 0.0,
worldTangent.y, worldBinormal.y, worldNormal.y, 0.0,
worldTangent.z, worldBinormal.z, worldNormal.z, 0.0,
0.0, 0.0, 0.0, 1.0);
// The matrix that transforms from world space to tangent space is inverse of tangentToWorld
float3x3 worldToTangent = inverse(tangentToWorld);
*/
//wToT = the inverse of tToW = the transpose of tToW as long as tToW is an orthogonal matrix.
float3x3 worldToTangent = float3x3(worldTangent, worldBinormal, worldNormal);
// Transform the light and view dir from world space to tangent space
o.lightDir = mul(worldToTangent, WorldSpaceLightDir(v.vertex));
o.viewDir = mul(worldToTangent, WorldSpaceViewDir(v.vertex));
///
/// Note that the code below can only handle uniform scales, not including non-uniform scales
///
// Compute the binormal
// float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w;
// // Construct a matrix which transform vectors from object space to tangent space
// float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);
// Or just use the built-in macro
// TANGENT_SPACE_ROTATION;
//
// // Transform the light direction from object space to tangent space
// o.lightDir = mul(rotation, normalize(ObjSpaceLightDir(v.vertex))).xyz;
// // Transform the view direction from object space to tangent space
// o.viewDir = mul(rotation, normalize(ObjSpaceViewDir(v.vertex))).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 tangentLightDir = normalize(i.lightDir);
fixed3 tangentViewDir = normalize(i.viewDir);
// Get the texel in the normal map
fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);
fixed3 tangentNormal;
// If the texture is not marked as "Normal map"
// tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;
// tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
// Or mark the texture as "Normal map", and use the built-in funciton
tangentNormal = UnpackNormal(packedNormal);
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));
fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
說(shuō)人話:
Shader "Unity Shaders Book/Chapter 7/Normal Map In Tangent Space" { }
?首先,我們?yōu)樵揢nity Shader定義一個(gè)名字。
Properties {
_Color ("Color Tint", Color) = (1,1,1,1)
_MainTex ("Main Tex", 2D) = "white" {}
_BumpMap ("Normal Map", 2D) = "bump" {}
_BumpScale ("Bump Scale", Float) = 1.0
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
?然后,我們?cè)赑roperties語(yǔ)義塊中添加了法線紋理的屬性,以及用于控制凹凸程度的屬性。
?對(duì)于法線紋理BumpMap,我們使用"bump"作為它的默認(rèn)值。"bump"是Unity內(nèi)置的法線紋理,當(dāng)沒(méi)有提供任何法線紋理時(shí),"bump"就對(duì)應(yīng)了模型自帶的法線信息。
?BumpScale則是用于控制凹凸程度的,當(dāng)它為0時(shí),意味著該法線紋理不會(huì)對(duì)光照產(chǎn)生任何影響。
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
}
}
?我們?cè)赟ubShader語(yǔ)義塊中定義了一個(gè)Pass語(yǔ)義塊,并且在Pass的第一行指明了該P(yáng)ass的光照模式。
?LightMode標(biāo)簽是Pass標(biāo)簽中的一種,它用于定義該P(yáng)ass在Unity的光照流水線中的角色。
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
?)接著,我們使用CGPROGRAM和ENDCG來(lái)包圍住CG代碼片,以定義最重要的頂點(diǎn)著色器和片元著色器代碼。首先,我們使用#pragma指令來(lái)告訴Unity,我們定義的頂點(diǎn)著色器和片元著色器叫什么名字。在本例中,它們的名字分別是vert和frag。
#include "Lighting.cginc"
?為了使用Unity內(nèi)置的一些變量,如_LightColor0,還需要包含進(jìn)Unity的內(nèi)置文件Lighting.cginc。
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
fixed4 _Specular;
float _Gloss;
?為了和Properties語(yǔ)義塊中的屬性建立聯(lián)系,我們?cè)贑G代碼塊中聲明了和上述屬性類型匹配的變量。
?為了得到該紋理的屬性(平鋪和偏移系數(shù)),我們?yōu)開(kāi)MainTex和_BumpMap定義了_MainTex_ST和_BumpMap_ST變量。
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};
?我們已經(jīng)知道,切線空間是由頂點(diǎn)法線和切線構(gòu)建出的一個(gè)坐標(biāo)空間,因此我們需要得到頂點(diǎn)的切線信息。為此,我們修改頂點(diǎn)著色器的輸入結(jié)構(gòu)體a2v。
?我們使用TANGENT語(yǔ)義來(lái)描述float4類型的tangent變量,以告訴Unity把頂點(diǎn)的切線方向填充到tangent變量中。需要注意的是,和法線方向normal不同,tangent的類型是float4,而非float3,這是因?yàn)槲覀冃枰褂胻angent.w分量來(lái)決定切線空間中的第三個(gè)坐標(biāo)軸——副切線的方向性。
struct v2f {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float3 lightDir: TEXCOORD1;
float3 viewDir : TEXCOORD2;
};
?我們需要在頂點(diǎn)著色器中計(jì)算切線空間下的光照和視角方向,因此我們?cè)趘2f結(jié)構(gòu)體中添加了兩個(gè)變量來(lái)存儲(chǔ)變換后的光照和視角方向。
?由于我們使用了兩張紋理,因此需要存儲(chǔ)兩個(gè)紋理坐標(biāo)。為此,我們把v2f中的uv變量的類型定義為float4類型。
v2f vert(a2v v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
// Compute the binormal
// float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) *
v.tangent.w;
// // Construct a matrix which transform vectors from object space to tangent space
// float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);
// Or just use the built-in macro
TANGENT_SPACE_ROTATION;
// Transform the light direction from object space to tangent space
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
// Transform the view direction from object space to tangent space
o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
return o;
}
? o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
其中xy分量存儲(chǔ)了MainTex的紋理坐標(biāo),而zw分量存儲(chǔ)了BumpMap的紋理坐標(biāo)(實(shí)際上,MainTex和BumpMap通常會(huì)使用同一組紋理坐標(biāo),出于減少插值寄存器的使用數(shù)目的目的,我們往往只計(jì)算和存儲(chǔ)一個(gè)紋理坐標(biāo)即可)。
? TANGENT_SPACE_ROTATION;然后,我們把模型空間下切線方向、副切線方向和法線方向按行排列來(lái)得到從模型空間到切線空間的變換矩陣rotation。需要注意的是,在計(jì)算副切線時(shí)我們使用v.tangent.w和叉積結(jié)果進(jìn)行相乘,這是因?yàn)楹颓芯€與法線方向都垂直的方向有兩個(gè),而w決定了我們選擇其中哪一個(gè)方向。Unity也提供了一個(gè)內(nèi)置宏TANGENT_SPACE_ROTATION(在UnityCG.cginc中被定義)來(lái)幫助我們直接計(jì)算得到rotation變換矩陣,它的實(shí)現(xiàn)和上述代碼完全一樣。
? o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
然后,我們使用Unity的內(nèi)置函數(shù)ObjSpaceLightDir和ObjSpaceViewDir來(lái)得到模型空間下的光照和視角方向,再利用變換矩陣rotation把它們從模型空間變換到切線空間中。
fixed4 frag(v2f i) : SV_Target {
fixed3 tangentLightDir = normalize(i.lightDir);
fixed3 tangentViewDir = normalize(i.viewDir);
// Get the texel in the normal map
fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);
fixed3 tangentNormal;
// If the texture is not marked as "Normal map"
// tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;
// tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
// Or mark the texture as "Normal map", and use the built-in funciton
tangentNormal = UnpackNormal(packedNormal);
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal,
tangentLightDir));
fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal,
halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
?)由于我們?cè)陧旤c(diǎn)著色器中完成了大部分工作,因此片元著色器中只需要采樣得到切線空間下的法線方向,再在切線空間下進(jìn)行光照計(jì)算即可。
? fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);
在上面的代碼中,我們首先利用tex2D對(duì)法線紋理BumpMap進(jìn)行采樣。正如本節(jié)一開(kāi)頭所講的,法線紋理中存儲(chǔ)的是把法線經(jīng)過(guò)映射后得到的像素值,因此我們需要把它們反映射回來(lái)。如果我們沒(méi)有在Unity里把該法線紋理的類型設(shè)置成Normal map(詳見(jiàn)7.2.4節(jié)),就需要在代碼中手動(dòng)進(jìn)行這個(gè)過(guò)程。
? tangentNormal = UnpackNormal(packedNormal);
我們首先把packedNormal的xy分量按之前提到的公式映射回法線方向。
?tangentNormal.xy *= _BumpScale;
然后乘以BumpScale(控制凹凸程度)來(lái)得到tangentNormal的xy分量。
? tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
由于法線都是單位矢量,因此tangentNormal.z分量可以由tangentNormal.xy計(jì)算而得。由于我們使用的是切線空間下的法線紋理,因此可以保證法線方向的z分量為正。
?在Unity中,為了方便Unity對(duì)法線紋理的存儲(chǔ)進(jìn)行優(yōu)化,我們通常會(huì)把法線紋理的紋理類型標(biāo)識(shí)成Normal map, Unity會(huì)根據(jù)平臺(tái)來(lái)選擇不同的壓縮方法。這時(shí),如果我們?cè)偈褂蒙厦娴姆椒▉?lái)計(jì)算就會(huì)得到錯(cuò)誤的結(jié)果,因?yàn)榇藭r(shí)_BumpMap的rgb分量并不再是切線空間下法線方向的xyz值了。在7.2.4節(jié)中,我們會(huì)具體解釋。在這種情況下,我們可以使用Unity的內(nèi)置函數(shù)UnpackNormal來(lái)得到正確的法線方向。
Fallback "Specular"
?最后,我們?yōu)樵揢nity Shader設(shè)置合適的Fallback。
7.3.1 漸變紋理
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 7/Ramp Texture" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_RampTex ("Ramp Tex", 2D) = "white" {}
_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 _Color;
sampler2D _RampTex;
float4 _RampTex_ST;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _RampTex);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// Use the texture to sample the diffuse color
fixed halfLambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;
fixed3 diffuseColor = tex2D(_RampTex, fixed2(halfLambert, halfLambert)).rgb * _Color.rgb;
fixed3 diffuse = _LightColor0.rgb * diffuseColor;
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
說(shuō)人話:
Shader "Unity Shaders Book/Chapter 7/Ramp Texture" {
?我們需要為這個(gè)Shader起一個(gè)名字。
Properties {
_Color ("Color Tint", Color) = (1,1,1,1)
_RampTex ("Ramp Tex", 2D) = "white" {}
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
?在Properties語(yǔ)義塊中聲明一個(gè)紋理屬性來(lái)存儲(chǔ)漸變紋理。
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
}
}
?然后,我們?cè)赟ubShader語(yǔ)義塊中定義了一個(gè)Pass語(yǔ)義塊,并在Pass的第一行指明了該P(yáng)ass的光照模式。
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
?然后,我們使用CGPROGRAM和ENDCG來(lái)包圍住CG代碼片,以定義最重要的頂點(diǎn)著色器和片元著色器代碼。我們使用#pragma指令來(lái)告訴Unity,我們定義的頂點(diǎn)著色器和片元著色器叫什么名字。在本例中,它們的名字分別是vert和frag。
#include "Lighting.cginc"
?為了使用Unity內(nèi)置的一些變量,如_LightColor0,還需要包含進(jìn)Unity的內(nèi)置文件Lighting.cginc。
fixed4 _Color;
sampler2D _RampTex;
float4 _RampTex_ST;
fixed4 _Specular;
float _Gloss;
?隨后,我們需要定義和Properties中各個(gè)屬性類型相匹配的變量。
?我們?yōu)闈u變紋理_RampTex定義了它的紋理屬性變量_RampTex_ST。
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
}
?定義頂點(diǎn)著色器的輸入和輸出結(jié)構(gòu)體。
v2f vert(a2v v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(_Object2World, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _RampTex);
return o;
}
?定義頂點(diǎn)著色器函數(shù)。
?TRANSFORM_TEX(v.texcoord, _RampTex)使用了內(nèi)置的TRANSFORM_TEX宏來(lái)計(jì)算經(jīng)過(guò)平鋪和偏移后的紋理坐標(biāo)。
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// Use the texture to sample the diffuse color
fixed halfLambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;
fixed3 diffuseColor = tex2D(_RampTex, fixed2(halfLambert, halfLambert)).rgb *
_Color.rgb;
fixed3 diffuse = _LightColor0.rgb * diffuseColor;
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal,
halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
?定義片元著色器函數(shù)。
?fixed halfLambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;
在上面的代碼中,我們使用6.4.3節(jié)中提到的半蘭伯特模型,通過(guò)對(duì)法線方向和光照方向的點(diǎn)積做一次0.5倍的縮放以及一個(gè)0.5大小的偏移來(lái)計(jì)算半蘭伯特部分halfLambert。這樣,我們得到的halfLambert的范圍被映射到了[0,1]之間。
?fixed3 diffuseColor = tex2D(_RampTex, fixed2(halfLambert, halfLambert)).rgb *Color.rgb;
之后,我們使用halfLambert來(lái)構(gòu)建一個(gè)紋理坐標(biāo),并用這個(gè)紋理坐標(biāo)對(duì)漸變紋理RampTex進(jìn)行采樣。由于RampTex實(shí)際就是一個(gè)一維紋理(它在縱軸方向上顏色不變),因此紋理坐標(biāo)的u和v方向我們都使用了halfLambert。然后,把從漸變紋理采樣得到的顏色和材質(zhì)顏色_Color相乘,得到最終的漫反射顏色。
?剩下的代碼就是計(jì)算高光反射和環(huán)境光,并把它們的結(jié)果進(jìn)行相加。相信讀者已經(jīng)對(duì)這些步驟非常熟悉了。
Fallback "Specular"
?最后,我們?yōu)樵揢nity Shader設(shè)置合適的Fallback。
7.4.1 遮罩紋理
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 7/Mask Texture" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_BumpMap ("Normal Map", 2D) = "bump" {}
_BumpScale("Bump Scale", Float) = 1.0
_SpecularMask ("Specular Mask", 2D) = "white" {}
_SpecularScale ("Specular Scale", Float) = 1.0
_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 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float _BumpScale;
sampler2D _SpecularMask;
float _SpecularScale;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 lightDir: TEXCOORD1;
float3 viewDir : TEXCOORD2;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
TANGENT_SPACE_ROTATION;
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 tangentLightDir = normalize(i.lightDir);
fixed3 tangentViewDir = normalize(i.viewDir);
fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uv));
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));
fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
// Get the mask value
fixed specularMask = tex2D(_SpecularMask, i.uv).r * _SpecularScale;
// Compute specular term with the specular mask
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss) * specularMask;
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
說(shuō)人話:
Shader "Unity Shaders Book/Chapter 7/Mask Texture" {
?首先,我們需要為這個(gè)Shader起一個(gè)名字。
Properties {
_Color ("Color Tint", Color) = (1,1,1,1)
_MainTex ("Main Tex", 2D) = "white" {}
_BumpMap ("Normal Map", 2D) = "bump" {}
_BumpScale("Bump Scale", Float) = 1.0
_SpecularMask ("Specular Mask", 2D) = "white" {}
_SpecularScale ("Specular Scale", Float) = 1.0
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
?需要在Properties語(yǔ)義塊中聲明更多的變量來(lái)控制高光反射。上面屬性中的SpecularMask即是我們需要使用的高光反射遮罩紋理,SpecularScale則是用于控制遮罩影響度的系數(shù)。
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
}
}
?然后,我們?cè)赟ubShader語(yǔ)義塊中定義了一個(gè)Pass語(yǔ)義塊,并在Pass的第一行指明了該P(yáng)ass的光照模式。
?LightMode標(biāo)簽是Pass標(biāo)簽中的一種,它用于定義該P(yáng)ass在Unity的光照流水線中的角色。
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
?我們使用CGPROGRAM和ENDCG來(lái)包圍住CG代碼片,以定義最重要的頂點(diǎn)著色器和片元著色器代碼。我們使用#pragma指令來(lái)告訴Unity,我們定義的頂點(diǎn)著色器和片元著色器叫什么名字。在本例中,它們的名字分別是vert和frag。
#include "Lighting.cginc"
?為了使用Unity內(nèi)置的一些變量,如_LightColor0,還需要包含進(jìn)Unity的內(nèi)置文件Lighting.cginc。
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float _BumpScale;
sampler2D _SpecularMask;
float _SpecularScale;
fixed4 _Specular;
float _Gloss;
?隨后,我們需要定義和Properties中各個(gè)屬性類型相匹配的變量。
?我們?yōu)橹骷y理MainTex、法線紋理BumpMap和遮罩紋理SpecularMask定義了它們共同使用的紋理屬性變量MainTex_ST。這意味著,在材質(zhì)面板中修改主紋理的平鋪系數(shù)和偏移系數(shù)會(huì)同時(shí)影響3個(gè)紋理的采樣。使用這種方式可以讓我們節(jié)省需要存儲(chǔ)的紋理坐標(biāo)數(shù)目,如果我們?yōu)槊恳粋€(gè)紋理都使用一個(gè)單獨(dú)的屬性變量TextureName_ST,那么隨著使用的紋理數(shù)目的增加,我們會(huì)迅速占滿頂點(diǎn)著色器中可以使用的插值寄存器。而很多時(shí)候,我們不需要對(duì)紋理進(jìn)行平鋪和位移操作,或者很多紋理可以使用同一種平鋪和位移操作,此時(shí)我們就可以對(duì)這些紋理使用同一個(gè)變換后的紋理坐標(biāo)進(jìn)行采樣。
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 lightDir: TEXCOORD1;
float3 viewDir : TEXCOORD2;
};
?定義頂點(diǎn)著色器的輸入和輸出結(jié)構(gòu)體。
v2f vert(a2v v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
TANGENT_SPACE_ROTATION;
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
return o;
}
? TANGENT_SPACE_ROTATION;
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;在頂點(diǎn)著色器中,我們對(duì)光照方向和視角方向進(jìn)行了坐標(biāo)空間的變換,把它們從模型空間變換到了切線空間中,以便在片元著色器中和法線進(jìn)行光照運(yùn)算。
fixed4 frag(v2f i) : SV_Target {
fixed3 tangentLightDir = normalize(i.lightDir);
fixed3 tangentViewDir = normalize(i.viewDir);
fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uv));
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));
fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
// Get the mask value
fixed specularMask = tex2D(_SpecularMask, i.uv).r * _SpecularScale;
// Compute specular term with the specular mask
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal,
halfDir)), _Gloss) * specularMask;
return fixed4(ambient + diffuse + specular, 1.0);
}
?使用遮罩紋理的地方是片元著色器。我們使用它來(lái)控制模型表面的高光反射強(qiáng)度。
?環(huán)境光照和漫反射光照和之前使用過(guò)的代碼完全一樣。
? fixed specularMask = tex2D(_SpecularMask, i.uv).r * _SpecularScale;
在計(jì)算高光反射時(shí),我們首先對(duì)遮罩紋理SpecularMask進(jìn)行采樣。由于本書(shū)使用的遮罩紋理中每個(gè)紋素的rgb分量其實(shí)都是一樣的,表明了該點(diǎn)對(duì)應(yīng)的高光反射強(qiáng)度,在這里我們選擇使用r分量來(lái)計(jì)算掩碼值。然后,我們用得到的掩碼值和SpecularScale相乘,一起來(lái)控制高光反射的強(qiáng)度。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-773104.html
?需要說(shuō)明的是,我們使用的這張遮罩紋理其實(shí)有很多空間被浪費(fèi)了——它的rgb分量存儲(chǔ)的都是同一個(gè)值。在實(shí)際的游戲制作中,我們往往會(huì)充分利用遮罩紋理中的每一個(gè)顏色通道來(lái)存儲(chǔ)不同的表面屬性。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-773104.html
到了這里,關(guān)于[Unity Shader入門精要]初級(jí)篇 代碼拆解的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!