
>这篇文章就简单 用 ThreeJs 去实现是一个 月亮 围绕着地球的公转。

## 项目搭建
>这里就简单实用webpack + ts 进行搭建项目

1.简单的创建一个文件夹 `npm init` 初始化一个 项目，这里面一路回车下去就好了

![img](http://qnpic.top/2022-03-22-075409.jpg)

然后就开始安装我们的依赖 `yarn add three` , `yarn add @types/three ts-loader typescript webpack webpack-cli webpack-dev-server -D`
其中 `three` 就是我们的 生产依赖，  `types/three` 是 ts 的声明文件， ts-loader 和 typescript 是 我们的 ts， webpack webpack-cli webpack-dev-server 是我们的webpack 依赖

记得自己 生成下 tsconfig.json  然后配置 webpack.config.js. webpack 配置文件.  也是十分的简单， 这里的入口 文件是 src下的 main.ts 记得自己去创建一下， 比如我这边就只是打印了个 `console.log('three')`

```javascript
const path = require('path')

module.exports = {
  mode: 'development',
  devtool: 'source-map',
  entry: {
    index: './src/main.ts'
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        exclude: /(node_modules)/,
        use: [
          {
          loader: 'ts-loader',
        }
      ],
        exclude: /node_modules/
      }
    ],
  },
  resolve: {
    alias: {
      '@': './src',
    },
    extensions: ['.ts', '.js', 'tsx']
  },
  output: { // 打包文件
    filename: 'bundle.js',
    path: path.resolve(__dirname, './dist')
  },
  devServer: { // webpack-dev-server 配置
    port: '0.0.0.0',
    hot: true,
    open: true,
    port: 9015
  }
}
```

> 注意，我们上面那样配置 因为没有配置 public， 公共资源的配置项，所以，webpack 会默认 用 项目根目录下的 `public`文件夹，并且入口 html 是 index.html ，所以我们就需要去 配置这个 public/index.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Three</title>
  <style>

  </style>
</head>
<body>
  <!-- 这里的 bundle 就是 webpack 临时打包出来 -->
  <script src="bundle.js"></script>
</body>
</html>
```

> 然后就是配置 我们的 项目命令 `npm set-script dev webpack-dev-server` 和 `npm set-script build webpack`，当然也可以手动去 package.json 里面去进行配置
>
> 最后 我们就可以 使用 `npm run dev` 启动我们的项目。

![](http://qnpic.top/2022-03-22-081035.jpg)

## 简答的 Three 基础

> Three.js是基于原生WebGL封装运行的三维引擎，在所有WebGL引擎中，Three.js是国内文资料最多、使用最广泛的三维引擎。是对WebGL的进行 封装使用，这里就不对 WebGL进行阐述了，咱也不懂-，-

简单的 Three 三大组成： 场景Scene， 相机 Camera，渲染器

### Scene 场景

> Scene 就比较容易理解，就是在3D 世界里面的 容器，这里面比较需要助力的是， 我们3D 坐标系 是 右手坐标系，就是 拿出我们的右手，分别用 大拇指，食指和中指  像如下。 z指向我们屏幕外

![](http://qnpic.top/2022-03-22-082251.jpg)

### Camera 相机

> 3D世界里，呈现给屏幕上就需要 Camera 相机，这很合理，所以在场景中就需要一个 相机进行 渲染图像，Camera的种类也有很多，我们主要用到的就是 正交相机`OrthographicCamera`和投影相机`PerspectiveCamera`, OrthographicCamera的 有着六个参数分别是 left, right, top, bottom, near, far， 在认知上可以理解有是一个长方体， 物体就是在长方体内，如果超出这个长方体就 不做展示渲染。 投影相机， 有着四个参数， 第一个参数是 角度，别的 自己百度下，这里 不做过多阐述，咱也不懂。

![](http://qnpic.top/2022-03-22-083951.jpg)

### 渲染器

> renderer 就老牛逼，用白话文就是 renderer 会 计算 scene和Camera 的矩阵数据，并通过 THREE 里面的计算 呈现给用户，这里做过多阐述了，咱也不懂

#### 简单实践

> 下面就简单实践一下， 东西太多了， 直接贴源码，有些我会 注释在里面，自己看一下，理解一下， 主要是实践，咱也不懂, 哦，还有 CSS2DObjct， CSS2DRenderer， OrbitControls 这里 并不是这里的重点，可以 自行注释掉， 然后 加上 camera.lockAt(this.scene.postion)  就好了

```typescript
import * as THREE from 'three'
import { CSS2DObjct, CSS2DRenderer } from './CSS2DRender'
import { OrbitControls } from './OrbitControls'

export class EarthAndMoon {
  scene: THREE.Scene
  camera: THREE.PerspectiveCamera
  renderer: THREE.Renderer
  twoDRenderer: CSS2DRenderer

  worldWidth: number
  worldHeight: number
  textureLoader: THREE.TextureLoader
  clock: THREE.Clock
  moon: THREE.Mesh
  constructor() {

    this.textureLoader = new THREE.TextureLoader()
    this.clock = new THREE.Clock()
    this.initScene()
    this.initCamera()
    this.initGeometry()
    this.initRenderer()
    this.initEvents()
    this.loop()
  }

  /**
   * 初始化场景
   */
  initScene() {
    this.worldHeight = window.innerHeight
    this.worldWidth = window.innerWidth
    const scene = this.scene = new THREE.Scene()
    // 创建 平行光， Three 里面的 光也会是有着很多种的， 比如 PointLight 点光源, DirectionalLight 平行光, SpotLight 聚光灯, AmbientLight 环境光
    // 这里的 是模拟太阳光 平行光就 十分合适
    const dirLight = new THREE.DirectionalLight(0xffffff)
    dirLight.position.set(1, 1, 1)
    scene.add(dirLight)

    // 创建 debug的 坐标
    scene.add(new THREE.AxesHelper(5))
    // 创建光源debug
    scene.add(new THREE.DirectionalLightHelper(dirLight))
  }

  /**
   * 初始化相机
   */
  initCamera() {
    // 投影矩阵 
    // THREE.OrthographicCamera()
    this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200)
    this.camera.position.set(10, 5, 20)
    // this.camera.lookAt(this.scene.position)
  }

  /**
   * 初始化渲染器
   */
  initRenderer() {
    // 创建WebGL渲染器
    const renderer = this.renderer = new THREE.WebGLRenderer()
    renderer.setPixelRatio( window.devicePixelRatio );
    this.renderer.setSize(this.worldWidth, this.worldHeight)
    // 把renderer的 domElement 添加到 网页中
    document.body.appendChild(this.renderer.domElement)

    // 创建 CSS2D 渲染器
    // CSS2DRenderer 这里面主要会有一个 render方法 通过入参 scene和 camera 来对 特定的Dom进行CSS渲染设置
    const _css2dRenderer = this.twoDRenderer = new CSS2DRenderer()
    _css2dRenderer.setSize( window.innerWidth, window.innerHeight );
    _css2dRenderer.domElement.style.position = 'absolute';
    _css2dRenderer.domElement.style.top = '0px';
    document.body.appendChild( _css2dRenderer.domElement );

    // OrbitControls Three 自带的 鼠标控制器,这里的入参就是 我们的相机
    const controls = new OrbitControls(this.camera, this.renderer.domElement)
    controls.minDistance = 5;
    controls.maxDistance = 100;
  }

  /**
   * 初始化事件
   */
  initEvents() {
    window.addEventListener( 'resize', () => {
      // 计算 camera的角度值
      this.camera.aspect = window.innerWidth / window.innerHeight;
      // 更新 camerade 的投影矩阵
      this.camera.updateProjectionMatrix();

      this.renderer.setSize( window.innerWidth, window.innerHeight );

      this.twoDRenderer.setSize( window.innerWidth, window.innerHeight );
    });
  }

  /**
   * 初始化几何体
   */
  initGeometry() {
    this.initEarthGeometry()
    this.initMoonGeometry()
  }

  /**
   * 初始化地球几何体
   */
  initEarthGeometry() {
    const textureLoader = this.textureLoader
    // 创建球体
    const earthGeometry = new THREE.SphereGeometry(1, 16, 16)
    // MeshPhongMaterial 高光纹理，可以进行光照的漫反射和镜面反射
    const earthMaterial = new THREE.MeshPhongMaterial({
      specular: 0x333333, // 反光
      shininess: 5, // 反光范围
      map: textureLoader.load('./earth_atmos_2048.jpg'), // 地球上的贴图
      specularMap: textureLoader.load( './earth_specular_2048.jpg' ),
      normalMap: textureLoader.load( './earth_normal_2048.jpg' ),
      normalScale: new THREE.Vector2( 0.85, 0.85 )
    })
    // 创建地球 THREE.Mesh 入参就是 几何体和 纹理
    const earthMesh = new THREE.Mesh(earthGeometry, earthMaterial)
    this.scene.add(earthMesh)

    // CSS2DObjct 继承 Object3D 所以就可以 通过 add方法 添加到场景中,这里就直接加入到 earthMesh中 后面就可以通过 
    // twoDRenderer 进行渲染， 因为这里面会有着 isCSS2DObject 来进行区分
    const earthLabel = new CSS2DObjct(document.createElement('div'), {
      marginTop: '-1em',
      color: '#fff',
    }, {
      className: 'label',
      textContent: 'Earth'
    })
    earthLabel.position.set( 0, 1, 0 );
    earthMesh.add(earthLabel)
  }

  /**
   * 初始化月球几何体
   */
  initMoonGeometry() {
    // 下面就基本和 地球是一致的
    const textureLoader = this.textureLoader
    const moonGeometry = new THREE.SphereGeometry(0.75, 16, 16)
    const moonMaterial = new THREE.MeshPhongMaterial({
      shininess: 5, // 反光范围
      map: textureLoader.load('./moon_1024.jpg'),
    })
    const moonMesh = this.moon = new THREE.Mesh(moonGeometry, moonMaterial)
    moonMesh.position.x = 3
    this.scene.add(moonMesh)

    const moonLabel = new CSS2DObjct(document.createElement('div'), {marginTop: '-1em',
    color: '#fff',}, {
      className: 'label',
      textContent: 'Moon'
    })
    moonLabel.position.set(0, 0.75, 0)
    moonMesh.add(moonLabel)
  }

  /**
   * 帧回调
   */
  loop() {
    const elapsed = this.clock.getElapsedTime();
    // 计算月球的位置
    this.moon.position.set( Math.sin( elapsed ) * 5, 0, Math.cos( elapsed ) * 5 );
    this.renderer.render(this.scene, this.camera)
    this.twoDRenderer.render(this.scene, this.camera)
    requestAnimationFrame(this.loop.bind(this))
  }
}

new EarthAndMoon()
```

> 简单的一个THREE 入门

