13
\$\begingroup\$

I have found it rather difficult to find a solution to this in the Unity documents.

Color.Lerp(Color a, Color b, float t) is a function that gradually changes a color according to a step t, giving it the final value of the Color b.

How do I Lerp between multiple colors one after another?

\$\endgroup\$

8 Answers 8

16
\$\begingroup\$

Let _colors be an array of colors let LENGHT be the number of colors in the array let t be the 0..1 float value

float scaledTime = t * (float) (LENGHT - 1);
Color oldColor = _colors[(int) scaledTime];
Color newColor = _colors[(int) (scaledTime + 1f)];
float newT = scaledTime - Mathf.Round(scaledTime); 

finally you can use Lerp

Color.Lerp(oldColor, newColor, newT)
\$\endgroup\$
4
  • \$\begingroup\$ +1 - great answer - doesn't require any looping or special branches, and is completely general to N colors. \$\endgroup\$
    – jpaver
    Commented Apr 21, 2015 at 7:22
  • \$\begingroup\$ When the value 1 is hit will the color be the very last color in the array? \$\endgroup\$ Commented May 24, 2015 at 22:10
  • \$\begingroup\$ G3tinmybelly you're right. The t==1 is not managed correctly. So we may add before : if (t==1) return Arr[N-1] \$\endgroup\$ Commented May 25, 2015 at 6:57
  • \$\begingroup\$ *** IMPORTANT *** It should be Mathf.Floor; NOT Mathf.Round. \$\endgroup\$
    – Xtro
    Commented Sep 15, 2020 at 20:45
16
\$\begingroup\$

One approach that can be taken with multiple color transitions is to leverage a Gradient.

By exposing a public variable of this type a developer an use the Inspector to launch the Gradient Editor to design a gradient containing any number of colors. This editor allows you to use a the unity color pickers, fine tune placement of the color/alpha keys and save/load gradients.

enter image description here

Once designed the Gradient.Evaluate() method will accept a float in the range 0-1 to return the appropriate color.

using UnityEngine;

public class GradientTest : MonoBehaviour
{
    public Gradient myGradient;
    public float strobeDuration = 2f;

    public void Update() {
        float t = Mathf.PingPong(Time.time / strobeDuration, 1f);
        Camera.main.backgroundColor = myGradient.Evaluate(t);
    }
}

Unfortunately the API for programmatically building a Gradient is not as elegant .

\$\endgroup\$
5
  • 1
    \$\begingroup\$ ColorBands can be used for the same goal but with more freedom over color \$\endgroup\$ Commented Aug 24, 2016 at 14:02
  • 1
    \$\begingroup\$ Omg, this is the best answer. <3 \$\endgroup\$
    – Racoon
    Commented May 3, 2020 at 17:28
  • \$\begingroup\$ This, as it turns out, is also just linear interpolation (Lerp) performed via a different set of API calls. \$\endgroup\$ Commented Jan 25 at 19:12
  • \$\begingroup\$ @dodexahedron When I wrote this answer 9 years ago it was a simple lerp at the back end with the only difference being that Unity was doing the housekeeping so it offered an opportunity to simplify user code. But it looks like they have extendind it to support a PerceptualBlend using oklabs colorspace, this looks pretty nice compared to a linear HSV. \$\endgroup\$ Commented Jan 29 at 4:45
  • \$\begingroup\$ Yep. There are a few neat libraries now. One I like that appears pretty popular is KGySoft.Drawing. It's under a license that he describes as GPL-like, but which looks closer to MIT to me (IANAL). It's got a ton of facilities for all sorts of operations, and it's heavily hardware-optimized where possible. It also brings a lot of the missing System.Drawing stuff to non-windows platforms, with compatible types. \$\endgroup\$ Commented Feb 9 at 10:27
1
\$\begingroup\$
public float every;   //The public variable "every" refers to "Lerp the color every X"
float colorstep;
Color[] colors = new Color[4]; //Insert how many colors you want to lerp between here, hard coded to 4
int i;
Color lerpedColor = Color.red;  //This should optimally be the color you are going to begin with

void Start () {

    //In here, set the array colors you are going to use, optimally, repeat the first color in the end to keep transitions smooth

    colors [0] = Color.red;
    colors [1] = Color.yellow;    
    colors [2] = Color.cyan;
    colors [3] = Color.red;

}


// Update is called once per frame
void Update () {

    if (colorstep < every) { //As long as the step is less than "every"
        lerpedColor = Color.Lerp (colors[i], colors[i+1], colorstep);
        this.GetComponent<Camera> ().backgroundColor = lerpedColor;
        colorstep +=0.025f;  //The lower this is, the smoother the transition, set it yourself
    } else { //Once the step equals the time we want to wait for the color, increment to lerp to the next color

        colorstep = 0;

        if (i < (colors.Length - 2)){ //Keep incrementing until i + 1 equals the Lengh
        i++;
        }
        else { //and then reset to zero
            i=0;
        }
    }
}

So this is the code I ended up using to lerp between three colors of mine, I hope I will be of use to anyone who decides to look for this.

\$\endgroup\$
0
1
\$\begingroup\$

Ok, as we know lerping between 2 values is easy however it seems lerping between more than 2 is... well easy but usually means a heap of branches and/or mucking around.

I had the need to pass in a time value 0.0f to 1.0f and getting back an interpolated float4 color that went from white -> yellow -> orange -> red -> brown -> gray -> black. Basically the 0.0 to 1.0 was the percentage of an explosion duration and the color is used to tint it.

Originally I had a heap of branches and broke it up into 6 conditional lerps. However the code is in a particle system so rather not be adding heaps of branches per particle.

My solution, a small bit of code and a lookup table. The table could be static or filled on the fly.

