pic

效果概述

本文解析一个基于Unity URP(通用渲染管线)的平面反射系统。该系统通过动态创建反射相机矩阵变换计算反射斜裁剪矩阵优化等技术,在平面表面(如水面、镜面、地板)上生成高质量的实时反射效果,适用于水体渲染、镜子效果、光滑地面等场景。

在Built-in时代,平面反射几乎是唯一可选项,在URP下使用,也依然很稳定,效果也不错。

系统架构总览

text
主相机 → 反射相机创建 → 反射矩阵计算 → 斜裁剪矩阵优化 → 反射纹理渲染 → 全局纹理传递 → 着色器采样
    ↑           ↑               ↑                  ↑                  ↑               ↑
 FollowCamera CreateMirrorObjects CalculateReflectionMatrix CalculateObliqueMatrix RenderSingleCamera _PlanarReflectionTexture

核心技术原理

1. 反射相机动态管理

csharp
private Camera CreateMirrorObjects()
{
    var go = new GameObject("Planar Reflections", typeof(Camera));
    var cameraData = go.AddComponent(typeof(UniversalAdditionalCameraData)) 
                     as UniversalAdditionalCameraData;
    cameraData.requiresColorOption = CameraOverrideOption.Off;
    cameraData.requiresDepthOption = CameraOverrideOption.Off;
    cameraData.SetRenderer(2);  // 指定使用的Renderer索引
    
    var reflectionCamera = go.GetComponent<Camera>();
    reflectionCamera.enabled = false;  // 手动控制渲染
    return reflectionCamera;
}

设计要点

  • 动态创建:按需创建反射相机,避免始终占用资源

  • 手动控制:设置enabled=false,通过RenderSingleCamera手动触发渲染

  • Renderer指定:可指定特定的Renderer处理反射,与主相机分离

  • HideFlags设计(注释部分):可选择隐藏相机对象,避免场景污染

2. 反射矩阵计算

csharp
private static void CalculateReflectionMatrix(ref Matrix4x4 reflectionMat, Vector4 plane)
{
// 平面方程: plane = (normal.x, normal.y, normal.z, d)
// 其中 d = -Dot(normal, pointOnPlane)
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;

数学原理:反射矩阵是通过平面方程推导出的仿射变换矩阵,它将任意点映射到平面另一侧的对称点。矩阵公式:

 

R=I−2n⃗n⃗T

其中plane是归一化的平面法线,I是单位矩阵。

矩阵验证

  • 反射矩阵的两次应用等于恒等变换:R × R = I

  • 反射是等距变换,保持距离和角度

  • 行列式为-1,表示镜像变换

3. 反射相机位置计算

csharp
private void UpdateReflectionCamera(Camera realCamera)
{
    // 计算反射平面
    Vector3 pos = target.transform.position + Vector3.up * m_planeOffset;
    Vector3 normal = target.transform.up;
    var d = -Vector3.Dot(normal, pos) - m_settings.m_ClipPlaneOffset;
    var 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, reflectionPlane);
    
    // 计算反射后的相机位置和朝向
    var oldPosition = realCamera.transform.position;
    var newPosition = ReflectPosition(oldPosition);
    _reflectionCamera.transform.forward = Vector3.Scale(
        realCamera.transform.forward, new Vector3(1, -1, 1));
    
    // 组合视图矩阵
    _reflectionCamera.worldToCameraMatrix = realCamera.worldToCameraMatrix * reflection;
    _reflectionCamera.transform.position = newPosition;
}

变换步骤

  1. 定义反射平面:基于目标物体的位置和法线

  2. 构建反射矩阵:通过平面方程计算反射变换

  3. 反射相机位置:将主相机位置映射到平面对称点

  4. 反射相机朝向:Y轴反向,保持左右方向一致

  5. 组合视图矩阵:主相机视图矩阵 × 反射矩阵

4. 斜裁剪矩阵优化

