在這一篇中會實作會介紹折射和反射,以及菲尼爾反射;并且實作鏡子和玻璃效果;
這裡和之前不同的地方在于取樣的是一張CubeMap;
demo裡的cubemap使用的一樣,相機所在位置拍出來的周圍環境圖;
生成CubeMap的工具腳本:
public class RenderCubemapWizard : ScriptableWizard {
public Transform renderFromPosition;
public Cubemap cubemap;
void OnWizardUpdate () {
helpString = "Select transform to render from and cubemap to render into";
isValid = (renderFromPosition != null) && (cubemap != null);
}
void OnWizardCreate () {
// create temporary camera for rendering
GameObject go = new GameObject( "CubemapCamera");
go.AddComponent<Camera>();
// place it on the object
go.transform.position = renderFromPosition.position;
// render into cubemap
go.GetComponent<Camera>().RenderToCubemap(cubemap);
// destroy temporary camera
DestroyImmediate( go );
}
[MenuItem("GameObject/Render into Cubemap")]
static void RenderCubemap () {
ScriptableWizard.DisplayWizard<RenderCubemapWizard>(
"Render cubemap", "Render!");
}
}
1.反射
用反射方向在CubeMap上取樣,_ReflectAmount控制反射程度,_ReflectColor反射顔色;
v2f vert (appdata v){
//計算反射向量
o.worldReflect = reflect(-o.worldViewDir,o.worldNormal);
...
}
fixed4 frag (v2f i) : SV_Target{
//根據反射向量從cubemap紋理上取樣
fixed3 reflection = texCUBE(_Cubemap,i.worldReflect).rgb * _ReflectColor.rgb;
//混合反射和漫反射
return fixed4(ambient + lerp(diffuse,reflection,_ReflectAmount)*atten, 1.0);
}
2.折射
和反射幾乎相同,将反射改成折射,計算公式改成折射計算公式;
v2f vert (appdata v){
//計算反射向量
o.worldRefract = refract(-normalize(o.worldViewDir),normalize(o.worldNormal),_RefractRatio);
...
}
fixed4 frag (v2f i) : SV_Target{
//根據反射向量從cubemap紋理上取樣
fixed3 refraction = texCUBE(_Cubemap,i.worldRefract).rgb * _RefractColor.rgb;
//混合反射和漫反射
return fixed4(ambient + lerp(diffuse,refraction,_RefractAmount)*atten, 1.0);
}
成像是倒的;透過茶壺可以看到對面;
3.菲尼爾
反射光的強度與視線方向和法線方向的夾角有關,夾角越大反射光越強;最高90度,也就是邊緣光最強;
Schlick菲尼爾公式:Fschlick(v,n) = F0 + (1-F0)(1- dot(v,n)) ^ 5;F0控制菲尼爾強度;
fixed4 frag (v2f i) : SV_Target{
...
//Schlick Fresnel——邊緣光
fixed3 reflection = texCUBE(_Cubemap,i.worldRefl).rgb;
fixed3 fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1 - dot(worldViewDir,worldNormal), 5);
//菲尼爾系數控制反射強度
return fixed4(ambient + lerp(diffuse,reflection,saturate(fresnel)) * atten, 1.0);
}
4.玻璃效果
通過GrabPass{"_RefractionTex"} 抓取目前螢幕内容渲染到_RefractionTex貼圖上
RefractionTex貼圖用來取樣折射紋理;_Distortion參數模拟法線擾動的程度;
GrabPass{"_RefractionTex"}
...
//GrabPass紋理
sampler2D _RefractionTex;
//紋素大小
float4 _RefractionTex_TexelSize;
fixed4 frag (v2f i) : SV_Target
{
//法線偏移擾動-模拟折射
fixed3 bump = UnpackNormal(tex2D(_BumpMap,i.uv.zw));
float2 offset = bump.xy*_Distortion*_RefractionTex_TexelSize.xy;
//折射計算-螢幕坐标偏移後透視除法取樣折射紋理
i.screenPos.xy = offset + i.screenPos.xy;
fixed3 refractColor = tex2D(_RefractionTex,i.screenPos.xy/i.screenPos.w).rgb;
//矩陣計算世界法線
bump = normalize(half3(dot(i.TtoW0.xyz,bump),dot(i.TtoW1.xyz,bump),dot(i.TtoW2.xyz,bump)));
//反射計算
fixed3 reflectDir = reflect(-worldViewDir,bump);
fixed4 texColor = tex2D(_MainTex,i.uv.xy);
fixed3 reflectColor = texCUBE(_Cubemap,reflectDir).rgb * texColor.rgb;
//混合反射和折射_RefractAmount
return fixed4(reflectColor*(1-_RefractAmount)+refractColor*_RefractAmount, 1.0);
}
5.鏡子
tex2Dproj(_ReflectionTex,UNITY_PROJ_COORD(i.refl));
UNITY_PROJ_COORD:given a 4-component vector, return a texture coordinate suitable for projected texture reads. On most platforms this returns the given value directly.
傳入Vector4,傳回一張用來投影取樣的紋理,大部分平台直接傳回給定值;
鏡子直接傳入螢幕頂點坐标獲得投影紋理,再通過投影取樣獲得顔色,和最終結果混合;
但是上面效果和局限性都比較大,是以找了個大佬寫的鏡子效果;
使用相機和RenderTexture,底層原理差不多,效果要好了很多;
Unity鏡子效果制作教程
Life is too short for so much sorrow.