Unity学习shader笔记[三十]镜面shader

源码来源于 MirrorReflection4
主要参考中文网址: Unity中用Shader实现镜子效果

源码一些无用的部分这里做了删减,另外加上了关键步骤的解说
Unity学习shader笔记[三十]镜面shader

先看看c#代码,

using UnityEngine;
using System.Collections;
 
// This is in fact just the Water script from Pro Standard Assets,
// just with refraction stuff removed.
 
[ExecuteInEditMode] // Make mirror live-update even when not in play mode
public class MirrorReflection : MonoBehaviour
{
	public bool m_DisablePixelLights = true;
	public int m_TextureSize = 256;
	public float m_ClipPlaneOffset = 0.07f;
 
	public LayerMask m_ReflectLayers = -1;
 
	private Hashtable m_ReflectionCameras = new Hashtable(); // Camera -> Camera table
 
	private RenderTexture m_ReflectionTexture = null;
	private int m_OldReflectionTextureSize = 0;
 
	private static bool s_InsideRendering = false;
	
	public void OnWillRenderObject()
	{
		var rend = GetComponent<Renderer>();
		if (!enabled || !rend || !rend.sharedMaterial || !rend.enabled)
			return;
 
		Camera cam = Camera.current;
		if( !cam )
			return;
 
		// Safeguard from recursive reflections.        
		if( s_InsideRendering )
			return;
		s_InsideRendering = true;
 
		Camera reflectionCamera;
		CreateMirrorObjects( cam, out reflectionCamera );
 
		// find out the reflection plane: position and normal in world space
		Vector3 pos = transform.position;
		Vector3 normal = transform.up;
 
		// Optionally disable pixel lights for reflection
		int oldPixelLightCount = QualitySettings.pixelLightCount;
		if( m_DisablePixelLights )
			QualitySettings.pixelLightCount = 0;
 
		UpdateCameraModes( cam, reflectionCamera );
		

		float d = -Vector3.Dot (normal, pos) - m_ClipPlaneOffset;

		Vector4 reflectionPlane = new Vector4 (normal.x, normal.y, normal.z, d);
 
		Matrix4x4 reflection = Matrix4x4.zero;
		CalculateReflectionMatrix (ref reflection, reflectionPlane);

		reflectionCamera.worldToCameraMatrix = cam.worldToCameraMatrix * reflection;
 

		
		reflectionCamera.cullingMask = ~(1<<4) & m_ReflectLayers.value; // never render water layer
		reflectionCamera.targetTexture = m_ReflectionTexture;
		GL.SetRevertBackfacing (true);
	
		reflectionCamera.Render();
	
		GL.SetRevertBackfacing (false);
		Material[] materials = rend.sharedMaterials;
		foreach( Material mat in materials ) {
			if( mat.HasProperty("_ReflectionTex") )
				mat.SetTexture( "_ReflectionTex", m_ReflectionTexture );
		}
 
		// Restore pixel light count
		if( m_DisablePixelLights )
			QualitySettings.pixelLightCount = oldPixelLightCount;
 
		s_InsideRendering = false;
	}
 
 
	// Cleanup all the objects we possibly have created
	void OnDisable()
	{
		if( m_ReflectionTexture ) {
			DestroyImmediate( m_ReflectionTexture );
			m_ReflectionTexture = null;
		}
		foreach( DictionaryEntry kvp in m_ReflectionCameras )
			DestroyImmediate( ((Camera)kvp.Value).gameObject );
		m_ReflectionCameras.Clear();
	}
 
 
	private void UpdateCameraModes( Camera src, Camera dest )
	{
		if( dest == null )
			return;
		// set camera to clear the same way as current camera
		dest.clearFlags = src.clearFlags;
		dest.backgroundColor = src.backgroundColor;        
		if( src.clearFlags == CameraClearFlags.Skybox )
		{
			Skybox sky = src.GetComponent(typeof(Skybox)) as Skybox;
			Skybox mysky = dest.GetComponent(typeof(Skybox)) as Skybox;
			if( !sky || !sky.material )
			{
				mysky.enabled = false;
			}
			else
			{
				mysky.enabled = true;
				mysky.material = sky.material;
			}
		}
		// update other values to match current camera.
		// even if we are supplying custom camera&projection matrices,
		// some of values are used elsewhere (e.g. skybox uses far plane)
		dest.farClipPlane = src.farClipPlane;
		dest.nearClipPlane = src.nearClipPlane;
		dest.orthographic = src.orthographic;
		dest.fieldOfView = src.fieldOfView;
		dest.aspect = src.aspect;
		dest.orthographicSize = src.orthographicSize;
	}
 
