Metal 学习笔记四:顶点函数

到目前为止,您已经完成了 3D 模型和图形管道。现在,是时候看看 Metal 中两个可编程阶段中的第一个阶段,即顶点阶段,更具体地说,是顶点函数。

着色器函数

定义着色器函数时,可以为其指定一个属性。您将在本书中学到这些属性:
• vertex:顶点函数:计算顶点的位置。
• fragment: 片段函数: 计算片段的颜色。
• kernel:内核功能:用于通用的并行计算,例如图像处理。

在本章中,您将只关注 vertex 函数。在第 7 章 “片段函数”中,您将探索如何控制每个片段的颜色。在第 16 章 “GPU 计算编程” 中,您将了解如何使用具有多个线程的并行编程来写入缓冲区和纹理。

到目前为止,您应该已经熟悉顶点描述符,以及如何使用它们来描述如何从加载的 3D 模型中排列顶点属性。回顾一下:

• MDLVertexDescriptor:使用Model I/O 顶点描述符读取 USD 文件。Model I/O 会创建缓存区,缓存区会按我们所需的布局,存放属性值,例如位置、法线和纹理坐标。

• MTLVertexDescriptor:在创建管线状态时使用 Metal 顶点描述符。GPU 顶点函数使用 [[stage_in]] 属性将传入数据与管线状态中的顶点描述符进行匹配。

在学习本章时,您将在不使用顶点描述符情况下,构建自己的顶点网格并将顶点发送到 GPU。您将学习如何在顶点函数中控制这些顶点,然后升级到使用顶点描述符。在此过程中,您将看到如何使用 Model I/O 导入网格,从而为您完成许多繁重的工作。

开始项目

➤ 打开本章的初始项目。
此 SwiftUI 项目包含一个简化的 Renderer,以便您可以添加自己的网格,并且着色器函数是缺失的,因此您可以构建它们。您尚未进行任何绘图,因此在运行应用程序时看不到任何内容。

渲染一个四边形

您可以使用两个三角形创建一个四边形。每个三角形有 3 个顶点,总共有 6 个顶点。

➤ 创建一个名为 Quad.swift 的新 Swift 文件。

➤ 将现有代码替换为:

import MetalKit
struct Vertex {var x: Floatvar y: Floatvar z: Float
}
struct Quad {var vertices: [Vertex] = [Vertex(x: -1, y:  1, z: 0),Vertex(x:  1, y: -1, z: 0),Vertex(x: -1, y: -1, z: 0),Vertex(x: -1, y:  1, z: 0),Vertex(x:  1, y:  1, z: 0),Vertex(x:  1, y: -1, z: 0)
] }
// triangle 1
// triangle 2

您可以创建一个结构体来组成一个具有 x、y 和 z 值的顶点。在这里,顶点的环绕顺序 (顶点顺序) 是顺时针方向的,这很重要。

➤ 向 Quad 添加新的顶点缓存区property并初始化它:

let vertexBuffer: MTLBuffer
init(device: MTLDevice, scale: Float = 1) {vertices = vertices.map {Vertex(x: $0.x * scale, y: $0.y * scale, z: $0.z * scale)}guard let vertexBuffer = device.makeBuffer(bytes: &vertices,length: MemoryLayout<Vertex>.stride * vertices.count,options: []) else {fatalError("Unable to create quad vertex buffer")
}self.vertexBuffer = vertexBuffer
}

 使用此代码,您可以使用顶点数组初始化 Metal 缓存区。将每个顶点乘以 scale,这样就可以在初始化期间设置四边形的大小。

➤ 打开 Renderer.swift,并为 quad 网格添加一个新property:

 lazy var quad: Quad = {Quad(device: Self.device, scale: 0.8)
}()

在这里,您将使用 Renderer 的设备初始化 quad。您必须延迟初始化 quad,因为在运行 init(metalView:) 之前,device 不会初始化。您还可以调整四边形的大小,以便可以清楚地看到它。

注意:如果您将比例保持在默认的1.0下,四边形将覆盖整个屏幕。覆盖屏幕对于全屏绘图很有用,因为您只能在渲染几何图形的区域绘制片段。

在 draw(in:) 中,在 // do drawing here 之后,添加:

renderEncoder.setVertexBuffer(quad.vertexBuffer,offset: 0,index: 0)

