OpenGL: ¿por qué tengo que establecer un normal con glNormal?

19

Estoy aprendiendo algunos conceptos básicos de OpenGL, pero me pregunto por qué hay una llamada glNormalpara establecer la normalidad de los vértices.

Si creo un triángulo simple como este:

glBegin(GL_TRIANGLES);
    glVertex3f(0,0,0);
    glVertex3f(1,0,0);
    glVertex3f(0,1,0);
glEnd();

¿No deberían definirse las normales implícitamente por el tipo de primitivo geométrico? Si no establezco un valor normal, ¿OpenGL lo calculará?

xblax
fuente
77
Solo me gustaría señalar que este código está usando una tubería de función fija en desuso, y en OpenGL moderno, tanto las normales como los vértices son solo atributos de vértice genéricos.
Bartek Banachewicz
44
No uses el modo inmediato. Está en desuso. Recomiendo aprender la programación moderna de gráficos 3D .
derecha el
3
Creo que el comentario sobre el uso del modo inmediato es completamente irrelevante aquí. Sí, uno no debería usarlo, pero el punto de la pregunta sigue siendo el mismo, independientemente de si se trata de modo inmediato, matrices de vértices, VBO, atributos genéricos, atributos fijos o tostadas.
Maximus Minimus
1
La razón por la que no se determina automáticamente es porque haría que la iluminación fuera muy poligonal (por falta de una palabra mejor). Lo que quiero decir con esto es que todas las vértices en un triángulo tendrían el mismo valor normal. Para superficies planas esto es lo que esperaríamos, sin embargo, si tuviera un modelo de una cara de personas, cada triángulo en la cara tendría el mismo valor de iluminación (por lo que es muy fácil ver los polígonos). Al especificar sus propios valores normales, puede hacer que las cosas se vean mucho más suaves (normalmente encuentra un valor normal promediando los valores normales de todos los triángulos que contienen el vértice actual).
Benjamin Danger Johnson

Respuestas:

30

Llamar glNormalvarias veces entre ellas le glBegin/Endpermite establecer una normal por vértice en lugar de por primitiva.

glBegin(GL_TRIANGLES);
    glNormal3f(0,0,1);
    glVertex3f(0,0,0);
    glNormal3f(0,0,1);
    glVertex3f(1,0,0);
    glNormal3f(0,0,1);
    glVertex3f(0,1,0);
glEnd();

Para una malla más compleja que consta de múltiples triángulos, desearía que la normal en un vértice dependa de los otros triángulos de la geometría circundante y no solo de la normal del triángulo al que pertenece.

Aquí hay un ejemplo de la misma geometría con:

  • a la izquierda de las normales por vértice , por lo que cada vértice de una cara tiene una normal diferente (para esta esfera significa que todas las normales apuntan hacia afuera desde su centro), y
  • en las normales normales por cara : cada vértice obtiene el mismo valor normal (ya que lo especifica solo una vez o porque usa el valor normal predeterminado de (0,0,1)):

normales por vértice a la izquierda, normales por rostro a la derecha

El modelo de sombra predeterminado ( GL_SMOOTH) hace que las normales de los vértices de la cara se interpolen a través de la cara. Para las normales por vértice, esto da como resultado una esfera sombreada suave, pero para las normales por cara , termina con el aspecto facetado característico.

Eric
fuente
4

No, GL no calcula una norma para usted. No puede saber cuál debería ser lo normal. Sus primitivas no significan nada fuera de la rasterización (y algunos otros lugares). GL no sabe qué caras (triángulos) comparten un vértice (calcular esto en tiempo de ejecución es muy costoso sin usar diferentes estructuras de datos).

Debe preprocesar una "sopa de triángulos" de una malla para encontrar vértices comunes y calcular valores normales. Esto debe hacerse antes de que el juego corra por velocidad.

También hay casos como bordes duros en los que geométricamente hay un vértice compartido en la esquina, pero desea que las normales sean separadas para que se ilumine correctamente. En el modelo de representación GL / D3D, necesita dos vértices separados (en la misma posición) para esto, cada uno con una normalidad separada.

Necesitará todo esto para UV y mapeo de texturas también.

Sean Middleditch
fuente
3

La respuesta corta es que en realidad no tiene que establecer una normalidad en absoluto. Los vectores normales solo se usan si está realizando iluminación OpenGL (o tal vez algún otro cálculo que pueda depender de ellos), pero si eso no se aplica a usted (o si está utilizando algún otro método para iluminar, por ejemplo, mapas de luz ), entonces puede omitir todas las llamadas glNormal.

Así que supongamos que está haciendo algo donde especificar glNormal tiene sentido y veamos un poco más.

glNormal se puede especificar por primitivo o por vértice. Con normales por primitivas todo lo que significa es que todas las normales para cada vértice en la cara primitiva en la misma dirección. A veces esto es lo que quiere, pero a veces no lo es; donde las normales por vértice son útiles es que permiten que cada vértice tenga su propia orientación.

Tome el ejemplo clásico de una esfera. Si cada triángulo en una esfera tuviera la misma normalidad, entonces el resultado final sería bastante facetado. Probablemente eso no sea lo que quieres, en su lugar quieres un sombreado suave. Entonces, lo que debe hacer es promediar las normales para cada triángulo que comparte un vértice común y obtener un resultado sombreado suave.

Maximus Minimus
fuente
2

Ejemplo mínimo que ilustra algunos detalles de cómoglNormal funciona con un rayo difuso.

