oCy头像
关注

【qt openGL入门笔记】着色器、纹理、变换

前言

        网上查opengl入门学习网站,介绍LearnOpenGL比较多,虽然不是基于qt项目,但原理是一致的,本篇基于样例把着色器、纹理和变换3个知识点合并到一个qt项目中,方便关联阅读。

一、着色器

        opengl是用显卡来绘制的,VBO负责把数据从内存搬进显存,VBO可以存储坐标、颜色、纹理。

1.1顶点着色器

        决定每个顶点最终落在屏幕哪个位置,位置由三维坐标(x,y,z)来决定,z=0.0f时即为二维坐标系。

QOpenGLShader vShader(QOpenGLShader::Vertex);
    vShader.compileSourceCode(
        "#version 150\n"
        "in vec3 aPos;\n"        // 输入:来自顶点的坐标(VBO 的前3个float:0、1、2)
        "in vec3 aColor;\n"      // 输入:来自顶点的颜色(VBO 接下来的3个float:3、4、5)
        "in vec2 aTexCoord;\n"   // 输入:来自顶点的纹理(VBO 的最后2个float:6、7)
        "out vec3 vColor;\n"     // 输出:转发给片段着色器
        "out vec2 vTexCoord;\n"     // ★ 纹理输出
        "uniform mat4 uMatrix;\n" // 外部传入的变换矩阵
        "void main() {\n"
        "    gl_Position = uMatrix * vec4(aPos, 1.0);\n"         // 顶点最终位置(系统内置输出)
        "    vColor = aColor;\n"         // 把颜色往下传
        "    vTexCoord = aTexCoord;\n"
        "}"
    );

 vecn:包含n个float分量的默认向量,下面代码段说明VBO中存储的值是(3+3+2)8个一组

"in vec3 aPos;\n"        // 输入:来自顶点的坐标(VBO 的前3个float:0、1、2)
"in vec3 aColor;\n"      // 输入:来自顶点的颜色(VBO 接下来的3个float:3、4、5)
"in vec2 aTexCoord;\n"   // 输入:来自顶点的纹理(VBO 的最后2个float:6、7)

compileSourceCode中看起来像是类C的代码,这整段字符串不是直接交给GPU运行的,而是提交给显卡驱动,由显卡驱动去解析、编译(是CPU来跑),编译产物才进显存。

    QOpenGLShaderProgram m_program;//头文件中声明
    
    m_program.addShader(&vShader);// 字符串拷给驱动(CPU 内存)
    m_program.addShader(&fShader);
    if (!m_program.link()) {//编译并链接程序
        qDebug() << "Shader Link Error:" << m_program.log();
    } else {
        qDebug() << "Shader Linked Successfully!";
    }

1.2片段着色器

        决定顶点组成的形状内部每个像素是什么颜色。

QOpenGLShader fShader(QOpenGLShader::Fragment);
    fShader.compileSourceCode(
        "#version 150\n"
        "in vec3 vColor;\n"         // 输入:从顶点着色器插值而来的颜色
        "in vec2 vTexCoord;\n"      // 输入:纹理
        "out vec4 FragColor;\n"     // 输出:这个像素最终颜色,如果在片段着色器没有定义输出颜色,OpenGL会把物体渲染为黑色(或白色)。
        "uniform sampler2D uTexture;\n"   // 输出:纹理
        "void main() {\n"
        "    vec4 texColor = texture(uTexture, vTexCoord);\n"
        "    FragColor = texColor * vec4(vColor, 1.0);\n" // 纹理 × 顶点色
        "}"
    );

        判断着色器的语法与顶点着色器差不多,职能不同。下面这张从网络获取的图跟容易看出它是干嘛的。

二、纹理

        粗俗些的说法就是让着色器显示指定的图片,看起来像生活中的实物,而不是有规律的RGB。本篇引用了LearnOpenGL提供的示例纹理图。

【一、着色器】比较基础也比较重要,上面是没有给出代码过程的,纹理按照代码关键流程来。

2.1 initializeGL准备

