由于篇幅原因,本文只介绍:
下面再简单介绍一次吧:
在CSharp脚本层,设置Camera.depthTextureMode = DepthTextureMode.Depth;
或是Camera.depthTextureMode = DepthTextureMode.DepthNormals;
,然后在Shader层声明_CameraDepthTexture
或是_CameraDepthNormalsTexture
;来获取,如下伪代码:
CSharp脚本
public class ScriptName : MonoBehaviour {
private Camera cam;
private void Start() {
cam = gameObject.GetComponent<Camera>();
cam.depthTextureMode |= DepthTextureMode.Depth;
cam.depthTextureMode |= DepthTextureMode.DepthNormals;
}
...
}
Shader
...
sampler2D _CameraDepthTexture; // 如果Camera.depthTextureMode = DepthTextureMode.Dept的Flag就可以获取这个uniform
sampler2D _CameraDepthNormalsTexture; // 如果Camera.depthTextureMode = DepthTextureMode.DepthNormals的Flag就可以获取该uniform
...
现在脚本层对Camera.RenderWithShader到RT上,再传入Shader,如下伪代码:
用于绘制深度的Shader
// vertex:
## o.depth = o.pos.z; // 这是错误的
o.depth = -mul(UNITY_MATRIX_MV, i.vertex).z; // 这是正确的
// fragment:
// 这是错误的
{
## float n = _ProjectionParams.y;
## float f = _ProjectionParams.z;
## float c = (i.depth - n) / f;
## return c;
}
// 这是正确的
{
float f = _ProjectionParams.z;
float c = i.depth / f;
}
v2f vert( appdata_base v )
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.nz.xyz = COMPUTE_VIEW_NORMAL;
o.nz.w = COMPUTE_DEPTH_01;
return o;
}
fixed4 _Color;
fixed4 frag(v2f i) : SV_Target
{
//clip(_Color.a-0.01);
return EncodeDepthNormal (i.nz.w, i.nz.xyz);
}
public class ScriptName : MonoBehaviour {
...
private Camera cam;
private int depthHash;
private RenderTexture customDepthRT;
private void Start() {
depthHash = Shader.PropertyToID("_CustomDepthTexture");
cam = gameObject.GetComponent<Camera>();
customDepthRT = RenderTexture.GetTemporary(Screen.width, Screen.height);
}
private void OnPreRender() {
var srcTT = cam.targetTexture;
var srcClearFlags = cam.clearFlags;
var srcBgColor = cam.backgroundColor;
// 测试RenderWithShader
// RenderWithShader是调用时就会去执行替换,并渲染相机的内容
cam.backgroundColor = Color.black;
cam.clearFlags = CameraClearFlags.Color;
cam.targetTexture = replaceColorRT; // 渲染目标到rt
// 立刻渲染Shader中的pass有"RenderType"的tag的对象
// 所以你的替换使用的Shader中的pass的tag要设置好友"RenderType"="Opaque"就好了
// 这里最好用"RenderType"="Opaque",因为内置的很多shader,也都是对不透明对象用这个tag标记了的
cam.RenderWithShader(replaceShader, "RenderType");
cam.targetTexture = srcTT; // 恢复渲染目标,等设置
cam.clearFlags = srcClearFlags;
cam.backgroundColor = srcBgColor;
// 设置全局的_CustomDepthTexture纹理
Shader.SetGlobalTexture(depthHash , replaceColorRT);
}
...
}
...
sampler2D _CustomDepthTexture; // 声明一下对应的全局纹理就好了,然后在后续着色器中都可以使用到了
...
使用Unity提供的深度纹理时,注意下面的问题:
什么是ndc,它是NDC(Normalized Device Coordinates)的简写。
ndc.z就是ndc坐标空间下的z轴的值。
ndc是在GPU内置的几何变换管线中的一部分处理,有兴趣可以参考之前写的一篇用英文总结(因为这些描述用英文表达比较准确):OpenGL Transformation 几何变换的顺序概要(MVP,NDC,Window坐标变换过程)
eyeZ中的eye代表:View/Eye/Camera Space。
所以eyeZ可以理解为:视图空间下z轴值。
注意:在unity的_CameraDepthTexture中R通道存储的是非线性的ndc下的z值,这点与后面使用的_CameraDepthNormalsTexture的不同。参考:Unity3D中的深度纹理和法线纹理。
但是unity在正交相机下,_CameraDepthTexture的是一个EyeZ/Far的线性值(后面有将)==Linear01Depth。
先准备好要获取,并显示深度的shader
// jave.lin 2020.03.09
Shader "Custom/GetDepthTexture" {
SubShader {
Cull Off ZWrite Off ZTest Always
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f {
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _CameraDepthTexture;
v2f vert (appdata v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target {
return Linear01Depth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv));
}
ENDCG
}
}
}
材质:GetDepthTextureMat,设置好使用上面的shader
脚本挂载到Camera上,设置好Mat材质:GetDepthTextureMat
// jave.lin 2020.03.09
using UnityEngine;
public class GetDepthTextureScript1 : MonoBehaviour
{
public Material mat;
private Camera cam;
private void Start()
{
cam = gameObject.GetComponent<Camera>();
cam.depthTextureMode |= DepthTextureMode.Depth;
cam.depthTextureMode |= DepthTextureMode.DepthNormals;
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
Graphics.Blit(null, destination, mat);
}
}
注意:在unity的_CameraDepthNormalsTexture中BA(ZW)通道存储的是Linear01Depth=线性z值/far的比例值,这点与后面使用的_CameraDepthTexture的不同。Unity3D中的深度纹理和法线纹理
就只调整shader就好了。
// jave.lin 2020.03.09
Shader "Custom/GetDepthTexture" {
SubShader {
Cull Off ZWrite Off ZTest Always
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f {
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
//sampler2D _CameraDepthTexture;
sampler2D _CameraDepthNormalsTexture;
v2f vert (appdata v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target {
//return Linear01Depth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv));
float4 depthNormals = SAMPLE_RAW_DEPTH_TEXTURE(_CameraDepthNormalsTexture, i.uv);
return DecodeFloatRG (depthNormals.zw);
}
ENDCG
}
}
}
可以看到frag函数中,我们该用了_CameraDepthNormalsTexture的方式了。
在VS阶段使用COMPUTE_VIEW_NORMAL宏得到视图空间下的法线,COMPUTE_DEPTH_01宏得到视图空间下的深度。
COMPUTE_VIEW_NORMAL宏内容如下:
#define COMPUTE_VIEW_NORMAL normalize(mul((float3x3)UNITY_MATRIX_IT_MV, v.normal))
COMPUTE_DEPTH_01宏内容如下:
#define COMPUTE_DEPTH_01 -(UnityObjectToViewPos( v.vertex ).z * _ProjectionParams.w)
然后在FS阶段EncodeDepthNormal(depth, normal),将法线编码到像素的xy(rg)通道,将深度编码到像素的zw(ba)通道。
EncodeDepthNormal宏内容如下:
// Encoding/decoding [0..1) floats into 8 bit/channel RG. Note that 1.0 will not be encoded properly.
inline float2 EncodeFloatRG( float v )
{
float2 kEncodeMul = float2(1.0, 255.0);
float kEncodeBit = 1.0/255.0;
float2 enc = kEncodeMul * v;
enc = frac (enc);
enc.x -= enc.y * kEncodeBit;
return enc;
}
// Encoding/decoding view space normals into 2D 0..1 vector
inline float2 EncodeViewNormalStereo( float3 n )
{
float kScale = 1.7777;
float2 enc;
enc = n.xy / (n.z+1);
enc /= kScale;
enc = enc*0.5+0.5;
return enc;
}
inline float4 EncodeDepthNormal( float depth, float3 normal )
{
float4 enc;
enc.xy = EncodeViewNormalStereo (normal);
enc.zw = EncodeFloatRG (depth);
return enc;
}
然后是我们的shader
// jave.lin 2020.03.09
Shader "Custom/CustomDepthTexture" {
SubShader {
Tags { "RenderType"="Opaque" }
LOD 100
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 vertex : SV_POSITION;
float4 depthNormals : TEXCOORD0;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.depthNormals.xyz = COMPUTE_VIEW_NORMAL;
o.depthNormals.w = COMPUTE_DEPTH_01;
return o;
}
fixed4 frag (v2f i) : SV_Target {
return EncodeDepthNormal(i.depthNormals.w, i.depthNormals.xyz);
}
ENDCG
}
}
}
调用深度纹理像素编码的Shader
然后设置到全局的uniform纹理_CustomDepthNormalsTexture;
// jave.lin 2020.03.09
using UnityEngine;
public class GetDepthTextureScript2 : MonoBehaviour
{
public Material mat; // 将_CustomDepthNormalsTexture解码并显示用的shader材质
public Shader shader; // 将深度,与法线编码到_CustomDepthNormalsTexture上的shader
private GameObject camGO; // 临时用的Camera,用于每帧绘制前先将_CustomDepthNormalsTexture绘制到RT
private Camera cam; // 原来主Camera
[SerializeField] private RenderTexture rt; // 打上[SerializeField]的Attribute方便在Inspector上双击查看RT的内容
private int depthHash; // 获取属性字符的HASH
private void Start()
{
camGO = new GameObject("DrawCustomDepthNormalsTextureCam");
camGO.SetActive(false);
cam = camGO.AddComponent<Camera>();
cam.CopyFrom(gameObject.GetComponent<Camera>());
depthHash = Shader.PropertyToID("_CustomDepthNormalsTexture");
}
private void OnPreRender()
{
if (rt == null || rt.width != Screen.width || rt.height != Screen.height)
{
if (rt != null) RenderTexture.ReleaseTemporary(rt);
rt = RenderTexture.GetTemporary(Screen.width, Screen.height, 24);
}
// 临时Camera不需要备份
//// 备份原始属性
//var srcTT = cam.targetTexture;
//var srcClearFlags = cam.clearFlags;
//var srcBgColor = cam.backgroundColor;
// 测试RenderWithShader
// RenderWithShader是调用时就会去执行替换,并渲染相机的内容
cam.backgroundColor = new Color(0.5f, 0.5f, 1f); // 默认的法线值
cam.clearFlags = CameraClearFlags.Color;
// 渲染目标到rt
cam.targetTexture = rt;
// 立刻渲染Shader中的pass有"RenderType"的tag的对象
// 所以你的替换使用的Shader中的pass的tag要设置好友"RenderType"="Opaque"就好了
// 这里最好用"RenderType"="Opaque",因为内置的很多shader,也都是对不透明对象用这个tag标记了的
// 所以自定义的替换绘制,不需要"LightMode"="ShadowCaster"的tag也可以绘制到我们想要的目标纹理
cam.RenderWithShader(shader, "RenderType");
// 临时Camera不需要恢复
//// 恢复渲染目标,等设置
//cam.targetTexture = srcTT;
//cam.clearFlags = srcClearFlags;
//cam.backgroundColor = srcBgColor;
// 设置全局的_CustomDepthTexture纹理
Shader.SetGlobalTexture(depthHash, rt);
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
// 显示到destination
Graphics.Blit(null, destination, mat);
}
private void OnDestroy()
{
if (rt != null) RenderTexture.ReleaseTemporary(rt);
Destroy(cam);
}
}
读取_CustomDepthNormalsTexture,解码_CustomDepthNormalsTexture.zw为depth,并显示。
如果我们的自定义的纹理不是深度+法线的编码,只是简单的将ndcZ值写入,那么我们解码就不如要任何处理,直接读取 tex2D(_CustomDepthTexture, screenPos.xy).r
即可,即可得到ndcZ值。
如果要解码的是深度与法线,还可以使用另一个函数:(下面这例子是深度与法线)
inline float3 DecodeViewNormalStereo( float4 enc4 )
{
float kScale = 1.7777;
float3 nn = enc4.xyz*float3(2*kScale,2*kScale,0) + float3(-kScale,-kScale,1);
float g = 2.0 / dot(nn.xyz,nn.xyz);
float3 n;
n.xy = g*nn.xy;
n.z = g-1;
return n;
}
inline void DecodeDepthNormal( float4 enc, out float depth, out float3 normal )
{
depth = DecodeFloatRG (enc.zw);
normal = DecodeViewNormalStereo (enc);
}
下面是我们用到解码的shader
// jave.lin 2020.03.09
Shader "Custom/GetCustomDepthTexture" {
SubShader {
Cull Off ZWrite Off ZTest Always
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f {
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _CustomDepthNormalsTexture;
v2f vert (appdata v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target {
//return tex2D(_CustomDepthNormalsTexture, i.uv);
float4 depthNormals = SAMPLE_RAW_DEPTH_TEXTURE(_CustomDepthNormalsTexture, i.uv);
return DecodeFloatRG (depthNormals.zw);
}
ENDCG
}
}
}
先不解码的内容显示:
fixed4 frag (v2f i) : SV_Target {
return tex2D(_CustomDepthNormalsTexture, i.uv);//先显示未解码的内容
//float4 depthNormals = SAMPLE_RAW_DEPTH_TEXTURE(_CustomDepthNormalsTexture, i.uv);
//return DecodeFloatRG (depthNormals.zw);
}
解码后的显示
fixed4 frag (v2f i) : SV_Target {
//return tex2D(_CustomDepthNormalsTexture, i.uv); //先显示未解码的内容
float4 depthNormals = SAMPLE_RAW_DEPTH_TEXTURE(_CustomDepthNormalsTexture, i.uv);
return DecodeFloatRG (depthNormals.zw);
}
注意:以上是自定义获取与内置的_CameraDepthNormalsTexture数据一样的方式。这个深度+法线编码在一起的纹理。前面说过,里面的depth是经过线性处理的。
如果你要自定义编码深度缓存保存的是ndc中z值的,那就是类似_CameraDepthTexture中的R通道值,但没有线性处理:tex2D(_CameraDepthTexture, screenPos.xy).r == ndcZ
你可以在编码的shader的vert函数拿到clipPos=UnityObjectToClipPos(v.vertex);
,然后frag函数取ndc.z == clip.z / clip.w
...
struct v2f {
float4 vertex : SV_POSITION;
float depth : TEXCOORD0;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target {
return i.vertex.z / i.vertex.w; // ndc.z == clip.z / clip.w
}
...
这样渲染出来的深度纹理,就是非线性的。按需求来使用对应的纹理就好了。
LinearEyeDepth, Linear01Depht得官方文档也有介绍:Using Depth Textures
为了方便理解,亲手用Windows的Paint.exe话了个图,难画。。。(手动哭笑+无语 -_-!)
上图中,要注意说明,LinearEyeDepth得出来的值,我们通常叫:EyeZ,它的意思是,视图空间(注意不是透视空间)下的z轴的距离值
作用是:将ndc.z值转为view space(视图空间)z,就是上图的EyeZ(距离A点的距离,如,far=1000,near=0.3(上图可能是550),透视空间中的G点的线性z轴值,对应的,就是在视图空间中的F点的z轴值,就是F距离A可能是800,就是说EyeZ=800)。
作用是:将ndc.z值转为一个线性比例值(view space(视图空间)z轴值除以far的比例值),就是上图的Linear01Depth = AG/AE,图中AG假设为800,AE(far)=1000,那么Linear01Depth = AG/AE=800/1000=0.8。
下面是两个函数的源码:
// Z buffer to linear 0..1 depth
inline float Linear01Depth( float z )
{
return 1.0 / (_ZBufferParams.x * z + _ZBufferParams.y);
}
// Z buffer to linear depth
inline float LinearEyeDepth( float z )
{
return 1.0 / (_ZBufferParams.z * z + _ZBufferParams.w);
}
...
// Values used to linearize the Z buffer (http://www.humus.name/temp/Linearize%20depth.txt)
// x = 1-far/near
// y = far/near
// z = x/far
// w = y/far
// or in case of a reversed depth buffer (UNITY_REVERSED_Z is 1)
// x = -1+far/near
// y = 1
// z = x/far
// w = 1/far
float4 _ZBufferParams;
Unity中的这两个inline函数都是参考这个总结的:Linearize depth.txt
我就将它的内容copy出来,方便阅读吧:
Some math I did ... (not 100% verified, but looks like it works)
n = near
f = far
z = depth buffer Z-value
EZ = eye Z value
LZ = depth buffer Z-value remapped to a linear [0..1] range (near plane to far plane)
LZ2 = depth buffer Z-value remapped to a linear [0..1] range (eye to far plane)
DX:
EZ = (n * f) / (f - z * (f - n))
LZ = (eyeZ - n) / (f - n) = z / (f - z * (f - n))
LZ2 = eyeZ / f = n / (f - z * (f - n))
GL:
EZ = (2 * n * f) / (f + n - z * (f - n))
LZ = (eyeZ - n) / (f - n) = n * (z + 1.0) / (f + n - z * (f - n))
LZ2 = eyeZ / f = (2 * n) / (f + n - z * (f - n))
LZ2 in two instructions:
LZ2 = 1.0 / (c0 * z + c1)
DX:
c1 = f / n
c0 = 1.0 - c1
GL:
c0 = (1 - f / n) / 2
c1 = (1 + f / n) / 2
-------------------
http://www.humus.ca
来看一段代码:(该源代码在此,是国外一名在暴雪公司工作的女性图形程序员,有兴趣可以到她首页看看一些教程。)
还记得我之前说过的吗?Linear01Depth
其实就是EyeZ/Far
的一个比例值。
代码中vert shader中的output.linearDepth = -(UnityObjectToViewPos(input.vertex).z * _ProjectionParams.w);
中可能大家有疑问:_ProjectionParam.w
是什么东西,为何相乘就可以等于linearDepth了,按照上面Linear01Depth=EyeZ/Far
来推算,只有一种可能,那就是_ProjectionParam.w==1/far
。
-(UnityObjectToViewPos(input.vertex).z
很明显就是==EyeZ的运算了(从函数名就可以看出来,object to view),至于取负数,是因为:Camera下的view space是右手坐标系,X轴,Y轴,Z轴,分别是:X拇指指向右(X轴正方向),食指指向上(Y轴正方向),中指指向你自己(Z轴正方向),所以可以知道,Z轴是指向镜头内为正的方向,与我们世界坐标是反的,因为世界坐标、投影坐标是用左手坐标表示的,所以需要取反。
然后我们打开UnityCG.glslinc和UnityShaderVariables.cginc文件中都有定义、说明:
// x = 1 or -1 (-1 if projection is flipped)
// y = near plane
// z = far plane
// w = 1/far plane // 没错,就是1/far
uniform vec4 _ProjectionParams;
关于这两纹理也可以先参考官方文档:Camera’s Depth Texture。
COMPUTE_EYEDEPTH(ndc.z)
,在片段着色器的取ndc.z==clipPos.z/clipPos.w
就好了。tex2D(_CameraDepthTexture).r == ndcZ
。o.depthNormals.xyz = COMPUTE_VIEW_NORMAL; o.depthNormals.w = COMPUTE_DEPTH_01;
,这里要留意COMPUTE_DEPTH_01
是有线性处理后的,在片段着色器 return EncodeDepthNormal(i.depthNormals.w, i.depthNormals.xyz);
编码即可。DecodeDepthNormal( tex2D(_CameraDepthNormalsTexture, screenPos.xy), depth, normal)
,这里读取的深度值是有线性处理过的,如果要将线性值,转回ndc.z值,可以反线性处理,意思说我们DecodeDepthNormal( tex2D(_CameraDepthNormalsTexture, screenPos.xy), depth, normal)
出来的depth值0~1范围的,所以我们按Linear01Depth的逆运算,即可求得ndc.z
。inline float Linear01Depth( float z ) { return 1.0 / (_ZBufferParams.x * z + _ZBufferParams.y); }
,逆运算就是已知Linear01Depth的结果,求参数z。z = (1/Linear01Depth - _ZBufferParams.y) / _ZBufferParams.x
,这个z
就是相当远_CameraDepthTexture的R通道值。_CameraDepthTexture.r==ndc.z
,那么_CameraDepthNormalsTexture.ba解码出来的线性值
是什么呢?上图中,A为相机位置,G为空间中我们要重建的一点,那么该点的世界坐标为A(worldPos) + 向量AG,我们要做的就是求得向量AG即可。根据三角形相似的原理,三角形AGH相似于三角形AFC,则得到AH / AC = AG / AF。由于三角形相似就是比例关系,所以我们可以把AH / AC看做01区间的比值,那么AC就相当于远裁剪面距离,即为1,AH就是我们深度图采样后变换到01区间的深度值,即Linear01Depth的结果d。那么,AG = AF * d。
如果还不了解,我再补充一下:_CameraDepthNormalsTexture.ba解码出来的线性值
就是depth
,而depth = AG / AF的一个比例值,结果肯定是0~1的。然后你可以用上面讲到的Linear01Depth的逆运算ndcZ = (1/Linear01Depth - _ZBufferParams.y) / _ZBufferParams.x
,将depth
代入Linear01Depth
,就可以求出ndcZ
,或是理解为:Linear01Depth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv))
== DecodeDepthNormal(tex2D(_CameraDepthNormalsTexture, i.uv), out float outLinear01Depth, out float3 normal)
中的outLinear01Depth
值
如果你要写软渲染器,就要处理如果坐标转换,并将一些坐标写入DepthBuffer深度缓存。可参考下列资料:
D e p t h V i e w = > D e p t h S c r e e n Depth_{View} => Depth_{Screen} DepthView=>DepthScreen
视空间深度 转化到 屏幕空间深度的公式如下:
a = F / ( F − N ) a = F / (F - N) a=F/(F−N)
b = N F / ( N − F ) b = NF / (N - F) b=NF/(N−F)
d e p t h 屏 幕 空 间 = ( a Z + b ) / Z 为 视 空 间 深 度 depth_{屏幕空间} = (aZ + b)/ Z_{为视空间深度} depth屏幕空间=(aZ+b)/Z为视空间深度
d e p t h = ( a Z + b ) / Z depth = (aZ + b) / Z depth=(aZ+b)/Z
D e p t h S c r e e n = > D e p t h V i e w Depth_{Screen} => Depth_{View} DepthScreen=>DepthView
反推得 屏幕空间深度 转化到 视图空间深度的等于以下公式:
d e p t h = ( a Z + b ) / Z depth = (aZ + b) / Z depth=(aZ+b)/Z
d e p t h = a + b / Z depth = a + b / Z depth=a+b/Z
d e p t h − a = b / Z depth - a = b / Z depth−a=b/Z
( d e p t h − a ) / b = 1 / Z (depth - a) / b = 1 / Z (depth−a)/b=1/Z
b / ( d e p t h − a ) = Z b / (depth - a) = Z b/(depth−a)=Z
D e p t h S c r e e n = > D e p t h V i e w Depth_{Screen} => Depth_{View} DepthScreen=>DepthView 公式: b / ( d e p t h − a ) = Z b / (depth - a) = Z b/(depth−a)=Z,代入a,b:
( N F / ( N − F ) / ( d e p t h − ( F / ( F − N ) ) ) = Z (NF / (N - F) / (depth - (F / (F - N))) = Z (NF/(N−F)/(depth−(F/(F−N)))=Z
假设N=0.3, F=1000,代入N,F:
((0.3 * 1000) / (0.3 - 1000)) / (depth - (1000 / (1000 - 0.3))) = Z
(300 / (-999.7)) / (depth - (1000 / 999.7)) = Z
-0.3000900270081024 / (depth - 1.000300090027008)= Z
假设depth = 0.5,代入depth
-0.3000900270081024 / (0.5 - 1.000300090027008) = Z
-0.3000900270081024 / -0.500300090027008 = Z
0.5998200539838049 = Z
当depth(屏幕空间) = 0.5,Z(Z为视空间深度) = 0.5998200539838049
那么屏幕空间的depth就是要输入深度纹理的值。
另外,还有一篇文章写得非常不错:OpenGL阴影映射
里面有将阴影如何生成,以及如何将片段转换到灯光坐标系空间下来获取深度图,然后判断片段深度与灯光坐标空间下的深度图的深度值做比较的决定是否在阴影中。
里面的深度图,就是将ndc中的z值写入到深度纹理的:tex2D(_CameraDepthTexture, screenPos.xy).r == ndcZ
我借一下他文章中引用的learnopengl中借来的图
我虽然没去下载Unity的内置CameraDepthTexture.shader相关的代码。
但是试验后证明,Unity的正交相机的_CameraDepthTexture存着的内容与透视不一样。
ndc.z
。UNITY_REVERSED_Z
的宏的情况。而透视下的是处理了。所以可以看到我们的shader代码中,透视的是没有处理这个UNITY_REVERSED_Z
的代码的。先用Profiler>Frame Debugger看看_CameraDepthTexture的内容效果:
可以看到,我镜头距离模型很近时,反而深度值越大,距离模型很远时,深度值越小。
这就与透视的不一样了。从这点大概猜到:随着Unity shader内置代码的升级,官方也不断完善处理。让外部使用透明化,但我现在用的是:unity 2018.3.0f2版本的,正交相机下的,他们漏了处理这块内容,我猜大概是正交相机下比较少人用这块深度功能?用正交的一般都是2D游戏。但现在很多2D,或是固定视角2.5D的也有一部分是3D的。
注意:
// 正交相机下,_CameraDepthTexture存储的是线性值,且:距离镜头远的物体,深度值小,距离镜头近的物体,深度值大,可以使用UNITY_REVERSED_Z宏做处理
// 透视相机下,_CameraDepthTexture存储的是ndc.z值,且:不是线性的。
// _CameraDepthNormalsTexture的纹理,正交相机,与透视相机下都没问题一样这么使用
具体可以参考:Unity Shader - 根据片段深度重建片段的世界坐标。只看在正交相机下的深度纹理重构世界坐标,在里头有详细的试验。
在Unity中,获取深度的方式有多种多样。
理解如何编解码深度,对理解深度纹理存储的是什么内容有帮助。
理解Unity提供的深度纹理存储的是什么,才能知道什么地方,什么时候去使用它。
因为总有某些功能在实现时,是基于深度值来实现。
backup : UnityShader_GetDepthNormalsTextureTesting_2018.03.2f0