Metal 学习笔记六:坐标空间

要在网格上轻松找到一个点,您需要一个坐标系。例如,如果网格恰好是您的 iPhone 15 屏幕,则中心点可能是 x:197、y:426。但是,该点可能会有所不同,具体取决于它所处的空间。
在上一章中,您了解了矩阵。通过将顶点的位置乘以特定矩阵,可以将顶点位置转换为不同的坐标空间。顶点在经过渲染管线时通常有 6 种空间:
1.物体局部空间
2.世界空间
3.相机空间
4.裁剪空间
5.规格化设备空间:NDC(Normalized Device Coordinate)
6.屏幕空间
这么多空间,听起来就像我们坐着宇宙飞船要冲出太阳系一样。让我们先对每个空间有一个具体的概念。
物体局部空间
我们之前学习的时候,应该都听过笛卡尔坐标系吧!下图显示的是一个2D的笛卡尔坐标,用2D的格子表示了图像的不同顶点的坐标。

这里的点的坐标都是相对狗狗图的原点来说的,这里的坐标原点(0, 0)是在狗狗的两只脚的中间。它们在这里被叫做物体局部空间(或 模型空间)。在上一章中,Triangle 在对象空间中保存了一个顶点数组,用于描述三角形每个顶点坐标。
世界空间
在接下来的图片,方向箭头标识了原点位置。世界空间的中心位置在(0, 0, 0)。在世界空间中,狗狗是在(1, 0, 1)的位置,而猫猫是在(-1, 0, -2)的位置。

不过,猫猫仍然会觉得自己在世界的中心,所以在猫的空间,猫觉得它自己是在(0, 0, 0),这就让狗狗的位置在猫的世界空间的(2, 0, 3)位置。当猫走动时,在它的世界空间中,它会觉得自己总在(0, 0, 0),而世界的其他物体都相对猫反向移动。
注意:猫空间并不会作为常用的3D坐标空间,只是说在数学概念上可以创建一个猫专属的空间。
相机空间
对于狗来说,世界的中心是对着它们拍照的、拿着相机的人。在相机空间中,相机本身是在(0, 0, 0),而狗狗大概是在(-3, -2, 7)的位置。当相机移动时,相机的位置一直保持在(0, 0, 0),而狗和猫会相对相机移动。
裁剪空间
这个空间存在的意义是,用透视来投影。换句话说就是,我们需要把一个3D场景带到一个2D空间。裁剪空间是一个扭曲的立方体,并准备好扁平处理。

在此场景中,狗和猫的大小相同,但由于狗在 3D 空间中的位置,狗看起来更小。在这里,狗比猫更远,所以他看起来更小。
注意,如果我们要工程制图的话,我们可能需要使用正交投影而不是透视投影。
NDC空间
投影到裁剪空间时创建了一个w大小的半立方体盒子。在光栅化时,GPU会转换w到正交坐标系的点,x和y轴的取值范围规格化到-1到1,而z轴的取值范围规格化到0到1。
屏幕空间
现在GPU已经有一个正交化的立方体了。它会把裁剪空间平面化,并且转换到屏幕坐标系中,准备好显示到设备的屏幕中。(比如在iphone12,屏幕空间分辨率应该是2532*1170 )。
在下图中,虽然狗和猫差不多大小,但是狗狗离相机远一些,所以看起来略小。

在不同空间中转换
可以使用变换矩阵从一个空间转换到另一个。在下图中,左边图中狗狗耳朵上的顶点,在狗的局部坐标系中是(-1, 4, 0)的位置上。在右图中,狗狗已经在世界空间上了,它耳朵上的点大概在(0.75, 1.5, 1)的世界位置上。

为了把狗狗的顶点从局部空间变换到世界空间,使用一个变换矩阵即可。我们可以移动它们,也可以缩放它们。
有4个空间需要我们控制,可以使用3个矩阵在它们之间转换:

