LearnOpenGL——法线贴图、视差贴图学习笔记

news2024/9/22 11:33:59

LearnOpenGL——法线贴图、视差贴图学习笔记

  • 法线贴图 Normal Mapping
    • 一、基本概念
    • 二、切线空间
      • 1. TBN矩阵
      • 2. 切线空间中的法线贴图
    • 三、复杂模型
    • 四、小问题
  • 视差贴图 Parallax Mapping
    • 一、基本概念
    • 二、实现视差贴图
    • 三、陡峭视差映射 Steep Parallax Mapping
    • 四、视差遮蔽映射 Parallax Occlusion Mapping

法线贴图 Normal Mapping

一、基本概念

通过调整每个曲面的法向量,来让光照变化,进而模拟凹凸不平的表面。为使法线贴图工作,我们需要为每个fragment提供一个法线。我们可以使用2D纹理来存储法线数据,然后通过采样来得到特定纹理的法向量。
在这里插入图片描述
将法线向量的x、y、z元素储存到纹理中,代替颜色的r、g、b元素,因为法线向量的范围在[-1,1],所以我们要先将其映射到[0,1],变换为RGB颜色元素。

vec3 rgb_normal = normal * 0.5 + 0.5;

在这里插入图片描述
法线贴图多是蓝色为主,是因为法线基本上以z轴正方向为主:存储为B分量(蓝色)。法线向量从z轴方向也有向其他方向的偏差,颜色也就发生了轻微的变化。
加载纹理,绑定到合适的纹理单元,然后将片元着色器中添加对法线贴图的采样。

uniform sampler2D normalMap;  

void main()
{           
    // 从法线贴图范围[0,1]获取法线
    normal = texture(normalMap, fs_in.TexCoords).rgb;
    // 将法线向量转换为范围[-1,1]
    normal = normalize(normal * 2.0 - 1.0);   

    [...]
    // 像往常那样处理光照
}

目前,如果我们让平面竖直面对我们,此时效果正常,因为法线贴图中的法线方向指向z正方向并且平面的法线也指向z轴正方向。但当我们移动旋转平面时,就会发现光照不正确。比如下图,因为此时平面法线方向为y轴正方向,但法线贴图中的方向仍然为z轴正方向。
在这里插入图片描述

解决办法:在一个不同的坐标空间中处理所有的光照——切线空间:
这个坐标空间中的法线贴图矢量总是指向z轴正方向,然后其他照明矢量(如光源方向、观察方向等)相对于这个z方向进行变换。这样法线贴图不需要根据物体的方向变化而变化。无论物体如何旋转,光照计算都能在切线空间中正确处理,这简化了计算过程。

二、切线空间

切线空间是位于三角形表面上的空间,法线相对于单个三角形的局部坐标系。可以看成法线贴图向量的局部坐标系。无论最终变换到什么方向,它们都指向z轴正方向。我们可以使用一个特殊的矩阵来将法线贴图中的法线向量从切线空间变换到世界或观察空间,使它们与表面的法线方向对齐。

1. TBN矩阵

Tangent正切、Bitangent双切、Normal法向量。
在这里插入图片描述
为了构造这个矩阵,我们需要向上N、向右T、向前B三个向量。目前我们已知向上的向量N。接下来我们将会推导计算T和B的过程。(需要一点数学基础)
我们发现法线贴图的T和B坐标跟纹理的UV坐标很相似,我们可以从这里入手。(因为纹理坐标和切线向量在同一空间中)
在这里插入图片描述
U就是T坐标,V就是B坐标,不难发现
在这里插入图片描述
然后我们将上述方程组写成矩阵乘法
在这里插入图片描述
然后左右两边都乘上UV矩阵的逆矩阵
在这里插入图片描述
现在难点就是计算UV矩阵的逆矩阵(可以用伴随矩阵来求解逆矩阵,不过对于2×2的矩阵,我们可以直接写)
在这里插入图片描述
来个代码例子:
目前我们有两个三角形123和134,我们挑选其中一个三角形来计算。我们只需为每个三角形计算一个切线/副切线,它们对于每个三角形上的顶点都是一样的。

