Unity3D Shader系列之深度纹理

这篇具有很好参考价值的文章主要介绍了Unity3D Shader系列之深度纹理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


1 引言

Unity3D Shader系列之深度纹理
这篇总结下场景扫描的效果。场景扫描背后的原理其实挺简单,先获取相机的深度图,再获取相机绘制的画面,然后从深度图中取出当前像素的深度值,如果深度值在我们扫描线的深度值的范围内,那么当前像素的颜色就为扫描线的颜色与原画面颜色的叠加。所以实现这个效果的重点在于,如何去获取相机的深度图。
其实网上有很多文章以及很多书籍都说到了如何在Unity中获取相机的深度图,但是自己还是想用这篇文章梳理下。只有自己能够完整表达出的知识才是真真正正掌握了的知识。

2 深度纹理

2.1 深度纹理是什么

我们知道GPU有一个深度缓冲区Z-Buffer,用来存储各个像素的深度值,这个深度值是从摄像机到对应该像素点的顶点之间的距离。既然是深度值,那它存的是哪个坐标系下的深度值呢,观察空间、裁剪空间还是NDC坐标系下的呢?答案是Z-Buffer存储的是NDC坐标系下的深度值,即该顶点对应的NDC坐标的Z值。至于为什么选择NDC坐标系下的Z值而不用其他两个空间的Z值,原因见下一节
在OpenGL下,NDC坐标的Z值的范围是[-1, 1];而在DirectX下,NDC坐标的Z值的范围是[0, 1]。我们的深度纹理其实就是在Z-Buffer的基础上稍微处理了一下得到的一张纹理。
这个稍微处理是什么呢?如下:
①在OpenGL平台下,由于NDC坐标的Z值的范围是[-1,1],而纹理每个像素只能存储的值的范围为0~1,所以需要对其进行编码,即深度纹理中该某像素的深度值=0.5 * ZNDC + 0.5
②在DirectX平台下,虽然NDC坐标的Z值的范围是[0,1],可以直接存储到深度纹理中,但是不是直接存的!DirectX平台下,深度纹理实际存储的是1 - ZNDC,即[1, 0],同时Unity的Shader中宏变量UNITY_REVERSED_Z 将会被定义。这个反转操作被称为Reverse-Z。至于为什么需要这样处理,这样处理带来的好处是什么,详见2.4.2节。
这里先看几个零散的知识点:
①深度纹理一般是16位或者32位的,当然也有用24位的,具体要看相机的设置和实际应用的平台。
②Unity3D中,如果将Camera的depthTextureMode设置为DepthTextureMode.DepthNormals,此时Unity3D会生成一张与屏幕大小一样、精度为32位(每通道各8位)的法线+深度纹理,其中,观察空间的法线信息会被编码到纹理的R与G通道,深度值会被编码到纹理的B和A通道(即此时的深度值的精度为16位)。
③另外,由于深度纹理是通过NDC坐标中的Z值计算来的,而NDC坐标是由裁剪空间的坐标进行齐次除法(xyz分别除以w)得到的,所以深度纹理中存储的深度值是非线性的。也就是说通过对深度纹理采样得到的深度值是非线性的,如果要使用线性的深度值,需要将采样深度纹理得到的深度值转换到观察空间下,这一点我们后面再说。
上面这些内容可参考以下官方文档,我这儿为了阅读的连贯性,直接截图了过来。

  • Camera’s Depth Texture
  • Using Depth Textures
    Unity3D Shader系列之深度纹理
    Unity3D Shader系列之深度纹理

2.2 NDC坐标中的Z值

通过上一节我们知道深度纹理中存储的深度值其实是由NDC坐标中的Z值转换而来的,这一节我们来看看NDC坐标是怎么计算来的。
我们知道要将模型渲染到屏幕上,需要对模型上的顶点进行一系列坐标变换,从模型空间到世界空间再到观察空间(MV变换),再到裁剪空间(P变换、裁剪变换或投影变换),然后经过齐次除法得到NDC坐标,最后再进行屏幕映射得到该顶点对应的像素坐标。由于深度值与相机有关,所以我们这里只需关注投影变换和齐次除法即可。

