Programação com OpenGL/Modern OpenGL Tutorial 05

Nosso animação do triângulo é divertido, mas vamos aprender agora sobre gráficos em 3D.

Vamos criar um cubo!

Colocando na 3ª Dimensão

editar
 
Coordinate system

Um cubo são 8 vértices em um espaço 3D (4 pontos na face frontal, 4 pontos na face traseira) Podemos renome o triângulo para cube Também comentaremos sobre as vinculações de fade

Agora vamos escreve as vertices do cubo. Vamos posicionar nosso sistema de coordenadas(X,Y,Z) o melhor possível para imagem. Vamos escrever ele assim para relacionar com o centro do objeto. É mais limpo assim, e ainda permitira rodar o cubo em torno do seu centro depois:

Nota: Aqui, a coordenada Z é no sentido do utilizador. Você pode procurar outras convenções que o Z ficara no topo(altura) como o Blender, mas no OpenGL´s o padrão é Y-is-up(Y é para cima )


  GLfloat cube_vertices[] = {
    // front
    -1.0, -1.0,  1.0,
     1.0, -1.0,  1.0,
     1.0,  1.0,  1.0,
    -1.0,  1.0,  1.0,
    // back
    -1.0, -1.0, -1.0,
     1.0, -1.0, -1.0,
     1.0,  1.0, -1.0,
    -1.0,  1.0, -1.0,
  };

Para algo melhor que um massa preta, vamos então definir algumas cores:

  GLfloat cube_colors[] = {
    // Cores de frente
    1.0, 0.0, 0.0,
    0.0, 1.0, 0.0,
    0.0, 0.0, 1.0,
    1.0, 1.0, 1.0,
    // Cores do Fundo
    1.0, 0.0, 0.0,
    0.0, 1.0, 0.0,
    0.0, 0.0, 1.0,
    1.0, 1.0, 1.0,
  };

Não esqueça do global buffer handles(manipuladores globais do buffer):

GLuint vbo_cube_vertices, vbo_cube_colors;

Elementos - Index Buffer Objects (IBO)

editar

Nosso cubo tem 6 faces. Duas faces são compartilhadas com algumas vértices, além disso nossa face como uma combinação de 2 triângulos (assim teremos 12 triângulos)

Por isto vamos introduzir o conceito de elementos: vamos usar o glDrawElements, em vez de glDrawArrays. É preciso um conjunto de indices que farão referencia ao array de vértices. Com glDrawElements, nós podemos especificar qualquer ordem, e até mesmo algumas vértices várias vezes. Vamos guardar estes índices em um Index Buffer Object (IBO).

É melhor especificar todas as faces de modo similar, em modo anti-horário, porque isto será importante para o mapeamento de textura(veremos no próximo tutorial) e luz.(assim um triângulo normal precisam apontar para o caminho certo)

/* Global */
GLuint ibo_cube_elements;
/* init_resources */
  GLushort cube_elements[] = {
    // front
    0, 1, 2,
    2, 3, 0,
    // top
    3, 2, 6,
    6, 7, 3,
    // back
    7, 6, 5,
    5, 4, 7,
    // bottom
    4, 5, 1,
    1, 0, 4,
    // left
    4, 0, 3,
    3, 7, 4,
    // right
    1, 5, 6,
    6, 2, 1,
  };
  glGenBuffers(1, &ibo_cube_elements);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_cube_elements);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(cube_elements), cube_elements, GL_STATIC_DRAW);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

Note que usamos um buffer object denovo, mas com GL_ELEMENT_ARRAY_BUFFER em vez GL_ARRAY_BUFFER.

Nós vamos chamar o OpenGL para desenhar nosso cubo em onDisplay

  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_cube_elements);
  int size;  glGetBufferParameteriv(GL_ELEMENT_ARRAY_BUFFER, GL_BUFFER_SIZE, &size);
  glDrawElements(GL_TRIANGLES, size/sizeof(GLushort), GL_UNSIGNED_SHORT, 0);

Nós usamos glGetBufferParameteriv para pegar o tamanho do buffer. deste jeito, nós não precisamos declarar cube_elements

Ativando profundidade

editar
glEnable(GL_DEPTH_TEST);
//glDepthFunc(GL_LESS);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

Agora podemos ver a face frontal do quadrado, mas para ver as outras faces do cubo, precisamos roda-lo. Ainda podemos olhar através da remoção de uma(ou duas) face frontal do triângulo. :)

Matriz de Model-View-Projection

editar

