簡介
這一篇還是一些基本的shader操作:裁剪、透明和法向量的應(yīng)用
(糾結(jié)了很久寫不寫這些,因?yàn)榇a很簡單,主要是些概念上的東西)
先來看下大概的效果圖:(從左到右依次是裁剪,透明,加了法向量的透明)
(好奇怪,為啥我字那么多,提示我少于150字)

裁剪
代碼
Shader "LT/Lesson3_Cull"
{
Properties
{
_Color ("Color", Color) = (1, 1, 1, 1)
}
SubShader
{
Pass
{
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
uniform float4 _Color;
appdata_base vert ( appdata_base input)
{
input.texcoord = input.vertex ;
input.vertex = mul(UNITY_MATRIX_MVP, input.vertex );
return input;
}
fixed4 frag (appdata_base input) : COLOR
{
if(input.texcoord.y < 0) {
discard;
}
return _Color;
}
ENDCG
}
}
}
代碼的基本格式我就不介紹了,不熟的請看教程一
這里引入了一個(gè)新的東西: Cull Off
裁剪簡單的可以分為三種裁剪:擦除裁剪(discard),正面裁剪(Front)和背面裁剪(Back)
這里的Cull Off代表關(guān)閉unity自帶的背面裁剪(因?yàn)槿绻粚懙脑捘J(rèn)為Cull Back)
Cull Off 關(guān)閉裁剪
Cull Back 背面裁剪(默認(rèn))
Cull Front 正面裁剪
然后關(guān)于這個(gè)面的正反的界定,是通過法線來完成的,法線向量>0則為正
Cull Back 和 Cull Front Unity替我們完成了,如果需要簡單的裁掉正面和反面的話直接使用預(yù)制的就行了
(Q:如果我想同時(shí)裁正面和反面怎么處理? A:如果你要同時(shí)裁掉正面和反面,你的模型就不用顯示了好嘛)
這里我想通過模型的坐標(biāo)位置來裁面,所以關(guān)閉了Unity的自動裁剪
然后就是把頂點(diǎn)信息緩存下來:
input.texcoord = input.vertex ;
為了方便(同時(shí)也能提高性能,由于GPU是頻繁調(diào)用這些函數(shù),所以不管什么空間啊,計(jì)算啊都能省就?。?,我直接將數(shù)據(jù)存在了input對象中,然后又將input返回給frag使用(這里隨便選一個(gè)不用的字段用來緩存我們的位置信息就行了,注意vertex是要使用的,所以vertex不能用來緩存這個(gè)信息)
然后在frag中判斷我們緩存的坐標(biāo)信息,滿足條件就discard掉
if(input.texcoord.y < 0) {
discard;
}
這里的discard相當(dāng)于強(qiáng)行中斷該次渲染,也可以說成取消渲染,這樣就完成了我們的裁剪功能了
順帶一提,discard很消耗性能的,所以能不用還是就不用了(何況這里還多了一步if判斷語句),需要指定裁剪還是改模型來的高效,當(dāng)然如果需要?jiǎng)討B(tài)裁剪就需要這樣的代碼了
透明
還是先來代碼
代碼
Shader "LT/Lesson3_Transparent"
{
Properties
{
_Color ("Color", Color) = (1, 1, 1, 0.5)
}
SubShader
{
Tags { "Queue" = "Transparent" }
Pass
{
ZWrite Off
Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
uniform float4 _Color;
appdata_base vert ( appdata_base input)
{
input.vertex = mul(UNITY_MATRIX_MVP, input.vertex );
return input;
}
fixed4 frag (appdata_base input) : COLOR
{
return _Color;
}
ENDCG
}
}
}
恩...這里呢代碼的內(nèi)容比較少,就是直接把外面設(shè)置的顏色應(yīng)用到物體上就完了
但是多了
Tags { "Queue" = "Transparent" }
ZWrite Off
Blend One One
這三個(gè)東西
Tags { "Queue" = "Transparent" }
先來第一個(gè)Tags { "Queue" = "Transparent" }
大家可能看到有個(gè)Transparent會想要寫透明是不是必須要這句話,其實(shí)不然,這行代碼只是指定一個(gè)渲染順序而已,一般情況下是具體情況具體分析,比如你希望這個(gè)shader是渲染背景物體的時(shí)候,你可以設(shè)置為
"Queue" = "Background",需要時(shí)物體的時(shí)候設(shè)置為“Geometry”,其實(shí)對應(yīng)下來的就是一個(gè)數(shù)值,這個(gè)數(shù)值越小,越先渲染,然后從我們的渲染順序可以理解,先渲染的自然在背后了
下面列舉下常用的值對應(yīng)的名字
"Background"。值為1000。比如用于天空盒。
"Geometry"。值為2000。大部分物體在這個(gè)隊(duì)列。不透明的物體也在這里。這個(gè)隊(duì)列內(nèi)部的物體的渲染順序會有進(jìn)一步的優(yōu)化(應(yīng)該是從近到遠(yuǎn),early-z test可以剔除不需經(jīng)過FS處理的片元)。其他隊(duì)列的物體都是按空間位置的從遠(yuǎn)到近進(jìn)行渲染。
"AlphaTest"。值為2450。已進(jìn)行AlphaTest的物體在這個(gè)隊(duì)列。
"Transparent"。值為3000。透明物體。
"Overlay"。值為4000。比如鏡頭光暈。
還有就是,用戶可以定義任意值,比如"Queue"="Geometry+10"
ZWrite Off
再來ZWriter Off,這句命令表示不寫入深度緩存
呃~~~,然后我們繼續(xù)來科普概念吧(由于語文不好,后面一大段話是從別的網(wǎng)站復(fù)制的)
(1)什么是深度?
深度其實(shí)就是該像素點(diǎn)在3d世界中距離攝象機(jī)的距離,深度值(Z值)越大,則離攝像機(jī)越遠(yuǎn)。
(2)什么是深度緩存?
深度緩存中存儲著每個(gè)像素點(diǎn)(繪制在屏幕上的)的深度值!如果啟用了深度緩沖區(qū),在繪制每個(gè)像素之前,OpenGL會把它的深度值和已經(jīng)存儲在這個(gè)像素的深度值進(jìn)行比較。新像素深度值<原先像素深度值,則新像素值會取代原先的;反之,新像素值被遮擋,其顏色值和深度將被丟棄,最終屏幕顯示的就是深度緩存中深度對應(yīng)的像素點(diǎn)的顏色!(深度主要起的是比較的作用)
(3)什么是深度測試?
在深度測試中,默認(rèn)情況是將要繪制的新像素的z值與深度緩沖區(qū)中對應(yīng)位置的z值進(jìn)行比較,如果比深度緩存中的值小,那么用新像素的顏色值更新深度緩存中對應(yīng)像素的顏色值。
(4)為什么需要深度?
在不使用深度測試的時(shí)候,如果我們先繪制一個(gè)距離較近的物體,再繪制距離較遠(yuǎn)的物體,則距離遠(yuǎn)的物體因?yàn)楹罄L制,會把距離近的物體覆蓋掉,這樣的效果并不是我們所希望的。而有了深度緩沖以后,繪制物體的順序就不那么重要了,都能按照遠(yuǎn)近(Z值)正常顯示,這很關(guān)鍵。(越后繪制的東西,距離相機(jī)就越近)
那么,在unity中,如果知道了渲染隊(duì)列,ZWrite,ZTest,如何確定哪個(gè)物體先顯示呢?
首先,unity先將渲染隊(duì)列中較前的進(jìn)行渲染,然后再執(zhí)行ZWrite,ZTest
ZWrite可以取的值為:On/Off,默認(rèn)值為On,代表是否要將像素的深度寫入深度緩存中
ZTest可以取的值為:Greater/GEqual/Less/LEqual/Equal/NotEqual/Always/Never/Off,默認(rèn)值為LEqual,代表如何將像素的顏色寫入深度緩存中,例如當(dāng)取默認(rèn)值的情況下,如果將要繪制的新像素的z值小于等于深度緩存中的值,則將用新像素的顏色值更新深度緩存中對應(yīng)像素的顏色值。需要注意的是,當(dāng)ZTest取值為Off時(shí),表示的是關(guān)閉深度測試,等價(jià)于取值為Always,而不是Never!Always指的是直接將當(dāng)前像素顏色(不是深度)寫進(jìn)顏色緩沖區(qū)中;而Never指的是不要將當(dāng)前像素顏色寫進(jìn)顏色緩沖區(qū)中,相當(dāng)于消失。
因?yàn)閆Write默認(rèn)值為On,ZTest默認(rèn)值為LEqual,所以這很好地解釋了為什么在unity中,距離相機(jī)近的東西會阻擋住距離相機(jī)遠(yuǎn)的東西。如果我們先繪制一個(gè)距離較近的物體,再繪制距離較遠(yuǎn)的物體,則距離遠(yuǎn)的物體因?yàn)楹罄L制,會把距離近的物體覆蓋掉,這時(shí)我們可以通過修改ZWrite和ZTest來改變物體的遮擋關(guān)系!
恩,好,介紹完了深度這個(gè)玩意兒咯, 那來解釋下我們?yōu)樯兑P(guān)掉了(其實(shí)不關(guān)掉也可以,但是關(guān)掉GPU可以少做一步操作啊,提高性能性能性能性能性能性能,所以能關(guān)就關(guān)吧),因?yàn)槲覀円獙懙氖峭该靼。还芟群箜樞虻?,透明都可以看得?- -
Blend One One
最后是Blend One One,這是個(gè)大概念,這個(gè)命令是寫透明shader所必須的,因?yàn)樗x了透明的模式
然而,這玩意兒很簡單的,命令是
Blend SrcFactor DstFactor
然后這個(gè)Factor 支持:
One
值為1,使用此設(shè)置來讓源或是目標(biāo)顏色完全的通過。
Zero
值為0,使用此設(shè)置來刪除源或目標(biāo)值。
SrcColor
此階段的值是乘以源顏色的值。
SrcAlpha
此階段的值是乘以源alpha的值。
DstColor
此階段的值是乘以幀緩沖區(qū)源顏色的值。
DstAlpha
此階段的值是乘以幀緩沖區(qū)源alpha的值。
OneMinusSrcColor
此階段的值是乘以(1 - source color)
OneMinusSrcAlpha
此階段的值是乘以(1 - source alpha)
OneMinusDstColor
此階段的值是乘以(1 - destination color)
OneMinusDstAlpha
此階段的值是乘以(1 - destination alpha)
然后下面是常用的搭配:
Blend SrcAlpha OneMinusSrcAlpha // Alpha blending alpha混合
Blend One One // Additive 相加混合
Blend One OneMinusDstColor // Soft Additive 柔和相加混合
Blend DstColor Zero // Multiplicative 相乘混合
Blend DstColor SrcColor // 2x Multiplicative 2倍相乘混合
具體效果,大家可以自己改代碼看結(jié)果,這里就不多說了
帶法向量計(jì)算的透明
代碼
Shader "LT/Lesson3_Silhouette"
{
Properties
{
_Color ("Color", Color) = (1, 1, 1, 0.5)
}
SubShader
{
Pass
{
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
uniform float4 _Color;
appdata_base vert ( appdata_base input)
{
fixed3 tempNormal = normalize(mul(fixed4(input.normal, 0.0), _World2Object).xyz);
fixed3 tempViewDir = normalize(_WorldSpaceCameraPos - mul(_Object2World, input.vertex).xyz);
input.vertex = mul(UNITY_MATRIX_MVP, input.vertex );
input.normal.x = min(1.0, _Color.a / abs(dot(tempNormal, tempViewDir)));
return input;
}
fixed4 frag (appdata_base input) : COLOR
{
return float4(float3(_Color.x,_Color.y,_Color.z), input.normal.x);
}
ENDCG
}
}
}
這里沒啥新東西,但是代碼上把法向量用起來了(原理主要是通過法向量和攝像機(jī)朝向算出一個(gè)新的透明度用來替換而已)
解釋一下吧:
fixed3 tempNormal = normalize(mul(fixed4(input.normal, 0.0), _World2Object).xyz);
// 計(jì)算unity坐標(biāo)系下的法向量
fixed3 tempViewDir = normalize(_WorldSpaceCameraPos - mul(_Object2World, input.vertex).xyz);
// 計(jì)算unity坐標(biāo)系啊的攝像機(jī)的向量
input.normal.x = min(1.0, _Color.a / abs(dot(tempNormal, tempViewDir)));
// 將兩個(gè)向量點(diǎn)乘然后換算給設(shè)定顏色的alpha通道,并緩存起來
// 至于為啥要點(diǎn)乘,然后用Color.a來除,這個(gè)是算法問題啦:
// a = min(1, a/ |V·N|),公式是書上來的,數(shù)學(xué)問題了,不做贅述
return float4(float3(_Color.x,_Color.y,_Color.z), input.normal.x);
// 使用設(shè)置的顏色的rgb值和算出來的新的alpha值
由于個(gè)人很喜歡這個(gè)效果,所以再來次效果展示

這樣是不是有一點(diǎn)簡單的立體效果咯捏(這不是光照,真的光照的反射啥的下一篇教程講)
總結(jié)
本篇教程的東西都很基礎(chǔ),但是在以后寫shader的過程中會用的很平凡,所以還是單獨(dú)拿出來講了一下,與我們的快速上手指南其實(shí)有點(diǎn)背道而馳 o(╯□╰)o ,但是為了后面不會看不懂,大家還是多自己寫寫熟悉一下,比如使用discard和Cull配合兩個(gè)Pass寫一個(gè)正反面渲染不同顏色的shader,利用前面講的SinTime和法向量啥的寫一個(gè)動態(tài)變化效果
還是那句話,又不懂得,QQ:821580467
|