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

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

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

發(fā)布時(shí)間:2023/12/19 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 unity 3d物体描边效果_从零开始的卡通渲染描边篇 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
序言:

一直對(duì)卡通渲染非常感興趣,前后翻找了不少的文檔,做了一些工作。前段時(shí)間《從零開(kāi)始》的手游上線了,試著渲染了一下的其中模型,覺(jué)得效果很不錯(cuò)。打算寫一個(gè)專欄記錄其中的渲染技術(shù)。在后面的篇章中也想展示一下各個(gè)項(xiàng)目中卡通渲染技術(shù)的變遷,以及討論未來(lái)的一些發(fā)展方向。

卡通渲染屬于非真實(shí)感渲染(Non-photorealistic rendering,簡(jiǎn)稱NPR)。對(duì)應(yīng)的還有真實(shí)感渲染(Photorealistic rendering)。后者旨在渲染真實(shí)感的畫面,而前者則追求更加有藝術(shù)感的畫面效果,例如手繪風(fēng)格的畫面。

NPR也有各種各樣的類型。比如像油畫,鉛筆畫,水墨畫風(fēng)格的畫面。這里主要探討像日本動(dòng)畫那樣的卡通渲染風(fēng)格,目前一般稱之為Cel Shading。卡通渲染在日本那邊很早就在主機(jī)游戲上使用,經(jīng)過(guò)了很多嘗試和變遷,最終在《GUILTY GEAR Xrd》系列游戲達(dá)到了非常不錯(cuò)的水準(zhǔn)。國(guó)內(nèi)的廠商在吸收了日本同行的經(jīng)驗(yàn)以后,也制作了非常好的作品,并且在此之上創(chuàng)新,提出了更多的解決方案。

說(shuō)了很多,現(xiàn)在回歸正題。描邊是卡通渲染的一個(gè)非常重要的主題。目前比較流行的描邊方法有兩種,一個(gè)是通過(guò)兩次繪制,一次繪制角色,一次繪制描邊。還有一種是基于后處理的描邊。基于后處理的描邊相對(duì)不容易定制,比較適用于對(duì)復(fù)雜場(chǎng)景進(jìn)行描邊。這里講述通過(guò)2次繪制來(lái)繪制描邊的方法。在《GUILTY GEAR Xrd》中稱其為Back Facing法。

  • Back facing描邊法

  • 基本思路是通過(guò)兩次繪制,第一次繪制角色,第二次繪制描邊。繪制描邊的時(shí)候,在頂點(diǎn)著色器將頂點(diǎn)沿著法線方向位移一段距離,使得模型輪廓放大,渲染作為描邊。同時(shí)描邊繪制時(shí)使用cull front。這樣描邊和角色重疊的部分會(huì)因?yàn)椴荒芡ㄟ^(guò)深度檢測(cè)而cull掉,保證描邊不會(huì)遮擋角色。兩次繪制顛倒順序也是可以的,不過(guò)后繪制描邊,可以通過(guò)深度檢測(cè)過(guò)濾掉很多描邊繪制的像素,效率會(huì)更好。這里先實(shí)現(xiàn)最簡(jiǎn)單的方法,然后逐步進(jìn)行優(yōu)化。

    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));//頂點(diǎn)沿著法線方向外擴(kuò)
    return o;
    }

    half4 frag(v2f i) : SV_TARGET
    {
    return _OutLineColor;
    }
    ENDCG
    }
    }
    }

    現(xiàn)在我們用Unity預(yù)設(shè)的球體進(jìn)行渲染

    看起來(lái)描邊效果正常

    修正攝像機(jī)距離問(wèn)題

    現(xiàn)在我們將攝像機(jī)拉近,發(fā)現(xiàn)攝像機(jī)拉近后,描邊變得很粗

    攝像機(jī)拉近后,描邊顯得很粗

    這是因?yàn)槊柽叺膶挾痊F(xiàn)在是相對(duì)世界空間不變的,這相機(jī)拉近后,顯示就會(huì)變粗。我們期望無(wú)論攝像機(jī)拉近拉遠(yuǎn),描邊的粗細(xì)都能不變。要解決這個(gè)問(wèn)題,可以通過(guò)將法線外擴(kuò)的大小調(diào)整為使用NDC空間的距離進(jìn)行外擴(kuò)。這里參考這篇文章對(duì)代碼進(jìn)行一些修改。

    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;描邊的兩邊比較粗,上下比較細(xì),寬度不統(tǒng)一

    結(jié)果似乎有些問(wèn)題,描邊的兩邊粗,上下細(xì)。這是因?yàn)镹DC空間的xy是范圍是[0,1]。但是我這里的窗口分辨率是16:9,所以直接用NDC空間的距離外擴(kuò),不能適配寬屏窗口。所以需要根據(jù)窗口的寬高比再進(jìn)行修正。這里再對(duì)描邊進(jìn)行修改

    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));//將近裁剪面右上角位置的頂點(diǎn)變換到觀察空間
    float aspect = abs(nearUpperRight.y / nearUpperRight.x);//求得屏幕寬高比
    ndcNormal.x *= aspect;
    pos.xy += 0.01 * _OutlineWidth * ndcNormal.xy;
    o.pos = pos;
    return o;
    描邊的寬度顯示正確了攝像機(jī)拉遠(yuǎn)以后,顯示的描邊寬度也保持不變

    現(xiàn)在描邊可以正確顯示,而且無(wú)論攝像機(jī)的遠(yuǎn)近,描邊的粗細(xì)可以保持不變了。

    修正不光滑物體斷邊問(wèn)題

    之前我們渲染了unity的預(yù)制球體,現(xiàn)在我們換成預(yù)制的立方體試一下。

    描邊的四角都斷開(kāi)了

    嗯…四個(gè)角的描邊都斷開(kāi)了。這方案不行,Pass,放棄,(摔)。改用后處理描邊吧。

    咳…因?yàn)檫@個(gè)模型每個(gè)面的頂點(diǎn)的法線都垂直于這個(gè)平面。所以描邊的外擴(kuò)也是垂直于平面,當(dāng)模型有轉(zhuǎn)角的情況下,描邊就會(huì)像這樣裂開(kāi)。Back facing的描邊方法會(huì)有這樣的問(wèn)題。困擾了我一段時(shí)間,后來(lái)看到一個(gè)叫Toony Colors Pro的Unity插件,有了比較好的解決方法。

    要解決這個(gè)問(wèn)題,需要對(duì)模型外擴(kuò)使用的法線數(shù)據(jù)進(jìn)行修改。這里需要將鄰接面的頂點(diǎn)法線數(shù)據(jù),進(jìn)行平均計(jì)算,計(jì)算出新的法線寫入模型切線數(shù)據(jù)中。然后使用這個(gè)切線數(shù)據(jù)進(jìn)行法線外擴(kuò)。至于為什么要寫到切線數(shù)據(jù)里,這是因?yàn)橹挥蟹ň€和切線數(shù)據(jù)會(huì)隨著骨骼動(dòng)畫而改變,如果角色使用了骨骼動(dòng)畫,就需要寫入切線數(shù)據(jù)。如果沒(méi)有使用骨骼動(dòng)畫的需求,將數(shù)據(jù)寫入頂點(diǎn)色中也是可以的。這里我寫了一個(gè)編輯器工具,完成對(duì)mesh數(shù)據(jù)的添加。

    public class PlugTangentTools
    {
    [MenuItem("Tools/模型平均法線寫入切線數(shù)據(jù)")]
    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;
    }
    }

    同時(shí)描邊的方法里,改為使用切線數(shù)據(jù)作為外擴(kuò)數(shù)據(jù)。

    float3 viewNormal = mul((float3x3)UNITY_MATRIX_IT_MV, v.tangent.xyz);立方體的描邊也顯示正確了

    現(xiàn)在這個(gè)立方體的模型也可以正確的描邊了。不過(guò)這個(gè)方法只是臨時(shí)修改了mesh數(shù)據(jù),如果要保存下來(lái)的話。一個(gè)可行的方案是使用FBX的SDK來(lái)編寫工具,將額外的切線數(shù)據(jù)寫入模型里。我們?cè)囍脙煞N方式對(duì)角色進(jìn)行描邊來(lái)對(duì)比表現(xiàn)。

    使用原始法線數(shù)據(jù)使用平均法線數(shù)據(jù)

    對(duì)比可以看到,使用新的法線數(shù)據(jù)進(jìn)行描邊,模型描邊斷邊的問(wèn)題少了很多。

    然后再添加一點(diǎn)細(xì)節(jié)

    嗯,有那味了,有關(guān)光照部分放在下一篇講

    頂點(diǎn)色的使用

    能多放入一些數(shù)據(jù),就能增加更多的效果。關(guān)于模型頂點(diǎn)色當(dāng)然也不能浪費(fèi)。在《GUILTY GEAR Xrd》中使用模型頂點(diǎn)顏色的四個(gè)通道,對(duì)模型描邊的粗細(xì)、顯隱、相機(jī)距離縮放等進(jìn)行了精細(xì)的控制。當(dāng)然頂點(diǎn)數(shù)據(jù)還可以用來(lái)做很多其他的事情,這取決于想要實(shí)現(xiàn)的效果,和美術(shù)制作的難度。在本篇中,我們使用頂點(diǎn)色控制描邊的粗細(xì)和顏色。對(duì)代碼進(jìn)行一些修改。

    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));//將近裁剪面右上角的位置的頂點(diǎn)變換到觀察空間
    float aspect = abs(nearUpperRight.y / nearUpperRight.x);//求得屏幕寬高比
    ndcNormal.x *= aspect;
    pos.xy += 0.01 * _OutlineWidth * ndcNormal.xy * v.vertColor.a;//頂點(diǎn)色a通道控制粗細(xì)
    o.pos = pos;
    o.vertColor = v.vertColor.rgb;
    return o;
    }

    fixed4 frag(v2f i) : SV_TARGET
    {
    return fixed4(_OutLineColor * i.vertColor, 0);//頂點(diǎn)色rgb通道控制描邊顏色
    }
    最終的描邊效果 E·M·T

    總結(jié)

    在本節(jié)實(shí)現(xiàn)了一個(gè)不管攝像機(jī)距離,可以保持寬度不變的Back Facing描邊方法。優(yōu)化了Back Facing描邊在不光滑物體出現(xiàn)的破邊問(wèn)題。實(shí)現(xiàn)了通過(guò)頂點(diǎn)色數(shù)據(jù)對(duì)描邊進(jìn)行調(diào)整的方法。在下一個(gè)章節(jié)中,將會(huì)討論一些用于卡通渲染的光照計(jì)算的實(shí)現(xiàn)方法。

    聲明:發(fā)布此文是出于傳遞更多知識(shí)以供交流學(xué)習(xí)之目的。若有來(lái)源標(biāo)注錯(cuò)誤或侵犯了您的合法權(quán)益,請(qǐng)作者持權(quán)屬證明與我們聯(lián)系,我們將及時(shí)更正、刪除,謝謝。

    作者:2173

    來(lái)源:https://zhuanlan.zhihu.com/p/109101851

    More:【微信公眾號(hào)】?u3dnotes

    總結(jié)

    以上是生活随笔為你收集整理的unity 3d物体描边效果_从零开始的卡通渲染描边篇的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。