OPenGL 基本知识(根据自己理解整理)-程序员宅基地

技术标签: 管线  qt  OpenGL  着色器  基本概念  openGL  

1.坐标系

计算机利用OpenGL可以把三维世界中的三维物体,在二维屏幕上显示出来。如下图(来源于网络):
OpenGL图形渲染管线(Pipeline)学习
在这里插入图片描述
一部摄像机放在视椎体的顶部,也就是视椎体四条线交汇的部分。只有视椎体内部的三维物体才会经过一系列的坐标转换被输出到计算机屏幕上。

视椎体是一个矩形底座和顶座被截去顶部的立锥体。视椎体外的红色圆圈和蓝色的部分区域没有显示出来。

因为要把三维的物体映射到二维屏幕上,所以需要坐标转换。

世界坐标:三维物体在现实空间的位置,以XYZ来表示,坐标原点可以自定义;在三维世界的模型坐标基于同一个坐标原点,可以通过平移、旋转、缩放调整三维物体的位置、方位、大小。

模型坐标:是三维模型自己的坐标系,坐标原点一般位于模型的中心位置。每个模型都有一个属于自己的坐标系统,不同的模型之间要想在坐标上发生关系,需要所有的模型统一到世界坐标系下。模型坐标系在处理模型自身的图元之间的关系非常方便。模型同样也可以在自己的坐标系下平移、旋转和缩放。

观察坐标:也可以称为视点坐标、摄像机等,观察坐标主要是把世界坐标经过一系列的平移、旋转换成摄像机的正前方。

坐标计算的过程如下图所示(图片来源于网络)OpenGL渲染管线解析

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

在这里插入图片描述

局部坐标是对象相对于局部原点的坐标;也是对象开始的坐标。
将局部坐标转换为世界坐标,世界坐标是作为一个更大空间范围的坐标系统。这些坐标是相对于世界的原点的。
接下来我们将世界坐标转换为观察坐标,观察坐标是指以摄像机或观察者的角度观察的坐标。
在将坐标处理到观察空间之后,我们需要将其投影到裁剪坐标。裁剪坐标是处理-1.0到1.0范围内并判断哪些顶点将会出现在屏幕上。
最后,我们需要将裁剪坐标转换为屏幕坐标,我们将这一过程成为视口变换(Viewport Transform)。视口变换将位于-1.0到1.0范围的坐标转换到由glViewport函数所定义的坐标范围内。最后转换的坐标将会送到光栅器,由光栅器将其转化为片段。
你可能了解了每个单独的坐标空间的作用。我们之所以将顶点转换到各个不同的空间的原因是有些操作在特定的坐标系统中才有意义且更方便。例如,当修改对象时,如果在局部空间中则是有意义的;当对对象做相对于其它对象的位置的操作时,在世界坐标系中则是有意义的;等等这些。如果我们愿意,本可以定义一个直接从局部空间到裁剪空间的转换矩阵,但那样会失去灵活性。接下来我们将要更仔细地讨论各个坐标系。

2. OpenGL 管线

简单来说管线就是一系列过程,三维模型转换成二维图形输出屏幕的过程。这个过程分为多个步骤,每个步骤的输出就是下一个步骤的输入。这些步骤有些实在CPU中运行,有些实在GPU中运行。具体如下:
在这里插入图片描述
其中着色器是运行在GPU的小程序,大家不要被它的名字所迷惑,它并不是单单用给像素点上颜色的,它还计算像素点的位置、纹理等信息。

因为GPU相对CPU来说,有更多的计算单元(成百上千计算核心)。CPU最多有两位数的计算单元。所以GPU可以同时使用大量的计算核心利用着色器程序计算每个像素点的位置、颜色、透明度等。因为每个像素单元的显示是独立的,所以每个GPU计算单元互不干扰。

例如:一张1080*900的图片,如果使用CPU计算的话,可能需要计算90多万次,但是使用GPU并行计算的话,一次性的就可以计算出来。所以效率就显而易见区分出来了。