2.2.1 OpenGL与DirectX的差异

在具体看投影变换和齐次除法之前,我们需要先知道两个平台上的几个差异,主要有以下3点:
①OpenGL平台的NDC坐标的Z值范围是[-1,1],而DirectX平台的NDC坐标的Z值范围是[0,1]
②DirectX的观察空间是左手标系(即观察空间中,距离相机越远,值越大),而OpenGL的观察矩阵是右手坐标系(即观察空间中,距离相机越远,值越小)
③OpenGL中,点或向量在进行坐标变换时,是把点或向量写成列向量,即将点或向量写在矩阵的右边Mv;而DirectX中,点或向量在进行坐标变换时,是把点或向量写成行向量,即将点或向量写在矩阵的左边vM
我们下面给出的投影矩阵以及NDC坐标中的Z值都是基于上面这三个条件的。

2.2.2 OpenGL下的NDC

OpenGL中观察空间是右手坐标系,点或向量写成列向量,NDC坐标的Z值范围为[-1,1]。
①透视相机
先看看透视相机的裁剪矩阵。
(注:本小节灰色截图均来自冯乐乐的《Unity Shader入门精要》一书)
Unity3D Shader系列之深度纹理
裁剪空间中的坐标值如下。
Unity3D Shader系列之深度纹理
经过透视除法之后,其NDC坐标为如下。
Unity3D Shader系列之深度纹理
即NDC坐标系下的深度值为:
Unity3D Shader系列之深度纹理
将Zview=-Near和-Far带入可知,其值分别为-1,1。
即ZNDC=-1时,表示该顶点在透视相机的近裁剪平面;ZNDC=1时,表示该顶点在透视相机的远裁剪平面。
Unity3D Shader系列之深度纹理
②正交相机
正交相机的裁剪矩阵。
Unity3D Shader系列之深度纹理
裁剪空间中的坐标值如下。
Unity3D Shader系列之深度纹理
NDC坐标值。
Unity3D Shader系列之深度纹理
即NDC坐标系下的深度为
Unity3D Shader系列之深度纹理
同透视相机,ZNDC=-1时,表示该顶点在正交相机的近裁剪平面;ZNDC=1时,表示该顶点在正交相机的远裁剪平面。
Unity3D Shader系列之深度纹理

2.2.3 DirectX下的NDC

DirectX中观察空间是左手坐标系,点或向量写成行向量,NDC坐标的Z值范围为[0,1]。
Unity3D Shader系列之深度纹理
①透视相机
裁剪空间中的坐标值如下。
Unity3D Shader系列之深度纹理
NDC坐标系下的深度值为:
Unity3D Shader系列之深度纹理
将Zview=Near和Far带入可知,其值分别为0,1。
即ZNDC=0时,表示该顶点在透视相机的近裁剪平面;ZNDC=1时,表示该顶点在透视相机的远裁剪平面。
②正交相机
裁剪空间中的坐标值如下。
Unity3D Shader系列之深度纹理
NDC坐标系下的深度值为:
Unity3D Shader系列之深度纹理
将Zview=Near和Far带入可知,其值分别为0,1。
即ZNDC=0时,表示该顶点在透视相机的近裁剪平面;ZNDC=1时,表示该顶点在透视相机的远裁剪平面。

2.2.4 小结

Unity3D Shader系列之深度纹理

2.2.5 将Zndc转换为线性深度值

我们上面知道,深度纹理中存的是NDC坐标中的深度值,它是非线性的。但是我们一般需要使用线性的深度值,即在观察空间中的深度值Zview,所以这就存在一个将深度纹理中的深度值转换为Zview的变换过程。
我们这里以OpenGL下的透视相机为例,DirectX下的求法类似就不再赘述。
具体步骤如下:
①我们在2.1节中说过,在OpenGL平台下,深度纹理中该某像素的深度值=0.5 * ZNDC + 0.5
Unity3D Shader系列之深度纹理
②在2.2.4中,我们可知ZNDC与Zview的关系如下
Unity3D Shader系列之深度纹理
③两式联立可得Zview与Ztexture的关系式
Unity3D Shader系列之深度纹理
④由于OpenGL中观察空间是右手坐标系,而裁剪空间是左手坐标系,所以Zview还要乘以-1
Unity3D Shader系列之深度纹理
此时,Zview的取值范围为[Near,Far]。
⑤值范围为[0,1]的深度值
但有时我们想使用取值范围为[0, 1]的线性深度值,这时我们只需要将Zview除以Far即可。
Unity3D Shader系列之深度纹理
当然,在写代码时不用我们这样去计算,也不用区分OpenGL与DirectX,Unity已经将上述过程封装为了LinearEyeDepth与Linear01Depth。
LinearEypDpeth复杂把深度纹理的采样结果转换到观察空间下的深度值,即第④步得到的Zview;
Linear01Depth返回的是范围在[0,1]的线性深度值,即第⑤步得到的Z01
两者位于UnityCG.cginc,定义如下:

// 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);
}

_ZBufferParams的值如下,位于UnityShaderVariables.cginc。
可参考:https://forum.unity.com/threads/_zbufferparams-values.39332/

// 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;

需要注意:
使用LinearEyeDepth得到的Zview永远都是正值!!!所以如果想真真正正得到观察空间的深度值的话,还需要乘以-1!
使用LinearEyeDepth得到的Zview永远都是正值!!!所以如果想真真正正得到观察空间的深度值的话,还需要乘以-1!
使用LinearEyeDepth得到的Zview永远都是正值!!!所以如果想真真正正得到观察空间的深度值的话,还需要乘以-1!

2.3 深度纹理为什么存的是NDC坐标中的深度值

我们看2.2节中透视相机的ZNDC,它其实是正比与1/Zview的。Zview是观察空间的深度值,它是线性的,而1/Zview不是线性的。而且我们在实际使用深度纹理时,一般会将其转换为Zview。那么问题来了,既然我们实际使用使用Zview,但为什么深度纹理中保存的值确实ZNDC呢?深度纹理为什么不直接保存我们的Zview值呢?
深度纹理保存NDC坐标的深度值主要原因是为了方便硬件进行透视校正插值

2.3.1 透视校正插值

什么是透视校正插值呢?具体细节可以参考这两篇文章《【基础】透视校正插值(Perspective-Correct Interpolation)》《图形学基础之透视校正插值》,我这儿就简单提一下。
我们知道,在GPU渲染流水线中的三角形遍历阶段中,会根据三角形三个顶点的属性,对该三角形所覆盖的像素进行插值,这个插值一般是用重心坐标法进行插值。
Unity3D Shader系列之深度纹理
假设三角形三个顶点的属性分别为I1、I2、I3,现在我们要插值得到三角形所覆盖的某一像素的属性值It
注意这里不是直接使用I1、I2、I3来插值,而是使用I1/ZNDC1、I2/ZNDC2、I3/ZNDC3来插值。插值完成后再除以I/ZNDC从而得到该像素的属性值。如下图。
Unity3D Shader系列之深度纹理
用I/ZNDC来插值就叫透视校正插值。 但是为什么一定要用I/ZNDC来插值呢?看上面那两篇文章。

2.3.2 深度纹理使用NDC的Z值的原因

现在我们回到本节最初提出的问题。如果深度纹理保存的是观察空间的深度值Zview,那么我们在三角形遍历进行透视校正插值时,不可避免的需要进行一次除法(因为需要将Zview变换为ZNDC),而三角形遍历完全是由硬件来实现的,这无疑会增加硬件实现的复杂度。所以深度纹理直接保存ZNDC还好一点,虽然我们在Shader中使用时需要将其转换为Zview。

2.4 深度值的精度问题

2.4.1 Z Fighting

我们先来看看由于深度值的精度问题导致的一个现象。
比如我们下面这个测试场景,Ground都是Plane,一个是绿色一个是灰色,两者的Y坐标完全相同,所以两者有重叠。
Unity3D Shader系列之深度纹理
然后我们变换观察角度,会发现重叠部分一会儿出现绿色一会出现灰色。就是因为两个面片隔得太近,由于深度缓冲区的精度问题,深度测试没办法确定哪一个面片在前哪一个面片在后。
Unity3D Shader系列之深度纹理
这种由于面片隔得太近或者由于深度值精度问题,导致深度测试没法区分两者的先后顺序的现象,就被称为Z Fighting(Z就是深度值,Fighting就是打架、竞争)。
下面我们看看深度值精度问题是如何出现的。
注:以下两小节基本是对《Depth Precision Visualized》这篇文章的翻译。

