threejs-skills

Create 3D scenes, interactive experiences, and visual effects using Three.js. Use when user requests 3D graphics, WebGL experiences, 3D visualizations,…

INSTALLATION
npx skills add https://github.com/sickn33/antigravity-awesome-skills --skill threejs-skills
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

Three.js Skills

Systematically create high-quality 3D scenes and interactive experiences using Three.js best practices.

When to Use

  • Requests 3D visualizations or graphics ("create a 3D model", "show in 3D")
  • Wants interactive 3D experiences ("rotating cube", "explorable scene")
  • Needs WebGL or canvas-based rendering
  • Asks for animations, particles, or visual effects
  • Mentions Three.js, WebGL, or 3D rendering
  • Wants to visualize data in 3D space

Core Setup Pattern

1. Essential Three.js Imports

Use ES module import maps for modern Three.js (r183+):

<script type="importmap">

{

  "imports": {

    "three": "https://cdn.jsdelivr.net/npm/three@0.183.0/build/three.module.js",

    "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.183.0/examples/jsm/"

  }

}

</script>

<script type="module">

import * as THREE from "three";

import { OrbitControls } from "three/addons/controls/OrbitControls.js";

</script>

For production with npm/vite/webpack:

import * as THREE from "three";

import { OrbitControls } from "three/addons/controls/OrbitControls.js";

2. Scene Initialization

Every Three.js artifact needs these core components:

// Scene - contains all 3D objects

const scene = new THREE.Scene();

// Camera - defines viewing perspective

const camera = new THREE.PerspectiveCamera(

  75, // Field of view

  window.innerWidth / window.innerHeight, // Aspect ratio

  0.1, // Near clipping plane

  1000, // Far clipping plane

);

camera.position.z = 5;

// Renderer - draws the scene

const renderer = new THREE.WebGLRenderer({ antialias: true });

renderer.setSize(window.innerWidth, window.innerHeight);

document.body.appendChild(renderer.domElement);

3. Animation Loop

Use renderer.setAnimationLoop() (preferred) or requestAnimationFrame:

// Preferred: setAnimationLoop (handles WebXR compatibility)

renderer.setAnimationLoop(() => {

  mesh.rotation.x += 0.01;

  mesh.rotation.y += 0.01;

  renderer.render(scene, camera);

});

// Alternative: manual requestAnimationFrame

function animate() {

  requestAnimationFrame(animate);

  mesh.rotation.x += 0.01;

  mesh.rotation.y += 0.01;

  renderer.render(scene, camera);

}

animate();

Systematic Development Process

1. Define the Scene

Start by identifying:

  • What objects need to be rendered
  • Camera position and field of view
  • Lighting setup required
  • Interaction model (static, rotating, user-controlled)

2. Build Geometry

Choose appropriate geometry types:

Basic Shapes:

  • BoxGeometry - cubes, rectangular prisms
  • SphereGeometry - spheres, planets
  • CylinderGeometry - cylinders, tubes
  • PlaneGeometry - flat surfaces, ground planes
  • TorusGeometry - donuts, rings

CapsuleGeometry is available (stable since r142):

new THREE.CapsuleGeometry(0.5, 1, 4, 8); // radius, length, capSegments, radialSegments

3. Apply Materials

Choose materials based on visual needs:

Common Materials:

  • MeshBasicMaterial - unlit, flat colors (no lighting needed)
  • MeshStandardMaterial - physically-based, realistic (needs lighting)
  • MeshPhongMaterial - shiny surfaces with specular highlights
  • MeshLambertMaterial - matte surfaces, diffuse reflection
const material = new THREE.MeshStandardMaterial({

  color: 0x00ff00,

  metalness: 0.5,

  roughness: 0.5,

});

4. Add Lighting

If using lit materials (Standard, Phong, Lambert), add lights:

// Ambient light - general illumination

const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);

scene.add(ambientLight);

// Directional light - like sunlight

const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);

directionalLight.position.set(5, 5, 5);

scene.add(directionalLight);

Skip lighting if using MeshBasicMaterial - it's unlit by design.

5. Handle Responsiveness

Always add window resize handling:

window.addEventListener("resize", () => {

  camera.aspect = window.innerWidth / window.innerHeight;

  camera.updateProjectionMatrix();

  renderer.setSize(window.innerWidth, window.innerHeight);

});

Common Patterns

Rotating Object

function animate() {

  requestAnimationFrame(animate);

  mesh.rotation.x += 0.01;

  mesh.rotation.y += 0.01;

  renderer.render(scene, camera);

}

OrbitControls

With import maps or build tools, OrbitControls works directly:

import { OrbitControls } from "three/addons/controls/OrbitControls.js";

const controls = new OrbitControls(camera, renderer.domElement);

controls.enableDamping = true;

// Update in animation loop

renderer.setAnimationLoop(() => {

  controls.update();

  renderer.render(scene, camera);

});

Custom Camera Controls (Alternative)

For lightweight custom controls without importing OrbitControls:

let isDragging = false;

let previousMousePosition = { x: 0, y: 0 };

renderer.domElement.addEventListener("mousedown", () => {

  isDragging = true;

});

renderer.domElement.addEventListener("mouseup", () => {

  isDragging = false;

});

renderer.domElement.addEventListener("mousemove", (event) => {

  if (isDragging) {

    const deltaX = event.clientX - previousMousePosition.x;

    const deltaY = event.clientY - previousMousePosition.y;

    // Rotate camera around scene

    const rotationSpeed = 0.005;

    camera.position.x += deltaX * rotationSpeed;

    camera.position.y -= deltaY * rotationSpeed;

    camera.lookAt(scene.position);

  }

  previousMousePosition = { x: event.clientX, y: event.clientY };

});

// Zoom with mouse wheel

renderer.domElement.addEventListener("wheel", (event) => {

  event.preventDefault();

  camera.position.z += event.deltaY * 0.01;

  camera.position.z = Math.max(2, Math.min(20, camera.position.z)); // Clamp

});

Raycasting for Object Selection

Detect mouse clicks and hovers on 3D objects:

const raycaster = new THREE.Raycaster();

const mouse = new THREE.Vector2();

const clickableObjects = []; // Array of meshes that can be clicked

// Update mouse position

window.addEventListener("mousemove", (event) => {

  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;

  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

});

// Detect clicks

window.addEventListener("click", () => {

  raycaster.setFromCamera(mouse, camera);

  const intersects = raycaster.intersectObjects(clickableObjects);

  if (intersects.length > 0) {

    const clickedObject = intersects[0].object;

    // Handle click - change color, scale, etc.

    clickedObject.material.color.set(0xff0000);

  }

});

// Hover effect in animation loop

function animate() {

  requestAnimationFrame(animate);

  raycaster.setFromCamera(mouse, camera);

  const intersects = raycaster.intersectObjects(clickableObjects);

  // Reset all objects

  clickableObjects.forEach((obj) => {

    obj.scale.set(1, 1, 1);

  });

  // Highlight hovered object

  if (intersects.length > 0) {

    intersects[0].object.scale.set(1.2, 1.2, 1.2);

    document.body.style.cursor = "pointer";

  } else {

    document.body.style.cursor = "default";

  }

  renderer.render(scene, camera);

}

Particle System

const particlesGeometry = new THREE.BufferGeometry();

const particlesCount = 1000;

const posArray = new Float32Array(particlesCount * 3);

for (let i = 0; i < particlesCount * 3; i++) {

  posArray[i] = (Math.random() - 0.5) * 10;

}

particlesGeometry.setAttribute(

  "position",

  new THREE.BufferAttribute(posArray, 3),

);

const particlesMaterial = new THREE.PointsMaterial({

  size: 0.02,

  color: 0xffffff,

});

const particlesMesh = new THREE.Points(particlesGeometry, particlesMaterial);

scene.add(particlesMesh);

User Interaction (Mouse Movement)

let mouseX = 0;

let mouseY = 0;

document.addEventListener("mousemove", (event) => {

  mouseX = (event.clientX / window.innerWidth) * 2 - 1;

  mouseY = -(event.clientY / window.innerHeight) * 2 + 1;

});

function animate() {

  requestAnimationFrame(animate);

  camera.position.x = mouseX * 2;

  camera.position.y = mouseY * 2;

  camera.lookAt(scene.position);

  renderer.render(scene, camera);

}

Loading Textures

const textureLoader = new THREE.TextureLoader();

const texture = textureLoader.load("texture-url.jpg");

const material = new THREE.MeshStandardMaterial({

  map: texture,

});

Best Practices

Performance

  • Reuse geometries and materials when creating multiple similar objects
  • **Use BufferGeometry** for custom shapes (more efficient)
  • Limit particle counts to maintain 60fps (start with 1000-5000)
  • Dispose of resources when removing objects:
geometry.dispose();

material.dispose();

texture.dispose();

Visual Quality

  • Always set antialias: true on renderer for smooth edges
  • Use appropriate camera FOV (45-75 degrees typical)
  • Position lights thoughtfully - avoid overlapping multiple bright lights
  • Add ambient + directional lighting for realistic scenes

