Mirror:Reflect By Unity

1.很高興你能找到這里没酣,我表示熱烈的歡迎
2.這是一篇寫給淵瞳的筆記王财,因?yàn)樗X子不太好,早晚會忘記
3.文中主要解釋代碼中難理解的地方裕便,如果這正是你需要的绒净,那我會很開心

從哪里開始?既然是一片shader有關(guān)的內(nèi)容偿衰,我們先從女神書(入門精要)開始挂疆,因?yàn)槲乙策€是從這里開始的改览。
在女神書中10.2章提及的渲染紋理,并且用渲染紋理來實(shí)現(xiàn)鏡面效果缤言,著實(shí)令我感到興奮宝当。無數(shù)的電影,游戲胆萧,CG庆揩,都會使用鏡面材質(zhì)來表現(xiàn)細(xì)節(jié),提高畫面的質(zhì)量(淵瞳:那個是菲涅爾)跌穗。


鏡面1.png
鏡面2.png
鏡面3.png

但是盾鳞,僅僅是使用一個RenderTargetTexture(RTT)并不能實(shí)現(xiàn)我們想要的效果。如果想要實(shí)現(xiàn)這樣的效果瞻离,還需要了解很多的東西。


GIF.gif

如果你努力學(xué)習(xí)并且查找了一些相關(guān)的資料的話乒裆,你可能會去調(diào)用Camera.OnWillRenderObject()函數(shù)套利,然后你會在官方的dictionary中找到。


dic.png

既然有官方學(xué)習(xí)資料鹤耍,自然大家都會看肉迫,(注意注意注意:water.cs并不在Effects內(nèi),而是在Environment下稿黄,雖然已經(jīng)向官方反饋了喊衫,也許你們看到的時候就已經(jīng)改好了)

這長篇的代碼只是給那些沒看過的人準(zhǔn)備的,以及為了后面講解杆怕,翻過來查找用(既然你已經(jīng)看過了族购,就跳過吧。)

water.cs的源碼可以說是現(xiàn)在常見的的鏡面反射的原型陵珍,或者說基礎(chǔ)寝杖。

water.cs

using System;
using System.Collections.Generic;
using UnityEngine;

namespace UnityStandardAssets.Water
{
    [ExecuteInEditMode] // Make water live-update even when not in play mode
    public class Water : MonoBehaviour
    {
        public enum WaterMode
        {
            Simple = 0,
            Reflective = 1,
            Refractive = 2,
        };


        public WaterMode waterMode = WaterMode.Refractive;
        public bool disablePixelLights = true;
        public int textureSize = 256;
        public float clipPlaneOffset = 0.07f;
        public LayerMask reflectLayers = -1;
        public LayerMask refractLayers = -1;


        private Dictionary<Camera, Camera> m_ReflectionCameras = new Dictionary<Camera, Camera>(); // Camera -> Camera table
        private Dictionary<Camera, Camera> m_RefractionCameras = new Dictionary<Camera, Camera>(); // Camera -> Camera table
        private RenderTexture m_ReflectionTexture;
        private RenderTexture m_RefractionTexture;
        private WaterMode m_HardwareWaterSupport = WaterMode.Refractive;
        private int m_OldReflectionTextureSize;
        private int m_OldRefractionTextureSize;
        private static bool s_InsideWater;


