OpenGL实现第一个窗口-三角形

news2024/11/27 18:52:24

1.简介

此代码是基于Qt+OpenGL实现的,但是大部分的代码是OpenGL,Qt封装了一些类,方便使用。

2.准备工作

QOpenGLWidget提供了三个便捷的虚函数,可以重写,用来重写实现典型的OpenGL任务。不需要GLFW。

  • paintGL:渲染OpenGL场景。widget需要更新时调用。
  • resizeGL:设置OpenGL视口、投影等,widget调整大小(或首次显示)时调用。
  • initializeGL:设置OpenGL资源和状态。第一次调用resizeGL/paintGL之前调用一次。

①如果需要paintGL以外的位置重新绘制,应调用widget的update()函数重新绘制。

②调用paintGL、resizeGL、initializeGL时,widget的OpenGL呈现上下文将变为当前。如果需要从其他位置调用OpenGL API函数,则必须首先调用makeCurrent()。

QOpenGLFunctions_x_x_Core:不需要GLAD

QOpenGLFunctions_x_x_Core提供了OpenGL X.X版本核心模式的所有功能。是对OpenGL函数的封装。

//初始化OpenGL函数,将Qt里的函数指针指向显卡的函数
initializeOpenGLFunctions()

自定义Widget类继承上述两个类,重写以下的几个函数,实现OpenGL任务,代码如下。

#ifndef MYOPENGLWIDGET_H
#define MYOPENGLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>

class MyOpenGLWidget : public QOpenGLWidget,public QOpenGLFunctions_3_3_Core
{
public:
    MyOpenGLWidget(QWidget *parent = nullptr);

protected:
    virtual void initializeGL();
    virtual void paintGL();
    virtual void resizeGL(int w, int h);
};

#endif // MYOPENGLWIDGET_H

 ui界面上拖拽以下openglwidget。

然后将这个openglwidget提升为我们自定义的 MyOpenGLWidget 。

以上准备工作已经完成,只要写重写paintGL、resizeGL、initializeGL这个三个函数。

3.你好,三角形

先了解以下几点:

3.1图形渲染管线

图形渲染管线接受一组3D坐标,然后把它们转变为你屏幕上的有色2D像素输出。图形渲染管线可以被划分为几个阶段,每个阶段将会把前一个阶段的输出作为输入。 注意蓝色部分代表的是我们可以注入自定义的着色器的部分。

以下概括性地解释一下渲染管线的每个部分:

  • 点着色器(Vertex Shader),它把一个单独的顶点作为输入。顶点着色器主要的目的是把3D坐标转为另一种3D坐标,同时顶点着色器允许我们对顶点属性进行一些基本处理。
  • 图元装配(Primitive Assembly)阶段将顶点着色器输出的所有顶点作为输入,并所有的点装配成指定图元的形状。
  • 几何着色器(Geometry Shader)。几何着色器把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或是其它的)图元来生成其他形状。
  • 光栅化阶段(Rasterization Stage),这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)。在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。
  • 片段着色器的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。
  • 测试和混合(Blending)阶段。这个阶段检测片段的对应的深度(和模板(Stencil))值,用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。

3.2标准化设备坐标(Normalized Device Coordinates, NDC)

一旦你的顶点坐标已经在顶点着色器中处理过,它们就应该是标准化设备坐标了,标准化设备坐标是一个x、y和z值在-1.0到1.0的一小段空间。任何落在范围外的坐标都会被丢弃/裁剪,不会显示在你的屏幕上。下面你会看到我们定义的在标准化设备坐标中的三角形(忽略z轴):

与通常的屏幕坐标不同,y轴正方向为向上,(0, 0)坐标是这个图像的中心,而不是左上角。最终你希望所有(变换过的)坐标都在这个坐标空间中,否则它们就不可见了。

3.3链接顶点属性

顶点着色器允许我们指定任何以顶点属性为形式的输入。这使其具有很强的灵活性的同时,它还的确意味着我们必须手动指定输入数据的哪一个部分对应顶点着色器的哪一个顶点属性。所以,我们必须在渲染前指定OpenGL该如何解释顶点数据。