如上图,我们可以看到这几个关键的变换矩阵:
- model matrix: 从“局部空间”变换到“世界空间”
- view matrix: 从“世界空间”变换到“相机空间”
- model matrix: 从“相机空间”变换到“裁剪空间”
坐标系统
不同的图形API使用不同的系统。我们已经看到了Metal的NDC空间在z轴取值范围是0到1。你可能熟悉OpenGL,它的NDC空间,z轴取值范围是-1到1.
除了z轴有不同的取值范围,OpenGL的z轴是指向和Metal的z轴相反的方向。OpenGL的坐标系统是右手坐标系统,而Metal的坐标系统是左手坐标系统。两种坐标系统都用x指向右边,y轴指向上面。
Blender使用另一种坐标系统,z轴指向上面,y指向屏幕里面。(其实是右手坐标系的变种)。

如果我们需要坚持自己熟悉的坐标系统,也是可以的,你用哪个坐标系统其实关系不大。本书为了方便,使用Metal默认的左手坐标系统,不过我们未来依然可以改成右手系统,只要使用不同的矩阵创建方法就行了。
起始项目
更好地了解坐标系和空间后,您就可以开始创建矩阵了。
➤ 在 Xcode 中,打开本章的入门项目并构建并运行应用程序。
该项目类似于您在第 2 章 “3D 模型” 中设置的 playground,您在其中渲染了 train.usdz。
Mathlibrary.swift(位于Utility中)包含用于创建平移,缩放和旋转矩阵的Float4x4上扩展的方法。该文件还包含float2/3/4的Typealiases,因此您无需键入SIMD_FLOAT2/3/4。
Model.Swift包含模型初始化和加载代码。
Rendering.swift具有模型的扩展。您从Renderer’s draw(in:)调用Model.render(encoder:)来渲染模型。
vertexdescriptor.swift创建一个默认的MDLVertexDescriptor。默认的MDLVertexDescriptor源自此描述符。当使用Model I/O加载模型带有顶点描述符时,代码可能会有点冗长。与其创建MDLVertexDescriptor,不如创建Model I/O MDLVertexDescriptor,然后使用MTKMetalVertexDescriptorFromModelIO(_:)将其转换为管道状态对象需要的MTLVertexDescriptor。如果您检查上一章中的顶点描述符代码,则那两个顶点描述符都使用相同的过程。分别描述属性和布局。
目前,您的火车:
•占用屏幕的整个宽度。
•没有深度透视感。
•拉伸至适合应用程序窗口的大小。
您可以将火车带入其他坐标空间,从而将火车的顶点位置从窗口尺寸中分离出来。顶点函数负责把火车顶点变换到不同的空间,在这里您将执行在不同空间之间进行转换的矩阵乘法。
Uniforms
在所有顶点或片段中相同的常量值通常称为 uniform。第一步是创建一个 uniform 结构体来保存转换矩阵。之后,您将 uniform 应用于每个顶点。
Swift端的着色器和代码都将访问这些uniform值。如果要在 Renderer 中创建一个结构体,并在 Shaders.metal 中创建一个匹配的结构体,则很可能会忘记使它们保持同步。因此,最好的方法是创建一个 C++ 和 Swift 都可以访问的桥接头文件。
您现在将执行如下操作:
➤ 使用 macOS 头文件模板,在着色器组中创建一个新文件,并将其命名为 Common.h。
➤ 在 Project navigator 中,单击主 Spaces 项目文件夹。
➤ 选择项目 Spaces,然后选择顶部的 Build Settings。确保将突出显示 All 和 Combined。
➤ 在搜索栏中,键入 bridg 以过滤设置。双击 Objective-C Bridging Header 值并输入 Spaces/Shaders/Common.h。

