[SOLVED] Gradient to TextGeometry

Hello,

I’m having trouble in modifying a gradient applied to a TextGeometry.
https://codepen.io/lucgenti/pen/WdybLg

The gradient I’m trying to apply is this one:
https://jsfiddle.net/lucav/zLw1jw80/

I took the algorithm for the gradient from there:
https://stemkoski.github.io/Three.js/Vertex-Colors.html (last cube)

Basically is using the position in the space of the point in the vertex to calculate the colors on each Face3. But there’s a way to do this way to make a more complex gradient?

I also tried a simpler approach with the canvas, but I din’t make it work (can’t draw a Rect from bottom to top on the arc of the letter).

var texture = new THREE.Texture( generateTexture() );
texture.needsUpdate = true; 
var materials = [ 
  new THREE.MeshBasicMaterial( { color: '#ECECEE', overdraw: 0.5 } ),
  new THREE.MeshBasicMaterial( { map: texture, overdraw: 0.5, transparent: true } )
];    

function generateTexture() {
            var size = 25;

            canvas = document.createElement( 'canvas' );
            canvas.width = size;
            canvas.height = size;

            var context = canvas.getContext( '2d' );

            var gradient = context.createLinearGradient( 0, 0, size, 0 );
            gradient.addColorStop(0, '#f7b000');
            gradient.addColorStop(0.25, '#dd0080');
            gradient.addColorStop(0.5, '#622b85');
            gradient.addColorStop(0.75, '#007dae');
            gradient.addColorStop(1, '#77c8db');
            context.fillStyle = gradient;
            context.fillRect(size/1.6, 7, size, size);

            return canvas;
}

ColorLerp

https://jsfiddle.net/prisoner849/nr141fk7/

A start point for you to make transition from one colour to another, using .lerp() method of THREE.Color().

var geom = new THREE.TorusKnotGeometry( 2.5, .5, 100, 16 );
geom.computeBoundingBox();

var bbox = geom.boundingBox;
var size = new THREE.Vector3().subVectors(bbox.max, bbox.min); // we'll use it to get normalized positions of vertices in faces of geometry

var vertexIndices = ['a', 'b', 'c'];
var face, vertex, normalized = new THREE.Vector3(), normalizedY = 0;

var red = new THREE.Color("red"), blue = new THREE.Color("blue");

for(var i = 0; i < geom.faces.length; i++){
  face = geom.faces[i];
  for (var v = 0; v < 3; v++){
    vertex = geom.vertices[face[vertexIndices[v]]];
    normalizedY = normalized.subVectors(vertex, bbox.min).divide(size).y; // we'll use the normalized Y-coordinate
    face.vertexColors[v] = red.clone().lerp(blue, normalizedY);
  }
}

Imagine that the red color is at 0, and the blue one is at 1. Having this, try to adapt it to a function which works with several colours.

2 Likes

Thanks for your reply.

I tried this

if (normalizedY < 0.35) {
    face.vertexColors[v] = yellow.clone().lerp(purple, normalizedY);
}
else if (normalizedY >= 0.35 && normalizedY < 0.5) {
    face.vertexColors[v] = purple.clone().lerp(violet, normalizedY);
}
else if (normalizedY >= 0.5 && normalizedY < 0.65) {
    face.vertexColors[v] = violet.clone().lerp(cyan, normalizedY);
}
else if (normalizedY >= 0.65) {
    face.vertexColors[v] = cyan.clone().lerp(lightblue, normalizedY);
}    

But it doesn’t smoothly interpolate between colors (spline interpolation?).

Any thoughts why it behaves like that? :slight_smile:

Honestly I have no idea (first time ever I’m diving into rendering, vectors etc.). Maybe is also due to the fact that a smooth curve means that from point a to the next point b the difference between vectors is subtle, and the lerp function works better (I’m not good in this kind of mathematics so I’m just wondering).

But it can also be to the fact that I should not use this kind of breakpoints like in a canvas gradient, but adapt the normalizedY dynamically between less number of colors.

With some edits I reached this result:

https://codepen.io/lucgenti/pen/jYpevR

var bluette = new THREE.Color(0x559BBF), lightblue = new THREE.Color(0x77c8db), cyan = new THREE.Color(0x007dae), violet = new THREE.Color(0x622b85), yellow = new THREE.Color("yellow"), purple = new THREE.Color(0xdd0080), magenta = new THREE.Color("magenta");
            var red = new THREE.Color("red"), blue = new THREE.Color("blue");

            for ( var i = 0; i < length; i++ ) 
            {
                face = geometry.faces[i];
                var j = i/length;
                for (var v = 0; v < 3; v++){
                    vertex = geometry.vertices[face[faceIndices[v]]];
                    normalizedX = normalized.subVectors(vertex, bbox.min).divide(size).x;
                    normalizedY = Math.abs(normalized.subVectors(vertex, bbox.max).divide(size).y);
                    if (normalizedY < 0.5) {
                        face.vertexColors[v] = yellow.clone().lerp(purple, normalizedY * (normalizedY/0.5)*2);
                    }
                    else if (normalizedY >= 0.5) {
                        face.vertexColors[v] = purple.clone().lerp(bluette, normalizedY * Math.abs((normalizedY/0.5-1))*2);
                    }                    
                }
            }

I’m basically exponentially increasing the lerp’s alpha in proximity of the breakpoint for the first part and viceversa on the other half, using only 3 colors.

StopColorsGradient

https://jsfiddle.net/prisoner849/k3895nho/

Okay, as you, at least, try to do something :+1:, there’s the function which applies a gradient to a geometry :slight_smile:

Maybe there is another, better, approach. I came up with that one :slight_smile:

var geom = new THREE.TorusKnotGeometry(2.5, .5, 100, 16);

var rev = true;

var cols = [{
  stop: 0,
  color: new THREE.Color(0xf7b000)
}, {
  stop: .25,
  color: new THREE.Color(0xdd0080)
}, {
  stop: .5,
  color: new THREE.Color(0x622b85)
}, {
  stop: .75,
  color: new THREE.Color(0x007dae)
}, {
  stop: 1,
  color: new THREE.Color(0x77c8db)
}];

setGradient(geom, cols, 'y', rev);

function setGradient(geometry, colors, axis, reverse){

  geometry.computeBoundingBox();

  var bbox = geometry.boundingBox;
  var size = new THREE.Vector3().subVectors(bbox.max, bbox.min);

  var vertexIndices = ['a', 'b', 'c'];
  var face, vertex, normalized = new THREE.Vector3(),
    normalizedAxis = 0;

  for (var c = 0; c < colors.length - 1; c++) {

    var colorDiff = colors[c + 1].stop - colors[c].stop;

    for (var i = 0; i < geometry.faces.length; i++) {
      face = geometry.faces[i];
      for (var v = 0; v < 3; v++) {
        vertex = geometry.vertices[face[vertexIndices[v]]];
        normalizedAxis = normalized.subVectors(vertex, bbox.min).divide(size)[axis];
        if (reverse) {
          normalizedAxis = 1 - normalizedAxis;
        }
        if (normalizedAxis >= colors[c].stop && normalizedAxis <= colors[c + 1].stop) {
          var localNormalizedAxis = (normalizedAxis - colors[c].stop) / colorDiff;
          face.vertexColors[v] = colors[c].color.clone().lerp(colors[c + 1].color, localNormalizedAxis);
        }
      }
    }
  }
}
2 Likes

Wow, many thanks for your help. I wouldn’t have done it by myself.

I just changed your function by taking the y axis, and voila, 100% as desidered.

:wink: You’re welcome! That gradiental “C” looks nice ))

1 Like