views:

277

answers:

1

I am working on a game where I want to create shadows under a series of sprites on a grid. The shadows are larger than the sprites themselves and the sprites are animated (i.e. move and rotate).

I cannot simply render them into the sprite png, or the shadows will overlap adjacent sprites.

I also cannot simply put shadows on a lower layer by themselves, because when they overlap, they will create dark bands at their intersection.

These sprites are animated, so it is not feasible to render these en masse.

Basically, I want the sprites' shadows to blend together such that they max out at a set opacity. Example:

alt text

I believe this is equivalent to an openGL blending of (Rs,Gs,Bs,Max(As,Ds)), where I don't really care about R,G, and B, as it will always be the same color in src and dst.

However, this is not a valid openGL blending mode. Is there an easy way to accomplish this, especially in cocos2d-iphone?

I would be able to approximate this by making the shadow sprites opaque, then applying them both to a parent sprite, and making the parent sprite 40% opacity. However, the way cocos2d works, this only sets the opacity of each child to 40%, rather than the combined sprite image, which results in the same stripe.

+3  A: 

OK, I think after much research, I realize the error in my thinking.

First, there is a way to do Max Alpha openGL blending, and that is using glBlendEquations. You would have to modify the draw method to do this (and make some new attribute for a textureNode to switch between these methods), and use either of these:

 glBlendEquationOES( GL_MAX_EXT );
 glBlendEquationSeparateOES( GL_ADD, GL_MAX_EXT );

The first uses max() for RGBA, while the second uses standard addition for RGB, and max() for alpha.

Now, the problem is that each sprite, and it's children, in z-order, are drawn to the screen. This means, that after the first shadow is drawn, it is part of the main screen buffer. In other words, its opacity is changed, and with max() blending and a standard opaque background, it's now painted onto the background, so when you draw the next shadow, it will do max(As, 1) which is 1, because the background is opaque. So I don't achieve the blending I want. It would "work" if my background was transparent, but then I could not apply any background "behind" that, we can only add to the buffer.

I could draw the shadows first, using the max() blend and then do a {ONE_MINUS_DST_ALPHA, DST_ALPHA} glBlend. This is an OK workaround, but I have other issues that make this difficult.

Now, the real solution, I finally realize, is to use a RenderTexture to render the shadows to a separate buffer before applying them to the background. This could have performance implications, so we'll see where this goes.

Update:

OK, I have a working solution now.

Oddly, I ended up not needing this solution after all, because in my particular case, the banding was not noticeable enough to warrant the effort, however this is the solution I came up with that seemed to do what I wanted:

  1. First draw a 0% black alpha png to the entire screen. (I don't know if this is necessary. I don't know what the default alpha of the screen is).

  2. Draw the shadows to the screen using glEquationSeperateOES( GL_ADD, GL_MAX_ENT) and glBlend( GL_ONE, GL_ONE ). This will blend all of the shadows together as described, taking the max() of the alphas.

    • Because you are compositing to black, it is equivalent to use glBlendEquationOES( GL_MAX_ENT ). (X+0 == max(X,0))

    • glBlend( GL_ZERO, GL_ONE ) will eliminate the need of step 1, or at least the requirement of that layer to be 0% black. GL_ZERO essentially forces it to be 0% black.

  3. Draw the floor, which will be over the shadows, but use glBlend( ONE_MINUS_DST_ALPHA, DST_ALPHA ). This results in exactly the same results as if you added the shadows to the floor with glBlend( SRC_ALPHA, ONE_MINUS_SRC_ALPHA ), however if you did it this way, you would not be able to blend the shadows together (read why at top of answer).

  4. You are done! The nice thing about this method is that the shadow sprites can be animated as usual, without having to call "visit" on a separate renderTexture.

The rest:

I also modified CocosNode to allow me to add a shadow to a layer, which links to another CocosNode. This way, the shadow is rendered as a child of the floor, (or the 0% black background sprite for the shadow blending), but is linked to another CocosNode. When a sprite moves, if it has a shadow, it updates the shadows position as well. This allows me to have all shadows below all objects on the screen, and to have the shadows automatically follow the object.

Sorry for the long answer. Maybe my solution is kludgy, but it seems to work great.

Jeff B