Article / 2026/05/29

MTLPixelFormat 完整格式指南

MTLPixelFormat 定义了 Metal 中纹理和渲染目标的像素数据格式。选择正确的像素格式对性能、内存使用和视觉质量至关重要。本指南涵盖所有可用格式及其使用场景。

🚀 MTLPixelFormat 完整格式指南

概述

MTLPixelFormat 定义了 Metal 中纹理和渲染目标的像素数据格式。选择正确的像素格式对性能、内存使用和视觉质量至关重要。本指南涵盖所有可用格式及其使用场景。

格式命名规范

Metal 像素格式遵循特定的命名约定:

[通道][位数][数据类型][色彩空间]
  • 通道:r, rg, rgb, rgba, bgra, depth, stencil 等
  • 位数:8, 16, 32, 64 等
  • 数据类型:Unorm, Snorm, Uint, Sint, Float
  • 色彩空间:srgb(可选)

数据类型说明

  • Unorm:Unsigned Normalized(无符号归一化)- 范围 [0.0, 1.0]
  • Snorm:Signed Normalized(有符号归一化)- 范围 [-1.0, 1.0]
  • Uint:Unsigned Integer(无符号整数)
  • Sint:Signed Integer(有符号整数)
  • Float:浮点数

单通道格式 (Single Channel)

8位格式

// 8位无符号归一化 (0-255 → 0.0-1.0)
.r8Unorm
// 使用场景:灰度图像、遮罩、高度图
// 内存:1 字节/像素
// 示例:阴影遮罩、透明度通道

.r8Snorm  
// 使用场景:有符号数据,如法线分量
// 内存:1 字节/像素
// 示例:压缩法线贴图的单个分量

.r8Uint
// 使用场景:索引数据、标识符
// 内存:1 字节/像素
// 示例:材质ID、对象ID缓冲

.r8Sint
// 使用场景:有符号整数数据
// 内存:1 字节/像素
// 示例:位移数据、偏移量

16位格式

.r16Unorm
// 使用场景:高精度灰度、深度数据
// 内存:2 字节/像素  
// 示例:16位深度缓冲、高精度高度图

.r16Snorm
// 使用场景:高精度有符号数据
// 内存:2 字节/像素
// 示例:位移贴图、法线分量

.r16Uint
// 使用场景:大范围整数索引
// 内存:2 字节/像素
// 示例:大型场景的对象ID

.r16Sint  
// 使用场景:有符号整数数据
// 内存:2 字节/像素

.r16Float
// 使用场景:半精度浮点数据
// 内存:2 字节/像素
// 示例:HDR 亮度、距离场

32位格式

.r32Uint
// 使用场景:大范围无符号整数
// 内存:4 字节/像素
// 示例:原子计数器、大型索引

.r32Sint
// 使用场景:大范围有符号整数  
// 内存:4 字节/像素

.r32Float
// 使用场景:全精度浮点数据
// 内存:4 字节/像素
// 示例:精确距离、物理模拟数据

双通道格式 (Dual Channel)

8位双通道

.rg8Unorm
// 使用场景:双分量数据、压缩法线贴图
// 内存:2 字节/像素
// 示例:切线空间法线贴图的XY分量

.rg8Snorm
// 使用场景:有符号双分量数据
// 内存:2 字节/像素  
// 示例:法线贴图、向量场

.rg8Uint
.rg8Sint
// 使用场景:整数双分量数据
// 内存:2 字节/像素

16位双通道

.rg16Unorm
.rg16Snorm  
.rg16Uint
.rg16Sint
.rg16Float
// 使用场景:高精度双分量数据
// 内存:4 字节/像素
// 示例:高精度法线贴图、速度缓冲

32位双通道

.rg32Uint
.rg32Sint
.rg32Float
// 使用场景:全精度双分量数据
// 内存:8 字节/像素
// 示例:GPU粒子位置、精确向量数据

四通道格式 (Quad Channel)

8位四通道 - 最常用的格式

.rgba8Unorm
// 使用场景:标准颜色纹理
// 内存:4 字节/像素
// 通道顺序:R-G-B-A
// 示例:普通纹理、UI元素

.rgba8Unorm_srgb  
// 使用场景:sRGB色彩空间的颜色纹理
// 内存:4 字节/像素
// 自动gamma校正
// 示例:照片、UI纹理(推荐用于显示)

.bgra8Unorm
// 使用场景:iOS设备优化格式
// 内存:4 字节/像素  
// 通道顺序:B-G-R-A
// 示例:帧缓冲、iOS原生格式

.bgra8Unorm_srgb
// 使用场景:iOS设备的sRGB显示(推荐)
// 内存:4 字节/像素
// 最佳性能 + 正确色彩
// 示例:iOS应用的主帧缓冲

.rgba8Snorm
// 使用场景:有符号四分量数据
// 内存:4 字节/像素
// 示例:法线贴图、切线贴图

.rgba8Uint
.rgba8Sint  
// 使用场景:整数四分量数据
// 内存:4 字节/像素
// 示例:多重索引、打包数据

16位四通道

.rgba16Unorm
// 使用场景:高精度颜色
// 内存:8 字节/像素
// 示例:HDR纹理、精确颜色渐变

.rgba16Snorm
// 使用场景:高精度有符号数据
// 内存:8 字节/像素

.rgba16Uint  
.rgba16Sint
// 使用场景:高精度整数数据
// 内存:8 字节/像素

.rgba16Float
// 使用场景:半精度HDR颜色
// 内存:8 字节/像素
// 示例:HDR渲染管线、光照计算

32位四通道

.rgba32Uint
.rgba32Sint
// 使用场景:全精度整数数据
// 内存:16 字节/像素

.rgba32Float  
// 使用场景:全精度浮点颜色
// 内存:16 字节/像素
// 示例:高精度HDR、科学可视化

特殊格式

深度格式

.depth16Unorm
// 使用场景:移动设备深度缓冲
// 内存:2 字节/像素
// 深度范围:0.0-1.0
// 示例:移动游戏、简单场景

.depth32Float
// 使用场景:桌面设备深度缓冲  
// 内存:4 字节/像素
// 更高精度,减少z-fighting
// 示例:复杂3D场景、精确深度测试

模板格式

.stencil8
// 使用场景:模板测试
// 内存:1 字节/像素
// 8位模板值
// 示例:阴影体、轮廓渲染

深度-模板组合格式

.depth24Unorm_stencil8
// 使用场景:传统深度+模板
// 内存:4 字节/像素
// 24位深度 + 8位模板

.depth32Float_stencil8
// 使用场景:高精度深度+模板
// 内存:8 字节/像素  
// 32位浮点深度 + 8位模板
// 示例:现代渲染管线

压缩格式

// PVRTC 格式(iOS专用)
.pvrtc_rgb_2bpp
.pvrtc_rgb_4bpp
.pvrtc_rgba_2bpp  
.pvrtc_rgba_4bpp
// 使用场景:移动设备纹理压缩
// 显著减少内存占用
// 示例:移动游戏资源

// ETC2 格式(Android/OpenGL ES兼容)
.etc2_rgb8
.etc2_rgb8_srgb
.etc2_rgb8a1
.etc2_rgba8
// 使用场景:跨平台纹理压缩

// ASTC 格式(现代压缩,质量最佳)
.astc_4x4_srgb
.astc_5x4_srgb
.astc_6x5_srgb
.astc_8x8_srgb
// 使用场景:高质量纹理压缩
// 可调节压缩率vs质量

YUV格式(视频)

.gbgr422
.bgrg422
// 使用场景:视频纹理
// YUV 4:2:2 采样
// 示例:视频播放、相机输入

格式选择指南

常见使用场景对应的最佳格式

// 1. iOS应用主帧缓冲
.bgra8Unorm_srgb  // 🏆 最佳选择

// 2. 标准纹理(照片、UI)
.rgba8Unorm_srgb  // 或 .bgra8Unorm_srgb(iOS)

// 3. HDR渲染
.rgba16Float      // 中间渲染目标
.rgba32Float      // 高精度需求

// 4. 深度缓冲
.depth32Float     // 桌面端
.depth16Unorm     // 移动端

// 5. 法线贴图
.rg8Snorm         // 压缩版本(重构Z分量)
.rgba8Snorm       // 完整版本

// 6. 遮罩/灰度图
.r8Unorm          // 单通道足够

// 7. 移动设备纹理
.pvrtc_rgba_4bpp  // iOS压缩
.astc_4x4_srgb    // 现代压缩

性能对比

iOS设备性能排序

// 渲染目标性能(从快到慢)
.bgra8Unorm_srgb    // 🚀 原生格式
.bgra8Unorm         // 🚀 原生格式  
.rgba8Unorm_srgb    // ⚡ 需要通道重排
.rgba8Unorm         // ⚡ 需要通道重排
.rgba16Float        // 🐌 带宽需求高
.rgba32Float        // 🐌🐌 带宽需求最高

内存占用对比

// 512x512纹理的内存占用
let size = 512 * 512

.r8Unorm:           size * 1  = 262,144 bytes   (256 KB)
.rg8Unorm:          size * 2  = 524,288 bytes   (512 KB)  
.rgba8Unorm:        size * 4  = 1,048,576 bytes (1 MB)
.rgba16Float:       size * 8  = 2,097,152 bytes (2 MB)
.rgba32Float:       size * 16 = 4,194,304 bytes (4 MB)

// 压缩格式
.pvrtc_rgba_4bpp:   size / 2  = 131,072 bytes   (128 KB)
.astc_4x4_srgb:     size / 2  = 131,072 bytes   (128 KB)

实际应用示例

渲染管线配置

// 标准前向渲染管线
class StandardRenderer {
    func setupFormats() -> RenderFormats {
        #if os(iOS)
        return RenderFormats(
            colorFormat: .bgra8Unorm_srgb,    // 主颜色缓冲
            depthFormat: .depth32Float,        // 深度缓冲
            normalFormat: .rg8Snorm,          // 法线贴图
            albedoFormat: .rgba8Unorm_srgb    // 漫反射纹理
        )
        #else
        return RenderFormats(
            colorFormat: .rgba8Unorm_srgb,
            depthFormat: .depth32Float,
            normalFormat: .rg8Snorm,
            albedoFormat: .rgba8Unorm_srgb  
        )
        #endif
    }
}

HDR渲染管线

class HDRRenderer {
    func setupHDRFormats() -> HDRFormats {
        return HDRFormats(
            hdrColorBuffer: .rgba16Float,      // HDR颜色累积
            brightnessMask: .r16Float,         // 亮度提取
            bloomBuffer: .rgba16Float,         // 光晕效果
            toneMappedOutput: .bgra8Unorm_srgb // 最终显示
        )
    }
}

纹理加载策略

class TextureManager {
    func chooseFormat(for textureType: TextureType, quality: Quality) -> MTLPixelFormat {
        switch (textureType, quality) {
        case (.albedo, .high):
            return .rgba8Unorm_srgb
        case (.albedo, .mobile):
            return .pvrtc_rgba_4bpp
            
        case (.normal, .high):  
            return .rgba8Snorm
        case (.normal, .mobile):
            return .rg8Snorm  // 节省50%内存
            
        case (.mask, _):
            return .r8Unorm
            
        case (.hdr, _):
            return .rgba16Float
            
        default:
            return .rgba8Unorm_srgb
        }
    }
}

格式转换和兼容性

自动转换

