3
votes

I've attached a SCNProgram to a SceneKit's geometry, and I'm trying to pass uniforms to the fragment shader. In my simple code snippet I just pass the output color to the fragment shader as a uniform, which returns it as an output value.

I've already tested the shaders and they work, in the sense that I can succesfully rotate an object in the vertex shader, or draw an object in a different color in the fragment shader, etc... but the problem is when I pass the uniforms. This is my fragment shader:

struct Uniforms
{
    float4 color;
};


fragment float4 myFragment(MyVertexOutput in [[ stage_in ]],
                           constant Uniforms& uniforms [[buffer(2)]])
{
    return uniforms.color;
}

And this is how I try to pass the uniforms in my SceneKit+Swift code:

SCNTransaction.begin()
cube.geometry?.setValue(NSValue(SCNVector4:SCNVector4(0.0,1.0,0.0,1.0)), forKey: "uniforms.color")
SCNTransaction.commit()

But my object (it's a cube) is not even drawn (it's black), and I get this error:

2016-04-01 01:00:34.485 Shaded Cube[30266:12687154] SceneKit: error, missing buffer [-1/0]

EDIT I tried to follow @lock's suggestions, but I'm still getting the same error. This is the full project repository: https://github.com/ramy89/Shaded-Cube.git

1
Hi Ramy could I email you about a potential project? Couldn't find your email address on the GitHub page. Thanks!Crashalot

1 Answers

3
votes

Your shader code looks fine, and the way you're passing the uniform in is close to working.

The name of the shader uniform must match that which is used in setValue. In this case your Metal variable name is uniforms and the value you use in setValue is uniforms.color. These don't match, hence the error you see. Suggest changing the swift code to simply use uniforms in the setValue call.

Next you need to ensure the data passed in as the value to setValue is the same in both Metal and Swift. A SCNVector4 is a struct of four 64bit doubles (CGFloats), whereas Metal's float4 is a struct of four 32bit floats. The documentation seems to indicate you can pass in a SCNVector as a NSValue but I've never got it to work.

In your swift code I'd create a struct to contain the uniforms you want to pass in. There's not a lot of documentation on the vector_float4 struct, but it matches the Metal float4 struct in that it is four 32bit floats.

struct Uniforms {
    var color:vector_float4
}

Pass this into the setValue function as NSData.

let myColor = vector_float4(0,1,0,1)
var myUniforms = Uniforms(color:myColor)
var myData = NSData(bytes:&myUniforms, length:sizeof(Uniforms))
cube.geometry?.setValue(myData, forKey: "uniforms")

I'm not sure you need the SCNTransaction calls, I've not needed them in past and they could be costly performance wise.

update

After looking at Ramy's code it seems there is an issue with setValue and SCNPrograms when applied to the geometry. I can't tell you why, but setting the custom shader to the material seems to fix this, eg;

func makeShaders()
{
    let program = SCNProgram()
    program.vertexFunctionName = "myVertex"
    program.fragmentFunctionName = "myFragment"
    //cube.geometry?.program = program
    cube.geometry?.firstMaterial?.program = program

    var uniforms = Uniforms(color: vector_float4(0.0,1.0,0.0,1.0))
    let uniformsData = NSData(bytes: &uniforms, length: sizeof(Uniforms))
    //cube.geometry?.setValue(uniformsData, forKey: "uniforms")
    cube.geometry?.firstMaterial?.setValue(uniformsData, forKey: "uniforms")
}