- PVSM.RU - https://www.pvsm.ru -
Всех с наступающим! Меня зовут Гриша, и я основатель CGDevs. Уже не за горами праздники, кто-то уже нарядил ёлку, поел мандаринов и во всю заряжается новогодним настроением. Но сегодня речь пойдёт не об этом. Сегодня мы поговорим про замечательный формат под названием LDraw и про плагин для Unity, который я реализовал и выложил в OpenSource. Ссылка на проект и исходники к статье, как всегда, прилагаются. Если вы так же, как и я любите лего – добро пожаловать под кат.
Формат LDraw
Начнём с того, что такое LDraw? LDraw [1]– это открытый стандарт для LEGO CAD программ, позволяющий пользователям создавать модели и сцены LEGO. В целом существую разные программы и плагины, с помощью которых можно визуализировать LDraw (к примеру, есть плагин для Blender).
Сам формат хорошо задокументирован, и мы поговорим про его последнюю версию, а точнее про 1.0.2.
LDraw – это текстовый формат, файлы которого должны быть созданы с кодировкой UTF-8. Файлы, поддерживаемые форматом, должны иметь расширение ldr, dat или mdp. Каждая строка файла – это отдельная команда, отвечающая за определённую функцию.
Важной деталью формата является правосторонняя система координат (Y направлен вверх) – подробнее обсудим позже в контексте юнити, а также то, что формат является рекурсивным (большая часть файлов содержит указание на другие файлы)
Команды LDraw
В целом с этой информацией можно ознакомиться в официальной документации [2], но рассмотрим немного в контексте Unity. Всего формат LDraw поддерживает 6 типов команд.
0. Комментарий или мета команда – это специальные команды, которых мы почти не будем касаться в плагине. Пример: 0 !META command additional parameters
1. Ссылка на файл. По сути, самая сложная в интеграции и интересная команда. Выглядит она как — 1 colour x y z a b c d e f g h i file
, где параметры являются TRS матрицей (подробнее про TRS можно прочитать в этой статье [3]). В контексте юнити в форме
/ a d g 0
| b e h 0 |
| c f i 0 |
x y z 1 /
2. Линия – не используется в случае Unity, нужно чтобы подчеркнуть грани определённым цветом в CAD системах.
3,4. Треугольник и квадрат. Команды достаточно простые, но есть один важный нюанс, так как формат LDraw не рассчитан на 3д моделирование, то обход треугольников и квадратов в нём не стандартизирован. Это важно, так как-то же юнити в зависимости от обхода треугольника определяет направление calculated нормали, а также какая сторона треугольника является задней, а какая передней (что так же важно для отрисовки и куллинга)
Пример команд:
Треугольник — 3 colour x1 y1 z1 x2 y2 z2 x3 y3 z3
Квадрат — 4 colour x1 y1 z1 x2 y2 z2 x3 y3 z3 x4 y4 z4
5. Опциональная линия – тоже не используется.
Цвета в LDraw
Как можно заметить в большинстве команд, отвечающих за отрисовку, цвет идёт сразу после типа команды. Цвета хорошо задокументированы в этих двух статьях www.ldraw.org/article/299.html [4] и www.ldraw.org/article/547.html [5], но поговорим про особенности, с которыми я столкнулся при реализации. Тут стоит чуть подробнее поговорить про форматы и так называемый “Scope” формата. В формате присутствуют 3 типа файлов.
DAT – по сути это базовые элементы из которых уже собираются детали, либо какие-то базовые детали. Если не рендерить отдельные детали – указанный в них цвет не важен. Чаще всего там стоят стандартные цвета официального стандарта.
LDR – это самое интересное, с точки зрения цветов, и где Scope играет роль. Правило довольно простое, хотя на сайте описано сложным языков. Если вы из одного ldr ссылаетесь на другой – игнорируйте цвет указанный в корневом.
Для примера часть файла 30051-1 — X-wing Fighter — Mini.mpd (X-wing на картинке выше):
1 71 -10 0 50 0 0 1 0 1 0 -1 0 0 60470a.dat
1 71 10 0 50 0 0 -1 0 1 0 1 0 0 60470a.dat
0 STEP
1 19 0 8 50 0 0 -1 0 1 0 1 0 0 4032b.dat
0 STEP
0 ROTSTEP 35 55 0 ABS
1 19 0 -16 0 0 0 -1 0 1 0 1 0 0 3623.dat
1 72 0 -16 50 0 0 -1 0 1 0 1 0 0 3022.dat
0 STEP
1 72 0 -8 -70 1 0 0 0 1 0 0 0 1 30051 - Nose.ldr
Во всех dat файлах мы учитываем указанный цвет, а в команде 1 72 0 -8 -70 1 0 0 0 1 0 0 0 1 30051 — Nose.ldr – игнорируем 72, и используем значения из файла 30051 — Nose.ldr.
MDP – это файл модели, чаще всего содержит в себе описание нескольких ldr файлов. С точки зрения цвета так же не особо важен. Единственное, что мы учитываем при парсинге — это мета-команду FILE.
Модели в LDraw
Самое прекрасное в формате LDraw, что у него достаточно много фанатов среди любителей лего. Многие интересные наборы можно найти на официальном сайте omr.ldraw.org [6], но, помимо этого, многие можно найти на отдельных форумах.
Про формат поговорили, теперь пора поговорить немного про плагин для Unity.
Плагин для Unity
Плагин [7]предоставляет возможность генерировать 3д модели на основе файлов LDraw. Результаты вы можете увидеть в картинках из статьи. Важно: если у вас слабое устройство, лучше открывайте только сцены mini в папке Demo. Модели не оптимизированы и всегда генерируют backface.
А теперь поговорим немного про реализацию. На данный момент поддержана большая часть описанного выше.
Одной из, пожалуй, самых главных особенностей являются разные системы координат. Проблема в том, что в формате правосторонняя система координат, а в Unity – левосторонняя. Что это, по сути, означает, что все повороты и TRS матрица будут работать неверно. Отрицательный Y обыграть просто – отражаем все координаты относительно Vector3.up и получаем нужные (умножаем на -1). Но вот в случае с TRS матрицей всё сложнее. Так как формат рекурсивный, то просто отражать матрицу – нельзя, так как Matrix.Identity везде превратится в матрицу отражения и каждая вложенность будет отражать нашу модель по оси Y, что приведёт к неправильному отображению (если сохранять положительный scale). Пока я пришёл к не совсем верному решению в виде того, что разрешил отрицательный scale, что нужно будет переделать в будущих версиях.
Вторая особенность, это ориентация треугольников. Для квадов реализовано то, чтобы треугольники смотрели в одну сторону:
public override void PrepareMeshData(List<int> triangles, List<Vector3> verts)
{
var v = _Verts;
var nA = Vector3.Cross(v[1] - v[0], v[2] - v[0]);
var nB = Vector3.Cross(v[1] - v[0], v[2] - v[0]);
var vertLen = verts.Count;
triangles.AddRange(new[]
{
vertLen + 1,
vertLen + 2,
vertLen,
vertLen + 1,
vertLen + 3,
vertLen + 2
});
var indexes = Vector3.Dot(nA, nB) > 0 ? new int[] {0, 1, 3, 2} : new int[] {0, 1, 2, 3};
for (int i = 0; i < indexes.Length; i++)
{
verts.Add(v[indexes[i]]);
}
}
Но вот однозначно определить базируясь на формате, в какую сторону в принципе должны быть направлены треугольники — нетривиальная задача. По этой причине сейчас генерируются обе стороны всегда.
Кроме того, из-за того, что формат рекурсивный, иерархическая система Unity пришлась как никогда кстати.
С помощью рекурсии в двух методах, мы генерируем нужные нам меши и применяем TRS (реализацию можно прочитать в прошлой статье [3]), и таким образом получаем в удобном формате все необходимые для нас смещения:
public class LDrawModel
{
public GameObject CreateMeshGameObject(Matrix4x4 trs, Material mat = null, Transform parent = null)
{
if (_Commands.Count == 0) return null;
GameObject go = new GameObject(_Name);
var triangles = new List<int>();
var verts = new List<Vector3>();
for (int i = 0; i < _Commands.Count; i++)
{
var sfCommand = _Commands[i] as LDrawSubFile;
if (sfCommand == null)
{
_Commands[i].PrepareMeshData(triangles, verts);
}
else
{
sfCommand.GetModelGameObject(go.transform);
}
}
if (mat != null)
{
var childMrs = go.transform.GetComponentsInChildren<MeshRenderer>();
foreach (var meshRenderer in childMrs)
{
meshRenderer.material = mat;
}
}
if (verts.Count > 0)
{
var visualGO = new GameObject("mesh");
visualGO.transform.SetParent(go.transform);
var mf = visualGO.AddComponent<MeshFilter>();
mf.sharedMesh = PrepareMesh(verts, triangles);
var mr = visualGO.AddComponent<MeshRenderer>();
if (mat != null)
{
mr.sharedMaterial = mat;
}
}
go.transform.ApplyLocalTRS(trs);
go.transform.SetParent(parent);
return go;
}
}
public class LDrawSubFile : LDrawCommand
{
public void GetModelGameObject(Transform parent)
{
_Model.CreateMeshGameObject(_Matrix, GetMaterial(), parent);
}
}
И по итогу мы получаем такие красивые визуализации:
Подробнее можно посмотреть в репозитории на Github [7].
В целом по развитию плагина очень много идей, хочется ввести такие функциональности, как:
1. Сглаживание некоторых форм
2. Генерация только front face
3. Конструктор и выгрузка моделей обратно в формат LDraw
4. По круче шейдер для пластика с subsurface scattering (и правильный набор материалов в целом)
5. Unwrap UV для лайтмапов
6. Оптимизация моделей (сейчас большинство состоят из 500к+, а к примеру модель эйфелевой башни 2.8 миллона полигонов)
Но и на данный момент, плагин позволяет использовать модельки из лего в Unity3d, что достаточно прикольно. (Все изображения для статьи сделаны с помощью плагина) Весь код проекта [7] выложен под MIT лицензией, но вот лицензию на конкретные модели советую смотреть на ресурсах LDraw.
Спасибо за внимание, надеюсь вы узнали для себя что-то новое, и вас заинтересовал формат и плагин! Если будет время – буду продолжать его развивать и буду рад помощи в этом нелёгком деле.
Автор: Григорий Дядиченко
Источник [8]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-2/302390
Ссылки в тексте:
[1] LDraw : http://www.ldraw.org
[2] официальной документации: http://www.ldraw.org/article/218.html
[3] в этой статье: https://habr.com/post/432544/
[4] www.ldraw.org/article/299.html: http://www.ldraw.org/article/299.html
[5] www.ldraw.org/article/547.html: http://www.ldraw.org/article/547.html
[6] omr.ldraw.org: http://omr.ldraw.org/
[7] Плагин : https://github.com/CGDevsCommunity/LDraw_Imporer_Unity
[8] Источник: https://habr.com/post/433364/?utm_source=habrahabr&utm_medium=rss&utm_campaign=433364
Нажмите здесь для печати.