Code Organization

  • Initialize scene, camera, renderer at the top
  • Group related objects (e.g., all particles in one group)
  • Keep animation logic in the animate function
  • Separate object creation into functions for complex scenes

Common Pitfalls to Avoid

  • ❌ Using outputEncoding instead of outputColorSpace (renamed in r152)
  • ❌ Forgetting to add objects to scene with scene.add()
  • ❌ Using lit materials without adding lights
  • ❌ Not handling window resize
  • ❌ Forgetting to call renderer.render() in animation loop
  • ❌ Using THREE.Clock without considering THREE.Timer (recommended in r183)

Example Workflow

User: "Create an interactive 3D sphere that responds to mouse movement"

  • Setup: Import Three.js, create scene/camera/renderer
  • Geometry: Create SphereGeometry(1, 32, 32) for smooth sphere
  • Material: Use MeshStandardMaterial for realistic look
  • Lighting: Add ambient + directional lights
  • Interaction: Track mouse position, update camera
  • Animation: Rotate sphere, render continuously
  • Responsive: Add window resize handler
  • Result: Smooth, interactive 3D sphere ✓

Troubleshooting

Black screen / Nothing renders:

  • Check if objects added to scene
  • Verify camera position isn't inside objects
  • Ensure renderer.render() is called
  • Add lights if using lit materials

Poor performance:

  • Reduce particle count
  • Lower geometry detail (segments)
  • Reuse materials/geometries
  • Check browser console for errors

Objects not visible:

  • Check object position vs camera position
  • Verify material has visible color/properties
  • Ensure camera far plane includes objects
  • Add lighting if needed

Advanced Techniques

Visual Polish for Portfolio-Grade Rendering

Shadows:

// Enable shadows on renderer

renderer.shadowMap.enabled = true;

renderer.shadowMap.type = THREE.PCFSoftShadowMap; // Soft shadows

// Light that casts shadows

const directionalLight = new THREE.DirectionalLight(0xffffff, 1);

directionalLight.position.set(5, 10, 5);

directionalLight.castShadow = true;

// Configure shadow quality

directionalLight.shadow.mapSize.width = 2048;

directionalLight.shadow.mapSize.height = 2048;

directionalLight.shadow.camera.near = 0.5;

directionalLight.shadow.camera.far = 50;

scene.add(directionalLight);

// Objects cast and receive shadows

mesh.castShadow = true;

mesh.receiveShadow = true;

// Ground plane receives shadows

const groundGeometry = new THREE.PlaneGeometry(20, 20);

const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x808080 });

const ground = new THREE.Mesh(groundGeometry, groundMaterial);

ground.rotation.x = -Math.PI / 2;

ground.receiveShadow = true;

scene.add(ground);

Environment Maps &#x26; Reflections:

// Create environment map from cubemap

const loader = new THREE.CubeTextureLoader();

const envMap = loader.load([

  "px.jpg",

  "nx.jpg", // positive x, negative x

  "py.jpg",

  "ny.jpg", // positive y, negative y

  "pz.jpg",

  "nz.jpg", // positive z, negative z

]);

scene.environment = envMap; // Affects all PBR materials

scene.background = envMap; // Optional: use as skybox

// Or apply to specific materials

const material = new THREE.MeshStandardMaterial({

  metalness: 1.0,

  roughness: 0.1,

  envMap: envMap,

});

Tone Mapping &#x26; Output Encoding:

// Improve color accuracy and HDR rendering

renderer.toneMapping = THREE.ACESFilmicToneMapping;

renderer.toneMappingExposure = 1.0;

renderer.outputColorSpace = THREE.SRGBColorSpace; // Was outputEncoding in older versions

// Makes colors more vibrant and realistic

Fog for Depth:

// Linear fog

scene.fog = new THREE.Fog(0xcccccc, 10, 50); // color, near, far

// Or exponential fog (more realistic)

scene.fog = new THREE.FogExp2(0xcccccc, 0.02); // color, density

Custom Geometry from Vertices

const geometry = new THREE.BufferGeometry();

const vertices = new Float32Array([-1, -1, 0, 1, -1, 0, 1, 1, 0]);

geometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3));

Post-Processing Effects

Post-processing effects are available via import maps or build tools. See threejs-postprocessing skill for EffectComposer, bloom, DOF, and more.

Group Objects

const group = new THREE.Group();

group.add(mesh1);

group.add(mesh2);

group.rotation.y = Math.PI / 4;

scene.add(group);

Summary