Los comentarios de la displayfunción explican qué significa cada triángulo.

ingrese la descripción de la imagen aquí

#include <stdlib.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

/* Triangle on the x-y plane. */
static void draw_triangle() {
    glBegin(GL_TRIANGLES);
    glVertex3f( 0.0f,  1.0f, 0.0f);
    glVertex3f(-1.0f, -1.0f, 0.0f);
    glVertex3f( 1.0f, -1.0f, 0.0f);
    glEnd();
}

/* A triangle tilted 45 degrees manually. */
static void draw_triangle_45() {
    glBegin(GL_TRIANGLES);
    glVertex3f( 0.0f,  1.0f, -1.0f);
    glVertex3f(-1.0f, -1.0f,  0.0f);
    glVertex3f( 1.0f, -1.0f,  0.0f);
    glEnd();
}

static void display(void) {
    glColor3f(1.0f, 0.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glPushMatrix();

    /*
    Triangle perpendicular to the light.
    0,0,1 also happens to be the default normal if we hadn't specified one.
    */
    glNormal3f(0.0f, 0.0f, 1.0f);
    draw_triangle();

    /*
    This triangle is as bright as the previous one.
    This is not photorealistic, where it should be less bright.
    */
    glTranslatef(2.0f, 0.0f, 0.0f);
    draw_triangle_45();

    /*
    Same as previous triangle, but with the normal set
    to the photorealistic value of 45, making it less bright.

    Note that the norm of this normal vector is not 1,
    but we are fine since we are using `glEnable(GL_NORMALIZE)`.
    */
    glTranslatef(2.0f, 0.0f, 0.0f);
    glNormal3f(0.0f, 1.0f, 1.0f);
    draw_triangle_45();

    /*
    This triangle is rotated 45 degrees with a glRotate.
    It should be as bright as the previous one,
    even though we set the normal to 0,0,1.
    So glRotate also affects the normal!
    */
    glTranslatef(2.0f, 0.0f, 0.0f);
    glNormal3f(0.0, 0.0, 1.0);
    glRotatef(45.0, -1.0, 0.0, 0.0);
    draw_triangle();

    glPopMatrix();
    glFlush();
}

static void init(void) {
    GLfloat light0_diffuse[] = {1.0, 1.0, 1.0, 1.0};
    /* Plane wave coming from +z infinity. */
    GLfloat light0_position[] = {0.0, 0.0, 1.0, 0.0};
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glShadeModel(GL_SMOOTH);
    glLightfv(GL_LIGHT0, GL_POSITION, light0_position);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, light0_diffuse);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glColorMaterial(GL_FRONT, GL_DIFFUSE);
    glEnable(GL_COLOR_MATERIAL);
    glEnable(GL_NORMALIZE);
}

static void reshape(int w, int h) {
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-1.0, 7.0, -1.0, 1.0, -1.5, 1.5);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

int main(int argc, char** argv) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
    glutInitWindowSize(800, 200);
    glutInitWindowPosition(100, 100);
    glutCreateWindow(argv[0]);
    init();
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutMainLoop();
    return EXIT_SUCCESS;
}

Teoría

En OpenGL, cada vértice tiene su propio vector normal asociado.

El vector normal determina qué tan brillante es el vértice, que luego se usa para determinar qué tan brillante es el triángulo.

Cuando una superficie es perpendicular a la luz, es más brillante que una superficie paralela.

glNormal establece el vector normal actual, que se usa para todos los vértices siguientes.

El valor inicial de lo normal antes de que todos estemos glNormales0,0,1 .

Los vectores normales deben tener la norma 1, ¡o los colores cambian! glScale¡También altera la duración de las normales! glEnable(GL_NORMALIZE);hace que OpenGL establezca automáticamente su norma en 1 para nosotros. Este GIF lo ilustra maravillosamente.

Bibliografía:

Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
fuente
Buena respuesta, pero ¿por qué centrarse en una vieja pregunta y una vieja tecnología?
Vaillancourt
@AlexandreVaillancourt gracias! Vieja pregunta: por qué no :-) Vieja tecnología: ¿cuál es la mejor manera hoy?
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
Creo que el OpenGL moderno usa objetos de búfer en lugar de especificar glNormal.
Vaillancourt
1
De hecho, glNormal y glVertex y similares han desaparecido en OpenGL moderno, y fueron desaprobados algún tiempo antes de eso ... de hecho fueron desaprobados incluso cuando esta pregunta se hizo originalmente.
0

Necesita glNormal para implementar algunas funciones dependientes del codificador. Por ejemplo, es posible que desee crear normales para preservar los bordes duros.

AB.
fuente
44
Quizás quieras mejorar un poco tu respuesta.
Bartek Banachewicz
1
Si quieres que mejore mi respuesta, deberías señalar los puntos débiles. Sin embargo, puedo explicarlo. Como xblax solía pensar, cada "debería" normal se calcula de la misma manera. Incluso a pesar de la diferencia entre las normales interpoladas para cada píxel frente a cada cara. Analicemos un poco más el tema.
AB.
1
A veces es posible que desee mantener el borde duro y hacerlo más distintivo. Aquí hay algunas imágenes de muestra: titan.cs.ukzn.ac.za/opengl/opengl-d5/trant.sgi.com/opengl/… PS. Perdón por la doble publicación. Soy nuevo en la interfaz SE
AB.
hay un botón "editar".
Bartek Banachewicz
1
No puede editar después de 5 minutos
AB.