本文章用于幫助自己學習,因此只記錄一些個人認為比較重要或者還不夠熟悉的內容,
原作者:http://blog.csdn.net/candycat1992/article/
第十章 高級紋理
10.1 立方體紋理
在圖形學中,立方體紋理(Cubemap)是環境映射(Environment Mapping)的一種實作方法,環境映射可以模擬物體周圍的環境,而使用了環境映射的物體可以看起來像鍍了層金屬一樣 反射岀周圍的環境,
立方體紋理一共包含了6張影像,這些影像對應了一個立方體的6個面,
與之前使用二維紋理坐標不同,對立方體紋理采樣我們需要提供一個三維的紋理坐標,這個三維紋理坐標表示了我們在世界空間下的一個3D方向,這個方向矢量從立方體的中心岀發,當它向外部延伸時就會和立方體的6個紋理之一發生相交,而釆樣得到的結果就是由該交點計算而來的,如圖:

使用立方體紋理的好處在于,它的實作簡單快速,而且得到的效果也比較好,但它也有一些缺點,例如當場景中引入了新的物體、光源,或者物體發生移動時,我們就需要重新生成立方體紋理,
10.1.1天空盒子
天空盒子(Skybox)是游戲中用于模擬背景的一種方法,當我們在場景中使用了天空盒子時,整個場景就被包圍在一個立方體內,這個立方體的每個面使用的技術就是立方體紋理映射技術,
在Unity中,想要使用天空盒子非常簡單,我們只需要創建一個Skybox材質,再把它賦給該場景的相關設定即可,
我們首先來看如何創建一個Skybox材質,
- 新建一個材質,
- 在SkyboxMat的Shader下拉選單中選擇Unity自帶的 Skybox,該材質需要6張紋理
- 使用6張紋理對第2步中的材質賦值,注意這6張紋理的正確位置(如posz紋理對應了Front [+Z]屬性),為了讓天空盒子正常渲染,我們需要把這6張紋理的Wrap Mode設定為Clamp,以防止在接縫處出現不匹配的現象,
上述步驟得到的材質如下圖所示,

然后在Lighting選單中,把SkyboxMat 賦給Skybox 選項即可,效果如圖所示:

