Unity制作二次元材質(zhì)角色
回到目錄
大家好,我是阿趙。
這里繼續(xù)講二次元角色材質(zhì)。這次打算講一下描邊和細節(jié)的添加。
一、外描邊
外描邊的做法也不止一種,比如后處理方法的偏導(dǎo)數(shù)ddx/ddy之類的,也能整個屏幕的求出邊緣。但一般來說單模型渲染常用的描邊方式,是寫多一個Pass,這個Pass是Cull Front的,也就是說是剔除了正面的,然后給模型的頂點沿著法線方向稍微擴大一點,填充成黑色,最后把正常顏色的模型放在黑色模型的重疊位置,那么黑色模型就變成了描邊了效果了。
這個就是Cull Front的Pass的效果。
把2個pass一起渲染,就能得到描邊的效果。
值得注意的是,沿著法線放大模型這一步,頂點坐標(biāo)應(yīng)該在哪個空間里面來做放大呢?
一般來說,使用世界空間肯定是可以的,就是先求出頂點的世界坐標(biāo)和世界法線方向,然后世界坐標(biāo)加上世界法線乘以一個控制大小的值。
但更好的做法,是在觀察空間里面做這個擴展。這是因為,有時候我們想讓這個Cull Front的Pass渲染的黑色模型,可以沿著我們觀察的方向的Z軸做偏移,如果在世界空間坐標(biāo)里面做法線擴展,明顯是很難找到一個軸是可以沿著攝像機方向的。如果我們先把頂點坐標(biāo)和法線都轉(zhuǎn)換到觀察空間,那么在觀察空間里面的Z坐標(biāo),其實就是離我們觀察點的遠近了。
這里有2個做法:
1、在法線轉(zhuǎn)換成觀察空間之后,把法線的z軸直接固定到一個值,這樣模型沿著法線方向擴展時,在z軸會沿著我們想要的方向去放大,比如:
v2f vert(appdata v)
{
v2f o;
float4 pos = mul(UNITY_MATRIX_MV, v.vertex);
float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
normal.z = -0.5;
pos = pos + float4(normalize(normal), 0) * _OutlineLen*0.001;
o.pos = mul(UNITY_MATRIX_P, pos);
return o;
}
2、在觀察空間擴展完法線之后,用一個值來控制偏移后頂點的z軸偏移,比如:
v2f vert(appdata v)
{
v2f o;
float4 pos = mul(UNITY_MATRIX_MV, v.vertex);
float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
pos = pos + float4(normalize(normal), 0) * _OutlineLen*0.001;
pos.z -= v.vertexColor.b;
o.pos = mul(UNITY_MATRIX_P, pos);
return o;
}
我這里用了一個頂點顏色的B通道去偏移Z軸,是因為這是一般的習(xí)慣做法,通過給頂點顏色指定一個通道,作為描邊Z軸偏移的量,這樣的做法,可以實現(xiàn)整個模型不同部位的描邊顯示不一樣。
由于我手上的這個模型并沒有烘焙頂點色,所以沒有頂點通道可以控制,所以我就使用第一種方式來處理。
二、內(nèi)描邊
剛才得到的是外描邊。正常來說,如果項目要求不高,也勉強夠用了。但如果我們想把效果做得更貼近卡通,那么按道理來說,模型除了外部有描邊,模型內(nèi)部應(yīng)該也會有一些描邊的細節(jié)。
回頭看看之前說的ILM貼圖的A通道:
可以發(fā)現(xiàn),這張貼圖提供了模型里面的描邊效果。一般來說,在貼圖上面畫內(nèi)描線,會遇到一個問題:
同樣一張貼圖上,水平或者垂直的線條,會很清晰,但斜線,會起馬賽克,會模糊。
放大我們的這個角色模型,看一下內(nèi)描線的效果,發(fā)現(xiàn)每一內(nèi)描線,基本都很清晰,沒有出現(xiàn)斜線模糊的情況。這是為什么呢?
接下來看一下這張ILM貼圖的A通道,可以發(fā)現(xiàn)一些東西:
會發(fā)現(xiàn),這個內(nèi)描線,基本上都是水平和垂直的線,并沒有出現(xiàn)任何斜線。其實這是一種經(jīng)驗和技巧。在展UV的時候,我們盡量把模型的UV展成這張水平和垂直的方向,這樣在畫這張線條形的貼圖時,線條就能非常的清晰。
這種事情最好是在一開始的時候就規(guī)劃好。如果說實在做不到,因為漫反射貼圖已經(jīng)畫好了,沒法改,那么,我們也可以通過展UV2,特別為這個內(nèi)描線展一張水平垂直的UV。
加上了內(nèi)描邊之后,整體的感覺就豐富了很多了。
三、細節(jié)線條
如果想再進一步添加一些小劃痕或者痕跡,這里還可以添加一張細節(jié)圖:
看得出來,這張圖片并沒有像內(nèi)描邊一樣,講究水平垂直的畫線,整體比較隨意。因為這張貼圖添加的是一些像手繪的筆觸一樣的線條,并不需要非常的整齊和明顯。
把這張圖也加上,那么線條方面的工作就基本完成了:
四、貼花
最后才說貼花,是因為這個貼花是獨立于之前的角色模型貼圖以外的。
本來這個模型是分為了身體和武器(吉他)2個部分的,所以剛才說到的所有圖,包括BaseMap、SSSMap、ILMMap,都是身體一套,武器一套的。但這一張貼花的圖片,卻是身體和武器共用的。
為什么不同的模型可以共用一張貼圖呢?這是因為,這張貼花貼圖,是使用了UV2的,也就是說,把身體和武器需要貼花的部分展成UV2然后合并在一起。至于不需要貼花的部分,UV基本上是縮小到看不見的。
所以這個部分其實也沒什么好說的,直接讀取模型的UV2,然后賦予貼圖就行了。我自己做了一點小改動,因為吉他弦的部分沒有地方控制透明通道,所以我給這張貼花圖片做了個透明通道,讓吉他弦能正常的顯示出來。文章來源:http://www.zghlxwxcb.cn/news/detail-479977.html
加上了貼花之后,整個模型的顯示基本上就完成了。文章來源地址http://www.zghlxwxcb.cn/news/detail-479977.html
五、完整Shader
Shader "azhao/ToonBodyOutline"
{
Properties
{
_BaseMap ("BaseMap", 2D) = "white" {}
_SSSMap("SSSMap", 2D) = "white" {}
_ILMMap("ILMMap", 2D) = "white" {}
_DetailMap("DetailMap",2D) = "white"{}
_specColor("specColor",Color) = (1,1,1,1)
_shininess("shininess", Range(1 , 100)) = 1
_SpecAdd("SpecAdd",float) = 1.0
_GradationMin("GradationMin",Range(0.0,1.0)) = 0.0
_GradationMax("GradationMax",Range(0.0,1.0)) = 1.0
_OutlineColor("OutlineColor",Color) = (0,0,0,1)
_OutlineLen("_OutlineLen",float) = 2
_MatCapTex("MatCapTex", 2D) = "white" {}
_MatCapIntensity("MatCapIntensity",Range(0,2)) = 1
_MatCapPow("MatCapPow",Range(0,5)) = 1
_MatCapUVScale("MatCapUVScale",Range(0,1)) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#pragma multi_compile_fwdbase
#include "AutoLight.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float2 uv2 : TEXCOORD1;
float3 normal:NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float2 uv2 : TEXCOORD1;
float3 worldPos :TEXCOORD2;
float3 worldNormal :TEXCOORD3;
};
sampler2D _BaseMap;
float4 _BaseMap_ST;
sampler2D _SSSMap;
sampler2D _ILMMap;
sampler2D _DetailMap;
float4 _specColor;
float _shininess;
float _SpecAdd;
float _GradationMin;
float _GradationMax;
sampler2D _MatCapTex;
float _MatCapIntensity;
float _MatCapPow;
float _MatCapUVScale;
//獲取HalfLambert漫反射值
float GetHalfLambertDiffuse(float3 worldPos, float3 worldNormal)
{
float3 lightDir = UnityWorldSpaceLightDir(worldPos);
float NDotL = dot(worldNormal, lightDir);
float halfVal = NDotL * 0.5 + 0.5;
return halfVal;
}
//獲取BlinnPhong高光
float GetBlinnPhongSpec(float3 worldPos, float3 worldNormal)
{
float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
float3 halfDir = normalize((viewDir + _WorldSpaceLightPos0.xyz));
float specDir = max(dot(normalize(worldNormal), halfDir), 0);
float specVal = pow(specDir, _shininess);
return specVal;
}
float2 GetMatCapUV(float3 normalWorld)
{
float3 normalView = mul(UNITY_MATRIX_IT_MV, normalWorld);
return normalView.xy*0.5 + 0.5;
}
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _BaseMap);
o.uv2 = TRANSFORM_TEX(v.uv2, _BaseMap);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.worldNormal = UnityObjectToWorldNormal(v.normal);
return o;
}
half4 frag (v2f i) : SV_Target
{
// sample the texture
half4 col = tex2D(_BaseMap, i.uv);
half4 sssCol = tex2D(_SSSMap, i.uv);
half4 ilmCol = tex2D(_ILMMap, i.uv);
half4 detailCol = tex2D(_DetailMap, i.uv2);
//色階化
half halfLambert = GetHalfLambertDiffuse(i.worldPos, i.worldNormal);
half toonVal = smoothstep(_GradationMin, _GradationMax, halfLambert);
half specVal = GetBlinnPhongSpec(i.worldPos, i.worldNormal);
float2 MatCapUV = GetMatCapUV(i.worldNormal)*_MatCapUVScale;
float4 MatCapCol = tex2D(_MatCapTex, MatCapUV)*_MatCapIntensity;
MatCapCol = pow(MatCapCol, _MatCapPow);
half3 finalRGB = col.rgb*toonVal + sssCol * (1 - toonVal)+_specColor* specVal*ilmCol.r+ _specColor * specVal*ilmCol.b*_SpecAdd;
finalRGB = finalRGB * (1-ilmCol.b) +MatCapCol.rgb*ilmCol.b;
finalRGB = finalRGB * ilmCol.a*detailCol.r;
half alpha = col.a;
return half4(finalRGB,alpha);
}
ENDCG
}
Pass
{
Cull Front
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#pragma multi_compile_fwdbase
#include "AutoLight.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal:NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 worldPos :TEXCOORD1;
float3 worldNormal :TEXCOORD2;
};
float4 _OutlineColor;
float _OutlineLen;
v2f vert(appdata v)
{
v2f o;
float4 pos = mul(UNITY_MATRIX_MV, v.vertex);
float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
normal.z = -0.5;
pos = pos + float4(normalize(normal), 0) * _OutlineLen*0.001;
o.pos = mul(UNITY_MATRIX_P, pos);
return o;
}
half4 frag(v2f i) : SV_Target
{
return _OutlineColor;
}
ENDCG
}
}
}
到了這里,關(guān)于Unity制作二次元卡通渲染角色材質(zhì)——4 、內(nèi)外描邊和細節(jié)添加的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!