Reputation: 26
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
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