O capítulo anterior mostrou todas as matrizes de uma vez. Este desacelera e faz uma coisa só: pega o primeiro triângulo que você desenhou e faz ele girar. São cinco passos pequenos, e você roda o programa depois de cada um. Primeiro ele gira uma vez, depois gira sem parar, depois você o controla pelo teclado. No caminho aparecem as duas coisas que surpreendem todo mundo na primeira vez: o triângulo parece esticado enquanto gira, e ele gira em volta do ponto errado.
Ao final você vai conseguir
- Girar uma forma com
glm::rotatee uma matriz uModel no vertex shader. - Desfazer o esticamento da janela com
glm::ortho, para uma forma que gira manter a forma. - Girar a uma velocidade fixa em graus por segundo, e depois pausar e controlar pelo teclado.
- Girar em volta de qualquer eixo e de qualquer ponto, e girar no lugar ou em órbita.
Este capítulo não mexe no seu main.cpp. Esse arquivo segue para o cubo e para a câmera. Aqui você monta um segundo programa pequeno, chamado spin, ao lado dele. Você o cria uma vez, e Girando o cubo, dois capítulos à frente, continua aumentando o mesmo arquivo.
Acrescente duas linhas no fim do CMakeLists.txt, como o programa mathlab do capítulo de vetores:
1add_executable(spin src/spin.cpp) # NEW: a third program
2target_link_libraries(spin PRIVATE glad glfw glm::glm) # NEW: the same libraries as appDepois crie src/spin.cpp como uma cópia do programa completo do capítulo Desenhando o triângulo: o triângulo colorido, nada mais. Mude só o título da janela, para distinguir as duas janelas, e acrescente os três includes do GLM embaixo dos outros:
1#include <glm/glm.hpp> // NEW: glm::vec3, glm::mat4
2#include <glm/gtc/matrix_transform.hpp> // NEW: glm::rotate, glm::translate, glm::ortho
3#include <glm/gtc/type_ptr.hpp> // NEW: glm::value_ptr
4
5GLFWwindow* window = glfwCreateWindow(1280, 720, "Spin", nullptr, nullptr); // was "GLApp"A GPU move os vértices no vertex shader, então é lá que o giro entra. Dê ao shader um uniform mat4 chamado uModel e multiplique cada posição por ele:
1#version 460 core
2layout (location = 0) in vec3 aPos;
3layout (location = 1) in vec3 aColor;
4out vec3 vColor;
5uniform mat4 uModel; // NEW: the turn, the same for every vertex
6
7void main() {
8 gl_Position = uModel * vec4(aPos, 1.0); // NEW: was vec4(aPos, 1.0)
9 vColor = aColor;
10}No passo 4, logo depois de criar o programa, pergunte uma vez onde o uniform está:
1GLint modelLoc = glGetUniformLocation(shaderProgram, "uModel");Depois, no render loop, monte a matriz e envie, entre o glUseProgram e o draw call:
1glm::mat4 model = glm::rotate(glm::mat4(1.0f), // start from "change nothing"
2 glm::radians(30.0f), // how far: 30°
3 glm::vec3(0.0f, 0.0f, 1.0f)); // around which axis: z
4
5glUseProgram(shaderProgram);
6glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
7glBindVertexArray(vao);
8glDrawArrays(GL_TRIANGLES, 0, 3);| Parte | O que significa |
|---|---|
glm::mat4(1.0f) | a matriz identidade: ela deixa cada vértice onde está. glm::rotate devolve essa matriz com um giro acrescentado. |
glm::radians(30.0f) | o ângulo. O GLM quer radianos, então glm::radians converte: 30° são 0,524 radiano. Um ângulo positivo gira no sentido anti-horário. |
glm::vec3(0.0f, 0.0f, 1.0f) | o eixo, a reta em volta da qual o triângulo gira. O z sai da tela em direção a você, então girar em volta de z mantém o triângulo deitado na tela, como um ponteiro de relógio. |
glUniformMatrix4fv(…) | envia os 16 números para uModel. Os quatro argumentos são explicados no capítulo Transformações + GLM. |
Rode. O triângulo girou para a esquerda, mas olhe com atenção: ele não parece o mesmo triângulo, girado. Parece entortado, um pouco mais largo e mais achatado. Siga o canto de cima, (0, 0,65). Girado 30°, ele vai para (−0,325, 0,563). Agora conte pixels. A janela tem 1280 pixels de largura e 720 de altura, e o NDC vai de −1 a 1 nos dois sentidos. Então uma unidade de x são 640 pixels, mas uma unidade de y são só 360.
| Canto de cima | NDC | Pixels a partir do centro (x, y) | Distância |
|---|---|---|---|
| antes do giro | (0, 0.65) | (0, 234) | 234 px |
| girado 30° | (−0.325, 0.563) | (−208, 203) | 290 px |
| girado 90° | (−0.65, 0) | (−416, 0) | 416 px |
O canto está sempre a 0,65 unidade do centro, mas na tela ele fica a 234 pixels quando aponta para cima e a 416 quando aponta para o lado. A janela estica tudo na horizontal por 640 / 360 = 1,78. Ela já fazia isso no capítulo do triângulo; você só não percebia enquanto nada girava. Experimente na figura: aperte girar e acompanhe a leitura.
glm::mat4 projection(1.0f); // none: x = ±1 are the window's side edges glm::mat4 model = glm::rotate(glm::mat4(1.0f), glm::radians(30.0f), glm::vec3(0, 0, 1));
Gire sem projeção e acompanhe a leitura: o canto de cima fica a 234 px do centro quando aponta para cima, mas a 416 px quando aponta para o lado, porque uma unidade de x são 640 pixels e uma unidade de y só 360. O triângulo está girando, mas também está sendo esticado, numa quantidade diferente a cada ângulo. Ligue o glm::ortho e a distância fica a mesma a volta toda.
A correção é fazer a área visível ter, em unidades, a mesma proporção que a janela tem em pixels. Se y vai de −1 a 1, x deve ir de −1,78 a 1,78. Aí uma unidade são 360 pixels nos dois sentidos. glm::ortho monta a matriz que faz isso. É uma projeção ortográfica, o tipo simples do capítulo Transformações + GLM: sem perspectiva, só uma caixa espremida para dentro do NDC. Acrescente um segundo uniform ao vertex shader:
1uniform mat4 uModel;
2uniform mat4 uProjection; // NEW
3
4void main() {
5 gl_Position = uProjection * uModel * vec4(aPos, 1.0); // NEW: uProjection on the left
6 vColor = aColor;
7}A ordem importa. Leia da direita para a esquerda: uModel gira o vértice primeiro, depois uProjection ajusta o resultado à janela. Em seguida, uma segunda location no passo 4 e, no loop, a projeção, refeita a cada frame a partir do tamanho real da janela:
1GLint projectionLoc = glGetUniformLocation(shaderProgram, "uProjection"); // step 4
2
3// step 5, before glUseProgram:
4int width, height;
5glfwGetFramebufferSize(window, &width, &height);
6float aspect = height > 0 ? (float)width / (float)height : 1.0f; // 1280 / 720 = 1.78
7glm::mat4 projection = glm::ortho(-aspect, aspect, // left and right edges: x = ±1.78
8 -1.0f, 1.0f, // bottom and top edges: y = ±1
9 -1.0f, 1.0f); // near and far: keep z from −1 to 1
10
11// step 5, after glUseProgram:
12glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));| Parte | O que significa |
|---|---|
aspect | largura ÷ altura. Os casts para (float) vêm antes, senão 1280 / 720 é dividido como inteiro e dá 1. O teste height > 0 evita dividir por 0 quando a janela está minimizada. |
-aspect, aspect | os valores de x que caem nas bordas esquerda e direita da janela. Uma janela mais larga mostra mais do mundo em vez de esticá-lo. |
-1.0f, 1.0f | os valores de y nas bordas de baixo e de cima, os mesmos de antes. |
-1.0f, 1.0f (last pair) | a faixa de profundidade que fica visível. O triângulo está em z = 0, e no passo 5 ele vai sair um pouco da tela ao girar, até z = ±0,65, ainda dentro. |
Com números: o canto de cima girado, (−0,325, 0,563), agora cai em x = −0,325 / 1,78 = −0,183 no NDC, que são −117 pixels a partir do centro, e y = 203 pixels como antes. A distância volta a ser 234 pixels, a mesma de antes do giro.
Um ângulo fixo de 30° é uma foto. Para se mexer, o ângulo precisa crescer enquanto o programa roda. O loop já redesenha o triângulo umas 60 vezes por segundo, então basta o ângulo ser diferente a cada vez. A fonte mais simples de um número que cresce é o relógio: glfwGetTime() devolve os segundos desde o glfwInit. Troque a linha dos 30°:
1float time = (float)glfwGetTime(); // 0, 0.016, 0.033, … seconds
2glm::mat4 model = glm::rotate(glm::mat4(1.0f),
3 time * glm::radians(90.0f), // 90° for every second
4 glm::vec3(0.0f, 0.0f, 1.0f));Leia time * glm::radians(90.0f) como "segundos × graus por segundo". Depois de 1 segundo o ângulo é 90°, depois de 2 segundos 180°, depois de 4 segundos 360°: uma volta inteira. O número dentro de glm::radians é a velocidade. Mude e veja:
| Velocidade | Uma volta inteira leva | O que você vê |
|---|---|---|
| glm::radians(90.0f) | 360 / 90 = 4 s | um giro calmo, anti-horário |
| glm::radians(360.0f) | 1 s | rápido, uma volta por segundo |
| glm::radians(-90.0f) | 4 s | o mesmo giro, horário: um ângulo negativo gira para o outro lado |
| glm::radians(6.0f) | 60 s | o ponteiro dos segundos de um relógio (andando para trás) |
O relógio não para. Se você pausar o giro por 3 segundos e soltar, time * speed pula 270° de uma vez. Para pausar, o programa precisa do próprio ângulo: uma variável que lembra onde o triângulo está e cresce um passo pequeno a cada frame, só quando deve.
De que tamanho é o passo? Um frame nem sempre dura o mesmo tempo. A 60 frames por segundo ele dura 1/60 de segundo, numa tela de 144 Hz dura 1/144. Então meça: dt é o tempo desde o último frame, e o passo é speed × dt. A 60 Hz isso dá 90° × 0,0167 = 1,5° por frame; a 144 Hz dá 0,625°, passos menores mas em maior número, então 90° por segundo em qualquer máquina. Acrescente o estado no passo 4, logo antes do loop:
1float angle = 0.0f; // where the triangle is, in radians
2bool spinning = true; // false while paused
3bool spaceWasDown = false; // was Space held during the last frame?
4float lastFrame = (float)glfwGetTime(); // when the last frame startedDepois, no topo do loop, meça o frame, leia as teclas e mova o ângulo:
1float time = (float)glfwGetTime();
2float dt = time - lastFrame; // how long the last frame took, in seconds
3lastFrame = time;
4
5bool spaceDown = glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS;
6if (spaceDown && !spaceWasDown) // only on the frame the key goes down
7 spinning = !spinning;
8spaceWasDown = spaceDown;
9
10float speed = glm::radians(90.0f); // 90° per second
11if (spinning) angle += speed * dt;
12if (glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_PRESS) angle += speed * dt; // counter-clockwise
13if (glfwGetKey(window, GLFW_KEY_RIGHT) == GLFW_PRESS) angle -= speed * dt; // clockwiseE a matriz model usa o ângulo em vez do relógio:
1glm::mat4 model = glm::rotate(glm::mat4(1.0f), angle, glm::vec3(0.0f, 0.0f, 1.0f));| Linha | O que faz |
|---|---|
dt = time - lastFrame | a duração do último frame. lastFrame é definido antes do loop, então o primeiro dt é minúsculo, e não o tempo todo da inicialização. |
spaceDown && !spaceWasDown | verdadeiro só no primeiro frame de um aperto. glfwGetKey diz "pressionada", e um toque curto dura vários frames; inverter spinning em cada um deles deixaria o giro ligado ou desligado ao acaso. |
angle += speed * dt | o giro de um frame. Segurar a seta para a esquerda enquanto ele gira soma um segundo passo, então ele gira duas vezes mais rápido. Segurar a da direita cancela o giro, e o triângulo fica parado. |
angle (no glm::radians) | angle já está em radianos, porque speed foi convertido uma vez. Não o coloque dentro de glm::radians de novo. |
Girar em volta de z mantém o triângulo deitado na tela. Os outros dois eixos estão no plano da tela: x aponta para a direita, y para cima. Girar em volta deles vira o triângulo como uma carta de baralho. Transforme o eixo numa variável, ao lado de angle no passo 4, e deixe as teclas X, Y e Z escolherem:
1glm::vec3 axis(0.0f, 0.0f, 1.0f); // step 4: start with z
2
3// step 5:
4if (glfwGetKey(window, GLFW_KEY_X) == GLFW_PRESS) axis = glm::vec3(1.0f, 0.0f, 0.0f);
5if (glfwGetKey(window, GLFW_KEY_Y) == GLFW_PRESS) axis = glm::vec3(0.0f, 1.0f, 0.0f);
6if (glfwGetKey(window, GLFW_KEY_Z) == GLFW_PRESS) axis = glm::vec3(0.0f, 0.0f, 1.0f);
7
8glm::mat4 model = glm::rotate(glm::mat4(1.0f), angle, axis); // was the fixed z axis| Eixo | O que você vê |
|---|---|
| z (0, 0, 1) | ele gira deitado na tela, como um ponteiro de relógio. |
| y (0, 1, 0) | ele gira como uma porta giratória. A 90° você o vê de lado: uma linha fina vertical. A 180° ele aparece espelhado, com o verde à esquerda e o vermelho à direita. |
| x (1, 0, 0) | ele tomba como uma gangorra vista de frente. A 90° é uma linha fina horizontal; a 180° está de cabeça para baixo. |
Duas coisas para notar. Primeiro, ele parece plano mesmo girando em 3D: um lado nunca fica maior ao vir na sua direção. Isso é a projeção ortográfica; a perspectiva chega com o cubo. Segundo, na metade da volta você vê as costas do triângulo, e elas continuam sendo desenhadas. O OpenGL desenha os dois lados de um triângulo, a menos que você peça o contrário; o capítulo de winding e culling mostra como e por que você faria isso.
glm::rotate sempre gira em volta da origem, (0, 0, 0). Por isso o triângulo balançava no passo 3: o meio dele não está na origem. O meio de um triângulo é a média dos três cantos: ((−0,6 + 0,65 + 0) / 3, (−0,55 − 0,45 + 0,65) / 3) = (0,017, −0,117). Perto de (0, 0), então ele quase gira no lugar. Cada variação abaixo substitui a linha da model do passo 5. Experimente uma, rode, depois experimente a próxima.
Girando em volta de qualquer ponto
Para girar em volta de outro ponto, o pivô, faça em três movimentos: deslize o triângulo até o pivô ficar na origem, gire, e deslize de volta. Aqui o pivô é o canto de cima, (0, 0,65):
1glm::vec3 pivot(0.0f, 0.65f, 0.0f); // the top corner
2glm::mat4 model(1.0f);
3model = glm::translate(model, pivot); // 3rd: slide the pivot back
4model = glm::rotate(model, angle, axis); // 2nd: turn around the origin
5model = glm::translate(model, -pivot); // 1st: slide the pivot to the originPor que os comentários são numerados de baixo para cima? Cada chamada do GLM multiplica a nova matriz pela direita, então a linha escrita por último é a primeira a tocar o vértice (o capítulo Transformações + GLM explica isso com uma figura). Siga o canto vermelho, (−0,6, −0,55), a 90°:
| Passo | O canto vermelho |
|---|---|
| início | (−0.6, −0.55) |
| 1º: menos o pivô (0, 0,65) | (−0.6, −1.2) |
| 2º: girar 90°, (x, y) vira (−y, x) | (1.2, −0.6) |
| 3º: mais o pivô | (1.2, 0.05) |
O canto de cima fica parado e o triângulo balança em volta dele como uma bandeira no mastro. Ponha o pivô no meio, (0,017, −0,117, 0), e o balanço do passo 3 desaparece.
Girando no lugar, ou dando a volta em círculo
Agora tire o triângulo do centro, leve-o para x = 0,6 e encolha-o para 0,4 vez o tamanho, para caber. Escreva translate primeiro e rotate depois, e ele gira no lugar lá:
1glm::mat4 model(1.0f);
2model = glm::translate(model, glm::vec3(0.6f, 0.0f, 0.0f)); // 3rd: move it right
3model = glm::rotate(model, angle, glm::vec3(0.0f, 0.0f, 1.0f)); // 2nd: spin
4model = glm::scale(model, glm::vec3(0.4f)); // 1st: 0.4 times the sizeTroque as duas primeiras chamadas. Agora o triângulo é movido primeiro e girado depois, e um giro é sempre em volta da origem, então ele viaja em volta do centro da janela num círculo de raio 0,6. Ele mostra sempre o mesmo lado para o centro, como a Lua para a Terra:
1glm::mat4 model(1.0f);
2model = glm::rotate(model, angle, glm::vec3(0.0f, 0.0f, 1.0f)); // 3rd: swing around the centre
3model = glm::translate(model, glm::vec3(0.6f, 0.0f, 0.0f)); // 2nd: move it right
4model = glm::scale(model, glm::vec3(0.4f)); // 1st: 0.4 times the sizeOs dois ao mesmo tempo é mais um rotate: uma órbita por fora, um giro por dentro. Com um quarto da velocidade na órbita, ele gira quatro vezes a cada volta em torno do centro:
1glm::mat4 model(1.0f);
2model = glm::rotate(model, angle * 0.25f, glm::vec3(0.0f, 0.0f, 1.0f)); // 4th: orbit, slowly
3model = glm::translate(model, glm::vec3(0.6f, 0.0f, 0.0f)); // 3rd: out to radius 0.6
4model = glm::rotate(model, angle, glm::vec3(0.0f, 0.0f, 1.0f)); // 2nd: spin in place
5model = glm::scale(model, glm::vec3(0.4f)); // 1st: 0.4 times the sizeQuando terminar, volte à linha simples do passo 5, glm::rotate(glm::mat4(1.0f), angle, axis). O checkpoint abaixo e Girando o cubo partem dela.
O arquivo inteiro depois do passo 5. Em relação ao programa de Desenhando o triângulo, as mudanças estão marcadas NEW: os includes do GLM, as duas matrizes no vertex shader, duas locations de uniform e o estado do giro no passo 4, e o tempo, as teclas e as matrizes no loop. Os dois helpers de shader estão encurtados porque não mudaram.
1#include <glad/glad.h> // MUST come before glfw3.h
2#include <GLFW/glfw3.h>
3#include <iostream>
4#include <glm/glm.hpp> // NEW
5#include <glm/gtc/matrix_transform.hpp> // NEW
6#include <glm/gtc/type_ptr.hpp> // NEW
7
8// ── Shaders ─────────────────────────────────────── NEW: two matrices
9const char* vertexShaderSource = R"(#version 460 core
10layout (location = 0) in vec3 aPos;
11layout (location = 1) in vec3 aColor;
12out vec3 vColor;
13uniform mat4 uModel;
14uniform mat4 uProjection;
15void main() {
16 gl_Position = uProjection * uModel * vec4(aPos, 1.0);
17 vColor = aColor;
18})";
19
20const char* fragmentShaderSource = R"(#version 460 core
21in vec3 vColor;
22out vec4 FragColor;
23void main() {
24 FragColor = vec4(vColor, 1.0);
25})";
26
27// ── Shader helpers (unchanged since First Shaders) ──────────────────────────
28GLuint compileShader(GLenum type, const char* src) { /* ... */ }
29GLuint makeProgram(const char* vsSrc, const char* fsSrc) { /* ... */ }
30
31void framebufferSizeCallback(GLFWwindow*, int width, int height) {
32 glViewport(0, 0, width, height);
33}
34
35int main() {
36 // ── 1–3. Window, GLAD, viewport (unchanged, except the title) ───────────
37 if (!glfwInit()) return 1;
38 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
39 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
40 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
41 GLFWwindow* window = glfwCreateWindow(1280, 720, "Spin", nullptr, nullptr);
42 if (!window) { glfwTerminate(); return 1; }
43 glfwMakeContextCurrent(window);
44 glfwSwapInterval(1);
45 if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) return 1;
46 int fbw, fbh;
47 glfwGetFramebufferSize(window, &fbw, &fbh);
48 glViewport(0, 0, fbw, fbh);
49 glfwSetFramebufferSizeCallback(window, framebufferSizeCallback);
50
51 // ── 4. One-time setup ────────────────────────────────────────────────────
52 // 4a. Shaders → program (unchanged)
53 GLuint shaderProgram = makeProgram(vertexShaderSource, fragmentShaderSource);
54 if (shaderProgram == 0) { glfwTerminate(); return 1; }
55
56 // 4b. Vertex data → VAO + VBO (unchanged: the triangle)
57 float vertices[] = {
58 // x y z r g b
59 -0.6f, -0.55f, 0.0f, 1.0f, 0.27f, 0.27f, // vertex 0: red, bottom left
60 0.65f,-0.45f, 0.0f, 0.13f,0.77f, 0.37f, // vertex 1: green, bottom right
61 0.0f, 0.65f, 0.0f, 0.23f,0.51f, 0.96f, // vertex 2: blue, top
62 };
63 GLuint vao, vbo;
64 glGenVertexArrays(1, &vao);
65 glGenBuffers(1, &vbo);
66
67 glBindVertexArray(vao);
68 glBindBuffer(GL_ARRAY_BUFFER, vbo);
69 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
70 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
71 glEnableVertexAttribArray(0);
72 glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
73 glEnableVertexAttribArray(1);
74 glBindVertexArray(0);
75
76 // 4c. Matrix uniforms >>> NEW
77 GLint modelLoc = glGetUniformLocation(shaderProgram, "uModel");
78 GLint projectionLoc = glGetUniformLocation(shaderProgram, "uProjection");
79
80 // 4d. The spin, kept between frames
81 float angle = 0.0f; // radians
82 glm::vec3 axis(0.0f, 0.0f, 1.0f); // z: flat on the screen
83 bool spinning = true;
84 bool spaceWasDown = false;
85 float lastFrame = (float)glfwGetTime();
86 // <<< end of NEW
87
88 // ── 5. The render loop ───────────────────────────────────────────────────
89 while (!glfwWindowShouldClose(window)) {
90 if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
91 glfwSetWindowShouldClose(window, true);
92
93 // >>> NEW: time, keys and the angle
94 float time = (float)glfwGetTime();
95 float dt = time - lastFrame;
96 lastFrame = time;
97
98 bool spaceDown = glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS;
99 if (spaceDown && !spaceWasDown) spinning = !spinning;
100 spaceWasDown = spaceDown;
101
102 float speed = glm::radians(90.0f);
103 if (spinning) angle += speed * dt;
104 if (glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_PRESS) angle += speed * dt;
105 if (glfwGetKey(window, GLFW_KEY_RIGHT) == GLFW_PRESS) angle -= speed * dt;
106
107 if (glfwGetKey(window, GLFW_KEY_X) == GLFW_PRESS) axis = glm::vec3(1.0f, 0.0f, 0.0f);
108 if (glfwGetKey(window, GLFW_KEY_Y) == GLFW_PRESS) axis = glm::vec3(0.0f, 1.0f, 0.0f);
109 if (glfwGetKey(window, GLFW_KEY_Z) == GLFW_PRESS) axis = glm::vec3(0.0f, 0.0f, 1.0f);
110 // <<< end of NEW
111
112 glClearColor(0.06f, 0.07f, 0.10f, 1.0f);
113 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
114
115 // >>> NEW: the two matrices, rebuilt every frame
116 int width, height;
117 glfwGetFramebufferSize(window, &width, &height);
118 float aspect = height > 0 ? (float)width / (float)height : 1.0f;
119 glm::mat4 projection = glm::ortho(-aspect, aspect, -1.0f, 1.0f, -1.0f, 1.0f);
120 glm::mat4 model = glm::rotate(glm::mat4(1.0f), angle, axis);
121 // <<< end of NEW
122
123 glUseProgram(shaderProgram);
124 glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model)); // NEW
125 glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection)); // NEW
126 glBindVertexArray(vao);
127 glDrawArrays(GL_TRIANGLES, 0, 3);
128
129 glfwSwapBuffers(window);
130 glfwPollEvents();
131 }
132
133 // ── 6. Clean up (unchanged) ──────────────────────────────────────────────
134 glDeleteVertexArrays(1, &vao);
135 glDeleteBuffers(1, &vbo);
136 glDeleteProgram(shaderProgram);
137 glfwTerminate();
138 return 0;
139}| O que você vê | Causa provável |
|---|---|
| nada, só a cor de fundo | um uniform de matriz que nunca foi definido é só zeros, e todo vértice cai num ponto só. Imprima modelLoc e projectionLoc: −1 significa um nome digitado errado, ou um uniform que o shader declara mas não usa. |
| o triângulo não se mexe | a matriz é montada uma vez, antes do loop, ou angle nunca cresce: confira se spinning começa true e se dt não é sempre 0 (lastFrame = time tem que vir depois do cálculo de dt). |
| ele gira enlouquecido, ou pula entre algumas poses | graus passados como radianos: glm::rotate(…, 90.0f, …) gira 90 radianos, umas 14 voltas inteiras. Use glm::radians. |
| ele muda de forma enquanto gira | não há projeção, ou uProjection não está na multiplicação do shader. É o esticamento do passo 1. |
| o Espaço às vezes não faz nada | spinning é invertido em cada frame em que a tecla fica apertada, e não uma vez por aperto. Guarde spaceWasDown, como no passo 4. |
| ele some quando você aperta uma tecla | o eixo virou (0, 0, 0). glm::rotate precisa de uma direção; sem ela, a matriz se enche de NaN e nada é desenhado. |
| Erro | O que acontece, e a correção |
|---|---|
| angle += speed, sem dt | o passo é por frame, então o triângulo gira 2,4 vezes mais rápido numa tela de 144 Hz do que numa de 60 Hz. Multiplique por dt para a velocidade ser "por segundo". |
| Pausar um ângulo baseado no relógio | angle = time * speed não consegue parar; ele pula para a frente quando você retoma. Guarde o próprio ângulo e some speed * dt. |
| uModel * uProjection no shader | a projeção agiria primeiro e o giro depois, no espaço esticado: a forma volta a mudar enquanto gira. A matriz que age primeiro fica junto do vetor: uProjection * uModel * pos. |
| Esperar que rotate gire em volta do meio da forma | ele sempre gira em volta da origem. Ponha o pivô na origem primeiro (translate por −pivô), gire, e leve-o de volta. |
| (float)(width / height) | a divisão é feita em inteiros primeiro e dá 1, então o esticamento volta. Converta antes de dividir. |
Em seguida, Desenhando um cubo monta o cubo no main.cpp. Depois, Girando o cubo leva o cubo para o spin.cpp, gira em volta de cada eixo, combina dois giros e deixa você girá-lo com o mouse.
Ideias-chave
- Um giro é uma matriz no vertex shader: gl_Position = uProjection * uModel * vec4(aPos, 1.0), com uModel =
glm::rotate(glm::mat4(1.0f), angle, axis). glm::rotatequer radianos e gira no sentido anti-horário para um ângulo positivo;glm::radiansconverte de graus.- O NDC é esticado até a janela, então uma forma que gira muda de forma.
glm::ortho(-aspect, aspect, -1, 1, -1, 1)faz uma unidade ter o mesmo número de pixels nos dois sentidos. - angle = time × speed gira a uma velocidade fixa. Para pausar ou controlar, guarde o próprio ângulo e some speed × dt a cada frame.
glm::rotatesempre gira em volta da origem. Para girar em volta de um pivô: translate(pivô) × rotate × translate(−pivô).- translate e depois rotate (na ordem escrita) gira uma forma no lugar; rotate e depois translate a faz orbitar a origem.