这里我们学习怎么通过深度、法线纹理和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
}