在Unity中,天空盒子是在所有不透明物體之后渲染的,而其背后使用的網格是一個立方體 或一個細分后的球體,
10.1.2創建用于環境映射的立方體紋理
除了天空盒子,立方體紋理最常見的用處是用于環境映射,通過這種方法,我們可以模擬出金屬質感的材質,
在Unity中,創建用于環境映射的立方體紋理的常用方法有兩種:第一種方法是直接由一些特殊布局的紋理創建;第二種方法是由腳本生成,
如果使用第一種方法,我們需要提供一張具有特殊布局的紋理,例如類似立方體展開圖的交叉布局、全景布局等,然后,我們只需要把該紋理的Texture Type設定為Cubemap即可,Unity 會為我們做好剩下的事情,在基于物理的渲染中,我們通常會使用一張HDR影像來生成高質量的Cubemap,可在官方檔案(http://docs.unity3d.com/Manual/class-Cubemap.html) 中找到更多的資料,
第二種方法,可以根據物體在場景中位置的不同,生成它們各自不同的立方體紋理,這時,我們就可以在Unity中使用腳本來創建,這是通過利用Unity提供的 Camera.RenderToCubemap函式來實作的,Camera.RenderToCubemap函式可以把從任意位置觀察到的場景影像存盤到6張影像中,從而創建出該位置上對應的立方體紋理,
在 Unity 的腳本手冊(http://docs.unity3d.com/ScriptRefbrence/Camera.RenderToCubemap.html) 中給出了如何使用Camera.RenderToCubemap函式來創建立方體紋理的代碼,其中關鍵代碼如下:
void OnWizardCreate()
{
// 創建臨時攝像機用于渲染
GameObject go = new GameObject("CubemapCamera");
go.AddComponent<Camera>();
//renderFromPosition (由用戶指定)位置處動態創建一個攝像機
go.transform.position = renderFromPosition.position;
go.transform.rotation = Quaternion.identity;
//呼叫Camera.RenderToCubemap函式把從當前位置觀察到的影像渲染到用戶指定的立方體紋理cubemap中
go.GetComponent<Camera>().RenderToCubemap(cubemap);
// 銷毀臨時攝像機
DestroyImmediate(go);
}
之后創建一個Cubemap
- 創建一個空的GameObject物件
- 新建一個用于存盤的立方體紋理(在Project視圖下單擊右鍵,選擇Create — Legacy — Cubemap來創建),為了讓腳本可以順利將影像渲染到該立方體紋理中,我們需要在它的面板中勾選Readable選項,
- 從Unity選單欄選擇GameObject -> Render into Cubemap,打開我們在腳本中實作的用于渲染立方體紋理的視窗,并把第1步中創建的GameObject和第2步中創建的Cubemap_0分別拖曳到視窗中的Render From Position和Cubemap選項,然后單擊視窗中的Render!按鈕,就可以把從該位置觀察到的世界空間下的6張影像渲染到 Cubemap_0中,如下圖所示,


10.1.3反射
想要模擬反射效果很簡單,只需要通過入射光線的方向和表面法線方向來計算反射方向,再利用反射方向對立方體紋理采樣即可,
代碼:
Shader "Unity Shaders Book/Chapter 10/Reflection"
{
Properties
{
_Color("Color Tint",Color) = (1,1,1,1)
//控制反射顏色
_ReflectColor("ReflectionColor",Color) = (1,1,1,1)
//控制材質反射程度
_ReflectAmount("Reflect Amount",Range(0,1)) = 1
//模擬反射的環境映射紋理
_Cubemap("Reflection Cubemap",Cube) = "_Skybox"{}
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Color;
fixed4 _ReflectColor;
fixed _ReflectAmount;
samplerCUBE _Cubemap;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldPos : TEXCOORD0;
fixed3 worldNormal : TEXCOORD1;
fixed3 worldViewDir : TEXCOORD2;
//世界空間下的反射方向
fixed3 worldRefl : TEXCOORD3;
SHADOW_COORDS(4)
};
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.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);
//計算世界空間下的反射方向
o.worldRefl = reflect(-o.worldViewDir, o.worldNormal);
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 worldViewDir = normalize(i.worldViewDir);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir));
//使用反射方向對Cubemap進行采樣
fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb * _ReflectColor.rgb;
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
//使用_ReflectAmount混合漫反射顏色和反射顏色
fixed3 color = ambient + lerp(diffuse, reflection, _ReflectAmount) * atten;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Reflective/VertexLit"
}
效果如下:

在上面的計算中,我們選擇在頂點著色器中計算反射方向,當然,我們也可以選擇在片元著色器中計算,這樣得到的效果更加細膩,但是,對于絕大多數人來說這種差別往往是可以忽略不計的,因此出于性能方面的考慮,我們選擇在頂點著色器中計算反射方向,
10.1.4折射
折射:當光線從一種介質斜射入另一種介質時,傳播方向一般會發生改變,當給定入射角時,我們可以使用斯涅耳定律來計算反射角,當光從介質1沿著表面法線夾角為θ(1)的方向斜射入介質2時,我們可以使用如下公式計算折射光線與法線的夾角θ(2):

其中η(1)和η(2) 分別是兩個介質的折射率,例如真空的折射率是1,而玻璃的折射率一般是1.5,下圖給出了這些變數之間的關系,

對一個透明物體來說,一種更準確的模擬方法需要計算兩次折射——一次是當光線進它的內部時,而另一次則是從它內部射出時,但是,想要在實時渲染中模擬出第二次折射方向是比較復雜的,因此,在實時渲染中通常僅模擬第一次折射,
代碼:
Shader "Unity Shaders Book/Chapter 10/Refraction"
{
Properties
{
_Color("Color Tint",Color) = (1,1,1,1)
_RefractColor("ReflectionColor",Color) = (1,1,1,1)
_RefractAmount("Reflect Amount",Range(0,1)) = 1
//得到不同介質的透射比
_RefractRatio("Refraction Ratio",Range(0.1,1)) = 0.5
_Cubemap("Reflection Cubemap",Cube) = "_Skybox"{}
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Color;
fixed4 _RefractColor;
fixed _RefractAmount;
fixed _RefractRatio;
samplerCUBE _Cubemap;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldPos : TEXCOORD0;
fixed3 worldNormal : TEXCOORD1;
fixed3 worldViewDir : TEXCOORD2;
fixed3 worldRefr : TEXCOORD3;
SHADOW_COORDS(4)
};
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.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);
//計算折射方向,
//第一個引數即為入射光線的方向,這里是視線方向因為光路可逆
//第二個引數是表面法線,
//第三個引數是入射光線所在介質的折射率和折射光線所在介質的折射率之間的比值.
o.worldRefr = refract(-normalize(o.worldViewDir), normalize(o.worldNormal), _RefractRatio);
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 worldViewDir = normalize(i.worldViewDir);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir));
//使用折射方向對Cubemap進行采樣
fixed3 refraction = texCUBE(_Cubemap, i.worldRefr).rgb * _RefractColor.rgb;
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
//使用_ReflectAmount混合漫反射顏色和折射顏色
fixed3 color = ambient + lerp(diffuse, refraction, _RefractAmount) * atten;
return fixed4(color, 1.0);
}
ENDCG
}
}
}
效果如下:

