【Unity】Unity Shader学习笔记(八)基础纹理2:高度纹理、法线纹理、模型空间下的法线纹理、切线空间下的法线纹理光照计算

文章目录

  • 凹凸映射
  • 法线纹理设置
  • 高度纹理(Height Map)
  • 法线纹理(Normal Map)
    • 模型空间的法线纹理
    • 切线空间的法线纹理
    • 优劣对比
  • 切线空间下的法线纹理光照计算
    • 最终效果
    • 完整代码
    • TANGENT语义
    • 内置宏 TANGENT_SPACE_ROTATION
    • ObjSpaceLightDir 函数
    • ObjSpaceViewDir 函数
    • tex2D函数
    • 设置法线贴图
      • UnpackNormal函数
      • 法线方向的 z 分量


凹凸映射

凹凸映射的目的是使用一张纹理来修改物体表面的发现,使其在同一个面片下也能呈现凹凸的效果。这种方法并不会改变模型的顶点位置,也就是说模型的整体形状并没有改变,只是看起来有了凹凸的效果而已,当我们从侧面看这个模型的凹凸面时会发现其实那个凹凸效果是假的。

凹凸映射有两种主要方法:

  • 高度纹理(Height Map):使用一张高度纹理图来模拟表面位移,然后得到一个修改后的法线值,这种方法也被称为高度映射(Height Mapping);
  • 法线纹理(Normal Map):使用一张法线纹理(Normal Map)来直接存储表面法线,这种方法被称为法线映射(Normal Mapping)。

法线纹理设置

当我们在 Unity 的 Project 中选中一张图片资源后,在 Inspector 面板中会有一些选项,其中 Texture Type 选为 Normal Map 后,该图片就会被标识为法线纹理。
另外一个重要设置为 Create from Grayscale ,如果该项勾选,则会将这张图片标记为高度纹理,反之则标记为法线纹理。
在这里插入图片描述
当我们勾选了 Create from Grayscale 选项后,会出现两个新的修改项,其中 Bumpiness 用于控制凹凸程度,而 Filtering 则包含两个选项,其中 Smooth 使得生成后的法线纹理比较平滑,而另一个选项 Sharp 则会使用 Sobel 滤波来生成法线。

在这里插入图片描述

高度纹理(Height Map)

高度纹理也就是我们俗称的高度图,高度图可以用来实现凹凸映射,高度图中存储的是强度值(intensity),它用于表示模型表面局部的海拔高度。因此,颜色越浅表明该位置的表面越向外凸起颜色越深表明该位置的表面越向内凹陷
这种方式的好处是非常直观,一眼就能看出凹凸情况;但缺点是计算更加复杂,在实时计算时不能直接得到表面法线,需要结合像素的灰度值计算才行。

法线纹理(Normal Map)

法线纹理存储的是表面的法线方向。通常有两种方式来存储发现纹理:

  1. 模型空间的法线纹理(Object-Space Normal Map)
  2. 切线空间的法线纹理(Tangent-Space Normal Map)

如下图:左边为模型空间下的法线纹理,右边为切线空间下的法线纹理。
在这里插入图片描述

模型空间的法线纹理

模型空间下的法线纹理实际上就是将模型空间下的法线方向存储到一张纹理图中,单个纹素的数据格式是 float3 ,对应的三个分量分别为:

  • float3的第一个分量:代表了法线向量在模型空间X轴方向上的分量。
  • float3的第二个分量:代表了法线向量在模型空间Y轴方向上的分量。
  • float3的第三个分量:代表了法线向量在模型空间Z轴方向上的分量。

因为这三个分量范围在 [ -1, 1 ] 之间,而像素的分量范围是 [ 0, 1 ] ,所以存储和使用模型空间的法线纹理时需要一部映射操作:

  • 存储模型空间的法线纹理时,需要将法线转换为纹素,公式为: p i x e l = n o r m a l + 1 2 pixel = \frac{normal + 1}{2} pixel=2normal+1
  • 解析法线纹理获得纹素后,需要通过 n o r m a l = p i x e l ∗ 2 − 1 normal = pixel * 2 - 1 normal=pixel21 的方式将纹素映射到法线方向上来。

注意,模型空间下的法线纹理虽然相对直观,但是由于提供的是模型空间下的法线方向,通常使用起来需要进行一次换算操作,相对来说性能会差一些,所以通常开发者会使用另外一种法线纹理方式,即切线空间的法线纹理。

模型空间下的法线纹理图示例如下:
在这里插入图片描述

切线空间的法线纹理

切线空间下的法线纹理使用的不是模型空间坐标系,而是将每个顶点作为新的坐标系(也可以称之为切线空间 Tangent Space),在切线空间坐标系下描述当前位置的法线方向。

为什么能够使用这样的方式进行存储呢?这样的存储方式是否具有唯一性呢?首先我们要了解一下什么是切线空间。模型的每个顶点都有一个属于自己的切线空间,这个切线空间的原点就是该顶点本身,它的 z 轴是顶点的法线方向,x 轴是顶点的切线方向,y 轴由法线和切线叉积获得,也被称之为 副切线(bitangent, b) 或副法线。而关于唯一性的问题,我们想到的可能是在同一个面上有多个顶点,如何能保证某一点上的法线是使用哪个顶点作为原点的呢,其实这个问题比较简单,因为只要是同一平面的顶点,其法线方向都是一致的,所以不会存在数据表述不唯一的情况。

切线空间下的法线纹理通常为蓝色的图片,比如一个这样的石头对应的法线图是下面这样的:
在这里插入图片描述
在这里插入图片描述
为什么模型空间下的法线纹理是五颜六色的,而切线空间下的法线纹理却是以蓝色为主的呢?
这是因为模型空间下的法线方向是使用同一个坐标空间(物体的模型空间)进行计算的,所以不同法线的朝向方向也是完全不同的,即便通过映射会少去一半的颜色,但仍然具有很多种颜色。而切线空间下的法线纹理中每个点存储的法线方向都是以当前顶点的方向为基准的,所以在存储时等同于大家都是以当前点所对应的法线方向为基准的,也就是说如果这个面是平的,那么这个面上的所有法线方向都将是 (0, 0, 1) ,换算成纹素的值就是 RGB (0.5, 0.5, 1) ,刚好是浅蓝色,所以在没有极特殊情况的话,通常法线方向对应的颜色值都与蓝色相近。

优劣对比

模型空间下的法线纹理有以下优势:

  1. 实现简单,更加直观。生成纹理的计算方式较为简单;
  2. 在纹理坐标的缝合处和尖锐的边角部分,可见的突变(缝隙)较少,即可以提供平滑的边界;

切线空间下的法线纹理具有更多优势:

  1. 自由度很高。模型空间下的法线纹理记录的是绝对法线信息,几乎可以说只能给当前模型使用,无法更换模型,而切线纹理记录的是相对信息,也就是说即便换一个其他模型也能得到一个相对合理的结果;
  2. 可以进行 UV 动画。比如我们最常见的水波效果,通常可以通过移动纹理的 UV 坐标来实现;
  3. 可以重用法线纹理。比如一个砖块只需要一张法线纹理就可以用到所有的 6 个面上;
  4. 可压缩。由于切线空间下的法线纹理中 z 方向总是正方向,因此我们可以只记录 x 和 y 两个方向,通过推导得到 z 方向。而模型空间下的纹理三个分量都是有用的,无法进行压缩。

切线空间下的法线纹理光照计算

最终效果

在这里插入图片描述
如果有需要纹理图和法线图的可以私信我,我发给你。但实际上这种东西完全可以在网站上去下载,只要是能用的就可以。

完整代码

