Unity Shader学习笔记(26)边缘检测(深度和法线纹理)
发表于2018-01-02
边缘检测的原理是利用一些边缘检测算子对图像进行卷积操作,下面就给大家介绍下边缘检测,一起来看看吧。
边缘检测
使用深度和法线纹理实现,是不受图像纹理和光照影响的,仅保存了当前渲染物体的模型信息。这里使用Roberts算子实现,采样的像素法线变化大的(差值大于0.1)或深度变化大的作为边。
using UnityEngine; /// <summary> /// 边缘检测(法线和深度纹理上进行,之前的是对颜色处理) /// </summary> 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<Camera>().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学习笔记系列教程: