- PVSM.RU - https://www.pvsm.ru -

LDraw + Unity. Как я Lego генерировал

Всех с наступающим! Меня зовут Гриша, и я основатель CGDevs. Уже не за горами праздники, кто-то уже нарядил ёлку, поел мандаринов и во всю заряжается новогодним настроением. Но сегодня речь пойдёт не об этом. Сегодня мы поговорим про замечательный формат под названием LDraw и про плагин для Unity, который я реализовал и выложил в OpenSource. Ссылка на проект и исходники к статье, как всегда, прилагаются. Если вы так же, как и я любите лего – добро пожаловать под кат.

LDraw + Unity. Как я Lego генерировал - 1

Формат LDraw
Начнём с того, что такое LDraw? LDraw [1]– это открытый стандарт для LEGO CAD программ, позволяющий пользователям создавать модели и сцены LEGO. В целом существую разные программы и плагины, с помощью которых можно визуализировать LDraw (к примеру, есть плагин для Blender).
Сам формат хорошо задокументирован, и мы поговорим про его последнюю версию, а точнее про 1.0.2.

LDraw – это текстовый формат, файлы которого должны быть созданы с кодировкой UTF-8. Файлы, поддерживаемые форматом, должны иметь расширение ldr, dat или mdp. Каждая строка файла – это отдельная команда, отвечающая за определённую функцию.
Важной деталью формата является правосторонняя система координат (Y направлен вверх) – подробнее обсудим позже в контексте юнити, а также то, что формат является рекурсивным (большая часть файлов содержит указание на другие файлы)

LDraw + Unity. Как я Lego генерировал - 2

Команды 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 + Unity. Как я Lego генерировал - 3

Цвета в 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 + Unity. Как я Lego генерировал - 4

Модели в LDraw

Самое прекрасное в формате LDraw, что у него достаточно много фанатов среди любителей лего. Многие интересные наборы можно найти на официальном сайте omr.ldraw.org [6], но, помимо этого, многие можно найти на отдельных форумах.
Про формат поговорили, теперь пора поговорить немного про плагин для Unity.

LDraw + Unity. Как я Lego генерировал - 5

Плагин для 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);
	}
}

И по итогу мы получаем такие красивые визуализации:
LDraw + Unity. Как я Lego генерировал - 6
LDraw + Unity. Как я Lego генерировал - 7

Подробнее можно посмотреть в репозитории на 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