完整的太阳系、一行一行仔细查资料注释,费了好大劲呢~~~
<template>
<div style="100%; height:800px" class="my_div">
<p>3D太陽系</p>
<canvas ref="main" />
<div id="canvas-frame" ref="myBody" style="1000px; height:800px" />
</div>
</template>
<script>
import * as THREE from 'three'
import { OBJLoader, MTLLoader } from 'three-obj-mtl-loader'
// import MTLLoader from 'three-mtl-loader';
// import OBJLoader from 'three-obj-loader';
import { CSS2DRenderer, CSS2DObject } from 'three-css2drender'
// import { Geometry, Line } from 'three'
// import { Geometry, Material, Scene, WebGLBufferRenderer } from 'three';
const OrbitControls = require('three-orbit-controls')(THREE)
export default {
data() {
return {
canvas: Element,
loader: THREE.TextureLoader,
sunSystem: THREE.Object3D,
sun: THREE.Mesh,
orbitcontrols: OrbitControls,
planets: [],
labelRenderer: new CSS2DRenderer(),
scene: new THREE.Scene(), // 場景
camera: new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
1,
10000
), // 透視相機
renderer: new THREE.WebGLRenderer(), // 渲染器
geometry: new THREE.Geometry(), // 設置物體
material: new THREE.LineBasicMaterial({ vertexColors: true }), // 設置材料
cube: {}, // 合起來
// 開始設置線條
light: new THREE.DirectionalLight(0xff0000, 1.0, 0)
}
},
mounted() {
this.threeStart()
console.log()
},
methods: {
initThree() {
this.canvas = this.$refs.main
this.canvas.width = window.innerWidth // 只读的Window 属性 innerWidth 返回以像素为单位的窗口的内部宽度。如果垂直滚动条存在,则这个属性将包括它的宽度。
this.canvas.height = window.innerHeight
const canvas = this.canvas
// 創建渲染器
// new WebGLRenderer会在body里面生成一个canvas标签,当然如果你想在某个位置插入canvas可以在指定的dom元素appendChild(renderer.domElement)
this.renderer = new THREE.WebGLRenderer({
canvas,
alpha: true, // alpha:true/false是否可以设置背景色透明
antialias: true // antialias:true/false是否开启反锯齿
})
this.renderer.setPixelRatio(window.devicePixelRatio) // setPixelRatio是为了兼容高清屏幕,Window 接口的devicePixelRatio返回当前显示设备的物理像素分辨率与CSS像素分辨率之比
this.renderer.shadowMap.enabled = true // 輔助線
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap // 柔化边缘的软阴影映射
this.renderer.setClearColor(0xffffff, 0) // 设置canvas背景色(clearColor)和背景色透明度(clearAlpha)
},
initScene() {
this.scene = new THREE.Scene() // 創建場景
},
initCamera() {
// 創建透視相機,
this.camera = new THREE.PerspectiveCamera(
45, // 视野角fov
window.innerWidth / window.innerHeight, // 纵横比:aspect
1, // 相机离视体积最近的距离:near
3000 // 相机离视体积最远的距离:far
)
this.camera.position.set(-200, 50, 0) // 设置相机的位置坐标xyz
this.camera.lookAt(0, 0, 0) // 设置视野的中心坐标
this.scene.add(this.camera) // 添加相機到場景裡
},
initAxesHelper() {
const axesHelper = new THREE.AxesHelper(500)
this.scene.add(axesHelper)
},
initLabelRender() {
console.log('canvas.clientWidth, canvas.clientHeight')
console.log(this.canvas.clientWidth, this.canvas.clientHeight)
this.labelRenderer.setSize(
this.canvas.clientWidth,
this.canvas.clientHeight
)
this.labelRenderer.domElement.style.position = 'absolute'
this.labelRenderer.domElement.style.top = '0px'
this.$refs.myBody.appendChild(this.labelRenderer.domElement) // 追加 【canvas】 元素到 【myBody】 元素中。
},
initLight() {
this.light = new THREE.DirectionalLight(0xff0000, 1.0, 0) // 设置平行光源
this.light.position.set(200, 200, 200) // 设置光源向量
this.scene.add(this.light) // 追加光源到场景
},
initObject() {
// Orbit controls(轨道控制器)可以使得相机围绕目标进行轨道运动
this.orbitcontrols = new OrbitControls(
this.camera,
this.labelRenderer.domElement
)
console.log('軌道控制器')
this.orbitcontrols.update() // false 更新控件,在手动改变了摄像机的钻换后必须调用。在设置了autoRotate或enableDamping时也要在循环中调用
this.loader = new THREE.TextureLoader() // 纹理加载器
// Object3D似乎是Three.js框架中最重要的类,相当一部分其他的类都是继承自Object3D类,比如场景类、几何形体类、相机类、光照类等等:他们都是3D空间中的对象,所以称为Object3D类
this.sunSystem = new THREE.Object3D()
this.scene.add(this.sunSystem) // 向场景中添加对象
// sun
// 网格基础材质
const sunMaterial = new THREE.MeshBasicMaterial({
map: this.loader.load(require('./webgl-assets/img/sun_bg.jpg'))
})
// 网孔对象的基类,MESH就是一系列的多边形组成的,三角形或者四边形,网格一般由顶点来描绘,我们看见的三维开发的模型就是由一系列的点组成的。Mesh( geometry几何模型, material材料 )
this.sun = new THREE.Mesh(
new THREE.SphereGeometry(14, 30, 30), // 一个 几何模型(Geometry) 实例,用来定义对象的结构。可以创建一个半径为14,经度划分成30份,纬度划分成30份的球体
sunMaterial // 一个 材料(Material) 实例,用来定义对象的外观
)
this.sun.name = 'SUN'
this.sunSystem.add(this.sun) // 将对象添加为该对象的子对象。可以添加任意数量的对象
},
initPlanet() {
const planetDiv = document.createElement('div')
planetDiv.className = 'label'
planetDiv.textContent = '太陽'
planetDiv.style.color = 'white'
planetDiv.style.marginTop = '-0.3em'
const planetaLabel = new CSS2DObject(planetDiv) // 把上述div对象转化为一个CSS2DObject对象
planetaLabel.position.set(0, 14, 0)
this.sun.add(planetaLabel) // 在球體模型中加入该CSS2DObject对象
},
addPlanets() {
// 添加水星
const Mercury = this.loadPlanet('mercury', 2, 20, 0.02)
this.planets.push(Mercury)
// 添加金星
const Venus = this.loadPlanet('venus', 4, 30, 0.012)
this.planets.push(Venus)
// 添加地球
const Earth = this.loadPlanet('earth', 5, 40, 0.01)
this.planets.push(Earth)
// 添加火星
const Mars = this.loadPlanet('mars', 4, 50, 0.008)
this.planets.push(Mars)
// 添加木星
const Jupiter = this.loadPlanet('jupiter', 9, 70, 0.006)
this.planets.push(Jupiter)
// 添加土星
const Saturn = this.loadPlanet('saturn', 7, 100, 0.005)
this.planets.push(Saturn)
// 添加天王星
const Uranus = this.loadPlanet('uranus', 4, 120, 0.003)
this.planets.push(Uranus)
// 添加海王星
const Neptune = this.loadPlanet('neptune', 3, 150, 0.002)
this.planets.push(Neptune)
// 添加冥王星
const Pluto = this.loadPlanet('pluto', 4, 160, 0.0016)
this.planets.push(Pluto)
const particleSystem = this.initParticle()
this.scene.add(particleSystem)
},
initParticle() {
// /*背景星星*/
const particles = 20000 // 星星数量
// /*buffer做星星*/
const bufferGeometry = new THREE.BufferGeometry()
const positions = new Float32Array(particles * 3)
const colors = new Float32Array(particles * 3)
const color = new THREE.Color()
const gap = 900 // 定义星星的最近出现位置
for (let i = 0; i < positions.length; i += 3) {
// positions
// /*-2gap < x < 2gap */
let x = Math.random() * gap * 2 * (Math.random() < 0.5 ? -1 : 1)
let y = Math.random() * gap * 2 * (Math.random() < 0.5 ? -1 : 1)
let z = Math.random() * gap * 2 * (Math.random() < 0.5 ? -1 : 1)
// /*找出x,y,z中绝对值最大的一个数*/
const biggest =
Math.abs(x) > Math.abs(y) ? Math.abs(x) > Math.abs(z) ? 'x' : 'z' : Math.abs(y) > Math.abs(z) ? 'y' : 'z'
const pos = { x, y, z }
// /*如果最大值比n要小(因为要在一个距离之外才出现星星)则赋值为n(-n)*/
if (Math.abs(pos[biggest]) < gap) {
pos[biggest] = pos[biggest] < 0 ? -gap : gap
}
x = pos['x']
y = pos['y']
z = pos['z']
positions[i] = x
positions[i + 1] = y
positions[i + 2] = z
// colors
// /*70%星星有颜色*/
const hasColor = Math.random() > 0.3
let vx, vy, vz
if (hasColor) {
vx = (Math.random() + 1) / 2
vy = (Math.random() + 1) / 2
vz = (Math.random() + 1) / 2
} else {
vx = 1
vy = 1
vz = 1
}
color.setRGB(vx, vy, vz)
colors[i] = color.r
colors[i + 1] = color.g
colors[i + 2] = color.b
}
bufferGeometry.setAttribute(
'position',
new THREE.BufferAttribute(positions, 3)
)
bufferGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3))
bufferGeometry.computeBoundingSphere()
// /*星星的material*/
const material = new THREE.PointsMaterial({
size: 6,
vertexColors: THREE.VertexColors
})
const particleSystem = new THREE.Points(bufferGeometry, material)
return particleSystem
},
loadPlanet(name, radius, position, speed) {
const planetSystem = new THREE.Mesh(
new THREE.SphereGeometry(1, 1, 1), // 一个 几何模型(Geometry) 实例,用来定义对象的结构。可以创建一个半径为1,经度划分成1份,纬度划分成1份的球体
new THREE.MeshLambertMaterial() // 材料實例,用來定義對象外觀
) // 材质设定
planetSystem.speed = speed
// 動態設置材質
const material = new THREE.MeshBasicMaterial({
map: this.loader.load(require(`./webgl-assets/img/${name}_bg.jpg`))
})
const planet = new THREE.Mesh(
new THREE.SphereGeometry(radius, 30, 30), // 動態創建球體
material // 使用動態貼圖渲染
)
planet.position.z = -position // 設置z坐標位置
// planet.rotateOnAxis(new THREE.Vector3(1, 0, 0).normalize(), -23.36 * Math.PI / 180)
planetSystem.add(planet)
if (name === 'saturn') {
const ringMaterial = new THREE.MeshBasicMaterial({
map: this.loader.load(`./webgl-assets/img/${name}_ring.jpg`), // 皮膚貼圖
side: THREE.DoubleSide // 雙面材質
})
const ring = new THREE.Mesh(
new THREE.RingGeometry(radius * 1.2, radius * 1.5, 64, 1),// RingGeometry用来在三维空间内创建一个二维圆环面对象.
ringMaterial
)
ring.rotation.x = -Math.PI / 2
planet.add(ring)
}
const track = new THREE.Mesh(
new THREE.RingGeometry(position, position + 0.05, 64, 1), // 二维圆环面对象(內徑,外徑,分段數,面細分)
new THREE.MeshBasicMaterial({
side: THREE.DoubleSide // 雙面材質
})
)
track.rotation.x = -Math.PI / 2
this.scene.add(track)
// Three.js中的div标签跟随(模型弹框)
const planetDiv = document.createElement('div') // 把div存为变量
planetDiv.className = 'label'
planetDiv.style.color = 'white'
planetDiv.textContent = name
planetDiv.style.marginTop = '-0.3em'
const planetLabel = new CSS2DObject(planetDiv) // 把上述div对象转化为一个CSS2DObject对象
planetLabel.position.set(0, radius, 0) // 前两个参数是对于屏幕xy坐标,可以取负数 第三个不清楚,按道理应该是z轴坐标,不知道怎么体现
planet.add(planetLabel) // 在模型中加入该CSS2DObject对象
this.sunSystem.add(planetSystem)
// 注意事项:上面的代码放在camera / OrbitControls之后, 否则相机控制不能用
return planetSystem
},
resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement
const width = canvas.clientWidth
const height = canvas.clientHeight
const needResize = canvas.width !== width || canvas.height !== height
if (needResize) {
renderer.setSize(width, height, false) // 指定渲染器的高宽
}
return needResize
},
render(time) {
time *= 0.0005
if (this.resizeRendererToDisplaySize(this.renderer)) {
const canvas = this.renderer.domElement
this.camera.aspect = canvas.clientWidth / canvas.clientHeight // aspect属性:设置摄像机视口比例,实际窗口的纵横比,即宽度除以高度,这个值越大,说明你宽度越大,那么你可能看的是宽银幕电影了,如果这个值小于1,则为竖屏。
this.camera.updateProjectionMatrix() // 如果相机对象与投影矩阵相关的属性发生了变化,就需要手动更新相机的投影矩阵,更新相机对象的投影矩阵属性
}
this.sunSystem.rotation.y = -time // 在three.js你可以使用rotation来设置object3D的旋转。
for (var i = 0; i < this.planets.length; i++) {
this.planets[i].rotation.y -= this.planets[i].speed
const planet = this.planets[i].children[0]
planet.rotation.y -= 0.1
}
this.orbitcontrols.update() // Orbit controls(轨道控制器)可以使得相机围绕目标进行轨道运动
this.renderer.render(this.scene, this.camera)
this.labelRenderer.render(this.scene, this.camera)
requestAnimationFrame(this.render)
},
animation() {},
threeStart() {
this.initThree()
this.initScene()
this.initCamera()
this.initAxesHelper()
this.initLabelRender()
this.initObject()
this.initPlanet()
this.addPlanets()
requestAnimationFrame(this.render)
},
consoleObj() {
console.log(THREE.REVISION)
console.log(OBJLoader)
console.log(MTLLoader)
console.log(CSS2DRenderer)
console.log(CSS2DObject)
}
}
}
</script>
<style lang="less" scoped>
.my_div {
background: #000 url('./webgl-assets/img/starry_sky_bg.jpg') no-repeat center
center;
margin: 0;
padding: 0;
overflow: hidden;
.label {
color: #fff;
font-family: sans-serif;
font-size: xx-small;
padding: 2px;
}
.label:hover {
color: red;
}
}
#main {
position: relative;
/* makes this the origin of its children */
100%;
height: 100%;
overflow: hidden;
}
</style>
参考原版地址:
原github大佬有详细介绍代码如何一步一步写出来,可以去看看哟~
原版是用的H5,我用的vue,和原版不太一样
https://github.com/iWun/solar-system