three.jsmeshbuffer-geometry

THREE.js - Apply Gradient Colors to Imported GLTF Model


I’ve been trying to apply gradient colors to the Flower model used in this official THREE.js tutorial, The flower model looks like this:

enter image description here

and it can be downloaded from here

So far I’ve been able to successfully load the model into my project, and I’ve also been able to apply different colors to its "blossom" Mesh - but only solid colors.

I’d like to apply gradient colors.
To illustrate what I have in mind, I made a very quick-and-dirty image in photoshop of what it might look like:

enter image description here

I tried doing this using the vertexColors technique - which is the only technique I know (I’m pretty new to THREE.js) - no luck thus far (code below.)

At this point a part of me is wondering if this is even possible to do with an imported GLTF model - or if its sort of a lost cause.

Would love some input/help.

Here’s my code - in two parts: the first is for solid colors - which works, the second is my attempt at applying Gradient colors - which does not work:

// NOTE: I stored the already imported GLTF model in a new Mesh called “blossomMesh”

// 1. Create a new Group to which I’ll add the Stem and Blossom:
var newFlower = new THREE.Group();

// 2. CLONE the MESH of the imported 3D model:
let newBlossomMesh = blossomMesh.clone();

// 3. CLONE it's GEOMETRY:
var newBlossomGeometry = blossomMesh.geometry.clone();

// 4. CLONE it's MATERIAL:
var newBlossomMaterial = blossomMesh.material.clone();
            
// 5. MAKE a NEW COLOR:
let newBlossomColor = new THREE.Color(generateRandomColor());
newBlossomMaterial.color = newBlossomColor;
            
newBlossomMesh.material = newBlossomMaterial;

newFlower.add(newBlossomMesh);
newFlower.add(newStemMesh);

scene.add(newFlower);

So the above works for SOLID colors.

Here’s what I tried to get gradient colors going:

// THE BLOSSOM:
// 1. CLONE the MESH of the imported 3D model:
let newBlossomMesh = blossomMesh.clone();

// 2. This time use a BufferGeometry to CLONE the GEOMETRY:
var newBlossomGeometry = new THREE.BufferGeometry();
newBlossomGeometry = blossomMesh.geometry.clone();

var blossomVertexPositionsArray = newBlossomGeometry.attributes.position;
newBlossomGeometry = newBlossomGeometry.toNonIndexed(); 
blossomVertexPositionsArray = newBlossomGeometry.attributes.position;
            
// Make a Colors Array:
var blossomColorsArray = [];
const newBlossomColor = new THREE.Color();
for(var i = 0, l = blossomVertexPositionsArray.count; i < l; i ++) {
 newBlossomColor.setHSL(Math.random() * 0.2 + 0.05, 0.95, 0.799);
 blossomColorsArray.push(newBlossomColor.r, newBlossomColor.g, newBlossomColor.b);
}

// Now “splash” the "blossomColorsArray" all over the Blossom:
newBlossomGeometry.setAttribute("color", new     THREE.Float32BufferAttribute(blossomColorsArray, 3));

newBlossomMesh.material = newBlossomMaterial;

newFlower.add(newBlossomMesh);
newFlower.add(newStemMesh);

scene.add(newFlower);

What I get from this is black colored blossom. It basically looks like it’s just not getting any color so the black I’m seeing is more like the ABSENCE of color, not the actual color “black.”

================================================

UPDATE:

OK so it’s working - but only "singularly", and only “locally.”

Here’s what I mean: I want to make not just one flower object, but 100 of them. And I didn’t want to do that in the callback function of GLTFLoader cause there’s all sorts of other logic I need to apply to my 100 flower objects (like give them (x, y, z) coordinates, give them userData values, add them to arrays, etc. So for that reason, I created a separate function to take care of all that work, and my strategy was to:

  1. Load the Flower.glb model using GLTFLoader like we’ve been doing, and once it loads successfully …
  2. Call my other function which creates 500 new instances of this Flower in a loop, each with it’s own unique blossom gradient color, x, y, z, coordinates, etc.

Your code works great, but I'm trying to make it work on multiple Blossom objects, and to do it outside of the callback of the GLTFLoader function. And it’s this latter part that’s driving me nuts.

Here’s my code:
(current results with it are that I keep getting the exact same gray color for each one of my 500 Blossoms - and no gradients; they’re just not working.)

var blossomMesh;
var stemMesh;

function load3DFlowerModel() {
   loader.load("./Flower.glb", function(theFlower) {
      flowerScene = theFlower.scene;

      // Assign values to my global "blossomMesh" and "stemMesh" variables:
      blossomMesh = flowerScene.children[0]; // Blossom 
      stemMesh = flowerScene.children[1];  // Stem

      // Now call my external function:
      makeFlowers();
   });
}


function makeFlowers() {
    for(var flowerCount = 0; flowerCount < TOTAL_FLOWERS; flowerCount ++) {
                
       var newFlowerGroup = new THREE.Group();

       // I. THE BLOSSOM:
       // 1. First, make a new copy of the incoming global "blossomMesh" object:
       var newBlossomMesh = blossomMesh.clone();
                
       // 2. Next, make a new copy of the MATERIAL of the incoming global "blossomMesh" object:
       var newBlossomMaterial = blossomMesh.material.clone(); 

       // 3. Now make a new copy of the GEOMETRY of the incoming global "blossomMesh" object:
       var newBlossomGeometry = blossomMesh.geometry.clone();

       // 4. Get its vertices:
       let blossomVertexPositionsArray = newBlossomGeometry.attributes.position;
       // 5. Make Colors for it:
       var blossomColorsArray = [];
       var newBlossomColor = new THREE.Color();
       for(var i = 0; i < blossomVertexPositionsArray.count; i ++) {
           newBlossomColor.setHSL(Math.random() * 0.2 + 0.05, 0.95, 0.799);
           blossomColorsArray.push(newBlossomColor.r, newBlossomColor.g, newBlossomColor.b);
                }
       // 6. Finally SPLASH the "blossomColorsArray" all over the Blossom's Geometry:
       newBlossomGeometry.setAttribute("color", new THREE.Float32BufferAttribute(blossomColorsArray, 3));   
       // And set "vertexColors" to true:               
       newBlossomMaterial.vertexColors = true;

       // II. THE STEM
       let newStemMesh = stemMesh.clone();
       newStemMesh.castShadow = true;


       newTulipGroup.add(newStemMesh);
       newTulipGroup.add(newBlossomMesh);
       newTulipGroup.name = "Tulip#" + tulipCount;

       // etc.

So I'm getting a whole bunch of flowers, but they all have the same GRAY blossom. It's always GRAY...


Solution

  • Works as expected. Add lights to your scene, set vertexColors: true for a material.

    body{
      overflow: hidden;
      margin: 0;
    }
    <script type="module">
      import * as THREE from "https://threejs.org/build/three.module.js";
      import {OrbitControls} from "https://threejs.org/examples/jsm/controls/OrbitControls.js";
      import {GLTFLoader} from "https://threejs.org/examples/jsm/loaders/GLTFLoader.js";
      
      let scene = new THREE.Scene();
      let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 10);
      camera.position.set(0, 5, 5);
      let renderer = new THREE.WebGLRenderer();
      renderer.setSize(innerWidth, innerHeight);
      document.body.appendChild(renderer.domElement);
      
      let controls = new OrbitControls(camera, renderer.domElement);
      
      let light = new THREE.DirectionalLight(0xffffff, 1);
      light.position.setScalar(10);
      scene.add(light);
      scene.add(new THREE.AmbientLight(0xffffff, 0.5));
      
      scene.add(new THREE.GridHelper());
      
      let loader = new GLTFLoader();
      loader.load("https://threejs.org/examples/models/gltf/Flower/Flower.glb", gltf => {
        let model = gltf.scene;
        model.scale.setScalar(10);
        //console.log(model);
        let g = model.children[0].geometry;
        let m = model.children[0].material;
        let p = g.attributes.position;
        let c = new THREE.Color();
        let colors = [];
        for(let i = 0; i < p.count; i++){
          c.set(Math.random() < 0.5 ? 0xff00ff : 0xffff00);
          colors.push(c.r, c.g, c.b);
        }
        g.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3));
        m.vertexColors = true;
        scene.add(model);
      });
      
      renderer.setAnimationLoop( _ => {
        renderer.render(scene, camera);
      });
    </script>