第二天:地球卫星案例

效果:

代码:

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"),
        });
      }
    }
  });
})
相关推荐
喵叔哟7 分钟前
重构代码之取消临时字段
java·前端·重构
还是大剑师兰特1 小时前
D3的竞品有哪些,D3的优势,D3和echarts的对比
前端·javascript·echarts
王解1 小时前
【深度解析】CSS工程化全攻略(1)
前端·css
一只小白菜~1 小时前
web浏览器环境下使用window.open()打开PDF文件不是预览,而是下载文件?
前端·javascript·pdf·windowopen预览pdf
方才coding1 小时前
1小时构建Vue3知识体系之vue的生命周期函数
前端·javascript·vue.js
阿征学IT1 小时前
vue过滤器初步使用
前端·javascript·vue.js
王哲晓1 小时前
第四十五章 Vue之Vuex模块化创建(module)
前端·javascript·vue.js
丶21361 小时前
【WEB】深入理解 CORS(跨域资源共享):原理、配置与常见问题
前端·架构·web
发现你走远了1 小时前
『VUE』25. 组件事件与v-model(详细图文注释)
前端·javascript·vue.js
Mr.咕咕1 小时前
Django 搭建数据管理web——商品管理
前端·python·django