第十四章 高级Unity渲染技术:深度图与法线贴图应用指南

第十四章 高级Unity渲染技术:深度图与法线贴图应用指南

14.1 理解与提取场景深度与法线信息

在现代游戏渲染技术中,深度图和法线图提供了关键的几何信息,这些信息可用于实现各种高级视觉效果。通过掌握这些基础要素,开发者可以构建从轮廓检测到全局光照等各种效果。

14.1.1 深度和法线渲染的理论基础

深度图是一种特殊的纹理,记录了场景中每个像素到摄像机的距离。这种信息对于实现许多后处理效果至关重要,如景深、边缘检测和屏幕空间环境光遮蔽(SSAO)。

法线图存储了场景中物体表面的法线方向,表示为RGB颜色值。这些法线信息在光照计算、边缘检测和许多图像空间效果中非常有用。

在Unity中,深度和法线信息通常通过以下方式获取:

  1. 深度缓冲区:摄像机在渲染过程中自动生成,可通过特定命令访问
  2. G-Buffer:在延迟渲染路径中,法线、位置等信息存储在G-Buffer中
  3. 自定义渲染通道:通过特殊着色器将深度或法线直接渲染到纹理中

深度值通常以非线性方式存储,以提高近距离物体的精度。要将这些值转换为实际距离,需要进行线性化处理:

glsl

// 将非线性深度值转换为线性深度
float LinearizeDepth(float depth)
{
    float near = _ProjectionParams.y;
    float far = _ProjectionParams.z;
    float z = depth * 2.0 - 1.0; // 从[0,1]转换到[-1,1]
    return (2.0 * near * far) / (far + near - z * (far - near));
}

14.1.2 获取深度和法线的实现方法

在Unity中,有几种方法可以获取深度和法线信息。以下是主要的实现方式:

方法1:通过相机深度纹理

Unity可以自动生成深度纹理,这是最直接的方法:

csharp

using UnityEngine;

public class DepthTextureExample : MonoBehaviour
{
    private Camera mainCamera;
    
    void Start()
    {
        mainCamera = GetComponent<Camera>();
        
        // 启用深度纹理
        mainCamera.depthTextureMode = DepthTextureMode.Depth;
    }
}

相应的着色器代码可以这样访问深度纹理:

glsl

Shader "Custom/DepthVisualizer"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _DepthPower ("Depth Visualization Power", Range(0.1, 5.0)) = 1.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float4 screenPos : TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _CameraDepthTexture;
            float _DepthPower;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.screenPos = ComputeScreenPos(o.vertex);
                return o;
            }
            
            float LinearizeDepth(float z)
            {
                float near = _ProjectionParams.y;
                float far = _ProjectionParams.z;
                return (2.0 * near) / (far + near - z * (far - near));
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                float2 uv = i.screenPos.xy / i.screenPos.w;
                
                // 采样深度纹理
                float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv);
                
                // 将深度转换为线性空间
                float linearDepth = LinearizeDepth(depth);
                
                // 可视化深度值
                linearDepth = pow(linearDepth, _DepthPower);
                
                return fixed4(linearDepth, linearDepth, linearDepth, 1);
            }
            ENDCG
        }
    }
}
方法2:同时获取深度和法线

如果同时需要深度和法线信息,可以设置相机启用这两种纹理:

csharp

using UnityEngine;

public class DepthNormalTextureExample : MonoBehaviour
{
    private Camera mainCamera;
    
    void Start()
    {
        mainCamera = GetComponent<Camera>();
        
        // 同时启用深度和法线纹理
        mainCamera.depthTextureMode = DepthTextureMode.DepthNormals;
    }
}

对应的着色器代码可以同时访问深度和法线:

glsl

