Ссылка на обучающее видео

Привет всем. Сегодня я вам расскажу как легко вы можете создать 3d игру прямо в вашем браузере и сделать вы сможете это очень быстро, примерно за минут 30.

Three.js

Это библиотека которая позволяет создавать 3д графику, она довольно проста и мощна. Мы будем использовать только её и больше никаких других фреймворков. Чтобы её подключить вам нужно скачать файл из этого рипозитория в папке build/three.js. Я качаю файл three.min.js (точно такая же, но весит меньше) и просто подключаю.

<html>
  <head></head>

  <body style="margin:0px;">
    <script src="three.min.js"></script>

    <script>
    	//тут пишем код
    </script>
   </body>
</html>

Обратите внимание что отступы у body стоят нулевые, это нужно чтобы экран не съезжал вправо.

scene, camera и renderer

Создание графики в данной библиотеке основано на трёх столпах. Первое Scene это весь наш мир, вся информация, которая содержится о нём. Второе Camera это объект, который обозначает с какого ракурса мы будем смотреть на этот мир. Третье Render, объект который обрабатывает камеру и сцену и выдаёт результат на экран. Давайте создадим эти три объекта для начала.

var scene = new THREE.Scene();

//камера содержит 4 аргумента: угол обзора в градусах, отношение сторон экрана, и растояние обработки минимальное и масимальное
var camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
camera.lookAt(new THREE.Vector3(0, 0, 0)); //указываем куда будет смотреть камера
scene.add(camera);

var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight); //указываем размеры рендера, которые соответсвую нашей камере
document.body.appendChild(renderer.domElement); //добавляем DOM элемент рендара в html код

Также нам нужна функция анимации, которая будет покадрово обновлять сцену.

function animate() {
  requestAnimationFrame(animate);//вызываем функцию при каждом обновлении кадра 
  renderer.render(scene, camera);//обновляем рендер
}
animate();//вызываем первый раз функцию

Автообновление размеров экрана

Размеры окна браузера могут меняться нам нужно поставить событие на изменение размеров и обновлять размеры камеры и рендера.

function setSize() {
    renderer.setSize(window.innerWidth, window.innerHeight);// устанавливаем размеры рендера
    camera.apect = window.innerWidth/window.innerHeight;//устанаваливает отношение сторон камеры
    camera.updateProjectionMatrix();//обновляем камеру
}
window.addEventListener("resize", setSize);//создаем событие на изменение размеров окна браущера

Освещение

Чтобы было что-товидно нам нужен свет. Я создам два типа света, первый свет Ambient, которая распределён равномерно по всей сцене и как бы имитирует солнце. И второй Point (точечный) имитирует свет лампочки.

var light = new THREE.AmbientLight(0xffffff, 0.2);//создаем равномерный свет белого цвета с мощностью 0.2
scene.add(light);//добавляем в сцену равномерный свет
var light = new THREE.SpotLight(0xffffff);//создаем точечный свет белогоцвета
light.position.set(0, 5, 0);//задаем координаты точечного света
scene.add(light);//добавляем в сцену точечный свет

Создаём первый объект

Объекты в three.js добавляется путём задание геометрии объекта и материала. Для начала создадим пол коричневого цвета, это будет просто обычный параллелограмм. Поэтому геометрию возьмём BOX и просто растянем его под размеры параллелограмма, а материал возьмём Phong чтобы хорошо было видно отражение точечного света.

//создаём пол с геометрией куба и Phong материалом коричневого цвета, side указываем с обоих сторон материал
var floor = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshPhongMaterial({color: 0xad7600, side: THREE.DoubleSide}));
floor.scale.set(5, 0.1, 10); //устанавливаем нужные размеры
floor.position.y = -0.5; //указываем координату чтобы он был под ногами
scene.add(floor); // добавляем в сцену

Смотрим что получилось:

Как мы видим у нас под ногами пол коричневого цвета.

Управление

Теперь нам нужно осуществить управление. Сделаем его с помощью кнопок WASD. W и S будем ходить вперёд назад. A и D будем поворачивать камеру по вертикальной оси. Сначала будем отслеживать нажатие кнопок WASD и записывать в переменную какая кнопка нажата в данный момент

var keys = { w: false, a: false, s: false, d: false }; //переменная указывает какие кнопки нажаты сейчас
function keyDown(e) {
  //если нажали какую-то кнопку запишем в переменную это
  if (e.code == "KeyW") {
    keys.w = true;
  } else if (e.code == "KeyA") {
    keys.a = true;
  } else if (e.code == "KeyS") {
    keys.s = true;
  } else if (e.code == "KeyD") {
    keys.d = true;
  }
}
function keyUp(e) {
  //если отпустили кукую-то кнопку запишем в переменную это
  if (e.code == "KeyW") {
    keys.w = false;
  } else if (e.code == "KeyA") {
    keys.a = false;
  } else if (e.code == "KeyS") {
    keys.s = false;
  } else if (e.code == "KeyD") {
    keys.d = false;
  }
}
window.addEventListener("keydown", keyDown);//событие нажатие кнопки
window.addEventListener("keyup", keyUp);//событие отпускание кнопки

