深度纹理
深度纹理实际就是一张渲染纹理,只不过它里边存储的是像素值不是颜色值,而是一个高精度的深度值。
顶点坐标转化到NDC(归一化的设备坐标)下的坐标的z分量就是顶点的深度值。NDC中,z分量范围在[-1,1],为了让z分量可以存到一张纹理中,需要使用公式将z分量映射:
d = 0.5 * + 0.5
Unity获取深度纹理方式分为两种方式:
在Unity中,可以让摄像机生成一张深度纹理或是深度+法线纹理。
1.当只生成一张深度纹理时,Unity在使用延迟渲染路径时(渲染路径决定了光照如何应用到Shader中,决定了把光源信息和处理后的光照信息放到那些数据中。),直接从G-buffer中获取深度缓存。
2.在使用前向渲染路劲时,渲染深度纹理通过使用着色器替换技术,渲染需要的不透明物体,并使用它投射阴影时使用的Pass(即LightMode 设置为ShadowCaster 的Pass)来得到深度纹理。具体实现是,Unity会使用着色器替换技术渲染那些渲染类型(即SubShader的RenderType标签)为Opaque的物体,判断它们使用的渲染队列是否 <= 2500,如果满足,就把它渲染到深度和法线纹理中。
深度纹理的精度通常是24位活16位。如果选择生成一张深度+法线纹理,Unity会创建一张和屏幕分辨率相同,精度为32位(每个通道8位)的纹理。法线信息存到R和G通道。深度信息存到B和A通道。
- 使用延迟渲染路径时:Unity只需要合并深度和法线缓存即可。
- 使用前向渲染路劲时:默认不会创建法线缓存。因此Unity底层使用了一个单独的Pass把整个场景再次渲染一遍。这个Pass被放在一个内置Shader中。
代码具体获取深度纹理步骤:
- camera.depehtTextureMode = DepthTextureMode.Depth; //获取深度纹理
- Shader中直接访问特定纹理名称(_CameraDepthTexture)即可。
camera.depehtTextureMode = DepthTextureMode.DepthNormals;//获取深度纹理 + 法线纹理
camera.depehtTextureMode |= DepthTextureMode.Depth;
camera.depehtTextureMode = DepthTextureMode.DepthNormals; 以上两句获取一张深度纹理 + 一张深度+法线纹理
采样深度纹理,非线性变线性推导。。。以后补上。。
当摄像机远裁剪平面的距离过大,会导致距离摄像机较近的物体被映射的深度值非常小,生成的深度纹理变"黑"。相反,远裁剪平面过大,会导致距离摄像机较近的物体被映射的深度值偏大。生成的深度纹理变"白"。
实践:
1.运动模糊
速度映射图模拟运动模糊。速度映射图中存储了每个像素的速度,然后使用这个速度决定模糊的方向和大小。
生成速度映射纹理有两种方式:
- 把场景中所有物体都渲染到一张纹理中,不过这种方法需要修改场景所有的Shader。
- 使用深度纹理在片元着色器中为每个像素计算世界空间下的位置(使用当前帧的视角*投影矩阵的逆矩阵计算)。当得到世界空间下的顶点坐标后,然后使用前一帧视角*投影矩阵对其进行变换,得到该顶点位置在前一帧的NDC坐标。当前帧顶点的NDC坐标 - 前一帧的NDC位置得到该像素的速度。优点是屏幕后统一处理计算,缺点是两次矩阵变换,效率低。
Shader "Chan/Chapter 13/Motion Blur With Depth Texture" {
Properties
{
_MainTex("Base Tex",2D) = "white"{}
_BlurSize("Blur Size",float) = 1.0
}
SubShader
{
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
half4 _MainTex_TexelSize;
//通过_CameraDepthTexture 直接就能访问到当前摄像机的深度纹理
sampler2D _CameraDepthTexture;
float4x4 _CurrentViewProjectionInverseMatrix;
float4x4 _PreviousViewProjectionMatrix;
half _BlurSize;
struct v2f
{
float4 pos:SV_POSITION;
half2 uv:TEXCOORD0;
half2 uv_depth:TEXCOORD1;
};
v2f vert(appdata_img v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
o.uv_depth = v.texcoord;
//多张渲染纹理,可能uv采样坐标会出问题 需要根据平台判断
# if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
o.uv_depth.y = 1 - o.uv_depth.y;
#endif
return o;
}
fixed4 frag(v2f i) :SV_Target
{
//不同平台要通过uv采样深度纹理得到深度值,可能有差异。使用宏定义 内部处理了。SAMPLE_DEPTH_TEXTURE
float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv_depth);
//归一化设备坐标系下的坐标[-1,1],纹理通道值[0,1] 所以存储到深度纹理时,进行了映射。此处为反映射
//归一化设备坐标系下的坐标坐标(x,y值由uv映射而来,z值由深度值映射而来)
float4 h = float4(i.uv.x * 2 - 1,i.uv.y * 2 - 1,d * 2 - 1,1);
//归一化设备坐标系下的坐标 乘以当前摄像机 * 投影矩阵的逆矩阵 得到世界空间坐标系的坐标
float4 D = mul(_CurrentViewProjectionInverseMatrix,h);
//世界空间坐标系的坐标
float4 worldPos = D / D.w;
//当前世界坐标乘以 当前摄像机视角 * 投影矩阵 得到上一帧时候在归一化设备坐标系下的坐标
float4 currentPos = h;
float4 previousPos = mul(_PreviousViewProjectionMatrix,worldPos);
previousPos /= previousPos.w;
//得到速度
float2 velocity = (currentPos.xy - previousPos.xy) / 2.0f;
float2 uv = i.uv;
float4 c = tex2D(_MainTex,uv);
uv += velocity * _BlurSize;//控制纹理采样距离
//采样三地方,取