2D的雪碧图已经够炫了,那么3D的呢?

13,968 阅读6分钟

output-16_6_11.gif

前言

前2篇文章,分别介绍了dat.gui纹理贴图,老是理论没有实战也是没有什么意思的,今天我们就来着手一个小案例,赶紧实现起来,让你的博客更加炫酷!

这个案例包含了tweenjs动画库的使用,该动画库已在three.js中内置,路径为: examples/jsm/libs/tween.module.js,使用起来也是比较简单。

初始化

老样子,场景、相机、渲染器三要素初始化,并导入需要的插件库,插件库都已在three中内置:

import * as THREE from 'three';
// tween动画库
import TWEEN from 'three/addons/libs/tween.module.js';
//通过轨迹球控件TrackballControls 我们可以实现场景的旋转、缩放、平移等功能
import { TrackballControls } from 'three/addons/controls/TrackballControls.js'
// 雪碧图
import { CSS3DRenderer, CSS3DSprite } from 'three/addons/renderers/CSS3DRenderer.js'

// 定义场景、相机、渲染器
let scene, camera, renderer;

// 初始化
init()
// 渲染
animate();

function init() {
    // 透视相机  远端距离最好设大一点  不然会展示不全
    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000);
    // 相机位置
    camera.position.set(600, 400, 1500);
    // 相机朝向位置
    camera.lookAt(0, 0, 0);

    // 场景
    scene = new THREE.Scene();

    // 渲染画布
    renderer = new CSS3DRenderer();
    renderer.setSize(innerWidth, window.innerHeight);
    document.getElementById('container').appendChild(renderer.domElement);
}

// 渲染
function animate() {
    requestAnimationFrame(animate);
    renderer.render(scene, camera)
}

CSS3DSprite创建521个水球

// 定义小球数量
const particlesTotal = 512;
// 定义位置
const positions = [];
// 定义物体
const objects = []
const image = document.createElement('img');
image.addEventListener('load', () => {
    for (let i = 0; i < particlesTotal; i++) {
        //   cloneNode() 方法可创建指定的节点的精确拷贝
        const object = new CSS3DSprite(image.cloneNode())
        // 随机分布位置 -2000 2000的立方体内
        object.position.x = Math.random() * 4000 - 2000,
            object.position.y = Math.random() * 4000 - 2000,
            object.position.z = Math.random() * 4000 - 2000,
            scene.add(object)

        objects.push(object);
    }
})
image.src = './static/img/sprite.png';

上面的代码中,我们创建了img标签,并使用CSS3DSpriteHTML元素转化为threejs的CSS3精灵模型,类似与转换成了three中的网格,并随机分布在-2000,2000的立方体中。

看下效果:

three04-1.jpg

添加控制器

关于控制器,前面也已经介绍过啦,通过控制器,我们就可以改变相机的位置,观察不同角度的物体。

// 定义控制器
let controls;
controls = new TrackballControls( camera, renderer.domElement );

// 渲染  
function animate() {
    ...
    controls.update();
    ...
}

注意哦,controls.update需要防止在animate中,每帧执行。

有了控制器。我们就可以实现交互啦:

output-15_37_12.gif

让小球按规律放大缩小

让小球按照正弦时间,放大缩小,即有一种闪烁的效果:

const time = performance.now();

for (let i = 0, l = objects.length; i < l; i++) {
    const object = objects[i];
    const scale = Math.sin((Math.floor(object.position.x) + time) * 0.002) * 0.3 + 1;
    object.scale.set(scale, scale, scale);
}

output-15_37_59.gif

让小球生成特定图形

生成矩形,对应的每个小球坐标:

const amount = 8;
const separationCube = 150;
const offset = ( ( amount - 1 ) * separationCube ) / 2;

for ( let i = 0; i < particlesTotal; i ++ ) {

    const x = ( i % amount ) * separationCube;
    const y = Math.floor( ( i / amount ) % amount ) * separationCube;
    const z = Math.floor( i / ( amount * amount ) ) * separationCube;

    positions.push( x - offset, y - offset, z - offset );

}

tween.js使用