您在渲染命令编码器上创建一个命令,将顶点缓冲区在缓冲区参数表中的索引设置为 0。

➤ 添加 draw 调用:

renderEncoder.drawPrimitives(type: .triangle,vertexStart: 0,vertexCount: quad.vertices.count)

 在这里,您将绘制四边形的六个顶点。

➤ 打开 Shaders.metal。
➤ 将 vertex 函数替换为:

vertex float4 vertex_main(constant float3 *vertices [[buffer(0)]],uint vertexID [[vertex_id]])
{float4 position = float4(vertices[vertexID], 1);return position;
}

此代码存在错误,您将很快观察并修复该错误。

GPU 为每个顶点执行顶点函数。在绘制调用中,您指定了有6个顶点。因此,顶点函数将执行六次。

将指针传递到 vertex 函数时,必须指定地址空间,constant 或 device。constant 经过优化,可在多个顶点函数上并行访问同一变量。device 最适合通过并行函数访问缓冲区的不同部分,例如使用交错顶点和颜色数据的缓冲区时。

[[vertex_id]] 是一个属性限定符,它为您提供当前顶点。您可以将其用作访问vertices 持有数组的入口。

您可能会注意到,您正在向 GPU 发送一个缓冲区,其中填充了一个 Vertexs 数组,该数组由 3 个 Float 组成。在顶点函数中,您读取的缓冲区与 float3 数组相同,从而导致显示错误。

尽管您可能会获得不同的渲染,但顶点位于错误的位置,因为 float3 类型比具有三个 Float 类型成员的 Vertex 占用更多的内存。Float 长 4 字节,Vertex 长 12 字节。SIMD float3 类型是带填充的,占用与 float4 类型相同的内存,即 16 字节。将此参数更改为 packed_float3 将修复错误,因为 packed_float3占用 12 个字节。

注意:您可以在https://apple.co/2UT993x查看Metal着色语言规范中类型的大小。

在 vertex 函数中,将第一个参数中的 float3 改为 packed_float3。

编译并运行。

四边形现在显示正确了。或者,您可以将 Float 数组顶点定义为 simd_float3 数组。在这种情况下,您将在顶点函数中使用 float3,因为这两种类型都需要 16 个字节。但是,每个顶点发送 16 个字节的效率略低于每个顶点发送 12 个字节的效率。

计算位置

Metal不但支持绚丽的色彩,也支持快速平滑的动画。在下一步,我们会让我们的四边形上下移动。为了做到这个,我们需要一个计时器,每帧都更新四边形的位置。顶点shader函数就是我们更新顶点位置的地方,我们会发送计时器数据到GPU。

在Renderer的头部,添加如下属性:

var timer: Float = 0

然后在draw(in:), 在这一行前面

renderEncoder.setRenderPipelineState(pipelineState)

添加如下代码:

// 1
timer += 0.05
var currentTime = sin(timer)
// 2
renderEncoder.setVertexBytes(&currentTime,length: MemoryLayout<Float>.stride,index: 11)

1,每帧都更新计时器,如果你希望你的四边形上下移动,你需要使用一个在-1和1之间的值,使用sin()函数是一个很好的限制值在-1到1之间的方法。你可以通过更改每帧中给timer增加的值,来更改动画的速度。

2,如果你发送少量的数据(小于4kb)给GPU,setVertexBytes(_:length:index:)是一个创建MTLBuffer的较好选择。这里你将currentTime设置给缓存参数表中索引为11的缓存。为顶点属性(例如顶点位置)保留缓冲区 1 到 10 有助于记住哪些缓冲区保存哪些数据。

在Shader.metal,把vertex_main函数改成这样:

vertex float4 vertex_main(constant packed_float3 *vertices [[buffer(0)]],constant float &timer [[buffer(11)]],uint vertexID [[vertex_id]])
{float4 position = float4(vertices[vertexID], 1);position.y += timer;return position;
}

您在缓冲区 11 中以浮点数的形式接收单值timer。您将 timer 值添加到 y 位置,并从函数返回新位置。

在下一章中,您将开始学习如何使用矩阵乘法将顶点投影到 3D 空间中。但是,您并不总是需要矩阵乘法来移动顶点;在这里,您可以使用简单的加法来实现沿着Y 轴平移。

