一、环境准备
1. 初始化 Vue3 项目
首先确保你已安装 Node.js(建议 v16+),然后通过 Vite 快速创建 Vue3 项目:
# 创建 Vue3 项目(选 Vue + TypeScript 或 JavaScript 均可) npm create vite@latest vue3-three-demo -- --template vue cd vue3-three-demo npm install
2. 安装核心依赖
需要安装 Three.js 核心库和轨道控制器(Three.js 128+ 版本后控制器单独拆分):
# 安装 Three.js 核心 + 轨道控制器npm install three @tweenjs/tween.js
three:Three.js 核心库(3D 渲染核心)@tweenjs/tween.js:可选,用于实现 3D 物体动画(本文会用到)
二、核心概念速览(必懂)
在写代码前,先记住 Three.js 3 个核心要素(缺少一个都无法渲染):
-
场景(Scene):3D 世界的容器,所有 3D 物体、光照都要放在场景中 -
相机(Camera):相当于人的眼睛,决定从哪个角度观察场景 -
渲染器(Renderer):将场景和相机的内容渲染到页面 DOM 元素上
- 几何体(Geometry):3D 物体的形状(如立方体、球体)
- 材质(Material):3D 物体的外观(颜色、纹理、光泽)
- 网格(Mesh):几何体 + 材质的组合(真正可渲染的 3D 物体)
- 光照(Light):让 3D 物体有明暗层次(否则是纯色块)
- 控制器(OrbitControls):让用户可以拖拽、缩放、旋转场景
三、编写第一个 3D 项目
创建 src/components/ThreeDemo.vue 组件,复制以下代码(含详细注释):
<template>
<!-- 3D 渲染容器 -->
<div class="three-container" ref="containerRef"></div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { Tween } from '@tweenjs/tween.js';
// 核心对象引用
const containerRef = ref<HTMLDivElement | null>(null);
let scene: THREE.Scene, camera: THREE.PerspectiveCamera, renderer: THREE.WebGLRenderer;
let cube: THREE.Mesh, controls: OrbitControls;
// 初始化 3D 场景
const initThree = () => {
if (!containerRef.value) return;
// 1. 创建场景(容器)
scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0); // 场景背景色(浅灰)
// 2. 创建相机(透视相机:模拟人眼视角)
// 参数:视野角度(75°)、宽高比、近裁切面、远裁切面
camera = new THREE.PerspectiveCamera(
75,
containerRef.value.clientWidth / containerRef.value.clientHeight,
0.1,
1000
);
// 设置相机位置(x, y, z),默认在原点(0,0,0),需要拉开距离才能看到物体
camera.position.z = 5;
// 3. 创建渲染器(WebGL 渲染器,高性能)
renderer = new THREE.WebGLRenderer({ antialias: true }); // 开启抗锯齿
renderer.setSize(containerRef.value.clientWidth, containerRef.value.clientHeight); // 设置渲染尺寸
containerRef.value.appendChild(renderer.domElement); // 将渲染画布添加到 DOM
// 4. 创建 3D 物体(立方体)
// 几何体:BoxGeometry(宽, 高, 深)
const geometry = new THREE.BoxGeometry(2, 2, 2);
// 材质:MeshLambertMaterial(漫反射材质,需要光照才能显示)
const material = new THREE.MeshLambertMaterial({
color: 0x42b983, // 基础颜色(绿色)
wireframe: false, // 是否显示线框(true 可看到几何体结构)
});
// 网格:几何体 + 材质
cube = new THREE.Mesh(geometry, material);
scene.add(cube); // 将立方体添加到场景
// 5. 添加光照(否则物体是纯色块,无明暗)
// 环境光:均匀照亮所有物体,无阴影
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
// 方向光:模拟太阳光,有阴影效果
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 5, 5); // 光源位置
scene.add(directionalLight);
// 6. 添加轨道控制器(允许用户交互:拖拽旋转、滚轮缩放)
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // 开启阻尼效果(旋转更顺滑)
controls.dampingFactor = 0.05;
// 7. 添加动画(立方体自动旋转)
const animateCube = () => {
// 使用 Tween 实现平滑旋转动画
new Tween(cube.rotation)
.to({ x: cube.rotation.x + Math.PI * 2, y: cube.rotation.y + Math.PI * 2 }, 5000)
.repeat(Infinity) // 无限循环
.start();
};
animateCube();
// 8. 监听窗口大小变化(自适应调整)
window.addEventListener('resize', onWindowResize);
};
// 窗口大小自适应
const onWindowResize = () => {
if (!containerRef.value) return;
// 更新相机宽高比
camera.aspect = containerRef.value.clientWidth / containerRef.value.clientHeight;
camera.updateProjectionMatrix(); // 更新相机投影矩阵
// 更新渲染器尺寸
renderer.setSize(containerRef.value.clientWidth, containerRef.value.clientHeight);
};
// 渲染循环(Three.js 核心,持续渲染场景)
const renderLoop = () => {
requestAnimationFrame(renderLoop); // 浏览器帧循环
controls.update(); // 阻尼效果需要持续更新控制器
Tween.update(); // 更新动画
renderer.render(scene, camera); // 渲染场景和相机
};
// 生命周期钩子:组件挂载时初始化
onMounted(() => {
initThree();
renderLoop();
});
// 生命周期钩子:组件卸载时清理(防止内存泄漏)
onUnmounted(() => {
window.removeEventListener('resize', onWindowResize);
controls.dispose(); // 销毁控制器
renderer.dispose(); // 销毁渲染器
// 清理场景中所有物体
scene.traverse((obj) => {
if (obj instanceof THREE.Mesh) {
obj.geometry.dispose();
(obj.material as THREE.Material).dispose();
}
});
});
</script>
<style scoped>
.three-container {
width: 100vw;
height: 80vh;
margin: 20px 0;
}
</style>
2. 在主组件中使用
修改 src/App.vue,引入并使用 ThreeDemo 组件:
<template>
<div id="app">
<h1>Vue3 + Three.js 第一个 3D 项目</h1>
<ThreeDemo />
</div>
</template>
<script setup lang="ts">
import ThreeDemo from './components/ThreeDemo.vue';
</script>
<style scoped>
#app {
text-align: center;
padding: 2rem;
}
</style>
四、运行项目
npm run dev
http://localhost:5173 即可五、进阶优化:添加纹理贴图
如果想让立方体更真实,可以给它添加纹理贴图(替换之前的纯色材质)。
1. 准备纹理图片
在 public 文件夹下放入一张纹理图片(如 texture.jpg,可自行下载)。
2. 修改材质代码
将之前的 MeshLambertMaterial 替换为带纹理的材质:
// 加载纹理(Three.js 内置纹理加载器)
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load('/texture.jpg'); // 图片路径(public 下直接写文件名)
// 纹理优化(可选)
texture.wrapS = THREE.RepeatWrapping; // 水平重复
texture.wrapT = THREE.RepeatWrapping; // 垂直重复
texture.repeat.set(2, 2); // 重复次数(x 轴 2 次,y 轴 2 次)
texture.anisotropy = renderer.capabilities.getMaxAnisotropy(); // 各向异性过滤(提升纹理清晰度)
// 带纹理的材质
const material = new THREE.MeshLambertMaterial({
map: texture, // 纹理贴图
});
六、关键 API 总结(快速查阅)
|
|
|
|---|---|
|
|
new THREE.Scene() |
|
|
new THREE.PerspectiveCamera(fov, aspect, near, far) |
|
|
new THREE.WebGLRenderer({ antialias: true }) |
|
|
new THREE.BoxGeometry(width, height, depth) |
|
|
new THREE.MeshLambertMaterial({ color, map }) |
|
|
new THREE.Mesh(geometry, material) |
|
|
new THREE.AmbientLight(color, intensity) |
|
|
new THREE.DirectionalLight(color, intensity) |
|
|
new OrbitControls(camera, renderer.domElement) |
|
|
new THREE.TextureLoader().load(url) |
|
|
requestAnimationFrame(renderLoop) |


近期评论