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

Metal学习笔记七:片元函数

知道如何通过将顶点数据发送到 vertex 函数来渲染三角形、线条和点是一项非常巧妙的技能 — 尤其是因为您能够使用简单的单行片段函数为形状着色。但是,片段着色器能够执行更多操作。

➤ 打开网站 https://shadertoy.com,在那里您会发现大量令人眼花缭乱的社区创建的出色着色器。

这些示例可能看起来像复杂 3D 模型的渲染图,但外观具有欺骗性!您在此处看到的每个 “模型” 都是完全使用数学生成的,用 GLSL 片段着色器编写。GLSL 是 OpenGL 的图形库着色语言 — 在本章中,您将开始了解所有着色高手使用的原理。

注意:每个图形API都使用自己的着色器语言。原理是相同的,因此,如果您找到喜欢的GLSL着色器,则可以使用Metal MSL重新创建它。

起始项目

Starter 项目展示了一个示例,该示例将多个管线状态与不同的顶点函数结合使用,具体取决于您渲染的是旋转的火车还是全屏四边形。
➤ 打开本章的入门项目。
➤ 构建并运行项目。(您可以选择渲染火车或四边形。您将先从四边形开始。)
让我们仔细看看代码。
➤ 打开 Shaders 组中的 Vertex.metal,您将看到两个顶点函数:
• vertex_main:此函数将呈现火车,就像在上一章中所做的那样。
• vertex_quad:此函数使用着色器中定义的数组渲染全屏四边形。

这两个函数都输出一个 VertexOut结构体,其中仅包含顶点的位置。

➤ 打开 Renderer.swift。
在 init(metalView:options:) 中,您将看到两个管线状态对象 (PSO)。两个 PSO 之间的唯一区别是 GPU 在绘制时将调用的顶点函数。
根据 options.renderChoice 的值,draw(in:) 渲染火车模型或四边形,并换入正确的管线状态。SwiftUI 视图处理 Options 的更新,而 MetalViewRepresentable 将当前选项传递给 Renderer。
➤ 在继续之前,请确保您了解此项目的运作方式。

屏幕空间

片段函数可以执行的许多操作之一是创建复杂的模式,这些模式用来填充呈现的四边形上的屏幕像素。目前,片段函数只有 vertex 函数的插值position输出可供其使用。因此,首先,您将了解您可以利用此position做什么以及它的局限性是什么。
➤ 打开 Fragment.metal,将 fragment 函数内容改为:

float color;
in.position.x < 200 ? color = 0 : color = 1;
return float4(color, color, color, 1);

当光栅器处理顶点位置时,它会将它们从 NDC(标准化设备坐标)转换为屏幕空间。您在 ContentView.swift 中将 Metal 视图的宽度定义为 400点。使用新添加的代码,您说如果 x 位置小于 200,则将颜色设为黑色。否则,将颜色设为白色。


注意:虽然您可以使用 if 语句,但编译器可以更好地优化三元语句,因此使用它更有意义。

➤ 在您的 Mac 和 iPhone 15 Pro Max 模拟器上构建并运行该应用程序。


您是否预料到一半的屏幕是黑色的?视图的宽是 400 点,所以这是合理的。但是您可能没有考虑到一些事情:Apple Retina 显示屏具有不同的像素分辨率或像素密度。例如,MacBook Pro 配备 2 倍 Retina 显示屏,而 iPhone 15 Pro Max 配备 3 倍 Retina 显示屏。这些不同的显示屏意味着 MacBook Pro 上的 400 点, Metal 视图可创建 800x800 像素的可绘制纹理,而 iPhone 视图可创建 1200x1200 像素的可绘制纹理。

您的四边形填满了屏幕,您正在写入视图的可绘制渲染目标纹理(其大小与设备的显示屏相匹配),但没有简单的方法可以在 fragment 函数中找出当前渲染目标纹理的大小。
➤ 打开 Common.h,并添加新的结构体:

typedef struct {uint width;uint height;
} Params;

此代码包含可发送到 fragment 函数的参数。您可以根据需要向此结构体添加参数。
➤ 打开 Renderer.swift,并向 Renderer 添加一个新属性:

var params = Params()

