Sobel算子边缘检测——UnityShader学习笔记复习

本文介绍了使用Unity3D进行边缘检测的Shader实现,包括C#脚本和Shader代码的详细解析。作者通过调整Shader参数实现了混合与不混合原图的边缘检测效果,并在尝试扩展边缘宽度时意外创造出新的视觉效果。同时,文章讨论了卷积计算的理解,提到了冯乐乐女神书中的卷积计算误区及其后续的勘误讨论。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

自言自语

学习时根据乐乐女神书中教学走了一遍。记忆不是很深刻理解也不是很好 3D美术在TA路上爬行真的很艰难

复习的时候再来一遍 标记各个我个人需要关注的点

还是先上效果 图文并茂加深记忆

效果

设置面板

设置面板

检测效果图 与原图混合

在这里插入图片描述

检测效果图不与原图混合

在这里插入图片描述

自己尝试加入一个变量想扩展边缘宽度 失败 结果意外形成了另外两种效果

在这里插入图片描述
在这里插入图片描述

C#部分

基类就不贴出来了 就是抄的 自己没绕明白各种IF 绕明白后再注释贴出来

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

[ExecuteInEditMode]
public class EdgeDetectedSobelWang : PostProcessingWangBase
{
    [Range(0, 1f)]
    public float edgeOnly = 1f;
    public float edgeContrast = 1f;
    public Color backgroundColor = Color.white;
    public Color edgeColor = Color.black;

    public Shader mshader;
    private Material mmaterial;
    public Material Omaterial
    {
        get
        {
            mmaterial = CheckShaderAndMaterial(mshader, mmaterial);
            return mmaterial;
        }
    }

     void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if(Omaterial!=null)
        {
            //对shader中开放的参数进行动态设置
            Omaterial.SetFloat("_edgeOnly", edgeOnly);
            Omaterial.SetFloat("_edgeContrast", edgeContrast);
            Omaterial.SetColor("_backgroundColor", backgroundColor);
            Omaterial.SetColor("_edgeColor", edgeColor);
            //再利用OnRenderImage.Graphics.Blit 将使用该shader的材质球作用于原图像再存与目标图像中
            Graphics.Blit(source, destination, Omaterial);
        }
        else 
        {
            Graphics.Blit(source, destination);
        }

    }
}

Shader部分

Shader "TNShaderPractise/ShaderPractise_EdgeDetectionSobel_031"
{
    Properties
    {
        //主贴图经测试不能不再属性块中声明,否则获取的缓存贴图无法存入其中
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        ///其他属性可以不再属性块中声明 只要在CGPROGRAM块中定义了即可访问到
        //_edgeOnly ("EdgeOnly", Range(0,1)) = 1
        //_backgroundColor ("BackGroundColor", Color) = (1,1,1,1)
        //_edgeColor ("EdgeColor", Color) = (0,0,0,1)
    }
    SubShader
    {
        //屏幕后期 需要在最后渲染 渲染顺序定义为 Transparent
        Tags { "RenderType"="Opaque" "Queue" ="Transparent" }

        Pass 
        {

            //同样后期效果 需要关闭深度写入 开启深度测试 并关闭Cull
            ZWrite Off ZTest Always  Cull Off

            CGPROGRAM
#pragma vertex vert
#pragma fragment frag


            sampler2D _MainTex;
            //这里需要使用到 TexelSize 来进行图素采样计算
            float2 _MainTex_TexelSize;
            float _edgeOnly;
            fixed4 _backgroundColor;
            fixed4 _edgeColor;
            float _edgeContrast;
            

            struct a2v
            {
                float4 vertex : POSITION;
                float4 uv : TEXCOORD0;
            };

            struct v2f 
            {
                float4 pos : SV_POSITION;
                //声明一个uv数组 
                float2 uv[9] : TEXCOORD1;

            };

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

                float2 temuv = v.uv;
                //注意不要写错顺序了
                //图素采样用乘法 即可  其采样范围 由算子NXN决定  Sobel算子此处采用3X3的算子 
                //因此采样坐标为以图片像素点为中心点 即坐标0,0 间隔单位为1的九宫格坐标分布 如下排列
                // Sobel 3X3  
                //-1,1    0,1    1,1
                //-1,0    0,0    1,0
                //-1,-1   0,-1  1,-1
                //因为OPENGL的算法坐标是从左下角开始计算则 o.uv[0] 对应的图素位置应该是从  -1,-1 开始
                o.uv[0] = temuv + _MainTex_TexelSize.xy*half2(-1,-1);
                o.uv[1] = temuv + _MainTex_TexelSize.xy*half2(0,-1);
                o.uv[2] = temuv + _MainTex_TexelSize.xy*half2(1,-1);
                o.uv[3] = temuv + _MainTex_TexelSize.xy*half2(-1,0);
                o.uv[4] = temuv + _MainTex_TexelSize.xy*half2(0,0); //  等同于 o.uv[4]=v.uv; 即原始像素所在坐标点
                o.uv[5] = temuv + _MainTex_TexelSize.xy*half2(1,0);
                o.uv[6] = temuv + _MainTex_TexelSize.xy*half2(-1,1);
                o.uv[7] = temuv + _MainTex_TexelSize.xy*half2(0,1);
                o.uv[8] = temuv + _MainTex_TexelSize.xy*half2(1,1);

                return o;
            }

            //去色算法传入一个颜色值 返回一个 去色过后的颜色. 忽略掉Alpha
            fixed3 luminance (fixed4 color)
            {
            //去色系数 0.2125 r  0.7145 g  0.0721b  这个可以记下来
                fixed temp =0.2125*color.r+0.7145*color.g+0.0721*color.b;

                return fixed3 (temp,temp,temp);
            }


            //声明一个sobel的卷积核去计算梯度和边界  (这点冯乐乐女神书中关于sobel算子卷积计算理解有点问题 在其勘误贴中有和网友讨论得出过正确结论 但是我没看懂)
            float Sobel (v2f i)
            {

            //需要把sobel算子 翻转一下。。。。 (冯乐乐原本理解的 卷积计算需要翻转下卷积核来进行计算)
            // 原本sobel卷积核 X Y方向的分布排列如下
            //X 
            // -1, -2, -1,
            //  0,   0,  0,
            //  1,   2,  1

            //Y
            // -1,  0,  1,
            // -2,  0,  2,
            // -1,  0,  1
                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};

                //虽然冯乐乐女神理解错了, 但最后计算却巧合的对了.

                //声明一个存放去色像素的变量
                half3 texcol;
                //声明梯度分量X Y
                half edgeX=0;
                half edgeY=0;

                //用for循环减少代码数量 循环9次计算累加 完成卷积计算 
                for (int it=0;it<9;it++)
                {
                    //得到一个去色后的图素
                    texcol = luminance(tex2D(_MainTex,i.uv[it]));
                    //该图素与对应X Y 方向的Sobel算子进行卷积操作  即 相乘之后累加
                    edgeX += texcol * Gx[it]*_edgeContrast; //乘以个系数 增加描边的权重和对比度 想增加描边宽度的 没成功 思考失败了再找找方案
                    edgeY += texcol * Gy[it]*_edgeContrast;
                }

                // 将其绝对值进行相加得到该图素的梯度值  G = |Gx| + |Gy| 本来是要平方相加开方,但这样运算量大 改为直接用绝对值相加
                float edge = 1-(abs(edgeX)+abs(edgeY));  // 不知道这里为啥要用1去减。书上写这样得到的edge越小 越有可能是边界

                return edge;
            }

            fixed4 frag (v2f i ):SV_Target
            {
                float edge = Sobel(i);
                //让描边和图像颜色有关联 即正片叠底计算
                _edgeColor *= tex2D(_MainTex,i.uv[4]); 

                fixed4 withTexture = lerp (_edgeColor,tex2D(_MainTex,i.uv[4]),edge);  // 【4】正好是算子中心点即原本贴图像素的坐标点
                fixed4 onlyEdge = lerp (_edgeColor,_backgroundColor,edge);
                fixed4 finalCol = lerp(withTexture,onlyEdge,_edgeOnly);

                return finalCol;
            }


            ENDCG
        }
    }
    FallBack Off
}

关于卷积计算的讨论

乐乐女神书中对于卷积计算的操作是要先翻转卷积核 这个当时没理解为啥要翻转 当然我也不明白卷积计算具体该怎么理解

后来查看了勘误,发现了乐乐女神认为自己当时书中的理解是有误的 来源于她和一个网友的讨论 讨论卷积计算翻转不翻转的问题。神仙聊天,我也看不懂,至少目前看不懂。

因此标记在此,日后继续去悟!!

https://github.com/candycat1992/Unity_Shaders_Book/issues/24

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值