当前位置: 首页 > news >正文

OpenGL 原生库6 坐标系统

概述

为了将坐标从一个坐标系变换到另一个坐标系,我们需要用到几个变换矩阵,最重要的几个分别是模型(Model)、观察(View)、投影(Projection)三个矩阵。我们的顶点坐标起始于局部空间(Local Space),在这里它称为局部坐标(Local Coordinate),它在之后会变为世界坐标(World Coordinate),观察坐标(View Coordinate),裁剪坐标(Clip Coordinate),并最后以屏幕坐标(Screen Coordinate)的形式结束。下面的这张图展示了整个流程以及各个变换过程做了什么:
在这里插入图片描述

  1. 局部坐标是对象相对于局部原点的坐标,也是物体起始的坐标。
  2. 下一步是将局部坐标变换为世界空间坐标,世界空间坐标是处于一个更大的空间范围的。这些坐标相对于世界的全局原点,它们会和其它物体一起相对于世界的原点进行摆放。
  3. 接下来我们将世界坐标变换为观察空间坐标,使得每个坐标都是从摄像机或者说观察者的角度进行观察的。
  4. 坐标到达观察空间之后,我们需要将其投影到裁剪坐标。裁剪坐标会被处理至-1.0到1.0的范围内,并判断哪些顶点将会出现在屏幕上。
  5. 最后,我们将裁剪坐标变换为屏幕坐标,我们将使用一个叫做视口变换(Viewport Transform)的过程。视口变换将位于-1.0到1.0范围的坐标变换到由glViewport函数所定义的坐标范围内。最后变换出来的坐标将会送到光栅器,将其转化为片段。

你可能已经大致了解了每个坐标空间的作用。我们之所以将顶点变换到各个不同的空间的原因是有些操作在特定的坐标系统中才有意义且更方便。例如,当需要对物体进行修改的时候,在局部空间中来操作会更说得通;如果要对一个物体做出一个相对于其它物体位置的操作时,在世界坐标系中来做这个才更说得通,等等。如果我们愿意,我们也可以定义一个直接从局部空间变换到裁剪空间的变换矩阵,但那样会失去很多灵活性。

正射投影和透视投影

要创建一个正射投影矩阵,我们可以使用GLM的内置函数glm::ortho:

glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);

GLM中可以这样创建一个透视投影矩阵:

glm::mat4 proj = glm::perspective(glm::radians(45.0f), (float)width/(float)height, 0.1f, 100.0f);

代码:

