Three.js基础功能学习十三:太阳系实例上

使用实际的尺寸作为各个元素的大小及坐标,使用随机生成的方式生成星空背景及各个星空的颜色,使用指定的几何图形生成星河/各个星系/黑洞/恒星等。

源码下载地址: 点击下载
效果演示:

三维-太阳系













一、学习视频

Three.js太阳系实例:基础搭建

二、项目创建

使用vue3搭建项目框架,引入three.js实现三维效果。

2.1 项目创建

官网: https://cn.vuejs.org/guide/introduction
命令: npm create vue@latest

2.2 三维引入

官网: https://threejs.org/
命令: npm install three

三、项目结构

项目结构如下:

四、三维基础框架

4.1 基础框架

  1. 主页
    main.ts
javascript 复制代码
import { createApp } from 'vue'

import './assets/main.css'

import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
// 如果您正在使用CDN引入,请删除下面一行。
import * as ElementPlusIconsVue from '@element-plus/icons-vue'

import { createPinia } from 'pinia'

import App from './App.vue'
import router from './router'

const app = createApp(App)

app.use(createPinia())
app.use(ElementPlus)
app.use(router)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component)
}

app.mount('#app')

App.vue

javascript 复制代码
<script setup lang="ts">
import { RouterView } from 'vue-router'
</script>

<template>
  <RouterView />
</template>

<style scoped>

</style>

HomeView.vue

javascript 复制代码
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue'
import  INSTANCE from "./assets"
import { config } from '@/utils';
import { useStateStore } from '@/stores/state';

//渲染的根元素
const rootEle = ref()

//当前的视角
const state = useStateStore();

//初始化场景
const init = () => {
  INSTANCE.init(rootEle.value);
}
const toPosition= (index:number)=>{
  INSTANCE.toPosition(config("views")[index]?.position,config("views")[index]?.lookAt);
  state.currentObject = index;
}
//宇宙漫游
const animate= ()=>{

}
//获取当前的位置
const position = ()=>{
    console.log('camera',INSTANCE.camera.getCamera().position)
    console.log('controls',INSTANCE.controls.getControls().target)
}
//是否开启公转
const revolution = ()=>{
    state.revolution = ! state.revolution;
}
//是否开启自转
const autobiography = ()=>{
  state.autobiography = ! state.autobiography;
}
//是否开启跟随效果
const follow = ()=>{
  state.follow = ! state.follow;
}

onMounted(() => {
  init()
})

onBeforeUnmount(()=>{
  INSTANCE.dispose();
})
</script>
<template>
  <div id="sun" ref="rootEle"></div>
  <div class="btns" >
    <div class="btns-inner">
      <el-button-group >
        <el-button @click="toPosition(0)" round :class="[state.currentObject == 0?'selected':'']">宇宙</el-button>
        <el-button @click="toPosition(1)"  :class="[state.currentObject == 1?'selected':'']">银河系</el-button>
        <el-button @click="toPosition(2)" :class="[state.currentObject == 2?'selected':'']">太阳系</el-button>
        <el-button @click="toPosition(3)" :class="[state.currentObject == 3?'selected':'']">太阳</el-button>
        <el-button @click="toPosition(4)" :class="[state.currentObject == 4?'selected':'']">水星</el-button>
        <el-button @click="toPosition(5)" :class="[state.currentObject == 5?'selected':'']">金星</el-button>
        <el-button @click="toPosition(6)" :class="[state.currentObject == 6?'selected':'']">地球</el-button>
        <el-button @click="toPosition(7)" :class="[state.currentObject == 7?'selected':'']">月亮</el-button>
        <el-button @click="toPosition(8)" :class="[state.currentObject == 8?'selected':'']">火星</el-button>
        <el-button @click="toPosition(9)" :class="[state.currentObject == 9?'selected':'']">木星</el-button>
        <el-button @click="toPosition(10)" :class="[state.currentObject == 10?'selected':'']">土星</el-button>
        <el-button @click="toPosition(11)" :class="[state.currentObject == 11?'selected':'']">天王星</el-button>
        <el-button @click="toPosition(12)" round :class="[state.currentObject == 12?'selected':'']">海王星</el-button>
      </el-button-group>

    </div>
  </div>
  <div class="btn-tools">
    <div class="btns-inner">
      <el-button-group direction="vertical">
        <el-button @click="animate" round disabled>漫游</el-button>
        <el-button @click="position"  >定位</el-button>
        <el-button @click="autobiography" :type="state.autobiography?'danger':''" >自转</el-button>
        <el-button @click="revolution" :type="state.revolution?'danger':''">公转</el-button>
        <el-button @click="follow" round :type="state.follow?'danger':''">跟随</el-button>
      </el-button-group>
    </div>
  </div>
