当前位置: 首页 > 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…...

慢慢欣赏linux 之 last = switch_to(prev, next)分析

last switch_to(prev, next); 为什么需要定义last作为调用switch_to之前的prev的引用 原因如下&#xff1a; struct task_struct * switch_to(struct task_struct *prev,struct task_struct *next) {... ...return cpu_switch_to(prev, next);> .global cpu_switch_tocpu_…...

JAVA反序列化应用 : URLDNS案例

反序列化的基本原理 基础普及 &#xff1a; 对象初始化数据方法 &#xff1a;1、使用构造方法 2、使用封装中的 set,get方法 这边我们就使用 1 注意 我们之后还需要进行 接入 序列化的接口 &#xff1a; 先进行序列化 &#xff1a; 反序列化&#xff1a; 反序列化导致的安…...

SSE (Server-Sent Events) 技术简介

一、SSE 技术概述 Server-Sent Events (SSE) 是一种允许服务器向客户端实时推送数据的 Web 技术&#xff0c;它基于 HTTP 协议实现服务器到客户端的单向通信。 基本特点 ● 单向通信&#xff1a;仅服务器→客户端方向 ● 基于HTTP&#xff1a;使用标准HTTP协议&#xff0c;无需…...

端午编程小游戏--艾草驱邪

刚刚过去的端午&#xff0c;参加了学校的一个活动&#xff0c;用python做了一个小游戏&#xff0c;当然这个小游戏还可以继续改进&#xff0c;可以加个bgm什么的...... 可以小玩一下 import pygame import random import math import sys import timepygame.init() pygame.mi…...

AUTOSAR实战教程--DoIP_02_诊断链路建立流程

第一步&#xff1a;DoIP实体车辆声明/诊断仪车辆识别请求 打开激活线以后&#xff0c;DoIP实体发的三帧车辆声明报文。其中包含了DoIP实体的诊断逻辑地址&#xff08;可以类比DoCAN的物理请求/响应地址&#xff09;&#xff0c;对应车辆的VIN码&#xff08;若已配置&#xff0…...

【明日方舟 × 红黑树】干员调度如何不掉线?算法工程的平衡魔法全揭秘!

【明日方舟 红黑树】干员调度如何不掉线&#xff1f;算法工程的平衡魔法全揭秘&#xff01; 作者&#xff1a;星之辰 标签&#xff1a;#红黑树 #明日方舟 #工程平衡树 #算法科普 #动态数据结构 引子&#xff1a;为什么你的干员调度能实时平衡&#xff0c;从不崩盘&#xff1f;…...

【Matlab】连接SQL Server 全过程

文章目录 一、下载与安装1.1 SQL Server1.2 SSMS1.3 OLE DB 驱动程序 二、数据库配置2.1 SSMS2.2 SQL Server里面设置2.3 设置防火墙2.4 设置ODBC数据源 三、matlab 链接测试 一、下载与安装 微软的&#xff0c;所以直接去微软官方下载即可。 1.1 SQL Server 下载最免费的Ex…...

【HarmonyOS5】UIAbility组件生命周期详解:从创建到销毁的全景解析

⭐本期内容&#xff1a;【HarmonyOS5】UIAbility组件生命周期详解&#xff1a;从创建到销毁的全景解析 &#x1f3c6;系列专栏&#xff1a;鸿蒙HarmonyOS&#xff1a;探索未来智能生态新纪元 文章目录 前言生命周期全景图详细状态解析与最佳实践&#x1f3ac; Create状态&#…...

使用MinIO搭建自己的分布式文件存储

目录 引言&#xff1a; 一.什么是 MinIO &#xff1f; 二.MinIO 的安装与部署&#xff1a; 三.Spring Cloud 集成 MinIO&#xff1a; 1.前提准备&#xff1a; &#xff08;1&#xff09;安装依赖&#xff1a; &#xff08;2&#xff09;配置MinIO连接&#xff1a; &…...

【.net core】天地图坐标转换为高德地图坐标(WGS84 坐标转 GCJ02 坐标)

类文件 public static class WGS84ToGCJ02Helper {// 定义一些常量private const double PI 3.14159265358979324;private const double A 6378245.0;private const double EE 0.00669342162296594323;// 判断坐标是否在中国范围内&#xff08;不在国内则不进行转换&#x…...