教你如何用Three.js创造一个三维太阳系

前言

笔者认为Three.js是一个伟大的框架,为什么这样说,因为它可以让我们轻易创造三维世界,甚至好像笔者写这遍教程,可以创造一个太阳系,在这个三维世界里你就是创世主。哈哈!好像说得有点夸!!

三维太阳系完整效果

了解一些基本天文知识

学习创造这个三维太阳系之前先了解一下基本的天文知识:太阳系有“八大行星”,按照离太阳的距离从近到远,它们依次为水星、金星、地球、火星、木星、土星、天王星、海王星。八大行星自转方向多数也和公转方向一致。只有金星和天王星两个例外。金星自转方向与公转方向相反。而天王星则是在轨道上“横滚”的。例如地球自转一天是23.9小时,公转一年有365.2天 ,而相邻的火星自转一天是24.6小时 公转一年则有687天,其他行星也有不同的公转和自转信息,有了这些信息就可以定义一些基本规则

image.png

了解Three框架

Three的一些基本概念我在用最简单方式打造Three.js 3D汽车展示厅一文也粗略介绍一下,为了让同学们加深理解,笔者就相对于太阳系来比如一下

  1. 场景 Sence 相当于太阳系,宇宙中有无数星系,比如现在说的太阳系,后续还可以增加其他星系,那不是永远都加不完的呀 o(╥﹏╥)o
  2. 相机 Carma 相当一枚哈勃天文望远镜
  3. 几何体 Geometry 相当于太阳和八大行星
  4. 控制 Controls 相当”创世者“的你

有了这几个概念我们就创建一些函数一一对应

完整效果

屏幕录制2021-07-12 下午2.34.26.gif

进入教程

先引入Three 要用到的对象

  import {
        Group,
        Mesh,
        MeshBasicMaterial,
        PerspectiveCamera,
        PointCloud,
        PointCloudMaterial,
        Scene,
        SphereGeometry,
        TextureLoader,
        Vector3,
        WebGLRenderer
    } from 'three'
     import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js'

场景(太阳系),相机(哈勃天文望远镜),控制(创世主)

//场景
const setScene = () => {
        scene = new Scene()
        renderer = new WebGLRenderer({
            antialias: true,
        })
        renderer.setSize(innerWidth, innerHeight)
        document.querySelector('#planet').appendChild(renderer.domElement)
    }
//相机
const setCamera = () => {
        camera = new PerspectiveCamera(60, innerWidth / innerHeight, 1, 100000)
        camera.position.set(0, 500, 2000)
        camera.lookAt(scene.position)
    }
//控制    
 const setControls = () => {
        controls = new OrbitControls(camera, renderer.domElement)
    }        

创建一个太阳系的背景(繁星背景)

这个满天星效果是太阳系的背景,运用到Three的粒子系统,行星密度可自行调整

 const starForge = () => {
        const starQty = 10000
        const geometry = new SphereGeometry(10000, 100, 50)
        const materialOptions = {}
        const starStuff = new PointCloudMaterial(materialOptions)
        geometry.vertices = []
        for (let i = 0; i < starQty; i++) {
            let starVertex = new Vector3()
            starVertex.x = Math.random() * 20000 - 10000
            starVertex.y = Math.random() * 20000 - 10000
            starVertex.z = Math.random() * 20000 - 10000
            geometry.vertices.push(starVertex)
        }
        const stars = new PointCloud(geometry, starStuff)
        scene.add(stars)
    }

效果如下图:

image.png

创建太阳和行星前先说说行星自转同时公转的规律

屏幕录制2021-07-12 上午11.23.20.gif

旋转方式:实现旋转功能有三种方式

  1. 旋转照相机
  2. 旋转整个场景(Scene)
  3. 旋转单个元素

因为我们这里每个行星的自转速度,公转速度都不一样。所以设置整体旋转并不可行,所以要给每个元素设置不同的旋转属性。

