1. 准备工作
先创建基础设施,为开始做好准备,实现最基本的构成以及鼠标可以放大缩小和拖拽换角度:
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
const w = window.innerWidth;
const h = window.innerHeight;
// 场景和相机
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, w / h, 0.1, 500);
// 坐标
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
camera.position.set(0, 2, 5);
// 网格
const gridHelper = new THREE.GridHelper(30, 30);
scene.add(gridHelper);
// 鼠标缩放 / 旋转角度
const orbit = new OrbitControls(camera, renderer.domElement);
const animate = () => {
orbit.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
};
animate();
我们使用 vite + vue3 + TypeScript 搭建我们的项目
安装node,官网:url.nodejs.cn/download/
下载之后直接双击安装,建议下载 v18 及以上
打开终端cmd,输入node -v,看到版本,说明已经安装成功了
新建 vite + vue3 + TypeScript 项目:
npm create vue@latest
按照所需要安装的选择进行安装,之后cd进入项目,安装依赖:
npm install
之后拖拽到vscode中运行项目:
npm run dev
之后安装threejs的依赖包:
npm install three
npm install --save-dev @types/three
npm install dat.gui
重启项目,项目中删掉没用的内容,只留一个默认index页面就行
App.vue:
<template>
<RouterView />
</template>
<script setup lang="ts">
import { RouterView } from "vue-router";
</script>
我们在index.vue中将最上面的代码进行改动和封装,改成适用于vue的语法
index.vue:
<template>
<div>
<div ref="threeBoxRef" class="threeBox"></div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
// 创建三个场景的容器引用
const threeBoxRef = ref<HTMLElement | null>(null);
// 在组件挂载后初始化 Three.js
onMounted(() => {
if (threeBoxRef.value) {
initThreeFn(threeBoxRef.value);
}
});
// 初始化 Three.js 场景
const initThreeFn = (container: HTMLElement) => {
const { renderer, scene, camera } = createRendererSceneCamera(container);
addHelpers(scene);
addObjectsToScene(scene);
addResizeListener(renderer, camera);
animate(renderer, scene, camera);
};
// 创建渲染器、场景和相机
const createRendererSceneCamera = (container: HTMLElement) => {
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const w = window.innerWidth;
const h = window.innerHeight;
const camera = new THREE.PerspectiveCamera(75, w / h, 0.1, 500);
camera.position.set(10, 25, 25);
return { renderer, scene, camera };
};
// 添加辅助元素(坐标轴和网格)
const addHelpers = (scene: THREE.Scene) => {
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
const gridHelper = new THREE.GridHelper(30, 30);
scene.add(gridHelper);
};
// 添加物体到场景(地面、几何体)
const addObjectsToScene = (scene: THREE.Scene) => {
const plane = createPlane();
scene.add(plane);
const box = createBox();
scene.add(box);
};
// 创建平面
const createPlane = () => {
const geometry = new THREE.PlaneGeometry(30, 30);
const material = new THREE.MeshBasicMaterial({ color: 0x333333, side: THREE.DoubleSide });
const plane = new THREE.Mesh(geometry, material);
plane.rotation.x = -0.5 * Math.PI; // 旋转平面,使其平行于地面
return plane;
};
// 创建立方体
const createBox = () => {
const geometry = new THREE.BoxGeometry(6, 6, 6);
const material = new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true });
const box = new THREE.Mesh(geometry, material);
box.position.y = 3.01; // 将立方体抬高一点,使其不与地面重叠
return box;
};
// 设置鼠标控制
const setupOrbitControls = (camera: THREE.PerspectiveCamera, renderer: THREE.WebGLRenderer) => {
return new OrbitControls(camera, renderer.domElement);
};
// 动画循环
const animate = (renderer: THREE.WebGLRenderer, scene: THREE.Scene, camera: THREE.PerspectiveCamera) => {
const orbit = setupOrbitControls(camera, renderer);
const animateFn = () => {
orbit.update();
renderer.render(scene, camera);
requestAnimationFrame(animateFn);
};
animateFn();
};
// 动态调整窗口大小
const addResizeListener = (renderer: THREE.WebGLRenderer, camera: THREE.PerspectiveCamera) => {
window.addEventListener('resize', () => {
if (renderer) {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
}
});
};
</script>
<style scoped>
.threeBox {
width: 100%;
height: 100vh;
}
</style>
之后运行项目就会看到浏览器中展示:
2. new THREE.TextureLoader() - 创建纹理加载器实例1:
import skyBlue from "@/assets/images/skyBlue.jpg";
……
// 创建背景渲染
const createBackground = (scene: THREE.Scene) => {
// 创建一个纹理加载器实例,用于加载图片纹理
const textureLoader = new THREE.TextureLoader();
// 使用加载器加载背景纹理(skyBlue 为纹理的图片路径),并将加载后的纹理应用到场景的背景
scene.background = textureLoader.load(skyBlue);
}
使用场景:
这种方式常用于创建天空盒子、背景图片等效果,例如加载一个天空图片或自定义的背景来装饰场景。
2. new THREE.CubeTextureLoader() - 创建纹理加载器实例2:
import skyBlue from "@/assets/images/skyBlue.jpg";
……
// 创建背景渲染
const createBackground = (scene: THREE.Scene) => {
// 创建一个立方体纹理加载器实例
const cudeTextureLoader = new THREE.CubeTextureLoader();
// 使用 CubeTextureLoader 加载六个纹理面,给场景设置立方体背景
scene.background = cudeTextureLoader.load([
material, // 正面纹理
material, // 背面纹理
material, // 左侧纹理
material, // 右侧纹理
material, // 上侧纹理
material, // 下侧纹理
]);
}
使用场景:
此方法通常用于创建 3D 场景中的天空盒或环境贴图。每一面都可以是不同的图像,例如天、地、四面墙等,用于模拟环境或渲染背景。
3. map - 创建几何体贴图
import skyBlue from "@/assets/images/skyBlue.jpg";
……
// 创建立方体
const createBox = () => {
// 创建纹理加载器实例,用于加载纹理
const textureLoader = new THREE.TextureLoader();
// 创建立方体几何体,大小为 6x6x6
const geometry = new THREE.BoxGeometry(6, 6, 6);
// 创建材质,设置颜色为红色,同时加载纹理,并禁用线框模式
const material = new THREE.MeshBasicMaterial({
color: 0xff0000, // 立方体的颜色设置为红色
map: textureLoader.load(skyBlue), // 使用纹理贴图(skyBlue 图片)
wireframe: false // 设置材质为实心(非线框模式)
});
// 使用创建的几何体和材质创建立方体网格
const box = new THREE.Mesh(geometry, material);
// 设置立方体的位置,使其稍微抬高一点,避免与地面重叠
box.position.y = 3.01; // 将立方体的 Y 坐标设置为 3.01
// 返回创建的立方体
return box;
};
也可以简写,从而实现矩形几何体贴图:
// 创建立方体
const createBox = () => {
const textureLoader = new THREE.TextureLoader();
const geometry = new THREE.BoxGeometry(6, 6, 6);
const material = new THREE.MeshBasicMaterial({
// color: 0xff0000,
// map: textureLoader.load(skyBlue),
// wireframe: false
});
const box = new THREE.Mesh(geometry, material);
box.position.y = 3.01; // 将立方体抬高一点,使其不与地面重叠
// 简写贴图
box.material.map = textureLoader.load(skyBlue);
return box;
};
设置每个方向单独贴图
// 创建立方体
const createBox = () => {
// 创建纹理加载器实例,用于加载纹理贴图
const textureLoader = new THREE.TextureLoader();
// 创建立方体几何体,大小为 6x6x6
const geometry = new THREE.BoxGeometry(6, 6, 6);
// 创建一个材质列表,分别为立方体的六个面设置不同的纹理
const boxMaterialList = [
// 前面贴上 boxMaterial1 纹理
new THREE.MeshBasicMaterial({ map: textureLoader.load(boxMaterial1) }),
// 后面贴上 boxMaterial1 纹理
new THREE.MeshBasicMaterial({ map: textureLoader.load(boxMaterial1) }),
// 左面贴上 boxMaterial3 纹理
new THREE.MeshBasicMaterial({ map: textureLoader.load(boxMaterial3) }),
// 右面贴上 boxMaterial1 纹理
new THREE.MeshBasicMaterial({ map: textureLoader.load(boxMaterial1) }),
// 上面贴上 boxMaterial2 纹理
new THREE.MeshBasicMaterial({ map: textureLoader.load(boxMaterial2) }),
// 下面贴上 boxMaterial3 纹理
new THREE.MeshBasicMaterial({ map: textureLoader.load(boxMaterial3) }),
];
// 使用几何体和材质列表创建立方体网格
const box = new THREE.Mesh(geometry, boxMaterialList);
// 设置立方体的位置,使其稍微抬高一点,避免与地面重叠
box.position.y = 18; // 将立方体的 Y 坐标设置为 18
// 返回创建的立方体
return box;
};
使用场景:
创建一个 6 面不同纹理的立方体,并将其浮动在地面上方。通过这种方式,可以使每个面拥有不同的材质,从而展现丰富的视觉效果。
4. 使用 webGL 的方式绘制几何体
// 常见球形对象创建函数
const createBall = () => {
// 顶点着色器代码
const vShader = `
void main() {
// 计算顶点的最终位置,投影矩阵和模型视图矩阵用于转换坐标到屏幕空间
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
// 片段着色器代码
const fShader = `
void main() {
// 设置像素颜色为蓝绿色,RGBA 格式,值范围 [0.0, 1.0]
gl_FragColor = vec4(0.5, 0.25, 1.0, 1.0);
}
`;
// 创建球体几何体,半径为 4
const sphereGeometry = new THREE.SphereGeometry(4);
// 创建着色器材质,将自定义的顶点和片段着色器代码传入
const sphereMaterial = new THREE.ShaderMaterial({
vertexShader: vShader, // 使用自定义顶点着色器
fragmentShader: fShader // 使用自定义片段着色器
});
// 创建网格对象,将几何体和材质结合
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
// 设置球体的位置,放置在 (-5, 10, 10)
sphere.position.set(-5, 10, 10);
// 返回球体对象,方便添加到场景中
return sphere;
};
效果图:
完整的代码:
<template>
<div>
<div ref="threeBoxRef" class="threeBox"></div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import skyBlue from "@/assets/images/skyBlue.jpg";
import boxMaterial1 from "@/assets/images/material1.jpeg";
import boxMaterial2 from "@/assets/images/material2.jpeg";
import boxMaterial3 from "@/assets/images/material3.jpeg";
// 创建三个场景的容器引用
const threeBoxRef = ref<HTMLElement | null>(null);
// 在组件挂载后初始化 Three.js
onMounted(() => {
if (threeBoxRef.value) {
initThreeFn(threeBoxRef.value);
}
});
// 初始化 Three.js 场景
const initThreeFn = (container: HTMLElement) => {
const { renderer, scene, camera } = createRendererSceneCamera(container);
addHelpers(scene);
addObjectsToScene(scene);
addResizeListener(renderer, camera);
animate(renderer, scene, camera);
};
// 创建渲染器、场景和相机
const createRendererSceneCamera = (container: HTMLElement) => {
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const w = window.innerWidth;
const h = window.innerHeight;
const camera = new THREE.PerspectiveCamera(75, w / h, 0.1, 500);
camera.position.set(10, 25, 25);
return { renderer, scene, camera };
};
// 添加辅助元素(坐标轴和网格)
const addHelpers = (scene: THREE.Scene) => {
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
const gridHelper = new THREE.GridHelper(30, 30);
scene.add(gridHelper);
};
// 添加物体到场景(地面、几何体)
const addObjectsToScene = (scene: THREE.Scene) => {
const plane = createPlane();
scene.add(plane);
const box = createBox();
scene.add(box);
const ball = createBall();
scene.add(ball)
createBackground(scene);
};
// 创建平面
const createPlane = () => {
const geometry = new THREE.PlaneGeometry(30, 30);
const material = new THREE.MeshBasicMaterial({ color: 0x333333, side: THREE.DoubleSide });
const plane = new THREE.Mesh(geometry, material);
plane.rotation.x = -0.5 * Math.PI; // 旋转平面,使其平行于地面
return plane;
};
// 创建立方体
const createBox = () => {
const textureLoader = new THREE.TextureLoader();
const geometry = new THREE.BoxGeometry(6, 6, 6);
const boxMaterialList = [
new THREE.MeshBasicMaterial({ map: textureLoader.load(boxMaterial1) }),
new THREE.MeshBasicMaterial({ map: textureLoader.load(boxMaterial1) }),
new THREE.MeshBasicMaterial({ map: textureLoader.load(boxMaterial3) }),
new THREE.MeshBasicMaterial({ map: textureLoader.load(boxMaterial1) }),
new THREE.MeshBasicMaterial({ map: textureLoader.load(boxMaterial2) }),
new THREE.MeshBasicMaterial({ map: textureLoader.load(boxMaterial3) }),
]
const box = new THREE.Mesh(geometry, boxMaterialList);
box.position.y = 18; // 将立方体抬高一点,使其不与地面重叠
return box;
};
// 常见球形
const createBall = () => {
const vShader = `
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
`;
const fShader = `
void main() {
gl_FragColor = vec4(0.5, 0.5, 1.0, 1.0);
}
`;
const sphereGeometry = new THREE.SphereGeometry(4);
const sphereMaterial = new THREE.ShaderMaterial({
vertexColors: vShader,
fragmentShader: fShader
})
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.set(-5, 10, 10);
return sphere;
}
// 创建背景渲染
const createBackground = (scene: THREE.Scene) => {
const textureLoader = new THREE.TextureLoader();
scene.background = textureLoader.load(skyBlue);
}
// 设置鼠标控制
const setupOrbitControls = (camera: THREE.PerspectiveCamera, renderer: THREE.WebGLRenderer) => {
return new OrbitControls(camera, renderer.domElement);
};
// 动画循环
const animate = (renderer: THREE.WebGLRenderer, scene: THREE.Scene, camera: THREE.PerspectiveCamera) => {
const orbit = setupOrbitControls(camera, renderer);
const animateFn = () => {
orbit.update();
renderer.render(scene, camera);
requestAnimationFrame(animateFn);
};
animateFn();
};
// 动态调整窗口大小
const addResizeListener = (renderer: THREE.WebGLRenderer, camera: THREE.PerspectiveCamera) => {
window.addEventListener('resize', () => {
if (renderer) {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
}
});
};
</script>
<style scoped>
.threeBox {
width: 100%;
height: 100vh;
}
</style>
5. 在场景中导入三维模型模型文件 - *.glb
import * as THREE from "three";
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
const horseUrl = new URL("@/assets/images/Horse.glb", import.meta.url);
// 定义导入模型的函数
// 参数:scene 是当前的 Three.js 场景
const importModel = (scene: THREE.Scene) => {
// 创建 GLTFLoader 实例,用于加载 GLB 或 GLTF 模型
const assetLoader = new GLTFLoader();
// 调用 load 方法加载模型
assetLoader.load(
horseUrl.href, // 模型文件的路径,使用 horseUrl 提供的 href 属性
(gltf) => {
// 当模型加载成功时的回调函数
const model = gltf.scene; // 获取模型的场景对象
// 设置模型的位置,将其移动到指定坐标
model.position.set(-12, 4, 10);
// 调整模型的缩放比例
model.scale.set(0.06, 0.06, 0.06);
// 将模型添加到场景中,使其在渲染器中可见
scene.add(model);
// 打印日志,确认模型加载成功并输出模型信息
console.log("模型加载成功:", model);
},
undefined, // 这里可以提供一个函数用于处理加载进度,但当前未实现
(error) => {
// 当加载失败时的回调函数
// 打印错误日志以便排查问题
console.error("模型加载失败:", error);
}
);
};
完整代码:
<template>
<div>
<div ref="threeBoxRef" class="threeBox"></div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import skyBlue from "@/assets/images/skyBlue.jpg";
import boxMaterial1 from "@/assets/images/material1.jpeg";
import boxMaterial2 from "@/assets/images/material2.jpeg";
import boxMaterial3 from "@/assets/images/material3.jpeg";
const horseUrl = new URL("@/assets/images/Horse.glb", import.meta.url);
// 创建三个场景的容器引用
const threeBoxRef = ref<HTMLElement | null>(null);
// 在组件挂载后初始化 Three.js
onMounted(() => {
if (threeBoxRef.value) {
initThreeFn(threeBoxRef.value);
}
});
// 初始化 Three.js 场景
const initThreeFn = (container: HTMLElement) => {
const { renderer, scene, camera } = createRendererSceneCamera(container);
addHelpers(scene);
addObjectsToScene(scene);
addResizeListener(renderer, camera);
animate(renderer, scene, camera);
};
// 创建渲染器、场景和相机
const createRendererSceneCamera = (container: HTMLElement) => {
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const w = window.innerWidth;
const h = window.innerHeight;
const camera = new THREE.PerspectiveCamera(75, w / h, 0.1, 500);
camera.position.set(10, 25, 25);
return { renderer, scene, camera };
};
// 添加辅助元素(坐标轴和网格)
const addHelpers = (scene: THREE.Scene) => {
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
const gridHelper = new THREE.GridHelper(30, 30);
scene.add(gridHelper);
};
// 添加物体到场景(地面、几何体)
const addObjectsToScene = (scene: THREE.Scene) => {
const plane = createPlane();
scene.add(plane);
const box = createBox();
scene.add(box);
const ball = createBall();
scene.add(ball)
importModel(scene);
createBackground(scene);
};
// 创建平面
const createPlane = () => {
const geometry = new THREE.PlaneGeometry(30, 30);
const material = new THREE.MeshBasicMaterial({ color: 0x333333, side: THREE.DoubleSide });
const plane = new THREE.Mesh(geometry, material);
plane.rotation.x = -0.5 * Math.PI; // 旋转平面,使其平行于地面
return plane;
};
// 创建立方体
const createBox = () => {
const textureLoader = new THREE.TextureLoader();
const geometry = new THREE.BoxGeometry(6, 6, 6);
const boxMaterialList = [
new THREE.MeshBasicMaterial({ map: textureLoader.load(boxMaterial1) }),
new THREE.MeshBasicMaterial({ map: textureLoader.load(boxMaterial1) }),
new THREE.MeshBasicMaterial({ map: textureLoader.load(boxMaterial3) }),
new THREE.MeshBasicMaterial({ map: textureLoader.load(boxMaterial1) }),
new THREE.MeshBasicMaterial({ map: textureLoader.load(boxMaterial2) }),
new THREE.MeshBasicMaterial({ map: textureLoader.load(boxMaterial3) }),
]
const box = new THREE.Mesh(geometry, boxMaterialList);
box.position.y = 18; // 将立方体抬高一点,使其不与地面重叠
return box;
};
// 创建球形
const createBall = () => {
const vShader = `
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
`;
const fShader = `
void main() {
gl_FragColor = vec4(0.5, 0.5, 1.0, 1.0);
}
`;
const sphereGeometry = new THREE.SphereGeometry(4);
const sphereMaterial = new THREE.ShaderMaterial({
vertexColors: vShader,
fragmentShader: fShader
})
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.set(-5, 10, 10);
return sphere;
}
// 创建导入模型
const importModel = (scene: THREE.Scene) => {
const assetLoader = new GLTFLoader();
assetLoader.load(
horseUrl.href,
(gltf) => {
const model = gltf.scene;
model.position.set(-12, 4, 10);
model.scale.set(0.06, 0.06, 0.06);
scene.add(model);
console.log("模型加载成功:", model);
},
undefined,
(error) => {
console.error("模型加载失败:", error);
}
)
}
// 创建背景渲染
const createBackground = (scene: THREE.Scene) => {
const textureLoader = new THREE.TextureLoader();
scene.background = textureLoader.load(skyBlue);
}
// 设置鼠标控制
const setupOrbitControls = (camera: THREE.PerspectiveCamera, renderer: THREE.WebGLRenderer) => {
return new OrbitControls(camera, renderer.domElement);
};
// 动画循环
const animate = (renderer: THREE.WebGLRenderer, scene: THREE.Scene, camera: THREE.PerspectiveCamera) => {
const orbit = setupOrbitControls(camera, renderer);
const animateFn = () => {
orbit.update();
renderer.render(scene, camera);
requestAnimationFrame(animateFn);
};
animateFn();
};
// 动态调整窗口大小
const addResizeListener = (renderer: THREE.WebGLRenderer, camera: THREE.PerspectiveCamera) => {
window.addEventListener('resize', () => {
if (renderer) {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
}
});
};
</script>
<style scoped>
.threeBox {
width: 100%;
height: 100vh;
}
</style>