Desenhando um cubo fez o cubo rodopiar com uma linha. Este capítulo desmonta essa linha. Você leva o cubo para o programa spin de Girando um triângulo, onde as teclas já giram as coisas, e experimenta um eixo de cada vez, depois dois giros juntos, depois o mouse. No fim, três cubos giram cada um na sua velocidade e uma pequena lua dá voltas no do meio.
Ao final você vai conseguir
- Prever que faces um cubo mostra ao girar em volta de x, y ou z.
- Combinar duas rotações e escolher a ordem delas: um prato giratório ou um bamboleio.
- Girar o cubo arrastando o mouse, com yaw e pitch.
- Dar a vários objetos o próprio giro, e fazer um orbitar outro.
Ele continua o spin.cpp do fim de Girando um triângulo: o triângulo que gira, pausa com Espaço, gira na mão com as setas e escolhe o eixo com X, Y e Z. O seu main.cpp não é tocado. Rode o programa depois de cada passo; cada passo diz o que você deve ver.
- Passo 1: o cubo colorido no lugar do triângulo, em perspectiva.
- Passo 2: um eixo de cada vez, com as teclas que você já tem.
- Passo 3: dois giros juntos, nas duas ordens.
- Passo 4: arraste com o mouse para girá-lo.
- Passo 5: três cubos e uma lua.
Use o cubo do passo 1 de Desenhando um cubo: 8 vértices só com posição, 36 índices, colorido pela própria posição. Ele não tem textura, então o spin.cpp não precisa de imagem nem de stb_image. Troque os vértices do triângulo no passo 4b:
1float vertices[] = {
2// x y z
3 -0.5f, 0.5f, 0.5f, // 0: front top-left
4 -0.5f, -0.5f, 0.5f, // 1: front bottom-left
5 0.5f, -0.5f, 0.5f, // 2: front bottom-right
6 0.5f, 0.5f, 0.5f, // 3: front top-right
7 -0.5f, 0.5f, -0.5f, // 4: back top-left
8 -0.5f, -0.5f, -0.5f, // 5: back bottom-left
9 0.5f, -0.5f, -0.5f, // 6: back bottom-right
10 0.5f, 0.5f, -0.5f, // 7: back top-right
11};
12GLuint indices[] = {
13 0, 1, 2, 0, 2, 3, // front (+z)
14 7, 6, 5, 7, 5, 4, // back (−z)
15 3, 2, 6, 3, 6, 7, // right (+x)
16 4, 5, 1, 4, 1, 0, // left (−x)
17 4, 0, 3, 4, 3, 7, // top (+y)
18 1, 5, 6, 1, 6, 2, // bottom (−y)
19};
20GLuint vao, vbo, ebo; // NEW: ebo
21glGenVertexArrays(1, &vao);
22glGenBuffers(1, &vbo);
23glGenBuffers(1, &ebo); // NEW
24
25glBindVertexArray(vao);
26glBindBuffer(GL_ARRAY_BUFFER, vbo);
27glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
28glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); // stride: was 6
29glEnableVertexAttribArray(0);
30// attribute 1 (the colour) is gone: delete its two lines
31glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); // NEW, while the VAO is bound
32glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); // NEW
33glBindVertexArray(0);O vertex shader perde o aColor e calcula a cor a partir da posição. Ele também ganha a matriz view do capítulo Transformações + GLM, entre as outras duas:
1#version 460 core
2layout (location = 0) in vec3 aPos;
3out vec3 vColor;
4uniform mat4 uModel;
5uniform mat4 uView; // NEW
6uniform mat4 uProjection;
7void main() {
8 gl_Position = uProjection * uView * uModel * vec4(aPos, 1.0); // NEW: uView in the middle
9 vColor = aPos + 0.5; // NEW: −0.5…0.5 becomes 0…1
10}O fragment shader fica como está. No passo 4c, procure o novo uniform e ligue o teste de profundidade, para as faces da frente esconderem as de trás:
1GLint viewLoc = glGetUniformLocation(shaderProgram, "uView"); // NEW
2glEnable(GL_DEPTH_TEST); // NEWPor último, o loop. O glm::ortho plano dá lugar ao glm::perspective, para as faces que vêm na sua direção ficarem maiores, e a matriz view recua a câmera 3 unidades. As duas são as do capítulo Transformações + GLM. O draw call lê 36 índices:
1glm::mat4 projection = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 100.0f); // was glm::ortho
2glm::mat4 view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -3.0f)); // NEW
3glm::mat4 model = glm::rotate(glm::mat4(1.0f), angle, axis); // unchanged
4
5glUseProgram(shaderProgram);
6glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
7glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view)); // NEW
8glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
9glBindVertexArray(vao);
10glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, (void*)0); // was glDrawArrays(…, 3)No passo 6, apague o EBO também: glDeleteBuffers(1, &ebo). O seu glClear já limpa o depth buffer (GL_DEPTH_BUFFER_BIT), de que o teste de profundidade precisa.
Não há nada para digitar neste passo: as teclas X, Y e Z do capítulo anterior já escolhem o eixo. Aperte cada uma e veja que faces aparecem. A figura faz o mesmo; escolha um eixo nela e compare.
| Tecla | Eixo | O que você vê | Nunca fica de frente |
|---|---|---|---|
| Y | (0, 1, 0) | um prato giratório. A face da frente escorrega para a direita e a da esquerda aparece, depois a de trás, depois a da direita. | cima e baixo |
| X | (1, 0, 0) | uma roda rolando na sua direção. A face de cima desce para a frente, depois vem a de trás, depois a de baixo. | esquerda e direita |
| Z | (0, 0, 1) | a face da frente girando como um ponteiro de relógio. Parece plana porque o eixo aponta direto para você. | todas menos a da frente |
Então um eixo sempre esconde duas faces. Aperte Y, e aperte Espaço quando duas faces dividirem a imagem por igual, a 45°: a face da frente à direita, a da esquerda à esquerda. Segure as setas para girá-lo na mão a partir dali.
glm::mat4 model(1.0f); model = glm::rotate(model, glm::radians(25.0f), glm::vec3(0.0f, 1.0f, 0.0f));
Em volta de y o cubo gira como um prato giratório: a face da frente escorrega para a direita e a da esquerda aparece. Cima e baixo nunca ficam de frente para você.
Para ver a face de cima enquanto o cubo gira em volta de y, incline-o na sua direção também. Isso é um segundo rotate. Troque a linha da model; as teclas X, Y e Z e a variável axis não são mais usadas, então apague-as:
1glm::mat4 model(1.0f);
2model = glm::rotate(model, glm::radians(30.0f), glm::vec3(1.0f, 0.0f, 0.0f)); // 2nd: tilt the top toward you
3model = glm::rotate(model, angle, glm::vec3(0.0f, 1.0f, 0.0f)); // 1st: spin around its own up axisComo sempre, a chamada escrita por último age primeiro. Então, a cada frame, o cubo primeiro gira em volta do próprio eixo vertical, por angle, e depois o cubo já girado é inclinado 30° na sua direção. A inclinação vem depois do giro, então é sempre a mesma: você olha um prato giratório um pouco de cima, e a face de cima nunca sai de vista.
Agora troque as duas linhas e rode de novo. A inclinação age primeiro, e o giro carrega o cubo inclinado junto. Depois de meia volta, o lado que se inclinava para você se inclina para longe, e você vê a face de baixo em vez da de cima. O cubo bamboleia, como uma moeda prestes a cair. As mesmas duas chamadas, outra ordem, outro movimento. Na figura, compare "inclinar, depois girar" com "girar, depois inclinar".
| Ordem escrita | Age no vértice | Movimento |
|---|---|---|
| inclinar, depois girar | gira primeiro, inclina por último | um prato giratório visto de cima; a face de cima está sempre visível |
| girar, depois inclinar | inclina primeiro, gira por último | um bamboleio; cima e baixo se revezam |
A inclinação fixa de 30° vai virar algo que você arrasta. Dois ângulos fazem isso, com nomes da aviação: yaw, o giro em volta do eixo vertical (y), e pitch, a inclinação para perto ou para longe de você (em volta de x). Arrastar para a direita gira o cubo para a direita; arrastar para baixo inclina a face de cima na sua direção. É assim que editores 3D como o Blender giram um objeto. Acrescente os dois ao estado no passo 4, junto com a posição do mouse no último frame:
1float yaw = 0.0f; // around y, set by dragging sideways
2float pitch = glm::radians(30.0f); // around x, set by dragging up and down; starts as the old tilt
3double lastX, lastY; // where the mouse was during the last frame
4glfwGetCursorPos(window, &lastX, &lastY);No loop, depois das teclas, leia o mouse:
1double mouseX, mouseY;
2glfwGetCursorPos(window, &mouseX, &mouseY); // from the top-left corner of the window
3if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) {
4 float perPixel = glm::radians(0.5f); // half a degree for every pixel dragged
5 yaw += (float)(mouseX - lastX) * perPixel; // drag right: turn right
6 pitch += (float)(mouseY - lastY) * perPixel; // drag down: tip the top toward you
7 pitch = glm::clamp(pitch, glm::radians(-90.0f), glm::radians(90.0f));
8}
9lastX = mouseX; // every frame, pressed or not
10lastY = mouseY;| Linha | O que faz |
|---|---|
glfwGetCursorPos | a posição do mouse, medida a partir do canto superior esquerdo da janela. O y cresce para baixo, ao contrário do y do OpenGL. |
mouseX - lastX | quanto o mouse andou desde o último frame. lastX é atualizado em todo frame, apertado ou não, então o primeiro frame de um arraste só vê o pequeno movimento daquele frame, nunca um salto a partir de onde o último arraste terminou. |
perPixel | a sensibilidade: arrastar 180 pixels gira o cubo 90°. Não há dt aqui: um movimento do mouse já é uma distância, não uma velocidade. |
mouseY - lastY | positivo quando você arrasta para baixo, porque o y do mouse cresce para baixo. Um pitch positivo em volta de x inclina a face de cima na sua direção, então arrastar para baixo mostra o topo. |
glm::clamp | mantém o pitch entre −90° e 90°. Depois de 90° o cubo ficaria de cabeça para baixo, e arrastar para a direita o giraria para a esquerda. |
Depois a matriz model: pitch toma o lugar da inclinação fixa, e yaw se junta ao giro:
1glm::mat4 model(1.0f);
2model = glm::rotate(model, pitch, glm::vec3(1.0f, 0.0f, 0.0f)); // 3rd: tip toward you (mouse up and down)
3model = glm::rotate(model, yaw, glm::vec3(0.0f, 1.0f, 0.0f)); // 2nd: turn (mouse sideways)
4model = glm::rotate(model, angle, glm::vec3(0.0f, 1.0f, 0.0f)); // 1st: its own spin (Space pauses it)A ordem é a do prato giratório do passo 3, pelo mesmo motivo: o pitch é aplicado por último, então um arraste para o lado sempre gira o cubo em volta do próprio eixo vertical, seja qual for a inclinação. O giro e o yaw são ambos em volta de y e vêm um depois do outro, então simplesmente se somam: yaw é um giro que fica parado, angle um giro que continua.
Um VAO basta para muitos cubos, como o Experimente de Desenhando um cubo mostrou: desenhe-o várias vezes, com uma matriz model diferente a cada vez. Cada cubo ganha o próprio lugar e o próprio giro, e o mouse gira todos juntos. O giro que eles dividem vai numa matriz chamada scene, montada uma vez por frame a partir de yaw e pitch. Primeiro recue a câmera de 3 para 4 unidades, para caberem três:
1glm::mat4 view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -4.0f)); // was −3
2
3glm::mat4 scene(1.0f); // the mouse turns everything
4scene = glm::rotate(scene, pitch, glm::vec3(1.0f, 0.0f, 0.0f));
5scene = glm::rotate(scene, yaw, glm::vec3(0.0f, 1.0f, 0.0f));Depois o draw call vira um laço. Troque as linhas da model, o glUniformMatrix4fv da model e o glDrawElements (view e projection são enviadas uma vez, antes):
1glm::vec3 positions[] = {
2 glm::vec3(-1.6f, 0.0f, 0.0f),
3 glm::vec3( 0.0f, 0.0f, 0.0f),
4 glm::vec3( 1.6f, 0.0f, 0.0f),
5};
6float rates[] = { 0.5f, 1.0f, 2.0f }; // half, normal and double speed
7
8glBindVertexArray(vao);
9for (int i = 0; i < 3; ++i) {
10 glm::mat4 model = scene; // 4th: the mouse turn
11 model = glm::translate(model, positions[i]); // 3rd: into its place
12 model = glm::rotate(model, angle * rates[i], glm::vec3(0.5f, 1.0f, 0.0f)); // 2nd: its own tumble
13 model = glm::scale(model, glm::vec3(0.6f)); // 1st: a bit smaller
14 glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
15 glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, (void*)0);
16}
17
18// The moon: a small cube that circles the middle one
19glm::mat4 moon = scene; // 5th: the mouse turn
20moon = glm::translate(moon, positions[1]); // 4th: to the middle cube
21moon = glm::rotate(moon, angle * 1.5f, glm::vec3(0.0f, 1.0f, 0.0f)); // 3rd: swing around it
22moon = glm::translate(moon, glm::vec3(0.8f, 0.0f, 0.0f)); // 2nd: out to radius 0.8
23moon = glm::scale(moon, glm::vec3(0.2f)); // 1st: small
24glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(moon));
25glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, (void*)0);Leia cada matriz de baixo para cima, a ordem em que o vértice as encontra. Um cubo é diminuído, rodopia em volta do eixo inclinado de Desenhando um cubo, é levado para o seu lugar e só então girado com o resto da cena. O rodopio vem antes do deslocamento, então cada cubo gira no lugar. A lua é a órbita do capítulo do triângulo, agora em 3D: ela é levada para fora primeiro e girada depois, em volta de y, então circula o cubo do meio em vez de girar.
Começar cada matriz de scene, e não da identidade, é o que faz o mouse girar tudo de uma vez, em volta do meio da cena. O ângulo também é compartilhado, então o Espaço pausa os quatro. E quando a lua passa atrás do cubo do meio, o teste de profundidade a esconde, sem código extra.
O arquivo inteiro depois do passo 5. Em relação ao fim de Girando um triângulo, as mudanças estão marcadas NEW: os shaders do cubo colorido com uView, os vértices e índices do cubo com o EBO, o teste de profundidade, o estado de yaw e pitch, o mouse, a câmera em perspectiva e o laço de desenho. As teclas X, Y e Z saíram. 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>
5#include <glm/gtc/matrix_transform.hpp>
6#include <glm/gtc/type_ptr.hpp>
7
8// ── Shaders ─────────────────────────────────────── NEW: colour from the position, uView
9const char* vertexShaderSource = R"(#version 460 core
10layout (location = 0) in vec3 aPos;
11out vec3 vColor;
12uniform mat4 uModel;
13uniform mat4 uView;
14uniform mat4 uProjection;
15void main() {
16 gl_Position = uProjection * uView * uModel * vec4(aPos, 1.0);
17 vColor = aPos + 0.5;
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) ─────────────────────────────
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 + EBO >>> NEW: the colour cube
57 float vertices[] = {
58 // x y z
59 -0.5f, 0.5f, 0.5f, -0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 0.5f, // 0–3: front
60 -0.5f, 0.5f, -0.5f, -0.5f, -0.5f, -0.5f, 0.5f, -0.5f, -0.5f, 0.5f, 0.5f, -0.5f, // 4–7: back
61 };
62 GLuint indices[] = {
63 0, 1, 2, 0, 2, 3, // front
64 7, 6, 5, 7, 5, 4, // back
65 3, 2, 6, 3, 6, 7, // right
66 4, 5, 1, 4, 1, 0, // left
67 4, 0, 3, 4, 3, 7, // top
68 1, 5, 6, 1, 6, 2, // bottom
69 };
70 GLuint vao, vbo, ebo;
71 glGenVertexArrays(1, &vao);
72 glGenBuffers(1, &vbo);
73 glGenBuffers(1, &ebo);
74
75 glBindVertexArray(vao);
76 glBindBuffer(GL_ARRAY_BUFFER, vbo);
77 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
78 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
79 glEnableVertexAttribArray(0);
80 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
81 glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
82 glBindVertexArray(0);
83 // <<< end of NEW
84
85 // 4c. Matrix uniforms and depth
86 GLint modelLoc = glGetUniformLocation(shaderProgram, "uModel");
87 GLint viewLoc = glGetUniformLocation(shaderProgram, "uView"); // NEW
88 GLint projectionLoc = glGetUniformLocation(shaderProgram, "uProjection");
89 glEnable(GL_DEPTH_TEST); // NEW
90
91 // 4d. The spin and the mouse turn, kept between frames
92 float angle = 0.0f;
93 bool spinning = true;
94 bool spaceWasDown = false;
95 float lastFrame = (float)glfwGetTime();
96 float yaw = 0.0f; // NEW
97 float pitch = glm::radians(30.0f); // NEW
98 double lastX, lastY; // NEW
99 glfwGetCursorPos(window, &lastX, &lastY); // NEW
100
101 // ── 5. The render loop ───────────────────────────────────────────────────
102 while (!glfwWindowShouldClose(window)) {
103 if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
104 glfwSetWindowShouldClose(window, true);
105
106 float time = (float)glfwGetTime();
107 float dt = time - lastFrame;
108 lastFrame = time;
109
110 bool spaceDown = glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS;
111 if (spaceDown && !spaceWasDown) spinning = !spinning;
112 spaceWasDown = spaceDown;
113
114 float speed = glm::radians(90.0f);
115 if (spinning) angle += speed * dt;
116 if (glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_PRESS) angle += speed * dt;
117 if (glfwGetKey(window, GLFW_KEY_RIGHT) == GLFW_PRESS) angle -= speed * dt;
118
119 // >>> NEW: drag to turn
120 double mouseX, mouseY;
121 glfwGetCursorPos(window, &mouseX, &mouseY);
122 if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) {
123 float perPixel = glm::radians(0.5f);
124 yaw += (float)(mouseX - lastX) * perPixel;
125 pitch += (float)(mouseY - lastY) * perPixel;
126 pitch = glm::clamp(pitch, glm::radians(-90.0f), glm::radians(90.0f));
127 }
128 lastX = mouseX;
129 lastY = mouseY;
130 // <<< end of NEW
131
132 glClearColor(0.06f, 0.07f, 0.10f, 1.0f);
133 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
134
135 // >>> NEW: a perspective camera, and the turn the whole scene shares
136 int width, height;
137 glfwGetFramebufferSize(window, &width, &height);
138 float aspect = height > 0 ? (float)width / (float)height : 1.0f;
139 glm::mat4 projection = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 100.0f);
140 glm::mat4 view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -4.0f));
141 glm::mat4 scene(1.0f);
142 scene = glm::rotate(scene, pitch, glm::vec3(1.0f, 0.0f, 0.0f));
143 scene = glm::rotate(scene, yaw, glm::vec3(0.0f, 1.0f, 0.0f));
144
145 glUseProgram(shaderProgram);
146 glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
147 glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
148 glBindVertexArray(vao);
149
150 glm::vec3 positions[] = {
151 glm::vec3(-1.6f, 0.0f, 0.0f),
152 glm::vec3( 0.0f, 0.0f, 0.0f),
153 glm::vec3( 1.6f, 0.0f, 0.0f),
154 };
155 float rates[] = { 0.5f, 1.0f, 2.0f };
156 for (int i = 0; i < 3; ++i) {
157 glm::mat4 model = scene;
158 model = glm::translate(model, positions[i]);
159 model = glm::rotate(model, angle * rates[i], glm::vec3(0.5f, 1.0f, 0.0f));
160 model = glm::scale(model, glm::vec3(0.6f));
161 glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
162 glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, (void*)0);
163 }
164
165 glm::mat4 moon = scene;
166 moon = glm::translate(moon, positions[1]);
167 moon = glm::rotate(moon, angle * 1.5f, glm::vec3(0.0f, 1.0f, 0.0f));
168 moon = glm::translate(moon, glm::vec3(0.8f, 0.0f, 0.0f));
169 moon = glm::scale(moon, glm::vec3(0.2f));
170 glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(moon));
171 glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, (void*)0);
172 // <<< end of NEW
173
174 glfwSwapBuffers(window);
175 glfwPollEvents();
176 }
177
178 // ── 6. Clean up ──────────────────────────────────────────────────────────
179 glDeleteVertexArrays(1, &vao);
180 glDeleteBuffers(1, &vbo);
181 glDeleteBuffers(1, &ebo); // NEW
182 glDeleteProgram(shaderProgram);
183 glfwTerminate();
184 return 0;
185}| O que você vê | Causa provável |
|---|---|
| um quadrado, não um cubo | o cubo gira em volta de z, o eixo que aponta para você, ou não gira. Aperte Y, ou confira a matriz model. |
| faces de trás aparecem através das da frente | falta o glEnable(GL_DEPTH_TEST), ou falta GL_DEPTH_BUFFER_BIT no glClear. |
| o cubo está enorme, ou nada é desenhado | falta a matriz view ou ela move por +3: a câmera fica dentro do cubo ou na frente dele, olhando para o outro lado. uView precisa ser enviada e multiplicada no shader. |
| arrastar gira para o lado errado | um sinal está trocado. Arrastar para a direita deve somar ao yaw, arrastar para baixo deve somar ao pitch; o y do mouse cresce para baixo. |
| ele pula quando você começa a arrastar | lastX e lastY só são atualizados enquanto o botão está apertado, então o primeiro frame de um novo arraste mede a partir de onde o último terminou. Atualize os dois em todo frame. |
| os cubos dão voltas em torno do meio em vez de girar no lugar | translate e rotate estão trocados: cada cubo é movido primeiro e girado depois, então orbita, que é a receita da lua. Escreva o translate antes do rotate. |
| Erro | O que acontece, e a correção |
|---|---|
| Escrever as rotações na ordem em que devem acontecer | a última chamada do GLM age primeiro. Para girar e depois inclinar, escreva a inclinação primeiro. Quando um movimento parece errado, trocar duas linhas é a primeira coisa a tentar. |
| Multiplicar o movimento do mouse por dt | um arraste é uma distância, então o giro deve depender só de quanto você arrastou. Com dt, o mesmo arraste gira menos numa máquina mais rápida. |
| Nenhum limite no pitch | depois de 90° o cubo fica de cabeça para baixo e um arraste para o lado gira para o lado oposto. Limite o pitch a ±90°. |
| Dar a cada cubo a sua própria cópia do giro do mouse | começar cada model da identidade e pôr yaw e pitch depois do translate gira cada cubo em volta do próprio centro, e não a fila em volta do meio. Comece cada model de scene. |
| Esquecer o EBO na limpeza | nada visível acontece: o programa termina de qualquer jeito. Mas todo glGen precisa do seu glDelete, e programas maiores criam buffers enquanto rodam. |
A matriz scene que você acabou de montar é uma câmera disfarçada: girar o mundo inteiro é o mesmo que andar em volta dele. Em seguida, Câmera e matriz de visão torna isso real, de volta ao main.cpp: glm::lookAt, o mouse para olhar em volta e o teclado para andar.
Ideias-chave
- Girar em volta de um eixo esconde duas faces: em volta de y você nunca vê cima e baixo, em volta de x nunca os lados, e em volta de z, o eixo que aponta para você, um cubo parece um quadrado plano.
- Duas rotações não comutam. A última chamada do GLM age primeiro: inclinar e depois girar (na escrita) é um prato giratório, girar e depois inclinar é um bamboleio.
- Yaw (em volta de y) e pitch (em volta de x) vindos de um arraste do mouse giram um objeto como um editor 3D: pitch escrito primeiro, para agir por último; limite-o a ±90°.
- Um arraste do mouse é uma distância: multiplique-o por um ângulo por pixel, nunca por dt.
- Um VAO, muitos desenhos: cada objeto ganha a própria matriz model. Começar todas de uma matriz scene compartilhada gira todos juntos.
- Mover e depois girar (na ordem em que o vértice os encontra) faz um objeto orbitar; girar e depois mover o faz girar no lugar.