卷积核
定义 | 原理 | 可实现效果 |
---|---|---|
通常是一个四边形网格结构,该区域内每个方格都有一个权重值 | 1.当对图像中的某个像素进行卷积时,会把卷积核的中心放置在这个像素上 2.翻转核之后再依次计算核中每个元素和其覆盖的图像像素值的乘积并求和 3.得到的结果就是该位置新像素值 | 图像模糊、边缘检测 |
边缘检测算子
包含了两个方向的卷积核,分别用于检测水平方向和竖直方向上的边缘信息。
检测时:需要对每个像素分别进行依次卷积计算,得到两个方向上的梯度值Gx和Gy。
整体的计算为:G = Sqrt(Gx^2 + Gy^2) ~~ G = |Gx| + |Gy|
Gx:检测水平方向上的梯度变化。结果:垂直方向的边缘线
==>可判断出哪些像素对应了边缘(梯度值越大,越有可能是边缘点)
卷积
定义 | 实现效果 |
---|---|
使用一个卷积核对一张图像的每个像素进行一系列操作 | 均值模糊 |
算子 | 卷积核 | 特点 |
---|---|---|
高斯核1 | 正方形大小的滤波核 | 高斯核的维数越高,模糊程度越大 |
Roberts2 | 梯度算法2X2 | 当图像边缘接近于+45或-45时,效果最佳 缺:边缘的定位不太准确,提取的边缘线条较粗 |
Prewitt3 | 微分算法3X3 | 边缘检测的结果比Roberts明显,适用于识别噪声较多、灰度渐变的图像 |
Sobel4 | 离散微分算子 | 边缘定位更准确 |
参考:边缘检测算子
边缘检测
定义 | 原理 |
---|---|
描边效果的一种实现方法 | 利用一些边缘检测算子对图像进行卷积操作 |
/******************************************************
* 使用Sobel算子进行边缘检测,实现描边效果
* EdgeDetection.cs
* EdgeShader.shader
*****************************************************/
public class EdgeDetection : PostEffectsBase {
public Shader edgeShader;
private Material _material;
public Material material{
get{
_material = CheckShaderAndCreateMaterial(edgeShader, _material);
return _material;
}
}
[Range(0,1)]
public float edgeOnly = 0.5f; //描边效果
public Color edgeColor = Color.Black; //边缘颜色
public Color backgroundColor = Color.White; //背景颜色
void OnRenderImage(RenderTexture src, RenderTexture dest){
if(material == null)
{
Graphics.Blit(src, dest);
} else {
material.SetFloat("_EdgeOnly", edgeOnly);
material.SetColor("_EdgeColor", edgeColor);
material.SetColor("_BackgroundColor", backgroundColor);
Graphics.Blit(src, dest, material);
}
}
}
Shader "EdgeShader"{
Properties{
_EdgeOnly("Edge Only",Float) = 0.4
_EdgeColor("Edge Color", Color) = (0,0,0,0)
_BackgroundColor("Background Color", Color)=(1,1,1,1)
_MainTex("Main Tex", 2D)="white"{}
}
SubShader{
Pass{
ZTest Always Cull Off ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f {
float4 pos:SV_POSITION;
half2 uv[9]:TEXCOORD0;
};
float _EdgeOnly;
float4 _EdgeColor;
float4 _BackgroundColor;
sampler2D _MainTex;
float4 _MainTex_TexelSize; //纹素值
v2f vert(appdata_img v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
o.uv[0] = uv + _MainTex_TexelSize.xy + half2(-1,-1);
o.uv[1] = uv + _MainTex_TexelSize.xy + half2(-1,0);
o.uv[2] = uv + _MainTex_TexelSize.xy + half2(-1,1);
o.uv[3] = uv + _MainTex_TexelSize.xy + half2(0,-1);
o.uv[4] = uv + _MainTex_TexelSize.xy + half2(0,0);
o.uv[5] = uv + _MainTex_TexelSize.xy + half2(0,1);
o.uv[6] = uv + _MainTex_TexelSize.xy + half2(1,-1);
o.uv[7] = uv + _MainTex_TexelSize.xy + half2(1,0);
o.uv[8] = uv + _MainTex_TexelSize.xy + half2(1,1);
return o;
}
fixed luminance(fixed4 Color){
return o.2125 * color.r + 0.7154*color.g + 0.0721*color.b;
}
half Sobel(v2f i){
//按行输入矩阵
const half Gx[9] = {-1,0,1,-2,0,2,-1,0,1};
const half Gy[9] = {-1,-2,-1,0,0,0,1,2,1};
half texcoordColor = 0;
half edgex = 0;
half edgey = 0;
for(int it = 0; it < 9; ++it){
texcoordColor = luminace(tex2D);
edgex += texcoordColor * Gx[it];
edgey += texcoordColor * Gy[it];
}
half G = abs(edgex) + abs(edgey);
return half(1-G);
}
fixed4 frag(v2f i):SV_Target{
half edge = Sobel(i);
fixed4 withEdgeColor = lerp(_EdgeColor ,tex2D(_MainTex, i.uv[4]), edge);
fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor, edge);
fixed4 final = lerp(withColor, onlyColor, _EdgeOnly);
return final
}
ENDCG
}
}
Fallback Off
}
/******************************************************
* 使用Roberts算子进行边缘检测,实现描边效果
* 在深度和法线纹理上进行
* EdgeDetectNormalDepth.cs
* EdgeDetectNormalDepth.shader
*****************************************************/
public Class EdgeDetectNormalDepth : PostEffectsBase{
public Shader edgeDetectSgader = null;
private Material _material = null;
public Material material {
get{
_material = CheckShaderAndCreateMaterial(edgeDetectSgader, _material );
return _material;
}
}
[Range(0.0f, 1.0f)]
public float edgeOnly = 0.0f;
public Color edgeColor = Color.black;
public Color backgroundColor = Color.white;
public float sampleDistance = 1.0f; //控制对深度+法线纹理采样时,使用的采样距离。
//灵敏度
public float senstivityDepth = 1.0f; //影响邻域的深度值
public float senstivityNormal = 1.0f; //影响邻域的法线值
void OnEnable(){
//获取摄像机的深度+法线纹理
GetComponent<Camera>().depthTextureMode |= DepthTextureMode.DepthNormals;
}
[ImageEffectOpaque]//在所有的不透明的物体执行完毕后调用OnRenderImage
void OnRenderImage(RenderTexture src, RenderTexture dest){
if(material == null){
Graphics.Blit(src, dest);
} else {
material.SetFloat("_SampleDistance", sampleDistance);
material.SetFloat("_EdgeOnly", edgeOnly);
material.SetColor("_EdgeColor", edgeColor);
material.SetColor("_BackgroundColor", backgroundColor);
material.SetVector("_Senstivity", new Vectore4(senstivityNormal, senstivityDepth, 0, 0));
}
}
}
Shader "EdgeDetectNormalDepth"{
Properties{
_MainTex("Main Tex", 2D) = "white"{}
_EdgeOnly("Edge Only", Float) = 1.0
_SampleDistance("Sampler Distance", Float) = 0.5
_EdgeColor("Edge Color", Color) = (0,0,0,0)
_BackgroundColor("Background Color", Color) = (1,1,1,1)
_Senstivity("Senstivity", Vector) = (1,1,1,1)
}
SubShader{
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_TexelSize; //纹素大小
half _SampleDistance;
half _EdgeOnly;
float4 _EdgeColor;
float4 _BackgroundColor;
half4 _Senstivity;
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] = v.texcoord;
//平台差异化处理
#if UNITY_START_AT_TOP
if(_MainTex_TexelSize.y < 0)
uv.y = 1- uv.y;
#endif
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;
}
//计算对角线上两个纹理值的差值判断是否是边缘
half CheckSame(half4 center, half4 sample){
//获取两个采样店的法线与深度值
half2 centerNormal = center.xy;
float centerDepth = DecodeFloatRG(center.zw);
half2 sampleNormal = sample.xy;
float sampleDepth = DecodeFloatRG(sample.zw);
half2 diffNormal = abs(centerNormal - sampleNormal ) * _Senstivity.x;
int isSameNormal = (diffNormal.x + diffNormal.y) < 0.1;
half2 diffDepth = abs(centerDepth - sampleDepth ) * _Senstivity.y;
int isSameDepth = (diffDepth .x + diffDepth .y) < 0.1;
return isSameNormal *isSameDepth ? 1.0 : 0.0;
}
fixed4 frag(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;
edge *= CheckSame(sample1, sample2);
edge *= CheckSame(sample3, sample4);
fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[0]), edge);
fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor, edge);
fixed4 col = lerp(withEdgeColor , onlyEdgeColor, _EdgeOnly);
return col;
}
ENDCG
Pass{
ZTest Always Cull Off ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
Fallback Off
}
高斯模糊
模糊 | 原理 | 方式 | 卷积核 |
---|---|---|---|
均值模糊 | 卷积后得到的像素值是其领域内各个像素值的平均值 | 卷积 | 各个元素值都相等,且相加 == 1 |
中值模糊 | 选择领域内对所有像素排序后的中值替换掉原颜色 | ||
高斯模糊 | 卷积 | 高斯核 |
/******************************************************
* 使用5X5的高斯核对图像进行高斯模糊
* 1.先后调用2个Pass<1>:使用竖直方向的一维高斯核对图像进行滤波<2>:使用水平方向的一维高斯核对图像进行滤波
* 2.利用图像缩放来进一步提高性能
* 3.并通过调整高斯滤波的应用次数来控制模糊程度
* GuassianShader.Shader
* GaussianBlur.cs
*****************************************************/
public class GaussianBlur:PostEffectsBase {
public Shader gaussianShader;
public Material _material;
private Material material{
get{
_material = CheckShaderAndCreateMaterial(gaussianShader, _material);
return _material;
}
}
[Range(0,4)]
public int iterattion = 2; //高斯模糊迭代次数
[Range(0.2f,3.0f)]
public float blurSpread = 2.0f; //模糊范围
[Range(1,8)]
public int downSample = 2; //缩放系数
//blurSpread 越大, 模糊程度越高,但采样数却不会受到影响,但会造成虚影
//downSample 越大,需要处理的像素数越少,也能进一步提高模糊程度,但会导致图像像素化
void OnImageRender(RenderTexture src, RenderTexture des) {
if(material == null) {
Graphics.Blit(src, des);
} else {
int rtW = src.width / downSample;
int rtH = src.height / downSample;
RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0); //分配一个临时渲染纹理
buffer0.filterMode = FilteMode.Bilinear; //过滤模式:双线性
Graphics.Blit(src, buffer0);
//使用2个pass处理
for(int i = 0; i< iterattion; ++i){
material.SetFloat("_BlurSize", 1.0f + i * blurSpread);
RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
Graphics.Blit(buffer0, buffer1, material, 0);
RetureTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
Graphics.Blit(buffer0, buffer1, material, 1);
RetureTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
}
Graphics.Blit(buffer0, dest)
RetureTexture.ReleaseTemporary(buffer0);
}
}
}
Shader "GuassianShader"{
Properties{
_MainTex("Main Tex", 2D) = "white"{}
_BlurSize("Blur Size", Float) = 2
}
SubShader{
CGINCLUDE
#include "UnityCG.cginc"
struct v2f{
float4 pos:SV_POSITION;
half2 uv[5]:TEXCOORD0;
};
sampler2D _MainTex;
float4 _MainTex_TexelSize;
half _BlurSize;
v2f vert(appdata_img v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
o.uv[0] = uv;
//高斯模糊中对领域采样时使用的纹理坐标 * _BlurSize = 控制采样距离
o.uv[1] = uv + float2(0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
o.uv[2] = uv - float2(0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
o.uv[3] = uv + float2(0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
o.uv[4] = uv - float2(0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
return o;
}
fixed4 frag(v2f i):SV_Target{
float3 weight = float3(0.4026, 0.2442, 0.0545);
fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];
for(int it = 1; it < 3; it++){
sum += tex2D(_MainTex, i.uv[2*it - 1]).rgb * weight[it];
sum += tex2D(_MainTex, i.uv[2*it]).rgb * weight[it];
}
return fixed4(sum, 1.0);
}
ENDCG
ZTest Always Cull Off ZWrite Off
Pass{
Name "GAUSSIAN_BLUR_VERTICAL"
CGPROGARM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
Pass{
Name "GAUSSIAN_BLUR_HORIZONTAL"
CGPROGARM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
Fallback Off
}
/// RenderTexture GetTemporary(int width, int height, int depthBuffer)
/// <summary>
/// 分配一个临时的渲染纹理
/// </summary>
/// <param name="width">宽度</param>
/// <param name="height">高度</param>
/// <param name="depthBuffer"></param>
/// <returns>临时的渲染纹理</returns>
/// public void ReleaseTemporary(RenderTexture temp)
/// <summary>
/// 释放一个由GetTemporary分配的临时渲染纹理
/// </summary>
/// <param name="temp">临时纹理</param>