	// On-demand create any objects we need
	private void CreateMirrorObjects( Camera currentCamera, out Camera reflectionCamera )
	{
		reflectionCamera = null;
 
		// Reflection render texture
		if( !m_ReflectionTexture || m_OldReflectionTextureSize != m_TextureSize )
		{
			if( m_ReflectionTexture )
				DestroyImmediate( m_ReflectionTexture );
			m_ReflectionTexture = new RenderTexture( m_TextureSize, m_TextureSize, 16 );
			m_ReflectionTexture.name = "__MirrorReflection" + GetInstanceID();
			m_ReflectionTexture.isPowerOfTwo = true;
			m_ReflectionTexture.hideFlags = HideFlags.DontSave;
			m_OldReflectionTextureSize = m_TextureSize;
		}
 
		// Camera for reflection
		reflectionCamera = m_ReflectionCameras[currentCamera] as Camera;
		if( !reflectionCamera ) // catch both not-in-dictionary and in-dictionary-but-deleted-GO
		{
			GameObject go = new GameObject( "Mirror Refl Camera id" + GetInstanceID() + " for " + currentCamera.GetInstanceID(), typeof(Camera), typeof(Skybox) );
			reflectionCamera = go.GetComponent<Camera>();
			reflectionCamera.enabled = false;
			reflectionCamera.transform.position = transform.position;
			reflectionCamera.transform.rotation = transform.rotation;
			reflectionCamera.gameObject.AddComponent<FlareLayer>();
			// go.hideFlags = HideFlags.HideAndDontSave;
			m_ReflectionCameras[currentCamera] = reflectionCamera;
		}        
	}
 
	// Extended sign: returns -1, 0 or 1 based on sign of a
	private static float sgn(float a)
	{
		if (a > 0.0f) return 1.0f;
		if (a < 0.0f) return -1.0f;
		return 0.0f;
	}
 
	// Given position/normal of the plane, calculates plane in camera space.
	private Vector4 CameraSpacePlane (Camera cam, Vector3 pos, Vector3 normal, float sideSign)
	{
		Vector3 offsetPos = pos + normal * m_ClipPlaneOffset;
		Matrix4x4 m = cam.worldToCameraMatrix;
		Vector3 cpos = m.MultiplyPoint( offsetPos );
		Vector3 cnormal = m.MultiplyVector( normal ).normalized * sideSign;
		return new Vector4( cnormal.x, cnormal.y, cnormal.z, -Vector3.Dot(cpos,cnormal) );
	}
 
	// Calculates reflection matrix around the given plane
	private static void CalculateReflectionMatrix (ref Matrix4x4 reflectionMat, Vector4 plane)
	{
		reflectionMat.m00 = (1F - 2F*plane[0]*plane[0]);
		reflectionMat.m01 = (   - 2F*plane[0]*plane[1]);
		reflectionMat.m02 = (   - 2F*plane[0]*plane[2]);
		reflectionMat.m03 = (   - 2F*plane[3]*plane[0]);
 
		reflectionMat.m10 = (   - 2F*plane[1]*plane[0]);
		reflectionMat.m11 = (1F - 2F*plane[1]*plane[1]);
		reflectionMat.m12 = (   - 2F*plane[1]*plane[2]);
		reflectionMat.m13 = (   - 2F*plane[3]*plane[1]);
 
		reflectionMat.m20 = (   - 2F*plane[2]*plane[0]);
		reflectionMat.m21 = (   - 2F*plane[2]*plane[1]);
		reflectionMat.m22 = (1F - 2F*plane[2]*plane[2]);
		reflectionMat.m23 = (   - 2F*plane[3]*plane[2]);
 
		reflectionMat.m30 = 0F;
		reflectionMat.m31 = 0F;
		reflectionMat.m32 = 0F;
		reflectionMat.m33 = 1F;
	}
}

