Chapter9 更复杂的光照——Shader入门精要学习笔记

Chapter9 更复杂的光照

  • 一、Unity的渲染路径
    • 1.渲染路径的概念
    • 2.渲染路径的类型
      • ①前向渲染路径
        • a. 前向渲染路径的原理
        • b. Unity中的前向渲染
        • c. 两种Pass
      • ②延迟渲染路径
        • a. 延迟渲染路径的原理
        • b. Unity中的延迟渲染
        • c. 两种Pass
      • ③顶点照明渲染路径
  • 二、Unity的光源类型
    • 1.光源类型
      • ①平行光
      • ②点光源
      • ③聚光灯
    • 2.前向渲染中处理不同的光源
      • ①实践
        • Base Pass
        • Additional Pass
  • 三、Unity光照衰减
    • 1.用于光照衰减的纹理
    • 2.数学公式计算
  • 四、Unity的阴影
    • 1.阴影如何实现
      • ①Shadow Map的生成
      • ②屏幕空间的阴影映射技术
      • ③阴影映射
    • 4.Unity Shader 使用内置宏和函数 来统一管理光照衰减和阴影
      • ①光照衰减和阴影的影响
      • ②内置宏 UNITY_LIGHT_ATTENUATION
    • 5.透明物体阴影的办法
      • ①透明物体阴影问题
      • ②透明度测试 物体的阴影处理
      • ③透明度混合 物体的阴影处理
  • 五、标准的Unity Shader

一、Unity的渲染路径

1.渲染路径的概念

渲染路径是 Unity 处理光照信息的方式,它决定了光照是如何被应用到 Unity Shader 中的。简单来说,渲染路径就像一个“沟通桥梁”,它告诉 Unity 底层渲染引擎,开发者想要以哪种方式来处理光照,以及需要哪些光照信息

2.渲染路径的类型

大多数情况下一个项目只使用一个渲染路径,在Player Setting中进行Rendering Path设置。也可以在每个摄像机中设置该摄像机的渲染路径。完成设置后,就可以在每个Pass中 使用 LightMode 标签 来指定Pass使用的渲染路径。
在这里插入图片描述

①前向渲染路径

a. 前向渲染路径的原理
  • 它将每个光源的光照计算独立进行,并逐个应用到物体上
  • 每进行一次完整的前向渲染,需要渲染该对象的渲染图元,计算两个缓冲区信息:颜色缓冲区(更新颜色缓存区中的颜色值)和 深度缓冲区(决定一个片元是否可见)
  • 对每个逐像素光源都要进行一次这样的Pass渲染,如果有多个逐像素光源,就要进行多次
b. Unity中的前向渲染
  • 渲染一个物体时,Unity会计算哪些光源照亮了它,以及这些光源照亮该物体的方式
  • 前向渲染中有三种照亮物体的方式:逐顶点处理、逐像素处理、球谐函数(SH)处理
  • 决定光源用哪种处理模式取决于 类型 和 渲染模式
    • 类型:指光源是平行光还是什么
    • 渲染模式:指该光源是否重要,重要就是逐像素
      在这里插入图片描述
c. 两种Pass
  • 前向渲染路径通常包含两个 Pass
  • Base Pass: 计算环境光、最重要的平行光(1个)、逐顶点/SH 光源和 Lightmaps
    • 只会执行一次
  • Additional Pass: 计算额外的逐像素光源,每个光源对应一个 Pass(不支持阴影)
    • 每个逐像素光源会被调用一次
    • 还开启和设置了渲染模式,每个Additional Pass可以与上一次的光照结果在帧缓存中进行叠加

②延迟渲染路径

a. 延迟渲染路径的原理
  • 除了前向渲染用到的颜色缓冲和深度缓冲,延迟渲染还会用到G缓冲(G-buffer),存储了我们所关心的表面的其他信息(法线、位置、用于光照计算的材质属性)
b. Unity中的延迟渲染
  • 若光源数目多,前向渲染会造成性能瓶颈,就适合延迟渲染;延迟渲染中每个光源都可以按逐像素的方式处理
  • 缺点:
    • 不支持真正的抗锯齿(anti-aliasing)
    • 不能处理半透明物体
    • 对显卡有要求
c. 两种Pass
  • 第一个Pass:不进行任何光照计算,仅仅计算哪些片元是可见的(深度缓冲);如果该片元可见,就把相关信息存储到G缓冲区中
    • 漫反射颜色、高光反射颜色、平滑度、法线、自发光和深度信息
  • 第二个Pass:利用G缓冲区中的信息进行真正的光照计算
  • 不依赖与场景复杂度,而是与屏幕空间的大小有关(缓冲区可以理解为2D图像,空间即为图像空间)