2.4.2 DirectX中的深度值精度问题

从2.2节推导的公式我们可以知道,DirectX中,透视相机中,深度纹理存储的深度值ZNDC是正比与1/Zview的,其值范围为0~1。
注意:下面这几张图都是基于DirectX的
用一个通用公式来表示就是
d = ZNDC = a * 1/Zview + b,
其中a,b为与相机近裁剪平面与远裁剪平面有关的常数。
用曲线将这个公式画出来,就是这样。
Unity3D Shader系列之深度纹理
上面那张图中,横坐标是相机观察空间的深度值,即Zview;纵坐标是我们深度纹理中存储的ZNDC值,即ZNDC = 0时,表示近裁剪平面,ZNDC = 1时,表示远裁剪平面。图中,我们假设使用了一个4bit的变量来存储我们的ZNDC,或者说我们的深度纹理是4位的。4bit能表示多少个数字呢?24=16个数字。这16个数字将ZNDC等间隔划分,即纵坐标的刻度是等间隔的。但是我们会发现横坐标刻度在近裁剪平面附近明显要比远裁剪平面密集得多。也就是说虽然我们的ZNDC值是均匀分布的,但是转换后的观察空间的深度值却不是均匀分布的了,在近裁剪平面密集,在远裁剪平面稀疏。也就是说,我们通过采样深度纹理得到ZNDC值后,再转换为观察空间的深度值,其在近裁剪平面的精度远远大于远裁剪平面的精度。

其实正常来说,这样的深度分布也是有好处的,因为我们在近处的精度高一些,远处精度低点,感觉也比较符合正常思维。如果只是为了保证近处渲染的效果,那么直接用正常的ZBuffer就是最好的选择了。但是,主要就在于超大视距,类似超大地图这种,既需要保证远处的精度,又希望保证近处的精度,远处精度衰减太厉害,所以ZFighting现象就出现了。

如果我们将近裁剪平面拉得更近(即near值减小),会导致近裁剪平的深度值精度变得更高,而远裁剪平面附近的深度精度越来越低。两者的不平衡进一步拉大。
Unity3D Shader系列之深度纹理
当然,如果我们将近裁剪平面拉远,这种精度分布不均的情况将会有所缓解。
Unity3D Shader系列之深度纹理
那么我们怎么解决这个精度分布不均的问题呢?
我们想到用浮点数来存储深度值ZNDC。下图中的ZNDC是用指数位为3位,尾数也为3位的浮点数存储。虽然现在可以分隔为40段,但是上述近远裁剪平面分布不均的问题依然存在。
Unity3D Shader系列之深度纹理
那到底怎么解决这个问题呢?很简单,将深度纹理中的值翻转以下即可,即深度纹理中不直接存储ZNDC而是存储的是1-ZNDC,即1表示近裁剪平面,0表示远裁剪平面。
然后我们看看结果。可见使用这种方式有效解决了这个问题。这种技术被形象的称为Reverse Z(反转Z值)。
Unity3D Shader系列之深度纹理
在DirectX中,Reverse Z已经是标配的操作了。也就是说,1表示近裁剪平面,0表示远裁剪平面。我们一般用R通道来存储深度值,所以在DirectX中,深度纹理越红的地方表示距离相机越近。

2.4.3 OpenGL中的深度值精度问题

在DirectX中,我们可以使用Reverse Z来解决深度值的精度问题,OpenGL中是否也可以这样处理呢?
很不幸,OpenGL中Reverse Z是无效的,因为其ZNDC的值范围为[-1, 1],反转了也没意义。
其深度值分布如下。
Unity3D Shader系列之深度纹理

2.5 Unity中生成深度纹理

2.5.1 前向渲染中的深度纹理

