Note: Since writing this originally I’ve figured out how to get at least some aspects of shaders working. I’m still trying to flesh out the rest. I’ll post an update or revised entry at some point.
Ugh. SceneKit supports shaders, technically. But it’s almost unequivocally unusable. Let me count the reasons:
- It doesn’t support geometry shaders at all.
- There’s absolutely no example code anywhere on the web. Certainly none from Apple.
- There’s essentially zero documentation. Not even from 3rd parties.
- You must set both a fragment and a vertex shader on every SCNProgram. This is actually documented, though poorly, and the behaviour is certainly not intuitive. It’s not hard to write a “no-op” version of either, but it’s stupid and it exacerbates all the other issues below.
Those are just the warnings. I should have paid heed. Instead I decided to reverse-engineer it anyway. Turns out it was a waste of time, because:
- Once you start using shaders, you lose all the functionality of SceneKit. The most obvious being textures, but it’s actually everything – geometry, projections, positions – everything. Now you must load and bind everything manually. Vertices must be fed into your shader manually. Everything is manual. No intrinsics, no built-ins. Stupidly simple things like gl_Color and gl_ModelViewProjectionMatrix are considered arbitrary and treated as uniforms, that must be bound manually. And to do so you have to implement an Objective-C delegate method, so your performance is going to be shit unless they utilise IMP caching, in which case it’s merely going to be terrible. I don’t understand how this could possibly be made to work.
- Your shader is compiled not only once for every single material it’s used on, but plus 50 percent. i.e. if you create a single SCNProgram and assign it to 100 distinct SCNMaterials (each on their own unique SCNNode), SceneKit will compile both the vertex & fragment shaders 150 times each. This only makes sense when you consider it in the context of the above observation; all the SceneKit objects your shaders happen to hang off are completely irrelevant. So of course each use is treated as a completely independent shader, because each one has to be completely setup manually. Though I have no idea what the extra 50% are about.
- Since your shader is not shared between materials or nodes, it gets set up and torn down once for every single drawable. Every SCNNode. So you get ~twenty OpenGL calls minimum per drawable. Admittedly some of these are SceneKits usual drawing commands, but most are related to the shader, and include gems like resetting the blend function every single time, and glMatrixMode + glPopMatrix + glMatrixMode + glPushMatrix in sequence, repeated, for the same matrix & value (the identity matrix, naturally).
As far as I can tell, if you want to use any shaders at all, you can’t use SceneKit.