Unity3d游戏角色描边

本文发布于游戏程序员刘宇的个人博客,欢迎转载,请注明来源https://www.cnblogs.com/xiaohutu/p/10834491.html 

 

游戏里经常需要在角色上做描边,这里总结一下平时几种常见的描边做法。

一,两批次法:

优点是简单,效果直接,性价比高。

1. 定点对着法线方向外移,缺点是可以看出顶点之间有断裂

 

Shader "ly/Outline_2Pass_1"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white"{}
        _Outline("Outline", range(0, 1)) = 0.02
        _OutlineColor("Outline Color", Color) = (1,1,1,1)
    }
        
    SubShader
    {
        //第一个批次,画描边
        Pass
        {
            //Cull掉前面的一半,只让描边显示在后面
            Cull Front
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            fixed _Outline;
            fixed4 _OutlineColor;

            struct v2f
            {
                float4 pos : SV_POSITION;
                float4 color : COLOR;
            };

            v2f vert (appdata_full v)
            {
                v2f o;

                //源顶点位置添加法线方向乘以参数的偏移量
                v.vertex.xyz += v.normal * _Outline;

                //位置从自身坐标系转换到投影空间
                //旧版本o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
                o.pos = UnityObjectToClipPos(v.vertex);

                //描边颜色
                o.color = _OutlineColor;
                return o;
            }
            
            float4 frag (v2f i) : COLOR
            {
                return i.color; //描边
            }
            ENDCG
        }


        //第二个批次
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            sampler2D _MainTex;
            half4 _MainTex_ST;

            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                fixed4 color : COLOR;
            };

            v2f vert(appdata_base v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = v.texcoord;
                o.color = fixed4(0, 0, 0, 1);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

 

2. 得到法线在投影空间上的xy轴,作为偏移方向将顶点外移,得到的结果类似1,也有断裂

 

3. 顶点的位置作为方向矢量,则不会因为方向差距较大而断裂

 

Shader "ly/Outline_2Pass_2"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white"{}
        _Outline("Outline", range(0, 1)) = 0.02
        _OutlineColor("Outline Color", Color) = (1,1,1,1)
    }
        
    SubShader
    {
        //第一个批次,画描边
        Pass
        {
            //Cull掉前面的一半,只让描边显示在后面
            Cull Front
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            fixed _Outline;
            fixed4 _OutlineColor;

            struct v2f
            {
                float4 pos : SV_POSITION;
                float4 uv : TEXCOORD0;
            };

            v2f vert (appdata_full v)
            {
                v2f o;

                //位置从自身坐标系转换到投影空间
                //旧版本o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
                o.pos = UnityObjectToClipPos(v.vertex);

                //方式二,扩张顶点位置
                //法线变换到投影空间
                //float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
                //得到投影空间的偏移
                //float2 offset = TransformViewToProjection(normal.xy);

                ////方式三,把顶点当做方向矢量,在方向矢量的方向偏移
                float3 dir = normalize(v.vertex.xyz);
                dir = mul((float3x3)UNITY_MATRIX_IT_MV, dir);
                float2 offset = TransformViewToProjection(dir.xy);

                //有一些情况下,侧边看不到,所以把方式一和二的算法相结合
                //float3 dir = normalize(v.vertex.xyz);
                //float3 dir2 = v.normal;
                //float D = dot(dir, dir2);
                //D = (1 + D / _Outline) / (1 + 1 / _Outline);
                //dir = lerp(dir2, dir, D);
                //dir = mul((float3x3)UNITY_MATRIX_IT_MV, dir);
                //float2 offset = TransformViewToProjection(dir.xy);
                //offset = normalize(offset);

                //在xy两个方向上偏移顶点的位置
                o.pos.xy += offset * o.pos.z * _Outline;

                return o;
            }
            
            float4 frag (v2f i) : COLOR
            {
                return _OutlineColor; //描边
            }
            ENDCG
        }
        //第二个批次,略
}

 

二,边缘光

顶点的视角dir和法线dir点乘,得出偏离度,越靠近边缘,颜色的强度越高。

优点是节约批次。

v2f vert (appdata_full v)
{
  v2f o;

  //
  //_RimColor边缘光颜色  
  //_RimPower边缘光强度
  float3 viewDir = normalize(ObjSpaceViewDir(v.vertex));   float dotProduct = 1 - dot(normalize(v.normal), viewDir);   fixed3 rimCol = smoothstep(1 - _RimPower, 1.0, dotProduct) * _RimColor;   o.color = rimCol;   //  return o; }

  

三,后处理方式来画描边

优点是效果完美,缺点是消耗性能。

摄像机上挂一个脚本,处理后处理的步骤,outlineCamera 为临时摄像机,参数与主摄像机相同,看着同样的Unit层。

临时摄像机渲染到RT上,先画剪影,然后用自定义的描边shader画上去。

using UnityEngine;
using UnitySampleAssets.ImageEffects;

[RequireComponent(typeof(Camera))]
[AddComponentMenu("Image Effects/Other/Post Effect Outline")]

class PostEffectOutline : PostEffectsBase
{
    public enum OutLineMethod
    {
        eIteration,
        eScale,
    }

    private Camera attachCamera;
    private Camera outlineCamera;
    private Shader simpleShader;
    private Shader postOutlineShader;
    private Material postOutlineMat;
    private RenderTexture mTempRT;

    public Color outlineColor = new Color(0, 1f, 0, 1f);// Color.green;

    [Range(0, 10)]
    public int outlineWidth = 1;

    [Range(1, 9)]
    public int iterations = 1;

    public OutLineMethod outlineMethod = OutLineMethod.eIteration;


    void Awake()
    {
        FindShaders();
    }

    void FindShaders()
    {
        if (!simpleShader)
            simpleShader = Shader.Find("ly/DrawSimple");

        if (outlineMethod == OutLineMethod.eIteration)
        {
            if (!postOutlineShader)
                postOutlineShader = Shader.Find("ly/PostOutlineIteration");
        }
        else
        {
            if (!postOutlineShader)
                postOutlineShader = Shader.Find("ly/PostOutlineScale");
        }
    }

    protected override void Start()
    {
        base.Start();
        attachCamera = GetComponent<Camera>();
        if (outlineCamera == null)
        {
            outlineCamera = new GameObject().AddComponent<Camera>();
            outlineCamera.enabled = false;
            outlineCamera.transform.parent = attachCamera.transform;
            outlineCamera.name = "outlineCam";
        }
        postOutlineMat = new Material(postOutlineShader);
    }

    public override bool CheckResources()
    {
        CheckSupport(false);

        if (!isSupported)
            ReportAutoDisable();
        return isSupported;
    }

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (CheckResources() == false)
        {
            Graphics.Blit(source, destination);
            return;
        }


        outlineCamera.CopyFrom(attachCamera);
        outlineCamera.clearFlags = CameraClearFlags.Color;
        outlineCamera.backgroundColor = Color.black;
        outlineCamera.cullingMask = 1 << LayerMask.NameToLayer("Unit");

        if (mTempRT == null)
            mTempRT = RenderTexture.GetTemporary(source.width, source.height, source.depth);

        mTempRT.Create();

        outlineCamera.targetTexture = mTempRT;
        outlineCamera.RenderWithShader(simpleShader, "");

        postOutlineMat.SetTexture("_SceneTex", source);
        postOutlineMat.SetColor("_Color", outlineColor);
        postOutlineMat.SetInt("_Width", outlineWidth);
        postOutlineMat.SetInt("_Iterations", iterations);

        //画描边混合材质
        Graphics.Blit(mTempRT, destination, postOutlineMat);

        mTempRT.Release();
    }
}

 

先用简单的shader画出剪影

Shader "ly/DrawSimple"
{
    FallBack OFF
}

 

然后就是这个自定义的描边shader画的过程。

第一种是类似高斯模糊的方式来迭代,迭代次数越多则越细腻。

// ly 类似高斯模糊方式迭代循环处理描边

