ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

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

【qt openGL入门笔记】着色器、纹理、变换 前言网上查opengl入门学习网站介绍LearnOpenGL比较多虽然不是基于qt项目但原理是一致的本篇基于样例把着色器、纹理和变换3个知识点合并到一个qt项目中方便关联阅读。一、着色器opengl是用显卡来绘制的VBO负责把数据从内存搬进显存VBO可以存储坐标、颜色、纹理。1.1顶点着色器决定每个顶点最终落在屏幕哪个位置位置由三维坐标(x,y,z)来决定z0.0f时即为二维坐标系。QOpenGLShader vShader(QOpenGLShader::Vertex); vShader.compileSourceCode( #version 150\n in vec3 aPos;\n // 输入来自顶点的坐标VBO 的前3个float0、1、2 in vec3 aColor;\n // 输入来自顶点的颜色VBO 接下来的3个float3、4、5 in vec2 aTexCoord;\n // 输入来自顶点的纹理VBO 的最后2个float6、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中存储的值是3328个一组in vec3 aPos;\n // 输入来自顶点的坐标VBO 的前3个float0、1、2 in vec3 aColor;\n // 输入来自顶点的颜色VBO 接下来的3个float3、4、5 in vec2 aTexCoord;\n // 输入来自顶点的纹理VBO 的最后2个float6、7compileSourceCode中看起来像是类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 创建VBOGLfloat 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));// 把数据上传到显存如上面的代码片段中纹理坐标存储在第78个注意以上不是二维数组一维数组需要自律排行顺序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*26 字节起共 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 // 纯顶点色无采样 }); #endif2.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只能按顺序读顶点跨索引时必须用EBOglDrawElements。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);相当于令sz1.0f3.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轴平移相当于上面的四位矩阵变为txdriftXty0,tz0。四、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_TRIANGLE0, 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四边形 QListint m_shapelist; }; #endif // GLWIDGET_Hglwidget.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. VBO5 个互不重复的顶点 (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*26 字节起共 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个float0、1、2 in vec3 aColor;\n // 输入来自顶点的颜色VBO 接下来的3个float3、4、5 in vec2 aTexCoord;\n // 输入来自顶点的纹理VBO 的最后2个float6、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(); }项目源码可在资源中下载。
返回列表