上篇文章介绍了基础理论概念,本章将带你了解如何用模板缓冲 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