【转载】Unity 的深度纹理及应用

521 阅读4分钟

原文链接

版权声明:本文为CSDN博主「wodownload2」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:blog.csdn.net/wodownload2…

正文

摄像机和深度纹理

官方文档中有两个注意点

  • 必须要有使用了 ShadowCaster 通道的物体,才能最后加入到深度图中去(原因在文后也有解释)。
  • 只有是 不透明 的物体才会被写入到深度图中去。

原作中已经过时的技术部分我就不转载了,感兴趣的读者可以自行前往阅读

如何获取深度图

关于如何获取深度图有几种方法,这里我们先学习下使用 后处理 的方式。

得到如下的方法,是使用后处理的方式,直接读取 Unity 给我们提供的 _CameraDepthTexture 变量即可。具体如下:

首先是后处理的 脚本 ,用于 开启要使用深度图,提供源头给 shader:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[ExecuteInEditMode]
public class OpenDepthMode : MonoBehaviour
{
    private Material mat;
    public Shader shader;
    private void OnEnable()
    {
        GetComponent<Camera>().depthTextureMode |= DepthTextureMode.Depth;
    }

    private void OnDisable()
    {
        GetComponent<Camera>().depthTextureMode &= ~DepthTextureMode.Depth;
    }

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if(mat == null)
        {
            mat = new Material(shader);
        }
        if(mat != null)
        {
            Graphics.Blit(source, destination, mat);
        }
    }
}

shader 代码:

Shader "MyShader/RenderDepth"
{
	SubShader
	{
		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include"UnityCG.cginc"

			sampler2D _CameraDepthTexture;

			struct VertexData
			{
				float4 pos:POSITION;
				float2 uv:TEXCOORD0;
			};

			struct V2F
			{
				float4 pos:POSITION;
				float2 uv : TEXCOORD0;
			};

			V2F vert(VertexData v)
			{
				V2F res;
				res.pos = UnityObjectToClipPos(v.pos);
				res.uv = v.uv;
				return res;
			}

			float4 _Color;

			fixed4 frag(V2F v) :SV_Target
			{
				float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, 1 - v.uv);
				depth = Linear01Depth(depth);
				return float4(depth, depth, depth, 1);
			}
			ENDCG
		}
	}
}

可是结果是:
全白的屏幕。

解决方案

  • 一个是加入:fallback,就是要保证必须有 ShadowCaster
  • 二个是修改:摄像机的 clip planenearfar 不要让 far 太大了
  • 三个是注意 DX 上的 uv 反转:
if (_MainTex_TexelSize.y < 0) // 像素的 y 小于则说明进行了反转
	res.uv.y = 1 - res.uv.y;

参考网址

完整的代码如下:

Shader "MyShader/RenderDepth"
{
	SubShader
	{
		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include"UnityCG.cginc"

			sampler2D _CameraDepthTexture;

			struct VertexData
			{
				float4 pos:POSITION;
				float2 uv:TEXCOORD0;
			};

			struct V2F
			{
				float4 pos:POSITION;
				float2 uv : TEXCOORD0;
			};

			float4 _MainTex_TexelSize; //内置变量,四维数组(1/width, 1/height, width, height)

			V2F vert(VertexData v)
			{
				V2F res;
				res.pos = UnityObjectToClipPos(v.pos);
				res.uv = v.uv;
				if (_MainTex_TexelSize.y < 0) //像素的y小于则说明进行了反转
					res.uv.y = 1 - res.uv.y;
				return res;
			}

			float4 _Color;

			fixed4 frag(V2F v) :SV_Target
			{
				float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,  v.uv);
				depth = Linear01Depth(depth);
				return float4(depth, depth, depth, 1);
			}
			ENDCG
		}
	}
	Fallback "Diffuse" //这里不加Fallback也可以,加了之后,是说明使用此shader的物体,也会加入到深度图中去
}

最终结果为:
在这里插入图片描述

ok,至此深度的第一种显示方式完毕。

在这里插入图片描述

这两个物体使用的是,上面的 shader。其他的两个物体使用的是 默认的材质。默认的材质里有 fallback,故加入了深度图中。

深度纹理具体应用

Unity Shader 学习:相交算法实现简易防能量盾 | ZzEeRO

主要思路:对比物体和场景深度图在 观察空间 下的深度差值,深度差越小表示相交,颜色越深,再加上边缘光勾出轮廓。

分别是通过 ComputeScreenPos + COMPUTE_EYEDEPTHLinearEyeDepth 三个方法转换到观察空间

image.png

image.png

shader 部分:

