跳到主要内容前端 3D 人体肌肉解剖图:基于 Three.js + React Three Fiber | 极客日志TypeScript大前端
前端 3D 人体肌肉解剖图:基于 Three.js + React Three Fiber
介绍如何使用 Three.js 和 React Three Fiber 在前端构建交互式 3D 人体肌肉解剖展示工具。核心流程包括从 Z-Anatomy 获取模型,使用 Blender 导出并 Draco 压缩至 Web 可用格式,通过 React Three Fiber 搭建场景,利用射线检测实现点击高亮交互,并结合 i18n 支持多语言肌肉名称映射。方案无需后端,适用于健身教育及医学教学等场景。
SparkGeek12 浏览 前端实现交互式 3D 人体肌肉解剖展示
本文将详细介绍如何在前端实现一个交互式的 3D 人体肌肉解剖展示工具,用户可以旋转、缩放模型,点击任意肌肉查看中英文名称。
技术架构概览
┌─────────────────────────────────────────────────────────────┐
│ 用户浏览器 │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ GLB 模型 │ │ Three.js │ │ React Three │ │
│ │ (Draco) │ │ 场景渲染 │ │ Fiber 组件 │ │
│ └─────────────┘ └─────────────┘ └─────────────────┘ │
│ │
│ ┌──────────────────┼──────────────────┐ │
│ v v v │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 射线检测 │ │ 肌肉高亮 │ │ i18n 翻译 │ │
│ │ Raycaster │ │ 材质切换 │ │ 中英文名称 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
->
->
模型来源与处理
Z-Anatomy 开源项目
Z-Anatomy 是一个开源的人体解剖学 Blender 项目,包含完整的人体肌肉、骨骼、器官等结构。原始文件约 300MB,包含 1000+ 个独立的 mesh 对象。
模型导出与压缩
为了在 Web 端使用,我们需要将 Blender 文件导出为 GLB 格式,并使用 Draco 压缩:
import bpy
muscle_keywords = [
'muscle', 'deltoid', 'bicep', 'tricep', 'pectoralis', ...
]
for obj in bpy.data.objects:
if obj.type == 'MESH':
name_lower = obj.name.lower()
if any(keyword in name_lower for keyword in muscle_keywords):
obj.select_set(True)
bpy.ops.export_scene.gltf(
filepath='muscle-anatomy.glb',
export_format='GLB',
use_selection=True,
export_draco_mesh_compression_enable=True,
export_draco_mesh_compression_level=6,
)
导出后的模型从 300MB 压缩到约 6.8MB,非常适合 Web 加载。
核心实现
1. 场景搭建 (React Three Fiber)
import { Canvas } from '@react-three/fiber';
import { OrbitControls, Environment } from '@react-three/drei';
export function MuscleScene({ onMuscleClick, onMuscleHover, hoveredMuscle, selectedMuscle }) {
return (
<Canvas camera={{ position: [1.5, 0.3, 2], fov: 50 }}>
{/* 环境光照 */}
<ambientLight intensity={0.6} />
<directionalLight position={[5, 5, 5]} intensity={0.8} />
{/* 3D 模型 */}
<Suspense fallback={null}>
<MuscleModel
onMuscleClick={onMuscleClick}
onMuscleHover={onMuscleHover}
hoveredMuscle={hoveredMuscle}
selectedMuscle={selectedMuscle}
/>
</Suspense>
{/* 轨道控制器 */}
<OrbitControls enablePan={false} minDistance={1} maxDistance={5} target={[0, 0.5, 0]} />
{/* 环境贴图 */}
<Environment preset="studio" />
</Canvas>
);
}
2. 模型加载与肌肉识别
import { useGLTF } from '@react-three/drei';
import * as THREE from 'three';
const muscleKeywords = [
'muscle', 'deltoid', 'bicep', 'tricep', 'pectoralis',
'latissimus', 'trapezius', 'rectus', 'oblique', 'gluteus',
'quadricep', 'vastus', 'hamstring', 'gastrocnemius', 'soleus',
];
const HIDDEN_KEYWORDS = [
'region', 'fascia', 'bursa', 'ligament', 'membrane', '.j', '.t',
];
function isMuscle(name: string): boolean {
const lowerName = name.toLowerCase();
return muscleKeywords.some(keyword => lowerName.includes(keyword)) &&
!HIDDEN_KEYWORDS.some(keyword => lowerName.includes(keyword));
}
export function MuscleModel({ onMuscleClick, onMuscleHover, hoveredMuscle, selectedMuscle }) {
const { scene } = useGLTF('/models/muscle-anatomy.glb');
const clonedScene = useMemo(() => {
const clone = scene.clone(true);
clone.traverse((child) => {
if (child instanceof THREE.Mesh) {
if (shouldHide(child.name)) {
child.parent?.remove(child);
return;
}
if (isMuscle(child.name)) {
child.material = child.material.clone();
child.material.color = new THREE.Color(0xc44d4d);
child.material.roughness = 0.7;
child.material.metalness = 0.1;
}
}
});
return clone;
}, [scene]);
return (
<primitive object={clonedScene} position={[0, -0.84, 0]}
onPointerOver={handlePointerOver}
onPointerOut={handlePointerOut}
onClick={handleClick}
/>
);
}
3. 交互高亮效果
const HIGHLIGHT_COLOR = new THREE.Color(0x5ac57a);
const SELECTED_COLOR = new THREE.Color(0x4caf50);
useEffect(() => {
clonedScene.traverse((child) => {
if (child instanceof THREE.Mesh && isMuscle(child.name)) {
const material = child.material as THREE.MeshStandardMaterial;
const muscleId = getMuscleIdFromModelName(child.name);
const isHovered = muscleId === hoveredMuscle;
const isSelected = muscleId === selectedMuscle;
if (isSelected) {
material.emissive = SELECTED_COLOR;
material.emissiveIntensity = 0.5;
} else if (isHovered) {
material.emissive = HIGHLIGHT_COLOR;
material.emissiveIntensity = 0.3;
} else {
material.emissive = new THREE.Color(0x000000);
material.emissiveIntensity = 0;
}
}
});
}, [hoveredMuscle, selectedMuscle, clonedScene]);
4. 肌肉名称映射与多语言
Z-Anatomy 的命名格式是 Muscle name.l(左侧)/ Muscle name.r(右侧),我们需要将其映射到标准的肌肉 ID:
const muscleAliases: Record<string,string[]> = {
'pectoralis_major': [
'pectoralis major',
'sternocostal head of pectoralis major',
'clavicular head of pectoralis major'
],
'biceps_brachii': [
'biceps brachii',
'long head of biceps brachii',
'short head of biceps brachii'
],
};
export function getMuscleIdFromModelName(modelName: string): string | undefined {
const cleanName = modelName
.replace(/\.(l|r|el|er)$/i, '')
.toLowerCase().trim();
for (const [muscleId, aliases] of Object.entries(muscleAliases)) {
if (aliases.some(alias => cleanName.includes(alias))) {
return muscleId;
}
}
return undefined;
}
export const muscleAnatomy = {
muscles: {
pectoralis_major: '胸大肌',
biceps_brachii: '肱二头肌',
triceps_brachii: '肱三头肌',
latissimus_dorsi: '背阔肌',
rectus_abdominis: '腹直肌',
gluteus_maximus: '臀大肌',
quadriceps_femoris: '股四头肌',
gastrocnemius: '腓肠肌',
},
};
性能优化
1. 模型压缩
- 使用 Draco 压缩,模型从 300MB 压缩到 6.8MB
- 在 Blender 中使用 Decimate Modifier 降低面数
2. 动态加载
const MuscleScene = dynamic(
() => import('./muscle-scene').then((mod) => mod.MuscleScene),
{ ssr: false }
);
3. 材质优化
- 克隆材质避免全局修改
- 使用 emissive 属性实现高亮,避免重新创建材质
应用场景
- 健身教育:帮助健身爱好者了解肌肉位置和名称
- 医学教学:辅助医学生学习人体解剖
- 康复指导:展示特定肌肉群的位置
- 游戏开发:角色肌肉系统参考
总结
本文介绍了如何在前端使用 Three.js + React Three Fiber 实现交互式 3D 人体肌肉解剖展示。核心技术点包括:
- 模型处理:从 Z-Anatomy 导出并压缩 GLB 模型
- 场景搭建:使用 React Three Fiber 构建 3D 场景
- 交互实现:射线检测 + 材质高亮
- 多语言支持:肌肉名称中英文翻译
这套方案完全在浏览器端运行,无需后端服务,非常适合构建教育类的 3D 可视化应用。
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
- JSON美化和格式化
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online