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

智能推荐

生活垃圾数据集(YOLO版)_垃圾回收数据集-程序员宅基地

文章浏览阅读1.6k次,点赞5次,收藏20次。【有害垃圾】:电池(1 号、2 号、5 号)、过期药品或内包装等;【可回收垃圾】:易拉罐、小号矿泉水瓶;【厨余垃圾】:小土豆、切过的白萝卜、胡萝卜,尺寸为电池大小;【其他垃圾】:瓷片、鹅卵石(小土豆大小)、砖块等。文件结构|----classes.txt # 标签种类|----data-txt\ # 数据集文件集合|----images\ # 数据集图片|----labels\ # yolo标签。_垃圾回收数据集

天气系统3------微服务_cityid=101280803-程序员宅基地

文章浏览阅读272次。之前写到 通过封装的API 已经可以做到使用redis进行缓存天气信息但是这一操作每次都由客户使用时才进行更新 不友好 所以应该自己实现半小时的定时存入redis 使用quartz框架 首先添加依赖build.gradle中// Quartz compile('org.springframework.boot:spring-boot-starter-quartz'..._cityid=101280803

python wxpython 不同Frame 之间的参数传递_wxpython frame.bind-程序员宅基地

文章浏览阅读1.8k次,点赞2次,收藏8次。对于使用触发事件来反应的按钮传递参数如下:可以通过lambda对function的参数传递:t.Bind(wx.EVT_BUTTON, lambda x, textctrl=t: self.input_fun(event=x, textctrl=textctrl))前提需要self.input_fun(self,event,t):传入参数而同时两个Frame之间的参数传..._wxpython frame.bind

cocos小游戏开发总结-程序员宅基地

文章浏览阅读1.9k次。最近接到一个任务要开发消消乐小游戏,当然首先就想到乐cocosCreator来作为开发工具。开发本身倒没有多少难点。消消乐的开发官网发行的书上有专门讲到。下面主要总结一下开发中遇到的问题以及解决方法屏幕适配由于设计尺寸是750*1336,如果适应高度,则在iphonX下,内容会超出屏幕宽度。按宽适应,iphon4下内容会超出屏幕高度。所以就需要根据屏幕比例来动态设置适配策略。 onLoad..._750*1336

ssm435银行贷款管理系统+vue_vue3重构信贷管理系统-程序员宅基地

文章浏览阅读745次,点赞21次,收藏21次。web项目的框架,通常更简单的数据源。21世纪的今天,随着社会的不断发展与进步,人们对于信息科学化的认识,已由低层次向高层次发展,由原来的感性认识向理性认识提高,管理工作的重要性已逐渐被人们所认识,科学化的管理,使信息存储达到准确、快速、完善,并能提高工作管理效率,促进其发展。论文主要是对银行贷款管理系统进行了介绍,包括研究的现状,还有涉及的开发背景,然后还对系统的设计目标进行了论述,还有系统的需求,以及整个的设计方案,对系统的设计以及实现,也都论述的比较细致,最后对银行贷款管理系统进行了一些具体测试。_vue3重构信贷管理系统

乌龟棋 题解-程序员宅基地

文章浏览阅读774次。题目描述原题目戳这里小明过生日的时候,爸爸送给他一副乌龟棋当作礼物。乌龟棋的棋盘是一行 NNN 个格子,每个格子上一个分数(非负整数)。棋盘第 111 格是唯一的起点,第 NNN 格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点。乌龟棋中 MMM 张爬行卡片,分成 444 种不同的类型( MMM 张卡片中不一定包含所有 444 种类型的卡片,见样例),每种类型的卡片上分别标有 1,2,3,41, 2, 3, 41,2,3,4 四个数字之一,表示使用这种卡片后,乌龟棋子将向前爬行相应的格子数

随便推点

python内存泄露的原因_Python服务端内存泄露的处理过程-程序员宅基地

