当前位置: 首页 > article >正文

5.OpenGL之uniform

在OpenGL中uniform是一种着色器程序中的变量类型存储限定符。简单来说可以把 uniform 理解为从CPU端你的C/Qt代码向GPU端着色器程序发送的一个“全局只读”参数。下面从几个维度来详细解释它的作用、特点和使用场景1. 核心作用传递“不变”的数据着色器Shader是运行在GPU上的小程序。当我们需要绘制一个物体时有些数据是每个顶点都不同的例如顶点的位置这些通常用attribute或in传递而有些数据是所有顶点或所有片段像素都共用的。uniform 就是用来传递这些“共用”数据的。例子如果我们想让一个三角形显示为红色这个“红色”的值只需要告诉GPU一次三角形的所有像素都应该使用这个红色这个颜色值就应该用uniform传递就不需要给每个顶点都设置颜色。如果我们给模型加了一个“亮度”系数这个系数也应该是 uniform。2. 关键特性全局性uniform 变量在着色器程序的每一次执行即每一个顶点或每一个片段中都保持同一个值。只读性你可以在着色器里读取它但不能在着色器里修改它。修改它的唯一途径是通过CPU端的OpenGL API应用程序编程接口调用。持久性一旦你设置了一个 uniform 的值它会一直保存在着色器程序中直到你下次修改它或者程序结束。3. 在QOpenGL中的具体用法在QOpenGL中操作 uniform 通常遵循以下三步曲步骤 1在着色器代码中声明在顶点着色器.vert或片段着色器.frag中你需要定义 uniform 变量。片段着色器示例 (fragment shader)#version 330 core uniform vec4 ourColor; // 声明一个uniform变量类型为4维向量RGBA out vec4 FragColor; void main() { FragColor ourColor; // 将这个全局颜色赋值给输出 }步骤 2获取 uniform 变量的位置ID在C代码中你需要找到这个变量在着色器程序中的索引位置。// 假设 m_program 是 QOpenGLShaderProgram 对象 int uniformLocation m_program-uniformLocation(ourColor);步骤 3上传数据到 GPU使用QOpenGLShaderProgram提供的便捷方法将数据从CPU发送到这个位置。// 设置颜色为半透明的绿色 GLfloat greenColor[4] {0.0f, 1.0f, 0.0f, 0.5f}; // 绑定着色器程序 m_program-bind(); // 将数组数据上传到 uniform 位置 m_program-setUniformValue(uniformLocation, greenColor); // 或者直接使用 QColor m_program-setUniformValue(uniformLocation, QColor(0, 255, 0, 128)); // 现在开始绘制... 绘制的所有物体都会使用这个绿色注意setUniformValue必须在shader调用bind后才会生效且上传的数据类型必须和着色器内的代码保持一致。4. uniform 到底传什么uniform 可以传递的数据类型非常丰富涵盖了图形编程的大部分需求基础类型int,float,bool向量类型vec2,vec3,vec4(对应QVector2D, QVector3D, QVector4D, QColor)矩阵类型mat2,mat3,mat4(对应QMatrix2x2, QMatrix4x4) —— 常用于传递模型、视图、投影矩阵。采样器sampler2D,samplerCube等。这是一种特殊的uniform用于告诉着色器使用哪一个纹理单元。5. 总结为什么需要 uniform想象一下你要用3D画笔画一幅画顶点属性 (Attribute)告诉你笔尖要经过的每一个点在哪里。Uniform告诉你现在这支笔是什么颜色、用的是多粗的笔刷、当前画布旋转了多少度。如果没有 uniform你就只能把颜色值硬编码到着色器里或者为每一个顶点都附带一个颜色信息这会造成巨大的内存浪费和性能下降。uniform 提供了一种高效、灵活的方式来控制渲染管线的全局状态。6.Qt OpenGL 与 GLSL 数据类型对应表1. 向量类型最常用Qt 类型GLSL 类型分量含义XYZW/RGBA作用QVector2Dvec2(x, y)2D 坐标、纹理坐标QVector3Dvec3(x, y, z)3D 坐标、RGB 颜色QVector4Dvec4(x, y, z, w)带 Alpha 的颜色、齐次坐标2. 矩阵类型Qt 类型GLSL 类型作用QMatrix4x4mat44x4 矩阵MVP 矩阵、变换矩阵QMatrix3x3mat33x3 矩阵旋转、法线矩阵3. 标量普通数值Qt 类型GLSL 类型作用floatfloat小数、强度、透明度intint整数、索引boolbool开关赋值方法// 2D坐标 QVector2D pos(100, 200); shader-setUniformValue(u_pos, pos); // 颜色 QVector4D color(1.0f, 0.0f, 0.0f, 1.0f); shader-setUniformValue(u_color, color); // 矩阵 QMatrix4x4 mat; mat.setToIdentity(); shader-setUniformValue(u_matrix, mat);7.具体应用示例1.uniform传颜色之前shader程序像流水线一样颜色从顶点shader传到片段shader#version 330 core layout(location0) in vec2 aPos; layout(location1) in vec3 aColor; out vec4 fragColor; void main() { gl_Positionvec4(aPos,0.0,1.0); fragColor(aColor,1.0); } //像流水线一样 #version 330 core in vec4 fragColor; out vec4 u_Color; void main() { u_ColorfragColor; }或下面这样把颜色写死了shader初始化后颜色就不能被改变了#version 330 core layout(location0) in vec2 aPos; void main() { gl_Positionvec4(aPos,0.0,1.0); } #version 330 core out vec4 u_Color; void main() { u_Colorvec4(1.0,0.0,0.0,1.0); }现在使用uniform对比之前就像全局变量对比函数参数。直接外部设置全局变量。#version 330 core layout(location0) in vec2 aPos; void main() { gl_Positionvec4(aPos,0.0,1.0); } #version 330 core uniform vec4 ourColor; out vec4 outColor; void main() { outColorourColor; }m_shadernew QOpenGLShaderProgram(this); m_shader-addShaderFromSourceCode(QOpenGLShader::Vertex,vertexShader); m_shader-addShaderFromSourceCode(QOpenGLShader::Fragment,fragShader); m_shader-link(); m_locm_shader-uniformLocation(ourColor);//获取uniform的位置方便后面直接通过loc设置完整代码创建定时器来改变颜色#ifndef GLWIDGET_H #define GLWIDGET_H #include QOpenGLWidget #include QOpenGLBuffer #include QOpenGLShaderProgram #include QOpenGLVertexArrayObject #include QOpenGLFunctions_3_3_Core #include QVector4D class GLWidget : public QOpenGLWidget,private QOpenGLFunctions_3_3_Core { public: GLWidget(QWidget *parent); void initializeGL(); void paintGL(); void resizeGL(int w,int h); void initRect(); void initShader(); private: QOpenGLBuffer *m_vbo; QOpenGLVertexArrayObject *m_vao; QOpenGLShaderProgram *m_shader; int m_loc; QVector4D m_colorQVector4D(1.0f,0.0f,0.0f,1.0f); bool m_isRedfalse; }; #endif // GLWIDGET_H#include GLWidget.h #include QTimer GLWidget::GLWidget(QWidget *parent):QOpenGLWidget(parent) { QTimer *timernew QTimer(this); timer-setInterval(1000); connect(timer,QTimer::timeout,this,[](){ if(m_isRed) { m_colorQVector4D(0.0f,1.0f,0.0f,1.0f); m_isRedfalse; } else { m_colorQVector4D(1.0f,0.0f,0.0f,1.0f); m_isRedtrue; } update(); }); timer-start(); } void GLWidget::initializeGL() { initializeOpenGLFunctions(); initRect(); initShader(); } void GLWidget::paintGL() { glClearColor(0.1,0.1,0.1,1.0); glClear(GL_COLOR_BUFFER_BIT); m_vao-bind(); m_shader-bind(); m_shader-setUniformValue(m_loc,m_color); glDrawArrays(GL_TRIANGLE_FAN,0,4); } void GLWidget::resizeGL(int w,int h) { m_shader-bind(); m_vao-bind(); } void GLWidget::initRect() { float vertex[]{ -0.5f,0.5f, 0.5f,0.5f, 0.5f,-0.5f, -0.5f,-0.5f }; m_vaonew QOpenGLVertexArrayObject(this); m_vao-create(); m_vao-bind(); m_vbonew QOpenGLBuffer(QOpenGLBuffer::VertexBuffer); m_vbo-create(); m_vbo-bind(); m_vbo-allocate(vertex,sizeof(vertex)); glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,sizeof(float)*2,(void*)0); glEnableVertexAttribArray(0); m_vbo-release(); m_vao-release(); } void GLWidget::initShader() { char *vertexShaderR( #version 330 core layout(location0) in vec2 aPos; void main() { gl_Positionvec4(aPos,0.0,1.0); } ); char *fragShaderR( #version 330 core uniform vec4 ourColor; out vec4 outColor; void main() { outColorourColor; } ); m_shadernew QOpenGLShaderProgram(this); m_shader-addShaderFromSourceCode(QOpenGLShader::Vertex,vertexShader); m_shader-addShaderFromSourceCode(QOpenGLShader::Fragment,fragShader); m_shader-link(); m_locm_shader-uniformLocation(ourColor); }效果2.uniform开关-2选1通过外部传入bool变量在shader决定跑哪个分支参考上面示例主要修改以下代码connect(timer,QTimer::timeout,this,[](){ if(m_isRed) { m_isRedfalse; } else { m_isRedtrue; } update(); });void GLWidget::paintGL() { glClearColor(0.1,0.1,0.1,1.0); glClear(GL_COLOR_BUFFER_BIT); m_vao-bind(); m_shader-bind(); m_shader-setUniformValue(m_loc,m_isRed); glDrawArrays(GL_TRIANGLE_FAN,0,4); }void GLWidget::initShader() { char *vertexShaderR( #version 330 core layout(location0) in vec2 aPos; void main() { gl_Positionvec4(aPos,0.0,1.0); } ); char *fragShaderR( #version 330 core uniform bool u_isRed; out vec4 outColor; void main() { if(u_isRed) { outColorvec4(1.0,0.0,0.0,1.0); } else { outColorvec4(0.0,1.0,0.0,1.0); } } ); m_shadernew QOpenGLShaderProgram(this); m_shader-addShaderFromSourceCode(QOpenGLShader::Vertex,vertexShader); m_shader-addShaderFromSourceCode(QOpenGLShader::Fragment,fragShader); m_shader-link(); m_locm_shader-uniformLocation(u_isRed); }效果跟上面示例一样的3.uniform矩阵-控制位置MVP引入MVP是什么意思MModel 模型变换位移旋转缩放VView 相机 2D一般不用PProjection 投影 正交投影2D必备QMatrix4x4 mvp;创建了一个4x4变换矩阵刚创建时是单位矩阵相当于数字1乘了等于没乘mvp.ortho( 0, //left 左边x0 width(), //right 右边x窗口宽度 height(), //bottom 底部y窗口高度 0, //top 顶部y0 -1, //zNear Z近平面 1, //zFar Z远平面 );最后两个参数是z轴方向上的可见范围在2D渲染里所有的Z都是0所以写-1,1永远没问题。定义了一个“像素坐标系”窗口左上角(0,0)窗口右下角(width(),height())和Qt原生坐标完全一致和图像坐标也一致shader-setUniformValue(u_mvp,mvp);作用把CPU的QMatrix4x4——传给GPU着色器里面的mat4 u_map对应uniform vec4 u_mvp;不是传的具体坐标而是位置变换规则用于给aPos进行坐标变换。物体本身的顶点坐标永远不变 → 用矩阵把它 “移动 / 旋转 / 缩放” 到目标位置#version 330 core layout(location0)in vec2 aPos; uniform mat4 u_mvp; void main() { gl_Positionu_mvp*vec4(aPos,0.0,1.0); }含义顶点像素坐标xMVP矩阵OpenGL能识别的标准坐标。只要传:float rect[]{ 50,50, 150,50, 150,150, 50,150 };就可以直接在窗口的(50,50)位置画一个100*100的矩形。#ifndef GLWIDGET_H #define GLWIDGET_H #include QOpenGLWidget #include QOpenGLBuffer #include QOpenGLShaderProgram #include QOpenGLVertexArrayObject #include QOpenGLFunctions_3_3_Core #include QVector4D #include QMatrix4x4 class GLWidget : public QOpenGLWidget,private QOpenGLFunctions_3_3_Core { public: GLWidget(QWidget *parent); void initializeGL(); void paintGL(); void resizeGL(int w,int h); void initRect(); void initShader(); private: QOpenGLBuffer *m_vbo; QOpenGLVertexArrayObject *m_vao; QOpenGLShaderProgram *m_shader; int m_loc; QVector4D m_colorQVector4D(1.0f,0.0f,0.0f,1.0f); bool m_isRedfalse; int m_loc1; QMatrix4x4 m_mvp; }; #endif // GLWIDGET_H#include GLWidget.h #include QTimer GLWidget::GLWidget(QWidget *parent):QOpenGLWidget(parent) { QTimer *timernew QTimer(this); timer-setInterval(1000); connect(timer,QTimer::timeout,this,[](){ if(m_isRed) { m_isRedfalse; } else { m_isRedtrue; } update(); }); timer-start(); } void GLWidget::initializeGL() { initializeOpenGLFunctions(); initRect(); initShader(); m_mvp.ortho(0,width(),height(),0,-1,1); } void GLWidget::paintGL() { glClearColor(0.1,0.1,0.1,1.0); glClear(GL_COLOR_BUFFER_BIT); m_vao-bind(); m_shader-bind(); m_shader-setUniformValue(m_loc,m_isRed); m_shader-setUniformValue(m_loc1,m_mvp); glDrawArrays(GL_TRIANGLE_FAN,0,4); } void GLWidget::resizeGL(int w,int h) { } void GLWidget::initRect() { float vertex[]{ 50,50, 150,50, 150,150, 50,150 }; m_vaonew QOpenGLVertexArrayObject(this); m_vao-create(); m_vao-bind(); m_vbonew QOpenGLBuffer(QOpenGLBuffer::VertexBuffer); m_vbo-create(); m_vbo-bind(); m_vbo-allocate(vertex,sizeof(vertex)); glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,sizeof(float)*2,(void*)0); glEnableVertexAttribArray(0); m_vbo-release(); m_vao-release(); } void GLWidget::initShader() { char *vertexShaderR( #version 330 core layout(location0) in vec2 aPos; uniform mat4 u_mvp; void main() { gl_Positionu_mvp*vec4(aPos,0.0,1.0); } ); char *fragShaderR( #version 330 core uniform bool u_isRed; out vec4 outColor; void main() { if(u_isRed) { outColorvec4(1.0,0.0,0.0,1.0); } else { outColorvec4(0.0,1.0,0.0,1.0); } } ); m_shadernew QOpenGLShaderProgram(this); m_shader-addShaderFromSourceCode(QOpenGLShader::Vertex,vertexShader); m_shader-addShaderFromSourceCode(QOpenGLShader::Fragment,fragShader); m_shader-link(); m_locm_shader-uniformLocation(u_isRed); m_loc1m_shader-uniformLocation(u_mvp); }再来看下面这个例子在窗体的右下角1/4区域以这个区域作为一个窗体显示一个矩形:float vertex[]{ -0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f };这个适合就需要坐标变换了传入的是相对的位置新的原点到了右下角1/4区域的中点就是把原来的原点移动到右下角:translate0.5f,-0.5f:x0.5f(右移0.5f),y0.5f(下移0.5f)。大小也需要变成原来的1/4即宽高各缩小1/2scale( 0.5f, 0.5f )这个时候再传入-1,1之间的数就会在右下角显示了#ifndef GLWIDGET_H #define GLWIDGET_H #include QOpenGLWidget #include QOpenGLBuffer #include QOpenGLShaderProgram #include QOpenGLVertexArrayObject #include QOpenGLFunctions_3_3_Core #include QVector4D #include QMatrix4x4 class GLWidget : public QOpenGLWidget,private QOpenGLFunctions_3_3_Core { public: GLWidget(QWidget *parent); void initializeGL(); void paintGL(); void resizeGL(int w,int h); void initRect(); void initShader(); private: QOpenGLBuffer *m_vbo; QOpenGLVertexArrayObject *m_vao; QOpenGLShaderProgram *m_shader; int m_loc; QVector4D m_colorQVector4D(1.0f,0.0f,0.0f,1.0f); bool m_isRedfalse; int m_loc1; QMatrix4x4 m_mvp; }; #endif // GLWIDGET_H#include GLWidget.h #include QTimer GLWidget::GLWidget(QWidget *parent):QOpenGLWidget(parent) { QTimer *timernew QTimer(this); timer-setInterval(1000); connect(timer,QTimer::timeout,this,[](){ if(m_isRed) { m_isRedfalse; } else { m_isRedtrue; } update(); }); timer-start(); } void GLWidget::initializeGL() { initializeOpenGLFunctions(); initRect(); initShader(); // 第一步把取景框缩放到 1/4 大小并移动到右下角 m_mvp.translate( 0.5f, -0.5f ); // 坐标原点移到右下角 m_mvp.scale( 0.5f, 0.5f ); // 视口大小变成 1/4 // 第二步在这个右下角小窗口里使用 -1 ~ 1 坐标系 m_mvp.ortho( -1, 1, 1, -1, -1, 1 );// } void GLWidget::paintGL() { glClearColor(0.1,0.1,0.1,1.0); glClear(GL_COLOR_BUFFER_BIT); m_vao-bind(); m_shader-bind(); m_shader-setUniformValue(m_loc,m_isRed); m_shader-setUniformValue(m_loc1,m_mvp); glDrawArrays(GL_TRIANGLE_FAN,0,4); } void GLWidget::resizeGL(int w,int h) { } void GLWidget::initRect() { float vertex[]{ -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5 }; m_vaonew QOpenGLVertexArrayObject(this); m_vao-create(); m_vao-bind(); m_vbonew QOpenGLBuffer(QOpenGLBuffer::VertexBuffer); m_vbo-create(); m_vbo-bind(); m_vbo-allocate(vertex,sizeof(vertex)); glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,sizeof(float)*2,(void*)0); glEnableVertexAttribArray(0); m_vbo-release(); m_vao-release(); } void GLWidget::initShader() { char *vertexShaderR( #version 330 core layout(location0) in vec2 aPos; uniform mat4 u_mvp; void main() { gl_Positionu_mvp*vec4(aPos,0.0,1.0); } ); char *fragShaderR( #version 330 core uniform bool u_isRed; out vec4 outColor; void main() { if(u_isRed) { outColorvec4(1.0,0.0,0.0,1.0); } else { outColorvec4(0.0,1.0,0.0,1.0); } } ); m_shadernew QOpenGLShaderProgram(this); m_shader-addShaderFromSourceCode(QOpenGLShader::Vertex,vertexShader); m_shader-addShaderFromSourceCode(QOpenGLShader::Fragment,fragShader); m_shader-link(); m_locm_shader-uniformLocation(u_isRed); m_loc1m_shader-uniformLocation(u_mvp); }效果再添加一个m_mvp1:void GLWidget::initializeGL() { initializeOpenGLFunctions(); initRect(); initShader(); // 第一步把取景框缩放到 1/4 大小并移动到右下角 m_mvp.translate( 0.5f, -0.5f ); // 坐标原点移到右下角 m_mvp.scale( 0.5f, 0.5f ); // 视口大小变成 1/4 // 第二步在这个右下角小窗口里使用 -1 ~ 1 坐标系 m_mvp.ortho( -1, 1, 1, -1, -1, 1 );// m_mvp1.translate( -0.5f, -0.5f ); // 坐标原点移到左下角 m_mvp1.scale( 0.5f, 0.5f ); // 视口大小变成 1/4 // 第二步在这个右下角小窗口里使用 -1 ~ 1 坐标系 m_mvp1.ortho( -1, 1, 1, -1, -1, 1 );// }void GLWidget::paintGL() { glClearColor(0.1,0.1,0.1,1.0); glClear(GL_COLOR_BUFFER_BIT); m_vao-bind(); m_shader-bind(); m_shader-setUniformValue(m_loc,m_isRed); m_shader-setUniformValue(m_loc1,m_mvp); glDrawArrays(GL_TRIANGLE_FAN,0,4); m_shader-setUniformValue(m_loc,m_isRed); m_shader-setUniformValue(m_loc1,m_mvp1); glDrawArrays(GL_TRIANGLE_FAN,0,4); }效果就变成ortho代表你要建立什么样的坐标系1.08000600-11(0,600) (800,600) ┌──────────────────────┐ │ │ │ │ │ │ │ │ │ │ │ │ └──────────────────────┘ (0,0) (800,0)2.08006000-11(0,0) (800,0) ┌──────────────────────┐ │ │ │ │ │ │ │ │ │ │ │ │ └──────────────────────┘ (0,600) (800,600)重要矩阵变换在着色器代码中矩阵变换采用左乘在代码里面的顺序就是按照相反的顺序执行的比如下面这几行代码QMatrix4x4 mat4; mat4.setToIdentity();//重置 mat4.translate(...); mat4.scale(...); mat4.ortho(...);那么着色器中实际的生效顺序就是ortho(正交投影)-scale(缩放)-translate(y移动)你可能会疑惑不都是执行了移动缩放投影吗最后结果还能不一样还真是不一样。举个实际的例子假如窗口的宽为800高为600在正中间有一个小框宽为300需要进行缩放到右边咋一看是先缩放再平移或者先平移再缩放最后都能得到这个结果但是在矩阵数学计算中不是想当然的而是要看数据的实际变化。原左上角顶点位置300200宽200 高 200先投影看看正常的位置m_mvp.ortho( 0, 800, 600, 0, -1, 1 );// float vertex[]{ 300,200, 500,200, 500,400, 300,400 };300200先平移显然不是因为现在由于比例问题不太好移动300200先缩放必然会缩放到原来的一半即150100到了窗口的第一块区域的正中间位置但是这个时候窗口坐标是-11之间所以要进行正交投影。将150100在800600上的相对位置转到-11上来。经过上面变换现在坐标的范围就在-11之间了然后再进行平移右移1.0下移动1.0就可以得到右边的效果了。m_mvp.translate( 1.0, -1.0 ); // 坐标原点移到右下角 m_mvp.ortho( 0, 800, 600, 0, -1, 1 );// m_mvp.scale( 0.5f, 0.5f ); // 视口大小变成 1/4那如果是先投影呢300200在800 600上的投影得到了具体的图像位置这个时候点大概是-0.50.66左右也就是左边图像的效果然后尝试进行平移但是发现平移在缩放之前不好判断那就先缩放到原来的1/2这个时候点来到-0.250.33左右这个时候再去平移看中点的坐标变化就知道该怎么移动啦中点00——0.5-0.5那就是x移动0.5y移动-0.5,最后也可以得到右边的效果。m_mvp.translate( 0.5, -0.5 ); m_mvp.scale( 0.5f, 0.5f ); // 视口大小变成 1/4 m_mvp.ortho( 0, 800, 600, 0, -1, 1 );//效果跟上面一样的。4.uniform传纹理这里传uniform并不是传的真正的纹理因为纹理对象是单独的对象保存纹理对象自身调用setDate时会在GPU上分配一块显存保存纹理信息然后需要调用bind(n)将纹理信息绑定到n号纹理单元这个n就与着色器相关着色器怎么知道去GPU上面找哪块纹理单元就靠这个uniform来确定操作哪个纹理单元了。比如shader-setUniform(u_tex0,0);在着色器中使用这个u_tex0全局变量就表示去0号纹理单元找那个纹理。shader-setUniform(u_tex1,1);在着色器中使用这个u_tex1全局变量就表示去1号纹理单元找那个纹理。之前没有给着色器设置这个是因为纹理对象默认bind会绑定到0号纹理单元着色器程序也默认去0号纹理单元取出纹理。实际应用如果着色器只需要同时操作一个纹理不需要对他们进行融合等操作那么就不用设置这个纹理uniform了下面这个例子就展示了如何使用这个uniform传纹理的实际运用。你看到了吗图片里面有一只猫和一辆车的融合就是一次shader跑出来的代码见下方。#include GLWidget.h #include QTimer GLWidget::GLWidget(QWidget *parent):QOpenGLWidget(parent) { } void GLWidget::initializeGL() { initializeOpenGLFunctions(); initRect(); initShader(); m_tex0new QOpenGLTexture(QOpenGLTexture::Target2D); m_tex0-setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); m_tex0-setMagnificationFilter(QOpenGLTexture::Linear); m_tex0-setWrapMode(QOpenGLTexture::ClampToEdge); m_tex0-setData(QImage(car.jpg)); m_tex1new QOpenGLTexture(QOpenGLTexture::Target2D); m_tex1-setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); m_tex1-setMagnificationFilter(QOpenGLTexture::Linear); m_tex1-setWrapMode(QOpenGLTexture::ClampToEdge); m_tex1-setData(QImage(cat.jpg)); } void GLWidget::paintGL() { glClearColor(0.1,0.1,0.1,1.0); glClear(GL_COLOR_BUFFER_BIT); m_tex0-bind(0); m_tex1-bind(1); m_vao-bind(); m_shader-bind(); m_shader-setUniformValue(m_shader-uniformLocation(u_tex0),0); m_shader-setUniformValue(m_shader-uniformLocation(u_tex1),1); glDrawArrays(GL_TRIANGLE_FAN,0,4); } void GLWidget::resizeGL(int w,int h) { } void GLWidget::initRect() { float vertex[]{ -0.5, -0.5, 0.0,1.0, 0.5, -0.5, 1.0,1.0, 0.5, 0.5, 1.0,0.0, -0.5, 0.5, 0.0,0.0 }; m_vaonew QOpenGLVertexArrayObject(this); m_vao-create(); m_vao-bind(); m_vbonew QOpenGLBuffer(QOpenGLBuffer::VertexBuffer); m_vbo-create(); m_vbo-bind(); m_vbo-allocate(vertex,sizeof(vertex)); glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,sizeof(float)*4,(void*)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,sizeof(float)*4,(void*)(sizeof(float)*2)); glEnableVertexAttribArray(1); m_vbo-release(); m_vao-release(); } void GLWidget::initShader() { char *vertexShaderR( #version 330 core layout(location0) in vec2 aPos; layout(location1) in vec2 aTexcoord; out vec2 ourTexcoord; void main() { gl_Positionvec4(aPos,0.0,1.0); ourTexcoordaTexcoord; } ); char *fragShaderR( #version 330 core in vec2 ourTexcoord; uniform sampler2D u_tex0; uniform sampler2D u_tex1; out vec4 outColor; void main() { vec4 color0texture(u_tex0,ourTexcoord); vec4 color1texture(u_tex1,ourTexcoord); outColorcolor0color1; } ); m_shadernew QOpenGLShaderProgram(this); m_shader-addShaderFromSourceCode(QOpenGLShader::Vertex,vertexShader); m_shader-addShaderFromSourceCode(QOpenGLShader::Fragment,fragShader); m_shader-link(); }#ifndef GLWIDGET_H #define GLWIDGET_H #include QOpenGLWidget #include QOpenGLBuffer #include QOpenGLShaderProgram #include QOpenGLVertexArrayObject #include QOpenGLFunctions_3_3_Core #include QOpenGLTexture class GLWidget : public QOpenGLWidget,private QOpenGLFunctions_3_3_Core { public: GLWidget(QWidget *parent); void initializeGL(); void paintGL(); void resizeGL(int w,int h); void initRect(); void initShader(); private: QOpenGLBuffer *m_vbo; QOpenGLVertexArrayObject *m_vao; QOpenGLShaderProgram *m_shader; QOpenGLTexture *m_tex0; QOpenGLTexture *m_tex1; }; #endif // GLWIDGET_H5.其他.....(用到再学吧写的太乱了这篇后面想单独把矩阵变换做一章内容

