Skip to content

Instantly share code, notes, and snippets.

@stilllisisi
Last active March 13, 2020 02:01
Show Gist options
  • Save stilllisisi/8582c29e21e9fdd71bf67c60a773bbe8 to your computer and use it in GitHub Desktop.
Save stilllisisi/8582c29e21e9fdd71bf67c60a773bbe8 to your computer and use it in GitHub Desktop.
【游戏-shader】Unity Shader-边缘检测效果(基于颜色,基于深度法线,边缘流光效果,转场效果)
//1.基于图像的边缘检测
//C#代码如下:
/********************************************************************
FileName: EdgeEffect.cs
Description: 后处理描边效果,使用Roberts和Sobel算子,可调强度&检测距离
history: 11:11:2018 by puppet_master
https://blog.csdn.net/puppet_master
*********************************************************************/
using UnityEngine;
[ExecuteInEditMode]
public class EdgeEffect : MonoBehaviour
{
public enum EdgeOperator
{
Sobel = 0,
Roberts = 1,
}
private Material edgeEffectMaterial = null;
public Color edgeColor = Color.black;
public Color nonEdgeColor = Color.white;
[Range(1.0f, 10.0f)]
public float edgePower = 1.0f;
[Range(1, 5)]
public int sampleRange = 1;
public EdgeOperator edgeOperator = EdgeOperator.Sobel;
private void Awake()
{
var shader = Shader.Find("Edge/EdgeEffect");
edgeEffectMaterial = new Material(shader);
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
edgeEffectMaterial.SetColor("_EdgeColor", edgeColor);
edgeEffectMaterial.SetColor("_NonEdgeColor", nonEdgeColor);
edgeEffectMaterial.SetFloat("_EdgePower", edgePower);
edgeEffectMaterial.SetFloat("_SampleRange", sampleRange);
Graphics.Blit(source, destination, edgeEffectMaterial, (int)edgeOperator);
}
}
//1.基于图像的边缘检测
//基于图像的边缘检测,只在后处理阶段使用边缘检测算子针对图像的灰度计算梯度。我们能看到图像的边界,在于图像中的亮度等因素有明显差异,我们可以用梯度来表示这种边界的权重,梯度越大,边缘就越明显。
//在图像处理领域已经有了很成熟的边缘检测卷积方式,比如Roberts算子和Sobel算子。主要的思想就是使用横竖两个方向的两个矩阵对原图进行卷积运算,得到两个方向的亮度的梯度。
//Sobel算子的效果会更好一些,但是我们如果将其都乘以一定的Power,实际上二者可以达到接近的效果,而Roberts的性能是要由于Sobel的。
//我们在Shader中同时包含两种边缘检测的算子,对比效果。Shader代码如下:
/********************************************************************
FileName: EdgeEffect.shader
Description: 后处理描边效果,使用Roberts和Sobel算子,可调强度&检测距离
history: 11:11:2018 by puppet_master
https://blog.csdn.net/puppet_master
*********************************************************************/
Shader "Edge/EdgeEffect"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
CGINCLUDE
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uvRoberts[5] : TEXCOORD0;
float2 uvSobel[9] : TEXCOORD5;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_TexelSize;
fixed4 _EdgeColor;
fixed4 _NonEdgeColor;
float _EdgePower;
float _SampleRange;
float Sobel(v2f i)
{
const float Gx[9] =
{
-1, -2, -1,
0, 0, 0,
1, 2, 1
};
const float Gy[9] =
{
1, 0, -1,
2, 0, -2,
1, 0, -1
};
float edgex, edgey;
for(int j = 0; j < 9; j++)
{
fixed4 col = tex2D(_MainTex, i.uvSobel[j]);
float lum = Luminance(col.rgb);
edgex += lum * Gx[j];
edgey += lum * Gy[j];
}
return 1 - abs(edgex) - abs(edgey);
}
float Roberts(v2f i)
{
const float Gx[4] =
{
-1, 0,
0, 1
};
const float Gy[4] =
{
0, -1,
1, 0
};
float edgex, edgey;
for(int j = 0; j < 4; j++)
{
fixed4 col = tex2D(_MainTex, i.uvRoberts[j]);
float lum = Luminance(col.rgb);
edgex += lum * Gx[j];
edgey += lum * Gy[j];
}
return 1 - abs(edgex) - abs(edgey);
}
v2f vert_Sobel (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uvSobel[0] = v.uv + float2(-1, -1) * _MainTex_TexelSize * _SampleRange;
o.uvSobel[1] = v.uv + float2( 0, -1) * _MainTex_TexelSize * _SampleRange;
o.uvSobel[2] = v.uv + float2( 1, -1) * _MainTex_TexelSize * _SampleRange;
o.uvSobel[3] = v.uv + float2(-1, 0) * _MainTex_TexelSize * _SampleRange;
o.uvSobel[4] = v.uv + float2( 0, 0) * _MainTex_TexelSize * _SampleRange;
o.uvSobel[5] = v.uv + float2( 1, 0) * _MainTex_TexelSize * _SampleRange;
o.uvSobel[6] = v.uv + float2(-1, 1) * _MainTex_TexelSize * _SampleRange;
o.uvSobel[7] = v.uv + float2( 0, 1) * _MainTex_TexelSize * _SampleRange;
o.uvSobel[8] = v.uv + float2( 1, 1) * _MainTex_TexelSize * _SampleRange;
return o;
}
fixed4 frag_Sobel (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uvSobel[4]);
float g = Sobel(i);
g = pow(g, _EdgePower);
col.rgb = lerp(_EdgeColor, _NonEdgeColor, g);
return col;
}
v2f vert_Roberts (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uvRoberts[0] = v.uv + float2(-1, -1) * _MainTex_TexelSize * _SampleRange;
o.uvRoberts[1] = v.uv + float2( 1, -1) * _MainTex_TexelSize * _SampleRange;
o.uvRoberts[2] = v.uv + float2(-1, 1) * _MainTex_TexelSize * _SampleRange;
o.uvRoberts[3] = v.uv + float2( 1, 1) * _MainTex_TexelSize * _SampleRange;
o.uvRoberts[4] = v.uv;
return o;
}
fixed4 frag_Roberts (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uvRoberts[4]);
float g = Roberts(i);
g = pow(g, _EdgePower);
col.rgb = lerp(_EdgeColor, _NonEdgeColor, g);
return col;
}
ENDCG
SubShader
{
// No culling or depth
Cull Off ZWrite Off ZTest Always
//Pass 0 Sobel Operator
Pass
{
CGPROGRAM
#pragma vertex vert_Sobel
#pragma fragment frag_Sobel
ENDCG
}
//Pass 1 Roberts Operator
Pass
{
CGPROGRAM
#pragma vertex vert_Roberts
#pragma fragment frag_Roberts
ENDCG
}
}
}
//2.基于深度法线的边缘检测
//C#代码如下:
/********************************************************************
FileName: EdgeEffectDepthNormal.cs
Description: 后处理描边效果,使用DepthNormalTexture进行检测
history: 13:11:2018 by puppet_master
https://blog.csdn.net/puppet_master
*********************************************************************/
using UnityEngine;
[ExecuteInEditMode]
public class EdgeEffectDepthNormal : MonoBehaviour
{
private Material edgeEffectMaterial = null;
public Color edgeColor = Color.black;
public Color nonEdgeColor = Color.white;
[Range(1, 5)]
public int sampleRange = 1;
[Range(0, 1.0f)]
public float normalDiffThreshold = 0.2f;
[Range(0, 5.0f)]
public float depthDiffThreshold = 2.0f;
private void Awake()
{
var shader = Shader.Find("Edge/EdgeEffectDepthNormal");
edgeEffectMaterial = new Material(shader);
}
private void OnEnable()
{
var cam = GetComponent<Camera>();
cam.depthTextureMode |= DepthTextureMode.DepthNormals;
}
private void OnDisable()
{
var cam = GetComponent<Camera>();
cam.depthTextureMode = DepthTextureMode.None;
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
edgeEffectMaterial.SetColor("_EdgeColor", edgeColor);
edgeEffectMaterial.SetColor("_NonEdgeColor", nonEdgeColor);
edgeEffectMaterial.SetFloat("_SampleRange", sampleRange);
edgeEffectMaterial.SetFloat("_NormalDiffThreshold", normalDiffThreshold);
edgeEffectMaterial.SetFloat("_DepthDiffThreshold", depthDiffThreshold);
Graphics.Blit(source, destination, edgeEffectMaterial);
}
}
//2.基于深度法线的边缘检测
//基于颜色的边缘检测的主要优点在于无需额外信息,只需要场景图本身,但是也有一定的缺点,如果两个对象的颜色差异不明显,即使有边界也检测不出来,可能出现一些瑕疵。如果我们想要纯正的边缘的效果的话,就需要用另一种更加准确的边缘检测方式。
//3D渲染相对于普通的二维图像处理的优势就在于我们还可以得到一些其他的信息,比如场景的深度,场景的法线,通过这两者,我们可以在当前采样点的周围像素点计算法线的差异以及深度的差异,如果超过一定的阈值,就认为是边界。
//单独使用Depth和单独使用Normal都可以实现边缘检测,但是二者结合起来使用可能效果更好一些,正好CameraDepthNormalTexture中二者都包含,索性一起用啦。
//Shader代码如下:
/********************************************************************
FileName: EdgeEffectDepthNormal.shader
Description: 后处理描边效果,使用DepthNormalTexture检测
history: 13:11:2018 by puppet_master
https://blog.csdn.net/puppet_master
*********************************************************************/
Shader "Edge/EdgeEffectDepthNormal"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
CGINCLUDE
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv[5] : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_TexelSize;
sampler2D _CameraDepthNormalsTexture;
fixed4 _EdgeColor;
fixed4 _NonEdgeColor;
float _SampleRange;
float _NormalDiffThreshold;
float _DepthDiffThreshold;
float CheckEdge(fixed4 s1, fixed4 s2)
{
float2 normalDiff = abs(s1.xy - s2.xy);
float normalEdgeVal = (normalDiff.x + normalDiff.y) < _NormalDiffThreshold;
float s1Depth = DecodeFloatRG(s1.zw);
float s2Depth = DecodeFloatRG(s2.zw);
float depthEdgeVal = abs(s1Depth - s2Depth) < 0.1 * s1Depth * _DepthDiffThreshold;
return depthEdgeVal * normalEdgeVal;
}
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv[0] = v.uv + float2(-1, -1) * _MainTex_TexelSize * _SampleRange;
o.uv[1] = v.uv + float2( 1, -1) * _MainTex_TexelSize * _SampleRange;
o.uv[2] = v.uv + float2(-1, 1) * _MainTex_TexelSize * _SampleRange;
o.uv[3] = v.uv + float2( 1, 1) * _MainTex_TexelSize * _SampleRange;
o.uv[4] = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv[4]);
fixed4 s1 = tex2D(_CameraDepthNormalsTexture, i.uv[0]);
fixed4 s2 = tex2D(_CameraDepthNormalsTexture, i.uv[1]);
fixed4 s3 = tex2D(_CameraDepthNormalsTexture, i.uv[2]);
fixed4 s4 = tex2D(_CameraDepthNormalsTexture, i.uv[3]);
float result = 1.0;
result *= CheckEdge(s1, s4);
result *= CheckEdge(s2, s3);
col.rgb = lerp(_EdgeColor, _NonEdgeColor, result);
return col;
}
ENDCG
SubShader
{
// No culling or depth
Cull Off ZWrite Off ZTest Always
//Pass 0 Roberts Operator
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}
//3.边缘检测实现其他效果
//3.1 边缘检测实现高亮流光效果
//实现了基本的边缘检测效果,除了可以用这个技术做一些特殊的渲染风格外,还可以实现一些特殊的效果。比如加一个Flash贴图,类似之前的流光效果:
float v = tex2D(_FlashTexture, i.uvSobel[4] + float2(_EffectPercentage * _Time.y, 0.0)).r * 10;
fixed3 edge = lerp(_EdgeColor, _NonEdgeColor, g);
col.rgb = lerp(edge, col.rgb, saturate(v));
//我们也可以只让描边本身和原始效果融合,达到仅显示高两部分边缘的效果:
float v = tex2D(_FlashTexture, i.uv[4] + float2(_EffectPercentage * _Time.y, 0.0)).r;
col.rgb = v * (1 - result) * _EdgeColor + col.rgb;
//如果使用DepthNormalMap检测,可以获得更精准的边缘流动效果
//3.2 边缘检测实现过渡效果
//有了边缘检测的基本效果,可以再做一些其他的效果,比如转场的效果,把边缘效果和场景原始效果做一个基本的插值,实现一个最基本的转场:
fixed3 edge = lerp(_EdgeColor, _NonEdgeColor, g);
col.rgb = lerp(edge, col.rgb, _EffectPercentage);
//不够酷炫,那么我们就让这个转场实现一个按照方向来的渐变,根据uv控制渐变的方向,再用噪声添加一些随机效果:
fixed3 edge = lerp(_EdgeColor, _NonEdgeColor, g);
float noise = tex2D(_FlashTexture, i.uvSobel[4]).r * _NoiseFactor;
float control = _EffectPercentage > (i.uvSobel[4].x + noise);
control = saturate(control);
col.rgb = lerp(edge, col.rgb, control);
@stilllisisi
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment