找回密码
 立即注册
查看: 407|回复: 0

Unity 实现平面反射(基于 URP)

[复制链接]
发表于 2022-4-20 15:10 | 显示全部楼层 |阅读模式
平面反射(Planar Reflection)原理

平面反射可以模拟光滑度很高的镜面效果,但也只能用于高度一致的平面,用在水面的效果是非常不错的。网上的文章讲反射矩阵和斜截视锥体的文章很多了,但细讲反射原理的很少,我觉得还是有些细节值得讲的。
基本思路

首先看一下平面反射的实现思路。(关于最基本的反射原理可以参考:王二:[数学] 光线的反射(reflect)与折射(refract))



平面反射原理

先介绍一种最简单直接的思路,不需要反射矩阵。
当前目标是,使用相机  ,在看向光滑平面上的 、 两点时,根据光线的反射原理,应该分别对应显示物体上的  、 两点。目前的思路是,平面上采样一张贴图,在对应的  点显示  点的颜色, 点显示  点的颜色,并且要保证光照效果正确,那么这张贴图应该长啥样?并且如何采样?
首先,想象出一个物体关于平面对称后产生一个倒立的一模一样的物体,原物体的 、   对应想象物体的  、 ,根据光线的反射原理推出,相机  要在  点通过反射看到  点,等同于相机的视线穿过平面看到  点,我们只要采样到相机  此时看到的  点时的颜色就可以了。
按上面的方法,我们有了反射平面的贴图了,该怎么采样它才能正确的显示呢?通过上面的推导和图可以知道,  点在  和  的裁剪空间中的位置是刚好颠倒的,所以直接用裁剪空间的坐标经过透视除法,转为屏幕空间坐标,再把 y 轴颠倒过来,用 xy 坐标当作 uv 去采样就可以了。
计算过程可以概括为以下几步:

  • 将相机   的 World Space 坐标转换到平面的 Object Space(简称平面空间)
  • 将相机   的平面空间坐标的 Y 轴取反,获得关于平面对称的反射相机  的平面空间坐标,并转换回 World Space
  • 在平面空间获取相机   的 Z 轴朝向和 Y 轴朝向,将它们关于平面对称,获得反射相机  在平面空间对应的两个Z、Y轴朝向向量,然后转换到 World Space 然后赋予给反射相机
  • 将反射相机渲染的图片缓存,在平面上使用将 y 轴颠倒的屏幕坐标采样
相关代码:
        private void UpdateReflectionCamera(Camera curCamera) {
            if (targetPlane == null) {
                Debug.LogError("target plane is null!");
            }

            UpdateCamera(curCamera, _reflectionCamera);  // 同步当前相机数据

            // 将相机移转换到平面空间 plane space,再通过平面对称创建反射相机
            Vector3 camPosPS = targetPlane.transform.worldToLocalMatrix.MultiplyPoint(curCamera.transform.position);
            Vector3 reflectCamPosPS = Vector3.Scale(camPosPS, new Vector3(1, -1, 1)) + new Vector3(0, m_planeOffset, 0);  // 反射相机平面空间
            Vector3 reflectCamPosWS = targetPlane.transform.localToWorldMatrix.MultiplyPoint(reflectCamPosPS);  // 将反射相机转换到世界空间
            _reflectionCamera.transform.position = reflectCamPosWS;

            // 设置反射相机方向
            Vector3 camForwardPS = targetPlane.transform.worldToLocalMatrix.MultiplyVector(curCamera.transform.forward);
            Vector3 reflectCamForwardPS = Vector3.Scale(camForwardPS, new Vector3(1, -1, 1));
            Vector3 reflectCamForwardWS = targetPlane.transform.localToWorldMatrix.MultiplyVector(reflectCamForwardPS);
            
            Vector3 camUpPS = targetPlane.transform.worldToLocalMatrix.MultiplyVector(curCamera.transform.up);
            Vector3 reflectCamUpPS = Vector3.Scale(camUpPS, new Vector3(-1, 1, -1));
            Vector3 reflectCamUpWS = targetPlane.transform.localToWorldMatrix.MultiplyVector(reflectCamUpPS);
            _reflectionCamera.transform.rotation = Quaternion.LookRotation(reflectCamForwardWS, reflectCamUpWS);

            // 斜截视锥体
            Vector3 planeNormal = targetPlane.transform.up;
            Vector3 planePos = targetPlane.transform.position + planeNormal * m_planeOffset;
            var clipPlane = CameraSpacePlane(_reflectionCamera, planePos - Vector3.up * 0.1f, planeNormal, 1.0f);
            _reflectionCamera.projectionMatrix = projection;
            _reflectionCamera.cullingMask = m_settings.m_ReflectLayers; // never render water layer
        }


