Feature Request: Change a surface's blend mode

Started by Satoh, July 06, 2016, 08:49:04 PM

Previous topic - Next topic
The doc gives the exact formulas: http://www.solarus-games.org/doc/1.5/lua_api_drawable.html#lua_api_drawable_set_blend_mode
If you set RGB to all zero and use color modulate, you obtain black because it is a multiplication.

a lot of neat effects can be gained by drawing surfaces with different blend modes onto each other, before finally rendering them on the screen. This is exactly what I hoped for.

For those who don't fully comprehend blend modes, here's a quick rule of thumb:

Treat the 0-255 values as though they are 0-100% or 0.0-1.0
(You still have to type them in as 0-255, but the math they do is more equivalent to 0.0 to 1.0)

Multiplication works this way. If you have 0.5 * 0.5 (which would be 128*128 RGB) what you end up with is 0.25 (which turns into 64 in RGB equivalent.)

Additive works the same basic way. If you add 0.75 (or 192) to 0.75 (192) you end up with 1.5 (technically 384, but it gets capped at 255)

Keeping that in mind, you just have to imagine whether you want to make something darker (by making the numbers smaller, by multiplying their percentages) or brighter (by adding them together).


If you set up 2 surfaces, lets say

local shadow = sol.surface.create()
local light_mask = sol.surface.create()
--You can then set them to two different blend modes
shadow:set_blend_mode("color_modulate")
light_mask:set_blend_mode("additive_blending")
--now, lets pretend each entity in the map will draw a 'glow sprite' onto the light_mask surface
--having drawn those entities' sprites in light_mask, we can use it to cut away selective dark areas in shadow
--by simply drawing it into the shadow surface
light_mask:draw(shadow)
--and finally we can draw it in our scene like so

local scene_surface = sol.surface.create()

map:on_draw(scene_surface)
  shadow:draw(scene_surface)
end

and our map will be darkened everywhere there is not an entity drawing a light mask sprite

*Note that trying to change the blend mode on [map:on_draw(scene_surface)] directly, it doesn't seem to have an effect.
Patience is difficult and rarely thanked, but always appreciated, and sorely missed when absent.

July 12, 2016, 11:38:10 PM #17 Last Edit: July 12, 2016, 11:51:08 PM by MetalZelda
Okay, I get it, I finally added the blending effect and changed the values, etc.

But, except for being darker, I don't notice that much difference, except colors are more vibrant in sunset / sunrise and more natural at night time.
Looks fine to me, but again I'm a noob at these things, and OP seems to know these so I need some feedbacks

Before & After (Don't pay attention to the clouds, I'll look at them later)



Am I doing it wrong ?

July 13, 2016, 12:34:31 AM #18 Last Edit: July 13, 2016, 12:41:56 AM by Satoh
It depends. What effect are you trying to achieve?

If you want the daytime to be bright, set the surface to pure white, and it will not be darkened at all.
If you want the daytime to be yellow tinted, set the color to fully bright yellow. (255,255,0,255)
if you want the scene to turn blue, set the color to (0,0,255,255).
If you want the scene to be slightly darker, with a little bit of a blue tint, set it to something like (192,192,255,255)
If you want it really dark and slightly blue, set it to (64,64,128,255)

The reason to use modulate blending is to darken the scene without making it look smokey and desaturated, like what happens when you just use a regular semitransparent solid color on top of it.

That is, if you used a dark color, with blend mode Blend, at 50% transparent, the blacks would lighten and the lights would darken.
With color_modulate the darks stay dark, but the lights get darker too.

With additive, the lights stay light, and the darks get lighter, but colors stay bright, instead of being washed out, like the same that happens in the previous example.

Color choice is really important  with blend modes.

Additive blend is really good for things like fire and light beams and mirages, modulate is better for things like shadows and color tinting.

Light colored mods are best for slight effects, while dark colored mods will have more severe effects. Pure white mods will be invisible. (multiplying 1 * whatever)