// positions
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);
// texture coordinates
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);
// normal vector
glm::vec3 nm(0.0, 0.0, 1.0);

我们计算第一个三角形的 E 和 deltaUV

glm::vec3 edge1 = pos2 - pos1;
glm::vec3 edge2 = pos3 - pos1;
glm::vec2 deltaUV1 = uv2 - uv1;
glm::vec2 deltaUV2 = uv3 - uv1;

然后就可以根据上面的公式来计算tangent和bitangent

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);  

[...] // 对平面的第二个三角形采用类似步骤计算切线和副切线

2. 切线空间中的法线贴图

为了让法线贴图工作,我们需要创建一个TBN矩阵,我们可以将之前计算的切线和副切线传给顶点着色器。然后在main中创建TBN矩阵

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec2 texCoords;
layout (location = 3) in vec3 tangent;
layout (location = 4) in vec3 bitangent;

void main()
{
   [...]
   vec3 T = normalize(vec3(model * vec4(tangent,   0.0)));
   vec3 B = normalize(vec3(model * vec4(bitangent, 0.0)));
   vec3 N = normalize(vec3(model * vec4(normal,    0.0)));
   mat3 TBN = mat3(T, B, N)
}

有两种使用TBN矩阵的办法

  1. 直接使用TBN矩阵:
    将TBN矩阵传给片元着色器,并使用TBN矩阵将法线向量从切线空间传到世界空间。让法线与其他光照变量处于同一空间。因为法线贴图中的法线向量是在切线空间中的,而其他光照矢量是在世界空间中的。
out VS_OUT {
    vec3 FragPos;
    vec2 TexCoords;
    mat3 TBN;
} vs_out;  

void main()
{
    [...]
    vs_out.TBN = mat3(T, B, N);
}

在片元着色器中我们用mat3作为输入变量

in VS_OUT {
    vec3 FragPos;
    vec2 TexCoords;
    mat3 TBN;
} fs_in;

然后将采样的法线贴图来转换(先采样,再映射,再转换)

normal = texture(normalMap, fs_in.TexCoords).rgb;
normal = normalize(normal * 2.0 - 1.0);   
normal = normalize(fs_in.TBN * normal);
  1. 使用TBN的逆矩阵,将所有世界空间向量转换到切线空间中计算
vs_out.TBN = transpose(mat3(T, B, N));

我们这里使用的是transpose是因为TBN是正交矩阵,正交矩阵的转置和逆矩阵相等。在shader中,使用逆矩阵的开销比转置大。
然后将TBN逆矩阵传给片元着色器,将其他变量都转换为切线空间进行计算,法线向量不做变换。

void main()
{           
    vec3 normal = texture(normalMap, fs_in.TexCoords).rgb;
    normal = normalize(normal * 2.0 - 1.0);   

    vec3 lightDir = fs_in.TBN * normalize(lightPos - fs_in.FragPos);
    vec3 viewDir  = fs_in.TBN * normalize(viewPos - fs_in.FragPos);    
    [...]
}

我们可以不用在片元着色器中进行转换,我们可以直接在顶点着色器中,对lightPos、viewPos以及FragPos进行变换,这样就可以免去在片元着色器中的操作了。也可以节省开销,因为顶点着色器运行次数比片元着色器少。(以下是在顶点着色器中)

out VS_OUT {
    vec3 FragPos;
    vec2 TexCoords;
    vec3 TangentLightPos;
    vec3 TangentViewPos;
    vec3 TangentFragPos;
} vs_out;

uniform vec3 lightPos;
uniform vec3 viewPos;

[...]