创建 VBO / 创建 EBO / 编译着色器 / 链接程序 / 加载纹理

        以上代码段是准备工作中的关键环节,需要注意的是,上面的顺序是可以调换的,只要是在initializeGL()里面即可。

2.1.1 创建VBO

GLfloat vertices[] = {
        // ---- 位置 x,y,z ------   -- 颜色 r,g,b --   -- 纹理 u,v --
        /*0*/ -0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f,  0.0f, 0.0f,  // 左下
        /*1*/  0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f,  1.0f, 0.0f,  // 右下
        /*2*/  0.0f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f,  0.5f, 1.0f,  // 正上(U取中间)
        /*3*/ -0.5f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f,  0.0f, 1.0f,  // 左上
        /*4*/  0.5f,  0.5f, 0.0f,  1.0f, 1.0f, 0.0f,  1.0f, 1.0f   // 右上
    };

 //VBO:把数据从内存搬进显存
    m_vbo.create(); // 申请一个 VBO 句柄
    m_vbo.bind();   // 绑定它,之后的操作都针对它
    m_vbo.allocate(vertices, sizeof(vertices));// 把数据上传到显存

如上面的代码片段,中纹理坐标存储在第7,8个,注意以上不是二维数组,一维数组需要自律排行顺序,uv值代表的意思如下,与前面的二维坐标位置是可以对应得上的。

       //u = 0:图片最左边  u = 1:图片最右边
       //v = 0:图片最下边  v = 1:图片最上边

2.1.2 创建EBO

        这项不是必须的,但是如果有个形状有出现重复位置出现在VBO时,使用EBO可以节省VBO的处理耗时。

// 2. EBO 索引:告诉 GPU "按哪个顺序、跳到哪些下标去取顶点"
    //    前 3 个 {0,1,2}    -> 三角形 (GL_TRIANGLES)
    //    后 4 个 {0,1,3,4}  -> 四边形 (GL_TRIANGLE_STRIP,之字排列,自动拼 2 个三角形)
    //    顶点 0、1 被两段索引同时引用,这就是"复用"。
    GLushort indices[] = {
        0, 1, 2,        // 三角形索引段(EBO 偏移 0 字节起,共 3 个索引)
        0, 1, 3, 4      // 四边形索引段(EBO 偏移 3*2=6 字节起,共 4 个索引)
    };


    // EBO:把索引数据搬进显存(绑定后它占据 GL_ELEMENT_ARRAY_BUFFER 目标)
    m_ebo.create();
    m_ebo.bind();
    m_ebo.allocate(indices, sizeof(indices));

2.1.3 着色器

        本文的qt样例中同时包含了着色器、纹理、变换,为了体现特性切换的特点,我设置了一个开启纹理功能的开关,在代码中体现。

#define TEXTURE_STATUS false    //是否开启纹理功能

      在【1.1顶点着色器】中已经给出了包含纹理坐标输出的代码片段,这个片段即使不使用,也不会影响到效果,而片段着色器如果如同【1.2片段着色器】中增加了代码片段,又不提供输入时,会导致出现全黑的BUG,所以可以如下用预编译来处理

    QOpenGLShader fShader(QOpenGLShader::Fragment);
    #if TEXTURE_STATUS
    fShader.compileSourceCode(
        "#version 150\n"
        "in vec3 vColor;\n"
        "in vec2 vTexCoord;\n"
        "out vec4 FragColor;\n"
        "uniform sampler2D uTexture;\n"
        "void main() {\n"
        "    FragColor = texture(uTexture, vTexCoord) * vec4(vColor, 1.0);\n"
        "}");
    #else
    fShader.compileSourceCode(
        "#version 150\n"
        "in vec3 vColor;\n"
        "out vec4 FragColor;\n"
        "void main() {\n"
        "    FragColor = vec4(vColor, 1.0);\n"   // 纯顶点色,无采样
        "}");
    #endif

2.1.4 编译并链接程序

QOpenGLShaderProgram m_program;
    
m_program.addShader(&vShader);
    m_program.addShader(&fShader);
    if (!m_program.link()) {
        qDebug() << "Shader Link Error:" << m_program.log();
    } else {
        qDebug() << "Shader Linked Successfully!";
    }