Three.js artifacts require systematic setup:

  • Import Three.js via import maps or build tools
  • Initialize scene, camera, renderer
  • Create geometry + material = mesh
  • Add lighting if using lit materials
  • Implement animation loop (prefer setAnimationLoop)
  • Handle window resize
  • Set renderer.outputColorSpace = THREE.SRGBColorSpace

Follow these patterns for reliable, performant 3D experiences.

Modern Three.js Practices (r183)

Modular Imports

// With npm/vite/webpack:

import * as THREE from "three";

import { OrbitControls } from "three/addons/controls/OrbitControls.js";

import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";

import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";

WebGPU Renderer (Alternative)

Three.js r183 includes a WebGPU renderer as an alternative to WebGL:

import { WebGPURenderer } from "three/addons/renderers/webgpu/WebGPURenderer.js";

const renderer = new WebGPURenderer({ antialias: true });

await renderer.init();

renderer.setSize(window.innerWidth, window.innerHeight);

WebGPU uses TSL (Three.js Shading Language) instead of GLSL for custom shaders. See threejs-shaders for details.

Timer (r183 Recommended)

THREE.Timer is recommended over THREE.Clock as of r183:

const timer = new THREE.Timer();

renderer.setAnimationLoop(() => {

  timer.update();

  const delta = timer.getDelta();

  const elapsed = timer.getElapsed();

  mesh.rotation.y += delta;

  renderer.render(scene, camera);

});

Benefits over Clock:

  • Not affected by page visibility (pauses when tab is hidden)
  • Cleaner API design
  • Better integration with setAnimationLoop

Animation Libraries (GSAP Integration)

// Smooth timeline-based animations

import gsap from "gsap";

// Instead of manual animation loops:

gsap.to(mesh.position, {

  x: 5,

  duration: 2,

  ease: "power2.inOut",

});

// Complex sequences:

const timeline = gsap.timeline();

timeline

  .to(mesh.rotation, { y: Math.PI * 2, duration: 2 })

  .to(mesh.scale, { x: 2, y: 2, z: 2, duration: 1 }, "-=1");

Why GSAP:

  • Professional easing functions
  • Timeline control (pause, reverse, scrub)
  • Better than manual lerping for complex animations

Scroll-Based Interactions

// Sync 3D animations with page scroll

let scrollY = window.scrollY;

window.addEventListener("scroll", () => {

  scrollY = window.scrollY;

});

function animate() {

  requestAnimationFrame(animate);

  // Rotate based on scroll position

  mesh.rotation.y = scrollY * 0.001;

  // Move camera through scene

  camera.position.y = -(scrollY / window.innerHeight) * 10;

  renderer.render(scene, camera);

}

Advanced scroll libraries:

  • ScrollTrigger (GSAP plugin)
  • Locomotive Scroll
  • Lenis smooth scroll

Performance Optimization in Production

// Level of Detail (LOD)

const lod = new THREE.LOD();

lod.addLevel(highDetailMesh, 0); // Close up

lod.addLevel(mediumDetailMesh, 10); // Medium distance

lod.addLevel(lowDetailMesh, 50); // Far away

scene.add(lod);

// Instanced meshes for many identical objects

const geometry = new THREE.BoxGeometry();

const material = new THREE.MeshStandardMaterial();

const instancedMesh = new THREE.InstancedMesh(geometry, material, 1000);

// Set transforms for each instance

const matrix = new THREE.Matrix4();

for (let i = 0; i < 1000; i++) {

  matrix.setPosition(

    Math.random() * 100,

    Math.random() * 100,

    Math.random() * 100,

  );

  instancedMesh.setMatrixAt(i, matrix);

}

Modern Loading Patterns

// In production, load 3D models:

import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";

const loader = new GLTFLoader();

loader.load("model.gltf", (gltf) => {

  scene.add(gltf.scene);

  // Traverse and setup materials

  gltf.scene.traverse((child) => {

    if (child.isMesh) {

      child.castShadow = true;

      child.receiveShadow = true;

    }

  });

});

When to Use What

Import Map Approach:

  • Quick prototypes and demos
  • Educational content
  • Artifacts and embedded experiences
  • No build step required

Production Build Approach:

  • Client projects and portfolios
  • Complex applications
  • Performance-critical applications
  • Team collaboration with version control

Recommended Production Stack

Three.js r183 + Vite

├── GSAP (animations)

├── React Three Fiber (optional - React integration)

├── Drei (helper components)

├── Leva (debug GUI)

└── Post-processing effects

Limitations

  • Use this skill only when the task clearly matches the scope described above.
  • Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
  • Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card