原文链接
版权声明:本文为CSDN博主「wodownload2」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
正文
摄像机和深度纹理
官方文档中有两个注意点
- 必须要有使用了
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 plane 的
near
和far
不要让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_EYEDEPTH
、LinearEyeDepth
三个方法转换到观察空间
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 以上)。我们可以使用这些纹理来实现屏幕后处理效果。
当你需要某种纹理的时候,可以为 Camera
的 depthTextureMode
和以下变量或(|
)运算:
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
=1000Geometry
=2000(默认)AlphaTest
=2450Transparent
=3000
同时获取深度和法线的纹理
而使用 DepthTextureMode.DepthNormals
时,我们可以获取 深度法线 纹理: _CameraDepthNormalTexture
。
- Unity 将 法线 编码到 R 和 G 通道里,
- 而 深度 编码到 B 和 A 通道里。
我们可以通过 DecodeDepthNormal
方法获取 深度 和 法线:
float2 uv = i.uv;
half depth;
half3 norm;
DecodeDepthNormal(tex2D(_CameraDepthNormalTexture, uv), depth, norm);