Теперь зная какая кнопка нажата в данный момент, мы можем в нашу функцию анимации добавлять координаты и поворот камеры. Вот как мы изменяем функцию animate

function animate() {
  var speed = 0.05;//скорость пердвежение
  if (keys.w && !keys.s) {//если нажата w и не нажата s
    //идем вперет в зависиости от поворота камеры
    position[0] += speed*Math.sin(rotate);
    position[1] += speed*Math.cos(rotate);
  }
  if (!keys.w && keys.s) {//если нажата s и не нажата w
    //идем назад в зависимости от поворота камеры
    position[0] -= speed*Math.sin(rotate);
    position[1] -= speed*Math.cos(rotate);
  }
  if (keys.a && !keys.d) {//если нажата a и не нажата d
    rotate += speed; //поворачиваем влево
  }
  if (!keys.a && keys.d) {//если нажата d и не нажата a
    rotate -= speed; //поворачиваем вправо
  }
  // добавляем ограничения по координатам чтобы мы не могли уйти бесконечно далеко
  if (position[0] > 2.3) { position[0] = 2.3 }
  if (position[0] < -2.3) { position[0] = -2.3 }
  if (position[1] > 4.8) { position[1] = 4.8 }
  if (position[1] < -4.8) { position[1] = -4.8 }
  //устанаваливаем координаты камеры
  camera.position.x = position[0];
  camera.position.z = position[1];
  //устанавливаем точку куда должна смотреть камера
  camera.lookAt(position[0]+5*Math.sin(rotate), 0, position[1]+5*Math.cos(rotate))
  //эти две строчки уже и так были в той функции
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}

Теперь мы можем передивигаться.

Заканчиваем

Теперь же просто для завершённости проекта добавим еще стены и потолок точно по такому же принципу как с полом и покрасим стены в зелёный и потолок в сиреневвый.

var wall1 = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshPhongMaterial({color: 0x00870e, side: THREE.DoubleSide}));
wall1.scale.set(5, 1, 0.1);
wall1.position.z = 5;
scene.add(wall1);

var wall2 = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshPhongMaterial({color: 0x00870e, side: THREE.DoubleSide}));
wall2.scale.set(5, 1, 0.1);
wall2.position.z = -5;
scene.add(wall2);

var wall3 = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshPhongMaterial({color: 0x00870e, side: THREE.DoubleSide}));
wall3.scale.set(0.1, 1, 10);
wall3.position.x = 2.5;
scene.add(wall3);

var wall4 = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshPhongMaterial({color: 0x00870e, side: THREE.DoubleSide}));
wall4.scale.set(0.1, 1, 10);
wall4.position.x = -2.5;
scene.add(wall4);

var ceil = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshPhongMaterial({color: 0xff00aa, side: THREE.DoubleSide}));
ceil.scale.set(5, 0.1, 10);
ceil.position.y = 0.5;
scene.add(ceil);

Вот что у нас получилось:

Конечный код

Вот полностью код, который мы написали:

