unity 3d物体描边效果_从零开始的卡通渲染描边篇
一直對卡通渲染非常感興趣,前后翻找了不少的文檔,做了一些工作。前段時間《從零開始》的手游上線了,試著渲染了一下的其中模型,覺得效果很不錯。打算寫一個專欄記錄其中的渲染技術。在后面的篇章中也想展示一下各個項目中卡通渲染技術的變遷,以及討論未來的一些發展方向。
卡通渲染屬于非真實感渲染(Non-photorealistic rendering,簡稱NPR)。對應的還有真實感渲染(Photorealistic rendering)。后者旨在渲染真實感的畫面,而前者則追求更加有藝術感的畫面效果,例如手繪風格的畫面。
NPR也有各種各樣的類型。比如像油畫,鉛筆畫,水墨畫風格的畫面。這里主要探討像日本動畫那樣的卡通渲染風格,目前一般稱之為Cel Shading。卡通渲染在日本那邊很早就在主機游戲上使用,經過了很多嘗試和變遷,最終在《GUILTY GEAR Xrd》系列游戲達到了非常不錯的水準。國內的廠商在吸收了日本同行的經驗以后,也制作了非常好的作品,并且在此之上創新,提出了更多的解決方案。
說了很多,現在回歸正題。描邊是卡通渲染的一個非常重要的主題。目前比較流行的描邊方法有兩種,一個是通過兩次繪制,一次繪制角色,一次繪制描邊。還有一種是基于后處理的描邊。基于后處理的描邊相對不容易定制,比較適用于對復雜場景進行描邊。這里講述通過2次繪制來繪制描邊的方法。在《GUILTY GEAR Xrd》中稱其為Back Facing法。
Back facing描邊法
基本思路是通過兩次繪制,第一次繪制角色,第二次繪制描邊。繪制描邊的時候,在頂點著色器將頂點沿著法線方向位移一段距離,使得模型輪廓放大,渲染作為描邊。同時描邊繪制時使用cull front。這樣描邊和角色重疊的部分會因為不能通過深度檢測而cull掉,保證描邊不會遮擋角色。兩次繪制顛倒順序也是可以的,不過后繪制描邊,可以通過深度檢測過濾掉很多描邊繪制的像素,效率會更好。這里先實現最簡單的方法,然后逐步進行優化。
Shader "Unlit/Ouline"{
Properties
{
_OutlineWidth ("Outline Width", Range(0.01, 1)) = 0.24
_OutLineColor ("OutLine Color", Color) = (0.5,0.5,0.5,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
pass
{
Tags {"LightMode"="ForwardBase"}
Cull Back
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float4 vert(appdata_base v): SV_POSITION
{
return UnityObjectToClipPos(v.vertex);
}
half4 frag() : SV_TARGET
{
return half4(1,1,1,1);
}
ENDCG
}
Pass
{
Tags {"LightMode"="ForwardBase"}
Cull Front
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
half _OutlineWidth;
half4 _OutLineColor;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
float4 vertColor : COLOR;
float4 tangent : TANGENT;
};
struct v2f
{
float4 pos : SV_POSITION;
};
v2f vert (a2v v)
{
v2f o;
UNITY_INITIALIZE_OUTPUT(v2f, o);
o.pos = UnityObjectToClipPos(float4(v.vertex.xyz + v.normal * _OutlineWidth * 0.1 ,1));//頂點沿著法線方向外擴
return o;
}
half4 frag(v2f i) : SV_TARGET
{
return _OutLineColor;
}
ENDCG
}
}
}
現在我們用Unity預設的球體進行渲染
看起來描邊效果正常修正攝像機距離問題
現在我們將攝像機拉近,發現攝像機拉近后,描邊變得很粗
攝像機拉近后,描邊顯得很粗這是因為描邊的寬度現在是相對世界空間不變的,這相機拉近后,顯示就會變粗。我們期望無論攝像機拉近拉遠,描邊的粗細都能不變。要解決這個問題,可以通過將法線外擴的大小調整為使用NDC空間的距離進行外擴。這里參考這篇文章對代碼進行一些修改。
v2f o;UNITY_INITIALIZE_OUTPUT(v2f, o);
float4 pos = UnityObjectToClipPos(v.vertex);
float3 viewNormal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal.xyz);
float3 ndcNormal = normalize(TransformViewToProjection(viewNormal.xyz)) * pos.w;//將法線變換到NDC空間
pos.xy += 0.01 * _OutlineWidth * ndcNormal.xy;
o.pos = pos;
return o;描邊的兩邊比較粗,上下比較細,寬度不統一
結果似乎有些問題,描邊的兩邊粗,上下細。這是因為NDC空間的xy是范圍是[0,1]。但是我這里的窗口分辨率是16:9,所以直接用NDC空間的距離外擴,不能適配寬屏窗口。所以需要根據窗口的寬高比再進行修正。這里再對描邊進行修改
v2f o;UNITY_INITIALIZE_OUTPUT(v2f, o);
float4 pos = UnityObjectToClipPos(v.vertex);
float3 viewNormal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal.xyz);
float3 ndcNormal = normalize(TransformViewToProjection(viewNormal.xyz)) * pos.w;//將法線變換到NDC空間
float4 nearUpperRight = mul(unity_CameraInvProjection, float4(1, 1, UNITY_NEAR_CLIP_VALUE, _ProjectionParams.y));//將近裁剪面右上角位置的頂點變換到觀察空間
float aspect = abs(nearUpperRight.y / nearUpperRight.x);//求得屏幕寬高比
ndcNormal.x *= aspect;
pos.xy += 0.01 * _OutlineWidth * ndcNormal.xy;
o.pos = pos;
return o;
描邊的寬度顯示正確了攝像機拉遠以后,顯示的描邊寬度也保持不變
現在描邊可以正確顯示,而且無論攝像機的遠近,描邊的粗細可以保持不變了。
修正不光滑物體斷邊問題
之前我們渲染了unity的預制球體,現在我們換成預制的立方體試一下。
描邊的四角都斷開了嗯…四個角的描邊都斷開了。這方案不行,Pass,放棄,(摔)。改用后處理描邊吧。
咳…因為這個模型每個面的頂點的法線都垂直于這個平面。所以描邊的外擴也是垂直于平面,當模型有轉角的情況下,描邊就會像這樣裂開。Back facing的描邊方法會有這樣的問題。困擾了我一段時間,后來看到一個叫Toony Colors Pro的Unity插件,有了比較好的解決方法。
要解決這個問題,需要對模型外擴使用的法線數據進行修改。這里需要將鄰接面的頂點法線數據,進行平均計算,計算出新的法線寫入模型切線數據中。然后使用這個切線數據進行法線外擴。至于為什么要寫到切線數據里,這是因為只有法線和切線數據會隨著骨骼動畫而改變,如果角色使用了骨骼動畫,就需要寫入切線數據。如果沒有使用骨骼動畫的需求,將數據寫入頂點色中也是可以的。這里我寫了一個編輯器工具,完成對mesh數據的添加。
public class PlugTangentTools{
[MenuItem("Tools/模型平均法線寫入切線數據")]
public static void WirteAverageNormalToTangentToos()
{
MeshFilter[] meshFilters = Selection.activeGameObject.GetComponentsInChildren();
foreach (var meshFilter in meshFilters)
{
Mesh mesh = meshFilter.sharedMesh;
WirteAverageNormalToTangent(mesh);
}
SkinnedMeshRenderer[] skinMeshRenders = Selection.activeGameObject.GetComponentsInChildren();
foreach (var skinMeshRender in skinMeshRenders)
{
Mesh mesh = skinMeshRender.sharedMesh;
WirteAverageNormalToTangent(mesh);
}
}
private static void WirteAverageNormalToTangent(Mesh mesh)
{
var averageNormalHash = new Dictionary();
for (var j = 0; j < mesh.vertexCount; j++)
{
if (!averageNormalHash.ContainsKey(mesh.vertices[j]))
{
averageNormalHash.Add(mesh.vertices[j], mesh.normals[j]);
}
else
{
averageNormalHash[mesh.vertices[j]] =
(averageNormalHash[mesh.vertices[j]] + mesh.normals[j]).normalized;
}
}
var averageNormals = new Vector3[mesh.vertexCount];
for (var j = 0; j < mesh.vertexCount; j++)
{
averageNormals[j] = averageNormalHash[mesh.vertices[j]];
}
var tangents = new Vector4[mesh.vertexCount];
for (var j = 0; j < mesh.vertexCount; j++)
{
tangents[j] = new Vector4(averageNormals[j].x, averageNormals[j].y, averageNormals[j].z, 0);
}
mesh.tangents = tangents;
}
}
同時描邊的方法里,改為使用切線數據作為外擴數據。
float3 viewNormal = mul((float3x3)UNITY_MATRIX_IT_MV, v.tangent.xyz);立方體的描邊也顯示正確了現在這個立方體的模型也可以正確的描邊了。不過這個方法只是臨時修改了mesh數據,如果要保存下來的話。一個可行的方案是使用FBX的SDK來編寫工具,將額外的切線數據寫入模型里。我們試著用兩種方式對角色進行描邊來對比表現。
使用原始法線數據使用平均法線數據對比可以看到,使用新的法線數據進行描邊,模型描邊斷邊的問題少了很多。
然后再添加一點細節
嗯,有那味了,有關光照部分放在下一篇講頂點色的使用
能多放入一些數據,就能增加更多的效果。關于模型頂點色當然也不能浪費。在《GUILTY GEAR Xrd》中使用模型頂點顏色的四個通道,對模型描邊的粗細、顯隱、相機距離縮放等進行了精細的控制。當然頂點數據還可以用來做很多其他的事情,這取決于想要實現的效果,和美術制作的難度。在本篇中,我們使用頂點色控制描邊的粗細和顏色。對代碼進行一些修改。
v2f vert (a2v v){
v2f o;
UNITY_INITIALIZE_OUTPUT(v2f, o);
float4 pos = UnityObjectToClipPos(v.vertex);
float3 viewNormal = mul((float3x3)UNITY_MATRIX_IT_MV, v.tangent.xyz);
float3 ndcNormal = normalize(TransformViewToProjection(viewNormal.xyz)) * pos.w;//將法線變換到NDC空間
float4 nearUpperRight = mul(unity_CameraInvProjection, float4(1, 1, UNITY_NEAR_CLIP_VALUE, _ProjectionParams.y));//將近裁剪面右上角的位置的頂點變換到觀察空間
float aspect = abs(nearUpperRight.y / nearUpperRight.x);//求得屏幕寬高比
ndcNormal.x *= aspect;
pos.xy += 0.01 * _OutlineWidth * ndcNormal.xy * v.vertColor.a;//頂點色a通道控制粗細
o.pos = pos;
o.vertColor = v.vertColor.rgb;
return o;
}
fixed4 frag(v2f i) : SV_TARGET
{
return fixed4(_OutLineColor * i.vertColor, 0);//頂點色rgb通道控制描邊顏色
}
最終的描邊效果 E·M·T
總結
在本節實現了一個不管攝像機距離,可以保持寬度不變的Back Facing描邊方法。優化了Back Facing描邊在不光滑物體出現的破邊問題。實現了通過頂點色數據對描邊進行調整的方法。在下一個章節中,將會討論一些用于卡通渲染的光照計算的實現方法。
聲明:發布此文是出于傳遞更多知識以供交流學習之目的。若有來源標注錯誤或侵犯了您的合法權益,請作者持權屬證明與我們聯系,我們將及時更正、刪除,謝謝。
作者:2173
來源:https://zhuanlan.zhihu.com/p/109101851
More:【微信公眾號】?u3dnotes
總結
以上是生活随笔為你收集整理的unity 3d物体描边效果_从零开始的卡通渲染描边篇的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 这科技感绝了!马英九体验华为鸿蒙智能座舱
- 下一篇: 查找窗口隐藏了怎么办_如何快速查找网站管