边缘检测 【Unity Shader入门精要12.3】

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

//12.3 边缘检测
Shader "Unlit/Chapter12-EdgeDetection"
{
	Properties
	{
		//主纹理对应输入纹理
		_MainTex ("Base(RGB)", 2D) = "white" {}
		//边缘线强度
		_EdgeOnly("Edge Only",Float) = 1.0
		//边缘线颜色
		_EdgeColor("Edge Color",Color) = (0,0,0,0)
		//背景颜色
		_BackgroundColor("Background Color",Color) = (1,1,1,1)
	}
		SubShader
		{
			Pass
			{
			//关闭深度测试和写入
				ZTest Always Cull Off ZWrite Off

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

		//声明对应变量
		sampler2D _MainTex;
		//XXX_TexelSize是unity提供的访问XXX纹理对应的每个纹素的大小,由于卷轴需要对相邻区域内的纹理进行采样,因此需要_TexelSize计算各个相邻区域的纹理坐标
		half4 _MainTex_TexelSize;
		fixed _EdgeOnly;
		fixed4 _EdgeColor;
		fixed4 _BackgroundColor;

			

			struct v2f
			{
				float4 pos : SV_POSITION;
				
				half2 uv[9]:TEXCOORD0;
			};

		
			//顶点着色器 计算了边缘检测时需要的纹理坐标
			v2f vert (appdata_img v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				half2 uv = v.texcoord;


				//定义了一个9维的纹理数组,对应使用Sobel算子采样时需要的9个邻域纹理坐标
				//通过把计算采样纹理坐标的代码从片元着色器中转移到顶点着色器中,可以减少运算,提高性能,
				//由于从顶点着色器到片元着色器的插值是线性的,因此这样的转移并不会影响纹理坐标的计算结果
				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.215* color.r + 0.7154*color.g + 0.721*color.b;
			}

			//Sobel函数将利用Sobel算子对原图进行边缘检测,定义如下
			half Sobel(v2f i) {
				//定义水平方向和竖直方向使用的卷积核
				const half Gx[9] = { -1,-2,-1,
									  0, 0, 0,
									  1, 2, 1 };
				const half Gy[9] = { -1,0,1,
									 -2,0,2,
									 -1,0,1 };

				half texColor;
				half edgeX = 0;
				half edgeY = 0;
				//依次对9个像素进行采样,
				for (int it = 0; it < 9; it++) {
					//计算他们的亮度值,
					texColor = luminance(tex2D(_MainTex, i.uv[it]));
					//再与卷积核Gx,Gy中对应的权重相乘后,叠加到各自的梯度值上
					edgeX += texColor * Gx[it];
					edgeY += texColor * Gy[it];
				}
				//最后从一中减去水平方向和竖直方向梯度值的绝对值
				half edge = 1 - abs(edgeX) - abs(edgeY);
				return edge;
			}

			//片元着色器
			fixed4 frag(v2f i) : SV_Target
			{
				//调用 Sobel函数计算当前像素的梯度值edge
				half edge = Sobel(i);

			//背景为原图下的颜色值
			fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[4]), edge);
			//背景为纯色下的颜色值
			fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor, edge);

			//利用_EdgeOnly在两者之间插值得到最终的像素值
			//当_EdgeOnly值为0时,边缘将会叠加到原渲染图像上,为1时只显示边缘
			return lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);
			}

			ENDCG
		}
	}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//12.3 边缘检测


//继承测试
public class EdgeDetection : PostEffectsBase {

    //shader 的坑
    public Shader edgeDetectShader;
    private Material edgeDetectMaterial;

    //创建材质
    public Material material {
        get
        {
            edgeDetectMaterial = CheckShaderAndCreateMaterial(edgeDetectShader, edgeDetectMaterial);
            return edgeDetectMaterial;
        }
    }

    [Range(0.0f, 1.0f)]
    public float edgesOnly = 0.0f;
    public Color edgesColor = Color.black;
    public Color backgroundColor = Color.white;


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

            Graphics.Blit(src, dest, material);
        } else
        {
            Graphics.Blit(src, dest);
        }
    }


}

卷积神马的。。。真的头大,看了好久,还是一知半解,原理大体懂,可是细节还是想不通。。。目前暂时还没有办法有应用性,但是不可否认卷积的重要性,这块骨头放一放再啃!

猜你喜欢

转载自blog.csdn.net/ABigDeal/article/details/83116360