Using an array of materials, and setting material index values in three.js

When working with a mesh in three.js a single instance of some kind of mesh material can be passed to the mesh constructor as the second argument which will be used to skin the the whole geometry of the mesh. This is fine if you are okay with every face in the geometry being skinned with the same material, otherwise you might want to pass an array of materials instead.

When working with an array of materials there is a property of a face3 instance in the geometry of the mesh that is of interest when setting the material index property of the faces, or at least that was the case with the old Geometry Constructor that was removed in r125 of threejs. So there is how to go about doing this sort of thing with a Buffered Geometry that is still part of the core f the three.js library.

In this post I will be covering some basic demos of how to work with more than one material, and how to go about setting the material index values of a geometry.

1 - What to know before getting into Mesh Material index values

It should go without saying that this is not a getting started post with three.js, and I also will not be getting into the basics of javaScript and any additional topics that you should have a solid grasp on before hand. Still in this section I will be going over some of the things that you show know before getting into mesh material index values. If you thing that you know what you need to know with that you can of course skip over this section.

1.1 - Version Numbers matter big time with three.js

Three.js has been, and as of this writing still is, a fast moving target of a library when it comes to development. When I first wrote this post back in May of 2018 I was using r91 of threejs, and at this time there is now an r127 which is what I am observing at the time that I have edited this post last.

1.2 - It might be good to look over the Buffer Geometry class in detail if you can get around to it

This is a post on the mesh class object and three.js and how to use an array of materials rather than just one with a geometry. That is when using more than one material with a mesh there is creating an instance of a mesh, passing a geometry as the first argument, and an array of materials as the second argument. When it comes to having control over what material index value is for what that is a matter of what it is that is going on with the instance of buffer geometry rather than the array of materials or the mesh object as a whole.

When it comes to using the built in geometry constructors to create an instance of buffer geometry groups and material index values for them are set up for you and it is just a matter of looping over the groups of the geometry and changing the index values to the desired value if needed. However when it comes to making a custom geometry, as well as some of the built in geometries adding groups is something that needs to be done manually. In any case it makes sense to look into the buffer geometry in greater detail beyond the scope of just this post alone.

2 - New example with groups array using r127

So the first example that I should cover here is an example that makes use of a new version of threejs, as of this writing I was using r127. In any case the process is somewhat similar an array of materials must be used rather than just a single material. After that it is just a matter of making sure that each face has the proper index value in the materials array set.

In late versions of threejs that only support the use of Buffered Geometry in the core of the library itself there should be a groups array for a geometry. This is now the array of face objects that contain material index properties. When it comes to this example I am using the built in Box Geometry constructor that will create and return a buffered geometry, and on top of that there will be index values set for it by default that will work well with an array of materials that is a collection of six materials one for each side. However what if I just want to use three? In that case I will want to do something to make sure that I am only using index values in that range, such as looping over the groups object and setting the material index values that way.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
(function () {
// REVISION 127 was used for this example
console.log(THREE.REVISION);
// ARRAY OF MATERIALS
var materials = [
new THREE.MeshBasicMaterial({
color: 0xff0000
}),
new THREE.MeshBasicMaterial({
color: 0x00ff00
}),
new THREE.MeshBasicMaterial({
color: 0x0000ff
})
];
// geometry
var geometry = new THREE.BoxGeometry(1, 1, 1);
// SET THE INDEX VALUES FOR EACH FACE
geometry.groups.forEach(function (face, i) {
face.materialIndex = Math.floor(i % materials.length);
});
// mesh
var mesh = new THREE.Mesh(
// geometry as first argument
geometry,
// array of materials as the second argument
materials);
// scene, add mesh
var scene = new THREE.Scene();
scene.add(mesh);
// camera, renderer
var camera = new THREE.PerspectiveCamera(50, 4 / 3, .5, 1000);
camera.position.set(2, 2, 2);
camera.lookAt(0, 0, 0);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(640, 480);
document.getElementById('demo').appendChild(renderer.domElement);
renderer.render(scene, camera);
}
());

The situation will change up a little from one situation to the next, but the basic idea is there.

3 - Old Basic Example of an array of materials, and face material index values using r91.

A basic example of this would be to just have an array of instances of some kind of Mesh Material such as the Mesh Basic Material. Once I have an array the materials can be used by setting the material index value of all face3 instances in the geometry that I am using to point to the corresponding index of the material in the array of materials that I want to use with a given face.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
(function () {
// REVISION 91 was used for this example
console.log(THREE.REVISION);
// an array of materials
var materials = [
new THREE.MeshBasicMaterial({
color: 0xff0000
}),
new THREE.MeshBasicMaterial({
color: 0x00ff00
}),
new THREE.MeshBasicMaterial({
color: 0x0000ff
})
];
// create the mesh
var geometry = new THREE.BoxGeometry(1, 1, 1);
// set material index values
var len = materials.length;
geometry.faces.forEach(function (face3, i) {
face3.materialIndex = Math.floor(i % (len * 2) / 2);
});
// create the mesh
var mesh = new THREE.Mesh(
geometry,
materials);
// SCENE
var scene = new THREE.Scene();
// add mesh to scene
scene.add(mesh);
// some helpers
scene.add(new THREE.FaceNormalsHelper(mesh, 2, 0x00ff00, 1));
scene.add(new THREE.VertexNormalsHelper(mesh, 2, 0xff0000, 1));
// camera, orbit controls, renderer
var camera = new THREE.PerspectiveCamera(50, 4 / 3, .5, 1000);
camera.position.set(2, 2, 2);
camera.lookAt(0, 0, 0);
var controls = new THREE.OrbitControls(camera);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(640, 480);
document.getElementById('demo').appendChild(renderer.domElement);
// loop
var loop = function () {
requestAnimationFrame(loop);
renderer.render(scene, camera);
};
loop();
}
());

Using modulo to get the remainder when diving the current face index over the length of materials will result in an effect where each material is used in an alternating fashion. I can write different expressions to get different effects, but you should get the basic idea. The process is to have a collection of materials, and then do what is necessary to see that each face is painted with the desired material.

4 - Conclusion

When starting to make a real project of one kind or another it is important to know how to go about doing this sort of thing of course. Event when it comes to developing some kind of crude yet effective kind of style for 3d modeling I am still going to want to know how to skin different faces with different materials.

4.1 - More Examples of Material index values

On my post on the sphere geometry constructor I worked out an example that has to do with creating groups for a sphere geometry. However maybe the best additional post on this topic thus far would be my post on the plane geometry constructor where I worked out a few more examples of this sort of thing.

4.2 - Additional THREEJS reading

To really get a solid grasp on working with material index values, as well as the materials themselves, and everything that branches off from that it would be best to just start making one or two actual projects of some kind and learn as you go. At least I have found that is the best way to go about things speaking from my experience thus far. With that said it might be a good idea to check out some of my threejs project examples thus far, one that stands out when it comes to material index values would be my guy on a hamster wheel example where I am making use of material index values, and canvas generated textures for those materials.