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(¤tTime,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 值添加到position.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和一个缓冲区布局数组layouts。
• attributes:对于每个属性,你需要指定类型格式,以及第一个(属性值)距离缓冲区开始位置的偏移量(以字节为单位)。您还可以指定存储该属性的缓冲区的索引。
• buffer layout:你需要指定每个缓冲区中所有属性组合的步幅长度。这里可能会让人感到困惑,因为你正在使用下标0 来索引layouts和attributes数组,但layouts下标0 对应于attributes数组使用到的 bufferIndex 0(也就是索引为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 数组的方式。你没有显式(例如调用setVertexBuffer方法)地将 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,该索引必须与顶点描述符layouts数组中的下标匹配。
➤ 打开 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 端保存任何等效数据。所以,不要太担心数学。您可以使用当前的vertexID的正弦和余弦来绘制圆周围的点。
请注意,GPU 上没有 pi 的内置值。
您将看到 50 个点被绘制成一个圆圈。
尝试通过将 timer 添加到 current 来为点添加动画。
如果你有任何困难,你可以在本章的项目挑战目录中找到解决方案。
参考
https://zhuanlan.zhihu.com/p/385638027
相关文章:
Metal 学习笔记四:顶点函数
到目前为止,您已经完成了 3D 模型和图形管道。现在,是时候看看 Metal 中两个可编程阶段中的第一个阶段,即顶点阶段,更具体地说,是顶点函数。 着色器函数 定义着色器函数时,可以为其指定一个属性。您将在本…...
C# string转unicode字符
在 C# 中,将字符串转换为 Unicode 字符(即每个字符的 Unicode 码点)可以通过遍历字符串中的每个字符并获取其 Unicode 值来实现。Unicode 值是一个整数,表示字符在 Unicode 标准中的唯一编号。 以下是实现方法: 1. 获…...
HITCON2017SSRFME-学习复盘
代码审计 192.168.122.15 <?phpif (isset($_SERVER[HTTP_X_FORWARDED_FOR])) {$http_x_headers explode(,, $_SERVER[HTTP_X_FORWARDED_FOR]);//用逗号分割多个IP$_SERVER[REMOTE_ADDR] $http_x_headers[0];}echo $_SERVER["REMOTE_ADDR"];//给第一个IP发送请…...
【Http和Https区别】
概念: 一、Http协议 HTTP(超文本传输协议)是一种用于传输超媒体文档(如HTML)的应用层协议,主要用于Web浏览器和服务器之间的通信。http也是客户端和服务器之间请求与响应的标准协议,客户端通常…...
2025数学建模竞赛汇总,错过再等一年
01、2025第十届数维杯大学生数学建模挑战赛(小国赛) 竞赛介绍:数学建模行业内仅次于国赛和美赛的的第三赛事,被多所高校认定为国家级二类竞赛。赛题类型是国内唯一和高教社杯国赛题型风格完全一致的全国性数学建模竞赛࿰…...
基于SSM的《计算机网络》题库管理系统(源码+lw+部署文档+讲解),源码可白嫖!
摘 要 《计算机网络》题库管理系统是一种新颖的考试管理模式,因为系统是用Java技术进行开发。系统分为三个用户进行登录并操作,分别是管理员、教师和学生。教师在系统后台新增试题和试卷,学生进行在线考试,还能对考生记录、错题…...
ReentrantLock 用法与源码剖析笔记
📒 ReentrantLock 用法与源码剖析笔记 🚀 一、ReentrantLock 核心特性 🔄 可重入性:同一线程可重复获取锁(最大递归次数为 Integer.MAX_VALUE)🔧 公平性:支持公平锁(按等…...
矩阵的 正定(Positive Definite)与负定(Negative Definite):从Fisher信息矩阵看“曲率”的秘密
矩阵的正定与负定:从Fisher信息矩阵看“曲率”的秘密 在数学和统计学中,矩阵的“正定性”和“负定性”是一对重要概念,尤其在优化、统计推断和机器学习中频繁出现。比如,Fisher信息矩阵(Fisher Information Matrix, F…...
被裁20240927 --- WSL-Ubuntu20.04安装cuda、cuDNN、tensorRT
cuda、cuDNN、tensorRT的使用场景 1. CUDA(Compute Unified Device Architecture) 作用: GPU 通用计算:CUDA 是 NVIDIA 的并行计算平台和编程模型,允许开发者直接利用 GPU 的并行计算能力,加速通用计算任…...
uniapp写的h5跳转小程序
使用场景: 我们对接第三方支付的时候,对方只提供了原生小程序id和appid,由我们的app和h5平台跳转至小程序。 遇到的问题: app跳转本地正常,线上报错如下 解决办法: 需要去微信开放平台申请应用appid 易…...
[SWPUCTF 2022 新生赛]ez_rce
打开题目就在线环境,发现只有一句话:真的什么都没有吗 F12查看控制台和源代码也没发现任何信息,然后用虚拟机里面的dirsearch扫一下这个网站就能得到: 然后这里扫出来的结果查看的直接就是robots.txt,然后就能看到: …...
递归、搜索与回溯算法 —— 名词解析
目录 一、递归 1、什么是递归? 2、递归的数学类比 3、为什么要用到递归? 问题具有递归结构: 代码简洁易懂: 解决复杂问题: 处理嵌套结构: 4、如何理解递归? 明确基准条件: …...
【docker】docker swarm lock和unlock的区别,以及旧节点重启的隐患
docker swarm lock/unlock 的作用 Docker Swarm 提供了**加密集群状态(Encrypted Raft logs)**的功能,可以防止 Swarm 集群的管理数据(如任务分配、集群配置等)在磁盘上被未授权访问。 docker swarm lock:…...
Grafana使用日志5--如何重置Grafana密码
背景 有时候当账号太多的时候,根本记不住所有的账号密码,这时候就很容易登录失败,这时候怎么办呢? 接下来就让我来给大家演示一下Grafana的账号如果忘记了的话,该怎么找回自己的账号密码 操作 让我们来看一下具体的…...
ELK搭建初入
ELK搭建: 1、安装ElasticSearch (用于存储收集到的日志信息) 解压安装包 tar -xzvf elasticsearch-8.17.2-linux-x86_64.tar.gz 启动es:bin/elasticsearch –d(默认端口号9200) 浏览器输入es地址。出现…...
JVM 高级面试题及答案整理,最新面试题
JVM中的垃圾收集器有哪些,它们的工作原理是什么? JVM中的垃圾收集器主要包括以下几种: 1、 Serial收集器:它是一个单线程收集器,工作时会暂停所有其他工作线程("Stop-The-World")&a…...
第9章:LangChain结构化输出-示例5(基于大模型如何精确匹配POJO的字段)
如何使用LangChain4j框架创建和使用多种AI服务。它通过定义接口和注解,将自然语言处理任务(如情感分析、数字提取、日期提取、POJO提取等)封装为服务,并通过LangChain4j的AiServices动态生成这些服务的实现。 本章主要讲述基于LangChain调用大模型如何进行结构化输出的真实…...
ref和reactive的区别 Vue3
Vue3中ref和reactive的区别 ref 可以定义基本数据类型,也可定义对象类型的响应式数据 reactive 只能定义对象类型的响应式数据 ref和reactive定义对象类型的响应式数据有什么不同 不同点1 ref定义的响应式数据,取值时需要先 .value 不同点2 替换整…...
基于MATLAB的OFDM通信系统仿真设计
下面将为你详细介绍基于MATLAB的OFDM通信系统仿真设计的步骤和示例代码。 1. OFDM系统原理概述 正交频分复用(OFDM)是一种多载波调制技术,它将高速数据流通过串并转换,分配到多个正交的子载波上进行传输,这样可以有效…...
地铁站内导航系统:基于蓝牙Beacon与AR技术的动态路径规划技术深度剖析
本文旨在分享一套地铁站内导航系统技术方案,通过蓝牙Beacon技术与AI算法的结合,解决传统导航定位不准确、路径规划不合理等问题,提升乘客出行体验,同时为地铁运营商提供数据支持与增值服务。 如需获取校地铁站内智能导航系统方案文…...
golang循环变量捕获问题
在 Go 语言中,当在循环中启动协程(goroutine)时,如果在协程闭包中直接引用循环变量,可能会遇到一个常见的陷阱 - 循环变量捕获问题。让我详细解释一下: 问题背景 看这个代码片段: fo…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...
STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
ffmpeg(四):滤镜命令
FFmpeg 的滤镜命令是用于音视频处理中的强大工具,可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下: ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜: ffmpeg…...
04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
