效果:
代码:
ini
<template>
<div>
<!-- <h1>登录页面</h1>
<el-button type="danger" @click="goHome">跳转首页</el-button><br> -->
<canvas id="earth"></canvas>
</div>
</template>
<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router';
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { onMounted } from 'vue';
onMounted(()=>{
// 初始化渲染器
const canvas = document.getElementById('earth')
const render = new THREE.WebGLRenderer({ antialias: true, canvas });
render.setSize(window.innerWidth,window.innerHeight)
render.setPixelRatio(Math.min(window.devicePixelRatio, 2));
// 初始化场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1A1A1A);
scene.fog = new THREE.Fog(0x1A1A1A, 0, 500);
// 初始化相机
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight)
scene.add(camera);
camera.position.set(-50, 50, 300);
// 轨道控制器 通过它可以对三维场景用鼠标 🖱 进行缩放、平移、旋转等操作,本质上改变的不是场景,而是相机的位置参数。可以选择通过设置 controls.enableDamping 为 true 来开启控制器的移动惯性,这样在使用鼠标交互过程中就会感觉更加流畅和逼真。
const controls = new OrbitControls(camera, render.domElement);
controls.target.set(0,0,0)
controls.enableDamping = true;
function onKeyDown(event) {
switch (event.key) {
case 'ArrowUp':
camera.translateZ(-10)
break;
case 'ArrowDown':
camera.translateZ(10)
break;
case 'ArrowLeft':
camera.translateX(-10);
break;
case 'ArrowRight':
camera.translateX(10);
break;
}
}
document.addEventListener('keydown', onKeyDown);
// controls.autoRotate = true
// 光源
const light = new THREE.AmbientLight(0xffffff, 5);
scene.add(light);
// 地球材质
const SphereMaterial = new THREE.MeshPhongMaterial({
color: 0x03c03c,
wireframe: true,
});
// 地球几何体
const SphereGeometry = new THREE.SphereGeometry(80, 32, 32);
const planet = new THREE.Mesh(SphereGeometry, SphereMaterial);
scene.add(planet);
// 环球轨道
const TorusGeometry = new THREE.TorusGeometry(150, 8, 2, 120);
const TorusMaterial = new THREE.MeshLambertMaterial({
color: 0x40a9ff,
wireframe: true
});
const ring = new THREE.Mesh(TorusGeometry, TorusMaterial);
ring.rotation.x = Math.PI / 2;
ring.rotation.y = -0.1 * (Math.PI / 2);
scene.add(ring);
// 卫星
const IcoGeometry = new THREE.IcosahedronGeometry(16, 0);
const IcoMaterial = new THREE.MeshToonMaterial({ color: 0xfffc00 });
const satellite = new THREE.Mesh(IcoGeometry, IcoMaterial);
satellite.position.x = 200
satellite.position.y = 100
scene.add(satellite);
// 星星
const stars = new THREE.Group();
for (let i = 0; i < 500; i++) {
const geometry = new THREE.IcosahedronGeometry(Math.random() * 2, 1);
const material = new THREE.MeshToonMaterial({ color: 0xeeeeee });
const mesh = new THREE.Mesh(geometry, material);
mesh.position.x = (Math.random() - 0.5) * 700;
mesh.position.y = (Math.random() - 0.5) * 700;
mesh.position.z = (Math.random() - 0.5) * 700;
mesh.rotation.x = Math.random() * Math.PI;
mesh.rotation.y = Math.random() * Math.PI;
mesh.rotation.z = Math.random() * Math.PI;
stars.add(mesh);
}
scene.add(stars);
// 辅助轴
const axesHelper = new THREE.AxesHelper(2000)
scene.add(axesHelper)
let rot = 2;
// 动画
const axis = new THREE.Vector3(0, 0, 1);
const tick = () => {
// 给网格模型添加一个转动动画
// rot += Math.random() * 3;
// const radian = (rot * Math.PI) / 180;
rot+=0.05
// 星球位置动画
planet && (planet.rotation.y += .005);
// 星球轨道环位置动画
ring && ring.rotateOnAxis(axis, Math.PI / 400);
// 卫星位置动画
satellite.position.x = -120 * Math.sin(rot)*Math.cos(180); // 将球坐标系(r,θ,∂)转为直角坐标系(x,y,z)
satellite.position.y = -120 * Math.sin(rot)*Math.sin(180); // 将球坐标系(r,θ,∂)转为直角坐标系(x,y,z)
satellite.position.z = 180 * Math.cos(rot); // 将球坐标系(r,θ,∂)转为直角坐标系(x,y,z)
satellite.rotation.x += 0.005;
satellite.rotation.y += 0.005;
satellite.rotation.z -= 0.005;
// 星星动画
stars.rotation.y += 0.0009;
stars.rotation.z -= 0.0003;
// 更新控制器
controls.update();
// 更新渲染器
render.render(scene, camera);
// 页面重绘时调用自身
window.requestAnimationFrame(tick);
}
tick();
// 页面缩放事件监听
window.addEventListener('resize', () => {
const width = window.innerWidth;
const height = window.innerHeight;
// 更新渲染
render.setSize(width, height);
// render.setPixelRatio(Math.min(window.devicePixelRatio, 2))
// 更新相机
camera.aspect = width / height;
camera.updateProjectionMatrix();
});
console.log(111)
})
const router = useRouter()
const route = useRoute()
const goHome = () => {
router.push('/home')
}
</script>
<style scoped>
#earth {
display: block;
width: 100%;
height: 100%;
}
</style>
知识点:
1.雾
在3D引擎里,雾通常是基于离摄像机的距离褪色至某种特定颜色的方式。在three.js中添加雾是通过创建 Fog
或者 FogExp2
实例并设定scene的fog
属性。 Fog
让你设定 near
和 far
属性,代表距离摄像机的距离。任何物体比 near
近不会受到影响,任何物体比 far
远则完全是雾的颜色。在 near
和 far
中间的物体,会从它们自身材料的颜色褪色到雾的颜色。
FogExp2
会根据离摄像机的距离呈指数增长。
选择其中一个类型,创建雾并设定到场景中如下:
ini
- const scene = new THREE.Scene();
- {
- const color = 0xFFFFFF; // white
- const near = 10;
- const far = 100;
- scene.fog = new THREE.Fog(color, near, far);
- }
或者对于 FogExp2
会是:
ini
const scene = new THREE.Scene();
{
const color = 0xFFFFFF;
const density = 0.1;
scene.fog = new THREE.FogExp2(color, density);
}
需要注意的是雾是作用在 渲染的物体 上的,是物体颜色中每个像素计算的一部分。这意味着如果你想让你的场景褪色到某种颜色,你需要设定雾 和 场景的背景颜色为同一种颜色。背景颜色通过scene.background
属性设置。你可以通过 THREE.Color
选择背景颜色设置
fog
在材料上有个布尔属性,用来设置渲染物体的材料是否会受到雾的影响。默认设置为 true
,作为你可能想关掉雾生效的例子,设想下你正在制作一个3D车辆模拟器并处于驾驶员座位或座舱的视角,你很可能为了看清车内的物体将它们的是否受雾影响属性关闭。
2.轨道控制器
Orbit controls(轨道控制器)可以使得相机围绕目标进行轨道运动。
OrbitControls 是一个附加组件,必须显式导入
javascript
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
常用属性
ini
controls.enablePan = false; //禁止右键拖拽
controls.enableZoom = false;//禁止缩放
controls.enableRotate = false; //禁止旋转
controls.minZoom = 0.5; // 最小缩放比例
controls.maxZoom = 2; // 放大最大比例
// 上下旋转范围
controls.minPolarAngle = 0;
controls.maxPolarAngle = Math.PI;
// 左右旋转范围
controls.minAzimuthAngle = -Math.PI * (100 / 180);
controls.maxAzimuthAngle = Math.PI * (100 / 180);
//将其设为true,以自动围绕目标旋转。请注意,如果它被启用,你必须在你的动画循环里调用.update()。
controls.autoRotate = true
// 当.autoRotate为true时,围绕目标旋转的速度将有多快,默认值为2.0,相当于在60fps时每旋转一周需要30秒。
controls.autoRotateSpeed = 2
// 当使用键盘按键的时候,相机平移的速度有多快。默认值为每次按下按键时平移7像素。
controls.keyPanSpeed = 7
// 这一对象包含了用于控制相机平移的按键代码的引用。默认值为4个箭头(方向)键。
controls.keys = {
LEFT: 'ArrowLeft', //left arrow
UP: 'ArrowUp', // up arrow
RIGHT: 'ArrowRight', // right arrow
BOTTOM: 'ArrowDown' // down arrow
}
// 移除所有的事件监听
controls.dispose ()
// 为指定的DOM元素添加按键监听。推荐将window作为指定的DOM元素。
controls.listenToKeyEvents( domElement : HTMLDOMElement )
// 更新控制器。必须在摄像机的变换发生任何手动改变后调用,或如果.autoRotate或.enableDamping被设置时,在update循环里调用。
controls.update ()
更多属性查看官方文档:threejs.org/docs/index....
3.组 Group 多物体的集合与Object3D是一样的
csharp
const geometry = new THREE.BoxGeometry( 1, 1, 1 );
const material = new THREE.MeshBasicMaterial( {color: 0x00ff00} );
const cubeA = new THREE.Mesh( geometry, material );
cubeA.position.set( 100, 100, 0 );
const cubeB = new THREE.Mesh( geometry, material );
cubeB.position.set( -100, -100, 0 );
//create a group and add the two cubes
//These cubes can now be rotated / scaled etc as a group
const group = new THREE.Group();
group.add( cubeA );
group.add( cubeB );
scene.add( group );
4.模型加载
在three.js中只会内置一部分加载器(例如:ObjectLoader) ------ 其它的需要在你的应用中单独引入。
typescript
var loader = new THREE.OBJLoader();
loader.load(model, function (object) {
object.traverse(function (child) {
if (child.isMesh) {
// 对模型子网格的一些操作
}
});
scene.add(object);
});
javascript
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const loader = new GLTFLoader();
loader.load( 'path/to/model.glb', function ( gltf ) {
scene.add( gltf.scene );
}, undefined, function ( error ) {
console.error( error );
} );
5.贴图
为了模拟更加真实的效果,就要给模型材质添加贴图,贴图就像模型的皮肤一样,使其三维效果更佳。添加贴图的原理是通过纹理贴图加载器 TextureLoader()
去新创建一个贴图对象出来,然后再去调用里面的 load()
方法去加载一张图片,这样就会返回一个纹理对象,纹理对象可以作为模型材质颜色贴图 map
属性的值,材质的颜色贴图属性 map
设置后,模型会从纹理贴图上采集像素值。下面列出了几种常用的贴图类型以及加载贴图的基本流程。
map
:材质贴图normalMap
:法线贴图bumpMap
:凹凸贴图envMap
:环境贴图specularMap
:高光贴图lightMap
:光照贴图
代码示例:
ini
const texLoader = new THREE.TextureLoader();
loader.load('assets/models/meta.fbx', function (mesh) {
mesh.traverse(function (child) {
if (child.isMesh) {
if (child.name === '需要添加贴图的模型') {
child.material = new THREE.MeshPhysicalMaterial({
map: texLoader.load("./assets/images/metal.png"),
});
}
}
});
})