前端实现交互式3D人体肌肉解剖图:基于 Three.js + React Three Fiber 的完整方案


本文将详细介绍如何在前端实现一个交互式的3D人体肌肉解剖展示工具,用户可以旋转、缩放模型,点击任意肌肉查看中英文名称。

为什么要做这个?

传统的肌肉解剖学习通常依赖静态图片或昂贵的3D软件。作为健身爱好者,我希望能有一个免费、易用的在线工具来学习肌肉解剖知识。于是我决定自己动手,基于开源的 Z-Anatomy 项目,在浏览器中实现一个交互式的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 压缩:

# export_cli.py - Blender 命令行导出脚本import bpy # 选择所有肌肉相关的对象 muscle_keywords =['muscle','deltoid','bicep','tricep','pectoralis',...]for obj in bpy.data.objects:if obj.type=='MESH': name_lower = obj.name.lower()ifany(keyword in name_lower for keyword in muscle_keywords): obj.select_set(True)# 导出为 GLB,启用 Draco 压缩 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)

// muscle-scene.tsx 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. 模型加载与肌肉识别

// muscle-model.tsx 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', // Z-Anatomy 的标签后缀 ]; 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:

// muscles.tsconst 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',],// ... 更多映射};exportfunctiongetMuscleIdFromModelName(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;}}returnundefined;}

多语言翻译存储在 i18n 模块中:

// locales/zh/muscle-anatomy.tsexportconst muscleAnatomy ={ muscles:{ pectoralis_major:'胸大肌', biceps_brachii:'肱二头肌', triceps_brachii:'肱三头肌', latissimus_dorsi:'背阔肌', rectus_abdominis:'腹直肌', gluteus_maximus:'臀大肌', quadriceps_femoris:'股四头肌', gastrocnemius:'腓肠肌',// ... 150+ 肌肉翻译},};

性能优化

1. 模型压缩

  • 使用 Draco 压缩,模型从 300MB 压缩到 6.8MB
  • 在 Blender 中使用 Decimate Modifier 降低面数

2. 动态加载

// 使用 Next.js 动态导入,禁用 SSR const MuscleScene = dynamic( () => import('./muscle-scene').then((mod) => mod.MuscleScene), { ssr: false } ); 

3. 材质优化

  • 克隆材质避免全局修改
  • 使用 emissive 属性实现高亮,避免重新创建材质

应用场景

这套技术方案可以应用于多种场景:

  1. 健身教育:帮助健身爱好者了解肌肉位置和名称
  2. 医学教学:辅助医学生学习人体解剖
  3. 康复指导:展示特定肌肉群的位置
  4. 游戏开发:角色肌肉系统参考

如果你对健身相关的工具感兴趣,可以看看这些在线计算器:

总结

本文介绍了如何在前端使用 Three.js + React Three Fiber 实现交互式3D人体肌肉解剖展示。核心技术点包括:

  1. 模型处理:从 Z-Anatomy 导出并压缩 GLB 模型
  2. 场景搭建:使用 React Three Fiber 构建 3D 场景
  3. 交互实现:射线检测 + 材质高亮
  4. 多语言支持:肌肉名称中英文翻译

这套方案完全在浏览器端运行,无需后端服务,非常适合构建教育类的3D可视化应用。


相关链接:

Read more

2026 最新 FPGA——学霸学习导图汇总

2026 最新 FPGA——学霸学习导图汇总

随着 FPGA 技术在 AI、通信、自动驾驶、5G/6G、大规模数据中心等领域的重要性持续攀升,越来越多同学希望系统掌握 FPGA 的核心技能。但面对浩繁的知识点和庞大的工具链,新手常常不知从何学起。 为此,这篇文章整理了 2026 年最新 FPGA 学习导图与学习路径总结,帮你从零打造 FPGA 学霸级路线图! 🔥 一、FPGA 学习总览 FPGA(Field Programmable Gate Array)是一种可重构硬件设备,它不像 MCU 那样运行软件,而是通过硬件电路本身实现逻辑功能。 一个完整的 FPGA 学习体系通常包括: 📌 基础理论 📌 HDL 编程(Verilog / VHDL / SystemVerilog) 📌 仿真 & 综合 & 时序分析

ClawdBot镜像免配置:docker-compose一键启动多语言机器人教程

ClawdBot镜像免配置:docker-compose一键启动多语言机器人教程 你是否试过在本地搭一个真正能用的AI助手,结果卡在环境配置、模型下载、API密钥、端口冲突上,折腾半天连首页都打不开?ClawdBot不是又一个需要你手动编译、改配置、调依赖的“半成品项目”。它是一套开箱即用的个人AI工作流系统——后端由vLLM驱动,前端带可视化控制台,所有服务打包进一个轻量镜像,一条docker-compose up -d命令,5分钟内就能拥有自己的多语言智能机器人。 更关键的是,它不只做聊天。当你把ClawdBot和MoltBot组合起来,就诞生了一个真正落地的Telegram全能翻译官:语音发过去自动转文字再翻译,截图发进来立刻OCR识别+多语种输出,群聊里@一下就能实时互译,顺手还能查天气、换汇率、搜维基。整个流程全部离线运行、零额外费用、不上传任何数据——你的消息永远留在你自己的设备里。 这不是概念演示,而是已经跑在树莓派4、MacBook M1、甚至老旧笔记本上的真实方案。本文不讲原理、不堆参数,只带你一步步从空白系统开始,用最自然的方式完成部署、验证、调优和日常使用。

AI绘画关键词网站效率提升实战:从数据预处理到模型加速

快速体验 在开始今天关于 AI绘画关键词网站效率提升实战:从数据预处理到模型加速 的探讨之前,我想先分享一个最近让我觉得很有意思的全栈技术挑战。 我们常说 AI 是未来,但作为开发者,如何将大模型(LLM)真正落地为一个低延迟、可交互的实时系统,而不仅仅是调个 API? 这里有一个非常硬核的动手实验:基于火山引擎豆包大模型,从零搭建一个实时语音通话应用。它不是简单的问答,而是需要你亲手打通 ASR(语音识别)→ LLM(大脑思考)→ TTS(语音合成)的完整 WebSocket 链路。对于想要掌握 AI 原生应用架构的同学来说,这是个绝佳的练手项目。 从0到1构建生产级别应用,脱离Demo,点击打开 从0打造个人豆包实时通话AI动手实验 AI绘画关键词网站效率提升实战:从数据预处理到模型加速 最近在开发一个AI绘画关键词推荐网站时,遇到了不少性能瓶颈。用户输入描述词后,系统需要快速返回最相关的绘画风格关键词,但最初的版本响应慢、推荐结果也不够精准。经过一系列优化,最终将查询响应时间降低了60%。下面分享整个优化过程的关键技术和实战经验。 痛点分析:

PyBullet实战:用AABB碰撞检测让R2D2机器人避开障碍物(附完整代码)

从碰撞检测到智能避障:用PyBullet为R2D2机器人注入“触觉” 如果你曾经尝试过在虚拟世界里让一个机器人动起来,大概率会遇到一个令人头疼的问题:它要么像个醉汉一样横冲直撞,要么对眼前的障碍物视而不见,一头撞上去。几年前,我第一次用PyBullet做机器人仿真时,就遇到了这个尴尬。我让一个R2D2模型在场景里跑,结果它径直冲向一个立方体,然后……穿过去了。那一刻我意识到,让机器人“动起来”只是第一步,让它“感知”并“避开”环境中的物体,才是仿真从玩具走向实用的关键。 PyBullet作为一款强大的物理仿真引擎,其真正的价值不仅在于能模拟重力、关节运动这些基础物理现象,更在于它提供了丰富的环境交互能力,其中碰撞检测就是实现智能避障的基石。而AABB(轴对齐包围盒) 作为一种高效、实用的碰撞检测方法,是我们在仿真中为机器人赋予“触觉”的首选工具。这篇文章,我将带你深入PyBullet的碰撞检测世界,手把手教你如何为经典的R2D2机器人实现一套实时、可靠的动态避障系统。我们不止步于让轮子转起来,更要让机器人学会“看路”。 1. 理解PyBullet中的碰撞检测:不止于AABB