Skip to content

De onde vêm os buracos nas malhas geradas ​

Por Oleg Sidorkin, CTO e cofundador da Cinevva

A raiz da asa de um pássaro gerado antes e depois da correção: uma aba cinza com fendas verdes brilhantes à esquerda e uma dobra limpa entre a asa e o corpo à direita
Mesma imagem, mesmo gerador, mesmas configurações. À esquerda está o que entregávamos até esta semana. À direita, o que entregamos agora.

Gerei um pássaro com o TRELLIS.2, apliquei rigging e, assim que uma animação começou a rodar, surgiram buracos sob as duas asas. Fendas brilhantes pelas quais era possível ver diretamente através do corpo. O sistema de rigging levou a culpa primeiro — e ele realmente tinha um problema próprio —, mas os buracos já estavam na malha antes que qualquer osso a tocasse. Este post relata a investigação que veio em seguida: o que os buracos realmente são, as nove coisas que não os corrigiram, o motivo disso e a única coisa que funcionou. Estou incluindo os becos sem saída de propósito. A maioria deles corresponde às soluções óbvias que você tentaria, e cada uma nos ensinou algo de que a correção final dependia.

Primeiro passo: entrar em acordo sobre o que é um buraco ​

Nosso verificador de malhas relatou zero arestas de contorno, e um teste de paridade de raios indicou que a superfície estava fechada. Então eu disse à equipe que a malha estava limpa. Não estava, e a pessoa que havia marcado as fendas em vermelho tinha razão em insistir. Uma malha pode estar fechada e ainda ter túneis. A única medida que os detecta é a característica de Euler, V menos E mais F, que fornece o número de alças por casca. O pássaro tinha 69. Cada parede na exportação estava duplicada, portanto uma fenda na parede externa conectada à cavidade atrás dela é uma alça, não uma abertura, e nada que procure arestas abertas jamais vai detectá-la.

Segunda lição, ainda mais constrangedora: passei um tempo examinando pixels escuros em busca de buracos, porque buracos são escuros, até alguém me dizer para olhar primeiro para a imagem. Os buracos eram claros. Eram fendas revelando a textura verde da parede oposta através de uma dobra cinza. Um algoritmo que não foi direcionado para a coisa certa não diz nada, e a maneira mais barata de direcioná-lo é olhar.

Segundo passo: encontrar a etapa que os cria ​

O TRELLIS.2 constrói uma malha em três etapas. Um decodificador emite uma superfície bruta com resolução cúbica de 1024. Uma etapa de contorno dual em banda estreita refaz a malha. Um decimador a reduz para cem mil faces, e a textura é assada. Salvamos a geometria após cada etapa e disparamos o mesmo conjunto de raios através da raiz da asa em todas elas.

É na superfície bruta que tudo começa. Ela é uma sopa de superfícies abertas: 266.242 loops de contorno, a maioria deles composta por um único quad ausente, e 65.371 arestas não manifold. Na raiz da asa, um terço dos raios de teste atravessou direto. O preenchimento de buracos executado em seguida quase não fecha nada: 144 faces em cinco milhões. O remalhador trabalha com um campo de distância sem sinal porque precisa fazer isso. Ele não tem um interior ou exterior com que trabalhar, então envolve cada superfície com uma casca fina e deslocada. É por isso que cada parede fica duplicada, separada por cerca de quatro voxels, e é assim que cada faixa porosa se transforma em uma dobra com uma fenda. A decimação então piora a situação, e faz isso de maneira aleatória: a mesma malha bruta decimada duas vezes produziu 69 e 107 túneis.

O pássaro com um mapa de calor da densidade de arestas abertas: o colar do pescoço, as raízes das duas asas, os quadris e as pontas das penas da cauda brilham, enquanto o corpo e a cabeça estão limpos
Densidade de arestas abertas na extração bruta. Ela se acende apenas onde as partes se encontram: colar do pescoço, raízes das asas, quadris e pontas da cauda. O corpo e a cabeça estão limpos.

Esse mapa foi a primeira pista de verdade. A porosidade não é aleatória. Ela aparece em todas as junções entre partes e em nenhum outro lugar.

As coisas que não funcionaram ​

Nós as testamos aproximadamente na ordem do quão óbvias pareciam.