Dark colored adds will be better for slight effects, like a dim glow from a candle, while brighter adds will more sharply glow (until they reach pure white). Pure black adds are completely invisible (adding 0 + whatever)
Patience is difficult and rarely thanked, but always appreciated, and sorely missed when absent.

I wanted to make something similar to your second screen , but I'm satisfied with the current result, sunrise, sunset and night feels right, and much vibrant than before

July 13, 2016, 01:09:51 AM #20 Last Edit: July 13, 2016, 01:13:51 AM by Satoh
Quote from: MetalZelda on July 13, 2016, 12:43:22 AM
I wanted to make something similar to your second screen , but I'm satisfied with the current result, sunrise, sunset and night feels right, and much vibrant than before
Well, I don't remember the exact values I used in the original screenshot, but
shadow_surface:fill_color({32,64,240,255})
shadow_surface:set_blend_mode("color_modulate")

Gets really close to it.

And yeah, your sunrise/set look pretty rich and nice.
Patience is difficult and rarely thanked, but always appreciated, and sorely missed when absent.

Thanks for the feedback!
Satoh: since you are clearly an expert, can you tell me if the names I chose are appropriate blending mode names: "none", "alpha_blending", "additive_blending" and "color_modulate"?
Instead of "color_modulate", would "modulate_blending" be better?

July 13, 2016, 10:50:32 AM #22 Last Edit: July 13, 2016, 11:15:03 AM by Diarandor
Hi, I could be wrong but I think something is wrong with the definition of alpha blending.
I wrote the explanations here:
https://github.com/christopho/solarus/issues/931

EDIT: I added another issue to request custom blendings. That may be very useful:
https://github.com/christopho/solarus/issues/932
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

Well if its just naming convention, most engines simplify it to 'None', 'Alpha'/'Blend', 'Add', 'Multiply'
Some engines also have a variant of additive that is subtractive ( (-1*source) +destination )** which is sort of similar in effect to multiply, but applied differently and with different side effects... (still used usually for the same goal, darkening).

But if its preference for naming... none, alpha, add, multiply are the most directly intuitive (not only because that's what many engines call them, but also because that's the operation they do mathematically)

@Diarandor
the four modes Christopho added were predefined by the SDL library. It would likely require a much deeper amount of digging into that to supply a custom blend mode. Not to say it wouldn't be a neat feature, but it would likely be more difficult to implement.

**There are also 'power' /'exponent' and 'divide' modes... none of which I've ever actually seen used outside of purely mathematic calculations.


Incidentally, it seems like certain blend modes don't work well on sprites. I may just be testing it wrong, but switching a sprite to additive_blending or color_modulate seems to make them not draw at all on a surface. (It might be that I'm misunderstanding them though. I'm using sprites like any other surface, with a simple sprite:set_blend_mode("additiv_blending") sprite:draw(dst_surface,x,y) )

That might be something to test and look into.. I'm not sure. (Sprites inherit from drawable, but they also have a lot more code, so I don't know)
Patience is difficult and rarely thanked, but always appreciated, and sorely missed when absent.

It is probably a bug if sprites don't work with new blending modes. You can open an issue on github.
Ok I think I will rename the blending modes before the release, thanks for the info.

Thanks for the info, Satoh.

I thought the alpha blending had a unique definition, but I was wrong. The definition of the Wikipedia is probably the most standard definition as I have read (and it has very nice properties, like associativity). The definition given in the wiki of the SDL library seems different and non-equivalent (it almost coincides with the formula for pre-multiplied alpha, but there appears one factor that should not be there in the formula, as you can check with the Wikipedia: https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending); maybe their code is fine and it's just that their documentation has a mistake in the formula, who knows. But it is ok anyway, even if the alpha blending is a different ones, since the results are similar in most of the cases. Well, I might be wrong in all of this, so it does not matter at all.

At first I thought all of this had been progammed by Chris, that's why I opened the issues, but I was wrong, this all comes from some SDL library and we cannot change it. So forget about this. I will close both issues. Sorry for the annoyance.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

