Розробка прототипа ігрового рушія: проблеми, виклики та прийняті рішення
Вітання, мене звати Віталій Гикавчук. Я займаюся 3D-друком на принтерах технології DLP, цікавлюся 3D-графікою та розробкою ігор. У своєму блозі хочу розповісти про свій досвід створення пет-проєкту рушія та роботу з вже готовими рішеннями — Unreal Engine і XRay Engine. Я хотів розібратися в роботі рушіїв «під капотом» і дізнатися, як таке диво як ігри відбувається на екрані. Можливо завдяки цьому матеріалу хтось з вас отримає відповіді на питання, які цікавлять, або ж мотивацію для себе.
Час, витрачений з користю
Я граю в ігри з дитинства, проте не розумів як вони влаштовані. Проходячи платформери, я думав, що за спрайтами є інша сторона, тому не уявляв як все це оживає. Пʼять років тому я писав диплом, в якому мали бути моделі спроєктованих робіт. Щоб реалізувати їх, я використовував програми для моделювання, і часто мені не вистачало достатньо зрозумілого функціоналу або ж спрощеного інтерфейсу. Так би мовити, однієї чарівної кнопки. Це було важко, тому що ніхто не міг показати, як краще зробити.
Як великий фанат S.T.A.L.K.E.R., я проходив ігри серії десятки разів і постійно знаходив нові баги або ж вирізаний контент в модах. Я хотів розібратися, чому так стається; як програються звуки; чому на різних налаштуваннях графіка працює по-різному; як влаштовані скрипти; як взагалі створили гру. Після короткого пошуку я знайшов назву рушія — XRay Engine. Я почав читати про нього — як на ньому працюють, історію створення тощо. Завантаживши SDK, я спробував розібратися в технології. Помалу я почав розуміти, як гра виглядає зсередини, і що включає інтерфейс рушія.
Далі я почав цікавитися, які ще готові рушії існують, і згадав про Unreal Engine. Я був враженим тим, що його можна безплатно завантажити та почати користуватися. Запустивши UE, я побачив готову сцену, де вже можна переміщатися як у готовій грі. Інтерфейс містив дуже багато кнопок, які одразу додавали в середовище щось нове. Тоді я вирішив пройти курс з Unreal Engine. В результаті мені вдалося створити свій невеликий шутер з кількома видами зброї, примітивним ШІ, анімаціями перезаряджання та простою графікою старих-добрих
З того моменту я почав цікавитися, як зробити красиву графіку, чітку систему часток, приємне освітлення та гарний дизайн рівнів. Розібравшись з редактором матеріалів, я почав розуміти, що на моделі накладається не одна текстура, а цілих пʼять, і кожна налаштовується додатково. Зрештою графіка стала ближчою до більш сучасної, але все ж не було бажаного результату. Я налаштовував постобробку, освітлення, але якось відчував себе із зав’язаними руками, хоча рушій має безліч можливостей. Коли відбувся реліз UE5, я вирішив спробувати його. Так, у пʼятій версії деякі моменти покращилися, тому що можна було детальніше налаштувати освітлення, але я досі відчував себе так, ніби мені не дозволяли налаштувати графіку так, щоб раділо моє око.
Відтоді я почав задумуватися, чому б не спробувати написати власне рішення, адже я трохи розбирався в рушієві й думав, що ніяких тортур не буде. Відправною точкою до цього кроку стало те, що я ніяк не міг налаштувати глобальне освітлення в сцені. При переміщенні актора в кут все ставало занадто темним і ніяк не виправлялося. Та й налаштувань було дуже багато. Я не міг зрозуміти, що краще обрати для себе.
Перехід до написання власного рушія, або як я вирішив «перевернути айсберг»
Почну розповідь з того, що я був повним дилетантом в цьому напрямку. Дуже багато чув від інших людей, що загалом почати вивчати С++ — це як «вистрілити в коліно». Дуже важко розібратися в ній, і це відбирало мотивацію, але я дуже горів бажанням. Я уявлення не мав, з чого складається рушій, які у ньому компоненти, і за що вони відповідають. Я знайшов дві книги про створення ігор — «Програмування ігор на Windows» від Андре Ламота та «Архітектура ігрового рушія» від Джейсона Грегорі. А ще я передивлявся багато відео про те, як інші люди створюють рушії. Це надихало, адже навіть для мого досвіду задача не була неможливою.
Першим питанням стало те, який API використовувати. На вибір було три варіанти — Vulkan, DirectX і OpenGl. Я обрав останній, адже особисто для мене в ньому найнижчий поріг входую Багато чого він виконує автоматично, хоча підтримка розробником зупинена. Знайшовши форум, на якому були готові приклади різних компонентів рендера, я спробував відтворити їх. Я почав підключати бібліотеки, такі як OpenGl, GLFW, GLAD і GLM. Це був мій перший подібний досвід — як все це все під’єднати до VisualStudio та зібрати проєкт без помилок компілятора.
Після тривалих налаштувань мені вдалося запустити вікно Windows з буфером кадрів, очищеним до вказаного кольору. Далі я спробував візуалізувати плаский полігон і заповнити його різними кольорами. Саме тоді я почав працювати з шейдерами й усвідомив їхню потужність. В той момент я відчув, що тепер можна робити що завгодно, а обмеження тільки в знаннях, можливостях ПК та часі.
Коротко про бібліотеки
OpenGL (Open Graphics Library)
OpenGL — це кросплатформний API для рендерингу 2D та 3D-графіки. Він надає низькорівневі інструменти для роботи з графічним апаратним забезпеченням, дозволяючи створювати графічні сцени з використанням шейдерів, текстур, та освітлення.GLFW (Graphics Library Framework)
GLFW — це бібліотека для роботи з вікнами, контекстами OpenGL та введенням. Вона дозволяє створювати вікна, керувати буфером кадрів і взаємодіяти з клавіатурою, мишею чи геймпадом. Дуже легко інтегрується з OpenG та Vulkan.GLAD (GL Loader Generator)
GLAD — це лоадер функцій OpenGL. OpenGL функціонує через драйвери, тому для доступу до нових можливостей графічного API потрібна бібліотека, яка завантажує ці функції. GLAD автоматично генерує відповідний код для підключення функцій.GLM (OpenGL Mathematics)
GLM — це бібліотека для роботи з математикою — векторами, матрицями, перетвореннями тощо, — що часто використовується в графіці. Вона базується на стандарті GLSL (мова шейдерів OpenGL) і надає зручні функції для роботи з 3D-геометрією.
Весь вільний час я присвятив своєму захопленню, тому перші шість місяців, протягом яких тривала розробка дебютної версії проєкту, у мене все виходило та працювало. Завантажувалися моделі та текстури, створено примітивну систему часток, освітлення за моделлю Фонга, прямий рендерінг, скайбокс та звичайну постобробку з ефектом Bloom і чимось, далеко схожим на GodRays. Були також тіні, де використовувалися тільки 2 текстури (Albedo та Normal), які одразу при завантаженні моделі накладалися на неї
Я почувався дуже окриленим завдяки своїм досягненням та результатам, тому що все, написане моїми руками, функціонувало. Тому я хотів розширити цей проєкт, поділивши його на компоненти dlll. Однак я зіткнувся з великою проблемою — контекст OpenGl працює тільки на одному потоці. Через це поділ завантаження моделей та текстур паралельно з роботою рендера не працював.


