Shader案例篇二《鏡子2》

一、前言
上一篇介紹了有關鏡子的製作,有關理論部分的內容我會在後續相關的文章中陸續介紹,莫急,我先趁着自己腦子還是對此技術比較熱,趁熱打鐵儘早把實現部分先寫出來。上一章的介紹製作的鏡子其實只是一個取巧的方法,並不能做到實時的反射出實物,但是思路還是比較有意思的。

Mirrors3.gif (1.23 MB, 下載次數: 2)

下載附件  保存到相冊

2016-9-1 21:34 上傳



二、中製作原理
1、簡單說明:其實這個原理就是用一個攝像機去拍鏡子上面的物體將得到的圖像投影給Plane,最後主攝像機就能看到Plane上物體的鏡像,所以關鍵的部分就是計算攝像機上的投影矩陣和主攝像機的投影矩陣的關係,因爲站在不同的角度看(主攝像機轉動或移動)鏡像是要跟着偏移的
2、創建一個Camera作爲鏡像攝像機,將下面計算攝像機的投影平面的腳本代碼拖到這個Camera上
[C#] 純文本查看 複製代碼
 
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
public class ViewPlane : MonoBehaviour {
    public GameObject mirrorPlane;                      //鏡子屏幕
 
    public bool estimateViewFrustum = true;
    public bool setNearClipPlane = false;               //是否設置近剪切平面
 
    public float nearClipDistanceOffset = -0.01f;       //近剪切平面的距離
 
    private Camera mirrorCamera;                        //鏡像攝像機
    // Use this for initialization
    void Start () {
        mirrorCamera = GetComponent<Camera>();
         
        }
         
        // Update is called once per frame
        void Update () {
 
        if (null != mirrorPlane && null != mirrorCamera)
        {
            //世界座標系的左下角
            Vector3 pa = mirrorPlane.transform.TransformPoint(new Vector3(-5.0f, 0.0f, -5.0f));
 
            //世界座標系的右下角
            Vector3 pb = mirrorPlane.transform.TransformPoint(new Vector3(5.0f, 0.0f, -5.0f));
 
            //世界座標系的左上角
            Vector3 pc = mirrorPlane.transform.TransformPoint(new Vector3(-5.0f, 0.0f, 5.0f));
 
            //鏡像觀察角度的世界座標位置
            Vector3 pe = transform.position;
 
            //鏡像攝像機的近剪切面的距離
            float n = mirrorCamera.nearClipPlane;
 
            //鏡像攝像機的遠剪切面的距離
            float f = mirrorCamera.farClipPlane;
 
            //從鏡像攝像機到左下角
            Vector3 va = pa - pe;
 
            //從鏡像攝像機到右下角
            Vector3 vb = pb - pe;
 
            //從鏡像攝像機到左上角
            Vector3 vc = pc - pe;
 
            //屏幕的右側旋轉軸
            Vector3 vr = pb - pa;
 
            //屏幕的上側旋轉軸
            Vector3 vu = pc - pa;
 
            //屏幕的法線
            Vector3 vn;
 
            //到屏幕左邊緣的距離
            float l;
 
            //到屏幕右邊緣的距離
            float r;
 
            //到屏幕下邊緣的距離
            float b;
 
            //到屏幕上邊緣的距離
            float t;
 
            //從鏡像攝像機到屏幕的距離
            float d;
 
            //如果看向鏡子的背面
            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();
 
            //兩個向量的叉乘,最後在取負,因爲Unity是使用左手座標系
            vn = -Vector3.Cross(vr, vu);
 
            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;
 
            //投影矩陣
            Matrix4x4 p = new Matrix4x4();
            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;
 
            //旋轉矩陣
            Matrix4x4 rm = new Matrix4x4();
            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;
 
            Matrix4x4 tm = new Matrix4x4();
            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)
            {
                //旋轉攝像機
                Quaternion q = new Quaternion();
                q.SetLookRotation((0.5f * (pb + pc) - pe), vu);
                //聚焦到屏幕的中心點
                mirrorCamera.transform.rotation = q;
 
                //保守估計fieldOfView的值
                if (mirrorCamera.aspect >= 1.0)
                {
                    mirrorCamera.fieldOfView = Mathf.Rad2Deg
                       Mathf.Atan(((pb - pa).magnitude + (pc - pa).magnitude)
                       / va.magnitude);
                }
                else
                {
                    //在攝像機角度考慮,保證視錐足夠寬
                    mirrorCamera.fieldOfView =
                       Mathf.Rad2Deg / mirrorCamera.aspect
                       Mathf.Atan(((pb - pa).magnitude + (pc - pa).magnitude)
                       / va.magnitude);
                }
            }
        }
        }
}