#include <glad/glad.h>       // GLAD: 用于管理 OpenGL 函数指针
#include <GLFW/glfw3.h>      // GLFW: 用于创建窗口及处理用户输入
#include <stb_image.h>       // STB_IMAGE: 用于加载图像#include <glm/glm.hpp>       // GLM: 用于数学运算(如向量和矩阵)
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>#include <learnopengl/shader_m.h>  // 自定义 Shader 类,用于管理着色器程序#include <iostream>          // 用于标准输入输出// 回调函数声明
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);// 设置窗口宽度和高度
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;int main()
{// 初始化 GLFWglfwInit();// 设置 OpenGL 版本为 3.3glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);// 使用核心模式glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);#ifdef __APPLE__// MacOS 需要设置这个以便获得 OpenGL 3.2 及以上版本glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif// 创建一个 GLFW 窗口GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);if (window == NULL){std::cout << "Failed to create GLFW window" << std::endl;glfwTerminate();return -1;}glfwMakeContextCurrent(window);// 注册窗口大小变化回调函数glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);// 初始化 GLAD:加载所有 OpenGL 函数指针if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){std::cout << "Failed to initialize GLAD" << std::endl;return -1;}// 编译和构建着色器程序Shader ourShader("6.1.coordinate_systems.vs", "6.1.coordinate_systems.fs");// 顶点数据和配置顶点属性float vertices[] = {// 位置              // 纹理坐标0.5f,  0.5f, 0.0f,   1.0f, 1.0f, // 右上0.5f, -0.5f, 0.0f,   1.0f, 0.0f, // 右下-0.5f, -0.5f, 0.0f,   0.0f, 0.0f, // 左下-0.5f,  0.5f, 0.0f,   0.0f, 1.0f  // 左上 };unsigned int indices[] = {0, 1, 3, // 第一个三角形1, 2, 3  // 第二个三角形};unsigned int VBO, VAO, EBO;glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);glGenBuffers(1, &EBO);glBindVertexArray(VAO);glBindBuffer(GL_ARRAY_BUFFER, VBO);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);// 位置属性glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);// 纹理坐标属性glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));glEnableVertexAttribArray(1);// 加载和创建纹理 unsigned int texture1, texture2;// 纹理 1glGenTextures(1, &texture1);glBindTexture(GL_TEXTURE_2D, texture1);// 设置纹理环绕方式glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);// 设置纹理过滤方式glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);// 加载图像,创建纹理并生成 mipmapsint width, height, nrChannels;stbi_set_flip_vertically_on_load(true); // 告诉 stb_image.h 翻转加载的纹理在 y 轴上。unsigned char *data = stbi_load(FileSystem::getPath("resources/textures/container.jpg").c_str(), &width, &height, &nrChannels, 0);if (data){glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);glGenerateMipmap(GL_TEXTURE_2D);}else{std::cout << "Failed to load texture" << std::endl;}stbi_image_free(data);// 纹理 2glGenTextures(1, &texture2);glBindTexture(GL_TEXTURE_2D, texture2);// 设置纹理环绕方式glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);// 设置纹理过滤方式glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);// 加载图像,创建纹理并生成 mipmapsdata = stbi_load(FileSystem::getPath("resources/textures/awesomeface.png").c_str(), &width, &height, &nrChannels, 0);if (data){// awesomeface.png 有透明通道,因此使用 GL_RGBAglTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);glGenerateMipmap(GL_TEXTURE_2D);}else{std::cout << "Failed to load texture" << std::endl;}stbi_image_free(data);// 告诉 OpenGL 每个采样器属于哪个纹理单元(只需要做一次)ourShader.use();ourShader.setInt("texture1", 0);ourShader.setInt("texture2", 1);// 渲染循环while (!glfwWindowShouldClose(window)){// 处理输入processInput(window);// 渲染指令glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);// 绑定纹理glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, texture1);glActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, texture2);// 激活着色器ourShader.use();// 创建变换矩阵glm::mat4 model         = glm::mat4(1.0f); // 确保矩阵初始化为单位矩阵glm::mat4 view          = glm::mat4(1.0f);glm::mat4 projection    = glm::mat4(1.0f);model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));view  = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);// 获取矩阵 uniform 位置unsigned int modelLoc = glGetUniformLocation(ourShader.ID, "model");unsigned int viewLoc  = glGetUniformLocation(ourShader.ID, "view");// 传递它们到着色器glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));glUniformMatrix4fv(viewLoc, 1, GL_FALSE, &view[0][0]);// 注意:目前我们每帧都设置投影矩阵,但是由于投影矩阵很少变化,通常最好只设置一次。ourShader.setMat4("projection", projection);// 渲染容器glBindVertexArray(VAO);glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);// 交换缓冲区并查询 IO 事件(如按键、鼠标移动等)glfwSwapBuffers(window);glfwPollEvents();}// 释放所有资源glDeleteVertexArrays(1, &VAO);glDeleteBuffers(1, &VBO);glDeleteBuffers(1, &EBO);// 终止 GLFW,释放所有分配的资源glfwTerminate();return 0;
}// 处理所有输入:查询 GLFW 是否在这一帧中按下/释放了相关按键,并做出相应处理
void processInput(GLFWwindow *window)
{if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)glfwSetWindowShouldClose(window, true);
}// GLFW 回调函数:窗口大小改变时执行
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{// 确保视口与新的窗口尺寸匹配;注意 width 和 height 会显著大于在视网膜显示器上的指定值。glViewport(0, 0, width, height);
}

6.1.coordinate_systems.vs

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;out vec2 TexCoord;uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;void main()
{gl_Position = projection * view * model * vec4(aPos, 1.0);TexCoord = vec2(aTexCoord.x, aTexCoord.y);
}

6.1.coordinate_systems.fs