        // This is called when it's known that the object will be rendered by some
        // camera. We render reflections / refractions and do other updates here.
        // Because the script executes in edit mode, reflections for the scene view
        // camera will just work!
        public void OnWillRenderObject()
        {
            if (!enabled || !GetComponent<Renderer>() || !GetComponent<Renderer>().sharedMaterial ||
                !GetComponent<Renderer>().enabled)
            {
                return;
            }

            Camera cam = Camera.current;
            if (!cam)
            {
                return;
            }

            // Safeguard from recursive water reflections.
            if (s_InsideWater)
            {
                return;
            }
            s_InsideWater = true;

            // Actual water rendering mode depends on both the current setting AND
            // the hardware support. There's no point in rendering refraction textures
            // if they won't be visible in the end.
            m_HardwareWaterSupport = FindHardwareWaterSupport();
            WaterMode mode = GetWaterMode();

            Camera reflectionCamera, refractionCamera;
            CreateWaterObjects(cam, out reflectionCamera, out refractionCamera);

            // 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/refraction
            int oldPixelLightCount = QualitySettings.pixelLightCount;
            if (disablePixelLights)
            {
                QualitySettings.pixelLightCount = 0;
            }

            UpdateCameraModes(cam, reflectionCamera);
            UpdateCameraModes(cam, refractionCamera);

            // Render reflection if needed
            if (mode >= WaterMode.Reflective)
            {
                // Reflect camera around reflection plane
                float d = -Vector3.Dot(normal, pos) - clipPlaneOffset;
                Vector4 reflectionPlane = new Vector4(normal.x, normal.y, normal.z, d);

                Matrix4x4 reflection = Matrix4x4.zero;
                CalculateReflectionMatrix(ref reflection, reflectionPlane);
                Vector3 oldpos = cam.transform.position;
                Vector3 newpos = reflection.MultiplyPoint(oldpos);
                reflectionCamera.worldToCameraMatrix = cam.worldToCameraMatrix * reflection;

                // Setup oblique projection matrix so that near plane is our reflection
                // plane. This way we clip everything below/above it for free.
                Vector4 clipPlane = CameraSpacePlane(reflectionCamera, pos, normal, 1.0f);
                reflectionCamera.projectionMatrix = cam.CalculateObliqueMatrix(clipPlane);

                // Set custom culling matrix from the current camera
                reflectionCamera.cullingMatrix = cam.projectionMatrix * cam.worldToCameraMatrix;

                reflectionCamera.cullingMask = ~(1 << 4) & reflectLayers.value; // never render water layer
                reflectionCamera.targetTexture = m_ReflectionTexture;
                bool oldCulling = GL.invertCulling;
                GL.invertCulling = !oldCulling;
                reflectionCamera.transform.position = newpos;
                Vector3 euler = cam.transform.eulerAngles;
                reflectionCamera.transform.eulerAngles = new Vector3(-euler.x, euler.y, euler.z);
                reflectionCamera.Render();
                reflectionCamera.transform.position = oldpos;
                GL.invertCulling = oldCulling;
                GetComponent<Renderer>().sharedMaterial.SetTexture("_ReflectionTex", m_ReflectionTexture);
            }

            // Render refraction
            if (mode >= WaterMode.Refractive)
            {
                refractionCamera.worldToCameraMatrix = cam.worldToCameraMatrix;

                // Setup oblique projection matrix so that near plane is our reflection
                // plane. This way we clip everything below/above it for free.
                Vector4 clipPlane = CameraSpacePlane(refractionCamera, pos, normal, -1.0f);
                refractionCamera.projectionMatrix = cam.CalculateObliqueMatrix(clipPlane);

                // Set custom culling matrix from the current camera
                refractionCamera.cullingMatrix = cam.projectionMatrix * cam.worldToCameraMatrix;

                refractionCamera.cullingMask = ~(1 << 4) & refractLayers.value; // never render water layer
                refractionCamera.targetTexture = m_RefractionTexture;
                refractionCamera.transform.position = cam.transform.position;
                refractionCamera.transform.rotation = cam.transform.rotation;
                refractionCamera.Render();
                GetComponent<Renderer>().sharedMaterial.SetTexture("_RefractionTex", m_RefractionTexture);
            }

            // Restore pixel light count
            if (disablePixelLights)
            {
                QualitySettings.pixelLightCount = oldPixelLightCount;
            }

            // Setup shader keywords based on water mode
            switch (mode)
            {
                case WaterMode.Simple:
                    Shader.EnableKeyword("WATER_SIMPLE");
                    Shader.DisableKeyword("WATER_REFLECTIVE");
                    Shader.DisableKeyword("WATER_REFRACTIVE");
                    break;
                case WaterMode.Reflective:
                    Shader.DisableKeyword("WATER_SIMPLE");
                    Shader.EnableKeyword("WATER_REFLECTIVE");
                    Shader.DisableKeyword("WATER_REFRACTIVE");
                    break;
                case WaterMode.Refractive:
                    Shader.DisableKeyword("WATER_SIMPLE");
                    Shader.DisableKeyword("WATER_REFLECTIVE");
                    Shader.EnableKeyword("WATER_REFRACTIVE");
                    break;
            }

            s_InsideWater = false;
        }


