趣投稿|走进 Stencil Buffer 系列 4:Stencil 后处理局部描边


趣投稿|走进 Stencil Buffer 系列 4:Stencil 后处理局部描边
本文插图

一、前言
我们之前已经介绍了一种几何过程式描边方法了 。 几何过程式描边可以很好的为不同模型设置不同的描边参数(描边颜色 , 宽度等等) , 不过也正是如此 , 要为每个模型都额外渲染一遍描边模型 , 性能上花费比较多 。 而有另外一种描边方法就是基于屏幕图像后处理描边方法 , 它只需要对一张屏幕图像进行边缘检测 , 无论模型多么复杂 , 计算量也是恒定的 , 也就节省了性能开销 。
屏幕图形后处理比较常见的是在渲染的最后的阶段 , 拿到屏幕已经渲染的结果(一张 2D 图像) , 再对其进行图像处理 , 这也是“后处理”的这个名字来源 。 不过这样一来对整一张屏幕图像进行处理 , 有些地方我们不太希望被处理的地方也会被“误操作”了 。 比如在下图《英雄联盟(LOL)》游戏里 , 我们只想对英雄与小兵进行描边 , 而场景背景保持不变 。 那我们该怎么办呢?
趣投稿|走进 Stencil Buffer 系列 4:Stencil 后处理局部描边
本文插图

趣投稿|走进 Stencil Buffer 系列 4:Stencil 后处理局部描边
本文插图

上图没有描边 , 下图只针对小兵描边
没错 , 这时又需要请出我们的 Stencil Test 啦![1]
注意因为这是 Stencil系列的文章 , 对于涉及到的屏幕后处理和图像边缘检测算法 , 不会太过于全面地介绍的相关知识 。 如果大家有看不太懂的地方 , 可能需要去查找一些屏幕后处理相关的资料了 。
二、实现思路
我们主要思路是:首先让所有需要描边的物体在渲染的时候 , 将 Stencil 参考值写入 Stencil Buffer 中 。 全部写入完成之后 , 我们就把 Stencil Buffer 提取出来转换成一直图像 , 并使得图像上只有 Stencil 值的地方有颜色 。 然后把这张图像传入屏幕后处理所用自定义提取 Shader 中 , 根据 Sobel 边缘检测算法对其边缘检测 , 检测出边缘后与原屏幕图像进行叠加就完成了 。
我们再来分析一下其中的技术细节 。
1、对于 Stencil参考值写入用一个Stencil指令就 ok 了 。
2、将 Stencil Buffer 提取并转换成图像 。 我们需要借助一张渲染纹理 RenderTexture [2] , 渲染纹理这个名字和“渲染到纹理”技术相关 。 通常渲染结果都是直接输出到屏幕窗口帧缓冲中 , 而渲染到纹理技术 , 可以把渲染结果渲染到一张纹理中(即渲染纹理) 。 这也是屏幕后处理的核心技术 。
通常需要借助 Graphics.Blit (Texture source, RenderTexture dest,Material mat) 函数将屏幕渲染结果通过某个材质的 Shader 处理后搬运到目标渲染纹理中 , 其中 Blit函数会把source设置为材质的 Shader 中的_MainTex 。 而这个 Shader 就是我们提取 StencilBuffer 为图像的关键 。 我们可以对屏幕图像里每一个像素检测 Stencil 值 , 如果相等就渲染一个固定颜色(比如白色RBGA(1,1,1,1)) , 否者就不进行任何渲染(RBGA(0,0,0,0)) , 由此渲染到一张渲染纹理中就完成对 StencilBuffer 提取转换图像 [3] 。
3、 Sobel 边缘检测算法 。 边缘检测的目的是标识数字图像中亮度变化明显的点 , 即对图像用 Soebl 卷积核进行卷积运算 [4] 。 A代表原始图像 , Gx和Gy分别代表经横向及纵向边缘检测的图像,通过以上公式就可以分别计算出横向 和 纵向 的梯度值 , 即Gx和Gy , 梯度值越大 , 边缘就越明显 。
趣投稿|走进 Stencil Buffer 系列 4:Stencil 后处理局部描边
本文插图

Sobel 卷积核算子
三、具体实现
首先建一个场景 , 放一个可爱的小兔子 bunny 还有一个立方体 cube , 并使 bunny 的材质 Shader 中写入 Stencil参考值2 , 但 cube 不写入参考值 。


推荐阅读