③顶点照明渲染路径

  • 一种简单的渲染方式,它只使用逐顶点光照,不支持阴影、法线映射等高级光照效果

二、Unity的光源类型

  • Unity中有4中类型:平行光、点光源、聚光灯和面光源(面光源仅在烘焙时用到)

1.光源类型

①平行光

没有具体位置,也没有衰减,所有点的方向都是一样的

②点光源

  • 照亮的范围可以由面板中Range来调,有光照衰减(衰减可以由函数定义)
  • 需要用 点光源的位置 - 某点位置 来得到该点的方向

③聚光灯

  • 是一块锥形区域,半径由Range来调,角度由Spot Angle来调,有光照衰减(衰减可以由函数定义)
  • 需要用 聚光灯的位置 - 某点位置 来得到该点的方向

2.前向渲染中处理不同的光源

  • 如何在Shader中访问5个属性:位置、方向、颜色、强度、衰减

①实践

// Upgrade NOTE: replaced '_LightMatrix0' with 'unity_WorldToLight'
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'Shader "Unity Shaders Book/Chapter 9/Forward Rendering" {Properties {_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)_Specular ("Specular", Color) = (1, 1, 1, 1)_Gloss ("Gloss", Range(8.0, 256)) = 20}SubShader {Tags { "RenderType"="Opaque" }Pass {// Pass for ambient light & first pixel light (directional light)Tags { "LightMode"="ForwardBase" }CGPROGRAM// Apparently need to add this declaration #pragma multi_compile_fwdbase	#pragma vertex vert#pragma fragment frag#include "Lighting.cginc"fixed4 _Diffuse;fixed4 _Specular;float _Gloss;struct a2v {float4 vertex : POSITION;float3 normal : NORMAL;};struct v2f {float4 pos : SV_POSITION;float3 worldNormal : TEXCOORD0;float3 worldPos : TEXCOORD1;};v2f vert(a2v v) {v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.worldNormal = UnityObjectToWorldNormal(v.normal);o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;return o;}fixed4 frag(v2f i) : SV_Target {fixed3 worldNormal = normalize(i.worldNormal);fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);fixed3 halfDir = normalize(worldLightDir + viewDir);fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);fixed atten = 1.0;return fixed4(ambient + (diffuse + specular) * atten, 1.0);}ENDCG}Pass {// Pass for other pixel lightsTags { "LightMode"="ForwardAdd" }Blend One OneCGPROGRAM// Apparently need to add this declaration#pragma multi_compile_fwdadd#pragma vertex vert#pragma fragment frag#include "Lighting.cginc"#include "AutoLight.cginc"fixed4 _Diffuse;fixed4 _Specular;float _Gloss;struct a2v {float4 vertex : POSITION;float3 normal : NORMAL;};struct v2f {float4 pos : SV_POSITION;float3 worldNormal : TEXCOORD0;float3 worldPos : TEXCOORD1;};v2f vert(a2v v) {v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.worldNormal = UnityObjectToWorldNormal(v.normal);o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;return o;}fixed4 frag(v2f i) : SV_Target {fixed3 worldNormal = normalize(i.worldNormal);#ifdef USING_DIRECTIONAL_LIGHTfixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);#elsefixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);#endiffixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);fixed3 halfDir = normalize(worldLightDir + viewDir);fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);#ifdef USING_DIRECTIONAL_LIGHTfixed atten = 1.0;#else#if defined (POINT)float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;#elif defined (SPOT)float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;#elsefixed atten = 1.0;#endif#endifreturn fixed4((diffuse + specular) * atten, 1.0);}ENDCG}}FallBack "Specular"
}
Base Pass
  • #pragma multi_compile_fwdbase 保证我们在shader中使用 光照衰减 等光照变量时可以被正确使用
#pragma multi_compile_fwdbase	
  • 在Base Pass中计算环境光照后,在Additional Pass中就不会再计算(物体自发光也是)
Additional Pass
  • 同样使用#pragma multi_compile_fwdadd 指令
  • 开启了Blend 命令,设置了混合模式,希望Additional Pass计算得到的光照结果可以在帧缓存中与之前的光照结果进行叠加