顶点着色器和图元着色器在程序中可以进行编程的,把编程好的着色器放到GPU中运行。

顶点着色器:是GPU渲染管线的第一步,它的数据来源于CPU。CPU把定点坐标、颜色、纹理等数据送入GPU。GPU会使用定点着色器把每个顶点都运行一次。计算每个顶点的坐标、光照、颜色等。顶点着色器输入是坐标顶点输出是经过变换后的顶点。

图元装配:将顶点着色器输出的图元,装配成指定的图元(点、线、三角形),这些图元是构成模型的基本要素。

几何着色器:几何着色器一个可编程的可选阶段。几何着色器的输入是完整的图元,输出是一个或者多个其他的图元或者不输出任何图形。也就是将输入的点或者线扩展成多边形。能够将(这一组)顶点变换为完全不同的图元,并且还能生成比原来更多的顶点。

光栅化:是把几何图元转换成像素的过程,通俗来讲就是把一个三维图形拍平后的效果,然后在屏幕上显示。确定图元所围成的像素的位置以及图元边界像素的位置。

片段着色器:计算图元中每个像素点的颜色、光照、阴影等(主要是和颜色相关)。每个像素点有4个元素组成(RGBA:红、绿、兰、透明度),每个分量取值范围都是0.0-1.0。片段着色器单独处理每一个片段,并不会影响到周围片元的计算,每个片元的计算都是独立的。正是因为独立性才保证了GPU可以高并发的工作。

3.编程测试

顶点缓存对象:(Vertex Buffer Object)VBO,把内存数据转移到显卡缓存。
顶点数组对象:(Vertex Array Object)VAO。(一个记忆机,记录了绘制一个物体所需要的状态,本身并不存储数据)
为VBO属性配置,记录和哪个VBO绑定的数据;
记忆绑定VBOs,怎么绑定的;
记忆绘制一些顺序的EBO;
一个VAO可以对应多个VBO.一个VBO也可以对应多个VAO.
索引缓存对象:(Element Buffer Object)EBO或(Index Buffer Object)IBO

#pragma once

#include <QOpenGLWindow>
#include <QOpenGLShader>
#include <QOpenGLShaderProgram>
#include<QOpenGLFunctions_3_3_Core>

class QOpenGLFunctions_3_3_Core;

class MyOpenGLWnd : public QOpenGLWindow {
    
	Q_OBJECT

public:
	MyOpenGLWnd();
	~MyOpenGLWnd();

private:
	void initializeGL()override;
	void resizeGL(int w, int h)override;
	void paintGL()override;

private:
	QOpenGLFunctions_3_3_Core* core;

	GLuint VAO;
	GLuint VBO;

	QOpenGLShaderProgram shaderProgram;//着色器程序,所里系统所有的着色器
}; 

#include "MyOpenGLWnd.h"
#include <qgl.h>
MyOpenGLWnd::MyOpenGLWnd() {
    
}

MyOpenGLWnd::~MyOpenGLWnd() {
    
}

