因为OpenGL中的坐标转换有些复杂,所以做一篇笔记记录一下。

文章目录

  • 一、简介
  • 二、代码实现
    • 2.1简单的测试
    • 2.2旋转测试
  • 三、小结

一、简介

学习OpenGL一段时间之后,数据的坐标转换将会成为一个令人头疼的问题,因为我们总不能一直只使用窗口的规范化设备坐标系(NDC)来显示数据,这样会对我们产生很大的约束,而如果我们要把我们真实世界中的东西在OpenGL中显示出来,就必须学会使用坐标转换。

在真正进行坐标转换之前,我们首先要了解OpenGL中到底有什么坐标空间,总的来说,其共有五种坐标空间:局部空间(或者称作模型空间)、世界空间、观察空间(或者称作人眼空间)、裁剪空间以及屏幕空间。
1、我们所有的顶点数据刚一开始都位于局部空间之中,这个空间有点类似于我们生活的真实空间,空间内的模型可能很大,几米甚至几十米等。那么这么大的东西如果直接一比一的放入电脑之中,那电脑屏幕什么也不会显示出来。另一方面则是每一个对象都会拥有一套属于自己的局部坐标系,局部空间内对象的原点可能是(0,0,0),也有可能不是,这不利于我们对这些对象进行定位;再者就是局部空间可能很小(根据Learning OpenGL中的内容进行的一些猜测)。总的来说,局部空间就像是专门为了承载单个对象模型而创建的小天地,而每个对象模型的小天地都是独立存在的,没有任何关系,所以我们需要将他们放到一个统一的坐标系(世界坐标系)下,这样我们才能更好的去描述他们之间的相对关系,所以在这个过程中我们可能就需要对我们的模型进行平移、旋转和缩放等操作。
2、这个世界空间为了方便理解,我们可以将其想象成一种游戏空间,玩过游戏的人都清楚游戏内的对战场景会把多个人物模型给放在一起,也就是放到同一个空间之中进行对战,而这个对战空间其实就有点类似于我们所说的世界空间,它是一个更为庞大的空间。
3、之后我们就是要模拟人类看东西的过程,将物体置入观察空间,也就是让我们可以看到这个物体。这个过程有点类似于将一个照相机移到了模型前方的某个位置,然后再设置一下照相机的朝向,让这个照相机可以看到我们所置入的模型。
4、在观察空间中,类比于现实中的人类,我们视域总是有限的,也就是我们能看到的东西总是有限的,所以在OpenGL中其也会让我们去设置一个视域范围,如果模型存在一部分超出了我们设置的视域范围,屏幕将不会显示该部分的模型。
5、当前面都设置好了之后只需要在设置一下视口(一般与窗口大小相同),就可以将模型在屏幕上显示出来。

详细的内容可以参看:https://learnopengl.com/Getting-started/Coordinate-Systems

二、代码实现

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;//构造函数Shader(const char* vertexPath, const char* fragmentPath);//激活着色器void use();//设置着色器中的转换模型void setMat4(const std::string& name ,const glm::mat4 &mat) const;};Shader::Shader(const char* vertexPath, const char* fragmentPath) {//检索顶点和片元着色器源代码的路径std::string vertexCode;std::string fragmentCode;std::ifstream vShaderFile;std::ifstream fShaderFile;//如果发生以下错误程序将会抛出异常,使用下面的语句可以保证异常的抛出vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);       //其实就是重置了输入输出和文件读取写入的状态标记fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);try{//打开文件vShaderFile.open(vertexPath);fShaderFile.open(fragmentPath);std::stringstream vShaderStream, fShaderStream;//文件的缓冲区内容读入sstream对象中vShaderStream << vShaderFile.rdbuf();fShaderStream << fShaderFile.rdbuf();//关闭文件vShaderFile.close();fShaderFile.close();//将字符流转换为string字符串vertexCode = vShaderStream.str();fragmentCode = fShaderStream.str();}catch (const std::exception&){std::cout << "文件读取失败!" << std::endl;}const char* vShaderCode = vertexCode.c_str();      //将字符串中的字符数组赋给一个字符指针const char* fShaderCode = fragmentCode.c_str();////编译着色器//unsigned int vertex, fragment;       //记录着色器对象的索引int success;        //记录着色器编译成功与否char infolog[512];//顶点着色器vertex = glCreateShader(GL_VERTEX_SHADER);       //创建一个空的着色器对象,把源代码填进去就可以制作顶点着色器程序了glShaderSource(vertex, 1, &vShaderCode, NULL);     //将Shader中的源代码设置为指定数组中的字符串,之前Shader对象中的原代码将会被替代glCompileShader(vertex);  //编译顶点着色器//检查着色器编译的情况,如果有错误打印出来glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(vertex, 512, NULL, infolog);std::cout << "顶点着色器编译错误信息:" << 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;}//编译为着色器程序,将着色器对象添加到一个程序对象上去,其实也就是链接两个着色器对象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;}//将创建的着色器对象给删除掉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]);        //设置指定转换的模型的值
}#endif // !SHADER_H

CT.h

#ifndef CT_H
#define CT_H//一般的C++头文件
#include<iostream>
#include<vector>//GLAD
#include<glad\glad.h>         //用于初始化我们所需的OpenGL函数//GLFW
#include<GLFW\glfw3.h>        //用于处理窗口相关的事件//glm
#include<glm\glm.hpp>
#include<glm\gtc\matrix_transform.hpp>
#include<glm\gtc\type_ptr.hpp>//自定义的类头文件
#include"Shader.h"//相关的函数原型
//void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);namespace CT {//窗口相关信息const GLuint WIDTH = 800, HEIGHT = 600;GLfloat lastX = 400, lastY = 300;void CT_test() {//初始化GLFW函数库,操作系统会分配相应的资源glfwInit();//设置我们所需要的一些选项,也就是配置GLFWglfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);      //GLFW所使用的OpenGL版本为3.3版本,如果用户没有3.3版本,则GLFW不能运行glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);      //使用OpenGL的核心模式glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);//使用GLFW函数创建一个窗口GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "CoordinateTransform", nullptr, nullptr);if (window == NULL){std::cout << "创建窗口失败!" << std::endl;glfwTerminate();    //终止进程并释放资源return;}glfwMakeContextCurrent(window);          //创建当前上下文环境//初始化GLAD,在我们调用OpenGL函数之前,然后我们就可以直接使用函数了,这类似于将显式链接变为了隐式链接,但是其查找过程并没有改变if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {std::cout << "初始化GLAD失败\n";return;}//设置回调函数让Windows进行调用//glfwSetKeyCallback(window, key_callback);//设置视口,也就是我们可见的区域glViewport(0, 0, WIDTH, HEIGHT);//创建与编译着色器程序Shader myShader("../Shader/shader_ct.vs", "../Shader/shader_ct.fs");//定义顶点数据//std::vector<GLfloat> vertice;      //OpenGL中的函数会接收数组型的数据GLfloat vertice[] = {0.0,100,0.0,-100,0.0,0.0,100,0.0,0.0};////显示部分//GLuint VBO, VAO;glGenVertexArrays(1, &VAO);      //创建顶点数组对象的名字,其更像是一组指针,负责管理缓冲对象glGenBuffers(1, &VBO);         //创建缓冲区对象名字,分配缓存,负责保存一系列顶点的数据glBindVertexArray(VAO);      //绑定一个顶点数组对象,仅仅只有对象还是没有用处的,我们还需要将他们与当前的OpenGL环境进行绑定才能够被OpenGL使用,也就是激活它glBindBuffer(GL_ARRAY_BUFFER, VBO);      //绑定一个命名的缓冲区对象glBufferData(GL_ARRAY_BUFFER, sizeof(vertice), &vertice[0], GL_STATIC_DRAW);      //创建和初始化一个缓冲区对象的数据存储//位置属性glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GL_FLOAT), (GLvoid*)0);        //指定顶点位置数据glEnableVertexAttribArray(0);     //启用了布局标志符为0的变量glBindBuffer(GL_ARRAY_BUFFER, 0);glBindVertexArray(0); // Unbind VAOglEnable(GL_DEPTH_TEST);     //启用深度测试glPointSize(10);while (!glfwWindowShouldClose(window)){glfwPollEvents();        //监视所有的窗口事件glClearColor(0.2f, 0.3f, 0.3f, 1.0f);        //用什么样的颜色来清除之前的缓冲区//glClear(GL_COLOR_BUFFER_BIT);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);myShader.use();glBindVertexArray(VAO);////坐标转换////模型变换,将模型置入世界空间之中glm::mat4 model(1);model = glm::translate(model, glm::vec3(0, 0, 0));     //将模型平移到点(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])));        //绘制点glDrawArrays(GL_TRIANGLES, 0, sizeof(vertice) / (3 * sizeof(vertice[0])));glfwSwapBuffers(window);     //交换缓冲区}//释放掉相应的资源glDeleteVertexArrays(1, &VAO);glDeleteBuffers(1, &VBO);//清理窗口glfwDestroyWindow(window);glfwTerminate();       //清除掉分配给glfw的资源}
}#endif // !CT_H

