Привет всем. Сегодня я вам расскажу как легко вы можете создать 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)
OlegGedzjuns
31.07.2021 18:57+1https://playcanvas.com/
Делаем все тоже, но в удобном редакторе и за 5 минут
Sabubu
31.07.2021 19:14Плохая статья. Например, не описано, используется ли аппаратное ускорение, на каких этапах, или он каждый пиксель медленно просчитывает на яваскрипте. Эффективные ли используются алгоритмы для расчета освещения, и сколько источников можно добавить. Без этого трудно понять, что можно, а что нельзя сделать с помощью такой библиотеки.
Tyusha
31.07.2021 20:44Очень часто понять, что нельзя, становится возможно только хорошенько изучив предмет. С такими "нельзя" в случае three.js я столкнулась уже когда глубоко вляпалась в проект. Мало спецов в части того, что находится за пределами обычных: сцена, камера, объект, освещение... До сих пор у меня висят неотвеченные вопросы на stackoverflow.
vgaidadei Автор
01.08.2021 00:24-1Если хочешь, я могу попробовать ответить на твои вопросы. Я писал игру на данной библиотеке и изучал как загружать 3д модели, как создавать тени, текстуры и т.п. Также делал анимацию моделей.
gameplayer55055
02.08.2021 01:06Классная штука чтобы узнать мощь 3д без скачивания тонны говна, и исследований сложных интерфейсов unity или unreal
Советую глянуть на pointerlockcontrols, можно от первого лица с мышкой сделать управление.
Tyusha
Меня очень сильно огорчило качество теней в three.js. Главная проблема, что объект не может отбрасывать тень сам на себя, а только на другие. Поэтому невыпуклые предметы выглядят плохо. Приходится такие объекты разделять на выпуклые части, что офигеть какой геморрой. Но three.js люблю.
vgaidadei Автор
Да, это действительно проблема. Можно кстати в гитхабе написать об этом, хотя там и так 319 открытых вопросов, думаю у них много работы)