UnityShader-边缘检测
UnityShader-边缘检测
卷积
卷积操作指使用一个卷积核(kernel)对一张图像中的每个像素进行一系列操作。卷积核通常是一个四方形的网格结构,该区域内的每个方格都有一个权重值,当对图像中的某个像素进行卷积时,会把卷积核的中心放置到该像素上,翻转核之后再依次计算核中每个元素和其覆盖的图像像素的乘积并求和,得到结果就是该点的新像素值
基于像素差值的边缘判定
如果相邻像素之间存在差别明显的颜色、亮度、纹理等属性,就认为他们之间应该有一条边界,这种像素之间的差值用梯度来表示
在进行边缘检测时,需要对每个像素分别进行一次卷积计算,得到两个方向的梯度值$$G_x$$和$$G_y$$,整体的梯度计算公式为:
$$
G = \sqrt{G_x^2 + G_y^2}
$$
出于性能考虑,有时会使用绝对值操作来代替开根号操作:
$$
G = \abs{G_x} + \abs{G_y}
$$
当得到梯度G后,可以设置一个特定的阈值,超过该阈值的像素判定为边缘;或者建立像素颜色和梯度值之间的函数映射关系,梯度值越大,像素叠加的边缘颜色越深
使用Sobel算子实现
shader的代码实现如下,有一个疑问点在于,如果像素位于图像边缘,如何计算超出图像范围的像素值,经过测试,超出的部分可能取得clamp环绕模式,原因如下:
- 将i.uv[it] +/- dx, dx >=1时,edge = 1,即完全不影响原图,代表邻域中的像素颜色都是相同的
- 如果是repeat模式,则边缘检测的效果应该不会发生变化
当然,另一种情况是采样的像素颜色全是非0值,但我人为上述解释的可能性更高
1 |
|
挂载在脚本上的代码如下:
1 |
|
基于深度和法线的边缘检测
以上方法存在许多缺陷,原因在于,单纯使用颜色信息得到的边缘并不一定真的是世界空间中物体的边缘,如果某个表面上有许多色彩差异较大的色块,依然会被错误地判定为边缘
更好的解决办法是,使用深度和法线纹理进行边缘检测,这样,图像不会受到纹理和光照的影响
使用Roberts算子实现
Roberts算子的使用:计算左上角和右下角的差值,乘以右上角和左下角的差值,作为评估边缘的依据。本实现中,我们会取对角方向的深度或者法线值,比较它们之间的差值,如果超过某个阈值,就认为它们之间存在一条边
shader的代码如下:
1 |
|
挂载到相机上的脚本如下:
1 |
|
对特定物体的描边
如果只想对某个物体描边,可以使用Graphics.DrawMesh或者Graphics.DrawMeshNow函数把需要描边的物体再渲染一遍(在所有不透明物体渲染完毕之后),然后使用边缘检测算法计算每个像素的梯度值,判断是否小于某个阈值,如果是,则在Shader中使用clip()函数将该像素剔除掉,从而获得原来的物体颜色