方法其实都差不多,就是用两个过滤器,分别处理两个分量

Sobel算子

先说Sobel算子


 

unity sobel_游戏开发


GX为水平过滤器,GY为垂直过滤器,垂直过滤器就是水平过滤器旋转90度。


过滤器为3x3的矩阵,将与图像作平面卷积。


如果不存在边则两个点颜色很接近,过滤器返回一个较小的值,否则就可判断出边缘的存在。


当前点为中间点


具体计算如下:

unity sobel_算法_02

 


求出图像的每一个像素的横向及纵向灰度值通过以下公式结合,来计算该点灰度的大小


 


unity sobel_游戏开发_03


 



本shdaer将G值作为颜色输出

Roberts算子

Roberts算子与之相似


过滤器是2x2的矩阵


过滤器如下:


 

unity sobel_游戏开发_04


当前点为左上角的点


具体计算如下:



 

unity sobel_shader_05



Canny算子

过滤器是2x2的矩阵


 


unity sobel_shader_06



unity sobel_游戏开发_03



以sobel为例:


看看shader的实现

在frag函数中 



 float3 lum = float3(0.2125,0.7154,0.0721); 

 转化为luminance亮度值的变量 

            

 获取当前点的周围的点,并与luminance点积,求出亮度值(黑白图) 

 float mc00 = dot(tex2D (_MainTex, i.uv_MainTex-fixed2(1,1)/_Size).rgb, lum); 

 float mc10 = dot(tex2D (_MainTex, i.uv_MainTex-fixed2(0,1)/_Size).rgb, lum); 

 float mc20 = dot(tex2D (_MainTex, i.uv_MainTex-fixed2(-1,1)/_Size).rgb, lum); 

 。。。。。。。



由于CG函数tex2DSize函数(获取图片长宽的像素数)在unity中不能用,我也不知道用什么函数来替代它,就弄了个外部变量_Size方便调节。


如果有什么函数能代替tex2DSize函数各位看官一定要告诉我。


   


根据过滤器矩阵求出GX水平和GY垂直的灰度值

float GX = -1 * mc00 + mc20 + -2 * mc01 + 2 * mc21 - mc02 + mc22; 

float GY = mc00 + 2 * mc10 + mc20 - mc02 - 2 * mc12 - mc22;

 G = sqrt(GX*GX+GY*GY); 

 标准灰度公式 

 G = abs(GX)+abs(GY); 

近似灰度公式

 c = length(float2(GX,GY)); 

 length的内部算法就是灰度公式的算法,欧几里得长度 


 float length(float3 v) 

 { 

   return sqrt(dot(v,v)); 

 }

c即是最终输出



让我们看看效果:

unity sobel_shader_08

unity sobel_特效_09

unity sobel_unity sobel_10

unity sobel_shader_11

可以看见对于这种简单的卡通,三种算法都非常清晰,sobel和roberts稍好一点。

再看看复杂一些的图片


unity sobel_游戏开发_12

unity sobel_游戏开发_13

Roberts的纹路非常清晰,有一些噪声

unity sobel_unity sobel_14

Sobel比Roberts还要清晰,噪声相对少些

unity sobel_特效_15

canny已经没法看了,噪声太多,边缘判断的不清楚



综上,从结果来看,sobel算子的实现效果最好


下面给出sobel的shader:

Shader "Custom/sobel" {
	Properties {
		_MainTex ("MainTex", 2D) = "white" {}
		_Size("Size", range(1,2048)) = 256//size
		
	}
	SubShader {
		pass{
		Tags{"LightMode"="ForwardBase" }
		Cull off
		CGPROGRAM
		#pragma vertex vert
		#pragma fragment frag
		#include "UnityCG.cginc"

		float _Size;
		sampler2D _MainTex;
		float4 _MainTex_ST;
		struct v2f {
			float4 pos:SV_POSITION;
			float2 uv_MainTex:TEXCOORD0;
			
		};

		v2f vert (appdata_full v) {
			v2f o;
			o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
			o.uv_MainTex = TRANSFORM_TEX(v.texcoord,_MainTex);
			return o;
		}
		float4 frag(v2f i):COLOR
		{
			float3 lum = float3(0.2125,0.7154,0.0721);//转化为luminance亮度值
			//获取当前点的周围的点
			//并与luminance点积,求出亮度值(黑白图)
			float mc00 = dot(tex2D (_MainTex, i.uv_MainTex-fixed2(1,1)/_Size).rgb, lum);
			float mc10 = dot(tex2D (_MainTex, i.uv_MainTex-fixed2(0,1)/_Size).rgb, lum);
			float mc20 = dot(tex2D (_MainTex, i.uv_MainTex-fixed2(-1,1)/_Size).rgb, lum);
			float mc01 = dot(tex2D (_MainTex, i.uv_MainTex-fixed2(1,0)/_Size).rgb, lum);
			float mc11mc = dot(tex2D (_MainTex, i.uv_MainTex).rgb, lum);
			float mc21 = dot(tex2D (_MainTex, i.uv_MainTex-fixed2(-1,0)/_Size).rgb, lum);
			float mc02 = dot(tex2D (_MainTex, i.uv_MainTex-fixed2(1,-1)/_Size).rgb, lum);
			float mc12 = dot(tex2D (_MainTex, i.uv_MainTex-fixed2(0,-1)/_Size).rgb, lum);
			float mc22 = dot(tex2D (_MainTex, i.uv_MainTex-fixed2(-1,-1)/_Size).rgb, lum);
			//根据过滤器矩阵求出GX水平和GY垂直的灰度值
			float GX = -1 * mc00 + mc20 + -2 * mc01 + 2 * mc21 - mc02 + mc22;
			float GY = mc00 + 2 * mc10 + mc20 - mc02 - 2 * mc12 - mc22;
		//	float G = sqrt(GX*GX+GY*GY);//标准灰度公式
			float G = abs(GX)+abs(GY);//近似灰度公式
//			float th = atan(GY/GX);//灰度方向
			float4 c = 0;
//			c = G>th?1:0;
//			c = G/th*2;
			c = length(float2(GX,GY));//length的内部算法就是灰度公式的算法,欧几里得长度

			return c;
		}
		ENDCG
		}//

	} 
}