const position = {x: 0,y: 0};
;//创建一段tween动画
const tween = new TWEEN.Tween(position)
//经过2秒,position对象的x和y属性分别从零变化为100、50
tween.to({x: 100,y: 50}, 2000);
//tween动画开始执行
tween.start();
// 动画效果  类似annimation
tween.easing()
// 完成时执行的钩子,里面可以继续执行下一个操作
tween.onComplete()

使杂乱的小球变成矩形

import * as THREE from 'three';
// tween动画库
import TWEEN from 'three/addons/libs/tween.module.js';
//通过轨迹球控件TrackballControls 我们可以实现场景的旋转、缩放、平移等功能
import { TrackballControls } from 'three/addons/controls/TrackballControls.js'
// 雪碧图
import { CSS3DRenderer, CSS3DSprite } from 'three/addons/renderers/CSS3DRenderer.js'

// 定义场景、相机、渲染器
let scene, camera, renderer;
// 定义控制器
let controls;

// 定义小球数量
const particlesTotal = 512;
// 定义位置
const positions = [];
// 定义物体
const objects = []
let current = 0;
// 初始化
init()
// 渲染
animate();

function init() {
    // 透视相机  远端距离最好设大一点  不然会展示不全
    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000);
    // 相机位置
    camera.position.set(600, 400, 1500);
    // 相机朝向位置
    camera.lookAt(0, 0, 0);


    scene = new THREE.Scene();

    const image = document.createElement('img');
    image.addEventListener('load', () => {
        for (let i = 0; i < particlesTotal; i++) {
            //   cloneNode() 方法可创建指定的节点的精确拷贝
            const object = new CSS3DSprite(image.cloneNode())
            // 随机分布位置 -2000 2000的立方体内
            object.position.x = Math.random() * 4000 - 2000,
                object.position.y = Math.random() * 4000 - 2000,
                object.position.z = Math.random() * 4000 - 2000,
                scene.add(object)

            objects.push(object);
        }
        transition()
    })
    image.src = './static/img/sprite.png';

    // cube
    const amount = 8;
    const separationCube = 150;
    const offset = ((amount - 1) * separationCube) / 2;

    for (let i = 0; i < particlesTotal; i++) {

        const x = (i % amount) * separationCube;
        const y = Math.floor((i / amount) % amount) * separationCube;
        const z = Math.floor(i / (amount * amount)) * separationCube;

        positions.push(x - offset, y - offset, z - offset);

    }

    // 渲染画布
    renderer = new CSS3DRenderer();
    renderer.setSize(innerWidth, window.innerHeight);
    document.getElementById('container').appendChild(renderer.domElement);

    controls = new TrackballControls(camera, renderer.domElement);

}


// 动画
function transition() {
    const offset = current * particlesTotal * 3;
    const duration = 2000;
    for (let i = 0, j = offset; i < particlesTotal; i++, j += 3) {
        const object = objects[i];
        new TWEEN.Tween(object.position)
            .to({
                x: positions[j],
                y: positions[j + 1],
                z: positions[j + 2]
            }, Math.random() * duration + duration)
            .easing(TWEEN.Easing.Exponential.InOut)
            .start();
    }
    new TWEEN.Tween(this)
        .to({}, duration * 3)
        .onComplete(transition)
        .start();
    current = (current + 1) % 4;
}

// 渲染
function animate() {
    requestAnimationFrame(animate);
    controls.update();
    TWEEN.update();

    // 让小球按照正弦时间,放大缩小
    const time = performance.now();

    for (let i = 0, l = objects.length; i < l; i++) {

        const object = objects[i];
        const scale = Math.sin((Math.floor(object.position.x) + time) * 0.002) * 0.3 + 1;
        object.scale.set(scale, scale, scale);

    }
    renderer.render(scene, camera)
}

杂乱的小球变成多种形态完整代码

限制文件大小啦,没法完全展示,大家自行运行看看吧!

output-15_49_5.gif

