Mirror:Reflect By Unity

1.很高兴你能找到这里,我表示热烈的欢迎
2.这是一篇写给渊瞳的笔记,因为他脑子不太好,早晚会忘记
3.文中主要解释代码中难理解的地方,如果这正是你需要的,那我会很开心

从哪里开始?既然是一片shader有关的内容,我们先从女神书(入门精要)开始,因为我也还是从这里开始的。
在女神书中10.2章提及的渲染纹理,并且用渲染纹理来实现镜面效果,着实令我感到兴奋。无数的电影,游戏,CG,都会使用镜面材质来表现细节,提高画面的质量(渊瞳:那个是菲涅尔)。


镜面1.png
镜面2.png
镜面3.png

但是,仅仅是使用一个RenderTargetTexture(RTT)并不能实现我们想要的效果。如果想要实现这样的效果,还需要了解很多的东西。


GIF.gif

如果你努力学习并且查找了一些相关的资料的话,你可能会去调用Camera.OnWillRenderObject()函数,然后你会在官方的dictionary中找到。


dic.png

既然有官方学习资料,自然大家都会看,(注意注意注意:water.cs并不在Effects内,而是在Environment下,虽然已经向官方反馈了,也许你们看到的时候就已经改好了)

这长篇的代码只是给那些没看过的人准备的,以及为了后面讲解,翻过来查找用(既然你已经看过了,就跳过吧。)

water.cs的源码可以说是现在常见的的镜面反射的原型,或者说基础。

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 m_ReflectionCameras = new Dictionary(); // Camera -> Camera table
        private Dictionary m_RefractionCameras = new Dictionary(); // 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() || !GetComponent().sharedMaterial ||
                !GetComponent().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().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().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())
            {
                return;
            }
            Material mat = GetComponent().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 mysky = dest.GetComponent();
                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();
                    reflectionCamera.enabled = false;
                    reflectionCamera.transform.position = transform.position;
                    reflectionCamera.transform.rotation = transform.rotation;
                    reflectionCamera.gameObject.AddComponent();
                    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();
                    refractionCamera.enabled = false;
                    refractionCamera.transform.position = transform.position;
                    refractionCamera.transform.rotation = transform.rotation;
                    refractionCamera.gameObject.AddComponent();
                    go.hideFlags = HideFlags.HideAndDontSave;
                    m_RefractionCameras[currentCamera] = refractionCamera;
                }
            }
        }

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

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

            Material mat = GetComponent().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;
        }
    }
}

上面只是单纯的源码,不仅包括了水面反射部分,也包括了水面折射部分。
在分析源码之前,我们更应该自主思考,如何才能实现水面反射,他的现实的原理(模型)是什么样子的。
中学期间,我们就做过这样的题,已知,镜面L,物体的A点,观察点O,求镜面L上观察A点处光线的入射点i


que.jpg

答案是,反转A点,到镜面L的对称面,得到A' 连接A'O得到与镜面L的焦点i,在i点,出入射角等于出射角。


ans.jpg

但是我们并不能将所有的物体都反转到镜面的下面,而且我们已经知道了要用RTT来实现,那么我们就需要做的是反转摄像机,到镜面下方对称的位置。这样我们就会得到我们想要的东西。要渲染的对象是很多的,但是摄像机是唯一的,不如把摄像机倒过来
image.png

摄像机正常视角图

up.png

uptrans.png

摄像机倒置视角图

down.png

downtrans.png

把摄像机倒置过来,y取反,角度在x方向上取反,并却在z方向上旋转180度。
但是摄像机的左右是颠倒的,我们先用ps处理一下,来查看一下效果。

PhotoShop进行p图(可以发现,倒影的位置完全重合)

效果图.jpg

水平反转后用选区抠出来的位置,正好与就是倒影的位置(我给加了一个红色的外发光)。那么到目前为止,我们的理论模型是已经正确的建立了。接下来开始尝试解析源码。源码中很多的API都在字典中,大家自己翻翻看就能够理解。

那么我主要说说,我遇到的一些不理解的地方。很多地方真的简简单单的一句代码,引出了很多不得了的东西。此处感谢要几位大佬,文章中和结束结束的地方会有各位大佬的链接。

问题1:这是在干什么?

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

这个问题很直接,我当时就是想问,“这奶奶的是个什么鬼东西?自己的法线点积自己的位置?”
这是在表示一个平面

(现在回想起高中来,那是所学的立体几何都是为了就卷子上那点可怜的分数吧)
如果想表示一个平面需要知道哪些条件:
1.三点确定一个平面
2.两条相交的直线确定一个平面
3.已知平面内一点和平面的法线
(但愿你看到这个还有点印象)
平面在空间内的表示方程,包括截距式,点发式,一般式等。我们需要使用的就是点发式方程转换成一般式,如已经忘得干干净净了,可以看一看这个视频:
https://www.youtube.com/watch?v=dbO4P95Kxxg

