Unity对选中物体实现描边效果(一)指定layer的外轮廓渲染

发表于2017-12-28
评论0 6.3k浏览

对选择物体实现描边效果系列主要是让大家可以了解多种描边效果的实现方法,下面介绍的方法是指定layer的外轮廓渲染,首先让我们来了解下原理。

原理

  添加额外相机(位置等信息等同原相机),将指定Layer的可见物体渲染出物体所占的面积,并把渲染的纹理存到一个临时渲染纹理(RenderTexture)中,再通过这个纹理扩展出外边,最后将这个外边和原场景纹理混合即可。

DrawOutline类继承于PostEffectsBase类:


实现

原始场景:包含左边一个有剔除片元的立方体,右边半透明立方体,以及没有在”Outline”层的右边小球。

1. 初始化配置额外相机

 
额外相机不需要enable,渲染处理都在代码中实现

 
额外相机默认渲染效果

DrawOutline类,添加在主相机对象。在对象Awake时初始化额外相机(不需要active 或 enable)。

private void Awake()
{
    additionalCamera.CopyFrom(MainCamera);
    additionalCamera.clearFlags = CameraClearFlags.Color;
    additionalCamera.backgroundColor = Color.black;
    additionalCamera.cullingMask = 1 << LayerMask.NameToLayer("Outline");       // 只渲染"Outline"层的物体
}

2. 渲染物体所占面积

小技巧:直接Fallback OFF。 

DrawOutline类中,OnRenderImage接口实现屏幕后处理。

void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (TargetMaterial != null && drawOccupied != null && additionalCamera != null)
        {
            RenderTexture TempRT = RenderTexture.GetTemporary(source.width, source.height, 0);
            TempRT.Create();
            additionalCamera.targetTexture = TempRT;
            // 额外相机中使用shader,绘制出物体所占面积
            additionalCamera.RenderWithShader(drawOccupied, "");
            Graphics.Blit(TempRT, destination);
            TempRT.Release();
        }
        else
            Graphics.Blit(source, destination);
    }

绘制所在面积的shader:DrawOccupied.shader。

Shader "Custom/DrawOccupied"
{
    FallBack OFF
}

3. 绘制外边与混合原场景

 
基于上面面积,扩展每个像素的颜色(类似高斯模糊),黑色值全为0,所以无法扩展

 
最后将黑色部分,和原占面积(粉红色部分),渲染为原场景。

在上一步绘制完面积后,添加修改如下代码:

// TargetMaterial为含有描绘混合shader的材质
// 设置相关属性,source为接口传入的原场景纹理
TargetMaterial.SetTexture("_SceneTex", source);
TargetMaterial.SetColor("_Color", outlineColor);
TargetMaterial.SetInt("_Width", outlineWidth);
TargetMaterial.SetInt("_Iterations", iterations);
// 使用描边混合材质实现描边效果
Graphics.Blit(TempRT, destination, TargetMaterial, 0);

DrawOutline.shader:绘制描边效果的shader

Shader "Custom/DrawOutline"
{
    Properties
    {
        _MainTex("Main Texture",2D)="black"{}           // 绘制完物体面积后纹理
        _SceneTex("Scene Texture",2D)="black"{}         // 原场景纹理
        _Color("Outline Color",Color) = (0,1,0,1)       // 描边颜色
        _Width("Outline Width",int) = 4                 // 描边宽度(像素级别)
        _Iterations("Iterations",int) = 3               // 描边迭代次数(越高越平滑,消耗越高,复杂度O(n^2))
    }
    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
        }
    }
}

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引