Unity地面交互效果——4、制作地面凹陷轨迹

  大家好,我是阿赵。
  上一篇介绍了曲面细分着色器的基本用法和思路,这一篇在曲面细分的基础上,制作地面凹陷的轨迹效果。

一、思路分析

  这次需要达到的效果是这样的:
在这里插入图片描述

  从效果上看,这个凹陷在地面下的轨迹,里面有法线变化的效果,然后地表模型也是真实的发生了凹陷变化。所以其实就是之前说到的法线混合轨迹和曲面细分的综合应用。
在这里插入图片描述

  曲面细分的等级实际上是根据自己的需要去调整的
在这里插入图片描述

  做到这么高的细分也可以,但实际上也没太大必要。
  之前已经介绍过法线轨迹的混合,然后有了曲面细分之后,剩下的事情就是把两者结合起来,并且做一个顶点偏移效果了。顶点偏移,实际上也是通过绘制出来的轨迹做偏移,比如我现在有一张黑白的图片,轨迹就绘制在上面,然后对贴图进行局部采样,最后把某些色值范围内的顶点做一个高度的偏移,就做出了这个凹陷的效果了。

二、贴图通道的利用

  还记得最开始的一篇的内容吗?我是使用一个顶视正交摄像机去拍摄这个范围内的轨迹的。
在这里插入图片描述

  这里的这个法线贴图,实际上只用到了RGB三个通道而已,还有一个A通道可以用。于是,可以把需要实现高度偏移的黑白图片,记录在这张贴图的A通道。
在这里插入图片描述

  这样,只需要打一个摄像机,渲染一张RenderTexture,就同时实现了法线贴图和顶点偏移2种效果了。
  值得注意的是,我这里只把需要凹陷的地方刷成了白色,然后不需要凹陷的地方默认是黑色。这样做的好处是计算简单,只需要根据A通道的0-1去决定凹陷的深度就行了。不过有时候,做轨迹是不止凹陷的,不如一个球在沙地上移动,实际上轨迹的边缘还会凸起来的。如果要实现这种效果,那么就要把平地的Alpha值设置成0.5,然后大于0.5的部分是凸起,小于0.5的部分是凹陷。

三、Shader实现

1、顶点片段着色器里的实现:

