UE4_材质基础_切线空间与法线贴图

news2024/11/20 15:39:23

学习笔记,不喜勿喷,侵权立删,祝愿大家生活越来越好!                 

一、切线空间

在《OpenGL基础11:空间》中提到了观察空间、裁剪空间、世界空间等。切线空间和它们一样,都属于坐标空间

上面就是一个切线空间的例子,对于切线空间:

  • N:该顶点本身的法线方向,z轴
  • T:该顶点的一条切线,但由于切线数量有无数条,其一般由模型给定,对应着UV图中的U,也就是使用和纹理坐标方向相同的那条
  • B:由前两者叉乘得到,对应着UV图中的V

UV图:用于告知计算机,如何用2维的贴图包住3维的物体,本质上UV图提供了一种模型表面与纹理图像之间的连接关系,也就是确定纹理图像上的每一个像素应该放置在模型表面的哪一个顶点上,如果没有UV图,多边形网格将不能被渲染出纹理,其中U和V分别指的是纹理空间的水平轴和垂直轴

二、为什么需要切线空间


在此之前,先需要大致了解一下法线贴图(Normal Mapping):为了得到正确的光照,需要知道物体每个顶点的法向量,但为了保证效率,一般物体的顶点不会太多,就像一块砖块,它的表面往往凹凸不平,但事实上它可能单纯的只是一个立方体,每一面给上了一个贴图。这样如果还想要体现出物体“凹凸不平”的效果,就需要用到法线贴图或者高度贴图,也就是对于纹理的每一个像素,都指定一个特定的法向量!

很巧,法向量是个3维向量,而颜色正好也是一个3维向量,所以直接将向量信息存储成颜色信息没有任何的压力

可是这样就出现了另一个问题:就像下图是一个黑色小包模型的一部分,中间有两个拉链拉头,这两个子模型是完全一样的,唯一的区别就是位置不同从而光照的效果不同。那么很明显,为了节省空间和性能,用的也会是同样的贴图,但是!他们的法向量却不一样,也就是说这两个完全相同的物体并不能使用同一张法线贴图(法线贴图可以说只是个数据存储媒介,和颜色没有关系)

确实可以为这些相同的子模型准备不同的法线贴图,就像一个六个面相同的立方体,为它专门准备6张法线贴图,但是有可能这样的子模型特别多,并且方向都不相同,这个时候还准备不同的法线贴图就比较尴尬了

上面的问题归根结底就是物体的朝向问题,我们只要找到这样的一个空间:无论当前是什么朝向,所有“纹理像素点”的法向量一定都是不变的,并且可以通过空间变换就可以得到世界坐标下正确的法向量,就可以解决问题了。所有相同的面,都可以赋予同样的法线贴图。

这个空间就是切线空间,酷不酷?

三、如何求出切线空间

切线空间是什么?对于一个网格模型,我们逐顶点来分析,每个顶点都有着自己的切线空间,如下图所示,我们可以将其称为TBN空间。其中N代表该点处的法线,T(tangent)和B(binormal)都是该点处的切线。由于一个点处的切线有无数条,我们指定T切线是沿着纹理的u坐标方向的,B切线是沿着纹理的v坐标方向的。 

从最简单的开始算:一个4个顶点100%平坦的平面,怎么求出它的切线空间呢?

T (Tangent) B (Bitangent) N (Normal) 向量一个一个来:

N 就不说了,它就是法向量,然后就是 T 切线向量:

肯定的,T、B、N都是单位长度,这样的话根据右图可以得到:

\begin{array}{l} E_{1}=\Delta U_{1} T+\Delta V_{1} B \\ E_{2}=\Delta U_{2} T+\Delta V_{2} B \end{array}

