走进 Stencil Buffer 系列 1:模型轮廓描边

作者:阿创
2020-04-30
10 4 9

上篇文章介绍了基础理论概念,本章将带你了解如何用模板缓冲 Stencil Buffer 绘制模型轮廓描边效果。

零、前言

轮廓描边是卡通渲染中常用的技术,适当的描边会给人一种卡通漫画的感觉。

轮廓描边的方法有很多种,运用模板缓冲可以绘制模型轮廓描边效果,不过它能产生不同于其他描边方法的效果噢。

上篇文章我们了解了关于模板缓冲和模板测试基础理论概念,本章我们就进行理论的实践,带领大家了解如何用模板缓冲绘制模型轮廓描边效果。

还没看过上篇文章的同学,赶快花个 5 分钟康康吧→

https://indienova.com/u/1149119967/blogread/25692

一、轮廓描边的思路

(1) 手绘练习!

首先大家大家发散想一想,假设我们在纸上有一个白色的圆形噢↓我们能怎么给这个圆形来描边咧?

想好再看哦
嘿嘿

肯定是用手按着圆的边界描一圈啊,像↓这样

肯定有个别手比较抖的同学,因为手太抖了,根本不能很好的描到圆的边界,横七竖八的很难看啊。就像下面那张图一样↓ 额……

好吧,那我们照顾一下手抖的同学吧,我们再想想另外一种办法吧……

欸有了!!!我们拿个大点的圆形尺子,在原来的基础上拿铅笔轻轻地再画一个更大黑色的圆,并填充它,像↓这样

然后,我们小心地拿橡皮擦轻轻地擦掉中间的黑色铅笔痕迹。

铛铛铛!!我们就得到了有黑色描边轮廓的圆辣!!!

好的,我们就结束咯,这就是我们的描边思路。

肯定有同学就会说:不对啊,你这是手绘啊,游戏里的 3D 模型不能用啊,总不能把手伸进去屏幕吧???

别着急嘛,下面我们再想想办法~


(2) 三维联想!

我们发散一下噢~,从二维平面空间拓展到三维立体空间里。

我们首先在 Unity 建一个 3D 白色圆球模型 ,就像下面这样↓

然后,我们再建一个比较大黑色的圆球模型覆盖原来白球,就像这样↓

最后,也就是重点,在我们视角方向看去,挖掉黑色中间表面的面片,露出里面的白球

铛铛铛,出现了!!!轮廓描边效果!!

有同学又说了:“啊!!!我懂了……可是跟模板测试有什么关系的?”

别急别急,来来来,我这就说给你听~


(3) 运用模板测试!

我们在上一章说过咯,模板测试可以再图形渲染出来颜色后,根据模板缓冲的值进行比对,比对不通过就丢去此像素颜色~

在上图中假设我们先建一个白色圆球模型,假设它的参考值是 0,我们再建一个黑色圆球模型,假设它的参考值是 1。然后我们把黑色圆球模型覆盖掉白色模型。

我们现在来分析分析,在没有重合的外围处,肯定是黑色的模型,没错 OK 吧,简单~

然而里面重头戏来了,在他们重合的地方运用模板测试进行比对,1 等不等于 0?不等于 0,好,我们就把黑色丢掉,只露出里面白色。这样就达到了黑色外轮廓,白色内容的模型了,即轮廓描边!

二、程序实现

1. 首先创建 Unlit Shader 和材质


2. 程序思路:

代码上有两个关键点:

 SubShader 下设置 Stencil 两个 Pass 分别用来渲染原本模型和偏大用于描边效果黑色模型。

Stencil{
      Ref [_RefValue]  
      Comp Equal 
      Pass IncrSat
}

_RefValue 就是我们设置的参考值,默认需要写为 0,因为默认的 Stencil Buffer 中的 Stencil 值是为 0,一开始需要和 0 比较是否相等。

Equal 表示当参考值与缓冲值相等时才算通过。

IncrSat 是重点!!!IncrSat 意思时当一个 Pass 渲染完成后,对参考值 +1

第一个 Pass 除了就是正常渲染的模型,还往 Stencil Buffer 里写入了参考值假设为 0 吧。

然后 IncrSat 效果对参考值进行了 +1 操作,这时第二个 Pass 渲染的时候参考值已经是 1 了,在模型重合的地方会和上一个 Pass 写入模板缓冲中的 0 进行比对,10 显然是不一样的,就抛弃此(黑)颜色,从而保持原有的白色。