行星需要让它们围绕着太阳转,就要先给它们自身设置一个位置偏移。以水星为例:mercury.position.x -= 300,而此时设置mercury.rotation.y属性,它就会实现自转。因为它的Y轴位置已经改变了。

当我们移动了mercury时,mercuryParent的位置是没有变的,自然它的Y轴也不会变,又因为mercuryParent包含了mercury,所以旋转mercuryParent时,mercury也会绕着初始的默认Y轴旋转。所以设置那么多组,是为了实现每颗行星不同的速度和公转的同时自转。至于设置以下代码数值就根据 行星自转一天、公转一年用多少时间来大概定义一下。

//设置公转函数
const revolution = () => {
        mercuryParent.rotation.y += 0.015
        venusParent.rotation.y += 0.0065
        earthParent.rotation.y += 0.05
        marsParent.rotation.y += 0.03
        jupiterParent.rotation.y += 0.001
        saturnParent.rotation.y += 0.02
        uranusParent.rotation.y += 0.09
        neptuneParent.rotation.y += 0.001
    }
    //设置自转函数
    const selfRotation = () => {
        sun.rotation.y += 0.004
        mercury.rotation.y += 0.002
        venus.rotation.y += 0.005
        earth.rotation.y += 0.01
        mars.rotation.y += 0.01
        jupiter.rotation.y += 0.08
        saturn.rotation.y += 1.5
        uranus.rotation.y += 1
        neptune.rotation.y += 0.1
    }

创建太阳和八大行星

创建星系用到几何球体+纹理贴图

首先介绍一下太阳如何创造,利用 SphereGeometry创建球体,利用MeshBasicMaterial添加纹理,太阳是质量是最大的,所以设置球体的时候数值是最大。 下图是太阳的纹理贴图

sun.jpg
  // 添加设置太阳
    let sun, sunParent
    const setSun = () => {
        sun = new Group()//建立一个组
        sunParent = new Group()
        scene.add(sunParent) //把组都添加到场景里
        loader.load('src/assets/universe/sun.jpg', (texture) => {
            const geometry = new SphereGeometry(500, 20, 20) //球体模型
            const material = new MeshBasicMaterial({map: texture}) //材质 将图片解构成THREE能理解的材质
            const mesh = new Mesh(geometry, material)  //网孔对象 第一个参数是几何模型(结构),第二参数是材料(外观)
            sun.add(mesh)//添加到组里
            sunParent.add(sun)
        })
    }

image.png

按照离太阳最近一个接一个创建

创建水星

水星离太阳最近,质量是所有行星中最小,所以球体数值也给一个最小的数值。 下图水星纹理贴图


mercury.jpg
 let mercury, mercuryParent
    const setMercury = () => {
        mercury = new Group()
        mercuryParent = new Group()
        scene.add(mercuryParent)
        loader.load('src/assets/universe/mercury.jpg', (texture) => {
            const geometry = new SphereGeometry(25, 20, 20) //球体模型
            const material = new MeshBasicMaterial({map: texture}) //材质 将图片解构成THREE能理解的材质
            const mesh = new Mesh(geometry, material)  //网孔对象 第一个参数是几何模型(结构),第二参数是材料(外观)
            mercury.position.x -= 600
            mercury.add(mesh)//添加到组里
            mercuryParent.add(mercury)
        })
    }
image.png

创建金星

image.png

O(∩_∩)O哈哈~ 应该是下图,这张才是金星行星的纹理贴图,千万不要用错哟!!

venus.jpg
  let venus, venusParent
    const setVenus = () => {
        venus = new Group()//建立一个组
        venusParent = new Group()
        scene.add(venusParent)
        loader.load('src/assets/universe/venus.jpg', (texture) => {
            const geometry = new SphereGeometry(100, 20, 20) //球体模型
            const material = new MeshBasicMaterial({map: texture}) //材质 将图片解构成THREE能理解的材质
            const mesh = new Mesh(geometry, material)  //网孔对象 第一个参数是几何模型(结构),第二参数是材料(外观)
            venus.position.x -= 700
            venus.add(mesh)//添加到组里
            venusParent.add(venus)
        })
    }