只要理解了这个公式后面就都好办了,首先可以看出,这是求的顶点P2的切线空间,而P1、P2、P3是当前P2所在的一个三角形片元,其次,E1、E2其实已经当前点的其中的两条切线了,只不过为了统一切线,切线的方向必须是纹理UV的方向,正好点 P1和P3的纹理坐标映射在主副切线方向上的向量之和正是向量E1和向量E2之和,同时也是说E2是三角形的一条边,这个三角形的另外两条边是\DeltaU2和\DeltaV2 ,它们与切线向量T和副切线向量B方向相同。

既然 EUV 都是已知的,那么就可以求出 T 和 B 了:

上面的公式可以写成:

\begin{array}{l} \left(E_{1 x}, E_{1 y}, E_{1 z}\right)=\Delta U_{1}\left(T_{x}, T_{y}, T_{z}\right)+\Delta V_{1}\left(B_{x}, B_{y}, B_{z}\right) \\ \left(E_{2 x}, E_{2 y}, E_{2 z}\right)=\Delta U_{2}\left(T_{x}, T_{y}, T_{z}\right)+\Delta V_{2}\left(B_{x}, B_{y}, B_{z}\right) \end{array}

转成矩阵就是:

\left[\begin{array}{ccc} E_{1 x} & E_{1 y} & E_{1 z} \\ E_{2 x} & E_{2 y} & E_{2 z} \end{array}\right]=\left[\begin{array}{cc} \Delta U_{1} & \Delta V_{1} \\ \Delta U_{2} & \Delta V_{2} \end{array}\right]\left[\begin{array}{ccc} T_{x} & T_{y} & T_{z} \\ B_{x} & B_{y} & B_{z} \end{array}\right]

两边都乘以\DeltaU和\DeltaV的逆矩阵:

好了搞定,把未知数成功扔到了左边

转成代码就是(代码中的数据输入是最简单情况:一个平面正方形,4个顶点分别是:(-1, -1, 0)、(-1, 1, 0)、(1, -1, 0)和(1, 1, 0),对应的纹理顶点为(0, 0)、(0, 1)、(1, 0)和(1, 1)):

void GetTangent()
{
    glm::vec3 pos1(-1.0, 1.0, 0.0);
    glm::vec3 pos2(-1.0, -1.0, 0.0);
    glm::vec3 pos3(1.0, -1.0, 0.0);
    glm::vec3 pos4(1.0, 1.0, 0.0);
    glm::vec2 uv1(0.0, 1.0);
    glm::vec2 uv2(0.0, 0.0);
    glm::vec2 uv3(1.0, 0.0);
    glm::vec2 uv4(1.0, 1.0);
    glm::vec3 normal(0.0, 0.0, 1.0);
 
    glm::vec3 tangent1, bitangent1;     //第一个三角形(1,2,3)的顶点切线空间
    glm::vec3 tangent2, bitangent2;     //第二个三角形(1,3,4)的顶点切线空间
 
    glm::vec3 edge1 = pos2 - pos1;
    glm::vec3 edge2 = pos3 - pos1;
    glm::vec2 deltaUV1 = uv2 - uv1;
    glm::vec2 deltaUV2 = uv3 - uv1;
    GLfloat f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);
    tangent1.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x);
    tangent1.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y);
    tangent1.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z);
    tangent1 = glm::normalize(tangent1);
    bitangent1.x = f * (-deltaUV2.x * edge1.x + deltaUV1.x * edge2.x);
    bitangent1.y = f * (-deltaUV2.x * edge1.y + deltaUV1.x * edge2.y);
    bitangent1.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z);
    bitangent1 = glm::normalize(bitangent1);
 
    edge1 = pos3 - pos1;
    edge2 = pos4 - pos1;
    deltaUV1 = uv3 - uv1;
    deltaUV2 = uv4 - uv1;
    f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);
    tangent2.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x);
    tangent2.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y);
    tangent2.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z);
    tangent2 = glm::normalize(tangent2);
    bitangent2.x = f * (-deltaUV2.x * edge1.x + deltaUV1.x * edge2.x);
    bitangent2.y = f * (-deltaUV2.x * edge1.y + deltaUV1.x * edge2.y);
    bitangent2.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z);
    bitangent2 = glm::normalize(bitangent2);
 
    GLfloat quadVertices[] =
    {
        //位置                  //法向量          //纹理坐标    //切线                              //副切线
        pos1.x, pos1.y, pos1.z, nm.x, nm.y, nm.z, uv1.x, uv1.y, tangent1.x, tangent1.y, tangent1.z, bitangent1.x, bitangent1.y, bitangent1.z,
        pos2.x, pos2.y, pos2.z, nm.x, nm.y, nm.z, uv2.x, uv2.y, tangent1.x, tangent1.y, tangent1.z, bitangent1.x, bitangent1.y, bitangent1.z,
        pos3.x, pos3.y, pos3.z, nm.x, nm.y, nm.z, uv3.x, uv3.y, tangent1.x, tangent1.y, tangent1.z, bitangent1.x, bitangent1.y, bitangent1.z,
        pos1.x, pos1.y, pos1.z, nm.x, nm.y, nm.z, uv1.x, uv1.y, tangent2.x, tangent2.y, tangent2.z, bitangent2.x, bitangent2.y, bitangent2.z,
        pos3.x, pos3.y, pos3.z, nm.x, nm.y, nm.z, uv3.x, uv3.y, tangent2.x, tangent2.y, tangent2.z, bitangent2.x, bitangent2.y, bitangent2.z,
        pos4.x, pos4.y, pos4.z, nm.x, nm.y, nm.z, uv4.x, uv4.y, tangent2.x, tangent2.y, tangent2.z, bitangent2.x, bitangent2.y, bitangent2.z
    };
    //……
}

