屏幕后期处理效果的基本原理就是当游戏画面渲染完毕后通过获取到该画面的信息进行额外的效果处理 之前的边缘检测、高斯模糊、Bloom、运动模糊等效果都是基于获取当前屏幕图像中的像素信息进行后期处理的
如果仅仅根据像素信息来进行一些效果处理,存在以下问题:
- 效果欠佳:比如实现边缘检测时,边缘检测信息受物体纹理和光照等因素影响,无法更准确的检测边缘,会得到一些我们不需要的边缘点
- 无法实现:比如我们想要实现一些景深效果(虚化背景),我们无法通过像素的颜色信息来判断离摄像机的远近
因此可以通过屏幕空间的深度纹理和法线纹理进行优化。
- 屏幕空间深度纹理:用于存储屏幕图像中每个像素深度信息的纹理,制作出 边缘检测、运动模糊、景深、环境遮挡 等等效果
- 屏幕空间法线纹理:用于存储屏幕图像中每个像素法线信息的纹理,制作出 边缘检测、运动模糊、景深、环境遮挡 等等效果
1、深度和法线纹理的使用
在Shader当中直接声明对应变量
- 深度纹理:sampler2D _CameraDepthTexture;
- 深度+法线纹理:sampler2D _CameraDepthNormalsTexture;(一般RG通道存储法线,BA通道存深度)之后直接在Shader中使用这两个变量便可以获取到相关信息
Shader中获取深度值:
Shader中获取法线信息:
2、深度纹理中存储的是什么信息
Unity中的深度纹理中存储的信息,也就是Shader中使用 _CameraDepthTexture 或_CameraDepthNormalsTexture 采样的信息,是进行裁剪空间变换后的 z 分量再转换到0~1之后的结果,因为齐次裁剪空间坐标范围为 -1 ~ 1,而纹理中存储的信息范围是 0 ~ 1,因此Unity会将其利用以下公式进行转换:深度纹理值 = 0.5 * z + 0.5
也就是说我们通过深度纹理直接采样得到的深度纹理值是是进行裁剪空间变换后的 z 分量再转换到0~1之后的结果
3、法线纹理中存储的是什么信息
Unity中的法线纹理中存储的信息,也就是Shader中使用_CameraDepthNormalsTexture采样得到的float4中的部分信息,它是观察空间下的 法线 再转换到0~1之后的结果,因为观察空间下的单位向量的分量取值范围是 -1~1,而纹理中存储的信息范围是 0 ~ 1,因此Unity会将其利用以下公式进行转换:法线纹理值 =(观察空间下法线 + 1)* 0.5【公式跟深度纹理值纠正是一样的,只不过要对x,y,z 都改变】
也就是说我们通过法线纹理直接采样得到的法线纹理值是是观察空间下的 法线 再转换到0~1之后的结果
4、Unity 如何得到深度和法线纹理的
Unity中深度和法线纹理一般通过两种途径获取
- 从G-buffer几何缓冲区中获取
- 由一个专门的Pass渲染而来
具体Unity是通过哪种方式获取,取决于使用的渲染路径和设备的硬件限制。
当使用延迟渲染路径时,深度和法线纹理可以直接访问到,因为延迟渲染路径会把信息存储到
G-buffer几何缓冲区中(深度和法线等信息都存储在其中)。
而当无法直接获取到深度和法线纹理时(比如硬件不支持延迟渲染路径 或 使用的是前向渲染路
径时),Unity会通过一个单独的Pass来进行渲染,获取深度和法线信息。
需要注意的是,当使用单独的Pass渲染获得深度和法线纹理时,两者是有区别的
- 对于深度纹理来说:
Unity内部会使用着色器替换技术选择渲染类型 RenderType =“Opaque” (不透明物体)
然后判断它们的渲染队列Queue是否小于等于2500(Background-1000、Geometry-2000、AlphaTest-2450)
如果满足这个条件,就会使用物体投射阴影时的Pass(LightMode 为 ShadowCaster 的Pass)
来得到深度纹理,如果没有这个Pass,那么该物体不会出现在深度纹理中!
因此这里的重点是,如果我们希望物体能够正确的出现在深度纹理中
- 必须在Shader中正确的设置RenderType标签
- 必须有投射阴影用的Pass(LightMode为ShadowCaster的Pass)
- 对于法线纹理来说:
Unity底层会使用一个单独的Pass把整个场景再次渲染一次,从而得到深度和法线信息
这里为什么是深度和法线信息呢,因为当需要得到法线纹理时,Unity中是和深度一起获取的
( _CameraDepthNormalsTexture )
这个Pass包含在Unity内置的Shader中,我们可以在官方下载源文件解压后进行查看
5、深度和法线纹理使用时调用的函数原理
- 深度
- 法线
SAMPLE_DEPTH_TEXTURE 宏:
它是用于从深度纹理中进行采样的宏,相比直接用tex2D进行采样,它在内部会帮助我们适配各
种不同的平台,因为不同平台对深度纹理的采样规则会有所不同。它采样得到的深度值是裁剪空间下的z分量转换到0~1之间的结果
通过SAMPLE_DEPTH_TEXTURE得到的深度值是非线性的,所谓的非线性值的是指在透视摄像机的裁剪空间中深度值分部不均匀
- 当深度值接近裁剪面近时,深度值变化迅速,精度高
- 当深度值接远裁剪面近时,深度值变化缓慢,精度低
更直观的解释:一个相机在观察一个3D场景时,近处的物体移动一点,视觉上变化很大,所以需
要更高的精度来记录这种变化。而远处的物体移动同样的距离,视觉上的变化很小,因此可以使
用较低的精度来记录
因此为了让我们在Shader中利用深度值进行的计算更加准确,我们需要获得线性的深度值,只需要把裁剪空间下的深度值转换到观察空间下,便可以得到线性的深度值
Unity Shader中提供了内置函数LinearEyeDepth 和 Linear01Depth 都可以得到观察空间下的线性深度值
- LinearEyeDepth:得到的是像素到摄像机的实际距离
- Linear01Depth:得到的是实际距离被压缩到0~1之间的值
DecodeDepthNormal函数内部其实也是执行的DecodeFloatRG和DecodeViewNormalStereo函数,它的作用就是得到观察空间下的对应像素的 法线 和 线性 深度值(0~1)
可以一次性的获得两个信息,也可以选择分别调用DecodeFloatRG和DecodeViewNormalStereo
单独获取深度和法线信息
函数中具体做的事情,就是利用法线的xy算出z,得到最终的法线信息;将裁剪空间下的非线性深度值 转换为观察空间下线性的范围为0~1的深度值
总结:直接采样出来的深度和法线信息是不会直接使用的,我们需要将他们通过内置函数进行转换
得到最终我们会使用的观察空间下的深度和法线信息
6、获取深度纹理
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DepthTexture : PostEffectBase
{
// Start is called before the first frame update
void Start()
{
Camera.main.depthTextureMode = DepthTextureMode.Depth;
}
}
Shader "ShaderProj/13/DepthTexture"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _CameraDepthTexture;
v2f vert (appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
fixed linearDepth = Linear01Depth(depth);
return fixed4(linearDepth, linearDepth, linearDepth, 1);
}
ENDCG
}
}
Fallback Off
}
7、获取法线纹理
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DepthNormalsTexture : PostEffectBase
{
// Start is called before the first frame update
void Start()
{
Camera.main.depthTextureMode = DepthTextureMode.DepthNormals;
}
}
Shader "ShaderProj/13/DepthNormalTexture"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _CameraDepthNormalsTexture;
v2f vert (appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float4 depthNormal = tex2D(_CameraDepthNormalsTexture, i.uv);
fixed depth;
fixed3 normals;
DecodeDepthNormal(depthNormal, depth, normals);
return fixed4(normals * 0.5 + 0.5, 1);
}
ENDCG
}
}
Fallback Off
}
因为是按照观察空间来计算的,所以法线展示出来的颜色按照摄像机的【Local】坐标(x轴:右边,红色;y轴:上边,绿色;z 轴:朝向反向向,蓝色)