Shader "Custom/DepthNormalVisualizer"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _DepthPower ("Depth Visualization Power", Range(0.1, 5.0)) = 1.0
        [Toggle] _ShowDepth ("Show Depth", Float) = 1
        [Toggle] _ShowNormals ("Show Normals", Float) = 0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile _ _SHOWDEPTH_ON
            #pragma multi_compile _ _SHOWNORMALS_ON
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float4 screenPos : TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _CameraDepthNormalsTexture;
            float _DepthPower;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.screenPos = ComputeScreenPos(o.vertex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                float2 uv = i.screenPos.xy / i.screenPos.w;
                
                // 从组合纹理中解码深度和法线
                float3 normalValues;
                float depth;
                DecodeDepthNormal(tex2D(_CameraDepthNormalsTexture, uv), depth, normalValues);
                
                // 应用深度可视化增强
                depth = pow(depth, _DepthPower);
                
                // 将法线从[-1,1]映射到[0,1]范围以便可视化
                float3 normalColor = normalValues * 0.5 + 0.5;
                
                // 根据显示选项返回结果
                #if defined(_SHOWDEPTH_ON) && defined(_SHOWNORMALS_ON)
                    // 在左侧显示深度,右侧显示法线
                    if (uv.x < 0.5)
                        return fixed4(depth, depth, depth, 1);
                    else
                        return fixed4(normalColor, 1);
                #elif defined(_SHOWDEPTH_ON)
                    return fixed4(depth, depth, depth, 1);
                #elif defined(_SHOWNORMALS_ON)
                    return fixed4(normalColor, 1);
                #else
                    // 默认情况下显示原始纹理
                    return tex2D(_MainTex, i.uv);
                #endif
            }
            ENDCG
        }
    }
}
方法3:通过命令缓冲区自定义深度法线渲染

对于需要更多控制的情况,可以使用命令缓冲区实现自定义的深度和法线渲染:

csharp

using UnityEngine;
using UnityEngine.Rendering;

public class CustomDepthNormalRenderer : MonoBehaviour
{
    public RenderTexture depthTexture;
    public RenderTexture normalTexture;
    public Shader depthShader;
    public Shader normalShader;
    
    private Camera mainCamera;
    private CommandBuffer depthCommand;
    private CommandBuffer normalCommand;
    private Material depthMaterial;
    private Material normalMaterial;
    
    void Start()
    {
        mainCamera = GetComponent<Camera>();
        
        // 创建渲染纹理
        CreateRenderTextures();
        
        // 创建材质
        if (depthShader != null)
            depthMaterial = new Material(depthShader);
        
        if (normalShader != null)
            normalMaterial = new Material(normalShader);
        
        // 设置命令缓冲区
        SetupCommandBuffers();
    }
    
    void CreateRenderTextures()
    {
        // 创建深度纹理
        if (depthTexture == null)
        {
            depthTexture = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.RFloat);
            depthTexture.filterMode = FilterMode.Bilinear;
            depthTexture.wrapMode = TextureWrapMode.Clamp;
            depthTexture.name = "CustomDepthTexture";
        }
        
        // 创建法线纹理
        if (normalTexture == null)
        {
            normalTexture = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.ARGBFloat);
            normalTexture.filterMode = FilterMode.Bilinear;
            normalTexture.wrapMode = TextureWrapMode.Clamp;
            normalTexture.name = "CustomNormalTexture";
        }
    }
    
    void SetupCommandBuffers()
    {
        // 创建并设置深度命令缓冲区
        if (depthCommand != null)
            mainCamera.RemoveCommandBuffer(CameraEvent.BeforeImageEffects, depthCommand);
            
        if (depthMaterial != null)
        {
            depthCommand = new CommandBuffer();
            depthCommand.name = "Custom Depth Pass";
            depthCommand.Blit(BuiltinRenderTextureType.CameraTarget, depthTexture, depthMaterial);
            mainCamera.AddCommandBuffer(CameraEvent.BeforeImageEffects, depthCommand);
        }
        
        // 创建并设置法线命令缓冲区
        if (normalCommand != null)
            mainCamera.RemoveCommandBuffer(CameraEvent.BeforeImageEffects, normalCommand);
            
        if (normalMaterial != null)
        {
            normalCommand = new CommandBuffer();
            normalCommand.name = "Custom Normal Pass";
            normalCommand.Blit(BuiltinRenderTextureType.CameraTarget, normalTexture, normalMaterial);
            mainCamera.AddCommandBuffer(CameraEvent.BeforeImageEffects, normalCommand);
        }
    }
    
    void OnDestroy()
    {
        // 清理资源
        if (depthCommand != null)
            mainCamera.RemoveCommandBuffer(CameraEvent.BeforeImageEffects, depthCommand);
            
        if (normalCommand != null)
            mainCamera.RemoveCommandBuffer(CameraEvent.BeforeImageEffects, normalCommand);
            
        if (depthTexture != null)
            depthTexture.Release();
            
        if (normalTexture != null)
            normalTexture.Release();
            
        if (depthMaterial != null)
            Destroy(depthMaterial);
            
        if (normalMaterial != null)
            Destroy(normalMaterial);
    }
}

