Создайте классный персональный 3D-сайт с помощью Three.js (включая исходный код)

WebGL three.js
Создайте классный персональный 3D-сайт с помощью Three.js (включая исходный код)

Оригинальный адрес:Dev.to/Мистер Райан Флойд…

Автор гитхаб:github.com/MrRyanFloyd

Твиттер автора:twitter.com/mrryanfloyd

введение

Персональный сайт — второе резюме программиста. Если у вас крутая личная страница, рейтинг благосклонности интервьюера к вам будет постепенно повышаться.

В период карантина пользуюсьThree.jsа такжеAmmo.jsСделал интерактивную 3D личную веб-страницу.

image.png

Адрес онлайн-просмотра:www.ryan-floyd.com/

Трехмерный мир Three.js

когда я былGoogle ExperimentsПобродив вокруг, я обнаружил, что во многих работах используетсяthree.jsнаписано.

three.jsэто библиотека, упрощающая разработку 3D веб-приложений. Он родился в 2010 году, авторRicardo Cabello (Mr.doob),, имеет более 1300 участников на github и занимает 38-е место по количеству звезд во всех репозиториях.

когда видишьGoogle ExperimentsПоиграв с этими крутыми 3D-эффектами, я решил начать учитьсяthree.js.

Как работает Three.js

image.png

(Компонентная структура 3D-приложения, изображение взято изdiscoverthreejs.com)

Three.jsУпрощает отображение 3D-изображений в браузере, его основа основана наWebGL, который позволяет браузеру рисовать 3D-изображения на холсте с помощью системной видеокарты.

WebGLОн может рисовать только точки, линии и треугольники сам по себе, в то время какThree.jsправильноWebGLОн инкапсулирован, поэтому мы можем легко создавать объекты, текстуры и выполнять 3D-расчеты.

использоватьThree.js, мы добавляем все объекты (objects) в сцену (scene), а затем передаем данные, которые необходимо отрисовать, в рендерер (рендерер), рендерер отвечает за размещение сцены в сцене<canvas>нарисовано на холсте.

image.png(Архитектура приложения Three.js, картинки изthreejsfundamentals.org)

дляThree.jsЯдром приложения является сцена (scene object), представляющая собой граф сцены (scene graph).

В 3D-движке граф сцены представляет собой иерархический древовидный граф, где каждый узел в дереве представляет часть пространства. Эта структура немного похожа на дерево DOM, ноThree.jsСцена (scene) больше похожа на виртуальный DOM, только обновляет и рендерит часть смены сцены. Основой всего этого является Three.jsWebGLRenderer

Объект в сцене также называетсяMesh. существуетThree.jsв мире,MeshКгеометрияGeometry(определяет форму предмета) +материалMaterial(определенный внешний вид объекта) конфигурация.

Еще одним важным элементом сцены является камера.camera, который определяет, какие части сцены нарисованы в каком визуальном эффектеcanvasна холсте.

Затем идет анимация, для достижения анимации рендерер (рендерер) обычно используетrequestAnimationFrame()метод, который отрисовывает обновления сцены 60 раз в секундуcanvasначальство.requestAnimationFrame()Принцип и использование метода могут относиться кMDN.

Следующий пример взят изThree.jsОфициальная документация создает вращающийся 3D-куб.

<html>
  <head>
    <title>My first three.js app</title>
    <style>
      body {
        margin: 0;
      }
      canvas {
        display: block;
      }
    </style>
  </head>
  <body>
    <script src="https://unpkg.com/three@0.119.0/build/three.js"></script>
    <script>
      //创建场景和相机
      var scene = new THREE.Scene();
      var camera = new THREE.PerspectiveCamera(
        75,
        window.innerWidth / window.innerHeight,
        0.1,
        1000
      );

      //创建渲染器,设置尺寸为窗口尺寸,并将渲染后的元素添加到body
      var renderer = new THREE.WebGLRenderer();
      renderer.setSize(window.innerWidth, window.innerHeight);
      document.body.appendChild(renderer.domElement);

      //创建一个Mesh(绿色的3D立方体),并添加到场景中
      var geometry = new THREE.BoxGeometry();
      var material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
      var cube = new THREE.Mesh(geometry, material);
      scene.add(cube);

      //设置照相机的位置
      camera.position.z = 5;

      //浏览器每次渲染的时候更新立方体的旋转角度
      var animate = function () {
        requestAnimationFrame(animate);

        cube.rotation.x += 0.01;
        cube.rotation.y += 0.01;

        renderer.render(scene, camera);
      };

      animate();
    </script>
  </body>
</html>

Эффект следующий:

three-demo.gif

Физический движок Ammo.js

