关于three.js:整个宇宙将为你闪烁-除了三体人前端也可以-哈哈

60次阅读

共计 8388 个字符,预计需要花费 21 分钟才能阅读完成。

整个宇宙将为你闪动 除了三体人前端也能够 哈哈

整个宇宙将为你闪动,这句话出自《三体》,过后就被吸引住了,而后就想着把它实现进去。我的项目次要应用 three.js,设计灵感及贴图来源于 Solar System Scope。

📺 视频展现 :Bilibili 哔哩哔哩

🚀 我的项目源码 :GitHub

📖 前置项

在开始之前心愿你对 three.js 有肯定的理解,这里举荐一个教程,Discover threejs, three.js 主创之一编写的适宜入门的教程。

我的项目只用原生 js 并没有引入 Vue 或 React 等框架,因为我想让我的项目更纯正些,也心愿本人不要来到框架就不会写代码了。

📚 基础设施

  1. 创立场景
// scene.js
import {Scene} from 'three'

function createScene() {const scene = new Scene()
    return scene
}
export {createScene}
  1. 创立相机
// camera.js
import {PerspectiveCamera} from 'three'

function createCamera() {const camera = new PerspectiveCamera(45, 1, 0.1, 2000)

    camera.position.set(0, 0, 20)

    return camera
}
export {createCamera}
  1. 渲染场景
// renderer.js
import {WebGLRenderer} from 'three'

function createRenderer() {const renderer = new WebGLRenderer()

    return renderer
}

export {createRenderer}

🌌 增加物体到场景中

实现下面的基本操作,咱们就能够往场景中增加物体了。

🌖 银河系及行星

创立一个球体,并给球体的材质应用纹理贴图。别忘了增加到场景中。

// milkyWay.js
import {SphereGeometry, Mesh, MeshStandardMaterial, TextureLoader, BackSide} from 'three'
import * as THREE from 'three'

function createMaterial() {const textureLoader = new TextureLoader()
    const texture = textureLoader.load('/assets/textures/8k_stars_milky_way.jpg')
    const material = new MeshStandardMaterial({map: texture, side: THREE.DoubleSide})
    return material
}
function createMilkyWay() {const geometry = new SphereGeometry(1000)

    const material = createMaterial()

    const milkyWay = new Mesh(geometry, material)

    return milkyWay
}
export {createMilkyWay}

行星的创立与银河系相似,因为都是创立球体。

💫 行星的轨迹

这里次要应用了 CatmullRomCurve3 创立一条平滑的三维样条曲线。

// orbit.js
import {CatmullRomCurve3, BufferGeometry, LineBasicMaterial, LineLoop} from 'three'

function createOrbit(sphereVal) {
    let sphererObitalInclination = sphereVal.orbitalInclination || 0
    let sphereOrbit = sphereVal.orbit || 0
    let x = Math.cos((Math.PI / 180) * sphererObitalInclination) * sphereOrbit
    let y = Math.sin((Math.PI / 180) * sphererObitalInclination) * sphereOrbit
    const initialPoints = [
        {
            x: x,
            y: y,
            z: 0
        },
        {x: 0, y: 0, z: -sphereOrbit},
        {
            x: -x,
            y: -y,
            z: 0
        },
        {x: 0, y: 0, z: sphereOrbit}
    ]
    const curve = new CatmullRomCurve3(initialPoints)
    curve.curveType = 'catmullrom'
    curve.tension = 0.84
    curve.closed = true

    const points = curve.getPoints(50)

    const geometry = new BufferGeometry().setFromPoints(points)
    const material = new LineBasicMaterial({color: 0x718799, transparent: true, opacity: 0.8})

    const orbit = new LineLoop(geometry, material)

    if (sphereVal.id === 'moon') {orbit.position.x = 10}

    return {curve, orbit}
}

export {createOrbit}

创立圆环还有更简略的办法,就是间接应用 EllipseCurve 创立一个曲线,再将其进行旋转,也就是批改 orbit 的 rotation。然而最初在行星的静止环节,应用 getPointAt 获取不到精确的值,只能拿到旋转前的值。

import {EllipseCurve, BufferGeometry, LineBasicMaterial, Line} from 'three'