➤ 构建并运行应用程序,您将看到一个可爱的动画四边形。

 

更高效的渲染

目前,您正在使用 6 个顶点来渲染两个三角形。

在这些顶点中,0 和 3 位于同一位置,1 和 5 也是如此。如果您渲染具有数千个甚至数百万个顶点的网格,则尽可能减少重复是非常重要的。您可以使用索引渲染来实现。

仅为不同的顶点位置创建结构体,然后使用 indices 获取顶点的正确位置。

➤ 打开 Quad.swift,并将顶点重命名为 oldVertices。

➤ 将以下结构添加到 Quad:

var vertices: [Vertex] = [Vertex(x: -1, y:  1, z: 0),Vertex(x:  1, y:  1, z: 0),Vertex(x: -1, y: -1, z: 0),Vertex(x:  1, y: -1, z: 0)
]
var indices: [UInt16] = [0, 3, 2,
0, 1, 3 ]

vertices 现在以任意顺序保存四边形的唯一四个点。indices 以正确的顶点顺序保存每个顶点的索引。请参阅 oldVertices 以确保您的索引正确无误。 

➤ 添加新的 Metal 缓冲区来保存索引:

let indexBuffer: MTLBuffer

在 init(device:scale:) 的末尾,添加:

guard let indexBuffer = device.makeBuffer(bytes: &indices,length: MemoryLayout<UInt16>.stride * indices.count,options: []) else {fatalError("Unable to create quad index buffer")
}
self.indexBuffer = indexBuffer

 创建索引缓冲区的方式与创建顶点缓冲区的方式相同。

➤ 打开 Renderer.swift,在 draw(in:) 中,在 draw 调用之前,添加:

renderEncoder.setVertexBuffer(quad.indexBuffer,offset: 0,index: 1)

在这里,您将索引缓冲区发送到 GPU。

➤ 将 draw 调用更改为: 

renderEncoder.drawPrimitives(type: .triangle,vertexStart: 0,vertexCount: quad.indices.count)

使用索引计数来表示要渲染的顶点数;而不是顶点计数。

➤ 打开 Shaders.metal,并将顶点函数更改为: 

vertex float4 vertex_main(constant packed_float3 *vertices [[buffer(0)]],constant ushort *indices [[buffer(1)]],constant float &timer [[buffer(11)]],uint vertexID [[vertex_id]])
{ushort index = indices[vertexID];float4 position = float4(vertices[index], 1);return position;
}

此处,vertexID 是缓存区中的索引,该缓存区保存了四边形的索引。使用索引缓存区中的值,在顶点缓存区中正确索引顶点。 

➤ 构建并运行。

当然,你的四边形的位置与以前相同,但现在你向 GPU 发送的数据更少。

从数组中的条目数量来看,您实际上似乎在发送更多数据 — 但事实并非如此!oldVertices 的内存占用为 72 字节,而 vertices + indices 的内存占用为 60 字节。

顶点描述器

使用索引渲染顶点时,可以使用更高效的绘制调用。但是,您首先需要在管道中设置顶点描述符。
使用顶点描述符始终是一个好主意,因为大多数情况下,您不仅会向 GPU 发送位置。您还将发送法线、纹理坐标和颜色等属性。当您可以布置自己的顶点数据时,您可以更好地控制引擎处理模型网格的方式。

➤ 创建一个名为 VertexDescriptor.swift 的新 Swift 文件。

➤ 将代码替换为:

import MetalKit
extension MTLVertexDescriptor {static var defaultLayout: MTLVertexDescriptor {let vertexDescriptor = MTLVertexDescriptor()vertexDescriptor.attributes[0].format = .float3vertexDescriptor.attributes[0].offset = 0vertexDescriptor.attributes[0].bufferIndex = 0let stride = MemoryLayout<Vertex>.stridevertexDescriptor.layouts[0].stride = stridereturn vertexDescriptor} 
}

在这里,您将设置一个只有一个属性的顶点布局。此属性描述每个顶点的位置。

顶点描述符包含属性和缓存区布局的数组。

• attributes:对于每个属性,指定从缓冲区开头开始的第一项的类型格式和偏移量(以字节为单位)。您还可以指定保存该属性的缓冲区的索引。
• buffer layout:指定每个缓冲区中组合的所有属性的步幅长度。这里可能会让人感到困惑,因为你正在使用索引 0 来索引布局和属性,但布局索引 0 对应于属性使用到的 bufferIndex 0。 

注意: stride 描述了每个实例之间的字节数。由于内部填充和字节对齐,此值可能与 size 不同。有关大小、步幅和对齐的精彩解释,请查看 Greg Heo 的文章,网址为https://bit.ly/2V3gBJl.

对于 GPU,vertexBuffer 现在如下所示:

➤ 打开 Renderer.swift,并在 init(metalView:) 中找到创建管道状态的位置。

➤ 在 do {} 中创建管道状态之前,将以下代码添加到管道状态描述符中:

 pipelineDescriptor.vertexDescriptor =MTLVertexDescriptor.defaultLayout

GPU 现在期望顶点按描述符描述的格式存放。

➤ 在 draw(in:) 中,删除: 

renderEncoder.setVertexBuffer(quad.indexBuffer,offset: 0,index: 1)

您将在 draw 调用中包含索引缓冲区。

➤ 将 draw 调用更改为:

renderEncoder.drawIndexedPrimitives(type: .triangle,indexCount: quad.indices.count,indexType: .uint16,indexBuffer: quad.indexBuffer,indexBufferOffset: 0)

此绘图调用期望索引缓冲区使用 UInt16,这就是你在 Quad 中描述 indices 数组的方式。你没有显式地将 quad.indexBuffer 发送到 GPU,因为这个 draw 调用会为你做这件事。
➤ 打开 Shaders.metal。
➤ 将 vertex 函数替换为:

vertex float4 vertex_main(float4 position [[attribute(0)]] [[stage_in]],constant float &timer [[buffer(11)]])
{return position;
}

 你为 Swift 端的布局做了所有繁重的工作,所以顶点函数的大小大大减小了。

您可以使用 [[stage_in]] 属性描述每个逐顶点的输入。GPU 现在查看管道状态的顶点描述符。
[[attribute(0)]] 是顶点描述符中描述位置的属性。即使您将原始顶点数据定义为包含三个浮点数的顶点类型,也可以在此处将位置定义为 float4。GPU 可以进行转换。
值得注意的是,当 GPU 将 w 信息添加到 xyz 位置时,它会添加为1.0。正如您将在以下章节中看到的那样,这个 w 值在栅格化过程中非常重要。
GPU 现在拥有计算每个顶点位置所需的所有信息。
➤ 构建并运行应用程序以确保一切仍然有效。生成的渲染将与以前相同。

 

添加另一个顶点属性

您可能永远不会只有一个属性,因此让我们为每个顶点添加一个 color 属性。
您可以选择是使用两个缓冲区还是在每个顶点位置之间交错颜色。如果您选择交错,您将设置一个结构来保存位置和颜色。但是,在此示例中,添加新的颜色缓冲区以匹配每个顶点会更容易。

➤ 打开 Quad.swift,并添加新数组:

var colors: [simd_float3] = [[1, 0, 0], // red[0, 1, 0], // green[0, 0, 1], // blue[1, 1, 0]  // yellow
]

现在,您有四种 RGB 颜色来匹配这四个顶点。

➤ 创建一个新的缓冲区属性: 

let colorBuffer: MTLBuffer

➤ 在 init(device:scale:) 的末尾添加: 

guard let colorBuffer = device.makeBuffer(bytes: &colors,length: MemoryLayout<simd_float3>.stride * colors.count,options: []) else {fatalError("Unable to create quad color buffer")}
self.colorBuffer = colorBuffer

初始化 colorBuffer 的方式与前两个缓冲区相同。
➤ 打开 Renderer.swift,然后在 draw(in:) 中,在 draw 调用之前添加: 

renderEncoder.setVertexBuffer(quad.colorBuffer,offset: 0,index: 1)

使用缓冲区索引 1 将颜色缓冲区发送到 GPU,该索引必须与顶点描述符布局中的索引匹配。
➤ 打开 VertexDescriptor.swift,并在返回之前将以下代码添加到 defaultLayout:

vertexDescriptor.attributes[1].format = .float3
vertexDescriptor.attributes[1].offset = 0
vertexDescriptor.attributes[1].bufferIndex = 1
vertexDescriptor.layouts[1].stride =MemoryLayout<simd_float3>.stride