csharp
// 计算相机空间的裁剪平面
var clipPlane = CameraSpacePlane(_reflectionCamera, pos - Vector3.up * 0.1f, normal, 1.0f);

// 使用斜裁剪矩阵
var projection = IsOrginNearClip ? 
    realCamera.projectionMatrix : 
    realCamera.CalculateObliqueMatrix(clipPlane);
    
_reflectionCamera.projectionMatrix = projection;

斜裁剪原理

  • 标准投影矩阵的近平面垂直于相机视线

  • 斜裁剪矩阵允许将近平面倾斜,使其与反射平面对齐

  • 这样可以精确地裁剪掉反射平面另一侧的物体,避免不必要的渲染

优势

  • 精确裁剪:只渲染反射平面以上的物体

  • 性能优化:减少overdraw,提高渲染效率

  • 避免 artifacts:防止反射平面以下的物体出现在反射中

5. 分辨率管理

csharp
public enum ResolutionMulltiplier
{
    Full,     // 1.0x
    Half,     // 0.5x
    Third,    // 0.33x
    Quarter   // 0.25x
}

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

分辨率策略

  • 主相机分辨率 × URP渲染缩放 × 分辨率系数

  • 提供多种分辨率选项,平衡画质和性能

  • 移动平台可使用Quarter,PC平台可使用Full/Half

6. 渲染管线集成

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

private void ExecutePlanarReflections(ScriptableRenderContext context, Camera camera)
{
    // 过滤条件
    if (camera.cameraType == CameraType.Reflection || 
        camera.cameraType == CameraType.Preview) return;
    
    // 创建反射相机和纹理
    UpdateReflectionCamera(camera);
    PlanarReflectionTexture(camera);
    
    // 临时质量设置
    var data = new PlanarReflectionSettingData();
    data.Set();
    
    // 触发回调并渲染
    BeginPlanarReflections?.Invoke(context, _reflectionCamera);
    UniversalRenderPipeline.RenderSingleCamera(context, _reflectionCamera);
    
    // 恢复质量设置
    data.Restore();
    
    // 传递纹理到全局Shader
    Shader.SetGlobalTexture(_planarReflectionTextureId, _reflectionTexture);
}

性能优化策略

1. 渲染质量临时调整

csharp
class PlanarReflectionSettingData
{
    public void Set()
    {
        GL.invertCulling = true;  // 反转背面剔除(因为反射)
        // 可选的LOD和雾效控制
    }
}

2. 纹理复用

csharp
private void PlanarReflectionTexture(Camera cam)
{
    if (_reflectionTexture == null)
    {
        var res = ReflectionResolution(cam, UniversalRenderPipeline.asset.renderScale);
        _reflectionTexture = RenderTexture.GetTemporary(res.x, res.y, 32, hdrFormat);
    }
    _reflectionCamera.targetTexture = _reflectionTexture;
}

3. 相机复用

  • 反射相机在启用时创建,禁用时销毁

  • 避免每帧创建新相机的开销

  • 使用对象池模式管理相机资源

参数调节指南

 
 
参数 作用 推荐值 性能影响
m_ResolutionMultiplier 反射纹理分辨率 Third/Half 高(分辨率每增加一倍,性能消耗×4)
m_ClipPlaneOffset 裁剪平面偏移 0.07f
m_ReflectLayers 反射图层 -1(所有图层) 中(图层越多,渲染物体越多)
m_Shadows 是否渲染阴影 false 高(阴影渲染开销大)
IsOrginNearClip 是否使用原近平面 false

应用场景

1. 水面反射

csharp
// 配置水面反射
target = waterPlane;
m_planeOffset = 0.0f;  // 平面位于水面
m_settings.m_ReflectLayers = LayerMask.GetMask("Default", "Terrain");
m_settings.m_Shadows = true;  // 水面需要阴影增强真实感

2. 镜子效果

