Reorganize: Move all skills to skills/ folder
- Created skills/ directory - Moved 272 skills to skills/ subfolder - Kept agents/ at root level - Kept installation scripts and docs at root level Repository structure: - skills/ - All 272 skills from skills.sh - agents/ - Agent definitions - *.sh, *.ps1 - Installation scripts - README.md, etc. - Documentation Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
628
skills/threejs-textures/skill.md
Normal file
628
skills/threejs-textures/skill.md
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user