function createOrbit(sphereVal) {const curve = new EllipseCurve(0, 0, sphereVal.orbit, sphereVal.orbit, 0, 2 * Math.PI, false, 0)
    const points = curve.getPoints(100)
    const geometry = new BufferGeometry().setFromPoints(points)

    const material = new LineBasicMaterial({color: 0xffffff, transparent: true, opacity: 0.8})
    const orbit = new Line(geometry, material)
    orbit.rotation.x = Math.PI / 2
    orbit.rotation.y = (sphereVal.orbitalInclination * Math.PI) / 180

    return orbit
}

export {createOrbit}

🌞 太阳的发光成果

应用 EffectComposer 实现,具体的例子能够参考 three.js 官网示例 Bloom,用于太阳的辉光;
three.js 官网示例 Outline,用于太阳的边缘。

import {Vector2, ShaderMaterial} from 'three'
import {EffectComposer} from 'three/examples/jsm/postprocessing/EffectComposer.js'
import {RenderPass} from 'three/examples/jsm/postprocessing/RenderPass.js'
import {ShaderPass} from 'three/examples/jsm/postprocessing/ShaderPass.js'
import {UnrealBloomPass} from 'three/examples/jsm/postprocessing/UnrealBloomPass.js'
import {OutlinePass} from 'three/examples/jsm/postprocessing/OutlinePass.js'

function createEffectComposer(scene, camera, renderer) {const renderScene = new RenderPass(scene, camera)

    const bloomPass = new UnrealBloomPass(new Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85)

    const outlinePass = new OutlinePass(new Vector2(window.innerWidth, window.innerHeight), scene, camera)

    bloomPass.threshold = 0.2
    bloomPass.strength = 0.2
    bloomPass.radius = 0

    const bloomComposer = new EffectComposer(renderer)
    bloomComposer.renderToScreen = false
    bloomComposer.addPass(renderScene)
    bloomComposer.addPass(bloomPass)

    bloomComposer.addPass(outlinePass)
    outlinePass.edgeStrength = 10
    outlinePass.edgeGlow = 0.5
    outlinePass.edgeThickness = 8
    outlinePass.visibleEdgeColor.set(0xffc607)
    outlinePass.hiddenEdgeColor.set(0x000)

    const finalPass = new ShaderPass(
        new ShaderMaterial({
            uniforms: {baseTexture: { value: null},
                bloomTexture: {value: bloomComposer.renderTarget2.texture}
            },
            vertexShader: document.getElementById('vertexshader').textContent,
            fragmentShader: document.getElementById('fragmentshader').textContent,
            defines: {}}),
        'baseTexture'
    )
    finalPass.needsSwap = true

    const finalComposer = new EffectComposer(renderer)
    finalComposer.addPass(renderScene)
    finalComposer.addPass(finalPass)

    return {bloomPass, bloomComposer, finalComposer, outlinePass}
}

export {createEffectComposer}
美颜前 美颜后

🔖 行星的标签

应用 canvas 创立一张图片,再赋值给 Sprite 的材质,这个形式是参考了网上的一个例子。

import {Sprite, SpriteMaterial, TextureLoader} from 'three'

function createSprite(sphereVal) {const canvas = document.createElement('canvas')
    const ctx = canvas.getContext('2d')

    canvas.width = 48
    canvas.height = 30

    ctx.fillStyle = '#fff'
    ctx.font = 'normal 12pt 黑体'
    ctx.textAlign = 'center'
    ctx.fillText(sphereVal.name, 24, 25)

    let url = canvas.toDataURL('image/png')

    const spriteMaterial = new SpriteMaterial({map: new TextureLoader().load(url),
        sizeAttenuation: false
    })
    const sprite = new Sprite(spriteMaterial)
    sprite.scale.set(0.05, 0.03)

    return sprite
}
export {createSprite}

除此以外还有另外一种实现的形式,能够参考 three.js 官网示例 Label。

import {CSS2DObject} from 'three/examples/jsm/renderers/CSS2DRenderer.js'
// 用  CSS2DObject 创立标签
const div = document.createElement('div')
div.className = 'label'
div.textContent = sphereVal.name
const label = new CSS2DObject(div)
label.position.set(1.5 * sphereVal.radius, 0, 0)
label.center.set(0, 1)
sphere.add(label)
label.layers.set(0)

group.add(sprite)

这样看起来更简略一些,然而,那个标签的层级会比拟高,我的行星基本没有方法挡住它!

🪐 行星的静止

这里逻辑就是获取轨迹线 curve 的点,将其所在位置赋值给行星组 group,因为月亮比拟非凡所以要独自设置,这样就实现了公转。那么自转就更容易了只须要扭转 group 的 rotation 就好了。