不过还有个小问题:那我们怎么得到黑色偏大的模型呢?

在第二个 Pass 里黑色模型我们可以在原有模型数据上,对其各个顶点沿法向扩张一点点达到膨胀效果,这样我们就获得比原有还要大一点的模型辣~


3. 具体代码如下

Shader "Unlit/StencilBufferOutline"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color("Color",Color) = (1,1,1,1)
        _RefValue("Stencil RefValue",Int) = 0
        _Outline("Outline Width",Range(0,0.1)) = 0.05
        _OutlineColor("Outline Color",Color) = (0,0,0,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }

        Stencil{
            Ref [_RefValue]
            Comp Equal 
            Pass IncrSat
        }

        Pass
        {
        //..正常渲染原来模型,我们这里只渲染白色
        }

        //渲染偏大用于描边效果黑色模型
        Pass
        {
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #include "UnityCG.cginc"

        struct a2v
        {
               float4 vertex : POSITION;
               float3 normal : NORMAL;
        };

        struct v2f
        {
                float4 pos : SV_POSITION;
        };

        fixed _Outline;
        fixed4 _OutlineColor;

        v2f vert(a2v v)
        {
                v2f o;
                //对其各个顶点沿法向扩张一点点达到膨胀效果
                v.vertex = v.vertex + float4(normalize(v.normal) *_Outline,1);
                o.pos =  UnityObjectToClipPos(v.vertex);
                return o;
        }

        //这里只返回黑色颜色噢
        fixed4 frag (v2f i) : SV_Target
        {
            return _OutlineColor;
        }
        ENDCG
        } 
    }
    FallBack "Diffuse"
}

三、效果展现

下图就是最终效果啦

使用模板描边和其他描边方法有一个很大的区别就是,在使用同一模板参考值的物体交界处会发现边界融合在一起了。

就如右边胶囊体和圆球的交界处。大家自己可以思考一下为什么噢~(答案下一章揭晓哈哈哈哈哈哈哈哈

四、下一章预告

虚空之门!!

近期点赞的会员

 分享这篇文章

阿创 

一句话介绍测试 

您可能还会对这些文章感兴趣

参与此文章的讨论

  1. Jeremy 2020-04-30

    欢迎大佬 来https://unity.cn/articles 上分享你的文章呢

    • 阿创 2020-05-01

      @Jeremy:谢谢dalao推荐哈哈哈...个人打算先在这专心发布完整个系列文章,整理整理后再去搬运嗯..(还有我不是dalao/(ㄒoㄒ)/~~...我也想当哈哈哈哈,可惜离dalao的路还很远啊)

  2. META 2020-04-30

    边界融合是不是因为使用了“接近淡出”效果?我用的godot里面有个选项是proximity fade。

    • 阿创 2020-05-01

      @META:欸我没有用过Godot...emm不过我去查了查Godot文档,完整的名字好像叫Proximity and distance fade(接近和距离渐隐).
      文档里说到"启用这些功能会启用Alpha混合",所以应该用到的是Alpha混合技术.
      我猜可能是当摄像机接近/物体相交时,开启材质Shader的Alpha混合指令(在Unity中是Blend SrcAlpha OneMinusSrcAlpha),并且可能有减少其片元着色器返回颜色的Alpha通道透明度值.

      文章中所说的"边界融合",其实只是单单描边相容啦,两个物体本身的(纹理)颜色还是不一样的啦,文章里图片不太明显区分,可惜评论不能上传图片...只能下一章再解释了- -

  3. 谷某某 2020-06-22

    你这_RefValue默认情况下只能设置为0啊,不然一个pass都过不了吧

    • 阿创 2020-06-23

      @谷某某:嗯..你说的是对的,是我疏忽大意写错了,十分抱歉/(ㄒoㄒ)/~~

  4. 阿创 2020-06-23

    @阿创:嗯确实需要写为0,因为默认的Stencil Buffer中的Stencil值是为0,一开始需要和0比较是否相等。如果是其他值,一开始就通过不了。
    感谢大佬指正~~

    • ∞™ ≠ 52Cº 2020-06-25

      @阿创:文章做了修正

    • 阿创 2020-06-29

      @∞™ ≠ 52Cº:十分感谢~,自己的疏忽给您带来麻烦,十分抱歉.....

您需要登录或者注册后才能发表评论

登录/注册