jcvts
jcvts

Reputation: 26

Three.js shapes with more than one color

I'm quite new to Three.js and to webGL programming. I have a cone and a cylinder in my scene created according to the script below:

var scene;
var camera;
var renderer;

var createACone = function() {
    const geometry = new THREE.ConeGeometry( 3.5, 0.5, 100, 100, true, 0, 2*Math.PI);
    const material = new THREE.MeshBasicMaterial( {color: 'blue', opacity: 0.8, transparent: true} );
    const cone = new THREE.Mesh( geometry, material );
    cone.rotation.z +=3.1416;
    cone.position.x = 0.5;
    cone.position.y = 0.25;
    cone.position.z = 0;
    cone.castShadow = true;
    scene.add( cone );
}

var createACylinder = function(radiusTop, radiusBottom, height, x, y, z, opacity=0.5, z_rotate = 0, transparent=true, thetaStart=0, thetaLength=2*Math.PI) {
     const geometry = new THREE.CylinderGeometry(radiusTop, radiusBottom, height, 100, 100, true, thetaStart, thetaLength);
    const material = new THREE.MeshBasicMaterial( {color: 'blue', opacity: opacity, transparent: transparent} );
    const cylinder = new THREE.Mesh( geometry, material );
    cylinder.position.x = x;
    cylinder.position.y = y;
    cylinder.position.z = z;
    cylinder.rotation.z = z_rotate;
    scene.add( cylinder );
}

var createLight = function () {
    var spotLight1 = new THREE.DirectionalLight(0xffffff);
    var spotLight2 = new THREE.DirectionalLight(0xffffff, 0.3);
    var spotLight3 = new THREE.DirectionalLight(0xffffff, 0.3);
    var spotLight4 = new THREE.DirectionalLight(0xffffff, 0.3);
    spotLight1.position.set(20, 20, 50);
    spotLight2.position.set(0, 1, 0);
    spotLight3.position.set(-50, 20, 2);
    spotLight4.position.set(50, 20, 2);
    spotLight1.castShadow = true;
    spotLight2.castShadow = false;
    scene.add(spotLight1);
    scene.add(spotLight2);
    scene.add(spotLight3);
    scene.add(spotLight4);
};

var init = function(){
    scene = new THREE.Scene();
    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 2000 );
    renderer = new THREE.WebGLRenderer({antialias: true});
    renderer.setSize( window.innerWidth, window.innerHeight );
    document.body.appendChild( renderer.domElement );

    camera.position.z = 6;
    camera.position.y = 0.5;
    camera.position.x= 0.5;
    this.createLight();
    this.createACone();
    this.createACylinder(3.5, 3.5, 1, 0.5, 1, 0, 0.8, 0, true,0, 2*Math.PI);

    this.render();
}

window.onload = this.init;

Now, I want the cone and the cylinder to be divided horizontally by two colors, according to their height (for instance, from lower base to half the cylinder's height I want it to be red, and the rest of it to be blue; for the cone vertex to half its height I want it red, the rest of the cone should be blue). I looked for a solution in the material's and the geometry's sources but couldn't find anything that would help me achieve this. I thought about dividing each geometry in two, but that is cumbersome since i want to dynamically change this color size ratio. Is there a better way to achieve this in Three.js?

Thanks in advance for your help.

Upvotes: 1

Views: 901

Answers (1)

prisoner849
prisoner849

Reputation: 17586

Just an option of how you can have two colors, using UV coordinates in ShaderMaterial:

body {
  overflow: hidden;
  margin: 0;
}
<script type="module">
console.clear();
import * as THREE from "https://threejs.org/build/three.module.js";
import {OrbitControls} from "https://threejs.org/examples/jsm/controls/OrbitControls.js";
import {BufferGeometryUtils} from "https://threejs.org/examples/jsm/utils/BufferGeometryUtils.js";
import {GUI} from "https://threejs.org/examples/jsm/libs/dat.gui.module.js";

let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 100);
camera.position.set(0, -5, 8);
let renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);

let controls = new OrbitControls(camera, renderer.domElement);

let gCyl = new THREE.CylinderGeometry(5, 5, 1, 32, 1, true);
let gCon = new THREE.ConeGeometry(5, 1, 32, 1, true);
for(let i = 0; i < gCon.attributes.uv.count; i++){
  gCon.attributes.uv.setY(i, 1 - gCon.attributes.uv.getY(i));
}
gCon.rotateX(Math.PI);
gCon.translate(0, -1, 0);
let g = BufferGeometryUtils.mergeBufferGeometries([gCyl, gCon]);

let m = new THREE.ShaderMaterial({
  uniforms: {
    color1: {value: new THREE.Color("red")},
    color2: {value: new THREE.Color("blue")},
    colorRatio: {value: 0.5}
  },
  vertexShader:`
    varying vec2 vUv;
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
    }
`,
  fragmentShader: `
    uniform vec3 color1;
    uniform vec3 color2;
    uniform float colorRatio;
    varying vec2 vUv;
    void main() {
      vec3 col = mix(color1, color2, step(colorRatio, vUv.y)); 
      gl_FragColor = vec4( col, 1.0);
    }
  `
});

let o = new THREE.Mesh(g, m);
scene.add(o);

let gui = new GUI();
gui.add(m.uniforms.colorRatio, "value", 0., 1.).name("colorRatio");

renderer.setAnimationLoop( _ => {
  renderer.render(scene, camera);
});
</script>

Upvotes: 1

Related Questions