This tutorial walks through creating an interactive animation: starting in Blender by designing a button and simulating a cloth-like object that drops onto a surface and settles with a soft bounce.
After baking the cloth simulation, the animation is exported and brought into a Three.js project, where it becomes an interactive scene that can be replayed on click.
By the end, 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 Cube and Add Subdivisions
- Start a New Project: Open Blender and delete the default cube (select it and press X, then confirm).
- Add a Cube: Press Shift + A > Mesh > Cube to create a new cube.
- Enter Edit Mode: Select the cube, then press Tab to switch to Edit Mode.
- Subdivide the Cube: Press Ctrl + R to add a loop cut, hover over the cube, and scroll your mouse wheel to increase the number of cuts.
- Apply Subdivision: With the cube still selected in Object Mode, go to the Modifiers panel (wrench icon), and click Add Modifier > Subdivision Surface. Set the Levels to 2 or 3 for a smoother result, then click Apply.

Step 2: Add Cloth Physics and Adjust Settings
- Select the Cube: Ensure your subdivided cube is selected in Object Mode.
- Add Cloth Physics: Go to the Physics tab in the Properties panel. Click Cloth to enable cloth simulation.
- Pin the Edges (Optional): If you want parts of the cube to stay fixed (e.g., the top), switch to Edit Mode, select the vertices you want to pin, go back to the Physics tab, and under Cloth > Shape, click Pin to assign those vertices to a vertex group.
- Adjust Key Parameters:
- Quality Steps: Set to 10-15 for smoother simulation (higher values increase accuracy but slow down computation).
- Mass: Set to around 0.2-0.5 kg for a lighter, more flexible cloth.
- Pressure: Under Cloth > Pressure, enable it and set a positive value (e.g., 2-5) to simulate inflation. This will make the cloth expand as if air is pushing it outward.
- Stiffness: Adjust Tension and Compression (e.g., 10-15) to control how stiff or loose the cloth feels.
- Test the Simulation: Press the Spacebar to play the animation and see the cloth inflate. Tweak settings as needed.

Step 3: Add a Ground Plane with a Collision
- Create a Ground Plane: Press Shift + A > Mesh > Plane. Scale it up by pressing S and dragging (e.g., scale it to 5-10x) so it’s large enough for the cloth to interact with.
- Position the Plane: Move the plane below the cube by pressing G > Z > -5 (or adjust as needed).
- Enable Collision: Select the plane, go to the Physics tab, and click Collision. Leave the default settings.
- Run the Simulation: Press the Spacebar again to see the cloth inflate and settle onto the ground plane.


Step 4: Adjust Materials and Textures
- Select the Cube: In Object Mode, select the cloth (cube) object.
- Add a Material: Go to the Material tab, click New to create a material, and name it.
- Set Base Color/UV Map: In the Base Color slot, choose a fabric-like color (e.g., red or blue) or connect an image texture by clicking the yellow dot next to Base Color and selecting Image Texture. Load a texture file if you have one.
- Adjust Roughness and Specular: Set Roughness to 0.1-0.3 for a soft fabric look.
- Apply to Ground (Optional): Repeat the process for the plane, using a simple gray or textured material for contrast.

Step 5: Export as MDD and Generate Shape Keys for Three.js
To use the cloth animation in a Three.js project, we’ll export the physics simulation as an MDD file using the NewTek MDD plugin, then re-import it to create Shape Keys. Follow these steps:
- Enable the NewTek MDD Plugin:
- Go to Edit > Preferences > Add-ons.
- Search for “NewTek” or “MDD” and enable the “Import-Export: NewTek MDD format” add-on by checking the box. Close the Preferences window.
- Apply All Modifiers and All Transform:
- In Object Mode, select the cloth object.
- Go to the Modifiers panel (wrench icon). For each modifier (e.g., Subdivision Surface, Cloth), click the dropdown and select Apply. This “freezes” the mesh with its current shape and physics data.
- Ensure no unapplied deformations (e.g., scale) remain: Press Ctrl + A > All Transforms to apply location, rotation, and scale.
- Export as MDD:
- With the cloth object selected, go to File > Export > Lightwave Point Cache (.mdd).
- In the export settings (bottom left):
- Set FPS (frames per second) to match your project (e.g., 24, 30, or 60).
- Set the Start/End Frame of your animation.
- Choose a save location (e.g., “inflation.mdd”) and click Export MDD.
- Import the MDD:
- Go to File > Import > Lightwave Point Cache (.mdd), and load the “inflation.mdd” file.
- In the Physics and Modifiers panel, remove any cloth simulation-related options, as we now have shape keys.