您将把当前渲染目标大小存储在新属性中。
➤ 将以下代码添加到 mtkView(_:drawableSizeWillChange:) 的末尾:

 params.width = UInt32(size.width)
params.height = UInt32(size.height)

size 包含视图的可绘制纹理大小。换句话说,也就是视图的bounds按设备的比例因子进行缩放后的尺寸。
➤ 在 draw(in:)中调用渲染模型或四边形的方法之前,将参数发送到 fragment 函数:

renderEncoder.setFragmentBytes(&params,length: MemoryLayout<Params>.stride,index: 12)

请注意,您使用 setFragmentBytes(_:length:index:)将数据发送到片段函数的方式与之前使用 setVertexBytes(_:length:index:)的方式相同。
➤ 打开 Fragment.metal,将 fragment_main 的签名更改为:

 fragment float4 fragment_main(constant Params &params [[buffer(12)]],VertexOut in [[stage_in]])

具有目标绘图纹理大小的参数现在可用于 fragment 函数。
➤ 将设置 color 值的代码(基于 in.position.x 的值)更改为:

   in.position.x < params.width * 0.5 ? color = 0 : color = 1;

在这里,您将使用目标渲染大小进行计算。
➤ 在 macOS 和 iPhone 15 Pro Max 模拟器中运行该应用程序。

太棒了,现在两种设备的渲染看起来都一样。

Metal标准库函数

除了标准的数学函数(如 sin、abs 和 length)之外,还有一些其他有用的函数。让我们来看看:

step

如果 x 小于 edge,则 step(edge, x) 返回 0。否则,它将返回 1。此评估正是您对当前 fragment 函数执行的操作。

➤ 将 fragment 函数的内容替换为:

 float color = step(params.width * 0.5, in.position.x);
return float4(color, color, color, 1);

此代码生成的结果与以前相同,但代码略少。

➤ 构建并运行。


结果是,左侧为黑色,因为左侧 step 的结果为 0。而右侧为白色,因为右侧step 的结果为 1 。

让我们用棋盘格模式更进一步。
➤ 将 fragment 函数的内容替换为:

uint checks = 8;
// 1
float2 uv = in.position.xy / params.width;
// 2
uv = fract(uv * checks * 0.5) - 0.5;
// 3
float3 color = step(uv.x * uv.y, 0.0);
return float4(color, 1.0);


以下是正在发生的事情:
1. UV 坐标形成一个值介于 0 和 1 之间的网格。因此,中点位于 [0.5, 0.5],左上角位于 [0.0, 0.0]。UV 坐标通常与将顶点映射到纹理相关联,如第 8 章 “纹理”所示。
2. fract(x)返回 x 的小数部分。将 UV 的小数值乘以checks值的一半,得到一个介于 0 和 1 之间的值。然后减去 0.5,使一半的值小于零。
3. 如果 xy 乘法的结果小于零,则结果为 1 或白色。否则,它是 0 或黑色。
例如:

float2 uv = (550, 50) / 800;     // uv = (0.6875, 0.0625)
uv = fract(uv * checks * 0.5);   // uv = (0.75, 0.25)
uv -= 0.5; // uv = (0.25, -0.25)
float3 color = step(uv.x * uv.y, 0.0); // x > -0.0625, so color
is 1

➤ 构建并运行应用程序。

length

创建正方形很有趣,但让我们使用 length 函数创建一些圆。

➤ 将 fragment 函数替换为:

float center = 0.5;
float radius = 0.2;
float2 uv = in.position.xy / params.width - center;
float3 color = step(length(uv), radius);
return float4(color, 1.0);


➤ 构建并运行应用程序。

要调整形状大小并在屏幕上移动形状,请更改圆的中心和半径。

smoothstep

smoothstep(edge0, edge1, x)返回介于 0 和 1 之间的平滑艾米插值。
 注意:edge1 必须大于 edge0,x 应该是 edge0 <= x <= edge1。

➤ 将片段函数改为:

 float color = smoothstep(0, params.width, in.position.x);
return float4(color, color, color, 1);

color 包含介于 0 和 1 之间的值。当位置与屏幕宽度相同时,颜色为 0 或白色。当位置位于屏幕的最左侧时,颜色为 0 或黑色。
➤ 构建并运行应用程序。