自定义深度着色器示例:

glsl

Shader "Custom/DepthOnly"
{
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float depth : TEXCOORD0;
            };
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.depth = COMPUTE_DEPTH_01;
                return o;
            }
            
            float4 frag (v2f i) : SV_Target
            {
                // 直接输出深度值
                return float4(i.depth, i.depth, i.depth, 1);
            }
            ENDCG
        }
    }
}

自定义法线着色器示例:

glsl

Shader "Custom/NormalOnly"
{
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
            };
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                return o;
            }
            
            float4 frag (v2f i) : SV_Target
            {
                // 将法线范围从[-1,1]转换为[0,1]以便存储
                float3 normalColor = i.worldNormal * 0.5 + 0.5;
                return float4(normalColor, 1);
            }
            ENDCG
        }
    }
}

14.1.3 可视化和分析深度与法线信息

为了开发和调试基于深度和法线的效果,有效的可视化工具非常重要。以下是一个可视化器工具,允许开发者在运行时查看和分析深度和法线数据:

csharp

using UnityEngine;
using UnityEngine.UI;

public class DepthNormalVisualizer : MonoBehaviour
{
    public enum DisplayMode { Original, Depth, Normals, SplitView }
    
    [Header("Visualization Settings")]
    public DisplayMode displayMode = DisplayMode.Original;
    [Range(0.1f, 5.0f)]
    public float depthScale = 1.0f;
    public bool useColorGradient = true;
    public Gradient depthGradient;
    
    [Header("UI Elements")]
    public RawImage displayImage;
    public Text infoText;
    
    [Header("Render Textures")]
    public RenderTexture originalRT;
    public RenderTexture depthRT;
    public RenderTexture normalRT;
    
    // 可视化材质
    private Material visualizerMaterial;
    private Camera mainCamera;
    
    void Start()
    {
        mainCamera = Camera.main;
        if (mainCamera == null)
            mainCamera = GetComponent<Camera>();
            
        // 确保相机支持深度和法线纹理
        if (mainCamera != null)
            mainCamera.depthTextureMode = DepthTextureMode.DepthNormals;
            
        // 创建可视化材质
        visualizerMaterial = new Material(Shader.Find("Hidden/DepthNormalVisualizer"));
        
        // 创建渲染纹理
        CreateRenderTextures();
        
        // 初始化UI
        if (infoText != null)
            UpdateInfoText();
    }
    
    void CreateRenderTextures()
    {
        int width = Screen.width;
        int height = Screen.height;
        
        if (originalRT == null)
        {
            originalRT = new RenderTexture(width, height, 0);
            originalRT.name = "OriginalRT";
        }
        
        if (depthRT == null)
        {
            depthRT = new RenderTexture(width, height, 0, RenderTextureFormat.RFloat);
            depthRT.name = "DepthRT";
        }
        
        if (normalRT == null)
        {
            normalRT = new RenderTexture(width, height, 0, RenderTextureFormat.ARGBFloat);
            normalRT.name = "NormalRT";
        }
    }
    
