教你如何用Three.js創(chuàng)造一個三維太陽系
點(diǎn)擊上方 前端瓶子君,關(guān)注公眾號
回復(fù)算法,加入前端編程面試算法每日一題群

前言
筆者認(rèn)為Three.js是一個偉大的框架,為什么這樣說,因?yàn)樗梢宰屛覀冚p易創(chuàng)造三維世界,甚至好像筆者寫這遍教程,可以創(chuàng)造一個太陽系,在這個三維世界里你就是創(chuàng)世主。哈哈!好像說得有點(diǎn)夸??!
三維太陽系完整效果:https://shinewen189.github.io/nigo-vue-planet/
了解一些基本天文知識
學(xué)習(xí)創(chuàng)造這個三維太陽系之前先了解一下基本的天文知識:太陽系有“八大行星”,按照離太陽的距離從近到遠(yuǎn),它們依次為水星、金星、地球、火星、木星、土星、天王星、海王星。八大行星自轉(zhuǎn)方向多數(shù)也和公轉(zhuǎn)方向一致。只有金星和天王星兩個例外。金星自轉(zhuǎn)方向與公轉(zhuǎn)方向相反。而天王星則是在軌道上“橫滾”的。例如地球自轉(zhuǎn)一天是23.9小時,公轉(zhuǎn)一年有365.2天 ,而相鄰的火星自轉(zhuǎn)一天是24.6小時 公轉(zhuǎn)一年則有687天,其他行星也有不同的公轉(zhuǎn)和自轉(zhuǎn)信息,有了這些信息就可以定義一些基本規(guī)則
了解Three框架
Three的一些基本概念我在用最簡單方式打造Three.js 3D汽車展示廳[2]一文也粗略介紹一下,為了讓同學(xué)們加深理解,筆者就相對于太陽系來比如一下
-
場景 Sence相當(dāng)于太陽系,宇宙中有無數(shù)星系,比如現(xiàn)在說的太陽系,后續(xù)還可以增加其他星系,那不是永遠(yuǎn)都加不完的呀 o(╥﹏╥)o -
相機(jī) Carma相當(dāng)一枚哈勃天文望遠(yuǎn)鏡 -
幾何體 Geometry相當(dāng)于太陽和八大行星 -
控制 Controls相當(dāng)”創(chuàng)世者“的你
有了這幾個概念我們就創(chuàng)建一些函數(shù)一一對應(yīng)
完整效果
進(jìn)入教程
先引入Three 要用到的對象
import {
Group,
Mesh,
MeshBasicMaterial,
PerspectiveCamera,
PointCloud,
PointCloudMaterial,
Scene,
SphereGeometry,
TextureLoader,
Vector3,
WebGLRenderer
} from 'three'
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js'
復(fù)制代碼
場景(太陽系),相機(jī)(哈勃天文望遠(yuǎn)鏡),控制(創(chuàng)世主)
//場景
const setScene = () => {
scene = new Scene()
renderer = new WebGLRenderer({
antialias: true,
})
renderer.setSize(innerWidth, innerHeight)
document.querySelector('#planet').appendChild(renderer.domElement)
}
//相機(jī)
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)
}
復(fù)制代碼
創(chuàng)建一個太陽系的背景(繁星背景)
這個滿天星效果是太陽系的背景,運(yùn)用到Three的粒子系統(tǒng),行星密度可自行調(diào)整
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)
}
復(fù)制代碼
效果如下圖:
創(chuàng)建太陽和行星前先說說行星自轉(zhuǎn)同時公轉(zhuǎn)的規(guī)律
旋轉(zhuǎn)方式:實(shí)現(xiàn)旋轉(zhuǎn)功能有三種方式
-
旋轉(zhuǎn)照相機(jī) -
旋轉(zhuǎn)整個場景(Scene) -
旋轉(zhuǎn)單個元素
因?yàn)槲覀冞@里每個行星的自轉(zhuǎn)速度,公轉(zhuǎn)速度都不一樣。所以設(shè)置整體旋轉(zhuǎn)并不可行,所以要給每個元素設(shè)置不同的旋轉(zhuǎn)屬性。
行星需要讓它們圍繞著太陽轉(zhuǎn),就要先給它們自身設(shè)置一個位置偏移。以水星為例:mercury.position.x \-= 300,而此時設(shè)置mercury.rotation.y屬性,它就會實(shí)現(xiàn)自轉(zhuǎn)。因?yàn)樗腨軸位置已經(jīng)改變了。
當(dāng)我們移動了mercury時,mercuryParent的位置是沒有變的,自然它的Y軸也不會變,又因?yàn)?code style="font-size: 14px;border-radius: 4px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">mercuryParent包含了mercury,所以旋轉(zhuǎn)mercuryParent時,mercury也會繞著初始的默認(rèn)Y軸旋轉(zhuǎn)。所以設(shè)置那么多組,是為了實(shí)現(xiàn)每顆行星不同的速度和公轉(zhuǎn)的同時自轉(zhuǎn)。至于設(shè)置以下代碼數(shù)值就根據(jù) 行星自轉(zhuǎn)一天、公轉(zhuǎn)一年用多少時間來大概定義一下。
//設(shè)置公轉(zhuǎn)函數(shù)
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
}
//設(shè)置自轉(zhuǎn)函數(shù)
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
}
復(fù)制代碼
創(chuàng)建太陽和八大行星
創(chuàng)建星系用到幾何球體+紋理貼圖
首先介紹一下太陽如何創(chuàng)造,利用 SphereGeometry創(chuàng)建球體,利用MeshBasicMaterial添加紋理,太陽是質(zhì)量是最大的,所以設(shè)置球體的時候數(shù)值是最大。下圖是太陽的紋理貼圖
// 添加設(shè)置太陽
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}) //材質(zhì) 將圖片解構(gòu)成THREE能理解的材質(zhì)
const mesh = new Mesh(geometry, material) //網(wǎng)孔對象 第一個參數(shù)是幾何模型(結(jié)構(gòu)),第二參數(shù)是材料(外觀)
sun.add(mesh)//添加到組里
sunParent.add(sun)
})
}
復(fù)制代碼
按照離太陽最近一個接一個創(chuàng)建
創(chuàng)建水星
水星離太陽最近,質(zhì)量是所有行星中最小,所以球體數(shù)值也給一個最小的數(shù)值。下圖水星紋理貼圖
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}) //材質(zhì) 將圖片解構(gòu)成THREE能理解的材質(zhì)
const mesh = new Mesh(geometry, material) //網(wǎng)孔對象 第一個參數(shù)是幾何模型(結(jié)構(gòu)),第二參數(shù)是材料(外觀)
mercury.position.x -= 600
mercury.add(mesh)//添加到組里
mercuryParent.add(mercury)
})
}
復(fù)制代碼
創(chuàng)建金星
O(∩_∩)O哈哈~ 應(yīng)該是下圖,這張才是金星行星的紋理貼圖,千萬不要用錯喲!!
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}) //材質(zhì) 將圖片解構(gòu)成THREE能理解的材質(zhì)
const mesh = new Mesh(geometry, material) //網(wǎng)孔對象 第一個參數(shù)是幾何模型(結(jié)構(gòu)),第二參數(shù)是材料(外觀)
venus.position.x -= 700
venus.add(mesh)//添加到組里
venusParent.add(venus)
})
}
復(fù)制代碼
地球
怎可以沒有我們的家園呢,這么美麗的家園要好好保護(hù)它?。?!
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}) //材質(zhì) 將圖片解構(gòu)成THREE能理解的材質(zhì)
const mesh = new Mesh(geometry, material) //網(wǎng)孔對象 第一個參數(shù)是幾何模型(結(jié)構(gòu)),第二參數(shù)是材料(外觀)
earth.position.x -= 900
earth.add(mesh)//添加到組里
earthParent.add(earth)
})
}
復(fù)制代碼
火星、木星、土星、天王星、海王星
接下來的行星設(shè)置都是大同小異、只是公轉(zhuǎn)、自轉(zhuǎn)、和行星大小的設(shè)置不同。
接著對應(yīng)行星的紋理貼圖也一一發(fā)給大家
火星的紋理貼圖
木星的紋理貼圖
土星的紋理貼圖
天王星的紋理貼圖
海王星的紋理貼圖
最后
一個三維太陽系就創(chuàng)造出來啦,這個例子也是很適合剛?cè)腴Tthree.js的同學(xué),目的也是提高對三維的興趣,提高自身成就感。當(dāng)然在這列子上我們還可以增加一些功能,比如定位標(biāo)注一些行星的信息,點(diǎn)擊行星可以進(jìn)入星球內(nèi)部,利用天空盒子做一個VR全景效果,等等。另外小弟找這些行星紋理貼圖也不易,特別找金星的時候??,希望大家如果喜歡這篇文章能給個贊小弟,當(dāng)鼓勵一下。以后小弟必定為大家創(chuàng)作更多好文,謝謝啦!!^_^
上完整代碼
<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實(shí)例
let scene, camera, renderer, group, controls // 定義所有three實(shí)例變量
// 創(chuàng)建場景
const setScene = () => {
scene = new Scene()
renderer = new WebGLRenderer({
antialias: true,
})
renderer.setSize(innerWidth, innerHeight)
document.querySelector('#planet').appendChild(renderer.domElement)
}
// 創(chuàng)建相機(jī)
const setCamera = () => {
camera = new PerspectiveCamera(60, innerWidth / innerHeight, 1, 100000)
camera.position.set(0, 500, 2000)
camera.lookAt(scene.position)
}
// 設(shè)置模型控制
const setControls = () => {
controls = new OrbitControls(camera, renderer.domElement)
}
// 添加設(shè)置太陽
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}) //材質(zhì) 將圖片解構(gòu)成THREE能理解的材質(zhì)
const mesh = new Mesh(geometry, material) //網(wǎng)孔對象 第一個參數(shù)是幾何模型(結(jié)構(gòu)),第二參數(shù)是材料(外觀)
sun.add(mesh)//添加到組里
sunParent.add(sun)
})
}
// 設(shè)置水星
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}) //材質(zhì) 將圖片解構(gòu)成THREE能理解的材質(zhì)
const mesh = new Mesh(geometry, material) //網(wǎng)孔對象 第一個參數(shù)是幾何模型(結(jié)構(gòu)),第二參數(shù)是材料(外觀)
mercury.position.x -= 600
mercury.add(mesh)//添加到組里
mercuryParent.add(mercury)
})
}
//設(shè)置金星
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}) //材質(zhì) 將圖片解構(gòu)成THREE能理解的材質(zhì)
const mesh = new Mesh(geometry, material) //網(wǎng)孔對象 第一個參數(shù)是幾何模型(結(jié)構(gòu)),第二參數(shù)是材料(外觀)
venus.position.x -= 700
venus.add(mesh)//添加到組里
venusParent.add(venus)
})
}
//設(shè)置地球
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}) //材質(zhì) 將圖片解構(gòu)成THREE能理解的材質(zhì)
const mesh = new Mesh(geometry, material) //網(wǎng)孔對象 第一個參數(shù)是幾何模型(結(jié)構(gòu)),第二參數(shù)是材料(外觀)
earth.position.x -= 900
earth.add(mesh)//添加到組里
earthParent.add(earth)
})
}
//設(shè)置火星
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}) //材質(zhì) 將圖片解構(gòu)成THREE能理解的材質(zhì)
const mesh = new Mesh(geometry, material) //網(wǎng)孔對象 第一個參數(shù)是幾何模型(結(jié)構(gòu)),第二參數(shù)是材料(外觀)
mars.position.x -= 1200
mars.add(mesh)//添加到組里
marsParent.add(mars)
})
}
// 設(shè)置木星
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}) //材質(zhì) 將圖片解構(gòu)成THREE能理解的材質(zhì)
const mesh = new Mesh(geometry, material) //網(wǎng)孔對象 第一個參數(shù)是幾何模型(結(jié)構(gòu)),第二參數(shù)是材料(外觀)
jupiter.position.x -= 1500
jupiter.add(mesh)//添加到組里
jupiterParent.add(jupiter)
})
}
// 設(shè)置土星
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}) //材質(zhì) 將圖片解構(gòu)成THREE能理解的材質(zhì)
const mesh = new Mesh(geometry, material) //網(wǎng)孔對象 第一個參數(shù)是幾何模型(結(jié)構(gòu)),第二參數(shù)是材料(外觀)
saturn.position.x -= 1800
saturn.add(mesh)//添加到組里
saturnParent.add(saturn)
})
}
//設(shè)置天王星
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}) //材質(zhì) 將圖片解構(gòu)成THREE能理解的材質(zhì)
const mesh = new Mesh(geometry, material) //網(wǎng)孔對象 第一個參數(shù)是幾何模型(結(jié)構(gòu)),第二參數(shù)是材料(外觀)
uranus.position.x -= 2100
uranus.add(mesh)//添加到組里
saturnParent.add(uranus)
})
}
//設(shè)置海王星
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}) //材質(zhì) 將圖片解構(gòu)成THREE能理解的材質(zhì)
const mesh = new Mesh(geometry, material) //網(wǎng)孔對象 第一個參數(shù)是幾何模型(結(jié)構(gòu)),第二參數(shù)是材料(外觀)
neptune.position.x -= 2300
neptune.add(mesh)//添加到組里
neptuneParent.add(neptune)
})
}
//監(jiān)聽瀏覽器改變大小時重新渲染
function onWindowResize() {
const WIDTH = window.innerWidth,
HEIGHT = window.innerHeight
camera.aspect = WIDTH / HEIGHT
camera.updateProjectionMatrix()
renderer.setSize(WIDTH, HEIGHT)
}
//設(shè)置公轉(zhuǎn)函數(shù)
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
}
//設(shè)置自轉(zhuǎn)
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
}
// 設(shè)置太陽系背景
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)
}
// 循環(huán)場景 、相機(jī)、 位置更新
const loop = () => {
requestAnimationFrame(loop)
revolution()
selfRotation()
renderer.render(scene, camera)
camera.lookAt(scene.position)
}
//初始化所有函數(shù)
const init = () => {
setScene() //設(shè)置場景
setCamera() //設(shè)置相機(jī)
setSun() // 設(shè)置太陽
setMercury() //設(shè)置水星
setVenus() //設(shè)置金星
setEarth() // 地球
setMars() //火星
setJupiter() // 木星
setSaturn() // 土星
setUranus()// 天王星
setNeptune()//海王星
starForge()//設(shè)置滿天星背景
setControls() //設(shè)置可旋轉(zhuǎn)控制
loop() // 循環(huán)動畫
}
onMounted(init)
window.addEventListener('resize', onWindowResize)
</script>
復(fù)制代碼
關(guān)于本文
來源:lizhenwen
https://juejin.cn/post/6983938127911976990