<div id="container"></div>
<script type="module">
    import * as THREE from 'three';
    // tween动画库
    import TWEEN from 'three/addons/libs/tween.module.js';
    //通过轨迹球控件TrackballControls 我们可以实现场景的旋转、缩放、平移等功能
    import { TrackballControls } from 'three/addons/controls/TrackballControls.js'
    // 雪碧图
    import { CSS3DRenderer, CSS3DSprite } from 'three/addons/renderers/CSS3DRenderer.js'

    // 定义场景、相机、渲染器
    let scene, camera, renderer;
    // 定义控制器
    let controls;

    // 定义小球数量
    const particlesTotal = 512;
    // 定义位置
    const positions = [];
    // 定义物体
    const objects = []
    let current = 0;
    // 初始化
    init()
    // 渲染
    animate();

    function init() {
        // 透视相机  远端距离最好设大一点  不然会展示不全
        camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000);
        // 相机位置
        camera.position.set(600, 400, 1500);
        // 相机朝向位置
        camera.lookAt(0, 0, 0);


        scene = new THREE.Scene();

        const image = document.createElement('img');
        image.addEventListener('load', () => {
            for (let i = 0; i < particlesTotal; i++) {
                //   cloneNode() 方法可创建指定的节点的精确拷贝
                const object = new CSS3DSprite(image.cloneNode())
                // 随机分布位置 -2000 2000的立方体内
                object.position.x = Math.random() * 4000 - 2000,
                    object.position.y = Math.random() * 4000 - 2000,
                    object.position.z = Math.random() * 4000 - 2000,
                    scene.add(object)

                objects.push(object);
            }
            transition()
        })
        image.src = './static/img/sprite.png';

        // Plane
        const amountX = 16;
        const amountZ = 32;
        const separationPlane = 150;
        const offsetX = ((amountX - 1) * separationPlane) / 2;
        const offsetZ = ((amountZ - 1) * separationPlane) / 2;
        for (let i = 0; i < particlesTotal; i++) {
            const x = (i % amountX) * separationPlane;
            const z = Math.floor(i / amountX) * separationPlane;
            const y = (Math.sin(x * 0.5) + Math.sin(z * 0.5)) * 200;
            positions.push(x - offsetX, y, z - offsetZ);
        }

        // Cube
        const amount = 8;
        const separationCube = 150;
        const offset = ((amount - 1) * separationCube) / 2;
        for (let i = 0; i < particlesTotal; i++) {
            const x = (i % amount) * separationCube;
            const y = Math.floor((i / amount) % amount) * separationCube;
            const z = Math.floor(i / (amount * amount)) * separationCube;
            positions.push(x - offset, y - offset, z - offset);
        }

        // Random
        for (let i = 0; i < particlesTotal; i++) {
            positions.push(
                Math.random() * 4000 - 2000,
                Math.random() * 4000 - 2000,
                Math.random() * 4000 - 2000
            );
        }

        // Sphere
        const radius = 750;
        for (let i = 0; i < particlesTotal; i++) {
            const phi = Math.acos(- 1 + (2 * i) / particlesTotal);
            const theta = Math.sqrt(particlesTotal * Math.PI) * phi;
            positions.push(
                radius * Math.cos(theta) * Math.sin(phi),
                radius * Math.sin(theta) * Math.sin(phi),
                radius * Math.cos(phi)
            );
        }

        // 渲染画布
        renderer = new CSS3DRenderer();
        renderer.setSize(innerWidth, window.innerHeight);
        document.getElementById('container').appendChild(renderer.domElement);
        controls = new TrackballControls(camera, renderer.domElement);
    }


    // 动画
    function transition() {
        const offset = current * particlesTotal * 3;
        const duration = 2000;
        for (let i = 0, j = offset; i < particlesTotal; i++, j += 3) {
            const object = objects[i];
            new TWEEN.Tween(object.position)
                .to({
                    x: positions[j],
                    y: positions[j + 1],
                    z: positions[j + 2]
                }, Math.random() * duration + duration)
                .easing(TWEEN.Easing.Exponential.InOut)
                .start();
        }
        new TWEEN.Tween(this)
            .to({}, duration * 3)
            .onComplete(transition)
            .start();
        current = (current + 1) % 4;
    }

    // 渲染
    function animate() {
        requestAnimationFrame(animate);
        controls.update();
        TWEEN.update();

        // 让小球按照正弦时间,放大缩小
        const time = performance.now();

        for (let i = 0, l = objects.length; i < l; i++) {

            const object = objects[i];
            const scale = Math.sin((Math.floor(object.position.x) + time) * 0.002) * 0.3 + 1;
            object.scale.set(scale, scale, scale);

        }
        renderer.render(scene, camera)
    }

</script>