在两种边缘情况之间,颜色是在黑色和白色之间插值的渐变。在这里,您使用 smoothstep 来计算颜色,但您也可以使用它在任意两个值之间进行插值。例如,您可以使用 smoothstep 为 vertex 函数中的位置设置动画。


mix

mix(x, y, a)产生与 x + (y - x) * a 相同的结果。

➤ 将片段函数更改为:

float3 red = float3(1, 0, 0);
float3 blue = float3(0, 0, 1);
float3 color = mix(red, blue, 0.6);
return float4(color, 1);


混合 0 将产生全红色。混合 1 产生全蓝色。这些颜色共同产生 60% 的红色和蓝色混合。

➤ 构建并运行应用程序。
 
您可以将混合与 smoothstep 结合使用以产生颜色渐变。

➤ 将 fragment 函数替换为:

float3 red = float3(1, 0, 0);
float3 blue = float3(0, 0, 1);
float result = smoothstep(0, params.width, in.position.x);
float3 color = mix(red, blue, result);
return float4(color, 1);

此代码使用result的插值,将其用作红色和蓝色的混合比例。

➤ 构建并运行应用程序。

normalize

规范化过程是指重新调整数据比例以使用标准范围。例如,向量同时具有 direction 和 magnitude。在下图中,向量 A 的长度为 2.12132,方向为 45 度。向量 B 的长度相同,但方向不同。向量 C 的长度不同,但方向相同。

如果两个向量的大小相同,则更容易比较它们的方向,因此可以将向量标准化为单位长度。normalize(x)返回方向相同但长度为 1 的向量 x。
让我们看看另一个规范化的例子。假设您希望使用颜色可视化顶点位置,以便更好地调试某些代码。
➤ 将片段函数改为:

return in.position;

➤ 构建并运行应用程序。

片段函数应返回每个元素介于 0 和 1 之间的 RGBA 颜色。但是,由于位置位于屏幕空间中,因此每个位置在 [0, 0, 0] 和 [800, 800, 0] 之间变化,这就是四边形呈现黄色的原因(它仅在左上角位于 0 和 1 之间)。
➤ 现在,将代码更改为:

 float3 color = normalize(in.position.xyz);
return float4(color, 1);

在这里,您将向量 in.position.xyz 标准化为长度为 1。现在,所有颜色都保证介于 0 和 1 之间。归一化后,最右上角的位置 (800, 0, 0) 包含红色的 1, 0, 0。
➤ 构建并运行应用程序以查看结果。

法线

尽管可视化位置有助于调试,但通常对创建 3D 渲染没有帮助。但是,找到三角形的朝向对于着色很有用,而着色器正是法线发挥作用的地方。法线是表示顶点或表面朝向的向量。在下一章中,您将学习如何为模型增加光照。但首先,您需要了解法线。

从 Blender 捕获的以下图像显示了指向的顶点法线。球体的每个顶点都指向不同的方向。
 
球体的着色取决于这些法线。如果法线指向光源,则 Blender 将更亮。
四边形对于着色目的不是很有趣,因此请将默认渲染切换到火车。
➤ 打开 Options.swift,并将 renderChoice 的初始化更改为:

var renderChoice = RenderChoice.train

➤ 运行应用程序以检查您的火车渲染。
 
与全屏四边形不同,只有火车覆盖的片段才会显示。但是,每个片段的颜色仍然取决于片元的屏幕位置,而不是火车顶点的位置。

加载带法线的火车模型

3D模型文件通常包含表面法线值,您可以和模型一起加载这些值。如果您的文件不包含Surface Formals,则Model I/O可以使用MDLMesh的addNormals(withAttributeNamed:creaseThreshold:),在导入时生成它们。

为顶点描述器增加法线

➤ 打开 VertexDescriptor.swift。
目前,您只加载 position 属性。是时候将 normal 添加到顶点描述符。
➤ 在设置 offset 的代码之后,在设置 layouts[0] 的代码之前,将以下代码添加到 MDLVertexDescriptor 的 defaultLayout:

vertexDescriptor.attributes[1] = MDLVertexAttribute(name: MDLVertexAttributeNormal,format: .float3,offset: offset,bufferIndex: 0)
offset += MemoryLayout<float3>.stride