</template>
<style scoped>
#sun {
  width: 100vw;
  height: 100vh;
}
.btns{
  position: fixed;
  bottom: 20px;
  left: 0;
  width:100%;
}
.btns .btns-inner{
  margin: 0 auto;
  text-align: center;
}
.btns .btns-inner .el-button{
  --el-button-border-color:#ffffff22;
  --el-button-bg-color:#ffffff33;
  --el-button-text-color:#ffffff;
  --el-button-hover-bg-color:rgba(51, 125, 204, 0.404);
  --el-button-hover-border-color:rgba(51, 125, 204, 0.504);
}
.btns .btns-inner :deep(.el-button span){
  color:#fff
}
.btns .btns-inner .el-button.selected{
  --el-button-border-color:rgba(51, 125, 204, 0.404);
  --el-button-bg-color:rgba(51, 125, 204, 0.504);
  --el-button-text-color:#ffffff;
}


.btn-tools{
  position: fixed;
  top: calc(50vh - 200px);
  right: 20px;
  height:100%;
}
.btn-tools .btns-inner{
  height:200px;
  margin: 0 auto;
  text-align: center;
}
.btn-tools .btns-inner .el-button-group{
  height: 100%;
}
.btn-tools .btns-inner .el-button{
  flex: 1;
  width:50px;
  --el-button-border-color:#ffffff22;
  --el-button-bg-color:#ffffff33;
  --el-button-text-color:#ffffff;
  --el-button-hover-bg-color:rgba(51, 125, 204, 0.404);
  --el-button-hover-border-color:rgba(51, 125, 204, 0.504);
}
.btn-tools .btns-inner :deep(.el-button span){
  color:#fff
}
.btn-tools .btns-inner .el-button.selected{
  --el-button-border-color:rgba(51, 125, 204, 0.404);
  --el-button-bg-color:rgba(204, 51, 51, 0.504);
  --el-button-text-color:#ffffff;
}
.btn-tools .btns-inner .el-button--danger{
  --el-button-border-color:#c40000c5;
  --el-button-bg-color:#c40000c5;
  --el-button-text-color:#ffffff;
  --el-button-hover-bg-color:rgba(204, 51, 51, 0.404);
  --el-button-hover-border-color:rgba(212, 25, 25, 0.504);
}
.btn-tools .btns-inner .is-disabled{
  --el-button-disabled-bg-color:rgba(51, 125, 204, 0.404);
  --el-button-disabled-border-color:rgba(204, 51, 51, 0.504);
  --el-button-disabled-text-color:#ffffff;
}
</style>
  1. 路由
javascript 复制代码
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView,
    },
  ],
})

export default router
  1. Pinia
    state.ts
javascript 复制代码
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'

/**
 * 实时状态的定义
 */
export const useStateStore = defineStore('state', () => {
    //当前展示的视角信息
    const currentObject = ref(0);

    //自转
    const autobiography = ref(true);

    //公转
    const revolution = ref(false);

    //跟随
    const follow = ref(false);

    return { currentObject ,autobiography,revolution,follow}
})

config.ts

javascript 复制代码
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
import * as THREE from 'three'

/**
 * 基础信息的定义
 */
export const useConfigStore = defineStore('config', () => {

  //宇宙的半径10^26
  const maxSize =  4.4 * 1000000000000000
                        //  9007199254740991
  //缩放的倍数00000000000
  const scalePer = 100000000000
  
  //太阳的半径
  const sun = {
    radius:696300*1000/ 2 / scalePer,
  }

  //星空的背景
  const bgcolor= ref('#000000');

  //星星数量
  const starCount  = 10000;

  //恒星的最大半径
  const maxStarRadius = 1.1 * 1000000000000;

  //星系的半径
  const maxGalsxyRadius =  9.461 * 100000000000000;

  //黑洞的半径
  const maxBlackHoleRadius = 696300*1000/ 2 / scalePer;

  //各个行星的速度
  let config = {
    mercury: {//水星直径约4879公里,质量是地球的0.055倍,密度5.427克/立方厘米。水星‌:约5791万千米(0.38天文单位)
      name:'水星',
      x:57910000*10/scalePer,
      radius:4879*1000/2/scalePer,
      autobiography: 3.0 * 1000,//自转
      revolution: 47.9 * 1000,//公转
    },
    venus: {//金星直径约12104公里,质量是地球的0.815倍,密度5.24克/立方厘米。金星‌:约1.082亿千米(0.72天文单位)
      name:'金星',
      x:1.082*100000000*10/scalePer,
      radius:12104*1000/2/scalePer,
      autobiography: 1.8 * 1000,//自转
      revolution: 35 * 1000,//公转
    },
    mars: {//火星直径约6779公里,质量是地球的0.1074倍,密度3.94克/立方厘米。‌火星‌:约2.28亿千米(1.52天文单位)
      name:'火星',
      x:2.28*100000000*10/scalePer,
      radius:6779*1000/2/scalePer,
      autobiography: 0.24 * 1000,//自转
      revolution: 24.1 * 1000,//公转
    },
    earth: {//地球‌:直径约12742公里,质量1,密度5.52克/立方厘米。‌地球‌:约1.496亿千米(1天文单位)
      name:'地球',
      x:1.496*100000000*10/scalePer,
      radius:12742*1000/2/scalePer,
      autobiography: 0.465 * 1000,//自转
      revolution: 29.8 * 1000,//公转
    },
    jupiter: {//木星直径约142984公里,质量是地球的317.94倍,密度1.33克/立方厘米。木星‌:约7.78亿千米(5.2天文单位)
      name:'木星',
      x:7.78*100000000*10/scalePer,
      radius:142984*1000/2/scalePer,
      autobiography: 7.66 * 1000,//自转
      revolution: 9.7 * 1000,//公转
    },
    saturn: {//土星直径约120536公里,质量是地球的95.18倍,平均密度仅0.7克/立方厘米,是太阳系中唯一密度小于水的行星。‌土星‌:约14.29亿千米(9.54天文单位)
      name:'土星',
      x:14.29*100000000*10/scalePer,
      radius:120536*1000/2/scalePer,
      autobiography: 6.3 * 1000,//自转
      revolution: 9.7 * 1000,//公转
    },
    uranus: {//天王星直径约51118公里,质量是地球的14.63倍,密度1.24克/立方厘米。天王星‌:约28.71亿千米(19.18天文单位)
      name:'天王星',
      x:28.71*100000000*10/scalePer,
      radius:51118*1000/2/scalePer,
      autobiography: 2.621 * 1000,//自转
      revolution: 6.8 * 1000,//公转
    },
    neptune: {//海王星直径约49528公里,质量是地球的17.22倍,密度1.66克/立方厘米。海王星‌:约45.04亿千米(30.06天文单位)
      name:'海王星',
      x:45.04*100000000*10/scalePer,
      radius:49528*1000/2/scalePer,
      autobiography: 2.707 * 1000,//自转
      revolution: 5.4 * 1000,//公转
    },
  }

  //各个预定义的视角信息
  const views = [{
    position:new THREE.Vector3(0,maxSize * 1.2, maxSize * 1.8),
    lookAt:new THREE.Vector3(0,0, 10)
  },{
    position:new THREE.Vector3(-1000,sun.radius,sun.radius),
    lookAt:new THREE.Vector3(0,0,10)
  },{
    position:new THREE.Vector3(0.034732902744777805,0.3449531271902603,0.26654467884112715),
    lookAt:new THREE.Vector3(0.033685450201036304,-0.04967007306636794,0.06401762073895154)
  },{
    position:new THREE.Vector3(0.0031956114242372644,  0.002872176193311527, 0.007562885263941745),
    lookAt:new THREE.Vector3(0.0015854808622436782,  0.0005706336541266402,  0.0036135992057809573)
  },{
    position:new THREE.Vector3(0.006045906253202582, 0.00008003008369889398, 0.0002988954675529693),
    lookAt:new THREE.Vector3(0.006041302768695112, 0.00007825938227635868,  0.0002941797829009809)
  },{
    position:new THREE.Vector3(0.01084051636669485,  0.0001451239288667867,  0.0004403390861581306),
    lookAt:new THREE.Vector3(0.010831524374438522,  0.00008848922367198572,  0.0003118395857784389)
  },{
    position:new THREE.Vector3(0.014957403019679419,  0.000007337744173961887,  0.00016550172868891148),
    lookAt:new THREE.Vector3(0.01495762410132193, 0.0000029601810377091256,  0.00012868069184761798)
  },{
    position:new THREE.Vector3(0.015584094940632082,  0.000015600820697942422, 0.00004681596071252129),
    lookAt:new THREE.Vector3(0.015568968923486144,  0.000006820501494518071,  0.00002771980840764359)
  },{
    position:new THREE.Vector3( 0.02279670988844571, 0.00003522113585802835, 0.00008276729868475193),
    lookAt:new THREE.Vector3(0.022762587282826,  -0.0001796949422104101,  -0.00040485965302916013)
  },{
    position:new THREE.Vector3(0.07787499299522975,  0.0010636644906135365,  0.004081489027364495),
    lookAt:new THREE.Vector3(0.07785935011131331,  -0.00452272528354329,  -0.013440072354970871)
  },{
    position:new THREE.Vector3(0.14291450582676493, 0.001513438251508653,  0.0020417705170365083),
    lookAt:new THREE.Vector3(0.14267836092902175,  -0.004475123933605497,  -0.0046303752197579635)
  },{
    position:new THREE.Vector3(0.2872005905163468,  0.00015843950020901073, 0.00062313147047773),
    lookAt:new THREE.Vector3(0.2868239336951548, -0.0009440884184587143,  -0.0023734645190837756)
  },{
    position:new THREE.Vector3(0.45045140559862284, 0.00017459925595258158,  0.0007134956105158232),
    lookAt:new THREE.Vector3(0.45042484067928384, -0.00015143382341868935,  -0.0002189835599877074)
  }];

  return { maxSize,sun,config ,bgcolor,starCount,maxStarRadius,maxGalsxyRadius,maxBlackHoleRadius,views}
})

4.2 三维实现

  1. 基础场景
    index.ts
javascript 复制代码
import * as THREE from 'three'
import { useStateStore } from '@/stores/state';

import animate from "./animate"
import * as renderer from "./renderer"
import * as camera from "./camera"
import * as controls from "./controls"
import * as light from "./light"
import * as scene from "./scene"

//宇宙
import * as universe from "./universe"

//太阳系
import * as solar from "./solarSystem"
import { config } from '@/utils'
import { nextTick } from 'vue';

//初始化
export const init = (rootEle:Element)=>{
    renderer.init(rootEle);//初始化渲染器

    camera.init();//初始化相机

    scene.init();//初始化场景

    light.init();//初始化灯光

    universe.init();//初始化宇宙背景,包括星系

    solar.init();//初始化太阳系

    controls.init();//初始化控制器

    renderer.renderer.render(scene.scene, camera.camera)//渲染目标

    //坐标轴的辅助工具
    const axesHelper = new THREE.AxesHelper( config("maxSize") );
    scene.add( axesHelper );

    //启动动画
    requestAnimationFrame(animate)

    //当前的视角
    nextTick(()=>{
        const state = useStateStore();
        camera.toPosition(config("views")[state.currentObject].position,config("views")[state.currentObject].lookAt);

        //摄像机的辅助工具
        // const helper = new THREE.CameraHelper( camera.getCamera() );
        // scene.add( helper );
    })
}

//销毁
export const dispose = ()=>{
    solar.dispose();

    universe.dispose();

    light.dispose();

    controls.dispose();
    
    camera.dispose();

    renderer.dispose();
}

//转到对应的位置,摄像机和视角
export const toPosition=  (position:THREE.Vector3,lookAt:THREE.Vector3)=>{
    camera.toPosition(position,lookAt);
    // controls.lookAt(lookAt);
}

export default {
    renderer,
    animate,
    camera,
    controls,
    light,
    scene,
    init,
    dispose,toPosition
};

renderer.ts

javascript 复制代码
import * as THREE from 'three'

//全局的渲染器
export const renderer: THREE.WebGLRenderer = new THREE.WebGLRenderer({ antialias: true, logarithmicDepthBuffer: true })

//销毁
export const dispose = ()=>{
    if(renderer){
       renderer.dispose()
    }
}
export const init = (rootEle:Element)=>{
renderer.setPixelRatio(window.devicePixelRatio)
    renderer.setSize(window.innerWidth, window.innerHeight)
    rootEle.appendChild(renderer.domElement)
}
/**
 * 初始化渲染器
 */
export default {
    renderer,init,dispose
}

light.ts

javascript 复制代码
import * as THREE from 'three'
import  INSTANCE from "./index"

//全局的渲染器
export let lights: THREE.PointLight[] = [];

//销毁
export const dispose = ()=>{
    if(lights){
        lights.forEach(item=>item.dispose())
    }
}
export const init = ()=>{
    // const ambientLight = new THREE.AmbientLight(0xffffff)
    // lights.push(ambientLight)
    // INSTANCE.scene.add(ambientLight);

    let pointLight = new THREE.PointLight('#ffffff', 1, 0, 0)
    pointLight.position.set(0, 0, 0)
    lights.push(pointLight)
    INSTANCE.scene.add(pointLight);
}
/**
 * 初始化渲染器
 */
export default {
        lights,init,dispose
}   

scene.ts

javascript 复制代码
import { config } from '@/utils'
import * as THREE from 'three'

//场景
export const scene: THREE.Scene = new THREE.Scene()

export const init = () => {
    scene.background = new THREE.Color(config('bgcolor'))
}
export const add = (mesh:THREE.Object3D) => {
    scene.add(mesh);
}
/**
 * 初始化
 */
export default {
     init, scene,add
}

camera.ts

javascript 复制代码
import { config, disposeObject } from '@/utils';
import * as THREE from 'three'
import  controls, { lookAt } from './controls';
import * as TWEEN from 'three/examples/jsm/libs/tween.module';

//动画的周期
let animationDuration = 2000;
let easingFunction = TWEEN.Easing.Quadratic.Out;

//标记是否在动画当中
let animateing = false;

//摄像机
export let camera: THREE.PerspectiveCamera

//销毁
export const dispose = () => {
    if (camera) {
        disposeObject(camera);
    }
}

//动画
export const animate = (time:number)=>{
    // 更新Tween动画
    TWEEN.update();

    // console.log('camera',camera.position);
}

//初始化
export const init = () => {
    camera = new THREE.PerspectiveCamera(
        75,
        window.innerWidth / window.innerHeight,
        0.00000000000001,
        config('maxSize') * 2,
    )
    // camera.zoom = config('maxSize')*2
    // camera.position.set(0, config('maxSize') * 1.2, config('maxSize') * 1.8)//宇宙
    // camera.position.set(0.034732902744777805,0.3449531271902603,0.26654467884112715);//太阳系
    // camera.position.set(0.014957403019679419,  0.000007337744173961887,  0.0001655017286889114);//地球
    // camera.position.set(0.006045906253202582, 0.00008003008369889398, 0.0002988954675529693);//水星
    // camera.position.set(0.01084051636669485,  0.0001451239288667867,  0.0004403390861581306);//金星
    // camera.position.set(0.02279670988844571, 0.00003522113585802835, 0.00008276729868475193);//火星
    // camera.position.set(0.07787499299522975,  0.0010636644906135365,  0.004081489027364495);//木星
    // camera.position.set(0.14291450582676493, 0.001513438251508653,  0.0020417705170365083);//土星
    // camera.position.set(0.2872005905163468,  0.00015843950020901073, 0.00062313147047773);//天王星
    // camera.position.set(0.45045140559862284, 0.00017459925595258158,  0.0007134956105158232);//海王星
}

//转到指定的视角
export const toPosition = (targetPosition:THREE.Vector3,targetLookAt:THREE.Vector3)=>{
    animateing = true;
    // 停止当前所有动画
    TWEEN.removeAll();

    // 创建位置动画
    new TWEEN.Tween(camera.position)
        .to(targetPosition, animationDuration)
        .easing(easingFunction)
        .onComplete(function() {
           camera.position.set(targetPosition.x, targetPosition.y, targetPosition.z);
        //    lookAt(targetLookAt);
        })
        .start();
    new TWEEN.Tween(controls.getControls().target)
        .to(targetLookAt, animationDuration)
        .easing(easingFunction)
        .onComplete(function() {
           lookAt(targetLookAt);
            animateing = false;
        })
        .start();
}

//转到指定的视角,不启动动画效果
export const toPositionNoAnimate= (targetPosition:THREE.Vector3,targetLookAt:THREE.Vector3)=>{
    if(animateing){
        return;
    }
    camera.position.set(targetPosition.x, targetPosition.y, targetPosition.z);
    // lookAt(targetLookAt);
}
export const getCamera = ()=>{
    return camera;
}
//初始化
export default {
    camera:getCamera, init, dispose,animate,toPosition,getCamera,toPositionNoAnimate
}

