This tutorial walks by way of creating an interactive animation: beginning in Blender by designing a button and simulating a cloth-like object that drops onto a floor and settles with a mushy bounce.
After baking the fabric simulation, the animation is exported and introduced right into a Three.js mission, the place it turns into an interactive scene that may be replayed on click on.
By the top, you’ll have a user-triggered animation that blends Blender’s physics simulations with Three.js rendering and interactivity.
Let’s dive in!
Step 1: Create a Dice and Add Subdivisions
- Begin a New Mission: Open Blender and delete the default dice (choose it and press X, then verify).
- Add a Dice: Press Shift + A > Mesh > Dice to create a brand new dice.
- Enter Edit Mode: Choose the dice, then press Tab to change to Edit Mode.
- Subdivide the Dice: Press Ctrl + R so as to add a loop lower, hover over the dice, and scroll your mouse wheel to extend the variety of cuts.
- Apply Subdivision: With the dice nonetheless chosen in Object Mode, go to the Modifiers panel (wrench icon), and click on Add Modifier > Subdivision Floor. Set the Ranges to 2 or 3 for a smoother consequence, then click on Apply.

Step 2: Add Material Physics and Modify Settings
- Choose the Dice: Guarantee your subdivided dice is chosen in Object Mode.
- Add Material Physics: Go to the Physics tab within the Properties panel. Click on Material to allow material simulation.
- Pin the Edges (Optionally available): If you’d like components of the dice to remain fastened (e.g., the highest), change to Edit Mode, choose the vertices you need to pin, return to the Physics tab, and underneath Material > Form, click on Pin to assign these vertices to a vertex group.
- Modify Key Parameters:
- High quality Steps: Set to 10-15 for smoother simulation (increased values improve accuracy however decelerate computation).
- Mass: Set to round 0.2-0.5 kg for a lighter, extra versatile material.
- Stress: Below Material > Stress, allow it and set a constructive worth (e.g., 2-5) to simulate inflation. It will make the fabric develop as if air is pushing it outward.
- Stiffness: Modify Pressure and Compression (e.g., 10-15) to manage how stiff or free the fabric feels.
- Check the Simulation: Press the Spacebar to play the animation and see the fabric inflate. Tweak settings as wanted.

Step 3: Add a Floor Aircraft with a Collision
- Create a Floor Aircraft: Press Shift + A > Mesh > Aircraft. Scale it up by urgent S and dragging (e.g., scale it to 5-10x) so it’s massive sufficient for the fabric to work together with.
- Place the Aircraft: Transfer the airplane beneath the dice by urgent G > Z > -5 (or regulate as wanted).
- Allow Collision: Choose the airplane, go to the Physics tab, and click on Collision. Depart the default settings.
- Run the Simulation: Press the Spacebar once more to see the fabric inflate and settle onto the bottom airplane.


Step 4: Modify Supplies and Textures
- Choose the Dice: In Object Mode, choose the fabric (dice) object.
- Add a Materials: Go to the Materials tab, click on New to create a fabric, and identify it.
- Set Base Coloration/UV Map: Within the Base Coloration slot, select a fabric-like shade (e.g., crimson or blue) or join a picture texture by clicking the yellow dot subsequent to Base Coloration and choosing Picture Texture. Load a texture file you probably have one.
- Modify Roughness and Specular: Set Roughness to 0.1-0.3 for a mushy cloth look.
- Apply to Floor (Optionally available): Repeat the method for the airplane, utilizing a easy grey or textured materials for distinction.

Step 5: Export as MDD and Generate Form Keys for Three.js
To make use of the fabric animation in a Three.js mission, we’ll export the physics simulation as an MDD file utilizing the NewTek MDD plugin, then re-import it to create Form Keys. Comply with these steps:
- Allow the NewTek MDD Plugin:
- Go to Edit > Preferences > Add-ons.
- Seek for “NewTek” or “MDD” and allow the “Import-Export: NewTek MDD format” add-on by checking the field. Shut the Preferences window.
- Apply All Modifiers and All Remodel:
- In Object Mode, choose the fabric object.
- Go to the Modifiers panel (wrench icon). For every modifier (e.g., Subdivision Floor, Material), click on the dropdown and choose Apply. This “freezes” the mesh with its present form and physics information.
- Guarantee no unapplied deformations (e.g., scale) stay: Press Ctrl + A > All Transforms to use location, rotation, and scale.
- Export as MDD:
- With the fabric object chosen, go to File > Export > Lightwave Level Cache (.mdd).
- Within the export settings (backside left):
- Set FPS (frames per second) to match your mission (e.g., 24, 30, or 60).
- Set the Begin/Finish Body of your animation.
- Select a save location (e.g., “inflation.mdd”) and click on Export MDD.
- Import the MDD:
- Go to File > Import > Lightwave Level Cache (.mdd), and cargo the “inflation.mdd” file.
- Within the Physics and Modifiers panel, take away any material simulation-related choices, as we now have form keys.

