永远相信美好的事情即将发生 😊!

卡通渲染之边缘光的实现(URP)

分享 Mavis 29℃ 0评论

今天就来弄一弄边缘光,没记错的话还是在学习了大佬的文章后对自己的边缘光进行了改进,文章链接:https://zhuanlan.zhihu.com/p/111633226

先看一下加边缘光和不加的对比图吧:

本来我的边缘光弄得超级超级简单……代码如下,现在回过头看看,,,有点想撞墙,,,

//计算RimDir
float3 rim =saturate(dot(worldViewDir,normalWS));
float3 rimColor = rim>_RimRange?float3(0,0,0):_RimColor;
if (dot(normalWS,float3(0,-1,0))>0.9)
    rimColor = float3(0,0,0);

虽然代码丑了点,,但效果还算过得去……

不过学习了大佬的文章后,我的边缘光更好看了我觉得,起码代码好看了很多,哈哈哈哈

//RimDir
float4 Rim = float4(0,0,0,0);
#if _ENABLE_RIM_ON
    float rim =  1.0 - saturate(dot(viewDirWS, input.normalWS));//法线与视线垂直的地方边缘光强度最强     
    rim = smoothstep(1-_RimWidth, 1, rim);
    rim = smoothstep(0, _RimSmoothness, rim);
    Rim = rim * _RimColor *  _RimIntensity;
    float4  RimBrush = tex2D(_RimBrushPatterns, input.uv * _RimBrushPatterns_ST.xy + _RimBrushPatterns_ST.zw);
    Rim = lerp(Rim, RimBrush * Rim, _RimBrushStrengh);
    Rim *= Mask.g;
 #endif

现在来解释一下代码部分的吧,对了,还参考了米哈游的技术分享

1、_ENABLE_RIM_ON用来开启边缘光效果这个很好理解,至于这个开关怎么实现的就稍微讲一下吧,首先在属性那儿加上一个float值的属性,然后最前面加个前缀[Toggle],这样属性在面板中显示的时候就会变成一个框,有选中和没选中两种状态,对应1和0值。当选中这个框以后,unity会自动对该材质进行关键字的定义,生成的关键字名为属性名后面加个后缀_ON,对于材质目前已经拥有的的关键字可以在材质面板的Debug模式下查看到

当一个关键字被定义了,且该shader明确需要使用该关键字(用shader_feature标注一下),这个开关就算成了。

通常还可以在脚本中设置,这部分官方手册有说明,就不解释了,直接查shader_feature或者multi_compile就可以看到相关信息

2、float rim = 1.0 - saturate(dot(viewDirWS, input.normalWS));//法线与视线垂直的地方边缘光强度最强

这句代码很容易理解,就视线和法线的夹角来确定rim的强度

3、rim = smoothstep(1-_RimWidth, 1, rim);

这句代码是通过smoothstep去平滑边缘