这里,法线类型是 float3,并在缓冲区 0 中和position交错放置。float3 是在 MathLibrary.swift 中定义的 SIMD3<Float> 类型的别名。每个顶点在索引0缓冲区中占用两个 float3,即 32 字节。layouts[0] 描述带有 stride 的索引0缓冲区。

更新 Shader 函数

➤ 打开 Vertex.metal。
火车模型的管线状态使用此顶点描述符,以便顶点函数可以处理属性,并将这些属性与 VertexIn中的属性匹配。
➤ 构建并运行应用程序,您会发现一切仍然按预期工作。即使您向顶点缓冲区添加了新属性,管线也会忽略它。
因为您尚未将其作为attribute(n)包含在 VertexIn 中。是时候解决这个问题了。

➤ 在 VertexIn 中添加以下代码:

float3 normal [[attribute(1)]];

在这里,您将 attribute(1) 与顶点描述符的属性 1 匹配。现在你将能够访问 vertex 函数中的 normal 属性。
➤ 接下来,将以下代码添加到 VertexOut 中:

float3 normal;

通过在此处包含 normal,您现在可以将数据传递给 fragment 函数。
➤ 在 vertex_main 中,将赋值更改为 out:

VertexOut out {.position = position,.normal = in.normal
};


完美!通过该更改,您现在可以从 vertex 函数返回位置和法线。
➤ 打开 Fragment.metal,将 fragment_main 的内容替换为:

return float4(in.normal, 1);

别担心,编译错误是意料之中的。即使您在 Vertex.metal 中更新了 VertexOut,该结构体的作用域也仅在该文件中。


添加头文件

在多个着色器文件中需要结构体和函数是很常见的。因此,就像您对 Swift 和 Metal 之间的桥接头文件 Common.h 所做的那样,您可以添加其他头文件并将它们导入到着色器文件中。
➤ 使用 macOS 头文件模板在 Shaders 组中创建一个新文件,并将其命名为 ShaderDefs.h。
➤ 将代码替换为:
 

#include <metal_stdlib>
using namespace metal;
struct VertexOut {float4 position [[position]];float3 normal;
};

在这里,您可以在 metal 命名空间中定义 VertexOut。

➤ 打开 Vertex.metal,并删除 VertexOut 结构。

➤ 导入 Common.h 后,添加:

   #import "ShaderDefs.h"

➤ 打开 Fragment.metal,并删除 VertexOut 结构。

➤ 同样,在导入 Common.h 后,添加:

#import "ShaderDefs.h"

➤ 构建并运行应用程序。
哦,现在看起来有点奇怪!

您的法线看起来好像显示正确 — 红色法线位于火车的右侧,绿色法线向上,蓝色位于后面 — 但随着火车旋转,它的某些部分看起来几乎是透明的。
这里的问题是光栅器会混淆顶点的深度顺序。当你从前面看火车时,你不应该能看到火车的后面;它应该被遮挡。

深度

光栅器默认情况下不会处理深度顺序,因此您需要以深度模板状态为光栅器提供所需的信息。
您可能还记得第3章“渲染管道”,模板测试单元检查渲染管道期间片段是否可见。如果确定片段在另一个片段后面,则将其丢弃。
让我们给渲染编码器一个MTLDepthStencilState属性,以描述如何进行此测试。
➤打开Renderer.swift。
➤在init(metalView:options:)结束之前,设置metalView.clearColor之后,添加:

metalView.depthStencilPixelFormat = .depth32Float