main.cpp

#include"CT.h"int main() {CT::CT_test();return 0;
}

着色器代码:

shader_ct.vs

#version 330 core//进行布局
layout (location=0) in vec3 aPos;      //接收传进来的数据
out vec4 myColor;//定义坐标转换变量
uniform mat4 model;
uniform mat4 view;
uniform mat4 prj;void main(){gl_Position = prj * view * model * vec4(aPos, 1.0);       //对顶点数据进行坐标转换myColor = vec4(0,1,0,1);
}

shader_ct.fs

#version 330 corein vec4 myColor;        //接收传过来的数据
out vec4 fragColor;void main(){fragColor = myColor;
}

测试效果:

2.2旋转测试

在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));     //将模型平移到点(0,0,0)处
myShader.setMat4("model", model);

测试效果:

三、小结

为什么要搞这么多坐标空间呢?这是我在学习这部分的时候最大的疑问,《learning OpenGL》一书中对这个问题倒是有很好的解答。每一个空间其实都是为了后续的一些特定的操作而服务的,如对象自身的一些修改,那自然是在局部空间中进行这些修改操作是较好的;而如果要使用对象与对象之间的相对关系,则是在世界空间中更为合适,这类的操作可能还有很多。OpenGL的设计者肯定也是考虑过这些问题的,前面的繁琐是为了后面的便捷,而我现阶段要做的就是学会怎么灵活的使用它就很不错了*~*。

参考资料:《Learning OpenGL》《OpenGL编程指南》

