OpenGL系列(六)变换

    在三角形和纹理贴图示例中,顶点使用的是归一化设备坐标,在该坐标系下,顶点的每个轴的取值为-1到1,超出范围的顶点不可见。

    基于归一化设备坐标的物体的形状随着设备的大小变换而变化,这里产生的第一个问题是,本来要画一个正方形,顶点为(-0.5,0.5)、(0.5,0.5)(0.5,-0.5)(-0.5,-0.5),由于显示设备的宽与高不一致,结果显示出来的是一个长方形。即使针对某个设备定义好了一组顶点,显示出来刚好是正方形,可以在不一样尺寸显示的却是长方形。

    基于归一化坐标系定义物体顶点遇到的另一个问题是,无法让物体动起来,如果确实要动起来,需要为每一帧画面定义新的顶点数据,这个工作量非常大,一般不会这么操作。

     为了更好的绘制出想要的物体,一般在物体本身的坐标系定义顶点数据以确定物体的形状,再经过一系列的变换映射到归一化设备坐标系,在设备上才会显示期望的效果。通过改变变换的参数,顶点不用改动,物体显示效果也不一样,因此通过不断改变变换参数可以让物体动起来。

    变换是从一种坐标系映射到另一种坐标系的过程,接下来介绍变换过程中涉及到的坐标系。

坐标系

  1. Local Space(局部空间),局部空间基于局部坐标系。局部坐标系是指相对于某个物体或者某个参考点而言的坐标系。它是通过将物体的中心或者某个特定点作为原点,以物体自身的方向作为坐标轴来定义的。局部坐标系通常用于描述物体的内部结构或者变换。
  2. World Space(世界空间),世界空间基于世界坐标系。世界坐标系是指全局的坐标系,它是以整个场景或者世界空间作为参考的坐标系。世界坐标系通常是固定的,用于描述场景中各个物体之间的相对位置关系。
  3. View Space(观察空间),观察空间针对摄像机而已,把摄像机比作眼睛,在世界坐标系中不同的位置朝着不同的方向观察到的景象是不一样的,需要使用一个独立的坐标系来表示物体在观察者视图中的位置,这就是观察坐标系。
  4. Clip Space (裁剪空间),   裁剪空间的主要作用是减少处理的计算量,提高图形处理的效率。通过裁剪空间,可以只对感兴趣的图像区域进行处理,而不用对整个图像进行操作。这样可以节省计算资源。裁剪空间使用归一化设备坐标系(Normalized Device Coordinates ),每个轴的范围为-1到1,超出范围的图形不显示。
  5. Screen Space(屏幕空间),屏幕空间主要针对具体的显示设备,解决的是图形中的每个像素在屏幕中的位置。不同设备屏幕的宽、高、原点、及轴方向会有差异,经过渲染后的图形需要映射到屏幕坐标才能正确显示出来。

变换过程

    了解坐标系后,接下来介绍物体在各个坐标之间的变换过程,以三角形为例。

    首先在局部坐标系定义三角形的顶点数据,在局部坐标系中根据期望的形状来定义三角形的顶点数据,数据的取值范围不再局限在-1~1之间。

    模型矩阵

    接下来把三角形从局部坐标系映射到世界坐标系,这一步映射的目的是确定三角形在世界坐标系中的位置、占据的空间及朝向,从局部坐标系映射到世界坐标系通过模型矩阵(Model Matrix)进行变换,模型矩阵可由位移矩阵、缩放矩阵、旋转矩阵进行结合:比如只是简单把三角形移动到世界坐标的(1,0,0),模块矩阵使用位移矩阵即可;如何三角形移动到(1,0,0)后再缩小一半,模型矩阵为位移矩阵乘缩放矩阵;如果三角形移动到(1,0,0)后再缩小一半最后再绕z轴旋转90度,模型矩阵为位移矩阵乘缩放矩阵再乘旋转矩阵。

    值得注意的是矩阵乘法是由先后顺序的,顺序不一样,一般情况下结果也不一样。比如先位移再缩放和先缩放再位移结果是不一样的。

    观察矩阵

   三角形的顶点坐标映射到世界坐标系后,观察者在不同的位置超着不同的方向看到的结果是不一样的,因此需要从世界坐标系映射到观察坐标系。这一步映射使用视图矩阵(View Matrix)进行变换。观察矩阵需要通过3个向量才能确定:观察者的位置,观察中心点,穹顶向量,其中穹顶向量的作用是观察者头部的朝向。

    投影矩阵

    有了观察矩阵,观察者的位置、观察的中心点及朝向都已确定,此时观察者可以看到正前方的物体,但是这里还有视野的问题,观察者的眼睛睁大和眯成一条缝,看到的结果也是不一样的,睁大了可以看到整个三角形,眯成一条缝可能只看到三角形的一部分。视野在这里使用投影矩阵表示。

    投影分为正交投影(orthographic projection)和透视投影(perspective projection)。

    正交投影:在该投影方式下,物体的大小与距离无关。

    透视投影:在该投影方式下,物体靠观察者越近,大小越大。

    

    经过投影矩阵的映射后,得到的是NDC坐标,此时可以开始绘制了。接下来通过示例来加深对变换过程的理解。

