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

来源:互联网 发布:codex silenda淘宝 编辑:程序博客网 时间:2024/06/16 00:58

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

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


边缘检测

使用深度和法线纹理实现,是不受图像纹理和光照影响的,仅保存了当前渲染物体的模型信息。这里使用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把目标物体再渲染一遍(即渲染了两层),在通过边缘检测算法把小于阈值的剔除掉即可。

阅读全文
0 0
原创粉丝点击