HSV Color space conversion

Are people are doing this in console shaders at the moment ?, our render department thinks it sound a little expensive. I was wondering if there was any fast / hacky ways of doing it or if there are other color spaces with similar usefulness.

I would assume that most engines allow you to work in HSV inside the color picker, which then converts it to RGB before it’ sent to the renderer.
The only colorspaces that make sense during calculation would be sRGB and normal RGB - afaik the difference is that sRGB is already gamma corrected and thus yields less banding.

Convert to HSV? Why? I understand why color value inputs to a shader pipeline must be converted from sRGB -> linear space. I’ve never heard of shaders working better on HSV colors, though.

Here is a class, in Python, that subclasses wx.Colour and stores both the RGB and HSB (!V) colors. Maybe you can use it as a basis for some RGB -> HSV functions.

class HSB_Color( wx.Colour ):
	"""
	This is a subclass of wx.Colour, which adds Hue, Saturation and Brightness
	capability to the base class. It contains also methods to convert RGB triplets
	into HSB triplets and vice-versa.
	"""

	def __init__( self, color ):
		wx.Colour.__init__( self )

		self.r = color.Red( )
		self.g = color.Green( )
		self.b = color.Blue( )
		self.a = color.Alpha( )

		self.to_hsb( )


	def get_py_color( self ):
		""" Returns the wxPython wx.Colour associated with this instance. """

		return wx.Colour( self.r, self.g, self.b, self.a  )


	def to_hsb( self ):
		""" Converts a RGB triplet into a HSV triplet. """

		min_val = float( min( self.r, min( self.g, self.b ) ) )
		max_val = float( max( self.r, max( self.g, self.b ) ) )
		delta = max_val - min_val

		self.v = int( max_val )

		if abs( delta ) < 1e-6:
			self.h = self.s = 0
		else:
			temp = delta / max_val
			self.s = int( temp * 255.0 )
			if self.r == int( max_val ):
				temp = float( self.g-self.b ) / delta
			elif self.g == int( max_val ):
				temp = 2.0 + ( float( self.b - self.r ) / delta )
			else:
				temp = 4.0 + ( float( self.r - self.g ) / delta )

			temp *= 60
			if temp < 0:
				temp += 360
			elif temp >= 360.0:
				temp = 0

			self.h = int(temp)


	def to_rgb( self ):
		""" Converts a HSV triplet into a RGB triplet. """

		max_val = self.v
		delta = ( max_val * self.s ) / 255.0
		min_val = max_val - delta

		hue = float( self.h )

		if self.h > 300 or self.h <= 60:
			self.r = max_val
			if self.h > 300:
				self.g = int( min_val )
				hue = ( hue - 360.0 ) / 60.0
				self.b = int( -( hue * delta - min_val ) )
			else:
				self.b = int( min_val )
				hue = hue / 60.0
				self.g = int( hue * delta + min_val )
		elif self.h > 60 and self.h < 180:
			self.g = int( max_val )
			if self.h < 120:
				self.b = int( min_val )
				hue = ( hue / 60.0 - 2.0 ) * delta
				self.r = int( min_val - hue )
			else:
				self.r = int( min_val )
				hue = ( hue / 60.0 - 2.0 ) * delta
				self.b = int( min_val + hue )
		else:
			self.b = int( max_val )
			if self.h < 240:
				self.r = int( min_val )
				hue = ( hue / 60.0 - 4.0 ) * delta
				self.g = int( min_val - hue )
			else:
				self.g = int( min_val )
				hue = ( hue / 60.0 - 4.0 ) * delta
				self.r = int( min_val + hue )

Right. Our engine works like Sascha described. Our standard tool color picker defaults to HSB, with a collection of pre-approved color swatches the ADs defined. Internally the picker stores the values as sRGB (for display in tools) and linear (for input into the game). The HSB values are only ever used for user input on the picker. They are never pushed to the engine.

I investigated this for a while to do color modulation at runtime, but it was too expensive to be useful. I think it’s over 64 instructions just to get into and out of HSV space. I grabbed this conversion code from an ATI paper somewhere (sorry, can’t find the original right now). This function converts from RGB to HSV, uses the diffuse alpha and some UI spinner values to apply an HSV offset to the converted value and then converts back to RGB. The code isn’t very optimized since it was mostly a prototype, but you should be able to pull everything you need from it:

