一、ShaderLab

1.Alpha值边缘检测

根据图片的Alpha值边缘判定,向内扩一段距离做边缘,颜色设置未描边颜色;

片元着色阶段,向上下左右四个方向做检测,有一个点的透明度为0,判定为边缘;

Shader "2DOutline"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_LineWidth("Width",Range(0,0.4)) = 1.0
_LineColor("LineColor",color) = (1,1,1,1)
_Intensity("Intensity",Range(1,10)) = 1.0
} SubShader
{
Tags { "RenderType" = "Opaque" "Queue" = "Transparent"}
Blend SrcAlpha OneMinusSrcAlpha Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag #include "UnityCG.cginc" struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
}; struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
}; sampler2D _MainTex;
float4 _MainTex_ST;
fixed _LineWidth;
float4 _LineColor;
fixed _Intensity; v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
} fixed4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
// 采样周围4个点
float2 up_uv = i.uv + float2(0, 1) * _LineWidth * 1 / 10 * _MainTex_ST.xy;
float2 down_uv = i.uv + float2(0,-1) * _LineWidth * 1 / 10 * _MainTex_ST.xy;
float2 left_uv = i.uv + float2(-1,0) * _LineWidth * 1 / 10 * _MainTex_ST.xy;
float2 right_uv = i.uv + float2(1,0) * _LineWidth * 1 / 10 * _MainTex_ST.xy;
// 如果有一个点透明度为0 说明是边缘
float w = tex2D(_MainTex,up_uv).a * tex2D(_MainTex,down_uv).a * tex2D(_MainTex,left_uv).a * tex2D(_MainTex,right_uv).a; if (w == 0) {
col.rgb = lerp(_LineColor * _Intensity, col.rgb, w);
} return col;
}
ENDCG
}
}
}

如果图片内容恰好铺满整张图,没有alpha值,方法不适用;下图底部边缘消失了;


2.卷积边缘检测

在屏幕后处理阶段,使用卷积做边缘检测;

卷积:根据像素周围八个方向的像素的计算出新的像素值;

边缘检测卷积算子,都包含水平和竖直两个方向的卷积核;

梯度公式:G = sqrt(Gx*Gx + Gy*Gy);

考虑性能问题,使用:G = |Gx|+|Gy|;

顶点着色器计算卷积纹理采样坐标,减少计算量(片元数量更多);

片元着色阶段Sobel卷积计算,插值获得片元像素颜色;

Sobel计算结果和梯度Gradient比较,大于梯度和EdgeColor做插值;

屏幕后效调用OnRenderImage接口;

Shader "EdgeDetection"
{
Properties{
_MainTex("Base (RGB)", 2D) = "white" {}
_EdgeColor("Edge Color", Color) = (0, 0, 0, 1)
//卷积梯度
_Gradient("Gradient",float) =0.0
}
SubShader{
Pass
{
ZTest Always Cull Off ZWrite Off CGPROGRAM #include "UnityCG.cginc" #pragma vertex vert
#pragma fragment frag sampler2D _MainTex;
uniform half4 _MainTex_TexelSize;
//fixed _EdgeOnly;
fixed4 _EdgeColor;
//fixed4 _BackgroundColor;
fixed _Gradient; struct v2f {
float4 pos : SV_POSITION;
half2 uv[9] : TEXCOORD0;
}; 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(0, -1);
o.uv[2] = uv + _MainTex_TexelSize.xy * half2(1, -1);
o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 0);
o.uv[4] = uv + _MainTex_TexelSize.xy * half2(0, 0);
o.uv[5] = uv + _MainTex_TexelSize.xy * half2(1, 0);
o.uv[6] = uv + _MainTex_TexelSize.xy * half2(-1, 1);
o.uv[7] = uv + _MainTex_TexelSize.xy * half2(0, 1);
o.uv[8] = uv + _MainTex_TexelSize.xy * half2(1, 1); return o;
} fixed luminance(fixed4 color) {
return 0.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 texColor;
half edgeX = 0;
half edgeY = 0;
for (int it = 0; it < 9; it++) {
texColor = luminance(tex2D(_MainTex, i.uv[it]));
edgeX += texColor * Gx[it];
edgeY += texColor * Gy[it];
} half edge = 1 - abs(edgeX) - abs(edgeY); return edge;
} fixed4 frag(v2f i) : SV_Target {
half edge = Sobel(i); fixed4 col = tex2D(_MainTex, i.uv[4]); if(edge> _Gradient)
col = lerp(_EdgeColor, tex2D(_MainTex, i.uv[4]), edge); return col;
} ENDCG
}
}
FallBack Off
}


二、ShaderGraph

抓取图片缓冲,上下左右四个方位平移,乘以描边颜色;

四张图合并,减去原图范围的像素,只剩边缘;

最后将原图和边缘合并(可插值使边缘柔和);

升级项目到URP,修改projectsetting-graphic-pielinesettings;

导入ShaderGraph包,开始拖拖拽拽,真的香,效果好,速度快,思路清晰;