在这里,您将描述缓冲区索引 1 中颜色缓冲区的布局。

 ➤ 打开 Shaders.metal。
➤ 您只能在一个参数上使用 [[stage_in]],因此请创建一个新结构体
在 Vertex 函数之前:

struct VertexIn {float4 position [[attribute(0)]];float4 color [[attribute(1)]];
};

 ➤ 将 vertex 函数更改为:

vertex float4 vertex_main(VertexIn in [[stage_in]],constant float &timer [[buffer(11)]])
{return in.position;
}

这段代码仍然简短明了。GPU 知道如何从缓冲区中检索位置和颜色,因为结构中的 [[attribute(n)]] 限定符查看管道状态的顶点描述符。
➤ 构建并运行以确保您的蓝色象限仍然渲染。

fragment 函数确定每个渲染片段的颜色。您需要将顶点的颜色传递给 fragment 函数。您将在第 7 章 “片段函数” 中了解有关 fragment 函数的更多信息。
➤ 仍在 Shaders.metal 中,在 vertex 函数之前添加以下结构: 

 struct VertexOut {float4 position [[position]];float4 color;
};

现在,您不仅可以从 vertex 函数返回 position,还可以返回 position 和 color。您可以指定 position 属性,让 GPU 知道此结构中的哪个属性是 position。
➤ 将 vertex 函数替换为: 

vertex VertexOut vertex_main(VertexIn in [[stage_in]],constant float &timer [[buffer(11)]]) {VertexOut out {.position = in.position,.color = in.color};
return out; }

 现在,您返回 VertexOut 而不是 float4。

➤ 将片段功能改为:

 fragment float4 fragment_main(VertexOut in [[stage_in]]) {return in.color;
}

[[stage_in]] 属性指示 GPU 应从顶点函数获取 VertexOut 输出,并将其与栅格化片段匹配。在这里,您将返回顶点颜色。请记住第 3 章 “渲染管道” 中,每个片段的输入都会进行插值。

➤ 构建并运行应用程序,您将看到以美丽的颜色渲染的四边形。 

 

渲染成点状

您可以渲染点和线,而不是渲染三角形。

➤ 打开 Renderer.swift,然后在 draw(in:) 中更改

 renderEncoder.drawIndexedPrimitives(type: .triangle,

为:

 renderEncoder.drawIndexedPrimitives(type: .point,

如果您现在构建并运行,GPU 将渲染点,但它不知道要使用什么大小的点,因此它会在各种点大小上闪烁。要解决此问题,您还将在从 vertex 函数返回数据时,返回点尺寸。
➤ 打开 Shaders.metal,并将此属性添加到 VertexOut: 

float pointSize [[point_size]];

[[point_size]] 属性将告诉 GPU 要使用什么尺寸的点。

➤ 在 vertex_main 中,将 out 的初始化替换为: 

VertexOut out {.position = in.position,.color = in.color,.pointSize = 30
};

 在这里,您将分配点尺寸为30。
➤ 构建并运行以查看使用顶点颜色渲染的点:

 

 

挑战

到目前为止,您已将顶点位置发送到数组缓冲区中的 GPU。但这并不完全必要。GPU 需要知道的是要绘制多少个顶点。您的挑战是删除顶点和索引缓冲区,并在一个圆圈中绘制 50 个点。以下是您需要采取的步骤的概述,以及一些帮助您入门的代码:
1. 在 Renderer 中,从管道中删除顶点描述符。
2. 替换 Renderer 中的 draw 调用,使其不使用索引,但绘制 50个顶点。
3. 在 draw(in:) 中,删除所有 setVertexBuffer 命令。
4. GPU 需要知道总点数,因此请以与缓冲区 0 中的 timer 相同的方式发送此值。
5. 将 vertex 函数替换为:

vertex VertexOut vertex_main(constant uint &count [[buffer(0)]],constant float &timer [[buffer(11)]],uint vertexID [[vertex_id]])
{float radius = 0.8;float pi = 3.14159;float current = float(vertexID) / float(count);float2 position;position.x = radius * cos(2 * pi * current);position.y = radius * sin(2 * pi * current);VertexOut out {.position = float4(position, 0, 1),.color = float4(1, 0, 0, 1),.pointSize = 20
};
return out; }

请记住,这是一个练习,可帮助您了解如何在 GPU 上定位点,而无需在 Swift 端保存任何等效数据。所以,不要太担心数学。您可以使用当前顶点 ID 的正弦和余弦来绘制圆周围的点。
请注意,GPU 上没有 pi 的内置值。您将看到 50 个点被绘制成一个圆圈。


尝试通过将 timer 添加到 current 来为点添加动画。
如果你有任何困难,你可以在本章的项目挑战目录中找到解决方案。 

参考

https://zhuanlan.zhihu.com/p/385638027

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

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

相关文章

什么是零拷贝?

零拷贝是一种优化技术&#xff0c;用于减少数据在计算机系统中的拷贝次数&#xff0c;从而提高性能和效率。在传统的数据传输中&#xff0c;数据通常会在多个缓冲区之间进行多次拷贝&#xff0c;而零拷贝技术通过减少这些不必要的拷贝操作&#xff0c;显著降低了CPU和内存的开销…

【LeetCode20】有效的括号

题目描述 给定一个只包括 ‘(’&#xff0c;‘)’&#xff0c;‘{’&#xff0c;‘}’&#xff0c;‘[’&#xff0c;‘]’ 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 每…

算法训练(leetcode)二刷第三十七天 | *300. 最长递增子序列、674. 最长连续递增序列、*718. 最长重复子数组

刷题记录 *300. 最长递增子序列674. 最长连续递增序列基础解法&#xff08;非动规&#xff09;动态规划 718. 最长重复子数组滚动数组 *300. 最长递增子序列 leetcode题目地址 dp数组含义&#xff1a; dp[i]表示以nums[i]结尾的最长递增子序列长度&#xff0c;即以nums[i]结尾…

Elasticsearch 相关面试题

1. Elasticsearch基础 Elasticsearch是什么&#xff1f; Elasticsearch是一个分布式搜索引擎&#xff0c;基于Lucene实现。 Mapping是什么&#xff1f;ES中有哪些数据类型&#xff1f; Mapping&#xff1a;定义字段的类型和属性。 数据类型&#xff1a;text、keyword、integer、…

TCP/IP的分层结构、各层的典型协议,以及与ISO七层模型的差别

1. TCP/IP的分层结构 TCP/IP模型是一个四层模型&#xff0c;主要用于网络通信的设计和实现。它的分层结构如下&#xff1a; (1) 应用层&#xff08;Application Layer&#xff09; 功能&#xff1a;提供应用程序之间的通信服务&#xff0c;处理特定的应用细节。 典型协议&am…

pycharm技巧--鼠标滚轮放大或缩小 Pycharm 字体大小

1、鼠标滚轮调整字体 设置 Ctrl 鼠标滚轮调整字体大小 备注&#xff1a; 第一个是活动窗口&#xff0c;即缩放当前窗口 第二个是所有编辑器窗口&#xff0c;即缩放所有窗口的字体 2、插件 汉化包&#xff1a; Chinese Simplified 包

硬件工程师入门教程

1.欧姆定律 测电压并联使用万用表测电流串联使用万用表&#xff0c;红入黑出 2.电阻的阻值识别 直插电阻 贴片电阻 3.电阻的功率 4.电阻的限流作用 限流电阻阻值的计算 单位换算关系 5.电阻的分流功能 6.电阻的分压功能 7.电容 电容简单来说是两块不连通的导体加上中间的绝…

edge浏览器将书签栏顶部显示

追求效果&#xff0c;感觉有点丑&#xff0c;但总归方便多了 操作路径&#xff1a;设置-外观-显示收藏夹栏-始终

【SPIE出版,见刊快速,EI检索稳定,浙江水利水电学院主办】2025年物理学与量子计算国际学术会议(ICPQC 2025)

2025年物理学与量子计算国际学术会议&#xff08;ICPQC 2025&#xff09;将于2025年4月18-20日在中国杭州举行。本次会议旨在汇聚全球的研究人员、学者和业界专家&#xff0c;共同探讨物理学与量子计算领域的最新进展与前沿挑战。随着量子技术的快速发展&#xff0c;其在信息处…

谷歌浏览器更新后导致的刷新数据无法显示

这几天突然出现的问题&#xff0c;就是我做了一个网站&#xff0c;一直用Google展示&#xff0c;前两天突然就是刷新会丢失数据&#xff0c;然后再刷新几次吧又有了&#xff0c;之前一直好好的&#xff0c;后端也做了一些配置添加了CrossOrigin注解&#xff0c;然而换了edge浏览…

UE5从入门到精通之多人游戏编程常用函数

文章目录 前言一、权限与身份判断函数1. 服务器/客户端判断2. 网络角色判断二、网络同步与复制函数1. 变量同步2. RPC调用三、连接与会话管理函数1. 玩家连接控制2. 网络模式判断四、实用工具函数前言 UE5给我们提供了非常强大的多人网路系统,让我们可以很方便的开发多人游戏…

软件需求管理办法,软件开发管理指南(Word原件)

1. 目的 2. 适用范围 3. 参考文件 4. 术语和缩写 5. 需求获取的方式 5.1. 与用户交谈向用户提问题 5.1.1. 访谈重点注意事项 5.1.2. 访谈指南 5.2. 参观用户的工作流程 5.3. 向用户群体发调查问卷 5.4. 已有软件系统调研 5.5. 资料收集 5.6. 原型系统调研 5.6.1. …

利用python和gpt写一个conda环境可视化管理工具

最近在学习python&#xff0c;由于不同的版本之间的差距较大&#xff0c;如果是用环境变量来配置python的话&#xff0c;会需要来回改&#xff0c;于是请教得知可以用conda来管理&#xff0c;但是conda在管理的时候老是要输入命令&#xff0c;感觉也很烦&#xff0c;于是让gpt帮…

【复习】计算机网络

网络模型 OSI 应用层&#xff1a;给应用程序提供统一的接口表示层&#xff1a;把数据转换成兼容另一个系统能识别的格式会话层&#xff1a;负责建立、管理、终止表示层实体之间的通信会话传输层&#xff1a;负责端到端的数据传输网络层&#xff1a;负责数据的路由、转发、分片…

图书馆系统源码详解

本项目是一个基于Scala语言开发的图书馆管理系统。系统主要由以下几个部分组成&#xff1a;数据访问层&#xff08;DAO&#xff09;、数据模型层&#xff08;Models&#xff09;、服务层&#xff08;Service&#xff09;以及用户界面层&#xff08;UI&#xff09;。以下是对项目…

Redis——用户签到BitMap,UV统计

目录 BitMap 使用场景 1. 用户签到系统 2. 用户行为标记 3. 布隆过滤器&#xff08;Bloom Filter&#xff09; BitMap介绍 Redis中的使用 Redis功能示例 添加&#xff1a; 获取&#xff1a; 批量获取&#xff1a; java中实现 统计本月连续签到次数 UV统计 UV 统计…

【数据库】【MySQL】索引

MySQL中索引的概念 索引&#xff08;MySQL中也叫做"键&#xff08;key&#xff09;"&#xff09;是一种数据结构&#xff0c;用于存储引擎快速定找到记录。 简单来说&#xff0c;它类似于书籍的目录&#xff0c;通过索引可以快速找到对应的数据行&#xff0c;而无需…

【SpringBoot AI 集成DeepSeek 大模型API调用】

当DeepSeek开始盛行&#xff0c;提供强大的大语言模型&#xff0c;界面调用不能满足我们的需要&#xff0c;同时提供API接口供我们在服务中调用&#xff0c;来实现各种AI场景。 我们通过将DeepSeek的AI能力与SpringBoot AI相结合&#xff0c;实现智能聊天、问答机器人&#xf…

Linux 性能更好的ftp客户端 lftp 使用详解

简介 LFTP 是一个命令行 FTP 客户端&#xff0c;支持多种文件传输协议&#xff0c;包括 FTP、FTPS、HTTP、HTTPS和SFTP 。它以其通过镜像、后台操作和脚本支持等特性有效管理复杂传输的能力而闻名。 安装 Ubuntu/Debian sudo apt update sudo apt install lftpCentOS/RHEL/…

汽车智能制造企业数字化转型SAP解决方案总结

一、项目实施概述 项目阶段划分&#xff1a; 蓝图设计阶段主数据管理方案各模块蓝图设计方案下一阶段工作计划 关键里程碑&#xff1a; 2022年6月6日&#xff1a;项目启动会2022年12月1日&#xff1a;系统上线 二、总体目标 通过SAP实施&#xff0c;构建研产供销协同、业财一…