An article to understand the materials in Three.js

Yohoho
11 min readJul 6, 2022

foreword

The role of the material, in simple terms, is to add color to each visible pixel of the geometry.
But adding color is not that simple. It depends on factors such as lighting, physical properties of materials, etc., after a series of algorithms, to determine what color these pixels display. This algorithm is written in a program called shader shader. Shader programming is very challenging, and we will learn it in a later course.
Now, let’s learn how to use the preset materials in Three.js.

Preparing our scene

To test the material, we first prepare a basic scene and load some texture images.
Create 3 different geometries (a sphere, a plane and a torus) and use the same MeshBasicMaterial on all 3 geometries.

Give them different x-coordinates to separate them.
The scene’s add(…) method can also add all geometry to the scene at once by passing multiple parameters:

/**
* Objects
*/
const material = new THREE.MeshBasicMaterial()
const sphere = new THREE.Mesh(
new THREE.SphereGeometry(0.5, 16, 16),
material
)
sphere.position.x = - 1.5
const plane = new THREE.Mesh(
new THREE.PlaneGeometry(1, 1),
material
)
const torus = new THREE.Mesh(
new THREE.TorusGeometry(0.3, 0.2, 16, 32),
material
)
torus.position.x = 1.5
scene.add(sphere, plane, torus)

Then, we add some code to keep them spinning:

/**
* Animate
*/
const clock = new THREE.Clock()
const tick = () =>
{
const elapsedTime = clock.getElapsedTime()
// Update objects
sphere.rotation.y = 0.1 * elapsedTime
plane.rotation.y = 0.1 * elapsedTime
torus.rotation.y = 0.1 * elapsedTime
sphere.rotation.x = 0.15 * elapsedTime
plane.rotation.x = 0.15 * elapsedTime
torus.rotation.x = 0.15 * elapsedTime
// ...
}
tick()

Next, we load the various texture images that will be used for the test material.

/**
* Textures
*/
const textureLoader = new THREE.TextureLoader()
const doorColorTexture = textureLoader.load('/textures/door/color.jpg')
const doorAlphaTexture = textureLoader.load('/textures/door/alpha.jpg')
const doorAmbientOcclusionTexture = textureLoader.load('/textures/door/ambientOcclusion.jpg')
const doorHeightTexture = textureLoader.load('/textures/door/height.jpg')
const doorNormalTexture = textureLoader.load('/textures/door/normal.jpg')
const doorMetalnessTexture = textureLoader.load('/textures/door/metalness.jpg')
const doorRoughnessTexture = textureLoader.load('/textures/door/roughness.jpg')
const matcapTexture = textureLoader.load('/textures/matcaps/1.png')
const gradientTexture = textureLoader.load('/textures/gradients/3.jpg')

Create a base material MeshBasicMaterial and assign one of the texture images to the map (color map).

const material = new THREE.MeshBasicMaterial({ map: doorColorTexture })

OK, that’s it for the preparatory work, now let’s learn how to use the preset materials in Three.js.

Basic material MeshBasicMaterial

MeshBasicMaterial, as its name suggests, is indeed the most “basic” material…
We can pass property parameters when instantiating the material, or we can change these properties after creation:

const material = new THREE.MeshBasicMaterial({
map: doorColorTexture
})
// Equals
const material = new THREE.MeshBasicMaterial()
material.map = doorColorTexture

Let’s use the second method, it looks clearer

  • The map colormap attribute can map a texture map on the surface of the geometry
material.map = doorColorTexture
  • The color property will set a uniform color on the surface of the geometry. We can use a variety of common web-side color values, but we must instantiate a THREE.Color object.
material.color = new THREE.Color('#ff0000')
material.color = new THREE.Color('#f00')
material.color = new THREE.Color('red')
material.color = new THREE.Color('rgb(255, 0, 0)')
material.color = new THREE.Color(0xff0000)
When using `color` and `map` together, the texture map will be affected by the color.material.map = doorColorTexture
material.color = new THREE.Color('#ff0000')
  • Setting the wireframe property to true will display a mesh of triangles that make up the geometry, and the mesh lines will be 1 pixel line widths regardless of the distance from the camera.
material.wireframe = true
  • The opacity transparency attribute must be used together with the transparency attribute to set the transparency of the material. The value ranges from 0 to 1, where 0 means complete transparency and 1 means opaque.
material.transparent = true
material.opacity = 0.5
  • In addition to setting the transparency of the material as a whole through the opacity property, we can also use the alphaMap transparency map to make individual parts of the material transparent.
material.transparent = true
material.alphaMap = doorAlphaTexture
  • The geometry in the 3D world is composed of faces, and a face is also divided into front and back. By default, Three.js will only render the front.
  • Note: Not facing the camera is called the front.
  • And by setting the side rendering properties, we can decide whether to render the front or the back THREE.BackSide or both sides to render THREE.DoubleSide
  • Note: When set to DoubleSide, the performance cost is higher because 2 times the number of triangle faces are rendered.
material.side = THREE.DoubleSide

Some of these properties, such as wireframe and opacity, are also available in other types of materials, and will not be repeated later.

Normal Material MeshNormalMaterial

The MeshNormalMaterial can display nice purples, blues, and greens that look like the normal texture maps we saw in the previous section on texture maps. This is no coincidence, as both are related to what we call normals

const material = new THREE.MeshNormalMaterial()

So what exactly is a normal? A normal is a line that is always perpendicular to the plane, and represents the orientation of the face. In a 3D engine, each vertex has normal information.

Since normals represent the orientation of vertices, they can naturally be used to calculate how to reflect or refract light.

When using MeshNormalMaterial, the color will only show the direction of the normal relative to the camera. This means that if we rotate around the sphere, you will see that the color is always the same.

In addition to wireframe, opacity and other basic properties, MeshBasicMaterial can also use a new flatShading plane shading property:

material.flatShading = true

Flat shading means that normals are not interpolated from vertex to vertex. MeshNormalMaterial is usually used to debug and observe normal information, but it looks gorgeous, so it can also be used directly to do some very unique effects.

Material Capture Material MeshMatcapMaterial

The name is a bit confusing, but Matcap is indeed a combination of the words Material and Capture, which means material capture.

It’s a great material that works well and performs very well.

Rendering usually requires the participation of geometry, light sources, materials, and shaders. In matcap, the light source and material information are directly baked into a texture map in the 3D modeling software, which can be used directly during rendering. The amount of calculation is naturally greatly reduced, and the performance is significantly improved. We can also easily switch between different matcap textures, which looks like switching materials.

A reference texture map that looks like a sphere must be used when using the MeshMatcapMaterial material.

The material will extract the color on the texture map based on the normal direction relative to the camera.

Set up the material snap map:

const material = new THREE.MeshMatcapMaterial()
material.matcap = matcapTexture

There are some faces that appear to be illuminated by lights, but don’t actually need any lighting involved in the calculation.

There is one problem though, since the lighting and material information is pre-baked into the texture map, it looks the same no matter how the camera is oriented and how the lights are angled.

Below we can try a few different matcap texture maps

const matcapTexture = textureLoader.load('/textures/matcaps/2.png')
const matcapTexture = textureLoader.load('/textures/matcaps/3.png')
const matcapTexture = textureLoader.load('/textures/matcaps/4.png')
const matcapTexture = textureLoader.load('/textures/matcaps/5.png')
const matcapTexture = textureLoader.load('/textures/matcaps/6.png')
const matcapTexture = textureLoader.load('/textures/matcaps/7.png')
const matcapTexture = textureLoader.load('/textures/matcaps/8.png')

Many matcap texture maps can be found online, which can be understood as out-of-the-box materials https://github.com/nidorx/matcaps

Depth Material MeshDepthMaterial

The appearance of the MeshDepthMaterial is not determined by lighting or a certain material, but by the distance from the object to the camera. When the object is closer to the camera, it will appear white, and when it is farther away, it will appear black.

const material = new THREE.MeshDepthMaterial()

We can use this material to observe the distance between the geometry and the camera. We will use it in future lessons.

Add a little light!

Some of the materials we’ll cover below can only be seen with lights, so let’s add two simple lights to the scene.

Create an ambient light and add it to the scene:

/**
* Lights
*/
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
scene.add(ambientLight)

Create another point light and add it to the scene:

// ...const pointLight = new THREE.PointLight(0xffffff, 0.5)
pointLight.position.x = 2
pointLight.position.y = 3
pointLight.position.z = 4
scene.add(pointLight)

For the time being, we don’t need to know too much about these lights. There will be special chapters to learn more about lights later.

MeshLambertMaterial

MeshLambertMaterial is the first material we will learn that reacts to light:

const material = new THREE.MeshLambertMaterial()

While it’s over-shading due to lighting, if you look closely at the sphere, you should see some oddities in the details.

MeshPhongMaterial

Phong is an algorithm for rendering realistic lighting effects proposed in the 1970s, named after the author Bui Tuong Phong.

MeshPhongMaterial is the material that applies this algorithm. The effect is similar to MeshLambertMaterial, but the excessive light and shade are more natural, and the performance consumption is slightly higher than that of MeshLambertMaterial.