采样 UV

优化方法思路

上面这种方法矩阵计算比较多,有一个同等效果的,计算量更少的方法,需要用到反射矩阵,我找到的网上现有的脚本也都是用的这种方法。
我们知道模型通过 MVP 矩阵从模型空间变到裁剪空间,我们并不需要让  移动到镜面对称的位置,而是让  同样处于  的位置,然后在   的 M 矩阵和 V  矩阵之间加入一个反射矩阵,把整个世界倒过来,这样相机  看到的就永远是一个关于平面镜像的世界,而且两个相机视锥体完全重合了,采样时也不用颠倒 y 轴坐标了。
关于这个方法我在找资料的过程中有一些发现:
Unity Shader-反射效果(CubeMap,Reflection Probe,Planar Reflection,Screen Space Reflection), 关于反射这篇文章讲的非常详细,但其中应该有个错误:
我们把R矩阵插在V之后,在一个物体在进行MV变换后,变到正常相机坐标系下,然后再进行一次反射变换,就相当于变换到了相对于平面对称的相机坐标系下,然后再进行正常的投影变换,就可以得到反射贴图了。
reflectionCamera.worldToCameraMatrix = curCamera.worldToCameraMatrix * reflection;实际上,因为矩阵是左乘,所以代码中反射矩阵并非在 V 矩阵之后,而应该是在 V 矩阵之前,M 矩阵之后使用的,而且反射矩阵中的计算平面和点用的都是世界空间坐标,所以这一步实际上是将整个世界关于平面对称倒过来之后,再变到视空间的。
另外,  的 V 矩阵是基于原相机   的 V 矩阵的,所以两个相机的位置相同,应该没有必要再设置  的 transform 。
我看到一些代码,包括官方 URP 的示例项目 《Boat Attack》,有这样的操作:
reflectionCamera.transform.position = newpos;   // newpos 是关于目标平面对称或者世界空间 xz 平面对称的位置可按我的理解是没有必要的,因为反射相机的 V 矩阵都已经改了,再调整它的 transform 应该没有啥意义了,实际上我把这段代码注掉后的脚本都是正常运行,暂未发现有任何影响,如果确实是需要这一步的话,有知道原因的大佬希望能告知一下。

反射矩阵推导

上面的方法要在   的 V 矩阵前插入一个反射矩阵,让整个世界在它眼中倒过来。这个矩阵要做的就是  ,其中   是  关于平面对称的点,下面来推导一下这个反射矩阵。



反射矩阵推导

已知  为平面的单位法向量,  为平面上任意一点,  是 与平面的交点。 求反射矩阵   使平面外一点   ,和其关于平面对称点   满足  
我们的目标是找到反射矩阵  ,从图中很容易看出  和   的关系:


因为其中 已知


为了简化下面带入  
将上式带入(1)式


为方便观察,先列出每一项对应的计算






经过观察总结出反射矩阵


斜截视锥体(Oblique View Frustum)

虽然完成了镜面反射,但还有一个问题没解决,那就是反射相机当前的视锥体与主相机相同,所以镜像后能看到反射平面背后的东西,就会出现这种情况



反射相机视锥体问题

它的视锥体应该以反射平面为近平面,如下图所示,视锥体 A 部分应该被剔除,只保留 B 部分。



反射相机视锥体

这需要用到斜截视锥体的技术,简单概括,这种技术通过改变 MVP 中的 P 矩阵,实现用指定的平面来当作近平面,但同时会影响到远平面,而其他四个平面不受影响,这正是我们需要的。
以下内容参考论文:《Oblique View Frustum Depth Projection and Clipping》
平面的共变(covariant)

要想将视空间中的平面当作裁剪空间中的近平面,首先需要了解平面是如何变换的。
平面有两种表示形式,一种是四个系数的平面方程:


还可以用平面的法向量  ,和平面内已知的任意一点  来表示:


将(2)展开可以得到


结合(1)可以带入对应系数


