上一篇传送门:
/qq_27534999/article/details/101080649
一、赛璐璐风格简介
有些人可能会问,什么是赛璐珞风格?
赛璐珞是一种合成树脂。最早在制作动画的时候,美国开始使用以赛璐珞制作的透明胶片,把人物画在胶片上面,背景画在纸上面,就可以做到很多不同的效果。
采用赛璐珞来制作的动画也叫“做赛璐珞动画”,在很长一个时期里面几乎是“手绘动画”的代名词。因为现在已经不用赛璐珞制作动画了,所以现在通常指有线稿和颜色的风格。以分层的方式作画(线稿层、明暗层、上色层、特效层)。
当然,具体风格依照动画不同,还是有所差异,这里仅挑选一个最经典的赛璐珞风格进行实现。
为了更直观的说明,这里以经典动画《星际牛仔》为例:
实际去看动画的话,可以发现人物有几个特征:
①黑色描边,较细且粗细均匀
②颜色平涂,并分为暗部亮部,暗部颜色根据环境不同会有所变化
③头发、金属、皮革等特殊部分会有高光、边缘光
④内部结构线、衬线
其中,第④点在本篇中暂时无法解决,一般的做法是直接画贴图上,不过会比较模糊,更好的办法以后再说吧嘻嘻 ~
首先,本篇会在前几篇的基础上改进 Shader 以实现效果,这里先上最终的效果图:
这边模型和背景比较简陋,可能效果不是很好,如果换更好的模型和背景的话效果会挺惊艳,个人感觉这种风格需要优秀美术资源配合才行,比如下图这样,也是用的相同 Shader:
有一点需要注意的是,这类赛璐珞风格渲染纹理不需要不要过多的细节,有时候多用单色色块反而效果会更好。
话不多说,那么接下来开始对之前的 Shader进行改造吧!
二、添加描边
为了方便观察分析,我们还是用之前那个简陋的模型(初学者专用),到上一篇为止的效果如下图所示:
在上一篇 Shader 的基础上,我们来添加描边,这里用一种比较经典的描边方法——顶点外扩(又称法线外扩、Shell 法)。
顶点外扩的原理就是新增一个 Pass,这个 Pass 将模型的顶点沿着法线向外移动一定距离,然后用 Cull Front 剔除正面,残留下来的面正好作为描边。另外,为了让描边在远近都能保持粗细均匀,我们还需要添加一些针对相机距离的计算。
性能优化注意:描边 Pass务必放在 原 Pass 之后,这样就可以享受 Early-Z 带来的优化,防止造成 OverDraw!
该 Pass 代码具体如下:
Pass{Cull FrontCGPROGRAM#pragma vertex vert#pragma fragment frag// make fog work#pragma multi_compile_fog#include "UnityCG.cginc"struct appdata{float4 vertex : POSITION;float2 uv : TEXCOORD0;float3 normal : NORMAL;};struct v2f{float2 uv : TEXCOORD0;UNITY_FOG_COORDS(1)float4 vertex : SV_POSITION;};sampler2D _MainTex;float4 _MainTex_ST;float _Outline;fixed4 _OutlineColor;v2f vert (appdata v){v2f o;//计算与相机的距离,用来保持描边粗细程度float3 posView = mul(UNITY_MATRIX_MV,v.vertex).xyz;float dis = length(posView);float3 normal = v.normal;//顶点沿法线挤出v.vertex = v.vertex + float4(normalize(normal), 0) * _Outline * dis * 0.01;o.vertex = UnityObjectToClipPos(v.vertex);o.uv = TRANSFORM_TEX(v.uv, _MainTex);UNITY_TRANSFER_FOG(o,o.vertex);return o;}fixed4 frag (v2f i) : SV_Target{// apply fogUNITY_APPLY_FOG(i.fogCoord, col);return _OutlineColor;}ENDCG}
效果如下:
三、高光
参考《星际牛仔》的赛璐珞风格,我们不想在皮肤、衣物上有高光,这里可以调整顶点色(详见第三篇)或参数来控制细节,去除皮肤、衣物的高光,或者给金属材质加上高光,,这里就不再赘述。
第三篇顶点色细节控制传送门:/qq_27534999/article/details/100985558
去掉了大部分的边缘光,只保留鞋子部分的,调整后效果如下:
貌似没有原来好看,但为了还原赛璐珞的平涂风格,先这样吧。
四、阴影色
目前为止阴影色只是单纯的降低明度,然而有时候我们需要让阴影色根据环境来改变,这里有几个办法:
1、直接定义一个阴影色属性:
比较简单,就不多说了,将原来的 Shadow Brightness 阴影亮度换成阴影色即可。
2、根据 Lightning Settings 环境光参数进行调整:
#include "Lighting.cginc"...//使用Gradientfixed3 abientSky = unity_AmbientSky.rgb;fixed3 abientEquator = unity_AmbientEquator.rgb;fixed3 abientGround = unity_AmbientGround.rgb;//使用Colorfixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
然后将 Shadow Brightness 阴影亮度换成相应变量即可。
调整后效果如下:
五、成果
另外,开头的另一张效果图,资源可在 Asset Store 免费下(TaichiCharacterPack)。
使用的是相同的Shader(没用顶点色控制),有兴趣的同学可以自己试试:
按惯例,放出完整 Shader 代码如下:
(直接用的话整个模型可能变得全白,这可能是因为模型没有刷顶点色,将 Shader 中 i.color 相关计算删除即可)
Shader "Custom/ToonShadingSimple_v4_SimpleCelluloid"{Properties{[Header(Main)]_MainTex ("Texture", 2D) = "white" {}_Color ("Color", Color) = (1.0, 1.0, 1.0, 1.0)_ShadowColor ("ShadowColor", Color) = (1.0, 1.0, 1.0, 1.0)_RimColor ("RimColor", Color) = (1.0, 1.0, 1.0, 1.0)_ShadowThreshold ("ShadowThreshold", Range(-1.0, 1.0)) = 0.2//赛璐珞风格通常情况不使用边缘光,RimThreshold 可默认为1_RimThreshold ("RimThreshold", Range(0.0, 1.0)) = 1_RimPower ("RimPower", Range(0.0, 16)) = 4.0_Specular ("Specular", Color) = (1, 1, 1, 1)_SpecularScale("Specular Scale", Range(0, 0.1)) = 0.02_EdgeSmoothness("Edge Smoothness", Range(0,2)) = 2_Outline("Outline",Range(0,1))=0.1_OutlineColor("OutlineColor",Color)=(0,0,0,1)}SubShader{Tags { "RenderType"="Opaque" }LOD 100Pass{Cull BackTags { "LightMode"="ForwardBase" }CGPROGRAM#pragma vertex vert#pragma fragment frag// make fog work#pragma multi_compile_fog#include "UnityCG.cginc"#include "Lighting.cginc"struct appdata{float4 vertex : POSITION;float2 uv : TEXCOORD0;float3 normal : NORMAL;fixed4 color : COLOR;};struct v2f{float2 uv : TEXCOORD0;float3 worldNormal : TEXCOORD1;float3 worldPos : TEXCOORD2;UNITY_FOG_COORDS(3)float4 vertex : SV_POSITION;fixed4 color : COLOR;};sampler2D _MainTex;float4 _MainTex_ST;fixed4 _Color;fixed4 _ShadowColor;fixed4 _RimColor;fixed _ShadowThreshold;fixed _RimThreshold;half _RimPower;half _EdgeSmoothness;fixed4 _Specular;fixed _SpecularScale;v2f vert (appdata v){v2f o;o.vertex = UnityObjectToClipPos(v.vertex);o.uv = TRANSFORM_TEX(v.uv, _MainTex);o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;o.color = v.color;UNITY_TRANSFER_FOG(o,o.vertex);return o;}fixed4 frag (v2f i) : SV_Target{//return i.color;fixed3 worldNormal = normalize(i.worldNormal); //法线 Nfixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); //光照方向 Lfixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)); //视角方向 Vfixed3 worldHalfDir = normalize(worldLightDir + worldViewDir); //高光计算用// sample the texturefixed4 col = tex2D(_MainTex, i.uv); fixed spec = dot(worldNormal, worldHalfDir)+(i.color.g-0.5)*2;// w值也可用一个较小的值代替,效果差别不大fixed w = fwidth(spec)*_EdgeSmoothness;fixed4 specular = _Specular * lerp(0,1,smoothstep(-w, w, spec+_SpecularScale-1)) * step(0.001, _SpecularScale);fixed diffValue = dot(worldNormal, worldLightDir)+(i.color.r-0.5)*4;fixed diffStep = smoothstep(-w+_ShadowThreshold, w+_ShadowThreshold, diffValue);fixed4 light = _LightColor0 * 0.5 + 0.5;fixed4 diffuse = light * col * (diffStep + (1 - diffStep) * _ShadowColor) * _Color;// 模仿参考文章的方法,感觉效果不是太好// fixed rimValue = 1 - dot(worldNormal, worldViewDir);// fixed rimStep = step(_RimThreshold, rimValue * pow(dot(worldNormal,worldLightDir), _RimPower));fixed rimValue = pow(1 - dot(worldNormal, worldViewDir)+(i.color.b-0.5)*2, _RimPower);fixed rimStep = smoothstep(-w+_RimThreshold, w+_RimThreshold, rimValue);fixed4 rim = light * rimStep * 0.5 * diffStep * _RimColor;fixed4 final = diffuse + rim + specular;// apply fogUNITY_APPLY_FOG(i.fogCoord, final);return final;}ENDCG}//注意,描边 Pass 放后边,可享受 Early-Z 优化Pass{Cull FrontCGPROGRAM#pragma vertex vert#pragma fragment frag// make fog work#pragma multi_compile_fog#include "UnityCG.cginc"struct appdata{float4 vertex : POSITION;float2 uv : TEXCOORD0;float3 normal : NORMAL;};struct v2f{float2 uv : TEXCOORD0;UNITY_FOG_COORDS(1)float4 vertex : SV_POSITION;};sampler2D _MainTex;float4 _MainTex_ST;float _Outline;fixed4 _OutlineColor;v2f vert (appdata v){v2f o;//计算与相机的距离,用来保持描边粗细程度float3 posView = mul(UNITY_MATRIX_MV,v.vertex).xyz;float dis = length(posView);float3 normal = v.normal;//顶点沿法线挤出v.vertex = v.vertex + float4(normalize(normal), 0) * _Outline * dis * 0.01;o.vertex = UnityObjectToClipPos(v.vertex);o.uv = TRANSFORM_TEX(v.uv, _MainTex);UNITY_TRANSFER_FOG(o,o.vertex);return o;}fixed4 frag (v2f i) : SV_Target{// apply fogUNITY_APPLY_FOG(i.fogCoord, col);return _OutlineColor;}ENDCG}}}
下一篇传送门:
下一篇还没想好要写啥,先这么放着吧
参考文章:
1、萌娘百科 - 赛璐珞
/%E8%B5%9B%E7%92%90%E7%8F%9E
2、【NPR】漫谈轮廓线的渲染
/candycat1992/article/details/45577749