We are looking at using HSL textures for some things instead of RGB (for hueing). I’m curious if anyone has experience storing HSL with DXT compression, as it can really do a number on them. I remember seeing an article/paper somewhere when we looked into this at my last job but couldn’t come up with anything.
For HSL,
0 <= H < 360
0 <= S <= 100
0 <= L <= 100
What I’m thinking right now is:
Even though S and L are 0 to 100, expand them to 0-255 and re-crunch them in the shader.
Spill over the H into the alpha channel as well, maybe expand it to 0-511 and crunch in the shader.
Swizzling channels (will need to test for what works best, I know how dxt handles the different channels bit-wise but that may not be the whole story)
‘Hueing’ means we want to take an original texture and change the colors. This is a common feature where character customization is concerned.
In our case, our plan right now <since the original one was shot down due to performance > is to use HSL textures, then ‘adjust’ them with a function and some parameters, then convert that to RGB in the shader and render. So instead of storing RGBA information, we want to store Hue, Saturation, and Lightness info in our dxt-compressed dds textures. And obviously as dxt is a lossy, imperfect format, I thought it’d be worthwhile to find out what other people’s workarounds have been or suggestions are.
I think the biggest problem with that is that you can no longer use filtering on the texture, unless you manually do it all which is even worse for performance!
The issue is that H is not a strictly linear term, it’s more like a circular term. If the Hue goes from 0-360 - you have to remember that 0 and 360 are connected and continuous! Say 0 represents red. If you have two pixels next to each other, both a very similar red, one could have a value of 0, and one 360. No problem. Except when you filter it - say you need the texel colour that sits half-way between the two pixels, normally you’d just average the values, but that gives 180, which is cyan. You’d actually get a full spectrum in-between the two pixels. Same problem with mip-maps, as you average blocks down, there’s no notion of wrap-around so red areas will shift massively. What you need is a function which takes the “shortest” route around the circle, instead of the midpoint on the line.
whargoul: The filtering issue isn’t an issue. We paint all hueable areas in a neutral color. We are still planning on using the RGB diffuse texture as well. So the filtering will never wrap around or take any quick jumps from red to green and hit blue or yellow along the way, because the hueable areas are padded on the HSL texture and interpolate based on masks with the RGB texture. Otherwise, yeah, it would be an issue (filtering was why we dropped our superior-quality solution, it was really awesome, but there was no filtering solution other than in the shader and it was too expensive for that). Any other issues you’ve come across?
mikie: Yes we looked into a pre-rendering solution at runtime/loading. However, it adds lots of load time, and the driver-compressed DDS textures are going to always be a lower-quality compression than offline compression. Actually I suggested we do both if we can, and it may eventually come down to that I think (especially for lower-spec hardware), though it hasn’t been discussed- I’d like to see a baked solution that ‘streams’ and uses the real-time hueing for assets that haven’t been baked. But if the real-time is fast enough (it is right now), it is just simpler to do it that way.
All my experiences using HSL/HSV (as well as other color space conversions) has left me with one simple and final opinion.
Dont do it! :D:
Of course take it with a grain of salt, I’m sure there’s SOME use out there that I haven’t needed to use it for, but so far I’ve found the cost vs. benefit is just not worth it.
With all the combinations of methods and techniques of conversion/compression, all the color spaces I’ve used, and all the different results and applications I’m using it for… I haven’t found a single solution which you can’t get a better and cheaper result by just rgb re-combining. Here’s what I’ve tried:
Color spaces I’ve gone through:
HSL/HSV,
Lab,
Lch,
YCbCr,
RGB Tangential
and bit packed LUT.
Conversion methods between the above and to/from RGB:
straight mathematical. Obviously expensive.
colour cube LUT
matrix conversion tables
volume and texture array indexed LUT
cached interpolated table - quick but not very accurate.
a few others I forget
For colourising armour sets, I found doing a hue shift always fought the art. For example, you’d get metals with green or pink rust when you shift the hue away from it’s base colour, OR the alternative was to have dull texture with little colour / detail variation in those areas which just looks worse.
The best visual vs. function I’ve found is straight masked areas. Do a full vector dot product blend to get 4 colour types on a material and then a texture overlay for adding detail and colour variations. a tex1D can be used to slide the hue if you want that functionality. At the expense of extra tex calls and blend weights, this still comes out a lot cheaper than the alternatives. Retains the materials look too.
[QUOTE=j.i. styles;2606]For colourising armour sets, I found doing a hue shift always fought the art. For example, you’d get metals with green or pink rust when you shift the hue away from it’s base colour, OR the alternative was to have dull texture with little colour / detail variation in those areas which just looks worse.
The best visual vs. function I’ve found is straight masked areas. Do a full vector dot product blend to get 4 colour types on a material and then a texture overlay for adding detail and colour variations. a tex1D can be used to slide the hue if you want that functionality. At the expense of extra tex calls and blend weights, this still comes out a lot cheaper than the alternatives. Retains the materials look too.[/QUOTE]
The color drift issue is noted, and the biggest concern… we were going to do a per-channel brightness/contrast but that will likely be too expensive. We’ll just have to see what happens and see from there.
Can you describe what you are talking about some more? One problem is that we are using all hand-painted ‘painterly’ textures (I can imagine your method would be easier with more realistic textures), and working with outsourcers. The hueing method needed to be pretty simple from an art-production conceptualization point of view. Working with layers and overlays would probably be a nightmare on the management end (and actually I’m SURE our lead character guy wouldn’t go for it), but I’d like to hear more anyway. I think I know what you’re describing but could use some details.
sounds like you’re hitting the same practicality roadblocks I’ve come up against.
It’s simple and low-tech, but the solution I ran with before went like this:
rgb base diffuse texture, and rgba colour mask texture inputs.
use the rgba colour mask to define weighting for coloured areas.
input 4 user defineable colours per material
do a weighted overlay style blend of the colours using the blend weights
since it’s an overlay style blend, the blend weights may overlap and mix. The colours can have a brightening or darkening, and will respect hue variation to a certain degree.
on the art side:
diffuse textures are created in photoshop with their base colors seperated from all other layers on top. This fit easily with the texture artists pipeline since this was how the majority made and managed their textures anyway.
The areas that where to be colourised where defined in seperate masks
The layers on top of the base colour was used to cut out areas in this mask that the colourising shouldn’t touch (eg, the extra areas of colour variation and details, rust spots, or in your case maybe different weighted brush strokes).
Like I said, low-tech, but the artists where able to abuse the simplicity of it and come up with some great looking art that didn’t have that “just coloured over top” look to it.
I mentioned two more steps to this before - another overlay on top of that, and a 1D gradient to uniformly hue shift. These where added as optional extras supported shader side in two more technique permutations, but never really needed/used.
Thanks for that mikie. Ultimately our requirements are quite different… he needed a low spec shader and had a fixed ‘baking’ requirement, while we have a high-spec shader and could not bake as we have just way too much data and characters to not kill load times.
TBH maybe I need to see it but I don’t think his result is really artistically good or programmatically cheap enough over other methods (like ugh color fill!). Obviously the needs of the game are different and his solution fit his needs… our needs are different of course but I still don’t think his was a great solution for his needs. Oh well.
I am really happy with our solution though, we got everything we wanted to be able to do. Have a satisfactory solution for specular, border areas (both adjacent hued areas and interpolated masks), and got the whole thing down to like 30 or something instructions. I’d like to write up something more formal because I think this is the best hueing solution I’ve noticed… but first we should make sure there no problems with it in a few months!
To speed things up even further, I used the MotoGP equivalent of a custom content processor to change the source data into a custom variant of DXT1, which I guess you could call DXT-HSV. After compressing into DXT1, I preconverted the block header colors, changing them from 16 bit RGB to a 24 bit HSV format. This expanded each block from 8 to 10 bytes, and sped up the color replacement function because it no longer had to bother converting the source color from RGB to HSV.
That sounded pretty interesting and I’ll bring that up to the programmers, though.
Thanks for the heads up, mikie, and thanks for the info ji!