OpenGL学习笔记——坐标转换
因?yàn)镺penGL中的坐標(biāo)轉(zhuǎn)換有些復(fù)雜,所以做一篇筆記記錄一下。
文章目錄
- 一、簡介
- 二、代碼實(shí)現(xiàn)
- 2.1簡單的測試
- 2.2旋轉(zhuǎn)測試
- 三、小結(jié)
一、簡介
學(xué)習(xí)OpenGL一段時間之后,數(shù)據(jù)的坐標(biāo)轉(zhuǎn)換將會成為一個令人頭疼的問題,因?yàn)槲覀兛偛荒芤恢敝皇褂么翱诘囊?guī)范化設(shè)備坐標(biāo)系(NDC)來顯示數(shù)據(jù),這樣會對我們產(chǎn)生很大的約束,而如果我們要把我們真實(shí)世界中的東西在OpenGL中顯示出來,就必須學(xué)會使用坐標(biāo)轉(zhuǎn)換。
在真正進(jìn)行坐標(biāo)轉(zhuǎn)換之前,我們首先要了解OpenGL中到底有什么坐標(biāo)空間,總的來說,其共有五種坐標(biāo)空間:局部空間(或者稱作模型空間)、世界空間、觀察空間(或者稱作人眼空間)、裁剪空間以及屏幕空間。
1、我們所有的頂點(diǎn)數(shù)據(jù)剛一開始都位于局部空間之中,這個空間有點(diǎn)類似于我們生活的真實(shí)空間,空間內(nèi)的模型可能很大,幾米甚至幾十米等。那么這么大的東西如果直接一比一的放入電腦之中,那電腦屏幕什么也不會顯示出來。另一方面則是每一個對象都會擁有一套屬于自己的局部坐標(biāo)系,局部空間內(nèi)對象的原點(diǎn)可能是(0,0,0),也有可能不是,這不利于我們對這些對象進(jìn)行定位;再者就是局部空間可能很小(根據(jù)Learning OpenGL中的內(nèi)容進(jìn)行的一些猜測)??偟膩碚f,局部空間就像是專門為了承載單個對象模型而創(chuàng)建的小天地,而每個對象模型的小天地都是獨(dú)立存在的,沒有任何關(guān)系,所以我們需要將他們放到一個統(tǒng)一的坐標(biāo)系(世界坐標(biāo)系)下,這樣我們才能更好的去描述他們之間的相對關(guān)系,所以在這個過程中我們可能就需要對我們的模型進(jìn)行平移、旋轉(zhuǎn)和縮放等操作。
2、這個世界空間為了方便理解,我們可以將其想象成一種游戲空間,玩過游戲的人都清楚游戲內(nèi)的對戰(zhàn)場景會把多個人物模型給放在一起,也就是放到同一個空間之中進(jìn)行對戰(zhàn),而這個對戰(zhàn)空間其實(shí)就有點(diǎn)類似于我們所說的世界空間,它是一個更為龐大的空間。
3、之后我們就是要模擬人類看東西的過程,將物體置入觀察空間,也就是讓我們可以看到這個物體。這個過程有點(diǎn)類似于將一個照相機(jī)移到了模型前方的某個位置,然后再設(shè)置一下照相機(jī)的朝向,讓這個照相機(jī)可以看到我們所置入的模型。
4、在觀察空間中,類比于現(xiàn)實(shí)中的人類,我們視域總是有限的,也就是我們能看到的東西總是有限的,所以在OpenGL中其也會讓我們?nèi)ピO(shè)置一個視域范圍,如果模型存在一部分超出了我們設(shè)置的視域范圍,屏幕將不會顯示該部分的模型。
5、當(dāng)前面都設(shè)置好了之后只需要在設(shè)置一下視口(一般與窗口大小相同),就可以將模型在屏幕上顯示出來。
詳細(xì)的內(nèi)容可以參看:https://learnopengl.com/Getting-started/Coordinate-Systems
二、代碼實(shí)現(xiàn)
2.1簡單的測試
Shader.h
#ifndef SHADER_H #define SHADER_H#include<glad\glad.h>#include<string> #include<fstream> #include<sstream> //字符流 #include<iostream>class Shader { public://著色器程序的IDunsigned int ID;//構(gòu)造函數(shù)Shader(const char* vertexPath, const char* fragmentPath);//激活著色器void use();//設(shè)置著色器中的轉(zhuǎn)換模型void setMat4(const std::string& name ,const glm::mat4 &mat) const;};Shader::Shader(const char* vertexPath, const char* fragmentPath) {//檢索頂點(diǎn)和片元著色器源代碼的路徑std::string vertexCode;std::string fragmentCode;std::ifstream vShaderFile;std::ifstream fShaderFile;//如果發(fā)生以下錯誤程序?qū)伋霎惓?使用下面的語句可以保證異常的拋出vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit); //其實(shí)就是重置了輸入輸出和文件讀取寫入的狀態(tài)標(biāo)記fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);try{//打開文件vShaderFile.open(vertexPath);fShaderFile.open(fragmentPath);std::stringstream vShaderStream, fShaderStream;//文件的緩沖區(qū)內(nèi)容讀入sstream對象中vShaderStream << vShaderFile.rdbuf();fShaderStream << fShaderFile.rdbuf();//關(guān)閉文件vShaderFile.close();fShaderFile.close();//將字符流轉(zhuǎn)換為string字符串vertexCode = vShaderStream.str();fragmentCode = fShaderStream.str();}catch (const std::exception&){std::cout << "文件讀取失敗!" << std::endl;}const char* vShaderCode = vertexCode.c_str(); //將字符串中的字符數(shù)組賦給一個字符指針const char* fShaderCode = fragmentCode.c_str();////編譯著色器//unsigned int vertex, fragment; //記錄著色器對象的索引int success; //記錄著色器編譯成功與否char infolog[512];//頂點(diǎn)著色器vertex = glCreateShader(GL_VERTEX_SHADER); //創(chuàng)建一個空的著色器對象,把源代碼填進(jìn)去就可以制作頂點(diǎn)著色器程序了glShaderSource(vertex, 1, &vShaderCode, NULL); //將Shader中的源代碼設(shè)置為指定數(shù)組中的字符串,之前Shader對象中的原代碼將會被替代glCompileShader(vertex); //編譯頂點(diǎn)著色器//檢查著色器編譯的情況,如果有錯誤打印出來glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(vertex, 512, NULL, infolog);std::cout << "頂點(diǎn)著色器編譯錯誤信息:" << infolog << std::endl;}//片元著色器fragment = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragment, 1, &fShaderCode, NULL);glCompileShader(fragment);glGetShaderiv(fragment, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(fragment, 512, NULL, infolog);std::cout << "片元著色器編譯錯誤信息:" << infolog << std::endl;}//編譯為著色器程序,將著色器對象添加到一個程序?qū)ο笊先?其實(shí)也就是鏈接兩個著色器對象ID = glCreateProgram();glAttachShader(ID, vertex);glAttachShader(ID, fragment);glLinkProgram(ID);glGetProgramiv(ID, GL_LINK_STATUS, &success);if (!success){glGetProgramInfoLog(ID, 512, NULL, infolog);std::cout << "著色器鏈接錯誤信息:" << infolog << std::endl;}//將創(chuàng)建的著色器對象給刪除掉glDeleteShader(vertex);glDeleteShader(fragment); }void Shader::use() {glUseProgram(ID); //激活著色器程序 }void Shader::setMat4(const std::string& name, const glm::mat4& mat)const {glUniformMatrix4fv(glGetUniformLocation(this->ID, name.c_str()), 1, GL_FALSE, &mat[0][0]); //設(shè)置指定轉(zhuǎn)換的模型的值 }#endif // !SHADER_HCT.h
#ifndef CT_H #define CT_H//一般的C++頭文件 #include<iostream> #include<vector>//GLAD #include<glad\glad.h> //用于初始化我們所需的OpenGL函數(shù)//GLFW #include<GLFW\glfw3.h> //用于處理窗口相關(guān)的事件//glm #include<glm\glm.hpp> #include<glm\gtc\matrix_transform.hpp> #include<glm\gtc\type_ptr.hpp>//自定義的類頭文件 #include"Shader.h"//相關(guān)的函數(shù)原型 //void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);namespace CT {//窗口相關(guān)信息const GLuint WIDTH = 800, HEIGHT = 600;GLfloat lastX = 400, lastY = 300;void CT_test() {//初始化GLFW函數(shù)庫,操作系統(tǒng)會分配相應(yīng)的資源glfwInit();//設(shè)置我們所需要的一些選項(xiàng),也就是配置GLFWglfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); //GLFW所使用的OpenGL版本為3.3版本,如果用戶沒有3.3版本,則GLFW不能運(yùn)行glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //使用OpenGL的核心模式glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);//使用GLFW函數(shù)創(chuàng)建一個窗口GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "CoordinateTransform", nullptr, nullptr);if (window == NULL){std::cout << "創(chuàng)建窗口失敗!" << std::endl;glfwTerminate(); //終止進(jìn)程并釋放資源return;}glfwMakeContextCurrent(window); //創(chuàng)建當(dāng)前上下文環(huán)境//初始化GLAD,在我們調(diào)用OpenGL函數(shù)之前,然后我們就可以直接使用函數(shù)了,這類似于將顯式鏈接變?yōu)榱穗[式鏈接,但是其查找過程并沒有改變if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {std::cout << "初始化GLAD失敗\n";return;}//設(shè)置回調(diào)函數(shù)讓W(xué)indows進(jìn)行調(diào)用//glfwSetKeyCallback(window, key_callback);//設(shè)置視口,也就是我們可見的區(qū)域glViewport(0, 0, WIDTH, HEIGHT);//創(chuàng)建與編譯著色器程序Shader myShader("../Shader/shader_ct.vs", "../Shader/shader_ct.fs");//定義頂點(diǎn)數(shù)據(jù)//std::vector<GLfloat> vertice; //OpenGL中的函數(shù)會接收數(shù)組型的數(shù)據(jù)GLfloat vertice[] = {0.0,100,0.0,-100,0.0,0.0,100,0.0,0.0};////顯示部分//GLuint VBO, VAO;glGenVertexArrays(1, &VAO); //創(chuàng)建頂點(diǎn)數(shù)組對象的名字,其更像是一組指針,負(fù)責(zé)管理緩沖對象glGenBuffers(1, &VBO); //創(chuàng)建緩沖區(qū)對象名字,分配緩存,負(fù)責(zé)保存一系列頂點(diǎn)的數(shù)據(jù)glBindVertexArray(VAO); //綁定一個頂點(diǎn)數(shù)組對象,僅僅只有對象還是沒有用處的,我們還需要將他們與當(dāng)前的OpenGL環(huán)境進(jìn)行綁定才能夠被OpenGL使用,也就是激活它glBindBuffer(GL_ARRAY_BUFFER, VBO); //綁定一個命名的緩沖區(qū)對象glBufferData(GL_ARRAY_BUFFER, sizeof(vertice), &vertice[0], GL_STATIC_DRAW); //創(chuàng)建和初始化一個緩沖區(qū)對象的數(shù)據(jù)存儲//位置屬性glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GL_FLOAT), (GLvoid*)0); //指定頂點(diǎn)位置數(shù)據(jù)glEnableVertexAttribArray(0); //啟用了布局標(biāo)志符為0的變量glBindBuffer(GL_ARRAY_BUFFER, 0);glBindVertexArray(0); // Unbind VAOglEnable(GL_DEPTH_TEST); //啟用深度測試glPointSize(10);while (!glfwWindowShouldClose(window)){glfwPollEvents(); //監(jiān)視所有的窗口事件glClearColor(0.2f, 0.3f, 0.3f, 1.0f); //用什么樣的顏色來清除之前的緩沖區(qū)//glClear(GL_COLOR_BUFFER_BIT);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);myShader.use();glBindVertexArray(VAO);////坐標(biāo)轉(zhuǎn)換////模型變換,將模型置入世界空間之中glm::mat4 model(1);model = glm::translate(model, glm::vec3(0, 0, 0)); //將模型平移到點(diǎn)(0,0,0)處myShader.setMat4("model", model);//視圖變換glm::mat4 view;view = glm::lookAt(glm::vec3(0.0f, 0.0f, 250.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));myShader.setMat4("view", view);//投影變換glm::mat4 prj;prj = glm::perspective(glm::radians(45.0f), (GLfloat)WIDTH / (GLfloat)HEIGHT, 0.1f, 500.0f);myShader.setMat4("prj", prj);//glDrawArrays(GL_POINTS, 0, sizeof(vertice)/(3*sizeof(vertice[0]))); //繪制點(diǎn)glDrawArrays(GL_TRIANGLES, 0, sizeof(vertice) / (3 * sizeof(vertice[0])));glfwSwapBuffers(window); //交換緩沖區(qū)}//釋放掉相應(yīng)的資源glDeleteVertexArrays(1, &VAO);glDeleteBuffers(1, &VBO);//清理窗口glfwDestroyWindow(window);glfwTerminate(); //清除掉分配給glfw的資源} }#endif // !CT_Hmain.cpp
#include"CT.h"int main() {CT::CT_test();return 0; }著色器代碼:
shader_ct.vs
#version 330 core//進(jìn)行布局 layout (location=0) in vec3 aPos; //接收傳進(jìn)來的數(shù)據(jù) out vec4 myColor;//定義坐標(biāo)轉(zhuǎn)換變量 uniform mat4 model; uniform mat4 view; uniform mat4 prj;void main(){gl_Position = prj * view * model * vec4(aPos, 1.0); //對頂點(diǎn)數(shù)據(jù)進(jìn)行坐標(biāo)轉(zhuǎn)換myColor = vec4(0,1,0,1); }shader_ct.fs
#version 330 corein vec4 myColor; //接收傳過來的數(shù)據(jù) out vec4 fragColor;void main(){fragColor = myColor; }測試效果:
2.2旋轉(zhuǎn)測試
在model模型變換矩陣的代碼中添加下面的代碼:
//模型變換,將模型置入世界空間之中 glm::mat4 model(1); model = glm::rotate(model, (float)glfwGetTime(), glm::vec3(0.0f, 1.0f, 0.0f)); model = glm::translate(model, glm::vec3(0, 0, 0)); //將模型平移到點(diǎn)(0,0,0)處 myShader.setMat4("model", model);測試效果:
三、小結(jié)
為什么要搞這么多坐標(biāo)空間呢?這是我在學(xué)習(xí)這部分的時候最大的疑問,《learning OpenGL》一書中對這個問題倒是有很好的解答。每一個空間其實(shí)都是為了后續(xù)的一些特定的操作而服務(wù)的,如對象自身的一些修改,那自然是在局部空間中進(jìn)行這些修改操作是較好的;而如果要使用對象與對象之間的相對關(guān)系,則是在世界空間中更為合適,這類的操作可能還有很多。OpenGL的設(shè)計(jì)者肯定也是考慮過這些問題的,前面的繁瑣是為了后面的便捷,而我現(xiàn)階段要做的就是學(xué)會怎么靈活的使用它就很不錯了*~*。
參考資料:《Learning OpenGL》《OpenGL編程指南》
總結(jié)
以上是生活随笔為你收集整理的OpenGL学习笔记——坐标转换的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网站标题设计与搜索引擎
- 下一篇: MyBatis-Plus插件