angular8 + threejs 实现太阳系3D动画

liulinsp 2020-01-09

坑!

当项目决定要做宇宙星空效果的时候,我百度了一波;发现了threejs;然后就从0开始学习了;一路上坑坑洼洼的;网上确实有相关文章,但没有相对于新手的;尤其是我这种现看文档现做的;而且很多代码都是使用原生js或者jquery的;

这次是我把threejs引入angular8做的一个太阳系效果;至于后面怎么交互的;后面再补;首先把效果做出来;先上图~~~静态图,实际效果是可以动的

angular8 + threejs 实现太阳系3D动画

首先安装;

// 安装threejs
npm i three -s // npm i  -s 按版本安装 
npm i @types/three -s

// 安装控制器(可以移动相机)
npm i three-orbitcontrols-ts  // 这里安装的是orbicontorls的一个替代工具,因为发现 npm i three-full 然后引入orbitcontrols 不好使,其他的也不好用,最后我百度很久发现用这个可以解决;

// 安装JavaScript性能监控器(这个酌情安装)
npm i stats.js -s

目录结构

angular8 + threejs 实现太阳系3D动画

// base.ts 这里放 three.js 的重构 代码

import {
  Scene,
  AmbientLight,
  PointLight,
  WebGLRenderer,
  PerspectiveCamera,
  GridHelper,
  Color,
  WebGLShadowMap
} from ‘three‘;
import { OrbitControls } from ‘three-orbitcontrols-ts‘;
import * as THREE from ‘three‘;
import * as Stats from ‘stats.js‘;
// import * as Stats from ‘stats.js‘;
// import * as TWEEN from ‘@tweenjs/tween.js‘;

// 渲染器
export const RENDERER = new WebGLRenderer({
  antialias: true,
  alpha: true
});
export function initRenderer(doc: any) {
  RENDERER.setSize(doc.clientWidth, doc.clientHeight);
  doc.appendChild(RENDERER.domElement);
}

// 相机
export let CAMERA: any;
export let CONTROLS: any;
export function initCamera(doc: any) {
  CAMERA = new THREE.PerspectiveCamera(20, doc.clientWidth / doc.clientHeight, 1, 1000);
  CAMERA.position.set(0, 0, 500);
  CONTROLS = new OrbitControls(CAMERA, RENDERER.domElement); // 控制镜头
}

// 初始化场景
export const SCENE = new Scene();
export function initScene() {}

// 灯光
export let LIGHT: any;
export function initLight() {
  LIGHT = new THREE.DirectionalLight(0xff0000, 1.0);
  LIGHT.position.set(100, 100, 200);
  SCENE.add(LIGHT);
}

// 物体
export let CUBE: any;
export let CENTERBALL: any;