Pass {// Pass for other pixel lightsTags { "LightMode"="ForwardAdd" }Blend One OneCGPROGRAM// Apparently need to add this declaration#pragma multi_compile_fwdadd
  • Additional Pass处理的光源类型可能是平行光、点光源或是聚光灯,在计算位置、方向、颜色、强度、衰减时,颜色和强度仍然可以使用 _LightColor0 来得到,由于位置、方向和衰减属性就需要根据光源类型分别计算
  • 方向
    如果是平行光,可以直接通过 _WorldSpaceLightPos0.xyz 来得到;如果是其他光源,_WorldSpaceLightPos0.xyz 表示的是光源在世界坐标中的位置,光源方向需要用这个位置减去世界空间下顶点的位置
			#ifdef USING_DIRECTIONAL_LIGHTfixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);#elsefixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);#endif
  • 衰减
			#ifdef USING_DIRECTIONAL_LIGHTfixed atten = 1.0;#else#if defined (POINT)float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;#elif defined (SPOT)float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;#elsefixed atten = 1.0;#endif#endif

三、Unity光照衰减

1.用于光照衰减的纹理

  • 在Unity内部使用一张名为 _LightTexture0 的纹理来计算光源衰减(通常只关心对角线上的纹理颜色值),比如(0,0)点表示与光源重合的点的衰减值,(1,1)点表示了在光源中距离最远的点的衰减
  • 为了对_LightTexture0纹理采样得到给定点到该光源的衰减值,先知道点在光源空间中的位置 ,_LightMatrix0 为把顶点从世界坐标变换到光源空间的矩阵,与世界空间中的顶点坐标相乘即可
  • 再使用坐标的模的平方对衰减纹理进行采样
float3 lightCoord = mul(_LightMatrix0, float4(i.worldPos, 1)).xyz;
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;

2.数学公式计算

float3 distance = length(_WorldSpaceLightPos0.xyz - i.worldPosition.xyz);
fixed atten = 1.0 / distance;

四、Unity的阴影

1.阴影如何实现

  • 让一个物体向其他物体投影
  • 在实时渲染中,经常使用Shadow Map,先把摄像机放在光源位置上,光源的阴影区域就是摄像机看不到的区域

①Shadow Map的生成

  • 摄像机位置:将摄像机放置在与光源重合的位置
  • ShadowCaster Pass:使用 LightMode 标签为 ShadowCaster 的 Pass 专门更新光源的阴影映射纹理。这个 Pass 渲染的目标不是帧缓存,而是阴影映射纹理(或深度纹理)

②屏幕空间的阴影映射技术

  • 根据光源的阴影映射纹理和摄像机的深度纹理得到屏幕空间的阴影图
  • 步骤
    • 通过调用 LightMode 标签为 ShadowCaster 的 Pass 来得到 光源的阴影映射纹理相机的深度纹理
    • 根据 光源的阴影映射纹理相机的深度纹理 得到屏幕空间的阴影图
    • 若摄像机深度图中记录的表面深度 > 转换到阴影映射纹理中的深度值 → \rightarrow 物体表面虽然可见,但出于该光源的阴影中

③阴影映射

  • 让物体 接收 来自其他物体的投影
    • 在Shader中队阴影映射纹理(包括屏幕空间的阴影图)进行采样,把采样结果与光照结果相乘得到阴影
  • 让物体向其他 投射阴影
    • 把该物体加到光源的阴影映射纹理计算中(通过为该物体执行 LightMode 标签为 ShadowCaster 的 Pass 来实现的)

4.Unity Shader 使用内置宏和函数 来统一管理光照衰减和阴影

①光照衰减和阴影的影响

  • 两个对物体最终的渲染效果本质上是相同的,都是通过将衰减因子和阴影值与光照结果相乘得到最终结果
  • 可以使用一个方法来同时计算这两个信息

②内置宏 UNITY_LIGHT_ATTENUATION

  • 包含进需要的头文件 #include “AutoLight.cginc”
  • 在v2f结构体中使用内置宏 SHADOW_COORDS 声明阴影坐标
struct v2f{float4 pos : SV_POSITION;float3 worldNormal : TEXCOORD0;float3 worldPos : TEXCOORD1;SHADOW_COORDS(2);
};
  • 在顶点着色器中使用内置宏 TRANSFER_SHADOW 计算并向片元着色器传递阴影坐标
v2f vert(a2v v){v2f o;...TRANSFER_SHADOW(o);return o;
}
  • 在片元着色器中使用内置宏 UNITY_LIGHT_ATTENUATION 来计算光照衰减和阴影,有三个参数
    • 第一个:用于存储光照衰减和阴影值相乘后的结果
    • 第二个:结构体 v2f ,用于传递内置宏计算阴影值
    • 第三个:世界空间的坐标,计算光源空间下的坐标,再对光照衰减纹理采样来得到光照衰减
fixed4 frag(v2f i) : SV_Target {...UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);return fixed4(ambient + (diffuse + specular) * atten, 1.0);
} 
  • Base Pass 和 Additional Pass 代码得到统一,不需要在Base Pass里单独处理阴影,也不需要在Additional Pass中判断光源类型来处理光照衰减