示例

    在本示例中绘制立方体,首先定义立方体的顶点数据


struct Vertex {glm::vec3 Position;glm::vec3 Color;
};struct Transform {glm::vec3 Pose;glm::vec3 Scale;
};constexpr glm::vec3 Red{1, 0, 0};
constexpr glm::vec3 DarkRed{0.25f, 0, 0};
constexpr glm::vec3 Green{0, 1, 0};
constexpr glm::vec3 DarkGreen{0, 0.25f, 0};
constexpr glm::vec3 Blue{0, 0, 1};
constexpr glm::vec3 DarkBlue{0, 0, 0.25f};// Vertices for a 1x1x1 meter cube. (Left/Right, Top/Bottom, Front/Back)
constexpr glm::vec3 LBB{-0.5f, -0.5f, -0.5f};
constexpr glm::vec3 LBF{-0.5f, -0.5f, 0.5f};
constexpr glm::vec3 LTB{-0.5f, 0.5f, -0.5f};
constexpr glm::vec3 LTF{-0.5f, 0.5f, 0.5f};
constexpr glm::vec3 RBB{0.5f, -0.5f, -0.5f};
constexpr glm::vec3 RBF{0.5f, -0.5f, 0.5f};
constexpr glm::vec3 RTB{0.5f, 0.5f, -0.5f};
constexpr glm::vec3 RTF{0.5f, 0.5f, 0.5f};#define CUBE_SIDE(V1, V2, V3, V4, V5, V6, COLOR) {V1, COLOR}, {V2, COLOR}, {V3, COLOR}, {V4, COLOR}, {V5, COLOR}, {V6, COLOR},constexpr Vertex c_cubeVertices[] = {CUBE_SIDE(LTB, LBF, LBB, LTB, LTF, LBF, DarkRed)    // -XCUBE_SIDE(RTB, RBB, RBF, RTB, RBF, RTF, Red)        // +XCUBE_SIDE(LBB, LBF, RBF, LBB, RBF, RBB, DarkGreen)  // -YCUBE_SIDE(LTB, RTB, RTF, LTB, RTF, LTF, Green)      // +YCUBE_SIDE(LBB, RBB, RTB, LBB, RTB, LTB, DarkBlue)   // -ZCUBE_SIDE(LBF, LTF, RTF, LBF, RTF, RBF, Blue)       // +Z
};

    c_cubeVertices表示立法体的顶点数据,由6个面组成。

    CUBE_SIZE表示立法体的一个面,由3个2角形构成,需要6个顶点,外加该面的颜色

    接下来定义绘制立法体的顶点索引,如下所示。

constexpr unsigned short c_cubeIndices[] = {0,  1,  2,  3,  4,  5,   // -X6,  7,  8,  9,  10, 11,  // +X12, 13, 14, 15, 16, 17,  // -Y18, 19, 20, 21, 22, 23,  // +Y24, 25, 26, 27, 28, 29,  // -Z30, 31, 32, 33, 34, 35,  // +Z
};

   

    在这里所有的变换由着色器进行处理,着色器的定义如下。

    static const char* strVs = R"_(#version 300 esin vec3 VertexPos;in vec3 VertexColor;out vec3 PSVertexColor;uniform mat4 model;uniform mat4 view;uniform mat4 projection;void main() {gl_Position = projection * view * model * vec4(VertexPos, 1.0);PSVertexColor = VertexColor;})_";static const char* strFs = R"_(#version 300 esin lowp vec3 PSVertexColor;out lowp vec4 FragColor;void main() {FragColor = vec4(PSVertexColor, 1);})_";

    由于变换只针对顶点,因此 只需在顶点着色器中对进行做变换,在着色器有3个变换矩阵。

  1. model为模型矩阵。
  2. view为视图矩阵。
  3. projection为投影矩阵。

    在顶点着色器中,模型矩阵先与顶点向量先乘,得到新的向量再与视图矩阵相乘,最后于投影矩阵相乘,因此顶点变换的另一种写法如下。

    gl_Position = projection * ( view * (model * vec4(VertexPos, 1.0)));

    这个写法更容易体现model、view、projection与向量相乘的顺序。

    接下来把顶点数据绑定到VBO、VAO和EBO中,

    auto coordsLocation = mShader->getAttribLocation("VertexPos");auto colorLocation = mShader->getAttribLocation("VertexColor");//init VBO VAO EBOglGenBuffers(1, &VBO);glBindBuffer(GL_ARRAY_BUFFER, VBO);glBufferData(GL_ARRAY_BUFFER, sizeof(c_cubeVertices), c_cubeVertices, GL_STATIC_DRAW);glGenBuffers(1,&EBO);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(c_cubeIndices), c_cubeIndices, GL_STATIC_DRAW);glGenVertexArrays(1, &VAO);glBindVertexArray(VAO);LOGI("TransformSample::init coordsLocation %d colorLocation %d",coordsLocation,colorLocation);glEnableVertexAttribArray(coordsLocation);glEnableVertexAttribArray(colorLocation);glVertexAttribPointer(coordsLocation, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)0);glVertexAttribPointer(colorLocation, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex),reinterpret_cast<const void*>(sizeof(glm::vec3)));

    VBO、VAO已经对这部分内容做过介绍,不一样的地方是顶点属性的序号并没有在着色器通过location指定,这里通过getAttribLocation获取属性的序号。

    顶点数据和着色器准备好以后,在绘制时确定变换矩阵,接下来确定要绘制的立方体,如下所示。

    cubes.push_back(Transform{{-1.5,-0.5,0}, {0.5f, 0.5f, 0.5f}});//25cmcubes.push_back(Transform{{0,-0.3,0}, {0.5f, 0.5f, 0.5f}});cubes.push_back(Transform{{1.5,0.5,0}, {0.55f, 0.55f, 0.55f}});

    这里要绘制3个立法体:第1个平移到位置(-1.5, -0.5, 0),各个轴大小都缩小到原来的0.5倍;第2个平移到(0,-0.3,0),各个轴大小同样缩小为原来的0.5倍;第3个平移到(1.5, 0.5, 0),大小缩小为原来的0.55倍。

    glm::mat4 view = glm::lookAt(glm::vec3(0,0,3),glm::vec3(0,0,0),glm::vec3(0,1,0));glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)mWidth / (float)mHeight, 0.1f, 1000.0f);mShader->setMatrix4f("view", view);mShader->setMatrix4f("projection", projection);static float angle = 0;int i = 0;for(auto cube: cubes){//pass  identity matrixglm::mat4 model(1.0f);model = glm::translate(model,cube.Pose);model = glm::scale(model, cube.Scale);if(i%3==0){model = glm::rotate(model,glm::radians(angle),glm::vec3(1,0,0));}else if(i%3 == 1){model = glm::rotate(model,glm::radians(angle),glm::vec3(0,1,0));}else{model = glm::rotate(model,glm::radians(angle),glm::vec3(0,0,1));}i++;mShader->setMatrix4f("model", model);glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_SHORT, nullptr);}

  通过lookAt可以获得视图矩阵。

glm::mat4 view = glm::lookAt(glm::vec3(0,0,3), glm::vec3(0,0,0), glm::vec3(0,1,0));

第1个参数表示观察者位置,第2个参数表示观察中心点,第3个参数表示穹顶向量。

    通过perspective可获得透视投影矩阵。

 glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)mWidth / (float)mHeight, 0.1f, 1000.0f);

    第1个参数表示视野角度,第2个参数表示宽高比例,第3个参数表示最近距离,第4个参数表示最远距离。
 

    对于模型矩阵,首先定义一个初始的单位矩阵,glm::mat4 model(1.0f),先进行平移变换 model = glm::translate(model,cube.Pose); 接着进行缩放变换model = glm::scale(model, cube.Scale); 最后通过glm::rotate进行旋转变换,这里分3种情况,第1个绕x轴旋转,第2个绕y轴旋转,第3个绕z轴旋转。

    这些模型矩阵通过Shader类的setMatrix4f传递到着色器。着色器拿到这些矩阵后,执行变换后可得到期望的效果,示例运行效果图如下所示。

      从效果图可以看到,显示的是一个立方体,通过不断改变旋转变换矩阵以达到不断旋转的动画效果。  

  