// 在切线空间下计算法线纹理的光照
Shader "Y7Play/Chapter7/Normal Map In Tangent Space"
{Properties{// 主颜色,该颜色用于跟纹理及光照颜色相乘得到最终颜色_Color ("Color", Color) = (1,1,1,1)// 主纹理_MainTex ("Main Tex", 2D) = "white" {}// 法线纹理(Bump:凹凸)_BumpMap ("Normal Map", 2D) = "bump" {}// 法线贴图的缩放值_BumpScale ("Bump Scale", Float) = 1.0// 镜面反射颜色_Specular ("Specular", Color) = (1,1,1,1)// 光泽度_Gloss ("Gloss", Range(8.0, 256)) = 20}SubShader{Pass{Tags{"LightMode"="ForwardBase"}CGPROGRAM#pragma vertex vert#pragma fragment frag#include "Lighting.cginc"fixed4 _Color;sampler2D _MainTex;float4 _MainTex_ST;sampler2D _BumpMap;fixed4 _BumpMap_ST;float _BumpScale;fixed4 _Specular;float _Gloss;struct a2v{float4 vertex : POSITION;float3 normal : NORMAL;float4 tangent : TANGENT;float4 texcoord : TEXCOORD0;};struct v2f{float4 pos : SV_POSITION;float4 uv : TEXCOORD0;float3 lightDir : TEXCOORD1;float3 viewDir : TEXCOORD2;};// 顶点着色器负责将光源方向和观察方向从世界空间变换到切线空间v2f vert(a2v v){v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;// Compute the binormal// 计算副法线(副切线)// 顶点法线向量与顶点的切向量的叉积,得到副切线,再乘以纹理的切线方向的符号float3 binormal = cross(normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w;// Construct a matrix which transform vector from object space to tangent space// 构造一个矩阵,将向量从对象空间变换到切线空间float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);// Or just use the built-in function to do the same thing// 或者直接使用内置函数,这一行代码等于前面的两行代码binormal、rotation,// 最终会生成一个rotation对象,用于将顶点法线、副法线和观察方向从对象空间变换到切线空间// TANGENT_SPACE_ROTATION;// Transform the light direction from object space to tangent space// 将光源方向从对象空间变换到切线空间o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;// Transform the view direction from object space to tangent space// 将观察方向从对象空间变换到切线空间o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;return o;}// 片元着色器用于fixed4 frag(v2f i) : SV_Target{fixed3 tangentLightDir = normalize(i.lightDir);fixed3 tangentViewDir = normalize(i.viewDir);// Get the texel in the normal map// 获取法线贴图中的纹素fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);fixed3 tangentNormal;// // If the texture is not marked as "Normal map"// // 如果纹理没有被标记为“法线贴图”// tangentNormal.xy = (packedNormal.wy * 2 - 1) * _BumpScale;// tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));// Or mark the texture as "Normal map", and use the built-in function// 或者将纹理标记为 “Normal map”,并使用内置函数tangentNormal = UnpackNormal(packedNormal);tangentNormal.xy *= _BumpScale;tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));// 获取纹理颜色fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;// 计算环境光fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;// 计算漫反射fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));// 计算半程向量fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);// 计算镜面反射fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss);return fixed4(ambient + diffuse + specular, 1.0);}ENDCG}}FallBack "Specular"
}

TANGENT语义

TANGENT 语义用于把顶点的切线方向填充到 tangent 变量中,其类型为 float4 ,其中前面的xyz用于表明顶点的切线方向,而 w 则用于标明副切线的方向朝向正面还是反面,由于顶点的切线方向和法线方向已经有了,所以不需要使用xyz三个属性来表示副切线的方向,只需要说明副切线使用左手坐标系还是右手坐标系即可。通常这个 w 的值为 +1 或 -1 :

  • 如果 TANGENT.w 为 +1,则副切线向量通常按照某种约定的右手系规则来确定方向。
  • 如果 TANGENT.w 为 -1,则副切线向量按照相反的左手系规则来确定方向。

对于左手和右手坐标系不清楚的可以查看我的另外一篇文章:【Unity】Unity 几何知识、弧度、三角函数、向量运算、点乘、叉乘

内置宏 TANGENT_SPACE_ROTATION

内置宏 TANGENT_SPACE_ROTATION用于获取从模型空间到切线空间的变换矩阵 rotation ,如果不使用宏的代码为:

// Compute the binormal
// 计算副法线(副切线)
// 顶点法线向量与顶点的切向量的叉积,得到副切线,再乘以纹理的切线方向的符号
float3 binormal = cross(normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w;// Construct a matrix which transform vector from object space to tangent space
// 构造一个矩阵,将向量从对象空间变换到切线空间
float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);

如果使用了内置宏就变成了:

// 或者直接使用内置函数,这一行代码等于前面的两行代码binormal、rotation,
// 最终会生成一个rotation对象,用于将顶点法线、副法线和观察方向从对象空间变换到切线空间
TANGENT_SPACE_ROTATION;

最终会生成一个rotation对象,即从模型空间到切线空间的变换矩阵。

ObjSpaceLightDir 函数

获取模型空间下的光源方向。

ObjSpaceViewDir 函数

获取模型空间下的观察方向。

tex2D函数

tex2D函数用于从纹理图中获取对应位置的纹素(也可以理解为颜色),比如,当我想从主纹理 _MainTex 中获取纹素时就可以使用 tex2D 函数,代码如下:

fixed3 color = tex2D(_MainTex, i.uv).rgb;

但通常我们会使用一个颜色与之相乘以得到反照率(albedo)的值,代码如下:

fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;

如果要从法线图中获取纹素也可以:

fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);