10.1.5 菲涅耳反射
在實時渲染中,我們經常會使用菲涅耳反射(Fresnel reflection)來根據視角方向控制反射程 度,通俗地講,菲涅耳反射描述了一種光學現象,即當光線照射到物體表面上時,一部分發生反射,一部分進入物體內部,發生折射或散射,被反射的光和入射光之間存在一定的比率關系,這個比率關系可以通過菲涅耳等式進行計算,
計算菲涅耳反射需要使用菲涅耳等式,真實世界的菲涅耳等式是非常復雜的,但在實時渲染中,我們通常會使用一些近似公式來計算,其中一個著名的近似公式就是Schlick菲涅耳近似等式:

其中,F(0)是一個反射系數,用于控制菲涅耳反射的強度,v是視角方向,n是表面法線,另一個應用比較廣泛的等式是Empricial菲涅耳近似等式:

其中,bias、scale和power是控制項,
代碼:
Shader "Unity Shaders Book/Chapter 10/Fresnel"
{
Properties
{
_Color("Color Tint",Color) = (1,1,1,1)
_FresnelScale("Fresnel Scale",Range(0,1)) = 0.5
_Cubemap("Reflection Cubemap",Cube) = "_Skybox"{}
}
SubShader
{
Tags { "RenderType" = "Opaque" "Queue" = "Geometry"}
Pass {
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Color;
fixed _FresnelScale;
samplerCUBE _Cubemap;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldPos : TEXCOORD0;
fixed3 worldNormal : TEXCOORD1;
fixed3 worldViewDir : TEXCOORD2;
fixed3 worldRefl : TEXCOORD3;
SHADOW_COORDS(4)
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);
o.worldRefl = reflect(-o.worldViewDir, o.worldNormal);
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 worldViewDir = normalize(i.worldViewDir);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//計算陰影和光照衰減
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos)
fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb;
//使用Schlick菲涅耳近似等式來計算fresnel變數
fixed fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1 - dot(worldViewDir, worldNormal), 5);
//使用fresnel混合漫反射光照和反射光照
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 color = ambient + lerp(diffuse, reflection, saturate(fresnel)) * atten;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Reflective/VertexLit"
}
效果如下:

