第十四章 高级Unity渲染技术:深度图与法线贴图应用指南
14.1 理解与提取场景深度与法线信息
在现代游戏渲染技术中,深度图和法线图提供了关键的几何信息,这些信息可用于实现各种高级视觉效果。通过掌握这些基础要素,开发者可以构建从轮廓检测到全局光照等各种效果。
14.1.1 深度和法线渲染的理论基础
深度图是一种特殊的纹理,记录了场景中每个像素到摄像机的距离。这种信息对于实现许多后处理效果至关重要,如景深、边缘检测和屏幕空间环境光遮蔽(SSAO)。
法线图存储了场景中物体表面的法线方向,表示为RGB颜色值。这些法线信息在光照计算、边缘检测和许多图像空间效果中非常有用。
在Unity中,深度和法线信息通常通过以下方式获取:
- 深度缓冲区:摄像机在渲染过程中自动生成,可通过特定命令访问
- G-Buffer:在延迟渲染路径中,法线、位置等信息存储在G-Buffer中
- 自定义渲染通道:通过特殊着色器将深度或法线直接渲染到纹理中
深度值通常以非线性方式存储,以提高近距离物体的精度。要将这些值转换为实际距离,需要进行线性化处理:
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;