其实我感觉月亮和地球之间的解决形式有待优化,但我曾经尽力了。

// sphere.js
group.tick = (delta) => {if (sphereVal.orbitalPeriod) {const point = curve.getPointAt(progress)
        const pointBox = curve.getPointAt(progress + sphereVal.orbitalPeriod)
        if (sphereVal.id === 'earth') {earth = point}
        if (sphereVal.id === 'moon') {orbit.position.set(earth.x, earth.y, earth.z)
            group.position.set(earth.x + point.x, earth.y + point.y, earth.z + point.z)
        } else {group.position.set(point.x, point.y, point.z)
            group.lookAt(pointBox.x, pointBox.y, pointBox.z)
        }
        sprite.position.set(group.position.x, group.position.y - (sphereVal.radius + 0.2), group.position.z)
        progress = progress >= 1 - sphereVal.orbitalPeriod * 2 ? 0 : (progress += sphereVal.orbitalPeriod)
    }
    if (sphereVal.rotationPeriod) {group.rotation.y += sphereVal.rotationPeriod}
}

至此,就能够失去这样的场景。

✨ 星光闪闪

没有一个例子能逃过我的手掌心,three.js 官网示例 Points, 用于闪动的星星。

import {
    Vector3,
    Color,
    BufferGeometry,
    BufferAttribute,
    ShaderMaterial,
    Points,
    TextureLoader,
    AdditiveBlending
} from 'three'

function createStar(sphereVal, type) {
    const amount = 5000
    const radius = 400

    const positions = new Float32Array(amount * 3)
    const colors = new Float32Array(amount * 3)
    const sizes = new Float32Array(amount)

    const vertex = new Vector3()
    const color = new Color(0xffffff)

    for (let i = 0; i < amount; i++) {vertex.x = (Math.random() * 2 - 1) * radius
        vertex.y = (Math.random() * 2 - 1) * radius
        vertex.z = (Math.random() * 2 - 1) * radius
        vertex.toArray(positions, i * 3)
        color.setHSL(0.0 + 0.1 * (i / amount), 0.98, 0.38)

        color.toArray(colors, i * 3)

        sizes[i] = 1
    }

    const geometry = new BufferGeometry()
    geometry.setAttribute('position', new BufferAttribute(positions, 3))
    geometry.setAttribute('customColor', new BufferAttribute(colors, 3))
    geometry.setAttribute('size', new BufferAttribute(sizes, 1))

    const material = new ShaderMaterial({
        uniforms: {color: { value: new Color(0xffffff) },
            pointTexture: {value: new TextureLoader().load('assets/textures/spark1.png') }
        },
        vertexShader: document.getElementById('pointVertexshader').textContent,
        fragmentShader: document.getElementById('pointFragmentshader').textContent,

        blending: AdditiveBlending,
        depthTest: false,
        transparent: true
    })

    const stars = new Points(geometry, material)

    const starsGeometry = stars.geometry
    const attributes = starsGeometry.attributes

    stars.tick = (delta) => {const time = Date.now() * 0.005

        for (let i = 0; i < attributes.size.array.length; i++) {attributes.size.array[i] = 1 + 10 * Math.sin(1 * i + time)
        }
        attributes.size.needsUpdate = true
    }

    return stars
}
export {createStar}

🌠 宇宙闪动

这里是往场景中增加红色的雾,然而 fog 并没有继承 Object3D 的 visible 属性,所以批改 density 的值去实现忽明忽暗的成果。我真是个平平无奇的大聪慧,哈哈。

import {FogExp2} from 'three'

let fog

function createFog(scene) {fog = fog ? fog : new FogExp2(0xbd0302, 0.00025)
    scene.fog = fog

    fog.tick = (delta) => {const time = Date.now() * 0.005

        scene.fog && (scene.fog.density = Math.sin(time) * 0.001 + 0.0005)
    }
    return fog
}
export {createFog}

📃 结语

至此,你学废了吗?感兴趣的敌人能够看下 GitHub 上的源码。

📑 参考资料

  1. Discover threejs, three.js 主创之一编写的适宜入门的教程。
  2. Bloom, three.js 官网示例, 用于太阳发光的成果。
  3. Outline, three.js 官网示例,用于太阳的边缘。
  4. Sprite, three.js 官网示例,用于星体的标签
  5. Points, three.js 官网示例,用于闪动的星星。

正文完
 0