前向渲染中,Unity使用了额外的一个Pass来渲染深度纹理,这个Pass同时也是渲染阴影的Pass,即名为ShadowCaster的Pass(Unity内置Shader中的Fallback都会包含此Pass)。
需要注意不论是前向渲染还是延迟渲染,Unity都只会将Shader的渲染队列为2500(即Background、Geometry、AlphaTest)以下的物体渲染到深度纹理中。
Unity3D Shader系列之深度纹理
有同学可能会问,正常渲染场景的时候我们的深度缓冲区不是已经有整个屏幕的深度值了么,为什么还要单独再让相机额外渲染一遍整个场景,导致Draw Call加倍?
这想法确实没错,理论上不透明物体渲染全部渲染完毕后我们就可以直接从深度缓冲区拿到整个屏幕的深度值了,但是Unity目前还不支持。之前有人在论坛问过类似的问题,Unity技术人员回答说,没这样实现(直接将深度缓冲区保存成深度纹理)主要有以下两方面原因:
①是对于非全屏渲染的情况,本来是想拿对应相机渲染的深度,但是Depth Buffer是全屏的
②是因为很多平台不支持直接拿Depth Buffer的数据
Unity3D Shader系列之深度纹理
论坛详情见这里。

但是有个特殊的地方,由于Unity生成阴影使用的是屏幕空间的阴影映射技术(Screen Space Shadow Map),在生成阴影时本身就会生成一张相机的深度纹理图加上一张基于光源空间的阴影映射纹理。所以如果灯光开启了阴影,我们去获取相机的深度纹理并不会导致相机再额外去渲染一遍场景(因为生成阴影时本身就会生成一张深度纹理),也并不会额外增加DrawCall,这也是渲染深度纹理用到的Pass与渲染阴影用到的纹理是同一个Pass的原因。

2.5.2 延迟渲染中的深度纹理

延迟渲染的深度纹理只需要从G-Buffer中拿就行了,不需要像前向渲染那样再额外渲染一遍场景,不会增加任何DrawCall。因为无论我们是否需要深度纹理,延迟渲染都会生成一张深度纹理到G-Buffer中。
Unity3D Shader系列之深度纹理
说个题外话,延迟渲染只渲染一遍场景,但输出却有多个渲染纹理,所以延迟渲染需要显卡支持MRT(Multiple Render Targets,多重渲染目标)。各个渲染纹理的内容如下。
Unity3D Shader系列之深度纹理

2.4.3 两者对比

自然而然,我们就会问用哪一种渲染路径获取深度纹理效率更高?
如果只是考虑深度纹理的获取,无疑延迟渲染效率是更高的。
但并不是或延迟渲染就一定比前向渲染好,延迟渲染也有不好的地方。比如延迟渲染就无法支持半透明物体,同时显卡需要支持MRT,而且不支持真正的抗锯齿。

3 示例程序

3.1 代码解释

深度纹理的原理说了这么多,但其实在Unity中使用起来非常简单。
具体步骤如下:
①创建一个脚本,并继承自Monobehaviour
②指定相机的深度纹理模式为DepthTextureMode.Depth,让此相机输出深度纹理
(如果指定为DepthNormals,将会输出一张深度+法线纹理,这时采样深度+法线纹理需要特殊处理,具体见后面)

m_Camera.depthTextureMode |= DepthTextureMode.Depth;

③新建一个Shader,并将脚本中的m_Material材质指定为此Shader
④实现OnRenderImage方法,内部使用 Graphics.Blit方法,将source原图像(这里是相机看到的画面)经过m_Material的Shader处理后拷贝到destination(这就是我们将看到的画面)

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (m_Material == null)
        {
            Graphics.Blit(source, destination);
            return;
        }

        Graphics.Blit(source, destination, m_Material);
    }

⑥Shader中我们直接使用_CameraDepthTexture即可访问到该相机输出的深度纹理(当然,我们需要在Shader中声明,这样Unity才会自动给我们赋值)

sampler2D _CameraDepthTexture;

⑦然后在片元着色器中,用uv对_CameraDepthTexture采样可得到"深度值",然后再使用Linear01Depth将其转换为归一化后的观察空间深度值

float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv);
depth = Linear01Depth(depth);

