c++ opengl computational-geometry normals

c++ - Cálculo de normales en una malla triangular



opengl computational-geometry (3)

Dibujé una malla triangular con 10000 vértices (100x100) y será una base de césped. Usé gldrawelements () para eso. He buscado todo el día y todavía no puedo entender cómo calcular las normales para esto. ¿Cada vértice tiene sus propias normales o cada triángulo tiene sus propias normales? ¿Puede alguien señalarme en la dirección correcta sobre cómo editar mi código para incorporar las normales?

struct vertices { GLfloat x; GLfloat y; GLfloat z; }vertices[10000]; GLuint indices[60000]; /* 99..9999 98..9998 ........ 01..9901 00..9900 */ void CreateEnvironment() { int count=0; for (float x=0;x<10.0;x+=.1) { for (float z=0;z<10.0;z+=.1) { vertices[count].x=x; vertices[count].y=0; vertices[count].z=z; count++; } } count=0; for (GLuint a=0;a<99;a++){ for (GLuint b=0;b<99;b++){ GLuint v1=(a*100)+b;indices[count]=v1;count++; GLuint v2=(a*100)+b+1;indices[count]=v2;count++; GLuint v3=(a*100)+b+100;indices[count]=v3;count++; } } count=30000; for (GLuint a=0;a<99;a++){ for (GLuint b=0;b<99;b++){ indices[count]=(a*100)+b+100;count++;//9998 indices[count]=(a*100)+b+1;count++;//9899 indices[count]=(a*100)+b+101;count++;//9999 } } } void ShowEnvironment(){ //ground glPushMatrix(); GLfloat GroundAmbient[]={0.0,0.5,0.0,1.0}; glMaterialfv(GL_FRONT,GL_AMBIENT,GroundAmbient); glEnableClientState(GL_VERTEX_ARRAY); glIndexPointer( GL_UNSIGNED_BYTE, 0, indices ); glVertexPointer(3,GL_FLOAT,0,vertices); glDrawElements(GL_TRIANGLES,60000,GL_UNSIGNED_INT,indices); glDisableClientState(GL_VERTEX_ARRAY); glPopMatrix(); }

EDITAR 1 Aquí está el código que he escrito. Acabo de usar matrices en lugar de vectores y almacené todas las normales en la estructura llamada normales. Sin embargo, todavía no funciona. Obtengo una excepción no controlada en * índices.

struct Normals { GLfloat x; GLfloat y; GLfloat z; }normals[20000]; Normals* normal = normals; //***************************************ENVIRONMENT************************************************************************* struct vertices { GLfloat x; GLfloat y; GLfloat z; }vertices[10000]; GLuint indices[59403]; /* 99..9999 98..9998 ........ 01..9901 00..9900 */ void CreateEnvironment() { int count=0; for (float x=0;x<10.0;x+=.1) { for (float z=0;z<10.0;z+=.1) { vertices[count].x=x; vertices[count].y=rand()%2-2;; vertices[count].z=z; count++; } } //calculate normals GLfloat vector1[3];//XYZ GLfloat vector2[3];//XYZ count=0; for (int x=0;x<9900;x+=100){ for (int z=0;z<99;z++){ vector1[0]= vertices[x+z].x-vertices[x+z+1].x;//vector1x vector1[1]= vertices[x+z].y-vertices[x+z+1].y;//vector1y vector1[2]= vertices[x+z].z-vertices[x+z+1].z;//vector1z vector2[0]= vertices[x+z+1].x-vertices[x+z+100].x;//vector2x vector2[1]= vertices[x+z+1].y-vertices[x+z+100].y;//vector2y vector2[2]= vertices[x+z+1].z-vertices[x+z+100].z;//vector2z normals[count].x= vector1[1] * vector2[2]-vector1[2]*vector2[1]; normals[count].y= vector1[2] * vector2[0] - vector1[0] * vector2[2]; normals[count].z= vector1[0] * vector2[1] - vector1[1] * vector2[0];count++; } } count=10000; for (int x=100;x<10000;x+=100){ for (int z=0;z<99;z++){ vector1[0]= vertices[x+z].x-vertices[x+z+1].x;//vector1x -- JUST ARRAYS vector1[1]= vertices[x+z].y-vertices[x+z+1].y;//vector1y vector1[2]= vertices[x+z].z-vertices[x+z+1].z;//vector1z vector2[0]= vertices[x+z+1].x-vertices[x+z-100].x;//vector2x vector2[1]= vertices[x+z+1].y-vertices[x+z-100].y;//vector2y vector2[2]= vertices[x+z+1].z-vertices[x+z-100].z;//vector2z normals[count].x= vector1[1] * vector2[2]-vector1[2]*vector2[1]; normals[count].y= vector1[2] * vector2[0] - vector1[0] * vector2[2]; normals[count].z= vector1[0] * vector2[1] - vector1[1] * vector2[0];count++; } } count=0; for (GLuint a=0;a<99;a++){ for (GLuint b=0;b<99;b++){ GLuint v1=(a*100)+b;indices[count]=v1;count++; GLuint v2=(a*100)+b+1;indices[count]=v2;count++; GLuint v3=(a*100)+b+100;indices[count]=v3;count++; } } count=30000; for (GLuint a=0;a<99;a++){ for (GLuint b=0;b<99;b++){ indices[count]=(a*100)+b+100;count++;//9998 indices[count]=(a*100)+b+1;count++;//9899 indices[count]=(a*100)+b+101;count++;//9999 } } } void ShowEnvironment(){ //ground glPushMatrix(); GLfloat GroundAmbient[]={0.0,0.5,0.0,1.0}; GLfloat GroundDiffuse[]={1.0,0.0,0.0,1.0}; glMaterialfv(GL_FRONT,GL_AMBIENT,GroundAmbient); glMaterialfv(GL_FRONT,GL_DIFFUSE,GroundDiffuse); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glNormalPointer( GL_FLOAT, 0, normal); glVertexPointer(3,GL_FLOAT,0,vertices); glDrawElements(GL_TRIANGLES,60000,GL_UNSIGNED_INT,indices); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); glPopMatrix(); } //***************************************************************************************************************************


¿Cada vértice tiene sus propias normales o cada triángulo tiene sus propias normales?

Como tantas veces, la respuesta es: "Depende". Como una normal se define como el vector perpendicular a todos los vectores dentro de un plano dado (en N dimensiones), necesita un plano para calcular una normal. Una posición de vértice es solo un punto y, por lo tanto, singular, por lo que en realidad necesitas una cara para calcular lo normal. Por lo tanto, ingenuamente, se podría suponer que las normales son por cara, ya que el primer paso en el cálculo normal es determinar las caras normales, evaluando el producto cruzado de los bordes de las caras.

Digamos que tienes un triángulo con los puntos A , B , C , entonces estos puntos tienen vectores de posición ↑ A , ↑ B , ↑ C y los bordes tienen vectores ↑ B - ↑ A y ↑ C - ↑ A, por lo que el vector normal de la cara es ↑ N f = (↑ B - ↑ A) × (↑ C - ↑ A)

Tenga en cuenta que la magnitud de ↑ N f como se indica arriba es directamente proporcional al área de la cara.

En superficies lisas, los vértices se comparten entre las caras (o se podría decir que esas caras comparten un vértice). En ese caso, la normal en el vértice no es una de las caras normales de las caras de las que forma parte, sino una combinación lineal de ellas:

↑ N v = Σ p ↑ N f ; donde p es una ponderación para cada cara.

Uno podría asumir una ponderación igual entre las caras normales participantes. Pero tiene más sentido suponer que cuanto más grande es una cara, más contribuye a lo normal.

Ahora recuerde que se normaliza con un vector ↑ v al escalarlo con su longitud recíproca: ↑ v i = ↑ v / | ↑ v | . Pero como ya se dijo, la longitud de las caras normales ya depende del área de la cara. Entonces, el factor p de ponderación dado anteriormente ya está contenido en el vector en sí: su longitud, también conocida como magnitud. Así que podemos obtener el vector normal de vértices simplemente sumando todas las caras normales.

En los cálculos de iluminación, el vector normal debe tener una unidad de longitud, es decir, normalizado para ser utilizable. Entonces, después de resumir, normalizamos el vértice recién encontrado normal y usamos eso.

El lector cuidadoso puede haber notado que específicamente dije que las superficies lisas comparten vértices. Y, de hecho, si tiene algunos pliegues / bordes duros en su geometría, las caras de ambos lados no comparten vértices. En OpenGL, un vértice es la combinación completa de

