pbr-materials
Use this skill whenever creating, configuring, or assigning materials to any object in the Myelin simulator scene. This defines the PBR channel requirements, industrial material realism values, micro-
SKILL.mdprimary_build_source/docs/skills/3/mnt/user-data/outputs/pbr-materials/SKILL.md
GitHubThis skill defines the material authoring contract for the Myelin simulator. Every primary interactive object and environmental surface must use physically-based rendering (PBR) materials with the channel values and micro-detail requirements specified here. Visual quality is a non-negotiable part of the platform's trust and realism signal — especially for a safety-critical training context.
Core Principle: PBR-First
All primary objects use THREE.MeshStandardMaterial or THREE.MeshPhysicalMaterial. MeshPhysicalMaterial is required when clearcoat, transmission, IOR, or sheen is needed. MeshBasicMaterial and MeshLambertMaterial are prohibited on interactive training assets.
// Standard — use for most metal, painted, and rubber surfaces
const material = new THREE.MeshStandardMaterial({
map: baseColorTexture, // albedo
metalnessMap: metalnessTexture,
roughnessMap: roughnessTexture,
normalMap: normalTexture,
normalScale: new THREE.Vector2(1, 1),
aoMap: aoTexture, // ambient occlusion
aoMapIntensity: 1.0,
envMapIntensity: 1.0 // HDRI contribution — do not set to 0
});
// Physical — use for glass gauge faces, clearcoated painted surfaces
const physicalMaterial = new THREE.MeshPhysicalMaterial({
map: baseColorTexture,
metalnessMap: metalnessTexture,
roughnessMap: roughnessTexture,
normalMap: normalTexture,
clearcoat: 0.8,
clearcoatRoughness: 0.15,
ior: 1.52 // standard glass
});
envMapIntensity must never be set to 0 on primary objects. The HDRI environment is the primary light source for PBR — zeroing it out collapses the realism.
PBR Channel Requirements
Every primary interactive object must provide all channels marked Required. Channels marked Optional are strongly recommended for close-up quality tiers.
| Channel | Property | Required | Notes |
|---|---|---|---|
| Base color / albedo | map |
Yes | sRGB encoding |
| Metalness | metalnessMap |
Yes | Linear encoding |
| Roughness | roughnessMap |
Yes | Linear encoding |
| Normal | normalMap |
Yes | Tangent-space, OpenGL convention |
| Ambient occlusion | aoMap |
Yes | Baked, linear encoding |
| Emissive | emissiveMap |
Conditional | Required for indicator lights, warning labels |
| Clearcoat | clearcoat scalar |
Optional | Painted metal, safety equipment handles |
| Clearcoat roughness | clearcoatRoughness |
Optional | Pair with clearcoat |
| Transmission | transmission |
Optional | Glass gauge faces only |
| IOR | ior |
Optional | Glass (1.52), plastic (1.46), water (1.33) |
Texture encoding rules:
- Base color: load as
THREE.SRGBColorSpace - All other maps (metalness, roughness, normal, AO, emissive): load as
THREE.LinearSRGBColorSpace
function loadTexture(path, colorSpace) {
const loader = new THREE.TextureLoader();
return new Promise(resolve => {
loader.load(path, texture => {
texture.colorSpace = colorSpace;
texture.anisotropy = renderer.capabilities.getMaxAnisotropy(); // max sharpness at angles
resolve(texture);
});
});
}
// Usage
const baseColor = await loadTexture('/textures/valve/basecolor.jpg', THREE.SRGBColorSpace);
const roughness = await loadTexture('/textures/valve/roughness.jpg', THREE.LinearSRGBColorSpace);
Anisotropy must be set to the hardware maximum on all textures. Un-anisotropic textures blur badly at the oblique viewing angles common in close-up simulator inspection.
Industrial Material Reference Values
These are the starting-point PBR values for each material class. Adjust based on specific wear/age state of the object, but stay within the ranges.
Cast iron (valve body, pipe flanges)
{
metalness: 0.85, // mostly metallic
roughness: 0.70, // matte — heavily textured casting surface
color: new THREE.Color(0x2A2A2A), // dark grey base, adjusted by dirt/rust maps
// Normal map: coarse casting surface grain, ~1.5–2.0mm feature scale
}
Painted steel (valve handwheel, hasp body)
{
metalness: 0.0, // paint layer reads as non-metallic
roughness: 0.55, // slight sheen — industrial enamel
clearcoat: 0.4,
clearcoatRoughness: 0.3,
color: new THREE.Color(0xC8230A), // safety red — adjust per actual asset spec
}
Bare steel (hasp shackle, padlock shackle, bolt hardware)
{
metalness: 0.95,
roughness: 0.35, // polished but not mirror — machined finish
color: new THREE.Color(0xB8B8B8)
}
Safety padlock body (zinc alloy body, anodized)
{
metalness: 0.7,
roughness: 0.45,
clearcoat: 0.6,
clearcoatRoughness: 0.2,
color: new THREE.Color(0xFFCC00) // safety yellow; brand colour per asset spec
}
Rubber / grip handle (valve handwheel grip wrapping)
{
metalness: 0.0,
roughness: 0.85, // matte rubber
color: new THREE.Color(0x1A1A1A),
// Normal map: fine grip texture, ~0.5mm knurl pattern
}
Pressure gauge glass face
{
metalness: 0.0,
roughness: 0.05, // near-mirror for glass
transmission: 0.92, // mostly transparent
ior: 1.52,
thickness: 0.003, // 3mm glass thickness for refraction depth
color: new THREE.Color(0xFFFFFF)
}
PCB / circuit board surface (for PCB Rework scenario — feature-flagged)
{
metalness: 0.0,
roughness: 0.65,
color: new THREE.Color(0x1A3A1A), // dark green substrate
// Emissive map for indicator LEDs
// Normal map: trace pattern relief, solder mask texture
}
Micro-Detail Requirements
Micro-detail layers are required on all primary interactive objects at HD quality tier and above. They are the difference between a training-grade simulation and a game asset.
Scratch layer
Applied as a secondary normal map blended over the primary normal at reduced strength.
material.normalMap = primaryNormal;
material.normalScale = new THREE.Vector2(1.0, 1.0);
// Blend scratch detail via detail normal map in custom shader,
// or as a second UV channel normal if the asset supports it.
// Scratch normal strength: 0.15–0.25 (subtle, not dominant)
Scratch patterns must follow the geometry's use pattern — circular marks on rotating handwheels, linear marks on valve bodies from tool contact, edge wear on hasp jaws.
Rust / oxidation layer
Used on aged cast iron and exposed steel in the default scene state. Applied as a roughness/color blend mask — rusted areas have roughness 0.90–0.95 and a brown-orange hue shift.
// In the fragment shader or via a roughness variation map:
// Rust mask: white = rust, black = clean metal
// Rust roughness: lerp(baseRoughness, 0.92, rustMask)
// Rust color: lerp(baseColor, vec3(0.45, 0.22, 0.08), rustMask * 0.6)
Swirl marks and fingerprints
Applied to painted metal and polished steel. Concentric circular scratches on handwheel top surface. Fingerprint smears on padlock body.
These are baked into the roughness variation map as subtle light-grey smear patterns. Roughness delta: +0.05 to +0.12 over the base value.
Wear transfer
Edges of the hasp jaws and padlock shackle holes show wear from repeated locking cycles. Applied as edge-darkening in the AO map and edge-brightening (lower roughness) in the roughness map.
Emissive Materials
Emissive is used for:
- Pressure gauge indicator band (green → amber → red based on pressure value)
- Step indicator lights on the HUD proximity markers
- Warning label fluorescent ink on safety tags
function updateGaugeEmissive(material, pressureBar) {
if (pressureBar < 0.1) {
material.emissive.setHex(0x003300); // dim green — zero energy confirmed
material.emissiveIntensity = 0.4;
} else if (pressureBar < 2.0) {
material.emissive.setHex(0x332200); // amber — residual pressure
material.emissiveIntensity = 0.6;
} else {
material.emissive.setHex(0x330000); // red — active pressure
material.emissiveIntensity = 0.8;
}
}
Never set emissiveIntensity above 1.0 unless the bloom pass is active (HD_PLUS / UHD tier only). Over-bright emissives on MINIMUM tier produce blown-out artefacts without the bloom pass to tone them.
Failure State Visuals
Trade-specific failure state materials are required for the following events. These are triggered by the state machine — they are not decorative.
Arc flash (electrical isolation failure)
- Bright cyan-white emissive burst on the isolation point mesh
- Emissive intensity: 3.0–5.0 (requires bloom pass — only trigger on HD_PLUS+)
- Duration: 120ms flash, fade to char mark
- Follow-on: blackened roughness patch (roughness 0.98, metalness 0.1) at the arc point
Thermal runaway indicator (battery/PCB scenarios)
- Emissive gradient from yellow to red on the component body
- Thin smoke particle system (particle count <= 50 to stay within budget)
- Component roughness increases to 0.95 (heat damage)
Fluid leak (pressure release without zero-energy verification)
- Translucent spray particle effect from pipe joint
- Particle color: semi-opaque grey-white (steam/water)
- Particle count: <= 80 at HD tier
Smoke (overheating scenario)
- Slow-rising volumetric-style particle system
- Particle count: <= 60
- Opacity: 0.3–0.6 per particle with additive blending
All failure state effects must be preloaded and ready to trigger — not loaded on demand. Loading latency during a failure event breaks immersion.
Level of Detail (LOD) Material Strategy
At LOD0 and LOD1 (mobile/tablet), use simplified materials:
| LOD | Normal map | Micro-detail | AO | Emissive |
|---|---|---|---|---|
| LOD0 mobile | Disabled | No | Baked into albedo | Flat color only |
| LOD1 tablet | 512px | No | Separate map | Simple scalar |
| LOD2 desktop | 1024px | Yes | Separate map | Map + scalar |
| LOD3 VR/high | 2048px | Yes (2 layers) | Separate map | Map + dynamic |
See lod-performance-budget skill for face count and texture memory budgets per tier.
Texture Loading Strategy
All textures must be loaded non-blocking before the scene renders. Show a loading state — do not render a partially-textured scene.
async function loadAllMaterials() {
const [
valveMaterial,
haspMaterial,
padlockMaterial,
pipeMaterial,
gaugeMaterial
] = await Promise.all([
buildValveMaterial(),
buildHaspMaterial(),
buildPadlockMaterial(),
buildPipeMaterial(),
buildGaugeMaterial()
]);
return { valveMaterial, haspMaterial, padlockMaterial, pipeMaterial, gaugeMaterial };
}
Texture assets are loaded in parallel. Do not chain them sequentially — it compounds cold load time. Target: all textures loaded and applied before the first frame is rendered to the learner.
What Not To Do
- Do not use
MeshBasicMaterialorMeshLambertMaterialon any interactive training asset - Do not set
envMapIntensityto 0 — it collapses PBR lighting - Do not load base color textures as
LinearSRGBColorSpace— they must beSRGBColorSpace - Do not load non-color maps (roughness, normal, AO) as
SRGBColorSpace - Do not skip anisotropy setting on textures
- Do not set
emissiveIntensityabove 1.0 on MINIMUM or HD tiers without bloom pass - Do not trigger failure-state effects from on-demand loaded assets — preload them
- Do not apply micro-detail at LOD0 or LOD1 — budget does not support it
- Do not use flat-shaded geometry on any object visible at close-up inspection angles