Search code examples
javascriptanimationthree.jstweencolor-blending

How to tween between two colours using three.js?


I have an three.js object which is a given colour. I want to animate it smoothly to another colour. During the animation, it should only show a direct gradation between the start and end. That is, it should not perform the tween linearly in RGB colour space. I'm not even sure that a linear tween within HSV space would look good either.

How can I get this kind of colour tween on a three.js object?


Solution

  • I have a version of this that makes a tween in HSV space. It's not perfect, as many different hues can appear along the way.

    Three.js doesn't include a method for getting the HSV values from a THREE.Color. So, add one:

    THREE.Color.prototype.getHSV = function()
    {
        var rr, gg, bb,
            h, s,
            r = this.r,
            g = this.g,
            b = this.b,
            v = Math.max(r, g, b),
            diff = v - Math.min(r, g, b),
            diffc = function(c)
            {
                return (v - c) / 6 / diff + 1 / 2;
            };
    
        if (diff == 0) {
            h = s = 0;
        } else {
            s = diff / v;
            rr = diffc(r);
            gg = diffc(g);
            bb = diffc(b);
    
            if (r === v) {
                h = bb - gg;
            } else if (g === v) {
                h = (1 / 3) + rr - bb;
            } else if (b === v) {
                h = (2 / 3) + gg - rr;
            }
            if (h < 0) {
                h += 1;
            } else if (h > 1) {
                h -= 1;
            }
        }
        return {
            h: h,
            s: s,
            v: v
        };
    };
    

    Then, the tween is relatively straightforward:

    new TWEEN.Tween(mesh.material.color.getHSV())
        .to({h: h, s: s, v: v}, 200)
        .easing(TWEEN.Easing.Quartic.In)
        .onUpdate(
            function()
            {
                mesh.material.color.setHSV(this.h, this.s, this.v);
            }
        )
        .start();
    

    I'd be interested to hear of a more perceptually natural transition.