SAMPLE_DEPTH_TEXTURE的定义如下,其实就是使用uv对深度纹理采样,然后返回R通道值,不过该宏对PS2平台进行了差异化处理。

// Depth texture sampling helpers.
// On most platforms you can just sample them, but some (e.g. PSP2) need special handling.
//
// SAMPLE_DEPTH_TEXTURE(sampler,uv): returns scalar depth
// SAMPLE_DEPTH_TEXTURE_PROJ(sampler,uv): projected sample
// SAMPLE_DEPTH_TEXTURE_LOD(sampler,uv): sample with LOD level

#if defined(SHADER_API_PSP2) && !defined(SHADER_API_PSM)
#   define SAMPLE_DEPTH_TEXTURE(sampler, uv) (tex2D<float>(sampler, uv))
#   define SAMPLE_DEPTH_TEXTURE_PROJ(sampler, uv) (tex2DprojShadow(sampler, uv))
#   define SAMPLE_DEPTH_TEXTURE_LOD(sampler, uv) (tex2Dlod<float>(sampler, uv))
#   define SAMPLE_RAW_DEPTH_TEXTURE(sampler, uv) SAMPLE_DEPTH_TEXTURE(sampler, uv)
#   define SAMPLE_RAW_DEPTH_TEXTURE_PROJ(sampler, uv) SAMPLE_DEPTH_TEXTURE_PROJ(sampler, uv)
#   define SAMPLE_RAW_DEPTH_TEXTURE_LOD(sampler, uv) SAMPLE_DEPTH_TEXTURE_LOD(sampler, uv)
#else
    // Sample depth, just the red component.
#   define SAMPLE_DEPTH_TEXTURE(sampler, uv) (tex2D(sampler, uv).r)
#   define SAMPLE_DEPTH_TEXTURE_PROJ(sampler, uv) (tex2Dproj(sampler, uv).r)
#   define SAMPLE_DEPTH_TEXTURE_LOD(sampler, uv) (tex2Dlod(sampler, uv).r)
    // Sample depth, all components.
#   define SAMPLE_RAW_DEPTH_TEXTURE(sampler, uv) (tex2D(sampler, uv))
#   define SAMPLE_RAW_DEPTH_TEXTURE_PROJ(sampler, uv) (tex2Dproj(sampler, uv))
#   define SAMPLE_RAW_DEPTH_TEXTURE_LOD(sampler, uv) (tex2Dlod(sampler, uv))
#endif

// Deprecated; use SAMPLE_DEPTH_TEXTURE & SAMPLE_DEPTH_TEXTURE_PROJ instead
#if defined(SHADER_API_PSP2)
#   define UNITY_SAMPLE_DEPTH(value) (value).r
#else
#   define UNITY_SAMPLE_DEPTH(value) (value).r
#endif

注意,如果指定相机的深度纹理模式为DepthTextureMode.DepthNormals,即让此相机输出一张深度+法线纹理。其中观察空间的法线信息会被编码到纹理的R与G通道,深度值会被编码到纹理的B和A通道(即此时的深度值的精度为16位)。

m_Camera.depthTextureMode |= DepthTextureMode.DepthNormals;

此时,我们在Shader中不能直接使用SAMPLE_DEPTH_TEXTURE对深度+法线纹理(此时,Shader中需要定义名为_CameraDepthNormalsTexture的sampler2D变量)进行采样,而要使用Unity封装的DecodeDepthNormal方法来获取深度值与法线值。其位于UnityCG.cginc中,定义如下。

inline void DecodeDepthNormal(float4 enc,out float depth, out float3 normal)
{
	depth = DecodeFloatRG(enc.zw);
	normal = DecodeViewNormalStereo(enc);
}

需要注意,使用DecodeDepthNormal得到的深度值是值为[0,1]的观察空间的线性深度值,不需要再调用Linear01Depth进行处理;得到的法线值也是观察空间下的法线。

3.2 场景扫描效果

Unity3D Shader系列之深度纹理
相信只要看懂了深度纹理的原理,实现这个效果就是小菜一叠啦。
C#脚本,挂载到相机上。

using UnityEngine;