control.ts

javascript 复制代码
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { renderer } from "./renderer"
import { camera } from "./camera"
//控制器
export let controls: OrbitControls

//销毁
export const dispose = () => {
    if (controls) {
        controls.dispose();
    }
}

//动画
export const animate = (time:number)=>{
   controls.update()
//    console.log('controls',controls.target);
}

export const init = () => {
    controls = new OrbitControls(camera, renderer.domElement)
    controls.enableDamping = true; // 启用阻尼效果
    controls.minDistance = -9000000000000000; // 将相机放大多少
    // controls.panSpeed = 0.1;
    // controls.rotateSpeed = 0.1;
    // controls.zoomSpeed = 0.1;

    // controls.target.copy(new THREE.Vector3(0.01495762410132193, 0.0000029601810377091256,  0.00012868069184761798))//地球的视角
    // controls.target.copy(new THREE.Vector3(0.006041302768695112, 0.00007825938227635868,  0.0002941797829009809))//水星的视角
    // controls.target.copy(new THREE.Vector3(0.010831524374438522,  0.00008848922367198572,  0.0003118395857784389))//金星的视角
    // controls.target.copy(new THREE.Vector3(0.022762587282826,  -0.0001796949422104101,  -0.00040485965302916013))//火星的视角
    // controls.target.copy(new THREE.Vector3(0.07785935011131331,  -0.00452272528354329,  -0.013440072354970871))//木星的视角
    // controls.target.copy(new THREE.Vector3(0.14267836092902175,  -0.004475123933605497,  -0.0046303752197579635))//土星的视角
    // controls.target.copy(new THREE.Vector3(0.2868239336951548, -0.0009440884184587143,  -0.0023734645190837756))//天王星的视角
    // controls.target.copy(new THREE.Vector3(0.45042484067928384, -0.00015143382341868935,  -0.0002189835599877074))//海王星的视角
    
    controls.update()
}

export const getControls = ()=>{
    return controls;
}

//转到指定的视角
export const lookAt = (lookAt:THREE.Vector3)=>{
    // 创建目标点动画
    controls.target.copy(lookAt);
}

export default {
    controls:getControls(), init, dispose,animate,getControls
}

anmine.ts

javascript 复制代码
import { config, resizeRendererToDisplaySize } from '@/utils'
import * as THREE from 'three'
import * as solar from "./solarSystem"
import * as universe from "./universe"
import controls from "./controls"
import {renderer} from "./renderer"
import camera from "./camera"
import {scene} from "./scene"
// let clock = new THREE.Clock();
//初始化
const animate = (time: number) => {
    // const delta = clock.getDelta();
    // const time = clock.getElapsedTime();
    
    if(camera.camera()){
        if (resizeRendererToDisplaySize(renderer)) {
            const canvas = renderer.domElement
            camera.camera().aspect = canvas.clientWidth / canvas.clientHeight
            camera.camera().updateProjectionMatrix()
        }
        universe.animate(time);

        solar.animate(time); 

        camera.animate(time)

        controls.animate(time)

        renderer.render(scene, camera.camera())
    }
    requestAnimationFrame(animate)
}

export default animate;
相关推荐
bylander2 小时前
【AI学习】TM Forum《Autonomous Networks Implementation Guide》快速理解
人工智能·学习·智能体·自动驾驶网络
xxxmine2 小时前
redis学习
数据库·redis·学习
xiaoqi9223 小时前
React Native鸿蒙跨平台如何实现分类页面组件通过searchQuery状态变量管理搜索输入,实现了分类的实时过滤功能
javascript·react native·react.js·ecmascript·harmonyos
打小就很皮...3 小时前
Tesseract.js OCR 中文识别
前端·react.js·ocr
qq_177767373 小时前
React Native鸿蒙跨平台实现应用介绍页,实现了应用信息卡片展示、特色功能网格布局、权限/联系信息陈列、评分展示、模态框详情交互等通用场景
javascript·react native·react.js·ecmascript·交互·harmonyos
2603_949462103 小时前
Flutter for OpenHarmony社团管理App实战:预算管理实现
android·javascript·flutter
wuhen_n3 小时前
JavaScript内存管理与执行上下文
前端·javascript
Yff_world3 小时前
网络通信模型
学习·网络安全
Hi_kenyon3 小时前
理解vue中的ref
前端·javascript·vue.js