<html>
  <head></head>

  <body style="margin:0px;">
    <script src="three.min.js"></script>

    <script>
      var scene = new THREE.Scene();

      var camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
      camera.lookAt(new THREE.Vector3(0, 0, 0));
      scene.add(camera);

      var renderer = new THREE.WebGLRenderer();
      renderer.setSize(window.innerWidth, window.innerHeight);
      document.body.appendChild(renderer.domElement);

      function setSize() {
          renderer.setSize(window.innerWidth, window.innerHeight);
          camera.apect = window.innerWidth/window.innerHeight;
          camera.updateProjectionMatrix();
      }
      window.addEventListener("resize", setSize);

      var floor = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshPhongMaterial({color: 0xad7600, side: THREE.DoubleSide}));
      floor.scale.set(5, 0.1, 10);
      floor.position.y = -0.5;
      scene.add(floor);

      var wall1 = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshPhongMaterial({color: 0x00870e, side: THREE.DoubleSide}));
      wall1.scale.set(5, 1, 0.1);
      wall1.position.z = 5;
      scene.add(wall1);

      var wall2 = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshPhongMaterial({color: 0x00870e, side: THREE.DoubleSide}));
      wall2.scale.set(5, 1, 0.1);
      wall2.position.z = -5;
      scene.add(wall2);

      var wall3 = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshPhongMaterial({color: 0x00870e, side: THREE.DoubleSide}));
      wall3.scale.set(0.1, 1, 10);
      wall3.position.x = 2.5;
      scene.add(wall3);

      var wall4 = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshPhongMaterial({color: 0x00870e, side: THREE.DoubleSide}));
      wall4.scale.set(0.1, 1, 10);
      wall4.position.x = -2.5;
      scene.add(wall4);

      var ceil = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshPhongMaterial({color: 0xff00aa, side: THREE.DoubleSide}));
      ceil.scale.set(5, 0.1, 10);
      ceil.position.y = 0.5;
      scene.add(ceil);

      var light = new THREE.AmbientLight(0xffffff, 0.2);
      scene.add(light);
      var light = new THREE.SpotLight(0xffffff);
      light.position.set(0, 5, 0);
      scene.add(light);

      var keys = { w: false, a: false, s: false, d: false };
      function keyDown(e) {
        if (e.code == "KeyW") {
          keys.w = true;
        } else if (e.code == "KeyA") {
          keys.a = true;
        } else if (e.code == "KeyS") {
          keys.s = true;
        } else if (e.code == "KeyD") {
          keys.d = true;
        }
      }
      function keyUp(e) {
        if (e.code == "KeyW") {
          keys.w = false;
        } else if (e.code == "KeyA") {
          keys.a = false;
        } else if (e.code == "KeyS") {
          keys.s = false;
        } else if (e.code == "KeyD") {
          keys.d = false;
        }
      }
      window.addEventListener("keydown", keyDown);
      window.addEventListener("keyup", keyUp);

      function animate() {
        var speed = 0.05;
        if (keys.w && !keys.s) {
          position[0] += speed*Math.sin(rotate);
          position[1] += speed*Math.cos(rotate);
        }
        if (!keys.w && keys.s) {
          position[0] -= speed*Math.sin(rotate);
          position[1] -= speed*Math.cos(rotate);
        }
        if (keys.a && !keys.d) {
          rotate += speed;
        }
        if (!keys.a && keys.d) {
          rotate -= speed;
        }
        if (position[0] > 2.3) { position[0] = 2.3 }
        if (position[0] < -2.3) { position[0] = -2.3 }
        if (position[1] > 4.8) { position[1] = 4.8 }
        if (position[1] < -4.8) { position[1] = -4.8 }
        camera.position.x = position[0];
        camera.position.z = position[1];
        camera.lookAt(position[0]+5*Math.sin(rotate), 0, position[1]+5*Math.cos(rotate));
        requestAnimationFrame(animate);
        renderer.render(scene, camera);
      }
      animate();
    </script>
   </body>
</html>

Заключение

Теперь вы знаете как легко создать 3д графику в браузере, даже если вы не хотели создавать игру. 3д графика может пригодиться в проектирование и еще много где. Также в данной библиотеке можно добавлять текстуры, 3д модели и прочее, можно еще сделать управление с помощью мыши и клавиатуры как в шутерах, например. Если вы хотите узнать как это сделать, я могу создать отдельные статьи про это если в голосовании будет много желающих, я сделаю такие статьи. В данной статье я показал лишь основы, чтобы вы знали с чего начать. Спасибо за внимание

Ссылка на обучающее видео

Комментарии (9)


  1. Tyusha
    31.07.2021 17:57
    +1

    Меня очень сильно огорчило качество теней в three.js. Главная проблема, что объект не может отбрасывать тень сам на себя, а только на другие. Поэтому невыпуклые предметы выглядят плохо. Приходится такие объекты разделять на выпуклые части, что офигеть какой геморрой. Но three.js люблю.


    1. vgaidadei Автор
      31.07.2021 18:59

      Да, это действительно проблема. Можно кстати в гитхабе написать об этом, хотя там и так 319 открытых вопросов, думаю у них много работы)


  1. OlegGedzjuns
    31.07.2021 18:57
    +1

    https://playcanvas.com/
    Делаем все тоже, но в удобном редакторе и за 5 минут


    1. vgaidadei Автор
      31.07.2021 19:01
      -1

      Да, это тоже вариант, но многие предпочитают использовать threejs 73 тыс лайков на гитхабе.


      1. fsmorygo
        31.07.2021 20:43

        Взгляните на BabylonJS, достойная альтернатива


  1. Sabubu
    31.07.2021 19:14

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


    1. Tyusha
      31.07.2021 20:44

      Очень часто понять, что нельзя, становится возможно только хорошенько изучив предмет. С такими "нельзя" в случае three.js я столкнулась уже когда глубоко вляпалась в проект. Мало спецов в части того, что находится за пределами обычных: сцена, камера, объект, освещение... До сих пор у меня висят неотвеченные вопросы на stackoverflow.


      1. vgaidadei Автор
        01.08.2021 00:24
        -1

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


  1. gameplayer55055
    02.08.2021 01:06

    Классная штука чтобы узнать мощь 3д без скачивания тонны говна, и исследований сложных интерфейсов unity или unreal

    Советую глянуть на pointerlockcontrols, можно от первого лица с мышкой сделать управление.