[RequireComponent(typeof(Camera))]
public class SceneScanDemo : MonoBehaviour
{
    private Camera m_Camera;
    private Material m_Material;

    [SerializeField]
    private Color m_ScanColor = Color.red;
    [SerializeField]
    private float m_ScanWidth = 0.02f;
    [SerializeField]
    private float m_ScanSpeed = 0.03f;

    private void Awake()
    {
        m_Camera = gameObject.GetComponent<Camera>();
        m_Material = new Material(Shader.Find("LaoWang/DepthTexture"));
    }

    private void OnEnable()
    {
        m_Material.SetColor("_ScanColor", m_ScanColor);
        m_Material.SetFloat("_ScanWidth", m_ScanWidth);
        m_Material.SetFloat("_ScanSpeed", m_ScanSpeed);
        m_Camera.depthTextureMode |= DepthTextureMode.Depth;
        
    }

    private void OnDisable()
    {
        m_Camera.depthTextureMode &= ~DepthTextureMode.Depth;
    }

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (m_Material == null)
        {
            Graphics.Blit(source, destination);
            return;
        }

        Graphics.Blit(source, destination, m_Material);
    }
}

Shader

Shader "LaoWang/DepthTexture"
{
	Properties
	{
		_MainTex ("Main Tex", 2D) = "white"{}
		_ScanColor ("Scan Color", Color) = (1, 0, 0, 0.5)
		_ScanWidth ("Scan Width", Range(0.001, 0.5)) = 0.002
		_ScanSpeed ("Scan Speed", Range(0.001, 8)) = 0.03
	}

    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

		ZTest Off
		Cull Off
		ZWrite Off
		Fog{ Mode Off }

        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 _MainTex;
            sampler2D _CameraDepthTexture;
			fixed4 _ScanColor;
			float _ScanSpeed;
			float _ScanWidth;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
				fixed4 color = tex2D(_MainTex, i.uv);
 
				float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv);
				depth = Linear01Depth(depth);
				float pos = (_Time.y * _ScanSpeed * 1000 % 1000) * 0.001;

				// sign(x) 如果x<0,返回-1;如果x=0,返回0;如果x>0,返回1
				float scanFlag = saturate(sign(depth - pos)) * saturate(sign(pos + _ScanWidth - depth));
				color.rgb = color.rgb * (1 - _ScanColor.a * scanFlag) + _ScanColor.rgb * scanFlag;
				return color;
            }
            ENDCG
        }
    }
}

相机设置。
Unity3D Shader系列之深度纹理
当然,使用深度纹理可以实现很多酷炫的效果,我们后面会专门来实现几个。

4 其他问题

深度纹理怎么是全黑的?
那是因为近裁剪平面设得太小了,会导致深度值的精度问题,具体见2.4节。
Unity3D Shader系列之深度纹理


博主本文博客链接。文章来源地址https://www.toymoban.com/news/detail-409971.html


5 参考文章

  • 《Unity Shader入门精要》第13章
  • the-direct3d-transformation-pipeline
  • Depth Precision Visualized
  • 【Unity】深度图(Depth Texture)的简单介绍
  • Unity Shader-深度相关知识总结与效果实现(LinearDepth,Reverse Z,世界坐标重建,软粒子,高度雾,运动模糊,扫描线效果)
  • 深度的应用
  • 关于_CameraDepthTexture的疑惑
  • 如何在shader中避免使用if else