Até agora, temos trabalhado com as coordenadas de objetos, especificamente sobre o centro do objeto. Para trabalhar com vários objetos e posicionamento no mundo 3D, nós calcularemos uma matriz de transformação que vai:

  • Mudança da coordenada do modelo (object) para a coordenado do mundo (model->world)
  • Em seguida das coordenadas do mundo para a coordenadas de visualização (câmera) (world->view)
  • Depois as coordenadas de visualização para as coordenadas de projeção(2D screen) (view->projection)

Isto também vai cuidar do problema em relação ao aspecto.

O objetivo é calcular a matriz global de transformação. chamado MVP, que vamos aplicar em cada vértice para obter os pontos finais da visualização 2D.

Note que as coordenadas da tela 2D estão no intervalo [-1,1]. Existe um 4º passo non-matrix para converter estes para [0, screen_size], controlado por glViewPort

Nota histórica: O OpenGL 1.x tinha duas matrizes inclusas, acessível através de glMatrixModel(GL_PROJECTION) e glMatrixMode(GL_MODELVIEW). Aqui nós estamos substituindo eles, e estamos colocando uma camera :)

Vamos colocar em nosso código na função onIdle, onde nós atualizamos o uniform fade no tutorial anterior, mas ao invés disto nós passaremos um uniform mvp.

Iniciando: No começo de cada fase, nós temos uma matriz identidade, que não faz nenhuma transformação, criado usando glm::mat4(1.0f).

Modelo(Model): Vamos colocar nosso cubo um pouco em segundo plano, então não misturaremos com a câmera:

  glm::mat4 model = glm::translate(glm::mat4(1.0f), glm::vec3(0.0, 0.0, -4.0));

Visualização(View): O GLM tem uma ré-implementação do gluLookAt(eye, center, up). O eye é a posição da camera, o center é onde a camera é apontada, e o up é o topo da camera (no caso é inclinado). Vamos centralizar nosso objeto um pouco acima, com a camera reta:

  glm::mat4 view = glm::lookAt(glm::vec3(0.0, 2.0, 0.0), glm::vec3(0.0, 0.0, -4.0), glm::vec3(0.0, 1.0, 0.0));

Projeção(Projection): O GLM também possui um re-implementação do gluPerspective(fovy, aspect, zNear, zFar). fovy é o angulo da lente, aspect é o aspecto da tela (largura/altura), zNear e ZFar são os limites da visualização aérea (min/max profundidade), ambos positivo, zNear é um valor pequeno e diferente de zero. Nós precisamos ver nosso quadrado, então usuaremos 10 para zFar.

  glm::mat4 projection = glm::perspective(45.0f, 1.0f*screen_width/screen_height, 0.1f, 10.0f);

O screen_width e screen_height serão uma nova variável global que definirá o tamanho da janela:

/* global */
int screen_width=800, screen_height=600;
/* main */
  glutInitWindowSize(screen_width, screen_height);


Resultado:

  glm::mat4 mvp = projection * view * model;

Poderemos passar ele para o shader:

  /* Global */
  #include <glm/gtc/type_ptr.hpp>
  GLint uniform_mvp;
  /* init_resources() */
  const char* uniform_name;
  uniform_name = "mvp";
  uniform_mvp = glGetUniformLocation(program, uniform_name);
  if (uniform_mvp == -1) {
    fprintf(stderr, "Could not bind uniform %s\n", uniform_name);
    return 0;
  }
  /* onIdle() */
  glUniformMatrix4fv(uniform_mvp, 1, GL_FALSE, glm::value_ptr(mvp));

E no shader:

uniform mat4 mvp;
void main(void) {
  gl_Position = mvp * vec4(coord3d, 1.0);
  [...]

Animação

editar
 
Nosso cubo, rodando

Para animar o objeto, nós poderemos aplicar uma simples transformação antes da matriz do Modelo(Model).

Para rodar o cubo, vamos colocar no onIdle:

  float angle = glutGet(GLUT_ELAPSED_TIME) / 1000.0 * 45;  // 45° por segundos
  glm::vec3 axis_y(0.0, 1.0, 0.0);
  glm::mat4 anim = glm::rotate(glm::mat4(1.0f), angle, axis_y);
  [...]
  glm::mat4 mvp = projection * view * model * anim;

Então fizemos uma tradicional cubo com voo rotativo!

Redimensionando a Janela

editar

Para suportar o redimensionamento de janela do GLUT, você pode usar a função glutReshapeFunc:

void onReshape(int width, int height) {
  screen_width = width;
  screen_height = height;
  glViewport(0, 0, screen_width, screen_height);
}
/* main */
    glutReshapeFunc(onReshape);

Nota: A cena tende a pular quando é redimensionada - Eu não consigui descobrir de onde vem isto.


Navegue e baixe os códigos completos