《入門精要》中模擬玻璃是用了Unity里的一個特殊的Pass來實(shí)現(xiàn)的,這個Pass就是GrabPass,比起上一篇博客實(shí)現(xiàn)鏡子的方法,這個方法我認(rèn)為相對復(fù)雜,因此在實(shí)現(xiàn)之前需要對GrabPass及實(shí)現(xiàn)原理做一個更加詳細(xì)的介紹。
1 效果及代碼
1.1 效果
1.2 Shader完整代碼
Shader "Unity Shaders Book/Chapter 10/GlassRefraction"
{
//Properties
Properties {
_MainTex ("Main Tex", 2D) = "white" {} //玻璃材質(zhì)紋理
_Cubemap ("EM", Cube) = "_Skybox" {}
_BumpMap ("Bump Map", 2D) = "bump" {} //玻璃法線紋理
//control the distortion of refraction
_Distortion ("Distortion", range(0, 100)) = 10
_RefractAmount ("Refract Amount", Range(0.0, 1.0)) = 1.0
}
SubShader {
//Queue must be transparent, opaque objects will be drawn before
Tags { "Queue"="Transparent" "RenderType"="Opaque" }
//define a pass to grab the screen behind the object,
//see the result by using "_RefractionTex"
GrabPass {"_RefractionTex"}
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
//Properties
sampler2D _MainTex;
float4 _MainTex_ST;
samplerCUBE _Cubemap;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _Distortion;
fixed _RefractAmount;
//remember to add:
sampler2D _RefractionTex;
//get the texel size:
float4 _RefractionTex_TexelSize;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
float4 tangent : TANGENT;
};
struct v2f {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float4 TtoW0 : TEXCOORD1;
float4 TtoW1 : TEXCOORD2;
float4 TtoW2 : TEXCOORD3;
float4 srcPos : TEXCOORD4;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//抓取屏幕圖像的采樣坐標(biāo)
o.srcPos = ComputeGrabScreenPos(o.pos);
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
float3 worldNormal = UnityObjectToWorldNormal(v.normal).xyz;
float3 worldTangent = UnityObjectToWorldNormal(v.tangent).xyz;
float3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
//計(jì)算切線空間 -> 世界空間的矩陣,只需要3x3
//按列擺放
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
return o;
}
fixed4 frag(v2f i) :SV_Target {
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
//計(jì)算光照需要參數(shù):
fixed3 worldlightDir = normalize(UnityWorldSpaceLightDir(worldPos));
fixed3 worldviewDir = normalize(UnityWorldSpaceViewDir(worldPos));
//對紋理采樣+解碼,得到法線方向
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
//由于這里需要模擬的是玻璃的折射效果,因此不能是這種常規(guī)的法線紋理的偏移:
//bump.xy *= _BumpScale;
//bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy)));
//開始實(shí)現(xiàn)折射效果
float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
i.srcPos.xy = offset + i.srcPos.xy;
//采樣得到“折射”顏色:
fixed3 refractColor = tex2D(_RefractionTex, i.srcPos.xy/i.srcPos.w).rgb;
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
//開始環(huán)境映射:
fixed3 reflectDir = reflect(-worldviewDir, bump);
fixed4 texColor = tex2D(_MainTex, i.uv.xy);
fixed3 reflectColor = texCUBE(_Cubemap, reflectDir).rgb * texColor.rgb;
fixed3 finalColor = reflectColor * (1 - _RefractAmount) + refractColor * _RefractAmount;
return fixed4(finalColor, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
2 一些重點(diǎn)過程
2.1 制作場景
場景物體拜訪和貼圖完全參考《入門精要》:
以及當(dāng)前場景的Cubemap的創(chuàng)建:由于相同項(xiàng)目下之前已經(jīng)在Assets -> Edit加了一個可以獲取場景中某個GameObject角度的Cubemap,直接按照相似的方法創(chuàng)建了創(chuàng)建過程可以參考【Unity Shader】Unity中如何創(chuàng)建Cubemap?
2.2 關(guān)于渲染隊(duì)列設(shè)置
乍一看,SubShader的標(biāo)簽設(shè)置好像是前后矛盾的,?渲染隊(duì)列Queue是Transparent透明的,而當(dāng)前shader的渲染類型RenderType確是不透明:
Tags { "Queue"="Transparent" "RenderType"="Opaque" }
GrabPass {"_RefractionTex"}
設(shè)置Queue的作用
我們上效果,如果不加上"Queue"="Transparent",效果如下:
?當(dāng)前的Shader是掛在外面的Cube上的,從上圖效果和1.1的效果對比可以看出,Queue設(shè)置為Transparent是為了保證當(dāng)前屏幕空間畫面里的比Cube深度大、但是是不透明的物體(默認(rèn)的Queue就是不透明)也能渲染并呈現(xiàn)出來,達(dá)到“透過玻璃觀察”的效果。
設(shè)置RenderType的作用
還跟上面一樣,假設(shè)把"RenderType"="Opaque"去掉,會發(fā)現(xiàn)效果跟1.1的沒有任何變化。這是因?yàn)椋enderType其實(shí)是提前給當(dāng)前的Shader歸類了,為了方便之后使用著色器替換(Shader Relacement)的時候,當(dāng)前Shader能被正確的使用。至于什么是著色器替換,后面會涉及到,這里就先不解釋了,挖個坑以后填。
2.3 2種GrabPass的使用方法
參考ShaderLab:GrabPass - Unity 手冊,從官方文檔中可知GrabPass是ShaderLab語法中的一員,是包含在SubShader內(nèi)的一種特殊的通道類型,它直接定義了一個額外的抓取屏幕圖像的Pass,把即將繪制對象時的屏幕內(nèi)容抓取到某個紋理中,這個紋理可以在后面的Pass中被使用去做一些效果。它的使用方式通常有兩種:
GrabPass {}
即直接在Pass語義塊前添加GrabPass {},{}里啥也不寫,那么后續(xù)抓取屏幕圖像的Pass會使用_GrabTexture來訪問屏幕圖像。這種方法看似方便,省去了定義一個新texture的麻煩。但當(dāng)場景中多個物體都需要這種形式來抓取屏幕時(我理解的是有多個物體需要做出類似“玻璃”的效果),Unity都會為每個物體單獨(dú)執(zhí)行一次這個Pass的抓取操作,每個物體都會生成屬于自己的_GrabTexture,這樣的效果雖好,但代價是很大的。
GrabPass {"TextureName"}
就像上述代碼中的:
GrabPass {"_RefractionTex"}
給我們Pass抓取屏幕圖像定義一個專屬的、名為"_RefractionTex"的紋理,后續(xù)的Pass中如果需要使用,就可以通過這個名稱來訪問抓取屏幕圖像的紋理啦!比如上述代碼中的:
fixed3 refractColor = tex2D(_RefractionTex, i.srcPos.xy/i.srcPos.w).rgb;
就是直接使用了定義的紋理名稱來訪問這個紋理。這樣使用方法的好處是,同一個屏幕下多個需要GrabPass的物體都使用同一次渲染出的紋理,也就是僅進(jìn)行一次GrabPass的抓取圖像操作,這樣就可以大大節(jié)省消耗!而且大部分情況下,都使用一張抓取的圖像已經(jīng)能滿足效果需求了。
二者的對比
?這里我們還是用到了Unity提供的Frame Debug,同時為了更好的對比效果,我在場景中多添加了一個想實(shí)現(xiàn)透明效果的Cube,這里僅看透明物體的渲染步驟。
使用GrabPass {"_RefractionTex"}時,可以發(fā)現(xiàn)步驟中兩個Cube是共用同一張Texture的,僅Grab了一次:
而當(dāng)使用GrabPass {}時,Grab了兩次:
兩次的RenderTexuter分別是:
二者的消耗對比(左GrabPass {"_RefractionTex"};右GrabPass {}),可以發(fā)現(xiàn)右邊消耗明顯比左邊大:
2.4 獲取紋素大?。篲TexelSize
這里是為了提一提Shader中定義的:
//get the texel size:
float4 _RefractionTex_TexelSize;
以后的使用中如果想要獲取某張紋理的紋素大小,就可以在紋理名稱后加上_TexelSize啦!這個有點(diǎn)類似_MainTex_ST,都是Unity Shader的內(nèi)置屬性~
2.5 ComputerGrabScreenPos函數(shù)
這是一個Unity Shader的內(nèi)置函數(shù),ComputerGrabScreenPos()括號中輸入裁剪空間下的頂點(diǎn)位置坐標(biāo),可以得到當(dāng)前被抓取的屏幕圖像的屏幕坐標(biāo)。關(guān)于屏幕坐標(biāo)獲得好像有另一個函數(shù)?——ComputerScreenPos,那么問題來了:為什么不用ComputerScreenPos?關(guān)于這個問題,可以先保留著,我將在接下來的博客中仔細(xì)說明(又給自己挖了一個坑。。。)這里僅需要知道ComputerGrabScreenPos()的作用就行!
2.6?如何實(shí)現(xiàn)折射效果?
關(guān)于折射,我們好像真的學(xué)過并使用過一個折射相關(guān)的Unity內(nèi)置函數(shù)——Refract(i, n, ri),但這里實(shí)現(xiàn)折射并不是真的要實(shí)現(xiàn)折射光的效果(太消耗啦?。遣捎?strong>GrabPass+給屏幕坐標(biāo)一個偏移的方式實(shí)現(xiàn)玻璃的折射效果。
GrabPass在前面已經(jīng)介紹過了,這里過一遍如何給屏幕坐標(biāo)偏移,主要體現(xiàn)在如下代碼:
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
//由于這里需要模擬的是玻璃的折射效果,因此不能是這種常規(guī)的法線紋理的偏移:
//bump.xy *= _BumpScale;
//bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy)));
//開始實(shí)現(xiàn)折射效果
float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
i.srcPos.xy = offset + i.srcPos.xy;
//采樣得到“折射”顏色:
fixed3 refractColor = tex2D(_RefractionTex, i.srcPos.xy/i.srcPos.w).rgb;
對bump偏移
如果不記得如何在世界空間使用法線紋理了,可以先去看看【Unity Shader】紋理實(shí)踐5.0:世界空間下使用法線紋理,如果僅實(shí)現(xiàn)法線紋理但并不考慮玻璃效果,直接給bump一個傳統(tǒng)的變化就行,但這里需要加上玻璃的折射效果,因此還需要結(jié)合定義的_Distortion變量和_RefractionTex_TexelSize變量對bump“做手腳”。
- _Distorion——控制折射的扭曲程度,其實(shí)它的道理跟傳統(tǒng)應(yīng)用中的“_BumpScale”是一樣的
- _RefractionTex_TexelSize——偏移量的大小,當(dāng)然是根據(jù)紋理坐標(biāo)而偏移
獲取折射顏色
tex2D(_RefractionTex, i.srcPos.xy/i.srcPos.w)這里用了一個透視除法!這里涉及到了如何在Unity中獲取片元在屏幕上的像素位置,這一點(diǎn)之前在學(xué)習(xí)基礎(chǔ)理論時忽略了,后期會再補(bǔ)上,這里就不贅述(好家伙,又挖了一個坑。。。)。
最終呈現(xiàn)的顏色
fixed3 finalColor = reflectColor * (1 - _RefractAmount) + refractColor * _RefractAmount;
這里用了一個_RefractAmount巧妙地控制了折射和反射的占比(跟之前的環(huán)境映射中實(shí)現(xiàn)折射效果一樣的操作),其實(shí)就是一個自行給定的菲涅爾項(xiàng)。
關(guān)于實(shí)現(xiàn)玻璃效果的過程梳理到這就結(jié)束啦!文章來源:http://www.zghlxwxcb.cn/news/detail-488969.html
后面會再出一個關(guān)于Unity中GrabPass的使用、包括GrabPass和AlphaBlend的區(qū)別、URP下的GrabPass實(shí)現(xiàn)方案等等,以及《入門精要》中提到的一個Command Buffers這個新概念,這些點(diǎn)真的有太多太多可以學(xué)習(xí)的內(nèi)容了。文章來源地址http://www.zghlxwxcb.cn/news/detail-488969.html
到了這里,關(guān)于【Unity Shader】Unity中利用GrabPass實(shí)現(xiàn)玻璃效果的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!