unity深度及应用
获得深度我们知道,如果想要获取名为MainCamera的主相机且Tag为MainCamera的深度图
只需要在脚本里:
private void Start()
{
Camera cam = GameObject.Find(&#34;Main Camera&#34;).GetComponent<Camera>() ;
//申请深度图,然后unity会给一个全局变量传值,这个全局变量叫_CameraDepthTexture
cam.depthTextureMode = DepthTextureMode.Depth;//|=也行,因为unity除了生成深度之外,还有法线
//或者直接:
//Camera.main.depthTextureMode = DepthTextureMode.Depth;
}
这样Camera在渲染时会增加一个前置pass,将所有的opaque物体渲染一次,将深度保存到内置的一张深度图中。在渲染物体时,shader内通过sampler2D _CameraDepthTexture可以访问这张深度贴图。将脚本挂载到相机,点击运行在TargetEye下会有提示:
全局变量_CameraDepthTexture,shader中,不在properties中声明,在下面CGPROGRAM中声明则表明该变量为全局变量。
sampler2D _CameraDepthTexture;
_CameraDepthTexture在不同平台有不同的存储方式,用UNITY_REVERSED_Z宏能够判断,如果是1,通常是Direct3D平台,深度范围是;否则是OpenGL平台,深度范围是。透视投影时,如果是DX平台,深度的范围是,near plane为1,far plane为0,OpenGL平台下,_CameraDepthTexture中保存越接近near plane,depth越接近0,越接近far plane,越接近1
[*]Direct3D平台下,投影矩阵之后z∈ -> 透视除法后z∈
[*]OpenGL平台下,投影矩阵之后z∈[-w, w] -> 透视除法后z∈[-1,1] -> glDepthRange(0,1)后z∈
不同渲染平台下投影矩阵不同。 Camera.projectionMatrix保存的是OpenGL规范的投影矩阵(无论哪个平台),传到shader前要调用GL.GetGPUProjectionMatrix()处理平台差异。
//读取_CameraDepthTexture
//1.如果是后处理,可以直接用uv访问
//vertex
//当有多个RenderTarget时,需要自己处理UV翻转问题
#if UNITY_UV_STARTS_AT_TOP //DirectX之类的
if(_MainTex_TexelSize.y < 0) //开启了抗锯齿
o.uv.y = 1 - o.uv.y; //满足上面两个条件时uv会翻转,因此需要转回来
#endif
//fragment
float depth = UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, i.uv));
//depth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos))
//depth = tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos)).r
//2.其他:利用投影纹理采样
//fragment 里面
float depth = SAMLPLET_DEPTH_TEXTURE(_CameraDepthTexture,i.uv);
float linear01Depth = Linear01Depth(depth); //转换成内的线性变化深度值
float linearEyeDepth = LinearEyeDepth(depth); //转换到摄像机空间访问_CameraDepthTexture后就可以对纹理坐标进行采样。绝大多数情况下使用tex2D即可。
SAMPLE_DEPTH_TEXTURE用来处理平台差异问题。
SAMPEL_DEPTH_TEXTURE_PROJ:接受两个参数深度纹理和一个float3和float4类型的纹理坐标,内部使用了tex2Dproj进行投影纹理采样,通常用于阴影的实现。
SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture,UNITY_PROJ_COORD(i.screenPos ));
//vertex 里面
o.screenPos = ComputeScreenPos(o.vertex);
//ComputeScreenPos用来计算线性的屏幕坐标(实际上名不副实,其实是将[-w,w]范围内的齐次坐标xy缩放到范围),
//并保留zw值不变。这里的齐次坐标是不能做透视除法的,因为重心插值只能对线性变量插值,做了除法以后就不是线性值了,
//要保证插值永远在线性空间内进行(物体、世界、相机、齐次四个空间)。LinearEyeDepth:负责把深度纹理的采样转到视角空间下的深度值。
Linear01Depth:返回一个0~1的线性深度值 。
如果需要得到深度+法线纹理,可以直接使用tex2D对_CameraDepthNormalsTexture进行采样。
DecodeDepthNormal:的第一个参数是对深度+法线采样的结果0~1;
也可以通过DecodeFloatRG和DecodeViewNormalStereo来解码深度+法线纹理中的深度法线信息。
可视化
把深度给显示出来,思路借用后处理的方式,OnRenderImage传入的material用的shader直接返回_CameraDepthTexture查到的值转到线性空间(Linear01Depth)且是,为方便,这里不再直接把shader传给脚本在脚本创建材质传入OnRenderImage,而是直接创材质传给脚本:
//脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//保证非运行状态也能执行看到效果,不过建议挂载一个摄像机移动脚本,当运行状态下时,方便移动查看
//摄像机移动脚本: https://blog.csdn.net/pikefish/article/details/96424505
public class DepthVisiable : MonoBehaviour
{
public Material mat;
private Camera cam;
void Start()
{
cam = GetComponent<Camera>(); //获取当前绑定到脚本的相机
cam.farClipPlane = 10;//设置相机远屏幕,太大会导致近的物体深度值过小,即黑
cam.depthTextureMode = DepthTextureMode.Depth;
}
void OnRenderImage(RenderTexture source, RenderTexture destination)
{
Graphics.Blit(source, destination, mat);
}
}
shader返回转化的深度值。
Shader &#34;Custom/DepthVisiable&#34; {
SubShader{
Tags { &#34;RenderType&#34; = &#34;Opaque&#34; }
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include &#34;UnityCG.cginc&#34;
sampler2D _CameraDepthTexture;
struct v2f {
float4 pos : SV_POSITION;
float4 scrPos:TEXCOORD1;
};
//Vertex Shader
v2f vert(appdata_base v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.scrPos = ComputeScreenPos(o.pos);
return o;
}
//Fragment Shader
float4 frag(v2f i) : COLOR{
//Linear01Depth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv));
float depthValue = Linear01Depth(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.scrPos)).r);
return float4(depthValue.xxx,1.0);
}
ENDCG
}
}
FallBack &#34;Diffuse&#34;
}如果选择生成深度+法线纹理,Unity会创建一张与屏幕分辨率相同,32位精度四通道的纹理,其中ViewSpace下的法线会被编码到R与G通道,深度信息为B和A通道。
其次可以通过Frame Debugger查看深度图:UpdateDepth Texture这一步
红0,黑1,离相机近点更容易看到
其他相机的深度图
使用上面的方法,是unity内部完成的操作,unity内部设置好了一个全局变量_CameraDepthTexture提供使用,但是这样只能得到MainCamera的深度纹理,所以思路就是,模仿unity的操作,为其他的相机也设置一个全局变量提供使用,简而言之,需要通过SetTargetBuffers拿到相机深度缓存,然后放到纹理里,这个纹理就是全局变量,即sampler2D _DepthTex
//脚本
using System;
using UnityEngine;
using UnityEngine.Rendering;
public class OtherCamDepth : MonoBehaviour
{
/// 另一个摄像机
private Camera m_Camera;
/// 用于存储深度
public RenderTexture m_depthBufferTex;
/// 深度图
public RenderTexture m_DepthTex;
/// 当前摄像机渲染最终的图片
private RenderTexture m_CameraRenderTex;
/// 存储处理深度图的命令,在指定某个时期执行其中存储的命令
private CommandBuffer m_DepthBuffer;
public void Start()
{
//另一个相机
m_Camera = GameObject.Find(&#34;otherCamera&#34;).GetComponent<Camera>() ;
Init();
//主相机
Camera cam = GameObject.Find(&#34;Main Camera&#34;).GetComponent<Camera>() ;
cam.depthTextureMode = DepthTextureMode.Depth;
}
private void Init()
{
//屏幕渲染图
m_CameraRenderTex = RenderTexture.GetTemporary(Screen.width, Screen.height, 0, RenderTextureFormat.Default, RenderTextureReadWrite.Default);
//存储深度
m_depthBufferTex = new RenderTexture(Screen.width, Screen.height, 24, RenderTextureFormat.Depth);
m_depthBufferTex.name = &#34;DepthBuffer&#34;;
//深度图
m_DepthTex = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.RHalf);
m_DepthTex.name = &#34;DepthTex&#34;;
//添加处理深度图commandbuffer
m_DepthBuffer = new CommandBuffer();
m_DepthBuffer.name = &#34;CommandBuffer_DepthBuffer&#34;;
//把depthbuffer写入m_DepthTex的colorbuffer
//把depthbuffer合成一张rt和自带的是重新渲染一张rt效果一样
//我这里定义rt为_DepthTex,shader直接获取这个就可以使用自定义深度图
//向命令缓冲区写入命令
m_DepthBuffer.Blit(m_depthBufferTex.depthBuffer, m_DepthTex.colorBuffer);
//设置命令缓冲区中的命令何时执行
m_Camera.AddCommandBuffer(CameraEvent.AfterForwardOpaque, m_DepthBuffer);
//设置shader全局深度图
Shader.SetGlobalTexture(&#34;_DepthTex&#34;, m_DepthTex);
}
private void OnPreRender()
{
m_Camera.SetTargetBuffers(m_CameraRenderTex.colorBuffer, m_depthBufferTex.depthBuffer);
}
}
把脚本挂载到Main Camera上,创建另外一个相机命名为otherCamera(脚本里需要根据名字获取相机),然后就可以创建一个shader,在里面声明你定义的全局变量名称,这里是_DepthTex,用法与_CameraDepthTexture一致。
同样,想要可视化深度的话,可以采取后处理的办法,输出到屏幕上,向脚本里添加:
public Material mat;
...
private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
Graphics.Blit(src , dest ,mat);
}
...创建两个材质
//这里为方便不再创建两个shader,用一个int取0或1来控制使用那张深度纹理,_DepthTexture或_CameraDepthTexture
Shader &#34;Unlit/OtherCamDepth&#34;
{
Properties
{
_main2side ( &#34;main2side&#34; , int ) = 0
}
SubShader
{
ZTest Off
Tags { &#34;RenderType&#34;=&#34;transparent&#34; }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include &#34;UnityCG.cginc&#34;
sampler2D _DepthTex;
sampler2D _CameraDepthTexture;
int _main2side;
struct appdata{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float4 screenPos : TEXCOORD1;
};
v2f vert (appdata v){
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
o.screenPos = ComputeScreenPos(o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target{
return float4(Linear01Depth((SAMPLE_DEPTH_TEXTURE(_DepthTex, i.uv))).xxx,1.0) * _main2side + (1-_main2side)*float4(Linear01Depth((SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv))).xxx,1.0);
}
ENDCG
}
}
}
左:正视,右:俯视,两个相机,far plane均为10
应用
水面
拿到深度之后,可以做的事情就很多了,比如水面,水面一般来说
Creater Lake
morskie oko
明显可以感知到,水深的地方颜色就深,所以我们需要找到一种近似水深的量,联想到深度,假设我们是俯视看着水面,我们需要知道水面到水底的距离,很简单,我们只要把水底写入深度图,水面不写入,渲染水面时,能拿到当前shading Point的深度,也就是水面的深度,同时unity中通过LinearEyeDepth函数我们可以传入深度图上的深度,获得ViewSpace下的水底的z坐标,直接减去当前shading Point的ViewSpace下的z坐标:
float existingDepth01 = tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos)).r;
//float existingDepth01 = (SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos)))
//LinearEyeDepth可以拿到深度图上记录的点的ViewSpace下的深度也就是此时该点的z值
float existingDepthLinear = LinearEyeDepth(existingDepth01);
//view space 下水面与水底Z坐标差值,因为水面不写入深度,所以深度图 里记的是水底的深度,
//i.vertex.w是clip space下的坐标,它的w分量正好是经过
//mv变换后,处在view sapce下的顶点经过透视投影矩阵P,P的最后一行是0,0,1,0,
//所以作用完后得到clip space下顶点的w坐标正好为P矩阵作用的坐标的Z分量
//其次 unity相机看向-Z,所以i.viewSpace_pos.z全是负值,但是LinearEyeDepth算出的Z是正数
float viewSpace_pos_Z = -1 * i.viewSpace_pos.z;
float depthDifference= existingDepthLinear - i.viewSpace_pos.z;//i.vertex.w , i.screenPos.w
return float4( depthDifference.xxx,1.0 );
返回z坐标差值,左隐藏水面,右显示水面
使用插值lerp几种颜色,比如两种
Shader &#34;Unlit/water&#34;
{
Properties
{
_DepthGradientShallow(&#34;Depth Gradient Shallow&#34;, Color) = (0.0, 0.7, 1.0, 0.0)
_DepthGradientDeep(&#34;Depth Gradient Deep&#34;, Color) = (0.0, 0.26, 1, 0.98)
// Maximum distance the surface below the water will affect the color gradient.
_DepthMaxDistance(&#34;Depth Maximum Distance&#34;, Float) = 1
}
SubShader
{
Tags
{
&#34;Queue&#34; = &#34;Transparent&#34;
}
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include &#34;UnityCG.cginc&#34;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal :NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float4 screenPos : TEXCOORD1;
float3 normal :TEXCOORD2;
float4 viewSpace_pos : TEXCOORD3;
};
//sampler2D _MainTex;
//float4 _MainTex_ST;
float4 _DepthGradientShallow;
float4 _DepthGradientDeep;
sampler2D _CameraDepthTexture;
float _DepthMaxDistance;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
o.screenPos = ComputeScreenPos(o.vertex);
o.viewSpace_pos = mul(UNITY_MATRIX_MV , v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float existingDepth01 = tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos)).r;
float existingDepthLinear = LinearEyeDepth(existingDepth01);
//view space 下水面与水底Z坐标差值,因为水面不写入深度,所以深度图 里记的是水底的深度,
//LinearEyeDepth可以拿到深度图上记录的点的ViewSpace下的深度也就是此时该点的z值
//i.vertex.w是clip space下的坐标,它的w分量正好是经过
//mv变换后,处在view sapce下的顶点经过透视投影矩阵P,P的最后一行是0,0,1,0,
//所以作用完后得到clip space下顶点的w坐标正好为P矩阵作用的坐标的Z分量
//其次 unity相机看向-Z,所以i.viewSpace_pos.z全是负值,但是LinearEyeDepth算出的Z是正数
float viewSpace_pos_Z = -1 * i.viewSpace_pos.z;
float depthDifference = existingDepthLinear - viewSpace_pos_Z;//i.vertex.w , i.screenPos.w
//return float4(depthDifference.xxx,1.0);
float waterDepthDifference01 = saturate(depthDifference / _DepthMaxDistance);
float4 waterColor = lerp(_DepthGradientShallow, _DepthGradientDeep, waterDepthDifference01);
return waterColor;
}
ENDCG
}
}
}
这里只用到unity的_CameraDepthTexture一张深度图,脚本只需启用相机深度图。
扫描
扫描这里的实现思路很简单的,依旧是后处理的思路,为了方便直接在脚本里用材质,为了实现效果需要找到一定区域内的深度值,比如,比如按下c键,a就从0逐渐增长,扫描宽度c,判断深度是否处于这一区域,符合直接在shader里返回扫描线的颜色:
using UnityEngine;
public class Scan : MonoBehaviour {
public Material mat;
public float velocity = 0.4f;//速度
public float scanWidth = 0.01f;//扫描线宽度
public float slowNear = 0.22f;//降低近处扫描线速度,提升远处速度
public float decreaseNearWidth = 0.27f;//减小近处扫描线宽度,增大远处扫描线宽度
public float scanHeightPos = 0.11f;//扫描线最亮的部位处最前最后还是中间
private float dis;
void Start(){
Camera.main.depthTextureMode = DepthTextureMode.Depth;
}
void Update(){
this.dis += Time.deltaTime * this.velocity;
if (Input.GetKeyDown(KeyCode.C)){
this.dis = 0;
}
}
void OnRenderImage(RenderTexture src, RenderTexture dst) {
mat.SetFloat(&#34;_ScanDistance&#34;, dis);
mat.SetFloat(&#34;_ScanWidth&#34;, scanWidth);
mat.SetFloat(&#34;_SlowNear&#34;, slowNear);
mat.SetFloat(&#34;_ScanHeightPos&#34;, scanHeightPos);
mat.SetFloat(&#34;_DecreaseNearWidth&#34;, decreaseNearWidth);
Graphics.Blit(src, dst, mat);
}
}
Shader &#34;Unlit/Scan&#34;
{
Properties{
_MainTex (&#34;Texture&#34;, 2D) = &#34;white&#34; {}
_ScanColor(&#34;Scan Color&#34; , Color) = (1,0,0,1)
}
SubShader{
Tags {
&#34;RenderType&#34; = &#34;Transparent&#34;
&#34;Queue&#34; = &#34;Transparent&#34;
}
Blend SrcAlpha OneMinusSrcAlpha
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include &#34;UnityCG.cginc&#34;
struct v2f{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float4 scrPos : TEXCOORD1;
};
sampler2D _MainTex;
sampler2D _CameraDepthTexture;
float _ScanDistance,_ScanWidth,_SlowNear, _ScanHeightPos,_DecreaseNearWidth;
float4 _ScanColor;
v2f vert (appdata_base v){
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
//算中va长度占总长度的比例系数, 1-Inverse()即为vb长度占总长度
float InverseLerp( float a , float b , float v ){
return ( v - a ) / ( b - a ) ;
}
fixed4 frag (v2f i) : SV_Target{
float4 col = tex2D( _MainTex,i.uv );
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
depth = Linear01Depth(depth);
//优化问题,为什么不推荐用if在shader里 https://blog.csdn.net/liu_if_else/article/details/77455639
_ScanWidth += depth*_DecreaseNearWidth; //近处的扫描范围小远处的扫描范围大
_ScanDistance *= _SlowNear+depth;//distance从零均匀上涨(c#),近处的涨慢点,远处的涨快点
if (depth < _ScanDistance && depth > _ScanDistance - _ScanWidth && depth < 1)
{
//return float4(1,0,0,1);
//根据Depth到扫描线离相机远的位置距离,插值拖尾,头亮尾巴淡化
//return lerp( col , _ScanColor ,(InverseLerp( _ScanDistance - _ScanWidth ,_ScanDistance ,depth )) );
//根据Depth到扫描线中间的位置的距离,两边淡化
//return lerp( _ScanColor,col ,abs(depth - (_ScanDistance - _ScanWidth*0.5))/(_ScanWidth*0.5) );
//调参来控制尾巴淡化
float a = _ScanDistance -_ScanWidth;
float v = _ScanDistance -_ScanHeightPos * _ScanWidth;
float b = _ScanDistance;
//,有,depth在其中一个区间,需要根据depth到v的距离除以所在区间总长度得到系数来插值
if( depth < v ){
return lerp( col , _ScanColor ,InverseLerp(a,v,depth));
}else{
return lerp( col , _ScanColor ,InverseLerp(b,v,depth));
}
}
//return 0;
return col;
}
ENDCG
}
}
}
return float(1,0,0,0)
CHAI&#39;S BLOG &#34; Unity深度和深度贴图
Unity Toon Water Shader Tutorial at Roystan
页:
[1]