// Metal会自动处理某些格式转换
let sourceFormat: MTLPixelFormat = .rgba8Unorm
let targetFormat: MTLPixelFormat = .bgra8Unorm
// GPU会自动重排通道:RGBA -> BGRA

兼容性检查

extension MTLDevice {
    func supportsFormat(_ format: MTLPixelFormat, usage: MTLTextureUsage) -> Bool {
        return supportsTextureSampleCount(1) && 
               supportsRenderTargetFormat(format)
    }
    
    func supportsRenderTargetFormat(_ format: MTLPixelFormat) -> Bool {
        // 检查设备是否支持特定格式作为渲染目标
        let descriptor = MTLTextureDescriptor.texture2DDescriptor(
            pixelFormat: format,
            width: 1,
            height: 1,
            mipmapped: false
        )
        descriptor.usage = .renderTarget
        
        return makeTexture(descriptor: descriptor) != nil
    }
}

调试和性能分析

格式信息打印

extension MTLPixelFormat {
    var debugDescription: String {
        switch self {
        case .rgba8Unorm: return "RGBA8 Unsigned Normalized"
        case .bgra8Unorm_srgb: return "BGRA8 sRGB (iOS优化)"
        case .rgba16Float: return "RGBA16 Half Float (HDR)"
        case .depth32Float: return "Depth32 Float"
        default: return "Format: \(rawValue)"
        }
    }
    
    var bytesPerPixel: Int {
        switch self {
        case .r8Unorm, .r8Snorm, .r8Uint, .r8Sint: return 1
        case .rg8Unorm, .rg8Snorm, .r16Unorm, .r16Float: return 2
        case .rgba8Unorm, .bgra8Unorm, .rgba8Unorm_srgb, .bgra8Unorm_srgb: return 4
        case .rgba16Float, .rg32Float: return 8
        case .rgba32Float: return 16
        default: return 4
        }
    }
    
    var isCompressed: Bool {
        switch self {
        case .pvrtc_rgb_2bpp, .pvrtc_rgb_4bpp, 
             .pvrtc_rgba_2bpp, .pvrtc_rgba_4bpp,
             .astc_4x4_srgb, .etc2_rgb8:
            return true
        default:
            return false
        }
    }
}

内存使用分析

class PixelFormatAnalyzer {
    static func analyzeTexture(_ texture: MTLTexture) {
        let format = texture.pixelFormat
        let width = texture.width
        let height = texture.height
        let mipmapLevels = texture.mipmapLevelCount
        
        var totalMemory = 0
        for level in 0..<mipmapLevels {
            let levelWidth = max(1, width >> level)
            let levelHeight = max(1, height >> level)
            let levelMemory = levelWidth * levelHeight * format.bytesPerPixel
            totalMemory += levelMemory
            
            print("Mipmap级别 \(level): \(levelWidth)x\(levelHeight) = \(levelMemory) 字节")
        }
        
        print("总内存占用: \(totalMemory) 字节 (\(totalMemory / 1024)KB)")
        print("格式: \(format.debugDescription)")
    }
}

最佳实践总结

✅ 推荐做法

  1. iOS应用:优先使用 .bgra8Unorm_srgb
  2. 跨平台:使用 .rgba8Unorm_srgb
  3. HDR渲染:使用 .rgba16Float
  4. 移动优化:考虑压缩格式
  5. 内存敏感:选择最小满足需求的格式

❌ 避免的做法

  1. 不要对简单数据使用过高精度格式
  2. 不要在移动设备上滥用32位浮点格式
  3. 不要忽视sRGB色彩空间的重要性
  4. 不要为UI元素使用HDR格式
  5. 不要在不支持的设备上使用特定压缩格式

选择合适的像素格式是Metal渲染优化的基础,需要在性能、内存使用和视觉质量之间找到最佳平衡点。

Giscus 未启用:请在 src/site.config.ts 中配置 repoId 与 categoryId。