设置法线贴图

正常情况下,我们需要将法线贴图的纹理格式 Texture Type 设置为 Normal Map ,如下图:
在这里插入图片描述
在前面章节【模型空间的法线纹理】中我们提到法线图中存储的格式与实际上的法线方向不一样,需要进行一次映射( n o r m a l = p i x e l ∗ 2 − 1 normal = pixel * 2 - 1 normal=pixel21),如果已经设置为法线图,可以通过以下方式获取真正的法线方向:

// If the texture is not marked as "Normal map"
// 如果纹理没有被标记为“法线贴图”
tangentNormal.xy = (packedNormal.wy * 2 - 1) * _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

如果不想设置,需要在 Shader 中做如下处理,这种方式就可以获取到映射后的法线方向了:

// Or mark the texture as "Normal map", and use the built-in function
// 或者将纹理标记为“Normal map”,并使用内置函数
tangentNormal = UnpackNormal(packedNormal);
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

UnpackNormal函数

其中 UnpackNormal 为Unity提供的内置函数,函数通常用于解码存储在RGBA通道中的法线信息。通过这行代码tangentNormal = UnpackNormal(packedNormal);,UnpackNormal 函数已经将一个完整的法线信息赋值给了 tangentNormal 变量,然后我们再让 tangentNormal 的 xy *= _BumpScale ,就得到了最终的法线方向 xy。

UnpackNormal 函数在 UnityCG.cginc 中的实现代码如下:

inline fixed3 UnpackNormalDXT5nm(fixed4 packednormal)
{fixed3 normal;normal.xy = packednormal.wy * 2 - 1;normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy)));return normal;
}
inline fixed3 UnpackNormal(fixed4 packednormal)
{
#if defined(UNITY_NO_DXT5nm)return packednormal.xyz * 2 - 1; // 没有压缩的直接计算结果
#elsereturn UnpackNormalDXT5nm(packednormal); // 压缩过的分别计算xyz的结果
#endif
}

根据上述代码不难看出,Unity 内部通过 UnpackNormal 函数获取法线信息时会根据不同的运行环境进行不同的处理,因为有些平台使用 DXT5nm 格式对法线纹理进行了压缩。在 DXT5nm 格式的法线纹理中,纹素的 a 通道(即 w 分量)对应了法线的 x 分量,g 通道对应了法线的 y 分量,而纹理的 r 和 b 通道则会被舍弃,法线的 z 分量可以由 xy 分量推导获得。

法线方向的 z 分量

法线方向的 z 分量计算比较复杂,先使用 dot(tangentNormal.xy, tangentNormal.xy) 获得 tangentNormal.xy 与自身的点积,其结果为 tangentNormal.xy 向量长度的平方。