Step 6: Export the Material Simulation Object as GLB
After importing the MDD, choose the dice with the animation information.
- Export as glTF 2.0 (.glb/.gltf): Go to File > Export > glTF 2.0 (.glb/.gltf).
- Test Form Keys and Animation
- Below the Information part, test Form Keys to incorporate the morph targets generated from the animation.
- Test Animations to export the animation information tied to the Form Keys.
- Export: Select a save location (e.g., “inflation.glb”) and click on Export glTF 2.0. This file is now prepared to be used in Three.js.
Step 7: Implement the Material Animation in Three.js
On this step, we’ll use Three.js with React (through @react-three/fiber) to load and animate the fabric inflation impact from the inflation.glb file exported in Step 6. Beneath is the code with explanations:
- Set Up Imports and File Path:
- Import obligatory libraries: THREE for core Three.js performance, useRef, useState, useEffect from React for state and lifecycle administration, and utilities from @react-three/fiber and @react-three/drei for rendering and controls.
- Import GLTFLoader from Three.js to load the .glb file.
- Outline the mannequin path: const modelPath = ‘/inflation.glb’; factors to the exported file (regulate the trail based mostly in your mission construction).
- Create the Mannequin Part:
- Outline the Mannequin part to deal with loading and animating the .glb file.
- Use state variables: mannequin for the loaded 3D object, loading to trace progress, and error for dealing with points.
- Use useRef to retailer the AnimationMixer (mixerRef) and animation actions (actionsRef) for controlling playback.
- Load the Mannequin with Animations:
- In a useEffect hook, instantiate GLTFLoader and cargo inflation.glb.
- On success (gltf callback):
- Extract the scene (gltf.scene) and create an AnimationMixer to handle animations.
- For every animation clip in gltf.animations:
- Set length to six seconds (clip.length = 6).
- Create an AnimationAction (mixer.clipAction(clip)).
- Configure the motion: clampWhenFinished = true stops on the final body, loop = THREE.LoopOnce performs as soon as, and setDuration(6) enforces the 6-second length.
- Reset and play the motion instantly, storing it in actionsRef.present.
- Replace state with the loaded mannequin and set loading to false.
- Log loading progress with the xhr callback.
- Deal with errors within the error callback, updating error state.
- Clear up the mixer on part unmount.
- Animate the Mannequin:
- Use useFrame to replace the mixer every body with mixerRef.present.replace(delta), advancing the animation based mostly on time.
- Add interactivity:
- handleClick: Resets and replays all animations on click on.
- onPointerOver/onPointerOut: Modifications the cursor to point clickability.
- Render the Mannequin:
- Return null if nonetheless loading, an error happens, or no mannequin is loaded.
- Return a <primitive> ingredient with the loaded mannequin, enabling shadows and attaching occasion handlers.
- Create a Reflective Floor:
- Outline MetalGround as a mesh with a airplane geometry (args={[100, 100]}).
- Apply MeshReflectorMaterial with properties like metalness=0.5, roughness=0.2, and shade=”#202020″ for a metallic, reflective look. Modify blur, power, and determination as wanted.
- Set Up the Scene:
- Within the App part, create a <Canvas> with a digital camera positioned at [0, 15, 15] and a 50-degree FOV.
- Add a directionalLight at [0, 15, 0] with shadows enabled.
- Embody an Atmosphere preset (“studio”) for lighting, a Mannequin at [0, 5, 0], ContactShadows for realism, and the MetalGround rotated and positioned beneath.
- Add OrbitControls for interactive digital camera motion.
import * as THREE from 'three';
import { useRef, useState, useEffect } from 'react';
import { Canvas, useFrame } from '@react-three/fiber';
import { OrbitControls, Atmosphere, MeshReflectorMaterial, ContactShadows } from '@react-three/drei';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
const modelPath = '/inflation.glb';
operate Mannequin({ ...props }) {
const [model, setModel] = useState<THREE.Group | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<unknown>(null);
const mixerRef = useRef<THREE.AnimationMixer | null>(null);
const actionsRef = useRef<THREE.AnimationAction[]>([]);
const handleClick = () => {
actionsRef.present.forEach((motion) => {
motion.reset();
motion.play();
});
};
const onPointerOver = () => {
doc.physique.model.cursor = 'pointer';
};
const onPointerOut = () => {
doc.physique.model.cursor = 'auto';
};
useEffect(() => {
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/');
loader.setDRACOLoader(dracoLoader);
loader.load(
modelPath,
(gltf) => {
const mesh = gltf.scene;
const mixer = new THREE.AnimationMixer(mesh);
mixerRef.present = mixer;
if (gltf.animations && gltf.animations.size) {
gltf.animations.forEach((clip) => {
clip.length = 6;
const motion = mixer.clipAction(clip);
motion.clampWhenFinished = true;
motion.loop = THREE.LoopOnce;
motion.setDuration(6);
motion.reset();
motion.play();
actionsRef.present.push(motion);
});
}
setModel(mesh);
setLoading(false);
},
(xhr) => {
console.log(`Loading: ${(xhr.loaded / xhr.whole) * 100}%`);
},
(error) => {
console.error('An error occurred loading the mannequin:', error);
setError(error);
setLoading(false);
}
);
return () => {
if (mixerRef.present) {
mixerRef.present.stopAllAction();
}
};
}, []);
useFrame((_, delta) => {
if (mixerRef.present) {
mixerRef.present.replace(delta);
}
});
if (loading || error || !mannequin) {
return null;
}
return (
<primitive
{...props}
object={mannequin}
castShadow
receiveShadow
onClick={handleClick}
onPointerOver={onPointerOver}
onPointerOut={onPointerOut}
/>
);
}
operate MetalGround({ ...props }) {
return (
<mesh {...props} receiveShadow>
<planeGeometry args={[100, 100]} />
<MeshReflectorMaterial
shade="#151515"
metalness={0.5}
roughness={0.2}
blur={[0, 0]}
decision={2048}
mirror={0}
/>
</mesh>
);
}
export default operate App() {
return (
<div id="content material">
<Canvas digital camera={{ place: [0, 35, 15], fov: 25 }}>
<directionalLight place={[0, 15, 0]} depth={1} shadow-mapSize={1024} />
<Atmosphere preset="studio" background={false} environmentRotation={[0, Math.PI / -2, 0]} />
<Mannequin place={[0, 5, 0]} />
<ContactShadows opacity={0.5} scale={10} blur={5} far={10} decision={512} shade="#000000" />
<MetalGround rotation-x={Math.PI / -2} place={[0, -0.01, 0]} />
<OrbitControls
enableZoom={false}
enablePan={false}
enableRotate={true}
enableDamping={true}
dampingFactor={0.05}
/>
</Canvas>
</div>
);
}
And that’s it! Ranging from a material simulation in Blender, we turned it right into a button that drops into place and reacts with a little bit of bounce inside a Three.js scene.
This workflow exhibits how Blender’s physics simulations may be exported and mixed with Three.js to create interactive, real-time experiences on the internet.