Meshes, Geometry & Materials
Understanding how to create and work with 3D objects is fundamental to any Three.js application. This guide covers the building blocks: geometries (shapes), materials (appearance), and meshes (the combination).
The Core Equation
Every visible 3D object follows this pattern:
Mesh = Geometry + Material
Geometry: The shape (vertices, faces, UVs)
Material: The appearance (color, texture, shading)
Mesh: The renderable object that combines themIn IWSDK, you create these using Three.js classes and attach them to entities.
Geometries: The Shape of Things
Geometry defines the shape of 3D objects using vertices, faces, and texture coordinates.
Built-in Geometries
Three.js provides many primitive shapes:
import {
BoxGeometry,
SphereGeometry,
CylinderGeometry,
PlaneGeometry,
TorusGeometry,
} from '@iwsdk/core';
// Basic shapes with parameters
const box = new BoxGeometry(1, 1, 1); // width, height, depth
const sphere = new SphereGeometry(0.5, 32, 16); // radius, widthSeg, heightSeg
const cylinder = new CylinderGeometry(0.5, 0.5, 2, 32); // radiusTop, radiusBottom, height, segments
const plane = new PlaneGeometry(2, 2); // width, height
const torus = new TorusGeometry(1, 0.3, 16, 100); // radius, tube, radialSeg, tubularSegCustom Geometry
For complex shapes, create custom BufferGeometry:
import { BufferGeometry, BufferAttribute } from '@iwsdk/core';
// Create triangle
const geometry = new BufferGeometry();
// Define vertices (3 points in 3D space)
const vertices = new Float32Array([
-1,
-1,
0, // vertex 1
1,
-1,
0, // vertex 2
0,
1,
0, // vertex 3
]);
// Define normals (for lighting)
const normals = new Float32Array([
0,
0,
1, // normal for vertex 1
0,
0,
1, // normal for vertex 2
0,
0,
1, // normal for vertex 3
]);
// Define UV coordinates (for texturing)
const uvs = new Float32Array([
0,
0, // UV for vertex 1
1,
0, // UV for vertex 2
0.5,
1, // UV for vertex 3
]);
geometry.setAttribute('position', new BufferAttribute(vertices, 3));
geometry.setAttribute('normal', new BufferAttribute(normals, 3));
geometry.setAttribute('uv', new BufferAttribute(uvs, 2));Reusing Geometry
// Create geometry once, reuse for multiple entities
const sharedGeometry = new BoxGeometry(1, 1, 1);
// Create multiple entities with same shape
for (let i = 0; i < 10; i++) {
const material = new MeshStandardMaterial({
color: Math.random() * 0xffffff,
});
const mesh = new Mesh(sharedGeometry, material);
const entity = world.createTransformEntity(mesh);
// Position each entity differently
entity.object3D.position.set(i * 2, 0, 0);
}Materials: The Look of Things
Materials define how surfaces look - their color, shininess, transparency, and how they react to light.
Material Types
For WebXR, use physically-based materials:
import {
MeshStandardMaterial, // Good balance of quality and performance
MeshPhysicalMaterial, // Advanced PBR features (clearcoat, transmission)
MeshBasicMaterial, // Unlit (use sparingly)
} from '@iwsdk/core';
// Standard PBR material (recommended)
const pbr = new MeshStandardMaterial({
color: 0x00ff00, // Base color (green)
metalness: 0.1, // 0 = dielectric, 1 = metallic
roughness: 0.7, // 0 = mirror, 1 = completely rough
});
// Advanced PBR material
const advanced = new MeshPhysicalMaterial({
color: 0xffffff,
metalness: 0,
roughness: 0.1,
clearcoat: 1.0, // Clear coating effect
clearcoatRoughness: 0.1,
transmission: 0.9, // Glass-like transparency
thickness: 1.0,
});
// Unlit material (for UI elements)
const unlit = new MeshBasicMaterial({
color: 0xff0000,
transparent: true,
opacity: 0.8,
});Working with Textures
Textures add detail and realism:
import { TextureLoader } from '@iwsdk/core';
const loader = new TextureLoader();
// Load textures
const diffuseTexture = loader.load('/textures/brick_diffuse.jpg');
const normalTexture = loader.load('/textures/brick_normal.jpg');
const roughnessTexture = loader.load('/textures/brick_roughness.jpg');
// Apply to material
const material = new MeshStandardMaterial({
map: diffuseTexture, // Base color texture
normalMap: normalTexture, // Surface detail
roughnessMap: roughnessTexture, // Roughness variation
});
// Configure texture settings
diffuseTexture.wrapS = RepeatWrapping;
diffuseTexture.wrapT = RepeatWrapping;
diffuseTexture.repeat.set(2, 2); // Tile 2x2Color Space Management
Critical for correct colors in VR:
// Set color space for albedo textures
diffuseTexture.colorSpace = SRGBColorSpace;
// Linear textures (normals, roughness, etc.) use LinearSRGBColorSpace
normalTexture.colorSpace = LinearSRGBColorSpace;
roughnessTexture.colorSpace = LinearSRGBColorSpace;Meshes: Putting It Together
Meshes combine geometry and materials into renderable objects:
import { Mesh } from '@iwsdk/core';
// Create mesh
const geometry = new BoxGeometry(1, 1, 1);
const material = new MeshStandardMaterial({ color: 0x00ff00 });
const mesh = new Mesh(geometry, material);
// Add to IWSDK entity
const entity = world.createTransformEntity(mesh);
// Set additional Three.js properties
mesh.castShadow = true;
mesh.receiveShadow = true;
mesh.name = 'GreenBox';Multiple Materials
Objects can have different materials on different faces:
// Array of materials for different faces
const materials = [
new MeshStandardMaterial({ color: 0xff0000 }), // +X
new MeshStandardMaterial({ color: 0x00ff00 }), // -X
new MeshStandardMaterial({ color: 0x0000ff }), // +Y
new MeshStandardMaterial({ color: 0xffff00 }), // -Y
new MeshStandardMaterial({ color: 0xff00ff }), // +Z
new MeshStandardMaterial({ color: 0x00ffff }), // -Z
];
const mesh = new Mesh(new BoxGeometry(1, 1, 1), materials);Lighting and Environment
Authored Lighting
IWSDK does not inject lighting implicitly. Generated starter scenes explicitly declare a gradient dome and gradient IBL on the level root:
{
"components": {
"com.iwsdk.components.DomeGradient": {
"sky": [0.2423, 0.6172, 0.8308, 1],
"equator": [0.6584, 0.7084, 0.7913, 1],
"ground": [0.807, 0.7758, 0.7454, 1],
"intensity": 1
},
"com.iwsdk.components.IBLGradient": {
"sky": [0.6902, 0.749, 0.7843, 1],
"equator": [0.6584, 0.7084, 0.7913, 1],
"ground": [0.807, 0.7758, 0.7454, 1],
"intensity": 1
}
}
}Because background and IBL are independent, removing one component does not cause IWSDK to recreate it.
Custom Environment Maps
For realistic reflections and lighting, author IBLTexture and optionally DomeTexture on the level root. Their src values may reference an HDR asset from the manifest:
import { DomeTexture, IBLTexture } from '@iwsdk/core';
const levelRoot = world.activeLevel.value;
levelRoot.addComponent(IBLTexture, {
src: 'sunset-hdr',
intensity: 1,
});
levelRoot.addComponent(DomeTexture, {
src: 'sunset-hdr',
});Lighting Best Practices for WebXR
Environment vs Direct Lights:
// ✅ Prefer environment lighting for VR (matches real-world lighting)
world.scene.environment = envMap;
// ✅ Add directional light only if needed
const sun = new DirectionalLight(0xffffff, 0.5);
sun.position.set(10, 10, 5);
world.scene.add(sun);
// ❌ Avoid too many dynamic lights (performance impact)Performance Optimization
Geometry Sharing
// ✅ Reuse geometry for identical shapes
const boxGeometry = new BoxGeometry(1, 1, 1);
for (let i = 0; i < 100; i++) {
const material = new MeshStandardMaterial({
color: new Color().setHSL(i / 100, 1, 0.5),
});
const mesh = new Mesh(boxGeometry, material); // Same geometry
// ... create entities
}Material Sharing and Uniforms
// ✅ Share materials when possible
const sharedMaterial = new MeshStandardMaterial({ color: 0x00ff00 });
// ✅ Use uniforms for animated properties
const uniformMaterial = new MeshStandardMaterial({
color: 0xffffff,
});
// In system update:
uniformMaterial.color.setHSL(time * 0.001, 1, 0.5); // Animate colorTexture Optimization
// Optimize textures for VR
const texture = loader.load('/textures/diffuse.jpg');
// ✅ Use appropriate sizes (power of 2)
// 512x512, 1024x1024, 2048x2048
// ✅ Enable mipmaps for distant objects
texture.generateMipmaps = true;
// ✅ Use compressed formats when possible
// .ktx2, .basis for better performanceLOD (Level of Detail)
import { LOD } from '@iwsdk/core';
const lod = new LOD();
// High detail (close)
const highMesh = new Mesh(
new SphereGeometry(1, 32, 16),
new MeshStandardMaterial({ color: 0x00ff00 }),
);
// Medium detail
const medMesh = new Mesh(
new SphereGeometry(1, 16, 8),
new MeshStandardMaterial({ color: 0x00ff00 }),
);
// Low detail (far)
const lowMesh = new Mesh(
new SphereGeometry(1, 8, 4),
new MeshStandardMaterial({ color: 0x00ff00 }),
);
lod.addLevel(highMesh, 0); // 0-10 meters
lod.addLevel(medMesh, 10); // 10-50 meters
lod.addLevel(lowMesh, 50); // 50+ meters
const entity = world.createTransformEntity(lod);Common Patterns for WebXR
Real-World Sized Objects
// Create objects with appropriate VR/AR scale
const geometry = new BoxGeometry(0.1, 0.1, 0.1); // 10cm cube
const material = new MeshStandardMaterial({
color: 0x4caf50,
roughness: 0.3,
metalness: 0.1,
});
const mesh = new Mesh(geometry, material);
const entity = world.createTransformEntity(mesh);
// Position at comfortable interaction height
entity.object3D.position.set(0, 1.2, -0.5);UI Panels
// Use unlit materials for UI elements
const panelMaterial = new MeshBasicMaterial({
color: 0xffffff,
transparent: true,
opacity: 0.9,
});
const panelGeometry = new PlaneGeometry(0.3, 0.2); // 30cm x 20cm
const panel = new Mesh(panelGeometry, panelMaterial);
const panelEntity = world.createTransformEntity(panel, { persistent: true });Particle Effects
// Use instanced meshes for particles
import { InstancedMesh } from '@iwsdk/core';
const particleGeometry = new SphereGeometry(0.01, 8, 4); // Small spheres
const particleMaterial = new MeshBasicMaterial({ color: 0xffff00 });
const particles = new InstancedMesh(particleGeometry, particleMaterial, 1000);
// Update instances in systems
const matrix = new Matrix4();
for (let i = 0; i < 1000; i++) {
matrix.setPosition(
Math.random() * 10 - 5,
Math.random() * 10,
Math.random() * 10 - 5,
);
particles.setMatrixAt(i, matrix);
}
particles.instanceMatrix.needsUpdate = true;Troubleshooting Common Issues
Objects Appear Black
// ❌ Missing or incorrect normals
geometry.computeVertexNormals(); // Fix
// ❌ No environment lighting
world.scene.environment = envMap; // FixTextures Look Wrong
// ❌ Wrong color space
texture.colorSpace = SRGBColorSpace; // For albedo textures
texture.colorSpace = LinearSRGBColorSpace; // For data textures
// ❌ Incorrect UV coordinates
geometry.attributes.uv.needsUpdate = true;Performance Issues
// ❌ Too many draw calls
// Fix: Use InstancedMesh or merge geometries
// ❌ High-resolution textures
// Fix: Resize to appropriate sizes (512, 1024, 2048)
// ❌ Too many materials
// Fix: Share materials between objectsSummary
Key Concepts:
- Mesh = Geometry + Material - The fundamental building block
- Use PBR materials - MeshStandardMaterial for realistic lighting
- Manage color spaces - sRGB for colors, LinearSRGB for data
- Optimize for VR - Share resources, use appropriate resolutions
- IWSDK provides lighting - Environment maps work out of the box
- Real-world scale - Size objects appropriately for VR interaction
Understanding these concepts enables you to create visually appealing, performant 3D objects that look natural in WebXR environments.