Unity Palette Shader first pass

Hi all,

You know how sometimes, after you’ve been working on some important thing, you get sick after you finish it? Like your body was holding off on getting sick until it could get away with it? Maybe it’s just me, but either way, I got sick right after putting the new version of Sprite Lamp out there. Anyway, sorry it took me a few days, but here’s my first crack at the palette shader from Sprite Lamp, in Unity form.

ZombiePaletteUnity
Come for the palette zombie, stay for the flawless framing of the screengrab.

 

A couple of important things to note:

  • It basically works as you’d expect, I think. Put the textures from Sprite Lamp in the appropriate texture slots on the material and you should be good.
  • Currently it makes use of the diffuse map, just to get the opacity value from it. The obvious thing to do is put the opacity in the index map’s alpha channel, but since Sprite Lamp doesn’t output them that way automatically yet, I made the shader so it works readily with what Sprite Lamp exports.
  • You can get some very crappy results if you don’t load your textures in as ‘uncompressed’. Compressing the palette map in particular will pretty much make everything awful (or at least, that’s what happened when I tested it).
  • The palette system is designed to work without coloured lighting. I might hack something in later that just multiplies the output by the light colour, but that kind of misses the point of having close artist control over what colours are rendered to the screen. Ultimately, if you’re making use of the palette system, the key is to set the lighting mood via the palettes.
  • Since this is a straightforward shader-based implementation, it makes use of simple additive lighting to handle multiple lights. Technically, this isn’t quite correct, but I think for most cases it should look fine. I hope to work on a more complete and correct approach to this in the future, but it might get a little hairy (might require something resembling a deferred rendering pass). I’ll go into some detail later as to how this might work.

Anyway, that’s it! As you can probably guess, this shader is a work in progress, and will change in the future – both in terms of how it works under the hood and how you use it as a developer. Let me know if it’s giving you trouble, failing to compile, or if it’s not clear how to make use of it, and I’ll try to straighten things out.

Sprite Lamp’s palette system

Yesterday I released the latest update to Sprite Lamp, and with it, a properly implemented and documented palette system. Still pending are shaders for various engines to make it usable in your games, but in the mean time, I’m going to give a quick rundown of what it is, how it works, and how the (really pretty simple) shader works.

Why palettes?

This is something that came up due to repeated requests from artists. As a graphics programmer, my first thought was to calculate the light based on the angles of the light and the surface normal using standard Lambertian and basically call it a day. However, as several artists pointed out to me, this means that when a part of an image gets darker it approaches black, and artists very rarely use real black in their paintings. Shadows are more likely to have some blue to them, and highlights are likely to be slightly yellow rather than white. There are various ways to get around this – tweaking the ambient and direct light colour, for instance – and while those options remain, I thought it would be good to give artists more direct control. Hence the palette system.

This works by taking the diffuse map (that the artist creates), taking all the unique colours from it, and creating a sort of template palette image. This palette is then saved out and modified by the artist to get the effects they want. A palette image has a vertical column of pixels for every unique colour in the depth map. The vertical position in this column represents the colours that those pixels will be at different lighting levels. The midpoint of each column is the colour when that pixel is lit with full diffuse lighting but no specular lighting. Below that it fades as the diffuse lighting drops away to nothing, and above that it represents the colour when an increasingly strong specular highlight is added. Sprite Lamp can be used to generate either an ’empty’ palette that will create flat lighting (the columns are the same colour all the way up), or a palette that will produce simple calculated lighting, fading to black at the bottom and white at the top.

As an example, here is the diffuse channel of the Sprite Lamp zombie:

Diffuse image of a zombie

And here are the autogenerated palettes for said zombie (with and without default lighting built in):

EmptyAndDefaultPalettes

From here, you save one of these images out (whichever would work best for you as a guide) and then change the colours as you see fit. The only really important thing is that you generate the index map (more on that later) and then make sure you keep the horizontal positions of the pixel columns in the same place/order in the palette.

Here are some examples of effects that are possible using this system, with the zombie’s palette image visible on the inset.

ZombiePalette2
This is a simple case of adding some blue to the shadows and some yellow to the highlights to add some depth to the result.
ZombiePalette3
In this image, we’ve done something like a traditional palette swap, to make a different colour scheme entirely.
ZombiePalette4
This one makes use of the Dawnbringer palette – it uses fewer colours and gives a more retro look.

The Shader

So, as promised, I’m going to give a quick outline of how the shader works. It’s actually not very complicated, but it hinges around something that Sprite Lamp will generate for you, called an index map. You’ll need a normal map, as usual, and then a diffuse map, and from the diffuse map Sprite Lamp will generate the palette map template (which you then modify) and the index map (which you don’t). The index map for the zombie looks like this:

