Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use SCNBufferBindingBlock in SceneKit?

I'm looking at SceneKit's handle binding method with the SCNBufferBindingBlock call back as described here:

https://developer.apple.com/documentation/scenekit/scnbufferbindingblock

Does anyone have an example of how this works?

    let program = SCNProgram()
    program.handleBinding(ofBufferNamed: "", frequency: .perFrame) { (steam, theNode, theShadable, theRenderer) in

    }

To me it reads like I can use a *.metal shader on a SCNNode without having to go through the hassle of SCNTechniques....any takers?

like image 541
Chris Avatar asked May 17 '18 09:05

Chris


2 Answers

Just posting this in case someone else came here looking for a concise example. Here's how SCNProgram's handleBinding() method can be used with Metal:

First define a data structure in your .metal shader file:

struct MyShaderUniforms {
    float myFloatParam;
    float2 myFloat2Param;
};

Then pass this as an argument to a shader function:

fragment half4 myFragmentFunction(MyVertex vertexIn [[stage_in]], 
                                  constant MyShaderUniforms& shaderUniforms [[buffer(0)]]) {
    ...
}

Next, define the same data structure in your Swift file:

struct MyShaderUniforms {
    var myFloatParam: Float = 1.0
    var myFloat2Param = simd_float2()
}

Now create an instance of this data structure, changes its values and define the SCNBufferBindingBlock:

var myUniforms = MyShaderUniforms()
myUniforms.myFloatParam = 3.0

...

program.handleBinding(ofBufferNamed: "shaderUniforms", frequency: .perFrame) { (bufferStream, node, shadable, renderer) in
    bufferStream.writeBytes(&myUniforms, count: MemoryLayout<MyShaderUniforms>.stride)
}

Here, the string passed to ofBufferNamed: corresponds to the argument name in the fragment function. The block's bufferStream property then contains the user-defined data type MyShaderUniforms which can then be written to with updated values.

like image 102
MasDennis Avatar answered Oct 21 '22 14:10

MasDennis


The .handleBinding(ofBufferNamed:frequency:handler:) method registers a block for SceneKit to call at render time for binding a Metal buffer to the shader program. This method can only be used with Metal or OpenGL shading language based programs. SCNProgram object helps perform this custom rendering. Program object contains a vertex shader and a fragment shader. Using a program object completely replaces SceneKit’s rendering. Your shaders take input from SceneKit and become responsible for all transform, lighting and shading effects you want to produce. Use .handleBinding() method to associate a block with a Metal shader program to handle setup of a buffer used in that shader.

Here's a link to Developer Documentation on SCNProgram class.

Also you need an instance method writeBytes(_:count:) that copies all your necessary data bytes into the underlying Metal buffer for use by a shader.

SCNTechniqueclass specifically made for post-processing SceneKit's rendering of a scene using additional drawing passes with custom Metal or OpenGL shaders. Using SCNTechnique you can create such effects as color grading or displacement, motion blur and render ambient occlusion as well as other render passes.

Here is a first code's excerpt how to properly use .handleBinding() method:

func useTheseAPIs(shadable: SCNShadable,
                  bufferStream: SCNBufferStream
                  voidPtr: UnsafeMutableRawPointer,
                  bindingBlock: @escaping SCNBindingBlock, 
                  bufferFrequency: SCNBufferFrequency,
                  bufferBindingBlock: @escaping SCNBufferBindingBlock,
                  program: SCNProgram) {

    bufferStream.writeBytes(voidPtr, count: 4) 

    shadable.handleBinding!(ofSymbol: "symbol", handler: bindingBlock)
    shadable.handleUnbinding!(ofSymbol: "symbol", handler: bindingBlock)

    program.handleBinding(ofBufferNamed: "pass", 
                          frequency: bufferFrequency,
                          handler: bufferBindingBlock)
}

And here is a second code's excerpt:

let program = SCNProgram()
program.delegate = self as? SCNProgramDelegate
program.vertexShader = NextLevelGLContextYUVVertexShader
program.fragmentShader = NextLevelGLContextYUVFragmentShader

program.setSemantic(
    SCNGeometrySource.Semantic.vertex.rawValue, 
    forSymbol: NextLevelGLContextAttributeVertex, 
    options: nil)

program.setSemantic(
    SCNGeometrySource.Semantic.texcoord.rawValue, 
    forSymbol: NextLevelGLContextAttributeTextureCoord, 
    options: nil)

if let material = self._material {
    material.program = program

    material.handleBinding(ofSymbol: NextLevelGLContextUniformTextureSamplerY, handler: { 
        (programId: UInt32, location: UInt32, node: SCNNode?, renderer: SCNRenderer) in
            glUniform1i(GLint(location), 0);
    }) 
    material.handleBinding(ofSymbol: NextLevelGLContextUniformTextureSamplerUV, handler: { 
        (programId: UInt32, location: UInt32, node: SCNNode?, renderer: SCNRenderer) in
            glUniform1i(GLint(location), 1);
    })
}

Also, look at Simulating refraction in SceneKit SO post.

like image 35
Andy Jazz Avatar answered Oct 21 '22 16:10

Andy Jazz