2.1.5 加载纹理

        实际上就是加载一张图片。

    QOpenGLTexture *m_texture = nullptr;   // 纹理对象

    if(TEXTURE_STATUS){
        // 6. 加载纹理
        QImage img(":/images/wall.jpg");
        if (img.isNull()) {
            qDebug() << "Texture load failed!";
        } else {
            // QImage 原点在左上角,OpenGL 纹理原点在左下角 → 必须垂直翻转,否则图片上下颠倒
            m_texture = new QOpenGLTexture(img.mirrored());
            m_texture->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); // 缩小时用 mipmap
            m_texture->setMagnificationFilter(QOpenGLTexture::Linear);            // 放大时线性插值
            m_texture->setWrapMode(QOpenGLTexture::ClampToEdge);                  // UV 超出 [0,1] 时截断
        }
    }

2.2 paintGL渲染

         前面是initializeGL()中的准备工作,接下来是向显卡提交渲染的工作。

2.2.1 清屏和绑定

        把上一帧的画面擦掉,准备好新的画布,激活着色器程序、绑定 VBO/EBO/纹理,把 GPU 需要的数据和状态就位。

    // 1.清屏(深蓝色背景)
    glClearColor(0.05f, 0.05f, 0.12f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // 2.使用着色器程序
    m_program.bind();

    // 3. 绑定顶点数据 + 索引数据
    m_vbo.bind();
    m_ebo.bind();

2.2.2 配置顶点属性

        告诉 GPU 怎么解析顶点缓冲里的位置、颜色、UV 数据。

    //1. 使用 attributeLocation 从顶点着色器中获取位置,避免硬编码
    int posAttr = m_program.attributeLocation("aPos");
    int colorAttr = m_program.attributeLocation("aColor");
    int texAttr   = m_program.attributeLocation("aTexCoord");   // 纹理

    //2.设置属性值
    if(TEXTURE_STATUS){
        // 顶点着色器纹理坐标:偏移 6 个 float,取 2 个分量
        m_program.setAttributeBuffer(texAttr, GL_FLOAT, 6 * sizeof(GLfloat), 2, GLF_SIZE * sizeof(GLfloat));
        m_program.enableAttributeArray(texAttr);
    }

2.2.3 传递Uniform

把 CPU 侧算好的矩阵、采样器编号等参数推给 GPU。

//3.片段着色器取样
    if(TEXTURE_STATUS){
        // 片段着色器采样器是 int 类型:告诉它去 0 号纹理单元取样
            m_program.setUniformValue("uTexture", 0);
        if (m_texture) m_texture->bind(0);
    }

2.2.4 发出绘制命令

调用 glDrawElements / glDrawArrays,触发 GPU 执行顶点着色器 → 光栅化 → 片段着色器 → 输出到帧缓冲。

if (m_shape == 0) {
        //glDrawArrays(GL_TRIANGLES, 0, 3);        // 用下标0~2这3个顶点:三角形
        // 三角形:索引 0,1,2
        glDrawElements(GL_TRIANGLE_STRIP, 3, GL_UNSIGNED_SHORT,
                       (const void *)(m_shapelist[ELEMNNT_TRIANGLE] * sizeof(GLushort)));
    } else {
        // 四边形:索引 0,1,3,4 —— 偏移 3 个索引 = 3 * 2 字节
        glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT,
                       (const void *)(m_shapelist[ELEMNNT_QUAD] * sizeof(GLushort)));
    }

    //6.关闭属性数组,释放绑定---收尾
    if(TEXTURE_STATUS){
        if (m_texture) m_texture->release();
        m_program.disableAttributeArray(texAttr);
    }

注意,glDrawArrays只能按顺序读顶点,跨索引时,必须用EBO+glDrawElements。

2.2.4 收尾解绑

        释放纹理、禁用属性数组、解绑程序,避免污染下一帧的状态。

    m_program.disableAttributeArray(posAttr);
    m_program.disableAttributeArray(colorAttr);
    m_program.release();

