1. 程式人生 > >openGL學習----相機

openGL學習----相機

0.參考:https://learnopengl-cn.github.io/01%20Getting%20started/09%20Camera/

0.0其實相機就是搞清楚cameraPos,cameraFornt,cameraUp的關係和用法,以及跟三個尤拉角的關係,以及如何跟滑鼠、鍵盤的wasd鍵聯絡起來(也就是視角移動跟距離移動)實現跟使用者的互動,然後生成LookAt矩陣就OK了,重點是理解跟4中的注意點

0.1我們介紹的攝像機系統是一個FPS風格的攝像機,它能夠滿足大多數情況需要,而且與尤拉角相容,但是在建立不同的攝像機系統,比如飛行模擬攝像機,時就要當心。每個攝像機系統都有自己的優點和不足,所以確保對它們進行了詳細研究。比如,這個FPS攝像機不允許俯仰角大於90度,而且我們使用了一個固定的上向量(0, 1, 0),這在需要考慮滾轉角的時候就不能用了。

1.當我們討論攝像機/觀察空間(Camera/View Space)的時候,是在討論以攝像機的視角作為場景原點時場景中所有的頂點座標(之前的原點是世界座標系的原點):觀察矩陣把所有的世界座標變換為相對於攝像機位置與方向的觀察座標。要定義一個攝像機,我們需要它在世界空間中的位置、觀察的方向、一個指向它右測的向量以及一個指向它上方的向量。細心的讀者可能已經注意到我們實際上建立了一個三個單位軸相互垂直的、以攝像機的位置為原點的座標系。

2.具體相機座標系原理可參看資料。。。

3.這裡只講一下大致的觀察矩陣的初始化api

相機座標系轉換就是生成一個觀察矩陣,也就是著名的LookAt矩陣

3.1幸運的是,GLM已經提供了這些支援。我們要做的只是定義一個攝像機位置,一個目標位置和一個表示世界空間中的上向量的向量(我們計算右向量使用的那個上向量)。接著GLM就會建立一個LookAt矩陣,我們可以把它當作我們的觀察矩陣:

glm::mat4 view;
view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f), 
           glm::vec3(0.0f, 0.0f, 0.0f), 
           glm::vec3(0.0f, 1.0f, 0.0f));

glm::LookAt函式需要一個位置、目標和上向量。

其實比較經典和完美的初始化方法是下面(上面只是簡單情況下的寫法):

view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);

 

第一個引數是相機位置,是個點,是世界座標系,因為還沒有生成觀察矩陣作用到座標上,怎麼可能是相機座標系呢

第二個引數是看向的目標位置,也是個點,也是世界座標系。例子中是相機前面的點,也就是說相機一直是朝著它前面的目標點,也就是z軸負方向看的。它只是一個點,並不是一個向量,僅僅是表明在第一個引數的情況下,相機朝向哪個點看的(他與第一個引數一起才構成了相機的朝向向量,也就是朝向哪個方向看的)。

一般情況下這個點是由相機的點跟方向向量相加而得。因為已知他的方向向量(詳見下面程式碼)

第三個引數是一個向量表示相機的上向量,指向y軸,可以通過這個向量跟前兩個引數,兩次向量叉乘出三個互相正交的向量

4.有了觀察矩陣也就是都轉換到相機座標系了。這時候可以有一些互動設計了,比如滑鼠控制朝向方向,鍵盤wasd控制移動,但是有些注意點(以下為相機移動的重點,要重點理解)

4.0主要講解滑鼠控制的視角移動跟鍵盤wasd控制的距離移動,以及滾輪控制的縮放移動

4.1鍵盤的wasd鍵用來控制前後左右的移動,其原理就是控制相機的位置,也就是更改cameraPos的值,那具體向哪個方向移動呢?對啦,就是向相機的方向向量(cameraFront)所指的方向移動就好啦,w就是他的方向,s就是反向,a就是他和相機的cameraUp向量叉乘出來的方向,d就是a的反向。但是要注意相乘的向量必須是單位向量,不然移動距離不對

4.2關於移動速度:

目前我們的移動速度是個常量。理論上沒什麼問題,但是實際情況下根據處理器的能力不同,有些人可能會比其他人每秒繪製更多幀,也就是以更高的頻率呼叫processInput函式。結果就是,根據配置的不同,有些人可能移動很快,而有些人會移動很慢。當你釋出你的程式的時候,你必須確保它在所有硬體上移動速度都一樣。

圖形程式和遊戲通常會跟蹤一個時間差(Deltatime)變數,它儲存了渲染上一幀所用的時間。我們把所有速度都去乘以deltaTime值。結果就是,如果我們的deltaTime很大,就意味著上一幀的渲染花費了更多時間,所以這一幀的速度需要變得更高來平衡渲染所花去的時間。使用這種方法時,無論你的電腦快還是慢,攝像機的速度都會相應平衡,這樣每個使用者的體驗就都一樣了。

4.3關於視角移動:

一共有3種尤拉角:俯仰角(Pitch)、偏航角(Yaw)和滾轉角(Roll),下面的圖片展示了它們的含義:

尤拉角與相機方向向量的關係:

direction.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw)); 
direction.y = sin(glm::radians(pitch));
direction.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));

具體座標系再具體分析,滑鼠動一下,尤拉角響應加或者減一些,相機向量cameraFornt變一下,但是十分注意三個尤拉角的初始值,並不一定全是0,有可能是-90度

 1 void mouse_callback(GLFWwindow* window, double xpos, double ypos)
 2 {
 3     if (firstMouse)
 4     {
 5         lastX = xpos;
 6         lastY = ypos;
 7         firstMouse = false;
 8     }
 9 
10     float xoffset = xpos - lastX;
11     float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to top
12     lastX = xpos;
13     lastY = ypos;
14 
15     float sensitivity = 0.1f; // change this value to your liking
16     xoffset *= sensitivity;
17     yoffset *= sensitivity;
18 
19     yaw += xoffset;
20     pitch += yoffset;
21 
22     // make sure that when pitch is out of bounds, screen doesn't get flipped
23     if (pitch > 89.0f)
24         pitch = 89.0f;
25     if (pitch < -89.0f)
26         pitch = -89.0f;
27 
28     glm::vec3 front;
29     front.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
30     front.y = sin(glm::radians(pitch));
31     front.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
32     cameraFront = glm::normalize(front);
33 }
View Code

4.4還要注意一般不用翻滾角,且俯仰角的範圍有限制,在正負89度之間,另外還有第一次捕捉滑鼠位置時的問題

4.5滑鼠滾輪的縮放移動

 1 void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
 2 {
 3   if(fov >= 1.0f && fov <= 45.0f)
 4     fov -= yoffset;
 5   if(fov <= 1.0f)
 6     fov = 1.0f;
 7   if(fov >= 45.0f)
 8     fov = 45.0f;
 9 }
10 //fov是init投影矩陣的視角引數
View Code

5.完整的一個程式碼

  1 #include <glad/glad.h>
  2 #include <GLFW/glfw3.h>
  3 #include <stb_image.h>
  4 
  5 #include <glm/glm.hpp>
  6 #include <glm/gtc/matrix_transform.hpp>
  7 #include <glm/gtc/type_ptr.hpp>
  8 
  9 #include <learnopengl/shader_m.h>
 10 
 11 #include <iostream>
 12 
 13 void framebuffer_size_callback(GLFWwindow* window, int width, int height);
 14 void mouse_callback(GLFWwindow* window, double xpos, double ypos);
 15 void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
 16 void processInput(GLFWwindow *window);
 17 
 18 // settings
 19 const unsigned int SCR_WIDTH = 800;
 20 const unsigned int SCR_HEIGHT = 600;
 21 
 22 // camera
 23 glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);
 24 glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
 25 glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);
 26 
 27 bool firstMouse = true;
 28 float yaw = -90.0f;    // yaw is initialized to -90.0 degrees since a yaw of 0.0 results in a direction vector pointing to the right so we initially rotate a bit to the left.
 29 float pitch = 0.0f;
 30 float lastX = 800.0f / 2.0;
 31 float lastY = 600.0f / 2.0;
 32 float fov = 45.0f;
 33 
 34 // timing
 35 float deltaTime = 0.0f;    // time between current frame and last frame
 36 float lastFrame = 0.0f;
 37 
 38 int main()
 39 {
 40     // glfw: initialize and configure
 41     // ------------------------------
 42     glfwInit();
 43     glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
 44     glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
 45     glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
 46 
 47 #ifdef __APPLE__
 48     glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X
 49 #endif
 50 
 51     // glfw window creation
 52     // --------------------
 53     GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
 54     if (window == NULL)
 55     {
 56         std::cout << "Failed to create GLFW window" << std::endl;
 57         glfwTerminate();
 58         return -1;
 59     }
 60     glfwMakeContextCurrent(window);
 61     glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
 62     glfwSetCursorPosCallback(window, mouse_callback);
 63     glfwSetScrollCallback(window, scroll_callback);
 64 
 65     // tell GLFW to capture our mouse
 66     glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
 67 
 68     // glad: load all OpenGL function pointers
 69     // ---------------------------------------
 70     if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
 71     {
 72         std::cout << "Failed to initialize GLAD" << std::endl;
 73         return -1;
 74     }
 75 
 76     // configure global opengl state
 77     // -----------------------------
 78     glEnable(GL_DEPTH_TEST);
 79 
 80     // build and compile our shader zprogram
 81     // ------------------------------------
 82     Shader ourShader("6.2.coordinate_systems.vs", "6.2.coordinate_systems.fs");
 83 
 84     // set up vertex data (and buffer(s)) and configure vertex attributes
 85     // ------------------------------------------------------------------
 86     float vertices[] = {
 87         -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
 88          0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
 89          0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
 90          0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
 91         -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
 92         -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
 93 
 94         -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
 95          0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
 96          0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
 97          0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
 98         -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
 99         -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
100 
101         -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
102         -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
103         -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
104         -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
105         -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
106         -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
107 
108          0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
109          0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
110          0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
111          0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
112          0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
113          0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
114 
115         -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
116          0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
117          0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
118          0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
119         -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
120         -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
121 
122         -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
123          0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
124          0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
125          0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
126         -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
127         -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
128     };
129     // world space positions of our cubes
130     glm::vec3 cubePositions[] = {
131         glm::vec3(0.0f,  0.0f,  0.0f),
132         glm::vec3(2.0f,  5.0f, -15.0f),
133         glm::vec3(-1.5f, -2.2f, -2.5f),
134         glm::vec3(-3.8f, -2.0f, -12.3f),
135         glm::vec3(2.4f, -0.4f, -3.5f),
136         glm::vec3(-1.7f,  3.0f, -7.5f),
137         glm::vec3(1.3f, -2.0f, -2.5f),
138         glm::vec3(1.5f,  2.0f, -2.5f),
139         glm::vec3(1.5f,  0.2f, -1.5f),
140         glm::vec3(-1.3f,  1.0f, -1.5f)
141     };
142     unsigned int VBO, VAO;
143     glGenVertexArrays(1, &VAO);
144     glGenBuffers(1, &VBO);
145 
146     glBindVertexArray(VAO);
147 
148     glBindBuffer(GL_ARRAY_BUFFER, VBO);
149     glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
150 
151     // position attribute
152     glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
153     glEnableVertexAttribArray(0);
154     // texture coord attribute
155     glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
156     glEnableVertexAttribArray(1);
157 
158 
159     // load and create a texture 
160     // -------------------------
161     unsigned int texture1, texture2;
162     // texture 1
163     // ---------
164     glGenTextures(1, &texture1);
165     glBindTexture(GL_TEXTURE_2D, texture1);
166     // set the texture wrapping parameters
167     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
168     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
169     // set texture filtering parameters
170     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
171     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
172     // load image, create texture and generate mipmaps
173     int width, height, nrChannels;
174     stbi_set_flip_vertically_on_load(true); // tell stb_image.h to flip loaded texture's on the y-axis.
175     unsigned char *data = stbi_load("resources/textures/container.jpg", &width, &height, &nrChannels, 0);
176     if (data)
177     {
178         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
179         glGenerateMipmap(GL_TEXTURE_2D);
180     }
181     else
182     {
183         std::cout << "Failed to load texture" << std::endl;
184     }
185     stbi_image_free(data);
186     // texture 2
187     // ---------
188     glGenTextures(1, &texture2);
189     glBindTexture(GL_TEXTURE_2D, texture2);
190     // set the texture wrapping parameters
191     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
192     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
193     // set texture filtering parameters
194     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
195     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
196     // load image, create texture and generate mipmaps
197     data = stbi_load("resources/textures/awesomeface.png", &width, &height, &nrChannels, 0);
198     if (data)
199     {
200         // note that the awesomeface.png has transparency and thus an alpha channel, so make sure to tell OpenGL the data type is of GL_RGBA
201         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
202         glGenerateMipmap(GL_TEXTURE_2D);
203     }
204     else
205     {
206         std::cout << "Failed to load texture" << std::endl;
207     }
208     stbi_image_free(data);
209 
210     // tell opengl for each sampler to which texture unit it belongs to (only has to be done once)
211     // -------------------------------------------------------------------------------------------
212     ourShader.use();
213     ourShader.setInt("texture1", 0);
214     ourShader.setInt("texture2", 1);
215 
216 
217     // render loop
218     // -----------
219     while (!glfwWindowShouldClose(window))
220     {
221         // per-frame time logic
222         // --------------------
223         float currentFrame = glfwGetTime();
224         deltaTime = currentFrame - lastFrame;
225         lastFrame = currentFrame;
226 
227         // input
228         // -----
229         processInput(window);
230 
231         // render
232         // ------
233         glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
234         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
235 
236         // bind textures on corresponding texture units
237         glActiveTexture(GL_TEXTURE0);
238         glBindTexture(GL_TEXTURE_2D, texture1);
239         glActiveTexture(GL_TEXTURE1);
240         glBindTexture(GL_TEXTURE_2D, texture2);
241 
242         // activate shader
243         ourShader.use();
244 
245         // pass projection matrix to shader (note that in this case it could change every frame)
246         glm::mat4 projection = glm::perspective(glm::radians(fov), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
247         ourShader.setMat4("projection", projection);
248 
249         // camera/view transformation
250         glm::mat4 view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
251         ourShader.setMat4("view", view);
252 
253         // render boxes
254         glBindVertexArray(VAO);
255         for (unsigned int i = 0; i < 10; i++)
256         {
257             // calculate the model matrix for each object and pass it to shader before drawing
258             glm::mat4 model;
259             model = glm::translate(model, cubePositions[i]);
260             float angle = 20.0f * i;
261             model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
262             ourShader.setMat4("model", model);
263 
264             glDrawArrays(GL_TRIANGLES, 0, 36);
265         }
266 
267         // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
268         // -------------------------------------------------------------------------------
269         glfwSwapBuffers(window);
270         glfwPollEvents();
271     }
272 
273     // optional: de-allocate all resources once they've outlived their purpose:
274     // ------------------------------------------------------------------------
275     glDeleteVertexArrays(1, &VAO);
276     glDeleteBuffers(1, &VBO);
277 
278     // glfw: terminate, clearing all previously allocated GLFW resources.
279     // ------------------------------------------------------------------
280     glfwTerminate();
281     return 0;
282 }
283 
284 // process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
285 // ---------------------------------------------------------------------------------------------------------
286 void processInput(GLFWwindow *window)
287 {
288     if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
289         glfwSetWindowShouldClose(window, true);
290 
291     float cameraSpeed = 2.5 * deltaTime;
292     if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
293         cameraPos += cameraSpeed * cameraFront;
294     if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
295         cameraPos -= cameraSpeed * cameraFront;
296     if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
297         cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
298     if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
299         cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
300 }
301 
302 // glfw: whenever the window size changed (by OS or user resize) this callback function executes
303 // ---------------------------------------------------------------------------------------------
304 void framebuffer_size_callback(GLFWwindow* window, int width, int height)
305 {
306     // make sure the viewport matches the new window dimensions; note that width and 
307     // height will be significantly larger than specified on retina displays.
308     glViewport(0, 0, width, height);
309 }
310 
311 // glfw: whenever the mouse moves, this callback is called
312 // -------------------------------------------------------
313 void mouse_callback(GLFWwindow* window, double xpos, double ypos)
314 {
315     if (firstMouse)
316     {
317         lastX = xpos;
318         lastY = ypos;
319         firstMouse = false;
320     }
321 
322     float xoffset = xpos - lastX;
323     float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to top
324     lastX = xpos;
325     lastY = ypos;
326 
327     float sensitivity = 0.1f; // change this value to your liking
328     xoffset *= sensitivity;
329     yoffset *= sensitivity;
330 
331     yaw += xoffset;
332     pitch += yoffset;
333 
334     // make sure that when pitch is out of bounds, screen doesn't get flipped
335     if (pitch > 89.0f)
336         pitch = 89.0f;
337     if (pitch < -89.0f)
338         pitch = -89.0f;
339 
340     glm::vec3 front;
341     front.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
342     front.y = sin(glm::radians(pitch));
343     front.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
344     cameraFront = glm::normalize(front);
345 }
346 
347 // glfw: whenever the mouse scroll wheel scrolls, this callback is called
348 // ----------------------------------------------------------------------
349 void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
350 {
351     if (fov >= 1.0f && fov <= 45.0f)
352         fov -= yoffset;
353     if (fov <= 1.0f)
354         fov = 1.0f;
355     if (fov >= 45.0f)
356         fov = 45.0f;
357 }
View Code