tentativaresultado
resolução 1536 em vez de 1024pior em três de cinco modelos de teste. Mais resolução resolve mais contatos finos, e cada um deles é uma nova junção
a sequência integrada de limpezamais arestas não manifold, não menos
preencher todos os buracos pequenos com o MeshLib266 mil loops preenchidos, raiz da asa inalterada. A superfície ali nunca foi emitida, portanto não há loop para preencher
um remalhamento com sinal a partir de um número de enrolamentofragmenta a malha em milhares de cascas. A saída bruta não tem interior ao qual atribuir um sinal
uma banda de contorno mais larga, de 2 e 3 voxelsmenos túneis no total, mas a fenda fica mais larga, não mais estreita. Túneis pequenos se fundem em grandes
fechamento morfológico da estrutura de voxelssolidifica a casca; o decodificador então emite superfície dentro de cada voxel interior, quadruplica o número de faces e produz 969 túneis
remalhamento global por voxels da malha finalgênero 0 e fenda selada. Também suavizou as bordas de todas as penas. Rejeitado só de olhar, corretamente
ajuste do limiar de cruzamento do decodificadorarestas abertas de 65 mil para 58 mil. Ruído
completar voxels vizinhos ausentes560 casos no pássaro inteiro. Ruído
Quatro colunas: o pássaro original e três versões remalhadas por voxels com raios crescentes, a raiz da asa, zoom de dez vezes, uma seção transversal, o pássaro inteiro e uma vista por baixo
O fechamento global. A terceira coluna sela a fenda e mantém os dedos separados, mas também arredonda todas as dobras do pássaro. É assim que uma geometria “borrada” se parece.

Vale a pena examinar melhor o fechamento global, porque é o que um modelador tentaria primeiro: aplicar um remalhamento por voxels com um tamanho ligeiramente maior que a lacuna. Topologicamente, funciona. Mas também é uma mentira sobre o modelo. O gerador produziu bordas nítidas nas penas, e estaríamos trocando-as por uma raiz de asa selada. Duas das nove tentativas reduziram a quantidade de túneis, e ambas foram rejeitadas no momento em que alguém olhou para o pássaro em vez de olhar para o número.

Lendo o código ​

Nesse ponto, a única atitude honesta era parar de tratar o decodificador como uma caixa-preta e ler como sua saída se transforma em uma malha. O TRELLIS.2 usa uma representação chamada O-Voxel, e a extração é curta. Cada voxel ativo contém um vértice dual e três logits, um para cada aresta da grade que sai de seu canto, cada um significando “a superfície cruza esta aresta”. Um logit acima de zero emite um quad conectando os quatro voxels ao redor dessa aresta. Se qualquer um dos quatro estiver ausente, o quad é descartado silenciosamente. É só isso. Não há campo de sinais, ocupação, interior ou exterior em nenhum ponto do modelo.

Alteramos o decodificador para salvar esses logits antes do limiar, no mesmo pássaro, e fizemos a contagem.

Vizinhos ausentes: 560 entre 2,7 milhões de voxels. Logits próximos de zero: 8.600 entre 8 milhões. Nenhum dos dois é o problema. O problema aparece quando observamos as quatro arestas ao redor de cada face da grade. Uma superfície fechada cruza o contorno de uma face um número par de vezes. Em 126.542 faces, 2,3% delas, os quatro indicadores discordam, e toda face ímpar é, por definição, uma aresta aberta ou não manifold. E eles discordam com confiança. O indicador isolado em uma face de contagem um tem logit +10, enquanto seus vizinhos têm -4. O custo mediano para inverter um indicador e restaurar a paridade é de 2,3 logits. Não se trata de um limiar ligeiramente incorreto. O modelo tem certeza de uma superfície que não consegue se fechar.

Três colunas sobre um fundo escuro: o pássaro exportado, a saída bruta do decodificador e uma versão com sinais corrigidos. Com zoom de dez e trinta vezes, a junção da asa é uma faixa serrilhada e fragmentada nas duas versões brutas
A saída bruta do decodificador na raiz da asa; escuro significa que é possível enxergar através. A junção não tem quads ausentes. É um pente.

Depois medimos a própria faixa, no ponto exato da superfície para o qual nossa câmera fixa havia olhado durante toda a semana. Dentro de um raio de doze voxels, há 2,1 vezes mais voxels ativos do que uma única superfície plana precisaria. No peito, para comparação, há 1,2 vez mais. A parte inferior da asa e o corpo são duas superfícies passando dentro de uma única camada de voxels. A representação tem um vértice por voxel, portanto não consegue armazenar ambas. Durante o treinamento, o valor de referência para esse voxel é uma média de mínimos quadrados das duas superfícies com indicadores de “qualquer cruzamento”, o que por si só é inconsistente; assim, o modelo aprendeu fielmente a emitir um pente ali. O remalhador envolve o pente em uma placa, e a placa tem túneis.

A largura da faixa decorre da geometria: aproximadamente um voxel dividido pela tangente do ângulo de contato. Duas superfícies que se encontram a noventa graus produzem uma dobra de um voxel e não causam problemas. Uma asa apoiada contra um corpo, pena sobre pena, dedos no chão — todos esses casos produzem uma faixa de dez ou quinze voxels de largura. É a explicação do mapa de porosidade.

