Sprites with same shader but different uniform values

I have been trying to find information on this, but I just can't find anything. The SKShader and SKUniform reference documents do not provide this information, and neither does the SpriteKit Programming Guide document (and in fact, it seems that shaders are not mentioned there at all, even though the document is linked to from the SKShader and SKUniform reference documents). Couldn't find this by googling either.


The SKShader documentation recommends sharing shaders between sprites as much as possible. In other words, if two sprites use the same shader, it's better to share the same SKShader object between them rather than create a new shader for each one.


But what if two such sprites use the same shader, but different uniform values? For example, there may be a uniform named "factor" used in the shader, and one sprite wants to use a value of 0.25 for it, while another wants to use a value of 0.4. (As far as I understand, this is something that can be done with one single shader, and it should be completely efficient. Nothing changes when rendering the sprites, except the value of that uniform, which should be possible and efficient.)


I can't find any info on what the kosher way of doing this in SpriteKit using SKShader.


The 'shader' property of SKSpriteNode retains the object. Which means that the one and same SKShader object will be shared between two sprites if I simply assign one to both. Obviously this won't work if I want them to use a different value for a uniform.


I notice that SKShader is copyable. Should I assign a copy of the original SKShader object to each sprite? Will this work? Will it be efficient? Can then each copy be assigned different uniform values and then they will work properly? Or do I need to replace the SKUniform objects in each copy with new objects?


What is the correct way of doing this?

Accepted Reply

After reviewing the latest WWDC, it seems that you were right. If you have a 3 guys using the same shader but with different Uniform values, you need to create three instances of the shader.

Now, in iOS 10, you can use SKAttributes to share a shader between different Sprites and set and attribute on the sprite that the shader will use instead of the Uniform.

Check Session 610.

Hope it helps

Replies

If nobody knows the answer to this, then who does? Where can I go to get an answer?


I can't believe this is something that nobody knows, seemingly not even Apple staff. If they don't know this, then who does?


(I'm getting really, really tired of most of my questions on these forums going completely unanswered. Not a single peep. Not even a "it's not possible". Nothing. No answers, no feedback, no acknowledgment that the question has been read and understood. Nothing. It really gets on my nerves. What purpose does this forum serve if nobody will answer?)

Well, I'm not an expert on SKShader, but according to the docs: "Avoid changing the shader’s source or uniforms while your game is running. Either of these things recompiles the shader." That seems pretty unambiguous that SKShader considers the uniforms part of the compiled shader, so I'm guessing that different uniforms = different shader instances.

I'm not trying to change the uniforms. I'm changing their values. This is a completely normal and routine thing to do, and it's essentially how shaders work (shaders always get changing data via uniforms, which is use for all kinds of effects, like lighting or even animated shaders). In fact, all sprites in SpriteKit already get different uniform values (eg. uv coordinates).


It is completely normal in OpenGL to have different polygons use the same shader with different uniform values. And most graphics libraries which use OpenGL (or any shader-based API for that matter) support this directly.


My question is how it's done with SpriteKit, because I can't find any documentation about it. The documentation just says that shaders should be shared among sprites as much as possible, for efficiency reasons, but I can't find a single mention on how to give each sprite different uniform values (which, as said, is a completely normal operation in OpenGL.)

I repeat my question: If nobody knows the answer to this, then who does? Where can I go to get an answer?


I can't understand why this is so hard to answer. This is not some kind of extremely weird bug that happens once in a blue moon, or some great unsolved problem in computing science. This is just a question about the basic usage of the SpriteKit library (something that should, in fact, be found in its documentation, but isn't, for some reason.)


Do I need to keep posting to this thread until I get an answer, or what exactly should I do to get one?

Suppose that I have, for example, a kind of custom particle effect with up to a thousand particles, where the particles use a fancy shader. The shader needs to know, among other things, the age of the particle (in order to make it change according to how long it has existed). Obviously since particles are created at different times, the age varies with each particle, and needs to be passed to the shader of each particle via an uniform each time the particle effect is updated.


The SKUniform object of the shader of the particle exists for that exact purpose: You set the value of the SKUniform object (eg. using its 'floatValue' property). This gets passed to the shader.


