Metal入门,使用Metal绘制3D图形
这次是使用Metal绘制一个立方体,并且添加旋转效果,绘制正方形的步骤很简单,我们绘制一个正方形就相当于绘制两个三角形,那么绘制一个正方体,我们很容易想到需要绘制他六个面,很显然,我们也需要把二维坐标转换为三维坐标,这些都是一些基本需要修改的。
那么再来看看核心步骤:
从渲染一个2D正方形到渲染一个3D正方体,核心的增量步骤确实主要集中在以下两个方面:
深度测试 (Depth Testing):
-
对于2D正方形:通常不需要深度测试,因为所有顶点都在同一个平面上(z=0),或者深度信息不影响最终显示。
-
对于3D正方体:深度测试变得至关重要。因为它确保了立方体离观察者较近的面能够正确地遮挡较远的面,从而产生正确的3D视觉效果。没有深度测试,你可能会看到立方体“透视”的错误效果,比如背后的面错误地显示在前面的面之上。
深度测试的步骤:
- 设置深度缓冲区格式 (
depthStencilPixelFormat
):
self.depthStencilPixelFormat = .depth32Float
这行代码在 configure()
方法中。它告诉 Metal MTKView 需要一个深度缓冲区,并且每个像素的深度值将使用32位浮点数来存储。深度缓冲区就像一张与颜色缓冲区(存储屏幕上显示的颜色)大小相同的“深度图”,它记录了每个像素距离相机的远近。
- 创建深度测试状态 (
MTLDepthStencilState
):
let depthStateDescriptor = MTLDepthStencilDescriptor()
depthStateDescriptor.depthCompareFunction = .less
depthStateDescriptor.isDepthWriteEnabled = true
depthStencilState = device?.makeDepthStencilState(descriptor: depthStateDescriptor)
这段代码也在 configure()
方法中。它创建并配置了一个 MTLDepthStencilState
对象,这个对象定义了深度测试如何进行:
depthStateDescriptor.depthCompareFunction = .less
: 这是深度比较函数。当渲染一个新的像素时,Metal会比较这个新像素的深度值和深度缓冲区中对应位置已有的深度值。如果新像素的深度值“小于”(.less
)已有的深度值(意味着新像素更靠近相机),那么新像素就会通过测试。depthStateDescriptor.isDepthWriteEnabled = true
: 这表示如果一个像素通过了深度测试,它的深度值就会被写入到深度缓冲区中,替换掉旧的深度值。
- 在渲染通道描述符中清除深度 (
descriptor.depthAttachment.clearDepth
):
descriptor.depthAttachment.clearDepth = 1.0
这行代码在 draw()
方法中。在每一帧开始渲染之前,深度缓冲区需要被清除为一个初始值。通常这个初始值设置为 1.0
,它代表最远的深度。这样,场景中的第一个被渲染的物体(只要它在裁剪范围内)的任何部分都会比这个初始值更近,从而能够被写入。
- 在渲染命令编码器中设置深度测试状态:
encoder.setDepthStencilState(depthStencilState)
这行代码也在 draw()
方法中。它将我们之前创建的 depthStencilState
应用到当前的渲染命令编码器。这意味着后续的所有绘制命令都会遵循这个深度测试规则。
深度测试的作用:
当渲染3D场景时,物体之间可能会有遮挡。例如,一个立方体的一个面可能在另一个面的前面。深度测试就是用来解决这个问题的。
工作流程如下:
- 当Metal准备渲染一个像素(或者更准确地说,一个片段)时,它会计算出这个片段的深度值(通常是其在相机Z轴上的坐标,经过变换后)。
- 它会查找深度缓冲区中该像素位置已经存储的深度值。
- 根据
depthCompareFunction
(在我们的例子中是.less
),比较新片段的深度和已存储的深度。 - 如果新片段通过了测试(即它比已有的片段更靠近相机),那么:
- 新片段的颜色会被写入颜色缓冲区(显示在屏幕上)。
- 如果
isDepthWriteEnabled
为true
,新片段的深度值会更新到深度缓冲区。
- 如果新片段没有通过测试(即它比已有的片段更远或同样远),那么这个新片段就会被丢弃,颜色缓冲区和深度缓冲区都不会被修改。
通过这种方式,深度测试确保了只有离相机最近的物体表面才会被显示出来,从而正确地处理了3D场景中的遮挡关系,使得渲染结果看起来是正确的。没有深度测试,远处的物体可能会错误地绘制在近处物体的前面。
MVP模型 (Model-View-Projection Matrices):
-
对于2D正方形:
-
模型矩阵:可能只需要进行2D平移和缩放,或者简单的2D旋转。
-
视图矩阵:通常是一个单位矩阵,或者一个简单的2D相机变换。
-
投影矩阵:最常用的是正交投影(orthographic projection),它直接将坐标映射到屏幕,没有透视效果。
-
对于3D正方体:
-
模型矩阵:需要处理3D空间中的平移、旋转(可能绕任意轴)和缩放。
-
视图矩阵:定义了3D相机的位置、观察目标和上方向,这决定了你从哪个角度和位置观察立方体。
-
投影矩阵:通常使用透视投影(perspective projection),它模拟了人眼的视觉效果,即远处的物体看起来更小,近处的物体看起来更大,这是产生3D纵深感的关键。
// 创建简单的模型视图投影矩阵private func makeModelViewProjection() -> matrix_float4x4 {// 旋转角度rotationAngle += 0.01// 创建模型矩阵 (旋转)let rotationMatrix = matrix_float4x4([cos(rotationAngle), 0, sin(rotationAngle), 0],[0, 1, 0, 0],[-sin(rotationAngle), 0, cos(rotationAngle), 0],[0, 0, 0, 1])// 创建视图矩阵let viewMatrix = matrix_float4x4([1, 0, 0, 0],[0, 1, 0, 0],[0, 0, 1, 0],[0, 0, -3, 1] // 相机位置在z=-3)// 创建透视投影矩阵let aspect = Float(bounds.width / bounds.height)let fov = Float.pi / 3.0 // 60度let near: Float = 0.1let far: Float = 100.0let yScale = 1.0 / tan(fov * 0.5)let xScale = yScale / aspectlet zRange = far - nearlet zScale = -(far + near) / zRangelet wzScale = -2.0 * far * near / zRangelet projectionMatrix = matrix_float4x4([xScale, 0, 0, 0],[0, yScale, 0, 0],[0, 0, zScale, -1],[0, 0, wzScale, 0])// 组合为一个MVP矩阵return matrix_multiply(projectionMatrix, matrix_multiply(viewMatrix, rotationMatrix))}
这个 makeModelViewProjection()
函数的作用是创建和组合三个核心的变换矩阵,最终生成一个单一的 模型-视图-投影(MVP)矩阵。这个MVP矩阵在3D图形渲染中至关重要,它负责将3D模型的顶点坐标从模型空间转换到屏幕空间,从而让我们能够在2D屏幕上看到3D效果。
让我们分解一下这个函数中创建的三个主要矩阵:
- 模型矩阵 (Model Matrix):
- 用途: 定义了3D模型自身的位置、旋转和缩放。在这个函数中,它创建了一个绕Y轴旋转的矩阵,使得立方体能够旋转。
rotationMatrix
就是这里的模型矩阵。每次调用draw()
时,rotationAngle
都会增加,导致立方体持续旋转。
- 视图矩阵 (View Matrix):
- 用途: 模拟相机的位置和朝向。它定义了我们从哪个角度观察场景。
viewMatrix
将相机放置在Z轴的-3位置([0, 0, -3, 1]
),意味着相机在原点后方,朝向原点。
- 投影矩阵 (Projection Matrix):
- 用途: 将3D场景投影到2D屏幕上,并创建透视效果(远处的物体看起来更小)。
projectionMatrix
在这里是一个透视投影矩阵。它考虑了以下因素:aspect
: 视图的宽高比,确保物体不会变形。fov
: 视野角度(Field of View),决定了相机能看到的范围。near
和far
: 近裁剪面和远裁剪面,定义了可见的深度范围。
最终组合:
函数最后通过 matrix_multiply
将这三个矩阵组合起来:
projectionMatrix * viewMatrix * rotationMatrix
(即 P * V * M
)
这个顺序非常重要:
- 首先,模型矩阵 (
rotationMatrix
) 应用于模型的顶点,将其从模型空间转换到世界空间(在世界坐标系中的位置和方向)。 - 然后,视图矩阵 (
viewMatrix
) 应用于世界空间的顶点,将其转换到相机空间(相对于相机的位置和方向)。 - 最后,投影矩阵 (
projectionMatrix
) 应用于相机空间的顶点,将其转换到裁剪空间,并最终映射到屏幕上的2D坐标。
总结:
makeModelViewProjection()
函数的核心作用是计算出这个最终的MVP矩阵。这个矩阵随后会被传递到顶点着色器中,顶点着色器会用它来变换立方体的每一个顶点,从而实现在屏幕上正确渲染出带有旋转和透视效果的3D立方体。没有这个函数和它生成的MVP矩阵,我们就无法将3D模型正确地显示在2D屏幕上。
import MetalKit
import simdstruct Constants {var animateBy: Float = 0.0var modelMatrix: matrix_float4x4 = matrix_identity_float4x4var viewMatrix: matrix_float4x4 = matrix_identity_float4x4var projectionMatrix: matrix_float4x4 = matrix_identity_float4x4
}struct Vertex {var position: SIMD4<Float>var color: SIMD4<Float>init(position: SIMD4<Float>, color: SIMD4<Float>) {self.position = positionself.color = color}
}class MetalView: MTKView {private var commandQueue: MTLCommandQueue!private var pipelineState: MTLRenderPipelineState!private var vertexBuffer: MTLBuffer!private var indexBuffer: MTLBuffer!private var constants = Constants()private var depthStencilState: MTLDepthStencilState?private var rotationAngle: Float = 0.0// 立方体顶点数据let cubeVertices = [// 前面 (z = 1.0)Vertex(position: SIMD4<Float>(-0.5, -0.5, 0.5, 1.0), color: SIMD4<Float>(1, 0, 0, 1)),Vertex(position: SIMD4<Float>( 0.5, -0.5, 0.5, 1.0), color: SIMD4<Float>(0, 1, 0, 1)),Vertex(position: SIMD4<Float>( 0.5, 0.5, 0.5, 1.0), color: SIMD4<Float>(0, 0, 1, 1)),Vertex(position: SIMD4<Float>(-0.5, 0.5, 0.5, 1.0), color: SIMD4<Float>(1, 1, 0, 1)),// 后面 (z = -0.5)Vertex(position: SIMD4<Float>(-0.5, -0.5, -0.5, 1.0), color: SIMD4<Float>(0, 0, 1, 1)),Vertex(position: SIMD4<Float>( 0.5, -0.5, -0.5, 1.0), color: SIMD4<Float>(1, 1, 1, 1)),Vertex(position: SIMD4<Float>( 0.5, 0.5, -0.5, 1.0), color: SIMD4<Float>(1, 0, 0, 1)),Vertex(position: SIMD4<Float>(-0.5, 0.5, -0.5, 1.0), color: SIMD4<Float>(0, 1, 0, 1))]// 索引数据 - 每个面由两个三角形组成let indices: [UInt16] = [// 前面0, 1, 2,2, 3, 0,// 右面1, 5, 6,6, 2, 1,// 上面3, 2, 6,6, 7, 3,// 下面4, 5, 1,1, 0, 4,// 左面4, 0, 3,3, 7, 4,// 后面5, 4, 7,7, 6, 5]override init(frame: CGRect, device: MTLDevice?) {super.init(frame: frame, device: device)self.device = device ?? MTLCreateSystemDefaultDevice()configure()}required init(coder: NSCoder) {fatalError("init(coder:) has not been implemented")}private func configure() {// 设置刷新率和渲染控制self.colorPixelFormat = .bgra8Unormself.isPaused = falseself.enableSetNeedsDisplay = falseself.preferredFramesPerSecond = 60// 设置深度缓冲区self.depthStencilPixelFormat = .depth32Float// 创建命令队列commandQueue = device?.makeCommandQueue()// 创建顶点缓冲区let vertexSize = MemoryLayout<Vertex>.stridelet vertexCount = cubeVertices.countvertexBuffer = device?.makeBuffer(bytes: cubeVertices, length: vertexCount * vertexSize, options: [])// 创建索引缓冲区let indexSize = MemoryLayout<UInt16>.sizelet indexCount = indices.countindexBuffer = device?.makeBuffer(bytes: indices, length: indexCount * indexSize,options: [])// 创建渲染管线guard let library = device?.makeDefaultLibrary(),let vertexFunc = library.makeFunction(name: "vertex_func"),let fragFunc = library.makeFunction(name: "fragment_func") else {fatalError("Failed to load shaders.")}let pipelineDescriptor = MTLRenderPipelineDescriptor()pipelineDescriptor.vertexFunction = vertexFunc pipelineDescriptor.fragmentFunction = fragFunc pipelineDescriptor.colorAttachments[0].pixelFormat = self.colorPixelFormat pipelineDescriptor.depthAttachmentPixelFormat = self.depthStencilPixelFormatdo {pipelineState = try device?.makeRenderPipelineState(descriptor: pipelineDescriptor)} catch {fatalError("Unable to create pipeline state: \(error)")}// 创建深度测试状态let depthStateDescriptor = MTLDepthStencilDescriptor()depthStateDescriptor.depthCompareFunction = .lessdepthStateDescriptor.isDepthWriteEnabled = truedepthStencilState = device?.makeDepthStencilState(descriptor: depthStateDescriptor)print("Metal初始化完成")}// 创建简单的模型视图投影矩阵private func makeModelViewProjection() -> matrix_float4x4 {// 旋转角度rotationAngle += 0.01// 创建模型矩阵 (旋转)let rotationMatrix = matrix_float4x4([cos(rotationAngle), 0, sin(rotationAngle), 0],[0, 1, 0, 0],[-sin(rotationAngle), 0, cos(rotationAngle), 0],[0, 0, 0, 1])// 创建视图矩阵let viewMatrix = matrix_float4x4([1, 0, 0, 0],[0, 1, 0, 0],[0, 0, 1, 0],[0, 0, -3, 1] // 相机位置在z=-3)// 创建透视投影矩阵let aspect = Float(bounds.width / bounds.height)let fov = Float.pi / 3.0 // 60度let near: Float = 0.1let far: Float = 100.0let yScale = 1.0 / tan(fov * 0.5)let xScale = yScale / aspectlet zRange = far - nearlet zScale = -(far + near) / zRangelet wzScale = -2.0 * far * near / zRangelet projectionMatrix = matrix_float4x4([xScale, 0, 0, 0],[0, yScale, 0, 0],[0, 0, zScale, -1],[0, 0, wzScale, 0])// 组合为一个MVP矩阵return matrix_multiply(projectionMatrix, matrix_multiply(viewMatrix, rotationMatrix))}override func draw(_ rect: CGRect) {guard let commandBuffer = commandQueue.makeCommandBuffer(),let drawable = currentDrawable,let descriptor = currentRenderPassDescriptor else {return}// 计算MVP矩阵let mvpMatrix = makeModelViewProjection()// 更新常量缓冲区constants.modelMatrix = mvpMatrix// 设置清除颜色和深度descriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.2, 0.3, 0.3, 1.0)descriptor.depthAttachment.clearDepth = 1.0guard let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else {return}encoder.setRenderPipelineState(pipelineState)encoder.setDepthStencilState(depthStencilState)// 绘制立方体encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)encoder.setVertexBytes(&constants, length: MemoryLayout<Constants>.stride, index: 1)// 使用索引绘制encoder.drawIndexedPrimitives(type: .triangle,indexCount: indices.count,indexType: .uint16,indexBuffer: indexBuffer,indexBufferOffset: 0)encoder.endEncoding()commandBuffer.present(drawable)commandBuffer.commit()}
}
#include <metal_stdlib>using namespace metal;// 常量缓冲区
struct Constants {float animate_by;float4x4 modelMatrix; // 现在这个是MVP矩阵float4x4 viewMatrix; float4x4 projectionMatrix;
};// 顶点结构体 - 需要与Swift中的定义匹配
struct Vertex {float4 position;float4 color;
};// 顶点着色器输出/片段着色器输入结构体
struct VertexOut {float4 position [[position]];float4 color;
};vertex VertexOut vertex_func(device const Vertex* vertices [[buffer(0)]],constant Constants &constants [[buffer(1)]],uint vid [[vertex_id]])
{VertexOut out;// 使用MVP矩阵变换顶点out.position = constants.modelMatrix * vertices[vid].position;// 传递顶点颜色out.color = vertices[vid].color;return out;
}fragment float4 fragment_func(VertexOut in [[stage_in]]) {// 使用插值后的顶点颜色return in.color;
}
相关文章:
Metal入门,使用Metal绘制3D图形
这次是使用Metal绘制一个立方体,并且添加旋转效果,绘制正方形的步骤很简单,我们绘制一个正方形就相当于绘制两个三角形,那么绘制一个正方体,我们很容易想到需要绘制他六个面,很显然,我们也需要把…...
Java 04 API
API 简介 一些已经写好的应用程序编程接口Object toString 默认返回的是当前对象在堆内存中的地址值信息:类的全类名十六进制哈希值返回该对象的返回值 class A{ } //返回的是地址哦 String sA.toString(); //细节:使用打印语句,打印对象…...
基于Gitee 的开发分支版本管理规范
一、版本管理规范概述 目的:规范代码分支管理和版本发布流程,提高团队协作效率,确保代码质量和版本可追溯性。适用范围:基于 Gitee 平台开发的所有项目。分支策略:采用 Git Flow 模型的变体,主要分支包括 …...
HOW - 结合 AI 进行 Tailwind 样式开发
文章目录 情况 1:使用 Tailwind CSS 与手写传统 CSS 的开发效率对比情况 2:AI Tailwind 自动生成 UI 的效率如何?总结 在 WHAT - Tailwind 样式方案(不写任何自定义样式) 中我们已经简单介绍过 Tailwind。今天主要认识…...
系统数据对接-从获取到处理的全流程
在后端架构的复杂生态中,子系统间或与外部系统的对接是常态,其核心要义在于实现数据的精准传输。本文聚焦于数据传输后的运算逻辑与异常处理机制,旨在为后端开发者提供深度见解。 一、数据获取机制:触发式与定时任务的权衡 &…...
Java 09Stream流与File类
Stream流与File类 Stream流 简化集合和数组的操作,startWith(“张”) 第一个为这个返回true String1.获取Stream对象 单列集合 双列集合 先获得键值对 在遍历数组 零散的数据 Stream<Integer> arrStream.of(1,2,34,3); stream.forEach(sss); 即可2.中间…...
《光与影:33号远征队》栩栩如生的角色动画是如何创建的?
《光与影:33号远征队》是一款由Sandfall Interactive公司开发的回合制RPG游戏,背景是一个黑暗的幻想世界。游戏因其独特的艺术风格和引人注目的叙事赢得了无数赞誉,成为今年大热游戏中的一匹黑马。 在该游戏制作中Sandfall依靠包括Xsens在内的…...
GESP2024年12月认证C++二级( 第三部分编程题(1)寻找数字)
参考程序(枚举): #include <iostream> //#include <cmath> using namespace std;int main() {int t;cin >> t;while (t--) {long long a;cin >> a;bool found false;// 枚举 b for (long long b 1; b * b * b * b…...
《探索具身智能机器人视觉-运动映射模型的创新训练路径》
视觉 - 运动映射模型作为实现智能交互与精准行动的核心,吸引着全球科研人员与技术爱好者的目光。这一模型就像机器人的 “神经中枢”,连接着视觉感知与肢体运动,使机器人能够在复杂的现实环境中灵活应对各种任务。 传统的视觉 - 运动映射模型…...
Python打卡DAY31
今日的示例代码包含2个部分 notebook文件夹内的ipynb文件,介绍下今天的思路项目文件夹中其他部分:拆分后的信贷项目,学习下如何拆分的,未来你看到的很多大项目都是类似的拆分方法 知识点回顾 规范的文件命名规范的文件夹管理机器学…...
【SPIN】PROMELA远程引用与控制流验证(SPIN学习系列--5)
PROMELA语言提供了两种强大的机制用于验证并发系统:远程引用(remote references)和进程变量引用。这些机制使得在不引入额外状态变量的情况下,能够精确描述系统状态和属性。 远程引用(Remote References) 远程引用允许你直接引用进程中的控制位置(labe…...
GMSL:汽车里的音视频传输
参考链接: blog.csdn.net/weixin_50875614/article/details/119995651 blog.csdn.net/syjie19900426/article/details/145269782 SerDes 应用场景 WHAT GMSL是什么 GMSL(Gigabit Multimedia Serial Links),中文名称为千兆多媒体串行链路,是Maxim公司推出的一种…...
Java并发进阶系列:深度讨论jdk1.8 ConcurrentHashMap并发环境下transfer方法桶位分配过程
在前面有多篇关于jdk1.8的ConcurrentHashMap研究是基于源代码给出的深度分析,要知道多线程环境下的ConcurrentHashMap内部运行机制是相对复杂的,好在IDEA提供的相关断点和Debug功能确实好用,使得多线程调试起来直观,通过这种方式能…...
【深度学习-Day 14】从零搭建你的第一个神经网络:多层感知器(MLP)详解
Langchain系列文章目录 01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南 02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖 03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南 04-玩转 LangChai…...
fdisk和parted的区别
在Linux系统中,fdisk和parted是两种常用的分区工具。虽然它们都可以对硬盘进行分区,但在功能和适用范围上有显著的区别。 fdisk fdisk主要用于MBR(主引导记录)分区表的管理。MBR分区表有以下特点: 支持小于2TB的硬盘…...
springMVC拦截器,拦截器拦截策略设置
目录 1、MyInterceptor1 2、UserController 3、MvcConfig,拦截器4种拦截方法策略 做请求的校验,如果校验没有通过,直接返回,原来下面的处理,就不用处理了 将request进行拦截校验 将response进行拦截校验 preHandle…...
如何测试北斗卫星通讯终端的性能?
测试北斗卫星通讯终端的性能需从功能、性能、环境适应性、可靠性等多维度展开,以下是具体测试内容与方法: 一、基础功能测试 验证终端是否满足北斗系统的核心通讯功能。 (1)通信模式测试 短报文通信 测试终端发送 / 接收短报…...
基于MakeReal3D的虚拟预装系统:飞机装配效率与精度的双重突破
在航空制造领域,飞机部件的对接装配是飞机制造过程中的关键环节。传统的部件装配方式高度依赖操作人员的经验和反复调整,调姿过程耗时较长,且难以保证每次装配都能达到最优状态。随着虚拟现实技术的成熟,虚拟装配技术作为一种新兴…...
IP54是什么?
IP54是什么 定义 IP54是一种国际标准,用来指示设备的防护等级,该标准由国际电工委员会(IEC)制定,并在许多领域广泛使用13。IP是Ingress Protection的缩写,IP等级是针对电气设备外壳对异物侵入的防护等级。…...
Python异步编程详解
Python异步编程详解 引言 异步编程是Python中处理并发操作的重要方式,它允许程序在等待I/O操作时执行其他任务,从而提高程序的整体效率。本文将详细介绍Python异步编程的概念、实现方式以及实际应用场景。 1. 异步编程基础 1.1 什么是异步编程&#x…...
AUC与Accuracy的区别
下面分别解释下这两句话的含义及其原因,并说明 AUC 与 Accuracy(准确率)的区别: AUC 是阈值无关的指标 • 含义:在二分类问题中,模型通常会输出一个概率值或打分,需要设定一个阈值来将这些概…...
差分数组:原理与应用
一、什么是差分数组 差分数组是一种高效处理区间更新操作的数据结构技巧,特别适用于需要对数组的某个区间进行频繁增减操作的场景。差分数组的核心思想是通过存储相邻元素的差值而非元素本身,将区间操作转化为端点操作,从而将时间复杂度从O(…...
一些C++入门基础
关键字 图引自 C 关键词 - cppreference.com 命名空间 命名空间解决了C没办法解决的各类命名冲突问题 C的标准命名空间:std 命名空间中可以定义变量、函数、类型: namespace CS {//变量char cs408[] "DS,OS,JW,JZ";int cs 408;//函数vo…...
免费插件集-illustrator插件-Ai插件-路径尖角圆角化
文章目录 1.介绍2.安装3.通过窗口>扩展>知了插件4.功能解释5.总结 1.介绍 本文介绍一款免费插件,加强illustrator使用人员工作效率,实现图形编辑中路径尖角圆角化。首先从下载网址下载这款插件https://download.csdn.net/download/m0_67316550/87…...
数据分析_商务运营考核指标体系搭建
以抖音电商中的小学教辅书籍业务为例,搭建对接达人的商务运营团队能力考核指标体系,涵盖达人筛选、合作管理、效果追踪和长期价值维护等核心环节,结合教育产品特性和商务运营目标,设计分层量化指标: 一、考核目标 围绕…...
基于Java的校运会管理系统【附源码】
湄洲湾职业技术学院 毕业设计(论文) 课题名称: 系 别: 专 业: 年 级: 姓 名: 学 号: 指导教师: 摘 要 用传统的方式来管理信息,一是耗时较长,二是…...
保证数据库 + redis在读写分离场景中事务的一致性
在 Spring Boot 中实现数据库与 Redis 的一致性,特别是处理读写分离时,确保数据修改的事务一致性是一个常见的挑战。因为 Redis 是一个内存数据库,通常用于缓存,而关系型数据库是持久化存储,两者之间的数据同步和一致性…...
【Redis】跳表结构
目录 1、背景2、跳表【1】底层结构【2】关键操作【3】redis使用跳表原因【4】特性 1、背景 redis中的跳表是一种有序数据结构,主要用于实现有序集合(zset)。跳表通过多级索引实现高效查找(平均O(logN)时间复杂度)&…...
Semaphore解决高并发场景下的有限资源的并发访问问题
在高并发编程的领域中,我们常常面临着对有限资源的激烈抢夺问题。而 Java 的 java.util.concurrent 包提供的 Semaphore ,为我们提供了精准控制对有限资源并发访问的强大能力。 一、Semaphore? Semaphore,直译为 “信号量”&#…...
医学影像辅助诊断系统开发教程-基于tensorflow实现
源码下载地址: https://download.csdn.net/download/shangjg03/90873910 1. 简介 医学影像辅助诊断系统是利用计算机视觉和深度学习技术,帮助医生分析医学影像(如X光、CT、MRI等)并提供诊断建议的系统。本教程将指导你开发一个基于深度学习的胸部X光肺炎检测系统。 2. 准备…...
手动导出Docker进行并自动执行脚本命令的操作方法
若你已在 Docker 镜像里手动封装好文件,想让容器启动时自动执行 start.sh 脚本,可按以下步骤操作将镜像导出,同时确保启动时能自动执行脚本。 1. 提交当前容器为新镜像 假设你是在某个运行中的容器里进行文件封装操作的,要先把这个容器的当前状态提交为一个新的 Docker 镜…...
Mysql 中的日期时间函数汇总
前言 在 MySQL 中,处理日期和时间是非常常见的需求,MySQL中内置了大量的日期和时间函数,能够灵活、方便地处理日期和时间数据,本节就简单介绍一下 MySQL中内置的日期和时间函数,以便更好地利用这些函数来处理日期和时间…...
RabbitMQ Topic RPC
Topics(通配符模式) Topics 和Routing模式的区别是: topics 模式使⽤的交换机类型为topic(Routing模式使⽤的交换机类型为direct)topic 类型的交换机在匹配规则上进⾏了扩展, Binding Key⽀持通配符匹配(direct类型的交换机路 由规则是BindingKey和RoutingKey完全匹配) 在top…...
Conda环境管理:确保Python项目精准复现
探讨如何使用 Conda 有效地管理项目依赖,确保你的 Python 环境可以被精确复制和轻松共享 为什么依赖管理如此重要? 在开始具体操作之前,我们先来理解一下为什么环境依赖管理至关重要: 可复现性 (Reproducibility):无…...
基于PyTorch的医学影像辅助诊断系统开发教程
本文源码地址: https://download.csdn.net/download/shangjg03/90873921 1. 简介 本教程将指导你使用PyTorch开发一个完整的医学影像辅助诊断系统,专注于胸部X光片的肺炎检测。我们将从环境搭建开始,逐步介绍数据处理、模型构建、训练、评估以及最终的系统部署。...
Vue3——Pinia
目录 什么是 Pinia? 为什么选择 Pinia? 基本使用 安装pinia 配置pinia 定义store 使用 持久化插件 什么是 Pinia? Pinia 是一个轻量级的状态管理库,专为 Vue 3 设计。它提供了类似 Vuex 的功能,但 API 更加简…...
Java中Collections工具类中常用方法详解
文章从工具类的概述、常用方法的作用、实现原理到使用注意事项,都进行了详细说明,供你参考。 Java中Collections工具类中常用方法详解 在Java开发中,集合是存储和处理数据的重要容器,而java.util.Collections工具类则提供了一组静…...
面经总目录——持续更新中
说明 本面经总结了校招时我面试各个公司的面试题目,每场面试后我都及时进行了总结,同时后期补充扩展了同类型的相近面试题,校招时从两个方向进行投递,视觉算法工程师和软件开发工程师(C方向),所…...
电力设备智能化方案复盘
本文针对公司在售的一款电力设备智能化方案的运营情况进行复盘分析,提出一些基于研发人员角度的看法及建议,欢迎大家交流,因本人经验有限,多多包涵。具体的产品用途和公司名称不方便透露。 1.背景 本方案是针对电网配电侧中某关键…...
Rocketmq刷盘机制和复制机制区别及关系
在RocketMQ中,刷盘机制和复制机制是两种不同但相互协作的机制,分别解决数据持久化和数据高可用的问题。它们的核心区别与关系如下: 一、刷盘机制(Flush Disk) 目标:解决单机数据持久化问题,确保…...
HTB 赛季8靶场 - Puppy
nmap扫描全端口 Nmap scan report for 10.129.243.117 Host is up, received echo-reply ttl 127 (0.47s latency). Scanned at 2025-05-18 21:12:56 EDT for 551s Not shown: 65512 filtered tcp ports (no-response) Bug in iscsi-info: no string output. PORT STATE …...
频分复用信号在信道中的状态
频分复用是一种将信道总带宽划分为多个互不重叠的子频带,每路信号占用一个子频带以实现多路信号并行传输的复用技术。 1、基本概念和原理 频分复用(Frequency Division Multiplexing, FDM)的核心思想是通过频率划分实现多路信号共享同一物理…...
CSS之box-sizing、图片模糊、计算盒子宽度clac、(重点含小米、进度条案例)过渡
一、Box-sizing 在使用盒子模型时往往会出现由于border\ padding设置过大,从而导致的盒子被撑大的情况。 此时可以设置box-sizing: border-box (padding和boeder加起来设置的值不可超出width) 此时不会撑大盒子。可在初始化时一起设置 * { padding:0; maigin:…...
AliSQL:阿里巴巴开源数据库的技术革新与应用实践
精心整理了最新的面试资料和简历模板,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 引言 在数据驱动的互联网时代,高性能、高可靠的数据库系统是支撑企业核心业务的关键。AliSQL作为阿里巴巴集团基于MySQL深度定制的开源分支&…...
(二十四)Java网络编程全面解析:从基础到实践
一、网络编程概述 网络编程是现代软件开发中不可或缺的重要组成部分,它使得不同计算机上的程序能够相互通信和数据交换。Java作为一门成熟的编程语言,从最初版本就提供了强大的网络编程支持,使得开发者能够相对轻松地构建网络应用程序。 1.…...
50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | Rotating Navigation (旋转导航)
📅 我们继续 50 个小项目挑战!—— Rotating Navigation 组件 仓库地址:🔗https://github.com/SunACong/50-vue-projects 项目预览地址:🔗https://50-vue-projects.vercel.app/ ✨ 组件目标 🌀…...
OpenCV 第6课 图像处理之几何变换(重映射)
1. 概述 简单来说,重映射就是把一副图像内的像素点按照规则映射到到另外一幅图像内的对应位置上去,形成一张新的图像。 因为原图像与目标图像的像素坐标不是一一对应的。一般情况下,我们通过重映射来表达每个像素的位置(x,y),像这样: g(x,y)=f(h(x,y)) 在这里g()是目标图…...
传输层协议:UDP和TCP
1.传输层概念 传输层主要负责两台主机之间的数据传输,使数据从发送端到接收端。 端口号 端口号标识了一个主机上进行通信的不同的应用程序。 传输层接收到数据后,是要给到具体的应用层的进程。所以发送端的传输层封装报文时,就要添加上将来…...
[Linux] Linux线程信号的原理与应用
Linux线程信号的原理与应用 文章目录 Linux线程信号的原理与应用**关键词****第一章 理论综述****第二章 研究方法**1. **实验设计**1.1 构建多线程测试环境1.2 信号掩码策略对比实验 2. **数据来源**2.1 内核源码分析2.2 用户态API调用日志与性能监控 **第三章 Linux信号的用法…...
MySQL 库的操作 -- 字符集和校验规则,库的增删查改,数据库的备份和还原
目录 1. 字符集和校验规则 1.1 查看系统默认字符集以及检验规则 1.2 查看数据库支持的字符集 1.3 查看数据库支持的字符集检验规则 1.4 校验规则对数据库的影响 2. 数据库的操作 2.1 创建数据库 2.1.1 语法 2.1.2 示例 2.2 查看数据库 2.2.1 语法 2.2.2 查看当前使…...