2.3效果图

                无纹理效果图                                                        有纹理效果图

三、变换

        物体经过着色、加入纹理还只是还是静态的物体,OpenGL 变换的核心就是:顶点数据从“模型自己的坐标”一步步变成“屏幕上的像素位置”,中间主要靠模型矩阵、视图矩阵、投影矩阵这三组 4×4 矩阵完成。

阶段输入输出谁负责
模型变换模型局部坐标世界坐标模型矩阵 Model
视图变换世界坐标相机/眼坐标视图矩阵 View
投影变换相机坐标裁剪坐标投影矩阵 Projection

本篇只以模型矩阵的缩放、平移、旋转为例。在LearnOpenGL中,学习变换,需要去学习向量和矩阵运算,但QT已经封装好,以下只给出与模型矩阵相关的。

在顶点着色器中,有以下代码片段及意义说明:

gl_Position = uMatrix * vec4(aPos, 1.0);

aPos            → 原始顶点,比如 (x, y, z)
vec4(aPos, 1.0) → 补成齐次坐标
uMatrix         → 模型 × 视图 × 投影矩阵
gl_Position     → 变换后的裁剪空间坐标

三维空间里,旋转和缩放可以用 3×3 矩阵表示,但平移是加法,不能直接用 3×3 矩阵乘法表示,需要加上第四个分量,就变成vec4(aPos, 1.0)。

3.1 缩放

        即x、y轴同时缩写/放大相同的比例,opengl对四位矩阵提供了scale函数进行缩放,如下

float pulse  = 0.75f + 0.35f * qSin(qDegreesToRadians(m_angle * 2.0f)); // 缩放 0.4~1.1
QMatrix4x4 matrix;//四维矩阵
matrix.scale(pulse, pulse, 1.0f);          // 缩放(Scale),x、y轴等比变化,Z轴不变

其四维矩阵如下:

| sx  0   0   0 |
| 0   sy  0   0 |
| 0   0   sz  0 |
| 0   0   0   1 |

其中matrix.scale(pulse, pulse, 1.0f);相当于令sz=1.0f

3.2 旋转

        旋转不会改变坐标轴长度,但会改变它们指向哪里,本篇是绕Z轴旋转,即x、y坐标变换,Z不变。

matrix.rotate(m_angle, 0.0f, 0.0f, 1.0f);  // 积木二:旋转(Rotate) 绕Z轴

m_angle          → 旋转的角度θ
0.0f, 0.0f, 1.0f → 前面两个0.0f是x、y轴,1.0f是X轴

其四维矩阵如下:

| cosθ  -sinθ  0  0 |
| sinθ   cosθ  0  0 |
| 0       0    1  0 |
| 0       0    0  1 |

第1、2行和第1、2列有变换的是x、y轴。

3.3 平移

        即把整个空间移动一段距离,以下代码是只在X轴平移

float driftX = 0.25f * qCos(qDegreesToRadians(m_angle));                // 水平来回漂浮
matrix.translate(driftX, 0.0f, 0.0f);      // 平移(Translate),沿 X 轴左右移动

其四维矩阵如下:


| 1  0  0  tx |
| 0  1  0  ty |
| 0  0  1  tz |
| 0  0  0   1 |

在x轴平移,相当于上面的四位矩阵变为tx=driftX,ty=0,tz=0。

四、qt项目代码

glwidget.h
#ifndef GLWIDGET_H
#define GLWIDGET_H

#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>
#include <QOpenGLBuffer>
#include <QTimer>
#include <QMatrix4x4>
#include <QList>
#include <QtMath>
#include <QOpenGLTexture>
#define GLF_SIZE 8
#define TEXTURE_STATUS false    //是否开启纹理功能

typedef enum enElementsType{
    ELEMNNT_TRIANGLE=0,
    ELEMNNT_QUAD
}TEnElementsType;


class GLWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
    Q_OBJECT
public:
    explicit GLWidget(QWidget *parent = nullptr);
    ~GLWidget();