5.透明物体阴影的办法

①透明物体阴影问题

  • 透明物体的实现通常会使用透明度测试或透明度混合,需要小心设置这些物体的 Fallback
  • 使用 VertexLit、Diffuse、Specular 等作为回调,往往无法得到正确的阴影(不透明物体可以,VertexLit中有ShadowCaster Pass),因为这些 Shader 中的 ShadowCaster Pass 没有进行透明度测试的计算。

②透明度测试 物体的阴影处理

  • 需要提供一个有透明度测试功能的 ShadowCaster Pass
  • Fallback “VertexLit” 改为 “Transparent/Cutout/VertexLit”
  • Cude 的 Mesh Renderer 的Cast Shadows 设置为 Two Sided
    在这里插入图片描述

③透明度混合 物体的阴影处理

  • 因为透明度混合需要关闭深度写入,这会带来阴影生成的问题,所以所有内置的透明度混合的 Unity Shader 都没有包含阴影投射的 Pass,因此,这些半透明物体不会向其他物体投射阴影,也不会接收来自其他物体的阴影
  • 可以使用一些 dirty trick 来强制为半透明物体生成阴影
    • 把它们的 Fallback 设置为 VertexLit、Diffuse 等不透明物体使用的 Unity Shader
    • 通过物体的 Mesh Renderer 组件上的 Cast Shadows 和 Receive Shadows 选项来控制是否需要向其他物体投射或接收阴影
      在这里插入图片描述

五、标准的Unity Shader

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何找BMS算法、BMS软件的实习

之前一直忙,好久没有更新了,今天就来写一篇文章来介绍如何找BMS方向的实习,以及需要具备哪些条件,我的实习经历都是在读研阶段找的,读研期间两段的实习经历再加上最高影响因子9.4分的论文,我的秋招可以说是…

[22] Opencv_CUDA应用之 使用背景相减法进行对象跟踪

Opencv_CUDA应用之 使用背景相减法进行对象跟踪 背景相减法是在一系列视频帧中将前景对象从背景中分离出来的过程,它广泛应用于对象检测和跟踪应用中去除背景 背景相减法分四步进行:图像预处理 -> 背景建模 -> 检测前景 -> 数据验证 预处理去除…

《昇思25天学习打卡营第9天|onereal》

继续学习昨天的 基于MindNLPMusicGen生成自己的个性化音乐 生成音乐 MusicGen支持两种生成模式:贪心(greedy)和采样(sampling)。在实际执行过程中,采样模式得到的结果要显著优于贪心模式。因此我们默认启…

DP:子序列问题

文章目录 什么是子序列子序列的特点举例说明常见问题 关于子序列问题的几个例题1.最长递增子序列2.摆动序列3.最长递增子序列的个数4.最长数对链5.最长定差子序列 总结 什么是子序列 在计算机科学和数学中,子序列(Subsequence)是指从一个序列…

【JavaEE精炼宝库】多线程进阶(2)synchronized原理、JUC类——深度理解多线程编程

一、synchronized 原理 1.1 基本特点: 结合上面的锁策略,我们就可以总结出,synchronized 具有以下特性(只考虑 JDK 1.8): 开始时是乐观锁,如果锁冲突频繁,就转换为悲观锁。 开始是轻量级锁实现&#xff…

维护Nginx千字经验总结

Hello , 我是恒 。 维护putty和nginx两个项目好久了,用面向底层的思路去接触 在nginx社区的收获不少,在这里谈谈我的感悟 Nginx的夺冠不是偶然 高速:一方面,在正常情况下,单次请求会得到更快的响应;另一方面&#xff0…

Linux:网络基础1

文章目录 前言1. 协议1.1 为什么要有协议?1.2 什么是协议? 2. 网络2.1 网络通信的问题2.2 网络的解决方案——网络的层状结构2.3 网络和系统的关系2.4 网络传输基本流程2.5 简单理解IP地址2.6 跨网络传输 总结 前言 在早期的计算机发展中,一开…

免费翻译API及使用指南——百度、腾讯

