当前位置: 首页 > news >正文

games106 homework1实现

games106 homework1

gltf介绍图:

请添加图片描述

骨骼动画

动画相关属性:

对GLTF的理解参照了这篇文章:

glTF格式详解(动画)

GLTF文件格式详解

buffer和bufferView对象用于引用动画数据。 buffer对象用来指定原始动画数据, bufferView对象用来引用buffer对象。比如下面的bufferView对象引用了索引为0的指定了原始动画数据的buffer对象。

"buffers": [{"byteLength": 2514732,"uri": "busterDrone.bin"}
],
"bufferViews": [{"buffer": 0,"byteLength": 196356,"byteOffset": 0,"byteStride": 0,"target": 34963},{"buffer": 0,"byteLength": 99000,"byteOffset": 196356,"byteStride": 0,"target": 34962},
...
]

Accessor对象用于描述原始动画数据的结构。 count属性表示动画数据包含了1190个动画关键帧的计时信息,每个计时信息是一个float类型的标量,所有计时信息占用了14280个字节。第二个accessor对象引用的数据在计时信息这14280字节之后,共有1190个元素,每个元素是一个包含3个分量类型为float的向量,推测SCALAR类型为计时信息,VEC3为平移信息,VEC4为旋转四元数。

"accessors": [{"bufferView": 3,"byteOffset": 0,"componentType": 5126,"count": 1190,"max": [ 0.06134279, 0.07975265, 0.02774119 ],"min": [ -0.06144484, -0.1283657, -0.09091433 ],"type": "VEC3"},{"bufferView": 3,"byteOffset": 14280,"componentType": 5126,"count": 1190,"max": [ 1, 1, 1 ],"min": [ -1, -1, -1 ],"type": "VEC3"},{"bufferView": 2,"byteOffset": 0,"componentType": 5126,"count": 1190,"max": [ 0.9694519, 0.997895 ],"min": [ 0.001107991, 0.001113892 ],"type": "VEC2"},
...
]

Animation包含samplers和channels。samplers数组对象,用于描述动画数据来源。samplers数组对象,用于描述动画数据来源。

sampler对象包含了input和output属性,这两个属性通过索引来引用accessor对象 。这里的input属性引用了索引为2的用于计时信息accessor对象,output属性引用了索引 为3的用于旋转信息的accessor对象。此外,sampler对象还有包含了一个interpolation属性,用于指定插值方式,这里的示例使用的LINEAR插值方式。

channel对象用于在动画数据和node对象之间建立联系 ,指定动画所作用的node对象。

"samplers": [{"input": 60,"interpolation": "LINEAR","output": 61},
...
]"channels": [{"sampler": 0,"target": {"node": 8,"path": "translation"}},
...
]

代码修改:

详细查看了下gltfskinning.cpp中的动画代码。这个example读取的gltf文件多出来了一个skins属性,skins中包含inverseBindMatrices,joints,skeleton属性。

joints记录了作为关节点的node索引。gltf中的skeleton的形式更为简单,它包含着根骨骼的Node索引。inverseBindMatrices是gltf帮忙计算好的模型空间变换到对应骨骼空间的矩阵。

loadSkin函数把相关的joints node 存储在skin.joints 的容器中。我自己理解的意思是在render时,从骨骼的根节点开始依次处理joints及节点的变换。

对于结构体的更新可以直接参考gltfskinning.cpp去更新Node,添加AnimationSampler,AnimationChannel,Animation结构体。

对于动画的加载loadAnimation可以直接copy gltfskinning.cpp中的代码,更新时update Animation思路也相似,然而作业中的gltf文件没有蒙皮skins属性,所以不能直接使用updateJoint函数更新,此处动画的更新需要逐个去更新节点的位置。

初始代码的drawNode函数中表明最终绘制节点时用到的位置为node.matrix,通过vkCmdPushConstants传入shader对应的参数就是primitive. model。

	// drawNode Funcglm::mat4              nodeMatrix    = node.matrix;VulkanglTFModel::Node *currentParent = node.parent;while (currentParent){nodeMatrix    = currentParent->matrix * nodeMatrix;currentParent = currentParent->parent;}// Pass the final matrix to the vertex shader using push constantsvkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::mat4), &nodeMatrix);