export function initObject() {
  // 太阳材质
  const loader = new THREE.TextureLoader();

  const sunTexture = loader.load(‘./assets/imgs/img-three/sun_bg.jpg‘);
  // const sunTexture = THREE.ImageUtils.loadTexture(‘./assets/imgs/img-three/sun_bg.jpg‘, null, () => {
  //   RENDERER.render(SCENE, CAMERA);
  // });
  // 设定太阳
  CENTERBALL = new THREE.Mesh(
    new THREE.SphereGeometry(30, 30, 30),
    new THREE.MeshBasicMaterial({
      map: sunTexture
    })
  );
  // 添加太阳发光效果

  const centerBallLite = new THREE.Sprite(
    new THREE.SpriteMaterial({
      map: new THREE.CanvasTexture(generateSprite(‘253,111,7‘)),
      blending: THREE.AdditiveBlending
    })
  );
  centerBallLite.scale.x = centerBallLite.scale.y = centerBallLite.scale.z = 90;
  // 添加太阳到场景
  SCENE.add(centerBallLite);
  SCENE.add(CENTERBALL);
  // 添加各个行星
  // 添加水星
  STARLITES.push(
    initSatellite(
      2,
      34,
      { x: -Math.PI * 0.42, y: Math.PI * 0.02, z: 0 },
      0.02,
      ‘./assets/imgs/img-three/mercury_bg.png‘,
      SCENE
    )
  );
  // 添加金星
  STARLITES.push(
    initSatellite(
      3,
      38,
      { x: -Math.PI * 0.42, y: Math.PI * 0.02, z: 0 },
      0.018,
      ‘./assets/imgs/img-three/venus_bg.png‘,
      SCENE
    )
  );
  // 添加地球
  STARLITES.push(
    initSatellite(
      3.2,
      42.2,
      { x: -Math.PI * 0.42, y: Math.PI * 0.02, z: 0 },
      0.016,
      ‘./assets/imgs/img-three/earth_bg.png‘,
      SCENE
    )
  );
  // 添加火星
  STARLITES.push(
    initSatellite(
      2.5,
      47.1,
      { x: -Math.PI * 0.42, y: Math.PI * 0.02, z: 0 },
      0.014,
      ‘./assets/imgs/img-three/spark_bg.png‘,
      SCENE
    )
  );
  // 添加木星
  STARLITES.push(
    initSatellite(
      35,
      71,
      { x: -Math.PI * 0.42, y: Math.PI * 0.02, z: 0 },
      0.012,
      ‘./assets/imgs/img-three/jupiter_bg.png‘,
      SCENE
    )
  );
  // 添加土星
  STARLITES.push(
    initSatellite(
      45,
      110,
      { x: -Math.PI * 0.42, y: Math.PI * 0.02, z: 0 },
      0.01,
      ‘./assets/imgs/img-three/saturn_bg.png‘,
      SCENE
    )
  );
  // 添加天王星
  STARLITES.push(
    initSatellite(
      17,
      158,
      { x: -Math.PI * 0.42, y: Math.PI * 0.02, z: 0 },
      0.008,
      ‘./assets/imgs/img-three/uranus_bg.png‘,
      SCENE
    )
  );
  // 添加海王星
  STARLITES.push(
    initSatellite(
      15,
      188,
      { x: -Math.PI * 0.42, y: Math.PI * 0.02, z: 0 },
      0.006,
      ‘./assets/imgs/img-three/neptune_bg.png‘,
      SCENE
    )
  );
}

//  动画混合器组(把模型的动画混合器都push到这里面,在canvas.ts里面更新动画   )
export const MIXER = [];

//  性能检测
export const STATS = new Stats();
export function initStats(doc) {
  STATS.setMode(0);
  STATS.domElement.style.position = ‘absolute‘;
  STATS.domElement.left = ‘0px‘;
  STATS.domElement.top = ‘0px‘;
  doc.appendChild(STATS.domElement);
}
//  网格
export function initGrid() {
  const gridHelper = new GridHelper(100, 50);
  SCENE.add(gridHelper);
}

// * 返回行星轨道的组合体
// * @param starLiteSize 行星的大小
// * @param starLiteRadius 行星的旋转半径
// * @param rotation 行星组合体的x,y,z三个方向的旋转角度
// * @param speed 行星运动速度
// * @param imgUrl 行星的贴图
// * @param scene 场景
// * @returns {{satellite: THREE.Mesh, speed: *}} 卫星组合对象;速度
export let STARLITES = [];
export function initSatellite(
  starLiteSize: any,
  starLiteRadius: any,
  rotation: any,
  speed: any,
  imgUrl: any,
  scene: any
) {
  const ring = new THREE.RingGeometry(starLiteRadius, starLiteRadius + 0.05, 50, 1);
  const baseMaterial = new THREE.MeshBasicMaterial();
  const track = new THREE.Mesh(ring, baseMaterial);

  const centerMesh = new THREE.Mesh(new THREE.SphereGeometry(1, 1, 1), new THREE.MeshLambertMaterial()); // 材质设定
  const starLite = new THREE.Sprite(
    new THREE.SpriteMaterial({
      map: THREE.ImageUtils.loadTexture(imgUrl)
    })
  );
  starLite.scale.x = starLite.scale.y = starLite.scale.z = starLiteSize;
  starLite.position.set(starLiteRadius, 0, 0);

  const pivotPoint = new THREE.Object3D();
  pivotPoint.add(starLite);
  pivotPoint.add(track);
  centerMesh.add(pivotPoint);
  centerMesh.rotation.set(rotation.x, rotation.y, rotation.z);
  scene.add(centerMesh);
  return { starLite: centerMesh, speed };
}

// 实现球体发光
// @param color 颜色的r,g和b值,比如:“123,123,123”;
// @returns {Element} 返回canvas对象
export function generateSprite(color: any) {
  const canvas = document.createElement(‘canvas‘);
  canvas.width = 16;
  canvas.height = 16;
  const context = canvas.getContext(‘2d‘);
  const gradient = context.createRadialGradient(
    canvas.width / 2,
    canvas.height / 2,
    0,
    canvas.width / 2,
    canvas.height / 2,
    canvas.width / 2
  );
  gradient.addColorStop(0, ‘rgba(‘ + color + ‘,1)‘);
  gradient.addColorStop(0.2, ‘rgba(‘ + color + ‘,1)‘);
  gradient.addColorStop(0.4, ‘rgba(‘ + color + ‘,.6)‘);
  gradient.addColorStop(1, ‘rgba(0,0,0,0)‘);
  context.fillStyle = gradient;
  context.fillRect(0, 0, canvas.width, canvas.height);
  return canvas;
}

// 动画渲染
export function render() {
  RENDERER.render(SCENE, CAMERA);
  CENTERBALL.rotation.y -= 0.01;
  for (let i = 0; i < STARLITES.length; i++) {
    STARLITES[i].starLite.rotation.z -= STARLITES[i].speed;
  }
  CONTROLS.update();
  STATS.update();
  requestAnimationFrame(render);
}

// 窗口resize事件
export function resizeRender(doc: any) {
  // 重新初始化尺寸
  CAMERA.aspect = doc.clientWidth / doc.clientHeight;
  CAMERA.updateProjectionMatrix();
  RENDERER.setSize(doc.clientWidth, doc.clientHeight);
}

html: 

<div class="three-container">
  <div id="starry-frame" #starry></div>
</div>

css:

.three-container {
  width: 100%;
  height: 100%;
  background: #000 url(src/assets/imgs/img-three/starry_sky_bg.jpg) no-repeat center center;
  overflow: hidden;
}
#starry-frame {
  width: 100%;
  min-height: 800px; // 这里写了最小高度,因为发现使用100%,没有办法出现效果;
  position: relative;
}

ts

import { Component, OnInit, ViewChild, ElementRef, HostListener } from ‘@angular/core‘;
import { initRenderer, initCamera, initScene, initObject, initStats, render, resizeRender } from ‘./base‘;

@Component({
  selector: ‘app-monitor-new‘,
  templateUrl: ‘./monitor-new.component.html‘,
  styleUrls: [‘./monitor-new.component.scss‘]
})
export class MonitorNewComponent implements OnInit {
  @ViewChild(‘starry‘, { static: true }) starry: ElementRef;
  constructor() {}

  ngOnInit() {
    this.init();
  }
  init() {
    initRenderer(this.starry.nativeElement);
    initCamera(this.starry.nativeElement);
    initScene();
    initObject();
    initStats(this.starry.nativeElement);
    render();
  }
  // 监听当前窗口大小
  @HostListener(‘window:resize‘, [‘$event‘])
  onResize(event: any) {
    resizeRender(this.starry.nativeElement);
  }
}

代码出来了;这里放几个相对于新手友好的几个入门学习网站,我就是看这些学习的:注意以下网站有免费资料和收费资料

http://www.yanhuangxueyuan.com/

http://www.hewebgl.com/   

当时也参考了这位博主的一些;当然我没有运行他的例子,因为我在他的oribcontrols引入就卡住了;所以他这里也有坑;

https://www.jianshu.com/p/c359480b0e08

至于太阳系的贴图image;百度可以找到,或者参考这位博主的;我就是看他的文章然后修改了一些;

https://blog.csdn.net/baby_duoduo/article/details/100423785

相关推荐