void MyOpenGLWnd::initializeGL() {
    

	//初始化OpenGL的包装类,然后才可以使用OpenGL里面的函数
	core = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_3_Core>();

	//请主要数值必须在-1 ~ 1之间,如果不在这个范围内,就不会在视口中出现。

	GLfloat ver[] = {
    
		-0.5f, -0.5f, 0.0f,
		 0.5f, -0.5f, 0.0f,
		 0.0f,  0.5f, 0.0f,
	};

	//GLuint VAO;//在3.3版本及以后使用
	/*
	** 所有和GL_ARRAY_BUFFER有关系的操作都会被记录下来
	** 第一个参数需要创建的缓存数量
	** 第二个参数用于存储单一ID或者多个ID
	*/
	core->glGenVertexArrays(1, &VAO);

	/*
	** 把VAO绑定到openGL上
	*/
	core->glBindVertexArray(VAO);


	/* 1.创建一个VBO,告诉程序数据要送到显卡哪里(显卡缓存地址)
	
	** 第一个参数:缓存对象的数量;
	** 第二个参数:用来保存显卡中显存对象的地址(数组名称),在显卡中名称就代表了地址
	** 注:第二个参数不能是指针,如果是指针的话指的是内存中的地址。
	** 变量VBO就代表了显卡缓存中的地址

	*/
	//GLuint VBO;

	core->glGenBuffers(1, &VBO);

	/*
	** 2.把地址根数据绑定

	** 第一个参数表明如果以后送的数据也是这个宏GL_ARRAY_BUFFER类型,就表明数据和VBO绑定在一起的。
	** 确切的说值指明要绑定数据的数据类型
	*/

	core->glBindBuffer(GL_ARRAY_BUFFER, VBO);//绑定缓存对象

	/*
	** 3.把数据送进显卡的缓存
	** 第一个参数和glBindBuffer的第一个参数相同,说明就是把数据缓存到GPU的VBO的。
	** 第四个参数指定了我们希望显卡如何管理规定的数据,他有三种形式:
	** GL_STATIC_DRAW: 数据不会或者不会被改变
	** GL_DYNAMIC_DRAW:数据会被改变很多
	** GL_STREAM_DRAW: 每次绘制时都会改变
	** 三角形的位置数据不会被改变,每次渲染的时候都保持原样,所以它的类型最好是:GL_STATIC_DRAW
	** 如果一个缓存中数据会被频繁改变,那么就是用类型GL_DYNAMIC_DRAW或者GL_STREAM_DRAW,
	** 这样显卡就会把数据放在高速写入的部分。
	*/
	//把当前某种类型的缓冲数据从内存传输到GPU的缓存区,GPU缓存地址就是VBO所代表的地址
	core->glBufferData(GL_ARRAY_BUFFER, sizeof(ver), ver, GL_STATIC_DRAW);

	/*
	** 通过以上可以看到,OpenGL是一个状态机,openGL先和显卡缓存地址绑定在一起,
	** 然后openGL和数据绑定在一起,最后通过OpenGL把数据放到缓存中。
	** 当数据被存到显卡缓存以后,就可以把ver数据清理掉了,因为显卡中已经有数据了。
	*/

	/*
	** 对VBO进行属性配置
	** 第一个参数:与着色器的location对应,在一个VAO里面数字是不可以重复的
	** 第二个参数:顶点属性的大小,也就是一次性可以读多少个数据 3代表一次读取三个数据
	** 第三个参数:读取的数据类型
	** 第四个参数:数据是否被标准化,如果数据在-1~1之间,没有必要被标准化,如果数据不在-1~1这个区间,在它们之外,你可以使用GL_TRUE,
	**            目的是进行一个强制的标准化,OpenGL认为只要标准化的数据就一定要被画出来。
	** 第五个参数:代表读取数据的最大步长
	** 第六个参数:读取最大步长后再其中读取的起始位置;
	**
	** 第二第五和六两个参数共同起作用
	** GLfloat ver[] = {
	**    -0.5f, -0.5f, 0.0f, 1.0, 1.0, 1.0,
	**     0.5f, -0.5f, 0.0f, 1.0, 1.0, 1.0,
	**     0.0f,  0.5f, 0.0f, 1.0, 1.0, 1.0,
	** };
	** 如果第五个参数是6,第六个参数是3,说明一次性读取留个参数,每次只取后面3个参数
	** glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), (void*)(3*sizeof(GLfloat)));
	*/
	core->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(GLfloat), (void*)0);

	//当把上面的数据传入到显卡的缓存以后,显卡并不知道这些数据是做什么用的,是坐标数据还是颜色数据或者纹理数据?
	

	/*
	** 以顶点属性作为参数,启动顶点属性,让着色器可以访问这块数据
	** 参数:着色器中的location值相对应
	** 相当于设一个权限,确定和哪个着色器想关联
	*/
	core->glEnableVertexAttribArray(0);

	//绑定缓存区,这一步其实也可以不需要
	core->glBindBuffer(GL_ARRAY_BUFFER, 0);
	core->glBindVertexArray(0);  


	/******************************************************/
	/*
	** 着色器
	** 着色器属于动态编译
	*/
	QOpenGLShader vertexShager(QOpenGLShader::Vertex);//顶点着色器
	vertexShager.compileSourceFile("E:/Projects/QtGuiTest/OPenGLApp/shader/triangle.vert");
	QOpenGLShader fragmentShager(QOpenGLShader::Fragment);//片段着色器
	fragmentShager.compileSourceFile("E:/Projects/QtGuiTest/OPenGLApp/shader/triangle.frag");
	shaderProgram.addShader(&vertexShager);
	shaderProgram.addShader(&fragmentShager);

	shaderProgram.link();


}

