You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Metal中如何在CPU端获取片段着色器的选中objectId?

Metal对象选择相关问题解答

问题背景

我正在学习Raywenderlich的Metal教程,书中有一个对象选择的示例。目前能在片段着色器中完成命中测试,但遇到以下问题:

  1. 如何在Swift端获取选中的objectId值?
  2. CPU和GPU能否共享objectId这类数据?还是只能在着色器中处理传入的值?
  3. 在Swift端打印params.objectId时,该值始终为0,这是为什么?

相关代码如下:

typedef struct {
  uint width;
  uint height;
  uint tiling;
  uint lightCount;
  vector_float3 cameraPosition;
  uint objectId;
  uint touchX;
  uint touchY;
} Params;

// 将该结构体传入片段着色器
encoder.setFragmentBytes(&params, length: MemoryLayout<Params>.stride, index:
ParamsBuffer.index)

// 片段着色器中的命中测试逻辑
fragment float4 fragment_main(constant Params &params [[buffer(ParamsBuffer)]],
//...

  if(params.objectId != 0 && objectID == params.objectId) {
            material.baseColor = float3(0.9, 0.5, 0);
        }

解答

1. Swift端获取选中的objectId的方法

你当前的实现是单向从CPU传值到GPU,要获取GPU计算出的选中ID,必须使用可读写的共享缓冲区(MTLBuffer),而不是setFragmentBytes这种一次性传值方式:

  • 在Swift端创建一个共享缓冲区,大小对应UInt32的内存长度,存储模式设为.shared:
    let selectedIdBuffer = device.makeBuffer(
        length: MemoryLayout<UInt32>.stride,
        options: .storageModeShared
    )!
    
  • 在片段着色器中声明该缓冲区为可写的device类型,命中测试通过时写入选中的ID:
    fragment float4 fragment_main(
        constant Params &params [[buffer(ParamsBuffer)]],
        device uint &selectedObjectId [[buffer(SelectedIdBuffer)]],
        // 其他必要参数(如objectID输入)
    ) {
        // 命中测试逻辑
        if(objectID == params.objectId) {
            material.baseColor = float3(0.9, 0.5, 0);
            selectedObjectId = objectID; // 将选中ID写入缓冲区
        }
        // 返回颜色值...
    }
    
  • 渲染完成后,CPU直接读取缓冲区内容即可获取结果:
    // 等待GPU渲染完成(确保缓冲区写入完毕)
    commandBuffer.waitUntilCompleted()
    let selectedId = selectedIdBuffer.contents().load(as: UInt32.self)
    print("选中的objectId: \(selectedId)")
    

2. CPU和GPU能否共享objectId这类数据?

完全可以,但要选择正确的内存存储方式:

  • 用setFragmentBytes传值时,是一次性将CPU数据复制到GPU的临时内存,GPU只能读,CPU修改后需要重新传递,GPU的修改也不会同步回CPU。
  • 用共享缓冲区(.shared存储模式)时,CPU和GPU直接访问同一块内存区域,数据可以双向同步,GPU写入后CPU能立刻读取,反之亦然。
  • 若数据更新不频繁,也可以用.managed存储模式,Metal会自动在CPU和GPU之间同步数据,但性能略逊于.shared。

3. Swift端打印params.objectId始终为0的原因

这说明你在Swift端没有正确初始化或传递Params的objectId字段:

  • 检查Swift端创建Params实例时,是否给objectId赋值了正确的目标ID(比如用户点击后计算出的对象ID),而不是默认的0。
  • 确认调用setFragmentBytes时,传递的params是最新赋值的实例,没有被意外重置。
  • 保证Swift和Metal的Params结构体内存布局一致:可以给Swift的对应结构体添加@_spi(Metal) @MetalSerializable属性(需导入Metal),或者手动对齐字段,避免因内存偏移导致读取错误。

内容的提问来源于stack exchange,提问作者Mehmet Deran

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 20:00:47