1
Create Mirror —— 創建鏡子
本教程,無需自己找鏡子Shader,只需2個腳本即可在Unity中創建一個簡單的模擬鏡面反射效果
1. 在場景中創建一個 Plane —— 用來作為鏡子
2. 同時創建一個材質球 /Material —— 給到 Plane 上
3. 修改新創建的 Material 的 Shader 為 Unlit/Texture
2
Create Camera —— 創建一個新相機
1. 新建一個 Render Texture(我改名為 Plane 便於區分和理解)
2. 右鍵 層次列表/Hierarchy —— 創建一個新的 Camera
3. 將新建的 Render Texture(Plane)給新建的 Camera 組件中的 Target Texture
4. 給新建的 Camera相機,添加腳本 ChinarMirrorPlane
並將 Main Camera與 Plane 拖到 Inspector 面板中對應的屬性里
5. 給新建的 Camera相機,添加腳本 ChinarMirror ,並將 Plane 拖至 Inspector 面板中
注意: 一定要修改 Plane 材質的屬性為:
具體流程其實很簡單,如下
兩個腳本,都需要掛載到 Camera:
using UnityEngine; /// <summary> /// 鏡子管理腳本 —— 掛在新建的Camera上 /// </summary> [ExecuteInEditMode] public class ChinarMirror : MonoBehaviour { public GameObject mirrorPlane; //鏡子 public Camera mainCamera; //主攝像機 private Camera mirrorCamera; //鏡像攝像機 private void Start() { mirrorCamera = GetComponent<Camera>(); } private void Update() { if (null == mirrorPlane || null == mirrorCamera || null == mainCamera) return; Vector3 postionInMirrorSpace = mirrorPlane.transform.InverseTransformPoint(mainCamera.transform.position); //將主攝像機的世界坐標位置轉換為鏡子的局部坐標位置 postionInMirrorSpace.y = -postionInMirrorSpace.y; //一般y為鏡面的法線方向 mirrorCamera.transform.position = mirrorPlane.transform.TransformPoint(postionInMirrorSpace); //轉回到世界坐標系的位置 } }
using UnityEngine; /// <summary> /// Plane管理腳本 —— 掛載新建的Camera上 /// </summary> [ExecuteInEditMode] //編輯模式中執行 public class ChinarMirrorPlane : MonoBehaviour { public GameObject mirrorPlane; //鏡子Plane public bool estimateViewFrustum = true; public bool setNearClipPlane = true; //是否設置近剪切平面 public float nearClipDistanceOffset = -0.01f; //近剪切平面的距離 private Camera mirrorCamera; //鏡像攝像機 private Vector3 vn; //屏幕的法線 private float l; //到屏幕左邊緣的距離 private float r; //到屏幕右邊緣的距離 private float b; //到屏幕下邊緣的距離 private float t; //到屏幕上邊緣的距離 private float d; //從鏡像攝像機到屏幕的距離 private float n; //鏡像攝像機的近剪切面的距離 private float f; //鏡像攝像機的遠剪切面的距離 private Vector3 pa; //世界坐標系的左下角 private Vector3 pb; //世界坐標系的右下角 private Vector3 pc; //世界坐標系的左上角 private Vector3 pe; //鏡像觀察角度的世界坐標位置 private Vector3 va; //從鏡像攝像機到左下角 private Vector3 vb; //從鏡像攝像機到右下角 private Vector3 vc; //從鏡像攝像機到左上角 private Vector3 vr; //屏幕的右側旋轉軸 private Vector3 vu; //屏幕的上側旋轉軸 private Matrix4x4 p = new Matrix4x4(); private Matrix4x4 rm = new Matrix4x4(); private Matrix4x4 tm = new Matrix4x4(); private Quaternion q = new Quaternion(); private void Start() { mirrorCamera = GetComponent<Camera>(); } private void Update() { if (null == mirrorPlane || null == mirrorCamera) return; pa = mirrorPlane.transform.TransformPoint(new Vector3(-5.0f, 0.0f, -5.0f)); //世界坐標系的左下角 pb = mirrorPlane.transform.TransformPoint(new Vector3(5.0f, 0.0f, -5.0f)); //世界坐標系的右下角 pc = mirrorPlane.transform.TransformPoint(new Vector3(-5.0f, 0.0f, 5.0f)); //世界坐標系的左上角 pe = transform.position; //鏡像觀察角度的世界坐標位置 n = mirrorCamera.nearClipPlane; //鏡像攝像機的近剪切面的距離 f = mirrorCamera.farClipPlane; //鏡像攝像機的遠剪切面的距離 va = pa - pe; //從鏡像攝像機到左下角 vb = pb - pe; //從鏡像攝像機到右下角 vc = pc - pe; //從鏡像攝像機到左上角 vr = pb - pa; //屏幕的右側旋轉軸 vu = pc - pa; //屏幕的上側旋轉軸 if (Vector3.Dot(-Vector3.Cross(va, vc), vb) < 0.0f) //如果看向鏡子的背面 { vu = -vu; pa = pc; pb = pa + vr; pc = pa + vu; va = pa - pe; vb = pb - pe; vc = pc - pe; } vr.Normalize(); vu.Normalize(); vn = -Vector3.Cross(vr, vu); //兩個向量的叉乘,最后在取負,因為Unity是使用左手坐標系 vn.Normalize(); d = -Vector3.Dot(va, vn); if (setNearClipPlane) { n = d + nearClipDistanceOffset; mirrorCamera.nearClipPlane = n; } l = Vector3.Dot(vr, va) * n / d; r = Vector3.Dot(vr, vb) * n / d; b = Vector3.Dot(vu, va) * n / d; t = Vector3.Dot(vu, vc) * n / d; //投影矩陣 p[0, 0] = 2.0f * n / (r - l); p[0, 1] = 0.0f; p[0, 2] = (r + l) / (r - l); p[0, 3] = 0.0f; p[1, 0] = 0.0f; p[1, 1] = 2.0f * n / (t - b); p[1, 2] = (t + b) / (t - b); p[1, 3] = 0.0f; p[2, 0] = 0.0f; p[2, 1] = 0.0f; p[2, 2] = (f + n) / (n - f); p[2, 3] = 2.0f * f * n / (n - f); p[3, 0] = 0.0f; p[3, 1] = 0.0f; p[3, 2] = -1.0f; p[3, 3] = 0.0f; //旋轉矩陣 rm[0, 0] = vr.x; rm[0, 1] = vr.y; rm[0, 2] = vr.z; rm[0, 3] = 0.0f; rm[1, 0] = vu.x; rm[1, 1] = vu.y; rm[1, 2] = vu.z; rm[1, 3] = 0.0f; rm[2, 0] = vn.x; rm[2, 1] = vn.y; rm[2, 2] = vn.z; rm[2, 3] = 0.0f; rm[3, 0] = 0.0f; rm[3, 1] = 0.0f; rm[3, 2] = 0.0f; rm[3, 3] = 1.0f; tm[0, 0] = 1.0f; tm[0, 1] = 0.0f; tm[0, 2] = 0.0f; tm[0, 3] = -pe.x; tm[1, 0] = 0.0f; tm[1, 1] = 1.0f; tm[1, 2] = 0.0f; tm[1, 3] = -pe.y; tm[2, 0] = 0.0f; tm[2, 1] = 0.0f; tm[2, 2] = 1.0f; tm[2, 3] = -pe.z; tm[3, 0] = 0.0f; tm[3, 1] = 0.0f; tm[3, 2] = 0.0f; tm[3, 3] = 1.0f; mirrorCamera.projectionMatrix = p; //矩陣組 mirrorCamera.worldToCameraMatrix = rm * tm; if (!estimateViewFrustum) return; q.SetLookRotation((0.5f * (pb + pc) - pe), vu); //旋轉攝像機 mirrorCamera.transform.rotation = q; //聚焦到屏幕的中心點 //估值 —— 三目簡寫 mirrorCamera.fieldOfView = mirrorCamera.aspect >= 1.0 ? Mathf.Rad2Deg * Mathf.Atan(((pb - pa).magnitude + (pc - pa).magnitude) / va.magnitude) : Mathf.Rad2Deg / mirrorCamera.aspect * Mathf.Atan(((pb - pa).magnitude + (pc - pa).magnitude) / va.magnitude); //在攝像機角度考慮,保證視錐足夠寬 } }
Main Camera —— 主相機腳本(方便看到測試效果)
為了方便看到運行后的鏡面效果, Chinar 在這里提供了一個第三人稱的腳本
用於轉鏡頭,看不同方位
需要掛載到主相機上,並將層次列表中的 Plane 拖到 Pivot 上
4
Create Cube —— 創建一個立方體
為了看鏡子的效果
在場景中創建一個 Cube —— 用來作為參照對象
然后點擊運行后,即可看到鏡子效果已經完成
5
Indistinct —— 顯示效果不清晰
如果發現,鏡子的顯示效果並不清晰
這是因為我們創建的 Render Texture 時使用的是默認的分辨率 256*256
修改成較高的分辨率即可,這里我修改為:1024*1024 (可視情況自己設定)
注意:分辨率設置越高,是越耗性能的