Eu também tentei o reparo baseado em princípios: inferir, para cada canto, um sinal de interior/exterior que melhor explicasse os indicadores e então recalculá-los. Não pode funcionar. O campo de sinais só existe na casca de um voxel emitida pelo decodificador, e qualquer extensão para fora dessa casca sai do controle. Doze rodadas disso dobraram a quantidade de voxels com lixo.

O que um modelador faria ​

Um modelador nunca deixaria duas superfícies separadas pela espessura de um fio de cabelo. Ele uniria a asa ao corpo e deixaria uma dobra. O fechamento global faz isso em todos os lugares, e esse é o problema. Portanto, faça isso apenas onde estão as duas superfícies.

O detector segue uma ideia simples. Conte os vizinhos ativos de cada voxel dentro de quatro voxels. Em uma única superfície, esse número é igual em todos os lugares e corresponde à mediana. Onde duas superfícies compartilham a camada, ele é aproximadamente o dobro. Marque tudo que estiver acima de 1,5 vez a mediana, expanda o conjunto marcado em um voxel e coloque um cubo unitário em cada posição. O espaço entre as superfícies se torna sólido, a etapa de contorno enxerga uma única parede e nada mais no modelo é alterado.

Um recorte ao redor da raiz da asa contornado de três formas: como está, com a zona de duas superfícies preenchida com raio três e com raio quatro. Pontos vermelhos marcam os voxels preenchidos ao longo da emenda. A emenda serrilhada se transforma em uma dobra suave
Prova de conceito em um recorte, antes de alterar o pipeline. O vermelho marca os voxels preenchidos. A emenda serrilhada se transforma em uma dobra, e as penas ao redor não se movem.

Primeiro testamos em um recorte, usando o MeshLib no lugar do contorno na GPU. Cinquenta e três cascas e dezenove alças se tornaram duas cascas e cinco alças. Depois colocamos a correção no pipeline real, entre o decodificador e a exportação, e processamos o mesmo pássaro com o remalhador real e o bake de textura real.

Quatro colunas, primeiro com textura, depois com geometria simples e então uma seção transversal: original, a mesma saída do decodificador exportada como está e o preenchimento de duas superfícies com raios três e quatro. A aba cinza e as fendas verdes desaparecem nas duas últimas
O pipeline real. As colunas um e dois são a mesma saída do decodificador, com e sem uma decimação aleatória. As colunas três e quatro têm o preenchimento.
túneisarestas não manifoldaba na raiz da asaburaco nas costas
como está79,5318presentepresente
preenchimento, raio 384,597ausenteausente
preenchimento, raio 47238ausenteausente

A aba desapareceu. Um segundo buraco nas costas do pássaro, na mesma junção vista por trás — um que eu nem estava tentando corrigir —, também desapareceu. As penas continuam separadas, os dedos continuam separados e o bake da textura ocorre normalmente, porque os cubos ficam dentro da casca, onde o volume de atributos já contém dados. Isso acrescenta seis segundos a um processo de quatro minutos. Agora está ativado por padrão, com raio quatro.

O pássaro inteiro visto de frente, de costas, por baixo com textura e por baixo como geometria simples, em quatro colunas: original, como está, preenchimento com raio três e preenchimento com raio quatro
O pássaro inteiro. O buraco branco nas costas nas colunas um e dois é a mesma junção vista por trás. Tudo que o gerador acertou continua presente nas colunas três e quatro.

O que isso não corrige e o que isso revela ​

A quantidade de túneis praticamente não mudou, e quero explicar com honestidade o motivo. Os túneis restantes ficam nas pontas das penas e dos dedos, onde uma parte fina termina e a parede duplicada se fecha em uma pinça. Eles são pequenos, não ficam visíveis e resultam de um mecanismo diferente. O preenchimento trata os contatos entre partes, que é onde estavam os buracos visíveis. A correção de verdade precisa acontecer em uma etapa anterior. Um decodificador que preveja sinais por vértice em vez de indicadores por aresta seria consistente em termos de paridade por construção, que é como funcionava a geração anterior deste modelo e por que suas malhas eram estanques. Isso exige uma mudança no treinamento, não é algo que possamos corrigir na inferência. Até lá, duas conclusões práticas decorrem da fórmula das faixas. Se você estiver gerando um personagem, uma pose em que os membros fiquem afastados do corpo produzirá uma malha mais limpa do que uma em que fiquem encostados nele. E, se estiver desenvolvendo algo sobre um gerador como este, avalie suas malhas com a característica de Euler e examine o resultado com seus próprios olhos antes de confiar em um número. O número indicou que estava tudo certo. Ele errou duas vezes.

O decodificador instrumentado, os dumps de cada etapa, a análise de paridade e o preenchimento agora fazem parte do nosso pipeline, então o próximo modelo que apresentar buracos receberá o mesmo tratamento em questão de minutos, em vez de uma semana.