Jump to content

Colour Temperature (Kelvin) to RGB or LSL colour


Jenna Huntsman
 Share

You are about to reply to a thread that has been inactive for 713 days.

Please take a moment to consider if this thread is worth bumping.

Recommended Posts

Hey all,

Made a quick code port to LSL of an algorithm that converts colour temperature (in degrees Kelvin) to RGB or LSL colour.

Hope someone finds this useful!

Note: While the kelvin to RGB portion of this is pretty accurate, the addition (or subtraction) of the shift value (i.e. any values that aren't 0) will produce an inaccurate result - It's close enough to be usable, but something to keep in mind.

vector Kelvin2RGB(integer Kelvin, integer Shift, integer ToLSL)
{ //Convert Kelvin value to regular RGB or LSL colour. Credit: Jenna Huntsman, Mollymews, Tanner Helland
    //This will take an input value between 1000 and 40000 kelvin - values outside of this may work; however will likely be inaccurate.
    //Shift corresponds to the Green / Magenta shift value to be applied. This should be 0 for a perfect conversion, however not all light sources produce a pure white light. For example, flourescent sources (~4100k) often produce light with a green tint. Try using a positive value to shift towards green, and a negative one to shift towards magenta.
    float Temp = Kelvin/100;
    float Red;
    float Green;
    float Blue;
    if(Temp <= 66)
    {
        Red = 255;
        Green = Temp;
        Green = 99.4708025861 * llLog(Green) - 161.1195681661;
        if(Temp <= 19)
        {
            Blue = 0;
        }
        else
        {
            Blue = Temp-10;
            Blue = 138.5177312231 * llLog(Blue) - 305.0447927307;
        }
    }
    else
    {
        Red = Temp-60;
        Red = 329.698727446 * llPow(Red,-0.1332047592);
        Green = Temp-60;
        Green = 288.1221695283 * llPow(Green,-0.0755148492);
        Blue = 255;
    }
    
    Green = Green + Shift; //G/M colour shift
    Blue = Blue + ((Shift/2)*(Temp/25)); //Offset shift to fake accuracy.
    //Note: the shift value is a hack and not at all accurate.
    
    //Clamp RGB values to RGB range - Credit: Mollymews
    vector RGB =
    <  
        llList2Float([0, Red, 255], (Red >= 0) * (-(Red > 255) | 1)),
        llList2Float([0, Green, 255], (Green >= 0) * (-(Green > 255) | 1)),
        llList2Float([0, Blue, 255], (Blue >= 0) * (-(Blue > 255) | 1))
    >;
    
    if(ToLSL) //Does the user want us to return the colour in LSL format or regular RGB?
    {
        RGB = RGB/255;
    }
    return RGB;
} //https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html

 

Edited by Jenna Huntsman
  • Like 5
Link to comment
Share on other sites

  • 4 weeks later...

Hey all,

I have kept plugging away at this and have now created a V2, which in my opinion is a much improved version - It's a lot more accurate than the previous iteration.

It also occurred to me that I should provide a demo, so anyone interested but unfamiliar with the topic can have a play - thus, I present, the (Free!) Studio Saberhagen Bodylight:

https://marketplace.secondlife.com/p/Studio-Saberhagen-Lighting-System-Bodylight/23487490

Anyway, the V2 code:

// ===--- BEGIN KELVIN TO RGB CODE ---===
/*
 * MIT License
 * 
 * Copyright (c) 2022 Jenna Huntsman, based on original work(s) by Christopher J. Howard
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

list xyz2709 = [<3.2409699419045235,-0.9692436362808801,0.05563007969699365>,<-1.5373831775700941,1.875967501507721,-0.20397695888897652>,<-0.4986107602930036,0.041555057407175605,1.0569715142428784>]; //XYZ to Rec. 709 primaries & whitepoint.

vector TransformByMatrix(vector colourIn, list matrix)
{ //Transform vector by given 3x3 matrix. Credit: Jenna Huntsman
    vector Out;
    vector m1 = llList2Vector(matrix,0);
    vector m2 = llList2Vector(matrix,1);
    vector m3 = llList2Vector(matrix,2);
    Out.x = colourIn.x * m1.x + colourIn.y * m2.x + colourIn.z * m3.x;
    Out.y = colourIn.x * m1.y + colourIn.y * m2.y + colourIn.z * m3.y;
    Out.z = colourIn.x * m1.z + colourIn.y * m2.z + colourIn.z * m3.z;
    return Out;
}

/**
 * Calculates the color of an ideal black-body radiator, given its temperature in Kelvin.
 *
 * @param t Temperature, in Kelvin.
 * @return Correlated color, in linear RGB space.
 *
 * @see https://en.wikipedia.org/wiki/Planckian_locus
 * @see https://en.wikipedia.org/wiki/CIE_1960_color_space
 * @see https://google.github.io/filament/Filament.html
 */

float max(float x, float y)
{ //Return the higher of 2 given values.
    if( y > x ) return y;
    return x;
}

vector mix(vector x, vector y, vector t)
{
    vector ret;
    ret.x = x.x*(1-t.x) + y.x*t.x;
    ret.y = x.y*(1-t.y) + y.y*t.y;
    ret.z = x.z*(1-t.z) + y.z*t.z;  
    return ret;
}

vector step(vector edge, vector x)
{
    vector ret = <1,1,1>;
    if(x.x < edge.x) ret.x = 0;
    if(x.y < edge.y) ret.x = 0;
    if(x.z < edge.z) ret.x = 0;
    return ret;
}

//Hint: Gamma value must be above 0. Recommeded values: 0.95, 1.0, 1.8, 2.2 and 2.4
//Hint: Duv value should be within range of -300 to 300. Set to 0 to disable.

vector CCT2sRGB(float CCT, float Duv, float gamma)
{ //Convert CCT value and Duv value to RGB value. Credit: Jenna Huntsman, Christopher J. Howard
    // Approximate the Planckian locus in CIE 1960 UCS color space (Krystek's algorithm)
    if(gamma <= 0) return ZERO_VECTOR; //Return nothing if no gamma value was specified. Gamma MUST be above 0.
    float tt = CCT * CCT;
    float u = (0.860117757 + 1.54118254e-4 * CCT + 1.28641212e-7 * tt) / (1.0 + 8.42420235e-4 * CCT + 7.08145163e-7 * tt);
    float v = (0.317398726 + 4.22806245e-5 * CCT + 4.20481691e-8 * tt) / (1.0 - 2.89741816e-5 * CCT + 1.61456053e-7 * tt);
    
    if(Duv)
    { //Calculate offset from locus (not exactly accurate, but a good approximation of delta uv)
        float t1 = CCT + 5; //Calculate coords for next CCT value
        float tt1 = t1 * t1;
        float u1 = (0.860117757 + 1.54118254e-4 * t1 + 1.28641212e-7 * tt1) / (1.0 + 8.42420235e-4 * t1 + 7.08145163e-7 * tt1);
        float v1 = (0.317398726 + 4.22806245e-5 * t1 + 4.20481691e-8 * tt1) / (1.0 - 2.89741816e-5 * t1 + 1.61456053e-7 * tt1);
        vector Buv = <u,v,0>;
        vector Nuv = <u1,v1,0>-Buv; //Get 'direction' vector. (this isn't normalized, but it doesn't need to be in this case)
        Nuv = Nuv*llAxisAngle2Rot(<0,0,1>,(90*DEG_TO_RAD)); //Rotate 'direction' by 90deg.
        Nuv += Buv;
        Duv = -Duv;
        vector Fin = Buv*(1-Duv) + Nuv*Duv; //interpolate along line by x amount (Duv)
        u = Fin.x;
        v = Fin.y;
    }
    
    // CIE 1960 UCS -> CIE xyY, Y = 1
    vector xyy = <3.0 * u, 2.0 * v,1> / (2.0 * u - 8.0 * v + 4.0);
    xyy.z = 1; //Set transmissive (luminence) value to 1 as this is a light source, thus always bright.
    
    // CIE xyY -> CIE XYZ
    vector xyz = <xyy.x / xyy.y, 1.0, (1.0 - xyy.x - xyy.y) / xyy.y>;
    
    // CIE XYZ -> linear sRGB
    vector srgb = TransformByMatrix(xyz,xyz2709);
    
    // Normalize RGB to preserve chromaticity
    return srgb_eotf_inverse(srgb / max(srgb.x, max(srgb.y, srgb.z)),gamma);
}

vector srgb_eotf_inverse(vector l, float gamma)
{ //Conversion into gamma-encoded sRGB space. Credit: Jenna Huntsman, Christopher J. Howard
    vector low = l * 12.92;
    vector high = <llPow(l.x,1.0 / gamma), llPow(l.y, 1.0 / gamma), llPow(l.z, 1.0 / gamma)>;
    high = high * (1.055 - 0.055);
    return mix(low, high, step(<0.0031308,0.0031308,0.0031308>, l));
}

// https://www.shadertoy.com/view/tsKczy
// ===--- END KELVIN TO RGB CODE ---===

 

  • Like 1
Link to comment
Share on other sites

You are about to reply to a thread that has been inactive for 713 days.

Please take a moment to consider if this thread is worth bumping.

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
 Share

×
×
  • Create New...