Shader "ly/PostOutlineIteration"
{
    Properties
    {
        _MainTex("Main Texture", 2D) = "black"{}        //画完物体面积后的纹理
        _SceneTex("Scene Texture", 2D) = "black"{}        //原场景纹理
        _Color("Outline Color", Color) = (0,1,0,0.8)        //描边颜色
        _Width("Outline Width", int) = 1                //描边宽度
        _Iterations("Iterations", int) = 1                //描边迭代次数(越多越平滑,消耗越高)
    }

    SubShader
    {
        Pass
        {
            CGPROGRAM
            sampler2D _MainTex;
            float2 _MainTex_TexelSize;
            sampler2D _SceneTex;
            fixed4 _Color;
            float _Width;
            int _Iterations;

            #pragma vertex vert
            #pragma fragment frag    
            #include "UnityCG.cginc"

            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            v2f vert(appdata_base v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = v.texcoord.xy;
                return o;
            }

            half4 frag(v2f i) : COLOR
            {
                //迭代次数为奇数,保证对称
                int iterations = _Iterations * 2 + 1;
                float ColorIntensityInRadius;
                float Tx_x = _MainTex_TexelSize.x * _Width;
                float Tx_y = _MainTex_TexelSize.y * _Width;

                //计算是否大于0,则此像素属于外边的范围内
                for (int k=0; k<iterations; k+=1)
                {
                    for (int j=0; j<iterations; j+=1)
                    {
                        ColorIntensityInRadius += tex2D(_MainTex, i.uv.xy + float2((k - iterations / 2) * Tx_x, (j - iterations / 2) * Tx_y));
                    }
                }

                //如果有颜色,或者不在外边的范围内,则渲染原场景。否则,在外边内,渲染描边。
                if (tex2D(_MainTex, i.uv.xy).r > 0 || ColorIntensityInRadius == 0)
                    return tex2D(_SceneTex, i.uv);
                else
                    return  _Color.a * _Color + (1 - _Color.a)*tex2D(_SceneTex, i.uv);
            }

            ENDCG
        }
    }
}

第二种方法简单些,直接把剪影的部分uv扩大,再把原图叠上去。

// ly 扩张剪影uv来填充描边

Shader "ly/PostOutlineScale"
{
    Properties
    {
        _MainTex("Main Texture", 2D) = "black"{}        //画完物体面积后的纹理
        _SceneTex("Scene Texture", 2D) = "black"{}        //原场景纹理
        _Color("Outline Color", Color) = (0,1,0,1)        //描边颜色
        _Width("Outline Width", float) = 1                //描边宽度
    }

    SubShader
    {
        Pass
        {
            CGPROGRAM
            sampler2D _MainTex;
            sampler2D _SceneTex;
            float2 _SceneTex_TexelSize;

            fixed4 _Color;
            float _Width;

            #pragma vertex vert
            #pragma fragment frag    
            #include "UnityCG.cginc"

            struct v2f
            {
                float4 pos : SV_POSITION;
                half2 uv[2] : TEXCOORD0;
                half2 uv2[4] : TEXCOORD2;
            };

            v2f vert(appdata_base v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);

                o.uv[0] = v.texcoord.xy;
                o.uv[1] = v.texcoord.xy;

                half2 offs = _SceneTex_TexelSize.xy * _Width;

                o.uv2[0].x = v.texcoord.x - offs.x;
                o.uv2[0].y = v.texcoord.y - offs.y;

                o.uv2[1].x = v.texcoord.x + offs.x;
                o.uv2[1].y = v.texcoord.y - offs.y;

                o.uv2[2].x = v.texcoord.x + offs.x;
                o.uv2[2].y = v.texcoord.y + offs.y;

                o.uv2[3].x = v.texcoord.x - offs.x;
                o.uv2[3].y = v.texcoord.y + offs.y;

                if (_SceneTex_TexelSize.y < 0)
                {
                    o.uv[1].y = 1 - o.uv[1].y;
                    o.uv2[0].y = 1 - o.uv2[0].y;
                    o.uv2[1].y = 1 - o.uv2[1].y;
                    o.uv2[2].y = 1 - o.uv2[2].y;
                    o.uv2[3].y = 1 - o.uv2[3].y;
                }
                return o;
            }

            half4 frag(v2f i) : COLOR
            {
                fixed4 stencil = tex2D(_MainTex, i.uv[1]);

                // 有剪影的部分,显示原图
                if (any(stencil.rgb))
                {
                    fixed4 framebuffer = tex2D(_SceneTex, i.uv[0]);
                    return framebuffer;
                }
                // 没有剪影的部分,先把剪影扩张,扩张出颜色的部分用剪影,没有颜色的用原图
                else
                {
                    fixed4 color1 = tex2D(_MainTex, i.uv2[0]);
                    fixed4 color2 = tex2D(_MainTex, i.uv2[1]);
                    fixed4 color3 = tex2D(_MainTex, i.uv2[2]);
                    fixed4 color4 = tex2D(_MainTex, i.uv2[3]);
                    fixed4 color;
                    color.rgb = max(color1.rgb, color2.rgb);
                    color.rgb = max(color.rgb, color3.rgb);
                    color.rgb = max(color.rgb, color4.rgb);
                    if (any(color.rgb))
                    {
                        return _Color;
                        //color.a = (color1.a + color2.a + color3.a + color4.a) * 0.25;
                        //return color;
                    }
                    else
                    {
                        fixed4 framebuffer = tex2D(_SceneTex, i.uv[0]);
                        return framebuffer;
                    }
                }
            }

            ENDCG
        }
    }

    SubShader
    {
        Pass
        {
            SetTexture[_MainTex]{}
        }
    }

    Fallback Off
}

 