void MyOpenGLWnd::resizeGL(int w, int h) {
    
	
}

void MyOpenGLWnd::paintGL() {
    
	//设置清除颜色,使用当前颜色,清除背景
	core->glClearColor(0.6f, 0.8f, 0.5f, 1.0f);
	core->glClear(GL_COLOR_BUFFER_BIT);

	//把着色器送入显卡缓存
	shaderProgram.bind();

	core->glBindVertexArray(VAO);//会将它记忆的那些状态,相当于那几个函数执行一遍

	/*
	** 绘制一个三角形,
	** 第二个参数:数组的起始位置,
	** 第三个参数:绘制的点数
	*/
	core->glDrawArrays(GL_TRIANGLES, 0, 3);

	update();
}

main.cpp

#include "OPenGLApp.h"
#include <QtWidgets/QApplication>

#include <QtOpenGL/QtOpenGL>
#include "MyOpenGLWnd.h"

int main(int argc, char *argv[]) {
    
	QApplication a(argc, argv);

	MyOpenGLWnd window;

	window.setTitle(QStringLiteral("这是一个OpenGL窗口"));
	window.resize(800, 800);

	window.show();
	return a.exec();
}

两个着色器:
triangle.vert

#version 330 core
layout (location=0) in vec3 aPos;
void main(){
    
	gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}

triangle.frag

#version 330 core
out vec4 fragColor;

void main(){
    
	fragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}

文件目录结构:
在这里插入图片描述

运行结果:
在这里插入图片描述

aaa

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/wb175208/article/details/125455294

智能推荐

python简易爬虫v1.0-程序员宅基地

文章浏览阅读1.8k次,点赞4次,收藏6次。python简易爬虫v1.0作者:William Ma (the_CoderWM)进阶python的首秀,大部分童鞋肯定是做个简单的爬虫吧,众所周知,爬虫需要各种各样的第三方库,例如scrapy, bs4, requests, urllib3等等。此处,我们先从最简单的爬虫开始。首先,我们需要安装两个第三方库:requests和bs4。在cmd中输入以下代码:pip install requestspip install bs4等安装成功后,就可以进入pycharm来写爬虫了。爬

安装flask后vim出现:error detected while processing /home/zww/.vim/ftplugin/python/pyflakes.vim:line 28_freetorn.vim-程序员宅基地

文章浏览阅读2.6k次。解决方法:解决方法可以去github重新下载一个pyflakes.vim。执行如下命令git clone --recursive git://github.com/kevinw/pyflakes-vim.git然后进入git克降目录,./pyflakes-vim/ftplugin,通过如下命令将python目录下的所有文件复制到~/.vim/ftplugin目录下即可。cp -R ...._freetorn.vim

HIT CSAPP大作业:程序人生—Hello‘s P2P-程序员宅基地

文章浏览阅读210次,点赞7次,收藏3次。本文简述了hello.c源程序的预处理、编译、汇编、链接和运行的主要过程,以及hello程序的进程管理、存储管理与I/O管理,通过hello.c这一程序周期的描述,对程序的编译、加载、运行有了初步的了解。_hit csapp

18个顶级人工智能平台-程序员宅基地

文章浏览阅读1w次,点赞2次,收藏27次。来源:机器人小妹  很多时候企业拥有重复,乏味且困难的工作流程,这些流程往往会减慢生产速度并增加运营成本。为了降低生产成本,企业别无选择,只能自动化某些功能以降低生产成本。  通过数字化..._人工智能平台

