From bb402eba5f9c14801d8e9e3f72f8206386f990c4 Mon Sep 17 00:00:00 2001 From: KeyInfo Bot Date: Wed, 10 Jun 2026 21:31:17 +0800 Subject: [PATCH] Publish threejs skill to marketplace Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources. Confidence: high Scope-risk: narrow Directive: Keep private/internal skills out of the public marketplace. Tested: Marketplace validation passed. --- .agents/plugins/marketplace.json | 12 + .../codex/.agents/plugins/marketplace.json | 12 + .../plugins/threejs/.codex-plugin/plugin.json | 37 + .../threejs/skills/threejs-animation/SKILL.md | 552 +++++++++++++++ .../skills/threejs-fundamentals/SKILL.md | 488 +++++++++++++ .../threejs/skills/threejs-geometry/SKILL.md | 548 +++++++++++++++ .../skills/threejs-interaction/SKILL.md | 660 ++++++++++++++++++ .../threejs/skills/threejs-lighting/SKILL.md | 481 +++++++++++++ .../threejs/skills/threejs-loaders/SKILL.md | 623 +++++++++++++++++ .../threejs/skills/threejs-materials/SKILL.md | 520 ++++++++++++++ .../skills/threejs-postprocessing/SKILL.md | 602 ++++++++++++++++ .../threejs/skills/threejs-shaders/SKILL.md | 642 +++++++++++++++++ .../threejs/skills/threejs-textures/SKILL.md | 628 +++++++++++++++++ 13 files changed, 5805 insertions(+) create mode 100644 plugins/codex/plugins/threejs/.codex-plugin/plugin.json create mode 100644 plugins/codex/plugins/threejs/skills/threejs-animation/SKILL.md create mode 100644 plugins/codex/plugins/threejs/skills/threejs-fundamentals/SKILL.md create mode 100644 plugins/codex/plugins/threejs/skills/threejs-geometry/SKILL.md create mode 100644 plugins/codex/plugins/threejs/skills/threejs-interaction/SKILL.md create mode 100644 plugins/codex/plugins/threejs/skills/threejs-lighting/SKILL.md create mode 100644 plugins/codex/plugins/threejs/skills/threejs-loaders/SKILL.md create mode 100644 plugins/codex/plugins/threejs/skills/threejs-materials/SKILL.md create mode 100644 plugins/codex/plugins/threejs/skills/threejs-postprocessing/SKILL.md create mode 100644 plugins/codex/plugins/threejs/skills/threejs-shaders/SKILL.md create mode 100644 plugins/codex/plugins/threejs/skills/threejs-textures/SKILL.md diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json index 7d4f00817..eb479ab64 100644 --- a/.agents/plugins/marketplace.json +++ b/.agents/plugins/marketplace.json @@ -75,6 +75,18 @@ "authentication": "ON_INSTALL" }, "category": "文档处理" + }, + { + "name": "threejs", + "source": { + "source": "local", + "path": "./plugins/codex/plugins/threejs" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "设计" } ] } diff --git a/plugins/codex/.agents/plugins/marketplace.json b/plugins/codex/.agents/plugins/marketplace.json index 1a858f233..7e4203e9e 100644 --- a/plugins/codex/.agents/plugins/marketplace.json +++ b/plugins/codex/.agents/plugins/marketplace.json @@ -75,6 +75,18 @@ "authentication": "ON_INSTALL" }, "category": "文档处理" + }, + { + "name": "threejs", + "source": { + "source": "local", + "path": "./plugins/threejs" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "设计" } ] } diff --git a/plugins/codex/plugins/threejs/.codex-plugin/plugin.json b/plugins/codex/plugins/threejs/.codex-plugin/plugin.json new file mode 100644 index 000000000..853db7720 --- /dev/null +++ b/plugins/codex/plugins/threejs/.codex-plugin/plugin.json @@ -0,0 +1,37 @@ +{ + "name": "threejs", + "version": "1.0.0", + "description": "一套覆盖 Three.js 完整开发流程的 10 个专业技能,从基础场景搭建到高级视觉效果,助你快速构建专业级 3D 应用:\r\n核心基础\r\n- threejs-fundamentals - 场景、相机、渲染器配置\r\n- threejs-geometry - 几何体创建与自定义网格\r\n- threejs-materials - PBR 材质、ShaderMaterial 与材质优化\r\n视觉表现\r\n- threejs-lighting - 灯光类型、阴影配置与环境光照\r\n- threejs-textures - UV 映射、环境贴图与纹理优化\r\n- threejs-postprocessing - 后期处理、特效与画面调色\r\n交互与动画\r\n- threejs-animation - 关键帧动画、骨骼动画与动画混合\r\n- threejs-interaction - 射线检测、鼠标/触摸交互与相机控制\r\n高级特性\r\n- threejs-shaders - GLSL 着色器、自定义特效与顶点操作\r\n- threejs-loaders - GLTF/GLB 模型加载、资源管理与异步模式\r\n适合人群: WebGL 开发者、3D 可视化工程师、创意技术开发者\r\n使用方式: 在开发过程中按需调用对应技能,获得专业的 Three.js 开发指导与代码建议。", + "author": { + "name": "EAPIL", + "url": "https://git.playones.com/arechen/EapilSkillMarket" + }, + "homepage": "https://git.playones.com/arechen/EapilSkillMarket", + "repository": "https://git.playones.com/arechen/EapilSkillMarket", + "license": "Proprietary", + "keywords": [ + "eapil", + "codex-skill", + "threejs" + ], + "skills": "./skills/", + "interface": { + "displayName": "threejs", + "shortDescription": "一套覆盖 Three.js 完整开发流程的 10 个专业技能,从基础场景搭建到高级视觉效果,助你快速构建专业级 3D 应用:\r\n核心基础\r\n- threejs-fundamentals - 场景、相机、渲染器配置\r\n- threejs-geometry - 几何体创建与自定义网格\r\n- threejs-materials - PBR 材质、ShaderMaterial 与材质优化\r\n视觉表现\r\n- threejs-lighting - 灯光类型、阴影配置与环境光照\r\n- threejs-textures - UV 映射、环境贴图与纹理优化\r\n- threejs-postprocessing - 后期处理、特效与画面调色\r\n交互与动画\r\n- threejs-animation - 关键帧动画、骨骼动画与动画混合\r\n- threejs-interaction - 射线检测、鼠标/触摸交互与相机控制\r\n高级特性\r\n- threejs-shaders - GLSL 着色器、自定义特效与顶点操作\r\n- threejs-loaders - GLTF/GLB 模型加载、资源管理与异步模式\r\n适合人群: WebGL 开发者、3D 可视化工程师、创意技术开发者\r\n使用方式: 在开发过程中按需调用对应技能,获得专业的 Three.js 开发指导与代码建议。", + "longDescription": "一套覆盖 Three.js 完整开发流程的 10 个专业技能,从基础场景搭建到高级视觉效果,助你快速构建专业级 3D 应用:\r\n核心基础\r\n- threejs-fundamentals - 场景、相机、渲染器配置\r\n- threejs-geometry - 几何体创建与自定义网格\r\n- threejs-materials - PBR 材质、ShaderMaterial 与材质优化\r\n视觉表现\r\n- threejs-lighting - 灯光类型、阴影配置与环境光照\r\n- threejs-textures - UV 映射、环境贴图与纹理优化\r\n- threejs-postprocessing - 后期处理、特效与画面调色\r\n交互与动画\r\n- threejs-animation - 关键帧动画、骨骼动画与动画混合\r\n- threejs-interaction - 射线检测、鼠标/触摸交互与相机控制\r\n高级特性\r\n- threejs-shaders - GLSL 着色器、自定义特效与顶点操作\r\n- threejs-loaders - GLTF/GLB 模型加载、资源管理与异步模式\r\n适合人群: WebGL 开发者、3D 可视化工程师、创意技术开发者\r\n使用方式: 在开发过程中按需调用对应技能,获得专业的 Three.js 开发指导与代码建议。", + "developerName": "EAPIL", + "category": "设计", + "capabilities": [ + "Read", + "Write" + ], + "defaultPrompt": [ + "使用 threejs 帮我完成这个任务。" + ], + "websiteURL": "https://git.playones.com/arechen/EapilSkillMarket", + "privacyPolicyURL": "https://git.playones.com/arechen/EapilSkillMarket", + "termsOfServiceURL": "https://git.playones.com/arechen/EapilSkillMarket", + "brandColor": "#2563EB", + "screenshots": [] + } +} diff --git a/plugins/codex/plugins/threejs/skills/threejs-animation/SKILL.md b/plugins/codex/plugins/threejs/skills/threejs-animation/SKILL.md new file mode 100644 index 000000000..91742cede --- /dev/null +++ b/plugins/codex/plugins/threejs/skills/threejs-animation/SKILL.md @@ -0,0 +1,552 @@ +--- +name: threejs-animation +description: Three.js animation - keyframe animation, skeletal animation, morph targets, animation mixing. Use when animating objects, playing GLTF animations, creating procedural motion, or blending animations. +--- + +# Three.js Animation + +## Quick Start + +```javascript +import * as THREE from "three"; + +// Simple procedural animation +const clock = new THREE.Clock(); + +function animate() { + const delta = clock.getDelta(); + const elapsed = clock.getElapsedTime(); + + mesh.rotation.y += delta; + mesh.position.y = Math.sin(elapsed) * 0.5; + + requestAnimationFrame(animate); + renderer.render(scene, camera); +} +animate(); +``` + +## Animation System Overview + +Three.js animation system has three main components: + +1. **AnimationClip** - Container for keyframe data +2. **AnimationMixer** - Plays animations on a root object +3. **AnimationAction** - Controls playback of a clip + +## AnimationClip + +Stores keyframe animation data. + +```javascript +// Create animation clip +const times = [0, 1, 2]; // Keyframe times (seconds) +const values = [0, 1, 0]; // Values at each keyframe + +const track = new THREE.NumberKeyframeTrack( + ".position[y]", // Property path + times, + values, +); + +const clip = new THREE.AnimationClip("bounce", 2, [track]); +``` + +### KeyframeTrack Types + +```javascript +// Number track (single value) +new THREE.NumberKeyframeTrack(".opacity", times, [1, 0]); +new THREE.NumberKeyframeTrack(".material.opacity", times, [1, 0]); + +// Vector track (position, scale) +new THREE.VectorKeyframeTrack(".position", times, [ + 0, + 0, + 0, // t=0 + 1, + 2, + 0, // t=1 + 0, + 0, + 0, // t=2 +]); + +// Quaternion track (rotation) +const q1 = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, 0, 0)); +const q2 = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, Math.PI, 0)); +new THREE.QuaternionKeyframeTrack( + ".quaternion", + [0, 1], + [q1.x, q1.y, q1.z, q1.w, q2.x, q2.y, q2.z, q2.w], +); + +// Color track +new THREE.ColorKeyframeTrack(".material.color", times, [ + 1, + 0, + 0, // red + 0, + 1, + 0, // green + 0, + 0, + 1, // blue +]); + +// Boolean track +new THREE.BooleanKeyframeTrack(".visible", [0, 0.5, 1], [true, false, true]); + +// String track (for morph targets) +new THREE.StringKeyframeTrack( + ".morphTargetInfluences[smile]", + [0, 1], + ["0", "1"], +); +``` + +### Interpolation Modes + +```javascript +const track = new THREE.VectorKeyframeTrack(".position", times, values); + +// Interpolation +track.setInterpolation(THREE.InterpolateLinear); // Default +track.setInterpolation(THREE.InterpolateSmooth); // Cubic spline +track.setInterpolation(THREE.InterpolateDiscrete); // Step function +``` + +## AnimationMixer + +Plays animations on an object and its descendants. + +```javascript +const mixer = new THREE.AnimationMixer(model); + +// Create action from clip +const action = mixer.clipAction(clip); +action.play(); + +// Update in animation loop +function animate() { + const delta = clock.getDelta(); + mixer.update(delta); // Required! + + requestAnimationFrame(animate); + renderer.render(scene, camera); +} +``` + +### Mixer Events + +```javascript +mixer.addEventListener("finished", (e) => { + console.log("Animation finished:", e.action.getClip().name); +}); + +mixer.addEventListener("loop", (e) => { + console.log("Animation looped:", e.action.getClip().name); +}); +``` + +## AnimationAction + +Controls playback of an animation clip. + +```javascript +const action = mixer.clipAction(clip); + +// Playback control +action.play(); +action.stop(); +action.reset(); +action.halt(fadeOutDuration); + +// Playback state +action.isRunning(); +action.isScheduled(); + +// Time control +action.time = 0.5; // Current time +action.timeScale = 1; // Playback speed (negative = reverse) +action.paused = false; + +// Weight (for blending) +action.weight = 1; // 0-1, contribution to final pose +action.setEffectiveWeight(1); + +// Loop modes +action.loop = THREE.LoopRepeat; // Default: loop forever +action.loop = THREE.LoopOnce; // Play once and stop +action.loop = THREE.LoopPingPong; // Alternate forward/backward +action.repetitions = 3; // Number of loops (Infinity default) + +// Clamping +action.clampWhenFinished = true; // Hold last frame when done + +// Blending +action.blendMode = THREE.NormalAnimationBlendMode; +action.blendMode = THREE.AdditiveAnimationBlendMode; +``` + +### Fade In/Out + +```javascript +// Fade in +action.reset().fadeIn(0.5).play(); + +// Fade out +action.fadeOut(0.5); + +// Crossfade between animations +const action1 = mixer.clipAction(clip1); +const action2 = mixer.clipAction(clip2); + +action1.play(); + +// Later, crossfade to action2 +action1.crossFadeTo(action2, 0.5, true); +action2.play(); +``` + +## Loading GLTF Animations + +Most common source of skeletal animations. + +```javascript +import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; + +const loader = new GLTFLoader(); +loader.load("model.glb", (gltf) => { + const model = gltf.scene; + scene.add(model); + + // Create mixer + const mixer = new THREE.AnimationMixer(model); + + // Get all clips + const clips = gltf.animations; + console.log( + "Available animations:", + clips.map((c) => c.name), + ); + + // Play first animation + if (clips.length > 0) { + const action = mixer.clipAction(clips[0]); + action.play(); + } + + // Play specific animation by name + const walkClip = THREE.AnimationClip.findByName(clips, "Walk"); + if (walkClip) { + mixer.clipAction(walkClip).play(); + } + + // Store mixer for update loop + window.mixer = mixer; +}); + +// Animation loop +function animate() { + const delta = clock.getDelta(); + if (window.mixer) window.mixer.update(delta); + + requestAnimationFrame(animate); + renderer.render(scene, camera); +} +``` + +## Skeletal Animation + +### Skeleton and Bones + +```javascript +// Access skeleton from skinned mesh +const skinnedMesh = model.getObjectByProperty("type", "SkinnedMesh"); +const skeleton = skinnedMesh.skeleton; + +// Access bones +skeleton.bones.forEach((bone) => { + console.log(bone.name, bone.position, bone.rotation); +}); + +// Find specific bone by name +const headBone = skeleton.bones.find((b) => b.name === "Head"); +if (headBone) headBone.rotation.y = Math.PI / 4; // Turn head + +// Skeleton helper +const helper = new THREE.SkeletonHelper(model); +scene.add(helper); +``` + +### Programmatic Bone Animation + +```javascript +function animate() { + const time = clock.getElapsedTime(); + + // Animate bone + const headBone = skeleton.bones.find((b) => b.name === "Head"); + if (headBone) { + headBone.rotation.y = Math.sin(time) * 0.3; + } + + // Update mixer if also playing clips + mixer.update(clock.getDelta()); +} +``` + +### Bone Attachments + +```javascript +// Attach object to bone +const weapon = new THREE.Mesh(weaponGeometry, weaponMaterial); +const handBone = skeleton.bones.find((b) => b.name === "RightHand"); +if (handBone) handBone.add(weapon); + +// Offset attachment +weapon.position.set(0, 0, 0.5); +weapon.rotation.set(0, Math.PI / 2, 0); +``` + +## Morph Targets + +Blend between different mesh shapes. + +```javascript +// Morph targets are stored in geometry +const geometry = mesh.geometry; +console.log("Morph attributes:", Object.keys(geometry.morphAttributes)); + +// Access morph target influences +mesh.morphTargetInfluences; // Array of weights +mesh.morphTargetDictionary; // Name -> index mapping + +// Set morph target by index +mesh.morphTargetInfluences[0] = 0.5; + +// Set by name +const smileIndex = mesh.morphTargetDictionary["smile"]; +mesh.morphTargetInfluences[smileIndex] = 1; +``` + +### Animating Morph Targets + +```javascript +// Procedural +function animate() { + const t = clock.getElapsedTime(); + mesh.morphTargetInfluences[0] = (Math.sin(t) + 1) / 2; +} + +// With keyframe animation +const track = new THREE.NumberKeyframeTrack( + ".morphTargetInfluences[smile]", + [0, 0.5, 1], + [0, 1, 0], +); +const clip = new THREE.AnimationClip("smile", 1, [track]); +mixer.clipAction(clip).play(); +``` + +## Animation Blending + +Mix multiple animations together. + +```javascript +// Setup actions +const idleAction = mixer.clipAction(idleClip); +const walkAction = mixer.clipAction(walkClip); +const runAction = mixer.clipAction(runClip); + +// Play all with different weights +idleAction.play(); +walkAction.play(); +runAction.play(); + +// Set initial weights +idleAction.setEffectiveWeight(1); +walkAction.setEffectiveWeight(0); +runAction.setEffectiveWeight(0); + +// Blend based on speed +function updateAnimations(speed) { + if (speed < 0.1) { + idleAction.setEffectiveWeight(1); + walkAction.setEffectiveWeight(0); + runAction.setEffectiveWeight(0); + } else if (speed < 5) { + const t = speed / 5; + idleAction.setEffectiveWeight(1 - t); + walkAction.setEffectiveWeight(t); + runAction.setEffectiveWeight(0); + } else { + const t = Math.min((speed - 5) / 5, 1); + idleAction.setEffectiveWeight(0); + walkAction.setEffectiveWeight(1 - t); + runAction.setEffectiveWeight(t); + } +} +``` + +### Additive Blending + +```javascript +// Base pose +const baseAction = mixer.clipAction(baseClip); +baseAction.play(); + +// Additive layer (e.g., breathing) +const additiveAction = mixer.clipAction(additiveClip); +additiveAction.blendMode = THREE.AdditiveAnimationBlendMode; +additiveAction.play(); + +// Convert clip to additive +THREE.AnimationUtils.makeClipAdditive(additiveClip); +``` + +## Animation Utilities + +```javascript +import * as THREE from "three"; + +// Find clip by name +const clip = THREE.AnimationClip.findByName(clips, "Walk"); + +// Create subclip +const subclip = THREE.AnimationUtils.subclip(clip, "subclip", 0, 30, 30); + +// Convert to additive +THREE.AnimationUtils.makeClipAdditive(clip); +THREE.AnimationUtils.makeClipAdditive(clip, 0, referenceClip); + +// Clone clip +const clone = clip.clone(); + +// Get clip duration +clip.duration; + +// Optimize clip (remove redundant keyframes) +clip.optimize(); + +// Reset clip to first frame +clip.resetDuration(); +``` + +## Procedural Animation Patterns + +### Smooth Damping + +```javascript +// Smooth follow/lerp +const target = new THREE.Vector3(); +const current = new THREE.Vector3(); +const velocity = new THREE.Vector3(); + +function smoothDamp(current, target, velocity, smoothTime, deltaTime) { + const omega = 2 / smoothTime; + const x = omega * deltaTime; + const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x); + const change = current.clone().sub(target); + const temp = velocity + .clone() + .add(change.clone().multiplyScalar(omega)) + .multiplyScalar(deltaTime); + velocity.sub(temp.clone().multiplyScalar(omega)).multiplyScalar(exp); + return target.clone().add(change.add(temp).multiplyScalar(exp)); +} + +function animate() { + current.copy(smoothDamp(current, target, velocity, 0.3, delta)); + mesh.position.copy(current); +} +``` + +### Spring Physics + +```javascript +class Spring { + constructor(stiffness = 100, damping = 10) { + this.stiffness = stiffness; + this.damping = damping; + this.position = 0; + this.velocity = 0; + this.target = 0; + } + + update(dt) { + const force = -this.stiffness * (this.position - this.target); + const dampingForce = -this.damping * this.velocity; + this.velocity += (force + dampingForce) * dt; + this.position += this.velocity * dt; + return this.position; + } +} + +const spring = new Spring(100, 10); +spring.target = 1; + +function animate() { + mesh.position.y = spring.update(delta); +} +``` + +### Oscillation + +```javascript +function animate() { + const t = clock.getElapsedTime(); + + // Sine wave + mesh.position.y = Math.sin(t * 2) * 0.5; + + // Bouncing + mesh.position.y = Math.abs(Math.sin(t * 3)) * 2; + + // Circular motion + mesh.position.x = Math.cos(t) * 2; + mesh.position.z = Math.sin(t) * 2; + + // Figure 8 + mesh.position.x = Math.sin(t) * 2; + mesh.position.z = Math.sin(t * 2) * 1; +} +``` + +## Performance Tips + +1. **Share clips**: Same AnimationClip can be used on multiple mixers +2. **Optimize clips**: Call `clip.optimize()` to remove redundant keyframes +3. **Disable when off-screen**: Stop mixer updates for invisible objects +4. **Use LOD for animations**: Simpler rigs for distant characters +5. **Limit active mixers**: Each mixer.update() has a cost + +```javascript +// Pause animation when not visible +mesh.onBeforeRender = () => { + action.paused = false; +}; + +mesh.onAfterRender = () => { + // Check if will be visible next frame + if (!isInFrustum(mesh)) { + action.paused = true; + } +}; + +// Cache clips +const clipCache = new Map(); +function getClip(name) { + if (!clipCache.has(name)) { + clipCache.set(name, loadClip(name)); + } + return clipCache.get(name); +} +``` + +## See Also + +- `threejs-loaders` - Loading animated GLTF models +- `threejs-fundamentals` - Clock and animation loop +- `threejs-shaders` - Vertex animation in shaders diff --git a/plugins/codex/plugins/threejs/skills/threejs-fundamentals/SKILL.md b/plugins/codex/plugins/threejs/skills/threejs-fundamentals/SKILL.md new file mode 100644 index 000000000..21b634124 --- /dev/null +++ b/plugins/codex/plugins/threejs/skills/threejs-fundamentals/SKILL.md @@ -0,0 +1,488 @@ +--- +name: threejs-fundamentals +description: Three.js scene setup, cameras, renderer, Object3D hierarchy, coordinate systems. Use when setting up 3D scenes, creating cameras, configuring renderers, managing object hierarchies, or working with transforms. +--- + +# Three.js Fundamentals + +## Quick Start + +```javascript +import * as THREE from "three"; + +// Create scene, camera, renderer +const scene = new THREE.Scene(); +const camera = new THREE.PerspectiveCamera( + 75, + window.innerWidth / window.innerHeight, + 0.1, + 1000, +); +const renderer = new THREE.WebGLRenderer({ antialias: true }); + +renderer.setSize(window.innerWidth, window.innerHeight); +renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); +document.body.appendChild(renderer.domElement); + +// Add a mesh +const geometry = new THREE.BoxGeometry(1, 1, 1); +const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 }); +const cube = new THREE.Mesh(geometry, material); +scene.add(cube); + +// Add light +scene.add(new THREE.AmbientLight(0xffffff, 0.5)); +const dirLight = new THREE.DirectionalLight(0xffffff, 1); +dirLight.position.set(5, 5, 5); +scene.add(dirLight); + +camera.position.z = 5; + +// Animation loop +function animate() { + requestAnimationFrame(animate); + cube.rotation.x += 0.01; + cube.rotation.y += 0.01; + renderer.render(scene, camera); +} +animate(); + +// Handle resize +window.addEventListener("resize", () => { + camera.aspect = window.innerWidth / window.innerHeight; + camera.updateProjectionMatrix(); + renderer.setSize(window.innerWidth, window.innerHeight); +}); +``` + +## Core Classes + +### Scene + +Container for all 3D objects, lights, and cameras. + +```javascript +const scene = new THREE.Scene(); +scene.background = new THREE.Color(0x000000); // Solid color +scene.background = texture; // Skybox texture +scene.background = cubeTexture; // Cubemap +scene.environment = envMap; // Environment map for PBR +scene.fog = new THREE.Fog(0xffffff, 1, 100); // Linear fog +scene.fog = new THREE.FogExp2(0xffffff, 0.02); // Exponential fog +``` + +### Cameras + +**PerspectiveCamera** - Most common, simulates human eye. + +```javascript +// PerspectiveCamera(fov, aspect, near, far) +const camera = new THREE.PerspectiveCamera( + 75, // Field of view (degrees) + window.innerWidth / window.innerHeight, // Aspect ratio + 0.1, // Near clipping plane + 1000, // Far clipping plane +); + +camera.position.set(0, 5, 10); +camera.lookAt(0, 0, 0); +camera.updateProjectionMatrix(); // Call after changing fov, aspect, near, far +``` + +**OrthographicCamera** - No perspective distortion, good for 2D/isometric. + +```javascript +// OrthographicCamera(left, right, top, bottom, near, far) +const aspect = window.innerWidth / window.innerHeight; +const frustumSize = 10; +const camera = new THREE.OrthographicCamera( + (frustumSize * aspect) / -2, + (frustumSize * aspect) / 2, + frustumSize / 2, + frustumSize / -2, + 0.1, + 1000, +); +``` + +**ArrayCamera** - Multiple viewports with sub-cameras. + +```javascript +const cameras = []; +for (let i = 0; i < 4; i++) { + const subcamera = new THREE.PerspectiveCamera(40, 1, 0.1, 100); + subcamera.viewport = new THREE.Vector4( + Math.floor(i % 2) * 0.5, + Math.floor(i / 2) * 0.5, + 0.5, + 0.5, + ); + cameras.push(subcamera); +} +const arrayCamera = new THREE.ArrayCamera(cameras); +``` + +**CubeCamera** - Renders environment maps for reflections. + +```javascript +const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256); +const cubeCamera = new THREE.CubeCamera(0.1, 1000, cubeRenderTarget); +scene.add(cubeCamera); + +// Use for reflections +material.envMap = cubeRenderTarget.texture; + +// Update each frame (expensive!) +cubeCamera.position.copy(reflectiveMesh.position); +cubeCamera.update(renderer, scene); +``` + +### WebGLRenderer + +```javascript +const renderer = new THREE.WebGLRenderer({ + canvas: document.querySelector("#canvas"), // Optional existing canvas + antialias: true, // Smooth edges + alpha: true, // Transparent background + powerPreference: "high-performance", // GPU hint + preserveDrawingBuffer: true, // For screenshots +}); + +renderer.setSize(width, height); +renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); + +// Tone mapping +renderer.toneMapping = THREE.ACESFilmicToneMapping; +renderer.toneMappingExposure = 1.0; + +// Color space (Three.js r152+) +renderer.outputColorSpace = THREE.SRGBColorSpace; + +// Shadows +renderer.shadowMap.enabled = true; +renderer.shadowMap.type = THREE.PCFSoftShadowMap; + +// Clear color +renderer.setClearColor(0x000000, 1); + +// Render +renderer.render(scene, camera); +``` + +### Object3D + +Base class for all 3D objects. Mesh, Group, Light, Camera all extend Object3D. + +```javascript +const obj = new THREE.Object3D(); + +// Transform +obj.position.set(x, y, z); +obj.rotation.set(x, y, z); // Euler angles (radians) +obj.quaternion.set(x, y, z, w); // Quaternion rotation +obj.scale.set(x, y, z); + +// Local vs World transforms +obj.getWorldPosition(targetVector); +obj.getWorldQuaternion(targetQuaternion); +obj.getWorldDirection(targetVector); + +// Hierarchy +obj.add(child); +obj.remove(child); +obj.parent; +obj.children; + +// Visibility +obj.visible = false; + +// Layers (for selective rendering/raycasting) +obj.layers.set(1); +obj.layers.enable(2); +obj.layers.disable(0); + +// Traverse hierarchy +obj.traverse((child) => { + if (child.isMesh) child.material.color.set(0xff0000); +}); + +// Matrix updates +obj.matrixAutoUpdate = true; // Default: auto-update matrices +obj.updateMatrix(); // Manual matrix update +obj.updateMatrixWorld(true); // Update world matrix recursively +``` + +### Group + +Empty container for organizing objects. + +```javascript +const group = new THREE.Group(); +group.add(mesh1); +group.add(mesh2); +scene.add(group); + +// Transform entire group +group.position.x = 5; +group.rotation.y = Math.PI / 4; +``` + +### Mesh + +Combines geometry and material. + +```javascript +const mesh = new THREE.Mesh(geometry, material); + +// Multiple materials (one per geometry group) +const mesh = new THREE.Mesh(geometry, [material1, material2]); + +// Useful properties +mesh.geometry; +mesh.material; +mesh.castShadow = true; +mesh.receiveShadow = true; + +// Frustum culling +mesh.frustumCulled = true; // Default: skip if outside camera view + +// Render order +mesh.renderOrder = 10; // Higher = rendered later +``` + +## Coordinate System + +Three.js uses a **right-handed coordinate system**: + +- **+X** points right +- **+Y** points up +- **+Z** points toward viewer (out of screen) + +```javascript +// Axes helper +const axesHelper = new THREE.AxesHelper(5); +scene.add(axesHelper); // Red=X, Green=Y, Blue=Z +``` + +## Math Utilities + +### Vector3 + +```javascript +const v = new THREE.Vector3(x, y, z); +v.set(x, y, z); +v.copy(otherVector); +v.clone(); + +// Operations (modify in place) +v.add(v2); +v.sub(v2); +v.multiply(v2); +v.multiplyScalar(2); +v.divideScalar(2); +v.normalize(); +v.negate(); +v.clamp(min, max); +v.lerp(target, alpha); + +// Calculations (return new value) +v.length(); +v.lengthSq(); // Faster than length() +v.distanceTo(v2); +v.dot(v2); +v.cross(v2); // Modifies v +v.angleTo(v2); + +// Transform +v.applyMatrix4(matrix); +v.applyQuaternion(q); +v.project(camera); // World to NDC +v.unproject(camera); // NDC to world +``` + +### Matrix4 + +```javascript +const m = new THREE.Matrix4(); +m.identity(); +m.copy(other); +m.clone(); + +// Build transforms +m.makeTranslation(x, y, z); +m.makeRotationX(theta); +m.makeRotationY(theta); +m.makeRotationZ(theta); +m.makeRotationFromQuaternion(q); +m.makeScale(x, y, z); + +// Compose/decompose +m.compose(position, quaternion, scale); +m.decompose(position, quaternion, scale); + +// Operations +m.multiply(m2); // m = m * m2 +m.premultiply(m2); // m = m2 * m +m.invert(); +m.transpose(); + +// Camera matrices +m.makePerspective(left, right, top, bottom, near, far); +m.makeOrthographic(left, right, top, bottom, near, far); +m.lookAt(eye, target, up); +``` + +### Quaternion + +```javascript +const q = new THREE.Quaternion(); +q.setFromEuler(euler); +q.setFromAxisAngle(axis, angle); +q.setFromRotationMatrix(matrix); + +q.multiply(q2); +q.slerp(target, t); // Spherical interpolation +q.normalize(); +q.invert(); +``` + +### Euler + +```javascript +const euler = new THREE.Euler(x, y, z, "XYZ"); // Order matters! +euler.setFromQuaternion(q); +euler.setFromRotationMatrix(m); + +// Rotation orders: 'XYZ', 'YXZ', 'ZXY', 'XZY', 'YZX', 'ZYX' +``` + +### Color + +```javascript +const color = new THREE.Color(0xff0000); +const color = new THREE.Color("red"); +const color = new THREE.Color("rgb(255, 0, 0)"); +const color = new THREE.Color("#ff0000"); + +color.setHex(0x00ff00); +color.setRGB(r, g, b); // 0-1 range +color.setHSL(h, s, l); // 0-1 range + +color.lerp(otherColor, alpha); +color.multiply(otherColor); +color.multiplyScalar(2); +``` + +### MathUtils + +```javascript +THREE.MathUtils.clamp(value, min, max); +THREE.MathUtils.lerp(start, end, alpha); +THREE.MathUtils.mapLinear(value, inMin, inMax, outMin, outMax); +THREE.MathUtils.degToRad(degrees); +THREE.MathUtils.radToDeg(radians); +THREE.MathUtils.randFloat(min, max); +THREE.MathUtils.randInt(min, max); +THREE.MathUtils.smoothstep(x, min, max); +THREE.MathUtils.smootherstep(x, min, max); +``` + +## Common Patterns + +### Proper Cleanup + +```javascript +function dispose() { + // Dispose geometries + mesh.geometry.dispose(); + + // Dispose materials + if (Array.isArray(mesh.material)) { + mesh.material.forEach((m) => m.dispose()); + } else { + mesh.material.dispose(); + } + + // Dispose textures + texture.dispose(); + + // Remove from scene + scene.remove(mesh); + + // Dispose renderer + renderer.dispose(); +} +``` + +### Clock for Animation + +```javascript +const clock = new THREE.Clock(); + +function animate() { + const delta = clock.getDelta(); // Time since last frame (seconds) + const elapsed = clock.getElapsedTime(); // Total time (seconds) + + mesh.rotation.y += delta * 0.5; // Consistent speed regardless of framerate + + requestAnimationFrame(animate); + renderer.render(scene, camera); +} +``` + +### Responsive Canvas + +```javascript +function onWindowResize() { + const width = window.innerWidth; + const height = window.innerHeight; + + camera.aspect = width / height; + camera.updateProjectionMatrix(); + + renderer.setSize(width, height); + renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); +} +window.addEventListener("resize", onWindowResize); +``` + +### Loading Manager + +```javascript +const manager = new THREE.LoadingManager(); + +manager.onStart = (url, loaded, total) => console.log("Started loading"); +manager.onLoad = () => console.log("All loaded"); +manager.onProgress = (url, loaded, total) => console.log(`${loaded}/${total}`); +manager.onError = (url) => console.error(`Error loading ${url}`); + +const textureLoader = new THREE.TextureLoader(manager); +const gltfLoader = new GLTFLoader(manager); +``` + +## Performance Tips + +1. **Limit draw calls**: Merge geometries, use instancing, atlas textures +2. **Frustum culling**: Enabled by default, ensure bounding boxes are correct +3. **LOD (Level of Detail)**: Use `THREE.LOD` for distance-based mesh switching +4. **Object pooling**: Reuse objects instead of creating/destroying +5. **Avoid `getWorldPosition` in loops**: Cache results + +```javascript +// Merge static geometries +import { mergeGeometries } from "three/examples/jsm/utils/BufferGeometryUtils.js"; +const merged = mergeGeometries([geo1, geo2, geo3]); + +// LOD +const lod = new THREE.LOD(); +lod.addLevel(highDetailMesh, 0); +lod.addLevel(medDetailMesh, 50); +lod.addLevel(lowDetailMesh, 100); +scene.add(lod); +``` + +## See Also + +- `threejs-geometry` - Geometry creation and manipulation +- `threejs-materials` - Material types and properties +- `threejs-lighting` - Light types and shadows diff --git a/plugins/codex/plugins/threejs/skills/threejs-geometry/SKILL.md b/plugins/codex/plugins/threejs/skills/threejs-geometry/SKILL.md new file mode 100644 index 000000000..e077e2d78 --- /dev/null +++ b/plugins/codex/plugins/threejs/skills/threejs-geometry/SKILL.md @@ -0,0 +1,548 @@ +--- +name: threejs-geometry +description: Three.js geometry creation - built-in shapes, BufferGeometry, custom geometry, instancing. Use when creating 3D shapes, working with vertices, building custom meshes, or optimizing with instanced rendering. +--- + +# Three.js Geometry + +## Quick Start + +```javascript +import * as THREE from "three"; + +// Built-in geometry +const box = new THREE.BoxGeometry(1, 1, 1); +const sphere = new THREE.SphereGeometry(0.5, 32, 32); +const plane = new THREE.PlaneGeometry(10, 10); + +// Create mesh +const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 }); +const mesh = new THREE.Mesh(box, material); +scene.add(mesh); +``` + +## Built-in Geometries + +### Basic Shapes + +```javascript +// Box - width, height, depth, widthSegments, heightSegments, depthSegments +new THREE.BoxGeometry(1, 1, 1, 1, 1, 1); + +// Sphere - radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength +new THREE.SphereGeometry(1, 32, 32); +new THREE.SphereGeometry(1, 32, 32, 0, Math.PI * 2, 0, Math.PI); // Full sphere +new THREE.SphereGeometry(1, 32, 32, 0, Math.PI); // Hemisphere + +// Plane - width, height, widthSegments, heightSegments +new THREE.PlaneGeometry(10, 10, 1, 1); + +// Circle - radius, segments, thetaStart, thetaLength +new THREE.CircleGeometry(1, 32); +new THREE.CircleGeometry(1, 32, 0, Math.PI); // Semicircle + +// Cylinder - radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded +new THREE.CylinderGeometry(1, 1, 2, 32, 1, false); +new THREE.CylinderGeometry(0, 1, 2, 32); // Cone +new THREE.CylinderGeometry(1, 1, 2, 6); // Hexagonal prism + +// Cone - radius, height, radialSegments, heightSegments, openEnded +new THREE.ConeGeometry(1, 2, 32, 1, false); + +// Torus - radius, tube, radialSegments, tubularSegments, arc +new THREE.TorusGeometry(1, 0.4, 16, 100); + +// TorusKnot - radius, tube, tubularSegments, radialSegments, p, q +new THREE.TorusKnotGeometry(1, 0.4, 100, 16, 2, 3); + +// Ring - innerRadius, outerRadius, thetaSegments, phiSegments +new THREE.RingGeometry(0.5, 1, 32, 1); +``` + +### Advanced Shapes + +```javascript +// Capsule - radius, length, capSegments, radialSegments +new THREE.CapsuleGeometry(0.5, 1, 4, 8); + +// Dodecahedron - radius, detail +new THREE.DodecahedronGeometry(1, 0); + +// Icosahedron - radius, detail (0 = 20 faces, higher = smoother) +new THREE.IcosahedronGeometry(1, 0); + +// Octahedron - radius, detail +new THREE.OctahedronGeometry(1, 0); + +// Tetrahedron - radius, detail +new THREE.TetrahedronGeometry(1, 0); + +// Polyhedron - vertices, indices, radius, detail +const vertices = [1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1]; +const indices = [2, 1, 0, 0, 3, 2, 1, 3, 0, 2, 3, 1]; +new THREE.PolyhedronGeometry(vertices, indices, 1, 0); +``` + +### Path-Based Shapes + +```javascript +// Lathe - points[], segments, phiStart, phiLength +const points = [ + new THREE.Vector2(0, 0), + new THREE.Vector2(0.5, 0), + new THREE.Vector2(0.5, 1), + new THREE.Vector2(0, 1), +]; +new THREE.LatheGeometry(points, 32); + +// Extrude - shape, options +const shape = new THREE.Shape(); +shape.moveTo(0, 0); +shape.lineTo(1, 0); +shape.lineTo(1, 1); +shape.lineTo(0, 1); +shape.lineTo(0, 0); + +const extrudeSettings = { + steps: 2, + depth: 1, + bevelEnabled: true, + bevelThickness: 0.1, + bevelSize: 0.1, + bevelSegments: 3, +}; +new THREE.ExtrudeGeometry(shape, extrudeSettings); + +// Tube - path, tubularSegments, radius, radialSegments, closed +const curve = new THREE.CatmullRomCurve3([ + new THREE.Vector3(-1, 0, 0), + new THREE.Vector3(0, 1, 0), + new THREE.Vector3(1, 0, 0), +]); +new THREE.TubeGeometry(curve, 64, 0.2, 8, false); +``` + +### Text Geometry + +```javascript +import { FontLoader } from "three/examples/jsm/loaders/FontLoader.js"; +import { TextGeometry } from "three/examples/jsm/geometries/TextGeometry.js"; + +const loader = new FontLoader(); +loader.load("fonts/helvetiker_regular.typeface.json", (font) => { + const geometry = new TextGeometry("Hello", { + font: font, + size: 1, + depth: 0.2, // Was 'height' in older versions + curveSegments: 12, + bevelEnabled: true, + bevelThickness: 0.03, + bevelSize: 0.02, + bevelSegments: 5, + }); + + // Center text + geometry.computeBoundingBox(); + geometry.center(); + + const mesh = new THREE.Mesh(geometry, material); + scene.add(mesh); +}); +``` + +## BufferGeometry + +The base class for all geometries. Stores data as typed arrays for GPU efficiency. + +### Custom BufferGeometry + +```javascript +const geometry = new THREE.BufferGeometry(); + +// Vertices (3 floats per vertex: x, y, z) +const vertices = new Float32Array([ + -1, + -1, + 0, // vertex 0 + 1, + -1, + 0, // vertex 1 + 1, + 1, + 0, // vertex 2 + -1, + 1, + 0, // vertex 3 +]); +geometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3)); + +// Indices (for indexed geometry - reuse vertices) +const indices = new Uint16Array([ + 0, + 1, + 2, // triangle 1 + 0, + 2, + 3, // triangle 2 +]); +geometry.setIndex(new THREE.BufferAttribute(indices, 1)); + +// Normals (required for lighting) +const normals = new Float32Array([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]); +geometry.setAttribute("normal", new THREE.BufferAttribute(normals, 3)); + +// UVs (for texturing) +const uvs = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]); +geometry.setAttribute("uv", new THREE.BufferAttribute(uvs, 2)); + +// Colors (per-vertex colors) +const colors = new Float32Array([ + 1, + 0, + 0, // red + 0, + 1, + 0, // green + 0, + 0, + 1, // blue + 1, + 1, + 0, // yellow +]); +geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3)); +// Use with: material.vertexColors = true +``` + +### BufferAttribute Types + +```javascript +// Common attribute types +new THREE.BufferAttribute(array, itemSize); + +// Typed array options +new Float32Array(count * itemSize); // Positions, normals, UVs +new Uint16Array(count); // Indices (up to 65535 vertices) +new Uint32Array(count); // Indices (larger meshes) +new Uint8Array(count * itemSize); // Colors (0-255 range) + +// Item sizes +// Position: 3 (x, y, z) +// Normal: 3 (x, y, z) +// UV: 2 (u, v) +// Color: 3 (r, g, b) or 4 (r, g, b, a) +// Index: 1 +``` + +### Modifying BufferGeometry + +```javascript +const positions = geometry.attributes.position; + +// Modify vertex +positions.setXYZ(index, x, y, z); + +// Access vertex +const x = positions.getX(index); +const y = positions.getY(index); +const z = positions.getZ(index); + +// Flag for GPU update +positions.needsUpdate = true; + +// Recompute normals after position changes +geometry.computeVertexNormals(); + +// Recompute bounding box/sphere after changes +geometry.computeBoundingBox(); +geometry.computeBoundingSphere(); +``` + +### Interleaved Buffers (Advanced) + +```javascript +// More efficient memory layout for large meshes +const interleavedBuffer = new THREE.InterleavedBuffer( + new Float32Array([ + // pos.x, pos.y, pos.z, uv.u, uv.v (repeated per vertex) + -1, -1, 0, 0, 0, 1, -1, 0, 1, 0, 1, 1, 0, 1, 1, -1, 1, 0, 0, 1, + ]), + 5, // stride (floats per vertex) +); + +geometry.setAttribute( + "position", + new THREE.InterleavedBufferAttribute(interleavedBuffer, 3, 0), +); // size 3, offset 0 +geometry.setAttribute( + "uv", + new THREE.InterleavedBufferAttribute(interleavedBuffer, 2, 3), +); // size 2, offset 3 +``` + +## EdgesGeometry & WireframeGeometry + +```javascript +// Edge lines (only hard edges) +const edges = new THREE.EdgesGeometry(boxGeometry, 15); // 15 = threshold angle +const edgeMesh = new THREE.LineSegments( + edges, + new THREE.LineBasicMaterial({ color: 0xffffff }), +); + +// Wireframe (all triangles) +const wireframe = new THREE.WireframeGeometry(boxGeometry); +const wireMesh = new THREE.LineSegments( + wireframe, + new THREE.LineBasicMaterial({ color: 0xffffff }), +); +``` + +## Points + +```javascript +// Create point cloud +const geometry = new THREE.BufferGeometry(); +const positions = new Float32Array(1000 * 3); + +for (let i = 0; i < 1000; i++) { + positions[i * 3] = (Math.random() - 0.5) * 10; + positions[i * 3 + 1] = (Math.random() - 0.5) * 10; + positions[i * 3 + 2] = (Math.random() - 0.5) * 10; +} + +geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3)); + +const material = new THREE.PointsMaterial({ + size: 0.1, + sizeAttenuation: true, // Size decreases with distance + color: 0xffffff, +}); + +const points = new THREE.Points(geometry, material); +scene.add(points); +``` + +## Lines + +```javascript +// Line (connected points) +const points = [ + new THREE.Vector3(-1, 0, 0), + new THREE.Vector3(0, 1, 0), + new THREE.Vector3(1, 0, 0), +]; +const geometry = new THREE.BufferGeometry().setFromPoints(points); +const line = new THREE.Line( + geometry, + new THREE.LineBasicMaterial({ color: 0xff0000 }), +); + +// LineLoop (closed loop) +const loop = new THREE.LineLoop(geometry, material); + +// LineSegments (pairs of points) +const segmentsGeometry = new THREE.BufferGeometry(); +segmentsGeometry.setAttribute( + "position", + new THREE.BufferAttribute( + new Float32Array([ + -1, + 0, + 0, + 0, + 1, + 0, // segment 1 + 0, + 1, + 0, + 1, + 0, + 0, // segment 2 + ]), + 3, + ), +); +const segments = new THREE.LineSegments(segmentsGeometry, material); +``` + +## InstancedMesh + +Efficiently render many copies of the same geometry. + +```javascript +const geometry = new THREE.BoxGeometry(1, 1, 1); +const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 }); +const count = 1000; + +const instancedMesh = new THREE.InstancedMesh(geometry, material, count); + +// Set transforms for each instance +const dummy = new THREE.Object3D(); +const matrix = new THREE.Matrix4(); + +for (let i = 0; i < count; i++) { + dummy.position.set( + (Math.random() - 0.5) * 20, + (Math.random() - 0.5) * 20, + (Math.random() - 0.5) * 20, + ); + dummy.rotation.set(Math.random() * Math.PI, Math.random() * Math.PI, 0); + dummy.scale.setScalar(0.5 + Math.random()); + dummy.updateMatrix(); + + instancedMesh.setMatrixAt(i, dummy.matrix); +} + +// Flag for GPU update +instancedMesh.instanceMatrix.needsUpdate = true; + +// Optional: per-instance colors +instancedMesh.instanceColor = new THREE.InstancedBufferAttribute( + new Float32Array(count * 3), + 3, +); +for (let i = 0; i < count; i++) { + instancedMesh.setColorAt( + i, + new THREE.Color(Math.random(), Math.random(), Math.random()), + ); +} +instancedMesh.instanceColor.needsUpdate = true; + +scene.add(instancedMesh); +``` + +### Update Instance at Runtime + +```javascript +// Update single instance +const matrix = new THREE.Matrix4(); +instancedMesh.getMatrixAt(index, matrix); +// Modify matrix... +instancedMesh.setMatrixAt(index, matrix); +instancedMesh.instanceMatrix.needsUpdate = true; + +// Raycasting with instanced mesh +const intersects = raycaster.intersectObject(instancedMesh); +if (intersects.length > 0) { + const instanceId = intersects[0].instanceId; +} +``` + +## InstancedBufferGeometry (Advanced) + +For custom per-instance attributes beyond transform/color. + +```javascript +const geometry = new THREE.InstancedBufferGeometry(); +geometry.copy(new THREE.BoxGeometry(1, 1, 1)); + +// Add per-instance attribute +const offsets = new Float32Array(count * 3); +for (let i = 0; i < count; i++) { + offsets[i * 3] = Math.random() * 10; + offsets[i * 3 + 1] = Math.random() * 10; + offsets[i * 3 + 2] = Math.random() * 10; +} +geometry.setAttribute("offset", new THREE.InstancedBufferAttribute(offsets, 3)); + +// Use in shader +// attribute vec3 offset; +// vec3 transformed = position + offset; +``` + +## Geometry Utilities + +```javascript +import * as BufferGeometryUtils from "three/examples/jsm/utils/BufferGeometryUtils.js"; + +// Merge geometries (must have same attributes) +const merged = BufferGeometryUtils.mergeGeometries([geo1, geo2, geo3]); + +// Merge with groups (for multi-material) +const merged = BufferGeometryUtils.mergeGeometries([geo1, geo2], true); + +// Compute tangents (required for normal maps) +BufferGeometryUtils.computeTangents(geometry); + +// Interleave attributes for better performance +const interleaved = BufferGeometryUtils.interleaveAttributes([ + geometry.attributes.position, + geometry.attributes.normal, + geometry.attributes.uv, +]); +``` + +## Common Patterns + +### Center Geometry + +```javascript +geometry.computeBoundingBox(); +geometry.center(); // Move vertices so center is at origin +``` + +### Scale to Fit + +```javascript +geometry.computeBoundingBox(); +const size = new THREE.Vector3(); +geometry.boundingBox.getSize(size); +const maxDim = Math.max(size.x, size.y, size.z); +geometry.scale(1 / maxDim, 1 / maxDim, 1 / maxDim); +``` + +### Clone and Transform + +```javascript +const clone = geometry.clone(); +clone.rotateX(Math.PI / 2); +clone.translate(0, 1, 0); +clone.scale(2, 2, 2); +``` + +### Morph Targets + +```javascript +// Base geometry +const geometry = new THREE.BoxGeometry(1, 1, 1, 4, 4, 4); + +// Create morph target +const morphPositions = geometry.attributes.position.array.slice(); +for (let i = 0; i < morphPositions.length; i += 3) { + morphPositions[i] *= 2; // Scale X + morphPositions[i + 1] *= 0.5; // Squash Y +} + +geometry.morphAttributes.position = [ + new THREE.BufferAttribute(new Float32Array(morphPositions), 3), +]; + +const mesh = new THREE.Mesh(geometry, material); +mesh.morphTargetInfluences[0] = 0.5; // 50% blend +``` + +## Performance Tips + +1. **Use indexed geometry**: Reuse vertices with indices +2. **Merge static meshes**: Reduce draw calls with `mergeGeometries` +3. **Use InstancedMesh**: For many identical objects +4. **Choose appropriate segment counts**: More segments = smoother but slower +5. **Dispose unused geometry**: `geometry.dispose()` + +```javascript +// Good segment counts for common uses +new THREE.SphereGeometry(1, 32, 32); // Good quality +new THREE.SphereGeometry(1, 64, 64); // High quality +new THREE.SphereGeometry(1, 16, 16); // Performance mode + +// Dispose when done +geometry.dispose(); +``` + +## See Also + +- `threejs-fundamentals` - Scene setup and Object3D +- `threejs-materials` - Material types for meshes +- `threejs-shaders` - Custom vertex manipulation diff --git a/plugins/codex/plugins/threejs/skills/threejs-interaction/SKILL.md b/plugins/codex/plugins/threejs/skills/threejs-interaction/SKILL.md new file mode 100644 index 000000000..9c93424ee --- /dev/null +++ b/plugins/codex/plugins/threejs/skills/threejs-interaction/SKILL.md @@ -0,0 +1,660 @@ +--- +name: threejs-interaction +description: Three.js interaction - raycasting, controls, mouse/touch input, object selection. Use when handling user input, implementing click detection, adding camera controls, or creating interactive 3D experiences. +--- + +# Three.js Interaction + +## Quick Start + +```javascript +import * as THREE from "three"; +import { OrbitControls } from "three/addons/controls/OrbitControls.js"; + +// Camera controls +const controls = new OrbitControls(camera, renderer.domElement); +controls.enableDamping = true; + +// Raycasting for click detection +const raycaster = new THREE.Raycaster(); +const mouse = new THREE.Vector2(); + +function onClick(event) { + mouse.x = (event.clientX / window.innerWidth) * 2 - 1; + mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; + + raycaster.setFromCamera(mouse, camera); + const intersects = raycaster.intersectObjects(scene.children); + + if (intersects.length > 0) { + console.log("Clicked:", intersects[0].object); + } +} + +window.addEventListener("click", onClick); +``` + +## Raycaster + +### Basic Raycasting + +```javascript +const raycaster = new THREE.Raycaster(); + +// From camera (mouse picking) +raycaster.setFromCamera(mousePosition, camera); + +// From any origin and direction +raycaster.set(origin, direction); // origin: Vector3, direction: normalized Vector3 + +// Get intersections +const intersects = raycaster.intersectObjects(objects, recursive); + +// intersects array contains: +// { +// distance: number, // Distance from ray origin +// point: Vector3, // Intersection point in world coords +// face: Face3, // Intersected face +// faceIndex: number, // Face index +// object: Object3D, // Intersected object +// uv: Vector2, // UV coordinates at intersection +// uv1: Vector2, // Second UV channel +// normal: Vector3, // Interpolated face normal +// instanceId: number // For InstancedMesh +// } +``` + +### Mouse Position Conversion + +```javascript +const mouse = new THREE.Vector2(); + +function updateMouse(event) { + // For full window + mouse.x = (event.clientX / window.innerWidth) * 2 - 1; + mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; +} + +// For specific canvas element +function updateMouseCanvas(event, canvas) { + const rect = canvas.getBoundingClientRect(); + mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; + mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; +} +``` + +### Touch Support + +```javascript +function onTouchStart(event) { + event.preventDefault(); + + if (event.touches.length === 1) { + const touch = event.touches[0]; + mouse.x = (touch.clientX / window.innerWidth) * 2 - 1; + mouse.y = -(touch.clientY / window.innerHeight) * 2 + 1; + + raycaster.setFromCamera(mouse, camera); + const intersects = raycaster.intersectObjects(clickableObjects); + + if (intersects.length > 0) { + handleSelection(intersects[0]); + } + } +} + +renderer.domElement.addEventListener("touchstart", onTouchStart); +``` + +### Raycaster Options + +```javascript +const raycaster = new THREE.Raycaster(); + +// Near/far clipping (default: 0, Infinity) +raycaster.near = 0; +raycaster.far = 100; + +// Line/Points precision +raycaster.params.Line.threshold = 0.1; +raycaster.params.Points.threshold = 0.1; + +// Layers (only intersect objects on specific layers) +raycaster.layers.set(1); +``` + +### Efficient Raycasting + +```javascript +// Only check specific objects +const clickables = [mesh1, mesh2, mesh3]; +const intersects = raycaster.intersectObjects(clickables, false); + +// Use layers for filtering +mesh1.layers.set(1); // Clickable layer +raycaster.layers.set(1); + +// Throttle raycast for hover effects +let lastRaycast = 0; +function onMouseMove(event) { + const now = Date.now(); + if (now - lastRaycast < 50) return; // 20fps max + lastRaycast = now; + + // Raycast here +} +``` + +## Camera Controls + +### OrbitControls + +```javascript +import { OrbitControls } from "three/addons/controls/OrbitControls.js"; + +const controls = new OrbitControls(camera, renderer.domElement); + +// Damping (smooth movement) +controls.enableDamping = true; +controls.dampingFactor = 0.05; + +// Rotation limits +controls.minPolarAngle = 0; // Top +controls.maxPolarAngle = Math.PI / 2; // Horizon +controls.minAzimuthAngle = -Math.PI / 4; // Left +controls.maxAzimuthAngle = Math.PI / 4; // Right + +// Zoom limits +controls.minDistance = 2; +controls.maxDistance = 50; + +// Enable/disable features +controls.enableRotate = true; +controls.enableZoom = true; +controls.enablePan = true; + +// Auto-rotate +controls.autoRotate = true; +controls.autoRotateSpeed = 2.0; + +// Target (orbit point) +controls.target.set(0, 1, 0); + +// Update in animation loop +function animate() { + controls.update(); // Required for damping and auto-rotate + renderer.render(scene, camera); +} +``` + +### FlyControls + +```javascript +import { FlyControls } from "three/addons/controls/FlyControls.js"; + +const controls = new FlyControls(camera, renderer.domElement); +controls.movementSpeed = 10; +controls.rollSpeed = Math.PI / 24; +controls.dragToLook = true; + +// Update with delta +function animate() { + controls.update(clock.getDelta()); + renderer.render(scene, camera); +} +``` + +### FirstPersonControls + +```javascript +import { FirstPersonControls } from "three/addons/controls/FirstPersonControls.js"; + +const controls = new FirstPersonControls(camera, renderer.domElement); +controls.movementSpeed = 10; +controls.lookSpeed = 0.1; +controls.lookVertical = true; +controls.constrainVertical = true; +controls.verticalMin = Math.PI / 4; +controls.verticalMax = (Math.PI * 3) / 4; + +function animate() { + controls.update(clock.getDelta()); +} +``` + +### PointerLockControls + +```javascript +import { PointerLockControls } from "three/addons/controls/PointerLockControls.js"; + +const controls = new PointerLockControls(camera, document.body); + +// Lock pointer on click +document.addEventListener("click", () => { + controls.lock(); +}); + +controls.addEventListener("lock", () => { + console.log("Pointer locked"); +}); + +controls.addEventListener("unlock", () => { + console.log("Pointer unlocked"); +}); + +// Movement +const velocity = new THREE.Vector3(); +const direction = new THREE.Vector3(); +const moveForward = false; +const moveBackward = false; + +document.addEventListener("keydown", (event) => { + switch (event.code) { + case "KeyW": + moveForward = true; + break; + case "KeyS": + moveBackward = true; + break; + } +}); + +function animate() { + if (controls.isLocked) { + direction.z = Number(moveForward) - Number(moveBackward); + direction.normalize(); + + velocity.z -= direction.z * 0.1; + velocity.z *= 0.9; // Friction + + controls.moveForward(-velocity.z); + } +} +``` + +### TrackballControls + +```javascript +import { TrackballControls } from "three/addons/controls/TrackballControls.js"; + +const controls = new TrackballControls(camera, renderer.domElement); +controls.rotateSpeed = 2.0; +controls.zoomSpeed = 1.2; +controls.panSpeed = 0.8; +controls.staticMoving = true; + +function animate() { + controls.update(); +} +``` + +### MapControls + +```javascript +import { MapControls } from "three/addons/controls/MapControls.js"; + +const controls = new MapControls(camera, renderer.domElement); +controls.enableDamping = true; +controls.dampingFactor = 0.05; +controls.screenSpacePanning = false; +controls.maxPolarAngle = Math.PI / 2; +``` + +## TransformControls + +Gizmo for moving/rotating/scaling objects. + +```javascript +import { TransformControls } from "three/addons/controls/TransformControls.js"; + +const transformControls = new TransformControls(camera, renderer.domElement); +scene.add(transformControls); + +// Attach to object +transformControls.attach(selectedMesh); + +// Switch modes +transformControls.setMode("translate"); // 'translate', 'rotate', 'scale' + +// Change space +transformControls.setSpace("local"); // 'local', 'world' + +// Size +transformControls.setSize(1); + +// Events +transformControls.addEventListener("dragging-changed", (event) => { + // Disable orbit controls while dragging + orbitControls.enabled = !event.value; +}); + +transformControls.addEventListener("change", () => { + renderer.render(scene, camera); +}); + +// Keyboard shortcuts +window.addEventListener("keydown", (event) => { + switch (event.key) { + case "g": + transformControls.setMode("translate"); + break; + case "r": + transformControls.setMode("rotate"); + break; + case "s": + transformControls.setMode("scale"); + break; + case "Escape": + transformControls.detach(); + break; + } +}); +``` + +## DragControls + +Drag objects directly. + +```javascript +import { DragControls } from "three/addons/controls/DragControls.js"; + +const draggableObjects = [mesh1, mesh2, mesh3]; +const dragControls = new DragControls( + draggableObjects, + camera, + renderer.domElement, +); + +dragControls.addEventListener("dragstart", (event) => { + orbitControls.enabled = false; + event.object.material.emissive.set(0xaaaaaa); +}); + +dragControls.addEventListener("drag", (event) => { + // Constrain to ground plane + event.object.position.y = 0; +}); + +dragControls.addEventListener("dragend", (event) => { + orbitControls.enabled = true; + event.object.material.emissive.set(0x000000); +}); +``` + +## Selection System + +### Click to Select + +```javascript +const raycaster = new THREE.Raycaster(); +const mouse = new THREE.Vector2(); +let selectedObject = null; + +function onMouseDown(event) { + mouse.x = (event.clientX / window.innerWidth) * 2 - 1; + mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; + + raycaster.setFromCamera(mouse, camera); + const intersects = raycaster.intersectObjects(selectableObjects); + + // Deselect previous + if (selectedObject) { + selectedObject.material.emissive.set(0x000000); + } + + // Select new + if (intersects.length > 0) { + selectedObject = intersects[0].object; + selectedObject.material.emissive.set(0x444444); + } else { + selectedObject = null; + } +} +``` + +### Box Selection + +```javascript +import { SelectionBox } from "three/addons/interactive/SelectionBox.js"; +import { SelectionHelper } from "three/addons/interactive/SelectionHelper.js"; + +const selectionBox = new SelectionBox(camera, scene); +const selectionHelper = new SelectionHelper(renderer, "selectBox"); // CSS class + +document.addEventListener("pointerdown", (event) => { + selectionBox.startPoint.set( + (event.clientX / window.innerWidth) * 2 - 1, + -(event.clientY / window.innerHeight) * 2 + 1, + 0.5, + ); +}); + +document.addEventListener("pointermove", (event) => { + if (selectionHelper.isDown) { + selectionBox.endPoint.set( + (event.clientX / window.innerWidth) * 2 - 1, + -(event.clientY / window.innerHeight) * 2 + 1, + 0.5, + ); + } +}); + +document.addEventListener("pointerup", (event) => { + selectionBox.endPoint.set( + (event.clientX / window.innerWidth) * 2 - 1, + -(event.clientY / window.innerHeight) * 2 + 1, + 0.5, + ); + + const selected = selectionBox.select(); + console.log("Selected objects:", selected); +}); +``` + +### Hover Effects + +```javascript +const raycaster = new THREE.Raycaster(); +const mouse = new THREE.Vector2(); +let hoveredObject = null; + +function onMouseMove(event) { + mouse.x = (event.clientX / window.innerWidth) * 2 - 1; + mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; + + raycaster.setFromCamera(mouse, camera); + const intersects = raycaster.intersectObjects(hoverableObjects); + + // Reset previous hover + if (hoveredObject) { + hoveredObject.material.color.set(hoveredObject.userData.originalColor); + document.body.style.cursor = "default"; + } + + // Apply new hover + if (intersects.length > 0) { + hoveredObject = intersects[0].object; + if (!hoveredObject.userData.originalColor) { + hoveredObject.userData.originalColor = + hoveredObject.material.color.getHex(); + } + hoveredObject.material.color.set(0xff6600); + document.body.style.cursor = "pointer"; + } else { + hoveredObject = null; + } +} + +window.addEventListener("mousemove", onMouseMove); +``` + +## Keyboard Input + +```javascript +const keys = {}; + +document.addEventListener("keydown", (event) => { + keys[event.code] = true; +}); + +document.addEventListener("keyup", (event) => { + keys[event.code] = false; +}); + +function update() { + const speed = 0.1; + + if (keys["KeyW"]) player.position.z -= speed; + if (keys["KeyS"]) player.position.z += speed; + if (keys["KeyA"]) player.position.x -= speed; + if (keys["KeyD"]) player.position.x += speed; + if (keys["Space"]) player.position.y += speed; + if (keys["ShiftLeft"]) player.position.y -= speed; +} +``` + +## World-Screen Coordinate Conversion + +### World to Screen + +```javascript +function worldToScreen(position, camera) { + const vector = position.clone(); + vector.project(camera); + + return { + x: ((vector.x + 1) / 2) * window.innerWidth, + y: (-(vector.y - 1) / 2) * window.innerHeight, + }; +} + +// Position HTML element over 3D object +const screenPos = worldToScreen(mesh.position, camera); +element.style.left = screenPos.x + "px"; +element.style.top = screenPos.y + "px"; +``` + +### Screen to World + +```javascript +function screenToWorld(screenX, screenY, camera, targetZ = 0) { + const vector = new THREE.Vector3( + (screenX / window.innerWidth) * 2 - 1, + -(screenY / window.innerHeight) * 2 + 1, + 0.5, + ); + + vector.unproject(camera); + + const dir = vector.sub(camera.position).normalize(); + const distance = (targetZ - camera.position.z) / dir.z; + + return camera.position.clone().add(dir.multiplyScalar(distance)); +} +``` + +### Ray-Plane Intersection + +```javascript +function getRayPlaneIntersection(mouse, camera, plane) { + const raycaster = new THREE.Raycaster(); + raycaster.setFromCamera(mouse, camera); + + const intersection = new THREE.Vector3(); + raycaster.ray.intersectPlane(plane, intersection); + + return intersection; +} + +// Ground plane +const groundPlane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0); +const worldPos = getRayPlaneIntersection(mouse, camera, groundPlane); +``` + +## Event Handling Best Practices + +```javascript +class InteractionManager { + constructor(camera, renderer, scene) { + this.camera = camera; + this.renderer = renderer; + this.scene = scene; + this.raycaster = new THREE.Raycaster(); + this.mouse = new THREE.Vector2(); + this.clickables = []; + + this.bindEvents(); + } + + bindEvents() { + const canvas = this.renderer.domElement; + + canvas.addEventListener("click", (e) => this.onClick(e)); + canvas.addEventListener("mousemove", (e) => this.onMouseMove(e)); + canvas.addEventListener("touchstart", (e) => this.onTouchStart(e)); + } + + updateMouse(event) { + const rect = this.renderer.domElement.getBoundingClientRect(); + this.mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; + this.mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; + } + + getIntersects() { + this.raycaster.setFromCamera(this.mouse, this.camera); + return this.raycaster.intersectObjects(this.clickables, true); + } + + onClick(event) { + this.updateMouse(event); + const intersects = this.getIntersects(); + + if (intersects.length > 0) { + const object = intersects[0].object; + if (object.userData.onClick) { + object.userData.onClick(intersects[0]); + } + } + } + + addClickable(object, callback) { + this.clickables.push(object); + object.userData.onClick = callback; + } + + dispose() { + // Remove event listeners + } +} + +// Usage +const interaction = new InteractionManager(camera, renderer, scene); +interaction.addClickable(mesh, (intersect) => { + console.log("Clicked at:", intersect.point); +}); +``` + +## Performance Tips + +1. **Limit raycasts**: Throttle mousemove handlers +2. **Use layers**: Filter raycast targets +3. **Simple collision meshes**: Use invisible simpler geometry for raycasting +4. **Disable controls when not needed**: `controls.enabled = false` +5. **Batch updates**: Group interaction checks + +```javascript +// Use simpler geometry for raycasting +const complexMesh = loadedModel; +const collisionMesh = new THREE.Mesh( + new THREE.BoxGeometry(1, 1, 1), + new THREE.MeshBasicMaterial({ visible: false }), +); +collisionMesh.userData.target = complexMesh; +clickables.push(collisionMesh); +``` + +## See Also + +- `threejs-fundamentals` - Camera and scene setup +- `threejs-animation` - Animating interactions +- `threejs-shaders` - Visual feedback effects diff --git a/plugins/codex/plugins/threejs/skills/threejs-lighting/SKILL.md b/plugins/codex/plugins/threejs/skills/threejs-lighting/SKILL.md new file mode 100644 index 000000000..575cb0c47 --- /dev/null +++ b/plugins/codex/plugins/threejs/skills/threejs-lighting/SKILL.md @@ -0,0 +1,481 @@ +--- +name: threejs-lighting +description: Three.js lighting - light types, shadows, environment lighting. Use when adding lights, configuring shadows, setting up IBL, or optimizing lighting performance. +--- + +# Three.js Lighting + +## Quick Start + +```javascript +import * as THREE from "three"; + +// Basic lighting setup +const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); +scene.add(ambientLight); + +const directionalLight = new THREE.DirectionalLight(0xffffff, 1); +directionalLight.position.set(5, 5, 5); +scene.add(directionalLight); +``` + +## Light Types Overview + +| Light | Description | Shadow Support | Cost | +| ---------------- | ---------------------- | -------------- | -------- | +| AmbientLight | Uniform everywhere | No | Very Low | +| HemisphereLight | Sky/ground gradient | No | Very Low | +| DirectionalLight | Parallel rays (sun) | Yes | Low | +| PointLight | Omnidirectional (bulb) | Yes | Medium | +| SpotLight | Cone-shaped | Yes | Medium | +| RectAreaLight | Area light (window) | No\* | High | + +\*RectAreaLight shadows require custom solutions + +## AmbientLight + +Illuminates all objects equally. No direction, no shadows. + +```javascript +// AmbientLight(color, intensity) +const ambient = new THREE.AmbientLight(0xffffff, 0.5); +scene.add(ambient); + +// Modify at runtime +ambient.color.set(0xffffcc); +ambient.intensity = 0.3; +``` + +## HemisphereLight + +Gradient from sky to ground color. Good for outdoor scenes. + +```javascript +// HemisphereLight(skyColor, groundColor, intensity) +const hemi = new THREE.HemisphereLight(0x87ceeb, 0x8b4513, 0.6); +hemi.position.set(0, 50, 0); +scene.add(hemi); + +// Properties +hemi.color; // Sky color +hemi.groundColor; // Ground color +hemi.intensity; +``` + +## DirectionalLight + +Parallel light rays. Simulates distant light source (sun). + +```javascript +// DirectionalLight(color, intensity) +const dirLight = new THREE.DirectionalLight(0xffffff, 1); +dirLight.position.set(5, 10, 5); + +// Light points at target (default: 0, 0, 0) +dirLight.target.position.set(0, 0, 0); +scene.add(dirLight.target); + +scene.add(dirLight); +``` + +### DirectionalLight Shadows + +```javascript +dirLight.castShadow = true; + +// Shadow map size (higher = sharper, more expensive) +dirLight.shadow.mapSize.width = 2048; +dirLight.shadow.mapSize.height = 2048; + +// Shadow camera (orthographic) +dirLight.shadow.camera.near = 0.5; +dirLight.shadow.camera.far = 50; +dirLight.shadow.camera.left = -10; +dirLight.shadow.camera.right = 10; +dirLight.shadow.camera.top = 10; +dirLight.shadow.camera.bottom = -10; + +// Shadow softness +dirLight.shadow.radius = 4; // Blur radius (PCFSoftShadowMap only) + +// Shadow bias (fixes shadow acne) +dirLight.shadow.bias = -0.0001; +dirLight.shadow.normalBias = 0.02; + +// Helper to visualize shadow camera +const helper = new THREE.CameraHelper(dirLight.shadow.camera); +scene.add(helper); +``` + +## PointLight + +Emits light in all directions from a point. Like a light bulb. + +```javascript +// PointLight(color, intensity, distance, decay) +const pointLight = new THREE.PointLight(0xffffff, 1, 100, 2); +pointLight.position.set(0, 5, 0); +scene.add(pointLight); + +// Properties +pointLight.distance; // Maximum range (0 = infinite) +pointLight.decay; // Light falloff (physically correct = 2) +``` + +### PointLight Shadows + +```javascript +pointLight.castShadow = true; +pointLight.shadow.mapSize.width = 1024; +pointLight.shadow.mapSize.height = 1024; + +// Shadow camera (perspective - 6 directions for cube map) +pointLight.shadow.camera.near = 0.5; +pointLight.shadow.camera.far = 50; + +pointLight.shadow.bias = -0.005; +``` + +## SpotLight + +Cone-shaped light. Like a flashlight or stage light. + +```javascript +// SpotLight(color, intensity, distance, angle, penumbra, decay) +const spotLight = new THREE.SpotLight(0xffffff, 1, 100, Math.PI / 6, 0.5, 2); +spotLight.position.set(0, 10, 0); + +// Target (light points at this) +spotLight.target.position.set(0, 0, 0); +scene.add(spotLight.target); + +scene.add(spotLight); + +// Properties +spotLight.angle; // Cone angle (radians, max Math.PI/2) +spotLight.penumbra; // Soft edge (0-1) +spotLight.distance; // Range +spotLight.decay; // Falloff +``` + +### SpotLight Shadows + +```javascript +spotLight.castShadow = true; +spotLight.shadow.mapSize.width = 1024; +spotLight.shadow.mapSize.height = 1024; + +// Shadow camera (perspective) +spotLight.shadow.camera.near = 0.5; +spotLight.shadow.camera.far = 50; +spotLight.shadow.camera.fov = 30; + +spotLight.shadow.bias = -0.0001; + +// Focus (affects shadow projection) +spotLight.shadow.focus = 1; +``` + +## RectAreaLight + +Rectangular area light. Great for soft, realistic lighting. + +```javascript +import { RectAreaLightHelper } from "three/examples/jsm/helpers/RectAreaLightHelper.js"; +import { RectAreaLightUniformsLib } from "three/examples/jsm/lights/RectAreaLightUniformsLib.js"; + +// Must initialize uniforms first +RectAreaLightUniformsLib.init(); + +// RectAreaLight(color, intensity, width, height) +const rectLight = new THREE.RectAreaLight(0xffffff, 5, 4, 2); +rectLight.position.set(0, 5, 0); +rectLight.lookAt(0, 0, 0); +scene.add(rectLight); + +// Helper +const helper = new RectAreaLightHelper(rectLight); +rectLight.add(helper); + +// Note: Only works with MeshStandardMaterial and MeshPhysicalMaterial +// Does not cast shadows natively +``` + +## Shadow Setup + +### Enable Shadows + +```javascript +// 1. Enable on renderer +renderer.shadowMap.enabled = true; +renderer.shadowMap.type = THREE.PCFSoftShadowMap; + +// Shadow map types: +// THREE.BasicShadowMap - fastest, low quality +// THREE.PCFShadowMap - default, filtered +// THREE.PCFSoftShadowMap - softer edges +// THREE.VSMShadowMap - variance shadow map + +// 2. Enable on light +light.castShadow = true; + +// 3. Enable on objects +mesh.castShadow = true; +mesh.receiveShadow = true; + +// Ground plane +floor.receiveShadow = true; +floor.castShadow = false; // Usually false for floors +``` + +### Optimizing Shadows + +```javascript +// Tight shadow camera frustum +const d = 10; +dirLight.shadow.camera.left = -d; +dirLight.shadow.camera.right = d; +dirLight.shadow.camera.top = d; +dirLight.shadow.camera.bottom = -d; +dirLight.shadow.camera.near = 0.5; +dirLight.shadow.camera.far = 30; + +// Fix shadow acne +dirLight.shadow.bias = -0.0001; // Depth bias +dirLight.shadow.normalBias = 0.02; // Bias along normal + +// Shadow map size (balance quality vs performance) +// 512 - low quality +// 1024 - medium quality +// 2048 - high quality +// 4096 - very high quality (expensive) +``` + +### Contact Shadows (Fake, Fast) + +```javascript +import { ContactShadows } from "three/examples/jsm/objects/ContactShadows.js"; + +const contactShadows = new ContactShadows({ + resolution: 512, + blur: 2, + opacity: 0.5, + scale: 10, + position: [0, 0, 0], +}); +scene.add(contactShadows); +``` + +## Light Helpers + +```javascript +import { RectAreaLightHelper } from "three/examples/jsm/helpers/RectAreaLightHelper.js"; + +// DirectionalLight helper +const dirHelper = new THREE.DirectionalLightHelper(dirLight, 5); +scene.add(dirHelper); + +// PointLight helper +const pointHelper = new THREE.PointLightHelper(pointLight, 1); +scene.add(pointHelper); + +// SpotLight helper +const spotHelper = new THREE.SpotLightHelper(spotLight); +scene.add(spotHelper); + +// Hemisphere helper +const hemiHelper = new THREE.HemisphereLightHelper(hemiLight, 5); +scene.add(hemiHelper); + +// RectAreaLight helper +const rectHelper = new RectAreaLightHelper(rectLight); +rectLight.add(rectHelper); + +// Update helpers when light changes +dirHelper.update(); +spotHelper.update(); +``` + +## Environment Lighting (IBL) + +Image-Based Lighting using HDR environment maps. + +```javascript +import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"; + +const rgbeLoader = new RGBELoader(); +rgbeLoader.load("environment.hdr", (texture) => { + texture.mapping = THREE.EquirectangularReflectionMapping; + + // Set as scene environment (affects all PBR materials) + scene.environment = texture; + + // Optional: also use as background + scene.background = texture; + scene.backgroundBlurriness = 0; // 0-1, blur the background + scene.backgroundIntensity = 1; +}); + +// PMREMGenerator for better reflections +const pmremGenerator = new THREE.PMREMGenerator(renderer); +pmremGenerator.compileEquirectangularShader(); + +rgbeLoader.load("environment.hdr", (texture) => { + const envMap = pmremGenerator.fromEquirectangular(texture).texture; + scene.environment = envMap; + texture.dispose(); + pmremGenerator.dispose(); +}); +``` + +### Cube Texture Environment + +```javascript +const cubeLoader = new THREE.CubeTextureLoader(); +const envMap = cubeLoader.load([ + "px.jpg", + "nx.jpg", + "py.jpg", + "ny.jpg", + "pz.jpg", + "nz.jpg", +]); + +scene.environment = envMap; +scene.background = envMap; +``` + +## Light Probes (Advanced) + +Capture lighting from a point in space for ambient lighting. + +```javascript +import { LightProbeGenerator } from "three/examples/jsm/lights/LightProbeGenerator.js"; + +// Generate from cube texture +const lightProbe = new THREE.LightProbe(); +scene.add(lightProbe); + +lightProbe.copy(LightProbeGenerator.fromCubeTexture(cubeTexture)); + +// Or from render target +const cubeCamera = new THREE.CubeCamera( + 0.1, + 100, + new THREE.WebGLCubeRenderTarget(256), +); +cubeCamera.update(renderer, scene); +lightProbe.copy( + LightProbeGenerator.fromCubeRenderTarget(renderer, cubeCamera.renderTarget), +); +``` + +## Common Lighting Setups + +### Three-Point Lighting + +```javascript +// Key light (main light) +const keyLight = new THREE.DirectionalLight(0xffffff, 1); +keyLight.position.set(5, 5, 5); +scene.add(keyLight); + +// Fill light (softer, opposite side) +const fillLight = new THREE.DirectionalLight(0xffffff, 0.5); +fillLight.position.set(-5, 3, 5); +scene.add(fillLight); + +// Back light (rim lighting) +const backLight = new THREE.DirectionalLight(0xffffff, 0.3); +backLight.position.set(0, 5, -5); +scene.add(backLight); + +// Ambient fill +const ambient = new THREE.AmbientLight(0x404040, 0.3); +scene.add(ambient); +``` + +### Outdoor Daylight + +```javascript +// Sun +const sun = new THREE.DirectionalLight(0xffffcc, 1.5); +sun.position.set(50, 100, 50); +sun.castShadow = true; +scene.add(sun); + +// Sky ambient +const hemi = new THREE.HemisphereLight(0x87ceeb, 0x8b4513, 0.6); +scene.add(hemi); +``` + +### Indoor Studio + +```javascript +// Multiple area lights +RectAreaLightUniformsLib.init(); + +const light1 = new THREE.RectAreaLight(0xffffff, 5, 2, 2); +light1.position.set(3, 3, 3); +light1.lookAt(0, 0, 0); +scene.add(light1); + +const light2 = new THREE.RectAreaLight(0xffffff, 3, 2, 2); +light2.position.set(-3, 3, 3); +light2.lookAt(0, 0, 0); +scene.add(light2); + +// Ambient fill +const ambient = new THREE.AmbientLight(0x404040, 0.2); +scene.add(ambient); +``` + +## Light Animation + +```javascript +const clock = new THREE.Clock(); + +function animate() { + const time = clock.getElapsedTime(); + + // Orbit light around scene + light.position.x = Math.cos(time) * 5; + light.position.z = Math.sin(time) * 5; + + // Pulsing intensity + light.intensity = 1 + Math.sin(time * 2) * 0.5; + + // Color cycling + light.color.setHSL((time * 0.1) % 1, 1, 0.5); + + // Update helpers if using + lightHelper.update(); +} +``` + +## Performance Tips + +1. **Limit light count**: Each light adds shader complexity +2. **Use baked lighting**: For static scenes, bake to textures +3. **Smaller shadow maps**: 512-1024 often sufficient +4. **Tight shadow frustums**: Only cover needed area +5. **Disable unused shadows**: Not all lights need shadows +6. **Use light layers**: Exclude objects from certain lights + +```javascript +// Light layers +light.layers.set(1); // Light only affects layer 1 +mesh.layers.enable(1); // Mesh is on layer 1 +otherMesh.layers.disable(1); // Other mesh not affected + +// Selective shadows +mesh.castShadow = true; +mesh.receiveShadow = true; +decorMesh.castShadow = false; // Small objects often don't need to cast +``` + +## See Also + +- `threejs-materials` - Material light response +- `threejs-textures` - Lightmaps and environment maps +- `threejs-postprocessing` - Bloom and other light effects diff --git a/plugins/codex/plugins/threejs/skills/threejs-loaders/SKILL.md b/plugins/codex/plugins/threejs/skills/threejs-loaders/SKILL.md new file mode 100644 index 000000000..4754a7aaa --- /dev/null +++ b/plugins/codex/plugins/threejs/skills/threejs-loaders/SKILL.md @@ -0,0 +1,623 @@ +--- +name: threejs-loaders +description: Three.js asset loading - GLTF, textures, images, models, async patterns. Use when loading 3D models, textures, HDR environments, or managing loading progress. +--- + +# Three.js Loaders + +## Quick Start + +```javascript +import * as THREE from "three"; +import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"; + +// Load GLTF model +const loader = new GLTFLoader(); +loader.load("model.glb", (gltf) => { + scene.add(gltf.scene); +}); + +// Load texture +const textureLoader = new THREE.TextureLoader(); +const texture = textureLoader.load("texture.jpg"); +``` + +## LoadingManager + +Coordinate multiple loaders and track progress. + +```javascript +const manager = new THREE.LoadingManager(); + +// Callbacks +manager.onStart = (url, loaded, total) => { + console.log(`Started loading: ${url}`); +}; + +manager.onLoad = () => { + console.log("All assets loaded!"); + startGame(); +}; + +manager.onProgress = (url, loaded, total) => { + const progress = (loaded / total) * 100; + console.log(`Loading: ${progress.toFixed(1)}%`); + updateProgressBar(progress); +}; + +manager.onError = (url) => { + console.error(`Error loading: ${url}`); +}; + +// Use manager with loaders +const textureLoader = new THREE.TextureLoader(manager); +const gltfLoader = new GLTFLoader(manager); + +// Load assets +textureLoader.load("texture1.jpg"); +textureLoader.load("texture2.jpg"); +gltfLoader.load("model.glb"); +// onLoad fires when ALL are complete +``` + +## Texture Loading + +### TextureLoader + +```javascript +const loader = new THREE.TextureLoader(); + +// Callback style +loader.load( + "texture.jpg", + (texture) => { + // onLoad + material.map = texture; + material.needsUpdate = true; + }, + undefined, // onProgress - not supported for image loading + (error) => { + // onError + console.error("Error loading texture", error); + }, +); + +// Synchronous (returns texture, loads async) +const texture = loader.load("texture.jpg"); +material.map = texture; +``` + +### Texture Configuration + +```javascript +const texture = loader.load("texture.jpg", (tex) => { + // Color space (important for color accuracy) + tex.colorSpace = THREE.SRGBColorSpace; // For color/albedo maps + // tex.colorSpace = THREE.LinearSRGBColorSpace; // For data maps (normal, roughness) + + // Wrapping + tex.wrapS = THREE.RepeatWrapping; + tex.wrapT = THREE.RepeatWrapping; + // ClampToEdgeWrapping, RepeatWrapping, MirroredRepeatWrapping + + // Repeat/offset + tex.repeat.set(2, 2); + tex.offset.set(0.5, 0.5); + tex.rotation = Math.PI / 4; + tex.center.set(0.5, 0.5); + + // Filtering + tex.minFilter = THREE.LinearMipmapLinearFilter; // Default + tex.magFilter = THREE.LinearFilter; // Default + // NearestFilter - pixelated + // LinearFilter - smooth + // LinearMipmapLinearFilter - smooth with mipmaps + + // Anisotropic filtering (sharper at angles) + tex.anisotropy = renderer.capabilities.getMaxAnisotropy(); + + // Flip Y (usually true for standard textures) + tex.flipY = true; + + tex.needsUpdate = true; +}); +``` + +### CubeTextureLoader + +For environment maps and skyboxes. + +```javascript +const loader = new THREE.CubeTextureLoader(); + +// Load 6 faces +const cubeTexture = loader.load([ + "px.jpg", + "nx.jpg", // positive/negative X + "py.jpg", + "ny.jpg", // positive/negative Y + "pz.jpg", + "nz.jpg", // positive/negative Z +]); + +// Use as background +scene.background = cubeTexture; + +// Use as environment map +scene.environment = cubeTexture; +material.envMap = cubeTexture; +``` + +### HDR/EXR Loading + +```javascript +import { RGBELoader } from "three/addons/loaders/RGBELoader.js"; +import { EXRLoader } from "three/addons/loaders/EXRLoader.js"; + +// HDR +const rgbeLoader = new RGBELoader(); +rgbeLoader.load("environment.hdr", (texture) => { + texture.mapping = THREE.EquirectangularReflectionMapping; + scene.environment = texture; + scene.background = texture; +}); + +// EXR +const exrLoader = new EXRLoader(); +exrLoader.load("environment.exr", (texture) => { + texture.mapping = THREE.EquirectangularReflectionMapping; + scene.environment = texture; +}); +``` + +### PMREMGenerator + +Generate prefiltered environment maps for PBR. + +```javascript +import { RGBELoader } from "three/addons/loaders/RGBELoader.js"; + +const pmremGenerator = new THREE.PMREMGenerator(renderer); +pmremGenerator.compileEquirectangularShader(); + +new RGBELoader().load("environment.hdr", (texture) => { + const envMap = pmremGenerator.fromEquirectangular(texture).texture; + + scene.environment = envMap; + scene.background = envMap; + + texture.dispose(); + pmremGenerator.dispose(); +}); +``` + +## GLTF/GLB Loading + +The most common 3D format for web. + +```javascript +import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"; + +const loader = new GLTFLoader(); + +loader.load("model.glb", (gltf) => { + // The loaded scene + const model = gltf.scene; + scene.add(model); + + // Animations + const animations = gltf.animations; + if (animations.length > 0) { + const mixer = new THREE.AnimationMixer(model); + animations.forEach((clip) => { + mixer.clipAction(clip).play(); + }); + } + + // Cameras (if any) + const cameras = gltf.cameras; + + // Asset info + console.log(gltf.asset); // Version, generator, etc. + + // User data from Blender/etc + console.log(gltf.userData); +}); +``` + +### GLTF with Draco Compression + +```javascript +import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"; +import { DRACOLoader } from "three/addons/loaders/DRACOLoader.js"; + +const dracoLoader = new DRACOLoader(); +dracoLoader.setDecoderPath( + "https://www.gstatic.com/draco/versioned/decoders/1.5.6/", +); +dracoLoader.preload(); + +const gltfLoader = new GLTFLoader(); +gltfLoader.setDRACOLoader(dracoLoader); + +gltfLoader.load("compressed-model.glb", (gltf) => { + scene.add(gltf.scene); +}); +``` + +### GLTF with KTX2 Textures + +```javascript +import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"; +import { KTX2Loader } from "three/addons/loaders/KTX2Loader.js"; + +const ktx2Loader = new KTX2Loader(); +ktx2Loader.setTranscoderPath( + "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/basis/", +); +ktx2Loader.detectSupport(renderer); + +const gltfLoader = new GLTFLoader(); +gltfLoader.setKTX2Loader(ktx2Loader); + +gltfLoader.load("model-with-ktx2.glb", (gltf) => { + scene.add(gltf.scene); +}); +``` + +### Process GLTF Content + +```javascript +loader.load("model.glb", (gltf) => { + const model = gltf.scene; + + // Enable shadows + model.traverse((child) => { + if (child.isMesh) { + child.castShadow = true; + child.receiveShadow = true; + } + }); + + // Find specific mesh + const head = model.getObjectByName("Head"); + + // Adjust materials + model.traverse((child) => { + if (child.isMesh && child.material) { + child.material.envMapIntensity = 0.5; + } + }); + + // Center and scale + const box = new THREE.Box3().setFromObject(model); + const center = box.getCenter(new THREE.Vector3()); + const size = box.getSize(new THREE.Vector3()); + + model.position.sub(center); + const maxDim = Math.max(size.x, size.y, size.z); + model.scale.setScalar(1 / maxDim); + + scene.add(model); +}); +``` + +## Other Model Formats + +### OBJ + MTL + +```javascript +import { OBJLoader } from "three/addons/loaders/OBJLoader.js"; +import { MTLLoader } from "three/addons/loaders/MTLLoader.js"; + +const mtlLoader = new MTLLoader(); +mtlLoader.load("model.mtl", (materials) => { + materials.preload(); + + const objLoader = new OBJLoader(); + objLoader.setMaterials(materials); + objLoader.load("model.obj", (object) => { + scene.add(object); + }); +}); +``` + +### FBX + +```javascript +import { FBXLoader } from "three/addons/loaders/FBXLoader.js"; + +const loader = new FBXLoader(); +loader.load("model.fbx", (object) => { + // FBX often has large scale + object.scale.setScalar(0.01); + + // Animations + const mixer = new THREE.AnimationMixer(object); + object.animations.forEach((clip) => { + mixer.clipAction(clip).play(); + }); + + scene.add(object); +}); +``` + +### STL + +```javascript +import { STLLoader } from "three/addons/loaders/STLLoader.js"; + +const loader = new STLLoader(); +loader.load("model.stl", (geometry) => { + const material = new THREE.MeshStandardMaterial({ color: 0x888888 }); + const mesh = new THREE.Mesh(geometry, material); + scene.add(mesh); +}); +``` + +### PLY + +```javascript +import { PLYLoader } from "three/addons/loaders/PLYLoader.js"; + +const loader = new PLYLoader(); +loader.load("model.ply", (geometry) => { + geometry.computeVertexNormals(); + const material = new THREE.MeshStandardMaterial({ vertexColors: true }); + const mesh = new THREE.Mesh(geometry, material); + scene.add(mesh); +}); +``` + +## Async/Promise Loading + +### Promisified Loader + +```javascript +function loadModel(url) { + return new Promise((resolve, reject) => { + loader.load(url, resolve, undefined, reject); + }); +} + +// Usage +async function init() { + try { + const gltf = await loadModel("model.glb"); + scene.add(gltf.scene); + } catch (error) { + console.error("Failed to load model:", error); + } +} +``` + +### Load Multiple Assets + +```javascript +async function loadAssets() { + const [modelGltf, envTexture, colorTexture] = await Promise.all([ + loadGLTF("model.glb"), + loadRGBE("environment.hdr"), + loadTexture("color.jpg"), + ]); + + scene.add(modelGltf.scene); + scene.environment = envTexture; + material.map = colorTexture; +} + +// Helper functions +function loadGLTF(url) { + return new Promise((resolve, reject) => { + new GLTFLoader().load(url, resolve, undefined, reject); + }); +} + +function loadRGBE(url) { + return new Promise((resolve, reject) => { + new RGBELoader().load( + url, + (texture) => { + texture.mapping = THREE.EquirectangularReflectionMapping; + resolve(texture); + }, + undefined, + reject, + ); + }); +} + +function loadTexture(url) { + return new Promise((resolve, reject) => { + new THREE.TextureLoader().load(url, resolve, undefined, reject); + }); +} +``` + +## Caching + +### Built-in Cache + +```javascript +// Enable cache +THREE.Cache.enabled = true; + +// Clear cache +THREE.Cache.clear(); + +// Manual cache management +THREE.Cache.add("key", data); +THREE.Cache.get("key"); +THREE.Cache.remove("key"); +``` + +### Custom Asset Manager + +```javascript +class AssetManager { + constructor() { + this.textures = new Map(); + this.models = new Map(); + this.gltfLoader = new GLTFLoader(); + this.textureLoader = new THREE.TextureLoader(); + } + + async loadTexture(key, url) { + if (this.textures.has(key)) { + return this.textures.get(key); + } + + const texture = await new Promise((resolve, reject) => { + this.textureLoader.load(url, resolve, undefined, reject); + }); + + this.textures.set(key, texture); + return texture; + } + + async loadModel(key, url) { + if (this.models.has(key)) { + return this.models.get(key).clone(); + } + + const gltf = await new Promise((resolve, reject) => { + this.gltfLoader.load(url, resolve, undefined, reject); + }); + + this.models.set(key, gltf.scene); + return gltf.scene.clone(); + } + + dispose() { + this.textures.forEach((t) => t.dispose()); + this.textures.clear(); + this.models.clear(); + } +} + +// Usage +const assets = new AssetManager(); +const texture = await assets.loadTexture("brick", "brick.jpg"); +const model = await assets.loadModel("tree", "tree.glb"); +``` + +## Loading from Different Sources + +### Data URL / Base64 + +```javascript +const loader = new THREE.TextureLoader(); +const texture = loader.load("data:image/png;base64,iVBORw0KGgo..."); +``` + +### Blob URL + +```javascript +async function loadFromBlob(blob) { + const url = URL.createObjectURL(blob); + const texture = await loadTexture(url); + URL.revokeObjectURL(url); + return texture; +} +``` + +### ArrayBuffer + +```javascript +// From fetch +const response = await fetch("model.glb"); +const buffer = await response.arrayBuffer(); + +// Parse with loader +const loader = new GLTFLoader(); +loader.parse(buffer, "", (gltf) => { + scene.add(gltf.scene); +}); +``` + +### Custom Path/URL + +```javascript +// Set base path +loader.setPath("assets/models/"); +loader.load("model.glb"); // Loads from assets/models/model.glb + +// Set resource path (for textures referenced in model) +loader.setResourcePath("assets/textures/"); + +// Custom URL modifier +manager.setURLModifier((url) => { + return `https://cdn.example.com/${url}`; +}); +``` + +## Error Handling + +```javascript +// Graceful fallback +async function loadWithFallback(primaryUrl, fallbackUrl) { + try { + return await loadModel(primaryUrl); + } catch (error) { + console.warn(`Primary failed, trying fallback: ${error}`); + return await loadModel(fallbackUrl); + } +} + +// Retry logic +async function loadWithRetry(url, maxRetries = 3) { + for (let i = 0; i < maxRetries; i++) { + try { + return await loadModel(url); + } catch (error) { + if (i === maxRetries - 1) throw error; + await new Promise((r) => setTimeout(r, 1000 * (i + 1))); + } + } +} + +// Timeout +async function loadWithTimeout(url, timeout = 30000) { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), timeout); + + try { + const response = await fetch(url, { signal: controller.signal }); + clearTimeout(timeoutId); + return response; + } catch (error) { + if (error.name === "AbortError") { + throw new Error("Loading timed out"); + } + throw error; + } +} +``` + +## Performance Tips + +1. **Use compressed formats**: DRACO for geometry, KTX2/Basis for textures +2. **Load progressively**: Show placeholders while loading +3. **Lazy load**: Only load what's needed +4. **Use CDN**: Faster asset delivery +5. **Enable cache**: `THREE.Cache.enabled = true` + +```javascript +// Progressive loading with placeholder +const placeholder = new THREE.Mesh( + new THREE.BoxGeometry(1, 1, 1), + new THREE.MeshBasicMaterial({ wireframe: true }), +); +scene.add(placeholder); + +loadModel("model.glb").then((gltf) => { + scene.remove(placeholder); + scene.add(gltf.scene); +}); +``` + +## See Also + +- `threejs-textures` - Texture configuration +- `threejs-animation` - Playing loaded animations +- `threejs-materials` - Material from loaded models diff --git a/plugins/codex/plugins/threejs/skills/threejs-materials/SKILL.md b/plugins/codex/plugins/threejs/skills/threejs-materials/SKILL.md new file mode 100644 index 000000000..4731f2826 --- /dev/null +++ b/plugins/codex/plugins/threejs/skills/threejs-materials/SKILL.md @@ -0,0 +1,520 @@ +--- +name: threejs-materials +description: Three.js materials - PBR, basic, phong, shader materials, material properties. Use when styling meshes, working with textures, creating custom shaders, or optimizing material performance. +--- + +# Three.js Materials + +## Quick Start + +```javascript +import * as THREE from "three"; + +// PBR material (recommended for realistic rendering) +const material = new THREE.MeshStandardMaterial({ + color: 0x00ff00, + roughness: 0.5, + metalness: 0.5, +}); + +const mesh = new THREE.Mesh(geometry, material); +``` + +## Material Types Overview + +| Material | Use Case | Lighting | +| -------------------- | ------------------------------------- | ------------------ | +| MeshBasicMaterial | Unlit, flat colors, wireframes | No | +| MeshLambertMaterial | Matte surfaces, performance | Yes (diffuse only) | +| MeshPhongMaterial | Shiny surfaces, specular highlights | Yes | +| MeshStandardMaterial | PBR, realistic materials | Yes (PBR) | +| MeshPhysicalMaterial | Advanced PBR, clearcoat, transmission | Yes (PBR+) | +| MeshToonMaterial | Cel-shaded, cartoon look | Yes (toon) | +| MeshNormalMaterial | Debug normals | No | +| MeshDepthMaterial | Depth visualization | No | +| ShaderMaterial | Custom GLSL shaders | Custom | +| RawShaderMaterial | Full shader control | Custom | + +## MeshBasicMaterial + +No lighting calculations. Fast, always visible. + +```javascript +const material = new THREE.MeshBasicMaterial({ + color: 0xff0000, + transparent: true, + opacity: 0.5, + side: THREE.DoubleSide, // FrontSide, BackSide, DoubleSide + wireframe: false, + map: texture, // Color/diffuse texture + alphaMap: alphaTexture, // Transparency texture + envMap: envTexture, // Reflection texture + reflectivity: 1, // Env map intensity + fog: true, // Affected by scene fog +}); +``` + +## MeshLambertMaterial + +Diffuse-only lighting. Fast, no specular highlights. + +```javascript +const material = new THREE.MeshLambertMaterial({ + color: 0x00ff00, + emissive: 0x111111, // Self-illumination color + emissiveIntensity: 1, + map: texture, + emissiveMap: emissiveTexture, + envMap: envTexture, + reflectivity: 0.5, +}); +``` + +## MeshPhongMaterial + +Specular highlights. Good for shiny, plastic-like surfaces. + +```javascript +const material = new THREE.MeshPhongMaterial({ + color: 0x0000ff, + specular: 0xffffff, // Highlight color + shininess: 100, // Highlight sharpness (0-1000) + emissive: 0x000000, + flatShading: false, // Flat vs smooth shading + map: texture, + specularMap: specTexture, // Per-pixel shininess + normalMap: normalTexture, + normalScale: new THREE.Vector2(1, 1), + bumpMap: bumpTexture, + bumpScale: 1, + displacementMap: dispTexture, + displacementScale: 1, +}); +``` + +## MeshStandardMaterial (PBR) + +Physically-based rendering. Recommended for realistic results. + +```javascript +const material = new THREE.MeshStandardMaterial({ + color: 0xffffff, + roughness: 0.5, // 0 = mirror, 1 = diffuse + metalness: 0.0, // 0 = dielectric, 1 = metal + + // Textures + map: colorTexture, // Albedo/base color + roughnessMap: roughTexture, // Per-pixel roughness + metalnessMap: metalTexture, // Per-pixel metalness + normalMap: normalTexture, // Surface detail + normalScale: new THREE.Vector2(1, 1), + aoMap: aoTexture, // Ambient occlusion (uses uv2!) + aoMapIntensity: 1, + displacementMap: dispTexture, // Vertex displacement + displacementScale: 0.1, + displacementBias: 0, + + // Emissive + emissive: 0x000000, + emissiveIntensity: 1, + emissiveMap: emissiveTexture, + + // Environment + envMap: envTexture, + envMapIntensity: 1, + + // Other + flatShading: false, + wireframe: false, + fog: true, +}); + +// Note: aoMap requires second UV channel +geometry.setAttribute("uv2", geometry.attributes.uv); +``` + +## MeshPhysicalMaterial (Advanced PBR) + +Extends MeshStandardMaterial with advanced features. + +```javascript +const material = new THREE.MeshPhysicalMaterial({ + // All MeshStandardMaterial properties plus: + + // Clearcoat (car paint, lacquer) + clearcoat: 1.0, // 0-1 clearcoat layer strength + clearcoatRoughness: 0.1, + clearcoatMap: ccTexture, + clearcoatRoughnessMap: ccrTexture, + clearcoatNormalMap: ccnTexture, + clearcoatNormalScale: new THREE.Vector2(1, 1), + + // Transmission (glass, water) + transmission: 1.0, // 0 = opaque, 1 = fully transparent + transmissionMap: transTexture, + thickness: 0.5, // Volume thickness for refraction + thicknessMap: thickTexture, + attenuationDistance: 1, // Absorption distance + attenuationColor: new THREE.Color(0xffffff), + + // Refraction + ior: 1.5, // Index of refraction (1-2.333) + + // Sheen (fabric, velvet) + sheen: 1.0, + sheenRoughness: 0.5, + sheenColor: new THREE.Color(0xffffff), + sheenColorMap: sheenTexture, + sheenRoughnessMap: sheenRoughTexture, + + // Iridescence (soap bubbles, oil slicks) + iridescence: 1.0, + iridescenceIOR: 1.3, + iridescenceThicknessRange: [100, 400], + iridescenceMap: iridTexture, + iridescenceThicknessMap: iridThickTexture, + + // Anisotropy (brushed metal) + anisotropy: 1.0, + anisotropyRotation: 0, + anisotropyMap: anisoTexture, + + // Specular + specularIntensity: 1, + specularColor: new THREE.Color(0xffffff), + specularIntensityMap: specIntTexture, + specularColorMap: specColorTexture, +}); +``` + +### Glass Material Example + +```javascript +const glass = new THREE.MeshPhysicalMaterial({ + color: 0xffffff, + metalness: 0, + roughness: 0, + transmission: 1, + thickness: 0.5, + ior: 1.5, + envMapIntensity: 1, +}); +``` + +### Car Paint Example + +```javascript +const carPaint = new THREE.MeshPhysicalMaterial({ + color: 0xff0000, + metalness: 0.9, + roughness: 0.5, + clearcoat: 1, + clearcoatRoughness: 0.1, +}); +``` + +## MeshToonMaterial + +Cel-shaded cartoon look. + +```javascript +const material = new THREE.MeshToonMaterial({ + color: 0x00ff00, + gradientMap: gradientTexture, // Optional: custom shading gradient +}); + +// Create step gradient texture +const colors = new Uint8Array([0, 128, 255]); +const gradientMap = new THREE.DataTexture(colors, 3, 1, THREE.RedFormat); +gradientMap.minFilter = THREE.NearestFilter; +gradientMap.magFilter = THREE.NearestFilter; +gradientMap.needsUpdate = true; +``` + +## MeshNormalMaterial + +Visualize surface normals. Useful for debugging. + +```javascript +const material = new THREE.MeshNormalMaterial({ + flatShading: false, + wireframe: false, +}); +``` + +## MeshDepthMaterial + +Render depth values. Used for shadow maps, DOF effects. + +```javascript +const material = new THREE.MeshDepthMaterial({ + depthPacking: THREE.RGBADepthPacking, +}); +``` + +## PointsMaterial + +For point clouds. + +```javascript +const material = new THREE.PointsMaterial({ + color: 0xffffff, + size: 0.1, + sizeAttenuation: true, // Scale with distance + map: pointTexture, + alphaMap: alphaTexture, + transparent: true, + alphaTest: 0.5, // Discard pixels below threshold + vertexColors: true, // Use per-vertex colors +}); + +const points = new THREE.Points(geometry, material); +``` + +## LineBasicMaterial & LineDashedMaterial + +```javascript +// Solid lines +const lineMaterial = new THREE.LineBasicMaterial({ + color: 0xffffff, + linewidth: 1, // Note: >1 only works on some systems + linecap: "round", + linejoin: "round", +}); + +// Dashed lines +const dashedMaterial = new THREE.LineDashedMaterial({ + color: 0xffffff, + dashSize: 0.5, + gapSize: 0.25, + scale: 1, +}); + +// Required for dashed lines +const line = new THREE.Line(geometry, dashedMaterial); +line.computeLineDistances(); +``` + +## ShaderMaterial + +Custom GLSL shaders with Three.js uniforms. + +```javascript +const material = new THREE.ShaderMaterial({ + uniforms: { + time: { value: 0 }, + color: { value: new THREE.Color(0xff0000) }, + texture1: { value: texture }, + }, + vertexShader: ` + varying vec2 vUv; + uniform float time; + + void main() { + vUv = uv; + vec3 pos = position; + pos.z += sin(pos.x * 10.0 + time) * 0.1; + gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0); + } + `, + fragmentShader: ` + varying vec2 vUv; + uniform vec3 color; + uniform sampler2D texture1; + + void main() { + // Use texture2D() for GLSL 1.0, texture() for GLSL 3.0 (glslVersion: THREE.GLSL3) + vec4 texColor = texture2D(texture1, vUv); + gl_FragColor = vec4(color * texColor.rgb, 1.0); + } + `, + transparent: true, + side: THREE.DoubleSide, +}); + +// Update uniform in animation loop +material.uniforms.time.value = clock.getElapsedTime(); +``` + +### Built-in Uniforms (auto-provided) + +```glsl +// Vertex shader +uniform mat4 modelMatrix; // Object to world +uniform mat4 modelViewMatrix; // Object to camera +uniform mat4 projectionMatrix; // Camera projection +uniform mat4 viewMatrix; // World to camera +uniform mat3 normalMatrix; // For transforming normals +uniform vec3 cameraPosition; // Camera world position + +// Attributes +attribute vec3 position; +attribute vec3 normal; +attribute vec2 uv; +``` + +## RawShaderMaterial + +Full control - no built-in uniforms/attributes. + +```javascript +const material = new THREE.RawShaderMaterial({ + uniforms: { + projectionMatrix: { value: camera.projectionMatrix }, + modelViewMatrix: { value: new THREE.Matrix4() }, + }, + vertexShader: ` + precision highp float; + attribute vec3 position; + uniform mat4 projectionMatrix; + uniform mat4 modelViewMatrix; + + void main() { + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `, + fragmentShader: ` + precision highp float; + + void main() { + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + } + `, +}); +``` + +## Common Material Properties + +All materials share these base properties: + +```javascript +// Visibility +material.visible = true; +material.transparent = false; +material.opacity = 1.0; +material.alphaTest = 0; // Discard pixels with alpha < value + +// Rendering +material.side = THREE.FrontSide; // FrontSide, BackSide, DoubleSide +material.depthTest = true; +material.depthWrite = true; +material.colorWrite = true; + +// Blending +material.blending = THREE.NormalBlending; +// NormalBlending, AdditiveBlending, SubtractiveBlending, MultiplyBlending, CustomBlending + +// Stencil +material.stencilWrite = false; +material.stencilFunc = THREE.AlwaysStencilFunc; +material.stencilRef = 0; +material.stencilMask = 0xff; + +// Polygon offset (z-fighting fix) +material.polygonOffset = false; +material.polygonOffsetFactor = 0; +material.polygonOffsetUnits = 0; + +// Misc +material.dithering = false; +material.toneMapped = true; +``` + +## Multiple Materials + +```javascript +// Assign different materials to geometry groups +const geometry = new THREE.BoxGeometry(1, 1, 1); +const materials = [ + new THREE.MeshBasicMaterial({ color: 0xff0000 }), // right + new THREE.MeshBasicMaterial({ color: 0x00ff00 }), // left + new THREE.MeshBasicMaterial({ color: 0x0000ff }), // top + new THREE.MeshBasicMaterial({ color: 0xffff00 }), // bottom + new THREE.MeshBasicMaterial({ color: 0xff00ff }), // front + new THREE.MeshBasicMaterial({ color: 0x00ffff }), // back +]; +const mesh = new THREE.Mesh(geometry, materials); + +// Custom groups +geometry.clearGroups(); +geometry.addGroup(0, 6, 0); // start, count, materialIndex +geometry.addGroup(6, 6, 1); +``` + +## Environment Maps + +```javascript +// Load cube texture +const cubeLoader = new THREE.CubeTextureLoader(); +const envMap = cubeLoader.load([ + "px.jpg", + "nx.jpg", // positive/negative X + "py.jpg", + "ny.jpg", // positive/negative Y + "pz.jpg", + "nz.jpg", // positive/negative Z +]); + +// Apply to material +material.envMap = envMap; +material.envMapIntensity = 1; + +// Or set as scene environment (affects all PBR materials) +scene.environment = envMap; + +// HDR environment (recommended) +import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"; +const rgbeLoader = new RGBELoader(); +rgbeLoader.load("environment.hdr", (texture) => { + texture.mapping = THREE.EquirectangularReflectionMapping; + scene.environment = texture; + scene.background = texture; +}); +``` + +## Material Cloning and Modification + +```javascript +// Clone material +const clone = material.clone(); +clone.color.set(0x00ff00); + +// Modify at runtime +material.color.set(0xff0000); +material.needsUpdate = true; // Only needed for some changes + +// When needsUpdate is required: +// - Changing flat shading +// - Changing texture +// - Changing transparent +// - Custom shader code changes +``` + +## Performance Tips + +1. **Reuse materials**: Same material = batched draw calls +2. **Avoid transparent when possible**: Transparent materials require sorting +3. **Use alphaTest instead of transparency**: When applicable, faster +4. **Choose simpler materials**: Basic > Lambert > Phong > Standard > Physical +5. **Limit active lights**: Each light adds shader complexity + +```javascript +// Material pooling +const materialCache = new Map(); +function getMaterial(color) { + const key = color.toString(16); + if (!materialCache.has(key)) { + materialCache.set(key, new THREE.MeshStandardMaterial({ color })); + } + return materialCache.get(key); +} + +// Dispose when done +material.dispose(); +``` + +## See Also + +- `threejs-textures` - Texture loading and configuration +- `threejs-shaders` - Custom shader development +- `threejs-lighting` - Light interaction with materials diff --git a/plugins/codex/plugins/threejs/skills/threejs-postprocessing/SKILL.md b/plugins/codex/plugins/threejs/skills/threejs-postprocessing/SKILL.md new file mode 100644 index 000000000..62675b9a5 --- /dev/null +++ b/plugins/codex/plugins/threejs/skills/threejs-postprocessing/SKILL.md @@ -0,0 +1,602 @@ +--- +name: threejs-postprocessing +description: Three.js post-processing - EffectComposer, bloom, DOF, screen effects. Use when adding visual effects, color grading, blur, glow, or creating custom screen-space shaders. +--- + +# Three.js Post-Processing + +## Quick Start + +```javascript +import * as THREE from "three"; +import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js"; +import { RenderPass } from "three/addons/postprocessing/RenderPass.js"; +import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js"; + +// Setup composer +const composer = new EffectComposer(renderer); + +// Render scene +const renderPass = new RenderPass(scene, camera); +composer.addPass(renderPass); + +// Add bloom +const bloomPass = new UnrealBloomPass( + new THREE.Vector2(window.innerWidth, window.innerHeight), + 1.5, // strength + 0.4, // radius + 0.85, // threshold +); +composer.addPass(bloomPass); + +// Animation loop - use composer instead of renderer +function animate() { + requestAnimationFrame(animate); + composer.render(); // NOT renderer.render() +} +``` + +## EffectComposer Setup + +```javascript +import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js"; +import { RenderPass } from "three/addons/postprocessing/RenderPass.js"; + +const composer = new EffectComposer(renderer); + +// First pass: render scene +const renderPass = new RenderPass(scene, camera); +composer.addPass(renderPass); + +// Add more passes... +composer.addPass(effectPass); + +// Last pass should render to screen +effectPass.renderToScreen = true; // Default for last pass + +// Handle resize +function onResize() { + const width = window.innerWidth; + const height = window.innerHeight; + + camera.aspect = width / height; + camera.updateProjectionMatrix(); + + renderer.setSize(width, height); + composer.setSize(width, height); +} +``` + +## Common Effects + +### Bloom (Glow) + +```javascript +import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js"; + +const bloomPass = new UnrealBloomPass( + new THREE.Vector2(window.innerWidth, window.innerHeight), + 1.5, // strength - intensity of glow + 0.4, // radius - spread of glow + 0.85, // threshold - brightness threshold +); + +composer.addPass(bloomPass); + +// Adjust at runtime +bloomPass.strength = 2.0; +bloomPass.threshold = 0.5; +bloomPass.radius = 0.8; +``` + +### Selective Bloom + +Apply bloom only to specific objects. + +```javascript +import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js"; +import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js"; + +// Layer setup +const BLOOM_LAYER = 1; +const bloomLayer = new THREE.Layers(); +bloomLayer.set(BLOOM_LAYER); + +// Mark objects to bloom +glowingMesh.layers.enable(BLOOM_LAYER); + +// Dark material for non-blooming objects +const darkMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 }); +const materials = {}; + +function darkenNonBloomed(obj) { + if (obj.isMesh && !bloomLayer.test(obj.layers)) { + materials[obj.uuid] = obj.material; + obj.material = darkMaterial; + } +} + +function restoreMaterial(obj) { + if (materials[obj.uuid]) { + obj.material = materials[obj.uuid]; + delete materials[obj.uuid]; + } +} + +// Custom render loop +function render() { + // Render bloom pass + scene.traverse(darkenNonBloomed); + composer.render(); + scene.traverse(restoreMaterial); + + // Render final scene over bloom + renderer.render(scene, camera); +} +``` + +### FXAA (Anti-Aliasing) + +```javascript +import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js"; +import { FXAAShader } from "three/addons/shaders/FXAAShader.js"; + +const fxaaPass = new ShaderPass(FXAAShader); +fxaaPass.material.uniforms["resolution"].value.set( + 1 / window.innerWidth, + 1 / window.innerHeight, +); + +composer.addPass(fxaaPass); + +// Update on resize +function onResize() { + fxaaPass.material.uniforms["resolution"].value.set( + 1 / window.innerWidth, + 1 / window.innerHeight, + ); +} +``` + +### SMAA (Better Anti-Aliasing) + +```javascript +import { SMAAPass } from "three/addons/postprocessing/SMAAPass.js"; + +const smaaPass = new SMAAPass( + window.innerWidth * renderer.getPixelRatio(), + window.innerHeight * renderer.getPixelRatio(), +); + +composer.addPass(smaaPass); +``` + +### SSAO (Ambient Occlusion) + +```javascript +import { SSAOPass } from "three/addons/postprocessing/SSAOPass.js"; + +const ssaoPass = new SSAOPass( + scene, + camera, + window.innerWidth, + window.innerHeight, +); +ssaoPass.kernelRadius = 16; +ssaoPass.minDistance = 0.005; +ssaoPass.maxDistance = 0.1; + +composer.addPass(ssaoPass); + +// Output modes +ssaoPass.output = SSAOPass.OUTPUT.Default; +// SSAOPass.OUTPUT.Default - Final composited output +// SSAOPass.OUTPUT.SSAO - Just the AO +// SSAOPass.OUTPUT.Blur - Blurred AO +// SSAOPass.OUTPUT.Depth - Depth buffer +// SSAOPass.OUTPUT.Normal - Normal buffer +``` + +### Depth of Field (DOF) + +```javascript +import { BokehPass } from "three/addons/postprocessing/BokehPass.js"; + +const bokehPass = new BokehPass(scene, camera, { + focus: 10.0, // Focus distance + aperture: 0.025, // Aperture (smaller = more DOF) + maxblur: 0.01, // Max blur amount +}); + +composer.addPass(bokehPass); + +// Update focus dynamically +bokehPass.uniforms["focus"].value = distanceToTarget; +``` + +### Film Grain + +```javascript +import { FilmPass } from "three/addons/postprocessing/FilmPass.js"; + +const filmPass = new FilmPass( + 0.35, // noise intensity + 0.5, // scanline intensity + 648, // scanline count + false, // grayscale +); + +composer.addPass(filmPass); +``` + +### Vignette + +```javascript +import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js"; +import { VignetteShader } from "three/addons/shaders/VignetteShader.js"; + +const vignettePass = new ShaderPass(VignetteShader); +vignettePass.uniforms["offset"].value = 1.0; // Vignette size +vignettePass.uniforms["darkness"].value = 1.0; // Vignette intensity + +composer.addPass(vignettePass); +``` + +### Color Correction + +```javascript +import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js"; +import { ColorCorrectionShader } from "three/addons/shaders/ColorCorrectionShader.js"; + +const colorPass = new ShaderPass(ColorCorrectionShader); +colorPass.uniforms["powRGB"].value = new THREE.Vector3(1.2, 1.2, 1.2); // Power +colorPass.uniforms["mulRGB"].value = new THREE.Vector3(1.0, 1.0, 1.0); // Multiply + +composer.addPass(colorPass); +``` + +### Gamma Correction + +```javascript +import { GammaCorrectionShader } from "three/addons/shaders/GammaCorrectionShader.js"; + +const gammaPass = new ShaderPass(GammaCorrectionShader); +composer.addPass(gammaPass); +``` + +### Pixelation + +```javascript +import { RenderPixelatedPass } from "three/addons/postprocessing/RenderPixelatedPass.js"; + +const pixelPass = new RenderPixelatedPass(6, scene, camera); // 6 = pixel size + +composer.addPass(pixelPass); +``` + +### Glitch Effect + +```javascript +import { GlitchPass } from "three/addons/postprocessing/GlitchPass.js"; + +const glitchPass = new GlitchPass(); +glitchPass.goWild = false; // Continuous glitching + +composer.addPass(glitchPass); +``` + +### Halftone + +```javascript +import { HalftonePass } from "three/addons/postprocessing/HalftonePass.js"; + +const halftonePass = new HalftonePass(window.innerWidth, window.innerHeight, { + shape: 1, // 1 = dot, 2 = ellipse, 3 = line, 4 = square + radius: 4, // Dot size + rotateR: Math.PI / 12, + rotateB: (Math.PI / 12) * 2, + rotateG: (Math.PI / 12) * 3, + scatter: 0, + blending: 1, + blendingMode: 1, + greyscale: false, +}); + +composer.addPass(halftonePass); +``` + +### Outline + +```javascript +import { OutlinePass } from "three/addons/postprocessing/OutlinePass.js"; + +const outlinePass = new OutlinePass( + new THREE.Vector2(window.innerWidth, window.innerHeight), + scene, + camera, +); + +outlinePass.edgeStrength = 3; +outlinePass.edgeGlow = 0; +outlinePass.edgeThickness = 1; +outlinePass.pulsePeriod = 0; +outlinePass.visibleEdgeColor.set(0xffffff); +outlinePass.hiddenEdgeColor.set(0x190a05); + +// Select objects to outline +outlinePass.selectedObjects = [mesh1, mesh2]; + +composer.addPass(outlinePass); +``` + +## Custom ShaderPass + +Create your own post-processing effects. + +```javascript +import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js"; + +const CustomShader = { + uniforms: { + tDiffuse: { value: null }, // Required: input texture + time: { value: 0 }, + intensity: { value: 1.0 }, + }, + vertexShader: ` + varying vec2 vUv; + + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `, + fragmentShader: ` + uniform sampler2D tDiffuse; + uniform float time; + uniform float intensity; + varying vec2 vUv; + + void main() { + vec2 uv = vUv; + + // Wave distortion + uv.x += sin(uv.y * 10.0 + time) * 0.01 * intensity; + + vec4 color = texture2D(tDiffuse, uv); + gl_FragColor = color; + } + `, +}; + +const customPass = new ShaderPass(CustomShader); +composer.addPass(customPass); + +// Update in animation loop +customPass.uniforms.time.value = clock.getElapsedTime(); +``` + +### Invert Colors Shader + +```javascript +const InvertShader = { + uniforms: { + tDiffuse: { value: null }, + }, + vertexShader: ` + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `, + fragmentShader: ` + uniform sampler2D tDiffuse; + varying vec2 vUv; + + void main() { + vec4 color = texture2D(tDiffuse, vUv); + gl_FragColor = vec4(1.0 - color.rgb, color.a); + } + `, +}; +``` + +### Chromatic Aberration + +```javascript +const ChromaticAberrationShader = { + uniforms: { + tDiffuse: { value: null }, + amount: { value: 0.005 }, + }, + vertexShader: ` + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `, + fragmentShader: ` + uniform sampler2D tDiffuse; + uniform float amount; + varying vec2 vUv; + + void main() { + vec2 dir = vUv - 0.5; + float dist = length(dir); + + float r = texture2D(tDiffuse, vUv - dir * amount * dist).r; + float g = texture2D(tDiffuse, vUv).g; + float b = texture2D(tDiffuse, vUv + dir * amount * dist).b; + + gl_FragColor = vec4(r, g, b, 1.0); + } + `, +}; +``` + +## Combining Multiple Effects + +```javascript +import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js"; +import { RenderPass } from "three/addons/postprocessing/RenderPass.js"; +import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js"; +import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js"; +import { FXAAShader } from "three/addons/shaders/FXAAShader.js"; +import { VignetteShader } from "three/addons/shaders/VignetteShader.js"; +import { GammaCorrectionShader } from "three/addons/shaders/GammaCorrectionShader.js"; + +const composer = new EffectComposer(renderer); + +// 1. Render scene +composer.addPass(new RenderPass(scene, camera)); + +// 2. Bloom +const bloomPass = new UnrealBloomPass( + new THREE.Vector2(window.innerWidth, window.innerHeight), + 0.5, + 0.4, + 0.85, +); +composer.addPass(bloomPass); + +// 3. Vignette +const vignettePass = new ShaderPass(VignetteShader); +vignettePass.uniforms["offset"].value = 0.95; +vignettePass.uniforms["darkness"].value = 1.0; +composer.addPass(vignettePass); + +// 4. Gamma correction +composer.addPass(new ShaderPass(GammaCorrectionShader)); + +// 5. Anti-aliasing (always last before output) +const fxaaPass = new ShaderPass(FXAAShader); +fxaaPass.uniforms["resolution"].value.set( + 1 / window.innerWidth, + 1 / window.innerHeight, +); +composer.addPass(fxaaPass); +``` + +## Render to Texture + +```javascript +// Create render target +const renderTarget = new THREE.WebGLRenderTarget(512, 512); + +// Render scene to target +renderer.setRenderTarget(renderTarget); +renderer.render(scene, camera); +renderer.setRenderTarget(null); + +// Use texture +const texture = renderTarget.texture; +otherMaterial.map = texture; +``` + +## Multi-Pass Rendering + +```javascript +// Multiple composers for different scenes/layers +const bgComposer = new EffectComposer(renderer); +bgComposer.addPass(new RenderPass(bgScene, camera)); + +const fgComposer = new EffectComposer(renderer); +fgComposer.addPass(new RenderPass(fgScene, camera)); +fgComposer.addPass(bloomPass); + +// Combine in render loop +function animate() { + // Render background without clearing + renderer.autoClear = false; + renderer.clear(); + + bgComposer.render(); + + // Render foreground over it + renderer.clearDepth(); + fgComposer.render(); +} +``` + +## WebGPU Post-Processing (Three.js r150+) + +```javascript +import { postProcessing } from "three/addons/nodes/Nodes.js"; +import { pass, bloom, dof } from "three/addons/nodes/Nodes.js"; + +// Using node-based system +const scenePass = pass(scene, camera); +const bloomNode = bloom(scenePass, 0.5, 0.4, 0.85); + +const postProcessing = new THREE.PostProcessing(renderer); +postProcessing.outputNode = bloomNode; + +// Render +function animate() { + postProcessing.render(); +} +``` + +## Performance Tips + +1. **Limit passes**: Each pass adds a full-screen render +2. **Lower resolution**: Use smaller render targets for blur passes +3. **Disable unused effects**: Toggle passes on/off +4. **Use FXAA over MSAA**: Less expensive anti-aliasing +5. **Profile with DevTools**: Check GPU usage + +```javascript +// Disable pass +bloomPass.enabled = false; + +// Reduce bloom resolution +const bloomPass = new UnrealBloomPass( + new THREE.Vector2(window.innerWidth / 2, window.innerHeight / 2), + strength, + radius, + threshold, +); + +// Only apply effects in high-performance scenarios +const isMobile = /iPhone|iPad|Android/i.test(navigator.userAgent); +if (!isMobile) { + composer.addPass(expensivePass); +} +``` + +## Handle Resize + +```javascript +function onWindowResize() { + const width = window.innerWidth; + const height = window.innerHeight; + const pixelRatio = renderer.getPixelRatio(); + + camera.aspect = width / height; + camera.updateProjectionMatrix(); + + renderer.setSize(width, height); + composer.setSize(width, height); + + // Update pass-specific resolutions + if (fxaaPass) { + fxaaPass.material.uniforms["resolution"].value.set( + 1 / (width * pixelRatio), + 1 / (height * pixelRatio), + ); + } + + if (bloomPass) { + bloomPass.resolution.set(width, height); + } +} + +window.addEventListener("resize", onWindowResize); +``` + +## See Also + +- `threejs-shaders` - Custom shader development +- `threejs-textures` - Render targets +- `threejs-fundamentals` - Renderer setup diff --git a/plugins/codex/plugins/threejs/skills/threejs-shaders/SKILL.md b/plugins/codex/plugins/threejs/skills/threejs-shaders/SKILL.md new file mode 100644 index 000000000..f0dff82de --- /dev/null +++ b/plugins/codex/plugins/threejs/skills/threejs-shaders/SKILL.md @@ -0,0 +1,642 @@ +--- +name: threejs-shaders +description: Three.js shaders - GLSL, ShaderMaterial, uniforms, custom effects. Use when creating custom visual effects, modifying vertices, writing fragment shaders, or extending built-in materials. +--- + +# Three.js Shaders + +## Quick Start + +```javascript +import * as THREE from "three"; + +const material = new THREE.ShaderMaterial({ + uniforms: { + time: { value: 0 }, + color: { value: new THREE.Color(0xff0000) }, + }, + vertexShader: ` + void main() { + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `, + fragmentShader: ` + uniform vec3 color; + + void main() { + gl_FragColor = vec4(color, 1.0); + } + `, +}); + +// Update in animation loop +material.uniforms.time.value = clock.getElapsedTime(); +``` + +## ShaderMaterial vs RawShaderMaterial + +### ShaderMaterial + +Three.js provides built-in uniforms and attributes. + +```javascript +const material = new THREE.ShaderMaterial({ + vertexShader: ` + // Built-in uniforms available: + // uniform mat4 modelMatrix; + // uniform mat4 modelViewMatrix; + // uniform mat4 projectionMatrix; + // uniform mat4 viewMatrix; + // uniform mat3 normalMatrix; + // uniform vec3 cameraPosition; + + // Built-in attributes available: + // attribute vec3 position; + // attribute vec3 normal; + // attribute vec2 uv; + + void main() { + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `, + fragmentShader: ` + void main() { + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + } + `, +}); +``` + +### RawShaderMaterial + +Full control - you define everything. + +```javascript +const material = new THREE.RawShaderMaterial({ + uniforms: { + projectionMatrix: { value: camera.projectionMatrix }, + modelViewMatrix: { value: new THREE.Matrix4() }, + }, + vertexShader: ` + precision highp float; + + attribute vec3 position; + uniform mat4 projectionMatrix; + uniform mat4 modelViewMatrix; + + void main() { + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `, + fragmentShader: ` + precision highp float; + + void main() { + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + } + `, +}); +``` + +## Uniforms + +### Uniform Types + +```javascript +const material = new THREE.ShaderMaterial({ + uniforms: { + // Numbers + floatValue: { value: 1.5 }, + intValue: { value: 1 }, + + // Vectors + vec2Value: { value: new THREE.Vector2(1, 2) }, + vec3Value: { value: new THREE.Vector3(1, 2, 3) }, + vec4Value: { value: new THREE.Vector4(1, 2, 3, 4) }, + + // Colors (converted to vec3) + colorValue: { value: new THREE.Color(0xff0000) }, + + // Matrices + mat3Value: { value: new THREE.Matrix3() }, + mat4Value: { value: new THREE.Matrix4() }, + + // Textures + textureValue: { value: texture }, + cubeTextureValue: { value: cubeTexture }, + + // Arrays + floatArray: { value: [1.0, 2.0, 3.0] }, + vec3Array: { + value: [new THREE.Vector3(1, 0, 0), new THREE.Vector3(0, 1, 0)], + }, + }, +}); +``` + +### GLSL Declarations + +```glsl +// In shader +uniform float floatValue; +uniform int intValue; +uniform vec2 vec2Value; +uniform vec3 vec3Value; +uniform vec3 colorValue; // Color becomes vec3 +uniform vec4 vec4Value; +uniform mat3 mat3Value; +uniform mat4 mat4Value; +uniform sampler2D textureValue; +uniform samplerCube cubeTextureValue; +uniform float floatArray[3]; +uniform vec3 vec3Array[2]; +``` + +### Updating Uniforms + +```javascript +// Direct assignment +material.uniforms.time.value = clock.getElapsedTime(); + +// Vector/Color updates +material.uniforms.position.value.set(x, y, z); +material.uniforms.color.value.setHSL(hue, 1, 0.5); + +// Matrix updates +material.uniforms.matrix.value.copy(mesh.matrixWorld); +``` + +## Varyings + +Pass data from vertex to fragment shader. + +```javascript +const material = new THREE.ShaderMaterial({ + vertexShader: ` + varying vec2 vUv; + varying vec3 vNormal; + varying vec3 vPosition; + + void main() { + vUv = uv; + vNormal = normalize(normalMatrix * normal); + vPosition = (modelViewMatrix * vec4(position, 1.0)).xyz; + + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `, + fragmentShader: ` + varying vec2 vUv; + varying vec3 vNormal; + varying vec3 vPosition; + + void main() { + // Use interpolated values + gl_FragColor = vec4(vNormal * 0.5 + 0.5, 1.0); + } + `, +}); +``` + +## Common Shader Patterns + +### Texture Sampling + +```javascript +const material = new THREE.ShaderMaterial({ + uniforms: { + map: { value: texture }, + }, + vertexShader: ` + varying vec2 vUv; + + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `, + fragmentShader: ` + uniform sampler2D map; + varying vec2 vUv; + + void main() { + vec4 texColor = texture2D(map, vUv); + gl_FragColor = texColor; + } + `, +}); +``` + +### Vertex Displacement + +```javascript +const material = new THREE.ShaderMaterial({ + uniforms: { + time: { value: 0 }, + amplitude: { value: 0.5 }, + }, + vertexShader: ` + uniform float time; + uniform float amplitude; + + void main() { + vec3 pos = position; + + // Wave displacement + pos.z += sin(pos.x * 5.0 + time) * amplitude; + pos.z += sin(pos.y * 5.0 + time) * amplitude; + + gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0); + } + `, + fragmentShader: ` + void main() { + gl_FragColor = vec4(0.5, 0.8, 1.0, 1.0); + } + `, +}); +``` + +### Fresnel Effect + +```javascript +const material = new THREE.ShaderMaterial({ + vertexShader: ` + varying vec3 vNormal; + varying vec3 vWorldPosition; + + void main() { + vNormal = normalize(normalMatrix * normal); + vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `, + fragmentShader: ` + varying vec3 vNormal; + varying vec3 vWorldPosition; + + void main() { + // cameraPosition is auto-provided by ShaderMaterial + vec3 viewDirection = normalize(cameraPosition - vWorldPosition); + float fresnel = pow(1.0 - dot(viewDirection, vNormal), 3.0); + + vec3 baseColor = vec3(0.0, 0.0, 0.5); + vec3 fresnelColor = vec3(0.5, 0.8, 1.0); + + gl_FragColor = vec4(mix(baseColor, fresnelColor, fresnel), 1.0); + } + `, +}); +``` + +### Noise-Based Effects + +```glsl +// Simple noise function +float random(vec2 st) { + return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453); +} + +// Value noise +float noise(vec2 st) { + vec2 i = floor(st); + vec2 f = fract(st); + + float a = random(i); + float b = random(i + vec2(1.0, 0.0)); + float c = random(i + vec2(0.0, 1.0)); + float d = random(i + vec2(1.0, 1.0)); + + vec2 u = f * f * (3.0 - 2.0 * f); + + return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y; +} + +// Usage +float n = noise(vUv * 10.0 + time); +``` + +### Gradient + +```glsl +// Linear gradient +vec3 color = mix(colorA, colorB, vUv.y); + +// Radial gradient +float dist = distance(vUv, vec2(0.5)); +vec3 color = mix(centerColor, edgeColor, dist * 2.0); + +// Smooth gradient with custom curve +float t = smoothstep(0.0, 1.0, vUv.y); +vec3 color = mix(colorA, colorB, t); +``` + +### Rim Lighting + +```javascript +const material = new THREE.ShaderMaterial({ + vertexShader: ` + varying vec3 vNormal; + varying vec3 vViewPosition; + + void main() { + vNormal = normalize(normalMatrix * normal); + vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); + vViewPosition = mvPosition.xyz; + gl_Position = projectionMatrix * mvPosition; + } + `, + fragmentShader: ` + varying vec3 vNormal; + varying vec3 vViewPosition; + + void main() { + vec3 viewDir = normalize(-vViewPosition); + float rim = 1.0 - max(0.0, dot(viewDir, vNormal)); + rim = pow(rim, 4.0); + + vec3 baseColor = vec3(0.2, 0.2, 0.8); + vec3 rimColor = vec3(1.0, 0.5, 0.0); + + gl_FragColor = vec4(baseColor + rimColor * rim, 1.0); + } + `, +}); +``` + +### Dissolve Effect + +```glsl +uniform float progress; +uniform sampler2D noiseMap; + +void main() { + float noise = texture2D(noiseMap, vUv).r; + + if (noise < progress) { + discard; + } + + // Edge glow + float edge = smoothstep(progress, progress + 0.1, noise); + vec3 edgeColor = vec3(1.0, 0.5, 0.0); + vec3 baseColor = vec3(0.5); + + gl_FragColor = vec4(mix(edgeColor, baseColor, edge), 1.0); +} +``` + +## Extending Built-in Materials + +### onBeforeCompile + +Modify existing material shaders. + +```javascript +const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 }); + +material.onBeforeCompile = (shader) => { + // Add custom uniform + shader.uniforms.time = { value: 0 }; + + // Store reference for updates + material.userData.shader = shader; + + // Modify vertex shader + shader.vertexShader = shader.vertexShader.replace( + "#include ", + ` + #include + transformed.y += sin(position.x * 10.0 + time) * 0.1; + `, + ); + + // Add uniform declaration + shader.vertexShader = "uniform float time;\n" + shader.vertexShader; +}; + +// Update in animation loop +if (material.userData.shader) { + material.userData.shader.uniforms.time.value = clock.getElapsedTime(); +} +``` + +### Common Injection Points + +```javascript +// Vertex shader chunks +"#include "; // After position is calculated +"#include "; // After gl_Position +"#include "; // Normal calculation start + +// Fragment shader chunks +"#include "; // After diffuse color +"#include "; // Final output +"#include "; // After fog applied +``` + +## GLSL Built-in Functions + +### Math Functions + +```glsl +// Basic +abs(x), sign(x), floor(x), ceil(x), fract(x) +mod(x, y), min(x, y), max(x, y), clamp(x, min, max) +mix(a, b, t), step(edge, x), smoothstep(edge0, edge1, x) + +// Trigonometry +sin(x), cos(x), tan(x) +asin(x), acos(x), atan(y, x), atan(x) +radians(degrees), degrees(radians) + +// Exponential +pow(x, y), exp(x), log(x), exp2(x), log2(x) +sqrt(x), inversesqrt(x) +``` + +### Vector Functions + +```glsl +// Length and distance +length(v), distance(p0, p1), dot(x, y), cross(x, y) + +// Normalization +normalize(v) + +// Reflection and refraction +reflect(I, N), refract(I, N, eta) + +// Component-wise +lessThan(x, y), lessThanEqual(x, y) +greaterThan(x, y), greaterThanEqual(x, y) +equal(x, y), notEqual(x, y) +any(bvec), all(bvec) +``` + +### Texture Functions + +```glsl +// GLSL 1.0 (default) - use texture2D/textureCube +texture2D(sampler, coord) +texture2D(sampler, coord, bias) +textureCube(sampler, coord) + +// GLSL 3.0 (glslVersion: THREE.GLSL3) - use texture() +// texture(sampler, coord) replaces texture2D/textureCube +// Also use: out vec4 fragColor instead of gl_FragColor + +// Texture size (GLSL 1.30+) +textureSize(sampler, lod) +``` + +## Common Material Properties + +```javascript +const material = new THREE.ShaderMaterial({ + uniforms: { + /* ... */ + }, + vertexShader: "/* ... */", + fragmentShader: "/* ... */", + + // Rendering + transparent: true, + opacity: 1.0, + side: THREE.DoubleSide, + depthTest: true, + depthWrite: true, + + // Blending + blending: THREE.NormalBlending, + // AdditiveBlending, SubtractiveBlending, MultiplyBlending + + // Wireframe + wireframe: false, + wireframeLinewidth: 1, // Note: >1 has no effect on most platforms (WebGL limitation) + + // Extensions + extensions: { + derivatives: true, // For fwidth, dFdx, dFdy + fragDepth: true, // gl_FragDepth + drawBuffers: true, // Multiple render targets + shaderTextureLOD: true, // texture2DLod + }, + + // GLSL version + glslVersion: THREE.GLSL3, // For WebGL2 features +}); +``` + +## Shader Includes + +### Using Three.js Shader Chunks + +```javascript +import { ShaderChunk } from "three"; + +const fragmentShader = ` + ${ShaderChunk.common} + ${ShaderChunk.packing} + + uniform sampler2D depthTexture; + varying vec2 vUv; + + void main() { + float depth = texture2D(depthTexture, vUv).r; + float linearDepth = perspectiveDepthToViewZ(depth, 0.1, 1000.0); + gl_FragColor = vec4(vec3(-linearDepth / 100.0), 1.0); + } +`; +``` + +### External Shader Files + +```javascript +// With vite/webpack +import vertexShader from "./shaders/vertex.glsl"; +import fragmentShader from "./shaders/fragment.glsl"; + +const material = new THREE.ShaderMaterial({ + vertexShader, + fragmentShader, +}); +``` + +## Instanced Shaders + +```javascript +// Instanced attribute +const offsets = new Float32Array(instanceCount * 3); +// Fill offsets... +geometry.setAttribute("offset", new THREE.InstancedBufferAttribute(offsets, 3)); + +const material = new THREE.ShaderMaterial({ + vertexShader: ` + attribute vec3 offset; + + void main() { + vec3 pos = position + offset; + gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0); + } + `, + fragmentShader: ` + void main() { + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + } + `, +}); +``` + +## Debugging Shaders + +```javascript +// Check for compile errors +material.onBeforeCompile = (shader) => { + console.log("Vertex Shader:", shader.vertexShader); + console.log("Fragment Shader:", shader.fragmentShader); +}; + +// Visual debugging +fragmentShader: ` + void main() { + // Debug UV + gl_FragColor = vec4(vUv, 0.0, 1.0); + + // Debug normals + gl_FragColor = vec4(vNormal * 0.5 + 0.5, 1.0); + + // Debug position + gl_FragColor = vec4(vPosition * 0.1 + 0.5, 1.0); + } +`; + +// Check WebGL errors +renderer.debug.checkShaderErrors = true; +``` + +## Performance Tips + +1. **Minimize uniforms**: Group related values into vectors +2. **Avoid conditionals**: Use mix/step instead of if/else +3. **Precalculate**: Move calculations to JS when possible +4. **Use textures**: For complex functions, use lookup tables +5. **Limit overdraw**: Avoid transparent objects when possible + +```glsl +// Instead of: +if (value > 0.5) { + color = colorA; +} else { + color = colorB; +} + +// Use: +color = mix(colorB, colorA, step(0.5, value)); +``` + +## See Also + +- `threejs-materials` - Built-in material types +- `threejs-postprocessing` - Full-screen shader effects +- `threejs-textures` - Texture sampling in shaders diff --git a/plugins/codex/plugins/threejs/skills/threejs-textures/SKILL.md b/plugins/codex/plugins/threejs/skills/threejs-textures/SKILL.md new file mode 100644 index 000000000..53642053e --- /dev/null +++ b/plugins/codex/plugins/threejs/skills/threejs-textures/SKILL.md @@ -0,0 +1,628 @@ +--- +name: threejs-textures +description: Three.js textures - texture types, UV mapping, environment maps, texture settings. Use when working with images, UV coordinates, cubemaps, HDR environments, or texture optimization. +--- + +# Three.js Textures + +## Quick Start + +```javascript +import * as THREE from "three"; + +// Load texture +const loader = new THREE.TextureLoader(); +const texture = loader.load("texture.jpg"); + +// Apply to material +const material = new THREE.MeshStandardMaterial({ + map: texture, +}); +``` + +## Texture Loading + +### Basic Loading + +```javascript +const loader = new THREE.TextureLoader(); + +// Async with callbacks +loader.load( + "texture.jpg", + (texture) => console.log("Loaded"), + (progress) => console.log("Progress"), + (error) => console.error("Error"), +); + +// Synchronous style (loads async internally) +const texture = loader.load("texture.jpg"); +material.map = texture; +``` + +### Promise Wrapper + +```javascript +function loadTexture(url) { + return new Promise((resolve, reject) => { + new THREE.TextureLoader().load(url, resolve, undefined, reject); + }); +} + +// Usage +const [colorMap, normalMap, roughnessMap] = await Promise.all([ + loadTexture("color.jpg"), + loadTexture("normal.jpg"), + loadTexture("roughness.jpg"), +]); +``` + +## Texture Configuration + +### Color Space + +Critical for accurate color reproduction. + +```javascript +// Color/albedo textures - use sRGB +colorTexture.colorSpace = THREE.SRGBColorSpace; + +// Data textures (normal, roughness, metalness, AO) - leave as default +// Do NOT set colorSpace for data textures (NoColorSpace is default) +``` + +### Wrapping Modes + +```javascript +texture.wrapS = THREE.RepeatWrapping; // Horizontal +texture.wrapT = THREE.RepeatWrapping; // Vertical + +// Options: +// THREE.ClampToEdgeWrapping - Stretches edge pixels (default) +// THREE.RepeatWrapping - Tiles the texture +// THREE.MirroredRepeatWrapping - Tiles with mirror flip +``` + +### Repeat, Offset, Rotation + +```javascript +// Tile texture 4x4 +texture.repeat.set(4, 4); +texture.wrapS = THREE.RepeatWrapping; +texture.wrapT = THREE.RepeatWrapping; + +// Offset (0-1 range) +texture.offset.set(0.5, 0.5); + +// Rotation (radians, around center) +texture.rotation = Math.PI / 4; +texture.center.set(0.5, 0.5); // Rotation pivot +``` + +### Filtering + +```javascript +// Minification (texture larger than screen pixels) +texture.minFilter = THREE.LinearMipmapLinearFilter; // Default, smooth +texture.minFilter = THREE.NearestFilter; // Pixelated +texture.minFilter = THREE.LinearFilter; // Smooth, no mipmaps + +// Magnification (texture smaller than screen pixels) +texture.magFilter = THREE.LinearFilter; // Smooth (default) +texture.magFilter = THREE.NearestFilter; // Pixelated (retro games) + +// Anisotropic filtering (sharper at angles) +texture.anisotropy = renderer.capabilities.getMaxAnisotropy(); +``` + +### Generate Mipmaps + +```javascript +// Usually true by default +texture.generateMipmaps = true; + +// Disable for non-power-of-2 textures or data textures +texture.generateMipmaps = false; +texture.minFilter = THREE.LinearFilter; +``` + +## Texture Types + +### Regular Texture + +```javascript +const texture = new THREE.Texture(image); +texture.needsUpdate = true; +``` + +### Data Texture + +Create texture from raw data. + +```javascript +// Create gradient texture +const size = 256; +const data = new Uint8Array(size * size * 4); + +for (let i = 0; i < size; i++) { + for (let j = 0; j < size; j++) { + const index = (i * size + j) * 4; + data[index] = i; // R + data[index + 1] = j; // G + data[index + 2] = 128; // B + data[index + 3] = 255; // A + } +} + +const texture = new THREE.DataTexture(data, size, size); +texture.needsUpdate = true; +``` + +### Canvas Texture + +```javascript +const canvas = document.createElement("canvas"); +canvas.width = 256; +canvas.height = 256; +const ctx = canvas.getContext("2d"); + +// Draw on canvas +ctx.fillStyle = "red"; +ctx.fillRect(0, 0, 256, 256); +ctx.fillStyle = "white"; +ctx.font = "48px Arial"; +ctx.fillText("Hello", 50, 150); + +const texture = new THREE.CanvasTexture(canvas); + +// Update when canvas changes +texture.needsUpdate = true; +``` + +### Video Texture + +```javascript +const video = document.createElement("video"); +video.src = "video.mp4"; +video.loop = true; +video.muted = true; +video.play(); + +const texture = new THREE.VideoTexture(video); +texture.colorSpace = THREE.SRGBColorSpace; + +// No need to set needsUpdate - auto-updates +``` + +### Compressed Textures + +```javascript +import { KTX2Loader } from "three/examples/jsm/loaders/KTX2Loader.js"; + +const ktx2Loader = new KTX2Loader(); +ktx2Loader.setTranscoderPath("path/to/basis/"); +ktx2Loader.detectSupport(renderer); + +ktx2Loader.load("texture.ktx2", (texture) => { + material.map = texture; +}); +``` + +## Cube Textures + +For environment maps and skyboxes. + +### CubeTextureLoader + +```javascript +const loader = new THREE.CubeTextureLoader(); +const cubeTexture = loader.load([ + "px.jpg", + "nx.jpg", // +X, -X + "py.jpg", + "ny.jpg", // +Y, -Y + "pz.jpg", + "nz.jpg", // +Z, -Z +]); + +// As background +scene.background = cubeTexture; + +// As environment map +scene.environment = cubeTexture; +material.envMap = cubeTexture; +``` + +### Equirectangular to Cubemap + +```javascript +import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"; + +const pmremGenerator = new THREE.PMREMGenerator(renderer); +pmremGenerator.compileEquirectangularShader(); + +new RGBELoader().load("environment.hdr", (texture) => { + const envMap = pmremGenerator.fromEquirectangular(texture).texture; + scene.environment = envMap; + scene.background = envMap; + + texture.dispose(); + pmremGenerator.dispose(); +}); +``` + +## HDR Textures + +### RGBELoader + +```javascript +import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"; + +const loader = new RGBELoader(); +loader.load("environment.hdr", (texture) => { + texture.mapping = THREE.EquirectangularReflectionMapping; + scene.environment = texture; + scene.background = texture; +}); +``` + +### EXRLoader + +```javascript +import { EXRLoader } from "three/examples/jsm/loaders/EXRLoader.js"; + +const loader = new EXRLoader(); +loader.load("environment.exr", (texture) => { + texture.mapping = THREE.EquirectangularReflectionMapping; + scene.environment = texture; +}); +``` + +### Background Options + +```javascript +scene.background = texture; +scene.backgroundBlurriness = 0.5; // 0-1, blur background +scene.backgroundIntensity = 1.0; // Brightness +scene.backgroundRotation.y = Math.PI; // Rotate background +``` + +## Render Targets + +Render to texture for effects. + +```javascript +// Create render target +const renderTarget = new THREE.WebGLRenderTarget(512, 512, { + minFilter: THREE.LinearFilter, + magFilter: THREE.LinearFilter, + format: THREE.RGBAFormat, +}); + +// Render scene to target +renderer.setRenderTarget(renderTarget); +renderer.render(scene, camera); +renderer.setRenderTarget(null); // Back to screen + +// Use as texture +material.map = renderTarget.texture; +``` + +### Depth Texture + +```javascript +const renderTarget = new THREE.WebGLRenderTarget(512, 512); +renderTarget.depthTexture = new THREE.DepthTexture( + 512, + 512, + THREE.UnsignedShortType, +); + +// Access depth +const depthTexture = renderTarget.depthTexture; +``` + +### Multi-Sample Render Target + +```javascript +const renderTarget = new THREE.WebGLRenderTarget(512, 512, { + samples: 4, // MSAA +}); +``` + +## CubeCamera + +Dynamic environment maps for reflections. + +```javascript +const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256, { + generateMipmaps: true, + minFilter: THREE.LinearMipmapLinearFilter, +}); + +const cubeCamera = new THREE.CubeCamera(0.1, 1000, cubeRenderTarget); +scene.add(cubeCamera); + +// Apply to reflective material +reflectiveMaterial.envMap = cubeRenderTarget.texture; + +// Update in animation loop (expensive!) +function animate() { + // Hide reflective object, update env map, show again + reflectiveObject.visible = false; + cubeCamera.position.copy(reflectiveObject.position); + cubeCamera.update(renderer, scene); + reflectiveObject.visible = true; +} +``` + +## UV Mapping + +### Accessing UVs + +```javascript +const uvs = geometry.attributes.uv; + +// Read UV +const u = uvs.getX(vertexIndex); +const v = uvs.getY(vertexIndex); + +// Modify UV +uvs.setXY(vertexIndex, newU, newV); +uvs.needsUpdate = true; +``` + +### Second UV Channel (for AO maps) + +```javascript +// Required for aoMap +geometry.setAttribute("uv2", geometry.attributes.uv); + +// Or create custom second UV +const uv2 = new Float32Array(vertexCount * 2); +// ... fill uv2 data +geometry.setAttribute("uv2", new THREE.BufferAttribute(uv2, 2)); +``` + +### UV Transform in Shader + +```javascript +const material = new THREE.ShaderMaterial({ + uniforms: { + map: { value: texture }, + uvOffset: { value: new THREE.Vector2(0, 0) }, + uvScale: { value: new THREE.Vector2(1, 1) }, + }, + vertexShader: ` + varying vec2 vUv; + uniform vec2 uvOffset; + uniform vec2 uvScale; + + void main() { + vUv = uv * uvScale + uvOffset; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `, + fragmentShader: ` + varying vec2 vUv; + uniform sampler2D map; + + void main() { + gl_FragColor = texture2D(map, vUv); + } + `, +}); +``` + +## Texture Atlas + +Multiple images in one texture. + +```javascript +// Atlas with 4 sprites (2x2 grid) +const atlas = loader.load("atlas.png"); +atlas.wrapS = THREE.ClampToEdgeWrapping; +atlas.wrapT = THREE.ClampToEdgeWrapping; + +// Select sprite by UV offset/scale +function selectSprite(row, col, gridSize = 2) { + atlas.offset.set(col / gridSize, 1 - (row + 1) / gridSize); + atlas.repeat.set(1 / gridSize, 1 / gridSize); +} + +// Select top-left sprite +selectSprite(0, 0); +``` + +## Material Texture Maps + +### PBR Texture Set + +```javascript +const material = new THREE.MeshStandardMaterial({ + // Base color (sRGB) + map: colorTexture, + + // Surface detail (Linear) + normalMap: normalTexture, + normalScale: new THREE.Vector2(1, 1), + + // Roughness (Linear, grayscale) + roughnessMap: roughnessTexture, + roughness: 1, // Multiplier + + // Metalness (Linear, grayscale) + metalnessMap: metalnessTexture, + metalness: 1, // Multiplier + + // Ambient occlusion (Linear, uses uv2) + aoMap: aoTexture, + aoMapIntensity: 1, + + // Self-illumination (sRGB) + emissiveMap: emissiveTexture, + emissive: 0xffffff, + emissiveIntensity: 1, + + // Vertex displacement (Linear) + displacementMap: displacementTexture, + displacementScale: 0.1, + displacementBias: 0, + + // Alpha (Linear) + alphaMap: alphaTexture, + transparent: true, +}); + +// Don't forget UV2 for AO +geometry.setAttribute("uv2", geometry.attributes.uv); +``` + +### Normal Map Types + +```javascript +// OpenGL style normals (default) +material.normalMapType = THREE.TangentSpaceNormalMap; + +// Object space normals +material.normalMapType = THREE.ObjectSpaceNormalMap; +``` + +## Procedural Textures + +### Noise Texture + +```javascript +function generateNoiseTexture(size = 256) { + const data = new Uint8Array(size * size * 4); + + for (let i = 0; i < size * size; i++) { + const value = Math.random() * 255; + data[i * 4] = value; + data[i * 4 + 1] = value; + data[i * 4 + 2] = value; + data[i * 4 + 3] = 255; + } + + const texture = new THREE.DataTexture(data, size, size); + texture.needsUpdate = true; + return texture; +} +``` + +### Gradient Texture + +```javascript +function generateGradientTexture(color1, color2, size = 256) { + const canvas = document.createElement("canvas"); + canvas.width = size; + canvas.height = 1; + const ctx = canvas.getContext("2d"); + + const gradient = ctx.createLinearGradient(0, 0, size, 0); + gradient.addColorStop(0, color1); + gradient.addColorStop(1, color2); + + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, size, 1); + + return new THREE.CanvasTexture(canvas); +} +``` + +## Texture Memory Management + +### Dispose Textures + +```javascript +// Single texture +texture.dispose(); + +// Material textures +function disposeMaterial(material) { + const maps = [ + "map", + "normalMap", + "roughnessMap", + "metalnessMap", + "aoMap", + "emissiveMap", + "displacementMap", + "alphaMap", + "envMap", + "lightMap", + "bumpMap", + "specularMap", + ]; + + maps.forEach((mapName) => { + if (material[mapName]) { + material[mapName].dispose(); + } + }); + + material.dispose(); +} +``` + +### Texture Pooling + +```javascript +class TexturePool { + constructor() { + this.textures = new Map(); + this.loader = new THREE.TextureLoader(); + } + + async get(url) { + if (this.textures.has(url)) { + return this.textures.get(url); + } + + const texture = await new Promise((resolve, reject) => { + this.loader.load(url, resolve, undefined, reject); + }); + + this.textures.set(url, texture); + return texture; + } + + dispose(url) { + const texture = this.textures.get(url); + if (texture) { + texture.dispose(); + this.textures.delete(url); + } + } + + disposeAll() { + this.textures.forEach((t) => t.dispose()); + this.textures.clear(); + } +} +``` + +## Performance Tips + +1. **Use power-of-2 dimensions**: 256, 512, 1024, 2048 +2. **Compress textures**: KTX2/Basis for web delivery +3. **Use texture atlases**: Reduce texture switches +4. **Enable mipmaps**: For distant objects +5. **Limit texture size**: 2048 usually sufficient for web +6. **Reuse textures**: Same texture = better batching + +```javascript +// Check texture memory +console.log(renderer.info.memory.textures); + +// Optimize for mobile +const maxSize = renderer.capabilities.maxTextureSize; +const isMobile = /iPhone|iPad|Android/i.test(navigator.userAgent); +const textureSize = isMobile ? 1024 : 2048; +``` + +## See Also + +- `threejs-materials` - Applying textures to materials +- `threejs-loaders` - Loading texture files +- `threejs-shaders` - Custom texture sampling