上面这一段drawNode函数的代码很重要,它实现的是一个全静态模型的传值。currentParent->matrix是初始loadNode时根据translation,rotate,scale计算得到的值,这个值在后面是没有再被更新的。

在update Animation函数中,根据动画的数据对每个时间node的translation,rotate,scale的值进行了插值计算更新,而传值得到的matrix并没有得到更新。

在drawNode中应该使用 getLocalMatrix()去计算此刻的matrix,同时不能漏掉node的parent更新,否则绘制出模型的就会错位(我就是这么做的,检查了好久的问题QAQ)。

glm::mat4 getLocalMatrix()
{return glm::translate(glm::mat4(1.0f), translation) * glm::mat4(rotation) * glm::scale(glm::mat4(1.0f), scale) * matrix;
}// ** update nodeMatrix **
glm::mat4 nodeMatrix = node->getLocalMatrix(); //node->matrix;
VulkanglTFModel::Node* currentParent = node->parent;
while (currentParent) {nodeMatrix = currentParent->getLocalMatrix() * nodeMatrix;currentParent = currentParent->parent;
}

在初始化loadNode时,不需要更改node->matrix的值,加载初始的translation,rotate,scale即可。

// Get the local node matrix
// It's either made up from translation, rotation, scale or a 4x4 matrix
if (inputNode.translation.size() == 3) {//node->matrix = glm::translate(node->matrix, glm::vec3(glm::make_vec3(inputNode.translation.data())));node->translation = glm::make_vec3(inputNode.translation.data());
}
if (inputNode.rotation.size() == 4) {glm::quat q = glm::make_quat(inputNode.rotation.data());//node->matrix *= glm::mat4(q);node->rotation = q;
}
if (inputNode.scale.size() == 3) {//node->matrix = glm::scale(node->matrix, glm::vec3(glm::make_vec3(inputNode.scale.data())));node->scale = glm::make_vec3(inputNode.scale.data());
}
if (inputNode.matrix.size() == 16) {node->matrix = glm::make_mat4x4(inputNode.matrix.data());
};

由于计算以后没有push constant使得shader的参数更新,所以动画并不会显示出来。在render()中updateAnimation后重新调用一遍buildCommandBuffer即可。

PBR材质

PBR材质介绍:

PBR材质相关理解记录在了这篇文章里,作业中的实现参考了pbrbasic, pbribl, pbrtexture中的实现。

PBR材质讲解

数据结构更新

pbr.cpp文件中pushconsts中传递了一些属性,然而在作业中 roughness,metallic,包括rgb参数其实都不需要cpp传递,可以直接从纹理图片中获取。所以直接更新Material的struct为下,同步修改loadMaterial中对材质参数的加载。

struct Material {uint32_t baseColorTextureIndex;uint32_t metallicRoughnessTextureIndex;uint32_t normalTextureIndex = -1;uint32_t emissiveTextureIndex = -1;uint32_t occlusionTextureIndex = -1;VkDescriptorSet descriptorSet;};

如下是mesh的相关属性,primitives对应的是vertex基础属性,material是gltf文件中materials中材质的索引。gltf相关图片中已经写的比较清楚,就不再细述。

"meshes": [
{"name": "mesh_L_P4_17366L_P4","primitives": [{"attributes": {"POSITION": 0,"NORMAL": 1,"TEXCOORD_0": 2,"TANGENT": 3},"indices": 4,"material": 0,"mode": 4}]
},
]

根据primitives中的属性,应该给Vertex 新加上一个tangent属性。

在loadNode的过程中,会根据mesh的primitives数据向vertexBuffer中添加数据,Vertex新增属性tangent后也需要更新loadNode代码。

struct Vertex {glm::vec3 pos;glm::vec3 normal;glm::vec2 uv;glm::vec3 color;glm::vec4 tangent;};

在preparePipeline()中,需要更新VkVertexInputAttributeDescription的属性。

const std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = {vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFModel::Vertex, pos)),	// Location 0: Positionvks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFModel::Vertex, normal)),// Location 1: Normalvks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFModel::Vertex, uv)),	// Location 2: Texture coordinatesvks::initializers::vertexInputAttributeDescription(0, 3, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFModel::Vertex, color)),	// Location 3: Colorvks::initializers::vertexInputAttributeDescription(0, 4, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(VulkanglTFModel::Vertex, tangent)),    // Location 4: tangent
};

DescriptorBinding更新

数据结构和pipeline相关进行更新后也需要更新Descriptor的绑定。
在setupDescriptors()中对poolSizes,descriptorPoolInfo进行修改。VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER对应的是shader中的ubo,数量只有一个,因为material没有多余属性需要其他的ubo传递。VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER顾名思义是绑定的图片采样器,每张纹理有5个texture index。

std::vector<VkDescriptorPoolSize> poolSizes = { // matrices + materials uniform buffervks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),// One combined image sampler per model image/texturevks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, static_cast<uint32_t>(5 * glTFModel.materials.size())),
};

对于这两个类型的descriptior需要分别设定它们的layout。
对于uniform设定为VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,纹理设定为VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER。

// Descriptor set layout for passing matrices
VkDescriptorSetLayoutBinding setLayoutBinding = vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0);
// Descriptor set layout for passing material textures
std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0),vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 2),vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 3),vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 4)
};

根据descriptorPool和descriptorLayout的相关信息得到VkDescriptorSetAllocateInfo,并根据此信息调用vkAllocateDescriptorSets分配descriptor。最后再用vks::initializers::writeDescriptorSet把每个material对应的5张纹理的descriptor写入到material的descriptor中。

// Descriptor sets for materials textures
for (auto& mat : glTFModel.materials) {const VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.textures, 1);VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &mat.descriptorSet));std::vector<VkWriteDescriptorSet> writeDescriptorSets = {vks::initializers::writeDescriptorSet(mat.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &glTFModel.images[mat.baseColorTextureIndex].texture.descriptor),vks::initializers::writeDescriptorSet(mat.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &glTFModel.images[mat.metallicRoughnessTextureIndex].texture.descriptor),vks::initializers::writeDescriptorSet(mat.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &glTFModel.images[mat.normalTextureIndex].texture.descriptor),};if (mat.emissiveTextureIndex >= 0 && mat.emissiveTextureIndex < glTFModel.images.size()) writeDescriptorSets.push_back(vks::initializers::writeDescriptorSet(mat.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3, &glTFModel.images[mat.emissiveTextureIndex].texture.descriptor));if (mat.occlusionTextureIndex >= 0 && mat.occlusionTextureIndex < glTFModel.images.size()) writeDescriptorSets.push_back(vks::initializers::writeDescriptorSet(mat.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 4, &glTFModel.images[mat.occlusionTextureIndex].texture.descriptor));vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
}

如果material有其他一些参数要传入的话,可以考虑新建一个uniform buffer,此时需要对descriptorPoolSize的大小进行更新,对新建的uniform buffer的descriptor进行初始化,

除此外还需要参照Vertex shader uniform buffer block的建立,在prepareUniformBuffers()函数中给每个material新建一个uniform buffer并map。

着色器编译:

参照以下:

HLSL in Vulkan :: Vulkan Documentation Project Demo

vulkan读取的是编译后的spv文件,对于.frag和.vert文件编译。可以找到vulkansdk/bin/Win32/glslangValidator.exe的位置对glsl文件进行编译。

# glsl 编译
glslangValidator mesh.frag -V100 -o mesh.frag.spv
glslangValidator mesh.vert -V100 -o mesh.vert.spv# hlsl编译
dxc -spirv -T vs_6_0 -E main .\mesh.vert -Fo .\mesh.vert.spv
dxc -spirv -T ps_6_0 -E main .\mesh.frag -Fo .\mesh.frag.spv

mark一下: hlsl中的texture register,space 关键字 (keyword) 指定声明变量绑定到的逻辑寄存器空间。该关键字省略默认是space0,register(t3, space0) 永远不会与 register(t3, space1)冲突,也永远不会与另一个空间中可能包含 t3 的任何数组冲突。

要注意space的分配,分配不当程序运行时可能会出现一些问题。

Texture2D<float4> tex1 : register(t3,  space0)
Texture2D<float4> tex2[4] : register(t10)
Texture2D<float4> tex3[7][5][3] : register(t20, space1)

请添加图片描述

ToneMapping Pass

这部分的实现参考了,尝试了离屏渲染方法:

GAMES106 作业1 问题整理(Tone Mapping 部分)

上面讲述了两个方案,render passsubpass

render pass思路概括:将原先绘制模型的pass绘制结果作为图片存储在对应的frame buffer里,进行后处理后再渲染到屏幕上。为此需要创建新的render pass和frame buffer对原来的模型绘制进行离屏渲染。

用新增的一个后处理pass进行tonemapping,需要新增一个实现tonemapping的shader,然后添加一个新的pipeline去加载这个新的shader module。需要新增实现的部分就是下图中从pipeline向上到DescriptorSetLayout部分。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8UaHyIIp-1691162057283)(/images/games106homewrok1/Untitled%201.png)]

离屏渲染RenderPass

离屏渲染的案例可以参考vulkan示例的bloom。

renderDoc调试观察bloom.exe, 这个程序被划分为了3个pass。colorpass(offscreen)→blur→scene。先对colorpass进行离屏渲染,然后将渲染结果blur模糊处理,最后在场景绘制中将离屏渲染的内容也绘制上。

请添加图片描述
请添加图片描述

对相关关系不是很熟练,所以如下图所示整理了一下bloom这个程序中的相关绑定,便于自己理解相关绑定。

请添加图片描述

  1. 资源绑定

    首先需要对原先的数据结构进行修改。修改pipelines结构体,新增VkPipeline tonemap。

    struct Pipelines {VkPipeline solid;VkPipeline wireframe = VK_NULL_HANDLE;VkPipeline tonemap = VK_NULL_HANDLE;
    } pipelines;
    

    对于新增的VkPipeline tonemap,也要创建一个VkPipelineLayout和VkDescriptorSet与之对应。同步更新对应的DescriptorSetLayouts结构体.

    struct {VkPipelineLayout pipelineLayout;VkPipelineLayout postPipelineLayout;
    } pipelineLayouts;struct {VkDescriptorSet descriptorSet;VkDescriptorSet postDescriptorSet;
    } descriptorSets;struct DescriptorSetLayouts {VkDescriptorSetLayout matrices;VkDescriptorSetLayout textures;VkDescriptorSetLayout post;
    } descriptorSetLayouts;
    

    在setupDescriptors(),需要设置后处理pass的descriptor layout,把后处理pass需要用到的资源绑定到流水线中。

    对于新的tonemap pass,只需要将一张offscreen渲染得到的图片传给shader即可,不再需要uniform input。

    mark:记得修改descriptor poolSizes。

    在preparaPipeline()中,需要创建VkGraphicsPipelineCreateInfo去记录pipeline基本的信息。其中pipelineCI.pVertexInputState绑定了vertexInputStateCI,即vertex输入信息,它会和vertexInputAttributes联系起来,记录要传递到shader的输入信息。

    VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayouts.pipelineLayout, renderPass, 0);pipelineCI.pVertexInputState = &vertexInputStateCI;pipelineCI.pInputAssemblyState = &inputAssemblyStateCI;pipelineCI.pRasterizationState = &rasterizationStateCI;pipelineCI.pColorBlendState = &colorBlendStateCI;pipelineCI.pMultisampleState = &multisampleStateCI;pipelineCI.pViewportState = &viewportStateCI;pipelineCI.pDepthStencilState = &depthStencilStateCI;pipelineCI.pDynamicState = &dynamicStateCI;pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());pipelineCI.pStages = shaderStages.data();
    

    而tonemap pass不需要绘制模型,所以不需要额外的vertex输入,所以pipelineCI.pVertexInputState传入为emptyInputState。

    此外,表面剔除应该被禁用。pipelineCI.pRasterizationState中的cull mode需要修改为VK_CULL_MODE_NONE。

    绑定好要用到的资源后,在buildCommandBuffers()中将绘制模型的pass中的renderPass和framebuffer改为offscreenPass的renderPass和framebuffer,此时模型被绘制在offscreenPass的frameBuffer中,运行将不再显示。

    renderPassBeginInfo.renderPass = offscreenPass.renderPass; 
    renderPassBeginInfo.framebuffer = offscreenPass.frameBuffer;
    

    之后在buildCommandBuffers()中新增tonemap pass,可以直接参考bloom中的代码。令其framebuffer = frameBuffers[i]。renderPass = renderPass;。最后的绘制命令利用vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0)即可。

  2. shader实现

    tonemap pass的 frag shader已经给出,vert shader可以直接参照bloom中的gaussblur实现。

    下面记录一些我遇到的花时间解决的问题。

    我的shader是hlsl实现,做出来是这个样子,百思不得其解…不清楚是怎么把第一个pass生成的而图像传入第二个pass输入中的。调试分析了下应该是tonemap pass的 frag shader中的Texture2D textureColor读取了绘制模型最后一个material的纹理。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M2o0Alec-1691162057285)(/images/games106homewrok1/Untitled%205.png)]

    后续我将frag shader中space1修改为了space0,就能正确的绘制了。应该是原先的资源都被绑定到了逻辑寄存空间space1中,而新增的资源则是被绑定到默认的space0中。相关比较好的解释参考下面的链接:

    D3D12 RootSignature

    Texture2D textureColor : register(t0, space0);
    SamplerState samplerColor : register(s0, space0);
    

    下面是tonemap前后效果的对比截图。
    请添加图片描述
    请添加图片描述

