Unity 2d描边基于SpriteRender,高性能的描边解决方案
目标
以Unity默认渲染管线为例,打造不需要图片内边距,描边平滑,高性能的描边解决方案
前言
在2d游戏中经常需要给2d对象添加描边,来突出强调2d对象
当你去网上查找2d描边shader,移植到项目里面,大概率会得到这个情况
如果描边的基本原理不清楚的可以看我之前的文章 文本描边
①出现了边缘看起来像是被截断了,这个是因为使用在超过的范围,三角形没有覆盖到,这里没有片元着色器进行渲染,所以我们要扩展三角形的顶点,同时扩展uv
图片如果有内边距,并且描边宽度较小,可能不会出现。有时即使图片内边距足够了也会出现这个情况,这是因为生成的三角形的原因(同上),在图片的导入设置MeshType设置为FullRect可以解决,如下图,但是这会增加片元着色器的负担,会有更多的片元需要渲染,唯一的好处是可以减少顶点数据的内存。这里我们为性能考虑,使用三角形渲染,扩展多边形的顶点和uv
但是如果所以要描边的物体加内边距,会增加内存消耗
②不该有描边的区域出现了描边,
要解决的问题
- 边缘被截断了=>扩展多边形的顶点和uv
- 描边有锯齿感=>采样次数不足,只沿着4个或8个方向采样,在outlineWidth较大时会出现问题,增多采样次数,在实际测试中,权衡效果和性能,12次最佳
- 描边和图片过渡处不平滑=>在原图片边缘,边缘aphla为0-1,lerp(outlineCol,col,a),a为0,显示描边,a为1显示原来的图片
- 不该有描边的区域出现了描边
①tex2D得到的a,a>0认为是描边,图片在透明部分a不完全为0导致,提高阈值即可a>0.2
②当outlineWidth过大导致,uv可能会偏移到1.1,即采样uv为0.1的像素,该像素a为1导致的=>C#传入原始的uv范围,超过这个范围的不采样
最终效果演示
代码讲解
Shader部分
tex2D这个采样函数十分消耗性能,可以说,shader性能大部分由tex2D采样次数决定,在本shader中要想尽办法减少tex2D的采样
for会极大消耗性能,不使用for循环
half4 frag(g2f i) : SV_Target
{float4 col = tex2D(_MainTex, i.uv);col *= i.color;//乘以顶点颜色_ShowBound = float4(0, 0, 1, 1);col.a *= isInRange(i.uv);//扩展的uv不在原始uv范围,a设置为0float sum_a = 0;int iteration = 12;//当第一次采样a>0.9,说明片元为正常的像素,直接渲染,不采样邻近像素for (int ii = 0; ii < iteration; ++ii)//为了代码可读性,使用for,最终代码不使用for{if(sum_a<0.5)//如果采样结果累计>0.5,不进行采样,这样能减少tex采样{sum_a += SampleTex(i, ii, iteration);}}sum_a=step(0.5,sum_a)+sum_a;//a>0.5的部分认为1sum_a = saturate(sum_a);float4 outLineColor = float4(_OutlineColor.rgb, sum_a);//如果_OutlineWidth为0时,显示原来图片的颜色float a = step(_OutlineWidth, 0.001);//为0,描边区域;1,原始图片;0-1,图片边缘,用图片颜色和描边颜色插值过渡float4 finalCol = lerp(outLineColor, col,saturate(a+col.a));return finalCol;
}
float isInRange(float2 uv)
{float2 rs = step(_ShowBound.xy, uv) * step(uv, _ShowBound.zw);return rs.x * rs.y;
}
float SampleTex(g2f i, float ii, int sum)
{//使用预先计算好的结果,减少sincos的计算,将上下左右优先放在最前面,因为绝大部分描边由上下左右偏移得到,//可以大幅度减少在描边区域的采样次数,一旦上下左右采样得到a>threshold,就不会进行采样了const float OffsetX[12] = {1, 0, -1, 0, 0.866, 0.5, -0.5, -0.866, -0.866, -0.5, 0.5, 0.866};const float OffsetY[12] = {0, 1, 0, -1, 0.5, 0.866, 0.866, 0.5, -0.5, -0.866, -0.866, -0.5};float2 offset_uv = i.uv + float2(OffsetX[ii], OffsetY[ii]) * _MainTex_TexelSize.xy * _OutlineWidth;float sample_a=0;if(isInRange(offset_uv)>0)//如果偏移后的uv不在原始uv范围不进行采样,a为1{sample_a = tex2D(_MainTex, offset_uv).a;}float a = sample_a;a = step(0.2, a) * a;//采样结果<0.2时,不认为是描边return a;
}
C#部分
在解决上面的问题后,C#要解决最后的一个问题, 边缘看起来被截断了
如果使用的是FullRect渲染Sprite,是扩展矩形的顶点,问题会简单得多。可以在几何着色器geometry中扩展顶点和uv,但是苹果的Metal不支持几何着色器,而且FullRect渲染性能差,所以方案不行。
要扩展多边形的顶点,
首先要知道SpriteRender.sprite的vertices和uvs是只能读不可以修改的。
在网上找了一圈后,幸好unity提供了sprite.SetVertexAttribute这个扩展方法可以修改顶点
在Start时,设置原始的uv范围和描边宽度
ppu即n个像素对应1个单位长度m
扩展多边形得顶点,通过v[i-1]-v[i]和v[i+1]-v[i]得到PA和PB,(PA+PB).normalized得到PC,判断OP和PC方向夹角是否小于90,否则,PC取反,将点P沿PC方向偏移即可
因为使用sprite.SetVertexAttribute修改顶点,会自动计算修改后得uv,所以这里不需要修改uv了
void Start()
{// 获取SpriteRenderer组件和SpritespriteRenderer = GetComponent<SpriteRenderer>();sprite = spriteRenderer.sprite;spriteRenderer.material.SetVector("_ShowBound",bound);spriteRenderer.material.SetFloat("_OutlineWidth",outlineWidth);// 获取原始的顶点、三角形和UV数据originalVertices = sprite.vertices;ppu = 1/sprite.pixelsPerUnit;// 扩展顶点Vector2[] expandedVertices = ExpandVertices(originalVertices, outlineWidth);Vector3[] vertices = System.Array.ConvertAll(expandedVertices, v => (Vector3)v);NativeArray<Vector3> array = new NativeArray<Vector3>(vertices, Allocator.Temp);//将Vector3转换到NativeArray<Vector3>类型sprite.SetVertexAttribute(VertexAttribute.Position,array);
}
private void OnDestroy()//在销毁时还原到之前的顶点
{Vector3[] vertices = System.Array.ConvertAll(originalVertices, v => (Vector3)v);NativeArray<Vector3> array = new NativeArray<Vector3>(vertices, Allocator.Temp);sprite.SetVertexAttribute(VertexAttribute.Position,array);
}
sprite.vertices顶点不是按逆时针排列的,先得到一个按角度排列的顶点
private int CompareByAngle(Vector2 a, Vector2 b)
{float angleA = Mathf.Atan2(a.y, a.x);float angleB = Mathf.Atan2(b.y, b.x);return angleA.CompareTo(angleB);
}
Vector2[] ExpandVertices(Vector2[] vertices, float len)
{Vector2[] expandedVertices = new Vector2[vertices.Length];Vector2[] sortVertices = new Vector2[vertices.Length];for (int i = 0; i < sortVertices.Length; i++){sortVertices[i] = vertices[i];}//将顶点按逆时针排列Array.Sort(sortVertices, (a, b) => CompareByAngle(a, b));for (int i = 0; i < sortVertices.Length; i++){Vector2 vector2= sortVertices[i];int index = -1;for (int j = 0; j < vertices.Length; j++){Vector2 v= vertices[j];if (Vector2.Distance(v,vector2)<0.01f){index = j;//得到原来在vertices对应的索引break;}}Vector2 dir1 = sortVertices[(i + 1)% sortVertices.Length] - sortVertices[i];int index2 = (i - 1) % sortVertices.Length;if (index2 < 0){index2 = sortVertices.Length + index2;}Vector2 dir2 = sortVertices[index2] - sortVertices[i];dir1 = dir1.normalized;//得到P为原点的2个向量AP,BP,将其相加得到PC,结果和PO点乘,大于90度结果取反dir2 = dir2.normalized;Vector2 dir = (dir1 + dir2).normalized;int rs = Vector2.Dot(dir, vector2.normalized)>0 ? 1: -1;dir *= rs;//沿得到的dir偏移expandedVertices[index] = sortVertices[i] + dir * len * ppu;}return expandedVertices;
}
完整代码
Shader
Shader "Custom/SpriteOutline"
{Properties{[PerRendererData]_MainTex ("Sprite Texture", 2D) = "white" {}_OutlineWidth ("Outline Width", Range(0,30)) = 5_OutlineColor ("Outline Color", Color) = (1,1,1,1)_ShowBound("Show Bound" ,Vector)=(0,0,1,1)}SubShader{Tags{"Queue"="Transparent" "IgnoreProjector"="true" "RenderType"="Transparent"}Cull OffLighting OffZWrite OffBlend SrcAlpha OneMinusSrcAlphaPass{CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"struct appdata{float4 vertex : POSITION;float4 color : COLOR;float4 uv : TEXCOORD0;float4 uv2 : TEXCOORD1;float4 tangent : TANGENT;};struct g2f{float2 uv : TEXCOORD0;half4 color : COLOR;float4 vertex : SV_POSITION;float2 lightingUV:TEXCOORD1;float2 uv2 : TEXCOORD2;float4 tangent : TANGENT;};sampler2D _MainTex;float4 _MainTex_ST;float4 _MainTex_TexelSize;float _OutlineWidth;float4 _OutlineColor;float4 _ShowBound;g2f vert(appdata v){g2f o;o.vertex = UnityObjectToClipPos(v.vertex);o.uv = TRANSFORM_TEX(v.uv, _MainTex);o.color = v.color;o.tangent = v.tangent;o.uv2=v.uv2;o.lightingUV = half2(ComputeScreenPos(o.vertex / o.vertex.w).xy);return o;}float isInRange(float2 uv){float2 rs = step(_ShowBound.xy, uv) * step(uv, _ShowBound.zw);return rs.x * rs.y;}float SampleTex(g2f i, float ii){const float OffsetX[12] = {1, 0, -1, 0, 0.866, 0.5, -0.5, -0.866, -0.866, -0.5, 0.5, 0.866};const float OffsetY[12] = {0, 1, 0, -1, 0.5, 0.866, 0.866, 0.5, -0.5, -0.866, -0.866, -0.5};float2 offset_uv = i.uv + float2(OffsetX[ii], OffsetY[ii]) * _MainTex_TexelSize.xy * _OutlineWidth;float sample_a=0;if(isInRange(offset_uv)>0){sample_a = tex2D(_MainTex, offset_uv).a;}float a = sample_a;a = step(0.2, a) * a;return a;}half4 frag(g2f i) : SV_Target{float4 col = tex2D(_MainTex, i.uv);col *= i.color;//_ShowBound = float4(0, 0, 1, 1);_ShowBound = i.tangent;_OutlineWidth=i.uv2.x;col.a *= isInRange(i.uv);float sum_a = 0;float threshold=0.5;if(col.a<threshold){sum_a += SampleTex(i, 0);if (sum_a < threshold){sum_a += SampleTex(i, 1);if (sum_a < threshold){sum_a += SampleTex(i, 2);if (sum_a < threshold){sum_a += SampleTex(i, 3);if (sum_a < threshold){sum_a += SampleTex(i, 4);if (sum_a < threshold){sum_a += SampleTex(i, 5);if (sum_a < threshold){sum_a += SampleTex(i, 6);if (sum_a < threshold){sum_a += SampleTex(i, 7);if (sum_a < threshold){sum_a += SampleTex(i, 8);if (sum_a < threshold){sum_a += SampleTex(i, 9);if (sum_a < threshold){sum_a += SampleTex(i, 10);if (sum_a < threshold){sum_a += SampleTex(i, 11);}}}}}}}}}}}}sum_a=step(threshold,sum_a)+sum_a;sum_a = saturate(sum_a);float4 outLineColor = float4(_OutlineColor.rgb, sum_a);float a = step(_OutlineWidth, 0.001);float4 finalCol = lerp(outLineColor, col,saturate(a+col.a));return finalCol;}ENDCG}}Fallback "Sprites/Default"
}
C#
public class SpriteOutline : MonoBehaviour
{private SpriteRenderer spriteRenderer;private Sprite sprite;private Vector2[] originalVertices;public float outlineWidth = 0f;private float ppu;void Start(){// 获取SpriteRenderer组件和SpritespriteRenderer = GetComponent<SpriteRenderer>();sprite = spriteRenderer.sprite;Vector4 bound = new Vector4();Vector2[] uvs= sprite.uv;bound =new Vector4(1, 1, 0, 0);for (int i = 0; i < uvs.Length; i++){var uv = uvs[i];bound.x = Mathf.Min(bound.x, uv.x);bound.y = Mathf.Min(bound.y, uv.y);bound.z = Mathf.Max(bound.z, uv.x);bound.w = Mathf.Max(bound.w, uv.y);}//spriteRenderer.material.SetVector("_ShowBound",bound);//spriteRenderer.material.SetFloat("_OutlineWidth",outlineWidth);// 获取原始的顶点、三角形和UV数据originalVertices = sprite.vertices;ppu = 1/sprite.pixelsPerUnit;// 扩展顶点Vector2[] expandedVertices = ExpandVertices(originalVertices, outlineWidth);Vector3[] vertices = System.Array.ConvertAll(expandedVertices, v => (Vector3)v);NativeArray<Vector3> array = new NativeArray<Vector3>(vertices, Allocator.Temp);sprite.SetVertexAttribute(VertexAttribute.Position,array);Vector2[] uv2Vector4s=new Vector2[vertices.Length];for (int i = 0; i < uv2Vector4s.Length; i++){uv2Vector4s[i] =new Vector2(outlineWidth, 0);}NativeArray<Vector2> uv2s_array = new NativeArray<Vector2>(uv2Vector4s, Allocator.Temp);sprite.SetVertexAttribute(VertexAttribute.TexCoord1,uv2s_array);Vector4[] tangents=new Vector4[vertices.Length];for (int i = 0; i < tangents.Length; i++){tangents[i] = bound;}NativeArray<Vector4> tangent_array = new NativeArray<Vector4>(tangents, Allocator.Temp);sprite.SetVertexAttribute(VertexAttribute.Tangent,tangent_array);}private void OnDestroy(){Vector3[] vertices = System.Array.ConvertAll(originalVertices, v => (Vector3)v);NativeArray<Vector3> array = new NativeArray<Vector3>(vertices, Allocator.Temp);sprite.SetVertexAttribute(VertexAttribute.Position,array);}private int CompareByAngle(Vector2 a, Vector2 b){float angleA = Mathf.Atan2(a.y, a.x);float angleB = Mathf.Atan2(b.y, b.x);return angleA.CompareTo(angleB);}Vector2[] ExpandVertices(Vector2[] vertices, float len){Vector2[] expandedVertices = new Vector2[vertices.Length];Vector2[] sortVertices = new Vector2[vertices.Length];for (int i = 0; i < sortVertices.Length; i++){sortVertices[i] = vertices[i];}Array.Sort(sortVertices, (a, b) => CompareByAngle(a, b));for (int i = 0; i < sortVertices.Length; i++){Vector2 vector2= sortVertices[i];int index = -1;for (int j = 0; j < vertices.Length; j++){Vector2 v= vertices[j];if (Vector2.Distance(v,vector2)<0.01f){index = j;break;}}Vector2 dir1 = sortVertices[(i + 1)% sortVertices.Length] - sortVertices[i];int index2 = (i - 1) % sortVertices.Length;if (index2 < 0){index2 = sortVertices.Length + index2;}Vector2 dir2 = sortVertices[index2] - sortVertices[i];dir1 = dir1.normalized;dir2 = dir2.normalized;Vector2 dir = (dir1 + dir2).normalized;int rs = Vector2.Dot(dir, vector2.normalized)>0 ? 1: -1;dir *= rs;expandedVertices[index] = sortVertices[i] + dir * len * ppu;}return expandedVertices;}
}
相关文章:
Unity 2d描边基于SpriteRender,高性能的描边解决方案
目标 以Unity默认渲染管线为例,打造不需要图片内边距,描边平滑,高性能的描边解决方案 前言 在2d游戏中经常需要给2d对象添加描边,来突出强调2d对象 当你去网上查找2d描边shader,移植到项目里面,大概率会…...
2025第2周 | JavaScript中的Symbol
目录 1. Symbol是个什么东东?1.1 语法 2. 为什么需要Symbol?3. 怎么使用Symbol?3.1 定义对象字面量3.2 新增对象属性3.3 使用 Object.defineProperty方式3.4 遍历 4. 静态方法4.1 Symbol.for(key)4.2 Symbol.keyFor(symbol) 2025,做想做的事࿰…...
Unity学习之UGUI进阶
一、事件监听接口 1、作用 用于实现类型长按、双击、拖拽等基础控件无法实现的功能 所有控件都能够添加更多的事件监听来处理对应的逻辑 2、事件监听接口类型 (1)常用事件接口 (2)不常用事件接口 3、使用事件监听接口 &#…...
IT面试求职系列主题-Jenkins
想成功求职,必要的IT技能一样不能少,先说说Jenkins的必会知识吧。 1) 什么是Jenkins Jenkins 是一个用 Java 编写的开源持续集成工具。它跟踪版本控制系统,并在发生更改时启动和监视构建系统。 2)Maven、Ant和Jenkins有什么区别…...
Allure 集成 pytest
Allure 是一个强大的测试报告工具,与 pytest 集成可以生成详细的测试报告,包括测试步骤、测试数据、截图、错误堆栈等。 1. 安装 Allure 和相关依赖 安装 pytest-allure-adaptor 插件: pip install allure-pytest确保本地已安装 Allure 工具。…...
Redis高频知识点
Redis 目录 1 Redis是AP的还是CP的?2 介绍一下Redis的集群方案?3 什么是Redis的数据分片?4 Redis为什么这么快?5 Redis 的事务机制是怎样的?7 Redis的持久化机制是怎样的?8 Redis 的过期策略是怎么样的&a…...
【电子通识】PWM驱动让有刷直流电机恒流工作
电机的典型驱动方法包括电压驱动、电流驱动以及PWM驱动。本文将介绍采用PWM驱动方式的恒流工作。 首先介绍的是什么是PWM驱动的电机恒流工作,其次是PWM驱动电机恒流工作时电路的工作原理。 PWM驱动 当以恒定的电流驱动电机时,电机会怎样工作呢࿱…...
PyMysql 02|(包含项目实战)数据库工具类封装
目录 七、数据库工具类封装 1、封装的目的 2、设计数据库工具类 3、实现类方法 1️⃣获取、关闭连接 2️⃣查询一条记录 3️⃣增删改数据 4️⃣完整封装代码实现 七、数据库工具类封装 1、封装的目的 将常用的数据库操作,封装到一个方法。 后续再操作数据…...
Pixel 6a手机提示无法连接移动网络,打电话失败!
1、开启VoLTE 2、如果没有,下载shizuku和PixelIMS应用。 shizuke Releases RikkaApps/Shizuku GitHub PixellMS Release v1.2.8 kyujin-cho/pixel-volte-patch GitHub 3、安装shizuke启动,开通root可以直接点击下面的启动,如果没有就…...
ubuntu20.04 在线安装postgresql 扩展postgis
基础配置 /etc/apt/sources.list # 添加pg官方基础配置deb http://apt.postgresql.org/pub/repos/apt/ focal-pgdg main# 添加ubuntu官方依赖(防止下载依赖错误)deb http://archive.ubuntu.com/ubuntu/ focal main restricted universe multiverse de…...
fitz获取pdf内容
1.获取pdf单页,及所有内容 import fitz # PyMuPDF# 打开 PDF 文件 pdf_path r"/data2/ljsang/0106/0725_Self-organization-of-plasticity-and-specialization-in-a-primi_2022_Cell-Syst.pdfπσΣ╕╖σσ║Θ║∩╝Φ╛τ▒│σ¡τ╛π.pdf" d…...
LabVIEW软件Bug的定义与修改
在LabVIEW软件开发过程中,bug(程序错误或缺陷)指的是程序中导致不符合预期行为的任何问题。Bug可能是由于编码错误、逻辑漏洞、硬件兼容性问题、系统资源限制等因素引起的。它可能会导致程序崩溃、功能无法正常执行或输出结果不符合预期。理解…...
Vue3(elementPlus) el-table替换/隐藏行箭头,点击整行展开
element文档链接: https://element-plus.org/zh-CN/component/form.html 一、el-table表格行展开关闭箭头替换成加减号 注:Vue3在样式中修改箭头图标无效,可能我设置不对,欢迎各位来交流指导 转变思路:隐藏箭头&…...
HTML 音频(Audio)
HTML 音频(Audio) HTML5 引入了新的音频标签 <audio>,使得在网页上嵌入音频文件变得更加简单。在此之前,播放音频通常需要依赖于第三方插件,如 Flash。但随着 HTML5 的普及,浏览器原生支持音频播放,极大地提升了用户体验和网页性能。 基本用法 要使用 HTML5 的音…...
linux音视频采集技术: v4l2
简介 在 Linux 系统中,视频设备的支持和管理离不开 V4L2(Video for Linux 2)。作为 Linux 内核的一部分,V4L2 提供了一套统一的接口,允许开发者与视频设备(如摄像头、视频采集卡等)进行交互。无…...
基于高斯混合模型的数据分析及其延伸应用(具体代码分析)
一、代码分析 (一)清除工作区和命令行窗口 clear; clc;clear;:该命令用于清除 MATLAB 工作区中的所有变量,确保代码运行环境的清洁,避免之前遗留的变量对当前代码运行产生干扰。例如,如果之前运行的代码中…...
内网基础-防火墙-隧道技术
内网对抗-网络通讯篇&防火墙组策略&入站和出站规则&单层双层&C2正反向上线 关闭第一个防火墙: 第一个上线就走反向或者正向 第二个上线走反向(第二个防火墙阻止入站) 关闭第二个防火墙: 第一个上线就走反向&a…...
123.【C语言】数据结构之快速排序挖坑法和前后指针法
目录 1.挖坑法 执行流程 代码 运行结果 可读性好的代码 2.前后指针法(双指针法) 执行流程 单趟排序代码 将单趟排序代码改造后 写法1 简洁的写法 3.思考题 1.挖坑法 执行流程 "挖坑法"顾名思义:要有坑位,一开始将关键值放入临时变量key中,在数组中形成…...
【沉默的羔羊心理学】汉尼拔的“移情”游戏:操纵与理解的艺术,精神分析学视角下的角色互动
终极解读《沉默的羔羊》:弗洛伊德精神分析学视角下的深层剖析 关键词 沉默的羔羊弗洛伊德精神分析学角色心理意识与潜意识性别与身份 弗洛伊德精神分析学简介 弗洛伊德的精神分析学是心理学的一个重要分支,主要关注人类行为背后的无意识动机和冲突。…...
Bytebase 3.0.1 - 可配置在 SQL 编辑器执行 DDL/DML
🚀 新功能 新增环境策略,允许在 SQL 编辑器内直接执行 DDL/DML 语句。 支持为 BigQuery 数据脱敏。 在项目下新增数据访问控制及脱敏管理页面。 在数据库页面,支持回滚到变更历史的某个版本。 🔔 兼容性变更 禁止工单创建…...
从零手写线性回归模型:PyTorch 实现深度学习入门教程
系列文章目录 01-PyTorch新手必看:张量是什么?5 分钟教你快速创建张量! 02-张量运算真简单!PyTorch 数值计算操作完全指南 03-Numpy 还是 PyTorch?张量与 Numpy 的神奇转换技巧 04-揭秘数据处理神器:PyTor…...
git使用指南-实践-搭建git私服
一.创建git私服的核心基础 所谓的git私服,其实就是在一个服务器上创建一个个的git仓库,并且这些仓库允许其在一个网络上被其他用户访问。 创建一个最素的git私服:随便找一台linux服务器,这里假设其ip为192.168.0.6,使…...
Node.js JXcore 打包教程
Node.js JXcore 打包教程 介绍 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,它允许开发者使用 JavaScript 编写服务器端和网络应用程序。JXcore 是一个流行的 Node.js 发行版,它支持将 Node.js 应用程序打包成单一的可执行文件,使得部署和分发变得更加容易…...
Xshell 8 最新中文免安装绿色版
前言 Xshell8是一个非常受欢迎的远程连接管理软件,它的界面简单易懂,用起来特别方便。能支持好多种连接方式,比如SSH1、SSH2、SFTP、TELNET等等,还有串行协议和其他一些高级功能,基本上你想连什么都能满足。而且&…...
结构型模式4.装饰器模式
结构型模式 适配器模式(Adapter Pattern)桥接模式(Bridge Pattern)组合模式(Composite Pattern)装饰器模式(Decorator Pattern)外观模式(Facade Pattern)享元…...
备考蓝桥杯:顺序表详解(静态顺序表,vector用法)
目录 1.顺序表的概念 2.静态顺序表的实现 总代码 3.stl库动态顺序表vector 测试代码 1.顺序表的概念 要理解顺序表,我们要先了解一下什么是线性表 线性表是n个具有相同特征的数据元素的序列 这就是一个线性表 a1是表头 a4是表尾 a2是a3的前驱 a3是a2的后继 空…...
使用uniapp 微信小程序一些好用的插件分享
总结一下自己在开发中遇见的一问题,通过引入组件可以快速的解决 1.zxz-uni-data-select 下拉框选择器(添加下拉框检索,多选功能,多选搜索功能,自定义 下拉框插件,使用这个的原因是因为 uniui uview 组件库下拉框太…...
当遇到 502 错误(Bad Gateway)怎么办
很多安装雷池社区版的时候,配置完成,访问的时候可能会遇到当前问题,如何解决呢? 客户端,浏览器排查 1.刷新页面和清除缓存 首先尝试刷新页面,因为有时候 502 错误可能是由于网络临时波动导致服务器无法连…...
R语言安装教程与常见问题
生物信息基础入门笔记 R语言安装教程与常见问题 今天和大家聊一个非常基础但是很重要的技术问题——如何在不同操作系统上安装R语言?作为生物信息学数据分析的神兵利器,R语言的安装可谓是入门第一步,学术打工人的必备技能。今天分享在Windows…...
windows 下基于docker 部署 guacamole
背景 Apache Guacamole 是一种无客户端或插件的远程桌面网关。它支持多个标准协议,如 VNC、RDP 和 SSH等。记录下部署过程。 步骤 1, 安装docker desktop choco install docker-desktop -y 注: 若windows 11还未安装wsl,则需要…...
JVM实战—OOM的生产案例
1.每秒仅上百请求的系统为何会OOM(RPC超时时间设置过长导致QPS翻几倍) (1)案例背景 在这个案例中,一个每秒仅仅只有100请求的系统却因频繁OOM而崩溃。这个OOM问题会涉及:Tomcat底层工作原理、Tomcat内核参数的设置、服务请求超时时间。 (2)系统发生OOM的…...
oracle闪回恢复数据:(闪回查询,闪回表,闪回库,回收站恢复)
oracle的闪回查询,可以查询提交在表空间的闪回数据,并可以还原所查询的数据,用于恢复短时间内的delele 或者 update 误操作,非常方便,缺点是只能恢复大概几小时内的数据。 文章目录 概要闪回查询恢复数据的主要方法包括…...
STM32 单片机 练习项目 LED灯闪烁LED流水灯蜂鸣器 未完待续
个人学习笔记 文件路径:程序源码\STM32Project-DAP&DAPmini\1-1 接线图 3-1LED闪烁图片 新建项目 新建项目文件 选择F103C8芯片 关闭弹出窗口 拷贝资料 在项目内新建3个文件夹 Start、Library、User Start文件拷贝 从资料中拷贝文件 文件路径:固…...
计算机网络之---数据传输与比特流
数据传输的概念 数据传输是指将数据从一个设备传输到另一个设备的过程。传输过程涉及将高层协议中的数据(如包、帧等)转化为比特流,在物理介质上传输。 比特流的概念 比特流是数据传输中最基本的单位,它是由0和1组成的连续比特…...
es 单个节点cpu过高
背景: es 单个节点cpu一直持续很高,其它节点cpu不高。 观察这个节点的jvm使用率比较高,怀疑是jvm内存没释放导致内存寻址效率低,引起cpu过高。 解决方法:手动执行fullgc, 在线执行对业务无影响。 jcmd pi…...
设计模式 结构型 组合模式(Composite Pattern)与 常见技术框架应用 解析
组合模式(Composite Pattern)是一种结构型设计模式,它允许你将对象组合成树形结构来表示“部分-整体”的层次结构。通过这种模式,客户端可以一致地处理单个对象和对象组合。 在软件开发中,我们经常会遇到处理对象的层…...
pcie学习记录(1):基于xdma的工程搭建
写在最前面 Vivado的版本!!!(后面验证是错误的思路,可以不用看) XDMA PCIE开发期间两个版本问题的解决https://blog.csdn.net/kris_paul/article/details/128131527 我说实话在这里卡了很久ÿ…...
HTML5 动画效果:淡入淡出(Fade In/Out)详解
HTML5 动画效果:淡入淡出(Fade In/Out)详解 淡入淡出(Fade In/Out)是一种常见的动画效果,使元素逐渐显现或消失,增强用户体验。以下是淡入淡出的详细介绍及实现示例。 1. 淡入淡出的特点 平滑…...
AI赋能R-Meta分析核心技术:从热点挖掘到高级模型、助力高效科研与论文发表
Meta分析是针对某一科研问题,根据明确的搜索策略、选择筛选文献标准、采用严格的评价方法,对来源不同的研究成果进行收集、合并及定量统计分析的方法,现已广泛应用于农林生态,资源环境等方面,成为Science、Nature论文的…...
maven如何从外部导包
1.找到你项目的文件位置,将外部要导入的包复制粘贴进你当前要导入的项目下。 2.从你的项目目录下选中要导入的包的pom文件即可导包成功 注意一定是选中对应的pom文件 导入成功之后对应的pom.xml文件就会被点亮...
使用python发送gmail邮件
首先要有一个gmail邮箱:https://accounts.google.com/lifecycle/steps/signup/name?ddm1&dshS237022023:1736341599452877&flowEntrySignUp&flowNameGlifWebSignIn&TLAE–Llw01wbAW0P9gYEloi_C5wBurZd0MeIi4B1j8daH-72CNcc-1qwapbIRA_UZ&conti…...
Android 绘制学习总结
1、刷新率介绍 我们先来理一下基本的概念: 1、60 fps 的意思是说,画面每秒更新 60 次 2、这 60 次更新,是要均匀更新的,不是说一会快,一会慢,那样视觉上也会觉得不流畅 3、每秒 60 次,也就是 1…...
推动多语言语音科技迈向新高度:INTERSPEECH 2025 ML-SUPERB 2.0 挑战赛
随着语音技术在各领域应用的迅速扩展,全球语言与口音的多样性成为技术进一步突破的重大挑战。为了应对这一难题,来自卡内基梅隆大学(CMU)、斯坦福大学(Stanford University)、乔治梅森大学(George Mason Un…...
『SQLite』子查询可以这样用
摘要:本节主要讲子查询的使用,可以在查询、更新、修改、删除等操作中使用。 什么是子查询? 子查询是一种在 SELECT-SQL 语言中嵌套查询下层的程序模块。当一个查询是另一个查询的条件时,称之为子查询(Sub Query&#…...
thingsboard通过mqtt设备连接及数据交互---记录一次问题--1883端口没开,到服务器控制面板中打开安全组1883端口
1,链接不上:原因是1883端口没开,到服务器控制面板中打开安全组1883端口 2,参考链接: https://blog.csdn.net/bujingyun8/article/details/120024788...
Pod状态为“被驱逐(evicted)”的解决思路
在Kubernetes集群中,Pod状态为“被驱逐(evicted)”表示Pod无法在当前节点上继续运行,已被集群从节点上移除。针对Pod被驱逐的问题,以下是一些常见的解决方法: 一、识别被驱逐的原因 资源不足:…...
【代码随想录-数组篇02】:双指针(快慢指针)法相关力扣练习题
提示1:本篇共包含5道题,全部用python语言进行实践,看会不如行动会,请大家多多实践~ 提示2:强烈推荐 代码随想录 提示3:博主最近在跟着【代码随想录】进行刷题,有小伙伴有想法的可以私…...
Docker基础
Docker基础 命令介绍 其中,比较常见的命令有: 命令说明文档地址docker pull拉取镜像docker pulldocker push推送镜像到DockerRegistrydocker pushdocker images查看本地镜像docker imagesdocker rmi删除本地镜像docker rmidocker run创建并运行容器&am…...
计算机网络之---传输介质
传输介质的定义 传输介质是计算机网络中用于传递信号的物理媒介。它将数据从发送方传输到接收方,可以是有线的(如铜线、电缆、光纤等)或者无线的(如无线电波、微波等)。传输介质的作用是承载和传播物理信号(…...
数据结构:LinkedList与链表—面试题(三)
目录 1、移除链表元素 2、反转链表 3、链表的中间结点 4、返回倒数第k个结点 5、合并两个有序链表 1、移除链表元素 习题链接https://leetcode.cn/problems/remove-linked-list-elements/description/ 描述:给你一个链表的头节点 head 和一个整数 val ÿ…...