Metal 进阶: 混合

91 阅读2分钟

引言

上一篇博客中,我们学会了如何使用Metal 的深度测试来调整绘制物体的可见性。除了深度测试,混合模式(Blend)也可以调整绘制物体的可见性,我们可以通过不同的混合算法来实现不同的视觉效果。接下来,我们将绘制两个三角形,来解释混合的原理。

Blend 原理

  1. 渲染管线的顺序

    顶点着色器 → 光栅化 → 片段着色器 → 混合阶段 → 帧缓冲

  2. 混合阶段

    • 读取帧缓冲区的颜色(目标颜色)
    • 读取片段着色器输出的颜色 (源颜色)
    • 根据混合方程计算最终颜色
    • 将结果写回帧缓冲

实战

绘制背景颜色
metalView.clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
设置渲染管线
let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.vertexFunction = vertextFunc
pipelineDescriptor.fragmentFunction = fragmentFunc
pipelineDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;
//开启混合
let colorAttachment = pipelineDescriptor.colorAttachments[0]
colorAttachment?.isBlendingEnabled = true
colorAttachment?.rgbBlendOperation = .add
colorAttachment?.alphaBlendOperation = .add
colorAttachment?.sourceRGBBlendFactor = .sourceAlpha
colorAttachment?.destinationRGBBlendFactor = .oneMinusSourceAlpha
colorAttachment?.sourceAlphaBlendFactor = .one
colorAttachment?.destinationAlphaBlendFactor = .oneMinusSourceAlpha

这里使用了一个标准的透明度混合设置

  1. 颜色混合

    最终RGB = 源RGB × 源Alpha + 目标RGB × (1 - 源Alpha)

  2. 透明度混合

    最终Alpha = 源Alpha × 1 + 目标Alpha × (1 - 源Alpha)

让我来举一个实际例子来说明应用标准混合公式

假设有两个颜色:

  1. 半透明红色(源颜色):RGBA(1.0, 0.0, 0.0, 0.5)
  2. 不透明蓝色(目标颜色):RGBA(0.0, 0.0, 1.0, 1.0)

计算过程:

R = 1.0 × 0.5 + 0.0 × (1 - 0.5) = 0.5

G = 0.0 × 0.5 + 0.0 × (1 - 0.5) = 0.0

B = 0.0 × 0.5 + 1.0 × (1 - 0.5) = 0.5

A = 0.5 × 1.0 + 1.0 × (1 - 0.5) = 1.0

最终颜色:

RGBA(0.5, 0.0, 0.5, 1.0),这是一个不透明的紫色。

绘制三角形

//绘制第一个三角形
private func drawOneTriangle(commandEncoder: MTLRenderCommandEncoder?) {
    let vertices = [
        Vertext(position: [-0.5, -0.5, 0.0, 1.0], color: [1.0, 0.0, 0.0, 0.5]),
        Vertext(position: [ 0.5, -0.5, 0.0, 1.0], color: [1.0, 0.0, 0.0, 0.5]),
        Vertext(position: [ 0.0,  0.5, 0.0, 1.0], color: [1.0, 0.0, 0.0, 0.5]),
    ]
    let vertexBuffer = device.makeBuffer(bytes: vertices, length: MemoryLayout<Vertext>.stride * vertices.count)
    commandEncoder?.setVertexBuffer(vertexBuffer, offset: 0, index: 0)

    commandEncoder?.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 	vertices.count)
}
//绘制第二个三角形
private func drawAnotherTriangle(commandEncoder: MTLRenderCommandEncoder?) {
    let vertices = [
        Vertext(position: [ 0.0, -0.5, 0.0, 1.0], color: [0.0, 0.0, 1.0, 0.5]),
        Vertext(position: [ 1.0, -0.5, 0.0, 1.0], color: [0.0, 0.0, 1.0, 0.5]),
        Vertext(position: [ 0.5,  0.5, 0.0, 1.0], color: [0.0, 0.0, 1.0, 0.5]),
    ]

    let vertexBuffer = device.makeBuffer(bytes: vertices, length: MemoryLayout<Vertext>.stride * vertices.count)
    commandEncoder?.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
    commandEncoder?.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: vertices.count)
}

Shader 函数

vertex RasterizerData vertexShader(uint vertexID [[vertex_id]],
                           constant Vertex *vertices [[buffer(0)]]) {
    RasterizerData out;
    out.position = vertices[vertexID].position;
    out.color = vertices[vertexID].color;
    return out;
}
fragment float4 fragmentShader(RasterizerData in [[stage_in]]) {
    return in.color;
[}](url)

效果

  • 混合前

混合前.png

  • 混合后

Screenshot 2025-03-05 at 10.30.41.png

结语

除了深度测试,我们还可以使用混合来调整物体的可见性,它通过将源颜色和目标颜色按照特定的混合方程进行计算,实现半透明效果、粒子特效、材质叠加等视觉效果。

欢迎大家点赞、收藏。谢谢大家!

本项目已开源