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

智能推荐

FX3/CX3 JLINK 调试_ezusbsuite_qsg.pdf-程序员宅基地

文章浏览阅读2.1k次。FX3 JLINK调试是一个有些麻烦的事情,经常有些莫名其妙的问题。 设置参见 c:\Program Files (x86)\Cypress\EZ-USB FX3 SDK\1.3\doc\firmware 下的 EzUsbSuite_UG.pdf 文档。 常见问题: 1.装了多个版本的jlink,使用了未注册或不适当的版本 选择一个正确的版本。JLinkARM_V408l,JLinkA_ezusbsuite_qsg.pdf

用openGL+QT简单实现二进制stl文件读取显示并通过鼠标旋转缩放_qopengl如何鼠标控制旋转-程序员宅基地

文章浏览阅读2.6k次。** 本文仅通过用openGL+QT简单实现二进制stl文件读取显示并通过鼠标旋转缩放, 是比较入门的级别,由于个人能力有限,新手级别,所以未能施加光影灯光等操作, 未能让显示的stl文件更加真实。****效果图:**1. main.cpp```cpp#include "widget.h"#include <QApplication>int main(int argc, char *argv[]){ QApplication a(argc, argv); _qopengl如何鼠标控制旋转

刘焕勇&王昊奋|ChatGPT对知识图谱的影响讨论实录-程序员宅基地

文章浏览阅读943次,点赞22次,收藏19次。以大规模预训练语言模型为基础的chatgpt成功出圈,在近几日已经给人工智能板块带来了多次涨停,这足够说明这一风口的到来。而作为曾经的风口“知识图谱”而言,如何找到其与chatgpt之间的区别,找好自身的定位显得尤为重要。形式化知识和参数化知识在表现形式上一直都是大家考虑的问题,两种技术都应该有自己的定位与价值所在。知识图谱构建往往是抽取式的,而且往往包含一系列知识冲突检测、消解过程,整个过程都能溯源。以这样的知识作为输入,能在相当程度上解决当前ChatGPT的事实谬误问题,并具有可解释性。

如何实现tomcat的热部署_tomcat热部署-程序员宅基地

文章浏览阅读1.3k次。最重要的一点,一定是degbug的方式启动,不然热部署不会生效,注意,注意!_tomcat热部署

用HTML5做一个个人网站,此文仅展示个人主页界面。内附源代码下载地址_个人主页源码-程序员宅基地

文章浏览阅读10w+次,点赞56次,收藏482次。html5 ,用css去修饰自己的个人主页代码如下:&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;&lt;html xmlns="http://www.w3.org/1999/xh..._个人主页源码

程序员公开上班摸鱼神器!有了它,老板都不好意思打扰你!-程序员宅基地

文章浏览阅读201次。开发者(KaiFaX)面向全栈工程师的开发者专注于前端、Java/Python/Go/PHP的技术社区来源:开源最前线链接:https://github.com/svenstaro/gen..._程序员怎么上班摸鱼

随便推点

UG\NX二次开发 改变Block UI界面的尺寸_ug二次开发 调整 对话框大小-程序员宅基地

文章浏览阅读1.3k次。改变Block UI界面的尺寸_ug二次开发 调整 对话框大小

基于深度学习的股票预测(完整版,有代码)_基于深度学习的股票操纵识别研究python代码-程序员宅基地

文章浏览阅读1.3w次,点赞18次,收藏291次。基于深度学习的股票预测数据获取数据转换LSTM模型搭建训练模型预测结果数据获取采用tushare的数据接口(不知道tushare的筒子们自行百度一下,简而言之其免费提供各类金融数据 , 助力智能投资与创新型投资。)python可以直接使用pip安装tushare!pip install tushareCollecting tushare Downloading https://files.pythonhosted.org/packages/17/76/dc6784a1c07ec040e74_基于深度学习的股票操纵识别研究python代码

中科网威工业级防火墙通过电力行业测评_电力行业防火墙有哪些-程序员宅基地

文章浏览阅读2k次。【IT168 厂商动态】 近日,北京中科网威(NETPOWER)工业级防火墙通过了中国电力工业电力设备及仪表质量检验测试中心(厂站自动化及远动)测试,并成为中国首家通过电力协议访问控制专业测评的工业级防火墙生产厂商。   北京中科网威(NETPOWER)工业级防火墙专为工业及恶劣环境下的网络安全需求而设计,它采用了非X86的高可靠嵌入式处理器并采用无风扇设计,整机功耗不到22W,具备极_电力行业防火墙有哪些

第十三周 ——项目二 “二叉树排序树中查找的路径”-程序员宅基地

文章浏览阅读206次。/*烟台大学计算机学院 作者:董玉祥 完成日期: 2017 12 3 问题描述:二叉树排序树中查找的路径 */#include #include #define MaxSize 100typedef int KeyType; //定义关键字类型typedef char InfoType;typedef struct node

C语言基础 -- scanf函数的返回值及其应用_c语言ignoring return value-程序员宅基地

文章浏览阅读775次。当时老师一定会告诉你,这个一个"warning"的报警,可以不用管它,也确实如此。不过,这条报警信息我们至少可以知道一点,就是scanf函数调用完之后是有一个返回值的,下面我们就要对scanf返回值进行详细的讨论。并给出在编程时利用scanf的返回值可以实现的一些功能。_c语言ignoring return value

数字医疗时代的数据安全如何保障?_数字医疗服务保障方案-程序员宅基地

文章浏览阅读9.6k次。十四五规划下,数据安全成为国家、社会发展面临的重要议题,《数据安全法》《个人信息保护法》《关键信息基础设施安全保护条例》已陆续施行。如何做好“数据安全建设”是数字时代的必答题。_数字医疗服务保障方案

推荐文章

热门文章

相关标签