此配置指示 Xcode 将此文件用于基于C++的 Metal Shading Language 和 Swift。
➤ 在 Common.h 中,在最终 #endif 之前,添加以下代码:
#import <simd/simd.h>
此代码导入 simd 框架,该框架提供用于处理向量和矩阵的类型和函数。
➤ 接下来,添加 uniforms 结构:
typedef struct {matrix_float4x4 modelMatrix;matrix_float4x4 viewMatrix;matrix_float4x4 projectionMatrix;
} Uniforms;
这三个矩阵(每个矩阵有 4 行和 4 列)将保存不同空间之间的必要转换。
模型矩阵
火车顶点当前位于对象空间中。要将这些顶点转换为世界空间,您将使用 modelMatrix。通过更改 modelMatrix,您将能够平移、缩放和旋转您的火车。
➤ 在 Renderer.swift 中,将新结构添加到 Renderer:
var uniforms = Uniforms()
您在 Common.h(桥接头文件)中定义了 Uniforms,因此 Swift 能够识别 Uniforms 类型。
➤ 在 init(metalView:) 底部,添加:
let translation = float4x4(translation: [0.5, -0.4, 0])
let rotation =float4x4(rotation: [0, 0, Float(45).degreesToRadians])
uniforms.modelMatrix = translation * rotation
在这里,您将使用 MathLibrary.swift 中的矩阵实用程序方法。您将 modelMatrix 设置为向右平移 0.5 个单位,向下平移 0.4 个单位,逆时针旋转 45 度。
➤ 在 draw(in:) 的 model.render(encoder: renderEncoder) 之前,添加以下内容:
renderEncoder.setVertexBytes(&uniforms,length: MemoryLayout<Uniforms>.stride,index: 11)
此代码在 Swift 端设置uniform矩阵值。
➤ 打开 Shaders.metal,并在设置命名空间后导入桥接头文件:
#import “Common.h”
➤ 将 vertex 函数更改为:
vertex VertexOut vertex_main(VertexIn in [[stage_in]],constant Uniforms &uniforms [[buffer(11)]])
{float4 position = uniforms.modelMatrix * in.position;VertexOut out {.position = position};return out;
}
在这里,您接收 Uniforms 结构体作为参数,然后将所有顶点乘以模型矩阵。
➤ 构建并运行应用程序。

在 vertex 函数中,将顶点位置乘以模型矩阵。所有顶点都旋转,然后平移。火车顶点位置仍然与屏幕的宽度有关,因此火车看起来被拉伸了。您稍后会解决这个问题。
视图矩阵
为了转换世界空间到相机空间,我们需要设置好一个视图矩阵。依赖我们想如何移动你的相机,我们可以恰当地构建视图矩阵。我们这里构建一个最简单的矩阵,可以提供给FPS(第一人称射击)类型的游戏用。
➤ 在 Renderer.swift 中 init(metalView:) 末尾,添加以下代码:
uniforms.viewMatrix = float4x4(translation: [0.8, 0, 0]).inverse
请记住,场景中的所有对象都应沿与摄像机相反的方向移动。inverse 执行相反的转换。因此,当摄像机向右移动时,世界上的所有内容似乎都会向左移动 0.8 个单位。使用此代码,您可以在世界空间中设置相机,然后添加 .inverse,这样其他物体会相对于相机进行相反的变换。
在Shaders.metal中,修改代码:
float4 position = uniforms.modelMatrix * vertexIn.position;
为:
float4 position = uniforms.viewMatrix * uniforms.modelMatrix * vertexIn.position;
构建并运行app。

火车向左移动 0.8 个单位。稍后,您将能够使用键盘在场景中导航,只需更改视图矩阵即可更新摄像机周围场景中的所有对象。
最后一个矩阵将准备顶点以从摄像机空间移动到剪辑空间。此矩阵还允许您使用单位值,而不是您一直在使用的 -1 到 1 NDC(标准化设备坐标)。为了演示为什么这样做是必要的,您将向火车添加一些动画并在 y 轴上旋转它。
➤ 打开 Renderer.swift,然后在 draw(in:) 中,就在以下代码的上方:
renderEncoder.setVertexBytes(&uniforms,length: MemoryLayout<Uniforms>.stride,index: 11)
添加如下代码:
timer += 0.005
uniforms.viewMatrix = float4x4.identity
let translationMatrix = float4x4(translation: [0, -0.6, 0])
let rotationMatrix = float4x4(rotationY: sin(timer))
uniforms.modelMatrix = translationMatrix * rotationMatrix
在这里,您将重置相机视图矩阵,并将模型矩阵替换为绕 y 轴的旋转。
➤ 构建并运行应用程序。

