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

智能推荐

linux devkmem 源码,linux dev/mem dev/kmem实现访问物理/虚拟内存-程序员宅基地

文章浏览阅读451次。dev/mem: 物理内存的全镜像。可以用来访问物理内存。/dev/kmem: kernel看到的虚拟内存的全镜像。可以用来访问kernel的内容。调试嵌入式Linux内核时,可能需要查看某个内核变量的值。/dev/kmem正好提供了访问内核虚拟内存的途径。现在的内核大都默认禁用了/dev/kmem,打开的方法是在 make menuconfig中选中 device drivers --> ..._dev/mem 源码实现

vxe-table 小众但功能齐全的vue表格组件-程序员宅基地

文章浏览阅读7.1k次,点赞2次,收藏19次。vxe-table,一个小众但功能齐全并支持excel操作的vue表格组件_vxe-table

(开发)bable - es6转码-程序员宅基地

文章浏览阅读62次。参考:http://www.ruanyifeng.com/blog/2016/01/babel.htmlBabelBabel是一个广泛使用的转码器,可以将ES6代码转为ES5代码,从而在现有环境执行// 转码前input.map(item => item + 1);// 转码后input.map(function (item) { return item..._让开发环境支持bable

FPGA 视频处理 FIFO 的典型应用_fpga 频分复用 视频-程序员宅基地

文章浏览阅读2.8k次,点赞6次,收藏29次。摘要:FPGA视频处理FIFO的典型应用,视频输入FIFO的作用,视频输出FIFO的作用,视频数据跨时钟域FIFO,视频缩放FIFO的作用_fpga 频分复用 视频

R语言:设置工作路径为当前文件存储路径_r语言设置工作目录到目标文件夹-程序员宅基地

文章浏览阅读575次。【代码】R语言:设置工作路径为当前文件存储路径。_r语言设置工作目录到目标文件夹

background 线性渐变-程序员宅基地

文章浏览阅读452次。格式:background: linear-gradient(direction, color-stop1, color-stop2, ...);<linear-gradient> = linear-gradient([ [ <angle> | to <side-or-corner>] ,]? &l..._background线性渐变

随便推点

【蓝桥杯省赛真题39】python输出最大的数 中小学青少年组蓝桥杯比赛 算法思维python编程省赛真题解析-程序员宅基地

文章浏览阅读1k次,点赞26次,收藏8次。第十三届蓝桥杯青少年组python编程省赛真题一、题目要求(注:input()输入函数的括号中不允许添加任何信息)1、编程实现给定一个正整数N,输出正整数N中各数位最大的那个数字。例如:N=132,则输出3。2、输入输出输入描述:只有一行,输入一个正整数N输出描述:只有一行,输出正整数N中各数位最大的那个数字输入样例:

网络协议的三要素-程序员宅基地

文章浏览阅读2.2k次。一个网络协议主要由以下三个要素组成:1.语法数据与控制信息的结构或格式,包括数据的组织方式、编码方式、信号电平的表示方式等。2.语义即需要发出何种控制信息,完成何种动作,以及做出何种应答,以实现数据交换的协调和差错处理。3.时序即事件实现顺序的详细说明,以实现速率匹配和排序。不完整理解:语法表示长什么样,语义表示能干什么,时序表示排序。转载于:https://blog.51cto.com/98..._网络协议三要素csdn

The Log: What every software engineer should know about real-time data's unifying abstraction-程序员宅基地

文章浏览阅读153次。主要的思想,将所有的系统都可以看作两部分,真正的数据log系统和各种各样的query engine所有的一致性由log系统来保证,其他各种query engine不需要考虑一致性,安全性,只需要不停的从log系统来同步数据,如果数据丢失或crash可以从log系统replay来恢复可以看出kafka系统在linkedin中的重要地位,不光是d..._the log: what every software engineer should know about real-time data's uni

《伟大是熬出来的》冯仑与年轻人闲话人生之一-程序员宅基地

文章浏览阅读746次。伟大是熬出来的  目录  前言  引言 时间熬成伟大:领导者要像狼一样坚忍   第一章 内圣外王——领导者的心态修炼  1. 天纵英才的自信心  2. 上天揽月的企图心  3. 誓不回头的决心  4. 宠辱不惊的平常心  5. 换位思考的同理心  6. 激情四射的热心  第二章 日清日高——领导者的高效能修炼  7. 积极主动,想到做到  8. 合理掌控自己的时间和生命  9. 制定目标,马..._当狼拖着受伤的右腿逃生时,右腿会成为前进的阻碍,它会毫不犹豫撕咬断自己的腿, 以

有源光缆AOC知识百科汇总-程序员宅基地

文章浏览阅读285次。在当今的大数据时代,人们对高速度和高带宽的需求越来越大,迫切希望有一种新型产品来作为高性能计算和数据中心的主要传输媒质,所以有源光缆(AOC)在这种环境下诞生了。有源光缆究竟是什么呢?应用在哪些领域,有什么优势呢?易天将为您解答!有源光缆(Active Optical Cables,简称AOC)是两端装有光收发器件的光纤线缆,主要构成部件分为光路和电路两部分。作为一种高性能计..._aoc 光缆

浏览器代理服务器自动配置脚本设置方法-程序员宅基地

文章浏览阅读2.2k次。在“桌面”上按快捷键“Ctrl+R”,调出“运行”窗口。接着,在“打开”后的输入框中输入“Gpedit.msc”。并按“确定”按钮。如下图 找到“用户配置”下的“Windows设置”下的“Internet Explorer 维护”的“连接”,双击选择“自动浏览器配置”。如下图 选择“自动启动配置”,并在下面的“自动代理URL”中填写相应的PAC文件地址。如下..._設置proxy腳本