073【Unity3D】边缘检测特效

1 边缘检测原理

​ 边缘检测的原理是:检测每个像素周围的像素亮度差,如果亮度差异较大,就将该像素识别为边缘,并进行边缘着色。

​ 本文完整资源见→Unity3D边缘检测特效

​ 使用过卷积神经网络(CNN)的人,一定知道卷积运算,笔者之前有写过相关文章(使用CNN实现MNIST数据集分类基于keras的卷积神经网络(CNN)基于keras的时域卷积网络(TCN)基于keras的胶囊网络(CapsNet)),感兴趣的读者可以了解下。

​ 周围像素的亮度差异计算,也需要使用卷积运算。对于每个像素的周围像素,我们可以给它赋予一个权值,对这些像素的亮度进行加权求和,将该加权和记作该点的一个特征值 ,我们可以根据该特征值决策该点是否显示为边缘色。为方便描述上述运算,我们将周围像素的权值序列记作卷积核 ,将加权运算记作卷积运算

​ 我们将可以描述周围像素点亮度差异的卷积核称为边缘检测算子 ,将使用边缘检测算子进行卷积运算得到的特征值称为梯度(记为G)。常用的边缘检测算子有 Roberts、Prewitt、Sobel,如下,它们都有两个方向上的梯度 Gx、Gy。

​ 整体梯度可以按以下公式计算得到:

​ 由于上述计算包含了开根号操作,出于性能考虑,我们使用绝对值操作代替开根号操作:

​ 得到梯度后,就可以判断哪些像素对应了边缘(梯度越大,越有可能是边缘点)。

2 代码实现

​ EdgeDetection.cs

cs 复制代码
using UnityEngine;

[RequireComponent(typeof(Camera))] // 需要相机组件
public class EdgeDetection : MonoBehaviour {
    [Range(0.0f, 1.0f)]
    public float edgesOnly = 0.0f; // 是否仅显示边缘
    public Color edgeColor = Color.black; // 边缘颜色
    public Color backgroundColor = Color.white; // 背景颜色
    private Material material; // 材质

    private void Start() {
        material = new Material(Shader.Find("MyShader/EdgeDetect"));
        material.hideFlags = HideFlags.DontSave;
    }

    void OnRenderImage(RenderTexture src, RenderTexture dest) {
        if (material != null) {
            material.SetFloat("_EdgeOnly", edgesOnly);
            material.SetColor("_EdgeColor", edgeColor);
            material.SetColor("_BackgroundColor", backgroundColor);
            Graphics.Blit(src, dest, material);
        } else {
            Graphics.Blit(src, dest);
        }
    }
}

​ EdgeDetection.shader

cpp 复制代码
Shader "MyShader/EdgeDetect" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {} // 主纹理
        _EdgeOnly ("Edge Only", Float) = 1.0 // 是否仅显示边缘
        _EdgeColor ("Edge Color", Color) = (0, 0, 0, 1) // 边缘颜色
        _BackgroundColor ("Background Color", Color) = (1, 1, 1, 1) // 背景颜色
    }

    SubShader {
        Pass {
            // 深度测试始终通过, 关闭深度写入
            ZTest Always ZWrite Off

            CGPROGRAM
            
            #include "UnityCG.cginc"
            
            #pragma vertex vert  
            #pragma fragment frag
            
            sampler2D _MainTex; // 主纹理
            uniform half4 _MainTex_TexelSize;  // _MainTex的像素尺寸大小, float4(1/width, 1/height, width, height)
            fixed _EdgeOnly; // 是否仅显示边缘
            fixed4 _EdgeColor; // 边缘颜色
            fixed4 _BackgroundColor; // 背景颜色

            struct v2f {
                float4 pos : SV_POSITION; // 裁剪空间中顶点坐标
                half2 uv[9] : TEXCOORD0; // 顶点及其周围8个点的uv坐标
            };

            v2f vert(appdata_img v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex); // 模型空间顶点坐标变换到裁剪空间, 等价于: mul(UNITY_MATRIX_MVP, v.vertex)
                half2 uv = v.texcoord;
                o.uv[0] = uv + _MainTex_TexelSize.xy * half2(-1, -1);
                o.uv[1] = uv + _MainTex_TexelSize.xy * half2(0, -1);
                o.uv[2] = uv + _MainTex_TexelSize.xy * half2(1, -1);
                o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 0);
                o.uv[4] = uv + _MainTex_TexelSize.xy * half2(0, 0);
                o.uv[5] = uv + _MainTex_TexelSize.xy * half2(1, 0);
                o.uv[6] = uv + _MainTex_TexelSize.xy * half2(-1, 1);
                o.uv[7] = uv + _MainTex_TexelSize.xy * half2(0, 1);
                o.uv[8] = uv + _MainTex_TexelSize.xy * half2(1, 1);  
                return o;
            }

            fixed luminance(fixed4 color) { // 计算亮度, 以亮度作为梯度计算的参考量
                return  0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b; 
            }
            
            half Sobel(v2f i) { // 使用Sobel边缘检测算子做卷积运算, 计算梯度
                const half Gx[9] = {-1,  0,  1, -2,  0,  2, -1,  0,  1};
                const half Gy[9] = {-1, -2, -1, 0,  0,  0, 1,  2,  1};
                half lum;
                half Ex = 0;
                half Ey = 0;
                for (int j = 0; j < 9; j++) {
                    lum = luminance(tex2D(_MainTex, i.uv[j]));
                    Ex += lum * Gx[j];
                    Ey += lum * Gy[j];
                }
                half E = 1 - abs(Ex) - abs(Ey);
                return E;
            }

            fixed4 frag(v2f i) : SV_Target {
                half edge = Sobel(i);
                fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[4]), edge); // 边缘颜色与原图颜色插值
                fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor, edge); // 边缘颜色与背景颜色插值
                return lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);
            }
            
            ENDCG
        } 
    }

    FallBack Off
}

3 运行效果

1)原图

2)Edges Only 设置为 0,Edge Color 设置为绿色

3)Edges Only 设置为 1,Edge Color 设置为黑色,Background Color 设置为白色

​ 声明:本文转自【Unity3D】边缘检测特效

相关推荐
Mapmost8 小时前
【数据融合实战手册·进阶篇】模型融合总出错?先看看这些“对齐”了没!
unity3d
北桥苏2 天前
如何在 Unity3D 导入 Spine 动画
unity3d
Thomas游戏开发3 天前
Unity3D状态管理器实现指南
前端框架·unity3d·游戏开发
土豆宝8 天前
Unity Visual Scripting(可视化脚本) 自定义节点 踩坑教程
unity3d
Thomas游戏开发9 天前
Unity3D光照层级与动态切换指南
前端框架·unity3d·游戏开发
Thomas游戏开发20 天前
Unity3D 崩溃分析工具的集成与优化
前端框架·unity3d·游戏开发
Thomas游戏开发24 天前
Unity3D网格简化与LOD技术详解
前端框架·unity3d·游戏开发
Thomas_YXQ25 天前
Unity3D 图形渲染(Graphics & Rendering)详解
开发语言·unity·图形渲染·unity3d·shader
Thomas游戏开发1 个月前
Unity3D 图形渲染(Graphics & Rendering)详解
前端·unity3d·游戏开发
Thomas游戏开发1 个月前
Unity3D 光栅化 vs 光线追踪:技术详解
前端框架·unity3d·游戏开发