我们的顶点缓冲数据会被解析为下面这样子:

  • 位置数据被储存为32位(4字节)浮点值。
  • 每个位置包含3个这样的值。
  • 在这3个值之间没有空隙(或其他值)。这几个值在数组中紧密排列(Tightly Packed)。
  • 数据中第一个值在缓冲开始的位置。

有了这些信息我们就可以使用glVertexAttribPointer函数告诉OpenGL该如何解析顶点数据(应用到逐个顶点属性上)了:

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

glVertexAttribPointer函数的参数非常多,所以我会逐一介绍它们:

  • 第一个参数指定我们要配置的顶点属性。还记得我们在顶点着色器中使用layout(location = 0)定义了position顶点属性的位置值(Location)吗?它可以把顶点属性的位置值设置为0。因为我们希望把数据传递到这一个顶点属性中,所以这里我们传入0
  • 第二个参数指定顶点属性的大小。顶点属性是一个vec3,它由3个值组成,所以大小是3。
  • 第三个参数指定数据的类型,这里是GL_FLOAT(GLSL中vec*都是由浮点数值组成的)。
  • 下个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE。
  • 第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3个float之后,我们把步长设置为3 * sizeof(float)。要注意的是由于我们知道这个数组是紧密排列的(在两个顶点属性之间没有空隙)我们也可以设置为0来让OpenGL决定具体步长是多少(只有当数值是紧密排列时才可用)。这个参数的意思简单说就是从这个属性第二次出现的地方到整个数组0位置之间有多少字节)。
  • 最后一个参数的类型是void*,所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是0。

最后应该使用glEnableVertexAttribArray,以顶点属性位置值作为参数,启用顶点属性;顶点属性默认是禁用的。

代码实现。

#include "myopenglwidget.h"

//顶点坐标
GLfloat vertices[] = {
    // Positions
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
};

//顶点着色器语言
const GLchar* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 position;\n"
"void main()\n"
"{\n"
"gl_Position = vec4(position,1.0);\n"
"}\n\0";

//片段着色器语言
const GLchar* fragmentShaderSource = "#version 330 core\n"
"out vec4 color;\n"
"void main()\n"
"{\n"
"color = vec4(1.0f,0.5f,0.2f,1.0f);\n"
"}\n\0";

GLuint VBO, VAO;//声明VAO、VBO
GLuint shaderProgram;//声明着色器程序

MyOpenGLWidget::MyOpenGLWidget(QWidget *parent)
    : QOpenGLWidget(parent)
{

}

void MyOpenGLWidget::initializeGL()
{
    //初始化opengl的功能,必须调用
    initializeOpenGLFunctions();

    //创建顶点着色器
    GLuint vertexShader;
    vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);//第二参数指定了传递的源码字符串数量
    glCompileShader(vertexShader);

    //创建片段着色器
    GLuint fragmentShader;
    fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);//第二参数指定了传递的源码字符串数量
    glCompileShader(fragmentShader);

    //创建一个着色器程序
    shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);

    //使用完成后释放内存
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    //创建VAO VBO
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);

    glBindVertexArray(VAO);//绑定VAO
    glBindBuffer(GL_ARRAY_BUFFER, VBO);//顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//把顶点数据复制到缓冲的内存中GL_STATIC_DRAW :数据不会或几乎不会改变。

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);

    //解绑VBO
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    //解绑VAO
    glBindVertexArray(0);
}

void MyOpenGLWidget::paintGL()
{
    //设置清空屏幕所用的颜色
    glClearColor(0.2f,0.3f,0.3f,1.0f);

    //清除颜色缓冲
    glClear(GL_COLOR_BUFFER_BIT);

    //激活这个着色器程序对象
    glUseProgram(shaderProgram);

    glBindVertexArray(VAO);//绑定VAO
    glDrawArrays(GL_TRIANGLES,0,3);
    glBindVertexArray(0);  //解绑VAO
}

void MyOpenGLWidget::resizeGL(int w, int h)
{

}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C语言】Visual Studio社区版安装配置环境(保姆级图文)