#version 330 core
out vec4 FragColor;in vec2 TexCoord;// texture samplers
uniform sampler2D texture1;
uniform sampler2D texture2;void main()
{// linearly interpolate between both textures (80% container, 20% awesomeface)FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}

相关文章:

OpenGL 原生库6 坐标系统

概述 为了将坐标从一个坐标系变换到另一个坐标系&#xff0c;我们需要用到几个变换矩阵&#xff0c;最重要的几个分别是模型(Model)、观察(View)、投影(Projection)三个矩阵。我们的顶点坐标起始于局部空间(Local Space)&#xff0c;在这里它称为局部坐标(Local Coordinate)&a…...

LabVIEW提高开发效率技巧----VI服务器和动态调用

VI服务器&#xff08;VI Server&#xff09;和动态调用是LabVIEW中的两个重要功能&#xff0c;可以有效提升程序的灵活性、模块化和可扩展性。通过这两者的结合&#xff0c;开发者可以在运行时动态加载和调用VI&#xff08;虚拟仪器&#xff09;&#xff0c;实现更为复杂的应用…...

求1000以内所有恰好能分解成10组两个素数之和

要求 根据哥德巴赫猜想&#xff0c;任意一个大偶数都可以分解为两个素数之和。但许多偶数分解为两个素数之和并不是唯一的。 请编写函数fun&#xff0c;其功能是:求1000(不包括1000)以内的所有恰好能分解成10组两个素数之和(5109和1095被认为是同一组)的偶并依次存入数组a中并…...

Webpack 和 Vite 的区别

Webpack 是一种模块打包工具&#xff0c;主要功能是将各种资源&#xff08;如 JavaScript、CSS、图片等&#xff09;通过 loader 和 plugin 转换和打包成可以直接在浏览器中运行的代码。其核心思想是以代码分割、按需加载和优化资源来提升性能。 Vite 是一种新型构建工具&…...

C++——初步认识C++和namespace的用法

1.编程语言排行榜 我们通过排行可以看出 C在变成语言中还是占据着重要的地位 2.C在工作领域中的应用 1.PC客户端开发。⼀般是开发Windows上的桌面软件&#xff0c;比如WPS之类的&#xff0c;技术栈的话⼀般是C和 QT&#xff0c;QT 是⼀个跨平台的 C图形用户界面&#xff08;G…...

LeetCode118:杨辉三角

题目链接&#xff1a;118. 杨辉三角 - 力扣&#xff08;LeetCode&#xff09; 代码如下 class Solution {public:vector<vector<int>> generate(int numRows) {vector<vector<int>> dp(numRows);vector<int> temp(numRows);for (int i 0; i &…...

介绍一下大模型或者多模态?

什么是大模型、多模态 大模型多模态 大模型 定义&#xff1a; 大模型&#xff0c;通常指的是在深度学习领域&#xff0c;具有大规模参数和复杂结构的模型。这些模型往往需要大量的计算资源和数据进行训练和推理。大模型因其强大的表示能力和泛化性能&#xff0c;在多个领域展现…...

深度学习之图像数据集增强(Data Augmentation)

文章目录 一、 数据增强概述二、python实现传统数据增强参考文献 一、 数据增强概述 数据增强&#xff08;Data Augmentation&#xff09;是一种技术&#xff0c;通过对现有数据进行各种变换和处理来生成新的训练样本&#xff0c;从而增加数据集的多样性和数量。这些变换可以是…...

小程序与APP的区别

目录 前言1. 开发方式与成本2. 运行环境与获取途径3. 功能复杂度与交互体验4. 更新与维护5. 推广与用户获取6. 占用空间与存储7. 可分享性总结 前言 小程序与APP作为两种不同类型的应用程序&#xff0c;它们在多个方面存在明显的区别。以下是对这些区别的详细阐述&#xff1a;…...

Linux Kernel Makefiles 编译标志详解

在Linux内核开发中&#xff0c;Makefile文件扮演着至关重要的角色&#xff0c;它指导make命令如何编译和链接内核源代码。Makefile中包含了多种编译标志&#xff08;flags&#xff09;&#xff0c;这些标志控制着编译、汇编和链接过程的不同方面。本文将详细介绍几种关键的编译…...

数据可视化pyecharts——数据分析(柱状图、折线图、饼图)

安装 首先确保已经安装了pyecharts库&#xff0c;如果没有&#xff0c;可以通过pip install pyecharts进行安装。 柱状图 从pyecharts.charts导入Bar&#xff0c;从pyecharts导入options。准备数据&#xff08;如类别数据x_data和对应的数值数据y_data&#xff09;。创建Bar对…...

小程序构建npm失败

小程序构建npm失败 项目工程结构说明解决方法引入依赖导致的其他问题 今天在初始化后的小程序中引入TDesign组件库&#xff0c;构建npm时报错。 项目工程结构说明 初始化后的项目中&#xff0c;包含miniprogram文件夹和一些项目配置文件&#xff0c;在project.config.json文件中…...

计算机人工智能前沿进展-大语言模型方向-2024-09-20

计算机人工智能前沿进展-大语言模型方向-2024-09-20 1. Multimodal Fusion with LLMs for Engagement Prediction in Natural Conversation Authors: Cheng Charles Ma, Kevin Hyekang Joo, Alexandria K. Vail, Sunreeta Bhattacharya, Alvaro Fern’andez Garc’ia, Kailan…...

cv环境设置

pytorch TensorFlow。。。 环境布置&#xff0c;库的安装顺序&#xff1a; 确定显卡可用的cuda上下限 (比如3090需要至少11.x以上的cuda参考&#xff1a; 一文理顺&#xff1a;pytorch、cuda版本&#xff0c;从此不再为兼容问题头疼&#xff01; - 哔哩哔哩 (bilibili.com)&am…...

线性代数书中求解线性方程组的三种方法的实例

目录 一、克拉默法则(P45) 二、逆矩阵(P46) 三、高斯-约旦消元法(P65) 一、克拉默法则(P45) 二、逆矩阵(P46) 三、高斯-约旦消元法(P65)...

Linux容器化管理——Docker常见命令总结

创建镜像 docker build -t &#xff08;镜像名&#xff09; . 自动在当前目录下找dockerfile也可换成其他路径 查看本地镜像 docker images 登陆镜像服务器 docker login -u &#xff08;登录名&#xff09; -p &#xff08;登陆密码&#xff09; &#xff08;镜像服务器…...

智慧校园建设解决方案建设系统简介

一、建设背景 1.1 政策背景 1.2 班牌的演变 1.3 建设愿景 二、 智慧班牌简介 三、智慧班牌系统 3.1 系统概述 3.2 软件平台功能交互简介 3.2.1 智慧班牌与管理平台间的功能关联 3.2.2 手机客户端&#xff08;管理员、教师、家长端&#xff09; 3.2.3 手机客户端&#x…...

用Python打造互动式中秋节庆祝小程序

中秋节&#xff0c;这个充满传统韵味的节日&#xff0c;不仅是家人团聚的时刻&#xff0c;也是程序员展示创意的好机会。本文将引导您使用Python创建一个互动式中秋节庆祝小程序&#xff0c;它不仅能够展示节日祝福&#xff0c;还能通过一些简单的特效增加节日气氛。 文章目录 …...

Linux 生成 git ssh 公钥

在Linux系统中生成SSH公钥以用于Git的步骤如下&#xff1a; 打开终端&#xff1a;首先&#xff0c;你需要打开你的Linux系统的终端。 检查SSH密钥&#xff1a;在生成新的SSH密钥之前&#xff0c;你可以检查是否已经存在SSH密钥。在终端中输入以下命令&#xff1a; ls -al ~/.s…...

CertiK因发现Apple Vision Pro眼动追踪技术漏洞,第6次获苹果认可

​2024年9月20日&#xff0c;头部Web3.0安全机构CertiK自豪地宣布&#xff0c;CertiK的工程师因发现Apple Vision Pro MR&#xff08;混合现实&#xff09;头显设备中的关键漏洞而获得Apple公司认可&#xff0c;这已经是Apple公司第六次公开发布对CertiK的致谢&#xff0c;Cert…...

浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)

✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义&#xff08;Task Definition&…...

Ubuntu系统下交叉编译openssl

一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机&#xff1a;Ubuntu 20.04.6 LTSHost&#xff1a;ARM32位交叉编译器&#xff1a;arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

laravel8+vue3.0+element-plus搭建方法

创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)

在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马&#xff08;服务器方面的&#xff09;的原理&#xff0c;连接&#xff0c;以及各种木马及连接工具的分享 文件木马&#xff1a;https://w…...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...

人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式

今天是关于AI如何在教学中增强学生的学习体验&#xff0c;我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育&#xff0c;这并非炒作&#xff0c;而是已经发生的巨大变革。教育机构和教育者不能忽视它&#xff0c;试图简单地禁止学生使…...

Go 并发编程基础:通道(Channel)的使用

在 Go 中&#xff0c;Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式&#xff0c;用于在多个 Goroutine 之间传递数据&#xff0c;从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...

【JVM面试篇】高频八股汇总——类加载和类加载器

目录 1. 讲一下类加载过程&#xff1f; 2. Java创建对象的过程&#xff1f; 3. 对象的生命周期&#xff1f; 4. 类加载器有哪些&#xff1f; 5. 双亲委派模型的作用&#xff08;好处&#xff09;&#xff1f; 6. 讲一下类的加载和双亲委派原则&#xff1f; 7. 双亲委派模…...

LabVIEW双光子成像系统技术

双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制&#xff0c;展现出显著的技术优势&#xff1a; 深层组织穿透能力&#xff1a;适用于活体组织深度成像 高分辨率观测性能&#xff1a;满足微观结构的精细研究需求 低光毒性特点&#xff1a;减少对样本的损伤…...