平面可以用一个四维向量表示,不难发现,平面和法线类似,是一个共变向量,于是在透视变换中与法线变换一样,需要透视矩阵的逆的转置,具体原理可以参考:王二:[数学] 法线变换




另外,任意点与平面向量相乘可以表示为与平面的带符号的垂直距离,知道这一点后反射矩阵的计算更加容易了。

修改透视矩阵

从视空间变换到裁剪空间再经过透视除,就可以将视锥体转换成 xyz 三轴坐标都在 [-1,1] 之间的立方体(基于 OpenGL 风格,DirectX 的 z 轴是 [0,1])也就是 NDC,我们希望修改透视矩阵后,NDC 组成的仍然是这样一个立方体。于是可以将近平面在视空间中的坐标,用经过透视除法后的裁剪空间坐标表示





平面在视空间和裁剪空间的坐标关系

如上图所示,近平面对应了透视矩阵的第三行和第四行相加,而我们想修改透视矩阵来改变近平面的话,只能修改它的第三行,第四行不能改,因为第四行会把视空间中的 z 坐标转到裁剪空间的 w 坐标,这对于顶点属性的透视矫正插值是必要的,相关原因可以参考:王二:[数学] 重心坐标插值与透视校正插值
修改后的透视矩阵的第三行为 ,这会同时影响近平面和远平面,于是修改后的视空间中的近平面 和远平面 表示为:




如果我们指定的近平面的法线不垂直于 xy 平面的话,修改后的近远平面就不平行了,那它们在视空间中是什么样的呢?它们会在 xy 平面上相交,只要找 xy 平面上一点 就可以发现,因为   没有改动,所以 。所以它们现在可能长这样:



修改透视矩阵后的近远平面在 xy 平面相交

这只是一种可能,远平面也可能会截断原视锥体。这也挺反直觉的,都不能围成一个封闭体积了,还能通过透视矩阵变换成立方体吗?还真的可以,毕竟我们是根据透视矩阵反着推的。
不过这种形状很有问题,可能会裁剪掉原视锥体,也会严重影响深度缓冲的精度,这个在论文的最后做了分析,暂时可以理解为类似于原视锥体,深度值就是沿着z轴坐标值,为了让深度缓冲有更高的精度,在 [-1,1] 之间容纳更小的范围,我们尽可能让近远平面之间的距离更小。而现在深度值变得更复杂了,和近远平面的位置有关,沿着不同方向造成的精度损失也不同,为了让深度缓冲精度值更高,我们要让近远平面之间的夹角更小,同时不裁剪原视锥体,如下图所示



优化远平面

近平面隐藏了一个缩放系数  ,可以调整这个系数而不影响我们的最终目的,但可以调整远平面的角度:


下面通过求出视空间中的  从而要得到合适的   。 (其中 sgn 是符号函数,输出 ±1)








最终我们得到修改后的透视矩阵第三行


标准透视矩阵的应用

下面将我们得到的结果用于标准透视矩阵(OpenGL 风格)试试




当前的透视矩阵不会改变平面法线 xy 轴的正负, ,所以可免去将平面转到裁剪空间


系数 也可以化简为:


修改后的投影矩阵可以写为


相关代码:
        private Matrix4x4 CalculateObliqueMatrix(Camera cam, Vector4 plane) {
                Vector4 Q_clip = new Vector4(Mathf.Sign(plane.x), Mathf.Sign(plane.y), 1f, 1f);
                Vector4 Q_view = cam.projectionMatrix.inverse.MultiplyPoint(Q_clip);

                Vector4 scaled_plane = plane * 2.0f / Vector4.Dot(plane, Q_view);
                Vector4 M3 = scaled_plane - cam.projectionMatrix.GetRow(3);
               
                Matrix4x4 new_M = cam.projectionMatrix;
                new_M.SetRow(2, M3);

                // 使用 unity API
                // var new_M = cam.CalculateObliqueMatrix(plane);
                return new_M;
        }
平面反射 C# 代码实现 (基于 Unity URP )

完整代码:
using System;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Serialization;
using Unity.Mathematics;


namespace UnityEngine.Rendering.Universal {
    [ExecuteAlways]
    public class plannarReflectionTest : MonoBehaviour {
        [Serializable]
        public enum ResolutionMulltiplier { Full, Half, Third, Quarter }

