Three.js 的六轴机械臂仿真,可通过图形化界面控制机械臂六个关节旋转。
用户可通过滑块控制每个关节角度,动作实时反馈到 3D 视图。
支持鼠标旋转、缩放、平移视角,查看机器人不同角度操作效果。

1.模型实现
下载自BlenderKit 的在线资源库
2.代码与模型对接
Blender里面的模型有各种的类,在前端代码里获取这些类并控制就行。


3.代码实现
APP.VUE
<template>
<div class="container">
<div class="controls-top">
<div class="controls-row">
<div class="slider-group" v-for="(joint, index) in joints" :key="index">
<h3>{{ joint.name }}</h3>
<div class="slider-container">
<div class="slider-row">
<label>X: {{ joint.rotation.x.toFixed(2) }}</label>
<input type="range" min="-3.14" max="3.14" step="0.01" v-model="joint.rotation.x" @input="updateRoboticArm" />
</div>
<div class="slider-row">
<label>Y: {{ joint.rotation.y.toFixed(2) }}</label>
<input type="range" min="-3.14" max="3.14" step="0.01" v-model="joint.rotation.y" @input="updateRoboticArm" />
</div>
<div class="slider-row">
<label>Z: {{ joint.rotation.z.toFixed(2) }}</label>
<input type="range" min="-3.14" max="3.14" step="0.01" v-model="joint.rotation.z" @input="updateRoboticArm" />
</div>
</div>
</div>
</div>
</div>
<div class="canvas-container">
<canvas ref="threeCanvas"></canvas>
</div>
</div>
</template>
<script>
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { onMounted, ref, reactive } from 'vue';
export default {
setup() {
const threeCanvas = ref(null);
let scene, camera, renderer, model, controls;
let animationFrameId = null;
let jointMeshes = {};
const nodeNames = ref([]);
// 定义六个关节的数据
const joints = reactive([
{ name: 'Link1N', rotation: { x: 0, y: 0, z: 0 } },
{ name: 'Link2N', rotation: { x: 0, y: 0, z: 0 } },
{ name: 'Link3N', rotation: { x: 0, y: 0, z: 0 } },
{ name: 'Link4N', rotation: { x: 0, y: 0, z: 0 } },
{ name: 'Link5N', rotation: { x: 0, y: 0, z: 0 } },
{ name: 'Link6N', rotation: { x: 0, y: 0, z: 0 } }
]);
// 初始化Three.js场景
const initThree = () => {
scene = new THREE.Scene();
scene.background = new THREE.Color(0x333333);
// 相机设置
camera = new THREE.PerspectiveCamera(
75,
threeCanvas.value.clientWidth / threeCanvas.value.clientHeight,
0.1,
1000
);
camera.position.set(5, 5, 5);
camera.lookAt(0, 0, 0);
// 渲染器设置
renderer = new THREE.WebGLRenderer({ canvas: threeCanvas.value, antialias: true });
renderer.setSize(threeCanvas.value.clientWidth, threeCanvas.value.clientHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.shadowMap.enabled = true;
// 添加光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(10, 10, 10);
directionalLight.castShadow = true;
scene.add(directionalLight);
// 添加网格地面
const gridHelper = new THREE.GridHelper(20, 20);
scene.add(gridHelper);
// 设置轨道控制器
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// 加载机械臂模型
loadRoboticArm();
// 响应窗口大小变化
window.addEventListener('resize', onWindowResize);
// 启动动画循环
animate();
};
// 加载机械臂模型
const loadRoboticArm = () => {
const loader = new GLTFLoader();
loader.load('public/models/六轴机械臂.glb', (gltf) => {
model = gltf.scene;
// 坐标系校正(关键修复)
model.rotation.set(-Math.PI, 0, Math.PI); // X轴-90度 + Z轴180度
model.scale.set(0.8, 0.8, 0.8); // 可选缩放
// 精确地面定位
const bbox = new THREE.Box3().setFromObject(model);
const minY = bbox.min.y * model.scale.y; // 考虑缩放后的实际高度
model.position.y = -minY; // 将模型底部对齐y=0平面
// 节点绑定逻辑
const jointPattern = /Link([1-6])N/; // 精确匹配关节节点
model.traverse((child) => {
if (child.parent?.name.match(jointPattern)) {
const match = child.parent.name.match(jointPattern);
const index = parseInt(match[1]) - 1;
if (joints[index]) {
jointMeshes[joints[index].name] = child.parent;
console.log(`关节绑定:${joints[index].name} ↔ ${child.parent.name}`);
// 调试标记(可选)
child.parent.add(new THREE.AxesHelper(0.2));
}
}
});
scene.add(model);
// 添加地面参考(调试用)
const gridHelper = new THREE.GridHelper(10, 10);
scene.add(gridHelper);
// 优化摄像机初始位置
const center = new THREE.Vector3();
bbox.getCenter(center);
camera.position.set(
center.x + 2,
center.y + 1.5, // 降低观察高度
center.z + 2
);
controls.target.copy(center);
controls.update();
});
};
// 窗口大小变化处理
const onWindowResize = () => {
camera.aspect = threeCanvas.value.clientWidth / threeCanvas.value.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(threeCanvas.value.clientWidth, threeCanvas.value.clientHeight);
};
// 更新机械臂位置
const updateRoboticArm = () => {
joints.forEach((joint, index) => {
const node = jointMeshes[joint.name];
if (node) {
// 关键修复4:使用欧拉角顺序控制
node.rotation.set(
joint.rotation.x, // X轴旋转
joint.rotation.y, // Y轴旋转
joint.rotation.z, // Z轴旋转
'XYZ' // 明确旋转顺序
);
// 关键修复5:强制更新世界矩阵
node.updateMatrixWorld(true);
}
});
// 立即重绘
renderer.render(scene, camera);
};
// 动画循环
const animate = () => {
animationFrameId = requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
};
// 组件加载完成后执行
onMounted(() => {
initThree();
});
// 组件卸载时清理
const cleanUp = () => {
if (animationFrameId !== null) {
cancelAnimationFrame(animationFrameId);
}
window.removeEventListener('resize', onWindowResize);
// 释放资源
if (model) {
scene.remove(model);
}
if (renderer) {
renderer.dispose();
}
};
return {
threeCanvas,
joints,
nodeNames,
updateRoboticArm,
cleanUp
};
},
unmounted() {
this.cleanUp();
}
}
</script>
<style scoped>
/* 全屏容器 */
.container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* 顶部控制条 */
.controls-top {
width: 100%;
background: rgba(20, 20, 20, 0.95);
padding: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
z-index: 10;
}
/* 水平控制条排列 */
.controls-row {
display: flex;
flex-direction: row;
justify-content: space-between;
overflow-x: auto;
gap: 10px;
padding-bottom: 5px; /* 添加滚动条的空间 */
}
/* 关节控制组 */
.slider-group {
min-width: 160px;
flex: 1;
padding: 8px;
background: rgba(40, 40, 40, 0.6);
border-radius: 4px;
border-bottom: 2px solid #00ff88;
}
.slider-group h3 {
margin: 0 0 8px 0;
font-size: 14px;
font-weight: bold;
color: #00ff88;
text-align: center;
}
/* 滑块容器 */
.slider-container {
display: flex;
flex-direction: column;
gap: 6px;
}
/* 滑块行 */
.slider-row {
display: flex;
flex-direction: column;
}
.slider-row label {
font-size: 12px;
color: #eee;
margin-bottom: 2px;
}
/* 滑动条样式 */
input[type="range"] {
width: 100%;
height: 4px;
background: #555;
border-radius: 2px;
margin: 2px 0;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 12px;
height: 12px;
background: #00ff88;
border-radius: 50%;
cursor: pointer;
}
/* 主画布区域 - 占据大部分空间 */
.canvas-container {
flex: 1;
width: 100%;
position: relative;
background: #333;
}
.canvas-container canvas {
width: 100%;
height: 100%;
display: block;
}
/* 去除默认边距 */
body, html {
margin: 0;
padding: 0;
overflow: hidden;
font-family: Arial, sans-serif;
}
/* 响应式调整 */
@media (max-height: 700px) {
.controls-top {
padding: 4px;
}
.slider-group {
padding: 4px;
}
.slider-group h3 {
font-size: 12px;
margin-bottom: 4px;
}
}
</style>