四、TBN与空间变换

其实关于切线空间的了解到这里就结束了,下面可以当作扩展记录一下:

仔细看上面转换过程中的一个式子:

换一种表示方法就是:

或许就可以更容易发现, T 和 B 其实正是一组基向量,抛开法向量那一维,T 和 B 正好可以将二维uv空间中的向量和点,转到三维世界空间的某个平面上,而这个平面正好是切线平面

也就是说,如果T和B是已知的、切线空间是已知的,那么我们很容易就可以将二维纹理坐标映射到三维空间顶点坐标,而上面计算的所以意义,就在于我们知道二维空间纹理坐标,也知道三维空间顶点坐标,要反过来去找这个空间

如果理解了这个,就好办了,不用纠结于坐标和法线,它的本质就是如此

五、法线贴图存储的是什么?

法线贴图其实并不是真正的贴图,所以也不会直接贴到物体的表面,它所起的作用就是记录每个点上的法线的方向。所以这个贴图如果看起来也会比较诡异,经常呈现一种偏蓝紫色的样子,主要是因为法线纹理的RGB通道存储了在每个顶点各自的切线空间中的法线方向的映射值,其实法线向量有三个值,这三个值可以分别对应RGB三个值,一般法线垂直于物体的分量多一些,也就是Blue多一些,也就偏向于蓝色了。

六、法线压缩

 压缩的第一步很简单,由于归一化的法线长度为1,且在切线空间下,法线的z分量不可能为负数,所以只需要存储x和y值即可。本文的压缩方法在这一步压缩的基础上,利用现有的纹理压缩方法,进行进一步压缩。

  在支持DirectX10的显卡上,可以使用BC5格式进行压缩。BC5的压缩方法内存情况如图1所示,该格式有两个颜色通道(R和G),每个通道使用两个1Byte的值来表示,每个像素使用3Bit在这两个颜色值之间进行插值。将法线贴图中每个法线的x和y值利用BC5格式进行压缩,如图2所示。对每个Block(16个像素)存储x的最大、小值和y的最大、小值,然后每个像素利用3Bit进行插值,相当于在图2右图所示的8*8区域内取样(为了简化表示,图2只画了4*4点)。

