Unity3D: 使用深度检测的方法进行描边
1 通过深度信息做边缘检测
另一种描边的思路是在图像处理层面做边缘检测。下图是对颜色信息进行边缘检测的效果。

可以看到,检测出来的边缘是非常“杂乱”的,它将图像所有颜色不一致或者说变化较大的地方都标记出来了,这样对于表面颜色本身很复杂的物体(图形)也会检测出很多细小的边缘,而这些并不能算得上是边缘,更精确的说应该是颜色差异的位置。
在实时渲染中,我们看到的图像本身就是我们渲染出来的,我们是具备整个世界的所有构成要素,我们自然就具备当前画面的深度信息。
想一想,如果两个物体它们相对于镜头的深度位置不一样,那么边缘处的深度信息应该会有断层,那么边缘检测算法正好可以找到这些断层所在。
1.1 通过 PostProcessingStack 处理深度信息
1.1.1 安装与准备
我们需要能够进行后处理的地方,所以在Unity中,可以使用 Post Processing Stack 来处理。这是一个Package,可以直接在 Package Manager 中搜索并安装。

这里是它的官方文档【https://docs.unity3d.com/Packages/com.unity.postprocessing@3.1/manual/index.html】,讲解了如何使用等信息。
安装好了之后,我们需要在场景上设置好后处理的功能。
首先需要在相机对象上添加 post-process layer 和 post-process volume 两个组件。

给相机指定一个 layer,名字随便,这里使用 post-processed
。并且在 post-process layer 中的 layer 属性也指定为一样的 layer。
在 post-process volume 中勾选 is Global
,并 new 一个新的 profile 出来。

这个 profile 对象就可以在上面添加各种后处理的效果了。
比如我现在的场景是这样的:
加一个系统自带的暗角(Vignette)的效果。


接下来,我们要自己定义一个后处理效果的实现。
根据官方指南【https://docs.unity3d.com/Packages/com.unity.postprocessing@3.1/manual/Writing-Custom-Effects.html】所提到的方法,先创建一个 C# 脚本,并把下面这些代码 copy 进去。
上面这一段只是示例代码,有一些地方需要说明一下。
"Custom/Grayscale" 代码中的这个部分,是定义了这个后处理效果的路径,后面可以在 profile 对象的 Add effect... 中找到,如下图所示:

"Hidden/Custom/Grayscale" 代码中的这个部分,是 Shader 的路径。
上面这个 C# 文件,只是负责和 Profile 对象建立联系,定义好一些可以后面调节的参数,并传给shader,具体的工作还要在 shader 中做。
首先将代码改写一下,变成我们需要的实现。
然后就可以添加上这个效果。

接下来定义一个 shader ,这个shader 的代码也比较特殊,我们直接从官方指南上copy一份,再做改动。
这个shader执行的结果就是将图像变成灰度图。

OK,后处理的框架已经搭好了,接下来我们要获取深度信息,并做相应的处理。
1.1.2 使用深度信息做边缘检测
1.1.2.1 深度buffer 叫什么名字?
根据 Unity 的这一篇文档《CameraDepthTexture》【https://docs.unity3d.com/Manual/SL-CameraDepthTexture.html】可以得知,深度buffer的名字是 _CameraDepthTexture
。
所以我们在 shader 中申明它,并读取出来看看。
然后一运行你会发现什么都没有,一片漆黑。我们还漏了两个步骤没有做。
1.给相机添加一个 depthTextureMode,让它输出深度buffer
2.根据 Unity 文档中这一段的描述,如果shader没有 shadow casting,就不会显示在 camera depth buffer 上。
Depth texture is rendered using the same shader
passes as used for shadow caster rendering
(ShadowCaster pass type). So by extension, if a shader does not support shadow casting (i.e. there’s no shadow caster pass in the shader or any of the fallbacks), then objects using that shader will not show up in the depth texture.
因此,我们在物体的shader上最后加一个这个:
OK,接下来我们就可以看到深度图了。

如果看到的深度图不清楚,可以调节 Camera 的远近平面属性来控制。
1.1.2.2 做边缘检测
我们定义一个 _Scale
表示要取邻近多远的像素。
在 shader 中取四个邻近的像素点,并进行边缘检测的计算,最后输出这个颜色。
最终的效果如下所示:

再添加一个阈值参数,对其进行二值化。

1.1.3 深度信息边缘检测的局限性
当相机和实际观察的物体相对较远时,被观察的物体之间的深度差别就会相对较小,用这个方法进行边缘检测,这些物体的边缘就会连在一起,即当相机在较远的地方观察时,被观察的对象之间的深度信息差异过小,无法检测出良好的边缘信息。
2 通过法线信息做边缘检测
另一种方法是通过得到屏幕空间的法线信息做边缘检测。
2.1 如何获得法线信息(两种方法)
2.1.1 通过相机设置获取法线信息
可以通过设置 Camera 的 DepthTextureMode 来获取屏幕空间的法线信息。
这样设置之后,我们就有一个全局的深度法线纹理,名字是:_CameraDepthNormalsTexture
,我们可以通过 DecodeDepthNormal
这个方法将深度和法线信息从里面解析出来。
从哪里知道名字是
_CameraDepthNormalsTexture
?
这篇文章里面提到的: https://www.ronja-tutorials.com/post/018-postprocessing-normal/
2.1.2 通过ReplacementShader获取法线信息
2.2 通过法线信息做边缘检测计算
最终我们使用下面这段代码来做边缘检测
3.2.2 如何得到法线信息(方法二)
使用 ReplacementShader
请参考这里:https://docs.unity3d.com/Manual/SL-ShaderReplacement.html
参考资料
* 【CameraDepthTexture: 相机如何设置可以得到深度和法线纹理】https://docs.unity3d.com/Manual/SL-CameraDepthTexture.html
* 【Difference between tex2D and SAMPLE_TEXTURE2D (Post Processing effect): SAMPLE_TEXTURE2D的源码是什么】https://forum.unity.com/threads/difference-between-tex2d-and-sample_texture2d-post-processing-effect.651580/
*【Postprocessing with Normal Texture: 深度和法线的信息会存在哪里】https://www.ronja-tutorials.com/post/018-postprocessing-normal/
*【UnityCG源码: DecodeDepthNormal是怎么实现的】https://gist.github.com/hecomi/9580605
全文完