LDraw + Unity. Como eu gerei Lego

Tudo com a vinda! Meu nome é Grisha e sou o fundador da CGDevs. As férias estão chegando, alguém já vestiu uma árvore de Natal, comeu tangerina e está totalmente carregado com o clima do Ano Novo. Mas hoje não é sobre isso. Hoje falaremos sobre um formato maravilhoso chamado LDraw e sobre o plug-in para Unity, que eu implementei e enviei para o OpenSource. O link para o projeto e o código-fonte do artigo, como sempre, estão anexados. Se você ama Lego tanto quanto eu, seja bem-vindo ao gato.



Formato LDraw

Vamos começar com o que é LDraw? LDraw é um padrão aberto para programas LEGO CAD que permite aos usuários criar modelos e cenas LEGO. Em geral, existem vários programas e plugins com os quais você pode visualizar o LDraw (por exemplo, existe um plugin para o Blender).

O formato em si está bem documentado e falaremos sobre sua versão mais recente, ou melhor, sobre 1.0.2.

LDraw é um formato de texto cujos arquivos devem ser criados com a codificação UTF-8. Os arquivos suportados pelo formato devem ter a extensão ldr, dat ou mdp. Cada linha do arquivo é um comando separado responsável por uma função específica.

Um detalhe importante do formato é o sistema de coordenadas para a mão direita (Y é direcionado para cima) - discutiremos mais detalhadamente mais adiante no contexto da unidade, bem como o fato de o formato ser recursivo (a maioria dos arquivos contém uma indicação de outros arquivos).



Comandos LDraw

Em geral, essas informações podem ser encontradas na documentação oficial , mas vamos olhar um pouco no contexto do Unity. No total, o formato LDraw suporta 6 tipos de comandos.

0. Um comentário ou um comando meta são comandos especiais que dificilmente tocaremos no plugin. Exemplo: 0 !META command additional parameters

1. Link para o arquivo . Na verdade, a equipe mais difícil de integrar e interessante. Parece - 1 colour xyzabcdefghi file , em que os parâmetros são a matriz TRS (mais sobre TRS pode ser encontrado neste artigo ). No contexto da unidade no formulário

 / adg 0 \ | beh 0 | | cfi 0 | \ xyz 1 / 

2. Linha - não utilizada no caso do Unity, é necessário enfatizar as arestas com uma determinada cor nos sistemas CAD.

3.4 Triângulo e quadrado . Os comandos são bastante simples, mas há uma nuance importante, já que o formato LDraw não foi projetado para modelagem 3D, o desvio de triângulos e quadrados nele não é padronizado. Isso é importante porque a unidade, dependendo da travessia do triângulo, determina a direção do normal calculado, bem como de que lado do triângulo está a parte de trás e qual é a frente (o que também é importante para o desenho e seleção).

Exemplo de comando:
Triângulo - 3 colour x1 y1 z1 x2 y2 z2 x3 y3 z3
Quadrado - 4 colour x1 y1 z1 x2 y2 z2 x3 y3 z3 x4 y4 z4

5. Linha opcional - também não usada.



Cores no LDraw

Como você pode ver na maioria das equipes responsáveis ​​pela renderização, a cor vem imediatamente após o tipo de comando. As cores estão bem documentadas nesses dois artigos www.ldraw.org/article/299.html e www.ldraw.org/article/547.html , mas vamos falar sobre os recursos que encontrei durante a implementação. Aqui vale a pena falar um pouco mais sobre os formatos e o chamado formato "Escopo". Existem 3 tipos de arquivos no formato.

DAT - de fato, esses são os elementos básicos a partir dos quais as peças já estão montadas, ou algumas peças básicas. Se você não renderizar detalhes individuais, a cor indicada neles não é importante. Na maioria das vezes, existem cores padrão do padrão oficial.

LDR é a coisa mais interessante em termos de cores e onde o Scope desempenha um papel. A regra é bastante simples, embora o site tenha descrito linguagens complexas. Se você se referir a outro de um ldr, ignore a cor especificada na raiz.

Por exemplo, parte do arquivo 30051-1 - X-wing Fighter - Mini.mpd (X-wing na imagem acima):

Exemplo
 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 


