create-threejs-game 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -0
- package/bin/cli.js +370 -0
- package/package.json +29 -0
- package/template/.claude/skills/threejs-animation/SKILL.md +552 -0
- package/template/.claude/skills/threejs-fundamentals/SKILL.md +488 -0
- package/template/.claude/skills/threejs-geometry/SKILL.md +548 -0
- package/template/.claude/skills/threejs-interaction/SKILL.md +660 -0
- package/template/.claude/skills/threejs-lighting/SKILL.md +481 -0
- package/template/.claude/skills/threejs-loaders/SKILL.md +623 -0
- package/template/.claude/skills/threejs-materials/SKILL.md +520 -0
- package/template/.claude/skills/threejs-postprocessing/SKILL.md +602 -0
- package/template/.claude/skills/threejs-shaders/SKILL.md +642 -0
- package/template/.claude/skills/threejs-textures/SKILL.md +628 -0
- package/template/.codex/skills/threejs-animation/SKILL.md +552 -0
- package/template/.codex/skills/threejs-fundamentals/SKILL.md +488 -0
- package/template/.codex/skills/threejs-geometry/SKILL.md +548 -0
- package/template/.codex/skills/threejs-interaction/SKILL.md +660 -0
- package/template/.codex/skills/threejs-lighting/SKILL.md +481 -0
- package/template/.codex/skills/threejs-loaders/SKILL.md +623 -0
- package/template/.codex/skills/threejs-materials/SKILL.md +520 -0
- package/template/.codex/skills/threejs-postprocessing/SKILL.md +602 -0
- package/template/.codex/skills/threejs-shaders/SKILL.md +642 -0
- package/template/.codex/skills/threejs-textures/SKILL.md +628 -0
- package/template/README.md +352 -0
- package/template/docs/.gitkeep +0 -0
- package/template/plans/.gitkeep +0 -0
- package/template/prompts/01-mockup-generation.md +44 -0
- package/template/prompts/02-prd-generation.md +119 -0
- package/template/prompts/03-tdd-generation.md +127 -0
- package/template/prompts/04-execution-plan.md +89 -0
- package/template/prompts/05-implementation.md +61 -0
- package/template/public/assets/.gitkeep +0 -0
- package/template/scripts/config.example.json +12 -0
- package/template/scripts/generate-assets-json.js +149 -0
- package/template/scripts/generate-mockup.js +197 -0
- package/template/scripts/generate-plan.js +295 -0
- package/template/scripts/generate-prd.js +297 -0
- package/template/scripts/generate-tdd.js +283 -0
- package/template/scripts/pipeline.js +229 -0
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: threejs-textures
|
|
3
|
+
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.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Three.js Textures
|
|
7
|
+
|
|
8
|
+
## Quick Start
|
|
9
|
+
|
|
10
|
+
```javascript
|
|
11
|
+
import * as THREE from "three";
|
|
12
|
+
|
|
13
|
+
// Load texture
|
|
14
|
+
const loader = new THREE.TextureLoader();
|
|
15
|
+
const texture = loader.load("texture.jpg");
|
|
16
|
+
|
|
17
|
+
// Apply to material
|
|
18
|
+
const material = new THREE.MeshStandardMaterial({
|
|
19
|
+
map: texture,
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Texture Loading
|
|
24
|
+
|
|
25
|
+
### Basic Loading
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
const loader = new THREE.TextureLoader();
|
|
29
|
+
|
|
30
|
+
// Async with callbacks
|
|
31
|
+
loader.load(
|
|
32
|
+
"texture.jpg",
|
|
33
|
+
(texture) => console.log("Loaded"),
|
|
34
|
+
(progress) => console.log("Progress"),
|
|
35
|
+
(error) => console.error("Error"),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
// Synchronous style (loads async internally)
|
|
39
|
+
const texture = loader.load("texture.jpg");
|
|
40
|
+
material.map = texture;
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Promise Wrapper
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
function loadTexture(url) {
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
new THREE.TextureLoader().load(url, resolve, undefined, reject);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Usage
|
|
53
|
+
const [colorMap, normalMap, roughnessMap] = await Promise.all([
|
|
54
|
+
loadTexture("color.jpg"),
|
|
55
|
+
loadTexture("normal.jpg"),
|
|
56
|
+
loadTexture("roughness.jpg"),
|
|
57
|
+
]);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Texture Configuration
|
|
61
|
+
|
|
62
|
+
### Color Space
|
|
63
|
+
|
|
64
|
+
Critical for accurate color reproduction.
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
// Color/albedo textures - use sRGB
|
|
68
|
+
colorTexture.colorSpace = THREE.SRGBColorSpace;
|
|
69
|
+
|
|
70
|
+
// Data textures (normal, roughness, metalness, AO) - leave as default
|
|
71
|
+
// Do NOT set colorSpace for data textures (NoColorSpace is default)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Wrapping Modes
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
texture.wrapS = THREE.RepeatWrapping; // Horizontal
|
|
78
|
+
texture.wrapT = THREE.RepeatWrapping; // Vertical
|
|
79
|
+
|
|
80
|
+
// Options:
|
|
81
|
+
// THREE.ClampToEdgeWrapping - Stretches edge pixels (default)
|
|
82
|
+
// THREE.RepeatWrapping - Tiles the texture
|
|
83
|
+
// THREE.MirroredRepeatWrapping - Tiles with mirror flip
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Repeat, Offset, Rotation
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
// Tile texture 4x4
|
|
90
|
+
texture.repeat.set(4, 4);
|
|
91
|
+
texture.wrapS = THREE.RepeatWrapping;
|
|
92
|
+
texture.wrapT = THREE.RepeatWrapping;
|
|
93
|
+
|
|
94
|
+
// Offset (0-1 range)
|
|
95
|
+
texture.offset.set(0.5, 0.5);
|
|
96
|
+
|
|
97
|
+
// Rotation (radians, around center)
|
|
98
|
+
texture.rotation = Math.PI / 4;
|
|
99
|
+
texture.center.set(0.5, 0.5); // Rotation pivot
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Filtering
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
// Minification (texture larger than screen pixels)
|
|
106
|
+
texture.minFilter = THREE.LinearMipmapLinearFilter; // Default, smooth
|
|
107
|
+
texture.minFilter = THREE.NearestFilter; // Pixelated
|
|
108
|
+
texture.minFilter = THREE.LinearFilter; // Smooth, no mipmaps
|
|
109
|
+
|
|
110
|
+
// Magnification (texture smaller than screen pixels)
|
|
111
|
+
texture.magFilter = THREE.LinearFilter; // Smooth (default)
|
|
112
|
+
texture.magFilter = THREE.NearestFilter; // Pixelated (retro games)
|
|
113
|
+
|
|
114
|
+
// Anisotropic filtering (sharper at angles)
|
|
115
|
+
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Generate Mipmaps
|
|
119
|
+
|
|
120
|
+
```javascript
|
|
121
|
+
// Usually true by default
|
|
122
|
+
texture.generateMipmaps = true;
|
|
123
|
+
|
|
124
|
+
// Disable for non-power-of-2 textures or data textures
|
|
125
|
+
texture.generateMipmaps = false;
|
|
126
|
+
texture.minFilter = THREE.LinearFilter;
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Texture Types
|
|
130
|
+
|
|
131
|
+
### Regular Texture
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
const texture = new THREE.Texture(image);
|
|
135
|
+
texture.needsUpdate = true;
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Data Texture
|
|
139
|
+
|
|
140
|
+
Create texture from raw data.
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
// Create gradient texture
|
|
144
|
+
const size = 256;
|
|
145
|
+
const data = new Uint8Array(size * size * 4);
|
|
146
|
+
|
|
147
|
+
for (let i = 0; i < size; i++) {
|
|
148
|
+
for (let j = 0; j < size; j++) {
|
|
149
|
+
const index = (i * size + j) * 4;
|
|
150
|
+
data[index] = i; // R
|
|
151
|
+
data[index + 1] = j; // G
|
|
152
|
+
data[index + 2] = 128; // B
|
|
153
|
+
data[index + 3] = 255; // A
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const texture = new THREE.DataTexture(data, size, size);
|
|
158
|
+
texture.needsUpdate = true;
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Canvas Texture
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
const canvas = document.createElement("canvas");
|
|
165
|
+
canvas.width = 256;
|
|
166
|
+
canvas.height = 256;
|
|
167
|
+
const ctx = canvas.getContext("2d");
|
|
168
|
+
|
|
169
|
+
// Draw on canvas
|
|
170
|
+
ctx.fillStyle = "red";
|
|
171
|
+
ctx.fillRect(0, 0, 256, 256);
|
|
172
|
+
ctx.fillStyle = "white";
|
|
173
|
+
ctx.font = "48px Arial";
|
|
174
|
+
ctx.fillText("Hello", 50, 150);
|
|
175
|
+
|
|
176
|
+
const texture = new THREE.CanvasTexture(canvas);
|
|
177
|
+
|
|
178
|
+
// Update when canvas changes
|
|
179
|
+
texture.needsUpdate = true;
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Video Texture
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
const video = document.createElement("video");
|
|
186
|
+
video.src = "video.mp4";
|
|
187
|
+
video.loop = true;
|
|
188
|
+
video.muted = true;
|
|
189
|
+
video.play();
|
|
190
|
+
|
|
191
|
+
const texture = new THREE.VideoTexture(video);
|
|
192
|
+
texture.colorSpace = THREE.SRGBColorSpace;
|
|
193
|
+
|
|
194
|
+
// No need to set needsUpdate - auto-updates
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Compressed Textures
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
import { KTX2Loader } from "three/examples/jsm/loaders/KTX2Loader.js";
|
|
201
|
+
|
|
202
|
+
const ktx2Loader = new KTX2Loader();
|
|
203
|
+
ktx2Loader.setTranscoderPath("path/to/basis/");
|
|
204
|
+
ktx2Loader.detectSupport(renderer);
|
|
205
|
+
|
|
206
|
+
ktx2Loader.load("texture.ktx2", (texture) => {
|
|
207
|
+
material.map = texture;
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Cube Textures
|
|
212
|
+
|
|
213
|
+
For environment maps and skyboxes.
|
|
214
|
+
|
|
215
|
+
### CubeTextureLoader
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
const loader = new THREE.CubeTextureLoader();
|
|
219
|
+
const cubeTexture = loader.load([
|
|
220
|
+
"px.jpg",
|
|
221
|
+
"nx.jpg", // +X, -X
|
|
222
|
+
"py.jpg",
|
|
223
|
+
"ny.jpg", // +Y, -Y
|
|
224
|
+
"pz.jpg",
|
|
225
|
+
"nz.jpg", // +Z, -Z
|
|
226
|
+
]);
|
|
227
|
+
|
|
228
|
+
// As background
|
|
229
|
+
scene.background = cubeTexture;
|
|
230
|
+
|
|
231
|
+
// As environment map
|
|
232
|
+
scene.environment = cubeTexture;
|
|
233
|
+
material.envMap = cubeTexture;
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Equirectangular to Cubemap
|
|
237
|
+
|
|
238
|
+
```javascript
|
|
239
|
+
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";
|
|
240
|
+
|
|
241
|
+
const pmremGenerator = new THREE.PMREMGenerator(renderer);
|
|
242
|
+
pmremGenerator.compileEquirectangularShader();
|
|
243
|
+
|
|
244
|
+
new RGBELoader().load("environment.hdr", (texture) => {
|
|
245
|
+
const envMap = pmremGenerator.fromEquirectangular(texture).texture;
|
|
246
|
+
scene.environment = envMap;
|
|
247
|
+
scene.background = envMap;
|
|
248
|
+
|
|
249
|
+
texture.dispose();
|
|
250
|
+
pmremGenerator.dispose();
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## HDR Textures
|
|
255
|
+
|
|
256
|
+
### RGBELoader
|
|
257
|
+
|
|
258
|
+
```javascript
|
|
259
|
+
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";
|
|
260
|
+
|
|
261
|
+
const loader = new RGBELoader();
|
|
262
|
+
loader.load("environment.hdr", (texture) => {
|
|
263
|
+
texture.mapping = THREE.EquirectangularReflectionMapping;
|
|
264
|
+
scene.environment = texture;
|
|
265
|
+
scene.background = texture;
|
|
266
|
+
});
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### EXRLoader
|
|
270
|
+
|
|
271
|
+
```javascript
|
|
272
|
+
import { EXRLoader } from "three/examples/jsm/loaders/EXRLoader.js";
|
|
273
|
+
|
|
274
|
+
const loader = new EXRLoader();
|
|
275
|
+
loader.load("environment.exr", (texture) => {
|
|
276
|
+
texture.mapping = THREE.EquirectangularReflectionMapping;
|
|
277
|
+
scene.environment = texture;
|
|
278
|
+
});
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Background Options
|
|
282
|
+
|
|
283
|
+
```javascript
|
|
284
|
+
scene.background = texture;
|
|
285
|
+
scene.backgroundBlurriness = 0.5; // 0-1, blur background
|
|
286
|
+
scene.backgroundIntensity = 1.0; // Brightness
|
|
287
|
+
scene.backgroundRotation.y = Math.PI; // Rotate background
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Render Targets
|
|
291
|
+
|
|
292
|
+
Render to texture for effects.
|
|
293
|
+
|
|
294
|
+
```javascript
|
|
295
|
+
// Create render target
|
|
296
|
+
const renderTarget = new THREE.WebGLRenderTarget(512, 512, {
|
|
297
|
+
minFilter: THREE.LinearFilter,
|
|
298
|
+
magFilter: THREE.LinearFilter,
|
|
299
|
+
format: THREE.RGBAFormat,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Render scene to target
|
|
303
|
+
renderer.setRenderTarget(renderTarget);
|
|
304
|
+
renderer.render(scene, camera);
|
|
305
|
+
renderer.setRenderTarget(null); // Back to screen
|
|
306
|
+
|
|
307
|
+
// Use as texture
|
|
308
|
+
material.map = renderTarget.texture;
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Depth Texture
|
|
312
|
+
|
|
313
|
+
```javascript
|
|
314
|
+
const renderTarget = new THREE.WebGLRenderTarget(512, 512);
|
|
315
|
+
renderTarget.depthTexture = new THREE.DepthTexture(
|
|
316
|
+
512,
|
|
317
|
+
512,
|
|
318
|
+
THREE.UnsignedShortType,
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
// Access depth
|
|
322
|
+
const depthTexture = renderTarget.depthTexture;
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Multi-Sample Render Target
|
|
326
|
+
|
|
327
|
+
```javascript
|
|
328
|
+
const renderTarget = new THREE.WebGLRenderTarget(512, 512, {
|
|
329
|
+
samples: 4, // MSAA
|
|
330
|
+
});
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## CubeCamera
|
|
334
|
+
|
|
335
|
+
Dynamic environment maps for reflections.
|
|
336
|
+
|
|
337
|
+
```javascript
|
|
338
|
+
const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256, {
|
|
339
|
+
generateMipmaps: true,
|
|
340
|
+
minFilter: THREE.LinearMipmapLinearFilter,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
const cubeCamera = new THREE.CubeCamera(0.1, 1000, cubeRenderTarget);
|
|
344
|
+
scene.add(cubeCamera);
|
|
345
|
+
|
|
346
|
+
// Apply to reflective material
|
|
347
|
+
reflectiveMaterial.envMap = cubeRenderTarget.texture;
|
|
348
|
+
|
|
349
|
+
// Update in animation loop (expensive!)
|
|
350
|
+
function animate() {
|
|
351
|
+
// Hide reflective object, update env map, show again
|
|
352
|
+
reflectiveObject.visible = false;
|
|
353
|
+
cubeCamera.position.copy(reflectiveObject.position);
|
|
354
|
+
cubeCamera.update(renderer, scene);
|
|
355
|
+
reflectiveObject.visible = true;
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## UV Mapping
|
|
360
|
+
|
|
361
|
+
### Accessing UVs
|
|
362
|
+
|
|
363
|
+
```javascript
|
|
364
|
+
const uvs = geometry.attributes.uv;
|
|
365
|
+
|
|
366
|
+
// Read UV
|
|
367
|
+
const u = uvs.getX(vertexIndex);
|
|
368
|
+
const v = uvs.getY(vertexIndex);
|
|
369
|
+
|
|
370
|
+
// Modify UV
|
|
371
|
+
uvs.setXY(vertexIndex, newU, newV);
|
|
372
|
+
uvs.needsUpdate = true;
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Second UV Channel (for AO maps)
|
|
376
|
+
|
|
377
|
+
```javascript
|
|
378
|
+
// Required for aoMap
|
|
379
|
+
geometry.setAttribute("uv2", geometry.attributes.uv);
|
|
380
|
+
|
|
381
|
+
// Or create custom second UV
|
|
382
|
+
const uv2 = new Float32Array(vertexCount * 2);
|
|
383
|
+
// ... fill uv2 data
|
|
384
|
+
geometry.setAttribute("uv2", new THREE.BufferAttribute(uv2, 2));
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### UV Transform in Shader
|
|
388
|
+
|
|
389
|
+
```javascript
|
|
390
|
+
const material = new THREE.ShaderMaterial({
|
|
391
|
+
uniforms: {
|
|
392
|
+
map: { value: texture },
|
|
393
|
+
uvOffset: { value: new THREE.Vector2(0, 0) },
|
|
394
|
+
uvScale: { value: new THREE.Vector2(1, 1) },
|
|
395
|
+
},
|
|
396
|
+
vertexShader: `
|
|
397
|
+
varying vec2 vUv;
|
|
398
|
+
uniform vec2 uvOffset;
|
|
399
|
+
uniform vec2 uvScale;
|
|
400
|
+
|
|
401
|
+
void main() {
|
|
402
|
+
vUv = uv * uvScale + uvOffset;
|
|
403
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
404
|
+
}
|
|
405
|
+
`,
|
|
406
|
+
fragmentShader: `
|
|
407
|
+
varying vec2 vUv;
|
|
408
|
+
uniform sampler2D map;
|
|
409
|
+
|
|
410
|
+
void main() {
|
|
411
|
+
gl_FragColor = texture2D(map, vUv);
|
|
412
|
+
}
|
|
413
|
+
`,
|
|
414
|
+
});
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## Texture Atlas
|
|
418
|
+
|
|
419
|
+
Multiple images in one texture.
|
|
420
|
+
|
|
421
|
+
```javascript
|
|
422
|
+
// Atlas with 4 sprites (2x2 grid)
|
|
423
|
+
const atlas = loader.load("atlas.png");
|
|
424
|
+
atlas.wrapS = THREE.ClampToEdgeWrapping;
|
|
425
|
+
atlas.wrapT = THREE.ClampToEdgeWrapping;
|
|
426
|
+
|
|
427
|
+
// Select sprite by UV offset/scale
|
|
428
|
+
function selectSprite(row, col, gridSize = 2) {
|
|
429
|
+
atlas.offset.set(col / gridSize, 1 - (row + 1) / gridSize);
|
|
430
|
+
atlas.repeat.set(1 / gridSize, 1 / gridSize);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Select top-left sprite
|
|
434
|
+
selectSprite(0, 0);
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
## Material Texture Maps
|
|
438
|
+
|
|
439
|
+
### PBR Texture Set
|
|
440
|
+
|
|
441
|
+
```javascript
|
|
442
|
+
const material = new THREE.MeshStandardMaterial({
|
|
443
|
+
// Base color (sRGB)
|
|
444
|
+
map: colorTexture,
|
|
445
|
+
|
|
446
|
+
// Surface detail (Linear)
|
|
447
|
+
normalMap: normalTexture,
|
|
448
|
+
normalScale: new THREE.Vector2(1, 1),
|
|
449
|
+
|
|
450
|
+
// Roughness (Linear, grayscale)
|
|
451
|
+
roughnessMap: roughnessTexture,
|
|
452
|
+
roughness: 1, // Multiplier
|
|
453
|
+
|
|
454
|
+
// Metalness (Linear, grayscale)
|
|
455
|
+
metalnessMap: metalnessTexture,
|
|
456
|
+
metalness: 1, // Multiplier
|
|
457
|
+
|
|
458
|
+
// Ambient occlusion (Linear, uses uv2)
|
|
459
|
+
aoMap: aoTexture,
|
|
460
|
+
aoMapIntensity: 1,
|
|
461
|
+
|
|
462
|
+
// Self-illumination (sRGB)
|
|
463
|
+
emissiveMap: emissiveTexture,
|
|
464
|
+
emissive: 0xffffff,
|
|
465
|
+
emissiveIntensity: 1,
|
|
466
|
+
|
|
467
|
+
// Vertex displacement (Linear)
|
|
468
|
+
displacementMap: displacementTexture,
|
|
469
|
+
displacementScale: 0.1,
|
|
470
|
+
displacementBias: 0,
|
|
471
|
+
|
|
472
|
+
// Alpha (Linear)
|
|
473
|
+
alphaMap: alphaTexture,
|
|
474
|
+
transparent: true,
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
// Don't forget UV2 for AO
|
|
478
|
+
geometry.setAttribute("uv2", geometry.attributes.uv);
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### Normal Map Types
|
|
482
|
+
|
|
483
|
+
```javascript
|
|
484
|
+
// OpenGL style normals (default)
|
|
485
|
+
material.normalMapType = THREE.TangentSpaceNormalMap;
|
|
486
|
+
|
|
487
|
+
// Object space normals
|
|
488
|
+
material.normalMapType = THREE.ObjectSpaceNormalMap;
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
## Procedural Textures
|
|
492
|
+
|
|
493
|
+
### Noise Texture
|
|
494
|
+
|
|
495
|
+
```javascript
|
|
496
|
+
function generateNoiseTexture(size = 256) {
|
|
497
|
+
const data = new Uint8Array(size * size * 4);
|
|
498
|
+
|
|
499
|
+
for (let i = 0; i < size * size; i++) {
|
|
500
|
+
const value = Math.random() * 255;
|
|
501
|
+
data[i * 4] = value;
|
|
502
|
+
data[i * 4 + 1] = value;
|
|
503
|
+
data[i * 4 + 2] = value;
|
|
504
|
+
data[i * 4 + 3] = 255;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const texture = new THREE.DataTexture(data, size, size);
|
|
508
|
+
texture.needsUpdate = true;
|
|
509
|
+
return texture;
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Gradient Texture
|
|
514
|
+
|
|
515
|
+
```javascript
|
|
516
|
+
function generateGradientTexture(color1, color2, size = 256) {
|
|
517
|
+
const canvas = document.createElement("canvas");
|
|
518
|
+
canvas.width = size;
|
|
519
|
+
canvas.height = 1;
|
|
520
|
+
const ctx = canvas.getContext("2d");
|
|
521
|
+
|
|
522
|
+
const gradient = ctx.createLinearGradient(0, 0, size, 0);
|
|
523
|
+
gradient.addColorStop(0, color1);
|
|
524
|
+
gradient.addColorStop(1, color2);
|
|
525
|
+
|
|
526
|
+
ctx.fillStyle = gradient;
|
|
527
|
+
ctx.fillRect(0, 0, size, 1);
|
|
528
|
+
|
|
529
|
+
return new THREE.CanvasTexture(canvas);
|
|
530
|
+
}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
## Texture Memory Management
|
|
534
|
+
|
|
535
|
+
### Dispose Textures
|
|
536
|
+
|
|
537
|
+
```javascript
|
|
538
|
+
// Single texture
|
|
539
|
+
texture.dispose();
|
|
540
|
+
|
|
541
|
+
// Material textures
|
|
542
|
+
function disposeMaterial(material) {
|
|
543
|
+
const maps = [
|
|
544
|
+
"map",
|
|
545
|
+
"normalMap",
|
|
546
|
+
"roughnessMap",
|
|
547
|
+
"metalnessMap",
|
|
548
|
+
"aoMap",
|
|
549
|
+
"emissiveMap",
|
|
550
|
+
"displacementMap",
|
|
551
|
+
"alphaMap",
|
|
552
|
+
"envMap",
|
|
553
|
+
"lightMap",
|
|
554
|
+
"bumpMap",
|
|
555
|
+
"specularMap",
|
|
556
|
+
];
|
|
557
|
+
|
|
558
|
+
maps.forEach((mapName) => {
|
|
559
|
+
if (material[mapName]) {
|
|
560
|
+
material[mapName].dispose();
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
material.dispose();
|
|
565
|
+
}
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### Texture Pooling
|
|
569
|
+
|
|
570
|
+
```javascript
|
|
571
|
+
class TexturePool {
|
|
572
|
+
constructor() {
|
|
573
|
+
this.textures = new Map();
|
|
574
|
+
this.loader = new THREE.TextureLoader();
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
async get(url) {
|
|
578
|
+
if (this.textures.has(url)) {
|
|
579
|
+
return this.textures.get(url);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const texture = await new Promise((resolve, reject) => {
|
|
583
|
+
this.loader.load(url, resolve, undefined, reject);
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
this.textures.set(url, texture);
|
|
587
|
+
return texture;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
dispose(url) {
|
|
591
|
+
const texture = this.textures.get(url);
|
|
592
|
+
if (texture) {
|
|
593
|
+
texture.dispose();
|
|
594
|
+
this.textures.delete(url);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
disposeAll() {
|
|
599
|
+
this.textures.forEach((t) => t.dispose());
|
|
600
|
+
this.textures.clear();
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
## Performance Tips
|
|
606
|
+
|
|
607
|
+
1. **Use power-of-2 dimensions**: 256, 512, 1024, 2048
|
|
608
|
+
2. **Compress textures**: KTX2/Basis for web delivery
|
|
609
|
+
3. **Use texture atlases**: Reduce texture switches
|
|
610
|
+
4. **Enable mipmaps**: For distant objects
|
|
611
|
+
5. **Limit texture size**: 2048 usually sufficient for web
|
|
612
|
+
6. **Reuse textures**: Same texture = better batching
|
|
613
|
+
|
|
614
|
+
```javascript
|
|
615
|
+
// Check texture memory
|
|
616
|
+
console.log(renderer.info.memory.textures);
|
|
617
|
+
|
|
618
|
+
// Optimize for mobile
|
|
619
|
+
const maxSize = renderer.capabilities.maxTextureSize;
|
|
620
|
+
const isMobile = /iPhone|iPad|Android/i.test(navigator.userAgent);
|
|
621
|
+
const textureSize = isMobile ? 1024 : 2048;
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
## See Also
|
|
625
|
+
|
|
626
|
+
- `threejs-materials` - Applying textures to materials
|
|
627
|
+
- `threejs-loaders` - Loading texture files
|
|
628
|
+
- `threejs-shaders` - Custom texture sampling
|