Unity 2d描边基于SpriteRender,高性能的描边解决方案

news2025/1/9 2:24:40

目标

以Unity默认渲染管线为例,打造不需要图片内边距,描边平滑,高性能的描边解决方案

前言

在2d游戏中经常需要给2d对象添加描边,来突出强调2d对象
当你去网上查找2d描边shader,移植到项目里面,大概率会得到这个情况
如果描边的基本原理不清楚的可以看我之前的文章 文本描边
在这里插入图片描述

①出现了边缘看起来像是被截断了,这个是因为使用在超过的范围,三角形没有覆盖到,这里没有片元着色器进行渲染,所以我们要扩展三角形的顶点,同时扩展uv
图片如果有内边距,并且描边宽度较小,可能不会出现。有时即使图片内边距足够了也会出现这个情况,这是因为生成的三角形的原因(同上),在图片的导入设置MeshType设置为FullRect可以解决,如下图,但是这会增加片元着色器的负担,会有更多的片元需要渲染,唯一的好处是可以减少顶点数据的内存。这里我们为性能考虑,使用三角形渲染,扩展多边形的顶点和uv
但是如果所以要描边的物体加内边距,会增加内存消耗
②不该有描边的区域出现了描边,
在这里插入图片描述

要解决的问题

  1. 边缘被截断了=>扩展多边形的顶点和uv
  2. 描边有锯齿感=>采样次数不足,只沿着4个或8个方向采样,在outlineWidth较大时会出现问题,增多采样次数,在实际测试中,权衡效果和性能,12次最佳
  3. 描边和图片过渡处不平滑=>在原图片边缘,边缘aphla为0-1,lerp(outlineCol,col,a),a为0,显示描边,a为1显示原来的图片
  4. 不该有描边的区域出现了描边
    ①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设置为0
    float 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的部分认为1
    sum_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组件和Sprite
	spriteRenderer = 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 Off
        Lighting Off
        ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha
        Pass
        {
            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组件和Sprite
		spriteRenderer = 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;
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2273531.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

自动驾驶相关知识学习笔记

一、概要 因为想知道SIL、HIL是什么仿真工具&#xff0c;故而浏览了自动驾驶相关的知识。 资料来源《自动驾驶——人工智能理论与实践》胡波 林青 陈强 著&#xff1b;出版时间&#xff1a;2023年3月 二、图像的分类、分割与检测任务区别 如图所示&#xff0c;这些更高阶的…

“深入浅出”系列之FFmpeg:(1)音视频开发基础

我的音视频开发大部分内容是跟着雷霄骅大佬学习的&#xff0c;所以笔记也是跟雷老师的博客写的。 一、音视频相关的基础知识 首先播放一个视频文件的流程如下所示&#xff1a; FFmpeg的作用就是将H.264格式的数据转换成YUV格式的数据&#xff0c;然后SDL将YUV显示到电脑屏幕上…

日志服务 SQL 引擎全新升级

作者&#xff1a;戴志勇、顾汉杰&#xff08;执少&#xff09; SQL 作为 SLS 基础功能&#xff0c;每天承载了用户大量日志数据的分析请求&#xff0c;既有小数据量的快速查询&#xff08;如告警、即席查询等&#xff09;&#xff1b;也有上万亿数据规模的报表级分析。SLS 作为…

20250107在WIN10下使用无线ADB连接Andorid7.1.2

connected to 192.168.3.217:5555 adb shell 20250107在WIN10下使用无线ADB连接Andorid7.1.2 2025/1/7 18:12 缘起&#xff1a;公司买了一台6000-7000&#xffe5;的地面站【Andorid7.1.2】&#xff0c;需要通过ifconfig命令来获取其中的网络信息。 虽然系统是VERY非常的陈旧&a…

浙江省自然资源厅:基于“浙里办”的自然资源移动政务服务创新实践——“浙里自然资源”

摘 要&#xff1a;本文基于浙江省自然资源移动政务服务的创新实践&#xff0c;设计和实现“浙里自然资源”应用&#xff0c;依托浙江省省域空间治理数字化平台特有的架构基础&#xff0c;在提升功能性和可用性、加强运营力度、丰富服务内容等方面采取了管理举措和技术创新。通…

使用 Jupyter Notebook:安装与应用指南

文章目录 安装 Jupyter Notebook1. 准备环境2. 安装 Jupyter Notebook3. 启动 Jupyter Notebook4. 选择安装方式&#xff08;可选&#xff09; 二、Jupyter Notebook 的基本功能1. 单元格的类型与运行2. 可视化支持3. 内置魔法命令 三、Jupyter Notebook 的实际应用场景1. 数据…

NeurIPS 2024 | 像素级LLM实现图像视频理解、生成、分割和编辑大统一(昆仑万维等)

Accepted by NeurIPS 2024 文章链接&#xff1a;https://arxiv.org/pdf/2412.19806 项目链接&#xff1a;https://vitron-llm.github.io/ Github链接&#xff1a;https://github.com/SkyworkAI/Vitron 亮点直击 首次提出了一种通用的视觉多模态大语言模型&#xff08;MLLM&…

嵌入式技术之Linux(Ubuntu) 一

一、Linux入门 1.硬件和操作系统以及用户的关系 一个传感器&#xff0c;获得数据后&#xff0c;需要向服务器发送数据。传感器传数据给上位机。 上位机需要一个程序来接收数据&#xff0c;那么这个上位机是什么机器&#xff1f; 我们的笔记本电脑就可以当成上位机。 两个手…

用户界面软件02

基于表单的用户界面 在“基于表单的用户界面”里面&#xff0c;用户开始时选中某个业务处理&#xff08;模块&#xff09;&#xff0c;然后应用程序就使用一系列的表单来引导用户完成整个处理过程。大型机系统上的大部分用户界面都是这样子的。[Cok97]中有更为详细的讨论。 面…

YOLOv8/YOLOv11改进 添加CBAM、GAM、SimAM、EMA、CAA、ECA、CA等多种注意力机制

目录 前言 CBAM GAM SimAM EMA CAA ECA CA 添加方法 YAML文件添加 使用改进训练 前言 本篇文章将为大家介绍Ultralytics/YOLOv8/YOLOv11中常用注意力机制的添加&#xff0c;可以满足一些简单的涨点需求。本文仅写方法&#xff0c;原理不多讲解&#xff0c;需要可跳…

【express-generator】05-路由中间件和错误处理(第一阶段收尾)

一、前言 上篇文章我们介绍了express-generator的请求体解析&#xff0c;重点讲了常用的请求体数据格式&#xff08;JSON/URL 编码的表单数据&#xff09;以及一个FILE文件上传&#xff0c;同时搭配代码示范进行辅助理解。 二、本篇重点 我们继续第一阶段的知识&#xff0c;…

python无需验证码免登录12306抢票 --selenium(2)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 [TOC](python无需验证码免登录12306抢票 --selenium(2)) 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 就在刚刚我抢的票&#xff1a;2025年1月8日…

深度学习驱动的蛋白质设计技术与实践

通过设计特定的蛋白质结构&#xff0c;可以实现预期的生物功能&#xff0c;如催化特定化学反应、识别和结合特定分子、调控生物信号传导等&#xff0c;为生物医学、药物研发、生物技术等领域提供重要工具和解决方案。传统的蛋白质设计方法主要依赖于已知蛋白质结构的同源建模、…

【动态重建】时间高斯分层的长体积视频

标题&#xff1a;Representing Long Volumetric Video with Temporal Gaussian Hierarchy 来源&#xff1a;浙江大学 链接&#xff1a;https://zju3dv.github.io/longvolcap/ 文章目录 摘要一、前言二、主要方法2.1 时间高斯分层2.2 高效渲染2.3 层次结构更新2.4 紧凑的外观模型…

【STM32+CubeMX】 新建一个工程(STM32F407)

相关文章&#xff1a; 【HAL库】 STM32CubeMX 教程 1 --- 下载、安装 目录 第一部分、新建工程 第二部分、工程文件解释 第三部分、编译验证工程 友情约定&#xff1a;本系列的前五篇&#xff0c;为了方便新手玩家熟悉CubeMX、Keil的使用&#xff0c;会详细地截图每一步Cu…

el-date-picker 不响应change事件的解决办法

前言 接到需要把element plus组件的日期时间选择器的input输入框展示隐藏&#xff0c;遇到点击确认按钮change事件不被触发问题&#xff0c;解决办法如下&#xff1a; ①visible-change的回调参考 即根据visible-change的方法里的回调参数进行需要操作 const visibleChange …

api开发如何在代码中使用京东商品详情接口的参数?

选择编程语言和相关工具 以 Python 为例&#xff0c;你可以使用requests库来发送 HTTP 请求获取接口数据。如果是 Java&#xff0c;可以使用OkHttp等库。 Python 示例 假设你已经安装了requests库&#xff0c;以下是一个简单的代码示例来获取和使用京东商品详情接口参数&#…

【docker系列】可视化Docker 管理工具——Portainer

1. 介绍 Portainer是一个可视化的Docker操作界面&#xff0c;提供状态显示面板、应用模板快速部署、容器镜像网络数据卷的基本操作&#xff08;包括上传下载镜像&#xff0c;创建容器等操作&#xff09;、事件日志显示、容器控制台操作、Swarm集群和服务等集中管理和操作、登录…

机器学习基础-大语言模型

目录 大语言模型的基本概念 “大”体现在什么地方&#xff1f; 预训练微调两阶段的基本流程和作用 第一阶段&#xff1a;利用语言模型进行无监督预训练 第二阶段&#xff1a;通过监督微调的模式解决下游任务 BERT模型中MLM和NSP机制基本概念 MLM NSP Prompt学习的基本概…

Ubuntu挂载Windows 磁盘,双系统

首先我们需要在终端输入这个命令&#xff0c;来查看磁盘分配情况 lsblk -f 找到需要挂载的磁盘&#xff0c;检查其类型&#xff08; 我的/dev/nvme2n1p1类型是ntfs&#xff0c;名字叫3500winData&#xff09; 然后新建一个挂载磁盘的目录&#xff0c;我的是/media/zeqi/3500wi…