前言
web3D图形学,元宇宙,作为一名前端开发者,不可不学炫酷技能,引领下一个10年。最近突然有了大把时间,至于为什么有时间,不要问,江湖上的事少打听。有时间了,就学习充电,最近对3D感兴趣,就在自学threejs,这里分享一个可以快速上手并且好玩炫酷的3D效果--地球自转+月球围着地球转,话不多说,直接贴源码:gitee.com/weiweiwucod…
项目启动后,效果路径:http://localhost:5173/camera/T4
效果图
技术栈
- three.js v0.169.0
- vue v3.5.10
- vite v5.4.8
- node v20.17.0
- dat.gui v0.7.9
实现步骤
下面我将按军训时的分解动作,一步一动的方式,来逐步完成,相信你在阅读的时候也可以跟着一起敲击键盘来完成,passion
- 创建容器: 在开始使用three.js之前,你需要一个地方来显示它。vue技术栈,需要在template里创建一个div容器,用来挂载渲染后的cavans
<template>
<div ref="Cavans"></div>
</template>
- 创建场景:
// 创建一个场景
const scene = new THREE.Scene()
- 添加相机: 这里我们使用PerspectiveCamera,透视投影相机,它有四个参数视野角度(FOV)、长宽比(aspect ratio)、近截面(near)、远截面(far)
const camera = new THREE.PerspectiveCamera( 75, document.body.clientWidth / document.body.clientHeight, 0.1, 100 )
camera.position.set(1, 0.5, 10)
camera.lookAt(scene.position)
scene.add(camera)
- 渲染器: 接下来是渲染器,这里是施展魔法的地方。我们在这里用的WebGLRenderer渲染器,暂不考虑不支持WebGL的情况
const renderer = new THREE.WebGLRenderer(})
renderer.render( scene, camera );
- 挂载: onMounted 注意vue的生命周期函数
const Cavans = ref(null)
onMounted(()=>{
// 挂载
Cavans.value.appendChild(renderer.domElement)
})
- 创建几何体-地球: SphereGeometry 球缓冲几何体,第一个参数为球的半径,地球要大一点,这里我们设置为2,其他参数为默认值。MeshBasicMaterial,这里我们使用基础材质,不需要光照。
// 创建一个地球球体
const earth = new THREE.SphereGeometry(2)
const earthMaterial = new THREE.MeshBasicMaterial({
color: 0xacffdc,
wireframe: true
})
const earthMesh = new THREE.Mesh(earth, earthMaterial)
wireframe表示线框显示,效果图如下:
7. 创建几何体-月球: 创建过程同地球,只是半经小了一点,0.5
const moon = new THREE.SphereGeometry(0.5)
const moonMaterial = new THREE.MeshBasicMaterial({
color: 0xacffdc,
wireframe: true
})
const moonMesh = new THREE.Mesh(moon, moonMaterial);
从上面两个球的创建,可以看出,基本分三步来走:创建几何体Geometry,创建材质Material,几何体+材质作为参数,共同生成网格Mesh
- 添加到场景: 调用 scene.add(),物体将会被添加到 (0,0,0) 坐标
scene.add(moonMesh, earthMesh)
- 找准位置: 这时候月球和地球是重合的,坐标都是(0,0,0) ,调整一下
earthMesh.position.set(0, 0, 0)
moonMesh.position.set(-1, 0, 0)
- 贴图: 线框的地球和月球,还是比较丑,不真实,通过加载贴图的方式
import earthImg from '@/assets/earth.jpeg'
import moonImg from '@/assets/moon.jpeg'
const earthTexture = new THREE.TextureLoader().load(earthImg)
const moonTexture = new THREE.TextureLoader().load(moonImg)
地球和月球的图片可以在网上找,也可以下载我的源码,assets里有素材。 接下来要改一下球体的材质的代码了,如下:
const earthMaterial = new THREE.MeshBasicMaterial({
map: earthTexture
})
const moonMaterial = new THREE.MeshBasicMaterial({
map: moonTexture
})
立马就真实多了,这里看一下月球的效果图,做一个对比,如下:
- 渲染并动起来: 接下来,就是让球体动起来了,自转和公转。这里使用了性能好的requestAnimationFrame。也用到了数学的Math对象的方法,sin、cos,果然代码的尽头是数学。Mesh的相关属性:position 位置,rotation 旋转 ,x、y、z代表3D的方向。
const clock = new THREE.Clock()
const animate = () => {
const elapsedTime = clock.getElapsedTime()
// 浏览器API
requestAnimationFrame( animate );
moonMesh.position.x = Math.cos(elapsedTime*0.6) * 5
moonMesh.position.z = Math.sin(elapsedTime*0.6) * 5
moonMesh.rotation.y += 0.01;
earthMesh.rotation.y += 0.005;
controls.update() // 更新轨道控制器
stats.update() // 更新监控帧数
renderer.render( scene, camera );
}
animate();
基于以上步骤,就基本实现功能了,接下来增加一些额外调试与优化
- 调试: 官方调试工具 dat.gui,先安装,直接上代码
// 初始化调试
const gui = createGui()
const guiConfig = {
wireframe: false,
color: 0Xacffdc
}
// gui 调试
// gui.add()
// 参数 target: object, propName: never, min?: number, max?: number, step?: number
gui.add(camera.position,'x',0.1,10,0.1).name('positionX')
gui.add(moonMesh.rotation,'x',1,10,1).name('rotationX')
// 参数为自定义对象
gui.add(guiConfig,'wireframe').onChange((val)=>{
// wireframe 是否启用线框模式,默认为 false
moonMesh.material.wireframe = val
}).name('moonWireframe')
gui.add(guiConfig,'wireframe').onChange((val)=>{
// wireframe 是否启用线框模式,默认为 false
earthMesh.material.wireframe = val
}).name('earthWireframe')
gui.add(camera, 'fov', 30, 120 , 1).onChange((val)=>{
camera.fov = val // 相机参数 视场角 fov改变
camera.updateProjectionMatrix() // 当相机的参数发生变化时,更新相机的投影矩阵
})
//颜色调试
gui.addColor(guiConfig, 'color').onChange((val)=>{
moonMesh.material.color.set(val)
})
本案例实现了wireframe线框模式的切换功能,大家可以自行感受,页面的右上角
页面的左上角的性能监控,需要在onMounted里面挂载,并在animate里更新:stats.update() // 更新监控帧数
import Stats from 'three/examples/jsm/libs/stats.module';
// 添加性能监控器
const stats = new Stats()
stats.setMode(0)
onMounted(()=>{
// 挂载
Cavans.value.appendChild(stats.domElement)
})
- 优化:
// 渲染器优化 抗锯齿antialias
const renderer = new THREE.WebGLRenderer({
antialias: true, // 抗锯齿
})
// 优化 设置像素比
renderer.setPixelRatio(window.devicePixelRatio || 1)
// 渲染器的尺寸
renderer.setSize(document.body.clientWidth, document.body.clientHeight)
// 自适应
resizeHandler(renderer, camera)
// 自适应
const resizeHandler = (renderer: { setSize: (arg0: number, arg1: number) => void; },camera: { aspect: number; updateProjectionMatrix: () => void; }) => {
window.addEventListener('resize', ()=>{
// 更新相机
camera.aspect = document.body.clientWidth / document.body.clientHeight
camera.updateProjectionMatrix() // 请注意,在大多数属性发生改变之后,你将需要调用.updateProjectionMatrix来使得这些改变生效
// 更新渲染器大小
renderer.setSize(document.body.clientWidth, document.body.clientHeight)
})
}
另外,还可以添加辅助坐标系和辅助平面,大家可以自行感受
// 辅助方法
const createHelper = (scene: { add: (arg0: THREE.GridHelper, arg1: THREE.AxesHelper) => void; }) => {
// 创建辅助坐标系
const axesHelper = new THREE.AxesHelper()
// 创建辅助平面
const gridhelper = new THREE.GridHelper()
scene.add(gridhelper, axesHelper);
}
最后,还添加了轨道控制器OrbitControls,可以通过鼠标控制,可以放大、缩小、移动、旋转等。 需要animate里更新: controls.update() // 更新轨道控制器
// import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { OrbitControls } from 'three/addons/controls/OrbitControls'
const controls = new OrbitControls(camera,renderer.domElement)
// 将其设置为true以启用阻尼(惯性),这将给控制器带来重量感。默认值为false。 请注意,如果该值被启用,你将必须在你的动画循环里调用update()
controls.enableDamping = true
// 当.enableDamping设置为true的时候,阻尼惯性有多大。 Default is 0.05. 请注意,要使得这一值生效,你必须在你的动画循环里调用 update()
controls.dampingFactor = 0.03
写到这里基本完事了,目前threejs还在学习中,有错误和的不足地方欢迎指正和交流学习,代码部分会持续更新,期待可以完成更炫酷的3D效果
路虽远,行则将至~~