62.边缘检测加入纯色背景功能

62.屏幕后期处理效果-效果实现-边缘检测-加入纯色背景功能


62.1 知识点

什么是纯色背景功能

在进行边缘描边时,有时我们希望只保留描边的边缘线,而不显示原图的背景颜色。这个功能的目的是将原图的背景颜色替换为自定义的纯色,比如白色、黑色或其他颜色,同时保留图片的边缘线。通过这种方式,我们可以去掉原本图片的颜色信息,使得效果类似于一张只有边缘的描边图像。

加入纯色背景功能

主要步骤

  1. 修改Shader代码*
    在上节课的Shader代码中进行修改,复制Lesson61_EdgeDetection并将其命名为Lesson62_EdgeDetection

    1. 新属性声明和映射

      • 添加背景颜色程度变量 _BackgroundExtent,其中0表示保留图片的原始颜色,1表示完全抛弃图片原始颜色,0~1之间的值可以控制保留的程度。
      • 添加自定义背景颜色 _BackgroundColor,定义用于替换图片原始颜色的颜色。
    2. 修改片元着色器

      • 利用插值运算,记录纯色背景中像素的描边颜色。
      • 在原始图片描边和纯色图片描边之间,根据背景颜色程度变量进行插值控制。
  2. 修改C#代码
    复制Lesson61_EdgeDetectionLesson62_EdgeDetection的C#代码中进行修改:

    • 添加背景颜色程度变量。
    • 添加自定义背景颜色变量。
    • UpdateProperty函数中添加属性设置。

在上节课的Shader代码中进行修改,复制Lesson61_EdgeDetection,起名为Lesson62_EdgeDetection

Shader "Unlit/Lesson62_EdgeDetection"
{
    Properties
    {
        // 主纹理
        _MainTex ("Texture", 2D) = "white" {}
        // 边缘线的颜色
        _EdgeColor("EdgeColor", Color) = (0,0,0,0)
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Opaque" // 渲染类型为不透明
        }

        Pass
        {
            ZTest Always // 始终通过深度测试
            Cull Off // 关闭背面剔除
            ZWrite Off // 关闭深度写入

            CGPROGRAM
            #pragma vertex vert  
            #pragma fragment frag  

            #include "UnityCG.cginc"  

            sampler2D _MainTex; // 主纹理采样器
            half4 _MainTex_TexelSize; // 用于存储纹理大小的内置变量
            fixed4 _EdgeColor; // 边缘颜色

            struct v2f
            {
                // 用于存储9个像素的uv坐标
                half2 uv[9] : TEXCOORD0;
                float4 vertex : SV_POSITION; // 顶点位置
            };

            // 顶点着色器
            v2f vert(appdata_base appdata_base)
            {
                v2f v2f;
                v2f.vertex = UnityObjectToClipPos(appdata_base.vertex); // 将顶点从物体空间转换为裁剪空间
                
                // 当前顶点的纹理坐标
                half2 uv = appdata_base.texcoord;
                
                // 计算9个像素的uv坐标
                v2f.uv[0] = uv + _MainTex_TexelSize.xy * half2(-1, -1);
                v2f.uv[1] = uv + _MainTex_TexelSize.xy * half2(0, -1);
                v2f.uv[2] = uv + _MainTex_TexelSize.xy * half2(1, -1);
                v2f.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 0);
                v2f.uv[4] = uv + _MainTex_TexelSize.xy * half2(0, 0);
                v2f.uv[5] = uv + _MainTex_TexelSize.xy * half2(1, 0);
                v2f.uv[6] = uv + _MainTex_TexelSize.xy * half2(-1, 1);
                v2f.uv[7] = uv + _MainTex_TexelSize.xy * half2(0, 1);
                v2f.uv[8] = uv + _MainTex_TexelSize.xy * half2(1, 1);

                return v2f;
            }

            // 计算颜色的灰度值
            fixed calcGrayValue(fixed4 color)
            {
                return 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
            }

            // Sobel算子相关的卷积计算
            half Sobel(v2f v2f)
            {
                // Sobel算子对应的两个卷积核
                half Gx[9] = {
                    -1, -2, -1,
                    0, 0, 0,
                    1, 2, 1
                };
                half Gy[9] = {
                    -1, 0, 1,
                    -2, 0, 2,
                    -1, 0, 1
                };
                
                half nowGrayValue; // 灰度值
                half edgeX = 0; // 水平方向的梯度值
                half edgeY = 0; // 垂直方向的梯度值
                
                for (int i = 0; i < 9; i++)
                {
                    // 使用对应uv采样颜色 并计算灰度值
                    nowGrayValue = calcGrayValue(tex2D(_MainTex, v2f.uv[i]));
                    // 得到灰度值和对应卷积核相乘后加到梯度值中
                    edgeX += nowGrayValue * Gx[i];
                    edgeY += nowGrayValue * Gy[i];
                }
                
                // 计算该像素的最终梯度值
                return abs(edgeX) + abs(edgeY);
            }

            // 片段着色器
            fixed4 frag(v2f v2f) : SV_Target
            {
                // 使用Sobel算子计算当前像素的梯度值
                half edge = Sobel(v2f);
                
                // 根据计算得到的梯度值,在原始颜色和边缘颜色之间进行插值
                fixed4 color = lerp(tex2D(_MainTex, v2f.uv[4]), _EdgeColor, edge);
                
                return color;
            }
            
            ENDCG
        }
    }

    Fallback Off // 不使用任何备用着色器
}

添加背景颜色程度属性和自定义背景颜色并进行映射。背景颜色程度0表示保留图片原始颜色,1表示完全抛弃图片原始颜色,0~1之间可以自己控制保留程度。自定义背景颜色定义用于替换图片原始颜色的颜色

Properties
{
    // 主纹理
    _MainTex ("Texture", 2D) = "white" {}
    // 边缘线的颜色
    _EdgeColor("EdgeColor", Color) = (0,0,0,0)
    //背景颜色程度 1为纯色 0为原始颜色
    _BackgroundExtent("BackgroundExtent", Range(0,1)) = 0
    //背景颜色
    _BackgroundColor("BackgroundColor", Color) = (1,1,1,1)
}
sampler2D _MainTex; // 主纹理采样器
half4 _MainTex_TexelSize; // 用于存储纹理大小的内置变量
fixed4 _EdgeColor; // 边缘颜色
fixed _BackgroundExtent;//背景颜色程度
fixed4 _BackgroundColor;//背景颜色

修改片元函数,计算纯色上描边的颜色。通过背景颜色程度和原来的原始图片描边进行插值运算并返回

// 片段着色器
fixed4 frag(v2f v2f) : SV_Target
{
    // 使用Sobel算子计算当前像素的梯度值
    half edge = Sobel(v2f);

    // 原始图片描边 根据计算得到的梯度值,在原始颜色和边缘颜色之间进行插值
    fixed4 withEdgeColor = lerp(tex2D(_MainTex, v2f.uv[4]), _EdgeColor, edge);
    
    // 纯色图上描边 计算纯色,纯色上描边
    fixed4 onlyEdgeColor = lerp(_BackgroundColor, _EdgeColor, edge);
    
    //通过程度变量 去控制 是纯色描边 还是 原始颜色描边 在两者之间 进行过渡
    return lerp(withEdgeColor, onlyEdgeColor, _BackgroundExtent);
}

创建名为Lesson62_EdgeDetection的C#脚本 ,在Lesson61_EdgeDetection的C#代码中进行修改,添加背景颜色程度变量,添加自定义背景光颜色,在UpdateProperty函数中添加属性设置

public class Lesson62_EdgeDetection : PostEffectBase
{
    public Color EdgeColor;
    public Color BackgroundColor;
    [Range(0, 1)] public float BackgroundExtent;

    protected override void UpdateProperty()
    {
        if (material != null)
        {
            material.SetColor("_EdgeColor", EdgeColor);
            material.SetColor("_BackgroundColor", BackgroundColor);
            material.SetFloat("_BackgroundExtent", BackgroundExtent);
        }
    }
}

将这个Lesson62_EdgeDetection脚本挂载到摄像机上,并通过调整变量值来观察效果。通过修改背景颜色程度和背景颜色,您可以看到不同程度的边缘效果变化。


62.2 知识点代码

Lesson62_屏幕后期处理效果_效果实现_边缘检测_加入纯色背景功能.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Lesson62_屏幕后期处理效果_效果实现_边缘检测_加入纯色背景功能 : MonoBehaviour
{
    void Start()
    {
        #region 知识点一 什么是纯色背景功能

        //我们在边缘描边时,有时只想保留描边的边缘线
        //不想要显示原图的背景颜色
        //比如把整个背景变为白色、黑色、等等自定义颜色
        //而抛弃掉原本图片的颜色信息
        //效果就像是一张描边图片

        #endregion

        #region 知识点二 加入纯色背景功能

        //在上节课的Shader代码中进行修改,复制Lesson61_EdgeDetection,起名为Lesson62_EdgeDetection

        //1.新属性声明 属性映射
        //  添加 背景颜色程度变量 _BackgroundExtent 0表示保留图片原始颜色,1表示完全抛弃图片原始颜色,0~1之间可以自己控制保留程度
        //  添加自定义背景颜色 _BackgroundColor,定义用于替换图片原始颜色的颜色

        //2.修改片元着色器
        //  利用插值运算,记录纯色背景中像素描边颜色
        //  利用插值运算,在 原始图片描边 和 纯色图片描边 之间用程度变量进行控制

        //在Lesson61_EdgeDetection的C#代码中进行修改
        //  添加背景颜色程度变量
        //  添加自定义背景光颜色
        //  在UpdateProperty函数中添加属性设置

        #endregion
    }
}

Lesson62_EdgeDetection.shader

Shader "Unlit/Lesson62_EdgeDetection"
{
    Properties
    {
        // 主纹理
        _MainTex ("Texture", 2D) = "white" {}
        // 边缘线的颜色
        _EdgeColor("EdgeColor", Color) = (0,0,0,0)
        //背景颜色程度 1为纯色 0为原始颜色
        _BackgroundExtent("BackgroundExtent", Range(0,1)) = 0
        //背景颜色
        _BackgroundColor("BackgroundColor", Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Opaque" // 渲染类型为不透明
        }

        Pass
        {
            ZTest Always // 始终通过深度测试
            Cull Off // 关闭背面剔除
            ZWrite Off // 关闭深度写入

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            sampler2D _MainTex; // 主纹理采样器
            half4 _MainTex_TexelSize; // 用于存储纹理大小的内置变量
            fixed4 _EdgeColor; // 边缘颜色
            fixed _BackgroundExtent;//背景颜色程度
            fixed4 _BackgroundColor;//背景颜色

            struct v2f
            {
                // 用于存储9个像素的uv坐标
                half2 uv[9] : TEXCOORD0;
                float4 vertex : SV_POSITION; // 顶点位置
            };

            // 顶点着色器
            v2f vert(appdata_base appdata_base)
            {
                v2f v2f;
                v2f.vertex = UnityObjectToClipPos(appdata_base.vertex); // 将顶点从物体空间转换为裁剪空间

                // 当前顶点的纹理坐标
                half2 uv = appdata_base.texcoord;

                // 计算9个像素的uv坐标
                v2f.uv[0] = uv + _MainTex_TexelSize.xy * half2(-1, -1);
                v2f.uv[1] = uv + _MainTex_TexelSize.xy * half2(0, -1);
                v2f.uv[2] = uv + _MainTex_TexelSize.xy * half2(1, -1);
                v2f.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 0);
                v2f.uv[4] = uv + _MainTex_TexelSize.xy * half2(0, 0);
                v2f.uv[5] = uv + _MainTex_TexelSize.xy * half2(1, 0);
                v2f.uv[6] = uv + _MainTex_TexelSize.xy * half2(-1, 1);
                v2f.uv[7] = uv + _MainTex_TexelSize.xy * half2(0, 1);
                v2f.uv[8] = uv + _MainTex_TexelSize.xy * half2(1, 1);

                return v2f;
            }

            // 计算颜色的灰度值
            fixed calcGrayValue(fixed4 color)
            {
                return 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
            }

            // Sobel算子相关的卷积计算
            half Sobel(v2f v2f)
            {
                // Sobel算子对应的两个卷积核
                half Gx[9] = {
                    -1, -2, -1,
                    0, 0, 0,
                    1, 2, 1
                };
                half Gy[9] = {
                    -1, 0, 1,
                    -2, 0, 2,
                    -1, 0, 1
                };

                half nowGrayValue; // 灰度值
                half edgeX = 0; // 水平方向的梯度值
                half edgeY = 0; // 垂直方向的梯度值

                for (int i = 0; i < 9; i++)
                {
                    // 使用对应uv采样颜色 并计算灰度值
                    nowGrayValue = calcGrayValue(tex2D(_MainTex, v2f.uv[i]));
                    // 得到灰度值和对应卷积核相乘后加到梯度值中
                    edgeX += nowGrayValue * Gx[i];
                    edgeY += nowGrayValue * Gy[i];
                }

                // 计算该像素的最终梯度值
                return abs(edgeX) + abs(edgeY);
            }

            // 片段着色器
            fixed4 frag(v2f v2f) : SV_Target
            {
                // 使用Sobel算子计算当前像素的梯度值
                half edge = Sobel(v2f);

                // 原始图片描边 根据计算得到的梯度值,在原始颜色和边缘颜色之间进行插值
                fixed4 withEdgeColor = lerp(tex2D(_MainTex, v2f.uv[4]), _EdgeColor, edge);
                
                // 纯色图上描边 计算纯色,纯色上描边
                fixed4 onlyEdgeColor = lerp(_BackgroundColor, _EdgeColor, edge);
                
                //通过程度变量 去控制 是纯色描边 还是 原始颜色描边 在两者之间 进行过渡
                return lerp(withEdgeColor, onlyEdgeColor, _BackgroundExtent);
            }
            ENDCG
        }
    }

    Fallback Off // 不使用任何备用着色器
}

Lesson62_EdgeDetection.cs

using UnityEngine;


public class Lesson62_EdgeDetection : PostEffectBase
{
    public Color EdgeColor;
    public Color BackgroundColor;
    [Range(0, 1)] public float BackgroundExtent;

    protected override void UpdateProperty()
    {
        if (material != null)
        {
            material.SetColor("_EdgeColor", EdgeColor);
            material.SetColor("_BackgroundColor", BackgroundColor);
            material.SetFloat("_BackgroundExtent", BackgroundExtent);
        }
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