How to interpret luminance/saturation values from a lightmap

Started by GarageGothic, Mon 28/04/2008 05:07:15

Previous topic - Next topic

GarageGothic

Inspired by one of tolworthy's recent blog entries I decided to script a lightmap module, where the tint values of the character are applied based on the colors in a sprite or an external bitmap file rather than regions. This allows for much smoother (and easier to set up in cases of many shade values) lighting effects. Also, lightmaps can be easily swapped or even animated if room conditions change - e.g. flipping a lightswitch.

Currently retrieving and applying RGB tint data works great. However, I'm having a few problems deciding how to interpret saturation and luminance values based on a lightmap and haven't been able to find any existing implementations for reference. I tried downloading SLUDGE, which alledgedly has the feature, but my virus scanner found a trojan in the .exe (it may have been a false positive, but the next time I restarted the computer I was logged out of windows instantly, even in safe mode - I ended up having to do a full reinstall).

My one solution for it would be to retrieve the brightness values from a secondary black and white bitmap file, but it seems to me that it should be possible to calculate the values from the colored image. Trouble is that whenever I used an average (or even weighted average) of the RGB values, it's only possible to get 100% brightness in pure white light. I want it to be possible for instance to have the character tinted bright red (which with an average calculation of (255,0,0) would give 33% brightness) yet be 100% bright. Whenever I try to implement workarounds, I still get the problem of the luminance dropping whenever two colors mix - so that a character walking through a gradient from pure red to pure green drops in brightness while he's passing through the middle section.

Any suggestions on how to implement saturation/luminance without resorting to separate lightmaps? I'd really like to see this module available to the public - I'm even working on an in-game lightmap painting tool.

Pumaman

Well, the standard AGS tinting routine converts the RGB pixel to HSV, then keeps the V from the pixel and then multiplies it as per the tint luminance setting.

The H and S come from either the pixel or the tint colour or somewhere in between, depending on the saturation.

So if you're trying to emulate an AGS-style tint, you can't do it purely with RGB arithmetic.

GarageGothic

Thanks for the reply!

I'm using the DrawingSurface.GetPixel function to retrieve the pixel at (character.x, character.y), then converting the color value to RGB and applying it as a Character.Tint. Would it help me in any way to convert the RGB values to HSV and then converting the S and V values to percentages (sat, lum) to use in the Character.Tint(r,g,b,sat,lum)? - I'm still unsure if it makes any sense to try to retrieve saturation from the lightmap, since I guess a less saturated color will already tint the character less noticably (so lowering the tint saturation would make the tint almost invisible).

I didn't quite understand, did you say that tinting already changes the shading of the sprite's pixels and not just their color value? If so, is there any way to counterbalance this through arithmetic when calculating the luminance value?

EnterTheStory (aka tolworthy)

Quote from: GarageGothic on Mon 28/04/2008 05:07:15I'm having a few problems deciding how to interpret saturation and luminance values based on a lightmap and haven't been able to find any existing implementations for reference. I tried downloading SLUDGE

This is from the SLUDGE help file. If it helps:
QuoteWith a light-map loaded, every character's red, green and blue colour values will be multiplied by the red, green and blue values of the pixel in the imageFile at the corresponding location. Lightmap modes are: PERPIXEL: Each pixel of a character is multiplied by the light map colour in the same position - i.e. one character will have many different colours applied to it in different places. HOTSPOT: Each pixel of a character is multipled by the light map colour at the character's hotspot (base point) - i.e. each character will only have one colour applied to it, determined by where it is currently positioned. The light-map arithmetic is applied before setCharacterDrawMode is taken into consideration.

In Sludge, setCharacterDrawMode makes the character lighter, darker, transparent, etc., and is applied separately. On its own, a light map of pure white would have no effect, and a small amount of gray would create a noticeable shadow. Your idea of animated shadows sounds even better.

GarageGothic

Thanks tolworthy, that was very helpful. I've been looking for the SLUDGE manual online but couldn't find it anywhere but in that trojan infected installer.

