Using a Metal shader in SceneKit

3.8k views Asked by At

I'd like to use a Metal shader to apply a toon/cell shading to materials used in the scene. The shader I'm trying to implement is Apple's own AAPLCelShader found in the MetalShaderShowcase. I'm a little new to implementing shaders in SceneKit so I could be doing everything completely wrong.

From what I understand in the documentation a Metal or GL shader can be used to modify SceneKit rendering as of iOS 9 and Xcode 7. The documentation suggests that this is done in the same way as GL shaders.

My method is to try and get the path for the .metal file and then load it into a string to be used in the SCNMaterial shaderModifiers property in the form of a [String: String] dictionary, as you would with a GL shader (though I've yet to get that working properly).

My code is:

var shaders = [String: String]()
let path = NSBundle.mainBundle().pathForResource("AAPLCelShader", ofType: "metal")!

do{
  let celShader = try String(contentsOfFile: path, encoding: NSUTF8StringEncoding)
  shaders[SCNShaderModifierEntryPointLightingModel] = celShader
}catch let error as NSError{
  error.description
}
scene.rootNode.enumerateChildNodesUsingBlock({ (node, stop) -> Void in
  node.geometry?.firstMaterial?.shaderModifiers = shaders
})

This is being run on a test scene with a single box geometry in it that is being loaded from a .scn file. The file loading works fine so that's not an issue.

The error comes from the second line where I try and find the path, it gives me the "found nil while unwrapping optional value" which would be expected with the force unwrapping, other than the fact that the file it is trying to load is most certainly in the bundle and the same code works fine with other file types, just not .metal ones.

Am I going about this completely the wrong way? I can't understand why it won't access the file other than an issue with me misusing the metal file.

Is there a simpler or better way to implement cell shading and eventually a bold outline on edges?

1

There are 1 answers

1
AudioBubble On BEST ANSWER

AAPLCelShader.metal is a complete vertex/rastex/fragment implementation, which is not what shaderModifiers are: source code that will be injected into already-complete shaders.

Instead, you can create an SCNProgram, using the vertex and fragment functions in AAPLCelShader.metal. What's nice about Metal, versus GLSL, is that you can use names of Metal functions, instead of source code strings, which is easier to work with, and results in having the functions compiled before runtime, which GLSL doesn't support. (This is still not as good as it should be, where Swift would recognize the Metal functions as correctly-typed, refactorable, namespaced closures.) Of course, while GLSL SCNPrograms will be converted to Metal, for compatible hardware, Metal SCNPrograms will not be translated for obsolete devices.

As for mapping from SceneKit to the rastex's (incorrectly-named ColorInOut) members:

float4 position [[position]];
float shine;
float3 normal_cameraspace;
float3 eye_direction_cameraspace;
float3 light_direction_cameraspace;

…I unfortunately do not have an answer for you yet.