相关文章:

games106 homework1实现

games106 homework1 gltf介绍图&#xff1a; 骨骼动画 动画相关属性&#xff1a; 对GLTF的理解参照了这篇文章&#xff1a; glTF格式详解(动画) GLTF文件格式详解 buffer和bufferView对象用于引用动画数据。 buffer对象用来指定原始动画数据, bufferView对象用来引用buff…...

Pytorch使用VGG16模型进行预测猫狗二分类

目录 1. VGG16 1.1 VGG16 介绍 1.1.1 VGG16 网络的整体结构 1.2 Pytorch使用VGG16进行猫狗二分类实战 1.2.1 数据集准备 1.2.2 构建VGG网络 1.2.3 训练和评估模型 1. VGG16 1.1 VGG16 介绍 深度学习已经在计算机视觉领域取得了巨大的成功&#xff0c;特别是在图像分类任…...

安装nvm使用nvm管理node切换npm镜像后使用vue ui管理构建项目成功

如果安装nvm前已经单独安装过node.js的请先自行卸载原有node和环境变量里面的配置&#xff1b; 亲测成功&#xff0c;有哪些问题可以在评论区发消息或者私聊我 1、安装nvm的步骤如下 下载nvm安装包 在nvm的GitHub仓库&#xff0c;如下是国内镜像仓库&#xff1a; 点击这里跳…...

在线LaTeX公式编辑器编辑公式

在线LaTeX公式编辑器编辑公式 在编辑LaTex文档时候&#xff0c;需要输入公式&#xff0c;可以使用在线LaTeX公式编辑器编辑公式&#xff0c;其链接为: 在线LaTeX公式编辑器&#xff0c;https://www.latexlive.com/home 图1 在线LaTeX公式编辑器界面 图2 在线LaTeX公式编辑器…...

【C、C++】学习0

C、C学习路线 C语法&#xff1a;变量、条件、循环、字符串、数组、函数、结构体等指针、内存管理推荐书籍&#xff1a;《C Primer Plus》、《C和指针》、《C专家编程》 CC语言基础C的面向对象&#xff08;封装、继承与多态&#xff09;特性泛型模板STL等等推荐书籍&#xff08;…...

python GUI nicegui初识一(登录界面创建)

