我想使用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);
}
这个内核函数在我的 SCNSceneRendererDelegate
的 - renderer:willRenderScene:atTime:
方法中的每一帧运行 . 有两个缓冲区,它们在每帧之后切换 .
缓冲区创建如下;
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)
}
并使用以下命令运行计算着色器(在 willRenderScene
func中);
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)
}
这是有效的,我可以看到我的计算着色器正在更新数组中每个向量的y坐标 .
这个场景由三个均匀间隔的球体组成 . 顶点着色器只是获取计算着色器中计算的位置,并将其添加到每个顶点位置(无论如何都是y组件) . 我给每个球体一个索引,顶点着色器使用这个索引从我的计算数组中拉出适当的位置 .
金属顶点函数如下所示,它由 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;
}
MyPositions
是一个包含float3s数组的简单结构 .
struct MyPositions
{
float3 positions[3];
};
使用每个球体材质的 setValue
方法将数据传递到顶点着色器没有问题,如下所示(也在 willRenderScene
方法中完成) . 一切都按预期工作(三个球体向上移动) .
var i0:UInt32 = 0
let index0 = NSData(bytes: &i0, length: sizeof(UInt32))
sphere1Mat.setValue(index0, forKey: "index")
sphere1Mat.setValue(data, forKey: "myPos")
BUT 这需要将数据从GPU复制到CPU到GPU,这是我宁愿避免的 . 所以我的问题是...... How do I pass a MTLBuffer to a SCNProgram?
在 willRenderScene
尝试了以下内容,但除了 EXEC_BAD...
之外什么也没得到
let renderCommandEncoder = renderer.currentRenderCommandEncoder!
renderCommandEncoder.setVertexBuffer(buffer2, offset: 0, atIndex: 2)
renderCommandEncoder.endEncoding()
感谢阅读,一直在努力解决这个问题 . 解决方法是使用MTLTexture代替MTLBuffer,因为我已经能够通过漫反射mat prop将它们传递到SCNProgram中 .
1 回答
只需从一步到另一步切换缓冲区的绑定 .
step1 computeCommandEncoder.setBuffer(buffer1,offset:0,atIndex: 0 )computeCommandEncoder.setBuffer(buffer2,offset:0,atIndex: 1 )
step2 computeCommandEncoder.setBuffer(buffer1,offset:0,atIndex: 1 )computeCommandEncoder.setBuffer(buffer2,offset:0,atIndex: 0 )
step3 computeCommandEncoder.setBuffer(buffer1,offset:0,atIndex: 0 )computeCommandEncoder.setBuffer(buffer2,offset:0,atIndex: 1 )
等等 ...
out缓冲区成为缓冲区中的新缓冲区,反之亦然......