Create the artwork on cube surfaces as a complete unit, rather than focusing on the individual triangles that form the face - three.js

While trying to paint each face of a cube with a different color, I came across a helpful thread that outlines a method to achieve this:

var geometry = new THREE.BoxGeometry(5, 5, 5);
for (var i = 0; i < geometry.faces.length; i++) {
    geometry.faces[i].color.setHex(Math.random() * 0xffffff);
}

var material = new THREE.MeshBasicMaterial({
    color: 0xffffff,
    vertexColors: THREE.FaceColors
});

However, when using three.js r86, the outcome is as shown below:

https://i.sstatic.net/YvONr.png

The individual triangles that make up each face are painted separately.

To achieve the desired effect, I made a slight modification to the code as follows:

var geometry = new THREE.BoxGeometry(5, 5, 5);
for ( var i = 0; i < geometry.faces.length; i += 2 ) {
    var faceColor = Math.random() * 0xffffff;
    geometry.faces[i].color.setHex(faceColor);
    geometry.faces[i+1].color.setHex(faceColor);
}

var material = new THREE.MeshBasicMaterial({
    color: 0xffffff,
    vertexColors: THREE.FaceColors
});

https://i.sstatic.net/5wPJc.png

However, this solution seems somewhat convoluted!

'use strict';

var camera, scene, renderer, cube;

init();
render();

function init() {

  scene = new THREE.Scene();
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

  // renderer

  renderer = new THREE.WebGLRenderer({
    alpha: true
  });
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);

  camera.position.z = 12;

  // Mesh - cube

  var geometry = new THREE.BoxGeometry(5, 5, 5);
  for (var i = 0; i < geometry.faces.length; i += 2) {
    var faceColor = Math.random() * 0xffffff;
    geometry.faces[i].color.setHex(faceColor);
    geometry.faces[i + 1].color.setHex(faceColor);
  }

  var material = new THREE.MeshBasicMaterial({
    color: 0xffffff,
    vertexColors: THREE.FaceColors
  });

  cube = new THREE.Mesh(geometry, material);
  scene.add(cube);

  // Light

  var pointLight = new THREE.PointLight(0xFFFFFF);

  pointLight.position.x = 10;
  pointLight.position.y = 50;
  pointLight.position.z = 130;

  scene.add(pointLight);

}

function render() {

  cube.rotation.x = 16;
  cube.rotation.y = 4;
  cube.rotation.z -= 5;

  renderer.render(scene, camera);

}
body,
canvas {
  margin: 0;
  padding: 0;
}

body {
  overflow: hidden;
  background-color: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/86/three.js"></script>

Is there a simpler way in three.js to achieve painting the faces of a cube as a whole?

Answer №1

Utilizing the BufferGeometry enables you to exercise control over the material of specific sections within your geometry by incorporating groups. These groups are defined by the vertex indices and permit you to specify a material index that references a material within an array of materials.

For instance:

// start, count, material index
bufferGeometry.addGroup(12, 6, 2)

This instruction directs the geometry to initiate a new group of triangles at the index 12, encompassing 6 indices (related to 6 vertices). The final parameter designates the group of triangles to apply material index 2 (located at index 2 in the material array used to construct the mesh).

In the illustration below, distinct colors have been assigned to every side of a cube. While this may seem akin to setting face colors, it's crucial to recognize that this process assigns a material per group, rather than just a color, thereby allowing for the creation of compelling effects.

// code snippet

Edit: Introducing a secondary example utilizing the core BoxBufferGeometry

Per pailhead's input on the original post, here's a snippet that leverages the unaltered BoxBufferGeometry. However, as pointed out in their comment, an understanding of which group corresponds to which face is still imperative.

// code snippet

Answer №2

When working with groups, the geometry is divided into 6 faces. To create a basic cube, a simple custom ShaderMaterial can be used.

Dividing the geometry into 6 groups results in more draw calls. Instead of utilizing 1 draw call to draw a cube, 6 draw calls are necessary, one for each face.

With a ShaderMaterial, only 1 draw call is required:

Vertex Shader:

attribute vec3 vertexColor;
varying vec3 vColor;

void main() {
  vColor = vertexColor;
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.);
}

Fragment Shader:

varying vec3 vColor;

void main() {
  gl_FragColor = vec4(vColor, 1.);
}

This approach also allows for utilizing GLSL color blending to merge different colors.

A custom ShaderMaterial can be created by simply setting the vertex and fragment shader source strings:

const ColorCubeShader = function () {
  THREE.ShaderMaterial.call(this, {
    vertexShader: vertexShaderSrc,
    fragmentShader: fragmentShaderSrc
  })
}

ColorCubeShader.prototype = Object.create(THREE.ShaderMaterial.prototype)
ColorCubeShader.prototype.constructor = ColorCubeShader

Customize a Color Cube Mesh:

/**
 * Function for coloring a face
 * @param {Number} r 
 * @param {Number} g 
 * @param {Number} b 
 * @returns {Array}
 */
const buildVertexColorArrayFace = function (r, g, b) {
  return [
    r, g, b,
    r, g, b,
    r, g, b,
    r, g, b
  ]
}

const ColorCube = function (size) {
  const geometry = new THREE.BoxBufferGeometry(size, size, size)

  // create color array
  let colorArray = []
  colorArray = colorArray
  .concat(buildVertexColorArrayFace(1, 0, 0))
  .concat(buildVertexColorArrayFace(0, 1, 0))
  .concat(buildVertexColorArrayFace(0, 0, 1))
  .concat(buildVertexColorArrayFace(1, 0, 1))
  .concat(buildVertexColorArrayFace(1, 1, 0))
  .concat(buildVertexColorArrayFace(0, 1, 1))

  // create buffer attribute for colors (for attribute vec3 vertexColor)
  const colorAttribute = new THREE.Float32BufferAttribute(
    new Float32Array(colorArray), 3)

  // set attribute vertexColor in vertex shader
  geometry.setAttribute('vertexColor', colorAttribute)

  // instantiate custom Shader Material
  const material = new ColorCubeShader()

  THREE.Mesh.call(this, geometry, material)
}

