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

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

一、切线空间

在《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 axisfloat3(n1.x, n1.z, -n1.y), // -90 degree rotation around x axisfloat3(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.mzph.cn/pingmian/40484.shtml

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

相关文章

Lesson 46 Can you ... ?

Lesson 46 Can you … ? 词汇 lift v. 拿起&#xff0c;搬起 n. 电梯&#xff08;直梯&#xff09; 用法&#xff1a;1. lift … up 拎起……    例句&#xff1a;我可以拎起那只小猫。       I can lift that cat up.    2. take a lift    例句&#xff1a…

如何在Docker容器中,修改MySQL密码

如果MySQL运行在Docker容器中&#xff0c;修改MySQL密码的方法稍有不同。以下是如何在Docker中修改MySQL密码的步骤&#xff1a; 方法1&#xff1a;使用MySQL命令行工具 1. 找到MySQL容器的ID或者名字&#xff1a; docker ps 2. 进入MySQL容器&#xff1a; docker exec -i…

mongodb在windows环境安装部署

一、mongodb 1.释义 MongoDB 是一种开源的文档型 NoSQL 数据库管理系统&#xff0c;使用 C 编写&#xff0c;旨在实现高性能、高可靠性和易扩展性。MongoDB 采用了面向文档的数据模型&#xff0c;数据以 JSON 风格的 BSON&#xff08;Binary JSON&#xff09;文档存储&#x…

VSCode使用SSH无需输入密码远程连接服务器

目录 一、密钥生成 1、使用windows11自带的命令行 2、使用putty工具 二、查看密钥 三、设置服务器 这个过程是比较简单的&#xff0c;为了方便后续留用和查看&#xff0c;整理个笔记放着。 一、密钥生成 1、使用windows11自带的命令行 在任一文件夹中&#xff0c;空白处…

数据融合平台的概述、特点及技术方案

在当今数字化时代&#xff0c;数据已成为企业最宝贵的资产之一。然而&#xff0c;数据的分散存储和格式不一&#xff0c;常常导致数据孤岛现象&#xff0c;使得数据的潜在价值难以被充分挖掘和利用。在这样的背景下&#xff0c;数据融合平台应运而生&#xff0c;它的意义不仅在…

amis中条件组合器condition-builder的使用 和 解析

1.amis中配置一个条件组合器&#xff0c;condition-builder。并根据自己业务需求配置source。这里用了一些自定义filter来进行预处理。 {"type": "condition-builder","label": "条件组合","name": "node.conditions&q…

Python 插入、替换、提取、或删除Excel中的图片

Excel是主要用于处理表格和数据的工具&#xff0c;我们也能在其中插入、编辑或管理图片&#xff0c;为工作表增添视觉效果&#xff0c;提升报告的吸引力。本文将详细介绍如何使用Python操作Excel中的图片&#xff0c;包含以下4个基础示例&#xff1a; 文章目录 Python 在Excel…

.NET 漏洞情报 | 某整合管理平台SQL注入

01阅读须知 此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等&#xff08;包括但不限于&#xff09;进行检测或维护参考&#xff0c;未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失&#xf…

AI-算力集群通往AGI

背景&#xff1a; 自GPT-4发布以来&#xff0c;全球AI能力的发展势头有放缓的迹象。 但这并不意味着Scaling Law失效&#xff0c;也不是因为训练数据不够&#xff0c;而是结结实实的遇到了算力瓶颈。 具体来说&#xff0c;GPT-4的训练算力约2e25 FLOP&#xff0c;近期发布的几个…

驱使ai学习搭子,写出一份“完美”的代码“文档”

自己把控“核心关键”&#xff0c;ai会把文档写得比您预想的“完美”。 (笔记模板由python脚本于2024年07月04日 10:44:39创建&#xff0c;本篇笔记适合喜欢结伴ai学习的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff…

PHP房产小程序微信小程序系统源码

&#x1f3e0;—— 购房租房&#xff0c;一“指”搞定&#x1f3e1; &#x1f50d;【开篇&#xff1a;房产新视界&#xff0c;尽在掌握】 在这个信息爆炸的时代&#xff0c;找房子不再是一场漫长的奔波。有了“房产微信小程序”&#xff0c;无论是购房还是租房&#xff0c;都…

【PCIe】P2P DMA

PCIe P2P (peer-to-peer communication)是PCIe的一种特性&#xff0c;它使两个PCIe设备之间可以直接传输数据&#xff0c;而不需要使用主机RAM作为临时存储。如下图3的走向 比如EP1要发送和数据给EP2,操作流程如下&#xff1a; 1. 打开EP1的dma控制器&#xff1b;--client侧 …

配置jupyter时出现问题?怎么办?

在自己创建的虚拟环境&#xff08;nmjpytorch&#xff09;安装完jupyter&#xff0c;没有跳转到链接&#xff0c;问题如图&#xff1a; 解决方法&#xff1a; 1、查看自己的tornado版本为5.1.1&#xff0c;坑太高了&#xff0c;降低版本为4.5.3 2、卸载tornado-5.1.1 3、安装t…

firewalld(7)NAT、端口转发

简介 在前面的文章中已经介绍了firewalld了zone、rich rule等规则设置&#xff0c;并且在iptables的文章中我们介绍了网络防火墙、还有iptables的target,包括SNAT、DNAT、MASQUERADE、REDIRECT的原理和配置。那么在这篇文章中&#xff0c;将继续介绍在firewalld中的NAT的相关配…

git常用命令速查表

Git相关概念简述 版本库&#xff1a;git在本地开辟的一个存储空间&#xff0c;一般在 .git 文件里。工作区(workspace)&#xff1a; 就是编辑器里面的代码&#xff0c;我们平常开发直接操作的就是工作区。暂存区&#xff08;index/stage&#xff09;&#xff1a;暂时存放文件的…

如何用Python实现三维可视化?

Python拥有很多优秀的三维图像可视化工具&#xff0c;主要基于图形处理库WebGL、OpenGL或者VTK。 这些工具主要用于大规模空间标量数据、向量场数据、张量场数据等等的可视化&#xff0c;实际运用场景主要在海洋大气建模、飞机模型设计、桥梁设计、电磁场分析等等。 本文简单…

告别写作难题,这些AI写作工具让你文思泉涌

在现实生活中&#xff0c;除了专业的文字工作者&#xff0c;各行各业都避免不了需要写一些东西&#xff0c;比如策划案、论文、公文、讲话稿、总结计划……等等。而随着科技的进步&#xff0c;数字化时代的深入发展&#xff0c;AI已经成为日常工作中必不可少的工具了&#xff0…

学习LLM的随笔

1、信息量、信息熵、交叉熵和困惑度 注&#xff1a;因为真实分布的概率为1&#xff0c;所以在分类任务中交叉熵可以简化为上述形式。 &#xff08;1&#xff09;信息熵&#xff1a;信息熵中使用 l o g 2 ( p ( x ) ) log_2(p(x)) log2​(p(x)) 来表示对 x x x 编码需要的…

Linux Vim 最全面教程(Linux)

文章目录 前言一、vim工作模式1、命令模式2、编辑模式3、末行模式 二、常用其他按键说明总结 前言 vim是一款功能文本编辑器&#xff0c;也是早年vi编辑器的加强版&#xff0c;它的最大特色就是使用命令进行编辑&#xff0c;完全脱离了鼠标的操作。代码补全、编译和错误跳转等…

二叉树基础

一.树 1.树的定义 在计算机科学中&#xff0c;树是一种用于表示层次结构的抽象数据类型和非线性数据结构。树由一组节点&#xff08;Nodes&#xff09;和节点之间的关系&#xff08;通常通过边表示&#xff09;组成。 2.特性 树是一种递归的数据结构&#xff1a;树可以定义为…