目录 1. 官网下载社区版2. 选择安装项目2.1 点击使用C的桌面开发2.2 语言包选择简体中文2.3 设置安装位置 3. 创建新项目3.1 点击创建新项目3.2 点击空项目&#xff0c;下一步3.3 设置项目名称路径3.4 创建项目 4. 测试例程总结 欢迎关注 『C语言』 系列&#xff0c;持续更新中…

代码随想录 二叉树 Java (一)

文章目录 &#xff08;简单&#xff09;144. 二叉树的前序遍历&#xff08;简单&#xff09;94. 二叉树的中序遍历&#xff08;简单&#xff09;145. 二叉树的后序遍历二叉树的统一遍历方法&#xff08;参考代码随想录&#xff09;&#xff08;中等&#xff09;102. 二叉树的层…

横岗茂盛村旧改,已立项,一期已拆平。

项目位于龙岗区横岗街道红棉路与茂盛路交汇处&#xff0c;距离轨道3号线横岗站约700米。 茂盛片区城市更新单元规划&#xff08;草案&#xff09;已经在近日公示&#xff0c;该旧改被纳入《2012年深圳市城市更新单元计划第五批计划》&#xff0c;2019年曾被暂停&#xff0c;20…

Redis实战14-分布式锁基本原理和不同实现方式对比

在上一篇文章中&#xff0c;我们知道了&#xff0c;当在集群环境下&#xff0c;synchronized关键字实现的JVM级别锁会失效的。那么怎么解决这个问题呢&#xff1f;我们可以使用分布式锁来解决。本文咱们就来介绍分布式锁基本原理以及不同实现方式对比。 我们先来回顾&#xff…

【深度学习】混合精度训练与显存分析

混合精度训练与显存分析 ​ 关于参数精度的介绍可以见文章https://zhuanlan.zhihu.com/p/604338403 相关博客 【深度学习】混合精度训练与显存分析 【深度学习】【分布式训练】Collective通信操作及Pytorch示例 【自然语言处理】【大模型】大语言模型BLOOM推理工具测试 【自然语…

(论文阅读)Chain-of-Thought Prompting Elicits Reasoningin Large Language Models

论文地址 https://openreview.net/pdf?id_VjQlMeSB_J 摘要 我们探索如何生成一个思维链——一系列中间推理步骤——如何显著提高大型语言模型执行复杂推理的能力。 特别是&#xff0c;我们展示了这种推理能力如何通过一种称为思维链提示的简单方法自然地出现在足够大的语言模…

2023 更新版:苏生不惑开发过的那些原创工具和脚本

苏生不惑第431 篇原创文章&#xff0c;将本公众号设为星标&#xff0c;第一时间看最新文章。 4年来苏生不惑这个公众号已经写了400多篇原创文章&#xff0c;去年分享过文章更新版&#xff1a;整理下苏生不惑开发过的那些工具和脚本 &#xff0c;今年再更新下我开发过的原创工具…

【Python开发】FastAPI 07:Depends 依赖注入

在 FastAPI 中&#xff0c;Depends 是一个依赖注入系统&#xff0c;用于注入应用程序中所需的依赖项&#xff0c;通过 Depends&#xff0c;我们可以轻松地将依赖项注入到 FastAPI 路由函数中。简单来说&#xff0c;Depends 依赖注入的目的就是将代码重复最小&#xff01; 目录 …

Vue学习3

文章目录 Vuex工作原理配置环境各种函数mapState对象写法数组写法 MapGetterMapMutations对象写法数组写法 Mapaction总结 模块化模块化1总结 Vuex 工作原理 那三个要通过store管理 配置环境 使用import时&#xff0c;回先执行Import中的代码,在后面的也会提前。 index.js…

Vscode利用ssh登录ubuntu开发环境下,代码不能跳转问题解决

0 开发环境 环境&#xff1a;VScode remote ssh 虚拟机Ubuntu22.04 1 问题记录 在win环境下&#xff0c;Vscode可以实现代码跳转。但是&#xff0c;在利用VScode的ssh登录Ubuntu下&#xff0c;代码不能进行跳转。 网上看到很多帖子&#xff0c;有的更改settings.json&…

