Web Engine Docs
Preparing documentation
Use the search bar to quickly find any topic
Preparing documentation
Use the search bar to quickly find any topic
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.
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 |
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 colormetalness: 0.8, // 0 = dielectric, 1 = metalroughness: 0.3, // 0 = smooth, 1 = rough// Texture mapsmap: diffuseTexture, // Base color texturenormalMap: normalTexture, // Normal map for detailroughnessMap: roughnessTexture,metalnessMap: metalnessTexture,aoMap: aoTexture, // Ambient occlusion// Additional propertiesemissive: 0xff0000, // Self-illuminationemissiveIntensity: 0.5,envMapIntensity: 1.0, // Reflection strength});// Apply to a meshmesh.material = material;
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});
| 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 |
| 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 |
// Transparent material (alpha blending)const glassMaterial = new THREE.MeshPhysicalMaterial({color: 0xffffff,transparent: true,opacity: 0.5, // Overall transparencytransmission: 0.9, // Light transmission (glass)thickness: 0.5, // Thickness for refractionroughness: 0.05,metalness: 0.0,});// Alpha cutout (for foliage, chain-link fences)const foliageMaterial = new THREE.MeshStandardMaterial({map: leafTexture,alphaMap: leafTexture, // Use RGB as alphatransparent: true,alphaTest: 0.5, // Discard pixels below 0.5side: THREE.DoubleSide,});
Web Engine includes specialized materials for common scenarios:
Animated waves, reflection, refraction, and depth-based color
Vertex wind animation for grass, trees, and vegetation
Multi-layer blending with height-based texturing
Billboard rendering for distant LODs
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 waterdeepWaterColor: new THREE.Color(0x003355), // Deep waterreflectionStrength: 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.
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;
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 elevationslopeRange: [0, 0.3], // Flat to 30% slopescale: 10, // Texture tiling},{name: 'rock',diffuse: rockTexture,normal: rockNormalMap,heightRange: [30, 100],slopeRange: [0.3, 1.0], // Steep slopesscale: 15,},{name: 'snow',diffuse: snowTexture,normal: snowNormalMap,heightRange: [80, 200], // High elevationslopeRange: [0, 0.4],scale: 8,},],blendSharpness: 0.5, // Blend transition});
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.
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 texturevec4 texColor = texture2D(uTexture, vUv);// Animated effectfloat pulse = sin(uTime + vPosition.y * 2.0) * 0.5 + 0.5;// Combinevec3 color = texColor.rgb * uColor * pulse;gl_FragColor = vec4(color, texColor.a);}`,transparent: true,side: THREE.DoubleSide,});
Extend existing materials by injecting custom shader code:
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });material.onBeforeCompile = (shader) => {// Add custom uniformsshader.uniforms.uCustom = { value: 1.0 };// Inject custom code into vertex shadershader.vertexShader = shader.vertexShader.replace('#include <begin_vertex>',`#include <begin_vertex>// Custom vertex displacementtransformed.y += sin(position.x * 0.5 + uCustom) * 0.5;`);// Inject custom code into fragment shadershader.fragmentShader = shader.fragmentShader.replace('#include <color_fragment>',`#include <color_fragment>// Custom color modificationdiffuseColor.rgb *= uCustom;`);};// Trigger recompilationmaterial.needsUpdate = true;
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');
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.