目录 一、百度翻译API 二、腾讯翻译API 一、百度翻译API 百度翻译API接口免费翻译额度:标准版(5万字符免费/每月)、高级版(100万字符免费/每月-需个人认证,基本都能通过)、尊享版(200万字符免…

Linux驱动开发实战宝典:设备模型、模块编程、I2C/SPI/USB外设精讲

摘要: 本文将带你走进 Linux 驱动开发的世界,从设备驱动模型、内核模块开发基础开始,逐步深入 I2C、SPI、USB 等常用外设的驱动编写,结合实际案例,助你掌握 Linux 驱动开发技能。 关键词: Linux 驱动,设备驱动模型,内核模块,I2C,SPI,USB 一、Linux 设备驱动模型 Li…

cesium 聚合

cesium 聚合(下面附有源码) 示例代码 <html lang="en"><head><!-- Use correct character set. -->

python系列30:各种爬虫技术总结

1. 使用requests获取网页内容 以巴鲁夫产品为例&#xff0c;可以用get请求获取内容&#xff1a; https://www.balluff.com.cn/zh-cn/products/BES02YF 对应的网页为&#xff1a; 使用简单方法进行解析即可 import requests r BES02YF res requests.get("https://www.…

JSON JOLT常用示例整理

JSON JOLT常用示例整理 1、什么是jolt Jolt是用Java编写的JSON到JSON转换库&#xff0c;其中指示如何转换的"specification"本身就是一个JSON文档。以下文档中&#xff0c;我统一以 Spec 代替如何转换的"specification"json文档。以LHS(left hand side)代…

云计算基础技术

网络类技术 网络的作用 网络是设备间、虚拟机之间通信的桥梁。因此&#xff0c;在ICT基础设施中&#xff0c;网络是必不可少的。 传统网络的基本概念 广播和单播&#xff1a;两个设备通信就好像是人们之间的对话一样。如果一个人对另外一个人说话&#xff0c;那么用网络技术的…

从零开始搭建spring boot多模块项目

一、搭建父级模块 1、打开idea,选择file–new–project 2、选择Spring Initializr,选择相关java版本,点击“Next” 3、填写父级模块信息 选择/填写group、artifact、type、language、packaging(后面需要修改)、java version(后面需要修改成和第2步中版本一致)。点击“…

容器内存

一、容器内存概述 容器本质上还是一个进程&#xff0c;是一个被隔离和限制的进程。因此容器内存和进程内存在表现形式上其实是一样的&#xff0c;这块主要涉及三部分内容&#xff1a;RSS&#xff0c;page cache和swap这三部分&#xff0c;容器基于memory Cgroup对内存进行限制…

HSRP热备份路由协议(VRRP虚拟路由冗余协议)配置以及实现负载均衡

1、相关原理 在网络中&#xff0c;如果一台作为默认网关的三层交换机或者路由器损坏&#xff0c;所有使用该网关为下一跳的主机通信必然中断&#xff0c;即使配置多个默认网关&#xff0c;在不重启终端的情况下&#xff0c;也不能彻底换到新网关。Cisco提出了HSRP热备份路由协…

Paragon NTFS与Tuxera NTFS有何区别 Mac NTFS 磁盘读写工具选哪个好

macOS系统虽然以稳定、安全系数高等优点著称&#xff0c;但因其封闭性&#xff0c;不能对NTFS格式磁盘写入数据常被人们诟病。优质的解决方案是使用磁盘管理软件Paragon NTFS for Mac&#xff08;点击获取激活码&#xff09;和Tuxera NTFS&#xff08;点击获取激活码&#xff0…

消防认证-防火卷帘

一、消防认证 消防认证是指消防产品符合国家相关技术要求和标准&#xff0c;且通过了国家认证认可监督管理委员会审批&#xff0c;获得消防认证资质的认证机构颁发的证书&#xff0c;消防产品具有完好的防火功能&#xff0c;是住房和城乡建设领域验收的重要指标。 二、认证依据…

springboot学习,如何用redission实现分布式锁

目录 一、springboot框架介绍二、redission是什么三、什么是分布式锁四、如何用redission实现分布式锁 一、springboot框架介绍 Spring Boot是一个开源的Java框架&#xff0c;由Pivotal团队&#xff08;现为VMware的一部分&#xff09;于2013年推出。它旨在简化Spring应用程序…

C# 警告 warning MSB3884: 无法找到规则集文件“MinimumRecommendedRules.ruleset”

警告 warning MSB3884: 无法找到规则集文件“MinimumRecommendedRules.ruleset” C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\amd64\Microsoft.CSharp.CurrentVersion.targets(129,9): warning MSB3884: 无法找到规则集文件“MinimumRe…