Shader "Unlit/DepthOutline"
{
	Properties{
		_MainTex("MainTex",2D) = "white"{}
		_RimFactor("RimFactor",Range(0.0,5.0))=1.0
		_DistanceFactor("DistanceFactor",Range(0.0,10.0))=1.0
		_RimColor("RimColor",Color)=(1,0,0,1)
		_DistanceFactor2("DistanceFactor2",Range(0.0,10.0))=1.0
		_DistanceFactor3("DistanceFactor3",Range(0.0,5.0)) = 1.0
	}
		SubShader{
			Tags{"Queue" = "Transparent" "RenderType" = "Transparent" "IgnoreProjector"="true"}
			Pass{
				Blend SrcAlpha OneMinusSrcAlpha
				ZWrite Off
				Cull Off
				CGPROGRAM
				#include "UnityCG.cginc"
				#pragma vertex vert
				#pragma fragment frag

				sampler2D _MainTex;
				float4 _MainTex_ST;
				sampler2D _CameraDepthTexture;
				float _RimFactor;
				float _DistanceFactor;
				float4 _RimColor;
				float _DistanceFactor2;
				float _DistanceFactor3;
				
				struct a2v {
					float4 vertex:POSITION;
					float2 uv:TEXCOORD0;
					float3 normal:NORMAL;
				};
			
				struct v2f {
					float2 uv:TEXCOORD0;
					float4 pos:SV_POSITION;
					float4 screenPos:TEXCOORD1;
					float3 worldNormal:TEXCOORD2;
					float3 worldViewDir:TEXCOORD3;
				};

				v2f vert(a2v v) {
					v2f o;
					o.pos = UnityObjectToClipPos(v.vertex);
                                        
					/// @note ComputeScreenPos 函数,得到归一化前的视口坐标 xy
					/// z 分量为裁剪空间的 z 值,范围 [-Near,Far]
					o.screenPos = ComputeScreenPos(o.pos);
					o.uv = TRANSFORM_TEX(v.uv,_MainTex);
					/// @note COMPUTE_EYEDEPTH 函数,将 z 分量范围 [-Near,Far] 转换为 [Near,Far]
					COMPUTE_EYEDEPTH(o.screenPos.z);
                                        
					o.worldNormal = UnityObjectToWorldNormal(v.normal);
					o.worldViewDir = WorldSpaceViewDir(v.vertex).xyz;
					return o;
				}

				float4 frag(v2f i):SV_Target {
					float3 mainTex = 1-tex2D(_MainTex,i.uv).xyz;
                                        
					///@note 获取深度纹理, 通过 LinearEyeDepth 函数将采样的深度纹理值转换为对应的深度范围 [Near~Far]
					float sceneZ = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos)));
					// 观察空间深度差, 值越小颜色值越大
					float distance =1 - saturate(sceneZ - i.screenPos.z);
                                        
					// 消除内部深度变化较大时产生的锯齿
					if (distance>0.999999)
					{
						distance = 0;
					}
					// 调整深度差值的变化曲线
					distance = pow(saturate(_DistanceFactor * log(distance) + _DistanceFactor3), _DistanceFactor2);

					// 角度越大边缘光越亮
					float rim =1 - abs(dot(normalize(i.worldNormal), normalize(i.worldViewDir)));
					rim = pow(rim, _RimFactor);
					float4 col = float4(0,0,0,0);
					col = lerp(col, float4(_RimColor.rgb,0.3), mainTex.r);
					// 根据边缘光以及深度差渐变
					col = float4(_RimColor.rgb,lerp(col.a,_RimColor.a, distance));
					col = lerp(col, _RimColor, rim);
					return col;
				}

				ENDCG
		}
	}
}

深度+法线纹理

相机可以生成 深度深度法线 纹理,以及 运动向量 纹理(需要 Unity5.4 以上)。我们可以使用这些纹理来实现屏幕后处理效果。

当你需要某种纹理的时候,可以为 CameradepthTextureMode 和以下变量或(|)运算:

  • DepthTextureMode.Depth 深度 纹理
  • DepthTextureMode.DepthNormals 一个包含 深度视图空间 法线 的纹理

例如:代码与第一节《如何获取深度图》 十分类似

单独获取深度纹理

using UnityEngine;
using System.Collections;
 
[RequireComponent(typeof(Camera))]
public class TestDepth : MonoBehaviour {
 
	private Material _mat;
 
	// Use this for initialization
	void Start () {
		Camera cam = GetComponent<Camera> ();
		cam.depthTextureMode |= DepthTextureMode.Depth;
		Shader shader = Shader.Find ("Custom/TestDepth"); // 找到指定 Shader
		if (null != shader) {
			_mat = new Material (shader);
		}
	}
 
	void OnRenderImage(RenderTexture source, RenderTexture destination)
	{
		if (null != _mat) {
			Graphics.Blit (source, destination, _mat);
		} else {
			Graphics.Blit (source, destination);
		}
	}
}

深度会被保存在 _CameraDepthTexture 中,可以通过 uv 坐标来获取贴图中的值,然后使用UNITY_SAMPLE_DEPTH 宏来转换为深度。

float2 uv = i.uv;
float depth = UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, uv));

需要 注意 的是,深度纹理使用了与阴影投射(shadow caster)相同的 Shader pass,所以如果一个对象 不支持 阴影投射,那么它将不会出现在深度纹理当中,并且只有 RenderQueue 小于等于 2500 的对象才能被渲染到深度纹理当中去。

  • Background=1000
  • Geometry=2000(默认)
  • AlphaTest=2450
  • Transparent=3000

同时获取深度和法线的纹理

而使用 DepthTextureMode.DepthNormals 时,我们可以获取 深度法线 纹理: _CameraDepthNormalTexture

  • Unity 将 法线 编码到 RG 通道里,
  • 深度 编码到 BA 通道里。

我们可以通过 DecodeDepthNormal 方法获取 深度法线

float2 uv = i.uv;
half depth;
half3 norm;
DecodeDepthNormal(tex2D(_CameraDepthNormalTexture, uv), depth, norm);