The problem is, I have up to a thousand particle sprites, each using the same shader but different uniform values (among others, the 'age' uniform value varies depending on the particle sprite in question). Moreover, these particles are created and destroyed all the time.


Is the only possible solution to create a new shader every time a particle is created? Not only would there need to be up to a thousand shader programs at any given time, but they would need to be constantly re-compiled, every time a new particle is created. (I suppose that shaders could be buffered so that when one particle releases its shader, it's not destroyed but kept in a buffer so that it can then be reused with the next new particle that gets created. However, there are still a thousand shaders compiled, when one would suffice. And this for one single particle effect. Assume there are several...)


This is not rocket science. Most other 2D graphics engines supporting shaders have direct support for defining different uniform values for each sprite.

Would even an official "it's not possible" answer be too much to ask?

Ok, here's some investigation I did on my own. It's mostly guessing because the SpriteKit documentation is lacking, so I suppose what I'm asking is if it's correct.


It seems to be that it's just not possible to change the uniform values of different sprites sharing the same shader, and the major underlying technical reason for this is that sprites are rendered in batches, and all polygons rendered in a batch can have only one value for each uniform. (Having different values for the uniforms of each sprite would require each such sprite to be rendered separately; they could not be rendered in a batch. And SpriteKit currently has no support for this, it seems.)


The reason why each sprite can have a different color, even though they all (by default) use the same shader is because the color is transmitted to them in a different manner. Namely, via vertex shader attributes (which then pass the color to the fragment shader via a varying variable). Vertex shader attributes can be batched, and that is, I'm guessing, what SpriteKit does.


I suppose that what I want to do could be achieved in the same way. In other words, rather than using uniforms, I would need to use vertex shader attributes, and have the vertex shader pass the values to the fragment shader via varying variables. Unfortunately, SpriteKit has no support for user-defined vertex shaders. Which in essence means that what I want to do is just not possible with SpriteKit. (It might be possible by using a SceneKit object, and embedding it in the SpriteKit scene.)

Hey WarpRulez thanks for these posts.


I'm just starting to play around with shaders and I've learned from your posts.


The shaders I'm playing around with make my app look fantasic - but hard to get looking just so.


I do wish that getting any sort of info was less of a time consuming trial and error process.


Any change of an Apple Sprite/Scent Kit guru putting together something like this?: http://docs.unity3d.com/Manual/SL-SurfaceShaderExamples.html

After reviewing the latest WWDC, it seems that you were right. If you have a 3 guys using the same shader but with different Uniform values, you need to create three instances of the shader.

Now, in iOS 10, you can use SKAttributes to share a shader between different Sprites and set and attribute on the sprite that the shader will use instead of the Uniform.

Check Session 610.

Hope it helps

Sorry for having taken so long to answer, but yes, SKAttribute is exactly the feature I was looking for.


It indeed allows for the exact same shader to be used in all sprites, with values that are sprite-specific.

I doubt anyone is reading this six years later, but I just ran into this and it is not working. For some reason, it is applying the SKAttribute to all copies of the sprite node.

The sprite with shader is initialized like this:

birdFillShader = shaderWithFilename( "birdFill", fileExtension: "fsh", uniforms: uniforms )
birdFillShader.attributes = [SKAttribute(name: "a_rand", type: .float)]

self.birdNode = SKShapeNode( rect: CGRect(x: -100, y: -100, width: 200, height: 200) )
if let birdNode = self.birdNode {
    birdNode.strokeColor = .clear
    birdNode.fillShader = birdFillShader
}

Later on, the sprite is cloned and the SKAttribute is set:

if let birdNode = self.birdNode?.copy() as! SKShapeNode? {
    let aRand = Float.random(in: 0.0...1.0)
    birdNode.setValue(SKAttributeValue(float: aRand), forAttribute: "a_rand")
    /* ... set position of node ... */
}

For the moment, the shader is doing this:

void main()
{
    gl_FragColor = vec4(vec3(a_rand), 1.0);
}

I am cloning multiple sprites and they all seem to share the same value for a_rand as noted by the color being uniform and changing across all created sprites.

Apparently, this is not possible with SKShapeNode and is only possible with SKSpriteNode. I will make a new forum topic to see if this is a bug.