        [Serializable]
        public class PlanarReflectionSettings {
            public ResolutionMulltiplier m_ResolutionMultiplier = ResolutionMulltiplier.Third;
            public float m_ClipPlaneOffset = 0.07f;
            public LayerMask m_ReflectLayers = -1;
            public bool m_Shadows;
        }
        
        [SerializeField]
        public PlanarReflectionSettings m_settings = new PlanarReflectionSettings();
        public GameObject targetPlane;
        public float m_planeOffset;

        private static Camera _reflectionCamera;
        private RenderTexture _reflectionTexture;
        private readonly int _planarReflectionTextureId = Shader.PropertyToID("_ReflectTex");

        // public static event Action<ScriptableRenderContext, Camera> BeginPlanarReflections;
        private void OnEnable() {
            
            RenderPipelineManager.beginCameraRendering += runPlannarReflection;  // 订阅 beginCameraRendering 事件,加入平面反射函数
        }

        private void OnDisable() {
            Cleanup();
        }

        private void OnDestroy() {
            Cleanup();
        }

        private void Cleanup() {
            RenderPipelineManager.beginCameraRendering -= runPlannarReflection;
            if(_reflectionCamera) {  // 释放相机
                _reflectionCamera.targetTexture = null;
                SafeDestroy(_reflectionCamera.gameObject);
            }
            if (_reflectionTexture) {  // 释放纹理
                RenderTexture.ReleaseTemporary(_reflectionTexture);
            }
        }
        private static void SafeDestroy(Object obj) {
            if (Application.isEditor) {
                DestroyImmediate(obj);  //TODO
            }
            else {
                Destroy(obj);   //TODO
            }
        }
        private void runPlannarReflection(ScriptableRenderContext context, Camera camera) {
            // we dont want to render planar reflections in reflections or previews
            if (camera.cameraType == CameraType.Reflection || camera.cameraType == CameraType.Preview)
                return;

            if (targetPlane == null) {
                targetPlane = gameObject;
            }
            if (_reflectionCamera == null) {
                _reflectionCamera = CreateReflectCamera();
            }

            var data = new PlanarReflectionSettingData(); // save quality settings and lower them for the planar reflections
            data.Set(); // set quality settings

            UpdateReflectionCamera(camera);  // 设置相机位置和方向等参数
            CreatePlanarReflectionTexture(camera);  // create and assign RenderTexture

            // BeginPlanarReflections?.Invoke(context, _reflectionCamera); // callback Action for PlanarReflection "?."确保event被订阅
            UniversalRenderPipeline.RenderSingleCamera(context, _reflectionCamera); // render planar reflections  开始渲染函数

            data.Restore(); // restore the quality settings
            Shader.SetGlobalTexture(_planarReflectionTextureId, _reflectionTexture); // Assign texture to water shader
        }

        private int2 ReflectionResolution(Camera cam, float scale) {
            var x = (int)(cam.pixelWidth * scale * GetScaleValue());
            var y = (int)(cam.pixelHeight * scale * GetScaleValue());
            return new int2(x, y);
        }

        private float GetScaleValue() {
            switch(m_settings.m_ResolutionMultiplier) {
                case ResolutionMulltiplier.Full:
                    return 1f;
                case ResolutionMulltiplier.Half:
                    return 0.5f;
                case ResolutionMulltiplier.Third:
                    return 0.33f;
                case ResolutionMulltiplier.Quarter:
                    return 0.25f;
                default:
                    return 0.5f; // default to half res
            }
        }

        private void CreatePlanarReflectionTexture(Camera cam) {
            if (_reflectionTexture == null) {
                var res = ReflectionResolution(cam, UniversalRenderPipeline.asset.renderScale);  // 获取 RT 的大小
                const bool useHdr10 = true;
                const RenderTextureFormat hdrFormat = useHdr10 ? RenderTextureFormat.RGB111110Float : RenderTextureFormat.DefaultHDR;
                _reflectionTexture = RenderTexture.GetTemporary(res.x, res.y, 16,
                    GraphicsFormatUtility.GetGraphicsFormat(hdrFormat, true));
            }
            _reflectionCamera.targetTexture =  _reflectionTexture; // 将 RT 赋予相机
        }
        private void UpdateCamera(Camera src, Camera dest) {
            if (dest == null) return;

            // dest.CopyFrom(src);
            dest.aspect = src.aspect;
            dest.cameraType = src.cameraType;   // 这个参数不同步就错
            dest.clearFlags = src.clearFlags;
            dest.fieldOfView = src.fieldOfView;
            dest.depth = src.depth;
            dest.farClipPlane = src.farClipPlane;
            dest.focalLength = src.focalLength;
            dest.useOcclusionCulling = false;
            if (dest.gameObject.TryGetComponent(out UniversalAdditionalCameraData camData)) {  // TODO
                camData.renderShadows = m_settings.m_Shadows; // turn off shadows for the reflection camera
            }
        }

