La mejor manera de calcular las normales de vértice de la lista de un triángulo

8

hola soy un novato completo en informática, lo siento si es una respuesta estúpida. Estoy tratando de hacer un simple motor 3D desde cero, más con fines educativos que para uso real.

por ahora calculo solo las normales de la cara. De este modo:

Tengo un objeto Surface dentro de la lista de un triángulo. Calculo las normales dentro de la clase Triangle, de esta manera:

triangle.computeFaceNormals() {
    Vec3D u = v1.sub(v3)
    Vec3D v = v1.sub(v2)
    Vec3D normal = Vec3D.cross(u,v)
    normal.normalized()
    this.n1 = this.n2 = this.n3 = normal
}

y al construir la superficie:

t = new Triangle(v1,v2,v3)
t.computeFaceNormals()
surface.addTriangle(t)

y creo que esta es la mejor manera de hacerlo ... ¿no?

ahora ... esto funciona, está bien. Pero la luz no está suavizada. Estoy tratando de calcular también las normales de vértice. (Estoy probando mi motor con superficies tubulares, por lo que tengo casi todos los vértices compartidos con más de un triángulo)

He encontrado este algoritmo simple: flipcode vertex normal pero ... hei este algoritmo tiene ... ¿complejidad exponencial? (si mi memoria no falla mi experiencia en informática ...) (por cierto ... tiene 3 bucles anidados ... no creo que sea la mejor manera de hacerlo ...)

¿cualquier sugerencia?

nkint
fuente
¿Qué lenguaje es este? Me parece que tes el resultado de computeFaceNormals(que no devuelve nada), no un triángulo.
es pseudocódigo:) tienes razón en el código real, también he "devuelto esto", lo siento, he editado!
nkint

Respuestas:

6

"Lo mejor" es bastante subjetivo: implicará sopesar lo que necesita del algoritmo frente a sus entradas, la complejidad del tiempo de ejecución y otras propiedades.

Dicho esto, tanto su enfoque como el enfoque FlipCode vinculado son razonables en términos de producir resultados utilizables (simplemente podría copiar sus 'caras normales' a cada vértice, si no está compartiendo instancias de vértices reales entre triángulos, lo que no estoy claro desde su código). Otras técnicas incluyen ponderar la contribución de cada cara normal por el tamaño del ángulo hecho con cada cara compartida por el vértice.

Tiene razón en que el enfoque FlipCode parece subóptimo como está escrito, pero podría estar mal especificado: no está claro si tiene la intención de sugerir que el segundo bucle atraviese todas las caras en el modelo, en comparación con el puñado de caras que comparten el vértice en cuestión. Si tiene la información de adyacencia para reducir el espacio de búsqueda del segundo bucle, se vuelve menos preocupante. Por supuesto, es posible que no tenga esa información de adyacencia; esto es algo a lo que me refiero al considerar qué entradas tiene disponibles para su algoritmo.

Si no solo desea copiar la cara normal en los vértices, o si está compartiendo vértices y no desea dividirlos, podría:

foreach vertex:
   set vertex normal to (0,0,0)

foreach triangle:
   foreach vertex on that triangle:
      set vertex normal = normalize( vertex normal + face normal )

Esto supone que cada triángulo en realidad hace referencia a cada vértice en lugar de almacenar una copia del mismo: no sé qué idioma está utilizando, así que no sé si ese es el caso o no.


fuente
lo siento, el inglés no es mi primer idioma, así que quizás he explicado de mala manera lo que quiero ... ¡todavía no tengo vértices normales! He editado un poco mi respuesta, espero que ahora sea más claro
nkint
Está bien, entendí que no tenías vértices normales. El pseudocódigo que proporcioné los calculará para usted al establecer primero la normal de cada vértice en (0,0,0) y luego resumir las normales de la cara para cada cara que toca el vértice (y volver a normalizar, por supuesto).
55
No deberías normalizar las normales de esta manera. Debería normalizar después de todas las sumas en otro foreach. Si hay, por ejemplo, 10 triángulos con la misma normalidad, pero la última es diferente (el resultado normal debería ser casi igual a la normal de 10 triángulos), entonces tendrá esto, al sumar el último: establecer el vértice nromal = normalizar (( 1,0,0) + (0,0,1)) y eso es (0.5,0,0.5), lo cual está mal. Espero no haberte confundido.
zacharmarz
7

Josh Petrie tiene razón. Si desea calcular las normales de vértice, debe ponderar la contribución del triángulo por ángulo. Pero puede usar una solución ingenua y simplemente sumar todas las normales de todas las caras alrededor del vértice.

Así que solo calcula todas las normales de la cara y las normaliza. Luego establezca todas las normales de vértice a cero. Luego, para cada cara (triángulo) agregue su normal a todos sus vértices. Luego normalice todas las normales de vértice. Y ya está hecho.

No es demasiado correcto, pero podría ser suficiente.

Y su cálculo normal de la cara anterior es correcto, pero debe saber de qué lado son las cabezas normales. Podría subir o bajar. Depende del producto cruzado: AxB no es lo mismo que BxA, le da el vector opuesto.

zacharmarz
fuente
Buen punto con respecto al producto cruzado.
2

Supongamos que tenemos un cubo hecho de 6 rectángulos. Ya sabemos que el vértice normal para un cubo no es la suma normalizada de las caras de conexión debido al borde afilado. Si construye un mapa de valor de vértice y son diferentes vértices normales para un cubo, terminará con 3 normales para cada vértice.

Teniendo en cuenta el punto anterior, así es como calculo las normales de vértice (lo he probado y lo estoy usando).

class Face{
    uint vert_indices[3];
    vec3 surface_normal;
    vec3 vertex_normal[3];
}

Cada cara realiza un seguimiento de las normales del vértice a utilizar, ya que dos caras pueden compartir un vértice no significa que la vértice normal sea la misma para cada cara.

Supongamos que estamos tratando de calcular el vértice normal para vert al representar face2 .

vert es compartido por face0 , face1 , face2 , face3 y face4 . Todas las caras anteriores son vecinas en orden y face0 y face4 se conectan para formar un bucle.

El vértice normal para vert es la suma de toda la cadena vecina conectada a face2 normalizada. La cadena se detiene si el ángulo entre dos caras vecinas es superior a 0,8 radianes (ángulo == arccos (crossProduct (faceA.surface_normal, faceB.surface_normal))).

si el ángulo entre face0 y face1 es más de 0.8 radian y el ángulo entre face3 y face4 es más de 0.8 radian que el vértice normal para vert cuando renderizar face2 está normalizado ( surfnormal1 + surfnormal2 + surfnormal3 ).

Fredrique Samuels
fuente