ColorCube.prototype = Object.create(THREE.Mesh.prototype)
ColorCube.prototype.constructor = ColorCube

To use it:

const cube = new ColorCube(1)
cube.position.set(0, 2, -2)
scene.add(cube)

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Creating dynamic Ionic slides by fetching data from a database

I am currently experimenting with Ionic. Web development is not my strong suit, so I may be a bit off the mark. However, I would like to retrieve data from an SQLite database and display it on an ion-slide-box. Here is what I have attempted: function sel ...

"Learn how to capture the complete URL and seamlessly transfer it to another JavaScript page using Node.js and Express.js

Is there a way to access the token variable in another page (api.js)? I need to use it in my index.js. var express = require('express'); var router = express.Router(); router.get('/', function(req, res ...

Prevent accordion expansion triggered by the More button click

When the More button is clicked, it should NOT expand and collapse. https://i.sstatic.net/toV1b.gif If the accordion initial state is open, it must stay open even if button is clicked, if initial state is collapsed then after the more button the accordio ...

A simulated movement normal map without changing the camera position or adjusting the light source in Three.js

Is there a way to shift the normal map in Three.js without adjusting the camera or light source? I have a plane positioned perpendicular to the camera's view vector. This plane always remains at the center of the view. Whenever I move the camera, the ...

What is preventing the specific value in React state from being updated?

Starting off as a beginner, but I'm giving it a shot: In my React project, users input landing pages and I aim to extract data from these pages using JQuery and RegEx, then update the state with the extracted value. The issue I'm facing is that ...

Firestore version 9 - Retrieve nested collection depending on a string being present in an array

Working on a new chat application here. It has been a while since I last used Firestore. I am currently using v9 and facing an issue with retrieving the nested "messages" collection when the "users" array in the document contains a specific ID. I have man ...

A warning has been issued: CommonsChunkPlugin will now only accept one argument

I am currently working on building my Angular application using webpack. To help me with this process, I found a useful link here. In order to configure webpack, I created a webpack.config.js file at the package.json level and added the line "bundle": "web ...

Switch Button Hyperlink in HTML/CSS

I have the following code: document.addEventListener("DOMContentLoaded", function(event) { (function() { requestAnimationFrame(function() { var banner; banner = document.querySelector('.exponea-banner3'); banner.classList ...

Utilizing a TypeScript function to trigger an action from within a Chart.js option callback

Currently, I am utilizing a wrapper for Chart.js that enables an animation callback to signify when the chart has finished drawing. The chart options in my code are set up like this: public chartOptions: any = { animation: { duration: 2000, ...

Utilizing MongoDB and Express to access collections within a controller

Is there a way to access the collection in the controller using mongodb and express? I came across this code snippet in the mongodb documentation db.getCollection("countries");, but how do you import the database name: db into a controller? serv ...

How can we efficiently trigger a function that sends an axios request by leveraging data from a v-for loop?

Currently, I am developing an e-commerce platform using Rails and VueJS. In order to display the orders of a specific user, I have created a component file. Within this component, I am utilizing a v-for loop to iterate over and showcase all the information ...

Is conditional CSS possible with NextJS?

While working on an animated dropdown for a navbar, I came across this interesting dilemma. In a strict React setup, you can use an inline if/else statement with onClick toggle to manage CSS animation styles. To ensure default styling (no animation) when ...

Invoke a Google Apps Script through AJAX that necessitates authentication

I am looking to access a Google Apps Script via AJAX that requires user authorization to send an email from their Gmail account. The challenge lies in the fact that I need to send the email from within the Google Apps Script. The call originates from my w ...

I just made an ajax call. Now, how should I proceed with formatting the data that was returned

The ajax request below is functional, but not without its challenges. Working with HttpContext has proven to be difficult, as even Context.Response.Clear() does not seem to have any effect. How can I output only the desired content without any extra infor ...

Background image specifically for the Fullcalendar sun component

When setting a background image for Sunday in the month view, I noticed that the day "Sunday" in the header is also getting the same background. The CSS for the table thread has classes like .fc-sun and .fc-mon set as well, but I'm not sure how to rem ...

Mastering angle calculations in three.js

My current task involves creating a series of arcs using 102 lines (start point, endpoint, and 100 curve points) However, I'm facing an issue when the start point of an arc has a higher value than the end point. For instance: Start Point: 359 End ...

Obtaining an image from a URL, saving it, and then animating it? That definitely

Looking to create a dynamic animation with images that update every 15 minutes from the following URL: How should I go about storing and looping through the most recent 24 images for the animation? Is using a MySQL database the best option or are there o ...

Eliminate the need to input the complete URL every time when making server calls

Currently, my springbok application has a React-Typescript frontend that is functioning well. I am using the request-promise library to make requests like this: get('http://localhost:8080/api/items/allItems', {json: true}). However, I would like ...

Encountering an issue with JQuery when attempting to create a double dropdown SelectList. Upon submitting the POST request, the output received is always

Two dropdownlists have been created, where one acts as a filter for the other. When selecting a customer from the dropdown Customer, only a limited set of ClientUsers is displayed in the dropdown ClientUser. This functionality is achieved using a jQuery fu ...

Is browserHistory.push ineffective in react.js?

I am currently working on a React ecommerce website. I have encountered an issue in the login component where, upon clicking the submit button on the form, the system is supposed to check if the user is authenticated using useEffect. If the user is authent ...