Arrays of materials, and material index values in threejs
When working with a Mesh Object in threejs a single instance of a material can be passed to the mesh constructor as the second argument, after the geometry. This is fine if I am okay with every face in the geometry being skinned with the same material, otherwise I might want to do something else. Often just the use of one material is fine as the state of the uv attribute of the buffered geometry instance is in a state in which it will work well with the textures that I am using with one or more of the material map options. However another option might be to not have just one material, but an array of materials and then have a way to set what the material index value is for each face in the geometry. Also an array of materials might need to be used in conjugation with uv mapping as well as I might want some surfaces to be effected by one material, while using another for the rest.
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 then there is how to go about setting material index values with an instance of the Buffered Geometry constructor that is still part of the core of the three.js library in late versions of threejs. In this post then I will be touching base on this topic of working with an array of materials in a threejs project then, rather than alternatives to doing so such as uv mapping, or having groups of objects which would be yet another option for this sort of thing.
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 of group objects of a buffer geometry instance.
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 in 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 how to set what material is for what they way to go about doing it is to figure out a thing or two about groups in buffer geometry, or the face3 class if you are still using an old version of threejs that is before r125.
When it comes to using one of the built in geometry constructors such as the BoxGeometry constructor 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.
Using an array of materials is not a replacement for UV Mapping
Using an array of materials is just one tool in the toolbox when it comes to having control over materials that are used for a geometry in a mesh object. The first and foremost thing that should be considered though is what is going on with UV mapping and how that can be used to skin the geometry as desired. The UV attribute of the buffer geometry instance is an array of offset values that correspond to areas in a texture that are used to skin that specific area of the geometry. Getting into understanding this subject in depth can prove to be a little involved but it is how to go about making a geometry look they way I want it to with just one material.
Even when I do use UV mapping there might be a situation in which I might want to use one material for one area of a geometry and another for the rest. For example say I want to use the Lambert material for an area of a geometry that should be wood, and the rest of the geometry I want to use the Phong material for metal surfaces. In such a case uv mapping and arrays of materials with group objects all go hand in hand to get the desired end outcome.
The source code examples in this post are on Github
The source code examples that I am writing about in this post as well as for my many other posts on threejs can be found in my test threejs repository on Github. This is also where I park the source code examples for my many other blog posts on threejs as well.
Version Numbers matter big time with three.js
Threejs 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 I used r146 last time I edited this post. Between these two versions of threejs a whole lot of code breaking changes have happened, and this will likely continue to be the case moving forward. Always be mindful of the revision number of threejs that you are using when reading about threejs examples on the open web, much of the content is out dated.
1 - Basic static example of an array of materials with a Box Geometry ( r125+ )
A great way to start out with arrays of materials is to create a quick demo that makes use of the Box geometry constructor as this will have a groups array set up for it all ready. The groups array is a way to define how separate WebGL draw calls should be done by setting a start vertex, count index, and material index value for each call for non-indexed geometry. In some cases this can also be a triangle index as well for indexed geometry. What is great about starting out with the box geometry constructor though is that this is all worked otu before hand where each face will be a single call with material index values in the range of 0 to 5.
Simply put I can just create a mesh with an array of six materials, one for each side of the box, when using the box geometry constructor. So a simple hello world type example of arrays of materials in threejs might look something like this.
|
|
So far so good, but what if I want to change what the material index values are for each face? With that said lets look at a few more examples of arrays of materials with mesh objects.
2 - Working with the groups array
Starting out with arrays of materials is easy enough when using something like THREE.BoxGeometry as that is a situation in which the groups array is set up to begin with. However there is the question of how to go about doing the same thing but with only two materials and then set what the index values are for each side of the box using just those two materials rather than six. There is then maybe adjusting the draw call range for one or more materials, and then there is how to go about adding groups to a geometry that does not have one to begin with. So with that said in this section I will be writing a thing or two about how to work with the groups property of geometry.
2.1 - New example with groups array using ( r125+ )
In this section I will be going over the source code of an example that shows how to mutate material index values of a built in geometry using by looping over the groups array and setting the desired material index values. Once again this is an example where I am using a geometry created with the built in box geometry constructor that has the groups array set up for me all ready. The main difference here is that I am using less than six materials so I need to loop over the groups array and set material index values that are in the range of the length of the array of materials given.
So once again I create an array of materials, this time I am going with the phong material for each and just changing up the color. Sense I am using a material that will work with a light source I am also adding a directional light so that I will see something as i am just using the color property of the phong material. Anyway this time around although I have an array of materials this time I want to just work with two materials, which is less than six, so I will want to adjust the material index values in the group array.
With that said when I create the box geometry I just loop over the groups array and set all of the material index values to values that are in the range of the length of this array of materials.
|
|
2.2 - Create groups of a built in geometry that does not have one ( r125+ )
The box geometry is a great starting point for this sort of thing as there is a groups array is all ready set up for me. Often I might just need to adjust the material index values, but if I just use six materials for each face then I do not even need to do that. This is however not always the case with many of the other built in geometry constructors though such as the plane geometry. However maybe the plane geometry is a good built in geometry constructor to start with when it comes to learning a thing or two about how to go about adding groups to a geometry.
For this example I want to create a plane geometry that is 5 by 5 in terms of unit size, but just 2 by 2 when it comes to the grid size of the plane. I want to then use to materials to skin this plane but when I do so nothing happens, and the reason why is because the groups are not set up for me out of the box. This is not to big of a deal though as all I need to do is just make a few calls of the add group method of the buffer geometry class.
|
|
When calling the app group method the first argument is a start vertex index, followed by a count of vertices to use in the call. The final argument when calling add group is then the material index in the array of materials that I would like to use for the group. The end result of these few calls is then a group array in the buffer geometry of this plane set up to use two materials in a checkered kind of way.
3.1 - Art example one
This is the example that I have together for the first video that I have made for this blog post. For this example I am have a collection of materials for a cube in which I am making a data texture for each face of the cube. On top of that I also worked out a crude yet effective system for making images with materials in a plane geometry also.
|
|
3.2 - Material options loop example ( r125+ )
I wanted to make a new video for this post as the first one that I made I think is to complex, and it does not do the best job of showing what the deal is with material index values in the process. So for this animation loop example I just made a single cube that rotates around and I have a collection of six materials for the cube. In the first loop example I also did the same, however I used the Phong material for each of the side where with this example I am using all kinds of differing material options. Also I am doing things a little differently when it comes to the options objects that I am making for each of the materials as well. There is having a single common object where I set common options for all materials, and then there is having another option object on top of that which can contain options that might only work for a single type of material such as shininess in the phong material.
One cool note worthy thing about this that has to do with the base matreial class is the depth funciton that I am using here for the common options object. When it comes to the common object I am making all matreials transparent, and setting the opacity to 0.5. However when doing so I run into problems when it comes to faces behind another face not rendering. One way to adress this is to take a look at the depth function option of the base matreial class. There are a few options when it comes to the value to set for this base material class option and have found that the AlwaysDepth constant gives the end result I was looking for with this.
|
|
4 - Old Basic Example of an array of materials, and face material index values using ( r91 ).
If I am using a really old revision of threejs then I might want to be using the faces array. 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.
|
|
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.
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. Even 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.
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.
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.