上篇文章介绍了基础理论概念,本章将带你了解如何用模板缓冲 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 进行比对,1 和 0 显然是不一样的,就抛弃此(黑)颜色,从而保持原有的白色。
不过还有个小问题:那我们怎么得到黑色偏大的模型呢?
在第二个 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"
}三、效果展现
下图就是最终效果啦

使用模板描边和其他描边方法有一个很大的区别就是,在使用同一模板参考值的物体交界处会发现边界融合在一起了。
就如右边胶囊体和圆球的交界处。大家自己可以思考一下为什么噢~(答案下一章揭晓哈哈哈哈哈哈哈哈
四、下一章预告
虚空之门!!



欢迎大佬 来https://unity.cn/articles 上分享你的文章呢
@Jeremy:谢谢dalao推荐哈哈哈...个人打算先在这专心发布完整个系列文章,整理整理后再去搬运嗯..(还有我不是dalao/(ㄒoㄒ)/~~...我也想当哈哈哈哈,可惜离dalao的路还很远啊)
边界融合是不是因为使用了“接近淡出”效果?我用的godot里面有个选项是proximity fade。
@META:欸我没有用过Godot...emm不过我去查了查Godot文档,完整的名字好像叫Proximity and distance fade(接近和距离渐隐).
文档里说到"启用这些功能会启用Alpha混合",所以应该用到的是Alpha混合技术.
我猜可能是当摄像机接近/物体相交时,开启材质Shader的Alpha混合指令(在Unity中是Blend SrcAlpha OneMinusSrcAlpha),并且可能有减少其片元着色器返回颜色的Alpha通道透明度值.
文章中所说的"边界融合",其实只是单单描边相容啦,两个物体本身的(纹理)颜色还是不一样的啦,文章里图片不太明显区分,可惜评论不能上传图片...只能下一章再解释了- -
你这_RefValue默认情况下只能设置为0啊,不然一个pass都过不了吧
@谷某某:嗯..你说的是对的,是我疏忽大意写错了,十分抱歉/(ㄒoㄒ)/~~
@阿创:嗯确实需要写为0,因为默认的Stencil Buffer中的Stencil值是为0,一开始需要和0比较是否相等。如果是其他值,一开始就通过不了。
感谢大佬指正~~
@阿创:文章做了修正
@∞™ ≠ 52Cº:十分感谢~,自己的疏忽给您带来麻烦,十分抱歉.....
有问题吧,多Pass和Stencil没关系啊,这种描边不用Stencil也可以实现,本质就是画了两遍。
最近由 名字不重要a 修改于:2024-09-04 17:33:34@名字不重要a:好久没登indienova了.. 以前活泼幼稚老文看的我老脸一红,居然有最近消息,那回复一波:
是的,通常是通过多次渲染(multi-pass)然后移除正面来实现的,而stencil这是一种不同的实现方式,但同样需要多次渲染来标记模板缓冲区。不能说它们没有关系,正所谓条条大路通罗马,拥有更多的选择总是好的,而且在不同的情况下,它们的适用性也有所不同。