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 文件夹中找到完整的解决方案。

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

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

相关文章

深入浅出 Go 语言:协程(Goroutine)详解

深入浅出 Go 语言&#xff1a;协程(Goroutine)详解 引言 Go 语言的协程&#xff08;goroutine&#xff09;是其并发模型的核心特性之一。协程允许你轻松地编写并发代码&#xff0c;而不需要复杂的线程管理和锁机制。通过协程&#xff0c;你可以同时执行多个任务&#xff0c;并…

Python测试框架Pytest的参数化

上篇博文介绍过&#xff0c;Pytest是目前比较成熟功能齐全的测试框架&#xff0c;使用率肯定也不断攀升。 在实际工作中&#xff0c;许多测试用例都是类似的重复&#xff0c;一个个写最后代码会显得很冗余。这里&#xff0c;我们来了解一下pytest.mark.parametrize装饰器&…

基于微信小程序的停车场管理系统的设计与实现

第1章 绪论 1.1 课题背景 随着移动互联形式的不断发展&#xff0c;各行各业都在摸索移动互联对本行业的改变&#xff0c;不断的尝试开发出适合于本行业或者本公司的APP。但是这样一来用户的手机上就需要安装各种软件&#xff0c;但是APP作为一个只为某个公司服务的一个软件&a…

C++ Primer 动态内存与智能指针

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

QT——文件IO

QFile 类 构造函数 QFile() 无参构造 仅仅构建一个QFile 对象&#xff0c;不设定文件名 QFile(文件名) 构建一个QFile对象的同时&#xff0c;设定文件名 但是注意&#xff0c;仅仅设定文件名&#xff0c;并不会打开该文件 设定文件名 QFile file file.setFileName…

深度学习-大白话解释循环神经网络RNN

目录 一、RNN的思想 二、RNN的基本结构 网络架构 ​关键点 三、RNN的前向传播 四、RNN的挑战:梯度爆炸和梯度消失 问题分析 ​示例推导 五、LSTM:RNN的改进 核心组件 ​网络架构 3. LSTM 的工作流程 4. 数学公式总结 5. LSTM 的优缺点 ​优点 ​缺点 6. LSTM 的…

json介绍、python数据和json数据的相互转换

目录 一 json介绍 json是什么&#xff1f; 用处 Json 和 XML 对比 各语言对Json的支持情况 Json规范详解 二 python数据和json数据的相互转换 dumps() : 转换成json loads(): 转换成python数据 总结 一 json介绍 json是什么&#xff1f; 实质上是一条字符串 是一种…

LINUX网络基础 - 网络编程套接字,UDP与TCP

目录 前言 一. 端口号的认识 1.1 端口号的作用 二. 初识TCP协议和UDP协议 2.1 TCP协议 TCP的特点 使用场景 2.2 UDP协议 UDP的特点 使用场景 2.3 TCP与UDP的对比 2.4 思考 2.5 总结 三. 网络字节序 3.1 网络字节序的介绍 3.2 网络字节序思考 四. socket接口 …

泵吸式激光可燃气体监测仪:快速精准守护燃气管网安全

在城市化进程加速的今天&#xff0c;燃气泄漏、地下管网老化等问题时刻威胁着城市安全。如何实现精准、高效的可燃气体监测&#xff0c;守护“城市生命线”&#xff0c;成为新型基础设施建设的核心课题。泵吸式激光可燃气体监测仪&#xff0c;以创新科技赋能安全监测&#xff0…

[自然语言处理]pytorch概述--什么是张量(Tensor)和基本操作

pytorch概述 PyTorch 是⼀个开源的深度学习框架&#xff0c;由 Facebook 的⼈⼯智能研究团队开发和维护&#xff0c;于2017年在GitHub上开源&#xff0c;在学术界和⼯业界都得到了⼴泛应⽤ pytorch能做什么 GPU加速自动求导常用网络层 pytorch基础 量的概念 标量&#xf…

20250304vue-事件处理

监听事件 使用 v-on 指令(简写为 )来监听 DOM 事件。 用法&#xff1a;v-on:click"handler" 或者 click"handler". handler 可以是内联事件处理器&#xff0c;也可以是方法事件处理器。 内联事件处理器通常用于简单场景&#xff0c;例如&#xff1a; …

Excel基础(详细篇):总结易忽视的知识点,有用的细节操作

目录 写在前面基础篇Excel主要功能必会快捷键快捷键整理表LotusExcel的文件类型工作表基本操作表项操作选中与缩放边框线 自动添加边框线格式刷设置斜线表头双/多斜线表头不变形的:双/多斜线表头插入多行、多列单元格/行列的移动冻结窗口 方便查看数据打印的常见问题Excel格式…

【网络安全】——二进制协议 vs 文本协议:从原理到实战的深度解析

目录 引言 一、协议的本质与分类 二、二进制协议详解 1. 核心特点 2. 典型结构示例 3. 常见应用场景 4. 详细介绍 三、文本协议详解 1. 核心特点 2. 典型结构示例 3. 常见应用场景 4.详细介绍 四、关键对比&#xff1a;二进制协议 vs 文本协议 五、实战案例&…

深度学习---卷积神经网络

一、卷积尺寸计算公式 二、池化 池化分为最大池化和平均池化 最常用的就是最大池化&#xff0c;可以认为最大池化不需要引入计算&#xff0c;而平均池化需要引出计算&#xff08;计算平均数&#xff09; 每种池化还分为Pooling和AdaptiveAvgPool Pooling(2)就是每2*2个格子…

[BUUCTF]web--wp(持续更新中)

ps:文章所引用知识点链接&#xff0c;如有侵权&#xff0c;请联系删除 [极客大挑战 2019]EasySQL 题目类型&#xff1a;简单SQL注入 发现是登录页面&#xff0c;用万能登录方法测试&#xff0c;两种语句均能解出flag [极客大挑战 2019]Havefun 题目类型&#xff1a;代码审计…

详解matplotlib隐式pyplot法和显式axes法

Python的matplotlib提供了pyplot隐式方法和显式Axes方法&#xff0c;这让很多人在选择时感到困惑。本文用9000字彻底解析两种方法的区别与适用场景&#xff0c;节选自&#x1f449;Python matplotlib保姆级教程 matplotlib隐式绘图方法&#xff08;pyplot&#xff09; matplot…

网络安全需要掌握哪些技能?

&#x1f345; 点击文末小卡片 &#xff0c;免费获取网络安全全套资料&#xff0c;资料在手&#xff0c;涨薪更快 在这个高度依赖于网络的时代&#xff0c;网络安全已经成为我们工作和生活中不可或缺的一部分&#xff0c;更是0基础转行IT的首选&#xff0c;可谓是前景好、需求大…

推荐1款OCR的扫描仪软件,无需安装,打开即用!

聊一聊 现在日常办公&#xff0c;很多时候还是需要扫描仪配合。 很多时候需要将文件搜索成PDF再传输。 今天给大家分享一款OCR扫描仪软件。 软件介绍 OCR的扫描仪软件 支持扫描仪共享。 支持WIA、TWAIN、SANE和ESCL驱动程序。 还可以批量多扫描仪配置扫描&#xff0c;支持…

私有云基础架构

基础配置 使用 VMWare Workstation 创建三台 2 CPU、8G内存、100 GB硬盘 的虚拟机 主机 IP 安装服务 web01 192.168.184.110 Apache、PHP database 192.168.184.111 MariaDB web02 192.168.184.112 Apache、PHP 由于 openEuler 22.09 系统已经停止维护了&#xff…