将Metal缓冲区传递给SceneKit着色器

loc*_*ock 6 scenekit metal

我想使用Metal计算着色器来计算一些位置,然后将其输入到金属着色器中.听起来很直接,但我无法将我的MTLBuffer数据输入基于金属的SCNProgram.

计算内核如下,在这个设计的例子中,它接收三个3D向量(在两个缓冲区中).

kernel void doSimple(const device float3 *inVector [[ buffer(0) ]],
                    device float3 *outVector [[ buffer(1) ]],
                    uint id [[ thread_position_in_grid ]]) {

    float yDisplacement = 0;
    . . . .

    outVector[id] = float3(
        inVector[id].x, 
        inVector[id].y + yDisplacement, 
        inVector[id].z);
}
Run Code Online (Sandbox Code Playgroud)

这个内核函数在- renderer:willRenderScene:atTime:我的方法中每帧运行一次SCNSceneRendererDelegate.有两个缓冲区,它们在每帧之后切换.

缓冲区创建如下;

func setupBuffers() {
    positions = [vector_float3(0,0,0), vector_float3(1,0,0), vector_float3(2,0,0)]

    let bufferSize = sizeof(vector_float3) * positions.count
    //copy same data into two different buffers for initialisation
    buffer1 = device.newBufferWithBytes(&positions, length: bufferSize, options: .OptionCPUCacheModeDefault)
    buffer2 = device.newBufferWithBytes(&positions, length: bufferSize, options: .OptionCPUCacheModeDefault)
}
Run Code Online (Sandbox Code Playgroud)

并使用以下(在willRenderScenefunc中)运行计算着色器;

    let computeCommandBuffer = commandQueue.commandBuffer()
    let computeCommandEncoder = computeCommandBuffer.computeCommandEncoder()

    computeCommandEncoder.setComputePipelineState(pipelineState)
    computeCommandEncoder.setBuffer(buffer1, offset: 0, atIndex: 0)
    computeCommandEncoder.setBuffer(buffer2, offset: 0, atIndex: 1)

    computeCommandEncoder.dispatchThreadgroups(numThreadgroups, threadsPerThreadgroup: threadsPerGroup)
    computeCommandEncoder.endEncoding()
    computeCommandBuffer.commit()
    computeCommandBuffer.waitUntilCompleted()

    let bufferSize = positions.count*sizeof(vector_float3)
    var data = NSData(bytesNoCopy: buffer2.contents(), length: bufferSize, freeWhenDone: false)
    var resultArray = [vector_float3](count: positions.count, repeatedValue: vector_float3(0,0,0))
    data.getBytes(&resultArray, length:bufferSize)

    for outPos in resultArray {
        print(outPos.x, ", ", outPos.y, ", ", outPos.z)
    }
Run Code Online (Sandbox Code Playgroud)

这是有效的,我可以看到我的计算着色器正在更新数组中每个向量的y坐标.

这个场景由三个均匀间隔的球体组成.顶点着色器只是获取计算着色器中计算的位置,并将其添加到每个顶点位置(无论如何都是y组件).我给每个球体一个索引,顶点着色器使用这个索引从我的计算数组中拉出适当的位置.

金属顶点函数如下所示,它由a引用SCNProgram并设置为每个球体的材质.

vertex SimpleVertex simpleVertex(SimpleVertexInput in [[ stage_in ]],
                             constant SCNSceneBuffer& scn_frame [[buffer(0)]],
                             constant MyNodeBuffer& scn_node [[buffer(1)]],
                             constant MyPositions &myPos [[buffer(2)]],
                             constant uint &index [[buffer(3)]]
                             )
{

    SimpleVertex vert;
    float3 posOffset = myPos.positions[index];
    float3 pos = float3(in.position.x, 
                        in.position.y + posOffset.y, 
                        in.position.z);

    vert.position = scn_node.modelViewProjectionTransform * float4(pos,1.0);

    return vert;
}
Run Code Online (Sandbox Code Playgroud)

MyPositions 是一个包含float3s数组的简单结构.

struct MyPositions
{
    float3 positions[3];
};
Run Code Online (Sandbox Code Playgroud)

使用setValue每个球体材质的方法将数据传递到顶点着色器没有问题,如下所示(也在willRenderScene方法中完成).一切都按预期工作(三个球体向上移动).

    var i0:UInt32 = 0
    let index0 = NSData(bytes: &i0, length: sizeof(UInt32))
    sphere1Mat.setValue(index0, forKey: "index")
    sphere1Mat.setValue(data, forKey: "myPos")
Run Code Online (Sandbox Code Playgroud)

这个要求数据从GPU拷贝到CPU到GPU和真的东西,我宁愿避免.所以我的问题是...... 如何将MTLBuffer传递给SCNProgram?

尝试了以下内容,willRenderScene但除此之外什么也没得到EXEC_BAD...

let renderCommandEncoder = renderer.currentRenderCommandEncoder!
renderCommandEncoder.setVertexBuffer(buffer2, offset: 0, atIndex: 2)
renderCommandEncoder.endEncoding()
Run Code Online (Sandbox Code Playgroud)

完整的例子在GitHub上结束了.

感谢阅读,一直在努力解决这个问题.解决方法是使用MTLTexture代替MTLBuffer,因为我已经能够通过漫反射mat prop将它们传递到SCNProgram中.

Oli*_*ver -1

只需逐步切换缓冲区的绑定即可。

步骤1computeCommandEncoder.setBuffer(buffer1,偏移量:0,atIndex:0)computeCommandEncoder.setBuffer(buffer2,偏移量:0,atIndex:1

步骤2computeCommandEncoder.setBuffer(buffer1,偏移量:0,atIndex:1)computeCommandEncoder.setBuffer(buffer2,偏移量:0,atIndex:0

步骤3computeCommandEncoder.setBuffer(buffer1, offset: 0, atIndex: 0 )computeCommandEncoder.setBuffer(buffer2, offset: 0, atIndex: 1 )

等等 ...

输出缓冲区成为新的输入缓冲区,反之亦然......