这段代码用np+d=0的方式表示了一个平面,给定任意一个点p,只要它满足np+d=0,这个p就在np+d=0表示的平面上,这里的n是平面的法线方向,d表示了原点到这个平面的距离, 数值通过下面代码的方式计算出来。详细参考3D数学基础[十]三维平面的表示方式。文中的黑体字坐标部分解释了下面这段代码的由来,整篇参考文章看完,下面的代码就懂了。

float d = -Vector3.Dot (normal, pos) - m_ClipPlaneOffset;

Vector4 reflectionPlane = new Vector4 (normal.x, normal.y, normal.z, d);

下面的代码矩阵来源参考 3D数学基础[十一]镜面矩阵的推导, 传入的plane向量是上面的reflectionPlane向量。

private static void CalculateReflectionMatrix (ref Matrix4x4 reflectionMat, Vector4 plane)
	{
		reflectionMat.m00 = (1F - 2F*plane[0]*plane[0]);
		reflectionMat.m01 = (   - 2F*plane[0]*plane[1]);
		reflectionMat.m02 = (   - 2F*plane[0]*plane[2]);
		reflectionMat.m03 = (   - 2F*plane[3]*plane[0]);
 
		reflectionMat.m10 = (   - 2F*plane[1]*plane[0]);
		reflectionMat.m11 = (1F - 2F*plane[1]*plane[1]);
		reflectionMat.m12 = (   - 2F*plane[1]*plane[2]);
		reflectionMat.m13 = (   - 2F*plane[3]*plane[1]);
 
		reflectionMat.m20 = (   - 2F*plane[2]*plane[0]);
		reflectionMat.m21 = (   - 2F*plane[2]*plane[1]);
		reflectionMat.m22 = (1F - 2F*plane[2]*plane[2]);
		reflectionMat.m23 = (   - 2F*plane[3]*plane[2]);
 
		reflectionMat.m30 = 0F;
		reflectionMat.m31 = 0F;
		reflectionMat.m32 = 0F;
		reflectionMat.m33 = 1F;
	}
reflectionCamera.worldToCameraMatrix = cam.worldToCameraMatrix * reflection;

反射矩阵 reflection表示了 镜外物体关于镜子平面对称的位置信息,如下图中的Q‘’是Q乘以reflection矩阵得到的。相机的worldToCameraMatrix 即mvp中的v矩阵,表示了从世界空间转换到观察空间的矩阵,因为unity的做法是向量右乘矩阵,代码中的做法相当于物体在进行观察变换之前做了一次镜面对称转换。v矩阵的生成和v矩阵代表的相机的坐标以及旋转有关系, 因为反射相机用了主相机的v矩阵,所以物体做反射相机的观察变换的时候反射相机在哪里并不重要,不管投影相机在哪,相当于都投进了主相机的视野,而因为代码中的投影相机各个参数都与主相机相同,所以p矩阵两个相机也是相同的。决定镜子成像的是主相机的位置旋转和镜子平面的位置旋转。
Unity学习shader笔记[三十]镜面shader

再看看效果
Unity学习shader笔记[三十]镜面shader

镜子成像的原理简单理解是,主相机视野下的物体沿着镜面对称复制了一份,然后复制的图像,只能通过镜面看到,所以有一部分是被镜子的外形截取掉了,如下图所示,

Unity学习shader笔记[三十]镜面shader

上一篇:Android-Camera内存问题剖析,薪资翻倍


下一篇:源代码波浪粒子鼠标跟随3D动画效果