unity shader學習筆記(二十一)——Unity中的高階紋理之渲染紋理
渲染紋理
渲染目標紋理(Render Target Texture, RTT)是指一個攝像機將渲染結果輸出到顏色緩衝中,並顯示到螢幕上,而不是傳統的幀緩衝和後備緩衝。
多重渲染目標(Multiple Render Target, MRT)是指把場景同時渲染到多個渲染目標紋理中,不需要為每個渲染目標紋理單獨渲染完整的場景。延遲渲染就是多重渲染目標的一個應用。
在Unity中為渲染目標紋理定義了一種專門的紋理型別——渲染紋理(Render Texture)。有兩種使用方式:
- 在Project目錄下建立一個渲染紋理,把這個紋理設定到某個攝像機的渲染目標上,這樣這個攝像機的渲染結果就可以實時更新到渲染紋理中,還可以選擇渲染紋理的解析度、濾波模式等紋理屬性。
- 在屏幕後處理時使用GrabPass命令貨OnRenderImage函式來獲取當前螢幕影象,Unity會把這個影象放到一張渲染紋理中,之後可以在自定義的Pass中當作普通紋理處理,實現各種螢幕特效。
鏡子效果
以下是用第一種方法實現鏡子效果,程式碼如下:
Properties {
_MainTex("Main Tex", 2D) = "white" {}
}
SubShader{
Pass{
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
sampler2D _MainTex;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
o.uv.x = 1 - o.uv.x;
//o.uv.y = 1 - o.uv.y;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return tex2D(_MainTex, i.uv);
}
ENDCG
}
}
FallBack "Specular"
實現鏡子效果只需要將紋理座標的x進行計算就可以了。這裡設定方式按照第一種方法進行設定。實現效果圖如下:
玻璃效果
這裡使用GrabPass方式來實現一種玻璃效果。當在Shader中定義了一個GrabPass時,Unity會把當前螢幕的影象繪製在一張紋理中,可以在後續的Pass中訪問。GrabPass通常用來渲染透明物體,因此要小心物體的渲染佇列設定,即設定為透明佇列(“Queue” = “Transparent”)。
玻璃效果的實現要先使用一張法線紋理來修改模型的法線資訊,之後通過一個Cubemap來模擬玻璃的反射,模擬折射時需要使用GrabPass獲取玻璃後面的螢幕影象,並使用切線空間下的法線對螢幕紋理座標偏移後,再對螢幕影象進行取樣來模擬近似的折射效果。實現程式碼如下:
Properties {
_MainTex("Main Tex", 2D) = "white" {}
_BumpMap("Bump Map", 2D) = "bump" {}
_Cubemap("Environment Cubemap", Cube) = "_Skybox" {}
_Distortion("Distortion", Range(0, 100)) = 10
_RefractAmount("Refraction Amount", Range(0, 1)) = 1
}
SubShader{
Tags{ "RenderType" = "Opaque" "Queue" = "Transparent" }
GrabPass { "_RefractionTex" }
Pass{
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
sampler2D _MainTex;
fixed4 _MainTex_ST;
sampler2D _BumpMap; //法線紋理
fixed4 _BumpMap_ST;
samplerCUBE _Cubemap; //環境紋理
fixed _Distortion; //控制折射的扭曲程度
fixed _RefractAmount;
sampler2D _RefractionTex;
fixed4 _RefractionTex_TexelSize; //得到紋理的紋素大小
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
fixed4 tangent : TANGENT;
fixed4 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float4 scrPos : TEXCOORD0;
fixed4 uv : TEXCOORD1;
fixed4 TtoW0 : TEXCOORD2;
fixed4 TtoW1 : TEXCOORD3;
fixed4 TtoW2 : TEXCOORD4;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.scrPos = 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;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
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
{
fixed3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));
//計算切線空間中的法線
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
//計算在切線空間中的偏移量
float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
i.scrPos.xy = offset + i.scrPos.xy;
fixed3 refrCol = tex2D(_RefractionTex, i.scrPos.xy/i.scrPos.w).rgb;
//將法線轉換到世界空間中
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
fixed3 reflDir = reflect(-worldViewDir, bump);
fixed3 texColor = tex2D(_MainTex, i.uv.xy);
fixed3 reflCol = texCUBE(_Cubemap, reflDir).rgb * texColor.rgb;
fixed3 finalColor = reflCol * (1 - _RefractAmount) + refrCol * _RefractAmount;
return fixed4(finalColor, 1.0);
}
ENDCG
}
}
FallBack "Reflective/VertexLit"
首先需要設定渲染佇列為Transparent,確保在渲染該物體時,不透明的物體已經渲染到螢幕上了。之後再頂點著色器中用到了ComputeGrabScreenPos函式來得到被抓取的螢幕影象的取樣座標,然後把紋理座標和法線紋理座標分別進行計算,之後再計算通過將世界空間下的法線、副法線、切線的方向按列組成一個切線空間到世界空間的變換矩陣。
在片元著色器中,先對法線紋理取樣,得到切線空間下的法線方向,再和_Distortion以及_RefractionTex_TexelSize對螢幕取樣座標進行偏移,之後對_RefractionTex進行取樣,得到折射的顏色。然後通過變換矩陣的每一行分別和法線點乘,得到新的世界空間下的法線方向,計算出反射方向,使用反射方向對環境紋理進行取樣,並和主紋理顏色進行相乘得到反射顏色。最後用_RefractAmount對摺射顏色和反射顏色進行混合,返回最終顏色。實現效果如下:
注意在使用GrabPass時,指定一個TexturName會提高效率,這樣Unity每幀只會在後續的Pass中第一次使用時抓取一次螢幕影象,後面都會使用這張紋理。如果不指定TextureName,則會在每次使用時進行一次抓取操作。
總結
上面兩種方法都是實現了對螢幕的抓取,GrabPass在與實現簡單,但是渲染紋理的效率要高於GrabPass,因為渲染紋理可以自定義紋理大小,GrabPass的解析度則是和螢幕一致的。
在Unity5中,引入了命令緩衝(Command Buffers),使用命令緩衝也可以實現類似抓屏的效果,可以在不透明物體渲染後把當前影象複製到一個臨時的渲染目標紋理中,再進行一些操作。