最近尝试了python的nicegui库&#xff0c;虽然可能也有一些不足&#xff0c;但个人感觉对于想要开发不过对ui设计感到很麻烦的人来说是很友好的了&#xff0c;毕竟nicegui可以利用TailwindCSS和Quasar进行ui开发&#xff0c;并且也支持定制自己的css样式。 这里记录一下自己利…...

【单片机】51单片机串口的收发实验,串口程序

这段代码是使用C语言编写的用于8051单片机的串口通信程序。它实现了以下功能&#xff1a; 引入必要的头文件&#xff0c;包括reg52.h、intrins.h、string.h、stdio.h和stdlib.h。 定义了常量FSOC和BAUD&#xff0c;分别表示系统时钟频率和波特率。 定义了一个发送数据的函数…...

【bug】记录一次使用Swiper插件时loop属性和slidersPerView属性冲突问题

简言 最近在vue3使用swiper时&#xff0c;突然发现loop属性和slides-per-view属性同时存在启用时&#xff0c;loop生效&#xff0c;下一步只能生效一次的bug&#xff0c;上一步却是好的。非常滴奇怪。 解决过程 分析属性是否使用错误。 loop是循环模式&#xff0c;布尔型。 …...

云原生应用里的服务发现

服务定义&#xff1a; 服务定义是声明给定服务如何被消费者/客户端使用的方式。在建立服务之间的同步通信通道之前&#xff0c;它会与消费者共享。 同步通信中的服务定义&#xff1a; 微服务可以将其服务定义发布到服务注册表&#xff08;或由微服务所有者手动发布&#xff09;…...

【零基础学Rust | 基础系列 | 基础语法】变量,数据类型,运算符,控制流

文章目录 简介&#xff1a;一&#xff0c;变量1&#xff0c;变量的定义2&#xff0c;变量的可变性3&#xff0c;变量的隐藏 二、数据类型1&#xff0c;标量类型2&#xff0c;复合类型 三&#xff0c;运算符1&#xff0c;算术运算符2&#xff0c;比较运算符3&#xff0c;逻辑运算…...

运输层---概述

目录 运输层主要内容一.概述和传输层服务1.1 概述1.2 传输服务和协议1.3 传输层 vs. 网络层1.4 Internet传输层协议 二. 多路复用与多路分解&#xff08;解复用&#xff09;2.1 概述2.2 无连接与面向连接的多路分解&#xff08;解复用&#xff09;2.3面向连接的多路复用*2.4 We…...

高速公路巡检无人机,为何成为公路巡检的主流工具

随着无人机技术的不断发展&#xff0c;无人机越来越多地应用于各个领域。其中&#xff0c;在高速公路领域&#xff0c;高速公路巡检无人机已成为公路巡检的得力助手。高速公路巡检无人机之所以能够成为公路巡检中的主流工具&#xff0c;主要是因为其具备以下三大特性。 一、高速…...

仓库管理系统有哪些功能,如何对仓库进行有效管理

阅读本文&#xff0c;您可以了解&#xff1a;1、仓库管理系统有哪些功能&#xff1b;2、如何对仓库进行有效管理。 仓库是制造业的开端&#xff0c;原材料的领料开始。企业的仓库管理是涉及企业生产、企业资金流和企业的经营风险的关键环节。在众多的工业企业、制造型企业、贸…...

Java 比Automic更高效的累加器

1、 java常见的原子类 类 Atomiclnteger、AtomicIntegerArray、AtomicIntegerFieldUpdater、AtomicLongArray、 AtomicLongFieldUpdater、AtomicReference、AtomicReferenceArray 和 AtomicReference- FieldUpdater 常见的原子类使用方法 使用 AtomicReference 来创建一个原…...

antDv table组件滚动截图方法的实现

在开发中经常遇到table内容过多产生滚动的场景&#xff0c;正常情况下不产生滚动进行截图就很好实现&#xff0c;一旦产生滚动就会变得有点棘手。 下面分两种场景阐述解决的方法过程 场景一&#xff1a;右侧不固定列的情况 场景二&#xff1a;右侧固定列的情况 场景一 打开…...

JavaSE【抽象类和接口】(抽象类、接口、实现多个接口、接口的继承)

一、抽象类 在 Java 中&#xff0c;一个类如果被 abstract 修饰称为抽象类&#xff0c;抽象类中被 abstract 修饰的方法称为抽象方法&#xff0c;抽象方法不用 给出具体的实现体。 1.语法 // 抽象类&#xff1a;被 abstract 修饰的类 public abstract class Shape { …...

微信小程序如何跳转H5页面

1、登录微信公众后台&#xff0c;进入【开发->开发管理->业务域名】&#xff0c;点击修改。 2、首先请下载校验文件&#xff0c;并将文件放置在域名根目录下。 我是把文件放在nginx主机的data目录下&#xff0c;然后通过增加nginx.config配置&#xff0c;重启nginx后可…...

C++(20):bit_cast

C++20之前如果想对不同的指针之间做类型转换需要通过reinterpret_cast,对于整数与指针之前的转换也需要通过reinterpret_cast: C++:reinterpret_cast_c++ reparant_cast_风静如云的博客-CSDN博客 但是reinterpret_cast的缺点是不同的编译环境下,无法包装转型的安全一致。 …...

STM32 低功耗-停止模式

STM32 停止模式 文章目录 STM32 停止模式第1章 低功耗模式简介第2章 停止模式简介2.1 进入停止模式2.1 退出停止模式 第3章 停止模式程序部分总结 第1章 低功耗模式简介 在 STM32 的正常工作中&#xff0c;具有四种工作模式&#xff1a;运行、睡眠、停止以及待机模式。 在系统…...

Hutool中 常用的工具类和方法

文章目录 日期时间工具类 DateUtil日期时间对象-DateTime类型转换工具类 Convert字符串工具类 StrUtil数字处理工具类 NumberUtilJavaBean的工具类 BeanUtil集合操作的工具类 CollUtilMap操作工具类 MapUtil数组工具-ArrayUtil唯一ID工具-IdUtilIO工具类-IoUtil加密解密工具类 …...

[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?

&#x1f9e0; 智能合约中的数据是如何在区块链中保持一致的&#xff1f; 为什么所有区块链节点都能得出相同结果&#xff1f;合约调用这么复杂&#xff0c;状态真能保持一致吗&#xff1f;本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里&#xf…...

React hook之useRef

React useRef 详解 useRef 是 React 提供的一个 Hook&#xff0c;用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途&#xff0c;下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

边缘计算医疗风险自查APP开发方案

核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略

本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装&#xff1b;只需暴露 19530&#xff08;gRPC&#xff09;与 9091&#xff08;HTTP/WebUI&#xff09;两个端口&#xff0c;即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

Java 二维码

Java 二维码 **技术&#xff1a;**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...

以光量子为例,详解量子获取方式

光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学&#xff08;silicon photonics&#xff09;的光波导&#xff08;optical waveguide&#xff09;芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中&#xff0c;光既是波又是粒子。光子本…...

在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案

这个问题我看其他博主也写了&#xff0c;要么要会员、要么写的乱七八糟。这里我整理一下&#xff0c;把问题说清楚并且给出代码&#xff0c;拿去用就行&#xff0c;照着葫芦画瓢。 问题 在继承QWebEngineView后&#xff0c;重写mousePressEvent或event函数无法捕获鼠标按下事…...

代码随想录刷题day30

1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币&#xff0c;另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额&#xff0c;返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...

【Go语言基础【12】】指针:声明、取地址、解引用

文章目录 零、概述&#xff1a;指针 vs. 引用&#xff08;类比其他语言&#xff09;一、指针基础概念二、指针声明与初始化三、指针操作符1. &&#xff1a;取地址&#xff08;拿到内存地址&#xff09;2. *&#xff1a;解引用&#xff08;拿到值&#xff09; 四、空指针&am…...

免费数学几何作图web平台

光锐软件免费数学工具&#xff0c;maths,数学制图&#xff0c;数学作图&#xff0c;几何作图&#xff0c;几何&#xff0c;AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...