- Edited
Attachments still visible in runtime despite being disabled
I'm currently working with a spine-sdl runtime: https://github.com/GerogeChong/spine-sdl
I've ported it to the latest version of spine, sdl, and to c++. It's been working fine up until recently, when our graphic artist noticed some weird bugs with transparency.
As it turns out our artist has been giving us premultiplied spine assets and we've been rendering them as unpremultiplied.
So I went back, tweaked our implementation a bit to use premultipled; and... didn't work. We started getting weird graphic glitched on objects that are supposed to be premultiplied where hidden attachments or zero alpha objects sorta having this burn in effect.
I'm guessing this all comes back to our runtime implementation. I did some testing and more or less this is what's happening:
So I think this should be rendered where all of the attachments are invisible, but for whatever reason they're not. So I thought maybe the runtime wasn't skipping null attachment's, but I looked over the rendering code and that looks right:
for (int i = 0; i < skeleton->getDrawOrder().size(); i++)
{
//skeleton is of type Skeleton*
Slot* slot = skeleton->getDrawOrder()[i];
Attachment* attachment = slot->getAttachment();
if (!attachment)
{
continue; //I would think that this line would cause the renderer to skip invisible attachments
}
//rendering logic
}
Interestingly, when I actually play the attachment, things behave weirdly in a different respect:
The flow for this animation is, enabled all attachments, set their alpha to zero, and gradual fade them in and out.
Premultiplied does this weird thing where it gets super bright and wrong
However Straight Alpha works as expected.
Does anyone have any idea what might be causing this. I'm seriously just really confused by all of it.
It can be tricky since there are many stages where PMA can be applied: when the image is saved, when the image is loaded, when the image is read by the vertex shader, when the image is used by the fragment shader. There is also a consideration for the vertex colors which are used to tint the image data, you could PMA those before giving them to the GPU or in your shaders. We're not even done yet
there's another consideration that is how blending is performed. Eg, with OpenGL straight alpha needs SRC_ALPHA, ONE_MINUS_SRC_ALPHA
and PMA needs ONE, ONE_MINUS_SRC_ALPHA
. There is also a difference to achieve additive blending, but let's get normal rendering working before adding more worries.
Get any of that wrong, eg apply PMA twice, and the result is wrong with little clue as to why. That's the fun of the game! My favorite part and most common experience with graphics programming is getting a black screen instead of what I wanted to draw. :wounded: As with most things, start at the beginning and test your assumptions at each step of the way.
What stages do you apply PMA? Are your tint colors PMA'ed in the runtime or in the shader? What blend function are you using?
This doesn't look like it's applying PMA to the tint colors:
https://github.com/GerogeChong/spine-sdl/blob/master/spine-sdl/spine-sdl.cpp#L286
Doing so would look like this:
spine-runtimes/SkeletonRenderer.java at 3.8
A slightly simpler version that doesn't support two color tinting is in the same file:
spine-runtimes/SkeletonRenderer.java at 3.8
Note the trick with the blend function to achieve additive, with PMA we can do additive without changing the blend function, so we don't need to break the batch.
Another thing to note, an attachment is not truly hidden if a slot has that attachment. If you set an attachment to zero alpha, it will still be drawn: the GPU will sample the image data and it will still count against your pixel fill rate as if you had drawn pixels. You can add a special case in your renderer to skip rendering if alpha is zero, and/or you could hide attachments you don't want drawn.
Nate wroteIt can be tricky since there are many stages where PMA can be applied: when the image is saved, when the image is loaded, when the image is read by the vertex shader, when the image is used by the fragment shader. There is also a consideration for the vertex colors which are used to tint the image data, you could PMA those before giving them to the GPU or in your shaders. We're not even done yet
there's another consideration that is how blending is performed. Eg, with OpenGL straight alpha needs
SRC_ALPHA, ONE_MINUS_SRC_ALPHA
and PMA needsONE, ONE_MINUS_SRC_ALPHA
. There is also a difference to achieve additive blending, but let's get normal rendering working before adding more worries.
Admittedly my color math is kinda crap, but I looked over GerogeChong's repo and I believe he's setting up the blend modes correctly. He's got specific pma ones setup that seem to reflect the additive and normal mode differences you mentioned.
Nate wroteGet any of that wrong, eg apply PMA twice, and the result is wrong with little clue as to why. That's the fun of the game! My favorite part and most common experience with graphics programming is getting a black screen instead of what I wanted to draw. :wounded: As with most things, start at the beginning and test your assumptions at each step of the way.
What stages do you apply PMA? Are your tint colors PMA'ed in the runtime or in the shader? What blend function are you using?
This doesn't look like it's applying PMA to the tint colors:
https://github.com/GerogeChong/spine-sdl/blob/master/spine-sdl/spine-sdl.cpp#L286
Doing so would look like this:
https://github.com/EsotericSoftware/spine-runtimes/blob/3.8/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java#L344
A slightly simpler version that doesn't support two color tinting is in the same file:
https://github.com/EsotericSoftware/spine-runtimes/blob/3.8/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java#L104
Note the trick with the blend function to achieve additive, with PMA we can do additive without changing the blend function, so we don't need to break the batch.
You're intuition is on point man, I changed that section to:
Uint8 r, g, b, a;
if (!usePremultipliedAlpha)
{
r = static_cast<Uint8>(skeleton->getColor().r * slot->getColor().r * attachmentColor.r * 255);
g = static_cast<Uint8>(skeleton->getColor().g * slot->getColor().g * attachmentColor.g * 255);
b = static_cast<Uint8>(skeleton->getColor().b * slot->getColor().b * attachmentColor.b * 255);
a = static_cast<Uint8>(skeleton->getColor().a * slot->getColor().a * attachmentColor.a * 255);
}
else
{
float alpha = skeleton->getColor().a * slot->getColor().a * attachmentColor.a;
r = static_cast<Uint8>(skeleton->getColor().r * slot->getColor().r * attachmentColor.r * 255 * alpha);
g = static_cast<Uint8>(skeleton->getColor().g * slot->getColor().g * attachmentColor.g * 255 * alpha);
b = static_cast<Uint8>(skeleton->getColor().b * slot->getColor().b * attachmentColor.b * 255 * alpha);
a = static_cast<Uint8>(alpha * 255);
}
and that did it!
Nate wroteAnother thing to note, an attachment is not truly hidden if a slot has that attachment. If you set an attachment to zero alpha, it will still be drawn: the GPU will sample the image data and it will still count against your pixel fill rate as if you had drawn pixels. You can add a special case in your renderer to skip rendering if alpha is zero, and/or you could hide attachments you don't want drawn.
So I do have a question on that, I'm trying to setup a setup pose in spine such that all the attachments that don't appear by default are hidden. I thought I just had to enable or disable this dot, but it doesn't seem to make a difference one way or another:
Not really sure if that's what that dot is supposed to do, or if the runtime is just broken for that or something...
I'm contemplating doing the zero alpha optimizations you mentioned, it is pretty frequent that we'll fade an alpha to zero as part of an animation.
Great! Glad it was an easy fix.
Here dinopart1 is shown and dinopart2 is hidden. The dot next to the slot is for hiding the slot, but it's only for in the editor, eg if some slots are in your way. It doesn't have any affect at runtime. For that use the dots next to each attachment.
Loading Image
Ucenna wroteI'm contemplating doing the zero alpha optimizations you mentioned
Yeah it's a reasonable check, usually. Eg:
spine-runtimes/spine-sfml.cpp at 3.8
Nate wroteHere dinopart1 is shown and dinopart2 is hidden. The dot next to the slot is for hiding the slot, but it's only for in the editor, eg if some slots are in your way. It doesn't have any affect at runtime. For that use the dots next to each attachment.
Loading Image
Okay, so in that example; dinopart1 and dinopart2 both end up rendering; where I would expect just dinopart1 to renderer. Am I disabling dinopart2 correctly? I'd been under the impression that I was disabling the attachment in that image, but tbh not exactly the most pro with spine.
As you've shown it, dinopart2 can't be rendering because it's hidden. If you are showing the setup pose, maybe you show it in an animation.
Your slots look a bit odd: I would expect all those images to be under the same slot if only one of them is ever shown at the same time.
Nate wroteAs you've shown it, dinopart2 can't be rendering because it's hidden. If you are showing the setup pose, maybe you show it in an animation.
Your slots look a bit odd: I would expect all those images to be under the same slot if only one of them is ever shown at the same time.
Okay, must be something in the runtime I need to fix then, just gonna have to find it. Normal in that case calling slot->getAttachment() should return null, right?
Yeah, we're still learning spine here, so some of our animations are probably not made in the ideal way, although actually I think in that animation some of the attachments need to stick around for a bit so that they can fade to zero alpha.
Yep, when an attachment is hidden the slot has either null or some other attachment.