然后通过 saturate 函数(饱和函数)将前面点积的结果控制在 0 到 1 之间,这是为了确保即使由于数值误差导致点积结果大于1,我们也能得到一个有效的输入来计算平方根。然而,在理想情况下(即没有数值误差时),dot(tangentNormal.xy, tangentNormal.xy) 应该已经是一个介于0和1之间的值,因为tangentNormal是一个单位向量。但saturate的使用提供了一层额外的保护,以防万一。

最后,通过计算1.0减去点积(经过饱和处理)的结果的平方根来得到 z 分量。这个计算基于单位向量的性质: x 2 + y 2 + z 2 = 1 x^2 + y^2 + z^2 = 1 x2+y2+z2=1。由于我们已经知道了x和y的值,可以通过这个公式来求解z。


更多内容请查看总目录【Unity】Unity学习笔记目录整理

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

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

相关文章

028.魔改浏览器-抓取closed的shadowRoot下的内容

一、什么是Shadow DOM Shadow DOM是一种在web开发中用于封装HTML标记、样式和行为的技术,以避免组件间的样式和脚本冲突。它允许开发者将网页的一部分隐藏在一个独立的作用域内,从而实现更加模块化和可维护的代码结构 二、js操作Shadow DOM // 获取宿…

【火山引擎】AIGC图像风格化 | 风格实践 | PYTHON

目录 1 准备工作 2 实践 代码 效果图 1 准备工作 ① 服务开通 确保已开通需要访问的服务。您可前往火山引擎控制台,在左侧菜单中选择或在顶部搜索栏中搜索需要使用的服务,进入服务控制台内完成开通流程。

云手机:社交平台运营的热门工具

随着互联网的飞速发展,社交平台已经成为企业推广和营销的核心渠道。传统的运营方式已经无法满足高效运营的需求,而云手机作为新兴工具,逐渐成为社交平台运营的前沿趋势。本文将深入分析云手机如何优化社交平台的运营流程,助力企业…

足浴店+闸机+智能衣柜+门票系统一体化管理系统解决方案——未来之窗行业应用跨平台架构

一、足浴店收银台 二、智能柜子 三、智能闸机 在收银台开台后,直接通过手环开闸机 1. 提高效率:减少了顾客等待人工操作闸机的时间,能够快速进入店内,提升顾客的进店体验。 2. 便捷服务:无需繁琐的钥匙或卡片&#xf…

新电脑Win11家庭中文版跳过联网激活方法(教程)

预装Win11家庭中文版的新电脑,如何跳过联网激活;由于微软限制必须要联网激活,需要使用已有的微软账户登入或者注册新的微软账户后才可以继续开机使用,Win11联网后系统会自动激活。下面介绍一下初次开机初始化电脑时如何跳过联网激…

LLM:reward-model-deberta-v3-large-v2模型结构

https://hf-mirror.com/OpenAssistant/reward-model-deberta-v3-large-v2是在做合成数据的质量打分时的奖励模型。 模型依托deberta-v3-large-v2编码模型,给定一个qa对,能够给出一个分数来衡量qa对的质量。没有公开训练细节,由于模型的输出层…

llama.cpp 去掉打印,只显示推理结果

llama.cpp 去掉打印,只显示推理结果 1 llama.cpp/common/log.h #define LOG_INF(...) LOG_TMPL(GGML_LOG_LEVEL_INFO, 0, __VA_ARGS__) #define LOG_WRN(...) LOG_TMPL(GGML_LOG_LEVEL_WARN, 0, __VA_ARGS__) #define LOG_ERR(…

基于微信小程序的电影交流平台

作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏:…

毕业设计选题:基于Hadoop的热点新闻分析系统的设计与实现

开发语言:Python框架:djangoPython版本:python3.7.7数据库:mysql 5.7数据库工具:Navicat11开发软件:PyCharm 系统展示 管理员登录 管理员功能界面 用户管理 新闻类型管理 主题标签管理 热点新闻管理 新闻…

回归预测|时序预测|基于灰狼优化时域卷积TCN结合Transformer的多特征输入单输出的回归预测和多维时序预测Matlab程序

