官网:three.js docshttps://threejs.org/docs/index.html#manual/zh/introduction/Creating-a-scene
1.使用three.js绘制多个立方体
(1)定义基本的数组对象,描绘每个立方体的信息
如:绘制5个立方体,随机设置不同颜色(color),大小(宽w高h深d),位置(x,y,z轴坐标)
const cubeInfoArr = []
for (let i = 0; i < 5; i++) {
cubeInfoArr.push({
color: `rgb(${Math.floor(Math.random() * (255 - 0 + 1) + 0)}, ${Math.floor(Math.random() * (255 - 0 + 1) + 0)}, ${Math.floor(Math.random() * (255 - 0 + 1) + 0)})`,
w: Math.floor(Math.random() * (3 - 1 + 1) + 1),
h: Math.floor(Math.random() * (3 - 1 + 1) + 1),
d: Math.floor(Math.random() * (3 - 1 + 1) + 1),
x: Math.floor(Math.random() * (5 - -5 + 1) + -5),
y: Math.floor(Math.random() * (5 - -5 + 1) + -5),
z: Math.floor(Math.random() * (5 - -5 + 1) + -5),
})
}
(2) 针对每个数据对象,创建物体
cubeInfoArr.map(cubeObj => {
const {color, w, h, d, x, y, z} = cubeObj
const geometry = new THREE.BoxGeometry(w, h, d)
const material = new THREE.MeshBasicMaterial({ color })
const cube = new THREE.Mesh(geometry, material)
cube.position.set(x, y, z)
scene.add(cube)
})
2.three.js 性能监视器
目的:可以查看网页每秒传输帧数,每帧刷新用时,内存占用,帮我们更好的调试 3D 项目
步骤:
(1)单独引入 Stats 附加组件
import Stats from 'three/examples/jsm/libs/stats.module.js'
(2)创建性能监视器
let stats
stats = new Stats()
(3)设置监视器面板类型(0:fps-每秒传输帧数,1:ms-每帧刷新用时,2:mb-内存占用)
stats.setMode(0)
(4)设置监视器的位置并添加 DOM
stats.domElement.style.position = 'fixed'
stats.domElement.style.left = '0'
stats.domElement.style.top = '0'
document.querySelector('#threeID').appendChild(stats.domElement)
(5)性能监视器数值不断更新
function renderLoop() {
//。。。其他代码
// 性能监视器数值不断更新
stats.update()
//。。。其他代码
}
3.three.js 删除物体
目的:双击一次屏幕,删除一个立方体
(1)在创建立方体的时候,给物体定义名字(方便后续获取和甄别)
function createCube() {
const cubeInfoArr = []
for (let i = 0; i < 5; i++) {
//...其他代码
}
cubeInfoArr.map(cubeObj => {
//...其他代码
cube.name = 'cu' // 给物体定义名字(方便后续获取和甄别)
//...其他代码
})
}
(2)给 window 绑定事件
(3)调用 three.js 相关废置函数
(4)再从场景中移除物体
function removeCube() {
// 1. 给 window 绑定事件
window.addEventListener('dblclick', () => {
// 2. 调用 three.js 相关废置函数,名字与定义的名字对应匹配
const arr = scene.children.filter(obj => obj.name === 'cu')
const cube = arr[0]
if (cube) {
cube.geometry.dispose() // 移除图形数据
cube.material.dispose() // 移除材质数据
// 3. 再从场景中移除物体
scene.remove(cube)
}
})
}
4.three.js 物体分组管理
目的:把所有 three.js 物体放入到一个大的组物体中,可以同时位移,旋转,缩放,或删除等等
(1)新建分组
let group = new THREE.Group()
(2)创建物体的同时把物体加入add分组,再将分组加入add场景
function createCube() {
const cubeInfoArr = []
for (let i = 0; i < 2500; i++) {
//...其他代码
}
cubeInfoArr.map(cubeObj => {
//...其他代码
// 分组中加入物体
group.add(cube)
})
// 把分组加入到场景中
scene.add(group)
}
(3)比如在移除时可以直接移除分组对象
function removeCube() {
window.addEventListener('dblclick', () => {
group.children.map(obj => {
obj.geometry.dispose()
obj.material.dispose()
group.remove(obj)
})
// 把组对象移除掉
scene.remove(group)
})
}
以上效果所有代码:
<template>
<div id="threeID">
</div>
</template>
<script setup>
// 引入 three 库
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import Stats from 'three/examples/jsm/libs/stats.module.js'
//引入dat.gui 库
import * as dat from 'dat.gui'
// 创建场景,摄像机,渲染器
let scene, camera, renderer
// 创建轨道控制器
let controls
//创建立方体
let cube
// 性能监视器
let stats
// 新建分组
let group = new THREE.Group()
/**
* 创建基础场景
*/
function init() {
// 1. 创建场景对象
scene = new THREE.Scene()
// 2. 创建摄像机对象
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
//移动摄像机向z轴5个单位(默认摄像机和物体在原点)
camera.position.z = 5
// 3. 创建渲染器,并设置画布大小,添加到 DOM 显示
renderer = new THREE.WebGLRenderer()
// 设置画布大小
renderer.setSize(window.innerWidth, window.innerHeight)
// 把画布 canvas 标签添加到id为threeID上
document.querySelector('#threeID').append(renderer.domElement)
}
/**
* 创建多个立方体
*/
function createCube() {
// 1. 定义数据对象,描绘每个立方体的信息
const cubeInfoArr = []
for (let i = 0; i < 5; i++) {
cubeInfoArr.push({
color: `rgb(${Math.floor(Math.random() * (255 - 0 + 1) + 0)}, ${Math.floor(Math.random() * (255 - 0 + 1) + 0)}, ${Math.floor(Math.random() * (255 - 0 + 1) + 0)})`,
w: Math.floor(Math.random() * (3 - 1 + 1) + 1),
h: Math.floor(Math.random() * (3 - 1 + 1) + 1),
d: Math.floor(Math.random() * (3 - 1 + 1) + 1),
x: Math.floor(Math.random() * (5 - -5 + 1) + -5),
y: Math.floor(Math.random() * (5 - -5 + 1) + -5),
z: Math.floor(Math.random() * (5 - -5 + 1) + -5),
})
}
cubeInfoArr.map(cubeObj => {
const { color, w, h, d, x, y, z } = cubeObj
const geometry = new THREE.BoxGeometry(w, h, d)
const material = new THREE.MeshBasicMaterial({ color })
const cube = new THREE.Mesh(geometry, material)
cube.position.set(x, y, z)
cube.name = 'cu' // 给物体定义名字(方便后续获取和甄别)
// 分组中加入物体
group.add(cube)
// scene.add(cube) //单个加入场景
scene.add(group) //分组加入场景
})
}
/**
* 轨道控制器
*/
function controlsCreate() {
// 创建轨道控制器
controls = new OrbitControls(camera, renderer.domElement)
// 1. 阻尼效果
controls.enableDamping = true
// 2. 开启自动旋转轨道控制器效果->带动摄像机一起旋转(摄像机顺时针水平旋转)
// controls.autoRotate = true
// 3. 垂直角度范围控制(0 上面,Math.PI 下面)
// controls.maxPolarAngle = Math.PI
// controls.minPolarAngle = 0
// 水平角度范围控制
// controls.maxAzimuthAngle = 1.5 * Math.PI
// controls.minAzimuthAngle = 0.5 * Math.PI
// 4. 摄像机移动范围控制
// controls.minDistance = 2
// controls.maxDistance = 10
}
function renderLoop() {
// 在渲染循环中更新场景渲染
renderer.render(scene, camera)
// 手动 JS 代码更新过摄像机信息,必须调用轨道控制器 update 方法
controls.update()
// 性能监视器数值不断更新
stats.update()
// 根据当前计算机浏览器刷新帧率(默认 60 次/秒),不断递归调用此函数渲染最新的画面状态
// 好处:当前页面切换到后台,暂停递归
requestAnimationFrame(renderLoop)
}
/**
* 坐标轴
*/
function createHelper() {
// 1. 创建坐标轴对象,设置长度
const axesHelper = new THREE.AxesHelper(5)
// 2. 添加到场景中
scene.add(axesHelper)
}
/**
* 适配场景大小
*/
function renderResize() {
// 1. 创建适配函数,监听浏览器 resize 事件
document.querySelector('#threeID').addEventListener('resize', () => {
// 2. 调整渲染器画布大小,摄像机宽高比和更新视椎体空间
renderer.setSize(window.innerWidth, window.innerHeight)
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
})
}
/**
* 性能监视器
*/
function createStats() {
// 2. 创建性能监视器
stats = new Stats()
// 3. 设置监视器面板类型(0:fps-每秒传输帧数,1:ms-每帧刷新用时,2:mb-内存占用)
stats.setMode(0)
// 4. 设置监视器位置并添加 DOM
stats.domElement.style.position = 'fixed'
stats.domElement.style.left = '0'
stats.domElement.style.top = '0'
document.querySelector('#threeID').appendChild(stats.domElement)
}
/**
* 双击删除物体
*/
function removeCube() {
// 1. 给 window 绑定事件
window.addEventListener('dblclick', () => {
/**
* 单个移除
*/
// 2. 调用 three.js 相关废置函数
// const arr = scene.children.filter(obj => obj.name === 'cu')
// console.log(arr, 'arr')
// const cube = arr[0]
// if (cube) {
// cube.geometry.dispose() // 移除图形数据
// cube.material.dispose() // 移除材质数据
// // 3. 再从场景中移除物体
// scene.remove(cube)
// }
/**
* 移除分组
*/
group.children.map(obj => {
obj.geometry.dispose()
obj.material.dispose()
group.remove(obj)
})
// 把组对象移除掉
scene.remove(group)
})
}
onMounted(() => {
init() //初始化
createCube()//创建多个立方体
controlsCreate()//轨道控制器
createStats()
renderLoop()//循环渲染轨道控制器
createHelper()//坐标轴
renderResize()//适配场景大小
removeCube() //移除物体
renderer.render(scene, camera)
})
</script>
<style scoped></style>
5.three.js 点物体和材质
步骤如创建立方体类似,对应的材质不一样,点物体使用对应的点材质,几何图形不限制
function createSphere() {
// 1. 创建几何图形
const geometry = new THREE.SphereGeometry(1, 32, 16)
// 2. 创建点材质
const material = new THREE.PointsMaterial({ color: 0x6600ff, size: 0.05 })
// 3. 创建点物体
const points = new THREE.Points(geometry, material)
scene.add(points)
}
6.three.js 线物体和材质
Line: 一条连续的线
LineLoop: 一条从头链接到尾的闭合线
LineSegments:按顺序一对点链接一条线
function createLine() {
// 1. 创建几何图形
const geometry = new THREE.SphereGeometry(1, 32, 16)
// 2. 创建线材质
const material = new THREE.LineBasicMaterial({
color: 0x6600ff
})
// 3. 创建线物体
const line = new THREE.Line(geometry, material)
scene.add(line)
}
以上代码:
<template>
<div id="threeID">
</div>
</template>
<script setup>
// 引入 three 库
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import Stats from 'three/examples/jsm/libs/stats.module.js'
// 创建场景,摄像机,渲染器
let scene, camera, renderer
// 创建轨道控制器
let controls
/**
* 创建基础场景
*/
function init() {
// 1. 创建场景对象
scene = new THREE.Scene()
// 2. 创建摄像机对象
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
//移动摄像机向z轴5个单位(默认摄像机和物体在原点)
camera.position.z = 5
// 3. 创建渲染器,并设置画布大小,添加到 DOM 显示
renderer = new THREE.WebGLRenderer()
// 设置画布大小
renderer.setSize(window.innerWidth, window.innerHeight)
// 把画布 canvas 标签添加到id为threeID上
document.querySelector('#threeID').append(renderer.domElement)
}
/**
* 创建球形点物体
*/
function createSphere() {
// 1. 创建几何图形
const geometry = new THREE.SphereGeometry(1, 32, 16)
// 2. 创建点材质
const material = new THREE.PointsMaterial({ color: 0x6600ff, size: 0.05 })
// 3. 创建点物体
const points = new THREE.Points(geometry, material)
scene.add(points)
}
/**
* 球形点与点之间加上线段
*/
function createLine() {
// 1. 创建几何图形
const geometry = new THREE.SphereGeometry(1, 32, 16)
// 2. 创建线材质
const material = new THREE.LineBasicMaterial({
color: 0x6600ff
})
// 3. 创建线物体
const line = new THREE.LineSegments(geometry, material)
scene.add(line)
}
/**
* 轨道控制器
*/
function controlsCreate() {
// 创建轨道控制器
controls = new OrbitControls(camera, renderer.domElement)
// 1. 阻尼效果
controls.enableDamping = true
// 2. 开启自动旋转轨道控制器效果->带动摄像机一起旋转(摄像机顺时针水平旋转)
// controls.autoRotate = true
// 3. 垂直角度范围控制(0 上面,Math.PI 下面)
// controls.maxPolarAngle = Math.PI
// controls.minPolarAngle = 0
// 水平角度范围控制
// controls.maxAzimuthAngle = 1.5 * Math.PI
// controls.minAzimuthAngle = 0.5 * Math.PI
// 4. 摄像机移动范围控制
// controls.minDistance = 2
// controls.maxDistance = 10
}
function renderLoop() {
// 在渲染循环中更新场景渲染
renderer.render(scene, camera)
// 手动 JS 代码更新过摄像机信息,必须调用轨道控制器 update 方法
controls.update()
// 根据当前计算机浏览器刷新帧率(默认 60 次/秒),不断递归调用此函数渲染最新的画面状态
// 好处:当前页面切换到后台,暂停递归
requestAnimationFrame(renderLoop)
}
/**
* 坐标轴
*/
function createHelper() {
// 1. 创建坐标轴对象,设置长度
const axesHelper = new THREE.AxesHelper(5)
// 2. 添加到场景中
scene.add(axesHelper)
}
/**
* 适配场景大小
*/
function renderResize() {
// 1. 创建适配函数,监听浏览器 resize 事件
document.querySelector('#threeID').addEventListener('resize', () => {
// 2. 调整渲染器画布大小,摄像机宽高比和更新视椎体空间
renderer.setSize(window.innerWidth, window.innerHeight)
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
})
}
onMounted(() => {
init() //初始化
createSphere()//创建球形点物体
createLine()//连线
controlsCreate()//轨道控制器
renderLoop()//循环渲染轨道控制器
createHelper()//坐标轴
renderResize()//适配场景大小
renderer.render(scene, camera)
})
</script>
<style scoped></style>
7.three.js 全景图贴图
(1)引入图片,(vue3使用import本地引入图片,vue2可使用require)
import earthImg from '@/assets/earth.png'
(2)创建球体几何图形,使用纹理加载器并创建网格材质对象,创建网格物体
function createMap() {
// 1. 创建球体几何图形
const geometry = new THREE.SphereGeometry(1, 32, 16)
// 2. 使用纹理加载器并创建网格材质对象
const texture = new THREE.TextureLoader().load(earthImg);
// 立即使用纹理进行材质创建(map: 颜色贴图)
const material = new THREE.MeshBasicMaterial({ map: texture });
// 3. 创建网格物体
const sphere = new THREE.Mesh(geometry, material)
scene.add(sphere)
}
效果如图所示:
全部代码:
<template>
<div id="threeID">
</div>
</template>
<script setup>
// 引入 three 库
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import earthImg from '@/assets/earth.png'
// 创建场景,摄像机,渲染器
let scene, camera, renderer
// 创建轨道控制器
let controls
/**
* 创建基础场景
*/
function init() {
// 1. 创建场景对象
scene = new THREE.Scene()
// 2. 创建摄像机对象
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
//移动摄像机向z轴5个单位(默认摄像机和物体在原点)
camera.position.z = 5
// 3. 创建渲染器,并设置画布大小,添加到 DOM 显示
renderer = new THREE.WebGLRenderer()
// 设置画布大小
renderer.setSize(window.innerWidth, window.innerHeight)
// 把画布 canvas 标签添加到id为threeID上
document.querySelector('#threeID').append(renderer.domElement)
}
/**
* 创建球状全景贴图
*/
function createMap() {
// 1. 创建球体几何图形
const geometry = new THREE.SphereGeometry(1, 32, 16)
// 2. 使用纹理加载器并创建网格材质对象
const texture = new THREE.TextureLoader().load(earthImg);
// 立即使用纹理进行材质创建(map: 颜色贴图)
const material = new THREE.MeshBasicMaterial({ map: texture });
// 3. 创建网格物体
const sphere = new THREE.Mesh(geometry, material)
scene.add(sphere)
}
/**
* 轨道控制器
*/
function controlsCreate() {
// 创建轨道控制器
controls = new OrbitControls(camera, renderer.domElement)
// 1. 阻尼效果
controls.enableDamping = true
}
function renderLoop() {
// 在渲染循环中更新场景渲染
renderer.render(scene, camera)
// 手动 JS 代码更新过摄像机信息,必须调用轨道控制器 update 方法
controls.update()
// 根据当前计算机浏览器刷新帧率(默认 60 次/秒),不断递归调用此函数渲染最新的画面状态
// 好处:当前页面切换到后台,暂停递归
requestAnimationFrame(renderLoop)
}
/**
* 坐标轴
*/
function createHelper() {
// 1. 创建坐标轴对象,设置长度
const axesHelper = new THREE.AxesHelper(5)
// 2. 添加到场景中
scene.add(axesHelper)
}
/**
* 适配场景大小
*/
function renderResize() {
// 1. 创建适配函数,监听浏览器 resize 事件
document.querySelector('#threeID').addEventListener('resize', () => {
// 2. 调整渲染器画布大小,摄像机宽高比和更新视椎体空间
renderer.setSize(window.innerWidth, window.innerHeight)
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
})
}
onMounted(() => {
init() //初始化
createMap()
controlsCreate()//轨道控制器
renderLoop()//循环渲染轨道控制器
createHelper()//坐标轴
renderResize()//适配场景大小
renderer.render(scene, camera)
})
</script>
<style scoped></style>
8.three.js 立方体贴图
function createCubeMap() {
// 1. 创建立方缓冲几何体
const geometry = new THREE.BoxGeometry(1, 1, 1)
// 2. 加载不同纹理图片并创建材质对象 6 个
const imgUrlArr = ['posx.jpg', 'negx.jpg', 'posy.jpg', 'negy.jpg', 'posz.jpg', 'negz.jpg']
// 纹理加载器
const textureLoader = new THREE.TextureLoader()
// 设置当前纹理加载器公共的基础路径
textureLoader.setPath('/src/assets/park/')
// 遍历图片地址,映射成纹理材质对象
const materialArr = imgUrlArr.map(imgUrl => {
// 创建纹理图片对象
const texture = textureLoader.load(imgUrl)
// three.js 颜色通道为 rgb 颜色(为了防止图片太浅)
texture.colorSpace = THREE.SRGBColorSpace
return new THREE.MeshBasicMaterial({
map: texture,
side: THREE.DoubleSide
})
})
// 3. 创建网格物体并加入场景
const cube = new THREE.Mesh(geometry, materialArr)
scene.add(cube)
}
9.three.js 视频纹理
目的:把 video 原生视频标签,转成视频物体加入到 3D 空间中
function createPlaneMap() {
// 1. 创建平面几何物体
// 2. 创建并设置视频纹理贴图
const geometry = new THREE.PlaneGeometry(1, 0.5)
// 视频纹理
// 准备视频标签
const video = document.createElement('video')
video.src = '/src/assets/video/mouse_cat.mp4'
video.muted = true // 静音
video.addEventListener('loadedmetadata', () => { // 加载视频完成
video.play() // 开始播放视频
})
// 创建视频纹理对象
const texture = new THREE.VideoTexture(video)
// 把视频纹理->贴到材质上
const material = new THREE.MeshBasicMaterial({ map: texture })
// 创建物体
const plane = new THREE.Mesh(geometry, material)
scene.add(plane)
// 点击按钮->播放声音
const button = document.createElement('button')
button.innerHTML = '播放'
button.style.position = 'fixed'
button.style.left = '0'
button.style.bottom = '0'
document.body.appendChild(button)
button.addEventListener('click', () => {
video.muted = false // 关闭静音
})
}
10.three.js 点击事件
目的:与 3D 物体进行鼠标交互
常见的场景交互:比如点击一个标记,让场景交换,切换场景
目标:与 3D 物体进行鼠标交互
类型1:原生 DOM 支持原生事件(设置 pointerEvents = ‘all’)
类型2:three.js 物体使用光射投影 Raycaster
公式:
x 点坐标:(浏览器 x 轴坐标点 / 画布宽度) * 2 - 1
y 点坐标:- (浏览器 y 轴坐标点 / 画布高度) * 2 + 1
import { CSS3DObject, CSS3DRenderer } from 'three/addons/renderers/CSS3DRenderer.js'
let labelRenderer
function domTo3D() {
const tag = document.createElement('span')
tag.innerHTML = '立方体'
tag.style.color = 'white'
// 类型1:原生 DOM 使用原生的事件绑定(设置 pointerEvents='all')
tag.style.pointerEvents = 'all'
tag.addEventListener('click', e => {
alert('dom 被点击了')
e.stopPropagation()
})
const tag3d = new CSS3DObject(tag)
tag3d.scale.set(1 / 32, 1 / 32, 1 / 32)
tag3d.position.set(0, 1, 0)
scene.add(tag3d)
labelRenderer = new CSS3DRenderer()
labelRenderer.setSize(window.innerWidth, window.innerHeight)
labelRenderer.domElement.style.pointerEvents = 'none'
labelRenderer.domElement.style.position = 'fixed'
labelRenderer.domElement.style.left = '0'
labelRenderer.domElement.style.top = '0'
document.querySelector('#threeID').appendChild(labelRenderer.domElement)
}
function bindClick() {
window.addEventListener('click', e => {
// 定义光线投射对象
const raycaster = new THREE.Raycaster()
// 定义二维向量对象(保存转换后的平面 x,y 坐标值)
const pointer = new THREE.Vector2()
// 把屏幕坐标 => WebGL设备坐标
// 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
pointer.x = (e.clientX / window.innerWidth) * 2 - 1
pointer.y = - (e.clientY / window.innerHeight) * 2 + 1
// 更新摄像机和鼠标之间的连线(位置)
raycaster.setFromCamera(pointer, camera)
// 获取这条线穿过了哪些物体,收集成一个数组
const list = raycaster.intersectObjects(scene.children)
console.log(list)
})
}