Skip to content

Instantly share code, notes, and snippets.

@Quackward
Last active August 23, 2021 05:16
Show Gist options
  • Save Quackward/ec9f0122540b228e798bdbb7290881c0 to your computer and use it in GitHub Desktop.
Save Quackward/ec9f0122540b228e798bdbb7290881c0 to your computer and use it in GitHub Desktop.
// Clairvoire@gmail.com \(v ` >`)_v
// feel free to use as you see fit, attribution appreciated but not required, not fit for any purpose
// you'll need the following type, if you're not using glm
// struct vec3{ union{float r, x;}; union{float g, y;}; union{float b, z;}; };
// you'll need the following functions, if you're not using glm
// float abs(float x) :: returns the positive value of x
// float sin(float x) :: does the sin thing in radians
// float sign(float x) :: returns -1 if sign is negative, 1 if positive, 0 if 0
// float fract(float x) :: returns `x - floor(x)` (important distinction if the number is negative)
// float mix(float a, float b, float ratio) :: returns a if ratio<0, b if ratio>1, otherwise `a + (b-a)*ratio`
// this function mixes two hsv values to make a new one, in a non-linear way through saturation space if hue difference is high
// inputs `a` and `b` and the return value, are hsv values where:
// .x = hue from 0.0 to 1.0, where 0.0 is red, cycling to green, then blue, then back I guess
// .y = saturation from 0.0 to 1.0, no saturation is grayscale
// .z = value or brightness, from 0.0 to 1.0; we try to preserve this linearly as humans are stupid horny about value
// the greater the hue change, the more we try to cut through white/black to reach the other side
// `hueDiff...` dictates how aggressive we do this: 0.0 is not at all, 1.0 will empty saturation if hue shift=0.5
vec3 mixHSV(vec3 a, vec3 b, float ratio, float hueDiffThruWhiteMod = 0.75f) {
vec3 out;
// figure out which direction we're sending the hue
float hueDiff = a.x - a.x;
float hueSign = glm::sign(hueDiff);
hueDiff = glm::abs(hueDiff);
if (hueDiff > 0.5f) {
hueDiff = 1.0f - hueDiff;
hueSign = -hueSign;
}
// this let's us modulate the saturation the more we travel across the hue circle
float satMod = 1.0f - (hueDiff*(hueDiffThruWhiteMod*2.0f)) * glm::sin(ratio * float(PI));
out.x = glm::fract(a.x + (hueDiff*hueSign * ratio));
out.y = glm::mix(a.y, b.y, ratio) * satMod;
out.z = glm::mix(a.z, b.z, ratio);
return out;
}
// it's possible for hsv to have values outside 0.0 and 1.0
// This function will return garbage values if you do that BUT it won't crash at least.
vec3 convertHSVtoRGB(vec3 hsv) {
vec3 out;
// with no saturation, we are reigned by value only
if(hsv.y <= 0.0f) {
out.r = hsv.z;
out.g = hsv.z;
out.b = hsv.z;
return out;
}
// subdivide the hue into 6 spans
float hue = glm::fract(hsv.x) * 6.f;
unsigned int section = (unsigned int)hue;
float ratio = hue - section; // expanded ratio within the hue span
// we figure out the weights given the saturation and hue, cut it by the value,
// then apply it according to the section we're in
float x = hsv.z * (1.0f - hsv.y);
float y = hsv.z * (1.0f - (hsv.y * ratio));
float z = hsv.z * (1.0f - (hsv.y * (1.0f - ratio)));
switch(section) {
case 0: out.r = hsv.z; out.g = z; out.b = x; break;
case 1: out.r = y; out.g = hsv.z; out.b = x; break;
case 2: out.r = x; out.g = hsv.z; out.b = z; break;
case 3: out.r = x; out.g = y; out.b = hsv.z; break;
case 4: out.r = z; out.g = x; out.b = hsv.z; break;
case 5:default: out.r = hsv.z; out.g = x; out.b = y; break;
}
return out;
}
// be sure to keep your alpha somewhere, or you'll lose it
// it's possible for colors to have values outside 0.0 and 1.0.
// This function will return garbage values if you do that BUT it won't crash at least.
vec3 convertRGBtoHSV(vec3 color) {
vec3 out;
// figure out oru strongest and weakest channel
float min;
min = glm::min(color.r, color.g);
min = glm::min(color.b, min);
float max;
max = glm::max(color.r, color.g);
max = glm::max(color.b, max);
out.z = max; // our value is the strongest light in the sky.
float delta = max - min; // our saturation is dictated by the channel difference
// if it's zero, we're gray and have no hue either
// the value you see is 1/(2^16), it's basically the color epsilon of 16bit channels
// anything below that is basically 0.f
if (delta < 0.0000152587890625f) {
out.y = 0.f;
out.x = 0.f;
return out;
}
// it's possible for max to be 0, even if the delta isn't 0, so this is necessary to catch that
if (max > 0.0f) {
out.y = (delta / max);
} else {
out.y = 0.0f;
out.x = 0.0f;
return out;
}
// we're figuring out which of the 6 sections our color falls into on the hue spectrum
if (color.r == max)
out.x = (color.g - color.b) / delta;
else
if (color.g == max)
out.x = 2.0f + (color.b - color.r) / delta;
else
out.x = 4.0f + (color.r - color.g) / delta;
out.x = glm::fract(out.x / 6.0f);
return out;
}
@Quackward
Copy link
Author

Quackward commented Aug 23, 2021

WuRend_2021-08-22_06-32-10
This mixing function produces the left output

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment