Materials
Material system with standard PBR materials, custom shaders, and specialized materials for water, foliage, and terrain.
Materials define how surfaces respond to light and appear when rendered. Web Engine supports Three.js materials, custom shaders, and includes specialized materials for common use cases like water, foliage, and terrain.
Standard Materials#
Web Engine supports all Three.js materials with automatic optimization and instancing:
| Material | Use Case | Performance |
|---|---|---|
| MeshStandardMaterial | PBR materials with realistic lighting | Medium |
| MeshBasicMaterial | Unlit materials, UI, effects | Fast |
| MeshPhongMaterial | Legacy Phong lighting | Medium |
| MeshPhysicalMaterial | Advanced PBR with clearcoat, transmission | Slow |
| MeshToonMaterial | Cartoon/cel-shaded look | Fast |
| ShaderMaterial | Custom GLSL shaders | Depends on shader |
PBR Materials (MeshStandardMaterial)#
The most commonly used material, providing physically-based rendering with metalness/roughness workflow:
import * as THREE from 'three'; // Create a PBR materialconst material = new THREE.MeshStandardMaterial({ color: 0x888888, // Base color metalness: 0.8, // 0 = dielectric, 1 = metal roughness: 0.3, // 0 = smooth, 1 = rough // Texture maps map: diffuseTexture, // Base color texture normalMap: normalTexture, // Normal map for detail roughnessMap: roughnessTexture, metalnessMap: metalnessTexture, aoMap: aoTexture, // Ambient occlusion // Additional properties emissive: 0xff0000, // Self-illumination emissiveIntensity: 0.5, envMapIntensity: 1.0, // Reflection strength}); // Apply to a meshmesh.material = material;Unlit Materials (MeshBasicMaterial)#
Use MeshBasicMaterial for objects that should not be affected by lighting, such as UI elements or emissive effects:
const unlitMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00, map: texture, transparent: true, opacity: 0.8, side: THREE.DoubleSide, // Render both sides // Unlit materials ignore lights completely // Perfect for UI, skyboxes, emissive objects});Material Properties#
Color and Texture#
| Property | Type | Description |
|---|---|---|
| color | Color | Base color tint (multiplied with map) |
| map | Texture | Diffuse/albedo texture |
| emissive | Color | Self-illumination color |
| emissiveMap | Texture | Emission texture |
| emissiveIntensity | number | Emission strength multiplier |
Surface Properties (PBR)#
| Property | Range | Description |
|---|---|---|
| metalness | 0-1 | How metallic the surface is |
| roughness | 0-1 | Surface roughness (0 = mirror) |
| normalMap | Texture | Normal map for surface detail |
| normalScale | Vector2 | Normal map intensity (x, y) |
| aoMap | Texture | Ambient occlusion map |
| aoMapIntensity | 0-1 | AO effect strength |
Transparency and Blending#
// Transparent material (alpha blending)const glassMaterial = new THREE.MeshPhysicalMaterial({ color: 0xffffff, transparent: true, opacity: 0.5, // Overall transparency transmission: 0.9, // Light transmission (glass) thickness: 0.5, // Thickness for refraction roughness: 0.05, metalness: 0.0,}); // Alpha cutout (for foliage, chain-link fences)const foliageMaterial = new THREE.MeshStandardMaterial({ map: leafTexture, alphaMap: leafTexture, // Use RGB as alpha transparent: true, alphaTest: 0.5, // Discard pixels below 0.5 side: THREE.DoubleSide,});Custom Materials#
Web Engine includes specialized materials for common scenarios:
Water Material
Animated waves, reflection, refraction, and depth-based color
Foliage Material
Vertex wind animation for grass, trees, and vegetation
Terrain Material
Multi-layer blending with height-based texturing
Impostor Material
Billboard rendering for distant LODs
Water Material#
The WaterMaterial provides realistic water rendering with animated waves and Fresnel reflection:
import { WaterMaterial } from '@web-engine-dev/core/engine/materials'; const water = new WaterMaterial({ waterColor: new THREE.Color(0x0077be), // Shallow water deepWaterColor: new THREE.Color(0x003355), // Deep water reflectionStrength: 0.6, waveSpeed: 1.0, waveScale: 1.0, waveHeight: 0.2, normalMap: waterNormalTexture,}); // Update time uniform each framefunction animate(time) { water.uniforms.uTime.value = time * 0.001;}Water Reflections
For best results, combine WaterMaterial with a reflection probe or planar reflection to capture the environment. The material blends this with Fresnel-based reflection strength.
Foliage Material#
Add wind animation to vegetation using vertex displacement:
import { createFoliageMaterial, FoliageUniforms} from '@web-engine-dev/core/engine/materials'; // Create foliage material from base materialconst baseMaterial = new THREE.MeshStandardMaterial({ map: grassTexture, alphaTest: 0.5, side: THREE.DoubleSide,}); const foliageMaterial = createFoliageMaterial(baseMaterial); // Update wind uniformsFoliageUniforms.uTime.value = time;FoliageUniforms.uWind.value.set(1, 0, 0.5); // Wind directionFoliageUniforms.uWindSpeed.value = 1.0;Terrain Material#
Multi-layer terrain material with automatic blending based on height and slope:
import { createTerrainMaterial } from '@web-engine-dev/core/engine/materials'; const terrainMaterial = createTerrainMaterial({ layers: [ { name: 'grass', diffuse: grassTexture, normal: grassNormalMap, heightRange: [0, 50], // 0-50m elevation slopeRange: [0, 0.3], // Flat to 30% slope scale: 10, // Texture tiling }, { name: 'rock', diffuse: rockTexture, normal: rockNormalMap, heightRange: [30, 100], slopeRange: [0.3, 1.0], // Steep slopes scale: 15, }, { name: 'snow', diffuse: snowTexture, normal: snowNormalMap, heightRange: [80, 200], // High elevation slopeRange: [0, 0.4], scale: 8, }, ], blendSharpness: 0.5, // Blend transition});Material Instancing#
The engine automatically shares material instances to reduce memory and state changes:
import { getSharedMaterial, getMaterialStats} from '@web-engine-dev/core/engine/rendering'; // Automatically share materials with identical propertiesconst mat1 = new THREE.MeshStandardMaterial({ color: 0xff0000 });const mat2 = new THREE.MeshStandardMaterial({ color: 0xff0000 }); // Get shared instance (reuses mat1 or mat2)const sharedMat = getSharedMaterial(mat1); // Check material sharing statsconst stats = getMaterialStats();console.log(`Sharing ${stats.sharedMaterials} materials`);console.log(`Reduced to ${stats.poolCount} unique instances`);Automatic Optimization
Material instancing happens automatically in the rendering pipeline. You don't need to manually call getSharedMaterial unless you're optimizing specific scenarios.
Custom Shader Materials#
Create fully custom materials using GLSL shaders:
const customMaterial = new THREE.ShaderMaterial({ uniforms: { uTime: { value: 0 }, uColor: { value: new THREE.Color(0xff0000) }, uTexture: { value: texture }, }, vertexShader: /* glsl */ ` varying vec2 vUv; varying vec3 vNormal; varying vec3 vPosition; void main() { vUv = uv; vNormal = normalize(normalMatrix * normal); vPosition = position; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: /* glsl */ ` uniform float uTime; uniform vec3 uColor; uniform sampler2D uTexture; varying vec2 vUv; varying vec3 vNormal; varying vec3 vPosition; void main() { // Sample texture vec4 texColor = texture2D(uTexture, vUv); // Animated effect float pulse = sin(uTime + vPosition.y * 2.0) * 0.5 + 0.5; // Combine vec3 color = texColor.rgb * uColor * pulse; gl_FragColor = vec4(color, texColor.a); } `, transparent: true, side: THREE.DoubleSide,});Using Three.js Shader Chunks#
Extend existing materials by injecting custom shader code:
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 }); material.onBeforeCompile = (shader) => { // Add custom uniforms shader.uniforms.uCustom = { value: 1.0 }; // Inject custom code into vertex shader shader.vertexShader = shader.vertexShader.replace( '#include <begin_vertex>', ` #include <begin_vertex> // Custom vertex displacement transformed.y += sin(position.x * 0.5 + uCustom) * 0.5; ` ); // Inject custom code into fragment shader shader.fragmentShader = shader.fragmentShader.replace( '#include <color_fragment>', ` #include <color_fragment> // Custom color modification diffuseColor.rgb *= uCustom; ` );}; // Trigger recompilationmaterial.needsUpdate = true;Material Variants#
Create material variants for different quality levels or LODs:
import { MaterialVariants } from '@web-engine-dev/core/engine/materials'; // Define material variantsconst variants = new MaterialVariants({ high: new THREE.MeshPhysicalMaterial({ map: diffuse4k, normalMap: normal4k, roughnessMap: roughness4k, clearcoat: 1.0, transmission: 0.5, }), medium: new THREE.MeshStandardMaterial({ map: diffuse2k, normalMap: normal2k, roughnessMap: roughness2k, }), low: new THREE.MeshBasicMaterial({ map: diffuse512, }),}); // Switch based on distance or quality settingsconst distance = camera.position.distanceTo(mesh.position);mesh.material = variants.get(distance > 100 ? 'low' : 'medium');Best Practices#
Performance#
- Reuse materials across multiple meshes to reduce state changes
- Use MeshBasicMaterial for unlit objects to skip lighting calculations
- Disable features you don't need (transparent: false, depthWrite: true)
- Use texture atlases instead of many small textures
- Compress textures using KTX2 or Basis Universal format
Visual Quality#
- Use normal maps to add surface detail without geometry
- Combine AO maps with lighting for better depth perception
- Use roughness/metalness maps for realistic material variation
- Apply proper texture filtering (mipmaps, anisotropic)
- Match material properties to real-world values for realism
Memory Management#
- Dispose unused materials: material.dispose()
- Release textures when not needed: texture.dispose()
- Use texture compression to reduce GPU memory
- Share materials between objects instead of cloning
- Monitor memory with renderer.info.memory
Transparency Sorting
Transparent materials require back-to-front sorting, which can be expensive. Use alphaTest (cutout) instead of full transparency when possible, and minimize overlapping transparent objects.