GameMaker Drawing to a surface doesn't work like drawing to screen :(

Tsa05

Member
Hmmrm, blend mode help? I'm drawing 2 images to a surface, but it messes up. They are png files, a character face and then an alternate expression face:


They weren't drawing properly, so I've created a simplified example. Here's my complete example code (drawing event of object_0):
Code:
var sur = surface_create(352,791);
draw_clear_alpha(c_white,0);
surface_set_target(sur);
draw_sprite(sprite_0,-1,0,0);
draw_sprite(sprite_1,-1,152,39);
surface_reset_target();
draw_surface(sur,x,y);
surface_free(sur);

draw_sprite(sprite_0,-1,x+sprite_get_width(sprite_0),y);
draw_sprite(sprite_1,-1,x+sprite_get_width(sprite_0)+152,y+39);
Note that I'm drawing the same 2 images, twice. First time, I draw them to a surface, then draw the surface. Second time, I draw them to the screen. Here's the result:


On the left, you can see a sort of fuzzy ring around the face, where the top image's transparency ought to be. On the right, the result I'm looking for.

I've reasoned out that the cause of this is the fact that the top image, when writing, is spewing its alpha value onto the surface as it draws, causing the surface to go see-through at those spots. I need it to draw like normal, where it considers the alpha of the image for drawing, but doesn't apply it to the surface.

What route might I try to get these guys lining up properly? (I'll be sitting here thinking as well; just tried a variety of blend mode combinations before realizing I'm not actually sure I'm going about it right)
 

obscene

Member
Try this:

draw_set_blend_mode_ext(bm_one, bm_inv_src_color);

Not sure if that's what you need but it fixed similar issues for me.
 

CMAllen

Member
Drawing to surfaces blends alpha values in the same way it blends the other color channels. In fact, surfaces see the alpha channel as just another color channel. Now, depending on how you're doing things, you DO have options/ways to get around this. In the example you posted, you've already drawn over all the areas where subsequent draw calls will be made, so you don't need to worry about modifying unused pixels. You only want to avoid over-writing existing alpha values. There is a function for that -- draw_set_color_write_enabled(r, g, b, a). You can turn on or off write calls to any of those channels as needed, preventing anything from writing to a given channel until that channel is enabled again.

(non-ninja edit) you can also get around this problem by having your layering elements only have an alpha value of 0 or 1.0 (fully opaque or fully transparent). Then no alpha blending takes place, and you don't get any incorrect alpha blending. I almost forgot that option.

Now, if you're layering images, where only parts of subsequent draw calls overlap previous draw calls, the above won't work. I think there's a blend_mode_ext combination that can fill the gap a bit, but you need to draw things in the *reverse* order. Draw your top most elements first, ending with whatever is on the bottom. I'm not 100% on this, however. I've been using it in my own work, and it eliminated the nasty alpha blending, but it has the quirk that subsequent draw calls are drawn *behind* previous draw calls. It's blend_mode_ext(bm_alpha_sat, bm_one). Again, this is the behavior I've observed, but I cannot be assured that I understand it completely. Try it out first and see if it does what you want.

There's also the option that should (in theory) work but is more computationally pricey -- draw everything in blend_mode(bm_add), which will cause all your channels to add together, which is what you want for the alpha channel, then you draw everything again with blend_mode(bm_normal) while locking the alpha channel (as above), allowing you to overwrite the additive RGB values with those that use normal blend mode without touching the additive blended alpha channel. This is computationally more expensive because you're doing more than double the work. There are two blend mode calls, which interrupt GM's draw batching, causing it to slow down, and you are also drawing everything twice. So obviously, this sort of approach should only be used to create image images that are preserved for later use, as opposed to creating images on the fly.

Last option of all is shaders. And that's where my ability to help ends. I'm still trying to work out creating a shader that can be used to simulate bm_normal drawing functionality when drawing to surfaces, but I haven't made much progress.
 

John Andrews

Living Enigma
Why dont you make the outer part of the alternate face not blurry, but like something cut out of an image, not smoothed
 

Tsa05

Member
@CMAllen Oh!! I thought that gpu_set_colorwriteenable was a Windows target function only, which caused me to avoid it; Looking at the manual, it actually makes no mention of such a restriction. I'll have to test of course, but...neat! (I'm avoiding bm_src_alpha_sat for that reason).

So, for me, the thing that appears to be working is:
Code:
var sur = surface_create(352,791);
surface_set_target(sur);
draw_clear_alpha(c_black,0);

    draw_clear_alpha(c_black,0);
    gpu_set_blendmode(bm_add);
    draw_sprite(sprite_0,-1,0,0);
    draw_sprite(sprite_1,-1,152,39);   

    gpu_set_blendmode(bm_normal);
    gpu_set_colorwriteenable(1,1,1,0);
    draw_sprite(sprite_0,-1,0,0);
    draw_sprite(sprite_1,-1,152,39);
    gpu_set_blendenable(true);
    gpu_set_colorwriteenable(1,1,1,1);
        
surface_reset_target();

gpu_set_blendmode(bm_normal);
draw_surface(sur,x,y);
surface_free(sur);
As you mention, not the most elegantly smooth solution, but...I can make it work. Thanks for the assistance!

(@John Andrews : I would need to have exact, pixel-perfect cutouts, which is effectively the same as having no transparency at all. I actually found the issue originally by using a cutout of the image, with no blurriness on the edges, because even a smooth cutout has tiny variations (aliasing) along the edges, which made a very hard to see skinny ring instead of the large fuzzy one)
 

CMAllen

Member
No problem, and happy to help. This is an issue I've been tinkering away at to find simple-to-explain solutions that avoid taking the nuclear-option of shaders, because I'm still not familiar enough with those to accomplish the end goal.
 
Setup the images for premultiplied alpha and draw them to the surface with blend mode:
Code:
draw_set_blend_mode_ext(bm_one,bm_inv_src_alpha);
There is even a button in the image editor to do it for you. Edit: but that's GMS 1. Not sure if there's an option there. Basically you just multiply the color by the alpha channel. With gimp you can create an alpha mask, copy that to a new layer on top of your image, then just use multiply blend mode with the alpha mask.

 
Last edited:
Top