image.png

地球

怎可以没有我们的家园呢,这么美丽的家园要好好保护它啊!!

earth.jpg
let earth, earthParent
    const setEarth = () => {
        earth = new Group()//建立一个组
        earthParent = new Group()
        scene.add(earthParent)
        loader.load('src/assets/universe/earth.jpg', (texture) => {
            const geometry = new SphereGeometry(100, 20, 20) //球体模型
            const material = new MeshBasicMaterial({map: texture}) //材质 将图片解构成THREE能理解的材质
            const mesh = new Mesh(geometry, material)  //网孔对象 第一个参数是几何模型(结构),第二参数是材料(外观)
            earth.position.x -= 900
            earth.add(mesh)//添加到组里
            earthParent.add(earth)
        })
    }
image.png

火星、木星、土星、天王星、海王星

接下来的行星设置都是大同小异、只是公转、自转、和行星大小的设置不同。

接着对应行星的纹理贴图也一一发给大家

火星的纹理贴图

mars.jpg

木星的纹理贴图

jupiter.jpg

土星的纹理贴图

saturn.jpg

天王星的纹理贴图

uranus.jpg

海王星的纹理贴图


neptune.jpg

最后

一个三维太阳系就创造出来啦,这个例子也是很适合刚入门three.js的同学,目的也是提高对三维的兴趣,提高自身成就感。当然在这列子上我们还可以增加一些功能,比如定位标注一些行星的信息,点击行星可以进入星球内部,利用天空盒子做一个VR全景效果,等等。另外小弟找这些行星纹理贴图也不易,特别找金星的时候😏,希望大家如果喜欢这篇文章能给个赞小弟,当鼓励一下。以后小弟必定为大家创作更多好文,谢谢啦!!_

屏幕录制2021-07-12 下午2.34.26.gif

上完整代码

<template>
    <div id="planet">

    </div>
</template>
<script setup>
    import {onMounted} from 'vue'
    import {
        Group,
        Mesh,
        MeshBasicMaterial,
        PerspectiveCamera,
        PointCloud,
        PointCloudMaterial,
        Scene,
        SphereGeometry,
        TextureLoader,
        Vector3,
        WebGLRenderer
    } from 'three'
    import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js'

    const loader = new TextureLoader() //引入模型的loader实例
    let scene, camera, renderer, group, controls // 定义所有three实例变量

    // 创建场景
    const setScene = () => {
        scene = new Scene()
        renderer = new WebGLRenderer({
            antialias: true,
        })
        renderer.setSize(innerWidth, innerHeight)
        document.querySelector('#planet').appendChild(renderer.domElement)
    }

    // 创建相机
    const setCamera = () => {
        camera = new PerspectiveCamera(60, innerWidth / innerHeight, 1, 100000)
        camera.position.set(0, 500, 2000)
        camera.lookAt(scene.position)
    }

    // 设置模型控制
    const setControls = () => {
        controls = new OrbitControls(camera, renderer.domElement)
    }
    
    // 添加设置太阳
    let sun, sunParent
    const setSun = () => {
        sun = new Group()//建立一个组
        sunParent = new Group()
        scene.add(sunParent) //把组都添加到场景里
        loader.load('src/assets/universe/sun.jpg', (texture) => {
            const geometry = new SphereGeometry(500, 20, 20) //球体模型
            const material = new MeshBasicMaterial({map: texture}) //材质 将图片解构成THREE能理解的材质
            const mesh = new Mesh(geometry, material)  //网孔对象 第一个参数是几何模型(结构),第二参数是材料(外观)
            sun.add(mesh)//添加到组里
            sunParent.add(sun)
        })
    }

    // 设置水星
    let mercury, mercuryParent
    const setMercury = () => {
        mercury = new Group()//建立一个组
        mercuryParent = new Group()
        scene.add(mercuryParent)
        loader.load('src/assets/universe/mercury.jpg', (texture) => {
            const geometry = new SphereGeometry(25, 20, 20) //球体模型
            const material = new MeshBasicMaterial({map: texture}) //材质 将图片解构成THREE能理解的材质
            const mesh = new Mesh(geometry, material)  //网孔对象 第一个参数是几何模型(结构),第二参数是材料(外观)
            mercury.position.x -= 600
            mercury.add(mesh)//添加到组里
            mercuryParent.add(mercury)
        })
    }

    //设置金星
    let venus, venusParent
    const setVenus = () => {
        venus = new Group()//建立一个组
        venusParent = new Group()
        scene.add(venusParent)
        loader.load('src/assets/universe/venus.jpg', (texture) => {
            const geometry = new SphereGeometry(100, 20, 20) //球体模型
            const material = new MeshBasicMaterial({map: texture}) //材质 将图片解构成THREE能理解的材质
            const mesh = new Mesh(geometry, material)  //网孔对象 第一个参数是几何模型(结构),第二参数是材料(外观)
            venus.position.x -= 700
            venus.add(mesh)//添加到组里
            venusParent.add(venus)
        })
    }
    //设置地球
    let earth, earthParent
    const setEarth = () => {
        earth = new Group()//建立一个组
        earthParent = new Group()
        scene.add(earthParent)
        loader.load('src/assets/universe/earth.jpg', (texture) => {
            const geometry = new SphereGeometry(100, 20, 20) //球体模型
            const material = new MeshBasicMaterial({map: texture}) //材质 将图片解构成THREE能理解的材质
            const mesh = new Mesh(geometry, material)  //网孔对象 第一个参数是几何模型(结构),第二参数是材料(外观)
            earth.position.x -= 900
            earth.add(mesh)//添加到组里
            earthParent.add(earth)
        })
    }