The code below uses the DX XMVector type but the logic will work just as well for other similar classes.

#include <cmath>

// sample data array of colors

const XMVector arr_col_temp[] = {
XMFLOAT4(1, 0.8f, 0.8f, 1), // white
XMFLOAT4(1, 1, 145.0f / 255.0f, 1), // yellow
XMFLOAT4(1, 0.8f, 0, 1), // orange
XMFLOAT4(1, 0, 0, 1), // red
XMFLOAT4(99 / 255.0f, 37 / 255.0f, 19 / 255.0f, 1), // brown
XMFLOAT4(42 / 255.0f, 42 / 255.0f, 42 / 255.0f, 1), // gray
XMFLOAT4(0.2f, 0, 0, 1), // black

XMFLOAT4(0.2f, 0, 0, 1) // black ... last one needs to be duplicated
};


inline XMVector ColorTemp(float t) // t must be 0.0 to 1.0
{
    float i_int;
    float i_frac = modf(t * (ARRAYSIZE(arr_col_temp) - 2), &i_int);
    int i1 = i_int; 
    
    return XMVectorLerp(arr_col_temp[i1], arr_col_temp[i1+1], i_frac);
}

This will do a "linear" lerp between the values however if you want a curved fit then just adjust the passed in value.

For example.

XMVector c = ColorTemp(sqrtf(t));
\$\endgroup\$
1
  • \$\begingroup\$ Isnt this basically the accepted answer? \$\endgroup\$
    – Zibelas
    Commented Feb 1, 2023 at 8:44
0
\$\begingroup\$

I feel like there may be a better solution. The only reason I would see to lerp from color to color is if you were wanting to continuously change the hue... http://en.wikipedia.org/wiki/Hue

Here's how to convert HSV to RGB: http://en.wikipedia.org/wiki/HSL_and_HSV#From_HSV

With this you can use HSV colors, simply change the hue, then convert to RGB. Besides, with Color.Lerp, you have the problem of inconsistency. If you lerp from orange to yellow, then to green, you'll find that the color starts to go to yellow very quickly then starts to slow as it nears yellow, then speeds up again once it passes yellow and goes to green. So it will slow color changing at every point that you're lerping to. I think changing the hue would be much more effective - and in the long run, will give a better effect. :)

\$\endgroup\$
0
\$\begingroup\$

How about you write your own version, which leverages Color.Lerp()?

A very simple version that takes 3 colors, and puts the second one right in the middle could look like this:

Color Lerp3(Color a, Color b, Color c, float t)
{
    if (t < 0.5f) // 0.0 to 0.5 goes to a -> b
        return Color.Lerp(a, b, t / 0.5f);
    else // 0.5 to 1.0 goes to b -> c
        return Color.Lerp(b, c, (t - 0.5f) / 0.5f);
}
\$\endgroup\$
0
\$\begingroup\$

Since you didn't say what you wanted to change color to, I'll give a vague example with a color created on the method.

The point is to have a collection of the colors, and a duration in total (like on the example I'll provide) or a duration between each one (that's up to you).

I personally do not interpolate things on Update that I know I won't be constantly interpolating (Camera being an exception), so I use coroutines to handle that.

On this example I divide the duration given on inspector by the colors quantity, and then I Lerp the actual iterator color to the next iterator color, and the duration will be of the duration previously spliced. Here is the sample:

public class ColorLerping : MonoBehaviour
{
    public Color sampleColor; /// Just for debugging purposes.
    public float lerpDuration;
    public Color[] colors;

    void Awake()
    {
        StartCoroutine(LerpColors());
    }

    private IEnumerator LerpColors()
    {
        if(colors.Length > 0)
        {
            /// Split the time between the color quantities.
            float dividedDuration = lerpDuration / colors.Lenght;

            for(int i = 0; i < colors.Length - 1; i++)
            {
                float t = 0.0f;

                while(t < (1.0f + Mathf.Epsilon))
                {
                    sampleColor = Color.Lerp(colors[i], colors[i + 1], t);
                    t += Time.deltaTime / dividedDuration;
                    yield return null;
                }

                // Since it is posible that t does not reach 1.0, force it at the end.
                sampleColor = Color.Lerp(colors[i], colors[i + 1], 1.0f);
            }

        }
        else yield return null; /// Do nothing if there are no colors.
    }
}

Hope it helps.

\$\endgroup\$
0
\$\begingroup\$

I have written these as glsl shader functions, since this is what I needed, but the same thing could be written in other languages, and extended to handle arrays of 'n' length setting 'fromIndex' like this:

int fromIndex           = (int)(unitValue * (arrayLength - 1.000001));

But shaders require array dimensions to be explicit, so I didnt write it like that.

inline fixed4 Lerp3Colors(fixed4 colors[3], half value)
{
    float unitValue         = saturate(value);

    int fromIndex           = (int)(unitValue * 1.999999);     // Will be either 0 or 1
    fixed4 fromColor        = colors[fromIndex];

    int toIndex             = fromIndex + 1;
    fixed4 toColor          = colors[toIndex];

    float deltaAmount       = frac(unitValue * 2);

    return lerp(fromColor, toColor, deltaAmount);
}

inline fixed4 Lerp4Colors(fixed4 colors[4], half value)
{
    float unitValue         = saturate(value);

    int fromIndex           = (int)(unitValue * 2.999999);     // Will be either 0 or 1 or 2
    fixed4 fromColor        = colors[fromIndex];

    int toIndex             = fromIndex + 1;
    fixed4 toColor          = colors[toIndex];

    float deltaAmount       = frac(unitValue * 3);

    return lerp(fromColor, toColor, deltaAmount);
}
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .