Unity URP 平面反射

该代码实现了一个Unity脚本,用于在平面物体上创建反射效果。它使用额外的Camera进行渲染,反射纹理并应用到平面材质的ShaderGraph中。主要涉及的技术包括反射矩阵计算、RenderTexture和Camera设置。

https://www.bilibili.com/video/BV1TA4y1D7s4/?spm_id_from=333.999.0.0&vd_source=5672eeb8a9721da01495e313f0dff6c2https://zhuanlan.zhihu.com/p/493766119

PlannarReflectionTest.cs 挂载在平面对象上

// https://www.bilibili.com/video/BV1TA4y1D7s4/?spm_id_from=333.999.0.0&vd_source=5672eeb8a9721da01495e313f0dff6c2
// https://zhuanlan.zhihu.com/p/493766119
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class PlannarReflectionTest : MonoBehaviour {

    public LayerMask reflectMask = -1;

    [SerializeField] private MeshRenderer m_targetPlaneRenderer;
    [SerializeField] private int m_targetTextureWidth = 1024;
    [SerializeField] private string m_reflectTexName = "_MirrorReflectTex";
    [SerializeField] private float m_scale = 1.0f;
    [HideInInspector] private float m_clipPlaneOffset = 0.01f;

    private Camera m_cameraMain;
    private Transform m_cameraMainTransform;
    private Camera m_cameraReflection;
    private Transform m_cameraReflectionTransform;
    private RenderTexture m_targetTexture;
    private int m_reflectTexInt;
    private Vector3 m_planeNormal;
    private Vector3 m_planePosition;
    private Transform m_normalTransform;
    private Vector4 m_reflectionPlane;


    private void Awake() {
        m_cameraMain = Camera.main;
        m_cameraMainTransform = m_cameraMain.transform;

        var go = new GameObject("ReflectionCamera");
        m_cameraReflection = go.AddComponent<Camera>();
        m_cameraReflection.aspect = m_cameraMain.aspect;
        m_cameraReflection.fieldOfView = m_cameraMain.fieldOfView;
        m_cameraReflection.enabled = false;
        m_cameraReflection.depth = -10;
        //go.hideFlags = HideFlags.HideAndDontSave;

        var cameraData = go.AddComponent<UniversalAdditionalCameraData>();
        cameraData.requiresColorOption = CameraOverrideOption.Off;
        cameraData.requiresDepthOption = CameraOverrideOption.Off;
        cameraData.SetRenderer(0);

        m_cameraReflectionTransform = m_cameraReflection.transform;

        int newWidth = (int)(m_targetTextureWidth * m_scale);
        int newHeight = (int)(newWidth * ((float)Screen.height / Screen.width));
        m_targetTexture = new RenderTexture(newWidth, newHeight, 24);
        //m_targetTexture.antiAliasing = 4;
        m_targetTexture.format = RenderTextureFormat.ARGB32;
        m_cameraReflection.targetTexture = m_targetTexture;
        m_cameraReflection.cullingMask = reflectMask.value;

        m_normalTransform = new GameObject("Normal").transform;
        var planeTransform = m_targetPlaneRenderer.transform;
        m_normalTransform.SetPositionAndRotation(planeTransform.position, planeTransform.rotation);
        m_normalTransform.SetParent(planeTransform);
        m_planePosition = m_normalTransform.position;
        m_planeNormal = m_normalTransform.up;
        m_cameraReflection.transform.SetParent(m_normalTransform);

        m_reflectTexInt = Shader.PropertyToID(m_reflectTexName);
        Shader.SetGlobalTexture(m_reflectTexName, m_targetTexture);

    }

    private void OnEnable() {
        RenderPipelineManager.beginCameraRendering += OnBeginCameraRendering;
    }

    private void OnDisable() {
        RenderPipelineManager.beginCameraRendering -= OnBeginCameraRendering;
    }

    private static void CalculateReflectionMatrix(ref Matrix4x4 reflectionMat, ref 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;
    }

    private void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera) {
        if (camera.cameraType == CameraType.Reflection || camera.cameraType == CameraType.Preview) return;

        // 判断相机是不是在法线下方,如果在下方就不做渲染了
        Vector3 localPos = m_normalTransform.worldToLocalMatrix.MultiplyPoint3x4(m_cameraMainTransform.position);
        if (localPos.y < 0) return;

        // 调整位置
        // 首先计算反射矩阵
        // 法线
        Vector3 normal = m_normalTransform.up;
        // 平面上一个点的位置
        Vector3 pos = m_normalTransform.position;

        // 获取反射面
        float d = -Vector3.Dot(normal, pos) - m_clipPlaneOffset;
        m_reflectionPlane = new Vector4(normal.x, normal.y, normal.z, d);
        var reflection = Matrix4x4.identity;
        reflection *= Matrix4x4.Scale(new Vector3(1, -1, 1));
        // 计算反射矩阵
        CalculateReflectionMatrix(ref reflection, ref m_reflectionPlane);

        // 直接计算世界到相机矩阵
        Matrix4x4 worldToCameraMatrix = m_cameraMain.worldToCameraMatrix * reflection;
        m_cameraReflection.worldToCameraMatrix = worldToCameraMatrix;

        // m_cameraReflectionTransform.eulerAngles = m_cameraMainTransform.eulerAngles;
        // Vector3 localEuler = m_cameraReflectionTransform.localEulerAngles;
        // localEuler.x *= -1;
        // localEuler.z *= -1;
        // localEuler.y *= -1;
        // m_cameraReflectionTransform.localEulerAngles = localEuler;
        // m_cameraReflectionTransform.localPosition = localPos;

        // 计算相机空间下的斜切平面
        Vector3 offsetPos = pos + normal * m_clipPlaneOffset;
        Vector3 cpos = worldToCameraMatrix.MultiplyPoint3x4(offsetPos);
        Vector3 cnormal = worldToCameraMatrix.MultiplyVector(normal).normalized;
        // 通过斜切面算投影矩阵
        Vector4 clipPlane = new Vector4(cnormal.x, cnormal.y, cnormal.z, -Vector3.Dot(cpos, cnormal));
        m_cameraReflection.projectionMatrix = m_cameraMain.CalculateObliqueMatrix(clipPlane);
        //m_cameraReflection.projectionMatrix = CalculateObliqueMatrix(m_cameraMain, clipPlane);

        GL.invertCulling = true;
        UniversalRenderPipeline.RenderSingleCamera(context, m_cameraReflection);
        GL.invertCulling = false;
        Shader.SetGlobalTexture(m_reflectTexName, m_targetTexture);
    }

    // private Matrix4x4 CalculateObliqueMatrix(Camera cam, Vector4 clipPlane) {
    //     Matrix4x4 projectionMatrix= cam.projectionMatrix;
    //     Vector4 qClip = new Vector4(Mathf.Sign(clipPlane.x), Mathf.Sign(clipPlane.y), 1f, 1f);
    //     Vector4 qView = projectionMatrix.inverse.MultiplyPoint(qClip);
    //
    //     Vector4 scaledPlane = clipPlane * 2.0f / Vector4.Dot(clipPlane, qView);
    //    
    //     Vector4 m3 = scaledPlane - projectionMatrix.GetRow(3);
    //
    //     Matrix4x4 newM = projectionMatrix;
    //     newM.SetRow(2, m3);
    //     
    //     return newM;
    // }
}

平面对象使用的材质的ShaderGraph(Unity 2019.4.28f1c1):image

<think>我们正在处理一个Unity URP中实现模糊反射地面效果的问题。根据用户的问题,我们需要结合URP管线的特性来实现这个效果。模糊反射通常意味着反射不是完全清晰的,而是带有一定程度的模糊,这可以通过多种技术实现,例如使用模糊后的反射纹理或屏幕空间反射(SSR)结合模糊处理。 在URP中,实现反射效果常见的方法有: 1. 使用平面反射(Planar Reflection)技术,通常用于水面、地面等平坦表面的反射。 2. 使用屏幕空间反射(Screen Space Reflection, SSR),但URP内置没有提供SSR,需要自己实现或使用第三方解决方案。 3. 使用反射探针(Reflection Probe),但反射探针通常提供的是清晰反射,要实现模糊反射,需要对反射纹理进行后处理模糊。 由于用户要求的是模糊反射地面效果,我们可以考虑使用平面反射技术,然后对反射纹理进行模糊处理。在URP中,我们可以通过自定义Renderer Feature来实现。 步骤概述: 1. 创建一个用于渲染平面反射的摄像机,并将反射渲染到一张RT(Render Texture)上。 2. 对这张RT进行模糊处理(例如使用高斯模糊、Box模糊等)。 3. 在渲染地面时,将模糊后的反射纹理与地面材质进行混合。 具体实现步骤: ### 1. 创建反射摄像机 我们需要在代码中动态创建一个摄像机,用于渲染反射纹理。这个摄像机的位置和方向需要根据主摄像机和反射平面的位置来计算。 ### 2. 设置反射矩阵 为了得到正确的反射图像,我们需要将反射摄像机设置成关于反射平面对称的位置,并且使用反射平面的法线来调整摄像机的视图矩阵。 ### 3. 渲染反射纹理 将反射摄像机渲染的内容输出到一张RT上。 ### 4. 模糊反射纹理 使用一个后处理Pass(可以是一个全屏的Blit Pass)对反射纹理进行模糊处理。在URP中,我们可以通过自定义Renderer Feature来添加这个后处理步骤。 ### 5. 在地面材质中使用模糊后的反射纹理 在地面材质的Shader中,我们根据反射平面的位置和法线计算反射UV,然后采样模糊后的反射纹理,并与地面颜色混合。 ### 代码实现 #### 创建Renderer Feature 首先,我们需要创建一个Renderer Feature,用于渲染反射和模糊处理。 ```csharp using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; public class PlanarReflectionFeature : ScriptableRendererFeature { class PlanarReflectionPass : ScriptableRenderPass { // 省略具体实现:创建反射摄像机,渲染反射纹理,模糊处理等 } public override void Create() { // 初始化 } public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { // 添加Pass } } ``` #### 反射摄像机设置 在PlanarReflectionPass中,我们需要设置反射摄像机: ```csharp private Camera reflectionCamera; private RenderTexture reflectionRT; // 设置反射摄像机 private void SetupReflectionCamera(Camera mainCamera, Transform reflectionPlane) { if (reflectionCamera == null) { GameObject go = new GameObject("Reflection Camera"); reflectionCamera = go.AddComponent<Camera>(); reflectionCamera.CopyFrom(mainCamera); reflectionCamera.enabled = false; } // 计算反射矩阵 Vector3 normal = reflectionPlane.up; Vector3 pos = reflectionPlane.position; float d = -Vector3.Dot(normal, pos); Vector4 reflectionPlaneVector = new Vector4(normal.x, normal.y, normal.z, d); Matrix4x4 reflectionMatrix = Matrix4x4.identity; CalculateReflectionMatrix(ref reflectionMatrix, reflectionPlaneVector); // 设置反射相机的位置和方向 Vector3 newPos = reflectionMatrix.MultiplyPoint(mainCamera.transform.position); reflectionCamera.transform.position = newPos; Vector3 forward = reflectionMatrix.MultiplyVector(mainCamera.transform.forward); Vector3 up = reflectionMatrix.MultiplyVector(mainCamera.transform.up); reflectionCamera.transform.rotation = Quaternion.LookRotation(forward, up); // 设置反射相机的投影矩阵(可选,如果需要避免反射平面以下的内容被渲染,可以设置斜视锥体裁剪) reflectionCamera.worldToCameraMatrix = mainCamera.worldToCameraMatrix * reflectionMatrix; // 设置斜视锥体投影 Vector4 clipPlane = CameraSpacePlane(reflectionCamera, pos, normal, 1.0f); reflectionCamera.projectionMatrix = mainCamera.CalculateObliqueMatrix(clipPlane); } // 计算反射矩阵 private static void CalculateReflectionMatrix(ref Matrix4x4 reflectionMat, Vector4 plane) { // 具体实现可参考Unity官方文档或标准实现 } // 计算相机空间中的裁剪平面 private Vector4 CameraSpacePlane(Camera cam, Vector3 pos, Vector3 normal, float sideSign) { // 将点转换为相机空间 Vector3 offsetPos = pos + normal * 0.07f; // 避免深度冲突 Vector3 cpos = cam.worldToCameraMatrix.MultiplyPoint(offsetPos); Vector3 cnormal = cam.worldToCameraMatrix.MultiplyVector(normal).normalized * sideSign; return new Vector4(cnormal.x, cnormal.y, cnormal.z, -Vector3.Dot(cpos, cnormal)); } ``` #### 渲染反射纹理 在Execute方法中,我们渲染反射摄像机到RT: ```csharp public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { Camera mainCamera = renderingData.cameraData.camera; // 假设我们有一个反射平面(这里需要从场景中获取,比如通过Tag等) GameObject plane = GameObject.FindGameObjectWithTag("ReflectionPlane"); if (plane == null) return; SetupReflectionCamera(mainCamera, plane.transform); // 创建RT if (reflectionRT == null) { int width = Screen.width, height = Screen.height; reflectionRT = RenderTexture.GetTemporary(width, height, 24, RenderTextureFormat.DefaultHDR); } // 设置反射相机的目标纹理 reflectionCamera.targetTexture = reflectionRT; // 渲染反射相机 context.ExecuteCommandBuffer(Cmd); CommandBuffer cmd = CommandBufferPool.Get("Planar Reflection"); context.SetupCameraProperties(reflectionCamera); cmd.ClearRenderTarget(true, true, Color.clear); context.ExecuteCommandBuffer(cmd); cmd.Clear(); UniversalRenderPipeline.RenderSingleCamera(context, reflectionCamera); CommandBufferPool.Release(cmd); // 接下来,对reflectionRT进行模糊处理(可以使用另一个Pass进行模糊) BlurReflectionTexture(context, cmd, reflectionRT); } ``` #### 模糊处理 模糊处理可以使用高斯模糊或Box模糊。这里我们使用一个简单的双Pass模糊(先水平后垂直)。 ```csharp private void BlurReflectionTexture(ScriptableRenderContext context, CommandBuffer cmd, RenderTexture source) { // 创建临时RT RenderTexture tempRT = RenderTexture.GetTemporary(source.width, source.height, 0, source.format); // 使用材质进行模糊(材质使用一个包含两个Pass的Shader,第一个Pass水平模糊,第二个Pass垂直模糊) Material blurMaterial = ...; // 需要提前准备好模糊材质 // 水平模糊 cmd.Blit(source, tempRT, blurMaterial, 0); // 垂直模糊 cmd.Blit(tempRT, source, blurMaterial, 1); context.ExecuteCommandBuffer(cmd); cmd.Clear(); RenderTexture.ReleaseTemporary(tempRT); } ``` #### 地面材质Shader 在地面材质的Shader中,我们需要使用反射纹理。我们可以通过全局变量将反射纹理传递给Shader。 ```hlsl sampler2D _ReflectionTex; float4 _ReflectionPlane; // 平面方程 v2f vert (appdata v) { // 正常变换顶点 } fixed4 frag (v2f i) : SV_Target { // 计算反射UV(屏幕空间UV) float4 screenPos = ComputeScreenPos(i.pos); float2 screenUV = screenPos.xy / screenPos.w; // 采样反射纹理 fixed4 reflectionColor = tex2D(_ReflectionTex, screenUV); // 混合地面颜色和反射 fixed4 groundColor = ...; fixed4 col = lerp(groundColor, reflectionColor, _ReflectionIntensity); return col; } ``` ### 注意事项 1. 性能:渲染反射纹理需要额外的DrawCall,并且模糊处理也有一定的开销,因此需要谨慎使用。 2. 反射平面:上述实现假设场景中只有一个反射平面(比如地面),并且我们通过Tag来找到它。在实际应用中,可能需要更灵活的方式。 3. 避免递归反射:需要设置反射摄像机不渲染自身(避免递归反射),可以通过Layer来排除地面物体。 ### 相关引用 在实现过程中,我们可以参考以下技术: - 使用`CalculateObliqueMatrix`来设置斜视锥体投影,避免渲染反射平面以下的内容[^2]。 - 使用后处理模糊技术,类似于毛玻璃效果的实现[^1]。 ### 总结 在Unity URP中实现模糊反射地面效果,需要结合平面反射技术和后处理模糊。通过自定义Renderer Feature来渲染反射纹理并对其进行模糊,然后在地面材质中使用模糊后的反射纹理。这样就能实现带有模糊效果的地面反射。 §§相关问题§§ 1. 如何优化模糊反射地面的性能? 2. 除了平面反射,还有其他方法在URP中实现地面反射吗? 3. 如何根据距离调整反射的模糊程度?</think>### Unity URP 实现模糊反射地面效果 在 Unity URP 中实现模糊反射地面效果,需要结合**平面反射技术**和**后处理模糊**。以下是详细实现步骤: #### 1. **创建平面反射系统** ```csharp // 反射摄像机设置脚本 public class PlanarReflection : MonoBehaviour { public GameObject reflectionPlane; private Camera reflectionCamera; private RenderTexture reflectionRT; void Start() { // 创建反射摄像机 reflectionCamera = new GameObject("ReflectionCam").AddComponent<Camera>(); reflectionCamera.CopyFrom(Camera.main); reflectionCamera.enabled = false; // 创建反射RT reflectionRT = new RenderTexture(Screen.width, Screen.height, 24); reflectionCamera.targetTexture = reflectionRT; } void Update() { // 计算反射矩阵 Vector3 planeNormal = reflectionPlane.transform.up; Vector3 planePos = reflectionPlane.transform.position; float d = -Vector3.Dot(planeNormal, planePos); Vector4 reflectionPlaneVec = new Vector4(planeNormal.x, planeNormal.y, planeNormal.z, d); Matrix4x4 reflectionMatrix = Matrix4x4.identity; CalculateReflectionMatrix(ref reflectionMatrix, reflectionPlaneVec); // 设置反射相机位置 reflectionCamera.worldToCameraMatrix = Camera.main.worldToCameraMatrix * reflectionMatrix; // 设置斜视锥裁剪 Vector4 clipPlane = CameraSpacePlane(reflectionCamera, planePos, planeNormal); reflectionCamera.projectionMatrix = Camera.main.CalculateObliqueMatrix(clipPlane); // 渲染反射 reflectionCamera.Render(); } } ``` #### 2. **实现模糊后处理** ```csharp // Renderer Feature 模糊处理 public class BlurPass : ScriptableRenderPass { private Material blurMaterial; private RenderTargetIdentifier source; private int tmpId1, tmpId2; public BlurPass(Material blurMat) { blurMaterial = blurMat; } public override void Execute(ScriptableRenderContext context, ref RenderingData data) { CommandBuffer cmd = CommandBufferPool.Get("BlurEffect"); // 创建临时RT tmpId1 = Shader.PropertyToID("_TempRT1"); tmpId2 = Shader.PropertyToID("_TempRT2"); cmd.GetTemporaryRT(tmpId1, data.cameraData.cameraTargetDescriptor); cmd.GetTemporaryRT(tmpId2, data.cameraData.cameraTargetDescriptor); // 水平模糊 cmd.Blit(source, tmpId1, blurMaterial, 0); // 垂直模糊 cmd.Blit(tmpId1, tmpId2, blurMaterial, 1); // 回传结果 cmd.Blit(tmpId2, source); context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } } ``` #### 3. **地面材质 Shader** ```hlsl // 地面Shader关键部分 v2f vert(appdata v) { v2f o; o.pos = TransformObjectToHClip(v.vertex); o.screenPos = ComputeScreenPos(o.pos); // 计算反射UV float3 worldPos = TransformObjectToWorld(v.vertex); float3 viewDir = normalize(_WorldSpaceCameraPos - worldPos); float3 reflectDir = reflect(-viewDir, float3(0,1,0)); o.reflectionUV = reflectDir.xz; return o; } half4 frag(v2f i) : SV_Target { // 采样模糊反射纹理 half4 reflection = tex2D(_BlurredReflectionTex, i.reflectionUV); // 混合地面颜色和反射 half4 groundColor = tex2D(_MainTex, i.uv); return lerp(groundColor, reflection, _ReflectionIntensity); } ``` #### 4. **整合到 URP** 1. **创建 Renderer Feature**: - 在 URP Asset 中添加 `Render Objects` Feature - 设置 Layer 为反射平面层 - 使用自定义材质(包含模糊反射Shader) 2. **模糊参数控制**: ```csharp // 动态调整模糊强度 public void SetBlurIntensity(float intensity) { blurMaterial.SetFloat("_BlurSize", intensity); } ``` #### 优化建议 1. **性能优化**: - 降低反射纹理分辨率(如 512x512) - 使用 `RenderTextureFormat.DefaultHDR` 保留高光细节 - 添加距离衰减:`float fade = 1 - saturate(distance(_WorldSpaceCameraPos, worldPos)/_FadeDistance);` 2. **视觉增强**: - 添加法线扰动:`reflectionUV += UnpackNormal(tex2D(_BumpMap, i.uv)).xy * _Distortion;` - 菲涅尔效应:`float fresnel = pow(1 - saturate(dot(viewDir, float3(0,1,0))), _FresnelPower);` #### 技术要点 1. **斜视锥裁剪**:使用 `CalculateObliqueMatrix` 避免渲染平面下方的物体[^2] 2. **反射矩阵**:通过镜像变换计算反射摄像机位置[^1] 3. **双Pass模糊**:分离式模糊(水平+垂直)比单Pass模糊质量更高[^1] > 实现效果:地面将显示环境反射反射内容会根据模糊参数产生类似磨砂表面的扩散效果,适用于水面、大理石地面、冰面等场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值