点发式.jpg

坐标表示.jpg

如图所示,过点p,法向量为n的平面,可表示为:

np+d=0

如果平面面向原点,则d为正,如果平面背向原点,则d为负。

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

问题2:计算反射矩阵

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

矩阵都是用来变换的,反射矩阵会沿着镜面法线的方向,把你的点转换到镜面的另一面,会把你的向量,转换成镜面相反方向上的向量。(请不要妄想逃避矩阵,哪怕不会推导也要理解大概,才能在使用的得心应手)

推导如下:

Rm.jpg

reflection matrix推导:

如图平面为np+d=0,Q为空间任一点,Q'为Q在平面上的投影,Q''为Q关于平面的对称点,有如下关系:

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.二进制运算与取反?

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

并不是我表面上看的那样,官方的字典的解释也比较简单,但是足够让我们能猜出来怎么回事。
(1 << 4):表示layer4,这一层是默认的water层
~ :取反就是,除去这一层,reflectLayers.value = -1也就是everything
所以我们渲染水以外的全部。


image.png

问题4.倾斜的裁剪平面变换矩阵

reflectionCamera.projectionMatrix = cam.CalculateObliqueMatrix(clipPlane);

在官方的代码中这句轻描淡写的api是至关重要的。


image.png

官方给出的描述也过于模糊,所以我就继续的追查下去了。(还是得感谢杨超大佬)
使用矩阵变换,使得near,far裁剪平面发生了倾斜。

标准的剪切:我们会投影一些镜面下面的对象,这也是很多人出问题的地方


正常的.jpg

倾斜的剪切:经过矩阵变换,使得剪切近平面和远平面变的倾斜了,保证了摄像机只会投影镜面上方的目标。


obl.jpg

实际上,在使用这个api的时候,会有报错的可能性,这是官方的锅,我们可以自己写一个计算来得到变换矩阵。

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

(吐槽:居然还能斜着来,真是拓宽我的视野了)
推导参考:http://terathon.com/lengyel/Lengyel-Oblique.pdf

之前在其他地方看见了一个错误的效果,就是渲染了镜面背后的内容。

image.png

错误效果的原文链接:https://gameinstitute.qq.com/community/detail/106151

问题5.为什么要翻转剔除

//执行了翻转剔除代码
GL.invertCulling = !oldCulling;
GL.invertCulling = oldCulling;

因为不执行这项操作,会是这样的

image.png

在镜面上,我们希望表现的面,全部都消失了。显示的是,本应该被剔除的“背”面。
在我们一开始讲述原理的时候,我们将一个摄像机反转到了水下,其中对Z执行了180度的反转。但是我们在代码中只是使用了反射的矩阵,并没有旋转摄像机,这就会直接倒置我们的顶点。为什么会出现剔除现象,与unity的一个三角面的表示方法有关。这里先放几个参考:
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内每一个三角面都是由3个定点来表示的,所以,三角面的记录,是一组顶点数组,而且其长度永远是3的倍数(每3个表示一个三角面)。
而3个顶点按照顺时针存贮,表示的一个面是正面,若逆时针存储,则表示这个面是背面。(吐槽:一开始我还以为与法线有关呢,看来法线只是用来参与“无情的数学运算”)
因为我们经过了倒置,导致了在三角面,本来应该被顺时针记录的顶点,变成了逆时针记录,所以对应的三角面就会被视为背面给剔除了,而背面都被变成了正面,所以有必要执行反转剔除。
image.png

经过了一些列的操作终于得到了我们想要的RTT

问题6.如何在shader中读取纹理坐标

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

在之前的实验中(就是用ps去p图的那个实验),已经证明了,我们的RTT,若直接映射到屏幕空间,正好对应在倒影的位置。所以我们只需要得到屏幕空间下的uv就可以对RTT进行采样。

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

以上这些就是我们的镜面反射的核心内容

如果你想实现一些其他效果,就大胆的写在shader内(叠加材质也好,法线也好,扰动也好)
祝愿你能实现你想要的美丽shader

image.png

如果你喜欢这篇文章,或者觉得,这篇文章对你有帮助,那么请
.
.
.
.
.
.
.
.
.
.
.

.

和我交个朋友吧

留个邮箱:[email protected]
(这个qq不会登录,但是发邮件我一定会看的哦)

其他参考链接:
https://www.jianshu.com/p/3f9217be3fde
https://www.cnblogs.com/wantnon/p/5630915.html
https://zhuanlan.zhihu.com/p/74529106

你可能感兴趣的:(Mirror:Reflect By Unity)