使用一般纹理mipmap方法生成的法线贴图对于漫反射表面基本没问题,但是在镜面表面会导致严重的视觉问题。对于漫反射表面来说,光照的计算公式为l·nl为光线方向的相反方向,n为法线,l·n1 l·n2 l·n3 l·n4 = l·(n1 n2 n3 n4) / 4,而mipmap则是事先计算(n1 n2 n3 n4) / 4,所以对于漫反射表面,对法线贴图使用传统方式的mipmap基本没问题。为什么是基本没问题而不是完全没问题呢?因为这里存在一个近似,若l·< 0,则光照值为0(光照不能为负),若将这个因素考虑进去,漫反射表面也会有问题,不过在实际当中这种情况表现不明显,所以可以认为基本没问题。

  对于镜面表面来说,当视线偏离反射光线方向的时候,光照强度会急剧下降,反映在公式中是因为其含有cosm(h·n)项(具体公式可以Google),而漫反射光照是线性变化,所以对于镜面表面,不能使用传统方法生成法线贴图的mipmap。法线贴图对于镜面反射的mipmap如图3所示,第一幅图中有4个像素,每个像素有法线和镜面反射波瓣(红色的是法线,周围一圈是镜面反射波瓣,镜面反射波瓣用于表示不同方向的反射强度)。图2中间部分,表示正确的mipmap情况,分别从4个像素合并为2个像素,从两个像素合并为1个像素。而现有的方法中,没有方法可以做到这样的mipmap,所以只能用其他方法进行近似。

图2的底部左图,是使用一般纹理的mipmap方法对法线进行平均,可以看到这种方法产生出的镜面反射波瓣和正确的镜面反射波瓣差距很大,其根本原因是使用线性方法对非线性的参数进行计算。图2底部图右图,每次在平均法线的同时,改变表面的光泽度(即改变镜面光公式中的m),虽然最终结果与正确的mipmap有一些差距,但是比一般纹理的mipmap的方法要好很多。

  所以,对法线贴图的mipmap方法之一,就是在使用一般纹理的mipmap方法对法线进行平均的同时,每张mipmap都必须附带一张光泽贴图(gloss map),记录每个像素点的光泽度(即m),m的计算原则就是让最后的镜面反射波瓣与正确的镜面反射波瓣最接近。当然,还有其他很多方法能得到不错的结果,具体可以参看《Real-Time Rendering 3rd》,或去搜索相关论文。

七、法线混合算法

在平时的工作中,我们经常需要在一张基础的法线贴图上融合一张细节纹理贴图,也即将两张法线贴图融合。这一节我们就来简单讲一讲有哪些常用的法线融合算法。

Linear Blending(线性混合)

简单的线性混合代码如下:

float3 n1 = tex2D(texBase,   uv).xyz*2 - 1;
float3 n2 = tex2D(texDetail, uv).xyz*2 - 1;
float3 r  = normalize(n1 + n2);
return r*0.5 + 0.5;

可以看出,我们只是简单地Unpack Normal,然后相加后归一化,再pack回去。这种做法类似于做平均化处理,由于两张纹理不尽相同,我们最后得到的结果实际上是对两张纹理"Flattening(平铺化)"的效果。如果其中一张是平面纹理的话(比如(0,0,1)),我们最后得到的结果其实就是另一张纹理图展平后的结果,这可能并不是我们想要的(我们想让另一张不起作用)。

Overlay Blending(叠加混合)

叠加混合代码如下:

float3 n1 = tex2D(texBase,   uv).xyz;
float3 n2 = tex2D(texDetail, uv).xyz;
float3 r  = n1 < 0.5 ? 2*n1*n2 : 1 - 2*(1 - n1)*(1 - n2);
r = normalize(r*2 - 1);
return r*0.5 + 0.5;

叠加混合算法其实就是PS中的叠加混合,这种算法看似合理,实际上仍然显示不正确,我们仍然是对贴图的通道做统一处理,并没有根据矢量的特性对通道进行分别处理。之所以会有人使用叠加混合,可能是因为这种混合方式相比PS其他混合显示效果更好一些吧。

