完整实现

1.先新建一个ThirdPerson蓝图项目,再新建一个Material然后打开。

2.将左下角MaterialDomain更改为Post Process(后期处理)。

image.png

  • MaterialDomain(材质域):定义材质的总体用法,就是选择你要用在哪的。
    用的最多的是表面(Surface)和PostProcess(后期处理)
  • PostProcess(后期处理):顾名思义,就是在场景渲染完之后,再在画面上进行的处理。

3.添加两个SceneTexture节点,并将获取的值分别改为SceneDepth和CustomDepth。
分别介绍下各个名词的含义
image.png

  • UV坐标:UV是一个二维坐标系,水平方向是U,竖直方向是V,两个值的范围都是0~1。
    你可以通过UV值获取画面上面的每个像素点的位置。
    在UE4中,UV坐标系的原点在左上角。而Unity中,原点则在左下角。

image.png

  • SceneTexture(场景纹理)节点:通过这个节点,输入UV值,你可以获取到这个UV对应的像素点的各个属性,比如SceneColor(场景颜色),SceneDepth(场景深度),CustomDepth(自定义深度),Opacity(不透明度)等等。
    如果不输入UV,则默认为对所有像素点都做一样的处理。
  • SceneDepth(场景深度):当前画面上的像素点和摄像机之间的距离,也就是从你看得到的地方开始算,比如镜头正对着一面墙,墙后有块石头,那么UV(0.5,0.5)获取到的场景深度就是墙中间的部分与摄像机之间的距离,跟石头无关了。

image.png
红色虚线为场景深度

  • CustomDepth(自定义深度):想获得被遮挡的物体离相机的距离,就要开启物体上的自定义深度了。如果在UV对应的像素点方向上有物体开启了自定义深度,即使被遮挡也可以获取到它的场景深度。如果该位置没有物体开启自定义深度,那么访问到的值则是无穷大。
    所以我们就可以根据自定义深度和场景深度的差来判断是否有东西被挡住,如果被挡住显示什么颜色等等了。
    并且经过测试,有两个开启了自定义深度的物体在一条线上的话,获取到的CustomDepth是离得最近的那个。

image.png
在Actor上开启Render CustomDepth Pass

  • CustomDepth Stencil Value(自定义深度蒙版值):这个值的作用就是用来处理更复杂的情况的,等于把开启了自定义深度的物体再分级,也是可以在SceneTexture中获取到的。默认为0
    要记得在编辑器里把Custom Depath-Stencil Pass改为Enabled with Stencil,这样蒙版值才会生效。

image.png

  • 可以在编辑器窗口打开Buffer Visualization(可视化缓冲区)设置为Custom Stencil,可以看到蒙版值非0的物体。(还可以试试其他选项感受下)

image.png
4.开始写材质,用CustomDepth-SceneDepth来做判断,
如果开启CustomDepth被挡住,那么Custom>Scene,Custom-Scene=有限值,
如果开启CustomDepth没被挡住,那么Custom=Scene,
如果没有开启CustomDepth,那么Custom-Scene=无穷大。
image.png

  • SceneTexture:PostProcessInput0节点这个节点其实就是SceneColor,也就是场景本来的颜色。SceneColor只能用于MaterialDomain为Surface(表面)的材质。
  • 刚开始一直理解不了为什么选了深度,输出的值那里仍然是Color。其实这里的Color并不代表颜色,只是代表一个Vector参数,有RGBA四个值。
    当使用自定义深度时,我们要的只是一个值,就是用Mask节点取到第一个值也就是深度了。同理,PostProcessInput0节点那里,我们只取前三个值,因为后处理材质只有一个属性EmissiveColor(自发光颜色),这是个float3的属性,只需要三个值控制颜色,不需要透明度。
  • 这里的逻辑其实很简单,用两个if节点,判断CustomDepth-SceneDepth的结果
    当结果<=0时,也就是物体开启了自定义深度,但是没有被挡住,那么用本来的颜色,不做处理,也就是PostProcessInput0的Color值。
    当0<结果<9999999999时(用一个很大的值来区分是否正无穷),说明物体开启了自定义深度并且被挡住了,这时候需要做处理了,新建一个VectorParameter,调自己想要的颜色就行了。
    当结果>9999999999时,也就是为正无穷,说明没有物体开启自定义深度,同样不做处理,用本来的颜色。

5.将写好的材质添加到场景中已有的PostProcessVolume中的PostProcessMaterials数组中,权重默认为1。
image.png

  • Post Process Volume:这个是用来控制后期处理效果的模块,可以在左侧可放置的Volumes里找到。
    在settings里可以设置范围,如果勾选了Unbound,则是覆盖整个世界空间。

image.png
6.对边的判定。判定边有多种方法,我们只要先搞清楚以下几点:

  • 需要描边物体的内部:可以获取到自定义深度,上下左右4个像素点都可以获取自定义深度
  • 需要描边物体的边:可以获取到自定义深度,上下左右4个像素点不是全都能获取到自定义深度
  • 物体以外的地方:自定义深度为正无穷,上下左右4个像素点不是全都能获取到自定义深度

假设这是场景的自定义深度图,每一格是一个像素,里面存放着自定义深度值。
我们看到的物体是三角形的,红色的就是我们需要的边
正无穷就是没有开启自定义深度的部分,有深度值但却是黑色字体的部分就是物体的内部了
image.png
通过这些规律,我们可以用这种方法确定边:当一个像素点自身的自定义深度值不为正无穷,但加上上下左右四个像素点的和却为正无穷时,这个像素点就是物体的边。
(我们用一个很大的数来近似判定是否为正无穷)
判定边还有别的方法,比如比较蒙版值等等(未开启自定义深度的蒙版值默认为0),或者用周围八个像素点一起比较也可以,原理都差不多。
7.完成描边材质。

  • ScreenPosition(屏幕位置):ViewportUV输出的就是每个像素点在屏幕上的UV值,等于是每个像素点的UV都获取一次,然后进行处理。

image.png

  • SceneTexelSize(场景纹素大小):这是一个float2的值,对应着UV,(u,v),uv均为正数。
    这里u就代表着要在横向偏移一个像素单位,uv值中的u需要增加或者减少多少,v也同理
    比如你的屏幕是19201080的,那么你屏幕的横向上就有1920个像素,U的值区间是0~1,
    那么横向上每个像素就占1/1920≈0.000521,同理纵向上每个像素占1/1080≈0.000926,那么SceneTexelSize的值就为(0.000521,0.000926)。
    比如现在有个像素点uv为(0.3,0.5),那么这个像素上方的像素点则为
    (0.3,0.5-0.000926),下方则为(0.3,0.5+0.000926),左右同理。
    *(因为UE4中uv坐标系原点在左上角,所以这里向上为减,向下为加)

image.png

  • 那么我们就可以用这两个节点来计算出每个像素点上下左右四个像素点的自定义深度的和

image.png
从上到下依次是:+u(右侧像素点),-u(左侧像素点),+v(下方像素点),-v(上方像素点)
这里还给了一个Width(宽度)参数,这个参数和SceneTexelSize相乘,就可以调节描边的粗度了。

  • 最后我们再把求的和跟一个近似无穷大的数作比较,就可以确定是否为边了。然后和上一篇确定是否被遮挡的相结合,最终的材质写好了,下面是另一半。

image.png
图太大截不下,此图最左侧Add输出的为上一张截图所求的四个像素点之和。

现在描边已经实现了,如果你进入游戏查看的话,会发现描边是成功了,但是线条一直在抖动或者重影。我们要在材质中找到Blendable Location(可混合位置)并改为Before Tonemapping(在色调映射之前)。
这会对性能产生影响,但可防止视图运动时出现重影。

  • Tone Mapping:作用是把大范围HDR的颜色,映射为小范围的LDR颜色,以便显示器可以正常显示。因为显示器所能显示的颜色范围,小于真实世界的颜色范围。这个我只理解大概意思,没办法细讲了。

image.png
现在进入游戏,应该有这种效果了
image.png
以上内容转载自:https://zhuanlan.zhihu.com/p/81755071
仅供学习参考,无商业用途。