Dynamic Masking Technique
Welcome back to another devlog. You may remember that in the last one I had discussed the wibbly plasma effect that I use in the ‘GunSoul’ lettering on the title screen. I only had time to talk about the actual plasma effect in that video and I promised I would cover the ‘cookie-cutter’ masking technique next. Well, here I am to do just that!
Code and Sprites
I think the best thing to do is to just dive in and look at the sprites I’m using and the object code. We’ll need to take a quick detour into blend modes at some point but we’ll cross that bridge when we get to it.
Ok, so we have an object in our title room called ‘obj_TitleGFX’ which is assigned a sprite called ‘spr_TitleGFX’. This sprite is the lettering, consisting of an inner purple part (which doesn’t move or anything) and a thick grey outline.
I also need to point out this sprite called ‘spr_TitleGFX_PlasmaMask’ which only has the inner purple part. It’s important to note here that the pixels you can see (the purple) have alpha values of 1 whereas everything else is transparent which means the alpha value is 0.
The purple areas where alpha is 1 is the area where we are going to want to draw our plasma effect.
So now we can have a look at the code in the draw event of obj_TitleGFX to see how these are used…
The first line is
draw_self();
Which just daws the currently assigned object sprite in the normal way. This is spr_TitleGFX and so we get our GunSoul lettering drawn with the purple inner and the grey outline. So far so normal.
The next set of lines get a bit more complicated as we’re setting things up to go and set the alpha values of our screen surface to 0:
gpu_set_blendenable(false); gpu_set_colorwriteenable(false,false,false,true); draw_set_alpha(0); draw_rectangle(0,0, room_width, room_height, false);
Let’s look at these in turn.
gpu_set_blendenable(false) will turn off all the alpha blending maths. So anything we draw now will just be drawing the source directly over the destination.
gpu_set_colorwriteenable(false,false,false,true) is telling the GPU that it’s not allowed to write into the R, G or B channels. It can only write into the Alpha channel.
draw_set_alpha(0) is telling the GPU to set the alpha to 0 for all draw events
draw_rectangle(0,0, room_width, room_height, false) is drawing a rectangle over the whole screen.
Remember at this point we have told the GPU not to alter any of the actual colours, only the alpha channel and that has to be 0. So, the entire screen surface pixels have had their alpha values set to 0 (they would usually be 1)
The next couple of lines are
draw_set_alpha(1); draw_sprite(spr_TitleGFX_PlasmaMask,0, x, y);
Here we’re resetting the default alpha drawing to 1 as normal.
Then we’re drawing a sprite - note it’s not the same sprite we’ve already drawn. It’s the spr_TitleGFX_PlasmaMask.
But we haven’t re-enabled the colour writing and so the GPU will still only affect the Alpha values in the destination.
Following this the screen surface pixels will have alpha values of 1 only where they were 1 in the PlasmaMask i.e the purple bits.
The next couple of lines are
gpu_set_blendenable(true); gpu_set_colorwriteenable(true,true,true,true);
Here we’re just turning alpha blending and colour writing back on.
Everything so far has been to set up our screen alpha the way we want. Essentially we’ve set the screen alpha to 0 everywhere except for where we want to be able to draw our plasma.
The next part is to draw our plasma into those parts of the screen where alpha is 1. We need to set up a particular set of blend modes to do this though. These will use the destination alpha rather than the source alpha - remember the destination is the screen surface and this is where we’ve gone to great trouble to set the alpha just the way we want it.
The next line accomplishes this:
gpu_set_blendmode_ext(bm_dest_alpha,bm_inv_dest_alpha);
At this point I’ll take us on that detour into blend modes that I mentioned earlier. This is the mathematical, techy bit…
Blend Modes
When we draw something on screen the GPU is actually doing the following calculation for every pixel:
final_pixel_colour = (Rs,Gs,Bs,As) * source_blend_factor + (Rd,Gd,Bd,Ad) * destination_blend_factor
Ordinarily the source and destination blend factors are set to depend only on the source alpha like this:
gpu_set_blendmode_ext(bm_src_alpha, bm_inv_src_alpha);
But we’ve told it to use factors which depend on the destination alpha instead i.e the screen surface alpha:
gpu_set_blendmode_ext(bm_dest_alpha,bm_inv_dest_alpha);
Let’s work through an example where the screen alpha is 0 and where it is 1 (i.e where we had a purple sprite pixel and we want to draw from our source plasma).
First where destination alpha is 0 i.e we don’t want to draw here:
Let’s say that our source pixel from the plasma is purple which is a combination of Red and Blue:
Source = ( 1, 0, 1, 1) so Rs = 1, Gs = 0, Bs = 1, As = 1
Where Rs = Red of the Source,
Gs = Green of the Source,
Bs = Blue of the Source,
As = Alpha of the Source
And the Destination is black:
Destination = (0, 0, 0, 0) So Rd = 0, Gd = 0, Bd = 0, Ad = 0
So according to the 'final pixel' formula above then:
final_pixel_colour = (Rs,Gs,Bs,As) * Ad + (Rd,Gd,Bd,Ad) * (1-Ad)
Note - the inverse of Ad is (1 - Ad) and can be written as InvAd
So let’s work this out for our example pixel
Final_pixel_colour = [(Rs*Ad)+(Rd*InvAd), (Gs*Ad)+(Gd*InvAd), (Bs*Ad)+(Bd*InvAd), (As*Ad)+(Ad*InvAd)]
= [(1*0)+(0*1), (0*0)+(0*0), (1*0)+(0*0), (1*0)+(0*1)]
=[0, 0, 0, 0]
= our Destination pixel
Which is what we expect in this case because this is a screen pixel where we set it to 0 and it wasn’t overwritten to 1 by the purple region of our PlasmaMask sprite
Next let’s look at what happens when the screen pixel has an alpha of 1 i.e we do want to draw a plasma pixel here:
Let’s say that we have the same source values as the last example but this time the destination is
Destination = (0, 0, 0, 1) So Rd = 0, Gd = 0, Bd = 0, Ad = 1
This time, according to the formula:
Final_pixel_colour = [(Rs*Ad)+(Rd*InvAd), (Gs*Ad)+(Gd*InvAd), (Bs*Ad)+(Bd*InvAd), (As*Ad)+(Ad*InvAd)]
= [(1*1)+(0*1), (0*1)+(0*1), (1*1)+(0*1), (1*1)+(0*1)]
= [1, 0, 1, 1]
= our Source plasma pixel
Which is what we want because this is a screen pixel that was overwritten to an alpha of 1 by the purple region of our PlasmaMask sprite
The GPU goes through this process for every pixel being drawn and, as you can see, the only screen pixels which it will write into are those where the alpha is 1.
Ok, I’ll stop there and we can return to the rest of the code.
Code Continued
The next few lines are
shader_set(sh_Plasma); shader_set_uniform_f(sh_time, myTime); draw_self(); shader_reset();
Shader_set(sh_Plasma) command tells the GPU to use the plasma shader that we made in the last devlog instead of the default shader. If you recall, this shader completely ignores the sprite data it is given and uses some maths to make the wibbly plasma effect.
Shader_set_uniform_f(sh_time, myTime) you can basically ignore for this devlog. This command is how we pass the time into the shader to make it move. See the previous devlog to see how this is used by the shader.
draw_self() is where the GPU actually goes and does the drawing process using the plasma shader and the blending process we just went through. Up to this point we had just been doing setup.
shader_reset() tells the GPU to discard the plasma shader and go back to using the default shader or whatever it had been using previously. If we didn’t do this then the GPU would continue using the plasma shader for every sprite draw from now on and everything would end up looking like a plasma. Obviously we don’t want this.
The last couple of lines are also for resetting alphatest and blendmode behaviour back to normal:
gpu_set_alphatestenable(false); gpu_set_blendmode(bm_normal);
And that brings us to the end of this devlog. I would have liked to point you to the original GameMaker blog post for this but it seems to have been removed for some reason. Hopefully you’ve been able to follow my explanation though and you might be able to use it in your own projects.
Get Gun Soul
Gun Soul
Repent at the altar of firepower!
Status | Released |
Author | BarrowBloke |
Genre | Platformer, Action |
Tags | 16-bit, Arcade, GameMaker, gun-soul, Local Co-Op |
Languages | English |
More posts
- Small tweaks to gameplay and fontsJun 18, 2024
- GunSoul is released!May 04, 2022
- Playable Demo Released!Jan 13, 2022
- Old Skool Demo PlasmaSep 08, 2021
- Let's Talk About InputJun 03, 2021
- Welcome to Gun SoulApr 13, 2021
Leave a comment
Log in with itch.io to leave a comment.