UnityShader例子:边缘检测

 

一、边缘检测

没有什么新的东西,只需了解《UnityShader26:运动模糊》这一章里面的内容就好

1):Roberts算子:

本质就是计算左上角和右下角的差值,乘上右上角和左下角的插值,作为评估边缘的依据

2):边缘检测步骤:

了解 Roberts 算子的逻辑后,实现边沿检测就简单了:

  1. 拿到当前 uv 坐标相邻的4个采样点,分别作为 Roberts 的四个候选点
  2. 得到每个点的深度和法线信息
  3. 计算两个对角线上候选点的法线和深度差
  4. 判断这个差值是否超过设定的阈值,如果超过则判定当前像素为物体边缘,通过!

直接上代码:

  • [ImageEffectOpaque]:具有此属性的图像效果会在不透明物体渲染过后、透明物体渲染前被呈现
using UnityEngine;
using System.Collections;
public class EdgeDetect: PostEffectsBase
{
    public Shader shader;
    private Material _material;
    public Material material
    {
        get
        {
            _material = CheckShaderAndCreateMaterial(shader, _material);
            return _material;
        }
    }

    private Camera _myCamera;
    public Camera myCamera
    {
        get
        {
            if (_myCamera == null)
                _myCamera = GetComponent<Camera>();
            return _myCamera;
        }
    }

    //是否忽略其它片段仅显示描边,非描边片段将会和描边背景色做混合
    [Range(0.0f, 1.0f)]
    public float edgesOnly = 0.0f;
    //描边颜色
    public Color edgeColor = Color.black;
    //描边背景色
    public Color backgroundColor = Color.white;
    //采样距离,会影响描边粗细
    public float sampleDistance = 1.0f;
    //当领域深度和法线相差多少时,会被当作是边界
    public float sensitivityDepth = 1.0f;
    public float sensitivityNormals = 1.0f;

    void OnEnable()
    {
        //https://docs.unity3d.com/2021.1/Documentation/ScriptReference/DepthTextureMode.html
        myCamera.depthTextureMode |= DepthTextureMode.DepthNormals;
    }

    //如果声明了ImageEffectOpaque,在不透明的物体渲染完成后就会立刻执行OnRenderImage
    [ImageEffectOpaque]
    void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        if (material != null)
        {
            material.SetFloat("_EdgeOnly", edgesOnly);
            material.SetColor("_EdgeColor", edgeColor);
            material.SetColor("_BackgroundColor", backgroundColor);
            material.SetFloat("_SampleDistance", sampleDistance);
            material.SetVector("_Sensitivity", new Vector4(sensitivityNormals, sensitivityDepth, 0.0f, 0.0f));
            Graphics.Blit(src, dest, material);
        }
        else
        {
            Graphics.Blit(src, dest);
        }
    }
}
Shader "Jaihk662/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)
		_SampleDistance("Sample Distance", Float) = 1.0
		_Sensitivity("Sensitivity", Vector) = (1, 1, 1, 1)
    }

    CGINCLUDE
    #include "UnityCG.cginc"


    sampler2D _MainTex;
    fixed _EdgeOnly;
    fixed4 _EdgeColor;
    fixed4 _BackgroundColor;
    float _SampleDistance;
    half4 _Sensitivity;
    //如果设置了 myCamera.depthTextureMode |= DepthTextureMode.DepthNormals,那么这里就可以通过 _CameraDepthNormalsTexture 拿到深度法线图
    sampler2D _CameraDepthNormalsTexture;

    half4 _MainTex_TexelSize;
    struct vert2frag
    {
        float4 pos: SV_POSITION;
		half2 uv[5]: TEXCOORD0;
    };

    vert2frag vert(appdata_img v)
    {
		vert2frag o;
        o.pos = UnityObjectToClipPos(v.vertex);
        half2 uv = v.texcoord;
        o.uv[0] = uv;
        
        #if UNITY_UV_STARTS_AT_TOP
        if (_MainTex_TexelSize.y < 0)
            uv.y = 1 - uv.y;
        #endif
        
        //Robert算子的四个采样点
        o.uv[1] = uv + _MainTex_TexelSize.xy * half2(1, 1) * _SampleDistance;
        o.uv[2] = uv + _MainTex_TexelSize.xy * half2(-1, -1) * _SampleDistance;
        o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 1) * _SampleDistance;
        o.uv[4] = uv + _MainTex_TexelSize.xy * half2(1, -1) * _SampleDistance;
        return o;
	}

    //该函数只会返回0或1,当且仅当法线和深度值都相似才返回1,只要有一者差值超过了对应的阈值则返回0,通过边缘检测
    half CheckSame(half4 center, half4 sample)
    {
        //DecodeFloatRG:解码颜色RG通道信息到Float
        //因为只需要知道差异,所以这里没有对法线进行解码,直接取了xy值拿来比较
        half2 centerNormal = center.xy;
        float centerDepth = DecodeFloatRG(center.zw);
        half2 sampleNormal = sample.xy;
        float sampleDepth = DecodeFloatRG(sample.zw);
        
        half2 diffNormal = abs(centerNormal - sampleNormal) * _Sensitivity.x;
        int isSameNormal = (diffNormal.x + diffNormal.y) < 0.1;
        float diffDepth = abs(centerDepth - sampleDepth) * _Sensitivity.y;
        int isSameDepth = diffDepth < 0.1 * centerDepth;
        
        return isSameNormal * isSameDepth ? 1.0 : 0.0;
    }
    
    fixed4 frag(vert2frag i): SV_Target
    {
        half4 sample1 = tex2D(_CameraDepthNormalsTexture, i.uv[1]);
        half4 sample2 = tex2D(_CameraDepthNormalsTexture, i.uv[2]);
        half4 sample3 = tex2D(_CameraDepthNormalsTexture, i.uv[3]);
        half4 sample4 = tex2D(_CameraDepthNormalsTexture, i.uv[4]);
        
        half edge = 1.0;
        //两个对角线的深度法线差值若有一个超过了上面设定的某个阈值,则通过边缘检测:edge = 0
        edge *= CheckSame(sample1, sample2);
        edge *= CheckSame(sample3, sample4);
        
        fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[0]), edge);
        fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor, edge);
        
        return lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);
    }
    ENDCG

    Subshader
    {
        ZTest Always Cull Off ZWrite Off
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
			#pragma fragment frag
            ENDCG
        }
    }
    FallBack Off
}

这样实现的边缘检测依旧有三个缺陷

  • 测只适用于 3D 图像,对于 2D 纹理的边缘检测可能需要采用其它算子
  • 一些深度和法线变化很小的边很难被检测处理,例如桌面上的白纸
  • 难以实现一些轮廓线的风格渲染

 

参考资料:

  • 《UnityShader入门精要》

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值