一、前言
因为之前使用的边缘检测,通过屏幕后处理效果使用Sobel算子进行边缘检测的,这种很有可能会导致我们不需要的一些东西(比如阴影、纹理等等)
所以我们尽可能使用在深度纹理和法线纹理上进行边缘检测,这些纹理检测不会受光照和纹理影响,仅仅保存了当前物体的模型信息,这样边缘检测就更加可靠。在三维空间中可以更好地得到应用。
二、实现
摄像机代码(具体解释见代码注释):
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; //边缘线强度 为0时边缘会叠加在原渲染图像上,为1只显示边缘
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] //通常OnRenderImage会在所有物体渲染后调用,这里我们使用命令,使其不对透明物体产生影响
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);
}
}
}
Shader脚本代码:
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 13/Edge Detection Normals And Depth" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_EdgeOnly ("Edge Only", Float) = 1.0 //边缘线强度 为0时边缘会叠加在原渲染图像上,为1只显示边缘
_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分量分别对应法线和深度的检测灵敏度
}
SubShader {
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; //定义数组为5的纹理坐标数组
};
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
o.uv[1] = uv + _MainTex_TexelSize.xy * half2(1,1) * _SampleDistance; //存储我们需要采样的另外4个纹理坐标
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;
}
half CheckSame(half4 center, half4 sample) {
//分别提取两个采样结果得到法线和深度(这里并没有解码得到真的法线值,因为我们只需比较差异)
half2 centerNormal = center.xy;
float centerDepth = DecodeFloatRG(center.zw);
half2 sampleNormal = sample.xy;
float sampleDepth = DecodeFloatRG(sample.zw);
// difference in normals
// do not bother decoding normals - there's no need here
half2 diffNormal = abs(centerNormal - sampleNormal) * _Sensitivity.x; //差异再绝对值,乘以灵敏度
int isSameNormal = (diffNormal.x + diffNormal.y) < 0.1; //把差异值的每个分量相加再和阙值比较
// difference in depth
float diffDepth = abs(centerDepth - sampleDepth) * _Sensitivity.y; //同样
// scale the required threshold by the distance
int isSameDepth = diffDepth < 0.1 * centerDepth; //这里意思大概为检测深度值差异是否影响很大
// return:
// 1 - if normals and depth are similar enough
// 0 - otherwise
return isSameNormal * isSameDepth ? 1.0 : 0.0; //只要有一个不满足就为0
}
fixed4 fragRobertsCrossDepthAndNormal(v2f i) : SV_Target {
half4 sample1 = tex2D(_CameraDepthNormalsTexture, i.uv[1]); //对4个纹理坐标进行深度+法线采样
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;
edge *= CheckSame(sample1, sample2); //这里可以看出我们对比的是对角相邻纹理坐标
edge *= CheckSame(sample3, sample4);
fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[0]), edge);
//使用edge在自己本来的颜色和边缘线之间插值,edge越小withEdgeColor颜色越靠近边缘线颜色
fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor, edge);
//使用edge在边缘线颜色和背景颜色间插值,
return lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);
//_EdgeOnly插值上面两个颜色。
//实现当_EdgeOnly为0时 显示的是 图像颜色和边缘线的混合
//当_EdgeOnly为1时 显示的是 边缘线和背景颜色的混合(没有图像本身了),
}
ENDCG
Pass {
ZTest Always Cull Off ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment fragRobertsCrossDepthAndNormal
ENDCG
}
}
FallBack Off
}