到了这里,关于Unity3D Shader系列之深度纹理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Unity3D Shader 引导遮罩,支持圆形、矩形、圆角矩形框

    unity3D新手引导遮罩,支持圆形,矩形框,圆角矩形框。图形位置和大小可以根据控件的位置和大小调节,通用所有分辨率设备。黄色区域遮挡,只有白色区域可以点穿。 原文链接:https://www.kadastudio.cn/archives/89 ​ 将MyGuideMask挂载到脚本上,然后通过GuideMask创建材质并赋值,根据

    2024年02月11日
    浏览(41)
  • Unity读书系列《Unity3D游戏开发》——脚本(一)

    脚本在Unity的重要程度不用多说,她是大部分软件的核心组件。 我们将在此篇文章学习脚本模版及其拓展、脚本的生命周期、脚本的执行顺序、脚本序列化,下一篇为脚本编译与调试。 如下图我们可以在Project视图右键进行脚本创建,除了C#脚本,还有两类脚本;Testing用来做

    2024年04月28日
    浏览(40)
  • 让AI臣服,而不是被它替代!让ChatGPT为我们编写Unity3d Shaderlab的着色器(shader)

    ChatGPT的火热大家应该都有目共睹,文案工作者、翻译工作者和画师等各种行业都在被嘲即将失业。不光是这些岗位的员工,作为资深社畜程序猿也能感受到会受到冲击。网上很多人都在发ChatGPT写的代码,并开始大肆宣扬AI要取代程序员了,今天测一测使用ChatGPT来生成一些代码,

    2023年04月23日
    浏览(34)
  • Unity读书系列《Unity3D游戏开发》——基础知识

    本篇对应标题书籍的第一章基础知识,也就是unity的基本常识和工作流的说明。 Unity目前用于创建2D和3D游戏、模拟应用程序、可视化体验以及其他虚拟现实(VR)和增强现实(AR)应用,游戏工业两开花。 Unity初版是在2005年发布的,开始时用unity3.X、4.X这样的名称进行版本更新

    2024年01月18日
    浏览(50)
  • Unity读书系列《Unity3D游戏开发》——编辑器的结构

    本篇对应标题书籍的第二章编辑器的结构,也就是unity的编辑器的使用及菜单的使用。 当我们制作的软件或游戏需要多人合作时,就会使用git、svn进行版本管理。一般来说只保留Assets、ProjectSettings、Packages这几个文件夹,git会自动生成.git文件,我们添加需要屏蔽的文件夹或后

    2024年01月25日
    浏览(55)
  • unity3D基础操作之01--unity3d窗口界面介绍

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 1、Scene场景编辑窗口; 2、Game游戏运行窗口; 3、Hierarchy场景物体列表窗口; 4、Project项目资源列表窗口; 5、Inspector属性编辑列表窗口; 6、其他常调节窗口 在屏幕左上方为场景编辑窗口Scene,在场景编

    2024年02月06日
    浏览(51)
  • 【Unity3D日常开发】Unity3D中协程的使用

    推荐阅读 CSDN主页 GitHub开源地址 Unity3D插件分享 简书地址 我的个人博客 大家好,我是佛系工程师 ☆恬静的小魔龙☆ ,不定时更新Unity开发技巧,觉得有用记得一键三连哦。 最近有小伙伴问协程怎么用、怎么写,我也是会用会写,但是原理不是很明白。 学习了一下,总结出

    2024年02月12日
    浏览(43)
  • 【Unity3D日常开发】Unity3D中实现单例模式详解

    推荐阅读 CSDN主页 GitHub开源地址 Unity3D插件分享 简书地址 我的个人博客 大家好,我是佛系工程师 ☆恬静的小魔龙☆ ,不定时更新Unity开发技巧,觉得有用记得一键三连哦。 首先,说一下,什么是 单例模式(Singleton) 。 单例模式是设计模式中常见的一种设计模式,目的是为了

    2024年02月02日
    浏览(48)
  • 【Unity3D-01】 记录Unity3D调用外接摄像头

    最近想在Unity3D上调用一个摄像头,通过查找资料发现仙魁XAN和八哥快走开的博客符合我的想法,实现起来也不难就尝试了一下 2.1 在这个工程里新建Canvas 如下图所示 然后下设RawImage为载体 2.2 在Assets里面新建一个脚本命名为PlaneManager.cs 代码内容如下(参考八哥快走开的博客)

    2024年02月04日
    浏览(39)
  • 【Unity3D小功能】Unity3D中实现Text显示版本功能

    推荐阅读 CSDN主页 GitHub开源地址 Unity3D插件分享 简书地址 我的个人博客 大家好,我是佛系工程师 ☆恬静的小魔龙☆ ,不定时更新Unity开发技巧,觉得有用记得一键三连哦。 在项目开发中,会遇到要控制版本的情况,比如说对比版本号,版本不对再更新版本的功能,这些就是

    2024年02月05日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包