Shader "azhao/VFGround"
{
    Properties
    {
		_MainTex("Texture", 2D) = "white" {}
		_Color("Color", Color) = (1,1,1,1)
		_centerPos("CenterPos", Vector) = (0,0,0,0)
		_minDis("minVal", Float) = 0
		_maxDis("maxVal", Float) = 20
		_factor("factor", Float) = 15
		_footstepRect("footstepRect",Vector) = (0,0,0,0)
		_footstepTex("footstepTex",2D) = "gray"{}
		_height("height" ,Float) = 0.3
		_NormalTex("Normal Tex", 2D) = "black"{}
		_normalScale("normalScale", Range(-1 , 1)) = 0
		_specColor("SpecColor",Color) = (1,1,1,1)
		_shininess("shininess", Range(1 , 100)) = 1
		_specIntensity("specIntensity",Range(0,1)) = 1
		_ambientIntensity("ambientIntensity",Range(0,1)) = 1


    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
			//在正常的vertex和fragment之间还需要hull和domain,所以在这里加上声明
			#pragma hull hullProgram
			#pragma domain domainProgram
            #pragma fragment frag


            #include "UnityCG.cginc"
			sampler2D _MainTex;
			float4 _MainTex_ST;
			fixed4 _Color;
			uniform float _minDis;
			uniform float _maxDis;
			uniform float3 _centerPos;
			uniform float _factor;
			float4 _footstepRect;
			sampler2D _footstepTex;
			float _height;
			sampler2D _NormalTex;
			float4 _NormalTex_ST;
			float _normalScale;
			float4 _specColor;
			float _shininess;
			float _specIntensity;
			float _ambientIntensity;
			struct a2v
			{
				float4 pos	: POSITION;
				float2 uv  : TEXCOORD0;
				float3 normal:NORMAL;
				float3 tangent:TANGENT;
			};

			struct v2t
			{				
				float2 uv  : TEXCOORD0;
				float2 footstepUV : TEXCOORD1;
				float4 worldPos	: TEXCOORD2;
				float3 worldNormal : TEXCOORD3;
				float3 worldTangent :TEXCOORD4;
				float3 worldBitangent : TEXCOORD5;
			};
			struct t2f
			{
				float4 clipPos:SV_POSITION;
				float2 uv: TEXCOORD0;				
				float2 footstepUV:TEXCOORD1;
				float4 worldPos:TEXCOORD2;
				float3 worldNormal : TEXCOORD3;
				float3 worldTangent :TEXCOORD4;
				float3 worldBitangent : TEXCOORD5;
				
			};

			struct TessOut
			{
				float2 uv  : TEXCOORD0;
				float4 worldPos	: TEXCOORD1;
				float2 footstepUV:TEXCOORD2;
				float3 worldNormal : TEXCOORD3;
				float3 worldTangent :TEXCOORD4;
				float3 worldBitangent : TEXCOORD5;
				
			};
			struct TessParam
			{
				float EdgeTess[3]	: SV_TessFactor;//各边细分数
				float InsideTess : SV_InsideTessFactor;//内部点细分数
			};

			float RemapUV(float min, float max, float val)
			{
				return (val - min) / (max - min);
			}
			half3 UnpackScaleNormal(half4 packednormal, half bumpScale)
			{
				half3 normal;
				//由于法线贴图代表的颜色是0到1,而法线向量的范围是-1到1
				//所以通过*2-1,把色值范围转换到-1到1
				normal = packednormal * 2 - 1;
				//对法线进行缩放
				normal.xy *= bumpScale;
				//向量标准化
				normal = normalize(normal);
				return normal;
			}
			//获取Lambert漫反射值
			float GetLambertDiffuse(float3 worldPos, float3 worldNormal)
			{
				float3 lightDir = UnityWorldSpaceLightDir(worldPos);
				float NDotL = saturate(dot(worldNormal, lightDir));
				
				return NDotL;
			}

			//获取BlinnPhong高光
			float GetBlinnPhongSpec(float3 worldPos, float3 worldNormal)
			{
				float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
				float3 halfDir = normalize((viewDir + _WorldSpaceLightPos0.xyz));
				float specDir = max(dot(normalize(worldNormal), halfDir), 0);
				float specVal = pow(specDir, _shininess);
				return specVal;
			}

			v2t vert(a2v i)
			{
				v2t o;
				o.worldPos = mul(unity_ObjectToWorld,i.pos);
				o.uv = i.uv;
				o.footstepUV = float2(RemapUV(_footstepRect.x, _footstepRect.z, o.worldPos.x), RemapUV(_footstepRect.y, _footstepRect.w, o.worldPos.z));
				o.worldNormal = UnityObjectToWorldNormal(i.normal);
				o.worldTangent = UnityObjectToWorldDir(i.tangent);
				o.worldBitangent = cross(o.worldNormal, o.worldTangent);
				return o;
			}
			//在hullProgram之前必须设置这些参数,不然会报错
			[domain("tri")]//图元类型,可选类型有 "tri", "quad", "isoline"
			[partitioning("integer")]//曲面细分的过渡方式是整数还是小数
			[outputtopology("triangle_cw")]//三角面正方向是顺时针还是逆时针
			[outputcontrolpoints(3)]//输出的控制点数
			[patchconstantfunc("ConstantHS")]//对应之前的细分因子配置阶段的方法名
			[maxtessfactor(64.0)]//最大可能的细分段数

			//vert顶点程序之后调用,计算细分前的三角形顶点信息
			TessOut hullProgram(InputPatch<v2t, 3> i, uint idx : SV_OutputControlPointID)
			{
				TessOut o;
				o.worldPos = i[idx].worldPos;
				o.uv = i[idx].uv;
				o.footstepUV = i[idx].footstepUV;
				o.worldNormal = i[idx].worldNormal;
				o.worldTangent = i[idx].worldTangent;
				o.worldBitangent = i[idx].worldBitangent;
				return o;
			}

			//指定每个边的细分段数和内部细分段数
			TessParam ConstantHS(InputPatch<v2t, 3> i, uint id : SV_PrimitiveID)
			{
				TessParam o;
				float4 worldPos = (i[0].worldPos + i[1].worldPos + i[2].worldPos) / 3;
				float smoothstepResult = smoothstep(_minDis, _maxDis, distance(worldPos.xz, _centerPos.xz));
				float fac = max((1.0 - smoothstepResult)*_factor, 1);
				//由于我这里是根据指定的中心点和半径范围来动态算细分段数,所以才有这个计算,不然可以直接指定变量来设置。
				o.EdgeTess[0] = fac;
				o.EdgeTess[1] = fac;
				o.EdgeTess[2] = fac;
				o.InsideTess = fac;
				return o;
			}

			//在domainProgram前必须设置domain参数,不然会报错
			[domain("tri")]
			//细分之后,把信息传到frag片段程序
			t2f domainProgram(TessParam tessParam, float3 bary : SV_DomainLocation, const OutputPatch<TessOut, 3> i)
			{
				t2f o;				
				//线性转换

				float2 uv = i[0].uv * bary.x + i[1].uv * bary.y + i[2].uv * bary.z;
				o.uv = uv;
				o.footstepUV = i[0].footstepUV * bary.x + i[1].footstepUV * bary.y + i[2].footstepUV * bary.z;
				float4 footstepCol = tex2Dlod(_footstepTex, float4(o.footstepUV, 0, 0.0));
				float addVal = footstepCol.a*_height;

				float4 worldPos = i[0].worldPos * bary.x + i[1].worldPos * bary.y + i[2].worldPos * bary.z;
				worldPos.y = worldPos.y - addVal;
				o.worldPos = worldPos;
				o.clipPos = UnityWorldToClipPos(worldPos);
				o.worldNormal = i[0].worldNormal * bary.x + i[1].worldNormal * bary.y + i[2].worldNormal * bary.z;
				o.worldTangent = i[0].worldTangent * bary.x + i[1].worldTangent * bary.y + i[2].worldTangent * bary.z;
				o.worldBitangent = i[0].worldBitangent * bary.x + i[1].worldBitangent * bary.y + i[2].worldBitangent * bary.z;
				return o;
			}
            fixed4 frag (t2f i) : SV_Target
            {
    //            // sample the texture
				//float2 mainUV = i.uv*_MainTex_ST.xy + _MainTex_ST.zw;
    //            fixed4 col = tex2D(_MainTex, mainUV)*_Color;
				//fixed4 footstepCol = tex2D(_footstepTex, i.footstepUV);
				//fixed3 footstepRGB = fixed3(footstepCol.r - 0.5, footstepCol.g - 0.5, footstepCol.b - 0.5);
				//footstepRGB = footstepRGB * footstepCol.a;
				//fixed4 finalCol = col;// fixed4(saturate(col.rgb + footstepRGB), 1);
    //            return finalCol;
				//采样漫反射贴图的颜色
				half4 col = tex2D(_MainTex, i.uv*_MainTex_ST.xy + _MainTex_ST.zw);
				//计算法线贴图的UV
				half2 normalUV = i.uv * _NormalTex_ST.xy + _NormalTex_ST.zw;
				//采样法线贴图的颜色
				half4 normalCol = tex2D(_NormalTex, normalUV);

				fixed4 footstepCol = tex2D(_footstepTex, i.footstepUV);
				fixed3 footstepRGB = UnpackScaleNormal(footstepCol*footstepCol.a, _normalScale).rgb;

				//得到切线空间的法线方向
				half3 normalVal = UnpackScaleNormal(normalCol, _normalScale).rgb;
				//normalVal = footstepRGB;
				//normalVal = normalize(normalVal + footstepRGB);
				normalVal -= footstepRGB;
				//构建TBN矩阵
				float3 tanToWorld0 = float3(i.worldTangent.x, i.worldBitangent.x, i.worldNormal.x);
				float3 tanToWorld1 = float3(i.worldTangent.y, i.worldBitangent.y, i.worldNormal.y);
				float3 tanToWorld2 = float3(i.worldTangent.z, i.worldBitangent.z, i.worldNormal.z);

				//通过切线空间的法线方向和TBN矩阵,得出法线贴图代表的物体世界空间的法线方向
				float3 worldNormal = float3(dot(tanToWorld0, normalVal), dot(tanToWorld1, normalVal), dot(tanToWorld2, normalVal));

				//用法线贴图的世界空间法线,算漫反射
				half diffuseVal = GetLambertDiffuse(i.worldPos, worldNormal);
				diffuseVal = clamp(diffuseVal, 0.6, 1);
				diffuseVal = pow(diffuseVal, 1.5);

				//用法线贴图的世界空间法线,算高光角度
				half3 specCol = _specColor * GetBlinnPhongSpec(i.worldPos, worldNormal)*_specIntensity;

				//最终颜色 = 环境色+漫反射颜色+高光颜色
				half3 finalCol = UNITY_LIGHTMODEL_AMBIENT * _ambientIntensity + saturate(col.rgb*diffuseVal) + specCol;
				return half4(finalCol,1);
            }
            ENDCG
        }
    }
}

值得注意的地方:
1.为了获得细分后的顶点做偏移,所以顶点偏移的过程是写在domainProgram
2.在顶点类程序里面如果需要采样贴图,并不是使用tex2D方法,而是使用tex2Dlod方法

2、Surface着色器里的实现:

Shader "azhao/SurfaceGround"
{
	Properties
	{
		_MainTex("MainTex", 2D) = "white" {}
		_NormalTex("NormalTex", 2D) = "white" {}
		_minDis("minVal", Float) = 0
		_maxDis("maxVal", Float) = 15
		_factor("factor", Float) = 1
		_height("height",float) = 0

		_centerPos("centerPos", Vector) = (0,0,0,0)		
		_footstepRect("footstepRect", Vector) = (0,0,0,0)		
		_footstepTex("footstepTex", 2D) = "black" {}		
		[HideInInspector] _texcoord("", 2D) = "white" {}
		[HideInInspector] __dirty("", Int) = 1
	}

	SubShader
	{
		Tags{ "RenderType" = "Opaque"  "Queue" = "Geometry+0" }
		Cull Back
		CGPROGRAM
		#include "UnityStandardUtils.cginc"
		#pragma target 4.6
		#pragma surface surf Standard keepalpha addshadow fullforwardshadows vertex:vertexDataFunc tessellate:tessFunction 
		struct Input
		{
			float2 uv_texcoord;
			float3 worldPos;
		};

		sampler2D _NormalTex;
		float4 _NormalTex_ST;
		sampler2D _footstepTex;
		float4 _footstepRect;
		sampler2D _MainTex;
		float4 _MainTex_ST;
		float _minDis;
		float _maxDis;
		float4 _centerPos;
		float _factor;
		float _height;



		float RemapUV(float min, float max, float val)
		{
			return (val - min) / (max - min);
		}


		float4 tessFunction( appdata_full v0, appdata_full v1, appdata_full v2 )
		{
			float4 vertex = (v0.vertex + v1.vertex + v2.vertex) / 3;
			float3 worldPos = mul( unity_ObjectToWorld, vertex );
			float smoothstepResult = smoothstep( _minDis , _maxDis , distance(_centerPos.xz, worldPos.xz));
			float tessVal = max( ( ( 1.0 - smoothstepResult ) * _factor ) , 0.1 );
			return tessVal;
		}

		void vertexDataFunc( inout appdata_full v )
		{
			float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
			float temp_output_1_0_g1 = _footstepRect.x;
			float temp_output_1_0_g2 = _footstepRect.y;
			float2 footstepUV = float2(RemapUV(_footstepRect.x, _footstepRect.z, worldPos.x), RemapUV(_footstepRect.y, _footstepRect.w, worldPos.z));

			float4 tex2DNode = tex2Dlod(_footstepTex, float4(footstepUV, 0, 0.0));
			float offset = tex2DNode.a * _height*-0.01;
			v.vertex.y += offset;
		}

		void surf( Input i , inout SurfaceOutputStandard o )
		{
			float2 uv_NormalTex = i.uv_texcoord * _NormalTex_ST.xy + _NormalTex_ST.zw;
			float3 worldPos = i.worldPos;
			float temp_output_1_0_g1 = _footstepRect.x;
			float temp_output_1_0_g2 = _footstepRect.y;
			float2 footstepUV = float2(RemapUV(_footstepRect.x, _footstepRect.z, worldPos.x), RemapUV(_footstepRect.y, _footstepRect.w, worldPos.z));
			o.Normal = BlendNormals( UnpackNormal( tex2D( _NormalTex, uv_NormalTex ) ) , UnpackNormal( tex2D( _footstepTex, footstepUV) ) );
			float2 uv_MainTex = i.uv_texcoord * _MainTex_ST.xy + _MainTex_ST.zw;
			o.Albedo = tex2D( _MainTex, uv_MainTex ).rgb;
			o.Alpha = 1;
		}

		ENDCG
	}
	Fallback "Diffuse"
}

  用Surface写,过程会简单很多,毕竟不需要自己去声明曲面细分的各个步骤,也不需要自己写光照模型和法线贴图的计算。不过我觉得如果能用顶点片段程序去实现一下,会对这个过程更了解一些。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Android 13.0 Settings主页面去掉FocusRecyclerView相关功能

1.前言 在13.0的系统rom产品定制化开发中,在系统Settings主页面的主菜单中,在测试某些功能的时候,比如开启护眼模式和改变系统密度会在主菜单第一项的网络菜单头部增加 自定义您的设备和设置护眼模式时间安排 等等相关的设置模块 这对于菜单布局显示相当不美观,所以根据系…

libwebsockets入门

WebSocket是一种在单个TCP连接上进行全双工通讯的协议&#xff0c;用于在Web客户端和服务器之间建立持久连接&#xff0c;进行实时通信。它是HTML5开始提供的一种通讯方式&#xff0c;通过使用WebSocket连接&#xff0c;web应用程序可以执行实时的交互&#xff0c;而不是以前的…

【C++】stack | queue | priority_queue | deque

一、stack栈 介绍 1.栈是一种特殊的线性表&#xff0c;其元素遵循“后进先出”的原则&#xff0c;即仅允许在在表的一端进行插入、删除操作&#xff0c;这一模式被称为“后进先出”或LIFO&#xff08;last in fisrt out&#xff09;。 2.从底层实现来看&#xff0c;stack是作…

adb and 软件架构笔记

Native Service&#xff0c;这是Android系统里的一种特色&#xff0c;就是通过C或是C代码写出来的&#xff0c;供Java进行远程调用的Remote Service&#xff0c;因为C/C代码生成的是Native代码&#xff08;机器代码&#xff09;&#xff0c;于是叫Native Service。 native服务…

真正解决jellyfin硬解码转码

前段时间入手一个DS423集成显卡UHD600&#xff0c;搭了一个jellyfin&#xff0c;发现网上关于硬解码的教程基本都存在问题&#xff0c;没有真正解决我的硬解码问题。经过一系列分析修改&#xff0c;最终实现硬解码。先贴效果图&#xff1a; 下载安装jellyfin这里就不叙述&#…

php加密解密的用法(对称加密,非对称加密)

加密和摘要的区别 ***摘要&#xff1a;是从已知的数据中&#xff0c;通过摘要计算出一个值&#xff0c;一个数据对应一个或多个摘要的值 *** 比如&#xff1a;md5 和 sha1 sha256 hash 就是得到一个特定的值 &#xff0c;同一个数据得到的md5 是一样的&#xff0c;不会改变的 比…

python自动化测试(十一):写入、读取、修改Excel表格的数据

目录 一、写入 1.1 安装 xlwt 1.2 增加sheet页 1.2.1 新建sheet页 1.2.2 sheet页写入数据 1.2.3 excel保存 1.2.4 完整代码 1.2.5 同一坐标&#xff0c;重复写入 二、读取 2.1 安装读取模块 2.2 读取sheet页 2.2.1 序号读取shee页 2.2.2 通过sheet页的名称读取she…

实用知识(工作中常用)

mybatis-plus联表查询 pom.xml坐标 <!-- mybatis-plus-join --> <dependency><groupId>com.github.yulichang</groupId><artifactId>mybatis-plus-join</artifactId><version>1.2.4</version> </dependency>使用步骤&…

【可视化Java GUI程序设计教程】第5章 Swing容器的使用

Swing采用自顶向下的方式构建GUI&#xff0c;即先创建容器&#xff0c;再向容器中添加组件。 “组件”面板中的Swing容器 5.1 面板容器&#xff08;JPanel&#xff09; 5.5.1 使用方法 创建面板有以下两种方法 &#xff08;1&#xff09;创建一个窗体&#xff08;JFrame&…

SAP BASIS SET_PARAMETER_ID_TOO_LONG

