拆一下漫画风Shader的流程
本来想录个视频的,但是录完才发现没录声音,遂作罢,把自己公众号的文章搬过来发了。


这玩意的使用价值目前不是特别大,因为摩尔纹特别重,回头加个LOD会好很多。
拆效果 |
按例先拆一手效果,看看漫画长啥样。这边选的是JOJO的漫画,因为jojo里面光影的刻画比较细节,不同的光影有不同的处理手法,有些漫画阴影都是网点纸处理,可能细节就不太够。

可以基本拆出来就是几个部分:
1. 光到暗的部分,光的部分是纯白,浅色阴影的部分是浅色的网点纸过渡
2. 深色阴影是单排斜线或着双排斜线,最黑的地方是纯黑
3. 本色纹理部分,深色衣服以网点纸打底,褶皱和更深的部分是纯黑色
4. 阴影部分,以阴影轮廓线勾勒,以网点纸或者斜线涂色
拆出来效果之后就可以着手制作了。
原理 |
本质就是在屏幕UV上玩花样而已,没有太多新的东西。屏幕UV的部分可以参考庄佬的1。在我们获取屏幕UV之后,就可以着手进行网点纸和斜线的制作了,做法有两种:程序化生成或者用纹理,我这里是用程序化的方法做的,只能说吃力不讨好,理论上拿纹理做会简单很多,也省内存,也不用写很多代码,你说我这图啥呢。
网点纸的做法:
取得了屏幕UV之后,因为UV都是连续的数,所以只需要对屏幕UV取个小数,就可以取得以下的效果:

有这玩意就简单了,对每个像素取一下到中心的距离再step一手:
fixed spot = smoothstep(
spotScale-0.1,
spotScale+0.1,
distance(fixed2(0.5,0.5), fragCoord));
具体原理就是,到顶点的固定距离是一个圆,没step之前在屏幕上是这个样子的:

取完Step就可以得到最基本的网点图了,而这个step的数值就可以作为网点的大小取值:

如果用lambert作为step值,就能得到一个这样的图,这个就可以作为网点纸的基础了:

//波点部分
half2 fragCoord = scrUV * originDist * 0.001 * _SpotDensity;
fragCoord.x = frac(fragCoord.x);
fragCoord.y = frac(fragCoord.y);
fixed spot = smoothstep(spotScale-0.1, spotScale+0.1,distance(fixed2(0.5,0.5), fragCoord));
spot *= _ToonScale;
斜排线的做法:
和网点纸的思路差不多,不过斜排线需要一个角度去控制排线的方向,角度的思路就是我在屏幕上随便定一个向量,然后求屏幕上UV到这个向量的投影距离,之后取个小数,就可以取到斜排的0到1的取值了,之后对这个取值step就可以得到排线了,同样的,这个step的值也可以作为粗细来使用。

half2 dir2D = half2(cos(radians(_Angle)),sin(radians(_Angle)));
float dist = dot(dir2D, fragCoord);


(其实这些一张图就解决了,何必呢)
和得到网点纸不同,斜排线我们还需要添加一些手绘的效果,比如手绘的线条会有一定的扭曲,断开,排线会不整齐,同时会有细小的碎线来表示粗糙的部分等等。先放一张没有添加效果的纯排线:

手绘线条的扭曲,断开和粗细变化:这个很简单,用一张噪声去干扰角度,最终颜色和粗细的取值就可以了,其实也看不出来,所以写成了#if,不需要的时候可以不勾选:
//线的扭曲
float2 toonDistortUV = fragCoord.xy *_DistortScale * 0.03;
fixed DistortionL = saturate(tex2D(_ToonNoiseTex, toonDistortUV).r);
fixed DistortionS = saturate(tex2D(_ToonNoiseTex, toonDistortUV*6).g);
DistortionL = Remap(0,1,-0.5,0.5, DistortionL);
DistortionS = Remap(0,1,-0.5,0.5, DistortionS);
fixed Distortion = DistortionL * 4 + DistortionS;
_Angle -= Distortion * _DistortionIntensity;

//线的断开
float2 toonBreakUV = fragCoord.xy * _Density * 0.07;
fixed BreakTex = saturate(tex2D(_ToonNoiseTex, toonBreakUV).r);
Break = step(_BreakAmount, BreakTex);

//粗细随机
float2 thicknessRUV = fragCoord.xy * 0.8;
Thicken = tex2D(_ToonNoiseTex, thicknessRUV).r;
Thicken = Remap(0,1,-0.1,0.1,Thicken);
Thicken = lerp(0.0,Thicken,_ThicknessRIntensity);

手绘线条的排线随机:
这个需要在光影上做手脚,把兰伯特的光照部分和噪声图除一下,就可以得到一张这样的mask:

这个边缘就有很好的随机性,别问我为什么是除以,拿DS一个个试试出来的。
用这个mask去叠排线,就可以得到这样的效果:
细小的碎线:这里需要一张cell噪声图,随便在DS做一张:

然后用这张图去干扰角度值,来得到不同方向的排线:

最后用噪声贴图去mask一下就行了,不是特别的复杂:

最后得到的排线Shader的效果:
叠加上网点纸就能得到一个基本的漫画Shader了:

阴影的做法:阴影的原理就是先通过SHADOW_ATTENUATION(i)取得最基础的阴影范围,之后就可以用两个step卡出阴影线然后给一个粗细随机:

然后可以根据阴影的范围给一层网点纸,就能得到阴影了。

texture的部分是基于原有的texture,把荒木线提出来之后,对衣服的部分用step卡出阴影给纯黑,其余部分给一个网点就行了。
综合一下以上的所有乱七八糟的效果,就能得到最终的效果了:

再下个街道模型,给上shader,给个后处理的速度线:

承太郎可tm太帅了。