Como calcular normais de superfície para geometria gerada

12

Eu tenho uma classe que gera uma forma 3D com base nas entradas do código de chamada. As entradas são coisas como comprimento, profundidade, arco, etc. Meu código gera a geometria perfeitamente, mas estou tendo problemas ao calcular as normais da superfície. Quando iluminada, minha forma apresenta uma coloração / textura muito bizarra a partir das normais de superfície incorretas que estão sendo calculadas. De todas as minhas pesquisas, acredito que minha matemática está correta, parece que há algo errado com minha técnica ou método.

Em um nível alto, como calcular calculadamente as normais da superfície para uma forma gerada? Estou usando o Swift / SceneKit no iOS para o meu código, mas uma resposta genérica está correta.

Eu tenho duas matrizes que representam minha forma. Um é uma matriz de pontos 3D que representa os vértices que compõem a forma. A outra matriz é uma lista de índices da primeira matriz que mapeia os vértices em triângulos. Preciso pegar esses dados e gerar uma terceira matriz que é um conjunto de normais de superfície que ajudam na iluminação da forma. (veja SCNGeometrySourceSemanticNormalem SceneKit` )

A lista de vértices e índices é sempre diferente, dependendo das entradas da classe, portanto, não posso pré-calcular ou codificar com firmeza as normais da superfície.

macinjosh
fonte
Precisa de mais contexto. Você está tentando calcular normais analíticos para uma superfície paramétrica? Uma superfície implícita? Ou você deseja calcular as normais a partir de uma malha triangular genérica? Ou outra coisa?
Nathan Reed
Obrigado, eu adicionei mais detalhes. Para responder sua pergunta, preciso calcular normais a partir de uma malha triangular genérica. Embora fique claro que a malha é diferente dependendo das entradas. Minha forma é uma seta 3D, como exemplo, aqui está uma captura de tela com duas formas diferentes (radial e linear). A classe altera a largura, profundidade, comprimento, arco e raio da malha, conforme solicitado. cl.ly/image/3O0P3X3N3d1d Você pode ver a iluminação estranha que estou recebendo com minhas más tentativas de resolver isso.
macinjosh
3
A versão curta é: calcule cada vértice normal como a soma normalizada das normais de todos os triângulos que o tocam. No entanto, isso tornará tudo suave, o que pode não ser o que você deseja para essa forma. Vou tentar expandir para uma resposta completa mais tarde.
Nathan Reed
Suave é o que eu estou procurando!
macinjosh
4
Na maioria dos casos, se você calcular as posições de vértice analiticamente, também poderá calcular as normais analiticamente. Para uma superfície paramétrica, os normais são o produto cruzado dos dois vetores de gradiente. Calcular a média das normais do triângulo é apenas uma aproximação e geralmente resulta em qualidade visualmente muito mais baixa. Gostaria de postar uma resposta, mas já publiquei um exemplo detalhado no SO ( stackoverflow.com/questions/27233820/… ), e não tenho certeza se queremos conteúdo replicado aqui.
Reto Koradi

Respostas:

9

Você simplesmente não quer resultados totalmente suaves. Enquanto o método comentado por Nathan Reed: "Calcule cada vértice para enfrentar o normal, some-os, normalize-o" ", geralmente funciona, às vezes falha espetacularmente. Mas isso não tem importância aqui, podemos usar esse método adicionando uma cláusula de rejeição a ele.

Nesse caso, você simplesmente deseja que certas partes não sejam suavizadas contra outras partes. Você deseja arestas seletivas. Assim, por exemplo, a parte superior e a parte inferior planas são separadas da faixa triangular ao lado, assim como cada área plana.

Imagem que buscamos

Imagem 1 : O resultado que você deseja.

Na verdade, você só deseja calcular a média dos vértices da área curva, todos os outros podem usar o normal, obtendo apenas o triângulo. Portanto, é melhor pensar na malha como 9 regiões separadas que são tratadas sem as outras.

Mostrando malha e normais]

Imagem 2 : Imagem mostrando a estrutura da malha e as normais.

Você certamente pode deduzir isso automaticamente ao não incluir normais que estão fora de determinado ângulo dos vértices primários normais. Pseudo-código:

For vertex in faceVertex:
    normal = vertex.normal
    For adjVertex in adjacentVertices:
        if anglebetween(vertex.normal, adjVertex.normal )  < treshold:
            normal += adjVertex.normal
    normal = normalize(normal)

Isso funciona, mas você pode simplesmente evitar tudo isso no momento da criação, porque entende que planos separados estão funcionando de maneira diferente. Portanto, apenas os lados curvos precisam ser mesclados na direção normal. E, de fato, você pode apenas calculá-los diretamente da forma matemática subjacente.

joojaa
fonte
10

Eu vejo principalmente três maneiras de calcular normais para uma forma gerada.

Normais analíticas

Em alguns casos, você tem informações suficientes sobre a superfície para gerar as normais. Por exemplo, o normal de qualquer ponto em uma esfera é trivial para calcular. Simplificando, quando você conhece a derivada da função, também conhece o normal.

Se o seu caso for estreito o suficiente para permitir que você use normais analíticos, eles provavelmente fornecerão o melhor resultado em termos de precisão. Porém, a técnica não escala muito bem: se você também precisar lidar com casos em que não pode usar normais analíticos, pode ser mais fácil manter a técnica que lida com o caso geral e abandoná-lo completamente.

Normais do vértice

O produto cruzado de dois vetores fornece um vetor perpendicular ao plano ao qual eles pertencem. Portanto, obter o normal de um triângulo é simples:

vec3 computeNormal(vec3 a, vec3 b, vec3 c)
{
    return normalize(crossProduct(b - a, c - a));
}

Além disso, no exemplo acima, o comprimento do produto cruzado é proporcional à área dentro de abc . Portanto, o normal suavizado em um vértice compartilhado por vários triângulos pode ser calculado somando os produtos cruzados e normalizando como um último passo, ponderando cada triângulo por sua área.

vec3 computeNormal(vertex a)
{
    vec3 sum = vec3(0, 0, 0);
    list<vertex> adjacentVertices = getAdjacentVertices(a);
    for (int i = 1; i < adjacentVertices; ++i)
    {
        vec3 b = adjacentVertices[i - 1];
        vec3 c = adjacentVertices[i];
        sum += crossProduct(b - a, c - a);
    }
    if (norm(sum) == 0)
    {
        // Degenerate case
        return sum;
    }
    return normalize(sum);
}

Se você estiver trabalhando com quads, há um bom truque que você pode usar: para um quad abcd , use crossProduct(c - a, d - b)e ele lidará bem com casos em que o quad é de fato um triângulo.

Iñigo Quilez escreveu alguns artigos curtos sobre o tema: normalização inteligente de uma malha , e normal e área de n lados polígonos .

Normais de derivadas parciais

As normais podem ser computadas no sombreador de fragmentos a partir das derivadas parciais. A matemática por trás é a mesma, exceto que desta vez é feita no espaço da tela. Este artigo de Angelo Pesce descreve a técnica: Normais sem normais .

Julien Guertault
fonte
1
Há uma quarta forma, artista fornecido normais;)
joojaa
@joojaa: Presumo que você esteja se referindo a mapas normais? Eu nunca ouvi falar de normais criados manualmente de outra maneira.
Julien Guertault
1
Não, normais criados manualmente. Às vezes, o artista sabe mais sobre como os normais devem se comportar do que os modelos dos programadores. Às vezes, é um pouco problemático para os mecanismos de cálculo se eles assumem que os normais vêm de cálculos subjacentes. Mas certamente isso acontece e você economiza muito tempo na modelagem matemática.
Joojaa
1
Às vezes, eles são chamados de "normais explícitos" (terminologia 3ds max e maya).
Dusan Bosnjak 'pailhead'