void main()
{    
    [...]
    mat3 TBN = transpose(mat3(T, B, N));
    vs_out.TangentLightPos = TBN * lightPos;
    vs_out.TangentViewPos  = TBN * viewPos;
    vs_out.TangentFragPos  = TBN * vec3(model * vec4(position, 0.0));
}

在像素着色器中我们使用这些新的输入变量来计算切线空间的光照。因为法线向量已经在切线空间中了,光照就有意义了。

glm::mat4 model;
model = glm::rotate(model, (GLfloat)glfwGetTime() * -10, glm::normalize(glm::vec3(1.0, 0.0, 1.0)));
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
RenderQuad();

三、复杂模型

对于复杂的模型,Assimp加载器已经帮我们实现了为每个顶点计算出柔和的切线和副切线向量。我们可以通过下面的代码用Assimp获取计算出来的切线空间:

vector.x = mesh->mTangents[i].x;
vector.y = mesh->mTangents[i].y;
vector.z = mesh->mTangents[i].z;
vertex.Tangent = vector;

当加载模型时,Assimp的aiTextureType_NORMAL并不会加载它的法线贴图,而aiTextureType_HEIGHT却能

vector normalMaps = loadMaterialTextures(material, 
	aiTextureType_HEIGHT, "texture_normal");

四、小问题

对于网格很大的模型,上面有很多共享的顶点,法线贴图应用到这些表面时会讲切线向量平均化。但是这样的话TBN可能不会相互垂直,因此TBN可能不再是正交矩阵了,法线贴图就会稍稍偏移。

我们可以对其进行格拉姆-施密特正交化,对TBN进行重正交化。在顶点着色器中:

vec3 T = normalize(vec3(model * vec4(tangent, 0.0)));
vec3 N = normalize(vec3(model * vec4(normal, 0.0)));
// re-orthogonalize T with respect to N
T = normalize(T - dot(T, N) * N);
// then retrieve perpendicular vector B with the cross product of T and N
vec3 B = cross(T, N);

mat3 TBN = mat3(T, B, N)

视差贴图 Parallax Mapping

一、基本概念

视差贴图是和法线贴图类似,也是用来增加表面细节而不需要额外增加几何信息。它对根据储存在纹理中的几何信息对顶点进行位移或偏移。每个纹理像素包含了高度值的纹理叫做高度贴图
在这里插入图片描述

视差贴图是根据观察方向和高度图来改变纹理坐标。
在这里插入图片描述
红色线表示高度图中的值,V是观察方向。视差贴图目的是在A位置上的片元不再使用A的纹理坐标,而是使用B的纹理坐标。

如何从点A得到点B的纹理坐标:视差贴图通过A片元的高度值来缩放观察方向V。我们将V的长度缩放为等于A处高度 H(A),然后我们确定P向量,作为纹理坐标偏移量。这个点B得到的还是近似值,当高度快速变化的时候,看起来就不会很真实。
在这里插入图片描述
我们在旋转之后,点P就很难定位了,所以仿照法线贴图,我们引入了切线空间来计算。我们将观察方向变化到切线空间中,所以P向量的x和y分量会与表面切线和副切线对齐,由于切线和副切线向量与表面纹理坐标的方向相同,我们可以用P的x和y元素作为纹理坐标的偏移量。

二、实现视差贴图

这个例子的高度图的颜色是相反的,我们叫他深度贴图,模拟深度比高度更容易一些。
在这里插入图片描述
这个时候,我们使用向量V减去A的纹理坐标得到P。在着色器中,我们使用1-采样得到的深度贴图中的深度值。
位移贴图是在像素着色器中实现的,我们需要得到观察方向V,所以需要切线空间中的观察者位置和片元位置。

顶点着色器如下

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec2 texCoords;
layout (location = 3) in vec3 tangent;
layout (location = 4) in vec3 bitangent;

out VS_OUT {
    vec3 FragPos;
    vec2 TexCoords;
    vec3 TangentLightPos;
    vec3 TangentViewPos;
    vec3 TangentFragPos;
} vs_out;

uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;

uniform vec3 lightPos;
uniform vec3 viewPos;

void main()
{
    gl_Position      = projection * view * model * vec4(position, 1.0f);
    vs_out.FragPos   = vec3(model * vec4(position, 1.0));   
    vs_out.TexCoords = texCoords;    

    vec3 T   = normalize(mat3(model) * tangent);
    vec3 B   = normalize(mat3(model) * bitangent);
    vec3 N   = normalize(mat3(model) * normal);
    mat3 TBN = transpose(mat3(T, B, N));

    vs_out.TangentLightPos = TBN * lightPos;
    vs_out.TangentViewPos  = TBN * viewPos;
    vs_out.TangentFragPos  = TBN * vs_out.FragPos;
}

在片元着色器中,我们实现视差贴图的逻辑

#version 330 core
out vec4 FragColor;

in VS_OUT {
    vec3 FragPos;
    vec2 TexCoords;
    vec3 TangentLightPos;
    vec3 TangentViewPos;
    vec3 TangentFragPos;
} fs_in;

uniform sampler2D diffuseMap;
uniform sampler2D normalMap;
uniform sampler2D depthMap;

uniform float height_scale;

vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir);

void main()
{           
    // Offset texture coordinates with Parallax Mapping
    vec3 viewDir   = normalize(fs_in.TangentViewPos - fs_in.TangentFragPos);
    vec2 texCoords = ParallaxMapping(fs_in.TexCoords,  viewDir);

    // then sample textures with new texture coords
    vec3 diffuse = texture(diffuseMap, texCoords);
    vec3 normal  = texture(normalMap, texCoords);
    normal = normalize(normal * 2.0 - 1.0);
    // proceed with lighting code
    [...]    
}
vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{ 
    float height =  texture(depthMap, texCoords).r;    
    vec2 p = viewDir.xy / viewDir.z * (height * height_scale);
    return texCoords - p;    
}

我们定义了一个ParallaxMapping函数来获得纹理坐标。在此函数中,我们先从深度图中采样到深度值,然后计算偏移p,同时引入了一个height_scale来控制视差效果的强度。

为什么要用viewDir.xy / viewDir.z: 通过除以 viewDir.z,我们确保了视角接近平行于表面(即 viewDir.z 接近0),偏移量 p 会更大。这模拟了当一个物体从边缘观察时,由于视差效应,你能够看到的物体部分与直接正面观察时不同的现象。
在这里插入图片描述
此时视差贴图的边缘仍然有古怪的现象,原因是在平面的边缘上,纹理坐标超出了0到1的范围进行采样,根据纹理的环绕方式导致了不真实的结果。解决的方法是当它超出默认纹理坐标范围进行采样的时候就丢弃这个fragment:

texCoords = ParallaxMapping(fs_in.TexCoords,  viewDir);
if(texCoords.x > 1.0 || texCoords.y > 1.0 || texCoords.x < 0.0 || texCoords.y < 0.0)
    discard;

我们会发现在一些极端的视角,还是会有明显的走样。

三、陡峭视差映射 Steep Parallax Mapping

相比于正常的视差贴图,陡峭视差贴图用更多的样本点来确定向量P到B,所以即使陡峭的高度变化,由于提高了样本数量,效果也会不错。

陡峭视差贴图的思想是将总深度划分为多个相等深度的层,然后对于每一层都对深度图进行采样,沿着P方向移动纹理坐标,直到找到一个采样深度值小于当前层的深度值
在这里插入图片描述
我们需要修改一下ParallaxMapping函数

vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{ 
    // number of depth layers
    const float numLayers = 10;
    // calculate the size of each layer
    float layerDepth = 1.0 / numLayers;
    // depth of current layer
    float currentLayerDepth = 0.0;
    // the amount to shift the texture coordinates per layer (from vector P)
    vec2 P = viewDir.xy * height_scale; 
    vec2 deltaTexCoords = P / numLayers;
  
    vec2  currentTexCoords     = texCoords;
	float currentDepthMapValue = texture(depthMap, currentTexCoords).r;
	  
	while(currentLayerDepth < currentDepthMapValue)
	{
	    // shift texture coordinates along direction of P
	    currentTexCoords -= deltaTexCoords;
	    // get depthmap value at current texture coordinates
	    currentDepthMapValue = texture(depthMap, currentTexCoords).r;  
	    // get depth of next layer
	    currentLayerDepth += layerDepth;  
	}
	
	return currentTexCoords;   
}   
  • 首先设置层数,然后用1除以层数得到每层的深度值
  • 初始化currentLayerDepth(当前层深度值)
  • 然后计算得到P,再用P除以层数,将P也分层,得到分层后的纹理坐标
  • 再初始化当前纹理坐标的深度值
  • 开始循环比较,若当前层的深度值 < 当前纹理坐标的深度值,就继续下一层,直到当前层深度值 > 当前纹理坐标的深度值,就停止循环,返回此时纹理坐标

我们再改进一下,当视角方向是垂直表面时,就不需要太多采样点,当视角方向偏向侧面时,就增大采样点

const float minLayers = 8;
const float maxLayers = 32;
float numLayers = mix(maxLayers, minLayers, abs(dot(vec3(0.0, 0.0, 1.0), viewDir)));

陡峭视差贴图同样有自己的问题。因为这个技术是基于有限的样本数量的,我们会遇到锯齿效果以及图层之间有明显的断层。

四、视差遮蔽映射 Parallax Occlusion Mapping

与陡峭视差映射差不多,但我们不采用碰撞后的第一个深度层的纹理坐标,而是在碰撞前和碰撞后的深度层之间进行线性插值。线性插值的权重取决于表面高度两个深度层值之间的距离
在这里插入图片描述
我们还是修改ParallaxMapping代码

vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{ 
    // number of depth layers
    const float minLayers = 10;
    const float maxLayers = 20;
    float numLayers = mix(maxLayers, minLayers, abs(dot(vec3(0.0, 0.0, 1.0), viewDir)));  
    // calculate the size of each layer
    float layerDepth = 1.0 / numLayers;
    // depth of current layer
    float currentLayerDepth = 0.0;
    // the amount to shift the texture coordinates per layer (from vector P)
    vec2 P = viewDir.xy / viewDir.z * height_scale; 
    vec2 deltaTexCoords = P / numLayers;
  
    // get initial values
    vec2  currentTexCoords     = texCoords;
    float currentDepthMapValue = texture(depthMap, currentTexCoords).r;
      
    while(currentLayerDepth < currentDepthMapValue)
    {
        // shift texture coordinates along direction of P
        currentTexCoords -= deltaTexCoords;
        // get depthmap value at current texture coordinates
        currentDepthMapValue = texture(depthMap, currentTexCoords).r;  
        // get depth of next layer
        currentLayerDepth += layerDepth;  
    }
    
    // -- parallax occlusion mapping interpolation from here on
    // get texture coordinates before collision (reverse operations)
    vec2 prevTexCoords = currentTexCoords + deltaTexCoords;

    // get depth after and before collision for linear interpolation
    float afterDepth  = currentDepthMapValue - currentLayerDepth;
    float beforeDepth = texture(depthMap, prevTexCoords).r - currentLayerDepth + layerDepth;
 
    // interpolation of texture coordinates
    float weight = afterDepth / (afterDepth - beforeDepth);
    vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);

    return finalTexCoords;
}

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

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

相关文章

HexView 刷写文件脚本处理工具-命令行介绍(四)-地址范围缩减(/AR:‘range‘)

地址范围缩减(/AR:‘range’) 此选项可以限制加载到内存中的数据范围。 如果只想在 HexView 中处理数据的缩减范围,这将非常有用。 通过其块开始地址和长度来指定地址范围,地址和长度由逗号分隔。也可以使用开始地址和结束地址来指定范围。 然后,这两个值必须由‘-’分…