Все ж за потрібною архітектурою довелося створити окремі компоненти dll, але з певними обмеженнями передачі даних та контексту OpenGL. Пізніше я таки спробував варіант командних буферів, який працював правильно.
Компонент Core
Він виконував роль центрального вузла рушія, що забезпечував низку важливих функцій. Була реалізована система динамічного пулу пам’яті, яка:
- Виділяла пам’ять при створенні нових екземплярів класів.
- Вела облік загального використання пам’яті рушієм.
- Показувала використання пам’яті кожним окремим об’єктом.
Цей підхід дозволяв оптимізувати роботу рушія, а також спростити пошук «витоків» пам’яті та інших проблем, пов’язаних із її використанням. Проте через відсутність досвіду управління пам’яттю він працював не зовсім досконало.
Я також реалізував Інтегровану багаторівнева система логування, яка підтримувала наступні рівні:
- ERROR — критичні помилки;
- WARNING — попередження;
- INFO — загальна інформація про роботу рушія;
- DEBUG — налагоджувальна інформація;
- INPUT — логування подій введення;
- CLEAR — повідомлення про очищення даних;
- DESTROY — звіти про видалення об’єктів;
- TRACE — відстеження виконання коду;
- CRITICAL — найважливіші критичні події;
- ASSERTION — перевірки умов;
- VERBOSE — деталізована інформація.
Логи відображалися у консолі Windows, а також дублювалися в консоль редактора рушія. Усі повідомлення автоматично зберігалися в файл логів (Log file), що дозволяло переглядати історію подій і аналізувати причини крашів. Приклад виклику логу наведений нижче:
Log.Error("ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ", NULL, __FILE__, __LINE__); Log.Info("Checking mesh: " + mesh->getName() + " against loaded name: " + meshName);
Центральним елементом роботи з об’єктами став клас, який:
- Містив методи для трансформацій — переміщення, масштабування та обертання об’єктів у сцені.
- Автоматично присвоював кожному новому об’єкту унікальний ID, що спрощувало їх ідентифікацію та управління.
- Забезпечував базову функціональність для роботи з об’єктами, роблячи їх інтеграцію в сцену легкою і зрозумілою.
Для зручності налаштування рушія я створив клас менеджера конфігурацій. Його основні можливості:
- Завантаження та збереження конфігураційних файлів.
- Динамічне змінення параметрів без потреби у перекомпіляції рушія.
Приклади параметрів, які зберігалися:
- шляхи до шейдерів;
- посилання на текстури, збережені через редактор матеріалів;
- налаштування системи часток;
- інші важливі параметри рушія.
Конфігураційні файли створювалися та оновлювалися автоматично, забезпечуючи гнучкість у роботі з рушієм. Окремий простір імен Utils містив корисні методи, які допомагали у вирішенні базових задач. Серед них конвертація рядків у потрібні формати та генерація випадкових чисел — float у заданому діапазоні bool або int для різних сценаріїв. На початкових етапах розробки ці інструменти забезпечували достатній функціонал для роботи з базовими задачами, залишаючи простір для подальшого розширення.
Компонент Engine
Компонент Engine виконував роль «центрального мозку» рушія, забезпечуючи взаємодію між усіма іншими. Він виконував такі основні функції:
- Відповідав за ініціалізацію, запуск і координацію роботи всіх інших компонентів рушія.
- Передавав дані між модулями, забезпечуючи їх синхронізацію.
- Забезпечував послідовну роботу всіх систем рушія. Сюди входять рендеринг, завантаження ресурсів, логування, обробка подій.
Був реалізований головний цикл програми, який виконував усі ключові операції:
- сприймання введення (клавіатура, миша, геймпад тощо);
- оновлення логіки гри.
- рендеринг сцени.
- виклик системи подій.
Головний цикл був оптимізований для стабільної роботи та забезпечував плавну взаємодію між всіма системами рушія. Події введення передавалися до відповідних систем, наприклад, управління камерою, рух персонажа, взаємодія з UI тощо. Для обробки введення використовувалася інтеграція з бібліотекою GLFW, яка надавала зручний доступ до апаратних подій.
У рамках Engine був створений клас системи подій, який дозволяв налаштовувати та обробляти події всередині рушія. Його основні можливості:
- Створення, реєстрація та обробка подій, наприклад, натискання клавіш, зміна положення миші, завантаження ресурсів тощо.
- Передача подій до відповідних компонентів у реальному часі.
Engine напряму взаємодіяв із рендер-системою: Він запускав процес візуалізації; передавав необхідні дані для оновлення сцени; забезпечував взаємодію між рендером і іншими модулями рушія, наприклад, трансформації об’єктів. Роль компонента була ключовою частиною архітектури, яка об’єднувала всі модулі в єдину систему.
Одна з ключових особливостей компонента — це архітектура, яка дозволяла уникнути прямих залежностей між бібліотеками. Це було необхідно через обмеження багатьох бібліотек, які не підтримують прямі зв’язки між собою і можуть працювати лише через чітко визначені інтерфейси.
Чому це важливо?
Пряма залежність між бібліотеками може викликати серйозні проблеми. Перша — неможливість скомпілювати проєкт через конфлікти залежностей. Друга — підтримувати код стане важче. А третя — ускладнення розширення рушія, наприклад, додавання нових компонентів чи заміна старих.
Реалізація такої архітектури без попереднього досвіду стала складною задачею. Спочатку важко було зрозуміти, як створювати та використовувати інтерфейси. Потрібно було навчитися уникати прямих посилань між модулями й забезпечувати взаємодію через абстракції. Значну частину часу зайняло налагодження роботи окремих бібліотек, які не могли коректно працювати разом через конфлікти. Але через відсутність досвіду довелося відмовитися від інтерфейсів і створити один компонент, який управляв усіма іншими.
Компонент Render
Це центральний компонент рушія, який виконував усі задачі, пов’язані з графікою, управлінням редактором, обробкою текстур, освітленням і колізіями. Він відповідав за створення графічного вікна, ініціалізацію контексту OpenGL і реалізацію ключових функцій візуалізації. Хоча його налаштування й розробка викликали значні складнощі, саме цей компонент став основою рушія. Нижче я опишу основні класи Render.
За допомогою бібліотеки GLFW створювалося графічне вікно для відображення сцени, ініціалізувався контекст OpenGL, необхідний для роботи з графічним API, а буфери кадрів налаштовувалися для стабільного виведення зображення. Усі вікна редактора (консоль, налаштування параметрів, система часток, матеріали тощо) управлялися через Render. Це дозволяло редагувати сцену в реальному часі та застосовувати зміни до об’єктів. Обробка графічних даних включала завантаження шейдерів, текстур і моделей, візуалізацію мешів, налаштування освітлення й застосування постобробки.
Функції Render
1. Логування OpenGL:
Система логування OpenGL дозволяла відстежувати помилки, попередження та інші події під час виконання графічних операцій. Логи виводилися в консоль і зберігалися в лог-файлі для зручності налагодження.
2. Редактор і його вікна
Редактор складався з кількох основних елементів, які забезпечували зручну взаємодію з рушієм:
- Головна панель
Вона містила меню та кнопки для виконання основних дій. Серед них створення нової сцени, завантаження й збереження проєктів, доступ до налаштувань рушія.
- Вікно параметрів об’єктів
Тут можна було змінювати властивості вибраного об’єкта: позицію, обертання, масштаб, матеріали, текстури, кольори та інші параметри, що визначали поведінку об’єкта в сцені.
- Вікно постобробки
Дозволяло налаштовувати візуальні ефекти: Bloom, SSO (Screen Space Occlusion), SSGI (Screen Space Global Illumination), глибину різкості (Depth of Field). Усі параметри змінювалися в реальному часі, і результат одразу відображався в сцені.
- Вікно об’єктів сцени
Відображало список усіх об’єктів, які були додані в сцену, дозволяло вибирати об’єкти, групувати їх і видаляти, а також створювати інстанси.
- Вікно завантаження об’єктів у сцену
Інтерфейс для імпорту моделей і текстур. Підтримувалися різні формати файлів для моделей (наприклад, .obj, .fbx) і текстур (.png, .jpg).
- Вікно перегляду текстур
Показувало всі текстури, які були завантажені в проєкт. Дозволяло переглядати властивості текстур, наприклад розмір, формат і канали.
- Вікно створення матеріалів
Інтерфейс для створення та налаштування матеріалів. Дозволяв додавати до матеріалів кілька текстур, змінювати параметри освітлення, відбивання, прозорість, емісію тощо.
- Консоль
Виводила логи рушія, включаючи помилки, попередження й інформаційні повідомлення. Дозволяла швидко знаходити помилки під час роботи з рушієм.
3. Освітлення та тіні:
- Directional Light: напрямлене світло для базового освітлення сцени.
- Система освітлення: підтримка точкових і спрямованих джерел світла, з налаштуванням їхніх властивостей.
- Тіні: реалізація картографії тіней (shadow mapping) для створення динамічних тіней.
4. Система часток:
- Емітер: клас для генерації часток із заданими параметрами (швидкість, напрямок, розмір, час життя).
- Об’єкт емітера: зберігав властивості та стан кожного емітера.
5. Постобробка:
- Реалізація ефектів: Bloom, SSO (Screen Space Occlusion), SSGI (Screen Space Global Illumination).
- Налаштування освітлення та ефектів тональної корекції.
6. Моделі та геометрія:
- Клас моделі: завантаження та збереження 3D-моделей.
- Клас Primitive: базові геометричні форми (квадрат, сфера, циліндр).
- Static Mesh: для роботи зі статичними мешами.
7. Фрустум і BVH:
- Обробка фрустуму: визначення об’єктів, які потрапляють у поле зору камери.
- BVH (Bounding Volume Hierarchy): створення ієрархії об’ємів для оптимізації перевірок видимості й колізій.
8. Завантаження шейдерів:
- Клас для завантаження, компіляції та управління шейдерами.
- Передача даних у шейдери через уніформи та пізніше — UBO.
- Геометрія. Клас Вектор: обробка векторних операцій для освітлення, фізики й трансформацій. Клас Вертекса: опис координат, нормалей, текстурних координат і кольору вершин.
- Система LOD: автоматичне зменшення деталізації віддалених об’єктів для оптимізації продуктивності.
9. Skybox:
Відображення оточення для створення атмосферної сцени.
10. Менеджер колізій:
Інструмент для управління фізичними колізіями об’єктів у сцені. Відображав взаємодії між об’єктами та дозволяв налаштовувати колізійні параметри: форму, розмір, активність.
11. Завантаження й збереження сцени:
Об’єкти сцени містилися у бінарному файлі, що дозволяло швидко зберігати великий обсяг даних і завантажувати їх при повторному відкритті проєкту. У файлі зберігалися геометрія об’єктів, матеріали й текстури, параметри фізики, освітлення та постобробки.
12. Клас GBuffer
Він використовувався для збереження й передачі інформації між GPU і CPU, а також оптимізації роботи з текстурами, шейдерами та матеріалами.
Особливості архітектури Render
Єдиний контекст OpenGL
Усі операції виконувались у єдиному потоці через обмеження OpenGL. Це ускладнювало реалізацію багатопоточності, особливо для завдань завантаження текстур і обробки фізики.
Централізація коду
Велика частина функцій рушія була зосереджена саме в компоненті Render, що створювало складнощі в масштабуванні й підтримці.
Контейнери даних
Зберігання об’єктів сцени, матеріалів, текстур і параметрів для оптимізації роботи рушія.
13. Завантажувач ресурсів
Для роботи рушія необхідна ефективна система завантаження текстур, матеріалів і пов’язаних ресурсів. У рушії було реалізовано спеціалізований завантажувач, який забезпечував динамічне й асинхронне завантаження ресурсів, підвищуючи продуктивність і зручність використання.
OpenGL підтримує обмежену асинхронність, тому для завантаження текстур використовувалася спеціальна черга задач. Текстури завантажувалися у фоновому режимі, що дозволяло не блокувати головний потік рендерингу. Завантажені текстури автоматично передавалися у GPU, оптимізуючи час обробки.
Завантажувач підтримував популярні формати зображень, такі як PNG, JPEG — для Albedo текстур, DDS — для компресованих текстур, HDR — для текстур із розширеним динамічним діапазоном. Під час завантаження текстури автоматично конвертувалися у формат, оптимальний для GPU.
Матеріали підключалися за допомогою конфігураційних файлів, які містили посилання на текстури (Albedo, Normal, Roughness тощо) і параметри відображення (металевість, прозорість, сила відбиття). Під час завантаження конфігураційний файл аналізувався, і всі необхідні ресурси автоматично підвантажувалися.
Завантажені текстури й матеріали кешувалися, щоб уникнути повторного завантаження та зменшити час обробки. Система кешування автоматично перевіряла, чи ресурс уже був завантажений, і якщо так, то підключала його повторно.
Завантажувач також підтримував «гаряче оновлення» ресурсів. Наприклад, при зміні текстури або матеріалу асети могли бути оновлені без перезапуску рушія. Це дозволяло редагувати сцену й одразу бачити результат у редакторі.
Архітектура завантажувача
Клас завантаження текстур завантажував текстури з диска або інших джерел, обробляв компресію, масштабування та інші попередні обробки, передавав текстури у GPU через асинхронні буфери OpenGL.
Клас завантаження матеріалів аналізував конфігураційні файли, створював матеріали й підключав відповідні текстури. А ще — автоматично зчитував параметри матеріалу й передавав їх у шейдери.
Менеджер ресурсів відповідав за збереження посилань на завантажені текстури, матеріали й інші асети, забезпечував ефективну організацію доступу до них через унікальні ключі або ідентифікатори.
Переваги системи завантаження
- Асинхронність
Зменшення затримок у головному потоці рендерингу. Плавна робота рушія навіть під час завантаження великих текстур чи матеріалів.
- Модульність
Завантажувач текстур і матеріалів був відокремленим компонентом, що спрощувало його інтеграцію та оновлення.
- Оптимізація
Використання кешування для уникнення дублювання завантаження. Попередня обробка ресурсів для оптимального використання GPU.
14. Клас тригера
Тригери — це спеціальні об’єкти, які реагують на контакт з акторами та викликають певні події. Вони додавали інтерактивність у сцену й розширювали функціонал рушія. Тригери використовувалися для відкриття дверей, активації звуку або ефекту, зміни параметрів об’єкта або сцени.
При контакті актора з тригером відбувалася перевірка умов, після чого викликалася прив’язана подія. Це забезпечувалося зв’язком тригера з компонентом фізики та системою подій рушія. У редакторі можна було налаштувати, які події викликатиме тригер.
Тригери були інтегровані через компонент Render і редактор. Звідси тригер підключався до об’єктів або актора і візуалізувався в сцені. Тригери стали важливою частиною рушія, дозволяючи додати динамічну взаємодію між акторами та об’єктами. Завдяки їхній інтеграції з фізикою, системою подій і редактором вдалося розширити можливості створення інтерактивного контенту.
Труднощі та виклики
Налаштування OpenGL вимагало часу для забезпечення стабільної роботи. Виникли складнощі з багатопоточністю через обмеження єдиного контексту. А централізація функціональності призвела до складності підтримки коду.
Компонент Physics
Компонент фізики забезпечував інтеграцію фізичної симуляції у рушій. Для цього використовувалася бібліотека Bullet Physics Engine, яка надавала всі необхідні інструменти для роботи з фізичними об’єктами й колізіями.
Функціональність компонента фізики
При підключенні бібліотеки Bullet ініціалізувався фізичний світ (btDiscreteDynamicsWorld), у якому виконувалася симуляція. Всі фізичні об’єкти додавалися до світу, де враховувалися їхні властивості — маса, сила тяжіння тощо.
Клас базової фізичної моделі забезпечував ініціалізацію фізичних об’єктів у сцені. Його основні функції:
- InitShape: створення фізичної форми (колізійної оболонки) для об’єкта.
- World: посилання на фізичний світ для інтеграції об’єкта.
- GetRigidBody: повернення об’єкта (btRigidBody), що визначає фізичну поведінку.
Налаштування фізичних властивостей. Маса задавалася для кожного об’єкта, впливаючи на його рух і взаємодії. Фрікція визначала силу тертя між об’єктами. Демпінг налаштовував поглинання енергії руху для симуляції плавності. В колізіях (OOB) використовувалися обмежувальні об’єми (наприклад, AABB чи сфери) для обчислення взаємодій.
Компонент Scene
Він відповідав за створення та управління об’єктами, що відображаються у сцені. Це дозволяло реалізувати складні структури й організовувати взаємодію між ними.
Функціональність компонента сцени
Клас актора є основним для управління об’єктами сцени. Кожен актор мав унікальні ID, трансформацію (позиція, обертання, масштаб), матеріали й текстури, зв’язки з фізичними або аудіокомпонентами, якщо потрібно.
Клас чар-актора — це розширений клас актора, який використовувався для створення складніших об’єктів. Наприклад, персонажів з анімаціями, поведінкою або інтерактивними елементами.
Зв’язок із фізикою
Кожен актор міг бути пов’язаний із базовою фізичною моделлю через компонент фізики. Це дозволяло об’єктам брати участь у фізичних взаємодіях і колізіях.
Компонент Audio
Цей компонент забезпечував інтеграцію звуку в рушій. Для цього використовувалася бібліотека OpenAL, яка дозволяла відтворювати звук у реальному часі. Програвання звуку оброблялося в окремому потоці, що забезпечувало плавну роботу без впливу на рендеринг чи фізику.
Це дозволяло запускати звуки без затримок навіть у складних сценах. Створювався буфер для завантаження аудіофайлів, наприклад, у форматах .wav або .ogg).Підключався пристрій відтворення через OpenAL (ALCdevice), який забезпечував вивід звуку. Підтримувалися базові аудіоефекти, такі як гучність.
Висновок
У цій статті я описав основні моменти та компоненти, які є у моєму рушії. Насправді його структура набагато складніша, і в ньому значно більше класів, систем та деталей, які роблять його функціональним та унікальним. Я не заглиблювався в тонкощі роботи рендера чи інших складних систем, адже це тема для окремої розповіді.
Моєю метою було дати базове уявлення про те, з чого складається рушій, як виглядає його архітектура, і які рішення я приймав, спираючись на власний досвід. Сподіваюся, матеріал був цікавим і корисним для тих, хто також захоплюється розробкою рушіїв або хоче глибше зрозуміти, як усе працює «під капотом».
3 коментарі
Додати коментар Підписатись на коментаріВідписатись від коментарів