Cómo calcular las normales de superficie para la geometría generada

12

Tengo una clase que genera una forma 3D basada en las entradas del código de llamada. Las entradas son cosas como longitud, profundidad, arco, etc. Mi código genera la geometría perfectamente, sin embargo, me encuentro con problemas al calcular las normales de superficie. Cuando está iluminado, mi forma tiene una coloración / textura muy extraña de las normales de superficie incorrectas que se están calculando. De toda mi investigación, creo que mis matemáticas son correctas, parece que algo está mal con mi técnica o método.

En un nivel alto, ¿cómo se hace para calcular mediante programación las normales de superficie para una forma generada? Estoy usando Swift / SceneKit en iOS para mi código, pero una respuesta genérica está bien.

Tengo dos matrices que representan mi forma. Uno es una matriz de puntos 3D que representa los vértices que componen la forma. La otra matriz es una lista de índices de la primera matriz que mapea los vértices en triángulos. Necesito tomar esos datos y generar una tercera matriz que es un conjunto de normales de superficie que ayudan a iluminar la forma. (ver SCNGeometrySourceSemanticNormalen SceneKit` )

La lista de vértices e índices siempre es diferente dependiendo de las entradas a la clase, por lo que no puedo calcular previamente ni codificar las normales de la superficie.

macinjosh
fuente
Necesito más contexto. ¿Estás tratando de calcular las normales analíticas para una superficie paramétrica? ¿Una superficie implícita? ¿O quieres calcular las normales a partir de una malla triangular genérica? ¿O algo mas?
Nathan Reed
Gracias, agregué más detalles. Para responder a su pregunta, necesito calcular las normales a partir de una malla triangular genérica. Aunque para ser claros, la malla es diferente dependiendo de las entradas. Mi forma es una flecha 3D, como ejemplo, aquí hay una captura de pantalla de 2 formas diferentes (es decir, radial y lineal). La clase cambia el ancho, la profundidad, la longitud, el arco y el radio de la malla según lo solicitado. cl.ly/image/3O0P3X3N3d1d Puedes ver la extraña iluminación que recibo con mis pobres intentos de resolver esto.
macinjosh
3
La versión corta es: calcule cada vértice normal como la suma normalizada de las normales de todos los triángulos que lo tocan. Sin embargo, esto facilitará todo, lo que puede no ser lo que desea para esta forma. Intentaré expandirme a una respuesta completa más tarde.
Nathan Reed
¡Suave es lo que estoy buscando!
macinjosh
44
En la mayoría de los casos, si calcula las posiciones de vértice analíticamente, también puede calcular las normales analíticamente. Para una superficie paramétrica, las normales son el producto cruzado de los dos vectores de gradiente. Calcular el promedio de las normales de triángulo es solo una aproximación, y a menudo resulta en una calidad visualmente mucho peor. Publicaría una respuesta, pero ya publiqué un ejemplo detallado en SO ( stackoverflow.com/questions/27233820/… ), y no estoy seguro de si queremos contenido replicado aquí.
Reto Koradi

Respuestas:

10

Simplemente no quieres resultados totalmente suaves. Si bien el método comentado por Nathan Reed: "Calcular cada vértice para que se vea normal, sumarlos, normalizar suma", generalmente funciona, a veces falla espectacularmente. Pero eso no tiene importancia aquí, podemos usar ese método agregándole una cláusula de rechazo.

En este caso, simplemente desea que ciertas partes no se suavicen contra otras partes. Quieres bordes duros selectivos. Entonces, por ejemplo, la parte superior y la parte inferior planas están separadas de la banda triangular en el costado, al igual que cada área plana.

Imagen que buscamos

Imagen 1 : El resultado que deseas.

En efecto, solo desea promediar los vértices del área curva, todos los demás pueden usar lo normal que obtienen solo de su triángulo. Por lo tanto, es mejor pensar en la malla como 9 regiones separadas que se manejan sin las otras.

Mostrando mallas y normales]

Imagen 2 : Imagen que muestra la estructura de malla y las normales.

Ciertamente puede deducir esto automáticamente al no incluir las normales que están fuera de cierto ángulo de los vértices primarios normales. Pseudocó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)

Eso funciona, pero puedes evitar todo esto en el momento de la creación porque entiendes que los planos separados funcionan de manera diferente. Por lo tanto, solo los lados curvos necesitan fusión de dirección normal. Y, de hecho, puede calcularlos directamente desde la forma matemática subyacente.

joojaa
fuente
10

Veo principalmente tres formas de calcular las normales para una forma generada.

Normales analíticas

En algunos casos, tiene suficiente información sobre la superficie para generar las normales. Por ejemplo, la normalidad de cualquier punto en una esfera es trivial de calcular. En pocas palabras, cuando conoce la derivada de la función, también conoce la normalidad.

Si su caso es lo suficientemente estrecho como para permitirle usar valores normales analíticos, probablemente le darán el mejor resultado en términos de precisión. Sin embargo, la técnica no escala demasiado bien: si también necesita manejar casos en los que no puede usar valores normales analíticos, puede ser más fácil mantener la técnica que maneja el caso general y descartar el analítico por completo.

Vértex normales

El producto cruzado de dos vectores da un vector perpendicular al plano al que pertenecen. Entonces obtener la normalidad de un triángulo es sencillo:

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

Además, en el ejemplo anterior, la longitud del producto cruzado es proporcional al área dentro de abc . Por lo tanto, la normal suavizada en un vértice compartido por varios triángulos se puede calcular sumando los productos cruzados y normalizándolos como un último paso, ponderando cada triángulo por su á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);
}

Si está trabajando con quads, hay un buen truco que puede usar: para un quad abcd , use crossProduct(c - a, d - b)y manejará muy bien los casos en que el quad es en realidad un triángulo.

Iñigo quilez escribió algunos artículos breves sobre el tema: normalización inteligente de una malla y normal y área de polígonos de n lados .

Normales de derivadas parciales

Las normales se pueden calcular en el sombreador de fragmentos a partir de las derivadas parciales. La matemática detrás es la misma, excepto que esta vez se hace en el espacio de la pantalla. Este artículo de Angelo Pesce describe la técnica: normales sin normales .

Julien Guertault
fuente
1
Hay una cuarta forma, el artista proporcionó normales;)
joojaa
@joojaa: ¿Asumo que te refieres a los mapas normales? Nunca he oído hablar de normales creadas manualmente de lo contrario.
Julien Guertault
1
No, normales creadas manualmente. A veces sucede que su artista sabe más sobre cómo deberían comportarse las normales que los modelos de programadores. A veces es un poco problemático para los motores de cálculo si suponen que las normales provienen de cálculos subyacentes. Pero ciertamente sucede y ahorras mucho tiempo en modelado matemático.
joojaa
1
A veces se les conoce como "normales explícitas" (3ds max y terminología maya).
Dusan Bosnjak 'pailhead'