Unity Shader 学习笔记(26) 边缘检测(深度和法线纹理)

Unity Shader 学习笔记(26) 边缘检测(深度和法线纹理)

参考书籍:《Unity Shader 入门精要》
Unity Shader 学习笔记(20) 卷积、卷积核、边缘检测算子、边缘检测


边缘检测

使用深度和法线纹理实现,是不受图像纹理和光照影响的,仅保存了当前渲染物体的模型信息。这里使用Roberts算子实现,采样的像素法线变化大的(差值大于0.1)或深度变化大的作为边。
Unity Shader 学习笔记(26) 边缘检测(深度和法线纹理)_第1张图片


using UnityEngine;

/// 
/// 边缘检测(法线和深度纹理上进行,之前的是对颜色处理)
/// 
public class EdgeDetectNormalsAndDepth : PostEffectsBase
{
    [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()
    {
        GetComponent().depthTextureMode |= DepthTextureMode.DepthNormals;
    }

    [ImageEffectOpaque]     // 不透明物体渲染完后执行(不影响透明物体),因为默认是在不透明和透明物体都渲染完才调用的。
    void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        if (TargetMaterial != null)
        {
            ... // 把上面变量都传入Shader
        }
        Graphics.Blit(src, dest, TargetMaterial);
    }
}

Shdaer:

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)		// x作为法线的灵敏度,y作为深度的灵敏度,zw没用
}
SubShader {
	CGINCLUDE
	
	...
	
	sampler2D _CameraDepthNormalsTexture;
	
	struct v2f {
		float4 pos : SV_POSITION;
		half2 uv[5]: TEXCOORD0;		// 第一组存屏幕颜色采样纹理,剩下四个Roberts算子采样的纹理坐标
	};
	  
	v2f vert(appdata_img v) {
		v2f 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

		// Roberts算子
		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;
	}
	
	fixed4 fragRobertsCrossDepthAndNormal(v2f 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;
		
		// 两个纹理差值 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);
	}
	
	// 采样判断两点间是否存在一条边,存在返回0
	half CheckSame(half4 center, half4 sample) {
		half2 centerNormal = center.xy;
		float centerDepth = DecodeFloatRG(center.zw);
		half2 sampleNormal = sample.xy;
		float sampleDepth = DecodeFloatRG(sample.zw);
		
		// 法线差值,差值大于0.1判断为变换明显,作为边。
		half2 diffNormal = abs(centerNormal - sampleNormal) * _Sensitivity.x;
		int isSameNormal = (diffNormal.x + diffNormal.y) < 0.1;

		// 深度差值,差值大于0.1判断为变换明显,作为边。
		float diffDepth = abs(centerDepth - sampleDepth) * _Sensitivity.y;
		int isSameDepth = diffDepth < 0.1;
		
		// 法线 或 深度 其中一个相差大(为0)就是边
		return isSameNormal * isSameDepth ? 1.0 : 0.0;
	}

	ENDCG
	
	Pass { 
		ZTest Always Cull Off ZWrite Off
		CGPROGRAM      
		#pragma vertex vert  
		#pragma fragment fragRobertsCrossDepthAndNormal
		ENDCG  
	}
} 

要单独对某个物体描边,可以使用Graphics.DrawMesh把目标物体再渲染一遍(即渲染了两层),在通过边缘检测算法把小于阈值的剔除掉即可。

你可能感兴趣的:(Unity,Shader,图形学,Unity,Shader,学习笔记)