Last active
August 23, 2021 05:16
-
-
Save Quackward/ec9f0122540b228e798bdbb7290881c0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// [email protected] \(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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This mixing function produces the left output