深度扫描

经典效果
这是死亡搁浅里面的一个经典效果,以人物为中心,扫描周围的环境。我想复刻一下这个根据人物为中心,扫描周围的效果。

思路分析

第一眼看上去就跟自身位置有关,并且随着距离的增加发现每个物体的边缘都显现了出来。但是我总不能在所有模型里面加一个材质专门做这个事情吧,所以目前想法就是后处理做这件事,那么目前思路如下。

  • 通过屏幕坐标还原世界坐标。
  • 根据世界坐标生成一个圈,然后根据距离变动。
  • 修改这个圈附近的效果,将边缘识别放进去。
  • 美化一下,增加一下颜色等效果。

还原世界坐标

通过屏幕坐标还原世界坐标方法还蛮多的。

  • 暴力还原,如果先暂时不考虑性能,通过 NDC 暴力还原回去。
  • 一开始就考虑性能,通过相似三角形和向量加减在 Vert 里面做,然后在 Frag 里得到差值结果。

暴力还原

从 World Position 到 View Position 到 Clip Position 到 Screen Position,一步一步往回推。

  • 第一步先求 NDC 坐标,在后处理 Shader 中,Frag 函数内拿到的 UV 坐标就是屏幕范围了,注意这里并不是屏幕坐标,这里的 UV 大小取值区间是 [0,1],而 NDC 坐标Unity 用的是 OpenGL 的传统,所以求 NDC 坐标要映射回 [-1,1]
1
float4 ndc = float4(i.uv.x * 2 - 1, i.uv.y * 2 - 1, depthTextureValue * 2 - 1, 1);
  • NDC 的 Z 坐标就是深度缓冲中的值,W 坐标就是1,但是从深度缓冲中拿值的时候注意线性问题。
  • 已知条件(M为VP矩阵,M^-1即为其逆矩阵,Clip为裁剪空间,ndc为标准设备空间,world为世界空间):
  • ndc = Clip.xyzw / Clip.w = Clip / Clip.w
  • world = M^-1 * Clip
  • 二者结合得:world = M ^-1 * ndc * Clip.w
  • 我们已知M和ndc,然而还是不知道Clip.w,但是有一个特殊情况,是world的w坐标,经过变换后应该是1,即
  • 1 = world.w = (M^-1 * ndc).w * Clip.w
  • 进而得到Clip.w = 1 / (M^ -1 * ndc).w
  • 带入上面等式得到:
  • world = (M ^ -1 * ndc) / (M ^ -1 * ndc).w
  • 然后要根据当前位置进行距离计算,所以需要在 C# 端传入摄像机的距离。
1
postEffectMat.SetVector("_CameraPos", transform.position);
  • 然后定义一个宽度,在这个宽度内显示一个颜色,用来表示当前扫描的进度。

  • 到目前为止的效果图。
    效果图

  • 结下来就是进行这个宽度内的内容识别,显示出模型的边缘颜色。

边缘识别

边缘识别可以用 Sobel 算子卷积的方式识别。Shader Book 入门精要 12 章里面有讲,可以直接拿来用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
half Sobel(v2f i)
{
const half Gx[9] = {-1, 0, 1,
-2, 0, 2,
-1, 0, 1};
const half Gy[9] = {-1, -2, -1,
0, 0, 0,
1, 2, 1};
half texColor;
half edgeX = 0;
half edgeY = 0;

// 这里的 uvArray 就是当前像素点和周围的八个方向的像素点的数组
for (int it = 0; it < 9; it++) {
texColor = luminance(tex2D(_MainTex,i.uvArray[it]));
edgeX += texColor * Gx[it];
edgeY += texColor * Gy[it];
}
half edge = 1 - abs(edgeX) - abs(edgeY);
return edge;
}

成果图

感觉我这个场景似乎不是特别能体现边缘,如果觉得边缘不是很明显的话,可以稍微调整一下最后 Lerp 的分布区域,用 SmoothLerp 或者 Pow 都行,让靠近边缘的区域更明显一点。

参考链接