第二天:地球卫星案例

效果:

代码:

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 让你设定 nearfar 属性,代表距离摄像机的距离。任何物体比 near 近不会受到影响,任何物体比 far 远则完全是雾的颜色。在 nearfar 中间的物体,会从它们自身材料的颜色褪色到雾的颜色。

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"),
        });
      }
    }
  });
})
相关推荐
崔庆才丨静觅43 分钟前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60611 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了2 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅2 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅2 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅2 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment3 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅3 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊3 小时前
jwt介绍
前端
爱敲代码的小鱼3 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax