天天看点

Unity——ShaderLab实现玻璃和镜子效果

在这一篇中会实现会介绍折射和反射,以及菲尼尔反射;并且实现镜子和玻璃效果;

这里和之前不同的地方在于取样的是一张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);
}
           
Unity——ShaderLab实现玻璃和镜子效果

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

成像是倒的;透过茶壶可以看到对面;

Unity——ShaderLab实现玻璃和镜子效果

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); 
}
           
Unity——ShaderLab实现玻璃和镜子效果

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);
}
           
Unity——ShaderLab实现玻璃和镜子效果

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.