zombie_Index_Bigger

The grey values in this image are actually a value from 0-1, representing the horizontal position in the palette map where that column of colours resides. Black represents the leftmost column of the palette map, then the second darkest grey (the hair) will be the second column across, and so forth. This is why it’s important to keep the columns of the palette map in the same place.

As the shader programmer, all you need to do is calculate some lighting value between zero and one (the way Sprite Lamp’s palette shader does this is by averaging the diffuse and specular components and then clamping to the correct range). Then, a look up into the index map will get you a luminosity value between zero and one. From there, you do a look up into the palette map, using the value read from the index map as your U coordinate, and the calculated level of illumination from your lighting algorithm of choice for the V coordinate, and you have your final colour. Because the whole point of the palette system is to give the artist precise control over the colours that end up on the screen, nothing more is done – the colour is outputted onto the screen. The pseudocode for the shader might look something like this:

float colourLevel = (diffuseLevel + specularLevel) * 0.5;
float indexPosition = tex2D(indexMap, textureCoords.uv).r;
//Note that we use 1.0 - colourLevel because usually positive V
//goes down the texture map.
vec3 finalColour = 
         tex2D(paletteMap, vec2(indexPosition, 1.0 - colourLevel);

The generation of the index map

For the most part this isn’t something you’ll be worried about, but there are certain circumstances where it’s important to know the details. Sprite Lamp generates a palette map from the diffuse map to use as a template, but then it generates the index map from the diffuse and the palette maps. It does this by going through every pixel of the diffuse map, and then searching for that pixel’s colour in the middle row of the palette map (that is, it looks through the row of pixels half way down the palette map). When it finds the colour it’s looking for, it will use the horizontal position of that to determine the greyness of that pixel in the index map.

This probably isn’t terribly relevant to most use cases, but it’s possible that you will want to use a palette map for more than one piece of art in your game (to render a whole scene coherently, for instance). In that case, you’ll want to make your own palette map from scratch, making sure the important colours are present all the way along the centre. Having done that, paint all your diffuse maps using only colours from that centre row of pixels. Then, load up a given diffuse map, and rather than generating a palette from it, open up the palette you made into the palette map, then click the ‘generate index’ button. If the diffuse colours are all present in the palette’s central row, Sprite Lamp should generate a fully functional index map without issue – it doesn’t matter that there are colours in the palette not present in the diffuse.

Conclusion

I would imagine that this problem has been tackled by other developers at various points through gaming history – this approach is just what came to mind to answer artists’ complains about black shadows. Given that, this explanation is straight from my brain to the page, as it were. Please, let me know if there’s anything I’ve been unclear on, so I can fix my explanation.

More importantly, if you do anything cool and unexpected with this system, I’d be keen to see your work. I’ve already been surprised/impressed with Halley’s implementation of the Drawbringer palette from above, and I’m confident that various cel shading and other techniques are possible using this system too.

Sprite Lamp: A minor update and apology for lack of communication

Hi all,

I’m coming at you a bit humbly today, because a comment someone made to me recently called me on being behind in my schedule and generally not telling people what’s up.

I’ll start by saying that this isn’t the dreaded “Sorry, this project isn’t going to happen” post. Sprite Lamp has been and continues to be in development, and all the promised features are on their way.

What this post is, is me apologising for being less productive than I would have liked over the last two months, and not being very communicative about that fact over that time. I have been in the US recently – for personal reasons I won’t go into, it would have been awkward for me to not go on the trip, so I convinced myself that I could just take my laptop and keep up work on Sprite Lamp while I was there. As it turns out, this was kind of foolish on my part, and in retrospect I should have just stayed home. I did keep working while I was there, but productivity has been lower than I hoped, and I haven’t been very good at keeping you all up to date on this. The current state of play is that I’m working on a few things for the next alpha release – UI overhaul, functional/usable palette system, updating some issues with engine shaders, and updated documntation are all coming.

The other thing I haven’t communicated about well is release dates. Initially, I stated that Sprite Lamp would be out approximately now. This was when the Kickstarter was initially written, and it was a small project without any stretch goals. It’s grown beyond that, and of course the stretch goals were things that I hadn’t done any development on, some of which have turned out to be slightly rabbit-hole-ish. At this point I’m hoping for one last alpha release in a week or so, and the first beta release in about a month.

So having said all that, I’m now back home and in a much better state to work in an undistracted fashion. I’ve always been a bit shy about social media and the like, but I’m going to put in an extra effort to be forthcoming on that front, too. For now, I figure the best way to make amends is to get right back into coding, so that’s what I’m going to do.

~ Finn