相关文章:

5.OpenGL之uniform

在OpenGL中,uniform 是一种着色器程序中的变量类型(存储限定符)。简单来说,可以把 uniform 理解为:从CPU端(你的C/Qt代码)向GPU端(着色器程序)发送的一个“全局只读”参数…...

保姆级教程:用GParted Live USB无损调整Windows磁盘分区(含安全操作指南)

零风险实战:用GParted Live USB拯救你的Windows磁盘空间 每次打开电脑看到C盘飘红的剩余空间,是不是有种窒息感?系统运行越来越慢,新软件装不下,临时文件不敢删——这种困境我太熟悉了。三年前我的开发机C盘只剩500MB时…...

从x86架构到接口技术:微机原理实战笔记(含汇编语言编程示例)

从x86架构到接口技术:微机原理实战笔记(含汇编语言编程示例) 1. 理解计算机的"心脏":x86微处理器架构解析 在计算机科学领域,x86架构就像一座精密的瑞士钟表,每一个齿轮的转动都遵循着严格的物理…...

常见的8个Jmeter压测问题及解决方法

🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 为什么在JMeter中执行压力测试时,出现连接异常或连接重置错误?答案:连接异常或连接重置错误通常是由于服务器在处理请求时出现问…...

嵌入式系统开发知识体系:从硬件抽象到RTOS与Linux驱动