//RGB to HSV converion
float4 RGB_to_HSV (float4 color)
{
	float r, g, b, a, delta;
	float colorMax, colorMin;
	float h=0, s=0, v=0;
	r = color.r;
	g = color.g;
	b = color.b;
	a = color.a;
	colorMax = max (r,g);
	colorMax = max (colorMax,b);
	colorMin = min (r,g);
	colorMin = min (colorMin,b);
	//Convert from RGB to HSV	
	v = colorMax;
	if (colorMax != 0)
	{
		s = (colorMax - colorMin) / colorMax;
	}
	if (s != 0)
	{
		delta = colorMax - colorMin;
		if (r == colorMax)
		{
			h = (g-b)/delta;
		}
		else if (g == colorMax)
		{
			h = 2.0 + (b-r)/delta;
		}
		else 
		{
			h = 4.0 + (r-g)/delta;
		}
		h *= 60;
		//apply the offsets from the spinners multiply it by the alpha channel from the diffuse map.
		h += (hOffset * a);
		s += (sOffset * a);
		v += (vOffset * a);
		//Make sure that the values are not out of range after the offset
		if (h<0) {h += 360;}
		if (h>360) {h -= 360;}
		if (s<0.0) {s = 0.0;}
		if (s>1.0) {s = 1.0;}
		if (v<0.0) {v = 0.0;}
		if (v>1.0) {v = 1.0;}
	}
	float f,p,q,t;
	float i;
	//Convert back to RGB	
	if (h == 0)
	{
		if (s != 0)
		{
			color = s;
		}
	}
	else
	{
		if (h == 360.0)
		{
			h=0;
		}
		h /=60;
		i = floor (h);
		f = h-i;
		p = v * (1.0 - s);
		q = v * (1.0 - (s * f));
		t = v * (1.0 - (s * (1.0 - f)));

		if (i == 0)
		{
			r = v;
			g = t;
			b = p;
		}
		else if (i == 1)
		{
			r = q;
			g = v;
			b = p;
		}
		else if (i == 2)
		{
			r = p;
			g = v;
			b = t;
		}
		else if (i == 3)
		{
			r = p;
			g = q;
			b = v;
		}
		else if (i == 4)
		{
			r = t;
			g = p;
			b = v;
		}
		else if (i == 5)
		{
			r = v;
			g = p;
			b = q;
		}
		color.r = r;
		color.g = g;
		color.b = b;
		color.a = 1.0;
	}
	return color;
}

It was mostly a way to do shift colors (hue) in textures with masks as a form of texture memory optimization, i suppose it could be done with a texture lookup or something instead to shift the colors along a gradient instead of converting textures to HSV, which as Torgo confirms is quite expensive.

But yeah HSV color picking was not really the goal. I was looking at ways to shift diffuse textures nicely in color.

We also dicussed using the YCbCr color space to do it instead although it requires to tweak two values to change hue which is less intuitive for the artist. But it should be cheaper to convert to.

We switched to a masked color multiply system. Infinitely cheaper in the shader, plus you get more control over the colors (they don’t always have to stay in the same hue relationships like HSV). It does mean your artists always have to author the “white” or “grey” variation as the base, but it’s pretty easy to preview what the final result will look like in DCC packages if they need to see it before it hits the engine.

[QUOTE=MartinMadsen;7988]It was mostly a way to do shift colors (hue) in textures with masks as a form of texture memory optimization, i suppose it could be done with a texture lookup or something instead to shift the colors along a gradient instead of converting textures to HSV, which as Torgo confirms is quite expensive.

But yeah HSV color picking was not really the goal. I was looking at ways to shift diffuse textures nicely in color.

We also dicussed using the YCbCr color space to do it instead although it requires to tweak two values to change hue which is less intuitive for the artist. But it should be cheaper to convert to.[/QUOTE]

Store your textures in HSV and you only have to do the (relatively expensive but certainly manageable) conversion from HSV->RGB in the shader. We basically do this for our hueing shifting in SWTOR.

You might like this one I found recently, that refactors HSL<->RGB as a trig problem: http://www.quasimondo.com/archives/000696.php#000696

The author claims “no if’s” but he uses atan2() – so there are if’s, he just doesn’t spell the word. Note that the constants he uses are not quite precise enough for repeated back-and-forth conversions, but it’s not hard to generate better ones, as he himself notes in the comments.