转载于:https://www.cnblogs.com/xiaohutu/p/10834491.html

### 回答1: Unity3D是一款非常流行的游戏开发引擎,它提供了丰富的功能和工具,使开发人员能够轻松创建各种类型的游戏。其中一个强大的功能就是模型描边shader,它可以增强游戏中的视觉效果。 模型描边shader是一种特殊的着色器程序,用于给游戏中的3D模型添加轮廓线。通过描边shader,我们可以让模型的边缘线条更加清晰、醒目,让物体在场景中更加凸显出来。 使用Unity3D模型描边shader非常方便。首先,我们需要将描边shader应用于相应的材质上。然后,通过调整一些参数,如描边宽度、颜色等,可以实现不同的效果。在游戏运行时,模型的边缘线条就会被自动描绘出来,使得物体在场景中更加鲜明。 这个功能对于游戏开发人员来说非常实用。通过使用模型描边shader,我们可以增强游戏中物体的可视性,使它们在复杂的环境中更容易被玩家注意到。而且,模型描边shader还可以用于创建一些特殊效果,如描边发光等,让游戏更加生动有趣。 总的来说,Unity3D模型描边shader是一个非常方便实用的功能。它提供了丰富的定制选项,使开发人员能够轻松地改善游戏的视觉效果。无论是用于增强物体的可视性,还是为游戏添加特殊效果,模型描边shader都是一个非常有用的工具,可以使游戏更加出色。 ### 回答2: Unity3D模型描边Shader是一种非常方便实用且强大的技术。该Shader能够为模型的边缘添加一个特殊的描边效果,从而使模型在视觉上更加突出和立体。 使用Unity3D模型描边Shader非常方便。开发者只需将该Shader应用于目标模型的材质中,然后调整描边效果的参数即可。这些参数包括描边的颜色、宽度、锐度等。通过简单的参数调整,开发者可以获得不同效果的描边效果。 此外,Unity3D模型描边Shader还具有强大的功能。开发者可以根据需要自定义描边的样式,如添加渐变效果、使用图片纹理等。这样,描边效果不再局限于简单的线条,而是可以根据需求进行更加丰富多样的设计。 使用Unity3D模型描边Shader可以带来许多好处。首先,它能够增强模型的可视性,使其在游戏或应用中更加突出。其次,描边效果能够帮助玩家或用户更好地理解模型的形状和结构。再次,通过调整描边的参数,开发者可以在远近不同的场景中实现不同的效果,提升用户体验。 总之,Unity3D模型描边Shader是一种非常方便实用且强大的技术。它不仅能够为模型增加视觉效果,还能够提升用户体验,使游戏或应用的视觉呈现更加出色和吸引人。 ### 回答3: Unity3D模型描边Shader是一种非常方便实用且强大的功能。这个Shader可以在模型的边缘添加一条描边效果,使得模型在场景中更加突出和鲜明。 使用Unity3D模型描边Shader非常简单。首先,我们需要在Unity中创建一个材质,并将该材质的Shader类型设置为描边Shader。接下来,我们可以调整描边的颜色、宽度和透明度等属性,以满足我们的需求。 当我们将这个材质应用到模型上时,模型的边缘将自动显示出描边效果。不仅如此,Unity还提供了一些额外的功能,如控制描边的深度和三维感以及添加高光效果等。 使用Unity3D模型描边Shader的好处不仅仅是增加模型的视觉效果,它还可以提高游戏的性能。相较于其他方法,使用Shader进行描边可以在不增加更多几何体或复制模型的情况下实现,从而减少显卡的负载。 此外,Unity3D模型描边Shader还可以很容易地与其他特效和Shader进行结合。我们可以通过修改Shader代码来实现更多的效果,如闪烁、动态描边和不同颜色的描边等。这使得我们可以根据具体的游戏需求来定制和优化描边效果。 综上所述,Unity3D模型描边Shader是一项非常方便实用且强大的功能。它可以轻松地为模型添加描边效果,提高游戏的视觉效果和性能。无论是开发者还是玩家,都可以从中受益并创造出更加出色的游戏作品。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值