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 | editar código-fonte]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 | editar código-fonte]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 | editar código-fonte]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 | editar código-fonte]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 | editar código-fonte]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 | editar código-fonte]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.