//设置火星
    let mars, marsParent
    const setMars = () => {
        mars = new Group()//建立一个组
        marsParent = new Group()
        scene.add(marsParent)
        loader.load('src/assets/universe/mars.jpg', (texture) => {
            const geometry = new SphereGeometry(85, 20, 20) //球体模型
            const material = new MeshBasicMaterial({map: texture}) //材质 将图片解构成THREE能理解的材质
            const mesh = new Mesh(geometry, material)  //网孔对象 第一个参数是几何模型(结构),第二参数是材料(外观)
            mars.position.x -= 1200
            mars.add(mesh)//添加到组里
            marsParent.add(mars)
        })
    }

    // 设置木星
    let jupiter, jupiterParent
    const setJupiter = () => {
        jupiter = new Group()//建立一个组
        jupiterParent = new Group()
        scene.add(jupiterParent)
        loader.load('src/assets/universe/jupiter.jpg', (texture) => {
            const geometry = new SphereGeometry(150, 20, 20) //球体模型
            const material = new MeshBasicMaterial({map: texture}) //材质 将图片解构成THREE能理解的材质
            const mesh = new Mesh(geometry, material)  //网孔对象 第一个参数是几何模型(结构),第二参数是材料(外观)
            jupiter.position.x -= 1500
            jupiter.add(mesh)//添加到组里
            jupiterParent.add(jupiter)
        })
    }

    // 设置土星
    let saturn, saturnParent
    const setSaturn = () => {
        saturn = new Group()//建立一个组
        saturnParent = new Group()
        scene.add(saturnParent)
        loader.load('src/assets/universe/saturn.jpg', (texture) => {
            const geometry = new SphereGeometry(120, 20, 20) //球体模型
            const material = new MeshBasicMaterial({map: texture}) //材质 将图片解构成THREE能理解的材质
            const mesh = new Mesh(geometry, material)  //网孔对象 第一个参数是几何模型(结构),第二参数是材料(外观)
            saturn.position.x -= 1800
            saturn.add(mesh)//添加到组里
            saturnParent.add(saturn)
        })
    }

    //设置天王星
    let uranus, uranusParent
    const setUranus = () => {
        uranus = new Group()
        uranusParent = new Group()
        scene.add(uranusParent)
        loader.load('src/assets/universe/uranus.jpg', (texture) => {
            const geometry = new SphereGeometry(50, 20, 20) //球体模型
            const material = new MeshBasicMaterial({map: texture}) //材质 将图片解构成THREE能理解的材质
            const mesh = new Mesh(geometry, material)  //网孔对象 第一个参数是几何模型(结构),第二参数是材料(外观)
            uranus.position.x -= 2100
            uranus.add(mesh)//添加到组里
            saturnParent.add(uranus)
        })
    }

    //设置海王星
    let neptune, neptuneParent
    const setNeptune = () => {
        neptune = new Group()
        neptuneParent = new Group()
        scene.add(neptuneParent)
        loader.load('src/assets/universe/neptune.jpg', (texture) => {
            const geometry = new SphereGeometry(50, 20, 20) //球体模型
            const material = new MeshBasicMaterial({map: texture}) //材质 将图片解构成THREE能理解的材质
            const mesh = new Mesh(geometry, material)  //网孔对象 第一个参数是几何模型(结构),第二参数是材料(外观)
            neptune.position.x -= 2300
            neptune.add(mesh)//添加到组里
            neptuneParent.add(neptune)
        })
    }

//监听浏览器改变大小时重新渲染
    function onWindowResize() {
        const WIDTH = window.innerWidth,
            HEIGHT = window.innerHeight
        camera.aspect = WIDTH / HEIGHT
        camera.updateProjectionMatrix()
        renderer.setSize(WIDTH, HEIGHT)
    }

//设置公转函数
    const revolution = () => {
        mercuryParent.rotation.y += 0.015
        venusParent.rotation.y += 0.0065
        earthParent.rotation.y += 0.05
        marsParent.rotation.y += 0.03
        jupiterParent.rotation.y += 0.01
        saturnParent.rotation.y += 0.02
        uranusParent.rotation.y += 0.09
        neptuneParent.rotation.y += 0.01
    }

    //设置自转
    const selfRotation = () => {
        sun.rotation.y += 0.004
        mercury.rotation.y += 0.002
        venus.rotation.y += 0.005
        earth.rotation.y += 0.01
        mars.rotation.y += 0.01
        jupiter.rotation.y += 0.08
        saturn.rotation.y += 1.5
        uranus.rotation.y += 1
        neptune.rotation.y += 0.1
    }

    // 设置太阳系背景
    const starForge = () => {
        const starQty = 10000
        const geometry = new SphereGeometry(10000, 100, 50)
        const materialOptions = {}
        const starStuff = new PointCloudMaterial(materialOptions)
        geometry.vertices = []
        for (let i = 0; i < starQty; i++) {
            let starVertex = new Vector3()
            starVertex.x = Math.random() * 20000 - 10000
            starVertex.y = Math.random() * 20000 - 10000
            starVertex.z = Math.random() * 20000 - 10000
            geometry.vertices.push(starVertex)
        }
        const stars = new PointCloud(geometry, starStuff)
        scene.add(stars)
    }


    // 循环场景 、相机、 位置更新
    const loop = () => {
        requestAnimationFrame(loop)
        revolution()
        selfRotation()
        renderer.render(scene, camera)
        camera.lookAt(scene.position)
    }

  //初始化所有函数
    const init = () => {
        setScene() //设置场景
        setCamera() //设置相机
        setSun() // 设置太阳
        setMercury() //设置水星
        setVenus() //设置金星
        setEarth() // 地球
        setMars() //火星
        setJupiter() // 木星
        setSaturn() // 土星
        setUranus()// 天王星
        setNeptune()//海王星
        starForge()//设置满天星背景
        setControls() //设置可旋转控制
        loop() // 循环动画
    }

    onMounted(init)
    window.addEventListener('resize', onWindowResize)

</script>



image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容