OpenGL学习笔记——坐标转换相关推荐

  1. OpenGL学习笔记:矩阵变换

    文章目录 缩放 glm矩阵表示 glm缩放矩阵实现 位移 齐次坐标 glm位移矩阵实现 旋转 沿x轴旋转 沿y轴旋转 沿z轴旋转 沿任意轴旋转 glm旋转矩阵实现 矩阵的组合 glm矩阵组合使用 接上 ...

  2. OpenGL学习笔记(一)绘制点线面及多面体

    OpenGL学习笔记(一)绘制点线面及多面体 绘制点线面 #include <iostream> #include <GL/GLUT.h> #define PI 3.14159 ...

  3. OpenGL学习笔记(一):环境搭建、三维空间坐标系理解以及OpenGL的基本使用

    原博主博客地址:http://blog.csdn.net/qq21497936 本文章博客地址:http://blog.csdn.net/qq21497936/article/details/7866 ...

  4. 【OpenGL学习笔记⑧】——键盘控制正方体+光源【冯氏光照模型 光照原理 环境光照+漫反射光照+镜面光照】

    ✅ 重点参考了 LearnOpenGL CN 的内容,但大部分知识内容,小编已作改写,以方便读者理解. 文章目录 零. 成果预览图 一. 光照原理与投光物的配置 1.1 光照原理 1.2 投光物 二. ...

  5. OPENGL学习笔记之八

    OPENGL学习笔记之八 2017/11/15 阅读材料来自learnopengl.com以及learnopengl-cn.github.io 我们通常会自己设定一个坐标的范围,之后再在顶点着色器中将 ...

  6. 【OpenGL学习笔记】地月系

    OpenGL学习笔记2-地月系 文章目录 OpenGL学习笔记2-地月系 前言 运行结果 纹理图片 一.TexturePool 1.**TexturePool.h** 2.**TexturePool. ...

  7. 【OpenGL学习笔记⑥】——3D变换【旋转的正方体 实现地月系统 旋转+平移+缩放】

    ✈️ 文章目录 零. 成果预览图 一.3D立方体的顶点数组 二.纹理旋转 三.纹理缩放 四.画n个3D图形 五.轨道的数学公式 六.深度缓冲(Z 缓冲) 七.完整代码 八.参考附录: 神器的正方体 ☁ ...

  8. OpenGL 学习笔记 II:初始化 API,第一个黑窗,游戏循环和帧率,OpenGL 默认垂直同步,glfw 帧率

    前情提要: 上一篇: OpenGL 学习笔记 I:OpenGL glew glad glfw glut 的关系,OpenGL 状态机,现代操作系统的窗口管理器,OpenGL 窗口和上下文 OpenGL ...

  9. OpenGL学习笔记(八):进一步理解VAO、VBO和SHADER,并使用VAO、VBO和SHADER绘制一个三角形

    原博主博客地址:http://blog.csdn.net/qq21497936 本文章博客地址:http://blog.csdn.net/qq21497936/article/details/7888 ...

最新文章

  1. 使用 Python 的图像隐写术
  2. python 车牌识别简单_智能车牌识别 停车如此简单
  3. Duktape:一个新的小巧的超精简可嵌入式JavaScript引擎
  4. Buffer Pool--内存总结2
  5. 在麻省理工读计算机专业,看美国的计算机教育(转载)
  6. 机器学习和深度学习资料整理
  7. APP技巧:电脑登录微信,要删除这5个文件!否则别人能查看聊天记录
  8. 韩国FSC公布新方案允许分离银行加密业务 以帮助小型交易所继续运营
  9. 百度付费会员下载真是神速,每秒百兆
  10. 关于全能挤房器v2.1
  11. 数据库系统及应用——班级管理系统
  12. Formal Verification (三) abstraction strategy、reduce complexity for convergence
  13. 软件系统部署手册模版
  14. [附源码]Python计算机毕业设计常见病辅助食疗系统
  15. JS网页特效实例:动态关闭页面
  16. 搜狗号正式上线广告收益功能!
  17. VirtualBox下Centos7安装增强功能
  18. 分体式无线蓝牙耳机什么牌子好?分体式蓝牙耳机排行榜
  19. 基于SVM支持向量机的车牌分割识别算法matlab仿真
  20. 2022年外贸公司邮箱签名怎么设置?

热门文章

  1. 猿创征文 |【算法入门必刷】数据结构-栈(五)
  2. 高数 微分的几何意义
  3. 程序员的职场潜意识Top10
  4. 视频:忆童年有摇杆,《暗黑破坏神3》街机版演示
  5. 今天你够“敏捷”吗?
  6. 思维导图怎么画?原来思维导图还可以这样绘制
  7. Excel学习日记:L6-格式化为表格交叉分析筛选器
  8. 亚马逊如何使用二次验证码?
  9. 51单片机教程 :(一) 开发环境的搭建
  10. Python爬虫系列之唯品会商品数据采集