1. 嵌入式系统开发知识体系构建:从硬件底层到软件架构的工程实践指南嵌入式系统开发并非零散技术点的简单堆砌,而是一个需要系统性思维与工程化方法支撑的知识体系。本文基于多年一线嵌入式项目实践,对涵盖微控制器底层驱动、实时操作系统内核…...

Qwen3.5-9B开源大模型部署指南:9B参数量+CUDA加速+Gradio开箱即用

Qwen3.5-9B开源大模型部署指南:9B参数量CUDA加速Gradio开箱即用 1. 引言:为什么选择Qwen3.5-9B 想快速部署一个强大又高效的开源大模型吗?Qwen3.5-9B可能是你当前最理想的选择。这个拥有90亿参数的模型在保持轻量级的同时,通过C…...

JBoltAI智教小工坊:AI赋能教育的技术落地与价值体现

在AI技术向各行业深度渗透的当下,教育领域的数字化转型亟需技术与场景的深度融合,Java生态作为企业级开发的核心支撑,为教育AI应用落地筑牢了技术底座。JBoltAI以企业级Java AI应用开发框架为核心,构建起AIGS(人工智能…...

VirtualBox安装CachyOS避坑指南:EFI设置与GRUB修复全流程

VirtualBox安装CachyOS实战指南:从EFI配置到系统调优 在开源社区中,CachyOS凭借其基于Arch Linux的轻量级设计和性能优化,正吸引着越来越多的技术爱好者。而VirtualBox作为最受欢迎的虚拟化解决方案之一,为体验各种Linux发行版提供…...

ESP32异步MQTT客户端:QoS2/SSL/WSS全协议支持

1. PsychicMqttClient:面向ESP32全功能异步MQTT客户端深度解析1.1 项目定位与工程价值PsychicMqttClient并非又一个轻量级MQTT封装,而是在ESP-IDF原生MQTT客户端基础上构建的工业级异步通信中间件。其核心价值在于填补了ESP32生态中长期存在的三大技术空…...

YOLOv11网络结构拆解:从Anchor生成到损失计算的保姆级图解

YOLOv11架构深度解析:从Anchor机制到损失函数的全链路实现 在计算机视觉领域,目标检测算法的发展日新月异。作为YOLO系列的最新成员,YOLOv11凭借其卓越的性能和工程友好性,正在成为工业界和学术界的热门选择。本文将带您深入YOLOv…...

Emgu CV实战:用VideoCapture类快速实现摄像头监控(附常见报错解决)

Emgu CV实战:从零搭建智能摄像头监控系统 最近在帮朋友改造他的小型工作室安防系统时,我重新审视了Emgu CV这个强大的.NET图像处理库。作为OpenCV的.NET封装,Emgu CV让C#开发者也能轻松实现复杂的计算机视觉应用。本文将分享如何用VideoCaptu…...

VS Code 录屏模式:让你的教程像电影一样专业

推荐阅读 技术总监悄悄秀了一把 VS Code 神技,被我狠狠学到了! VS Code 又发布了一个 Agent 新玩具! VS Code 1.110 官宣 AI 新特性:AI 直接调试浏览器! VS Code 2026 效率秘籍:学完无敌&#xff01…...

安卓应用开发中自定义 View 绘制性能差问题详解及解决方案

目录安卓应用开发中自定义 View 绘制性能差问题详解及解决方案一、问题现象二、Android 绘制机制回顾2.1 绘制流程2.2 垂直同步与 16ms 原则2.3 硬件加速三、产生原因深度分析3.1 在 onDraw 中创建对象3.2 频繁调用 invalidate3.3 复杂绘制操作3.4 忽略硬件加速限制3.5 未使用局…...

OpenLayers实战:5分钟搞定WMTS地图服务参数解析(含天地图示例)

OpenLayers实战:5分钟搞定WMTS地图服务参数解析(含天地图示例) 第一次接触WMTS服务时,最让人头疼的就是那一堆参数:matrixIds、origin、resolutions...这些参数到底从哪来?为什么天地图和其他WMTS服务的参数…...

工业级飞控的故障诊断与容错控制技术:从故障检测到安全保障

引言工业级无人系统的作业场景多为户外复杂环境、高风险作业区域(如海上风电平台、高压线路旁、灾害现场),飞控作为 “核心大脑”,一旦出现故障且无有效处理机制,将导致系统失控、设备损毁,甚至引发安全事故…...

65.基于springboot+vue的酒店预约系统

可远程调试运行,时间宝贵!!!远程调试收费50,如有新需求按实际收费发源码系统功能: 分为三个角色:管理员、用户普通用户 浏览酒店房间信息 酒店预约 查看和管理个人预约 在线支付 提交评价 查看个…...

PyAudio PortAudio:Windows系统音频捕获技术深度解析与实践指南

PyAudio PortAudio:Windows系统音频捕获技术深度解析与实践指南 【免费下载链接】pyaudio_portaudio A fork to record speaker output with python. PyAudio with PortAudio for Windows | Extended | Loopback | WASAPI | Latest precompiled Version 项目地址:…...

Z-Image-GGUF多场景:法律文书配图、医学知识图谱、工程原理示意图生成

Z-Image-GGUF多场景实战:法律文书配图、医学知识图谱、工程原理示意图生成 1. 项目简介:一个低门槛的专业图像生成工具 如果你在工作中需要快速生成专业配图,比如给法律文书加个封面、为医学知识画个关系图,或者给工程文档配个原…...

从NAND原理到实际应用:一文读懂NVMe SSD寿命背后的技术细节

从NAND原理到实际应用:一文读懂NVMe SSD寿命背后的技术细节 在数据中心和消费级存储领域,NVMe SSD凭借其卓越的性能表现已成为存储介质的主流选择。但不同于传统机械硬盘近乎无限的写入寿命,SSD的寿命始终是用户最关心的核心指标之一。本文将…...

基于单层感知器(SLP)的多输出数据回归预测的Matlab代码

基于单层感知器(SLP)的多输出数据回归预测 不调用工具箱函数 SLP多输出数据回归 Matlab代码,注:暂无Matlab版本要求 -- 推荐 2018B 版本及以上最近在复现经典机器学习算法时发现,单层感知器(SLP)用于多输出回归的场景资料较少。咱们今天手撕个…...

星露谷农场规划器:5步打造你的完美虚拟农场指南

星露谷农场规划器:5步打造你的完美虚拟农场指南 【免费下载链接】stardewplanner Stardew Valley farm planner 项目地址: https://gitcode.com/gh_mirrors/st/stardewplanner 你是否曾经在星露谷游戏中面对杂乱无章的农场感到无从下手?你是否梦想…...

PYTHON_DAY02_ollama私有化大模型部署_以及apifox和chatbox调用大模型

##了解私有化大模型解决方案,能够选择企业常用的方案实现私有大模型部署 随着AI技术的不断普及,人们也积极拥抱其带来的变化,在生活或者工作中亦使用AI技术来帮助我们更高效的完成某些事件,但是在这个过程中,也暴露出A…...

3大场景攻克Android逆向难题:JADX让APK代码可读性提升90%的实战指南

3大场景攻克Android逆向难题:JADX让APK代码可读性提升90%的实战指南 【免费下载链接】jadx skylot/jadx: 是一个用于反编译Android应用的工具。适合用于需要分析和学习Android应用实现细节的开发者。特点是可以提供反编译功能,将Android应用打包的APK文件…...

医学多模态模型体验:MedGemma影像解读助手实战操作

医学多模态模型体验:MedGemma影像解读助手实战操作 1. 引言:当AI成为你的医学影像“实习助手” 如果你是一名医学生,面对一张复杂的胸部CT影像,是不是希望身边能有一位经验丰富的老师随时指点?如果你是一位医学研究者…...

LiuJuan Z-Image GeneratorBF16算力优势:对比FP16在4090D上PSNR提升2.1dB

LiuJuan Z-Image Generator BF16算力优势:对比FP16在4090D上PSNR提升2.1dB 1. 引言:当图片生成遇到精度瓶颈 你有没有遇到过这样的情况?用AI生成图片时,画面总感觉差那么一点意思——可能是细节不够锐利,也可能是色彩…...

永磁同步电机电压极限椭圆的形成机理与工程应用解析

1. 永磁同步电机电压极限椭圆的基本概念 第一次听说"电压极限椭圆"这个词时,我也是一头雾水。直到有一次调试电机时遇到转速上不去的怪现象,才发现这个概念原来这么重要。简单来说,电压极限椭圆就像是为永磁同步电机画的一个"…...

GTE模型在舆情监控中的应用:实时分析与预警

GTE模型在舆情监控中的应用:实时分析与预警 1. 引言 每天,互联网上产生着海量的用户评论、新闻文章和社交媒体内容。对于企业来说,如何从这些信息中快速识别出有价值的舆情信号,及时发现问题并做出响应,成为了一个巨…...

DR-MMC串联高压直流输电系统阻抗建模与稳定性分析

DR-MMC串联高压直流输电系统阻抗建模与稳定性分析 摘要 随着海上风电等大规模新能源并网需求的增长,基于二极管整流器(DR)与模块化多电平换流器(MMC)串联的混合高压直流输电系统因其经济性和可靠性优势成为研究热点。然而,DR与MMC在直流侧的串联结构导致二者之间存在复…...

OpenClaw备份恢复:ollama-QwQ-32B配置与任务的历史保存

OpenClaw备份恢复:ollama-QwQ-32B配置与任务的历史保存 1. 为什么需要备份OpenClaw配置 上周我的开发机突然硬盘故障,导致所有OpenClaw配置丢失。当时正在运行的十几个自动化任务全部中断,连最基本的飞书机器人对接都要重新配置。这次惨痛经…...

Pixel Dimension Fissioner实战案例:小红书种草文案10种人设风格裂变

Pixel Dimension Fissioner实战案例:小红书种草文案10种人设风格裂变 1. 工具介绍:像素语言维度裂变器 Pixel Dimension Fissioner(像素语言维度裂变器)是一款基于MT5-Zero-Shot-Augment核心引擎构建的创意文本改写工具。与传统…...