Unity—2D边缘检测(描边效果)的更多相关文章

  1. Unity Shader实现描边效果

    http://gad.qq.com/article/detail/28346 描边效果是游戏里面非常常用的一种效果,一般是为了凸显游戏中的某个对象,会给对象增加一个描边效果.本篇文章和大家介绍下利用S ...

  2. unity描边效果

    这里总结了几种在unity实现描边效果的方法,首先准备一个模型导入在unity中,使用默认shader,上传一张原始图,以便后面实现功能效果的对比 一.边缘光,这里参照官方的一个SurfaceShad ...

  3. Unity 2D游戏开发教程之2D游戏的运行效果

    Unity 2D游戏开发教程之2D游戏的运行效果 2D游戏的运行效果 本章前前后后使用了很多节的篇幅,到底实现了怎样的一个游戏运行效果呢?或者说,游戏中的精灵会不会如我们所想的那样运行呢?关于这些疑问 ...

  4. Unity Shader-热空气扭曲效果

    GrabPass GrabPass是Unity为我们提供的一个很方便的功能,可以直接将当前屏幕内容渲染到一张贴图上,我们可以直接在shader中使用这张贴图而不用自己去实现渲染到贴图这样的一个过程,大 ...

  5. UE4实现描边效果

    描边效果属于常见常用的功能,现VR项目中,也需要射线选中一个物体,使物体高亮. 于是在网上找了部分资料,同时也感谢群里的一位大神的提点,总算将描边的功能实现了,这里也写一个简单的示例步骤. 1.我并不 ...

  6. Unity 2D游戏开发教程之使用脚本实现游戏逻辑

    Unity 2D游戏开发教程之使用脚本实现游戏逻辑 使用脚本实现游戏逻辑 通过上一节的操作,我们不仅创建了精灵的动画,还设置了动画的过渡条件,最终使得精灵得以按照我们的意愿,进入我们所指定的动画状态. ...

  7. Unity 2D游戏开发教程之游戏精灵的开火状态

    Unity 2D游戏开发教程之游戏精灵的开火状态 精灵的开火状态 “开火”就是发射子弹的意思,在战争类型的电影或者电视剧中,主角们就爱这么说!本节打算为精灵添加发射子弹的能力.因为本游戏在后面会引入敌 ...

  8. Unity 2D 入门

    原文:Introduction to Unity 2D 作者:Sean Duffy 译者:kmyhy 3/15/17 更新说明: 升级至 Unity 5.5. Unity 是一个非常流行和强大的游戏引 ...

  9. Unity 2D骨骼动画2:创建真实动画

    http://bbs.9ria.com/thread-401781-1-1.html 在这个系列,我们将关注Unity引擎提供的基于骨骼动画工具.它的主要思想是为了把它应用到你自己的游戏来介绍和教基本 ...

随机推荐

  1. 前面顺序表的补充(复杂度,未实现的算法,空间扩展)(基于c语言)

    0.对于顺序表中的n个元素,如果在下标i的位置之前插入一个元素,则需要将后面n-i个元素向后移动一位:如果是删除下标为i处的元素,则是则需要将后面n-i-1个元素向前移动一位.如果说在i的位置插入和删 ...

  2. Java案例——字符串拼接

    /*案例:将一个int数组中的元素拼接为一个字符串 分析:1.静态定义一个int数组 2.定义方法将数组元素遍历并拼接,返回类型为String 3.定义变量接受方法所拼接出来的字符串 4.输出* */ ...

  3. zabbix图形中文乱码

    别的贴子都是说到修改/usr/share/zabbix/include/defines.inc.php中的 define('ZBX_FONT_NAME', 'msyh'); define('ZBX_G ...

  4. [SPDK/NVMe存储技术分析]001 - SPDK/NVMe概述

    1. NVMe概述 NVMe是一个针对基于PCIe的固态硬盘的高性能的.可扩展的主机控制器接口. NVMe的显著特征是提供多个队列来处理I/O命令.单个NVMe设备支持多达64K个I/O 队列,每个I ...

  5. 3D视觉 之 线激光3D相机

    1  3D 视觉 常见的三维视觉技术,包含双目.ToF.激光三角.结构光等,如下图:     1)毫米级 双目.ToF.结构光(散斑)的精度为 mm 级,多见于消费领域,如:导航避障,VR/AR,刷脸 ...

  6. 17调试经验之串口读写flash协议

    一是设计功能 我的理解协议就是一个命令包,通过给出不同的控制命令,来调动不同的功能模块,实现不同的功能,如读数据,写数据,擦除等. 二设计过程 先看了尤老师的视频,主要讲了大致设计原理和总体框架,当然 ...

  7. Dubbo 的整体架构设计有哪些分层?

    接口服务层(Service):该层与业务逻辑相关,根据 provider 和 consumer 的 业务设计对应的接口和实现 配置层(Config):对外配置接口,以 ServiceConfig 和  ...

  8. 什么是RabbitMQ?RabbitMQ的使用场景是什么?

    参考链接:RabbitMQ 简介以及使用场景

  9. Mycat新增数据报错can't fetch sequnce in db,sequnce

    Mycat报错 2021-11-08 17:13:01,310 [ERROR][Thread-1] MyCATSequenceProcessor.executeSeq(SesionSQLPair) j ...

  10. Java中自动装箱与拆箱

    一.什么是封装类? Java中存在基础数据类型,但是在某些情况下,我们要对基础数据类型进行对象的操作,例如,集合中只能存在对象,而不能存在基础数据类型,于是便出现了包装器类.包装器类型就是对基本数据类 ...