这里还要讲讲smoothstep的用法,我觉得网上的大佬都讲的挺好的,我就再讲讲自己怎么理解这个函数的,首先从函数的内部结构看起,这个网址可以查到很多函数的具体实现(https://developer.download.nvidia.cn/cg/index_stdlib.html):

float smoothstep(float a, float b, float x)
{ 
float t = saturate((x - a)/(b - a)); 
return t*t*(3.0 - (2.0*t)); 
}

先看第一句:float t = saturate((x – a)/(b – a));   把 abx看做数轴上的三个点,这句话的意思就是x离a的距离比上ba之间的距离,当这个值小于等于1大于等于0的时候返回本身,大于1返回1,小于0返回0,也就是这句话会把X控制在ab范围内,并且返回其在这个区间的位置比例,例如x取0.5,a取0.2, b取0.7;t=0.3/0.5=0.6,总结来说,这个函数返回的值跟这三个值的实际大小没关系,只跟这三个值的数轴位置有关系,

当x是[0,1]内的任何数的时候,a取0.2, b取0.7

则x∈[0,0.2]时,t为0,x∈[0.7,1]时,t为1,x∈[0.2,0.7]时,t∈[0,1]

所以t只会返回0到1的值,而这个值表示的是x在ab这个区间内的位置。然后再看一下t*t*(3.0 – (2.0*t))的函数图像:

因为已经明确了t值的范围,那么这个函数的图像我们也只要看0-1区间就好了,很明显,返回的是一个0-1的值,可以看出,这个曲线和线性的直线不同点在于斜率有变化,在靠近0和1的部分,函数值的变化比较缓慢,靠近中间的部分,函数值的变化会比较快,根据前面分析得到的,x坐标也就是t值代表的是x在ab区间的位置,假如x值在其范围内是线性变化的,那么通过smoothstep函数就会让它在给定范围的边界处变化柔和一些,这也就是为什么smoothstep通常用来处理边界的原因。

讲解完这个函数,来看一下我们这个函数的输入:a:1-RimWidth  b:1  x : rim

当RimWidth=0时,我们看一下加了平滑和不加的对比:

rim可以知道是在0-1中线性变化的一个值,通过对比图很明显的发现中间靠近0的位置在使用了smoothstep后变化很小,而没有使用的颜色变化很均匀。

再加入RimWidth的变化,这个值在我的代码中就是用来控制Rim的宽度的,也就是用来控制前面提到的边界,因为我这个返回值是要更新rim值,所以代表着rim值只有在处于该边界内的时候才能有变化,在边界外则要么为0,要么为1;

举个例子:

1-RimWidth=0.5的时候,A部分的Rim是从0-1平滑渐变的,B的部分Rim值为0;这就是边界值的在我这个代码中的控制作用插播一条分享内容:

数学表达式图形化的网站,忘了从哪个大佬那儿收藏的了,总之还是要传递知识,传递快乐!

https://www.desmos.com/calculator?lang=zh-CN

https://www.geogebra.org/graphing

https://graphtoy.com/

http://tobyschachman.com/Shadershop/editor/

4、 rim = smoothstep(0, _RimSmoothness, rim);

这句代码中的函数原理已经讲了,这里就不重复了,简单来说,这句话会让rim取值范围为[_RimSmoothness,1]的值都统一为1;然后在rim值属于[0, _RimSmoothness]的范围内时平滑变化。(针对的是rim已经经过上面那句话的平滑了)

再总结一下这两句代码,第一句用来规范左边界,在左边界至1的区间内平滑

第二句用来规范右边界,在0至右边界的区间内平滑,这两句是可以写在一起,但是因为只有当b>=a时效果才正确,所以分开写会更简洁明了一些

用图片来解释这两句代码的作用如下:C部分Rim值为0,由1-RimWidth控制,A部分Rim值为1,由Smoothness控制,B部分由这两个值控制,值在0-1中平滑过渡,好吧,写到这,我突然觉得命名似乎不太好……先不改了

5、 Rim = rim * _RimColor * _RimIntensity;

好了,计算好了rim值,rim只是用来控制初始的强度,然后乘以一个颜色值来确定边缘光的颜色,再用一个外部的强度值来更加细腻的控制强度

6、float4 RimBrush = tex2D(_RimBrushPatterns, input.uv * _RimBrushPatterns_ST.xy + _RimBrushPatterns_ST.zw);

当前的边缘光还是非常均匀的,但是想要一些更好的效果,可以加一些笔刷贴图来增加效果

7、Rim = lerp(Rim, RimBrush * Rim, _RimBrushStrengh);

根据笔刷的强度进行渐变

8、Rim *= Mask.g;

最后还可以通过Mask贴图的g通道(我使用这个通道而已,可以使用其他的贴图值)来调整

至此边缘光的实现效果就完成了。


Mavis , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:卡通渲染之边缘光的实现(URP)
喜欢 (3)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址