A Comprehensive Guide to Materials in Three.js
If you're newer to Three.js, choosing the right material can be downright miserable. You may have some of the following questions:
- Why doesn't my material have shadows?
- Why can't I see my shape anymore?
- I got the shape back, but why does it look like garbage?
- How do I make it look good for the love of god...
- Why can't I see my shape anymore?
Actually, I know you've had these questions because you're not the only one. This article is meant to demystify the various types of materials you can use in Three.js and how to use them so you're not left with a blank scene due to a misbehaving material.
MeshBasicMaterial
This is the most boring material you can get in Three.js world, but the benefit is, it doesn't need a light for it to show.
A MeshBasicMaterial
will color in your geometry with the exact color you supply it. No need to set up complicated lights or obscure material properties, just create the material, assign it a color, and you're good to go:
const plane = new THREE.Mesh(
new THREE.PlaneGeometry(3, 3, 3),
new THREE.MeshBasicMaterial({ color: 0xff0000 })
)
scene.add(plane)
This is cool and all, but one potential pitfall comes with rotation. If you were to rotate this mesh on the x-axis by 90 degrees, you wouldn't be able to see it. This isn't due to the material, but rather the camera's position being exactly inline with the new rotation of the plane. Not necessarily a material issue, but I wanted to bring it up in case you applied the material and respond with: "The mAtErIal Isn't WoRKiNG!!"
Ahem, anyways, the point is MeshBasicMaterial
will always be visible unless your camera is out of position or directly inline with a flat geometry like a plane.
MeshPhongMaterial
According to Three.js' documentation, a MeshPhongMaterial
is defined as a material for shiny surfaces with specular highlights...
I understand the shiny part, but please, tell me what the hell a specular highlight is?
A specular highlight is the shiny spot of light that shows on an object. Specular describes an object that has mirror-like properties, so when combined with the word "highlight," the specular highlight definition makes sense as in it's a mirrorlike highlight.
If you need a more understandable definition (because you're a simpleton like me), here it is: MeshPhongMaterial is a shiny material that requires light to see.
Easy, right?
Here's how we would write up a MeshPhongMaterial
:
const plane = new THREE.Mesh(
new THREE.PlaneGeometry(3, 3, 3),
new THREE.MeshPhongMaterial({ color: 0xff0000 })
)
scene.add(plane)
// a light is required for MeshPhongMaterial to be seen
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5)
directionalLight.position.z = 3
scene.add(directionalLight)
This is more interesting than MeshBasicMaterial
(in my humble opinion), but I was promised shiny. Let's give this material a shine with the following updates:
const plane = new THREE.Mesh(
new THREE.PlaneGeometry(3, 3, 3),
new THREE.MeshPhongMaterial({ color: 0xff0000, shininess: 200 })
)
scene.add(plane)
// a light is required for MeshPhongMaterial to be seen
const directionalLight = new THREE.DirectionalLight(0xffffff, 1)
directionalLight.position.z = 3
scene.add(directionalLight)
Notice I changed two things here... First, I added a shininess
property and set it to 200
. This determines how shiny your material is going to look when a light is reflected off of it (no shit). The value should be 0
or greater for the material to render correctly (negative values will not work). Values closer to 0
will produce less shine while higher values like 200
will produce ultra-shine.
The second thing I changed was our light's brightness within the second argument of our instantiation:
new THREE.DirectionalLight(0xffffff, 1)
This value ranges from 0
to 1
and determines how bright the instantiated light will shine. The brighter the light, the more shiny your object will look (this isn't a lesson on lights, but I'll cover those next since they're bastards to deal with too).
In all, know that MeshPhongMaterial
will add blended color and shininess to your material, but it requires a light to work.
EASY.
MeshLambertMaterial
A MeshLambertMaterial
is defined as a material for non-shiny surfaces, without specular highlights. Okay, so a non-shiny material, we're done here, right?
I suppose we'll at least see what it looks like...
Let's add the material to our plane with the following code:
const plane = new THREE.Mesh(
new THREE.PlaneGeometry(3, 3, 3),
new THREE.MeshLambertMaterial({ color: 0xff0000 })
)
scene.add(plane)
const directionalLight = new THREE.DirectionalLight(0xffffff, 1)
directionalLight.position.z = 3
scene.add(directionalLight)
This'll output the following result:
Similar to MeshPhongMaterial
, MeshLambertMaterial
requires a light to see. No light means you no see, so make sure you include one.
It's a bit hard to see the direct effect MeshLambertMaterial
has on a simple plane, but it's a little more apparent when swapping out with a geometry like a TorusKnotGeometry
:
const torusKnot = new THREE.Mesh(
new THREE.TorusKnotGeometry(1, 0.4, 100, 16),
new THREE.MeshLambertMaterial({ color: 0xff0000 })
)
scene.add(torusKnot)
const directionalLight = new THREE.DirectionalLight(0xffffff, 1)
directionalLight.position.z = 3
scene.add(directionalLight)
As you can see there are shadows showing how the light reflects off the material, yet there's no real shine anywhere compared to the phong material we examined previously.
MeshLambertMaterial
is material you use to get a matte effect with no shine. Think of it as wrapping your geometry in a matte wrap like true ballers do with their cars:
In all, MeshLambertMaterial
is a terrible name and it should be called MatteMaterial
. Same goes for MeshPhongMaterial
which should be ShinyMaterial
. Think of each material type as one of those better names to better retain the knowledge for future use.
MeshStandardMaterial
MeshStandardMaterial
is apparently the godsend of all materials, the creme de la creme as the kids say (they don't say this).
According to the docs, MeshStandardMaterial
gives a more accurate and realistic looking result than the MeshLambertMaterial or MeshPhongMaterial, at the cost of being somewhat more computationally expensive.
Okay cool, but really, what kind of look does it give off?
Let's refer to the docs once more for a strict definition: MeshStandardMaterial
is a standard physically based material, using Metallic-Roughness workflow.
All I can say is... wtf?
There is no way anyone knows what this actually means. To help do away with ambiguous definitions, let's see what a MeshStandardMaterial
looks like by default with the following code:
const torusKnot = new THREE.Mesh(
new THREE.TorusKnotGeometry(1, 0.4, 100, 16),
new THREE.MeshStandardMaterial({ color: 0xff0000 })
)
scene.add(torusKnot)
const directionalLight = new THREE.DirectionalLight(0xffffff, 1)
directionalLight.position.z = 3
scene.add(directionalLight)
As you can see, compared to a lambert material, there's not much difference, both look matte and don't have much shine applied. Where MeshStandardMaterial
shines (har) is that it has highly editable properties that can make it matte, shiny, or even metallic.
Let's edit our material a bit by adding in a roughness
property:
const torusKnot = new THREE.Mesh(
new THREE.TorusKnotGeometry(1, 0.4, 100, 16),
new THREE.MeshStandardMaterial({
color: 0xff0000,
roughness: 0
})
)
scene.add(torusKnot)
Set to 0
, we went from a pure matte material to one that is as shiny as can be. It's safe to say that a standard material can give you very similar looks to both those provided by MeshPhongMaterial
and MeshLambertMaterial
. The Three.js docs themselves recommend you use it, so it wouldn't hurt to choose the standard material as your first choice when looking for the perfect material to use.
As a quick disclaimer, I'm not experienced enough to see the true benefits of using a standard material over lambert or phong other than ease of customization, however, if I come across them, I'll be back here and update the article accordingly.
MeshToonMaterial
There are like 10 other materials that Three.js uses—I've covered the most widely used, but this one in particular needs a section as well.
Presenting... THE MeshToonMaterial
!
Ok, well, the Windwaker probably wasn't developed in Three.js as cool as that would be, but to get the same toon shading effect, we can use Three.js' MeshToonMaterial
.
To use this, all we need is a color
property on the material and a light shining on our mesh:
const torusKnot = new THREE.Mesh(
new THREE.TorusKnotGeometry(1, 0.4, 100, 16),
new THREE.MeshToonMaterial({
color: 0xff0000
})
)
scene.add(torusKnot)
const directionalLight = new THREE.DirectionalLight(0xffffff, 1)
directionalLight.position.z = 3
scene.add(directionalLight)
With this, we'll get a very similar effect to that used in the Windwaker without much effort.
All I want you to know here is that the toon effect is cool af and it only requires the material and a light to use. With that, we move onwards!
Wrap Up
If you're like me, you're learning about materials to try and make your scene not look like hot garbage. If that's the case, this is a great start, but you're going to need to learn a little more about renderers, shadows, and lighting to get something that looks halfway decent.
As I learn more, I will write articles on exactly those things and hyperlink them here when ready, but for now, if you'd like to learn more about Three.js, I have a pretty nice intro course for beginners which'll teach you how to make an interactive 3D portfolio website. Otherwise, thanks for reading 🙏