Ammo.js — это прямой порт физического движка Bullet на JavaScript (Bullet Physics — это движок физического моделирования с открытым исходным кодом). У меня нет глубокого понимания того, как физический движок работает под капотом.В двух словах, физический движок создает циклы на основе параметров, которые вы передаете (например, гравитации), и обновляет состояние в каждом цикле, чтобы имитировать естественные физические движение и эффекты столкновения.

Объекты в петле (обычно также твердые тела) с такими физическими свойствами, как сила, масса, инерция, трение и т. д. Каждый цикл, столкновения и взаимодействия обнаруживаются путем постоянной проверки положения, состояния и движения всех объектов. В случае взаимодействия положение объекта будет обновлено в зависимости от прошедшего времени и физических свойств объекта. Ниже приведен фрагмент моего кода, показывающий, как создать цикл физического движка и как добавить его к сфере в Three.js.

//引入库
import * as THREE from "three";
import * as Ammo from "./builds/ammo";
import {scene} from "./resources/world";

//初始化 Ammo.js 物理引擎
Ammo().then((Ammo) => {

    // 创建物理世界
    function createPhysicsWorld() {

        //完全碰撞检测算法
        let collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();

        // 重叠对/碰撞的调度计算
        let dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration);

        // 所有可能碰撞对的宽相位碰撞检测列表
        let overlappingPairCache = new Ammo.btDbvtBroadphase();

        // 使物体正确地交互,考虑重力、力、碰撞等
        let constraintSolver = new Ammo.btSequentialImpulseConstraintSolver();

        // 根据这些参数创建物理世界。 参考bullet physics文档
        let physicsWorld = new Ammo.btDiscreteDynamicsWorld(
            dispatcher,
            overlappingPairCache,
            constraintSolver,
            collisionConfiguration
        );

        // 添加重力
        physicsWorld.setGravity(new Ammo.btVector3(0, -9.8, 0));
    }

    //创建球体
    function createBall(){
        //球体参数
        let pos = {x: 0, y: 0, z: 0};
        let radius = 2;
        let quat = {x: 0, y: 0, z: 0, w: 1};
        let mass = 3;

        //three.js相关代码

        //创建球体并添加到场景中
        let ball = new THREE.Mesh(new THREE.SphereBufferGeometry(radius), new THREE.MeshStandardMaterial({color: 0xffffff}));
        ball.position.set(pos.x, pos.y, pos.z);
        scene.add(ball);

        //Ammo.js相关代码

        //设置位置和旋转
        let transform = new Ammo.btTransform();
        transform.setOrigin(new Ammo.btVector3(pos.x, pos.y, pos.z));
        transform.setRotation(
            new Ammo.btQuaternion(quat.x, quat.y, quat.z, quat.w)
        );

        //设置物体运动
        let motionState = new Ammo.btDefaultMotionState(transform);

        //设置碰撞边界框
        let collisionShape = new Ammo.btSphereShape(radius);
        collisionShape.setMargin(0.05);

        //设置惯性
        let localInertia = new Ammo.btVector3(0, 0, 0);
        collisionShape.calculateLocalInertia(mass, localInertia);

        //生成创建刚体(物体)的结构信息
        let rigidBodyStructure = new Ammo.btRigidBodyConstructionInfo(
            mass,
            motionState,
            collisionShape,
            localInertia
        );

        //基于上面的结构信息创建物体
        let body = new Ammo.btRigidBody(rigidBodyStructure);

        //当物体运动时,为其添加摩擦力
        body.setFriction(10);
        body.setRollingFriction(10);

        // 将物体添加到物理世界,这样Ammo.js引擎才能不断更新物体的状态
        physicsWorld.addRigidBody(body);
    }

    createPhysicsWorld();
    createBall()
}

движение и взаимодействие

В физическом мире, моделируемом Ammo.js, взаимодействия вычисляются на основе свойств и сил.

У каждого объекта есть свойство ограничивающей рамки, и физический движок использует эту ограничивающую рамку для определения положения объекта.

После проверки ограничивающих рамок всех объектов в каждом цикле анимации, если ограничивающие рамки любых двух объектов находятся в одном и том же положении, движок запишет «столкновение» и соответствующим образом обновит объекты.Для твердых тел это означает, что два объекта не могут находиться в одном и том же положении.

Ниже приведен фрагмент кода, показывающий, как обновляются цикл рендеринга и физика мира.

//渲染框架
function renderFrame() {

    //记录上一次渲染的时间
    let deltaTime = clock.getDelta();

    //基于用户输入,计算球会受到的力和产生的速度
    moveBall();

    //根据时间更新物理世界状态
    updatePhysics(deltaTime);

    //进行渲染
    renderer.render(scene, camera);

    // 循环
    requestAnimationFrame(renderFrame);
}

