Metal学习笔记七:片元函数

news2025/3/5 2:20:51

知道如何通过将顶点数据发送到 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? {
// 1
  let descriptor = MTLDepthStencilDescriptor()
// 2
  descriptor.depthCompareFunction = .less
// 3
  descriptor.isDepthWriteEnabled = true
  return 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 文件夹中找到完整的解决方案。

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

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

相关文章

【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…

探秘基带算法:从原理到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校…

水仙花数(华为OD)

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

《白帽子讲 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…

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

目录 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…

VUE集成Live2d

VUE集成Live2d 目前基于大模型&#xff0c;可以实现一个桌面的3D动画小人&#xff0c;个人猜测可以简介这个项目进行实现 1-参考网址 试了很多项目&#xff0c;只有这个项目直观的把问题说清楚了 Live2D Vue3技术应用:https://blog.csdn.net/hh1233321/article/details/1406947…

C++蓝桥杯基础篇(七)

片头 嗨~小伙伴们&#xff0c;大家好&#xff01;今天我们来一起学习蓝桥杯基础篇&#xff08;七&#xff09;&#xff0c;学习相关字符串的知识&#xff0c;准备好了吗&#xff1f;咱们开始咯&#xff01; 一、字符与整数的联系——ASCII码 每个常用字符都对应一个-128~127的…