csharp
// 镜子特殊配置
target = mirrorPlane;
m_planeOffset = 0.0f;
m_settings.m_ResolutionMultiplier = ResolutionMulltiplier.Full;  // 镜子需要高清晰度
IsOrginNearClip = true;  // 镜子不需要斜裁剪

3. 光滑地板

csharp
// 地板反射(模糊版本)
m_settings.m_ResolutionMultiplier = ResolutionMulltiplier.Quarter;
// 配合模糊Shader使用
Shader.SetGlobalFloat("_ReflectionBlur", 0.5f);

与其他反射技术对比

 
 
技术 真实感 性能 实现复杂度 适用场景
平面反射(本文) 水面、镜子、地板
反射探针 静态物体、室内
屏幕空间反射(SSR) 屏幕可见区域
光线追踪 极高 极低 极高 高端PC、主机

常见问题与解决方案

1. 反射闪烁

csharp
// 问题:反射相机和主相机深度冲突
// 解决:设置反射相机深度为负值
_reflectionCamera.depth = -1;

2. 反射裁剪错误

csharp
// 问题:反射平面附近的物体被错误裁剪
// 解决:调整裁剪平面偏移
var clipPlane = CameraSpacePlane(_reflectionCamera, 
    pos - Vector3.up * 0.1f, normal, 1.0f);  // 偏移平面位置

3. 反射错位

csharp
// 问题:反射图像位置不正确
// 检查平面偏移量
Vector3 pos = target.transform.position + Vector3.up * m_planeOffset;
// 确保m_planeOffset与目标物体的实际表面对齐

4. 性能问题

csharp
// 问题:反射渲染开销过大
// 解决方案:动态降级
if (isMobile)
{
    m_settings.m_ResolutionMultiplier = ResolutionMulltiplier.Quarter;
    m_settings.m_Shadows = false;
    m_settings.m_ReflectLayers &= ~LayerMask.GetMask("SmallObjects");
}

扩展优化思路

1. 动态分辨率

csharp
// 根据性能动态调整分辨率
float performanceScore = CalculatePerformanceScore();
if (performanceScore < 0.3f)
    m_settings.m_ResolutionMultiplier = ResolutionMulltiplier.Quarter;
else if (performanceScore < 0.6f)
    m_settings.m_ResolutionMultiplier = ResolutionMulltiplier.Third;

2. 反射模糊

hlsl
// 在Shader中对反射纹理进行模糊处理
float4 reflection = SAMPLE_TEXTURE2D(_PlanarReflectionTexture, sampler_PointClamp, uv);
reflection = GaussianBlur(reflection, uv, _BlurSize);

3. 反射强度控制

hlsl
// 基于视角和距离的反射衰减
float fresnel = dot(normal, viewDir);
float distanceFactor = saturate(1 - distance / _MaxReflectionDistance);
reflectionStrength *= fresnel * distanceFactor;

4. 多平面反射支持

csharp
// 管理多个反射平面
List<PlanarReflectionPlane> reflectionPlanes = new List<PlanarReflectionPlane>();
foreach(var plane in reflectionPlanes)
{
    RenderReflectionForPlane(plane);
}

总结

这个平面反射系统通过精妙的矩阵变换和渲染管线集成,实现了高质量的实时反射效果。其核心优势在于:

  • 数学精确性:严格的反射矩阵计算确保镜像准确性

  • 性能优化:斜裁剪矩阵、分辨率控制、临时质量调整

  • URP集成:无缝接入URP渲染管线,遵循现代渲染架构

  • 灵活配置:丰富的参数选项适应不同平台和场景需求

  • 资源管理:动态创建和销毁相机,避免资源浪费

该系统特别适合需要高质量平面反射的项目,如水体渲染、镜子效果、光滑地面等,通过合理的参数配置,可以在不同性能平台上取得良好的平衡。

© 版权声明
THE END
喜欢就支持一下吧
点赞15 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容