Satoh, using your example code, what kind of image would you want to use to create the brightened area? I'm currently trying with a black image where a white circle represents the light area and it doesn't seem to be working. I'm trying to attack a glow to some statues that I already have in a map, but while dimming the entire room works, I'm not getting any glow (I'm attempting to create the glow sprite at run time and draw everything to the shadow surface, which I am drawing in map:on_draw).

Code (lua) Select

    shadow:fill_color({32,32,255,255})
    shadow:set_blend_mode("color_modulate")
    light_mask:set_blend_mode("additive_blending")
    for entity in map:get_entities("statue") do
      local x, y, z = entity:get_position()
      local sprite = sol.sprite.create("entities/torch_light")
      sprite:set_xy(x, y)
      sprite:draw(light_mask)
    end
    light_mask:draw(shadow)

July 15, 2016, 02:12:55 AM #27 Last Edit: July 15, 2016, 03:01:50 AM by Satoh
Well... I'm not sure exactly why you're not getting the effect you need, but here's how I did mine (note that I used all hard coded values for stuff in this specific map, because its just testing things.)

Code (lua) Select
local map = ...
s = sol.surface.create(512,256)--s is my shadow
s2 = sol.surface.create(512,256)--s2 is my light
s:set_blend_mode("color_modulate")
s2:set_blend_mode("additive_blending")
local ss = sol.surface.create()--ss is my final surface for drawing the screen
function map:on_draw(ss)
  local x,y = map:get_camera():get_position()
  local w,h = map:get_camera():get_size()
  s:clear()
  s:fill_color({032,064,128,255})
  s2:clear()
  for e in map:get_entities("lit_") do
    local xx,yy = e:get_position()
    local sp = sol.sprite.create(e.sprite or "glow")
    sp:set_blend_mode("alpha_blending")
    sp:draw(s2,xx,yy)
  end
  s2:draw_region(x,y,w,h,s,x,y)
  s:draw_region(x,y,w,h,ss)
end


in your code I think
Code (lua) Select

local mapsurface = sol.surface.create()
function map:on_draw(mapsurface)
-------------------------------------------------
    shadow:fill_color({32,32,255,255})
    shadow:set_blend_mode("color_modulate")
    light_mask:set_blend_mode("additive_blending")
    for entity in map:get_entities("statue") do
      local x, y, z = entity:get_position()
      local sprite = sol.sprite.create("entities/torch_light")
      sprite:set_xy(x, y)
      sprite:draw(light_mask)
    end
    light_mask:draw(shadow)
    ------------------------------------------
    shadow:draw_region()--...stuff for camera here, my code has an example of how to get the portion of the surface to draw
end


You need three surfaces at least, as far as I can manage.
You draw your lights onto the additive surface
you draw the additive onto the modulate
and you draw the modulate onto the map's on_draw surface

I got the coords and size of the camera, so I could draw a portion of the map that was only as big as the screen, but my shadow and light surfaces were the full size of the map. (I only drew portions of them at a time though)

Setting sprites to additive directly, doesn't seem to work correctly right now. But try looking at my code (sorry its so undescriptive with its names...) to see where yours differs. Mine works for me.

EDIT:
I'd like to point out I'm no expert on the workings of surfaces in Solarus. I only just started messing with them when the blendmode snapshot came out.
I just kept trying things until I found what worked. I have some experience with blend modes and lua previous to this, but otherwise its all just something I'm learning as I go. Just a disclaimer in case there's a question I can't answer.
Patience is difficult and rarely thanked, but always appreciated, and sorely missed when absent.

Alright - got it to work! Thanks!

I think something else on the map was throwing things off, so I tested on a fresh map and then moved the correct code. This actually cleaned up my code by a lot and created an infinitely better effect!

I renamed the blend modes to "none", "blend" (default), "add" and "multiply".
Don't be surprised with the next snapshost or the release.