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)    // -X
    CUBE_SIDE(RTB, RBB, RBF, RTB, RBF, RTF, Red)        // +X
    CUBE_SIDE(LBB, LBF, RBF, LBB, RBF, RBB, DarkGreen)  // -Y
    CUBE_SIDE(LTB, RTB, RTF, LTB, RTF, LTF, Green)      // +Y
    CUBE_SIDE(LBB, RBB, RTB, LBB, RTB, LTB, DarkBlue)   // -Z
    CUBE_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,   // -X
    6,  7,  8,  9,  10, 11,  // +X
    12, 13, 14, 15, 16, 17,  // -Y
    18, 19, 20, 21, 22, 23,  // +Y
    24, 25, 26, 27, 28, 29,  // -Z
    30, 31, 32, 33, 34, 35,  // +Z
};

   

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

    static const char* strVs = R"_(#version 300 es

    in 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 es
    in 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 EBO
    glGenBuffers(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}});//25cm
    cubes.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 matrix
       glm::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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数学学习与研究杂志社《数学学习与研究》编辑部2024年第6期目录

课改前沿 基于核心素养的高中数学课堂教学研究——以“直线与圆、圆与圆的位置关系”为例 张亚红; 2-4 核心素养视角下初中生数学阅读能力的培养策略探究 贾象虎; 5-7 初中数学大单元教学实践策略探索 耿忠义; 8-10《数学学习与研究》投稿&#xff1a;cn7kantougao…

微信ipad协议8049新版本

首先我们要先了解下ipad协议是什么 &#xff0c;ipad协议又叫微信协议 是基于微信IPad协议的智能控制系统帮助企业快速连接客户&#xff0c;创造营销氛围&#xff0c;实现自动获客、自动传播、自动转化、智能营销等分布式营销服务。 通过API 实现 个性化微信功能 &#xff08;例…

精度丢失引起的支付失败问题

问题描述 在提交订单时候&#xff0c;输入充值金额和优惠码&#xff0c;后台会返回具体的订单信息&#xff0c;如下图&#xff0c;支付金额应该是1 * (1 - 0.09) 0.91&#xff08;这个是理想状态&#xff09;&#xff0c;但是表单显示的是0.90999997&#xff0c; 然后点击确…

React+TS前台项目实战(十二)-- 全局常用组件Toast封装,以及rxjs和useReducer的使用

文章目录 前言Toast组件1. 功能分析2. 代码详细注释&#xff08;1&#xff09;建立一个reducer.ts文件&#xff0c;用于管理状态数据&#xff08;2&#xff09;自定义一个清除定时器的hook&#xff08;3&#xff09;使用rxjs封装全局变量管理hook&#xff08;4&#xff09;在to…

2025计算机毕业设计选题题目推荐-毕设题目汇总大全

选题在于精&#xff0c;以下是推荐的容易答辩的选题&#xff1a; SpringBoot Vue选题: 基于SpringBoot Vue家政服务系统 基于SpringBoot Vue非物质文化遗产数字化传承 基于SpringBoot Vue兽医站管理系统 基于SpringBoot Vue毕业设计选题管理系统 基于SpringBoot Vue灾害应急救援…

使用Ollama+OpenWebUI本地部署阿里通义千问Qwen2 AI大模型

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;AI大模型部署与应用专栏&#xff1a;点击&#xff01; &#x1f916;Ollama部署LLM专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年6月17日22点50分 &#x1f004;️文章质量&#xff…

safari浏览器无法连接到服务器

问题&#xff1a;MacBook pro&#xff0c;网络连接正常&#xff0c;可以使用各种软件上网&#xff0c;唯独safari浏览器打不开网页&#xff0c;报错说Safari无法连接到服务器&#xff1b; 原因&#xff1a;使用了VPN&#xff0c;VPN自动更改了网络设置&#xff0c;导致Safari浏…

数据结构-十大排序算法集合(四万字精讲集合)

前言 1&#xff0c;数据结构排序篇章是一个大的工程&#xff0c;这里是一个总结篇章&#xff0c;配备动图和过程详解&#xff0c;从难到易逐步解析。 2&#xff0c;这里我们详细分析几个具备教学意义和实际使用意义的排序&#xff1a; 冒泡排序&#xff0c;选择排序&#xff0c…

【vue大作业-端午节主题网站】【预览展示视频和详细文档】

vue大作业-端午节主题网站介绍 端午节&#xff0c;又称为龙舟节&#xff0c;是中国的传统节日之一&#xff0c;每年农历五月初五庆祝。这个节日不仅是纪念古代爱国诗人屈原的日子&#xff0c;也是家人团聚、共享美食的时刻。今天&#xff0c;我们非常高兴地分享一个以端午节为…

建筑学跑路:揭秘热门转行新选择!

话说建筑学真的是我见过最关心同行的专业&#xff0c;每个建筑学跑路的帖子下面都有人问&#xff1a;你跑哪里去了&#xff1f; 很多人表示&#xff0c;我也想跑 当然不仅建筑学&#xff0c;园林的、城规的、土木的也会来凑热闹&#xff1a; 很多小伙伴分享了自己的转行经历&a…

用这个神级提示词插件,能让你的AI绘画工具Stable diffusion提示词直接写中文!

大家好&#xff0c;我是设计师阿威 最近&#xff0c;有同学在使用AI绘画工具 Stable Diffusion的时候和我说&#xff1a;老师&#xff0c;我英文不好&#xff0c;能不能直接让我写中文提示词啊&#xff1f;最好可以直接在SD的输入框就能直接写中文&#xff0c;不用切换网页或者…

哪个充电宝牌子好用又实惠?盘点四大平价充电宝分享

在当今快节奏的生活中&#xff0c;充电宝已成为我们日常生活中不可或缺的一部分。然而&#xff0c;面对市场上琳琅满目的充电宝品牌和型号&#xff0c;许多消费者误以为选择容量越大、价格越高的充电宝就是最好的选择。实际上&#xff0c;买充电宝并不是一味追求高容量和高价格…

好用的加密软件谁在用啊?不会吧,还不知道迅软DSE加密软件好用?

说起加密软件想必大家都不陌生&#xff0c;就是用来保护机密数据的&#xff0c;防止泄密行为的出现&#xff0c;那么重点来了&#xff0c;想要完全的把机密信息保护起来&#xff0c;那就用到了加密软件&#xff0c;需要选择一款靠谱、有效果的加密软件才能实现加密&#xff0c;…

2024信息系统、信号处理与通信技术国际会议(ICISPCT2024)

2024信息系统、信号处理与通信技术国际会议&#xff08;ICISPCT2024) 会议简介 2024国际信息系统、信号处理与通信技术大会&#xff08;ICISPCT2024&#xff09;将在青岛隆重开幕。本次会议旨在汇聚全球信息系统、信号处理和通信技术领域的专家学者&#xff0c;共同探索行业…

希尔排序-C语言版本

前言 从希尔开始&#xff0c;排序的速度就开始上升了&#xff0c;这里的排序开始上一个难度了&#xff0c;当然难一点的排序其实也不是很难&#xff0c;当你对于插入排序了解的足够深入的时候&#xff0c;你会发现其实希尔就是插入的异形&#xff0c;但是本质上还是一样的 希尔…

解决linux下载github项目下载不下来,下载失败, 连接失败的问题

第一步&#xff1a;打开/etc/hosts文件 linux vim /etc/hosts 第二步&#xff1a;文件拉到最下面&#xff0c;输入以下内容 linux #GitHub Start 140.82.113.3 github.com 140.82.114.20 gist.github.com 151.101.184.133 assets-cdn.github.com 151.101.184.133 raw.githubus…

在有限的分数有限下如何抉择?是选好专业还是选好学校

随着2024年高考的落幕&#xff0c;无数考生和家长站在了人生的重要十字路口。面对成绩单上的数字&#xff0c;一个难题摆在了面前&#xff1a;在分数限制下我们该如何平衡“心仪的专业”与“知名度更高的学校”之间的选择&#xff1f; 一、专业决定未来职业走向 选择一个好的专…

PostgreSQL源码分析——initdb

数据库初始化 在安装完数据库后&#xff0c;需要进行初始化数据库操作&#xff0c;对应PostgreSQL数据库中就是需要进行initdb后&#xff0c;才能对数据库进行启动。initdb的过程&#xff0c;其实就是创建数据库实例的过程&#xff0c;生成模板数据库和相应的目录、文件信息&a…

vue大作业-端午节主题网站

vue大作业-端午节主题网站介绍 端午节&#xff0c;又称为龙舟节&#xff0c;是中国的传统节日之一&#xff0c;每年农历五月初五庆祝。这个节日不仅是纪念古代爱国诗人屈原的日子&#xff0c;也是家人团聚、共享美食的时刻。今天&#xff0c;我们非常高兴地分享一个以端午节为…

如何完美解决 Oracle Database 19c 安装程序 - 第7步(共8步)卡住,半小时都不动

&#x1f680; 如何完美解决 Oracle Database 19c 安装程序 - 第7步&#xff08;共8步&#xff09;卡住&#xff0c;半小时都不动 摘要 在安装 Oracle Database 19c 时&#xff0c;很多用户会在第7步&#xff08;共8步&#xff09;遇到卡住的问题&#xff0c;尤其是安装程序长…