回归预测|时序预测|基于灰狼优化时域卷积TCN结合Transformer的多特征输入单输出的回归预测和多维时序预测Matlab程序 文章目录 一、基本原理一、基本概念二、原理和流程三、优势与应用四、总结 二、实验结果三、核心代码四、代码获取五、总结 回归预测|时序预测|基于灰狼优化时…

深度学习--CNN实现猫狗识别二分类(附带下载链接, 长期有效)

1. 代码实现(包含流程解释) 样本量: 8005 # # 1.导入数据集(加载图片)数据预处理# 进行图像增强, 通过对图像的旋转 ,缩放,剪切变换, 翻转, 平移等一系列操作来生成新样本, 进而增加样本容量, # 同时对图片数值进行归一化[0:1] from tensorflow.keras.preprocessing.image …

ADC在STM32F1系列的使用详解

目录 1. ADC简介 2. 逐次逼近型ADC(ADC0809) 3. ADC框图(STM32) 4. ADC基本结构 5. 输入通道 6. 转换模式 6.1 单次转换 6.1.1 非扫描模式 6.1.2 扫描模式 6.2 连续转换 6.2.1 非扫描模式 6.2.2 扫描模式…

计算机网络—静态路由

1.0 网络拓扑结构 星型拓扑结构是一个中心,多个分节点。它结构简单,连接方便,管理和维护都相对容易,而且扩展性强。网络延迟时间较小,传输误差低。中心无故障,一般网络没问题。中心故障,网络就出…

Android 内存优化——常见内存泄露及优化方案

看到了一篇关于内存泄漏的文章后,就想着分享给大家,最后一起学习,一起进步: 如果一个无用对象(不需要再使用的对象)仍然被其他对象持有引用,造成该对象无法被系统回收,以致该对象在…

汽车开发流程管理工具赋能安全与质量

随着数字化、人工智能、自动化系统及物联网技术的迅速发展,工程驱动型企业正面临重大转型挑战,亟需加速并深化其变革步伐。众多企业正试图通过采用基于模型的系统工程(MBSE)、产品线工程(PLE)、ASPICE、安全、网络安全、软件定义汽车、敏捷和精益开发实践…

漏洞挖掘JS构造新手向

前置思路文章 JS逆向混淆前端对抗 油猴JS逆向插件 JS加解密之mitmproxy工具联动Burp JS挖掘基础 伪协议 JavaScript伪协议是一种在浏览器中模拟网络请求的方法。它使用window.XMLHttpRequest对象或fetch()方法来模拟发送HTTP请求,而不是通过实际的网络请求来获…

最牛4G模组展示文件系统如何存储温湿度数据,有手就会还不牛?

有手就会的保姆级流程,展示大家常用的低功耗模组实用功能。 1.编写脚本 1.1 准备资料 780E开发板购买链接 780E开发板设计资料 LuatOS-Air780E-文件系统的使用-程序源码demo 合宙的TCP/UDP测试服务器 API使用介绍 780E开发板和DHT11 1.2 程序详解 第一步&a…

【C++ 算法进阶】算法提升五

先序遍历改二叉搜索树 &#xff08;二叉树的递归套路&#xff09; 题目 本题为LC原题目 题目如下 题目分析 本题为一道经典的二叉树递归套路题目 我们只需要想好一个递归函数 之后让左右节点分别执行即可 我们这里想到的递归函数为 TreeNode* process(vector<int>&a…

asp.net core mvc发布时输出视图文件Views

var builder WebApplication.CreateBuilder(args); builder.Services.AddRazorPages();builder.Services.AddControllersWithViews(ops > {//全局异常过滤器&#xff0c;注册ops.Filters.Add<ExceptionFilter>(); })// Views视图文件输出到发布目录&#xff0c;视图文…

【yolov8旋转框检测】微调yolov8-obb目标检测模型:数据集制作和训练

一、开发环境的准备 1.1 安装roLabelImg 参考【目标检测—旋转框标注】roLabelImg安装与使用文章的介绍&#xff0c;完成roLabelImg的安装。 1.2 Yolov8开发环境的准备 首先创建python虚拟环境&#xff0c;pip install ultralytics 来进行安装。 二、数据集准备 流程&…