【Ubuntu】保姆级图文介绍双系统win10卸载Ubuntu16.04

文章目录 删除Ubuntu分区数据删除Ubuntu启动项 这段时间想将前几年安装的Ubuntu16.04版本升级到Ubuntu20.04。 折腾了一番&#xff0c;升级失败了。想着还不如卸载了重新安装Ubuntu20.04。 由于Ubuntu16.04在升级过程中出现了一些问题&#xff0c;导致进不去Ubuntu系统。因此只…

tinkerCAD入门操作(2):移动、旋转和缩放对象

tinkerCAD入门操作&#xff1a;移动、旋转和缩放对象 介绍 现在您已经学会了如何在工作平面上旋转&#xff0c;是时候真正开始处理对象了。 在本课中&#xff0c;您将了解有关对象物理属性的更多信息。 放置一个盒子 我们需要一个对象来操作。让我们从一个盒子开始。在提示…

使用Druid数据源并查看监控页面

&#x1f4a7; 使 用 D r u i d 数 据 源 并 查 看 监 控 信 息 \color{#FF1493}{使用Druid数据源并查看监控信息} 使用Druid数据源并查看监控信息&#x1f4a7; &#x1f337; 仰望天空&#xff0c;妳我亦是行人.✨ &#x1f984; 个人主页——微风撞见云的博客&…

百度狂问3小时,大厂offer到手,小伙真狠!(百度面试真题)

前言&#xff1a; 在40岁老架构师尼恩的&#xff08;50&#xff09;读者社群中&#xff0c;经常有小伙伴&#xff0c;需要面试 百度、头条、美团、阿里、京东等大厂。 下面是一个小伙伴成功拿到通过了百度三次技术面试&#xff0c;小伙伴通过三个多小时技术拷问&#xff0c;最…

Docker镜像存储

前言 在之前的文章中有说过容器目录的隔离机制. 今天来分析一下镜像的文件系统. Docker 已经用了很久了, 也知道镜像存储的时候是分层存储的(从docker pull时分层下载就能看出), 但是具体是如何将多层进行聚合并生成最终展示的文件, 这个过程从未深究过. 既然不知道, 又难掩好…

chatgpt赋能python:Python反向切片:介绍与例子

Python反向切片&#xff1a;介绍与例子 Python是一种高级编程语言&#xff0c;具有简单易懂的语法和高效的运行速度&#xff0c;以及丰富的标准库和第三方库。其中一项有趣的功能是Python反向切片&#xff0c;它能够用一种简单而有效的方式处理列表&#xff08;list&#xff0…

大模型有什么用,从技术上看

一、大模型有什么用 目前为止&#xff0c;大模型主要是以NLP为主&#xff0c;因为NLP抛弃了RNN序列依赖的问题&#xff0c;采用了Attention is All you need的Transformer结构&#xff0c;使得NLP能够演变出更多大模型。图像领域也不甘示弱&#xff0c;CNN大模型也开始陆续涌现…

tcpdump命令抓取网络数据包并用wireshark软件分析

1、tcpdump命令部署 1.1、源码下载 (1)下载网址&#xff1a;http://www.tcpdump.org&#xff1b; (2)下载匹配的libpcap库和tcpdump库&#xff1b; (3)编译tcpdump命令依赖libpcap库&#xff0c;所以要先编译libpcap库再编译tcpdump命令&#xff1b; 1.2、源码编译 1.2.1、编…

chatgpt赋能python:Python如何去掉空值

Python如何去掉空值 数据处理过程中经常会出现空值&#xff0c;这些空值可以影响我们对数据的分析和处理。在Python中&#xff0c;有许多方法可以去除空值。本文将介绍常见的方法并提供实例说明。 什么是空值 在Python中&#xff0c;空值通常用None或NaN表示。None是Python内…

FutureTask简介

FutureTask简介 Future接口和实现Future接口的FutureTask类&#xff0c;代表异步计算的结果。FutureTask除了实现Future接口外&#xff0c;还实现了Runnable接口。因此&#xff0c;FutureTask可以交给Executor执行&#xff0c;也可以由调用线程直接执行&#xff08;FutureTask…