Partial Derivative Blending(偏导混合)

偏导混合的代码如下:

float3 n1 = tex2D(texBase,   uv).xyz*2 - 1;
float3 n2 = tex2D(texDetail, uv).xyz*2 - 1;
float2 pd = n1.xy/n1.z + n2.xy/n2.z; // Add the PDs
float3 r  = normalize(float3(pd, 1));
return r*0.5 + 0.5;

为了代码的健壮性,第三四行代码应改成:

float3 r = normalize(float3(n1.xy*n2.z + n2.xy*n1.z, n1.z*n2.z));

偏导函数理论的细节这里不赘述,从图中我们可以看出,偏导混合的结果相比之前有了很大提升(原始贴图的细节被保留下来),但问题依然存在,仔细观察可以发现圆锥部分的细节贴图仍然被Flatten,但这种融合在处理不同材质间过渡时是非常不错的

处理材质过渡时可能用到的代码:

float2 pd = lerp(n1.xy/n1.z, n2.xy/n2.z, blend);
float3 r = normalize(float3(pd, 1));

WhiteOut Blending

代码如下:

float3 n1 = tex2D(texBase,   uv).xyz*2 - 1;
float3 n2 = tex2D(texDetail, uv).xyz*2 - 1;
float3 r  = normalize(float3(n1.xy + n2.xy, n1.z*n2.z));
return r*0.5 + 0.5;

这种混合方法第一次在SIGGRAPH 2007 上被提出,从代码中可以看出,这种方式类似于偏导混合,只是在xy通道上没有乘以Z分量。从混合结果来看,这种方法很好地解决了圆锥面细节贴图flatten的问题,但仍然存在flatten的问题。

UDN Blending

代码如下:

float3 n1 = tex2D(texBase,   uv).xyz*2 - 1;
float3 n2 = tex2D(texDetail, uv).xyz*2 - 1;
float3 r  = normalize(float3(n1.xy + n2.xy, n1.z));
return r*0.5 + 0.5;

这种更简单的混合方法在虚幻引擎开发者论坛上被提出,相比WhiteOut Blending,这里的变化只是在Z分量上取消乘以n2.z。从另一个角度看,这种混合方式只是在线性混合的基础上取消了z分量上的相加。对比WhiteOut Blending,这种混合方式在边界的混合效果会差一些,flatten的效果更明显,但由于这种混合方式更加节省着色器指令,在低端机的使用上会更频繁。

对于法线纹理混合,我们通常需要满足以下三点:
逻辑性:操作过程可以使用简单数学几何实现
特殊情况:当其中一个纹理是平面时,结果显示为另一纹理
避免Flattening:两张法线纹理的强度都得到保存
上面的几种方法对以上几点很难兼顾,下面的方法将通过旋转(或者说重定向)细节贴图的方法来使细节纹理匹配基础纹理。这个过程就像是对几何物体进行光照计算时对切线空间法线进行变换一样。
举个简单例子,我们有一个物体平面的法线为s,基础纹理的法线方向为 t,细节纹理的法线方向为u,我们可以通过计算s到t的变换矩阵或者说算子,然后将算子应用到u上,我们就得到了最终的结果r,如图:

Unity

代码如下:

float3 n1 = tex2D(texBase,   uv).xyz*2 - 1;
float3 n2 = tex2D(texDetail, uv).xyz*2 - 1;

float3x3 nBasis = float3x3(
     float3(n1.z, n1.y, -n1.x), // +90 degree rotation around y axis
     float3(n1.x, n1.z, -n1.y), // -90 degree rotation around x axis
     float3(n1.x, n1.y,  n1.z));

float3 r = normalize(n2.x*nBasis[0] + n2.y*nBasis[1] + n2.z*nBasis[2]);
return r*0.5 + 0.5;

