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

news2024/10/18 8:44:04

文章目录

  • 凹凸映射
  • 法线纹理设置
  • 高度纹理(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; // 没有压缩的直接计算结果
#else
	return 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.coloradmin.cn/o/2217653.html

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

相关文章

前缀和--一维和二维模板

前缀和 【模板】前缀和 描述 给定一个长度为n的数组a1,a2,…ana1,a2,…a**n. 接下来有q次查询, 每次查询有两个参数l, r. 对于每个询问, 请输出alal1…ara**la**l1…a**r 输入描述: 第一行包含两个整数n和q. 第二行包含n个整数, 表示a1,a2,…ana1,a2,…a**n.…

JavaWeb——Maven(4/8):Maven坐标,idea集成-导入maven项目(两种方式)

目录 Maven坐标 导入Maven项目 第一种方式 第二种方式 Maven坐标 Maven 坐标 是 Maven 当中资源的唯一标识。通过这个坐标,我们就能够唯一定位资源的位置。 Maven 坐标主要用在两个地方。第一个地方:我们可以使用坐标来定义项目。第二个地方&#…

CLANet:基于明场图像的跨批次细胞系识别综合框架|文献速递-基于深度学习的医学影像分类,分割与多模态应用

Title 题目 CLANet: A comprehensive framework for cross-batch cell line identificationusing brightfield images CLANet:基于明场图像的跨批次细胞系识别综合框架 01 文献速递介绍 细胞系鉴定(Cell Line Authentication,CLA&#x…

英飞达医学影像存档与通信系统 WebUserLogin.asmx 信息泄露漏洞复现

0x01 产品简介 英飞达医学影像存档与通信系统 Picture Archiving and Communication System,它是应用在医院影像科室的系统,主要的任务就是把日常产生的各种医学影像(包括核磁,CT,超声,各种X光机,各种红外仪、显微仪等设备产生的图像)通过各种接口(模拟,DICOM,网络…

二叉树与堆讲解

目录 1.树的概念及结构 1.树的概念 2.树的相关概念 3.树的表示 2.二叉树 1.概念 2.特殊的二叉树 1.满二叉树 2.完全二叉树 3.二叉树的性质 4.二叉树的存储结构 1.顺序结构 2.链式存储 3.堆 1.堆的概念及结构 2.堆的实现 1.堆的创建 2.堆的初始化(H…

力扣力扣力:206. 反转链表

leetcode链接:206. 反转链表 题目描述: 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 示例 1: 输入:head [1,2,3,4,5]输出:[5,4,3,2,1] 示例 2: 输入&#xff1…

手写Spring IOC-简易版

目录 项目结构entitydaoIUserDaoUserDaoImpl serviceIUserServiceUserServiceImpl ApplicationContext 配置文件初始化 IOC 容器RunApplication 注解初始化 IOC 容器BeanAutowired Reference 项目结构 entity User Data NoArgsConstructor AllArgsConstructor Accessors(chai…

面试八股(自用)

什么是java序列化,什么时候需要序列化? 序列化是指将java对象转化成字节流的过程,反序列化是指将字节流转化成java对象的过程。 当java对象需要在网络上传输 或者 持久化到存储文件中,就需要对java对象进行序列化处理。 JVM的主要组成部分…

Leetcode—1242. 多线程网页爬虫【中等】Plus(多线程)

2024每日刷题&#xff08;187&#xff09; Leetcode—1242. 多线程网页爬虫 实现代码 /*** // This is the HtmlParsers API interface.* // You should not implement it, or speculate about its implementation* class HtmlParser {* public:* vector<string>…

【Java 并发编程】阻塞队列与仿真餐厅

前言 阻塞队列 (BlockingQueue) 顾名思义是一种支持阻塞操作的队列&#xff0c;因为内部机制中添加了 wait 和 notify 方法&#xff0c;所以阻塞队列具备了线程之前相互协调的功能。阻塞队列主要运用于两个场景&#xff0c;一是生产者与消费者模型&#xff0c;二是线程池。本章…

【C语言】循环嵌套:乘法表

循环嵌套&#xff0c;外层循环执行一次&#xff0c;内层循环执行i次。分别控制 在循环的过程中加一层循环。 多层循环属于循环嵌套、嵌套循环 #include <stdio.h> #include <math.h> /* 功能&#xff1a;循环嵌套 乘法表 时间&#xff1a;2024年10月 地点&#xf…

可编辑73页PPT | 企业智慧能源管控平台建设方案

荐言分享&#xff1a;随着全球能源形势的日益紧张和智能化技术的快速发展&#xff0c;企业对于能源的高效利用和精细化管理需求愈发迫切。智慧能源管控平台作为一种集成了物联网、大数据、云计算、人工智能等先进技术的综合管理系统&#xff0c;旨在帮助企业实现能源数据的实时…

【线性回归分析】:基于实验数据的模型构建与可视化

目录 线性回归分析&#xff1a;基于实验数据的模型构建与可视化 1. 数据准备 2. 构建线性回归模型 3. 可视化 数据分析的核心 构建预测模型 应用场景 预测模型中的挑战 结论 线性回归分析&#xff1a;基于实验数据的模型构建与可视化 在数据分析领域&#xff0c;线性…

永恒之蓝漏洞

MS17-010是微软于2017年3月发布的一个安全补丁&#xff0c;旨在修复Windows操作系统中的一个严重漏洞&#xff0c;该漏洞被称为“永恒之蓝”&#xff08;EternalBlue&#xff09;。这个漏洞影响了Windows的Server Message Block&#xff08;SMB&#xff09;协议&#xff0c;允许…

JavaEE-线程安全问题

1.多线程带来的的⻛险-线程安全 1.1 观察线性不安全 // 此处定义⼀个 int 类型的变量 private static int count 0;public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(() -> {// 对 count 变量进⾏⾃增 5w 次for (int i 0; i…

基于Java微信小程序的水果销售系统详细设计和实现(源码+lw+部署文档+讲解等)

详细视频演示 请联系我获取更详细的演示视频 项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而不…

Prometheus运维监控平台之监控指标注册到consul脚本开发、自定义监控项采集配置调试(三)

系列文章目录 运维监控平台搭建 运维监控平台监控标签 golang_Consul代码实现Prometheus监控目标的注册以及动态发现与配置V1版本 文章目录 系列文章目录目的一、监控指标注册到consul的golang脚本开发1、修改settings.yaml文件2、修改config/ocnsul,go文件3、修改core/consul…

视频剪辑和转换gif一体化UI页面【可以解决gif体积过大】

视频剪辑和转换gif一体化UI页面 一&#xff0c;简介 这段代码实现了一个简单的 GUI 应用程序&#xff0c;主要功能包括&#xff1a; 选择视频文件&#xff1a;用户可以通过点击“选择视频”按钮打开文件选择对话框&#xff0c;选择 MP4 格式的视频文件。 转换为 GIF&#xf…

Axure复选框全选反选取消高级交互

亲爱的小伙伴&#xff0c;在您浏览之前&#xff0c;烦请关注一下&#xff0c;在此深表感谢&#xff01; 课程主题&#xff1a;复选框全选反选取消制作 主要内容&#xff1a;点击复选框&#xff0c;实现列表数据项全选选中、反选和取消选中效果 应用场景&#xff1a;多项选定…

【完-网络安全】Windows防火墙及出入站规则

文章目录 防火墙入站和出站的区别域网络、专用网络、公用网络的区别 防火墙 防火墙默认状态一般是出站允许&#xff0c;入站阻止。 入站和出站的区别 入站就是别人来访问我们的主机&#xff0c;也就是正向shell的操作 出站就是反向shell&#xff0c;主机需要主动连接kali&am…