Updating a texture to have transparent pixels

I have an all-black texture which acts as a “veil” for a mini-map which is meant to be “uncovered” (by setting pixels to transparent) as the player moves through the world.

My first attempt involved setting render target to the texture and doing SDL_RenderFillRect() with SDL_SetRenderDrawColor(renderer,0,0,0,0) but this did not work.

Now I’m trying something like this:

        SDL_Texture * veilTexture = assetStore->GetTexture(MINIMAPVEIL);
        SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
        void* pixels;
        int pitch;
        SDL_LockTexture(veilTexture, NULL, &pixels, &pitch);
        Uint32* pixelData = (Uint32*)pixels;
        for(int y = 100; y < 120; ++y){
            for(int x = 100; x < 120; ++x){
                pixelData[y * (pitch / 4) + x] = 0x00000000;
            }
        }
        SDL_UnlockTexture(veilTexture);

This texture was initialized like this:

SDL_Texture * mapveil = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, 360, 360)

This approach also isn’t working for me. I believe there is some knowledge that I am lacking in order to proceed with this issue. I am also concerned about the efficiency of this; it updates each pixel individually (I’ve gotten it to update the pixel colors, but I cant seem to update the alpha value) and I wonder if there is a better approach.

Does anyone have a suggestion for how to proceed with something such as this? Thank you.

So the reason using SDL_FillRect() doesn’t work is because the alpha channel in the draw color gets used to blend the rect with whatever is already there. So with alpha = 0, you get no change.

edit: an alternate method would be having the fog of war be a tile map. Then your tileset will have tiles with transparent edges for the edge of the fog, a fully opaque tile, and a fully transparent tile. Then just draw the tiles based on the tileset. An optimization would be to check this tilemap when drawing the rest of the level, and skip anything under the fully opaque tiles. And obviously don’t submit a draw call at all for the fog of war tiles that are fully transparent.

edit 2: the function SDL_SetRenderDrawBlendMode() only sets the blending mode used for SDL_Renderer’s shape drawing functions (lines, points, rects, etc.) and doesn’t affect texture drawing. Instead use SDL_SetTextureBlendMode() after creating your texture. I would’ve sworn SDL had a program-wide way to set this, but trying to find things on the SDL2 wiki these days… :man_shrugging:

Here, this works (SDL 2.28.5)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <SDL2/SDL.h>

static const int SCREEN_WIDTH = 800;
static const int SCREEN_HEIGHT = 600;

int main(int argc, char **argv)
{
	if(SDL_Init(SDL_INIT_VIDEO) < 0) {
		fprintf(stderr, "ERROR: can't init SDL: %s\n", SDL_GetError());
		exit(EXIT_FAILURE);
	}

	atexit(SDL_Quit);

	uint32_t windowFlags = SDL_WINDOW_ALLOW_HIGHDPI;
	SDL_Window *window = SDL_CreateWindow("Fog of War",
			SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
			SCREEN_WIDTH, SCREEN_HEIGHT,
			windowFlags);
	if(window == NULL) {
		fprintf(stderr, "ERROR: can't create window: %s\n", SDL_GetError());
		exit(EXIT_FAILURE);
	}

	uint32_t renderFlags =	SDL_RENDERER_ACCELERATED |
							SDL_RENDERER_PRESENTVSYNC;
	SDL_Renderer *render = SDL_CreateRenderer(window, -1, renderFlags);
	if(render == NULL) {
		fprintf(stderr, "ERROR: can't create renderer: %s\n", SDL_GetError());
		exit(EXIT_FAILURE);
	}

	SDL_Texture *fow = SDL_CreateTexture(render, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, SCREEN_WIDTH, SCREEN_HEIGHT);
	if(fow == NULL) {
		fprintf(stderr, "ERROR: can't create Fog of War texture: %s\n", SDL_GetError());
		exit(EXIT_FAILURE);
	}

	// NOTE: WON'T WORK WITHOUT THIS!
	SDL_SetTextureBlendMode(fow, SDL_BLENDMODE_BLEND);

	uint32_t pixelFormat;
	SDL_QueryTexture(fow, &pixelFormat, NULL, NULL, NULL);
	SDL_PixelFormat *fowFormat = SDL_AllocFormat(pixelFormat);
	uint32_t blackTransparent = SDL_MapRGBA(fowFormat, 0, 0, 0, 0);
	uint32_t blackSolid = SDL_MapRGBA(fowFormat, 0, 0, 0, 255);
	SDL_FreeFormat(fowFormat);

	int running = 1;
	uint32_t frame = 0;
	while(running) {
		SDL_Event event;
		while(SDL_PollEvent(&event)) {
			switch(event.type) {
			case SDL_QUIT:
				running = 0;
				break;
			}
		}

		{
			void *pixels = NULL;
			int pitch = 0;
			if(!SDL_LockTexture(fow, NULL, &pixels, &pitch)) {
				int pitch4 = pitch / 4;
				uint32_t *pixelData = (uint32_t *)pixels;
				// Clear to solid black
				SDL_memset4(pixelData, blackSolid, SCREEN_HEIGHT * pitch4);

				// Clear area around player
				/*for(int y = 100; y < 120; y++) {
					for(int x = 100; x < 120; x++) {
						pixelData[y * pitch4 + x] = blackTransparent;
					}
				}*/
				// Alternate, probably faster way
				for(int y = 100; y < 120; y++) {
					SDL_memset4(&pixelData[y * pitch4 + 100], blackTransparent, 20);
				}

				SDL_UnlockTexture(fow);
			}
		}

		SDL_SetRenderDrawColor(render, 0, 127, 255, 255);
		SDL_RenderClear(render);

		SDL_RenderCopy(render, fow, NULL, NULL);

		SDL_RenderPresent(render);
	}

	return EXIT_SUCCESS;
}
1 Like

thank you for taking the time to help me

1 Like

What about SDL_BLENDMODE_NONE?

Another idea is to create your own blend mode — SDL_ComposeCustomBlendMode — and use the operation SDL_BLENDOPERATION_MINIMUM.

SDL_BLENDMODE_NONE is the default and results in no alpha blending at all (i.e. the fog-of-war texture is treated as though it has no alpha channel)