Unity采用的方法是根据基础法线纹理的x,y轴进行旋转得到重定向后的矩阵,再应用该矩阵对细节法线贴图进行变换得到混合后的结果。但是,这种方法当且仅当n1为(0,0,±1)时表现正确,当n1偏离这个值时,结果会逐渐偏离正确的显示效果。如上图9显示。

我们可以看出,当n1与x轴重合时,这些点会塌缩成一个圈,因为这个时候,细节贴图的变换矩阵为:

Reoriented Normal Mapping(RNM Blending)

代码如下:

float3 t = tex2D(texBase,   uv).xyz*float3( 2,  2, 2) + float3(-1, -1,  0);
float3 u = tex2D(texDetail, uv).xyz*float3(-2, -2, 2) + float3( 1,  1, -1);
float3 r = t*dot(t, u)/t.z - u;
return r*0.5 + 0.5;

相比Unity的做法,这种方法也是使用一个基于n1变换的矩阵来对n2进行变换,只不过变换过程采用四元数代替,修正了图8中unity出现的错误。这种方法的另一个好处是,如果t是单位长度,u的长度是不变的,也就是说如果u也是单位长度,这种方法是不需要进行(normalize)标准化处理的(会节省不少着色器指令消耗)。但在实际操作中这种理论情况很难实现,很多因素诸如:量化、压缩纹理、mipmaping、滤波操作等都可能有所影响。你可能在漫反射效果中无法看出区别,但这仍然会切切实实地影响PBR理论遵循的能量守恒定律。保守起见,我们最好还是对结果进行归一化处理。

float3 r = normalize(t*dot(t, u) - u*t.z);
这种方法是默认法线z值是>=0的,但是这在运算过程中并不总是满足的,这种方法的潜在问题是如果法线在程序编写中已经进行了重定向,然后被压缩进一张双分量格式的法线贴图,虽然解压重建贴图的过程通常假设Z>=0。这种情况下最直接的解决方法就是对z值clamp一下,然后再归一化,这样之后再压缩进贴图。
UE4里的BlendAngleCorrectNormal节点,是RNMBlending。

八、UE4法线调整

1、调整法线强度

想控制一张法线贴图的呈现强度,不能直接对整个贴图的UV进行运算,需要对法线贴图的RG通道进行运算,并将每个通道的计算结果通过追加节点Append组成新的数值,改变强度参数,从而改变物体表现的强度。

1.各通道使用乘法与强度参数NormalIntensify相乘

2.使用追加节点,组合新的向量。并将值与Normal链接。

经过法线增强后:

2、法线贴图融合 BlendAngleCorrectedNormals

使用 BlendAngleCorrectedNormals 节点,完成两张法线贴图的叠加。

3、使用普通贴图制作法线 NormalFromHeightMap

这是个材质函数,可以查看节点组成。

需要将贴图转换为纹理对象

参考教程:https://blog.csdn.net/Jaihk662/article/details/107917594                      
参考教程:https://blog.csdn.net/ZJU_fish1996/article/details/83934059

参考教程:OpenGL基础46:切线空间-CSDN博客

参考教程:Learn OpenGL, extensive tutorial resource for learning Modern OpenGL

参考教程:https://zhuanlan.zhihu.com/p/364821684

参考教程:https://www.cnblogs.com/wangchengfeng/p/3475489.html

参考教程:

UE4-材质法线强度调整、法线贴图混合、自定义材质函数、材质边缘过渡、植被动态效果_ue4法线强度调节节点-CSDN博客

 

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

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

相关文章

FPGA的理解,个人的见解,不一定对

类似于面包板上搭建电路&#xff0c;但是使用的是逻辑单元模块&#xff1b;如加法器&#xff0c;减法器&#xff0c;寄存器等 没有模拟电路的电容&#xff0c;电阻&#xff1b;但是逻辑单元的底层实现&#xff0c;使用MOS管等电路实现电路的开关&#xff1b;从而表示0&#xf…

springai+pgvector+ollama实现rag