protected:
    void initializeGL() override;//只做一次:上传数据、编译着色器(装修厨房)
    void resizeGL(int w, int h) override;//窗口尺寸变化时:调整视口(厨房搬了地方、灶台重新对齐)
    void paintGL() override;//每一帧都重复:把画面画出来(每做一道菜)

private:
    QOpenGLShaderProgram m_program;
    QOpenGLTexture *m_texture = nullptr;   // 纹理对象
    QOpenGLBuffer m_vbo;// 顶点缓冲:存坐标+颜色(瘦身后的 5 个顶点)
    QOpenGLBuffer m_ebo;       // 新增:索引缓冲(IndexBuffer),存"按什么顺序取顶点"
    QTimer *m_timer;
    float m_angle = 0.0f;
    int m_shape = 0;           // 新增:当前形状 0=三角形 1=四边形
    QList<int> m_shapelist;
};

#endif // GLWIDGET_H
glwidget.cpp
#include "glwidget.h"
#include <QDebug>
#include <QDir>

GLWidget::GLWidget(QWidget *parent)
    : QOpenGLWidget(parent)
    , m_vbo(QOpenGLBuffer::VertexBuffer)    // 顶点缓冲:存坐标+颜色
    , m_ebo(QOpenGLBuffer::IndexBuffer)     // 索引缓冲:存"按什么顺
{
    m_shapelist={0,3};
    // 创建定时器,每秒刷新60次 (1000 / 60 ≈ 16)
    m_timer = new QTimer(this);
    connect(m_timer, &QTimer::timeout, this, [=]{
        m_angle += 1.0f;    // 累积一个角度
        if (m_angle >= 360.0f) {    // 整个图形转满一圈
            m_angle -= 360.0f;      // 归零,避免数值无限累加溢出
            m_shape = !m_shape;     // 形状切换:0=三角形  1=四边形
        }
        update(); // 请求重绘 -> 触发 paintGL()
    });
}

GLWidget::~GLWidget()
{
    makeCurrent();
    delete m_texture;
    doneCurrent();
}

