mag_zbc
mag_zbc

Reputation: 6992

DirectX alpha masking

I'm working on a game using DirectX 9. Here's what I'm trying to do:

After the scene is rendered, on top of it I want to render few sprites: a black cover on entire scene and a few sprites, which are masks showing where the cover should have holes. So far I tried messing with blend mode but with no luck. My code setting it up looks like this:

D3DD->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
D3DD->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
D3DD->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

I guess the best way would be to multiply each sprites alpha, but according to http://msdn.microsoft.com/en-us/library/windows/desktop/bb172508%28v=vs.85%29.aspx no such mode is supported. Is There another way to do this?

edit

Following Nico Schertler's answer, here's the code I came up with:

LPDIRECT3DTEXTURE9      pRenderTexture;
LPDIRECT3DSURFACE9      pRenderSurface,
                        pBackBuffer;

// create texture
D3DD->CreateTexture(1024,
                    1024,
                    1,
                    D3DUSAGE_RENDERTARGET,
                    D3DFMT_R5G6B5,
                    D3DPOOL_DEFAULT,
                    &pRenderTexture,
                    NULL);

pRenderTexture->GetSurfaceLevel(0,&pRenderSurface);

// store old render target - back buffer
D3DD->GetRenderTarget(0,&pBackBuffer);

// set new render target - texture
D3DD->SetRenderTarget(0,pRenderSurface);

//clear texture to opaque black
D3DD->Clear(0,
            NULL,
            D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
            D3DCOLOR_XRGB(0,0,0),
            32.0f,
            0);

// set blending
D3DD->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);
D3DD->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
D3DD->SetRenderState(D3DRS_SRCBLENDALPHA, D3DBLEND_ZERO);
D3DD->SetRenderState(D3DRS_DESTBLENDALPHA, D3DBLEND_SRCALPHA);
D3DD->SetRenderState(D3DRS_SEPARATEALPHABLENDENABLE, TRUE);

//// now I render hole sprites the usual way

// restore back buffe as render target
D3DD->SetRenderTarget(0,pBackBuffer);

// restore blending
D3DD->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
D3DD->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
D3DD->SetRenderState(D3DRS_SRCBLENDALPHA, D3DBLEND_SRCALPHA);
D3DD->SetRenderState(D3DRS_DESTBLENDALPHA, D3DBLEND_INVSRCALPHA);
D3DD->SetRenderState(D3DRS_SEPARATEALPHABLENDENABLE, FALSE);

ulong color = ulong(-1);
Vertex2D v[4];

v[0] = Vertex2D(0, 0, 0);
v[1] = Vertex2D(1023, 0, 0);
v[3] = Vertex2D(1023, 1023, 0);
v[2] = Vertex2D(0, 1023, 0);

D3DD->SetTexture(0, pRenderTexture);
D3DD->SetFVF(D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1);
D3DD->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v, sizeof(Vertex2D));

D3DD->SetTexture(0, NULL);

// release used resources
pRenderTexture->Release();
pRenderSurface->Release();
pBackBuffer->Release();

Unfortunatelly, the app crashes when restoring the old render target. Any advice?

Upvotes: 3

Views: 3610

Answers (1)

Nico Schertler
Nico Schertler

Reputation: 32667

Firstly, you should create the mask in a separate texture first. Then you can add the holes as needed. Finally, draw the mask on the screen:

Initialize the texture
Clear it to opaque black
Using the following blend states:
    D3DRS_SRCBLEND -> D3DBLEND_ZERO (hole's color does not matter)
    D3DRS_DESTBLEND -> D3DBLEND_ONE (preserve the black color)
    D3DRS_SRCBLENDALPHA -> D3DBLEND_ZERO
    D3DRS_DESTBLENDALPHA -> D3DBLEND_SRCALPHA
    D3DRS_SEPARATEALPHABLENDENABLE -> TRUE
Draw each hole sprite
Restore default blending (src_alpha / inv_src_alpha)
Render the texture as a sprite to the back buffer

The above blend state assumes that the holes are opaque where there should be a hole. Then, the color is calculated by:

blended color = 0 * hole sprite color + 1 * background color

which should always be black.

And the alpha channel is calculated by:

blended alpha = 0 * hole sprite alpha + (1 - hole sprite alpha) * background alpha

So where the hole sprite is opaque, the blended alpha becomes 0. Where it is transparent, the blended alpha is the previous value. Values in between are blended.

Upvotes: 1

Related Questions