首先在ollama中安装mofanke/dmeta-embedding-zh:latest。执行ollama run mofanke/dmeta-embedding-zh 。实现将文本转化为向量数据 接着安装pgvector&#xff08;建议使用pgadmin4作为可视化工具&#xff0c;用navicate会出现表不显示的问题&#xff09; 安装好需要的软件后我们…

【python】OpenCV—Nighttime Low Illumination Image Enhancement

文章目录 1 背景介绍2 代码实现3 原理分析4 效果展示5 附录np.ndindexnumpy.ravelnumpy.argsortcv2.detailEnhancecv2.edgePreservingFilter 1 背景介绍 学习参考来自&#xff1a;OpenCV基础&#xff08;24&#xff09;改善夜间图像的照明 源码&#xff1a; 链接&#xff1a…

vue2 webpack使用optimization.splitChunks分包,实现按需引入,进行首屏加载优化

optimization.splitChunks的具体功能和配置信息可以去网上自行查阅。 这边简单讲一下他的使用场景、作用、如何使用&#xff1a; 1、没用使用splitChunks进行分包之前&#xff0c;所有模块都揉在一个文件里&#xff0c;那么当这个文件足够大、网速又一般的时候&#xff0c;首…

原厂商是什么意思?云管平台原厂商有哪些企业?

最近不少IT小伙伴在问关于原厂商相关问题&#xff0c;今天我们就来简单回答一下&#xff0c;仅供参考&#xff01; 原厂商是什么意思&#xff1f; 原厂商&#xff0c;或称原厂&#xff0c;是指生产特定产品或零部件的原始厂家。 软件原厂商是什么意思&#xff1f; 软件原厂…

课设:选课管理系统(Java+MySQL)

在本博客中&#xff0c;我将介绍用Java、MySQL、JDBC和Swing GUI开发一个简单的选课管理系统。 技术栈 Java&#xff1a;用于编写应用程序逻辑MySQL&#xff1a;用于存储和管理数据JDBC&#xff1a;用于连接Java应用程序和MySQL数据库Swing GUI&#xff1a;用于构建桌面应用程…

Let‘s Encrypt免费SSL证书申请最简单的步骤

随着互联网的飞速发展&#xff0c;网络安全问题愈发凸显其重要性。而HTTPS协议作为保障网站数据传输安全的重要手段&#xff0c;已经得到了广泛的应用。 申请Lets Encrypt免费泛域名SSL证书步骤 登录来此加密网站&#xff0c;输入域名&#xff0c;可以勾选泛域名和包含根域。…

Appium环境搭建,华为nova8鸿蒙系统(包括环境安装,环境配置)(一)

1.安装代码工具包 appium python client pip install appium-python-client 2.安装JDK 参考链接: ant+jmeter+jenkins从0实现持续集成(Windows)-CSDN博客 3.下载并安卓SDK 下载地址:AndroidDevTools - Android开发工具 Android SDK下载 Android Studio下载 Gradle下载…

搜维尔科技:详谈ART的工具追踪技术

您的生产流程中是否已经受益于刀具跟踪系统&#xff1f;您是否意识到它们的价值&#xff1f;因为它们可以优化您的装配顺序&#xff0c;从而节省您的时间和金钱。 目前我们提供两种工具跟踪解决方案&#xff1a; 1.ART与 VERPOSE的解决方案——易于使用的图像识别 安装在工…

C语言 | Leetcode C语言题解之第213题打家劫舍II

题目&#xff1a; 题解&#xff1a; int robRange(int* nums, int start, int end) {int first nums[start], second fmax(nums[start], nums[start 1]);for (int i start 2; i < end; i) {int temp second;second fmax(first nums[i], second);first temp;}retur…

[激光原理与应用-97]:南京科耐激光-激光焊接-焊中检测-智能制程监测系统IPM介绍 - 1 - 什么是焊接以及传统的焊接方法