void GLWidget::initializeGL()
{
    initializeOpenGLFunctions();
    //glDrawElements	决定用哪些顶点、按什么顺序、连成什么图元
    //顶点着色器	决定每个顶点最终落在屏幕哪个位置
    //片段着色器	决定三角形内部每个像素是什么颜色
    // 1. VBO:5 个互不重复的顶点 (XYZ + RGB)
    GLfloat vertices[] = {
        // ---- 位置 x,y,z ------   -- 颜色 r,g,b --   -- 纹理 u,v --
        //u = 0:图片最左边  u = 1:图片最右边
        //v = 0:图片最下边  v = 1:图片最上边
        /*0*/ -0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f,  0.0f, 0.0f,  // 左下
        /*1*/  0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f,  1.0f, 0.0f,  // 右下
        /*2*/  0.0f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f,  0.5f, 1.0f,  // 正上(U取中间)
        /*3*/ -0.5f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f,  0.0f, 1.0f,  // 左上
        /*4*/  0.5f,  0.5f, 0.0f,  1.0f, 1.0f, 0.0f,  1.0f, 1.0f   // 右上
    };

    // 2. EBO 索引:告诉 GPU "按哪个顺序、跳到哪些下标去取顶点"
    //    前 3 个 {0,1,2}    -> 三角形 (GL_TRIANGLES)
    //    后 4 个 {0,1,3,4}  -> 四边形 (GL_TRIANGLE_STRIP,之字排列,自动拼 2 个三角形)
    //    顶点 0、1 被两段索引同时引用,这就是"复用"。
    GLushort indices[] = {
        0, 1, 2,        // 三角形索引段(EBO 偏移 0 字节起,共 3 个索引)
        0, 1, 3, 4      // 四边形索引段(EBO 偏移 3*2=6 字节起,共 4 个索引)
    };

    //VBO:把数据从内存搬进显存
    m_vbo.create(); // 申请一个 VBO 句柄
    m_vbo.bind();   // 绑定它,之后的操作都针对它
    m_vbo.allocate(vertices, sizeof(vertices));// 把数据上传到显存

    // EBO:把索引数据搬进显存(绑定后它占据 GL_ELEMENT_ARRAY_BUFFER 目标)
    m_ebo.create();
    m_ebo.bind();
    m_ebo.allocate(indices, sizeof(indices));

    // 3. 顶点着色器 (使用 GLSL 150 标准--类C语言)
    QOpenGLShader vShader(QOpenGLShader::Vertex);
    vShader.compileSourceCode(
        "#version 150\n"
        "in vec3 aPos;\n"        // 输入:来自顶点的坐标(VBO 的前3个float:0、1、2)
        "in vec3 aColor;\n"      // 输入:来自顶点的颜色(VBO 接下来的3个float:3、4、5)
        "in vec2 aTexCoord;\n"   // 输入:来自顶点的纹理(VBO 的最后2个float:6、7)
        "out vec3 vColor;\n"     // 输出:转发给片段着色器
        "out vec2 vTexCoord;\n"     // ★ 纹理输出
        "uniform mat4 uMatrix;\n" // 外部传入的变换矩阵
        "void main() {\n"
        "    gl_Position = uMatrix * vec4(aPos, 1.0);\n"         // 顶点最终位置(系统内置输出)
        "    vColor = aColor;\n"         // 把颜色往下传
        "    vTexCoord = aTexCoord;\n"
        "}"
    );

    // 4. 片段着色器
    QOpenGLShader fShader(QOpenGLShader::Fragment);
    #if TEXTURE_STATUS
    fShader.compileSourceCode(
        "#version 150\n"
        "in vec3 vColor;\n"
        "in vec2 vTexCoord;\n"
        "out vec4 FragColor;\n"
        "uniform sampler2D uTexture;\n"
        "void main() {\n"
        "    FragColor = texture(uTexture, vTexCoord) * vec4(vColor, 1.0);\n"
        "}");
    #else
    fShader.compileSourceCode(
        "#version 150\n"
        "in vec3 vColor;\n"
        "out vec4 FragColor;\n"
        "void main() {\n"
        "    FragColor = vec4(vColor, 1.0);\n"   // 纯顶点色,无采样
        "}");
    #endif


    // 5. 编译并链接程序---把两个编译好的着色器加进一个程序对象并链接
    m_program.addShader(&vShader);
    m_program.addShader(&fShader);
    if (!m_program.link()) {
        qDebug() << "Shader Link Error:" << m_program.log();
    } else {
        qDebug() << "Shader Linked Successfully!";
    }

    if(TEXTURE_STATUS){
        // 6. 加载纹理
        QImage img(":/images/wall.jpg");
        if (img.isNull()) {
            qDebug() << "Texture load failed!";
        } else {
            // QImage 原点在左上角,OpenGL 纹理原点在左下角 → 必须垂直翻转,否则图片上下颠倒
            m_texture = new QOpenGLTexture(img.mirrored());
            m_texture->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); // 缩小时用 mipmap
            m_texture->setMagnificationFilter(QOpenGLTexture::Linear);            // 放大时线性插值
            m_texture->setWrapMode(QOpenGLTexture::ClampToEdge);                  // UV 超出 [0,1] 时截断
        }
    }

    // 启动定时器
    m_timer->start(16);//16 毫秒(约 60 帧/秒)一次的定时器
}

void GLWidget::resizeGL(int w, int h)
{
    //glViewport 的作用是:告诉 GPU“把 NDC 里 -1~1 的画面,映射到窗口这块 w×h 的矩形像素区域”。
    glViewport(0, 0, w, h);
}

