Unity——ShaderLab實現玻璃和鏡子效果

小紫蘇發表於2022-01-05

在這一篇中會實現會介紹折射和反射,以及菲尼爾反射;並且實現鏡子和玻璃效果;

這裡和之前不同的地方在於取樣的是一張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);
}

image-20220105095725191

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);
}

成像是倒的;透過茶壺可以看到對面;

image-20220105100127649

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); 
}

image-20220105101215393

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);
}

image-20220105122224309

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鏡子效果製作教程

12313sdsda

相關文章