Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Light and shade a three.js scene: choose materials (MeshStandardMaterial PBR vs unlit MeshBasicMaterial), add ambient/hemisphere/directional/point/spot lights, turn on shadow maps, and use an environment map (IBL) for realistic reflections. Use when a three.js model looks black, flat, or wrong — when the user mentions three.js materials, MeshStandardMaterial, lights, shadows, envMap, or PBR. For renderer/loop setup use threejs-scene-setup; for loading models use threejs-gltf-loading.
.claude/skills/gamedev-skills-threejs-materials-lighting/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 56% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 26% | 0% |
| case-07 | ✓→✓ | = Same ✓ | 61% | 0% |
| case-20 | ✓→✓ | = Same ✓ | 52% | 0% |
| case-21 | ✓→✓ | = Same ✓ | 109% | 0% |
Make three.js surfaces look right: pick the correct material, light the scene, enable shadows, and add image-based lighting. Patterns target r184, verified against r184 (lighting is physically based by default since r155).
enabling shadows, or setting up environment-map reflections (IBL).
MeshStandardMaterial, DirectionalLight, etc., or setsrenderer.shadowMap.enabled or scene.environment.
When not to use: the renderer/camera/loop → threejs-scene-setup. Loading models (whose PBR materials this complements) → threejs-gltf-loading. Custom GLSL/ShaderMaterial is its own topic; for the portable concept see shader-programming.
MeshStandardMaterial (PBR: roughness,metalness, reacts to lights/IBL) for realism; MeshPhysicalMaterial for clearcoat/transmission; MeshBasicMaterial (unlit, ignores lights) for UI/flat; MeshNormalMaterial/MeshDepthMaterial for debugging.
scene.environment. Combine a soft fill (AmbientLight/HemisphereLight) with a key DirectionalLight.
intensities are higher than old tutorials (a key DirectionalLight ≈ 1–3).
renderer.shadowMap.enabled = true, thelight's castShadow = true, and each mesh's castShadow/receiveShadow. Then fit the light's shadow camera to the scene.
PMREM-processed texture to scene.environment; PBR materials pick it up automatically.
(highlights move), shadows land where expected, and reflections look plausible.
jsimport * as THREE from 'three'; const material = new THREE.MeshStandardMaterial({ color: 0xcc4444, roughness: 0.5, // 0 = mirror, 1 = fully matte metalness: 0.0, // 0 = dielectric (plastic/wood), 1 = metal }); const mesh = new THREE.Mesh(new THREE.SphereGeometry(1, 32, 16), material); scene.add(mesh); // Soft sky/ground fill + a directional key light. scene.add(new THREE.HemisphereLight(0xbbddff, 0x443322, 1.0)); // sky, ground, intensity const key = new THREE.DirectionalLight(0xffffff, 2.5); key.position.set(5, 10, 7); scene.add(key);
js// MeshBasicMaterial ignores lights — for flat color, UI, or sprites/labels. const flat = new THREE.MeshBasicMaterial({ color: 0x44aa88 }); // A textured color map should be tagged sRGB so colors aren't washed out: const tex = new THREE.TextureLoader().load('assets/logo.png'); tex.colorSpace = THREE.SRGBColorSpace; const logo = new THREE.MeshBasicMaterial({ map: tex, transparent: true });
jsrenderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; // softer edges const sun = new THREE.DirectionalLight(0xffffff, 3); sun.position.set(8, 12, 6); sun.castShadow = true; sun.shadow.mapSize.set(2048, 2048); // default 512; raise for crisp // DirectionalLight uses an OrthographicCamera — fit it tightly to the scene: const cam = sun.shadow.camera; cam.near = 1; cam.far = 40; cam.left = -15; cam.right = 15; cam.top = 15; cam.bottom = -15; scene.add(sun); mesh.castShadow = true; ground.receiveShadow = true; // a plane to catch the shadow
jsconst loader = new THREE.TextureLoader(); const colorMap = loader.load('assets/brick_color.jpg'); colorMap.colorSpace = THREE.SRGBColorSpace; // color maps are sRGB const normalMap = loader.load('assets/brick_normal.jpg'); // data maps stay linear const roughMap = loader.load('assets/brick_rough.jpg'); const brick = new THREE.MeshStandardMaterial({ map: colorMap, normalMap, roughnessMap: roughMap, metalness: 0, });
jsimport { RGBELoader } from 'three/addons/loaders/RGBELoader.js'; new RGBELoader().load('assets/studio.hdr', (hdr) => { hdr.mapping = THREE.EquirectangularReflectionMapping; scene.environment = hdr; // lights + reflects all PBR materials scene.background = hdr; // optional: show it as the backdrop }); // Optional cinematic tone curve: renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 1.0;
scene.environment.Add a light or an environment map; to confirm geometry, temporarily swap to MeshBasicMaterial/MeshNormalMaterial.
based; raise intensities (key light ≈ 2–3) or add an environment map.
(renderer.shadowMap.enabled, light.castShadow, mesh castShadow/ receiveShadow).
DirectionalLight's orthographicshadow.camera frustum is too big/small or doesn't cover the scene; tighten left/right/top/bottom/near/far and raise shadow.mapSize. Visualise it with new THREE.CameraHelper(light.shadow.camera).
light.shadow.bias (small negative) andlight.shadow.normalBias.
texture.colorSpace = THREE.SRGBColorSpace; normal/roughness/metalness maps must stay linear (leave them as NoColorSpace).
(cube map). Prefer one shadow-casting DirectionalLight; use cheaper fakes elsewhere.
Mesh*Material for which look), light typesand their parameters/units, transparency vs alphaTest ordering, and the PMREMGenerator/RoomEnvironment route to IBL without an HDR file, read references/materials-lights-table.md.
threejs-scene-setup — renderer, camera, and loop (set shadowMap, tone mapping).threejs-gltf-loading — models arrive with PBR materials this skill tunes.shader-programming — custom shader effects (engine-agnostic concept).| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-07 | pass→pass | 13,449 | 11,414 | -15% | 1 | 1 | 0% | 2,574 | 4,139 | +61% | 0 | 0 | — |
case-20 | pass→pass | 11,539 | 9,165 | -21% | 1 | 1 | 0% | 2,393 | 3,626 | +52% | 0 | 0 | — |
case-01 | fail→pass | 10,917 | 12,107 | +11% | 1 | 1 | 0% | 2,030 | 3,166 | +56% | 0 | 0 | — |
case-02 | fail→pass | 19,943 | 8,386 | -58% | 1 | 1 | 0% | 2,678 | 3,369 | +26% | 0 | 0 | — |
case-21 | pass→pass | 9,960 | 10,826 | +9% | 1 | 1 | 0% | 1,827 | 3,813 | +109% | 0 | 0 | — |
case-03 | pass→pass | 11,327 | 4,888 | -57% | 1 | 1 | 0% | 2,282 | 2,817 | +23% | 0 | 0 | — |
case-04 | pass→pass | 9,195 | 6,252 | -32% | 1 | 1 | 0% | 1,859 | 3,078 | +66% | 0 | 0 | — |
case-05 | pass→pass | 11,980 | 10,674 | -11% | 1 | 1 | 0% | 1,513 | 3,005 | +99% | 0 | 0 | — |
case-06 | pass→pass | 20,094 | 18,379 | -9% | 1 | 1 | 0% | 2,746 | 4,750 | +73% | 0 | 0 | — |
case-08 | pass→pass | 7,061 | 8,215 | +16% | 1 | 1 | 0% | 1,192 | 3,356 | +182% | 0 | 0 | — |
case-09 | pass→pass | 17,608 | 7,417 | -58% | 1 | 1 | 0% | 2,342 | 3,268 | +40% | 0 | 0 | — |
case-10 | pass→pass | 16,238 | 14,577 | -10% | 1 | 1 | 0% | 2,038 | 3,560 | +75% | 0 | 0 | — |
case-11 | pass→pass | 10,328 | 7,355 | -29% | 1 | 1 | 0% | 1,766 | 3,218 | +82% | 0 | 0 | — |
case-12 | pass→pass | 10,941 | 9,485 | -13% | 1 | 1 | 0% | 2,055 | 3,724 | +81% | 0 | 0 | — |
case-13 | pass→pass | 13,056 | 10,555 | -19% | 1 | 1 | 0% | 2,160 | 4,003 | +85% | 0 | 0 | — |
case-14 | pass→pass | 10,369 | 10,472 | +1% | 1 | 1 | 0% | 1,939 | 3,950 | +104% | 0 | 0 | — |
case-15 | pass→pass | 4,589 | 3,915 | -15% | 1 | 1 | 0% | 922 | 2,640 | +186% | 0 | 0 | — |
case-16 | pass→pass | 4,051 | 3,226 | -20% | 1 | 1 | 0% | 639 | 2,490 | +290% | 0 | 0 | — |
case-17 | pass→pass | 5,441 | 8,351 | +53% | 1 | 1 | 0% | 962 | 2,700 | +181% | 0 | 0 | — |
case-18 | pass→pass | 7,280 | 6,481 | -11% | 1 | 1 | 0% | 1,233 | 2,919 | +137% | 0 | 0 | — |
case-19 | pass→pass | 15,088 | 11,475 | -24% | 1 | 1 | 0% | 2,388 | 3,895 | +63% | 0 | 0 | — |
case-22 | pass→pass | 12,226 | 12,040 | -2% | 1 | 1 | 0% | 2,307 | 4,229 | +83% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +9 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.