文章浏览阅读1.5k次。吐槽内存泄露 ? 内存暴涨 ? OOM ?首先提一下我自己曾经历过多次内存泄露,到底有几次? 我自己心里悲伤的回想了下,造成线上影响的内存泄露事件有将近5次了,没上线就查出内存暴涨次数可能更多。这次不是最惨,相信也不会是最后的内存的泄露。有人说,内存泄露对于程序员来说,是个好事,也是个坏事。 怎么说? 好事在于,技术又有所长进,经验有所心得…. 毕竟不是所有程序员都写过OOM的服务…. 坏事..._python内存泄露

Sensor (draft)_draft sensor-程序员宅基地

文章浏览阅读747次。1.sensor typeTYPE_ACCELEROMETER=1 TYPE_MAGNETIC_FIELD=2 (what's value mean at x and z axis)TYPE_ORIENTATION=3TYPE_GYROSCOPE=4 TYPE_LIGHT=5(in )TYPE_PRESSURE=6TYPE_TEMPERATURE=7TYPE_PRO_draft sensor

【刘庆源码共享】稀疏线性系统求解算法MGMRES(m) 之 矩阵类定义三(C++)_gmres不构造矩阵-程序员宅基地

文章浏览阅读581次。/* * Copyright (c) 2009 湖南师范大学数计院 一心飞翔项目组 * All Right Reserved * * 文件名:matrix.cpp 定义Point、Node、Matrix类的各个方法 * 摘 要:定义矩阵类,包括矩阵的相关信息和方法 * * 作 者:刘 庆 * 修改日期:2009年7月19日21:15:12 **/

三分钟带你看完HTML5增强的【iframe元素】_iframe allow-top-navigation-程序员宅基地

文章浏览阅读1.7w次,点赞6次,收藏20次。HTML不再推荐页面中使用框架集,因此HTML5删除了&lt;frameset&gt;、&lt;frame&gt;和&lt;noframes&gt;这三个元素。不过HTML5还保留了&lt;iframe&gt;元素,该元素可以在普通的HTML页面中使用,生成一个行内框架,可以直接放在HTML页面的任意位置。除了指定id、class和style之外,还可以指定如下属性:src 指定一个UR..._iframe allow-top-navigation

Java之 Spring Cloud 微服务的链路追踪 Sleuth 和 Zipkin(第三个阶段)【三】【SpringBoot项目实现商品服务器端是调用】-程序员宅基地

文章浏览阅读785次,点赞29次,收藏12次。Zipkin 是 Twitter 的一个开源项目,它基于 Google Dapper 实现,它致力于收集服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集、存储、查找和展现。我们可以使用它来收集各个服务器上请求链路的跟踪数据,并通过它提供的 REST API 接口来辅助我们查询跟踪数据以实现对分布式系统的监控程序,从而及时地发现系统中出现的延迟升高问题并找出系统性能瓶颈的根源。除了面向开发的 API 接口之外,它也提供了方便的 UI 组件来帮助我们直观的搜索跟踪信息和分析请求链路明细,

烁博科技|浅谈视频安全监控行业发展_2018年8月由于某知名视频监控厂商多款摄像机存在安全漏洞-程序员宅基地

文章浏览阅读358次。“随着天网工程的建设,中国已经建成世界上规模最大的视频监控网,摄像头总 数超过2000万个,成为世界上最安全的国家。视频图像及配套数据已经应用在反恐维稳、治安防控、侦查破案、交通行政管理、服务民生等各行业各领域。烁博科技视频安全核心能力:精准智能数据采集能力:在建设之初即以应用需求为导向,开展点位选择、设备选型等布建工作,实现前端采集设备的精细化部署。随需而动的AI数据挖掘能力:让AI所需要的算力、算法、数据、服务都在应用需求的牵引下实现合理的调度,实现解析能力的最大化。完善的数据治理能力:面_2018年8月由于某知名视频监控厂商多款摄像机存在安全漏洞