electron热加载_electron-reloader-程序员宅基地

文章浏览阅读2.2k次。热加载能够在每次保存修改的代码后自动刷新 electron 应用界面,而不必每次去手动操作重新运行,这极大的提升了开发效率。安装 electron 热加载插件热加载虽然很方便,但是不是每个 electron 项目必须的,所以想要舒服的开发 electron 就只能给 electron 项目单独的安装热加载插件[electron-reloader]:// 在项目的根目录下安装 electron-reloader,国内建议使用 cnpm 代替 npmnpm install electron-relo._electron-reloader

android 11.0 去掉recovery模式UI页面的选项_android recovery 删除 部分菜单-程序员宅基地

文章浏览阅读942次。在11.0 进行定制化开发,会根据需要去掉recovery模式的一些选项 就是在device.cpp去掉一些选项就可以了。_android recovery 删除 部分菜单

随便推点

echart省会流向图(物流运输、地图)_java+echart地图+物流跟踪-程序员宅基地

文章浏览阅读2.2k次,点赞2次,收藏6次。继续上次的echart博客,由于省会流向图是从echart画廊中直接取来的。所以直接上代码<!DOCTYPE html><html><head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /&_java+echart地图+物流跟踪

Ceph源码解析:读写流程_ceph 发送数据到其他副本的源码-程序员宅基地

文章浏览阅读1.4k次。一、OSD模块简介1.1 消息封装:在OSD上发送和接收信息。cluster_messenger -与其它OSDs和monitors沟通client_messenger -与客户端沟通1.2 消息调度:Dispatcher类,主要负责消息分类1.3 工作队列:1.3.1 OpWQ: 处理ops(从客户端)和sub ops(从其他的OSD)。运行在op_tp线程池。1...._ceph 发送数据到其他副本的源码

进程调度(一)——FIFO算法_进程调度fifo算法代码-程序员宅基地

文章浏览阅读7.9k次,点赞3次,收藏22次。一 定义这是最早出现的置换算法。该算法总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面予以淘汰。该算法实现简单,只需把一个进程已调入内存的页面,按先后次序链接成一个队列,并设置一个指针,称为替换指针,使它总是指向最老的页面。但该算法与进程实际运行的规律不相适应,因为在进程中,有些页面经常被访问,比如,含有全局变量、常用函数、例程等的页面,FIFO 算法并不能保证这些页面不被淘汰。这里,我_进程调度fifo算法代码

mysql rownum写法_mysql应用之类似oracle rownum写法-程序员宅基地

文章浏览阅读133次。rownum是oracle才有的写法,rownum在oracle中可以用于取第一条数据,或者批量写数据时限定批量写的数量等mysql取第一条数据写法SELECT * FROM t order by id LIMIT 1;oracle取第一条数据写法SELECT * FROM t where rownum =1 order by id;ok,上面是mysql和oracle取第一条数据的写法对比,不过..._mysql 替换@rownum的写法

eclipse安装教程_ecjelm-程序员宅基地

文章浏览阅读790次,点赞3次,收藏4次。官网下载下载链接:http://www.eclipse.org/downloads/点击Download下载完成后双击运行我选择第2个,看自己需要(我选择企业级应用,如果只是单纯学习java选第一个就行)进入下一步后选择jre和安装路径修改jvm/jre的时候也可以选择本地的(点后面的文件夹进去),但是我们没有11版本的,所以还是用他的吧选择接受安装中安装过程中如果有其他界面弹出就点accept就行..._ecjelm

Linux常用网络命令_ifconfig 删除vlan-程序员宅基地

文章浏览阅读245次。原文链接:https://linux.cn/article-7801-1.htmlifconfigping &lt;IP地址&gt;:发送ICMP echo消息到某个主机traceroute &lt;IP地址&gt;:用于跟踪IP包的路由路由:netstat -r: 打印路由表route add :添加静态路由路径routed:控制动态路由的BSD守护程序。运行RIP路由协议gat..._ifconfig 删除vlan