        // Calculates reflection matrix around the given plane
        private static Matrix4x4 CalculateReflectionMatrix(Vector4 plane)
        {
            Matrix4x4 reflectionMat = Matrix4x4.identity;
            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;

            return reflectionMat;
        }
        // Given position/normal of the plane, calculates plane in camera space.
        private Vector4 CameraSpacePlane(Camera cam, Vector3 pos, Vector3 normal, float sideSign) {
            var offsetPos = pos + normal * m_settings.m_ClipPlaneOffset;
            var m = cam.worldToCameraMatrix;
            var cameraPosition = m.MultiplyPoint(offsetPos);
            var cameraNormal = m.MultiplyVector(normal).normalized * sideSign;
            return new Vector4(cameraNormal.x, cameraNormal.y, cameraNormal.z, -Vector3.Dot(cameraPosition, cameraNormal));
        }

        private void UpdateReflectionCamera(Camera curCamera) {
            if (targetPlane == null) {
                Debug.LogError("target plane is null!");
            }

            Vector3 planeNormal = targetPlane.transform.up;
            Vector3 planePos = targetPlane.transform.position + planeNormal * m_planeOffset;

            UpdateCamera(curCamera, _reflectionCamera);  // 同步当前相机数据

            // 获取视空间平面,使用反射矩阵,将图像根据平面对称上下颠倒
            var planVS = new Vector4(planeNormal.x, planeNormal.y, planeNormal.z, -Vector3.Dot(planeNormal, planePos));
            Matrix4x4 reflectionMat = CalculateReflectionMatrix(planVS);
            _reflectionCamera.worldToCameraMatrix = curCamera.worldToCameraMatrix * reflectionMat;
            // 斜截视锥体
            var clipPlane = CameraSpacePlane(_reflectionCamera, planePos, planeNormal, 1.0f);
            var newProjectionMat = CalculateObliqueMatrix(curCamera, clipPlane);
            _reflectionCamera.projectionMatrix = newProjectionMat;
            _reflectionCamera.cullingMask = m_settings.m_ReflectLayers; // never render water layer

        }
        
        // private void UpdateReflectionCamera(Camera curCamera) {
        //     // 不使用反射矩阵的方法
        //     if (targetPlane == null) {
        //         Debug.LogError("target plane is null!");
        //     }

        //     UpdateCamera(curCamera, _reflectionCamera);  // 同步当前相机数据

        //     // 将相机移转换到平面空间 plane space,再通过平面对称创建反射相机
        //     Vector3 camPosPS = targetPlane.transform.worldToLocalMatrix.MultiplyPoint(curCamera.transform.position);
        //     Vector3 reflectCamPosPS = Vector3.Scale(camPosPS, new Vector3(1, -1, 1)) + new Vector3(0, m_planeOffset, 0);  // 反射相机平面空间
        //     Vector3 reflectCamPosWS = targetPlane.transform.localToWorldMatrix.MultiplyPoint(reflectCamPosPS);  // 将反射相机转换到世界空间
        //     _reflectionCamera.transform.position = reflectCamPosWS;

        //     // 设置反射相机方向
        //     Vector3 camForwardPS = targetPlane.transform.worldToLocalMatrix.MultiplyVector(curCamera.transform.forward);
        //     Vector3 reflectCamForwardPS = Vector3.Scale(camForwardPS, new Vector3(1, -1, 1));
        //     Vector3 reflectCamForwardWS = targetPlane.transform.localToWorldMatrix.MultiplyVector(reflectCamForwardPS);
            
        //     Vector3 camUpPS = targetPlane.transform.worldToLocalMatrix.MultiplyVector(curCamera.transform.up);
        //     Vector3 reflectCamUpPS = Vector3.Scale(camUpPS, new Vector3(-1, 1, -1));
        //     Vector3 reflectCamUpWS = targetPlane.transform.localToWorldMatrix.MultiplyVector(reflectCamUpPS);
        //     _reflectionCamera.transform.rotation = Quaternion.LookRotation(reflectCamForwardWS, reflectCamUpWS);