Em todos os arquivos dat, levamos em consideração a cor especificada e no comando 1 72 0 -8 -70 1 0 0 0 1 0 0 0 1 30051 - Nose.ldr - ignore 72 e use os valores do arquivo 30051 - Nose.ldr .

MDP é um arquivo de modelo, na maioria das vezes contém uma descrição de vários arquivos ldr. Em termos de cor, também não é muito importante. A única coisa que levamos em consideração ao analisar é o meta-comando FILE .



Modelos no LDraw

A melhor parte do formato LDraw é que ele tem muitos fãs entre os fãs de lego. Muitos kits interessantes podem ser encontrados no site oficial omr.ldraw.org , mas além disso, muitos podem ser encontrados em fóruns separados.

Conversamos sobre o formato, agora é hora de falar um pouco sobre o plugin do Unity.



Plugin para Unity

O plug-in fornece a capacidade de gerar modelos 3D com base em arquivos LDraw. Você pode ver os resultados nas fotos do artigo. Importante: se você tiver um dispositivo fraco, é melhor abrir apenas mini cenas na pasta Demo. Os modelos não são otimizados e sempre geram um backface.

Agora vamos falar um pouco sobre implementação. No momento, a maioria dos itens acima é suportada.

Uma das características talvez mais importantes são os diferentes sistemas de coordenadas. O problema é que o formato é um sistema de coordenadas para destros, enquanto o Unity é um sistema de coordenadas para canhotos. O que isso significa, em essência, que todas as curvas e a matriz TRS não funcionarão corretamente. Y negativo é fácil de superar - refletimos todas as coordenadas relativas ao Vector3.up e obtemos as necessárias (multiplique por -1). Mas no caso da matriz TRS, tudo é mais complicado. Como o formato é recursivo, é simplesmente impossível refletir a matriz, pois Matrix.Identity se tornará uma matriz de reflexão em todos os lugares e cada aninhamento refletirá nosso modelo ao longo do eixo Y, o que levará a uma exibição incorreta (se você mantiver uma escala positiva). Até agora, cheguei a uma decisão incorreta na forma de permitir uma escala negativa, que precisará ser refeita em versões futuras.

O segundo recurso é a orientação dos triângulos. Para quads, percebe-se que os triângulos parecem de uma maneira:

Código de Preparação para Quadrados
 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]]); } } 


Mas aqui é inequívoco determinar, com base no formato, em que direção os triângulos devem ser direcionados em princípio - uma tarefa não trivial. Por esse motivo, os dois lados são sempre gerados agora.

Além disso, devido ao fato de o formato ser recursivo, o sistema hierárquico do Unity foi útil como nunca antes.

Usando recursão em dois métodos, geramos as malhas necessárias e aplicamos o TRS (a implementação pode ser encontrada no artigo anterior ) e, portanto, obtemos todas as compensações necessárias em um formato conveniente:

Métodos para gerar um modelo no palco
 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); } } 


E, como resultado, obtemos visualizações tão bonitas:





Veja o repositório no Github para mais detalhes.

Em geral, existem muitas idéias sobre o desenvolvimento do plugin. Quero apresentar recursos como:

  1. Suavizando algumas formas
  2. Somente geração de face frontal
  3. Construtor e upload de modelos de volta ao formato LDraw
  4. O shader mais frio para plástico com espalhamento subterrâneo (e o conjunto correto de materiais em geral)
  5. Desembrulhe os UV para mapas de luz
  6. Otimização de modelos (agora a maioria consiste em 500k + e, por exemplo, o modelo da torre Eiffel é de 2,8 milhões de polígonos)

Mas, no momento, o plug-in permite que você use modelos da Lego no Unity3d, o que é bem legal. (Todas as imagens do artigo foram criadas usando o plug-in) Todo o código do projeto é publicado sob a licença MIT, mas aconselho que você olhe a licença para modelos específicos nos recursos do LDraw.

Obrigado por sua atenção, espero que você tenha aprendido algo novo e esteja interessado no formato e no plug-in! Se houver tempo, continuarei a desenvolvê-lo e ficarei feliz em ajudar nessa questão difícil.

Source: https://habr.com/ru/post/pt433364/


All Articles