const material = new THREE.MeshPhongMaterial()

You can control the reflection of light through the Luminance property. The higher the value, the stronger the reflection, the brighter the surface, and the more polished it looks. You can also use the specular specular color property to change the color of reflections:

material.shininess = 100
material.specular = new THREE.Color(0x1188ff)

This code makes the reflected light a little bluish, see that?

MeshToonMaterial

MeshToonMaterial cartoon material can make our geometry show the style of 2-dimensional cartoon, commonly known as 3 rendering 2:

const material = new THREE.MeshToonMaterial()

By default, we can only see two colors (one for the dark side and one for the light side). If you want more color transitions, you can use the gradientMap property and load the gradientTexture:

material.gradientMap = gradientTexture

/assets/lessons/12/step-19.png

If we set the gradientMap directly, we will find that the cartoon effect is invalid, and the light and shade are too silky. This is because the gradient texture we use is small, which is related to the minFilter, magFilter and mipmapping we learned about in the texture mapping section.

The solution is also very simple, just set minFilter and magFilter to THREE.NearestFilter

Don’t forget to add generatempmaps=false:

gradientTexture.minFilter = THREE.NearestFilter
gradientTexture.magFilter = THREE.NearestFilter
gradientTexture.generateMipmaps = false

Now we can see that the cartoon effect has three colors, and can also be replaced by a texture with 5 color transitions:

const gradientTexture = textureLoader.load('/textures/gradients/5.jpg')

MeshStandardMaterial

The MeshStandardMaterial standard material uses the principle of rendering based on physical rules, which is the PBR we learned about in the texture class. The reason why it is called the standard is because PBR has become the standard of many 3D rendering engines, and no matter you use standard materials in any software or engine, the result is the same.

Like MeshLambertMaterial and MeshPhongMaterial, standard materials must have lights involved, with more realistic shading and more parameters, such as roughness and metalness.

const material = new THREE.MeshStandardMaterial()

We can directly adjust the values of roughness and metalness to observe

material.metalness = 0.45
material.roughness = 0.65

MeshPhysicalMaterial

Physical material MeshPhysicalMaterial is an extension or enhanced version of MeshStandardMaterial, providing more advanced physically-based rendering properties, such as:

  • Clearcoat Properties: There are some materials (such as automotive paint, carbon fiber and wet surfaces) that require a clear reflective layer over another surface that may be irregular or rough. Clearcoat can achieve a similar effect without the need for a separate transparent surface.
  • Physically Based Transparency: A more realistic glass-like thin and transparent effect can be achieved.
  • Better reflections.

PointsMaterial

A material specially used for THREE.Points, we will use it later when we make particle effects.

ShaderMaterial

Both the shader material ShaderMaterial and the raw shader material RawShaderMaterial can be used to create our own materials, it requires us to learn GLSL syntax, skip it for now, we will learn it in a dedicated course.

Environment map

Why was the environment map suddenly added to the material section? Because the role of the environment map is to reflect the surrounding environment on the surface of the geometry, it can be used in combination with various materials to build a “realism” very quickly.

It would be great to practice how to use it here.

First, let’s set up a very simple MeshStandardMaterial using the debug UI as before:

const material = new THREE.MeshStandardMaterial()
material.metalness = 0.7
material.roughness = 0.2
gui.add(material, 'metalness').min(0).max(1).step(0.0001)
gui.add(material, 'roughness').min(0).max(1).step(0.0001)

To add an environment texture map to a material, the envMap property must be used. Three.js currently only supports cube-type environment texture maps. Imagine yourself in a box, and what you want to reflect is the six surfaces inside the box.

Please find the environment texture image I prepared for you in the folder /static/textures/environmentMap/

To load environment texture maps, CubeTextureLoader must be used instead of TextureLoader. Note that the parameter of cubeTextureLoader.load is an array.

const cubeTextureLoader = new THREE.CubeTextureLoader()const environmentMapTexture = cubeTextureLoader.load([
'/textures/environmentMaps/0/px.jpg',
'/textures/environmentMaps/0/nx.jpg',
'/textures/environmentMaps/0/py.jpg',
'/textures/environmentMaps/0/ny.jpg',
'/textures/environmentMaps/0/pz.jpg',
'/textures/environmentMaps/0/nz.jpg'
])

Now, we can use environmentMapTexture in the material’s envMap property:

material.envMap = environmentMapTexture

Try adjusting the metalness and roughness of the material to observe different reflection effects.

There are also many environment texture images in the /static/textures/environmentMap/ folder for you to test.

WeChat Official Account:大帅老猿(WeChat:ezshine

--

--

Yohoho

A frontend developer who want to learn more and more