Shader学习的基础知识(二十六)描边效果

这里我们学习怎么通过深度、法线纹理和Roberts算子来做描边效果。
Roberts算子的本质就是计算左上和右下角的差值作为估算边缘的依据。
解释都在代码里
摄像机脚本部分

using UnityEngine;
using System.Collections;

public class EdgeDetectNormalsAndDepth : PostEffectsBase {

	public Shader edgeDetectShader;
	private Material edgeDetectMaterial = null;
	public Material material {  
		get {
			edgeDetectMaterial = CheckShaderAndCreateMaterial(edgeDetectShader, edgeDetectMaterial);
			return edgeDetectMaterial;
		}  
	}

	[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只对队列2500以内的物体产生影响
    [ImageEffectOpaque]
	void OnRenderImage (RenderTexture src, RenderTexture dest) {
		if (material != null) {
			material.SetFloat("_EdgeOnly", edgesOnly);
			material.SetColor("_EdgeColor", edgeColor);
			material.SetColor("_BackgroundColor", backgroundColor);
			material.SetFloat("_SampleDistance", sampleDistance);
			material.SetVector("_Sensitivity", new Vector4(sensitivityNormals, sensitivityDepth, 0.0f, 0.0f));

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

基类代码

using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
[RequireComponent (typeof(Camera))]
public class PostEffectsBase : MonoBehaviour {

    //检查各种资源是否满足,我们调用
	protected void CheckResources()
    {
        bool isSupported = CheckSupport();
		
		if (isSupported == false) {
			NotSupported();
		}
	}

    //检查是否支持
	protected bool CheckSupport() {
		if (SystemInfo.supportsImageEffects == false || SystemInfo.supportsRenderTextures == false) {
			Debug.LogWarning("This platform does not support image effects or render textures.");
			return false;
		}
		
		return true;
	}

    //不支持的处理
	protected void NotSupported() {
		enabled = false;
	}
	
	protected void Start() {
		CheckResources();
	}

    /// 
    /// 后期处理
    /// 
    /// 该特效使用的Shader
    /// 用于处理的材质
    /// 
	protected Material CheckShaderAndCreateMaterial(Shader shader, Material material) {
		if (shader == null) {
			return null;
		}
		
		if (shader.isSupported && material && material.shader == shader)
			return material;
		
		if (!shader.isSupported) {
			return null;
		}
		else {
			material = new Material(shader);
			material.hideFlags = HideFlags.DontSave;
			if (material)
				return material;
			else 
				return null;
		}
	}
}

Shader部分

Shader "Custom/TestShader30" {
	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)
	}
	SubShader {
		//CGINCLUDE内的代码相当于声明在后面所有的Pass中了
		CGINCLUDE
		
		#include "UnityCG.cginc"
		
		sampler2D _MainTex;
		half4 _MainTex_TexelSize;
		fixed _EdgeOnly;
		fixed4 _EdgeColor;
		fixed4 _BackgroundColor;
		float _SampleDistance;
		half4 _Sensitivity;
		//深度加法线纹理
		sampler2D _CameraDepthNormalsTexture;
		
		struct v2f {
			float4 pos : SV_POSITION;
			half2 uv[5]: TEXCOORD0;
		};
		  
		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算子时需要采样的纹理坐,_SampleDistance是采样距离
			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;
		}
		
		//这个函数用于计算两个纹理的差值,返回0(有边界)或1(无边界)
		half CheckSame(half4 center, half4 sample) {
			//两个样点的深度和法线采样,只取了xy因为只需要知道差异,而不用具体
			half2 centerNormal = center.xy;
			float centerDepth = DecodeFloatRG(center.zw);
			half2 sampleNormal = sample.xy;
			float sampleDepth = DecodeFloatRG(sample.zw);
			
			//两个值相减后绝度值可以知道两个值法向量的差距
			half2 diffNormal = abs(centerNormal - sampleNormal) * _Sensitivity.x;
			//法线差距是否小于0.1
			int isSameNormal = (diffNormal.x + diffNormal.y) < 0.1;
			//两个值相减后绝度值可以知道两个值深度的差距
			float diffDepth = abs(centerDepth - sampleDepth) * _Sensitivity.y;
			// 深度的差距是否小于0.1
			int isSameDepth = diffDepth < 0.1 * centerDepth;
			
			// return:
			// 1 - if normals and depth are similar enough
			// 0 - otherwise
			return isSameNormal * isSameDepth ? 1.0 : 0.0;
		}
		
		//在下面的Pass中有指定,这个是片元着色器
		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);
			
			//lerp(色块一,色块二,色块二占比)
			return lerp(withEdgeColor, onlyEdgeColor, 0);
		}
		
		ENDCG
		
		Pass {
			//上面的CGINCLUDE会把内容拷贝到所有的Pass中
			ZTest Always Cull Off ZWrite Off
			
			CGPROGRAM      
			
			#pragma vertex vert  
			#pragma fragment fragRobertsCrossDepthAndNormal
			
			ENDCG  
		}
	} 
	FallBack Off
}


你可能感兴趣的:(Shader)