ji 原因 DATA:curvbelnid(40) TYPE c,"问题在这里curposnrid(40) TYPE c. "问题在这里curvbelnid sy-uname && VN.curposnrid sy-uname && PR.SET PARAMETER ID curvbelnid FIELD i_vbeln . SET PARAMETER ID curposnrid FIELD i_posnr . 改成 D…

ci-cd的流程

1、项目在gitlab上&#xff0c;从gitlab上使用git插件获取源码&#xff0c;构建成war包&#xff0c;所以使用tomcat作为运行环境 发布 &#xff1a;使用maven插件发布&#xff0c;使用ssh连接。

【Android】画面卡顿优化列表流畅度一

卡顿渲染耗时如图&#xff1a; 卡顿表现有如下几个方面&#xff1a; 网络图片渲染耗时大上下滑动反应慢&#xff0c;甚至画面不动新增一页数据加载渲染时耗时比较大&#xff0c;上下滑动几乎没有反应&#xff0c;画面停止没有交互响应 背景 实际上这套数据加载逻辑已经运行…

支持向量机 (SVM):初学者指南

照片由 Unsplash上的 vackground.com提供 一、说明 SVM&#xff08;支持向量机&#xff09;简单而优雅用于分类和回归的监督机器学习方法。该算法试图找到一个超平面&#xff0c;将数据分为不同的类&#xff0c;并具有尽可能最大的边距。本篇我们将介绍如果最大边距不存在的时候…

Failed to connect to github.com port 443:connection timed out

解决办法&#xff1a; 步骤1&#xff1a; 在这里插入图片描述 步骤2&#xff1a; -步骤3 &#xff1a;在git终端中执行如下命令&#xff1a; git config --global http.proxy http:ip:port git config --global https.proxy http:ip:port git config --global http.proxy htt…

重磅发布 OpenAI 推出用户自定义版 ChatGPT

文章目录 重磅发布 OpenAI 推出用户自定义版 ChatGPT个人简介 重磅发布 OpenAI 推出用户自定义版 ChatGPT OpenAI 首届开发者大会 (OpenAI DevDay) 于北京时间 11 月 7 日凌晨 02:00 开始&#xff0c;大会上宣布了一系列平台更新。其中一个重要更新是用户可以创建他们自己的自定…

智慧农业:农林牧数据可视化监控平台

数字农业是一种现代农业方式&#xff0c;它将信息作为农业生产的重要元素&#xff0c;并利用现代信息技术进行农业生产过程的实时可视化、数字化设计和信息化管理。能将信息技术与农业生产的各个环节有机融合&#xff0c;对于改造传统农业和改变农业生产方式具有重要意义。 图扑…

SysML理论知识

概述 由来 长期以来系统工程师使用的建模语言、工具和技术种类很多&#xff0c;如行为图、IDEF0、N2图等&#xff0c;这些建模方法使用的符号和语义不同&#xff0c;彼此之间不能互操作和重用。系统工程正是由于缺乏一种强壮的标准的建模语言&#xff0c;从而限制系统工程师和…

Makefile 介绍

目录 一、Makefile 的规则 二、一个示例 三、make 是如何工作的 四、makefile 中使用变量 五、让 make 自动推导 六、另类风格的 makefile 七、清空目标文件的规则 make 命令执行时&#xff0c;需要一个 Makefile 文件&#xff0c;以告诉 make 命令需要怎么样的去编译和…

用Python实现朴素贝叶斯垃圾邮箱分类

一、实验目的 通过本实验&#xff0c;旨在使用朴素贝叶斯算法实现垃圾邮箱分类&#xff0c;并能够理解并掌握以下内容&#xff1a; 了解朴素贝叶斯算法的基本原理和应用场景。 学习如何对文本数据进行预处理&#xff0c;包括去除标点符号、转换为小写字母、分词等操作。 理解特…

git 生成公钥

1、通过命令 ssh-keygen 生成 SSH Key&#xff1a; ssh-keygen -t ed25519 -C "Gitee SSH Key" 三次回车 2、查看生成的 SSH 公钥和私钥&#xff1a; ls ~/.ssh/ 3、把公钥设置到git id_ed25519.pub 4、测试 ssh -T gitgitee.com 成功&#xff01;&#xff01;&…