Performing a blit with an often-varying alpha


Pretty new to SDL2, and I’m wondering whether to venture out of my comfort zone and dip a toe in the waters…

I have a plan for a game with fog-of-war. Ideally I’d like to stamp an 800x800 pixel alpha channel into the alpha of a ‘map’ image, with white being completely visible, and black being completely dark. As the player moves and the map scrolls, the stamp happens and the map is revealed. It’s complicated slightly because the alpha in the map can’t ever get above 128, and the alpha around the player can go up to 255. This gives the tri-state {never-visited, seen-but-out-of-range, and visible} effect that makes it more playable.

I’m not sure the best way to go about this with SDL. I could do it with shaders, although the tri-state thing makes it a bit wasteful of textures :slight_smile:

At a very basic level, I could presumably create a surface with the map in it, and use the CPU to stamp the alpha channel as the player moved (bordered by (un)locksurface as appropriate), then just blit the surface to the screen. I was wondering if there was a more efficient way with blend-modes and render-to-texture.

That’s what I would try. None of the standard blend modes is suitable, but (depending on your platform) you can hopefully use SDL_ComposeCustomBlendMode() to create a blend mode which leaves the destination RGB unchanged but either multiplies the destination alpha by the source alpha or replaces the destination alpha by the source alpha, depending on which best suits your requirement.

Turned out to be not too tough in the end. The pseudo-code looks like:

  • Render screen-sized chunk of [map] to frame buffer {BLENDMODE_NONE}
  • Render screen-sized chunk of [fog-of-war] to [lights] {BLENDMODE_NONE}
  • Render [maxed-fog] into [lights] for each player {BLEND_MAX}
  • Render screen-sized [lights] to [fog-of-war] {BLENDMODE_NONE}
  • Render [full-range-fog] into [lights] for each player {BLEND_MAX}
  • Render [lights] to frame buffer {BLENDMODE_MOD}

In the above:
[map] is the huge map-area
[fog-of-war] is the same-sized overlay which masks out non-visited areas
[full-scale-fog] is an 800x800 pixel 2D-gaussian-like profile, values from 0…255
[maxed-fog] is the same but has its values capped to 96
[lights] is a screen-sized buffer for temporary work

BLENDMODE_NONE and BLENDMODE_MOD are the standard ones, BLEND_MAX is a composed custom blend mode that specifies SDL_BLENDOPERATION_MAXIMUM for both alpha and RGB operations.

The repeat render for [maxed-fog] (which is then copied into the overall fog-of-war) and [full-range-fog] (copied onto the framebuffer but not persisted) is what gives you the tri-state fog, with the nice tail-off into invisibility.

Put it all together, and it gave the following:

where you can see the three states …

  • black = never explored,
  • shaded = explored but currently out of range
  • brightened = currently visible

Which is exactly what I want - as you move around, you can see the immediate vicinity, but the area you’re moving away from darkens to show that you’ve explored it, but don’t know what’s there at this moment. All the above, even at 1080p takes about 6% of the GPU according to Activity Monitor, so it’s reasonably efficient :slight_smile:

1 Like