├── .gitattributes ├── LICENSE ├── README.md ├── color-functions.glsl ├── scratchpad.glsl └── screenshot2.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Adam Lastowka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GLSL-Color-Functions 2 | 3 | ![LCh color space sRGB gamut](screenshot2.png?raw=true "sRGB Gamut in LCh Color Space") 4 | 5 | Color space conversions, metrics, and other utility functions for color in GLSL. 6 | 7 | Still very much a work-in-progress! If you see anything wrong, please let me know (or make a new branch if you're feeling nice). 8 | 9 | ## Currently Included: 10 | Color Spaces: 11 | * XYZ 12 | * sRGB 13 | * RGB 14 | * L\*a\*b\* (CIELAB) 15 | * LCh (CIELCh) 16 | * P3 Display 17 | * sYCC (sRGB's official YCbCr space) 18 | * OkLab 19 | 20 | Other: 21 | * A (proper) implementation of CIE Delta-E 2000 22 | * Color from temperature (cubic approximation) and temperature from color (McCamy approximation) 23 | * Lightness / Luminance / Luma functions 24 | * D65/D50 white point option for RGB<->XYZ and L\*a\*b\*<->XYZ 25 | 26 | ## Planned (descending priority): 27 | * [OkHSL/OkHSV](https://bottosson.github.io/posts/colorpicker/) 28 | * Chroma/Hue getter functions 29 | * Delta E CMC 30 | * Color temperature adjustment 31 | * Interpolation wrapper functions 32 | * CIELUV 33 | * Gamut mapping? 34 | 35 | ## Credits 36 | 37 | Inspired by [tobspr's](https://github.com/tobspr) [GLSL-Color-Spaces](https://github.com/tobspr/GLSL-Color-Spaces). 38 | Algorithms and math from: 39 | * [CIE](https://cie.co.at/) and [ICC](https://www.color.org/) 40 | * [Bruce Lindbloom](http://www.brucelindbloom.com/) 41 | * [Color.js](https://colorjs.io/) 42 | * Other authors as listed in comments 43 | -------------------------------------------------------------------------------- /color-functions.glsl: -------------------------------------------------------------------------------- 1 | // @author Adam Lastowka 2 | // DISCLAIMER: I am not a color scientist, please correct the code if it is wrong anywhere! 3 | 4 | // precision highp float; // :) 5 | 6 | #ifndef PI 7 | #define PI 3.14159265359 8 | #endif 9 | #ifndef TWO_PI 10 | #define TWO_PI 6.28318530718 11 | #endif 12 | 13 | // A note on white points: 14 | // All the matrices shown here assume D65. 15 | // If you are making something with GLSL, there is a 99% chance it will appear on a monitor, not in print. 16 | // D65 is is the most common white point for computer displays, so I make it the default. 17 | // You can change the WHITE variable here, but it will only afffect RGB<->XYZ and XYZ<->L*a*b* conversions. 18 | // If you do end up needing to use D50 for printing or something, that should hopefully be sufficient. 19 | 20 | // ALSO: The chromatic adaptation matrices are NOT calculated on-the-fly!!! 21 | // If you want to use a custom white point, you will have to do it yourself, sorry! 22 | 23 | // 0.3127/0.3290 1.0 (1.0-0.3127-0.3290)/0.329 24 | const vec3 D65_WHITE = vec3(0.95045592705, 1.0, 1.08905775076); 25 | // 0.3457/0.3585 1.0 (1.0-0.3457-0.3585)/0.3585 26 | const vec3 D50_WHITE = vec3(0.96429567643, 1.0, 0.82510460251); 27 | 28 | vec3 WHITE = D65_WHITE; 29 | 30 | // sRGB / ITU-R BT.709 spec 31 | const vec3 LUMA_VEC = vec3(0.2126, 0.7152, 0.0722); 32 | 33 | //========// TRANSFORMATION MATRICES //========// 34 | 35 | // Chromatic adaptation between D65<->D50 36 | // XYZ color space does not depend on a reference white, but all other matrices here 37 | // assume D65. These "restretch" XYZ to the D50 reference white so the others can sitll work with D50. 38 | 39 | // from https://www.color.org/sRGB.pdf 40 | const mat3 XYZ_TO_XYZ50_M = mat3( 41 | 1.0479298208405488, 0.022946793341019088, -0.05019222954313557, 42 | 0.029627815688159344, 0.990434484573249, -0.01707382502938514, 43 | -0.009243058152591178, 0.015055144896577895, 0.7518742899580008 44 | ); 45 | const mat3 XYZ50_TO_XYZ_M = mat3( 46 | 0.9554734527042182, -0.023098536874261423, 0.0632593086610217, 47 | -0.028369706963208136, 1.0099954580058226, 0.021041398966943008, 48 | 0.012314001688319899, -0.020507696433477912, 1.3303659366080753 49 | ); 50 | 51 | // RGB<->XYZ 52 | // from IEC 61966-2-1:1999/AMD1:2003 (sRGB color amendment 1) 53 | const mat3 RGB_TO_XYZ_M = mat3( 54 | 0.4124, 0.3576, 0.1805, 55 | 0.2126, 0.7152, 0.0722, 56 | 0.0193, 0.1192, 0.9505 57 | ); 58 | const mat3 XYZ_TO_RGB_M = mat3( 59 | 3.2406255, -1.5372080, -0.4986286, 60 | -0.9689307, 1.8757561, 0.0415175, 61 | 0.0557101, -0.2040211, 1.0569959 62 | ); 63 | 64 | // P3Linear <-> XYZ 65 | const mat3 P3LINEAR_TO_XYZ_M = mat3( 66 | 0.4865709486482162, 0.26566769316909306, 0.1982172852343625, 67 | 0.2289745640697488, 0.6917385218365064, 0.079286914093745, 68 | 0.0000000000000000, 0.04511338185890264, 1.043944368900976 69 | ); 70 | const mat3 XYZ_TO_P3LINEAR_M = mat3( 71 | 2.493496911941425, -0.9313836179191239, -0.40271078445071684, 72 | -0.8294889695615747, 1.7626640603183463, 0.023624685841943577, 73 | 0.03584583024378447, -0.07617238926804182, 0.9568845240076872 74 | ); 75 | 76 | // From https://www.color.org/sYCC.pdf 77 | // This matrix is actually also used in the ITU-R BT.601 specification 78 | const mat3 SRGB_TO_SYCC_M = mat3( 79 | 0.2990, 0.5870, 0.1140, 80 | -0.1687, -0.3312, 0.5, 81 | 0.5, -0.4187, -0.0813 82 | ); 83 | 84 | const mat3 SYCC_TO_SRGB_M = mat3( 85 | 1.0, -0.0000368, 1.40198757, 86 | 1.0000344, -0.34412512, -0.71412839, 87 | 0.9998228, 1.77203910, -0.00000804 88 | ); 89 | 90 | // OKLab Stuff 91 | // The "M_1" matrix in Ottosson's specification 92 | const mat3 XYZ_TO_OKLAB_LMS = mat3( 93 | 0.8189330101, 0.0329845436, 0.0482003018, 94 | 0.3618667424, 0.9293118715, 0.2643662691, 95 | -0.1288597137, 0.0361456387, 0.6338517070 96 | ); 97 | // The "M_2" matrix in Ottosson's specification 98 | const mat3 OKLAB_LMS_TO_OKLAB = mat3( 99 | 0.2104542553, 1.97799849510, 0.0259040371, 100 | 0.793617785, -2.4285922050, 0.7827717662, 101 | -0.0040720468, 0.4505937099, -0.8086757660 102 | ); 103 | 104 | // Inverse M_1 105 | const mat3 OKLAB_LMS_TO_XYZ = mat3( 106 | 1.227013851, -0.040580178, -0.076381285, 107 | -0.557799981, 1.11225687, -0.421481978, 108 | 0.281256149, -0.071676679, 1.58616322 109 | ); 110 | // Inverse M_2 111 | const mat3 OKLAB_TO_OKLAB_LMS = mat3( 112 | 1.0, 1.0, 1.0, 113 | 0.396337792, -0.105561342, -0.089484182, 114 | 0.215803758, -0.063854175, -1.291485538 115 | ); 116 | 117 | //========// CONVERSION FUNCTIONS //========// 118 | 119 | // sRGB<->RGB 120 | // sRGB is standard "monitor" space, and the standard colorspace of the internet. 121 | // The EOTF is roughly equivalent to a gamma of 2.2, but it acts differently in low values. 122 | float UNCOMPAND_SRGB(float a) { 123 | return (a > 0.04045) ? pow((a + 0.055) / 1.055, 2.4) : (a / 12.92); 124 | } 125 | vec3 SRGB_TO_RGB(vec3 srgb) { 126 | return vec3(UNCOMPAND_SRGB(srgb.x), UNCOMPAND_SRGB(srgb.y), UNCOMPAND_SRGB(srgb.z)); 127 | } 128 | float COMPAND_RGB(float a) { 129 | return (a <= 0.0031308) ? (12.92 * a) : (1.055 * pow(a, 0.41666666666) - 0.055); 130 | } 131 | vec3 RGB_TO_SRGB(vec3 rgb) { 132 | return vec3(COMPAND_RGB(rgb.x), COMPAND_RGB(rgb.y), COMPAND_RGB(rgb.z)); 133 | } 134 | 135 | // RGB<->XYZ 136 | // XYZ is the classic tristimulus color space developed in 1931 by the International Commission on Illumination (CIE, confusingly). 137 | // Most conversions between color spaces end up going through XYZ; it is a central 'hub' in the color space landscape. 138 | vec3 RGB_TO_XYZ(vec3 rgb) { 139 | return WHITE == D65_WHITE ? (rgb * RGB_TO_XYZ_M) : ((rgb * RGB_TO_XYZ_M) * XYZ_TO_XYZ50_M); 140 | } 141 | vec3 XYZ_TO_RGB(vec3 xyz) { 142 | return WHITE == D65_WHITE ? (xyz * XYZ_TO_RGB_M) : ((xyz * XYZ50_TO_XYZ_M) * XYZ_TO_RGB_M); 143 | } 144 | 145 | // P3<->XYZ 146 | vec3 P3LINEAR_TO_XYZ(vec3 p3linear) { 147 | return p3linear * P3LINEAR_TO_XYZ_M; 148 | } 149 | vec3 XYZ_TO_P3LINEAR(vec3 xyz) { 150 | return xyz * XYZ_TO_P3LINEAR_M; 151 | } 152 | // Display P3 uses the sRGB TRC (gamma function). 153 | // It also uses the D65 white point. 154 | vec3 P3LINEAR_TO_DISPLAYP3(vec3 p3linear) { 155 | return vec3(COMPAND_RGB(p3linear.x), COMPAND_RGB(p3linear.y), COMPAND_RGB(p3linear.z)); 156 | } 157 | vec3 DISPLAYP3_TO_P3LINEAR(vec3 displayp3) { 158 | return vec3(UNCOMPAND_SRGB(displayp3.x), UNCOMPAND_SRGB(displayp3.y), UNCOMPAND_SRGB(displayp3.z)); 159 | } 160 | vec3 XYZ_TO_DISPLAYP3(vec3 xyz) { 161 | return P3LINEAR_TO_DISPLAYP3(XYZ_TO_P3LINEAR(xyz)); 162 | } 163 | vec3 DISPLAYP3_TO_XYZ(vec3 xyz) { 164 | return P3LINEAR_TO_XYZ(DISPLAYP3_TO_P3LINEAR(xyz)); 165 | } 166 | 167 | // L*a*b*/CIELAB 168 | // CIELAB was developed in 1976 in an attempt to make a perceptually uniform color space. 169 | // While it doesn't always do a great job of this (especially in the deep blues), it is still frequently used. 170 | float XYZ_TO_LAB_F(float x) { 171 | // (24/116)^3 1/(3*(6/29)^2) 4/29 172 | return x > 0.00885645167 ? pow(x, 0.333333333) : 7.78703703704 * x + 0.13793103448; 173 | } 174 | vec3 XYZ_TO_LAB(vec3 xyz) { 175 | vec3 xyz_scaled = xyz / WHITE; 176 | xyz_scaled = vec3( 177 | XYZ_TO_LAB_F(xyz_scaled.x), 178 | XYZ_TO_LAB_F(xyz_scaled.y), 179 | XYZ_TO_LAB_F(xyz_scaled.z) 180 | ); 181 | return vec3( 182 | (116.0 * xyz_scaled.y) - 16.0, 183 | 500.0 * (xyz_scaled.x - xyz_scaled.y), 184 | 200.0 * (xyz_scaled.y - xyz_scaled.z) 185 | ); 186 | } 187 | float LAB_TO_XYZ_F(float x) { 188 | // 3*(6/29)^2 4/29 189 | return (x > 0.206897) ? x * x * x : (0.12841854934 * (x - 0.137931034)); 190 | } 191 | vec3 LAB_TO_XYZ(vec3 Lab) { 192 | float w = (Lab.x + 16.0) / 116.0; 193 | return WHITE * vec3( 194 | LAB_TO_XYZ_F(w + Lab.y / 500.0), 195 | LAB_TO_XYZ_F(w), 196 | LAB_TO_XYZ_F(w - Lab.z / 200.0) 197 | ); 198 | } 199 | 200 | // LCh 201 | // LCh is simply L*a*b* converted to polar coordinates. 202 | // Note: by convention, h is in degrees! 203 | vec3 LAB_TO_LCH(vec3 Lab) { 204 | return vec3( 205 | Lab.x, 206 | sqrt(dot(Lab.yz, Lab.yz)), 207 | atan(Lab.z, Lab.y) * 57.2957795131 208 | ); 209 | } 210 | vec3 LCH_TO_LAB(vec3 LCh) { 211 | return vec3( 212 | LCh.x, 213 | LCh.y * cos(LCh.z * 0.01745329251), 214 | LCh.y * sin(LCh.z * 0.01745329251) 215 | ); 216 | } 217 | 218 | // xyY 219 | // This is the color space used in chromaticity diagrams. 220 | // x and y encode chromaticity, while Y encodes luminance. 221 | vec3 XYZ_TO_XYY(vec3 xyz) { 222 | return vec3( 223 | xyz.x / (xyz.x + xyz.y + xyz.z), 224 | xyz.y / (xyz.x + xyz.y + xyz.z), 225 | xyz.y 226 | ); 227 | } 228 | vec3 XYY_TO_XYZ(vec3 xyY) { 229 | return vec3( 230 | xyY.z * xyY.x / xyY.y, 231 | xyY.z, 232 | xyY.z * (1.0 - xyY.x - xyY.y) / xyY.y 233 | ); 234 | } 235 | 236 | // Björn Ottosson's OkLab 237 | // Details here https://bottosson.github.io/posts/oklab/ 238 | // Next comes OkHSL / OkHSV 239 | vec3 XYZ_TO_OKLAB(vec3 xyz) { 240 | vec3 lms = XYZ_TO_OKLAB_LMS*xyz; 241 | return OKLAB_LMS_TO_OKLAB*pow(lms, vec3(1.0)/3.0); 242 | } 243 | 244 | vec3 OKLAB_TO_XYZ(vec3 OkLab) { 245 | vec3 lms0 = OKLAB_TO_OKLAB_LMS*OkLab; 246 | return OKLAB_LMS_TO_XYZ*(lms0*lms0*lms0); 247 | } 248 | 249 | // Think of sYCC as a fast way to get from sRGB to a more perceptual color space that encodes chroma seperately from luma. 250 | // Output format: vec3(luma, blue-difference chroma, red-difference chroma) 251 | 252 | // sYCC is a part of the YCbCr color space family, and was formally introduced in 2003 by the ICC. 253 | // It uses the same transformation matrix as BT.601. 254 | // Note that JPEG uses the BT.601 matrix, too, just slightly modified (I assume to properly handle rounding), and it maps to [0...255] instead. 255 | vec3 SRGB_TO_SYCC(vec3 srgb) { 256 | return srgb*SRGB_TO_SYCC_M; 257 | } 258 | vec3 SYCC_TO_SRGB(vec3 sycc) { 259 | return sycc*SYCC_TO_SRGB_M; 260 | } 261 | 262 | // Composite function one-liners 263 | vec3 SRGB_TO_XYZ(vec3 srgb) { return RGB_TO_XYZ(SRGB_TO_RGB(srgb)); } 264 | vec3 XYZ_TO_SRGB(vec3 xyz) { return RGB_TO_SRGB(XYZ_TO_RGB(xyz)); } 265 | 266 | vec3 SRGB_TO_LAB(vec3 srgb) { return XYZ_TO_LAB(SRGB_TO_XYZ(srgb)); } 267 | vec3 LAB_TO_SRGB(vec3 lab) { return XYZ_TO_SRGB(LAB_TO_XYZ(lab)); } 268 | 269 | vec3 SRGB_TO_LCH(vec3 srgb) { return LAB_TO_LCH(SRGB_TO_LAB(srgb)); } 270 | vec3 LCH_TO_SRGB(vec3 lch) { return LAB_TO_SRGB(LCH_TO_LAB(lch)); } 271 | 272 | vec3 OKLAB_TO_SRGB(vec3 OkLab) { return XYZ_TO_SRGB(OKLAB_TO_XYZ(OkLab)); } 273 | vec3 SRGB_TO_OKLAB(vec3 srgb) { return XYZ_TO_OKLAB(SRGB_TO_XYZ(srgb)); } 274 | 275 | //========// OTHER UTILITY FUNCTIONS //========// 276 | 277 | // Cubic approximation of the planckian (black body) locus. This is a very good approximation for most purposes. 278 | // Returns chromaticity vec2 (x/y, no luminance) in xyY space. 279 | // Technically only designed for 1667K < T < 25000K, but you can push it further. 280 | 281 | // Credit to B. Kang et al. (2002) (https://api.semanticscholar.org/CorpusID:4489377) 282 | // Note: there may be a patent associated with this function 283 | // TODO: if()s are not shader-friendly. find faster method. 284 | vec2 PLANCKIAN_LOCUS_CUBIC_XY(float T) { 285 | vec2 xy = vec2(0.0, 0.0); 286 | if(T < 4000.0) { 287 | xy.x = -0.2661239*1000000000.0/(T*T*T) - 0.2343589*1000000.0/(T*T) + 0.8776956*1000.0/T + 0.179910; 288 | 289 | if(T < 2222.0) xy.y = -1.1063814*xy.x*xy.x*xy.x - 1.34811020*xy.x*xy.x + 2.18555832*xy.x - 0.20219683; 290 | else xy.y = -0.9549476*xy.x*xy.x*xy.x - 1.37418593*xy.x*xy.x + 2.09137015*xy.x - 0.16748867; 291 | } else { 292 | xy.x = -3.0258469*1000000000.0/(T*T*T) + 2.1070379*1000000.0/(T*T) + 0.2226347*1000.0/T + 0.24039; 293 | 294 | xy.y = 3.08175806*xy.x*xy.x*xy.x - 5.8733867*xy.x*xy.x + 3.75112997*xy.x - 0.37001483; 295 | } 296 | return xy; 297 | } 298 | 299 | // Finds the temperature of a color. 300 | // Approximation good to +/-3K for colors on the locus. 301 | // Note: For colors past isotherm intersection points, temperature has little meaning; 302 | // only use this method to interperet colors near the locus. 303 | 304 | // TODO: Implement method with Robertson isotherms 305 | // TODO: Implement Bruce Lindbloom's excellent approximation: 306 | // http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_T.html 307 | float XYY_MCCAMY_COLOR_TEMPERATURE(vec3 xyY) { 308 | float n = (xyY.x - 0.3320)/(0.1858 - xyY.y); 309 | return 449.0*n*n*n + 3525.0*n*n + 6823.3*n + 5520.33; 310 | } 311 | float XYZ_MCCAMY_COLOR_TEMPERATURE(vec3 XYZ) { 312 | vec3 xyY = XYY_TO_XYZ(XYZ); 313 | return XYY_MCCAMY_COLOR_TEMPERATURE(xyY); 314 | } 315 | 316 | // This function gives you the *perceptual* difference between two colors in L*a*b* space. 317 | // Most implementations of it online are are actually wrong!!! 318 | // 319 | // Additionally, although it is often hailed as the current "most accurate" color difference 320 | // formula, it actually contains a pretty decent-sized discontinuity for colors with opposite hues. 321 | // See "The CIEDE2000 Color-DifferenceFormula: Implementation Notes, 322 | // Supplementary Test Data, and Mathematical Observations" 323 | // by G. Sharma et al. for more information. Link: 324 | // http://www2.ece.rochester.edu/~gsharma/ciede2000/ciede2000noteCRNA.pdf 325 | // 326 | float LAB_DELTA_E_CIE2000(vec3 lab1, vec3 lab2) { 327 | // b = bar 328 | // p = prime 329 | float Cb7 = pow((sqrt(lab1.y*lab1.y + lab1.z*lab1.z) + sqrt(lab1.y*lab1.y + lab1.z*lab1.z))*0.5, 7.0); 330 | // 25^7 331 | float G = 0.5*(1.0-sqrt(Cb7/(Cb7 + 6103515625.0))); 332 | 333 | float ap1 = lab1.y*(1.0 + G); 334 | float ap2 = lab2.y*(1.0 + G); 335 | 336 | float Cp1 = sqrt(ap1*ap1 + lab1.z*lab1.z); 337 | float Cp2 = sqrt(ap2*ap2 + lab2.z*lab2.z); 338 | 339 | float hp1 = atan(lab1.z, ap1); 340 | float hp2 = atan(lab2.z, ap2); 341 | if(hp1 < 0.0) hp1 = TWO_PI + hp1; 342 | if(hp2 < 0.0) hp2 = TWO_PI + hp2; 343 | 344 | float dLp = lab2.x - lab1.x; 345 | float dCp = Cp2 - Cp1; 346 | float dhp = hp2 - hp1; 347 | dhp += (dhp>PI) ? -TWO_PI: (dhp<-PI) ? TWO_PI : 0.0; 348 | // don't need to handle Cp1*Cp2==0 case because it's implicitly handled by the next line 349 | float dHp = 2.0*sqrt(Cp1*Cp2)*sin(dhp/2.0); 350 | 351 | float Lbp = (lab1.x + lab2.x)*0.5; 352 | float Cbp = sqrt(Cp1 + Cp2)/2.0; 353 | float Cbp7 = pow(Cbp, 7.0); 354 | 355 | // CIEDE 2000 Color-Difference \Delta E_{00} 356 | // This where everyone messes up (because it's a pain) 357 | // it's also the source of the discontinuity... 358 | 359 | // We need to average the angles h'_1 and h'_2 (hp1 and hp2) here. 360 | // This is a surprisingly nontrivial task. 361 | // Credit to https://stackoverflow.com/a/1159336 for the succinct formula. 362 | float hbp = mod( ( hp1 - hp2 + PI), TWO_PI ) - PI; 363 | hbp = mod((hp2 + ( hbp / 2.0 ) ), TWO_PI); 364 | if(Cp1*Cp2 == 0.0) hbp = hp1 + hp2; 365 | 366 | // 30 deg 6 deg 63 deg 367 | float T = 1.0 - 0.17*cos(hbp - 0.52359877559) + 0.24*cos(2.0*hbp) + 0.32*cos(3.0*hbp + 0.10471975512) - 0.2*cos(4.0*hbp - 1.09955742876); 368 | 369 | float dtheta = 30.0*exp(-(hbp - 4.79965544298)*(hbp - 4.79965544298)/25.0); 370 | float RC = 2.0*sqrt(Cbp7/(Cbp7 + 6103515625.0)); 371 | 372 | float Lbp2 = (Lbp-50.0)*(Lbp-50.0); 373 | float SL = 1.0 + 0.015*Lbp2/sqrt(20.0 + Lbp2); 374 | float SC = 1.0 + 0.045*Cbp; 375 | float SH = 1.0 + 0.015*Cbp*T; 376 | 377 | float RT = -RC*sin(2.0*dtheta)/TWO_PI; 378 | 379 | return sqrt(dLp*dLp/(SL*SL) + dCp*dCp/(SC*SC) + dHp*dHp/(SH*SH) + RT*dCp*dHp/(SC*SH)); 380 | } 381 | 382 | float XYZ_DELTA_E_CIE2000(vec3 xyz1, vec3 xyz2) { 383 | return LAB_DELTA_E_CIE2000(XYZ_TO_LAB(xyz1), XYZ_TO_LAB(xyz2)); 384 | } 385 | 386 | float SRGB_DELTA_E_CIE2000(vec3 srgb1, vec3 srgb2) { 387 | return LAB_DELTA_E_CIE2000(SRGB_TO_LAB(srgb1), SRGB_TO_LAB(srgb2)); 388 | } 389 | 390 | // The most computationally expensive (and maybe the best?) way to tell how bright a color appears. 391 | // Calculates L* as "the perceptual difference between pure black and the input color". 392 | float SRGB_PERCEPTUAL_LIGHTNESS_DE2000(vec3 srgb) { 393 | return LAB_DELTA_E_CIE2000(SRGB_TO_LAB(srgb), vec3(0.0, 0.0, 0.0)); 394 | } 395 | 396 | // Just returns the L* component after L*a*b* conversion 397 | // Warning: resultant L* is in L*a*b* space (0 to 100) 398 | float SRGB_PERCEPTUAL_LIGHTNESS_LAB(vec3 srgb) { 399 | return SRGB_TO_LAB(srgb).x; 400 | } 401 | 402 | // These functions are very similar, but I am keeping them seperate to ensure they are used properly. 403 | // Luma is the weighted sum of GAMMA-COMPRESSED RGB components, while 404 | // Luminance (relative luminance) is the weighted sum of LINEAR RGB components. 405 | float SRGB_LUMA(vec3 srgb) { 406 | return dot(srgb, LUMA_VEC); 407 | } 408 | float RGB_RELATIVE_LUMINANCE(vec3 rgb) { 409 | return dot(rgb, LUMA_VEC); 410 | } 411 | 412 | // if you use this function and want to display it, make sure you gamma-compress the result with COMPAND_RGB(float x) 413 | float SRGB_RELATIVE_LUMINANCE(vec3 srgb) { 414 | return dot(SRGB_TO_RGB(srgb), LUMA_VEC); 415 | } 416 | -------------------------------------------------------------------------------- /scratchpad.glsl: -------------------------------------------------------------------------------- 1 | 2 | void mainImage(out vec4 fragColor, in vec2 fragCoord) 3 | { 4 | // Normalized pixel coordinates (from 0 to 1) 5 | vec2 uv = 1.1*fragCoord / iResolution.x; 6 | 7 | vec3 inno = vec3(uv.x, uv.y, abs(sin(iTime))); 8 | inno = XYY_TO_XYZ(inno); 9 | // Time varying pixel color 10 | vec3 col = RGB_TO_SRGB(XYZ_TO_RGB(inno)); 11 | inno = vec3(0.745, 0.423, 0.705); 12 | //col = inno; 13 | 14 | // Output to screen 15 | fragColor = vec4(col, 1.0); 16 | if(col.x < 0.0 || col.y < 0.0 || col.z < 0.0 || col.x > 1.0 || col.y > 1.0 || col.z > 1.0) fragColor = vec4(0.0, 0.0, 0.0, 1.0); 17 | } -------------------------------------------------------------------------------- /screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rachmanin0xFF/GLSL-Color-Functions/91ac0f97f1dc698bf1f315234ef5f51df474cd0b/screenshot2.png --------------------------------------------------------------------------------