继承的多种方式

1. 原型链继承 function Parent() {this.name "xiaohong"; } Parent.prototype.getName function () {console.log(this.name); };function Child() {}Child.prototype new Parent();const child new Child(); child.getName(); console.log(child.name);引用类…

带你速通C语言——指针(10)

指针是C语言中最强大但也最容易引起困惑的概念之一。它们直接关联内存管理&#xff0c;使得程序员可以高效地操作数据和内存。下面我将尽量以简单明了的方式介绍指针的基本概念。 1.指针基础 指针本质上是存储内存地址的变量&#xff0c;这个地址指向一个值。通过指针&#xf…

STM32 PWR电源控制 与 低功耗模式 详解

目录 STM32 PWR电源控制 与 低功耗模式 详解 1. PWR 电源控制 简介 2. PWR 电源控制 框图 3. 上电复位和掉电复位 与 可编程电压检测器&#xff08;PVD&#xff09; 3.1 内嵌复位与电源控制模块特性图 3.2 上电复位和掉电复位 3.3 可编程电压检测器&#xff08;PVD&…

蚁群算法原理与实战(Python、MATLAB、C++)

蚁群算法 1.蚁群算法来源 蚁群算法&#xff08;Ant Colony Optimization&#xff0c;简称ACO&#xff09;是一种模拟自然界中蚂蚁寻找食物路径行为的优化算法&#xff0c;主要用于解决组合优化问题。它的灵感来源于意大利学者Marco Dorigo在1992年提出的蚂蚁系统模型。 蚁群算…

脑网络相似性:方法与应用

摘要 图论方法已被证明是理解、表征和量化复杂大脑网络的有效工具。然而&#xff0c;定量比较两个图形的方法却较少受到关注。在一些网络神经科学应用中&#xff0c;比较大脑网络确实是必不可少的。在这里&#xff0c;本研究讨论了近年来用于比较大脑网络的技术现状、挑战以及…

Android常见界面控件(二)

目录 前言 一、 RadioButton控件 设置RadioGroup的监听事件 二、CheckBox控件 三、Toast类 改变Toast窗口的提示位置 前言 在上一篇中&#xff0c;我们讲解了三个常见的控件&#xff1a;TextView控件、Button控件、ImageView控件&#xff0c;那么本篇我们就接着讲剩下的…

Selenium实战:深度解析Python中嵌套Frame与iFrame的定位与切换技巧,解决Selenium定位不到的问题

在Web自动化测试中&#xff0c;处理网页中的Frame和iFrame是常见的挑战之一。这些元素在网页中扮演着承载独立HTML文档的角色&#xff0c;使得直接定位或操作其中的元素变得复杂。Python的Selenium库提供了强大的工具来应对这些挑战&#xff0c;本文将详细介绍如何使用Selenium…

SFP光模块、gt口、PMD、PMA、PCS之间的关系

ZYNQ内部的GT&#xff08;高速收发器&#xff09;接口包含了PCS&#xff08;物理编码子层&#xff09;与PMA&#xff08;物理介质接入层&#xff09;。这两个层在高速数据传输中起着至关重要的作用。 PCS层&#xff08;物理编码子层&#xff09; PCS层位于协调子层&#xff0…

Ubuntu虚拟机服务器的搭建

01.VMware安装 略。 02.Ubuntu虚拟机安装 略。 03.配置Ubuntu虚拟机网络 参考视频&#xff1a; Ubutu虚拟机网络配置&#xff08;桥接&#xff09;https://www.bilibili.com/video/BV1bG411V72A/?spm_id_from333.999.0.0&vd_sourced1fd4bcc46805ab35cc8bbb5a8bf318f…

win11如何查看串口的名字

1、右击win然后点击设备管理器 2、点击端口然后右击串口点击属性 3、进入窗口后点击Port information即可看见Port name属性就是串口名字

uniapp/vue如何实现一个子表单及子表单作用

子表单是一个辅助表单或一个表&#xff0c;它允许在主表单中添加多个行式项目&#xff0c;以处理与主记录相关联的多个辅助项目或数据。子表单在多种应用场景中发挥着重要作用&#xff0c;特别是在需要处理一对多关系的数据时。 以下是对子表单的详细解析&#xff1a; 定义与特…

苍穹外卖day10

苍穹外卖day10 Spring Task订单状态定时处理WebSocket应用&#xff08;弹幕&#xff0c;网页聊天&#xff0c;体育实况更新&#xff0c;股票基金实时更新&#xff09; 来单题型代码实现需求分析 客户催单 Spring Task 链接: 在线生成器 在线生成器 订单状态定时处理 每分钟检…

木舟0基础学习Java的第二十五天(JavaWeb)

XML 概念和体系 XML指可扩展标记语言&#xff08;EXtensible Markup Language&#xff09; XML没有预定义标签 需要自定义标签 <标签></标签> XML特点 XML数据以纯文本格式存储 实现不同应用程序之间的数据通信 实现不同平台的数据通信 实现不同平台的数据共…

Linux------Cortex-A架构的处理器运行模型与其寄存器组

寄存器组分为 外设寄存器组 比如&#xff1a;和总线相连的io寄存器&#xff0c;usart配置寄存器&#xff0c;spi配置寄存器等等 内核寄存器组&#xff1a;R0-R15 CPSR SPSR一共18个寄存器组&#xff0c;内核寄存器组用来记录当前程序地址状态&#xff0c;当前执行指令等&a…

8月来得及|1000/660/880题45天带刷计划!

刷题不在于多&#xff0c;而在于精 你选的这几本题集没有问题&#xff0c;660题专门训练客观题&#xff0c;880题和1000题都是不错的综合性题集&#xff0c;特别适合在强化阶段进行刷题训练&#xff0c;但是问题是你做这么多题&#xff0c;也不一定能起到多好的作用。 刷题的…

Linux | 进程概念详解:冯诺依曼体系结构、进程基本概念、PCB、进程组织、fork()创建子进程、进程运行逻辑

文章目录 进程概念1、冯诺依曼体系结构2、进程2.1基本概念2.2描述进程-PCB2.3组织进程2.4查看进程2.5通过系统调用获取进程标识符2.6通过系统调用创建进程-fork初识头文件与返回值fork函数的调用逻辑和底层逻辑 进程概念 1、冯诺依曼体系结构 目前我们认识的计算机中&#xff…

dm 到 dm 的 HS 同步部署

一、数据库部署 完成两节点数据库部署并初始化&#xff0c;配置参数如下&#xff1a; 节点 192.168.2.132 192.168.2.133 数据库版本 DM8 DM8 实例名 DM1 DM2 端口号 5236 5236 数据文件路径 /home/dmdba/dmdata /home/dmdba/dmdata 二、 dmhs 部署 1. 部署…

户外无线麦克风哪个牌子好,领夹麦克风十大品牌排行榜推荐

​为了在数字时代的浪潮中有效传达自己的思想和感情&#xff0c;选择合适的声音工具变得尤为重要。无线领夹麦克风就是这样一种工具&#xff0c;它能够帮助我们在任何环境下都保持最佳的语音传递效果。经过慎重考虑和市场调研&#xff0c;我为大家推荐几款性能卓越且价格合理的…

测试报告----功能测试

目录 一、项目背景 二、项目功能 &#xff08;1&#xff09;页面1&#xff1a;用户注册 &#xff08;2&#xff09;页面2&#xff1a;登陆页面 &#xff08;3&#xff09;页面3&#xff1a;信息主页 &#xff08;4&#xff09;页面4&#xff1a;详情页面 ​编辑&#xf…