Step 6: Export the Cloth Simulation Object as GLB
After importing the MDD, select the cube with the animation data.
- Export as glTF 2.0 (.glb/.gltf): Go to File > Export > glTF 2.0 (.glb/.gltf).
- Check Shape Keys and Animation
- Under the Data section, check Shape Keys to include the morph targets generated from the animation.
- Check Animations to export the animation data tied to the Shape Keys.
- Export: Choose a save location (e.g., “inflation.glb”) and click Export glTF 2.0. This file is now ready for use in Three.js.
Step 7: Implement the Cloth Animation in Three.js
In this step, we’ll use Three.js with React (via @react-three/fiber) to load and animate the cloth inflation effect from the inflation.glb file exported in Step 6. Below is the code with explanations:
- Set Up Imports and File Path:
- Import necessary libraries: THREE for core Three.js functionality, useRef, useState, useEffect from React for state and lifecycle management, and utilities from @react-three/fiber and @react-three/drei for rendering and controls.
- Import GLTFLoader from Three.js to load the .glb file.
- Define the model path: const modelPath = ‘/inflation.glb’; points to the exported file (adjust the path based on your project structure).
- Create the Model Component:
- Define the Model component to handle loading and animating the .glb file.
- Use state variables: model for the loaded 3D object, loading to track progress, and error for handling issues.
- Use useRef to store the AnimationMixer (mixerRef) and animation actions (actionsRef) for controlling playback.
- Load the Model with Animations:
- In a useEffect hook, instantiate GLTFLoader and load inflation.glb.
- On success (gltf callback):
- Extract the scene (gltf.scene) and create an AnimationMixer to manage animations.
- For each animation clip in gltf.animations:
- Set duration to 6 seconds (clip.duration = 6).
- Create an AnimationAction (mixer.clipAction(clip)).
- Configure the action: clampWhenFinished = true stops at the last frame, loop = THREE.LoopOnce plays once, and setDuration(6) enforces the 6-second duration.
- Reset and play the action immediately, storing it in actionsRef.current.
- Update state with the loaded model and set loading to false.
- Log loading progress with the xhr callback.
- Handle errors in the error callback, updating error state.
- Clean up the mixer on component unmount.
- Animate the Model:
- Use useFrame to update the mixer each frame with mixerRef.current.update(delta), advancing the animation based on time.
- Add interactivity:
- handleClick: Resets and replays all animations on click.
- onPointerOver/onPointerOut: Changes the cursor to indicate clickability.
- Render the Model:
- Return null if still loading, an error occurs, or no model is loaded.
- Return a <primitive> element with the loaded model, enabling shadows and attaching event handlers.
- Create a Reflective Ground:
- Define MetalGround as a mesh with a plane geometry (args={[100, 100]}).
- Apply MeshReflectorMaterial with properties like metalness=0.5, roughness=0.2, and color=”#202020″ for a metallic, reflective look. Adjust blur, strength, and resolution as needed.
- Set Up the Scene:
- In the App component, create a <Canvas> with a camera positioned at [0, 15, 15] and a 50-degree FOV.
- Add a directionalLight at [0, 15, 0] with shadows enabled.
- Include an Environment preset (“studio”) for lighting, a Model at [0, 5, 0], ContactShadows for realism, and the MetalGround rotated and positioned below.
- Add OrbitControls for interactive camera movement.
import * as THREE from 'three';
import { useRef, useState, useEffect } from 'react';
import { Canvas, useFrame } from '@react-three/fiber';
import { OrbitControls, Environment, 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';
function Model({ ...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.current.forEach((action) => {
action.reset();
action.play();
});
};
const onPointerOver = () => {
document.body.style.cursor = 'pointer';
};
const onPointerOut = () => {
document.body.style.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.current = mixer;
if (gltf.animations && gltf.animations.length) {
gltf.animations.forEach((clip) => {
clip.duration = 6;
const action = mixer.clipAction(clip);
action.clampWhenFinished = true;
action.loop = THREE.LoopOnce;
action.setDuration(6);
action.reset();
action.play();
actionsRef.current.push(action);
});
}
setModel(mesh);
setLoading(false);
},
(xhr) => {
console.log(`Loading: ${(xhr.loaded / xhr.total) * 100}%`);
},
(error) => {
console.error('An error happened loading the model:', error);
setError(error);
setLoading(false);
}
);
return () => {
if (mixerRef.current) {
mixerRef.current.stopAllAction();
}
};
}, []);
useFrame((_, delta) => {
if (mixerRef.current) {
mixerRef.current.update(delta);
}
});
if (loading || error || !model) {
return null;
}
return (
<primitive
{...props}
object={model}
castShadow
receiveShadow
onClick={handleClick}
onPointerOver={onPointerOver}
onPointerOut={onPointerOut}
/>
);
}
function MetalGround({ ...props }) {
return (
<mesh {...props} receiveShadow>
<planeGeometry args={[100, 100]} />
<MeshReflectorMaterial
color="#151515"
metalness={0.5}
roughness={0.2}
blur={[0, 0]}
resolution={2048}
mirror={0}
/>
</mesh>
);
}
export default function App() {
return (
<div id="content">
<Canvas camera={{ position: [0, 35, 15], fov: 25 }}>
<directionalLight position={[0, 15, 0]} intensity={1} shadow-mapSize={1024} />
<Environment preset="studio" background={false} environmentRotation={[0, Math.PI / -2, 0]} />
<Model position={[0, 5, 0]} />
<ContactShadows opacity={0.5} scale={10} blur={5} far={10} resolution={512} color="#000000" />
<MetalGround rotation-x={Math.PI / -2} position={[0, -0.01, 0]} />
<OrbitControls
enableZoom={false}
enablePan={false}
enableRotate={true}
enableDamping={true}
dampingFactor={0.05}
/>
</Canvas>
</div>
);
}
And that’s it! Starting from a cloth simulation in Blender, we turned it into a button that drops into place and reacts with a bit of bounce inside a Three.js scene.
This workflow shows how Blender’s physics simulations can be exported and combined with Three.js to create interactive, real-time experiences on the web.
دیدگاهتان را بنویسید