第一次用Three.js做智慧仓库项目时,我盯着空白的浏览器窗口发呆了半小时——3D场景开发就像装修毛坯房,得先打好地基。我们先来创建最基础的场景结构:
javascript复制// 初始化场景、相机和渲染器
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
这里有个新手容易踩的坑:相机位置不设置的话,你会看到一个黑屏。建议初始化时就把相机往后拉:
javascript复制camera.position.z = 50;
camera.position.y = 30;
camera.lookAt(0, 0, 0); // 让相机看向场景中心
别忘了添加光源,否则所有物体都是黑的。我习惯用环境光+平行光的组合:
javascript复制const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 20, 10);
scene.add(directionalLight);
仓库地板绝不是简单的一个平面,要考虑纹理重复、阴影接收等细节。这是我优化后的地板生成代码:
javascript复制function createFloor(width, depth) {
const geometry = new THREE.BoxGeometry(width, 1, depth);
const texture = new THREE.TextureLoader().load('floor_texture.jpg');
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(width/10, depth/10); // 按实际尺寸自动计算重复次数
const material = new THREE.MeshStandardMaterial({
map: texture,
roughness: 0.8
});
const floor = new THREE.Mesh(geometry, material);
floor.receiveShadow = true; // 关键!允许接收阴影
floor.position.y = -0.5; // 让地板顶部正好在y=0平面
return floor;
}
实用技巧:如果发现纹理模糊,记得设置纹理的anisotropy属性:
javascript复制texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
四面墙的创建可以封装成智能函数,这是我项目中使用的方案:
javascript复制function createWalls(roomWidth, roomDepth, height) {
const walls = [];
const materials = [
new THREE.MeshStandardMaterial({ color: 0xafc0ca }), // 前
new THREE.MeshStandardMaterial({ color: 0x9cb2d1 }), // 后
new THREE.MeshStandardMaterial({ color: 0xd6e4ec }), // 左右
];
// 前后墙
walls.push(createWall(roomWidth, height, 0.2,
[0, height/2, -roomDepth/2], materials[0]));
walls.push(createWall(roomWidth, height, 0.2,
[0, height/2, roomDepth/2], materials[1]));
// 左右墙
walls.push(createWall(0.2, height, roomDepth,
[-roomWidth/2, height/2, 0], materials[2]));
walls.push(createWall(0.2, height, roomDepth,
[roomWidth/2, height/2, 0], materials[2]));
return walls;
}
function createWall(width, height, depth, position, material) {
const geometry = new THREE.BoxGeometry(width, height, depth);
const wall = new THREE.Mesh(geometry, material);
wall.position.set(...position);
wall.castShadow = true;
return wall;
}
货架是仓库的核心元素,我设计了一个支持多层多列的货架生成器:
javascript复制class ShelfGenerator {
constructor(options) {
this.defaults = {
width: 30,
height: 200,
depth: 80,
levels: 5,
columns: 3,
legThickness: 5
};
this.config = { ...this.defaults, ...options };
}
generate() {
const shelfGroup = new THREE.Group();
// 生成支柱
this._createLegs(shelfGroup);
// 生成层板
for(let i = 0; i < this.config.levels; i++) {
this._createLevel(shelfGroup, i);
}
return shelfGroup;
}
_createLegs(group) {
const legMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 });
const legGeometry = new THREE.BoxGeometry(
this.config.legThickness,
this.config.height,
this.config.legThickness
);
// 四根支柱
const positions = [
[-this.config.width/2, 0, -this.config.depth/2],
[this.config.width/2, 0, -this.config.depth/2],
[-this.config.width/2, 0, this.config.depth/2],
[this.config.width/2, 0, this.config.depth/2]
];
positions.forEach(pos => {
const leg = new THREE.Mesh(legGeometry, legMaterial);
leg.position.set(...pos);
leg.position.y += this.config.height/2;
group.add(leg);
});
}
_createLevel(group, levelIndex) {
const levelHeight = this.config.height / this.config.levels;
const shelfMaterial = new THREE.MeshStandardMaterial({
color: 0xcccccc,
metalness: 0.3
});
// 主层板
const shelf = new THREE.Mesh(
new THREE.BoxGeometry(
this.config.width - this.config.legThickness*2,
2,
this.config.depth - this.config.legThickness*2
),
shelfMaterial
);
shelf.position.y = levelIndex * levelHeight + 2;
group.add(shelf);
// 每列的隔板
if(this.config.columns > 1) {
const columnWidth = this.config.width / this.config.columns;
for(let i = 1; i < this.config.columns; i++) {
const divider = new THREE.Mesh(
new THREE.BoxGeometry(2, levelHeight - 4, this.config.depth - this.config.legThickness*2),
shelfMaterial
);
divider.position.set(
-this.config.width/2 + i * columnWidth,
levelIndex * levelHeight + levelHeight/2,
0
);
group.add(divider);
}
}
}
}
当需要布置几十个货架时,性能优化就很重要了。这是我的布局方案:
javascript复制function createWarehouseLayout() {
const layoutGroup = new THREE.Group();
const shelfGenerator = new ShelfGenerator({
width: 25,
height: 180,
depth: 60,
levels: 6,
columns: 4
});
// 双排货架布局
for(let row = 0; row < 8; row++) {
for(let side = 0; side < 2; side++) {
const shelf = shelfGenerator.generate();
shelf.position.x = (side === 0 ? -1 : 1) * 30;
shelf.position.z = row * 70 - 240;
// 给货架添加唯一标识
shelf.userData = {
type: 'shelf',
id: `shelf-${row}-${side}`,
position: `${row}排${side === 0 ? '左' : '右'}`
};
layoutGroup.add(shelf);
}
}
return layoutGroup;
}
性能提示:当货架数量超过50个时,建议使用InstancedMesh来优化渲染性能。
货物管理是智慧仓库的核心功能,我设计了一个货物管理系统:
javascript复制class InventoryManager {
constructor(scene) {
this.scene = scene;
this.items = new Map();
this.loader = new THREE.GLTFLoader();
}
async loadItemModel(url) {
return new Promise((resolve) => {
this.loader.load(url, (gltf) => {
gltf.scene.traverse(child => {
if(child.isMesh) {
child.castShadow = true;
}
});
resolve(gltf.scene);
});
});
}
async placeItem(shelfId, level, column, modelUrl) {
const model = await this.loadItemModel(modelUrl);
// 计算放置位置
const position = this._calculatePosition(shelfId, level, column);
model.position.copy(position);
// 生成唯一ID
const itemId = `${shelfId}-${level}-${column}`;
model.userData = {
type: 'inventory',
id: itemId,
status: 'stored'
};
this.items.set(itemId, model);
this.scene.add(model);
return itemId;
}
_calculatePosition(shelfId, level, column) {
// 实际项目中这里会根据货架布局计算精确位置
const position = new THREE.Vector3();
// ...计算逻辑...
return position;
}
}
AGV小车的实现需要结合路径规划和动画:
javascript复制class AGVController {
constructor(modelUrl, scene) {
this.modelUrl = modelUrl;
this.scene = scene;
this.agvs = new Map();
this.mixer = null;
}
async spawnAGV(id, startPosition) {
const gltf = await this._loadModel(this.modelUrl);
gltf.scene.scale.set(0.5, 0.5, 0.5);
gltf.scene.position.copy(startPosition);
// 设置动画混合器
this.mixer = new THREE.AnimationMixer(gltf.scene);
if(gltf.animations?.length) {
this.mixer.clipAction(gltf.animations[0]).play();
}
this.agvs.set(id, {
model: gltf.scene,
mixer: this.mixer,
target: null,
speed: 2
});
this.scene.add(gltf.scene);
return id;
}
setDestination(agvId, path) {
const agv = this.agvs.get(agvId);
if(!agv) return;
agv.path = path;
agv.currentTargetIndex = 0;
agv.state = 'moving';
}
update(delta) {
this.agvs.forEach(agv => {
if(agv.mixer) agv.mixer.update(delta);
if(agv.state === 'moving') {
this._updateMovement(agv, delta);
}
});
}
_updateMovement(agv, delta) {
const currentTarget = agv.path[agv.currentTargetIndex];
const direction = new THREE.Vector3()
.subVectors(currentTarget, agv.model.position)
.normalize();
const moveDistance = agv.speed * delta;
agv.model.position.add(direction.multiplyScalar(moveDistance));
agv.model.lookAt(currentTarget);
// 检查是否到达当前目标点
if(agv.model.position.distanceTo(currentTarget) < 1) {
agv.currentTargetIndex++;
if(agv.currentTargetIndex >= agv.path.length) {
agv.state = 'idle';
}
}
}
}
当仓库场景变得复杂时,我总结了这些优化手段:
javascript复制const lod = new THREE.LOD();
const highDetail = new THREE.Mesh(highDetailGeometry, material);
const lowDetail = new THREE.Mesh(lowDetailGeometry, material);
lod.addLevel(highDetail, 50); // 50单位内使用高模
lod.addLevel(lowDetail, 100); // 超过50单位用低模
scene.add(lod);
javascript复制import { BufferGeometryUtils } from 'three/examples/jsm/utils/BufferGeometryUtils';
const geometries = [];
shelves.forEach(shelf => {
const geometry = shelf.geometry.clone();
geometry.applyMatrix4(shelf.matrixWorld);
geometries.push(geometry);
});
const mergedGeometry = BufferGeometryUtils.mergeBufferGeometries(geometries);
const mergedMesh = new THREE.Mesh(mergedGeometry, material);
scene.add(mergedMesh);
动态光照效果:
javascript复制// 仓库顶部照明
const warehouseLight = new THREE.SpotLight(0xffffff, 0.8, 0, Math.PI/4, 0.3);
warehouseLight.position.set(0, 100, 0);
warehouseLight.castShadow = true;
warehouseLight.shadow.mapSize.width = 2048;
warehouseLight.shadow.mapSize.height = 2048;
scene.add(warehouseLight);
// 添加灯光辅助线
const lightHelper = new THREE.SpotLightHelper(warehouseLight);
scene.add(lightHelper);
环境反射效果:
javascript复制const envMap = new THREE.CubeTextureLoader()
.load([
'px.jpg', 'nx.jpg',
'py.jpg', 'ny.jpg',
'pz.jpg', 'nz.jpg'
]);
const metalMaterial = new THREE.MeshStandardMaterial({
metalness: 0.9,
roughness: 0.1,
envMap: envMap
});
在完成这个智慧仓库项目后,我发现Three.js最强大的地方在于它的灵活性。记得第一次看到货架整齐排列在场景中时,那种成就感至今难忘。建议初学者从简单元素开始,逐步添加复杂功能,遇到性能问题时不要犹豫使用优化技术——在3D开发中,性能优化从来都不是过早考虑的事情。