//更新物理世界状态的方法定义
function updatePhysics(deltaTime) {

    physicsWorld.stepSimulation(deltaTime, 10);

    //遍历“刚体”列表,并更新物理世界中的所有刚体状态
    for (let i = 0; i < rigidBodies.length; i++) {

        //变量定义:three.js需要的meshObject,和ammo.js需要的ammoObject
        let meshObject = rigidBodies[i];
        let ammoObject = meshObject.userData.physicsBody;

        //获取物体当前运动状态
        let objectMotion = ammoObject.getMotionState();

        //如果物体正在移动,则获取物体的当前位置和旋转信息
        if (objectMotion) {
            objectMotion.getWorldTransform(transform);
            let mPosition = transform.getOrigin();
            let mQuaternion = transform.getRotation();

            // 更新物体的位置和旋转状态
            meshObject.position.set(mPosition.x(), mPosition.y(), mPosition.z());
            meshObject.quaternion.set(mQuaternion.x(), mQuaternion.y(), mQuaternion.z(), mQuaternion.w());
        }
    }
}

пользовательский ввод

Мы хотим, чтобы пользователи могли перемещать сферу в приложении как на настольных, так и на мобильных устройствах с сенсорным экраном.

Для событий клавиатуры, когда нажата клавиша со стрелкой, добавьте силу в соответствующем направлении к сфере, прослушивая события «keydown» и «keyup».

Для сенсорных экранов на экране создается джойстик-контроллер. Затем мы добавляем прослушиватели событий «touchstart», «touchmove» и «touchend» к элементу div (контроллеру), используемому для управления.

Контроллер отслеживает начальные, текущие и конечные координаты движения пальца пользователя, а затем соответствующим образом обновляет силы мяча при каждом рендеринге.

Ниже приведен лишь фрагмент кода контроллера, чтобы показать некоторые общие понятия. Полный код можно получить по адресу исходного кода в нижней части этой статьи.

// 在坐标平面上保持对当前球体运动的跟踪
let moveDirection = { left: 0, right: 0, forward: 0, back: 0 };

//控制器div在屏幕上的位置坐标
let coordinates = { x: 0, y: 0 };

//保存触摸事件的起始坐标的变量
let dragStart = null;

//创建控制器div元素
const stick = document.createElement("div");

//监听用户触摸点的移动
function handleMove(event) {
    //没有移动,返回
    if (dragStart === null) return;

    //有移动,获取新的触摸点的x、y坐标
    if (event.changedTouches) {
        event.clientX = event.changedTouches[0].clientX;
        event.clientY = event.changedTouches[0].clientY;
    }

    //根据触摸点的移动,计算出控制器div的实时坐标
    const xDiff = event.clientX - dragStart.x;
    const yDiff = event.clientY - dragStart.y;
    const angle = Math.atan2(yDiff, xDiff);
    const distance = Math.min(maxDiff, Math.hypot(xDiff, yDiff));
    const xNew = distance * Math.cos(angle);
    const yNew = distance * Math.sin(angle);
    coordinates = { x: xNew, y: yNew };

    //根据实时坐标更新样式
    stick.style.transform = `translate3d(${xNew}px, ${yNew}px, 0px)`;

    //根据坐标计算出球的运动方向
    touchEvent(coordinates);
}

//根据用户的触摸点移动坐标计算出球的运动方向
function touchEvent(coordinates) {

    // 向右运动
    if (coordinates.x > 30) {
        moveDirection.right = 1;
        moveDirection.left = 0;
    // 向左运动
    } else if (coordinates.x < -30) {
        moveDirection.left = 1;
        moveDirection.right = 0;
    } else {
        moveDirection.right = 0;
        moveDirection.left = 0;
    }

    //向前运动
    if (coordinates.y > 30) {
        moveDirection.back = 1;
        moveDirection.forward = 0;
    //向后运动
    } else if (coordinates.y < -30) {
        moveDirection.forward = 1;
        moveDirection.back = 0;
    } else {
        moveDirection.forward = 0;
        moveDirection.back = 0;
    }
}

Вот эффект от использования джойстика:

image.png

Эпилог

Сейчас мы создали模拟真实物理世界的、可交互的3D应用всех инструментов. Используйте свое воображение и желание творить красоту, чтобы создать собственное 3D-приложение! В эпоху Интернета любой человек учится всю жизнь.

Исходный код этого проекта можно найти на моем Github по адресу:GitHub.com/г-н Райан Флойд…

Если у вас есть какие-либо отзывы или вопросы, пожалуйста, оставьте сообщение или черезLinkedInСвяжитесь со мной!