该代码告诉Metal View,您需要保留深度信息。默认的像素格式为.invalid,它告知视图不需要创建深度和模板纹理。
渲染命令编码器使用的管线状态必须具有相同的深度像素格式。
➤在init(metalView:options:)设置PipelinedEscriptor.colorattachments [0] .pixelformat之后,在do {之前添加:

   pipelineDescriptor.depthAttachmentPixelFormat = .depth32Float

如果您现在要构建并运行该应用程序,那么您将获得与以前相同的结果。但是,在幕后,视图创建了纹理,光栅器可以在该纹理上写入深度值。
接下来,您需要设置希望光栅器计算深度值的方式。 

➤向渲染器添加新属性:

let depthStencilState: MTLDepthStencilState?

该属性具有正确的渲染设置,使其具有深度模板状态。

➤ 在 Renderer 中创建此方法以实例化深度模板状态:

static func buildDepthStencilState() -> MTLDepthStencilState? {
// 1let descriptor = MTLDepthStencilDescriptor()
// 2descriptor.depthCompareFunction = .less
// 3descriptor.isDepthWriteEnabled = truereturn Renderer.device.makeDepthStencilState(descriptor: descriptor)
}

浏览这段代码:
1. 创建一个描述符,用于初始化深度模板状态,就像您对管道状态对象所做的那样。
2. 指定如何比较当前和已处理的片段。使用 compare 函数 less 时,如果当前片段深度小于帧缓冲区中前一个片段的深度,则当前片段将替换前一个片段。
3. 说明是否写入深度值。如果您有多个通道,如第 12 章 “渲染通道”中所述,有时您需要读取已绘制的片段。在这种情况下,请将 isDepthWriteEnabled 设置为 false。请注意,当您绘制需要深度的对象时,isDepthWriteEnabled 始终为 true。
➤ 在 super.init() 之前从 init(metalView:options:) 调用方法:

depthStencilState = Renderer.buildDepthStencilState()

➤ 在 draw(in:) 中,将以下内容添加到方法顶部的 guard { } 之后:

renderEncoder.setDepthStencilState(depthStencilState)

➤ 构建并运行应用程序,以光彩夺目的 3D 形式查看您的火车。

当火车旋转时,它会以红色、绿色、蓝色和黑色的阴影出现。

考虑一下你在这个渲染中看到的内容。法线当前位于对象空间中。因此,即使火车在世界空间中旋转,颜色/法线也不会随着模型旋转的改变而改变。

当法线沿模型的 x 轴指向右侧时,值为 [1, 0, 0]。这与 RGB 值中的红色相同,因此对于指向右侧的法线,片段为红色。
指向上方的法线在 y 轴上为 1,因此颜色为绿色。

指向摄像机的法线为负数。当颜色为 [0, 0, 0] 或更小时,它们为黑色。当你看到火车旋转的后部时,你可以看出指向 z 方向的车轮后部是蓝色的 [0, 0, 1]。
现在,您在 fragment 函数中拥有了法线,您可以根据颜色的朝向开始操作颜色。当您开始使用光照时,操纵颜色非常重要。

半球光照

半球照明使用环境光。使用这种类型的照明,场景的一半使用一种颜色照明,另一半使用另一种颜色照明。例如,下图中的球体使用半球照明。

请注意球体如何呈现从天空反射的颜色(顶部)和从地面反射的颜色(底部)。要查看这种类型的光照效果,您需要更改 fragment 函数,以便:
• 朝上的法线为蓝色。
• 朝下的法线为绿色。
• 过渡值为蓝色和绿色混合。

➤ 打开 Fragment.metal,并将 fragment_main 的内容替换为:

float4 sky = float4(0.34, 0.9, 1.0, 1.0);
float4 earth = float4(0.29, 0.58, 0.2, 1.0);
float intensity = in.normal.y * 0.5 + 0.5;
return mix(earth, sky, intensity);


mix(x, y, z) 根据第三个值在前两个值之间进行插值,第三个值必须介于 0 和 1 之间。您的正常值介于 -1 和 1 之间,因此您可以在 0 和 1 之间转换强度。
➤ 构建并运行应用程序以查看您闪亮的火车。请注意,火车的顶部是蓝色的,而它的底部是绿色的。

片段着色器非常强大,允许您精确地为对象着色。在第 10 章 “光照基础知识”中,您将使用法线的力量为场景提供更逼真的光照着色。在第19章“镶嵌与地形”中,你将创建一个与此类似的效果,学习如何根据坡度在地形上放置雪。

挑战

目前,您正在对所有缓冲区索引和属性使用硬编码的魔数。随着应用程序的增长,跟踪这些数字将变得越来越困难。所以,你在本章中的挑战是寻找所有这些神奇的数字,并为它们起一个令人难忘的名字。对于此挑战,您将在 Common.h 中创建一个枚举。
以下是一些可帮助您入门的代码:
 

typedef enum {VertexBuffer = 0,UniformsBuffer = 11,ParamsBuffer = 12
} BufferIndices;

现在,您可以在 Swift 和 C++ 着色器函数中使用这些常量: 

//Swift
encoder.setVertexBytes(&uniforms,length: MemoryLayout<Uniforms>.stride,index: Int(UniformsBuffer.rawValue))
// Shader Function
vertex VertexOut vertex_main(const VertexIn in [[stage_in]],constant Uniforms &uniforms [[buffer(UniformsBuffer)]])

您甚至可以在 VertexDescriptor.swift 中添加扩展来美化代码:

extension BufferIndices {var index: Int {return Int(self.rawValue)}
}

使用此代码,您可以使用 UniformsBuffer.index 而不是 Int(UniformsBuffer.rawValue)。
您可以在本章的 challenge 文件夹中找到完整的解决方案。

相关文章:

Metal学习笔记七:片元函数

知道如何通过将顶点数据发送到 vertex 函数来渲染三角形、线条和点是一项非常巧妙的技能 — 尤其是因为您能够使用简单的单行片段函数为形状着色。但是&#xff0c;片段着色器能够执行更多操作。 ➤ 打开网站 https://shadertoy.com&#xff0c;在那里您会发现大量令人眼花缭乱…...

《一个端粒到端粒的参考基因组为木瓜中五环三萜类化合物生物合成提供了遗传学见解》

A telomere-to-telomere reference genome provides genetic insight into the pentacyclic triterpenoid biosynthesis in Chaenomeles speciosa Amplification of transposable elements 转座元件的扩增 Sequence mining disclosed that TEs were one main event in the ex…...

【Mac】2025-MacOS系统下常用的开发环境配置

早期版本的一个环境搭建参考 1、brew Mac自带终端运行&#xff1a; /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" Installation successful!成功后运行三行命令后更新环境&#xff08;xxx是mac的username&a…...

蓝桥杯web第三天

展开扇子题目&#xff0c; #box:hover #item1 { transform:rotate(-60deg); } 当悬浮在父盒子&#xff0c;子元素旋转 webkit display: -webkit-box&#xff1a;将元素设置为弹性伸缩盒子模型。-webkit-box-orient: vertical&#xff1a;设置伸缩盒子的子元素排列方…...

Qt基础入门-详解

前言 qt之路正式开启 &#x1f493; 个人主页&#xff1a;普通young man-CSDN博客 ⏩ 文章专栏&#xff1a;C_普通young man的博客-CSDN博客 ⏩ 本人giee: 普通小青年 (pu-tong-young-man) - Gitee.com 若有问题 评论区见&#x1f4dd; &#x1f389;欢迎大家点赞&#x1f44…...

FPGA开发,使用Deepseek V3还是R1(3):系统级与RTL级

以下都是Deepseek生成的答案 FPGA开发&#xff0c;使用Deepseek V3还是R1&#xff08;1&#xff09;&#xff1a;应用场景 FPGA开发&#xff0c;使用Deepseek V3还是R1&#xff08;2&#xff09;&#xff1a;V3和R1的区别 FPGA开发&#xff0c;使用Deepseek V3还是R1&#x…...

移动端国际化翻译同步解决方案-V3

1.前言 因为软件出海&#xff0c;从在上上家公司就开始做翻译系统&#xff0c;到目前为止已经出了两个比较大的版本了&#xff0c;各个版本解决的痛点如下&#xff1a; V1版本&#xff1a; 主要针对的是AndroidiOS翻译不一致和翻译内容管理麻烦的问题&#xff0c;通过这个工具…...

多空狙击线-新指标-图文教程,多空分界买点以及强弱操盘技术教程,通达信炒股软件指标

“多空狙击线”指标 “多空狙击线”特色指标是量能型技术指标&#xff0c;主要用于分析股票市场中机构做多/做空力量的强程度。该指标的构成、定义与原理如下: “多空狙击线”指标&#xff0c;又称机构做多/做空能量线&#xff0c;通过计算和分析股票市场中机构做多/做空力量…...

零信任架构和传统网络安全模式的

零信任到底是一个什么类型的模型&#xff1f;什么类型的思想或思路&#xff0c;它是如何实现的&#xff0c;我们要做零信任&#xff0c;需要考虑哪些问题&#xff1f; 零信任最早是约翰金德瓦格提出的安全模型。早期这个模型也是因为在安全研究上考虑的一个新的信任式模型。他最…...

Oracle 11g的部署配置

1、进入官网下载所需版本的Oracle 2、安装 ①&#xff1a;选择setup.exe开始安装 ②&#xff1a;安装提示如下&#xff0c;直接忽略&#xff0c;选是 ③&#xff1a;配置安全更新 填写邮箱&#xff0c;并取消勾选 ④&#xff1a;如果点击下一步&#xff0c;提示什么代理啥的…...

下载b站视频音频

文章目录 方案一&#xff1a;jjdown如何使用 方案二&#xff1a;bilibili哔哩哔哩下载助手如何使用进入插件网站插件下载插件安装 使用插件下载视频音频&#xff1a;复制音频下载地址 方案三&#xff1a;bat命令下载单个音频下载单个视频下载单个音视频 方案一&#xff1a;jjdo…...

记录spring-boot 3.X版本整合RocketMq

版本信息 先把该次整合的版本信息列如下&#xff1a; spring-boot spring-cloud rocketmq-spring-boot-starter rocketmq-client rocketmq 3.0.13 2022.0.5 2.2.3 4.9.8 4.9.8 版本信息是如何选择的呢&#xff1f;看rocketMq官网springcloud alibaba版本声明 rock…...

《基于HarmonyOS NEXT API 12+,搭建新闻创作智能写作引擎》

在信息爆炸的时代&#xff0c;新闻行业对于内容生产的效率和质量有着极高的要求。AI技术的发展为新闻创作带来了新的变革契机&#xff0c;借助AI智能写作助手&#xff0c;新闻工作者可以快速生成新闻稿件的初稿&#xff0c;大大提高创作效率。本文将基于HarmonyOS NEXT API 12及…...

探秘基带算法:从原理到5G时代的通信变革【六】CRC 校验

文章目录 2.5 CRC 校验2.5.1 前言2.5.2 CRC算法简介2.5.3 CRC计算的详细过程2.5.4 CRC校验的两种方法详解**分离比较法****整体运算法****不同位出错与余数的关系****总结** 2.5.5 CRC计算的C实现及工具介绍**C实现CRC计算****CRC计算工具推荐** **2.5.6 总结&#xff1a;CRC校…...

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_conf_add_dump

ngx_conf_add_dump 定义在src\core\ngx_conf_file.c static ngx_int_t ngx_conf_add_dump(ngx_conf_t *cf, ngx_str_t *filename) {off_t size;u_char *p;uint32_t hash;ngx_buf_t *buf;ngx_str_node_t *sn;ngx_conf_dump_t *cd;has…...

MySQL快速搭建主从复制

一、基于位点的主从复制部署流程 确定主库Binlog是否开启修改主从server_id主库导出数据从库导入数据确定主库备份时的位点在从库配置主库信息查看复制状态并测试数据是否同步 二、准备阶段(主库和从库配置都需要修改&#xff09; 1、确定主库Binlog是否开启 2、修改主从se…...

Linux注册进程终止处理函数

atexit() 是一个标准库函数&#xff0c;用于注册在进程正常终止时要调用的函数。通过 atexit()&#xff0c;你可以确保在程序结束时自动执行一些清理工作&#xff0c;比如释放资源、保存状态等。 函数原型如下&#xff1a; #include <stdlib.h> int atexit(void (*func…...

pytorch 模型测试

在使用 PyTorch 进行模型测试时,一般包含加载测试数据、加载训练好的模型、进行推理以及评估模型性能等步骤。以下为你详细介绍每个步骤及对应的代码示例。 1. 导入必要的库 import torch import torch.nn as nn import torchvision import torchvision.transforms as trans…...

水仙花数(华为OD)

题目描述 所谓水仙花数&#xff0c;是指一个n位的正整数&#xff0c;其各位数字的n次方和等于该数本身。 例如153是水仙花数&#xff0c;153是一个3位数&#xff0c;并且153 13 53 33。 输入描述 第一行输入一个整数n&#xff0c;表示一个n位的正整数。n在3到7之间&#x…...

(十二)基于 Vue 3 和 Mapbox GL 实现的坐标拾取器组件示例

下面是一个基于 Vue 3 和 Mapbox GL 实现的坐标拾取器组件示例: <template><div class="map-container"><div ref="mapContainer" class="map"></div><div class="coordinates-box"><div v-if=&qu…...

【华为OD机试真题29.9¥】(E卷,100分) - IPv4地址转换成整数(Java Python JS C++ C )

题目描述 存在一种虚拟IPv4地址,由4小节组成,每节的范围为0~255,以#号间隔,虚拟IPv4地址可以转换为一个32位的整数,例如: 128#0#255#255,转换为32位整数的结果为2147549183(0x8000FFFF) 1#0#0#0,转换为32位整数的结果为16777216(0x01000000) 现以字符串形式给出一…...

《白帽子讲 Web 安全》之深入同源策略(万字详解)

目录 引言 一、同源策略基础认知 &#xff08;一&#xff09;定义 &#xff08;二&#xff09;作用 &#xff08;三&#xff09;作用机制详解 二、同源策略的分类 &#xff08;一&#xff09;域名同源策略 &#xff08;二&#xff09;协议同源策略 &#xff08;三&…...

USRP4120-通用软件无线电平台

1、产品描述 USRP4120平台是彬鸿科技公司推出的以XILINX XC7Z020 SOC处理器为核心&#xff0c;搭配ADI AD9361射频集成芯片&#xff0c;针对无线通信系统科研与教学实验场景的一款通用软件无线电平台。产品频率范围70MHz~6GHz&#xff0c;模拟带宽200KHz~56MHz&#xff0c;支持…...

计算机毕业设计SpringBoot+Vue.js社区智慧养老监护管理平台(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…...

yoloV5的学习-pycharm版本

真的很让人气愤的一点&#xff0c;老师把我的pycharm给卸载了&#xff0c;我那个上面不仅有gpu-torch&#xff0c;还有gpu-torch&#xff0c;他给俺删了&#xff0c;删了很久&#xff0c;我心都碎了&#xff0c;过几天我就去找他负责&#xff0c;让他给我装回来我的环境&#x…...

蓝桥杯 之 图形规律

文章目录 分析组成&#xff0c;找到规律数正方形 在蓝桥杯中&#xff0c;常常会有一些图形的规律的题目需要我们去解决&#xff0c;所以我们需要学会其中的一些方法&#xff0c;我们这样才能解决对应的问题 方法1&#xff1a;直接对n进行拆分方法2&#xff1a;使用递归的思路&a…...

JavaScript 变量语法扩展

随着ECMAScript 6&#xff08;ES6&#xff09;及后续版本的发布&#xff0c;JavaScript引入了许多新的特性&#xff0c;极大地丰富了变量声明和使用的语法。这些改进不仅提升了代码的可读性和简洁性&#xff0c;还增强了开发效率。本文将介绍一些重要的变量语法扩展&#xff0c…...

SslConnection::SslConnection()详解

一、&#x1f50d; SslConnection::SslConnection() 详解 这个构造函数的主要作用是&#xff1a; 创建 SSL 对象创建 BIO&#xff08;I/O 缓冲区&#xff09;初始化 SSL 服务器模式绑定回调函数&#xff08;onRead() 处理接收数据&#xff09; &#x1f4cc; 1. 初始化 SSL 相…...

【计算机网络入门】初学计算机网络(八)

目录 1. S-W协议的信道利用率 2. GBN、SR协议的信道利用率 3.术语补充 3.1 滑动窗口协议 3.2 ARQ协议、连续ARQ协议 4. 信道划分介质访问控制 4.1 时分复用&#xff08;TDM&#xff09; 4.2 统计时分复用&#xff08;STDM&#xff09; 4.3 频分复用&#xff08;FDM&a…...

迷你世界脚本生物接口:Creature

生物接口&#xff1a;Creature 彼得兔 更新时间: 2024-05-22 17:51:22 继承自 Actor 具体函数名及描述如下: 序号 函数名 函数描述 1 getAttr(...) 生物属性获取 2 setAttr(...) 生物属性设置 3 isAdult(...) 判断该生物是否成年 4 setOxygenNeed(…...