        // Cleanup all the objects we possibly have created
        void OnDisable()
        {
            if (m_ReflectionTexture)
            {
                DestroyImmediate(m_ReflectionTexture);
                m_ReflectionTexture = null;
            }
            if (m_RefractionTexture)
            {
                DestroyImmediate(m_RefractionTexture);
                m_RefractionTexture = null;
            }
            foreach (var kvp in m_ReflectionCameras)
            {
                DestroyImmediate((kvp.Value).gameObject);
            }
            m_ReflectionCameras.Clear();
            foreach (var kvp in m_RefractionCameras)
            {
                DestroyImmediate((kvp.Value).gameObject);
            }
            m_RefractionCameras.Clear();
        }


        // This just sets up some matrices in the material; for really
        // old cards to make water texture scroll.
        void Update()
        {
            if (!GetComponent<Renderer>())
            {
                return;
            }
            Material mat = GetComponent<Renderer>().sharedMaterial;
            if (!mat)
            {
                return;
            }

            Vector4 waveSpeed = mat.GetVector("WaveSpeed");
            float waveScale = mat.GetFloat("_WaveScale");
            Vector4 waveScale4 = new Vector4(waveScale, waveScale, waveScale * 0.4f, waveScale * 0.45f);

            // Time since level load, and do intermediate calculations with doubles
            double t = Time.timeSinceLevelLoad / 20.0;
            Vector4 offsetClamped = new Vector4(
                (float)Math.IEEERemainder(waveSpeed.x * waveScale4.x * t, 1.0),
                (float)Math.IEEERemainder(waveSpeed.y * waveScale4.y * t, 1.0),
                (float)Math.IEEERemainder(waveSpeed.z * waveScale4.z * t, 1.0),
                (float)Math.IEEERemainder(waveSpeed.w * waveScale4.w * t, 1.0)
                );

            mat.SetVector("_WaveOffset", offsetClamped);
            mat.SetVector("_WaveScale4", waveScale4);
        }

