00:00/00:00
3:22
00:03:22

Shortcuts ⌨️

  • SPACE to play / pause
  • ARROW RIGHT or L to go forward
  • ARROW LEFT or J to go backward
  • ARROW UP to increase volume
  • ARROW DOWN to decrease volume
  • F to toggle fullscreen
  • M to toggle mute
  • 0 to 9 to go to the corresponding part of the video
  • SHIFT + , to decrease playback speed
  • SHIFT + . or ; to increase playback speed

Unlock content 🔓

To get access to 93 hours of video, a members-only Discord server, subtitles, lesson resources, future updates and much more join us for only $95!

Want to learn more? 👋

60%

That's the end of the free part 😔

To get access to 93 hours of video, a members-only Discord server and future updates, join us for only $95!

Next lesson
43.

Sliced Model

Difficulty Hard

Introduction 00:00

Another technique I get questions about, is how to slice a model like I did for the Citrix & Redbull website (the SSL certificate is broken and you need to scroll down once to see the slicing effect in action):

This effect is actually quite simple and involves concepts that we’ve already tackled, like how to enhance a built-in material, how to not display fragments and some trigonometry:

Setup 01:29

The starter already contains the following:

  • A IcosahedronGeometry with a MeshStandardMaterial applied to it
  • A plane to test the shadow
  • An HDR environment map for the background but also to light up the scene
  • A DirectionalLight to light up the sphere even more and cast a shadow on the plane
  • The vite-plugin-glsl dependency to handle GLSL files
  • A GLTFLoader instance with a DracoLoader instance associated with it
  • A gears model as gears.glb in the static/ folder
  • OrbitControls to rotate around
  • The three-custom-shader-material dependency to enhance built-in materials

Model 02:29

The lesson could have started with the model already implemented, but it’s a good opportunity to practice.

The gears model has been created in Blender and you can download the file using the Resources button:

If you open that file, you’ll see that it’s made up of 3 parts: the axle, the outerHull and the gears.

The slicing effect must be applied only to the outerHull so that it reveals the gears inside.

Since the sphere is a placeholder for the actual model, let’s remove the IcosahedronGeometry and the Mesh, but keep the material:

/**
 * Sliced model
 */
// Material
const material = new THREE.MeshStandardMaterial({
    metalness: 0.5,
    roughness: 0.25,
    envMapIntensity: 0.5,
    color: '#858080'
})

Load

The model is located in static/gears.glb. We already have an instance of GLTFLoader and an instance of DRACOLoader associated with it.

Use the load() method on the GLTFLoader instance, set the path to './gears.glb' and add a gltf parameter to the callback function:

// Model
gltfLoader.load('./gears.glb', (gltf) =>
{
})

Add the gltf.scene to your scene:

// Model
gltfLoader.load('./gears.glb', (gltf) =>
{
    scene.add(gltf.scene)
})

Rotation

We are going to make the model execute a perpetual rotation so that we can enjoy it from different angles and lighting.

Since we want to rotate gltf.scene on each frame, we need to do it in the tick function, but we don’t have access to gltf.scene outside of the callback function.

It’s a classic scope issue.

To fix that, before the load(), create a model variable using a let, to which you assign null:

let model = null
gltfLoader.load('./gears.glb', (gltf) =>
{
    // ...
})

In the callback, assign gltf.scene to model:

let model = null
gltfLoader.load('./gears.glb', (gltf) =>
{
    model = gltf.scene
    scene.add(model)
})

We now have access to model from anywhere.

In the tick function, assign elapsedTime multiplied by 0.1 to model.rotation.y:

const tick = () =>
{
    const elapsedTime = clock.getElapsedTime()

    // Update model
    model.rotation.y = elapsedTime * 0.1

    // ...
}

We get an error, because it takes time to load the model and the model variable will be null for a few frames.

Wrap the model rotation in an if statement so that we don’t update it while it’s null:

const tick = () =>
{
    const elapsedTime = clock.getElapsedTime()

    // Update model
    if(model)
        model.rotation.y = elapsedTime * 0.1

    // ...
}

Material

The model currently has a white MeshStandardMaterial applied to it because it’s the default one Three.js applies when loading a model without material.

We still have our own MeshStandardMaterial in the material variable and we want to apply it to the model.

To make sure that it’s applied to every Mesh of the model, we’re going to traverse it.

In the callback function, traverse the model using the traverse() method on model:

gltfLoader.load('./gears.glb', (gltf) =>
{
    model = gltf.scene

    model.traverse((child) =>
    {
        
    })

    scene.add(model)
})

The function will be called on each child and grandchild of the model.

In that function, test if the child is a Mesh using the isMesh property:

gltfLoader.load('./gears.glb', (gltf) =>
{
    model = gltf.scene

    model.traverse((child) =>
    {
        if(child.isMesh)
        {
            
        }
    })

    scene.add(model)
})

In that if(child.isMesh), assign the material to the child.material property:

gltfLoader.load('./gears.glb', (gltf) =>
{
    model = gltf.scene

    model.traverse((child) =>
    {
        if(child.isMesh)
        {
            child.material = material
        }
    })

    scene.add(model)
})

Shadows

We are going to activate both casting and receiving of the shadow on every Mesh of the model.

Still in the if(child.isMesh), set the castShadow and receiveShadow to true:

gltfLoader.load('./gears.glb', (gltf) =>
{
    model = gltf.scene

    model.traverse((child) =>
    {
        if(child.isMesh)
        {
            child.material = material
            child.castShadow = true
            child.receiveShadow = true
        }
    })

    scene.add(model)
})

Custom material 10:51

We have our model ready, so let’s apply our material to it.

We now want to replace the MeshStandardMaterial with an improved version which supports the slicing effect. To improve MeshStandardMaterial, we are going to use Custom Shader Material which is already available in the dependencies.

Instantiate

Import CustomShaderMaterial from 'three-custom-shader-material/vanilla':

import CustomShaderMaterial from 'three-custom-shader-material/vanilla'
Want to learn more?

That's the end of the free part 😔

To get access to 93 hours of video, a members-only Discord server and future updates, join us for only $95!

How to use it 🤔

  • Download the Starter pack or Final project
  • Unzip it
  • Open your terminal and go to the unzip folder
  • Run npm install to install dependencies
    (if your terminal warns you about vulnerabilities, ignore it)
  • Run npm run dev to launch the local server
    (project should open on your default browser automatically)
  • Start coding
  • The JS is located in src/script.js
  • The HTML is located in src/index.html
  • The CSS is located in src/style.css

If you get stuck and need help, join the members-only Discord server:

Discord