如圖是Fresnel Scale為0時的效果,只具有邊緣光照,就像現實中的全反射現象,只有在視線方向與表面法線達到一定角度時才會發生,
10.2 渲染紋理
在之前的學習中,一個攝像機的渲染結果會輸出到顏色緩沖中,并顯示到我們的螢屏上,現代的GPU允許我們把整個三維場景渲染到一個中間緩沖中,即渲染目標紋理(Render Target Texture, RTT),而不是傳統的幀緩沖或后備緩沖(back buffer),與之相關的是多重渲染目標 (Multiple Render Target, MRT),這種技術指的是**GPU允許我們把場景同時渲染到多個渲染目 標紋理中,而不再需要為每個渲染目標紋理單獨渲染完整的場景,**延遲渲染就是使用多重渲染目標的一個應用,
Unity為渲染目標紋理定義了一種專門的紋理型別——渲染紋理(Render Texture),在Unity中使用渲染紋理通常有兩種方式:
一種方式是在Project目錄下創建一個渲染紋理,然后把某個攝像機的渲染目標設定成該渲染紋理,這樣一來該攝像機的渲染結果就會實時更新到渲染紋理中, 而不會顯示在螢屏上,使用這種方法,我們還可以選擇渲染紋理的解析度、濾波模式等紋理屬性,
另一種方式是在螢屏后處理時使用GrabPass命令或OnRenderlmage函式來獲取當前螢屏影像, Unity會把這個螢屏影像放到一張和螢屏解析度等同的渲染紋理中,下面我們可以在自定義的Pass 中把它們當成普通的紋理來處理,從而實作各種螢屏特效,
10.2.1 鏡子效果
- 在場景中創建6個立方體,并調整它們的位置和大小,使得它們構成圍繞著攝像機的房 間的6面墻,給它們賦予在9.5節中創建的標準材質,向場景中添加3 個點光源,并調整它們的位置,使它們可以照亮整個房間,
- 創建3個球體和兩個正方體,調整它們的位置和大小,并給它們賦予在9.5節中創建的標準材質,這些物體將作為房間內的飾品,
- 創建一個四邊形(Quad),調整它的位置和大小,它將作為鏡子,把創建的鏡子材質賦給它,
- 在Project視圖下創建一個渲染紋理(右鍵單擊Create — Render Texture),命名為MirrorTexture,它使用的紋理設定如下圖所示,
- 最后,為了得到從鏡子出發觀察到的場景影像,我們還需要創建一個攝像機,由于這個攝像機不需要直接顯示在螢屏上,而是用于渲染到紋理,因此,我們把第4步中創建的MirrorTexture拖曳到該攝像機的Target Texture上,下圖顯示了攝像機面板和渲染紋理的相關設定,


鏡子材質代碼:
Shader "Unity Shaders Book/Chapter 10/Mirror"
{
Properties
{
_MainTex ("Main Tex", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
sampler2D _MainTex;
struct a2v
{
float4 vertex : POSITION;
float3 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert (a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
//翻轉x分量的紋理坐標,
//因為鏡子里影像左右相反
o.uv.x = 1 - o.uv.x;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
//對渲染紋理采樣和輸出
return tex2D(_MainTex,i.uv);
}
ENDCG
}
}
FallBack Off
}
保存后回傳場景,并把我們創建的MirrorTexture渲染紋理拖曳到材質的Main Tex屬性中,效果如下:

10.2.2玻璃效果
在Unity中,我們還可以在Unity Shader中使用一種特殊的Pass來完成獲取螢屏影像的目的,這就是GrabPass,
當我們在Shader中定義了一個GrabPass后,Unity會把當前螢屏的影像繪制在一張紋理中,以便我們在后續的Pass中訪問它,
我們通常會使用GrabPass來實作諸如玻璃等透明材質的模擬,與使用簡單的透明混合不同,使用GrabPass可以讓我們對該物體后面的影像進行更復雜的處理,例如使用法線來模擬折射效果,而不再是簡單的和原螢屏顏色進行混合,
需要注意的是,在使用GrabPass的時候,我們需要額外小心物體的渲染佇列設定,正如之前所說,GrabPass通常用于渲染透明物體,盡管代碼里并不包含混合指令,但我們往往仍然需要把物體的渲染佇列設定成透明佇列(即"Queue"=“Transparent”),這樣才可以保證當渲染該物體時, 所有的不透明物體都已經被繪制在螢屏上,從而獲取正確的螢屏影像,
代碼:
Shader "Unity Shaders Book/Chapter 10/Glass Refraction"
{
Properties
{
_MainTex ("Main Tex", 2D) = "white" {}
_BumpMap ("Normal Map", 2D) = "bump" {}
_Cubemap ("Environment Cubemap", Cube) = "_Skybox" {}
//控制折射時影像扭曲程度
_Distortion ("Distortion", Range(0, 100)) = 10
//控制折射程度(值為0時該玻璃只包含反射效果,值為1時只包括折射效果)
_RefractAmount ("Refract Amount", Range(0.0, 1.0)) = 1.0
}
SubShader
{
//Queue設定成Transparent可以確保該物體渲染時,
//其他所有不透明物體都已經被渲染到螢屏上了
Tags { "Queue"="Transparent" "RenderType"="Opaque" }
//定義一個抓取螢屏圖形的Pass
//該字串內部的名稱決定了抓取得到的螢屏影像將會被存入哪個紋理中
GrabPass { "_RefractionTex" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
samplerCUBE _Cubemap;
float _Distortion;
fixed _RefractAmount;
//對應使用GrabPass時指定的紋理名稱
sampler2D _RefractionTex;
//_TexelSize可以得到該紋理的紋素大小,
//例如一個大小為256X512的紋理,它的紋素大小為(1/256, 1/512),
//我們需要在對螢屏影像的釆樣坐標進行偏移時使用該變數
float4 _RefractionTex_TexelSize;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 texcoord: TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float4 scrPos : TEXCOORD0;
float4 uv : TEXCOORD1;
float4 TtoW0 : TEXCOORD2;
float4 TtoW1 : TEXCOORD3;
float4 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;
//切線空間到世界空間變換矩陣,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
{
float3 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;
//offset乘以scrPos的z分量,z分量越大折射效果越明顯
i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy;
//對scrPos進行齊次除法得到真正的螢屏坐標,
//然后用該坐標對抓取的螢屏影像進行采樣,得到折射顏色
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);
fixed4 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);
}
ENDCG
}
}
FallBack "Diffuse"
}
效果如下:

實際上,GrabPass支持兩種形式:
- 直接使用GrabPass { },然后在后續的Pass中直接使用_GrabTexture來訪問螢屏影像,對于每一個使用它的物體,Unity都會為它單獨進行一次昂貴的螢屏抓取操作,這種方法可以讓每個物體得到不同的螢屏影像,這取決于它們的渲染佇列及渲染它們時當前的螢屏緩沖中的顏色,
- 使用GrabPass ( ''TextureName" ),正如本節中的實作,我們可以在后續的Pass中使用TextureName來訪問螢屏影像,使用這種方法同樣可以抓取螢屏,但Unity只會在每一幀時為第一個使用名為TextureName的紋理的物體執行一次抓取螢屏的操作,而這個紋理同樣可以在其他Pass中被訪問,這種方法更高效,因為不管場景中有多少物體使用了該命令,每一幀中Unity都只會執行一次抓取作業,但這也意味著所有物體都會使用同一張螢屏影像,不過,在大多數情況下這已經足夠了,
10.2.3渲染紋理 vs. GrabPass
盡管GrabPass和之前使用的渲染紋理+額外的攝像機的方式都可以抓取螢屏影像,但它們還是有一些不同的,GrabPass的好處在于實作簡單,我們只需要再Shader中寫幾行代碼就可以實作抓取螢屏的目的,而要使用渲染紋理的話,我們首先需要創建一個渲染紋理和一個額外的攝像機,再把該攝像機的Render Target 設定為新建的渲染紋理物件,最后把該渲染紋理傳遞給回應的Shader,
但從效率上來說,使用渲染紋理的效率往往要好于GrabPass,尤其在移動設備上,使用渲染紋理我們可以自定義渲染紋理的大小,盡管這種方法需要把部分場景再次渲染一遍,但我么可以通過調整攝像機的渲染層來減少二次渲染時的場景大小,或使用其他方法來控制攝像機是否需要開啟,而使用GrabPass獲取到的影像解析度和顯示螢屏是一致的,這意味著在一些高解析度的設備上可能會造成嚴重的帶寬影響,而且在移動設備上,GrabPass雖然不會重新渲染場景,但它往往需要CPU直接讀取后備緩沖中的資料,破壞了CPU和GPU之間的并行性,這是比較耗時的,甚至在一些移動設備上這是不支持的,
10.3程式紋理
程式紋理(Procedural Texture)指的是那些由計算機生成的影像,我們通常使用一些特定的演算法來創建個性化圖案或非常真實的自然元素,例如木頭、石子等,
使用程式紋理的好處在于我們可以使用各種引數來控制紋理的外觀,而這些屬性不僅僅是那些顏色屬性,甚至可以是完全不同型別的圖案屬性,這使得我們可以得到更加豐富的影片和視覺效果,
10.3.1在Unity中實作簡單的程式紋理
代碼:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
[ExecuteInEditMode]
public class ProceduralTextureGeneration : MonoBehaviour {
//宣告一個材質,
//這個材質將使用腳本中生成的程式紋理
public Material material = null;
//宣告紋理引數
#region Material properties
[SerializeField, SetProperty("textureWidth")]
private int m_textureWidth = 512;
public int textureWidth {
get {
return m_textureWidth;
}
set {
m_textureWidth = value;
_UpdateMaterial();
}
}
[SerializeField, SetProperty("backgroundColor")]
private Color m_backgroundColor = Color.blue;
public Color backgroundColor {
get {
return m_backgroundColor;
}
set {
m_backgroundColor = value;
_UpdateMaterial();
}
}
[SerializeField, SetProperty("circleColor")]
private Color m_circleColor = Color.yellow;
public Color circleColor {
get {
return m_circleColor;
}
set {
m_circleColor = value;
_UpdateMaterial();
}
}
[SerializeField, SetProperty("blurFactor")]
//宣告模糊因子
private float m_blurFactor = 2.0f;
public float blurFactor {
get {
return m_blurFactor;
}
set {
m_blurFactor = value;
_UpdateMaterial();
}
}
#endregion
//宣告紋理變數保存生成的紋理
private Texture2D m_generatedTexture = null;
//在Start函式中進行檢查,得到需要使用該程式紋理的材質
void Start () {
if (material == null) {
//如果材質為空,
//就從使用該腳本的物體上得到相應的材質
Renderer renderer = gameObject.GetComponent<Renderer>();
if (renderer == null) {
Debug.LogWarning("Cannot find a renderer.");
return;
}
material = renderer.sharedMaterial;
}
_UpdateMaterial();
}
private void _UpdateMaterial() {
if (material != null) {
//呼叫函式生成程式紋理,賦給m_generatedTexture變數
m_generatedTexture = _GenerateProceduralTexture();
//把生成的紋理賦給材質
material.SetTexture("_MainTex", m_generatedTexture);
}
}
private Color _MixColor(Color color0, Color color1, float mixFactor) {
Color mixColor = Color.white;
mixColor.r = Mathf.Lerp(color0.r, color1.r, mixFactor);
mixColor.g = Mathf.Lerp(color0.g, color1.g, mixFactor);
mixColor.b = Mathf.Lerp(color0.b, color1.b, mixFactor);
mixColor.a = Mathf.Lerp(color0.a, color1.a, mixFactor);
return mixColor;
}
private Texture2D _GenerateProceduralTexture() {
//初始化一張二維紋理
Texture2D proceduralTexture = new Texture2D(textureWidth, textureWidth);
//定義圓與圓之間的間距
float circleInterval = textureWidth / 4.0f;
//定義圓的半徑
float radius = textureWidth / 10.0f;
//定義模糊系數
float edgeBlur = 1.0f / blurFactor;
for (int w = 0; w < textureWidth; w++) {
for (int h = 0; h < textureWidth; h++) {
//使用背景顏色進行初始化
Color pixel = backgroundColor;
//依次畫9個圓
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
//計算當前所絵制的圓的圓心位置
Vector2 circleCenter = new Vector2(circleInterval * (i + 1), circleInterval * (j + 1));
//計算當前像素與圓心的距離
float dist = Vector2.Distance(new Vector2(w, h), circleCenter) - radius;
//模糊圓的邊界
Color color = _MixColor(circleColor, new Color(pixel.r, pixel.g, pixel.b, 0.0f), Mathf.SmoothStep(0f, 1.0f, dist * edgeBlur));
//與之前得到的顏色進行混合
pixel = _MixColor(pixel, color, color.a);
}
}
proceduralTexture.SetPixel(w, h, pixel);
}
}
//把像素值寫入紋理中
proceduralTexture.Apply();
return proceduralTexture;
}
}
效果如下:

10.3.2Unity的程式材質
在Unity中,有一類專門使用程式紋理的材質,叫做程式材質(Procedural Materials),這類材質和我們之前使用的那些材質在本質上是一樣的,不同的是,它們使用的紋理不是普通的紋理, 而是程式紋理,需要注意的是,程式材質和它使用的程式紋理并不是在Unity中創建的,而是使用了一個名為Substance Designer的軟體在Unity外部生成的,
Substance Designer是一個非常出色的紋理生成工具,很多3A的游戲專案都使用了由它生成 的材質,我們可以從Unity的資源商店或網路中獲取到很多免費或付費的Substance材質,這些材質都是以.sbsar為后綴的,我們可以直接把這些材質像其他資源一樣拖入Unity專案中,
當把這些檔案匯入Unity后,Unity就會生成一個程式紋理資源(Procedural Material Asset),
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/272322.html
標籤:其他