        void UpdateCameraModes(Camera src, Camera dest)
        {
            if (dest == null)
            {
                return;
            }
            // set water 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<Skybox>();
                Skybox mysky = dest.GetComponent<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 for water
        void CreateWaterObjects(Camera currentCamera, out Camera reflectionCamera, out Camera refractionCamera)
        {
            WaterMode mode = GetWaterMode();

            reflectionCamera = null;
            refractionCamera = null;

            if (mode >= WaterMode.Reflective)
            {
                // Reflection render texture
                if (!m_ReflectionTexture || m_OldReflectionTextureSize != textureSize)
                {
                    if (m_ReflectionTexture)
                    {
                        DestroyImmediate(m_ReflectionTexture);
                    }
                    m_ReflectionTexture = new RenderTexture(textureSize, textureSize, 16);
                    m_ReflectionTexture.name = "__WaterReflection" + GetInstanceID();
                    m_ReflectionTexture.isPowerOfTwo = true;
                    m_ReflectionTexture.hideFlags = HideFlags.DontSave;
                    m_OldReflectionTextureSize = textureSize;
                }

                // Camera for reflection
                m_ReflectionCameras.TryGetValue(currentCamera, out reflectionCamera);
                if (!reflectionCamera) // catch both not-in-dictionary and in-dictionary-but-deleted-GO
                {
                    GameObject go = new GameObject("Water 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;
                }
            }

            if (mode >= WaterMode.Refractive)
            {
                // Refraction render texture
                if (!m_RefractionTexture || m_OldRefractionTextureSize != textureSize)
                {
                    if (m_RefractionTexture)
                    {
                        DestroyImmediate(m_RefractionTexture);
                    }
                    m_RefractionTexture = new RenderTexture(textureSize, textureSize, 16);
                    m_RefractionTexture.name = "__WaterRefraction" + GetInstanceID();
                    m_RefractionTexture.isPowerOfTwo = true;
                    m_RefractionTexture.hideFlags = HideFlags.DontSave;
                    m_OldRefractionTextureSize = textureSize;
                }

                // Camera for refraction
                m_RefractionCameras.TryGetValue(currentCamera, out refractionCamera);
                if (!refractionCamera) // catch both not-in-dictionary and in-dictionary-but-deleted-GO
                {
                    GameObject go =
                        new GameObject("Water Refr Camera id" + GetInstanceID() + " for " + currentCamera.GetInstanceID(),
                            typeof(Camera), typeof(Skybox));
                    refractionCamera = go.GetComponent<Camera>();
                    refractionCamera.enabled = false;
                    refractionCamera.transform.position = transform.position;
                    refractionCamera.transform.rotation = transform.rotation;
                    refractionCamera.gameObject.AddComponent<FlareLayer>();
                    go.hideFlags = HideFlags.HideAndDontSave;
                    m_RefractionCameras[currentCamera] = refractionCamera;
                }
            }
        }

        WaterMode GetWaterMode()
        {
            if (m_HardwareWaterSupport < waterMode)
            {
                return m_HardwareWaterSupport;
            }
            return waterMode;
        }

        WaterMode FindHardwareWaterSupport()
        {
            if (!GetComponent<Renderer>())
            {
                return WaterMode.Simple;
            }

            Material mat = GetComponent<Renderer>().sharedMaterial;
            if (!mat)
            {
                return WaterMode.Simple;
            }

            string mode = mat.GetTag("WATERMODE", false);
            if (mode == "Refractive")
            {
                return WaterMode.Refractive;
            }
            if (mode == "Reflective")
            {
                return WaterMode.Reflective;
            }

            return WaterMode.Simple;
        }

        // Given position/normal of the plane, calculates plane in camera space.
        Vector4 CameraSpacePlane(Camera cam, Vector3 pos, Vector3 normal, float sideSign)
        {
            Vector3 offsetPos = pos + normal * 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
        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;
        }
    }
}

上面只是單純的源碼,不僅包括了水面反射部分互纯,也包括了水面折射部分瑟幕。
在分析源碼之前,我們更應(yīng)該自主思考留潦,如何才能實(shí)現(xiàn)水面反射只盹,他的現(xiàn)實(shí)的原理(模型)是什么樣子的。
中學(xué)期間兔院,我們就做過這樣的題殖卑,已知,鏡面L秆乳,物體的A點(diǎn)懦鼠,觀察點(diǎn)O钻哩,求鏡面L上觀察A點(diǎn)處光線的入射點(diǎn)i


que.jpg

答案是,反轉(zhuǎn)A點(diǎn)肛冶,到鏡面L的對稱面街氢,得到A' 連接A'O得到與鏡面L的焦點(diǎn)i,在i點(diǎn)睦袖,出入射角等于出射角珊肃。


ans.jpg

但是我們并不能將所有的物體都反轉(zhuǎn)到鏡面的下面,而且我們已經(jīng)知道了要用RTT來實(shí)現(xiàn)馅笙,那么我們就需要做的是反轉(zhuǎn)攝像機(jī)伦乔,到鏡面下方對稱的位置。這樣我們就會得到我們想要的東西董习。要渲染的對象是很多的烈和,但是攝像機(jī)是唯一的,不如把攝像機(jī)倒過來
image.png

攝像機(jī)正常視角圖

up.png

uptrans.png

攝像機(jī)倒置視角圖

down.png

downtrans.png

把攝像機(jī)倒置過來皿淋,y取反招刹,角度在x方向上取反,并卻在z方向上旋轉(zhuǎn)180度窝趣。
但是攝像機(jī)的左右是顛倒的疯暑,我們先用ps處理一下,來查看一下效果哑舒。

PhotoShop進(jìn)行p圖(可以發(fā)現(xiàn)妇拯,倒影的位置完全重合)

效果圖.jpg

水平反轉(zhuǎn)后用選區(qū)摳出來的位置,正好與就是倒影的位置(我給加了一個紅色的外發(fā)光)洗鸵。那么到目前為止越锈,我們的理論模型是已經(jīng)正確的建立了。接下來開始嘗試解析源碼预麸。源碼中很多的API都在字典中瞪浸,大家自己翻翻看就能夠理解犀被。

那么我主要說說膏燕,我遇到的一些不理解的地方谬莹。很多地方真的簡簡單單的一句代碼帝火,引出了很多不得了的東西烹困。此處感謝要幾位大佬蝇庭,文章中和結(jié)束結(jié)束的地方會有各位大佬的鏈接揪漩。

問題1:這是在干什么畜眨?

float d = -Vector3.Dot(normal, pos) - clipPlaneOffset;
Vector4 reflectionPlane = new Vector4(normal.x, normal.y, normal.z, d);

這個問題很直接鸣驱,我當(dāng)時就是想問泛鸟,“這奶奶的是個什么鬼東西?自己的法線點(diǎn)積自己的位置踊东?”
這是在表示一個平面

(現(xiàn)在回想起高中來北滥,那是所學(xué)的立體幾何都是為了就卷子上那點(diǎn)可憐的分?jǐn)?shù)吧)
如果想表示一個平面需要知道哪些條件:
1.三點(diǎn)確定一個平面
2.兩條相交的直線確定一個平面
3.已知平面內(nèi)一點(diǎn)和平面的法線
(但愿你看到這個還有點(diǎn)印象)
平面在空間內(nèi)的表示方程刚操,包括截距式,點(diǎn)發(fā)式再芋,一般式等菊霜。我們需要使用的就是點(diǎn)發(fā)式方程轉(zhuǎn)換成一般式,如已經(jīng)忘得干干凈凈了济赎,可以看一看這個視頻:
https://www.youtube.com/watch?v=dbO4P95Kxxg