  • posición
  • normal
  • (color)
  • N coordenadas de textura
  • M atributos adicionales

Cambias uno de estos y obtienes un vértice completamente diferente. Ahora, algunos modeladores 3D ven un vértice solo como la posición de un punto y almacenan el resto de esos atributos por cara (Blender es un modelador de este tipo). Esto ahorra algo de memoria (o memoria considerable, dependiendo de la cantidad de atributos). Pero OpenGL necesita todo, por lo que si trabaja con un archivo de paradigma mixto, primero tendrá que descomponerlo en datos compatibles con OpenGL. Eche un vistazo a uno de los scripts de exportación de Blender, como el exportador PLY para ver cómo se hace.

Ahora para cubrir alguna otra cosa. En tu código tienes esto:

glIndexPointer( GL_UNSIGNED_BYTE, 0, indices );

¡El puntero de índice no tiene nada que ver con los índices de matriz de vértices! Este es un anachronsim de la época, cuando los gráficos aún usaban paletas en lugar de color verdadero. No se configuró un color de píxel por sus valores RGB, sino por un número único que se compensa con una paleta de colores limitada. Los colores de las paletas todavía se pueden encontrar en varios formatos de archivos gráficos, pero ya no hay piezas de hardware decentes que los usen.

Por favor, borre glIndexPointer (y glIndex) de su memoria y su código, no hacen lo que piensan que hacen. Todo el modo de color indexado es arcano para usar, y francamente no conozco ningún hardware creado después de 1998 que todavía sea compatible. eso.


¡Pulgares arriba para datenwolf! Estoy completamente de acuerdo con su enfoque. Agregar los vectores normales de los triángulos adyacentes para cada vértice y luego normalizar es el camino a seguir. Solo quiero presionar un poco la respuesta y echar un vistazo más de cerca al caso particular pero bastante común de una malla lisa y rectangular que tiene un paso x / y constante . En otras palabras, una cuadrícula x / y rectangular con una altura variable en cada punto.

Tal malla se crea haciendo un bucle sobre xey, estableciendo un valor para z y puede representar cosas como la superficie de una colina. Entonces, cada punto de la malla está representado por un vector

P = (x, y, f(x,y))

donde f (x, y) es una función que da la z de cada punto en la cuadrícula.

Por lo general, para dibujar una malla de este tipo usamos TriangleStrip o TriangleFan, pero cualquier técnica debería proporcionar una topografía similar para los triángulos resultantes.

|/ |/ |/ |/ ...--+----U----UR---+--... /| /| 2 /| /| Y / | / | / | / | ^ | / | / | / | / | |/ 1 |/ 3 |/ |/ | ...--L----P----R----+--... +-----> X /| 6 /| 4 /| /| / | / | / | / | | /5 | / | / | / |/ |/ |/ |/ ...--DL---D----+----+--... /| /| /| /|

Para un TriangleStrip cada vértice P = (x0, y0, z0) tiene 6 vértices adyacentes denotados

up = (x0 , y0 + ay, Zup) upright = (x0 + ax, y0 + ay, Zupright) right = (x0 + ax, y0 , Zright) down = (x0 , y0 - ay, Zdown) downleft = (x0 - ax, y0 - ay, Zdownleft) left = (x0 - ax, y0 , Zleft)

donde ax / ay es el paso de la grilla constante en el eje x / y, respectivamente. En una cuadrícula cuadrada ax = ay.

ax = width / (nColumns - 1) ay = height / (nRows - 1)

Por lo tanto, cada vértice tiene 6 triángulos adyacentes, cada uno con su propio vector normal (denotado de N1 a N6). Estos pueden calcularse usando el producto cruzado de los dos vectores que definen el lado del triángulo y teniendo cuidado en el orden en el que hacemos el producto cruzado. Si el vector normal apunta en la dirección Z hacia usted:

N1 = up x left = = (Yup*Zleft - Yleft*Zup, Xleft*Zup - Xup*ZLeft, Xleft*Yup - Yleft*Xup) =( (y0 + ay)*Zleft - y0*Zup, (x0 - ax)*Zup - x0*Zleft, x0*y0 - (y0 + ay)*(x0 - ax) ) N2 = upright x up N3 = right x upright N4 = down x right N5 = downleft x down N6 = left x downleft

Y el vector normal resultante para cada punto P es la suma de N1 a N6. Nos normalizamos después de sumar. Es muy fácil crear un ciclo, calcular los valores de cada vector normal, agregarlos y luego normalizarlos. Sin embargo, como señaló el Sr. Shickadance, esto puede llevar bastante tiempo, especialmente para mallas grandes y / o en dispositivos integrados.

Si observamos más de cerca y realizamos los cálculos a mano, descubriremos que la mayoría de los términos se cancelan mutuamente, dejándonos con una solución final muy elegante y fácil de calcular para el vector resultante N. El punto aquí es agilice los cálculos evitando calcular las coordenadas de N1 a N6, haciendo 6 productos cruzados y 6 adiciones para cada punto. Algebra nos ayuda a ir directamente a la solución, usar menos memoria y menos tiempo de CPU.

No mostraré los detalles de los cálculos, ya que es largo pero directo y saltará a la expresión final del vector Normal para cualquier punto de la cuadrícula. Solo N1 se descompone en aras de la claridad, los otros vectores se parecen. Después de sumar, obtenemos N que aún no está normalizado:

N = N1 + N2 + ... + N6 = .... (long but easy algebra) ... = ( (2*(Zleft - Zright) - Zupright + Zdownleft + Zup - Zdown) / ax, (2*(Zdown - Zup) + Zupright + Zdownleft - Zup - Zleft) / ay, 6 )

¡Aquí tienes! Simplemente normalice este vector y tendrá el vector normal para cualquier punto de la cuadrícula, siempre que conozca los valores Z de sus puntos circundantes y el paso horizontal / vertical de su cuadrícula.

Tenga en cuenta que este es el promedio ponderado de los vectores normales de los triángulos circundantes. El peso es el área de los triángulos y ya está incluido en el producto cruzado.

Incluso puede simplificarlo más solo teniendo en cuenta los valores Z de los cuatro puntos circundantes (arriba, abajo, izquierda y derecha). En ese caso, obtienes:

| /|/ | N = N1 + N2 + N3 + N4 ..--+----U----+--.. = ( (Zleft - Zright) / ax, | /|/ | (Zdown - Zup ) / ay, | / | / | 2 ) / | / 1|2 / | / /|/ | /|/ ..--L----P----R--... /|/ | /|/ / | / 4|3 / | / | / | / | | /|/ | ..--+----D----+--.. | /|/ |

que es aún más elegante e incluso más rápido de calcular.

Espero que esto haga que algunas mallas sean más rápidas. Aclamaciones


Per-vertex

Use productos cruzados para calcular las caras normales de los triángulos que rodean un vértice dado, agréguelos y normalícelos.