OpenGL: por que tenho que definir um normal com o glNormal?

19

Estou aprendendo alguns conceitos básicos do OpenGL, mas estou me perguntando por que existe uma chamada glNormalpara definir o normal dos vértices.

Se eu criar um triângulo simples como este:

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

Os normais não deveriam ser definidos implicitamente pelo tipo de primitivo geométrico? Se eu não definir um normal, o OpenGL o calculará?

xblax
fonte
7
Gostaria apenas de salientar que esse código está usando um pipeline de função fixa reprovado e, no OpenGL moderno, os normais e os vértices são apenas atributos genéricos de vértices.
Bartek Banachewicz
4
Não use o modo imediato. Está obsoleto. Eu recomendo aprender a programação moderna de gráficos 3D .
rightfold
3
Eu acho que comentar sobre o uso do modo imediato é completamente irrelevante aqui. Sim, não se deve usá-lo, mas o ponto da pergunta permanece o mesmo, independentemente do modo imediato, matrizes de vértices, VBO, atributos genéricos, atributos fixos ou pedaços de torrada.
Maximus Minimus 8/03/13
1
A razão pela qual não é determinada automaticamente é porque isso tornaria a iluminação muito poligônica (por falta de uma palavra melhor). O que quero dizer com isso é que todas as vértices em um triângulo teriam o mesmo valor normal. Para superfícies planas, é o que esperávamos; no entanto, se você tivesse um modelo de rosto de uma pessoa, cada triângulo na face teria o mesmo valor de iluminação (facilitando a visualização dos polígonos). Ao especificar seus próprios valores normais, você pode tornar as coisas muito mais suaves (normalmente você encontra um normal calculando a média dos valores normais de todos os triângulos que contêm o vértice atual).
Benjamin Perigo Johnson

Respostas:

30

Chamar glNormalvárias vezes entre glBegin/Endpermite definir um normal por vértice, em vez de por primitivo.

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 uma malha mais complexa que consiste em vários triângulos, você deseja que o normal em um vértice dependa dos outros triângulos da geometria circundante e não apenas do normal do triângulo ao qual pertence.

Aqui está um exemplo da mesma geometria com:

  • nos normais por vértice esquerdo - para que cada vértice de uma face tenha um normal diferente (para esta esfera, significa que todos os normais apontam para fora do centro); e
  • nos normais normais por face - cada vértice obtém o mesmo valor normal (desde que você o especifique apenas uma vez ou porque usa o valor normal padrão de (0,0,1)):

normais por vértice à esquerda, normais por face à direita

O modelo de sombra padrão ( GL_SMOOTH) faz com que os normais dos vértices da face sejam interpolados pela face. Para as normais por vértice, isso resulta em uma esfera sombreada suave, mas, para as normais por face, você acaba com a aparência facetada característica.

Eric
fonte
4

Não, o GL não calcula uma norma para você. Não sei como deve ser o normal. Suas primitivas não significam nada fora da rasterização (e alguns outros lugares). O GL não sabe quais faces (triângulos) compartilham um vértice (calcular isso em tempo de execução é muito caro sem o uso de diferentes estruturas de dados).

Você precisa pré-processar a "sopa triangular" de uma malha para encontrar vértices comuns e calcular normais. Isso deve ser feito antes que o jogo corra para a velocidade.

Também existem casos como arestas duras em que geometricamente existe um vértice compartilhado no canto, mas você deseja normais separados para que acenda corretamente. No modelo de renderização GL / D3D, você precisa de dois vértices separados (na mesma posição) para isso, cada um com um normal separado.

Você precisará de tudo isso para UVs e mapeamento de textura também.

Sean Middleditch
fonte
3

A resposta curta é que você realmente não precisa definir um normal. Os vetores normais são usados ​​apenas se você estiver executando a iluminação OpenGL (ou talvez algum outro cálculo que possa depender deles), mas se isso não se aplicar a você (ou se você estiver usando algum outro método para fazer iluminação, por exemplo, mapas de luz ), é possível omitir com prazer todas as chamadas glNormal.

Então, vamos supor que você esteja fazendo algo em que especificar glNormal faz sentido e analisar mais um pouco.

glNormal pode ser especificado por primitivo ou por vértice. Com os normais per primitivos, tudo o que isso significa é que todos os normais de cada vértice na face primitiva na mesma direção. Às vezes é isso que você deseja, mas às vezes não é - onde os normais por vértice são úteis é que permitem que cada vértice tenha sua própria face.

Veja o exemplo clássico de uma esfera. Se cada triângulo em uma esfera tivesse o mesmo normal, o resultado final seria bastante facetado. Provavelmente não é isso que você deseja, mas sim um sombreamento suave. Portanto, o que você faz é calcular a média normal de cada triângulo que compartilha um vértice comum e obter um resultado sombreado suave.

Maximus Minimus
fonte
2

Exemplo mínimo que ilustra alguns detalhes de como glNormalfunciona com raios difusos.

Os comentários da displayfunção explicam o que cada triângulo significa.

insira a descrição da imagem aqui

#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;
}

Teoria

No OpenGL, cada vértice possui seu próprio vetor normal associado.

O vetor normal determina a luminosidade do vértice, que é usado para determinar a luminosidade do triângulo.

Quando uma superfície é perpendicular à luz, é mais brilhante que uma superfície paralela.

glNormal define o vetor normal atual, usado para todos os seguintes vértices.

O valor inicial para o normal antes de todos nós glNormalé 0,0,1.

Os vetores normais devem ter a norma 1 ou as cores mudam! glScaletambém altera a duração dos normais! glEnable(GL_NORMALIZE);faz com que o OpenGL defina automaticamente sua norma como 1 para nós. This GIF ilustra isso lindamente.

Bibliografia:

Ciro Santilli adicionou uma nova foto
fonte
Boa resposta, mas por que focar em uma pergunta antiga e em uma tecnologia antiga?
Vaillancourt
@AlexandreVaillancourt thanks! Pergunta antiga: por que não :-) Tecnologia antiga: qual é a melhor maneira hoje?
Ciro Santilli escreveu
Eu acho que o OpenGL moderno usa objetos de buffer em vez de especificar o glNormal.
Vaillancourt
1
De fato, glNormal e glVertex e similares desapareceram no OpenGL moderno e foram preteridos algum tempo antes disso ... eles foram preteridos mesmo quando essa pergunta foi feita originalmente.
0

Você precisa do glNormal para implementar alguns recursos dependentes do codificador. Por exemplo, você pode querer criar normais para preservar arestas duras.

AB
fonte
4
Talvez você queira melhorar um pouco sua resposta.
Bartek Banachewicz
1
Se você quer que eu melhore minha resposta, você precisa apontar os pontos fracos. Eu posso elaborar sobre isso embora. Como xblax costumava pensar, cada "normal" deveria ser calculado da mesma maneira. Mesmo apesar da diferença entre normais interpolados para cada pixel vs cada face. Vamos nos aprofundar um pouco mais no assunto.
AB.
1
Às vezes, você pode querer manter a vantagem e torná-la mais distinta. Aqui estão algumas imagens de exemplo: titan.cs.ukzn.ac.za/opengl/opengl-d5/trant.sgi.com/opengl/… PS. Desculpe pela postagem dupla. Eu sou novo na interface SE
AB.
há um botão "editar".
Bartek Banachewicz
1
Você não pode editar após 5 minutos
AB.