本示例工程代码已上传到github,地址如下。

示例工程代码icon-default.png?t=N7T8https://github.com/leesino/samples/tree/main/OpenGL/Transformations

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/28498.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

三极管的理解

三极管的放大使用 基极集电极之间可理解为电子扩展 电化学效应&#xff1b;产生载流子多少&#xff0c;从而射集间而流动大小 电化学效应&#xff0c;电子漂移现象&#xff0c;基极与集电极的电流的作用在于产生载流子 电流的流动&#xff0c;需要载流子&#xff0c;从而基极…

Linux初识地址空间

前言 上一期我们对进程优先级、命令行参数以及环境和变量做了介绍&#xff01;以前我们就提到过一个问题有了运行队列为什么还要有优先级&#xff1f;本期将带你揭晓&#xff01; 本期内容介绍 虚拟地址空间的引入 虚拟地址空间的介绍 如何理解地址空间 为什么要有地址空间 如…

Elasticsearch:智能 RAG,获取周围分块(一)

作者&#xff1a;来自 Elastic Sunile Manjee 在检索增强生成 (RAG) 领域&#xff0c;一个持续存在的挑战是找到输入大型语言模型 (LLM) 的最佳数据量。数据太少会导致响应不足或不准确&#xff0c;而数据太多会导致答案模糊。这种微妙的平衡启发我开发了一个专注于智能分块和利…

花钱就能过?PMP到底有没有用

在项目管理领域&#xff0c;PMP&#xff08;Project Management Professional&#xff09;认证常被看作是专业能力的金牌标准。 然而&#xff0c;伴随着这一认证的普及&#xff0c;也出现了一些质疑声&#xff0c;比如“PMP认证是否只是金钱和时间的投入就能获得的证书&#xf…

Mybatis工作流程和插件开发

在了解插件开发之前&#xff0c;我们先总体的来梳理一下Mybatis的大致执行流程&#xff1a; 1.new SqlSessionFactoryBuilder().build(inputStream):先根据配置文件&#xff08;包含了全局配置文件和映射配置文件&#xff09;初始化一个对象Configuration&#xff08;这里对象里…

Java | Leetcode Java题解之第155题最小栈

题目&#xff1a; 题解&#xff1a; class MinStack {Deque<Integer> xStack;Deque<Integer> minStack;public MinStack() {xStack new LinkedList<Integer>();minStack new LinkedList<Integer>();minStack.push(Integer.MAX_VALUE);}public void …

哈喽GPT-4o——对GPT-4o 文本创作的思考与看法

目录 用法1&#xff1a;创作小说用法2&#xff1a;创作散文用法3&#xff1a;创作诗歌1、古诗2、现代诗 用法4&#xff1a;创作儿童故事用法5&#xff1a;创作剧本 大家好&#xff0c;我是哪吒。 都说ChatGPT4o是目前文本创作的最强大模型&#xff0c;它都可以用于哪些方面的文…

虚拟化 之四 详解 jailhouse 使能和创建 Cell 的工作流程

完整的 Jailhouse 组件主要由内核模块(jailhouse.ko)、虚拟机管理程序固件(jailhouse*.bin)、管理工具(jailhouse 命令行程序及一些 Python 脚本)以及配置文件(.cell)这四部分组成。用户使用它们来启用虚拟机管理程序、创建 Cell、加载 inmate 二进制文件以及运行和停止…

CCPD数据集

官网&#xff1a;https://github.com/detectRecog/CCPD 其它介绍&#xff1a;https://blog.csdn.net/qianbin3200896/article/details/103009221 CCPD (Chinese City Parking Dataset, ECCV) provinces ["皖", "沪", "津", "渝", &…

