日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

unity 3d物体描边效果_从零开始的卡通渲染描边篇

發布時間:2023/12/19 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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物体描边效果_从零开始的卡通渲染描边篇的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。