QT+ OpenGL学习
文章目录
- QT+ OpenGL
- QOpenGLWidget:不需要GLFW
- QOpenGLFunction_X_X_Core:不需要GLAD
- 你好,三角形
- 顶点输入
- 顶点着色器
- 片段着色器
- 链接着色器
- 本节代码
- 元素缓冲对象EBO
- QT交互
- GLSL
- GLSL支持的类型
- 输入输出
- Uniform
- 纹理
- 纹理单元
- 纹理环绕
- 纹理过滤
- 多级渐远纹理
QT+ OpenGL
本篇完整工程见gitee:QTOpenGL
对应点的tag,由turbolove提供技术支持,您可以关注博主或者私信博主。
什么是opengl
open graphics library 他是一个由Khronos组织制定并且维护的规范
opengl核心是一个c库,同时也支持多种语言的派生
核心模式(core-profile):
- 也叫可编程管线,提供了更多的灵活性,更高的效率,更重要的是可以深入的理解图形编程。
立即渲染模式(Immediate mode)
- 早期的OpenGL使用的模式(也就是固定渲染管线)
- OpenGL的大多数功能都被库隐藏起来,容易使用和理解,但是效率低下
- 开发者很少能控制OpenGL如何进行计算
- 因此从OpenGL3.2开始推出核心模式
状态机(state machine)
- OpenGL是一个聚到的状态机 - 描述如何操作的所有变量的大集合
- OpenGL的状态通常被称为上下文Context
- 状态设置函数(State-changing Function)
- 状态应用函数(State-using Function)
对象(Object)
- 一个对象是指一些选项的集合,代表OpenGL状态的一个子集
- 当前状态只有一份,如果每次显示不同的效果,都重新配置会很麻烦
- 我们需要使用一些小助理(对象),帮忙记录某些状态信息,以便复用
// 创建对象
GLunit objId = 0;
glGenObject(1, &objId);
// 绑定对象到上下文
glBindObject(GL_WINDOW_TARGET, objId);
// 设置GL_WINDOW_TARGET对象的一些选项
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_WIDTH, 800);
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_HEIGHT, 600);
// 将上下文的GL_WINDOW_TARGET对象设置成默认值
glBindObject(GL_WINDOW_TARGET, 0);
// 一旦我们重新绑定这个对象到GL_WINDOW_TARGET位置,这些选项就会重新生效
QOpenGLWidget:不需要GLFW
QOpenGLWidget提供了三个便捷的虚拟函数,可以冲在,用来实现典型的opengl任务
- paintGL:渲染opengl场景,widget需要更新时候调用
- resizeGL:设置opengl视口,投影等,widget调整大小(或者首次显示)时候调用
- initializeGL:设置opengl资源和状态,第一次调用resizeGL()或者paintGL()之前调用一次
如果需要从paintGL()意外的位置触发重新绘制(典型示例是使用计时器设置场景动画),则应该调用widget的update()函数来安排更新。
调用paintGL()、resizeGL()、initializeGL()时,widget的opengl呈现上下文变为当前。如果需要从其他位置(例如在widget的构造函数或者自己的绘制函数中)调用openglAPI函数,则必须首先调用makeCurrent()。
QOpenGLFunction_X_X_Core:不需要GLAD
QOpenGLFunction_X_X_Core提供OpenGL x.x版本核心模式的所有功能,是对OpenGL函数的封装
turboopenglwidget.h
#ifndef QTOPENGL_TURBOOPENGLWIDGET_H
#define QTOPENGL_TURBOOPENGLWIDGET_H#include <QOpenGLWidget>
#include <QOpenGLFunctions_4_5_Core>class TurboOpenGLWidget : public QOpenGLWidget, QOpenGLFunctions_4_5_Core
{Q_OBJECT
public:explicit TurboOpenGLWidget(QWidget *parent = 0);protected:void initializeGL() override;void paintGL() override;void resizeGL(int w, int h) override;private:};#endif //QTOPENGL_TURBOOPENGLWIDGET_H
turboopenglwidget.cpp
#include "turboopenglwidget.h"TurboOpenGLWidget::TurboOpenGLWidget(QWidget *parent): QOpenGLWidget(parent)
{}void TurboOpenGLWidget::initializeGL()
{initializeOpenGLFunctions();QOpenGLWidget::initializeGL();
}void TurboOpenGLWidget::paintGL()
{glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);QOpenGLWidget::paintGL();
}void TurboOpenGLWidget::resizeGL(int w, int h)
{QOpenGLWidget::resizeGL(w, h);
}
你好,三角形
float vertices[] =
{-0.5f, -0.5f, 0.0f,0.5f, -0.5f, 0.0f,0.0f, 0.5f, 0.0f,
};
标准化设备坐标:
订单着色器中处理过后,应该就是标准化设备坐标,x、y、z的值在-1.0到1.0的一小段空间(立方体)。落在范围外的坐标都会被裁减。
顶点输入
- 他会在GPU上创建内存,用于存储我们的顶点数据
- 通过点缓冲对象(vertex buffer object , VBO)管理
- 顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER
- 通过点缓冲对象(vertex buffer object , VBO)管理
- 配置OpenGL如何解释这些内存
- 通过顶点数组对象(vertex array object , VAO)管理
数组力的每一个项都对应一个属性的解析。
OpenGL允许我们同时绑定多个缓冲,只要他们是不同的缓冲类型(每个缓冲类型类似于前面说的子集,每个VBO是一个小助理)。
VAO并不保存实际数据,而是放顶点结构定义。
// 创建VAO和VBO对象,并且赋予ID
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);// 绑定VAO和VBO对象
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);// 为当前绑定到target的缓冲区对象创建一个新的数据存储
// 如果data不是NULL, 则使用来自此指针的数据初始化数据存储
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);// 告知显卡如何解析缓冲里面的属性值
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void *)0);
// 开启VAO管理的第一个属性的值
glEnableVertexAttribArray(0);// 释放VAO和VBO对象
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glBufferData
是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数。
-
第一个参数是目标缓冲的类型:顶点缓冲对象当前绑定到GL_ARRAY_BUFFER目标上。
-
第二个参数指定传输数据的大小(以字节为单位);用一个简单的
sizeof
计算出顶点数据大小就行。 -
第三个参数是我们希望发送的实际数据。
-
第四个参数指定了我们希望显卡如何管理给定的数据。它有三种形式:
-
GL_STATIC_DRAW :数据不会或几乎不会改变。
-
GL_DYNAMIC_DRAW:数据会被改变很多。
-
GL_STREAM_DRAW :数据每次绘制时都会改变。
-
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。我们会在后面详细解释这个参数。
顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;void main()
{gl_Position = vec4(aPos, 1.0);
}
// 创建顶点着色器
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader ,1 ,&vertexShaderSource, NULL);
glCompileShader(vertexShader);
glShaderSource
- 第一个参数是函数把要编译的着色器对象。
- 第二参数指定了传递的源码字符串数量,这里只有一个。
- 第三个参数是顶点着色器真正的源码,
- 第四个参数我们先设置为
NULL
。
片段着色器
#version 330 core
out vec4 FragColor;void main()
{FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
// 创建顶片段着色器
unsigned int fragShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragShader ,1 ,&fragShaderSource, NULL);
glCompileShader(fragShader);
链接着色器
unsigned int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragShader);
glLinkProgram(shaderProgram);glDeleteShader(vertexShader);
glDeleteShader(fragShader);
本节代码
turboopenglwidget.cpp
#include "turboopenglwidget.h"float vertices[] =
{-0.5f, -0.5f, 0.0f,0.5f, -0.5f, 0.0f,0.0f, 0.5f, 0.0f,
};
const char *vertexShaderSource ="#version 330 core \n""layout (location = 0) in vec3 aPos;\n""\n""void main()\n""{\n""\tgl_Position = vec4(aPos, 1.0);\n""}";
const char *fragShaderSource ="#version 330 core\n""out vec4 FragColor;\n""\n""void main()\n""{\n"" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n""} ";// 创建VAO和VBO对象,并且赋予ID
unsigned int VBO, VAO;
unsigned int shaderProgram;
TurboOpenGLWidget::TurboOpenGLWidget(QWidget *parent): QOpenGLWidget(parent)
{}void TurboOpenGLWidget::initializeGL()
{initializeOpenGLFunctions();glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);// 绑定VAO和VBO对象glBindVertexArray(VAO);glBindBuffer(GL_ARRAY_BUFFER, VBO);// 为当前绑定到target的缓冲区对象创建一个新的数据存储// 如果data不是NULL, 则使用来自此指针的数据初始化数据存储glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);// 告知显卡如何解析缓冲里面的属性值glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void *)0);// 开启VAO管理的第一个属性的值glEnableVertexAttribArray(0);// 释放VAO和VBO对象glBindBuffer(GL_ARRAY_BUFFER, 0);glBindVertexArray(0);// 创建顶点着色器unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader ,1 ,&vertexShaderSource, NULL);glCompileShader(vertexShader);// 创建顶片段着色器unsigned int fragShader = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragShader ,1 ,&fragShaderSource, NULL);glCompileShader(fragShader);shaderProgram = glCreateProgram();glAttachShader(shaderProgram, vertexShader);glAttachShader(shaderProgram, fragShader);glLinkProgram(shaderProgram);glDeleteShader(vertexShader);glDeleteShader(fragShader);
}void TurboOpenGLWidget::paintGL()
{glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);glUseProgram(shaderProgram);glBindVertexArray(VAO);glDrawArrays(GL_TRIANGLES, 0, 3);
}void TurboOpenGLWidget::resizeGL(int w, int h)
{}
元素缓冲对象EBO
可以绘制两个三角形来组合成一个矩形,这会生成下面的顶点的集合:
float vertices[] = {// 第一个三角形0.5f, 0.5f, 0.0f, // 右上角0.5f, -0.5f, 0.0f, // 右下角-0.5f, 0.5f, 0.0f, // 左上角// 第二个三角形0.5f, -0.5f, 0.0f, // 右下角-0.5f, -0.5f, 0.0f, // 左下角-0.5f, 0.5f, 0.0f // 左上角
};
值得庆幸的是,元素缓冲区对象的工作方式正是如此。 EBO是一个缓冲区,就像一个顶点缓冲区对象一样,它存储 OpenGL 用来决定要绘制哪些顶点的索引。这种所谓的索引绘制(Indexed Drawing)正是我们问题的解决方案。首先,我们先要定义(不重复的)顶点,和绘制出矩形所需的索引:
代码展示:
#include "turboopenglwidget.h"float vertices[] = {0.5f, 0.5f, 0.0f, // 右上角0.5f, -0.5f, 0.0f, // 右下角-0.5f, -0.5f, 0.0f, // 左下角-0.5f, 0.5f, 0.0f // 左上角
};unsigned int indices[] = {0, 1, 3, // 第一个三角形1, 2, 3 // 第二个三角形
};
const char *vertexShaderSource ="#version 330 core \n""layout (location = 0) in vec3 aPos;\n""\n""void main()\n""{\n""\tgl_Position = vec4(aPos, 1.0);\n""}";
const char *fragShaderSource ="#version 330 core\n""out vec4 FragColor;\n""\n""void main()\n""{\n"" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n""} ";// 创建VAO和VBO对象,并且赋予ID
unsigned int VBO, VAO;
unsigned int EBO;
unsigned int shaderProgram;
TurboOpenGLWidget::TurboOpenGLWidget(QWidget *parent): QOpenGLWidget(parent)
{}void TurboOpenGLWidget::initializeGL()
{initializeOpenGLFunctions();glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);glGenBuffers(1, &EBO);// 绑定VAO和VBO对象glBindVertexArray(VAO);glBindBuffer(GL_ARRAY_BUFFER, VBO);// 为当前绑定到target的缓冲区对象创建一个新的数据存储// 如果data不是NULL, 则使用来自此指针的数据初始化数据存储glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);// 告知显卡如何解析缓冲里面的属性值glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void *)0);// 开启VAO管理的第一个属性的值glEnableVertexAttribArray(0);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);// 释放VAO和VBO对象glBindBuffer(GL_ARRAY_BUFFER, 0);glBindVertexArray(0);// 创建顶点着色器unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader ,1 ,&vertexShaderSource, NULL);glCompileShader(vertexShader);// 创建顶片段着色器unsigned int fragShader = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragShader ,1 ,&fragShaderSource, NULL);glCompileShader(fragShader);shaderProgram = glCreateProgram();glAttachShader(shaderProgram, vertexShader);glAttachShader(shaderProgram, fragShader);glLinkProgram(shaderProgram);glDeleteShader(vertexShader);glDeleteShader(fragShader);glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}void TurboOpenGLWidget::paintGL()
{glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);glUseProgram(shaderProgram);glBindVertexArray(VAO);
// glDrawArrays(GL_TRIANGLES, 0, 6);
// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}void TurboOpenGLWidget::resizeGL(int w, int h)
{}
QT交互
- 如果需要从paintGL()以外的位置触发重新绘制(典型示例是使用计时器设置场景动画), 则应该调用widget的update()函数来安排更新
- 调用paintGL()、resizeGL()和initializeGL()时,widget的OpenGL呈现上下文将变为当前。如果需要从其他位置(例如,在widget的构造函数或自己的绘制函数中)调用opengl API函数,则必须首先调用makeCurrent()。
代码示例:
turboopenglwidget.h
#ifndef QTOPENGL_TURBOOPENGLWIDGET_H
#define QTOPENGL_TURBOOPENGLWIDGET_H#include <QOpenGLWidget>
#include <QOpenGLShaderProgram>
#include <QOpenGLFunctions_4_5_Core>class TurboOpenGLWidget : public QOpenGLWidget, QOpenGLFunctions_4_5_Core
{Q_OBJECT
public:enum Shape{None,Rect,Circle,Triangle,};explicit TurboOpenGLWidget(QWidget *parent = 0);~TurboOpenGLWidget() override;void drawShape(Shape shape);void setWireFrame(bool mode);protected:void initializeGL() override;void paintGL() override;void resizeGL(int w, int h) override;private:Shape shape_;QOpenGLShaderProgram shader_program_;
};#endif //QTOPENGL_TURBOOPENGLWIDGET_H
turboopenglwidget.cpp
#include <iostream>
#include "turboopenglwidget.h"float vertices[] = {0.5f, 0.5f, 0.0f, // 右上角0.5f, -0.5f, 0.0f, // 右下角-0.5f, -0.5f, 0.0f, // 左下角-0.5f, 0.5f, 0.0f // 左上角
};unsigned int indices[] = {0, 1, 3, // 第一个三角形1, 2, 3 // 第二个三角形
};// 创建VAO和VBO对象,并且赋予ID
unsigned int VBO, VAO;
unsigned int EBO;
TurboOpenGLWidget::TurboOpenGLWidget(QWidget *parent): QOpenGLWidget(parent)
{}TurboOpenGLWidget::~TurboOpenGLWidget()
{makeCurrent();glDeleteBuffers(1, &VBO);glDeleteBuffers(1, &EBO);glDeleteVertexArrays(1, &VAO);doneCurrent();
}void TurboOpenGLWidget::initializeGL()
{initializeOpenGLFunctions();glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);glGenBuffers(1, &EBO);// 绑定VAO和VBO对象glBindVertexArray(VAO);glBindBuffer(GL_ARRAY_BUFFER, VBO);// 为当前绑定到target的缓冲区对象创建一个新的数据存储// 如果data不是NULL, 则使用来自此指针的数据初始化数据存储glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);// 告知显卡如何解析缓冲里面的属性值glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void *)0);// 开启VAO管理的第一个属性的值glEnableVertexAttribArray(0);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);// 释放VAO和VBO对象glBindBuffer(GL_ARRAY_BUFFER, 0);glBindVertexArray(0);bool success = false;shader_program_.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/resources/shader.vert");shader_program_.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/resources/shader.frag");success = shader_program_.link();if(!success){std::cout << "shader is failed" << std::endl;}// glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}void TurboOpenGLWidget::paintGL()
{glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);shader_program_.bind();glBindVertexArray(VAO);
// glDrawArrays(GL_TRIANGLES, 0, 6);// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);switch(shape_){case Rect:glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);break;}update();
}void TurboOpenGLWidget::resizeGL(int w, int h)
{}void TurboOpenGLWidget::drawShape(TurboOpenGLWidget::Shape shape)
{shape_ = shape;
}void TurboOpenGLWidget::setWireFrame(bool mode)
{makeCurrent();if(mode){glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);}else{glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);}
}
mainwidow.h
#ifndef QTOPENGL_MAINWINDOW_H
#define QTOPENGL_MAINWINDOW_H#include <QMainWindow>QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECT
public:explicit MainWindow(QWidget *parent = nullptr);~MainWindow() override;protected slots:void drawRect();void clearPic();void lineModel(const bool &mode);private:Ui::MainWindow *ui;QToolBar *tool_bar_{nullptr};
};#endif //QTOPENGL_MAINWINDOW_H
mainwidow.cpp
#include "mainwindow.h"
#include "ui_MainWindow.h"
#include <QToolBar>
#include <QAction>MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);setCentralWidget(ui->openGLWidget);tool_bar_ = new QToolBar(this);auto *action = new QAction(tr("绘制矩形"), this);auto *action2 = new QAction(tr("清空图形"), this);auto *action3 = new QAction(tr("线框模式"), this);action3->setCheckable(true);connect(action, &QAction::triggered, this, &MainWindow::drawRect);connect(action2, &QAction::triggered, this, &MainWindow::clearPic);connect(action3, &QAction::triggered, this, &MainWindow::lineModel);tool_bar_->addAction(action);tool_bar_->addAction(action2);tool_bar_->addAction(action3);addToolBar(tool_bar_);
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::drawRect()
{ui->openGLWidget->drawShape(TurboOpenGLWidget::Rect);
}void MainWindow::clearPic()
{ui->openGLWidget->drawShape(TurboOpenGLWidget::None);
}void MainWindow::lineModel(const bool &mode)
{ui->openGLWidget->setWireFrame(mode);
}
GLSL
OpenGL Shading Languaage
一个典型的shader程序结构:
#version version_number
in type in_variable_name;
in type in_variable_name;out type out_variable_name;uniform type uniform_name;int main()
{// 处理输入并进行一些图形操作...// 输出处理过的结果到输出变量out_variable_name = weird_stuff_we_processed;
}
我们能声明的顶点数量是有限的,可以通过下面的代码获取:
int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;
OpenGL确保至少有16个包含4分量的顶点属性可用,但是有些硬件可能会允许更多的顶点属性。
GLSL支持的类型
类型:
GLSL中包含C等其他语言大部分默认的基础数据类型
int float double uint bool
GLSL也有两种容器类型
类型 | 含义 |
---|---|
vecn | 包含n 个float分量的默认向量 |
bvecn | 包含n 个bool分量的向量 |
ivecn | 包含n 个int分量的向量 |
uvecn | 包含n 个unsigned int分量的向量 |
dvecn | 包含n 个double分量的向量 |
向量这一数据类型也允许一些有趣而灵活的分量选择方式,叫做重组(Swizzling)。重组允许这样的语法:
vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec = differentVec.zyw;
vec4 otherVec = someVec.xxxx + anotherVec.yxzy;
vec2 vect = vec2(0.5, 0.7);
vec4 result = vec4(vect, 0.0, 0.0);
vec4 otherResult = vec4(result.xyz, 1.0);
输入输出
- 在发送方着色器声明一个输出
- 在接收方着色器声明一个类似的输入
- 当类型和名称都一致的时候OpenGL会把两个变量链接到一起(在链接程序对象时候完成)
顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos; // 位置变量的属性位置值为0out vec4 vertexColor; // 为片段着色器指定一个颜色输出void main()
{gl_Position = vec4(aPos, 1.0); // 注意我们如何把一个vec3作为vec4的构造器的参数vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // 把输出变量设置为暗红色
}
片段着色器
#version 330 core
out vec4 FragColor;in vec4 vertexColor; // 从顶点着色器传来的输入变量(名称相同、类型相同)void main()
{FragColor = vertexColor;
}
顶点着色器接收的是一种特殊形式的输入,否则就会效率低下
从顶点数据中直接接收输入。为了定义顶点数据该如何管理,我们使用location这一元数据(metadata)指定输入变量,这样我们才可以在CPU上配置顶点属性。例如: layout(location = 0)。 layout这个标识,使得我们能把它链接到顶点数据。
可以忽略layout( location = 0) 标识符,通过在OPenGL代码中使用glGetAttrribLocation查询属性位置(Location),或者是glBindAttribLocation属性位置值(Location),但是推荐在着色器中设置他们,这样会更容易理解而且节省你和(OpenGL)的工作量
#include <iostream>
#include "turboopenglwidget.h"float vertices[] = {0.5f, 0.5f, 0.0f, // 右上角0.5f, -0.5f, 0.0f, // 右下角-0.5f, -0.5f, 0.0f, // 左下角-0.5f, 0.5f, 0.0f // 左上角
};unsigned int indices[] = {0, 1, 3, // 第一个三角形1, 2, 3 // 第二个三角形
};// 创建VAO和VBO对象,并且赋予ID
unsigned int VBO, VAO;
unsigned int EBO;
TurboOpenGLWidget::TurboOpenGLWidget(QWidget *parent): QOpenGLWidget(parent)
{}TurboOpenGLWidget::~TurboOpenGLWidget()
{makeCurrent();glDeleteBuffers(1, &VBO);glDeleteBuffers(1, &EBO);glDeleteVertexArrays(1, &VAO);doneCurrent();
}void TurboOpenGLWidget::initializeGL()
{initializeOpenGLFunctions();glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);glGenBuffers(1, &EBO);// 绑定VAO和VBO对象glBindVertexArray(VAO);shader_program_.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/resources/shader.vert");shader_program_.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/resources/shader.frag");bool success = false;success = shader_program_.link();if(!success){std::cout << "shader is failed" << std::endl;}glBindBuffer(GL_ARRAY_BUFFER, VBO);// 为当前绑定到target的缓冲区对象创建一个新的数据存储// 如果data不是NULL, 则使用来自此指针的数据初始化数据存储glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);// 告知显卡如何解析缓冲里面的属性值int location = shader_program_.attributeLocation("aPos");glVertexAttribPointer(location, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void *)0);// 开启VAO管理的第一个属性的值glEnableVertexAttribArray(location);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);// 释放VAO和VBO对象glBindBuffer(GL_ARRAY_BUFFER, 0);glBindVertexArray(0);
}void TurboOpenGLWidget::paintGL()
{glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);shader_program_.bind();glBindVertexArray(VAO);
// glDrawArrays(GL_TRIANGLES, 0, 6);// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);switch(shape_){case Rect:glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);break;}update();
}void TurboOpenGLWidget::resizeGL(int w, int h)
{}void TurboOpenGLWidget::drawShape(TurboOpenGLWidget::Shape shape)
{shape_ = shape;
}void TurboOpenGLWidget::setWireFrame(bool mode)
{makeCurrent();if(mode){glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);}else{glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);}
}
Uniform
另一种从CPU的应用,向GPU中的着色器发送数据的方式
uniform是全局的,可以被任意的着色器程序在任一阶段访问
#version 330 core
out vec4 FragColor;uniform vec4 ourColor; // 在OpenGL程序代码中设定这个变量void main()
{FragColor = ourColor;
}
如果声明了一个uniform却没有用过,编译器会默认移除这个变,导致最后编译出的版本中并不会包含它,这可能导致几个非常麻烦的错误,切记
这次我们不去给像素单独传递一个颜色,而是让他随着时间改变颜色。
OpenGL在其核心是一个C库,所以他不支持类型重载,在函数参数类型不同时候就要为其定义新的函数,glUniform是一个典型的例子。这个函数有特定的后缀,用来标识设定的uniform的类型。可能的后缀有:
后缀 | 含义 |
---|---|
f | 函数需要一个float作为它的值 |
i | 函数需要一个int作为它的值 |
ui | 函数需要一个unsigned int作为它的值 |
3f | 函数需要3个float作为它的值 |
fv | 函数需要一个float向量/数组作为它的值 |
QT 为我们封装了这个函数,因此我们可以不用太过关注该函数的详细内容,但是你要是用原生的OpenGL的话,需要关注该函数。
纹理
当我们需要给图形赋予真实的颜色的时候,不大可能使用前面的方法为每一个顶点指定第一个颜色,通常我们会采用纹理贴图。
每个顶点关联一个纹理坐标(Texture Coordinate),之后在图形的其他片段上进行片段插值
我们只需要告诉OpenGL如何对纹理采样即可
顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 1) in vec2 aTexCord;
out vec3 ourColor; // 向片段着色器输出一个颜色
out vec2 texCord; // 向片段着色器输出一个颜色
void main()
{gl_Position = vec4(aPos, 1.0);ourColor = aColor; // 将ourColor设置为我们从顶点数据那里得到的输入颜色texCord = aTexCord;
}
片段着色器
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 texCord;
uniform sampler2D texture0;
void main()
{FragColor = texture(texture0, texCord);
}
对应的显示代码:
#include <iostream>
#include "turboopenglwidget.h"float vertices[] = {0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // 右上角0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 右下角-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // 左下角-0.5f, 0.5f, 0.0f, 0.5f, 0.5f, 0.5f, 0.0f, 1.0f // 左上角
};unsigned int indices[] = {0, 1, 3, // 第一个三角形1, 2, 3 // 第二个三角形
};// 创建VAO和VBO对象,并且赋予ID
unsigned int VBO, VAO;
unsigned int EBO;
TurboOpenGLWidget::TurboOpenGLWidget(QWidget *parent): QOpenGLWidget(parent)
{connect(&timer, &QTimer::timeout, this, &TurboOpenGLWidget::timeout);// timer.start(100);
}TurboOpenGLWidget::~TurboOpenGLWidget()
{if(!isValid()) return;makeCurrent();glDeleteBuffers(1, &VBO);glDeleteBuffers(1, &EBO);glDeleteVertexArrays(1, &VAO);doneCurrent();
}void TurboOpenGLWidget::initializeGL()
{initializeOpenGLFunctions();glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);glGenBuffers(1, &EBO);// 绑定VAO和VBO对象glBindVertexArray(VAO);shader_program_.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/resources/shader.vert");shader_program_.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/resources/shader.frag");bool success = false;success = shader_program_.link();if(!success){std::cout << "shader is failed" << std::endl;}glBindBuffer(GL_ARRAY_BUFFER, VBO);// 为当前绑定到target的缓冲区对象创建一个新的数据存储// 如果data不是NULL, 则使用来自此指针的数据初始化数据存储glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);// 告知显卡如何解析缓冲里面的属性值int location = shader_program_.attributeLocation("aPos");glVertexAttribPointer(location, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)0);// 开启VAO管理的第一个属性的值glEnableVertexAttribArray(location);int location2 = shader_program_.attributeLocation("aColor");glVertexAttribPointer(location2, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(3*sizeof(float)));// 开启VAO管理的第一个属性的值glEnableVertexAttribArray(location2);int location3 = shader_program_.attributeLocation("aTexCord");glVertexAttribPointer(location3, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(6*sizeof(float)));// 开启VAO管理的第一个属性的值glEnableVertexAttribArray(location3);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);texture_wall_ = new QOpenGLTexture(QImage(":/resources/wall.jpg"));// 释放VAO和VBO对象glBindBuffer(GL_ARRAY_BUFFER, 0);glBindVertexArray(0);}void TurboOpenGLWidget::paintGL()
{glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);shader_program_.bind();glBindVertexArray(VAO);
// glDrawArrays(GL_TRIANGLES, 0, 6);// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);switch(shape_){case Rect:texture_wall_->bind();glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);break;}update();
}void TurboOpenGLWidget::resizeGL(int w, int h)
{}void TurboOpenGLWidget::drawShape(TurboOpenGLWidget::Shape shape)
{shape_ = shape;
}void TurboOpenGLWidget::setWireFrame(bool mode)
{makeCurrent();if(mode){glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);}else{glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);}
}
#include <QTime>
void TurboOpenGLWidget::timeout()
{if(shape_){return;}makeCurrent();int time = QTime::currentTime().second();float green = (sin(time) / 2.0f) + 0.5f;shader_program_.setUniformValue("ourColor", 0.0f, green, 0.0f, 1.0f);
}
纹理单元
OpenGL保证至少16个纹理单元,也就是说你可以激活从GL_TEXTURE0到GL_TEXTURE15。他们都是按照顺序定义的, GL_TEXTURE0+8可以获得GL_TEXTURE8
以下是QT主要代码,在gitee项目中查看完整代码。
texture_wall_ = new QOpenGLTexture(QImage(":/resources/wall.jpg").mirrored());
texture_le_ = new QOpenGLTexture(QImage(":/resources/awesomeface.png").mirrored());shader_program_.bind();
shader_program_.setUniformValue("textureWall", 0);
shader_program_.setUniformValue("textureSmile", 1);
纹理环绕
环绕方式 | 描述 |
---|---|
GL_REPEAT | 对纹理的默认行为。重复纹理图像。 |
GL_MIRRORED_REPEAT | 和GL_REPEAT一样,但每次重复图片是镜像放置的。 |
GL_CLAMP_TO_EDGE | 纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。 |
GL_CLAMP_TO_BORDER | 超出的坐标为用户指定的边缘颜色。 |
相关代码请到gitee查看,这里不复制
纹理过滤
纹理坐标不依赖于分辨率,OpenGL需要知道怎么将纹理像素映射到纹理坐标;
可以想象你打开一张图片,不断放大,会发现它是由无数像素点组成的,这个点就是纹理像素
- 纹理坐标的精度是无限的,可以是任意浮点值
- 纹理像素是有限的(图片分辨率)
- 一个像素需要一个颜色
- 所谓采样就是通过纹理坐标,问图片要纹理像素的颜色值
大图片贴小面片时:纹理的精度高,相邻纹理像素往往色差不打,无需融合,直接就近选取即可。
主要函数:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
相关代码请到gitee查看,这里不复制
多级渐远纹理
简单来说就是一系列的纹理图像,根据观察者与物体的距离,参考临界值,选择最适合物体的距离的那个纹理
OpenGL有一个glGenerateMipmaps函数,可以生产多级渐远纹理
过滤方式 | 描述 |
---|---|
GL_NEAREST_MIPMAP_NEAREST | 使用最邻近的多级渐远纹理来匹配像素大小,并使用邻近插值进行纹理采样 |
GL_LINEAR_MIPMAP_NEAREST | 使用最邻近的多级渐远纹理级别,并使用线性插值进行采样 |
GL_NEAREST_MIPMAP_LINEAR | 在两个最匹配像素大小的多级渐远纹理之间进行线性插值,使用邻近插值进行采样 |
GL_LINEAR_MIPMAP_LINEAR | 在两个邻近的多级渐远纹理之间使用线性插值,并使用线性插值进行采样 |
主要函数:
texture_small_->generateMipMaps();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
相关代码请到gitee查看,这里不复制
相关文章:
QT+ OpenGL学习
文章目录QT OpenGLQOpenGLWidget:不需要GLFWQOpenGLFunction_X_X_Core:不需要GLAD你好,三角形顶点输入顶点着色器片段着色器链接着色器本节代码元素缓冲对象EBOQT交互GLSLGLSL支持的类型输入输出Uniform纹理纹理单元纹理环绕纹理过滤多级渐远纹理QT OpenGL 本篇完整…...
C语言(字符串输入)
目录 一.gets和puts组合 二.fgets()和fputs() 三.fgets()函数返回 四.fgets读取满问题 五.修改fgets函数,自动用\0替换\n 一.gets和puts组合 Gets()读取整行输入,知道遇到换行符,然后丢弃换行符,存储其余字符,并在这些字符的…...
背包问题求方案数(AcWing)(JAVA)
有 N件物品和一个容量是 V 的背包。每件物品只能使用一次。 第 i 件物品的体积是 vi,价值是 wi。 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。 输出 最优选法的方案数。注意答案可能很大,请输出答…...
一篇文章带你读懂HashMap
HashMap是面试中经常问到的一个知识点,也是判断一个候选人基础是否扎实的标准之一。可见HashMap的掌握是多重要。 一、HashMap源码分析 1、构造函数 让我们先从构造函数说起,HashMap有四个构造方法,别慌 1.1 HashMap() // 1.无参构造方法、// 构造一…...
Java如何进行优雅的判空——Optional类的灵活应用
0 引言 在Java Web项目开发中,经常令人头疼的NPE问题(NullPointerException)——空指针,例如我们在调用equal()方法时,就经常会出现NPE问题: String str null; str.equals("fsfs");…...
Fluent Python 笔记 第 12 章 继承的优缺点
重点是说明对 Python 而言尤为重要的两个细节: 子类化内置类型的缺点多重继承和方法解析顺序 12.1 子类化内置类型很麻烦 内置类型(使用 C 语言编写)不会调用用户定义的类覆盖的特殊方法。 不要子类化内置类型,用户自己定义的类应 该继承 collections 模块(http…...
Go语言读取解析yml文件,快速转换yml到go struct
YAML (YAML Aint a Markup Language)是一种标记语言,通常以.yml为后缀的文件,是一种直观的能够被计算机程序识别的数据序列化格式,并且容易被人类阅读,容易和脚本语言交互的,可以被支持YAML库的不同的编程语言程序导入…...
第二十六章 java并发常见知识内容(ThreadLocal 详解)
JAVA重要知识点带着疑问看ThreadLocalGC 之后 key 是否为 null?ThreadLocalMap Hash 算法ThreadLocalMap Hash 冲突ThreadLocalMap.set()方法ThreadLocalMap过期 key 的探测式清理流程ThreadLocalMap扩容机制ThreadLocalMap.get()详解ThreadLocalMap过期 key 的启发…...
人类的第一语言是什么
其实机器智能始终存在一个争议 没有人类的肢体和感受器无法理解和感同身受 这不用想是自然,但是可以通过虚拟数据进行模拟,深度学习便是 深度学习是模拟简单输入输出的最好选择,但不是开放性的学习 没有智能交互的智能永远不是智能 就像狼孩一…...
jsp(全部知识点)
👌 棒棒有言:也许我一直照着别人的方向飞,可是这次,我想要用我的方式飞翔一次!人生,既要淡,又要有味。凡事不必太在意,一切随缘,缘深多聚聚,缘浅随它去。凡事…...
测试开发面试基础题
1.对测试开发的理解 测试开发首先离不开测试,而软件测试是指,在规定的条件下对程序进行操作,以发现程序错误,衡量软件质量,并对其是否能满足设计要求进行评估的过程。 而且,现在不仅仅是通过手工测试来发…...
C++——多态|虚函数|重写|虚表
文章目录1. 多态的概念1.1 概念2. 多态的定义及实现2.1多态的构成条件2.2 虚函数2.3虚函数的重写虚函数重写的三个例外:2.4 普通调用和多态调用:2.5 C11 override 和 final2.6 重载、虚函数的覆盖(重写)、隐藏(重定义)的对比3. 抽象类(有关纯虚函数)3.1 …...
IPV4地址详解
文章目录IPV4地址分类编址划分子网无分类编制CIDR路由聚合应用规划(子网划分的细节)定长的子网掩码FLSM变长的子网掩码VLSMIPV4地址 IPV4地址就是给因特网(Internet)上的每一台主机(或路由器)的每一个接口…...
(一)初识Streamlit(附安装)
本入门指南介绍Streamlit的工作原理、如何在您首选的操作系统上安装Streamlit,以及如何创建第一个Streamlit应用程序! 1 安装 1.1 先决条件 Python 3.7 – Python 3.11 **注:我这里使用的是anaconda的虚拟环境,用pycharm编写代…...
【新】华为OD机试 - 斗地主 2(Python)| 刷完获取OD招聘渠道
斗地主 2 题目描述 在斗地主扑克牌游戏中,扑克牌由小到大的顺序为3 4 5 6 7 8 9 10 J Q K A 2 玩家可以出的扑克牌阵型有,单张,对子,顺子,飞机,炸弹等 其中顺子的出牌规则为,由至少 5 张由小到大连续递增的扑克牌组成 且不能包含2 例如:{3,4,5,6,7}、{3,4,5,6,7,8,9,1…...
秒杀项目之消息推送
目录一、创建消费者二、创建订单链路配置2.1 定义RabbitMQ配置类2.2 创建RabbitmqOrderConfig配置类三、如何实现RabbitMQ重复投递机制3.1 开启发送者消息确认模式3.2 消费发送确认3.2.1 创建ConfirmCallBack确认模式3.2.2 创建ReturnCallBack退回模式3.3 创建生产者3.4 创建消…...
【重磅】IEEE33配电网两阶段鲁棒优化调度CCG
目录 1 前言 2基本内容 2.1 配网两阶段鲁棒模型 2.2 求解步骤 3部分程序 4程序结果 5程序链接 1 前言 鲁棒优化是电力系统研究的热点,而两阶段鲁棒和分布鲁棒研究就成为各类期刊(sci/ei/核心)的宠儿,最简单的思路是通过改…...
GPT2代码拆解+生成实例
本文代码来自博客,GPT2模型解析参考 import torch import copy import torch.nn as nn import torch.nn.functional as F from torch.nn.modules import ModuleList from torch.nn.modules.normalization import LayerNorm import numpy as np import os from tqd…...
基于android的即时通讯APP 聊天APP
基于android的即时通讯APP 或者 聊天APP 一 项目概述 该项目是基于Android 的聊天APP系统,该APP包含前台,后台管理系统,前台包含用户通讯录,用户详情,用户聊天服务,用户二维码,发现功能,发现详情 , 个人中心, 个人信…...
【C++】二叉树之力扣经典题目1——详解二叉树的递归遍历,二叉树的层次遍历
如有错误,欢迎指正。 如有不理解的地方,可以私信问我。 文章目录题目1:根据二叉树创建字符串题目实例思路与解析代码实现题目2:二叉树的层序遍历题目思路与解析代码实现题目1:根据二叉树创建字符串 点击进入题目链接—…...
MySQL数据库调优————SQL性能分析
TIPS 本文基于MySQL 8.0 本文探讨如何深入SQL内部,去分析其性能,包括了三种方式: SHOW PROFILEINFORMATION_SCHEMA.PROFILINGPERFORMANCE_SCHEMA SHOW PROFILE SHOW PROFILE是MySQL的一个性能分析命令,可以跟踪SQL各种资源消耗。…...
sql数据库高级编程总结(一)
1、数学函数:操作一个数据,返回一个结果 (1)取上限 ceiling 如果有一个小数就取大于它的一个最小整数 列如9.5 就会取到 10 select code,name,ceiling(price) from car (2)取下限 floor 如果有一个小数就…...
软件工程(5)--喷泉模型
前言 这是基于我所学习的软件工程课程总结的第五篇文章。 迭代是软件开发过程中普遍存在的一种内在属性。经验表明,软件过程各个阶段之间的迭代或一个阶段内各个工作步骤之间的迭代,在面向对象范型中比在结构化范型中更常见。 一般说来,使用…...
SM2数字签名
文章目录6. 签名流程7. 验签流程实现参考资料6. 签名流程 M’ ZA || Msge Hash(M’),并转为大数;生成随机数k,范围0<k<n;计算kG (x1, y1)r (e x1) mod n, 若r0或(rkn)则重新生成k;s (k-rd) / (1d) mod n&…...
RPA+保险后台部门擦出不一样“火花” | RPA案例
在保险行业中,后台业务线主要是为前台和中台等提供支持,提供公司整体运营服务,包括财务、信息、人力、综合办等。相对于中前台部门,后台部门离核心价值链更远一些,更偏支持部门,其中某些岗位与业务相关度强…...
设备树相关概念的理解
设备树 定义 设备树是描述硬件信息的一种树形结构,设备树文件会在内核启动后被内核解析得到对应设备的具体信息。 树形结构就自然会存在节点,硬件设备信息就存储再设备树中的节点上,即设备节点。而一个设备节点中可以存储硬件的多个不同属性…...
ubuntu20.04下配置深度学习环境GPU
卸载子系统 C:\Users\thzn>wsl --list 适用于 Linux 的 Windows 子系统分发版: docker-desktop (默认) docker-desktop-data Ubuntu-18.04 Ubuntu-22.04 Ubuntu-20.04 C:\Users\thzn>wsl --unregister Ubuntu-18.04 ubuntu 换源 https://www.cnblogs.com/Horizon-asd/p…...
用egg.js来写一个api管理系统(一)
Egg.js是一个基于Node.js的企业级开发框架,非常适合构建API服务。 安装egg.js 首先,您需要安装Node.js和npm(Node Package Manager)。然后,您可以通过运行以下命令来安装Egg.js: npm i egg --save然后&a…...
企业数字化转型和升级:架构设计方法与实践
目录 企业架构整体结构 企业架构的驱动力 企业架构的基本概念 企业架构的发展 企业架构框架理论 主流企业架构框架之对比 企业架构整体结构 图例:企业架构整体结构 企业架构整体结构从战略层、规划层、落地层这三层来分别对应企业架构中 业务、架构和实施的各种重要…...
【LeetCode】环形链表 II [M](链表)
142. 环形链表 II - 力扣(LeetCode) 一、题目 给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链…...
网站建设属于什么支出/杭州seo排名优化外包
为什么80%的码农都做不了架构师?>>> 常见的数据库优化方法:索引(数据库),缓存,分表,分库,sql优化。 索引:创建索引一般有以下两个目的:维护被索引…...
网站一定也做数据库吗/一篇好的营销软文
单行overflow: hidden;text-overflow: ellipsis;white-space: nowrap;多行(兼容各个浏览器)//通过覆盖最后几个字的形式p{position:relative;line-height:1.4em;height:4.2em;/* 3 倍line-height 多少倍就是多少行*/overflow:hidden;}.p::after {content:"...";font-…...
如何用公司名称搜到公司网站/免费的推广平台
女朋友问我淘宝网国际站的地址是什么,她想看看海淘上面都有哪些东西,于是我把网址:https://world.taobao.com 发给了她。于是…… 首先介绍下到底什么是域名,然后再来介绍域名的各个组成部分。 域名 网域名称(英语&a…...
石家庄网站建设平台有哪些/百度推广怎么才能效果好
文章目录套接字相关接口(一)创建套接字socket(二)套接字地址(三)命名套接字bind(四)创建监听队列(套接字队列)listen(五)接收连接acce…...
大良o2o网站建设/百度推广客服投诉电话
QC 使用中问题点汇总,包括以下方面: 1、不兼容IE7,IE8的问题(服务器端设置) 2、无法在Win 7下正常下载页面(客户端设置) 3、在QC中填写中文内容后无法正常提交到数据库(客户端设置) 4、在QC中填写中文内容出现乱码的现象(待修改)…...
电子商务网站建设与运维论文/app推广平台网站
—— 红帽宣布2009年虚拟化策略和产品计划【IT168 专稿】在经济环境不确定的今天,虚拟化就像一棵救命稻草。这棵“稻草”不仅对于厂商来说意义非凡,对于IT预算面临紧缩困境的用户也同样充满诱惑力。2008年,虚拟化市场显得非同寻常的热闹&…...