61.屏幕后期处理效果-效果实现-边缘检测-具体实现
61.1 知识点
知识回顾
灰度值公式: L = 0.2126 * R + 0.7152 * G + 0.0722 * B
边缘检测效果的基本原理:
获取当前像素以及其上下左右、左上左下、右上右下共9个像素的灰度值。
使用这9个灰度值与Sobel算子进行卷积计算,得到梯度值:
G = abs(Gx) + abs(Gy)
最终颜色 = lerp(原始颜色,描边颜色,梯度值)如何得到当前像素周围8个像素的位置:
使用float4
纹理变量_TexelSize
获取当前像素周围8个像素的位置。
准备工作
- 导入图片资源并设置为Sprite。
- 新建场景。
- 在场景中使用导入资源新建Sprite对象,并将其填充满Game窗口进行测试。
实现边缘检测屏幕后期处理效果对应 Shader
主要步骤
新建一个 Shader,命名为
边缘检测Lesson61_EdgeDetection
,并删除无用代码。声明属性并进行属性映射:
- 主纹理
_MainTex
- 边缘描边颜色
_EdgeColor
- 注意属性映射时使用内置纹素变量
_MainTex_TexelSize
- 主纹理
配置屏幕后处理效果的标配:
ZTest Always
(始终通过深度测试)Cull Off
(关闭背面剔除)ZWrite Off
(关闭深度写入)
声明结构体,包括顶点和 uv 数组,用于存储 9 个像素点的 uv 坐标。
顶点着色器:
- 顶点坐标转换
- 使用 uv 数组装载 9 个像素的 uv 坐标
片元着色器:
- 利用卷积计算获取梯度值(可以声明一个 Sobel 算子计算函数和一个灰度值计算函数)
- 根据梯度值,在原始颜色和边缘颜色之间进行插值,得到最终颜色
配置
FallBack Off
,不使用任何备用着色器。
新建Shader,取名边缘检测Lesson61_EdgeDetection,删除无用代码,保留骨架
Shader "Unlit/Lesson61_EdgeDetection"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags
{
"RenderType"="Opaque"
}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
ENDCG
}
}
}
声明属性,进行属性映射。包括主纹理和 边缘描边用的颜色 _EdgeColor。注意属性映射时,使用内置纹素变量 _MainTex_TexelSize
Properties
{
// 主纹理
_MainTex ("Texture", 2D) = "white" {}
// 边缘线的颜色
_EdgeColor("EdgeColor", Color) = (0,0,0,0)
}
sampler2D _MainTex; // 主纹理采样器
half4 _MainTex_TexelSize; // 用于存储纹理大小的内置变量
fixed4 _EdgeColor; // 边缘颜色
屏幕后处理效果需要始终通过深度测试,关闭背面剔除和关闭深度写入
ZTest Always // 始终通过深度测试
Cull Off // 关闭背面剔除
ZWrite Off // 关闭深度写入
结构体相关要声明顶点和uv数组,uv数组用于存储9个像素点的uv坐标
struct v2f
{
// 用于存储9个像素的uv坐标
half2 uv[9] : TEXCOORD0;
float4 vertex : SV_POSITION; // 顶点位置
};
顶点函数将顶点从物体空间转换为裁剪空间,并且通过纹素计算9个像素的uv坐标
// 顶点着色器
v2f vert(appdata_base appdata_base)
{
v2f v2f;
v2f.vertex = UnityObjectToClipPos(appdata_base.vertex); // 将顶点从物体空间转换为裁剪空间
// 当前顶点的纹理坐标
half2 uv = appdata_base.texcoord;
// 计算9个像素的uv坐标
v2f.uv[0] = uv + _MainTex_TexelSize.xy * half2(-1, -1);
v2f.uv[1] = uv + _MainTex_TexelSize.xy * half2(0, -1);
v2f.uv[2] = uv + _MainTex_TexelSize.xy * half2(1, -1);
v2f.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 0);
v2f.uv[4] = uv + _MainTex_TexelSize.xy * half2(0, 0);
v2f.uv[5] = uv + _MainTex_TexelSize.xy * half2(1, 0);
v2f.uv[6] = uv + _MainTex_TexelSize.xy * half2(-1, 1);
v2f.uv[7] = uv + _MainTex_TexelSize.xy * half2(0, 1);
v2f.uv[8] = uv + _MainTex_TexelSize.xy * half2(1, 1);
return v2f;
}
基于灰度公式,实现计算颜色的灰度值函数
// 计算颜色的灰度值
fixed calcGrayValue(fixed4 color)
{
return 0.2126 * color.r + 0.7152 * color.g + 0.722 * color.b;
}
声明Soel算子函数,声明Sobel算子对应的两个卷积核,遍历相乘后累加返回梯度值和
// Sobel算子相关的卷积计算
half Sobel(v2f v2f)
{
// Sobel算子对应的两个卷积核
half Gx[9] = {
-1, -2, -1,
0, 0, 0,
1, 2, 1
};
half Gy[9] = {
-1, 0, 1,
-2, 0, 2,
-1, 0, 1
};
half nowGrayValue; // 灰度值
half edgeX = 0; // 水平方向的梯度值
half edgeY = 0; // 垂直方向的梯度值
for (int i = 0; i < 9; i++)
{
// 使用对应uv采样颜色 并计算灰度值
nowGrayValue = calcGrayValue(tex2D(_MainTex, v2f.uv[i]));
// 得到灰度值和对应卷积核相乘后加到梯度值中
edgeX += nowGrayValue * Gx[i];
edgeY += nowGrayValue * Gy[i];
}
// 计算该像素的最终梯度值
return abs(edgeX) + abs(edgeY);
}
片元函数中使用Sobel算子计算当前像素的梯度值,根据计算得到的梯度值,在原始颜色和边缘颜色之间进行插值
// 片段着色器
fixed4 frag(v2f v2f) : SV_Target
{
// 使用Sobel算子计算当前像素的梯度值
half edge = Sobel(v2f);
// 根据计算得到的梯度值,在原始颜色和边缘颜色之间进行插值
fixed4 color = lerp(tex2D(_MainTex, v2f.uv[4]), _EdgeColor, edge);
return color;
}
不使用备用着色器
Fallback Off // 不使用任何备用着色器
实现边缘检测屏幕后期处理效果对应 C# 代码
主要步骤
创建一个 C# 脚本,命名为
EdgeDetection
(边缘检测)。继承屏幕后处理基类
PostEffectBase
。声明边缘颜色变量,用于控制效果变化。
重写
UpdateProperty
方法,设置材质球的属性。
实现C#脚本
public class Lesson61_EdgeDetection : PostEffectBase
{
public Color EdgeColor;
protected override void UpdateProperty()
{
if (material != null)
{
material.SetColor("_EdgeColor", EdgeColor);
}
}
}
挂载C#脚本到摄像机伤害,拖入shader,可以看到边缘线的效果
61.2 知识点代码
Lesson61_屏幕后期处理效果_效果实现_边缘检测_具体实现.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson61_屏幕后期处理效果_效果实现_边缘检测_具体实现 : MonoBehaviour
{
void Start()
{
#region 知识回顾
//1.灰度值 L = 0.2126*R + 0.7152*G + 0.0722*B
//2.边缘检测效果的基本原理
// 得到 当前像素以及其 上下左右、左上左下、右上右下共9个像素的灰度值
// 用这9个灰度值和 Sobel算子 进行卷积计算得到梯度值 G = abs(Gx) + abs(Gy)
// 最终颜色 = lerp(原始颜色,描边颜色,梯度值)
//3.如何得到当前像素周围8个像素位置
// 利用 float4 纹理名_TexelSize 纹素 信息得到当前像素周围8个像素位置
#endregion
#region 准备工作
//1.导入图片资源 设置为Sprite
//2.新建场景
//3.在场景中使用导入资源新建Sprite对象 将其填充满Game窗口 用于测试
#endregion
#region 知识点一 实现边缘检测屏幕后期处理效果对应 Shader
//1.新建Shader,取名边缘检测Lesson61_EdgeDetection,删除无用代码
//2.声明属性,进行属性映射
// 主纹理 _MainTex
// 边缘描边用的颜色 _EdgeColor
// 注意属性映射时 使用内置纹素变量 _MainTex_TexelSize
//3.屏幕后处理效果标配
// ZTest Always
// Cull Off
// ZWrite Off
//4.结构体相关
// 顶点
// uv数组,用于存储9个像素点的uv坐标
//5.顶点着色器
// 顶点坐标转换
// 用uv数组装载9个像素uv坐标
//6.片元着色器
// 利用卷积获取梯度值(可以声明一个Sobel算子计算函数和一个灰度值计算函数)
// 利用梯度值在原始颜色和边缘颜色之间进行插值得到最终颜色
//7.FallBack Off
#endregion
#region 知识点二 实现边缘检测屏幕后期处理效果对应 C#代码
//1.创建C#脚本,名为EdgeDetection(边缘检测)
//2.继承屏幕后处理基类PostEffectBase
//3.声明边缘颜色变量,用于控制效果变化
//4.重写UpdateProperty方法,设置材质球属性
#endregion
}
}
Lesson61_EdgeDetection.shader
Shader "Unlit/Lesson61_EdgeDetection"
{
Properties
{
// 主纹理
_MainTex ("Texture", 2D) = "white" {}
// 边缘线的颜色
_EdgeColor("EdgeColor", Color) = (0,0,0,0)
}
SubShader
{
Tags
{
"RenderType"="Opaque" // 渲染类型为不透明
}
Pass
{
ZTest Always // 始终通过深度测试
Cull Off // 关闭背面剔除
ZWrite Off // 关闭深度写入
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex; // 主纹理采样器
half4 _MainTex_TexelSize; // 用于存储纹理大小的内置变量
fixed4 _EdgeColor; // 边缘颜色
struct v2f
{
// 用于存储9个像素的uv坐标
half2 uv[9] : TEXCOORD0;
float4 vertex : SV_POSITION; // 顶点位置
};
// 顶点着色器
v2f vert(appdata_base appdata_base)
{
v2f v2f;
v2f.vertex = UnityObjectToClipPos(appdata_base.vertex); // 将顶点从物体空间转换为裁剪空间
// 当前顶点的纹理坐标
half2 uv = appdata_base.texcoord;
// 计算9个像素的uv坐标
v2f.uv[0] = uv + _MainTex_TexelSize.xy * half2(-1, -1);
v2f.uv[1] = uv + _MainTex_TexelSize.xy * half2(0, -1);
v2f.uv[2] = uv + _MainTex_TexelSize.xy * half2(1, -1);
v2f.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 0);
v2f.uv[4] = uv + _MainTex_TexelSize.xy * half2(0, 0);
v2f.uv[5] = uv + _MainTex_TexelSize.xy * half2(1, 0);
v2f.uv[6] = uv + _MainTex_TexelSize.xy * half2(-1, 1);
v2f.uv[7] = uv + _MainTex_TexelSize.xy * half2(0, 1);
v2f.uv[8] = uv + _MainTex_TexelSize.xy * half2(1, 1);
return v2f;
}
// 计算颜色的灰度值
fixed calcGrayValue(fixed4 color)
{
return 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
}
// Sobel算子相关的卷积计算
half Sobel(v2f v2f)
{
// Sobel算子对应的两个卷积核
half Gx[9] = {
-1, -2, -1,
0, 0, 0,
1, 2, 1
};
half Gy[9] = {
-1, 0, 1,
-2, 0, 2,
-1, 0, 1
};
half nowGrayValue; // 灰度值
half edgeX = 0; // 水平方向的梯度值
half edgeY = 0; // 垂直方向的梯度值
for (int i = 0; i < 9; i++)
{
// 使用对应uv采样颜色 并计算灰度值
nowGrayValue = calcGrayValue(tex2D(_MainTex, v2f.uv[i]));
// 得到灰度值和对应卷积核相乘后加到梯度值中
edgeX += nowGrayValue * Gx[i];
edgeY += nowGrayValue * Gy[i];
}
// 计算该像素的最终梯度值
return abs(edgeX) + abs(edgeY);
}
// 片段着色器
fixed4 frag(v2f v2f) : SV_Target
{
// 使用Sobel算子计算当前像素的梯度值
half edge = Sobel(v2f);
// 根据计算得到的梯度值,在原始颜色和边缘颜色之间进行插值
fixed4 color = lerp(tex2D(_MainTex, v2f.uv[4]), _EdgeColor, edge);
return color;
}
ENDCG
}
}
Fallback Off // 不使用任何备用着色器
}
Lesson61_EdgeDetection.cs
using UnityEngine;
public class Lesson61_EdgeDetection : PostEffectBase
{
public Color EdgeColor;
protected override void UpdateProperty()
{
if (material != null)
{
material.SetColor("_EdgeColor", EdgeColor);
}
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com