點(diǎn)發(fā)式.jpg

坐標(biāo)表示.jpg

如圖所示鉴逞,過點(diǎn)p,法向量為n的平面司训,可表示為:

np+d=0

如果平面面向原點(diǎn)构捡,則d為正,如果平面背向原點(diǎn)壳猜,則d為負(fù)勾徽。

于是平面可以表示為四維向量(nx,ny,nz,d)。

問題2:計(jì)算反射矩陣

// Calculates reflection matrix around the given plane
        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;
        }

矩陣都是用來變換的统扳,反射矩陣會沿著鏡面法線的方向捂蕴,把你的點(diǎn)轉(zhuǎn)換到鏡面的另一面,會把你的向量闪幽,轉(zhuǎn)換成鏡面相反方向上的向量。(請不要妄想逃避矩陣涡匀,哪怕不會推導(dǎo)也要理解大概盯腌,才能在使用的得心應(yīng)手)

推導(dǎo)如下:

Rm.jpg

reflection matrix推導(dǎo):

如圖平面為np+d=0,Q為空間任一點(diǎn)陨瘩,Q'為Q在平面上的投影腕够,Q''為Q關(guān)于平面的對稱點(diǎn),有如下關(guān)系:

r=Q-p

a=(rn)n

b=r-a

c=-a

Q'=p+b

Q''=Q'+c

np+d=0

綜合以上各式舌劳,得:

Q''=Q-2(Qn+d)n

寫成分量形式即:

Q''x=Qx-2(Qxnx+Qyny+Qznz+d)nx

Q''y=Qy-2(Qxnx+Qyny+Qznz+d)ny

Q''z=Qz-2(Qxnx+Qyny+Qznz+d)nz

整理得:

Q''x=Qx(1-2nxnx)+Qy(-2nynx)+Qz(-2nznx)-2dnx

Q''y=Qx(-2nxny)+Qy(1-2nyny)+Qz(-2nzny)-2dny

Q''z=Qx(-2nxnz)+Qy(-2nynz)+Qz(1-2nznz)-2dnz

寫成矩陣形式即:

image

這樣就得到了reflection matrix帚湘。

問題3.二進(jìn)制運(yùn)算與取反?

reflectionCamera.cullingMask = ~(1 << 4) & reflectLayers.value;

并不是我表面上看的那樣甚淡,官方的字典的解釋也比較簡單大诸,但是足夠讓我們能猜出來怎么回事。
(1 << 4):表示layer4贯卦,這一層是默認(rèn)的water層
~ :取反就是资柔,除去這一層,reflectLayers.value = -1也就是everything
所以我們渲染水以外的全部撵割。


image.png

問題4.傾斜的裁剪平面變換矩陣

reflectionCamera.projectionMatrix = cam.CalculateObliqueMatrix(clipPlane);

在官方的代碼中這句輕描淡寫的api是至關(guān)重要的贿堰。


image.png

官方給出的描述也過于模糊,所以我就繼續(xù)的追查下去了啡彬。(還是得感謝楊超大佬)
使用矩陣變換羹与,使得near故硅,far裁剪平面發(fā)生了傾斜。

標(biāo)準(zhǔn)的剪切:我們會投影一些鏡面下面的對象纵搁,這也是很多人出問題的地方


正常的.jpg

傾斜的剪切:經(jīng)過矩陣變換吃衅,使得剪切近平面和遠(yuǎn)平面變的傾斜了,保證了攝像機(jī)只會投影鏡面上方的目標(biāo)诡渴。


obl.jpg

實(shí)際上捐晶,在使用這個api的時候,會有報(bào)錯的可能性妄辩,這是官方的鍋惑灵,我們可以自己寫一個計(jì)算來得到變換矩陣。

 private static float sgn(float a)
    {
        if (a > 0.0f) return 1.0f;
        if (a < 0.0f) return -1.0f;
        return 0.0f;
    }

 private static void CalculateObliqueMatrix (ref Matrix4x4 projection, Vector4 clipPlane)
    {
        Vector4 q = projection.inverse * new Vector4(
            sgn(clipPlane.x),
            sgn(clipPlane.y),
            1.0f,
            1.0f
        );
        Vector4 c = clipPlane * (2.0F / (Vector4.Dot (clipPlane, q)));

        projection[2] = c.x - projection[3];
        projection[6] = c.y - projection[7];
        projection[10] = c.z - projection[11];
        projection[14] = c.w - projection[15];
    }

(吐槽:居然還能斜著來眼耀,真是拓寬我的視野了)
推導(dǎo)參考:http://terathon.com/lengyel/Lengyel-Oblique.pdf

之前在其他地方看見了一個錯誤的效果英支,就是渲染了鏡面背后的內(nèi)容。

image.png

錯誤效果的原文鏈接:https://gameinstitute.qq.com/community/detail/106151

問題5.為什么要翻轉(zhuǎn)剔除

//執(zhí)行了翻轉(zhuǎn)剔除代碼
GL.invertCulling = !oldCulling;
GL.invertCulling = oldCulling;

因?yàn)椴粓?zhí)行這項(xiàng)操作哮伟,會是這樣的

image.png

在鏡面上干花,我們希望表現(xiàn)的面,全部都消失了楞黄。顯示的是池凄,本應(yīng)該被剔除的“背”面。
在我們一開始講述原理的時候鬼廓,我們將一個攝像機(jī)反轉(zhuǎn)到了水下肿仑,其中對Z執(zhí)行了180度的反轉(zhuǎn)。但是我們在代碼中只是使用了反射的矩陣碎税,并沒有旋轉(zhuǎn)攝像機(jī)尤慰,這就會直接倒置我們的頂點(diǎn)。為什么會出現(xiàn)剔除現(xiàn)象雷蹂,與unity的一個三角面的表示方法有關(guān)伟端。這里先放幾個參考:
https://www.cnblogs.com/JLZT1223/p/6080164.html
https://blog.csdn.net/liu_if_else/article/details/73294579
http://xdpixel.com/how-to-flip-an-image-via-the-cameras-projection-matrix/
unity內(nèi)每一個三角面都是由3個定點(diǎn)來表示的,所以匪煌,三角面的記錄责蝠,是一組頂點(diǎn)數(shù)組,而且其長度永遠(yuǎn)是3的倍數(shù)(每3個表示一個三角面)萎庭。
而3個頂點(diǎn)按照順時針存貯玛歌,表示的一個面是正面,若逆時針存儲擎椰,則表示這個面是背面支子。(吐槽:一開始我還以為與法線有關(guān)呢,看來法線只是用來參與“無情的數(shù)學(xué)運(yùn)算”)
因?yàn)槲覀兘?jīng)過了倒置达舒,導(dǎo)致了在三角面值朋,本來應(yīng)該被順時針記錄的頂點(diǎn)叹侄,變成了逆時針記錄,所以對應(yīng)的三角面就會被視為背面給剔除了昨登,而背面都被變成了正面趾代,所以有必要執(zhí)行反轉(zhuǎn)剔除。
image.png