    void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (visualizerMaterial == null)
        {
            Graphics.Blit(source, destination);
            return;
        }
        
        // 拷贝原始图像
        Graphics.Blit(source, originalRT);
        
        // 设置材质属性
        visualizerMaterial.SetFloat("_DepthScale", depthScale);
        visualizerMaterial.SetInt("_UseColorGradient", useColorGradient ? 1 : 0);
        
        if (useColorGradient && depthGradient != null)
        {
            // 创建渐变纹理
            Texture2D gradientTex = new Texture2D(256, 1);
            for (int i = 0; i < 256; i++)
            {
                gradientTex.SetPixel(i, 0, depthGradient.Evaluate(i / 255f));
            }
            gradientTex.Apply();
            visualizerMaterial.SetTexture("_GradientTex", gradientTex);
        }
        
        // 根据显示模式处理图像
        switch (displayMode)
        {
            case DisplayMode.Original:
                Graphics.Blit(source, destination);
                break;
                
            case DisplayMode.Depth:
                visualizerMaterial.SetInt("_DisplayMode", 1);
                Graphics.Blit(source, depthRT, visualizerMaterial);
                Graphics.Blit(depthRT, destination);
                break;
                
            case DisplayMode.Normals:
                visualizerMaterial.SetInt("_DisplayMode", 2);
                Graphics.Blit(source, normalRT, visualizerMaterial);
                Graphics.Blit(normalRT, destination);
                break;
                
            case DisplayMode.SplitView:
                visualizerMaterial.SetInt("_DisplayMode", 3);
                visualizerMaterial.SetTexture("_OriginalTex", originalRT);
                Graphics.Blit(source, destination, visualizerMaterial);
                break;
        }
        
        // 更新UI显示
        if (displayImage != null)
        {
            switch (displayMode)
            {
                case DisplayMode.Original:
                    displayImage.texture = originalRT;
                    break;
                case DisplayMode.Depth:
                    displayImage.texture = depthRT;
                    break;
                case DisplayMode.Normals:
                    displayImage.texture = normalRT;
                    break;
                case DisplayMode.SplitView:
                    displayImage.texture = destination;
                    break;
            }
        }
    }
    
    public void ChangeDisplayMode(int mode)
    {
        displayMode = (DisplayMode)mode;
        UpdateInfoText();
    }
    
    void UpdateInfoText()
    {
        if (infoText == null)
            return;
            
        switch (displayMode)
        {
            case DisplayMode.Original:
                infoText.text = "原始场景图像";
                break;
            case DisplayMode.Depth:
                infoText.text = "深度图 (亮 = 远, 暗 = 近)";
                break;
            case DisplayMode.Normals:
                infoText.text = "法线图 (RGB = 方向)";
                break;
            case DisplayMode.SplitView:
                infoText.text = "分屏视图 (左: 原始, 右: 深度/法线)";
                break;
        }
    }
    
    void OnDestroy()
    {
        // 释放资源
        if (visualizerMaterial != null)
            Destroy(visualizerMaterial);
            
        if (originalRT != null)
            originalRT.Release();
            
        if (depthRT != null)
            depthRT.Release();
            
        if (normalRT != null)
            normalRT.Release();
    }
}

可视化着色器:

glsl

Shader "Hidden/DepthNormalVisualizer"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _OriginalTex ("Original Texture", 2D) = "white" {}
        _DepthScale ("Depth Scale", Range(0.1, 5.0)) = 1.0
        _GradientTex ("Gradient Texture", 2D) = "white" {}
    }
    SubShader
    {
        // 不需要剔除或深度测试
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float4 screenPos : TEXCOORD1;
            };

            sampler2D _MainTex;
            sampler2D _OriginalTex;
            sampler2D _CameraDepthNormalsTexture;
            sampler2D _GradientTex;
            float _DepthScale;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小宝哥Code

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值