您可以看到,当火车旋转时,z 轴上任何大于 1.0 的顶点都会被裁剪。Metal NDC 之外的任何顶点都将被剪切。

投影
现在是时候对渲染应用一些透视来为场景提供一些深度了。
下图显示了一个 3D 场景。在右下角,您可以看到渲染的场景将如何显示。

渲染场景时,需要考虑:
- 场景的多少将要填充到屏幕,我们的眼睛的视野角度大概是200°,在这个视野角度下,我们看计算机的屏幕的宽度时大概用了70°左右。
- 我们最远能看到多远,需要定义一个远平面,计算机并不能看到无限远的事物。
- 我们最近能看到多近,需要定义一个近平面,上图上比近平面更近的老鼠是看不到的。
- 屏幕的长宽比是多少。当前,我们的火车会随着窗口的拉伸而变形。当我们实时更新长宽比时,就不会出现这个拉伸变形的问题了。
上图已经把所有概念说清楚了。我们相机能看到的空间的造型是一个截取了顶部的锥体,我们把它叫做平截头体或视锥体。任何在视锥体之外的东西都不会被渲染。
再次将渲染图像与场景设置进行比较。场景中的老鼠不会渲染,因为它位于近平面的前面。
MathLibrary.swift 提供了一种 projection 方法,该方法返回矩阵以将此视锥体内的对象投影到剪裁空间,以便转换为 NDC 坐标。
投影矩阵
打开Renderer.swift,在init(metalView:)函数的后面,创建投影矩阵:
let aspect =Float(view.bounds.width) / Float(view.bounds.height)
let projectionMatrix =float4x4(projectionFov: Float(45).degreesToRadians,near: 0.1,far: 100,aspect: aspect)
uniforms.projectionMatrix = projectionMatrix
每当视图大小发生变化时,都会调用此委托方法。由于纵横比将发生变化,因此必须重置投影矩阵。
你正在使用 45° 的视野;近平面为 0.1,远平面为 100 个单位。
➤ 在 init(metalView:) 的末尾,添加以下内容:
mtkView(metalView,drawableSizeWillChange: metalView.drawableSize)
当 metalView.autoResizeDrawable 为 true(默认值)时,每当视图大小发生变化时,视图的可绘制对象大小都会自动更新。视图创建的任何可绘制纹理都将具有此大小。
此代码可确保您在应用程序开始时设置投影矩阵。
注意:在应用程序开始时调用 mtkView(_:drawableSizeWillChange:) 在这里并不是绝对必要的。SwiftUI 视图的框架具有固定高度,但不是固定宽度,因此视图无论如何都会在 App 开始时调整大小。但是,如果您在 SwiftUI 中同时设置视图frame的宽度和高度,则视图不会调整大小,因此不会初始化投影矩阵。
➤ 在 Shaders.metal 的 vertex 函数中,将位置矩阵计算更改为:
float4 position =uniforms.projectionMatrix * uniforms.viewMatrix* uniforms.modelMatrix * in.position;
编译、运行程序:

由于投影矩阵的原因,现在z 坐标的测量方式不同,火车目前处于放大中。
➤ 在 Renderer.swift 的 draw(in:) 中,替换:
uniforms.viewMatrix = float4x4.identity
为:
uniforms.viewMatrix = float4x4(translation: [0, 0, -3]).inverse
这会将摄像机从场景中向后移动三个单位。构建并运行:

➤ 在 mtkView(_:drawableSizeWillChange:) 中,将投影矩阵的 projectionFOV 参数更改为 70°,然后构建并运行应用程序。

火车看起来更小,因为视野更宽,并且渲染场景水平方向上可以显示更多的对象。
注: 对投影值和模型变换进行一些试验。在 draw(in:) 中,将 translationMatrix 的 z 平移值设置为距离 97,这样火车的前部就可以看到了。在 z = 98 时,火车不再可见。因为投影的far值为 100 个单位,摄像机为向后 3 个(也就是-3)单位。如果将投影的 far 参数更改为 1000,则火车将再次可见。
➤ 要渲染实心火车,请在 draw(in:) 中删除:
renderEncoder.setTriangleFillMode(.lines)
透视除法
现在我们已经把我们的顶点从局部空间变换到世界空间,然后到相机空间,最后变换到裁剪空间,GPU会自动帮我们把裁剪空间变换到NDC空间(在x和y轴限制为-1到1,在z轴限制为0到1)。由于目标是把裁剪空间的所有顶点缩放到NDC空间,所以需要使用第四个分量:w。
要缩放一个点,例如(1, 2, 3),我们可以使用第四个分量:(1, 2, 3, 3)。把所有分量都除以第四分量(1/3, 2/3, 3/3, 1)。这样xyz值就缩小了。这个坐标系并称为齐次坐标系。
投影矩阵把视锥体的顶点投影到一个立方体,它的范围是-w到w。当顶点从顶点shader函数处理完毕后,GPU会执行透视除法,并且把x, y, 和z值除以它们的w值。w值越大,它在坐标系越靠后。结果是,所有可视物体的顶点都变换到了NDC中。
注:为了避免除零操作,近平面应该总是大于0。
w 值是 float4 向量方向和 float4 位置之间的主要区别。由于透视除法,位置中必须具有 w 值(通常为 1)。而向量的 w 值应该是 0,因为它不经过透视除法。
在下图中,狗和猫的身高相同 — 例如,y 值可能为 2。使用投影时,由于狗狗的位置更靠后,因此它在最终渲染中应该显得更小。

投影后,猫的 w 值可能为 ~1,而狗的 w 值可能为 ~8。除以 w 会得到猫的身高为 2,狗的身高为 1/4,这将使狗看起来更小。
NDC到屏幕
最后,GPU 将标准化坐标转换为设备屏幕大小。在职业生涯的某个时候,在标准化坐标和屏幕坐标之间进行转换时,您可能已经做过类似的事情。
要将介于 -1 和 1 之间的 Metal NDC(标准化设备坐标)转换为设备,您可以使用如下方法:
converted.x = point.x * screenWidth/2 + screenWidth/2
converted.y = point.y * screenHeight/2 + screenHeight/2
但是,您也可以使用矩阵来实现此目的,方法是缩放屏幕大小一半并平移屏幕大小的一半。此方法的明显优点是,您可以设置一次转换矩阵,然后将任何标准化点乘以该矩阵,使用如下代码将其转换为正确的屏幕空间:
converted = matrix * point
GPU 上的光栅器会为您处理矩阵计算。
重构模型矩阵
目前,您在 Renderer 中设置所有矩阵。稍后,您将创建一个 Camera 结构来计算视图和投影矩阵。
使用模型矩阵,可以移动的任何对象(例如模型或相机)都可以保持位置、旋转和缩放,而不是直接更新它。根据此信息,您可以构建模型矩阵。
➤ 创建一个名为 Transform.swift 的新 Swift 文件
➤ 添加新结构体:
struct Transform {var position: float3 = [0, 0, 0]var rotation: float3 = [0, 0, 0]var scale: Float = 1
}
此结构体将保存您可以移动的任何对象的转换信息。
➤ 添加具有计算属性的扩展:
extension Transform {var modelMatrix: matrix_float4x4 {let translation = float4x4(translation: position)let rotation = float4x4(rotation: rotation)let scale = float4x4(scaling: scale)let modelMatrix = translation * rotation * scalereturn modelMatrix
} }
此代码会自动从任何可转换对象创建模型矩阵。
➤ 添加新协议,以便您可以将对象标记为可转换:
protocol Transformable {var transform: Transform { get set }
}
➤ 因为键入 model.transform.position 有点啰嗦,所以为 Transformable 添加了一个新的扩展:
extension Transformable {var position: float3 {get { transform.position }set { transform.position = newValue }}var rotation: float3 {get { transform.rotation }set { transform.rotation = newValue }}var scale: Float {get { transform.scale }set { transform.scale = newValue }}
}
此代码提供了计算属性,允许您直接使用 model.position,并且模型的transform会根据此值更新。
➤ 打开 Model.swift,并将 Model 遵循Transformable协议。
class Model: Transformable {
➤ 将新的 transform 属性添加到 Model:
var transform = Transform()
➤ 打开 Renderer.swift,然后从 init(metalView:) 中删除:
let translation = float4x4(translation: [0.5, -0.4, 0])
let rotation =float4x4(rotation: [0, 0, Float(45).degreesToRadians])
uniforms.modelMatrix = translation * rotation
uniforms.viewMatrix = float4x4(translation: [0.8, 0, 0]).inverse
您将在 draw(in:) 中设置这些矩阵。
➤ 在 draw(in:) 中,替换:
let translationMatrix = float4x4(translation: [0, -0.6, 0])
let rotationMatrix = float4x4(rotationY: sin(timer))
uniforms.modelMatrix = translationMatrix * rotationMatrix
➤ 为:
model.position.y = -0.6
model.rotation.y = sin(timer)
uniforms.modelMatrix = model.transform.modelMatrix
➤ 构建并运行应用程序。

结果完全相同,但代码更易于阅读,并且也更容易更改模型的位置、旋转和缩放。稍后,您将此代码提取到 GameScene 中,以便 Renderer 仅用于渲染模型,而不是操纵它们。
参考
https://zhuanlan.zhihu.com/p/390450725
相关文章:
Metal 学习笔记六:坐标空间
要在网格上轻松找到一个点,您需要一个坐标系。例如,如果网格恰好是您的 iPhone 15 屏幕,则中心点可能是 x:197、y:426。但是,该点可能会有所不同,具体取决于它所处的空间。 在上一章中…...
React + TypeScript 实现 SQL 脚本生成全栈实践
React TypeScript 实现数据模型驱动 SQL 脚本生成全栈实践 引言:数据模型与 SQL 的桥梁革命 在现代化全栈开发中,数据模型与数据库的精准映射已成为提升开发效率的关键。传统手动编写 SQL 脚本的方式存在模式漂移风险高(Schema Drift&#…...
执行git操作时报错:`remote: [session-b8xxxda3] Access denied ...`解决方案
问题描述: 执行git push -u origin "master"时报错: > remote: [session-b849cda3] Access denied > fatal: unable to access https://gitee.com/jyunee/maibobo.git/: The requested URL returned error: 403表示没有权限访问远程仓库…...
brew search报错,xcrun:error:invalid active developer path CommandLineTools
问题出现的原因 出现“xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun”错误,通常是因为Xcode命令行工具未正确安装或其路径已损坏。以下是几种常见的…...
Java测试框架Mockito快速入门
Mockito结合TestNG快速入门 什么是Mockito Mockito 是一个专门用于 Java 的强大测试框架,主要用来创建和管理模拟对象,辅助开发者进行单元测试,具有以下特点和功能: 创建模拟对象:能通过简洁的语法创建类或接口的模…...
删除idea recent projects 记录
1、退出idea(一定要全部退出idea,要不然删除后,idea一退出,又保存上了) 2、进入 C:\Users\Administrator\AppData\Roaming\JetBrains\IntelliJIdea2024.1\options 目录 根据不同的版本号 IntelliJIdea2024.1 这个地方…...
16.2 LangChain 表达式语言设计哲学:重新定义大模型应用开发范式
LangChain 表达式语言设计哲学:重新定义大模型应用开发范式 关键词:LCEL 设计哲学、声明式编程范式、生产级应用架构、流式处理优化、模块化组合 1. 核心设计目标全景图 mindmap root((LCEL设计目标)) 开发效率 声明式编程 类型提示系统 自动补全支持 工程可靠性 错…...
LabVIEW 无法播放 AVI 视频的编解码器解决方案
用户在 LabVIEW 中使用示例程序 Read AVI File.vi(路径: 📌 C:\Program Files (x86)\National Instruments\LabVIEW 2019\examples\Vision\Files\Read AVI File.vi)时发现: ✅ LabVIEW 自带的 AVI 视频可正常播放 这是…...
【Java进阶】java设计模式之单例模式
一、单例设计模式的基本概念 在 Java 编程的广阔天地里,单例设计模式宛如一颗璀璨的明星,是一种极为实用的创建型设计模式。它的核心使命是确保一个类在整个应用程序的生命周期内仅仅存在一个实例,并且为外界提供一个全局唯一的访问点来获取…...
AI编程界的集大成者——通义灵码AI程序员
一、引言 随着软件行业的快速发展和技术的进步,人工智能(AI)正在成为软件开发领域的一个重要组成部分。近年来,越来越多的AI辅助工具被引入到开发流程中,旨在提高效率、减少错误并加速创新。在这样的背景下࿰…...
第三十三:6.3. 【mitt】 任意组件通讯
概述:与消息订阅与发布(pubsub)功能类似,可以实现任意组件间通信。 // 引入mitt import mitt from "mitt";// 创建emitter const emitter mitt()/*// 绑定事件emitter.on(abc,(value)>{console.log(abc事件被触发,…...
6.7 数据库设计
文章目录 数据库设计6个阶段新奥尔良法完整导图 数据库设计6个阶段 数据库设计是指,根据应用环境,构造数据库模式,建立数据库、应用系统,实现有效地数据存储,以满足用户需求。 数据库设计过程包含6个阶段 数据库规划&…...
Java 大视界 -- Java 大数据在智能安防入侵检测与行为分析中的应用(108)
💖亲爱的朋友们,热烈欢迎来到 青云交的博客!能与诸位在此相逢,我倍感荣幸。在这飞速更迭的时代,我们都渴望一方心灵净土,而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识,也…...
Vue3实现文件上传、下载及预览全流程详解(含完整接口调用)
文章目录 一、环境准备1.1 创建Vue3项目1.2 安装依赖1.3 配置Element Plus 二、文件上传实现2.1 基础上传组件2.2 自定义上传逻辑(Axios实现) 三、文件下载实现3.1 直接下载(已知文件URL)3.2 后端接口下载(二进制流&am…...
【云原生】SpringCloud-Spring Boot Starter使用测试
目录 Spring Boot Starter是什么? 以前传统的做法 使用 Spring Boot Starter 之后 starter 的理念: starter 的实现: ?创建Spring Boot Starter步骤 在idea新建一个starter项目、直接执行下一步即可生成项目。 ?在xml中加入如下配置…...
介绍下pdf打印工具类 JasperPrint
JasperPrint 工具类深度解析 JasperPrint 是 JasperReports 框架中实现 PDF 打印的核心载体类,其本质是 填充数据后的可打印报表对象,承担着从模板编译、数据填充到格式输出的全流程控制。以下从 7 个维度展开深度解析: 一、核心定位与生命周…...
idea中或pycharm中编写Markdown文件
参考 ltjt_aiseek: seek_backend_py 项目 数智科技ai探索API接口开发 1. 安装 Django 框架 在开始创建 Django 项目之前,需要先安装 Django 框架。可以通过 PyCharm 的终端或者系统的命令行工具来完成安装。 使用 PyCharm 终端安装 打开 PyCharm,如果…...
Go红队开发—并发编程
文章目录 并发编程go协程chan通道无缓冲通道有缓冲通道创建⽆缓冲和缓冲通道 等协程sync.WaitGroup同步Runtime包Gosched()Goexit() 区别 同步变量sync.Mutex互斥锁atomic原子变量 SelectTicker定时器控制并发数量核心机制 并发编程阶段练习重要的细节端口扫描股票监控 并发编程…...
使用自动化运维工具 Ansible 集中化管理服务器
一、概述 Ansible 是一款为类 Unix 系统开发的自由开源的配置和自动化工具 官方网站:https://www.ansible.com/ Ansible 成立于 2013 年,总部设在北卡罗来纳州达勒姆,联合创始人 ad Ziouani 和高级副总裁 Todd Barr都是红帽的老员工。Ansible 旗下的开源软件 Ansible 十分…...
数据集笔记:新加坡 一些交通的时间序列统计量
1 机动车年度保有量 data.gov.sg 各类机动车年度保有量 数据范围:2005年1月 - 2020年12月 1.1 数据说明 非高峰时段车辆 包括周末车(Weekend Cars)和 修订版非高峰时段车辆(Revised Off Peak Cars),该…...
企业jsapi_ticket,java举例
在企业微信开发中,使用 Java 获取 jsapi_ticket 并生成签名的步骤如下。以下是完整的 Java 示例代码。 1. 获取 jsapi_ticket 的流程 获取 access_token。 使用 access_token 获取 jsapi_ticket。 使用 jsapi_ticket 生成签名(signature)。…...
【FL0090】基于SSM和微信小程序的球馆预约系统
🧑💻博主介绍🧑💻 全网粉丝10W,CSDN全栈领域优质创作者,博客之星、掘金/知乎/b站/华为云/阿里云等平台优质作者、专注于Java、小程序/APP、python、大数据等技术领域和毕业项目实战,以及程序定制化开发…...
智能图像处理平台:图像处理配置类
这里我们先修改一下依赖,不用JavaCV,用openCV。 导入依赖: <!-- JavaCV 依赖,用于图像和视频处理 --> <!-- <dependency>--> <!-- <groupId>org.bytedeco</groupId>--> &l…...
《深度剖析:生成对抗网络中生成器与判别器的高效协作之道》
在人工智能的前沿领域,生成对抗网络(GAN)以其独特的对抗学习机制,为数据生成和处理带来了革命性的变革。生成器与判别器作为GAN的核心组件,它们之间的协作效率直接决定了GAN在图像生成、数据增强、风格迁移等众多应用中…...
【多模态大模型论文精读】MinMo语音交互大模型
写在前面:你需要一个更丝滑的语音助手 想象一下,你与一个语音助手对话,不再需要“嘿,Siri”或“小爱同学”这样的唤醒词,也不需要等待它一字一句地蹦出回复。你们可以像朋友一样,随时打断、插话,甚至同时说话。语音助手不仅能听懂你说了什么,还能理解你的语气、情感,…...
22-接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 方法一:双指针法 思路 使用两个指针 left 和 right 分别指向数组的两端,同时记录左边的最大高度 leftMax 和右边的最大高度 rig…...
如何长期保存数据(不包括云存储)最安全有效?
互联网各领域资料分享专区(不定期更新): Sheet 前言 这个问题需要考虑多个方面,比如存储介质的寿命、数据完整性、访问的便捷性,还有成本等因素。长期保存的话,存储介质的耐久性很重要。比如常见的硬盘、SSD、光盘、磁带等,各有优缺点。机械硬盘(HDD)的寿命一般在3-5年,…...
k8s拉取harbor镜像部署
在k8s中创建凭证 首先在节点docker登录harbor, 登录成功之后会在$HOME/.docker/ 生成一个config.json文件,这个就是登录凭证,后面docker pull就不需要再登录了。但是如果在k8s发布pod或者deploment时,这个凭证要在k8s中创建一个对…...
一周一个Unity小游戏2D反弹球游戏 - 球板的发球
前言 本文将实现当游戏开始时球在球板上,且不具备物理性,在Windows平台上通过点击屏幕来球发射,安卓平台上当手指触摸到屏幕上时进行发球,并此时开始具备物理性。 发球逻辑 首先在球板上创建一个球的发射点,新建一个空的游戏物体,并命名为BallPoint,并将其作为SpringBoa…...
C 语言共用体:深入理解与实践】
目录 一、引言 二、共用体的定义和基本语法 三、共用体的使用 3.1 声明共用体变量 3.2 给共用体成员赋值 3.3 共用体的内存布局 四、共用体的应用场景 4.1 节省内存空间 4.2 处理不同类型的数据 五、共用体使用的注意事项 六、总结 一、引言 在 C 语言中,共…...