跟着刘二大人学pytorch(第---13---节课之RNN高级篇)

文章目录 0 前言0.1 课程视频链接&#xff1a;0.2 课件下载地址&#xff1a; 1 本节课任务描述模型的处理过程训练循环初始化分类器是否使用GPU构造损失函数和优化器每个epoch所要花费的时间遍历每个epoch时进行训练和测试记录每次测试的准确率加入到列表中 具体实现&#xff0…

0基础可以直接学python自动化测试吗?

可以直接学习Python自动化测试&#xff0c;即使没有编程基础。Python是一种易于学习和上手的编程语言&#xff0c;也是自动化测试领域中最受欢迎的语言之一。下面是一篇超详细和规范的文章&#xff0c;将从零开始介绍Python自动化测试的基础知识。 1. 安装Python和开发环境 首…

项目进度和成本管理

1.如何正确标识出软件项目活动&#xff1f; 正确标识软件项目活动是项目管理中的一个重要步骤&#xff0c;它有助于确保项目的顺利进行和成功完成。以下是一些标识软件项目活动的步骤和建议&#xff1a; 项目范围定义&#xff1a;首先明确项目的目标和范围&#xff0c;这将帮助…

初识前端开发

目的 &#xff1a;开发一个网站 -前端开发&#xff1a;html 、css、javaScript -web框架&#xff1a;接收请求处理 -mysql数据库&#xff1a;存储数据的地方 快速上手&#xff1a; 基于Flask web框架让你快速搭建一个网站出来 学习&#xff1a; 基于Django框架 初识HTML …

刷代码随想录有感(106):动态规划——分割等和子集(01背包问题)

题干&#xff1a; 代码&#xff1a; class Solution { public:bool canPartition(vector<int>& nums) {int sum 0;for(int i : nums){sum i;}if(sum % 2 ! 0)return false;int target sum / 2;vector<int>dp(10001, 0);for(int i 0; i < nums.size(); …

Windows11和Ubuntu22双系统安装指南

一、需求描述 台式机电脑&#xff0c;已有Windows11操作系统&#xff0c;想要安装Ubuntu22系统&#xff08;版本任意&#xff09;。其中Windows安装在Nvme固态上&#xff0c;Ubuntu安装在Sata固态上&#xff0c;双盘双系统。开机时使用Grub控制进入哪个系统&#xff0c;效果图…

Python进阶:从函数到文件的编程艺术!!!

第二章&#xff1a;Python进阶 模块概述 函数是一段可重复使用的代码块&#xff0c;它接受输入参数并返回一个结果。函数可以用于执行特定的任务、计算结果、修改数据等&#xff0c;使得代码更具模块化和可重用性。 模块是一组相关函数、类和变量的集合&#xff0c;它们被封…

第 2 章:Spring Framework 中的 IoC 容器

控制反转&#xff08;Inversion of Control&#xff0c;IoC&#xff09;与 面向切面编程&#xff08;Aspect Oriented Programming&#xff0c;AOP&#xff09;是 Spring Framework 中最重要的两个概念&#xff0c;本章会着重介绍前者&#xff0c;内容包括 IoC 容器以及容器中 …

Yum安装LAMP

查看当前80端口是否被占用 ss -tulanp | grep 80查询httpd是否在yum源中 yum info httpd安装httpd yum -y install httpd启动httpd服务&#xff0c;设置开机自启 systemctl enable httpd --now systemctl start httpd查看当前进程 ps aux | grep httpd查看当前IP&#xff…

自动化技术如何影响企业数据分析的发展

当今时代&#xff0c;企业普遍面临着转型的压力&#xff0c;这些挑战主要源于在科技和市场的双重压力下如何实现增长。当前&#xff0c;企业发展的趋势是紧追自动化的浪潮&#xff0c;并通过优化预算管理流程&#xff0c;推进系统和数据分析的现代化。在这一过程中&#xff0c;…

LoRA用于高效微调的基本原理

Using LoRA for efficient fine-tuning: Fundamental principles — ROCm Blogs (amd.com) 大型语言模型的低秩适配&#xff08;LoRA&#xff09;用于解决微调大型语言模型&#xff08;LLMs&#xff09;的挑战。GPT和Llama等拥有数十亿参数的模型&#xff0c;特定任务或领域的微…