        //     // 斜截视锥体
        //     Vector3 planeNormal = targetPlane.transform.up;
        //     Vector3 planePos = targetPlane.transform.position + planeNormal * m_planeOffset;
        //     var clipPlane = CameraSpacePlane(_reflectionCamera, planePos - Vector3.up * 0.1f, planeNormal, 1.0f);
        //     var newProjectionMat = CalculateObliqueMatrix(curCamera, clipPlane);
        //     _reflectionCamera.projectionMatrix = newProjectionMat;
        //     _reflectionCamera.cullingMask = m_settings.m_ReflectLayers; // never render water layer
        // }
        private Matrix4x4 CalculateObliqueMatrix(Camera cam, Vector4 plane) {
                Vector4 Q_clip = new Vector4(Mathf.Sign(plane.x), Mathf.Sign(plane.y), 1f, 1f);
                Vector4 Q_view = cam.projectionMatrix.inverse.MultiplyPoint(Q_clip);

                Vector4 scaled_plane = plane * 2.0f / Vector4.Dot(plane, Q_view);
                Vector4 M3 = scaled_plane - cam.projectionMatrix.GetRow(3);
               
                Matrix4x4 new_M = cam.projectionMatrix;
                new_M.SetRow(2, M3);

                // 使用 unity API
                // var new_M = cam.CalculateObliqueMatrix(plane);
                return new_M;
        }

        private Camera CreateReflectCamera() {
            var go = new GameObject(gameObject.name + " Planar Reflection Camera",typeof(Camera));
            var cameraData = go.AddComponent(typeof(UniversalAdditionalCameraData)) as UniversalAdditionalCameraData;

            cameraData.requiresColorOption = CameraOverrideOption.Off;
            cameraData.requiresDepthOption = CameraOverrideOption.Off;
            cameraData.renderShadows = false;
            cameraData.SetRenderer(1);  // 根据 render list 的索引选择 render TODO

            var t = transform;
            var reflectionCamera = go.GetComponent<Camera>();
            reflectionCamera.transform.SetPositionAndRotation(transform.position, t.rotation);  // 相机初始位置设为当前 gameobject 位置
            reflectionCamera.depth = -10;  // 渲染优先级 [-100, 100]
            reflectionCamera.enabled = false;
            go.hideFlags = HideFlags.HideAndDontSave;

            return reflectionCamera;
        }
        
        class PlanarReflectionSettingData {
            private readonly bool _fog;
            private readonly int _maxLod;
            private readonly float _lodBias;
            private bool _invertCulling;

            public PlanarReflectionSettingData() {
                _fog = RenderSettings.fog;
                _maxLod = QualitySettings.maximumLODLevel;
                _lodBias = QualitySettings.lodBias;
            }

            public void Set() {
                _invertCulling = GL.invertCulling;
                GL.invertCulling = !_invertCulling;  // 因为镜像后绕序会反,将剔除反向
                RenderSettings.fog = false; // disable fog for now as it's incorrect with projection
                QualitySettings.maximumLODLevel = 1;
                QualitySettings.lodBias = _lodBias * 0.5f;
            }

            public void Restore() {
                GL.invertCulling = _invertCulling;
                RenderSettings.fog = _fog;
                QualitySettings.maximumLODLevel = _maxLod;
                QualitySettings.lodBias = _lodBias;
            }
        }
    }
}


URP 中没有 OnWillRenderObject 函数了,使用需要使用 C# 的 event 机制,订阅 beginCameraRendering 函数,脚本中的主函数是 runPlannarReflection ,保持与订阅事件相同函数签名。
UpdateReflectionCamera 是更新反射相机的函数,有两个实现,一种是不使用反射矩阵的,一种是使用的,两种方法切换时要切换 GL.invertCulling,因为使用反射矩阵会让相机的绕序反向。

参考

Unity Shader-反射效果(CubeMap,Reflection Probe,Planar Reflection,Screen Space Reflection)
破晓:Unity平面反射实现
http://www.terathon.com/lengyel/Lengyel-Oblique.pdf
Achieve beautiful, scalable, and performant graphics with the Universal Render Pipeline | Unity Blog
https://github.com/Unity-Technologies/BoatAttack

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-9-22 13:35 , Processed in 0.094298 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表