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 pass和subpass。
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这个程序中的相关绑定,便于自己理解相关绑定。
-
资源绑定
首先需要对原先的数据结构进行修改。修改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)即可。
-
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介绍图: 骨骼动画 动画相关属性: 对GLTF的理解参照了这篇文章: 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 介绍 深度学习已经在计算机视觉领域取得了巨大的成功,特别是在图像分类任…...

安装nvm使用nvm管理node切换npm镜像后使用vue ui管理构建项目成功
如果安装nvm前已经单独安装过node.js的请先自行卸载原有node和环境变量里面的配置; 亲测成功,有哪些问题可以在评论区发消息或者私聊我 1、安装nvm的步骤如下 下载nvm安装包 在nvm的GitHub仓库,如下是国内镜像仓库: 点击这里跳…...

在线LaTeX公式编辑器编辑公式
在线LaTeX公式编辑器编辑公式 在编辑LaTex文档时候,需要输入公式,可以使用在线LaTeX公式编辑器编辑公式,其链接为: 在线LaTeX公式编辑器,https://www.latexlive.com/home 图1 在线LaTeX公式编辑器界面 图2 在线LaTeX公式编辑器…...
【C、C++】学习0
C、C学习路线 C语法:变量、条件、循环、字符串、数组、函数、结构体等指针、内存管理推荐书籍:《C Primer Plus》、《C和指针》、《C专家编程》 CC语言基础C的面向对象(封装、继承与多态)特性泛型模板STL等等推荐书籍(…...

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

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

【bug】记录一次使用Swiper插件时loop属性和slidersPerView属性冲突问题
简言 最近在vue3使用swiper时,突然发现loop属性和slides-per-view属性同时存在启用时,loop生效,下一步只能生效一次的bug,上一步却是好的。非常滴奇怪。 解决过程 分析属性是否使用错误。 loop是循环模式,布尔型。 …...

云原生应用里的服务发现
服务定义: 服务定义是声明给定服务如何被消费者/客户端使用的方式。在建立服务之间的同步通信通道之前,它会与消费者共享。 同步通信中的服务定义: 微服务可以将其服务定义发布到服务注册表(或由微服务所有者手动发布)…...

【零基础学Rust | 基础系列 | 基础语法】变量,数据类型,运算符,控制流
文章目录 简介:一,变量1,变量的定义2,变量的可变性3,变量的隐藏 二、数据类型1,标量类型2,复合类型 三,运算符1,算术运算符2,比较运算符3,逻辑运算…...

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

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

仓库管理系统有哪些功能,如何对仓库进行有效管理
阅读本文,您可以了解:1、仓库管理系统有哪些功能;2、如何对仓库进行有效管理。 仓库是制造业的开端,原材料的领料开始。企业的仓库管理是涉及企业生产、企业资金流和企业的经营风险的关键环节。在众多的工业企业、制造型企业、贸…...
Java 比Automic更高效的累加器
1、 java常见的原子类 类 Atomiclnteger、AtomicIntegerArray、AtomicIntegerFieldUpdater、AtomicLongArray、 AtomicLongFieldUpdater、AtomicReference、AtomicReferenceArray 和 AtomicReference- FieldUpdater 常见的原子类使用方法 使用 AtomicReference 来创建一个原…...

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

JavaSE【抽象类和接口】(抽象类、接口、实现多个接口、接口的继承)
一、抽象类 在 Java 中,一个类如果被 abstract 修饰称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法,抽象方法不用 给出具体的实现体。 1.语法 // 抽象类:被 abstract 修饰的类 public abstract class Shape { …...

微信小程序如何跳转H5页面
1、登录微信公众后台,进入【开发->开发管理->业务域名】,点击修改。 2、首先请下载校验文件,并将文件放置在域名根目录下。 我是把文件放在nginx主机的data目录下,然后通过增加nginx.config配置,重启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 的正常工作中,具有四种工作模式:运行、睡眠、停止以及待机模式。 在系统…...
Hutool中 常用的工具类和方法
文章目录 日期时间工具类 DateUtil日期时间对象-DateTime类型转换工具类 Convert字符串工具类 StrUtil数字处理工具类 NumberUtilJavaBean的工具类 BeanUtil集合操作的工具类 CollUtilMap操作工具类 MapUtil数组工具-ArrayUtil唯一ID工具-IdUtilIO工具类-IoUtil加密解密工具类 …...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 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 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...

以光量子为例,详解量子获取方式
光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学(silicon photonics)的光波导(optical waveguide)芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中,光既是波又是粒子。光子本…...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...
代码随想录刷题day30
1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...

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