void GLWidget::paintGL()
{
    // 1.清屏(深蓝色背景)
    glClearColor(0.05f, 0.05f, 0.12f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // 2.使用着色器程序
    m_program.bind();

    // 3. 绑定顶点数据 + 索引数据
    m_vbo.bind();
    m_ebo.bind();
    // 使用 attributeLocation 获取位置,避免硬编码
    int posAttr = m_program.attributeLocation("aPos");
    int colorAttr = m_program.attributeLocation("aColor");
    int texAttr   = m_program.attributeLocation("aTexCoord");   // 纹理

    // 设置属性数组:(位置/步长6个GLfloat)
    //把显存布局接到 aPos/aColor
    // 位置坐标:从偏移 0 开始,取3 个 float
    m_program.setAttributeBuffer(posAttr, GL_FLOAT, 0, 3, GLF_SIZE * sizeof(GLfloat));
    m_program.enableAttributeArray(posAttr);
    // 颜色坐标:偏移 3 个 float,取 3 个分量
    m_program.setAttributeBuffer(colorAttr, GL_FLOAT, 3 * sizeof(GLfloat), 3, GLF_SIZE * sizeof(GLfloat));
    m_program.enableAttributeArray(colorAttr);
    if(TEXTURE_STATUS){
        // 纹理坐标:偏移 6 个 float,取 2 个分量
        m_program.setAttributeBuffer(texAttr, GL_FLOAT, 6 * sizeof(GLfloat), 2, GLF_SIZE * sizeof(GLfloat));
        m_program.enableAttributeArray(texAttr);
    }

    //Uniform是另一种从我们的应用程序在 CPU 上传递数据到 GPU 上的着色器的方式
    // ★ 重点:矩阵传值的正确写法 (Qt 5.12 必须先获取 location) ★
    //4.用 uniform 矩阵传旋转角(动画的真相)
    int matrixLocation = m_program.uniformLocation("uMatrix");//查询uniform ourColor的位置值
    if (matrixLocation != -1) {
        // 用 m_angle 派生出"会呼吸的缩放"和"来回漂浮的平移",全是局部变量,不用改头文件
        float pulse  = 0.75f + 0.35f * qSin(qDegreesToRadians(m_angle * 2.0f)); // 缩放 0.4~1.1
        float driftX = 0.25f * qCos(qDegreesToRadians(m_angle));                // 水平来回漂浮

        QMatrix4x4 matrix;
        matrix.scale(pulse, pulse, 1.0f);          // 积木一:缩放(Scale),x、y轴等比变化,Z轴不变
        matrix.rotate(m_angle, 0.0f, 0.0f, 1.0f);  // 积木二:旋转(Rotate) 绕Z轴
        matrix.translate(driftX, 0.0f, 0.0f);      // 积木三:平移(Translate),沿 X 轴左右移动

        m_program.setUniformValue(matrixLocation, matrix);
        if(TEXTURE_STATUS){
            // 采样器是 int 类型:告诉它去 0 号纹理单元取样
            m_program.setUniformValue("uTexture", 0);
            if (m_texture) m_texture->bind(0);
        }
    }

    // 5. 用索引绘制:靠"字节偏移"切三角形段 / 四边形段
    //    三角形段:从 EBO 第 0 个索引起,3 个索引  -> GL_TRIANGLES
    //    四边形段:从 EBO 第 3 个索引起,4 个索引  -> GL_TRIANGLE_STRIP
    //    最后一个参数是"字节偏移",不是索引个数:每个索引 2 字节(GLushort)。
    if (m_shape == 0) {
        //glDrawArrays(GL_TRIANGLES, 0, 3);        // 用下标0~2这3个顶点:三角形
        // 三角形:索引 0,1,2
        glDrawElements(GL_TRIANGLE_STRIP, 3, GL_UNSIGNED_SHORT,
                       (const void *)(m_shapelist[ELEMNNT_TRIANGLE] * sizeof(GLushort)));
    } else {
        // 四边形:索引 0,1,3,4 —— 偏移 3 个索引 = 3 * 2 字节
        glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT,
                       (const void *)(m_shapelist[ELEMNNT_QUAD] * sizeof(GLushort)));
    }

    //6.关闭属性数组,释放绑定---收尾
    if(TEXTURE_STATUS){
        if (m_texture) m_texture->release();
        m_program.disableAttributeArray(texAttr);
    }
    m_program.disableAttributeArray(posAttr);
    m_program.disableAttributeArray(colorAttr);
    m_program.release();
}

项目源码可在资源中下载。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/liangyuna8787/article/details/166995110

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--