上一篇传送门:
https://blog.csdn.net/qq_27534999/article/details/100925621
顶点色在卡通渲染中有挺多应用,本篇会在上一篇的基础上,运用模型顶点色来控制细节。塞尔达荒野之息不一定是用这种方法,也可能是用额外的贴图来实现,这里算是抛砖引玉一下,扩展一下思路。(不过这方法效果还挺不错哦!)
用顶点色控制细节还是有很多好处的,首先就是效果比较平滑(毕竟自动插值),之前曾尝试过用额外贴图控制,结果到处都是狗牙锯齿你懂的,挺难受的。
先上顶点色调整后的最终效果:
然后看看之前的效果:
之前的效果还是比较粗糙的,有许多细节不是很完美:
①脖子部分被头遮盖,应该有阴影
②脸部不希望有太多阴影,应尽量保持明亮
③除了头发外,其他地方不希望出现高光
④大腿部分的边缘光不够明显,头发部分的边缘光又过多了
可知,①、②属于阴影问题,③属于高光问题,④属于边缘光问题。
有些问题,可以通过拆分模型,给不同的材质球参数来解决,但是这样会让材质球个数变多,从而SetPassCall变多,在移动端上可能造成性能问题。因此,顶点色处理无疑是个好办法,我们可以用顶点色的R、G、B通道分别控制阴影、高光、边缘光。
接下来将一步步讲解如何用顶点色来对细节做修正:
一、准备模型
首先确认模型是否带有顶点色信息,在 Unity 内选中模型 Mesh 资源,即可在右下角观察。
如果是跟我一样下载的模型的话,很遗憾地会发现只有 uv 和 skin ,没有顶点色信息,这里得自己用 3ds max 或 maya 重新导出一下(随意指定一下顶点色后导出即可)。
之后,观察到窗口里有 colors 就没问题了。
二、准备工具
可以直接在 3ds Max 或者 Maya 里绘制顶点色,不过个人还是习惯在 Unity 里绘制,这样也方便观察调整。
(想在 3dsMax 里刷顶点色的请看下一篇:https://blog.csdn.net/qq_27534999/article/details/101080649)
一般这类工具会有公司的程序专门来开发,如果没有的话,可以去 Unity 的 Asset Store 搜索插件来用,这边推荐一个 TOZ Vertex Painter,小巧且免费。
插件导入后,在 Window -> TOZ -> Tools -> Vertex Painter 打开面板,如下图所示:
此时点击模型时,窗口可能会报错。会提示模型这边需要添加 Mesh Collider,且要使用 Mesh Filter 和 Mesh Renderer (这个插件不支持 Skinned Mesh Renderer),这些都要处理一下。模型设置如下图所示:
这时候再选中模型,就可以愉快地刷顶点色了~!刷完后点击 SAVE NEW MESH 会另存为一个新的模型文件。
三、Shader 修改
接下来是 Shader 的修改,获取顶点色信息然后对阴影、高光做出调整。
首先,在结构体中加入顶点色,当然别忘了 vert 中要赋值:
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;
};
...
v2f vert (appdata v)
{
v2f o;
...
o.color = v.color;
...
return o;
}
然后是针对阴影、高光、边缘光的计算略有修改,R通道控制阴影,G通道控制高光,B通道控制边缘光。
R通道:原点乘值区间为[-1,1],这里我希望控制阴影顶点色的修改能够覆盖这个区间,因为有些部位我希望永远是暗部,这样最极端的情况是把点乘值-1修正为1,这里用 (顶点色 - 0.5)* 4。
G、B通道:高光、边缘光两个通道的点乘值,稍微修正就好,不需要太大的调整,因此这里用 (顶点色 - 0.5)* 2。
fixed diffValue = dot(worldNormal, worldLightDir)+(i.color.r-0.5)*4;
...
fixed spec = dot(worldNormal, worldHalfDir)+(i.color.g-0.5)*2;
...
fixed rimValue = pow(1 - dot(worldNormal, worldViewDir+(i.color.b-0.5)*2), _RimPower);
这样,Shader的修改就完成了。
四、刷顶点色
刚改完Shader后,视觉表现会很奇怪(见左图),这是因为顶点色默认黑色(0, 0, 0)。
如果要恢复到原来的状态,根据我们之前修改 Shader 的公式,只要将所有顶点色涂上灰色(0.5, 0.5, 0.5)即可(见左图)。
接下来着手解决之前提出的四个问题,根据我们的需求,对顶点色RGB进行调整即可。
①脖子部分被头遮盖,应该有阴影 —— 脖子部分 R 值为0
②脸部不希望有太多阴影,应尽量保持明亮 —— 脸部 R 值调高
③除了头发外,其他地方不希望出现高光 —— 除头发外其他部位 G 值为0
④大腿部分的边缘光不够明显,头发部分的边缘光又过多了 —— 大腿 B值调高,头发 B值调低
调整后的效果如下图所示:
效果明显比之前要好多了~!
五、成果
按照惯例,最后给出完整 Shader 如下:
Shader "Custom/ToonShadingSimple_v3"
{
Properties
{
[Header(Main)]
_MainTex ("Texture", 2D) = "white" {}
_Color ("Color", 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
_ShadowBrightness ("ShadowBrightness", Range(0.0, 1.0)) = 0.6
_RimThreshold ("RimThreshold", Range(0.0, 1.0)) = 0.35
_RimPower ("RimPower", Range(0.0, 16)) = 4.0
_Specular ("Specular", Color) = (1, 1, 1, 1)
_SpecularScale("Specular Scale", Range(0, 0.1)) = 0.02
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
Cull Back
Tags { "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 _RimColor;
fixed _ShadowThreshold;
fixed _ShadowBrightness;
fixed _RimThreshold;
half _RimPower;
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); //法线 N
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); //光照方向 L
fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)); //视角方向 V
fixed3 worldHalfDir = normalize(worldLightDir + worldViewDir); //高光计算用
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
fixed spec = dot(worldNormal, worldHalfDir)+(i.color.g-0.5)*2;
// w值也可用一个较小的值代替,效果差别不大
fixed w = fwidth(spec)*2.0;
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) * _ShadowBrightness) * _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 fog
UNITY_APPLY_FOG(i.fogCoord, final);
return final;
}
ENDCG
}
}
}
谢谢观赏~!
应美术需求,下一篇将讲解如何在 3dsMax 里加载自定义 Shader 刷顶点色
下一篇传送门:
https://blog.csdn.net/qq_27534999/article/details/101080649
参考资料:
没有具体参考资料,算是对顶点色应用的一次尝试吧