目录 一、什么是焊接 1.1 概述 1.2 基本原理 二、传统的焊接技术与方法 2.1 手工电弧焊&#xff1a; 1、定义与原理 2、特点 3、焊条类型 4、应用领域 5、安全注意事项 2.2 气体保护焊&#xff1a; 1、原理与特点 2、应用领域 3、气体选择 4、注意事项 2.3 电阻…

六角法兰面螺栓机械性能

六角法兰面螺栓&#xff0c;作为一种常见的紧固件&#xff0c;因其独特的设计和优良的机械性能&#xff0c;在众多工业领域中占据重要地位。与传统的六角头螺栓相比&#xff0c;六角法兰面螺栓的底部有一个扁平的法兰面&#xff0c;能够提供更大的接触面积&#xff0c;分散压力…

[leetcode] n个骰子的点数

. - 力扣&#xff08;LeetCode&#xff09; class Solution { public:vector<double> statisticsProbability(int num) {vector<double> dp(6, 1.0 / 6.0);for (int i 2; i < num; i) {vector<double> tmp(5 * i 1, 0);for (int j 0; j < dp.size()…

算法day02 回文 罗马数字转整数

回文 搞错了String类型的indexOf方法&#xff0c;理解成获取对应下标的值&#xff0c;实际上是在找对应值的下标。 4ms 耗时最少的方法尽量不会去调用jdk提供的方法&#xff0c;而是直接使用对应的数学逻辑关系来处理&#xff0c; 甚至用 代替equals方法。 罗马数字转整数 考…

西安石油大学 课程习题信息管理系统(数据库课设)

主要技术栈 Java Mysql SpringBoot Tomcat HTML CSS JavaScript 该课设必备环境配置教程&#xff1a;&#xff08;参考给出的链接和给出的关键链接&#xff09; JAVA课设必备环境配置 教程 JDK Tomcat配置 IDEA开发环境配置 项目部署参考视频 若依框架 链接数据库格式注…

使用Python脚本实现SSH登录

调试IDE&#xff1a;PyCharm Python库&#xff1a;Paramiko 首先安装Paramiko包到PyCharm&#xff0c;具体步骤为&#xff1a;在打开的PyCharm工具中&#xff0c;选择顶部菜单栏中“File”下的“Settings”&#xff0c;在设置对话框中&#xff0c;选择“Project”下的“Proje…

taoCMS v3.0.2 文件上传漏洞(CVE-2022-23880)

前言 CVE-2022-23880是一个影响taoCMS v3.0.2的任意文件上传漏洞。攻击者可以利用此漏洞通过上传特制的PHP文件在受影响的系统上执行任意代码。 漏洞细节 描述: 在taoCMS v3.0.2的文件管理模块中存在任意文件上传漏洞。攻击者可以通过上传恶意的PHP文件来执行任意代码。 影响…

【IDEA】maven如何进行文件导入,配置并打包

一&#xff0c;介绍、安装 1、maven介绍 maven是一个Java世界中&#xff0c;构建工具。 核心功能&#xff1a; (1) 管理依赖&#xff1a; 管理文件运行的顺序逻辑依赖关系。对配置文件&#xff0c;进行构建和编译。其也是在调用jdk&#xff0c;来进行编译打包工作。 (2) 打…

编译lvgl(V8.4.0)源代码为.lib文件并验证

目录 概述 1. 软硬件信息 1.1 开发版硬件 1.2 软件版本信息 2 编译LVGL为.lib 2.1 准备工作 2.2 编译.lib 3 验证.lib 3.1 Keil中加载.lib 3.2 Keil配置头文件路径 3.3 编译代码 4 应用程序 4.1 主函数中初始化接口 4.2 LVGL demo测试 4.2.1 编写测试代码 4.2.2…

字符串和正则表达式踩坑

// 中石化加油卡号格式&#xff1a;以 100011 开头共19位public static final String ZHONGSHIYOU_OIL_CARD_PATTERN "^100011\\d{13}$";// 中石油加油卡号格式&#xff1a;以90、95、70开头共16位public static final String ZHONGYOU_OIL_CARD_PATTERN "^(9…