Multiplying the color seems like it would create very dark sprites if it works anything like in Photoshop (I'm not even sure what the process AGS performs is called, colorizing?). As a former SLUDGE user, were you happy with the way the engine interpreted lightmaps (both in regards to color and lightness)? Do you have any preference as to how you'd like to see it implemented in AGS?

Edit: I converted the RGB values to HSV and tried using S and V for saturation and luminance. It was closer to what I'm trying to achieve, but the character still gets VERY dark while passing through blending areas. I have a feeling that it could work, possible using some multipliers, but this whole thing makes my head spin.

EnterTheStory (aka tolworthy)

Quote from: GarageGothic on Mon 28/04/2008 12:27:45Multiplying the color seems like it would create very dark sprites
Yes, I found that! It's very sensitive to small changes in color, so I mostly used white and shades of very pale gray. Gradients worked fine, there was no obvious banding.

Quote from: GarageGothic on Mon 28/04/2008 12:27:45were you happy with the way the engine interpreted lightmaps (both in regards to color and lightness)?

Yes, but I was never very demanding. Most of my scenes needed just regular shadows here and there, and it was quick and easy. I think I used some red shadows for an underground Hades scene or two, or some blue underwater reflections, but I wasn't very adventurous. The most I did was just making black tunnels for characters to walk into and disappear from sight. I seem to recall that fading to white was a little harder, but doable by combining lightmaps and individual tinting.

Quote from: GarageGothic on Mon 28/04/2008 12:27:45Do you have any preference as to how you'd like to see it implemented in AGS?

I'm just amazed that you're doing it! That's great! Ease of use for simple stuff is all that matters to me. Oh, and the ability to have very subtle shades - I have a lot of shades of white.

My next game will be Dante's inferno, so anything that allows flickering flames would be uber-cool. :)

The only thing that ever bugged me about the Sludge implementation (and it's a VERY minor point) is that the lightmap had to be the exact same size as the background. Sometimes, being lazy, I just wanted to create a small lightmap and slap it in a corner of a scene or even reuse it in different scenes. As with the projector example. But that's a very trivial issue.

Beyond that...

Another idea that comes to mind (apart from the obvious one of animation) is to allow the offsetting of light maps according to a characters' position. And even scaling! Imagine a room where an overhead projector is turned on (using lightmaps). A character could walk between the projector and the wall and have the image shown on their face, offset and scaled in a dramatic way. Heck, they could even cast a shadow! But I suspect that would be a lot of work for one gimmick. But imagine if a lightmap could be offset according to the alpha channel on a character. You could have freaky 3D lighting effects on their face as they moved past a window or projector.. just thinking aloud here. Very moody, very atmospheric, but I'd imagine a real pain to implement.

GarageGothic

I think I misunderstood what lightmaps are in SLUDGE. I thought it was like our region lighting/tint, just with as many "regions" as there are pixels. But when I read the extract from the manual now, it seems to be an overlay that shades the sprite pixel by pixel like a multiply layer in Photoshop would. My module is simpler in that it shades the whole sprite in the color of the pixel at character.x, character.y.

Quote from: tolworthy on Mon 28/04/2008 14:41:28Another idea that comes to mind (apart from the obvious one of animation) is to allow the offsetting of light maps according to a characters' position. And even scaling! Imagine a room where an overhead projector is turned on (using lightmaps). A character could walk between the projector and the wall and have the image shown on their face, offset and scaled in a dramatic way.

I actually already have this in a different module for my game (which also features projectors :)). I may consider merging the modules, but technically it's a different thing altogether from the tint lightmaps. These projection overlays also support animation and offset values.

QuoteHeck, they could even cast a shadow! But I suspect that would be a lot of work for one gimmick.

And another module still  ;D, though one currently so buggy that I probably won't release it publicly until it has shown its worth in my own game. (Example screenshot here)

I'll take all your other comments into consideration for the lightmap module. Thanks for your input! :)

Edit: Oh, as for the scale of the lightmap, it must have the correct screen dimensions but can be in any resolution (such as using 320x240 lightmaps in a 640x480 game).

Pumaman

Quote from: GarageGothic on Mon 28/04/2008 11:23:46
I'm using the DrawingSurface.GetPixel function to retrieve the pixel at (character.x, character.y), then converting the color value to RGB and applying it as a Character.Tint. Would it help me in any way to convert the RGB values to HSV and then converting the S and V values to percentages (sat, lum) to use in the Character.Tint(are,g,b,sat,lum)? - I'm still unsure if it makes any sense to try to retrieve saturation from the lightmap, since I guess a less saturated color will already tint the character less noticably (so lowering the tint saturation would make the tint almost invisible).

I didn't quite understand, did you say that tinting already changes the shading of the sprite's pixels and not just their color value? If so, is there any way to counterbalance this through arithmetic when calculating the luminance value?

Getting the saturation and luminance from a light map pixel probably won't give you a decent result, because then you're effectively drawing three different components (colour, saturation and luminance) from the same source.

I would continue to take the RGB values from the light map, but you'd need to decide for yourself what you wanted to do for the saturation and luminance. Normally I'd leave luminance at 100, and also pass saturation as 100 if you want the sprites to be completely re-tinted to your light map colour. If you don't, then you need to decide how strongly you want the tint to be applied and use that for the saturation value.

GarageGothic

Quote from: Pumaman on Mon 28/04/2008 15:23:58Getting the saturation and luminance from a light map pixel probably won't give you a decent result, because then you're effectively drawing three different components (colour, saturation and luminance) from the same source.

Ah, thanks for clearing that up. I somehow suspected I would need separate maps for these values, but thought I might at least be able to retrieve brightness from the RGB map since AGS tinting seemed to be more about value than luminance - even very low RGB values can give light tint colors if balanced properly. I guess I will let this be up to the end users how it's implemented. I think the best solution would be to have a color map, a light map and a per-room saturation value, since you'd rarely need to change saturation at different spots in the room.

Pumaman

Actually, in a sense the saturation might be the thing you want to change the most.

Suppose you have a big blue light at one end of the room. The actual RGB tint could be fixed for the entire room (eg. 0, 0, 200) but then as you walked closer or further away from the light, the saturation would gradually vary to bathe the character in more/less blue light.

So in a sense it might be more useful to have a saturation map than a colour map, depending on what effect you're going for.

GarageGothic

Ah, good point indeed. I think my main issue with saturation maps is that while I have a pretty good idea of how to draw a color or light map, I wouldn't know where to start in drawing a realistic looking saturation map for a room. Currently I've implemented saturation based on the HSV S value, and it works pretty well with the saturation fading as RGB values near white. At the moment I'm wondering whether it shouldn't be the same at the other end of the spectrum, that saturation of colors lessens the less light there is.

I made a test room where the character is walking around a huge gradient like this:



and I must say he adapts pretty well to the tonal range. Obviously there are values you cannot achieve with automatic luminance/saturation calculation, but it will take some experimenting to see what needs to change.

In addition, I think this module would benefit greatly if support of luminance values up to 200% was implemented for Character.Tint as it is for region lighting (to gradually "white out" the character). I've mentioned it before, but particularly now that I'm coding the module, I see all kinds of cool uses - for instance you could have a very smooth looking fog effect. 

Pumaman

Would anyone else find support for luminance > 100% useful?

G

Quote from: Pumaman on Mon 28/04/2008 20:13:46
Would anyone else find support for luminance > 100% useful?

I would find support for luminance AND saturation 100% useful. As I'm interested in changing thar saturation of the colors to get grey tones of characters and rooms without using aditional sprites.

GarageGothic

You mean for region tinting, right? Because if you set the R, G and B in Character.Tint to the same value, you WILL get grayscale characters already (at saturation 100, saturation works a bit differently in AGS than you're used to from e.g. Photoshop), and can use luminance at the same time. It's a pain that Regions don't support both luminance and color tint (one reason for my module). But it should be quite easy to write some code in repeatedly_execute_always which tints every character in the current room to their current region's luminance (only up to 100%) AND to grayscale.

Code: ags
  int index;
  while (index < Game.CharacterCount) {
    if (character[index].Room == player.Room) {
      Region *charregion = Region.GetAtRoomXY(character[index].x,  character[index].y);
      int lightlevel = charregion.LightLevel + 100;
      if (lightlevel > 100) lightlevel = 100; //because character.Tint doesn't support +100 values
      character[index].Tint(255, 255, 255, 100, lightlevel);
      }
    index++;
    }

G

Quote from: GarageGothic on Thu 01/05/2008 10:23:45
You mean for region tinting, right? Because if you set the R, G and B in Character.Tint to the same value, you WILL get grayscale characters already (at saturation 100, saturation works a bit differently in AGS than you're used to from e.g. Photoshop). It's a pain that Regions don't support both luminance and color tint, but it should be quite easy to write some code in repeatedly_execute_always which tints every character in the current room to their current region's luminance (only up to 100%) AND to grayscale.

Code: ags
  int index;
  while (index < Game.CharacterCount) {
    if (character[index].Room == player.Room) {
      Region *charregion = Region.GetAtRoomXY(character[index].x,  character[index].y);
      int lightlevel = charregion.LightLevel + 100;
      if (lightlevel > 100) lightlevel = 100; //because character.Tint doesn't support +100 values
      character[index].Tint(255, 255, 255, 100, lightlevel);
      }
    index++;
    }


No, I'm afraid don't mean region tinting.

GarageGothic

What's the problem then? Character.Tint DOES support grayscale and luminance at the same time. Region tinting does not.

G

Let's say that I've tried several times to get a saturation transition, from a full-colored scene (not a character, object, the whole screen) to a greyscale look and resulted impossible.

If AGS works color with the HSV color system then there should be a simple way to control the saturation level of the sprites (from the original aspect to a greyscale look with all the 254 steps between, or maybe from 0% to 100% saturation), as far as I know about color theory. Maybe this could be added in the TintScreen function ar as another function (ScreenSaturation, or something else)

Pumaman

In theory all you should need to do is keep calling Character.Tint with the same RGB values (eg. 200, 200, 200) but gradually increasing the saturation from 0 to 100.

GarageGothic

Pumaman is right about the method, but if you want to do it for the whole screen, of course it's not enough to just do it for the characters. If you take the code I posted before and then put a variable in place of the saturation setting, you've got the code for tinting characters grayscale. You just need to modify the saturation variable in-between loops. Then add a similar function for objects and work out how to tint the background efficiently. Instead of using DrawingSurface routines, which can be quite slow, I recommend making a DynamicSprite copy of the background, tinting it grayscale and then displaying it as a transparent object, changing it's transparency along with the saturation variable.

G

Sorry guys... but I got completely lost. I'm afraid my skills at scripting are not very sharp.

Anyway, I meant to say that implementing some kind of saturation control should be useful to give more depth to the use of color as a graphic resource while making a game.

SMF spam blocked by CleanTalk