經(jīng)過了一些列的操作終于得到了我們想要的RTT

問題6.如何在shader中讀取紋理坐標(biāo)

//這是表面著色器的做法
float2 screenUV = IN.screenPos.xy / IN.screenPos.w;

在之前的實(shí)驗(yàn)中(就是用ps去p圖的那個實(shí)驗(yàn))丰辣,已經(jīng)證明了撒强,我們的RTT,若直接映射到屏幕空間笙什,正好對應(yīng)在倒影的位置飘哨。所以我們只需要得到屏幕空間下的uv就可以對RTT進(jìn)行采樣。

v2f vert (appdata v)
{
       v2f o;
       ...
       o.ScreenPos = ComputeScreenPos(o.vertex);
       ...
       return o;
}
fixed4 frag (v2f i) : SV_Target
{
       ...
       half4 reflectionColor = tex2D(_RefTexture, i.ScreenPos.xy/i.ScreenPos.w);
       ...

我們得到屏幕空間下的位置琐凭,然后除以w分量芽隆,得到屏幕空間下的uv
參考:
https://blog.csdn.net/wodownload2/article/details/88955666

以上這些就是我們的鏡面反射的核心內(nèi)容

如果你想實(shí)現(xiàn)一些其他效果,就大膽的寫在shader內(nèi)(疊加材質(zhì)也好统屈,法線也好胚吁,擾動也好)
祝愿你能實(shí)現(xiàn)你想要的美麗shader

image.png

如果你喜歡這篇文章,或者覺得愁憔,這篇文章對你有幫助腕扶,那么請
.
.
.
.
.
.
.
.
.
.
.

.

和我交個朋友吧

留個郵箱:3493668620@qq.com
(這個qq不會登錄,但是發(fā)郵件我一定會看的哦)

其他參考鏈接:
http://www.reibang.com/p/3f9217be3fde
https://www.cnblogs.com/wantnon/p/5630915.html
https://zhuanlan.zhihu.com/p/74529106

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吨掌,一起剝皮案震驚了整個濱河市蕉毯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌思犁,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件进肯,死亡現(xiàn)場離奇詭異激蹲,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)江掩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門学辱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人环形,你說我怎么就攤上這事策泣。” “怎么了抬吟?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵萨咕,是天一觀的道長。 經(jīng)常有香客問我火本,道長危队,這世上最難降的妖魔是什么聪建? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮茫陆,結(jié)果婚禮上金麸,老公的妹妹穿的比我還像新娘。我一直安慰自己簿盅,他們只是感情好挥下,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著桨醋,像睡著了一般棚瘟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上讨盒,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天解取,我揣著相機(jī)與錄音,去河邊找鬼返顺。 笑死禀苦,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的遂鹊。 我是一名探鬼主播振乏,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼秉扑!你這毒婦竟也來了慧邮?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤舟陆,失蹤者是張志新(化名)和其女友劉穎误澳,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體秦躯,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡忆谓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了踱承。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倡缠。...
    茶點(diǎn)故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖茎活,靈堂內(nèi)的尸體忽然破棺而出昙沦,到底是詐尸還是另有隱情,我是刑警寧澤载荔,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布盾饮,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏丐谋。R本人自食惡果不足惜芍碧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望号俐。 院中可真熱鬧泌豆,春花似錦、人聲如沸吏饿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽猪落。三九已至贞远,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間笨忌,已是汗流浹背蓝仲。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留官疲,地道東北人袱结。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像途凫,于是被迫代替她去往敵國和親垢夹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容