3、創建一個Plane作爲鏡子,這個Plane的Shader必須要是一個能接受貼圖的,所以這裏可以自行使用Unity自帶的Shader,我選擇了Unlit/Texture
4、將下面的代碼賦給2中創建的Camera,將主攝像機給MainCamrea變量,鏡子Plane賦值給MirrorPlane變量,
[C#] 純文本查看 複製代碼
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
public class Mirrors2 : MonoBehaviour {
 
    public GameObject mirrorPlane;//鏡子
    public Camera mainCamera;//主攝像機
    private  Camera mirrorCamera;//鏡像攝像機
        // Use this for initialization
        void Start () {
        mirrorCamera = GetComponent<Camera>();
         
        }
         
        // Update is called once per frame
        void Update () {
         
        if(null!=mirrorPlane&&null!=mirrorCamera&&null!=mainCamera)
        {
            //將主攝像機的世界座標位置轉換爲鏡子的局部座標位置
            Vector3 postionInMirrorSpace = mirrorPlane.transform.InverseTransformPoint(mainCamera.transform.position);
             
            //一般y爲鏡面的法線方向
            postionInMirrorSpace.y = -postionInMirrorSpace.y;
 
            //轉回到世界座標系的位置
            mirrorCamera.transform.position = mirrorPlane.transform.TransformPoint(postionInMirrorSpace);
        }
        }
}

5、如果是剛創建的Camera的投影梯形一定是非常規則的如圖所示,勾選這個Camera


QQ截圖20160901211124.png (63.18 KB, 下載次數: 2)

下載附件  保存到相冊

2016-9-1 21:34 上傳




上的ViewPlane腳本上的setNearClipPlane,這個時候其實是在設置這個Camera的近剪切屏幕,使得這個平面儘量與鏡子Plane平面重合,如圖所示,這個還不是完全的重合


QQ截圖20160901211217.png (48.25 KB, 下載次數: 1)

下載附件  保存到相冊

2016-9-1 21:34 上傳




可以通過調整參數nearClipDistanceOffset來調整,默認的-0.01其實就是我已經調整好了的參數,只要勾選setNearClipPlane就會自動調整到與鏡子平面重合,如圖所示


QQ截圖20160901211839.png (100.61 KB, 下載次數: 2)

下載附件  保存到相冊

2016-9-1 21:34 上傳




6、最後創建一個RenderTexture,這個Texture就是用來將鏡像攝像機投影出來的圖像信息傳遞給鏡子Plane的中間變量,所以將這個Texture分別託給Plane的Shader材質中的Texture和鏡像攝像機中的TargetTexture。最後設置這個Texture的分辨率,我設置成了1024×1024,默認的是256×256,這樣的會造成鏡像模糊還有鋸齒,如圖所示


QQ截圖20160901212457.png (37.74 KB, 下載次數: 3)

下載附件  保存到相冊

2016-9-1 21:34 上傳




設置成1024×1024之後就如第一章圖所示,清晰度比較高。設置分辨率如圖所示

QQ截圖20160901212403.png (38.86 KB, 下載次數: 1)

下載附件  保存到相冊

2016-9-1 21:34 上傳



三、總結
1、缺點:鏡子不能有自己的貼圖、不能實現多面鏡子同時相互反射(還有待發現)
2、相比上一篇的優點:可以實時的反射所有在鏡面上的物體,可以改變物體的光照和貼圖
3、什麼不是Shader,一句Shader代碼都沒有怎麼還是Shader的案例,跟Shader有毛關係?是的,的確沒有一句Shader的代碼,然而使用C#代碼控制其實都是Shader裏面的參數,比如投影矩陣的計算,尤其是此處的投影區域有不規則傾斜的情況。
後續待…
原文轉載請註明出處凱爾八阿哥專欄最後附上工程文件下載地址方便大家學習和參考點擊下載


發佈了40 篇原創文章 · 獲贊 7 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章