s2.GLSL学习之绘制三角形

主要内容

  本文主要内容:实现《OpenGL编程指南(原书第8版)》第一章中渲染两个蓝色三角形示例。通过该示例,介绍GLSL编程的一般步骤。为了便于学习,没有新建用来保存着色器代码的文件triangles.vert和triangles.frag,而是用常量字符串直接表示;同时没有使用书中提到的LoadShaders类,而是将其中用到的代码在MyQGLWidget 类中实现。自己第一次看红宝书第一章时很吃力,代码也分散在几个文件里,看起来更吃力了。所以自己做笔记时,编程示例就把代码集中一起,至少自己一目了然,希望对大家也有帮助。

示例演示

头文件myQGLWidget .h

#ifndef MY_QGLWIDGET_H
#define MY_QGLWIDGET_H

#include <QOpenGLWidget>
#include <QtOpenGL>
#include <QOpenGLFunctions_4_3_Compatibility>

class MyQGLWidget : public QOpenGLWidget , protected QOpenGLFunctions_4_3_Compatibility
{
    Q_OBJECT

public:
    MyQGLWidget(QWidget *parent = 0);

protected:
    void initializeGL();
    void resizeGL(int width, int height);
    void paintGL();

private:
    enum VAO_IDs {Triangles, NumVAOs};
    enum Buffer_IDs {ArrayBuffer, NumBuffers};
    enum Attrib_IDs {vPosition = 0};
    GLuint VAOs[NumVAOs];
    GLuint Buffers[NumBuffers];
    const GLuint NumVertices;
    void initShader();
};

#endif

源文件myQGLWidget .cpp

#include <QtGui>
#include "myQGLWidget.h"

MyQGLWidget::MyQGLWidget(QWidget *parent)
    : QOpenGLWidget(parent),
      NumVertices(6)
{
}

void MyQGLWidget::initializeGL()
{
    initializeOpenGLFunctions();
    glEnable(GL_DEPTH_TEST);
    initShader();
}

void MyQGLWidget::resizeGL(int width, int height)
{
    glViewport(0, 0, width, height);
}

void MyQGLWidget::paintGL()
{
    glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
    glClearColor(0.0, 0.0, 0.0, 1.0);
    glBindVertexArray(VAOs[Triangles]);
    glDrawArrays(GL_TRIANGLES, 0, NumVertices);
}

void MyQGLWidget::initShader()
{
    glGenVertexArrays(NumVAOs, VAOs);
    glBindVertexArray(VAOs[Triangles]);
    GLfloat vertices[6][2] = {
        {-0.90, -0.90},
        {0.85, -0.90},
        {-0.90, 0.85},
        {0.90, -0.85},
        {0.90, 0.90},
        {-0.85, 0.90}
    };

    glGenBuffers(NumBuffers, Buffers);
    glBindBuffer(GL_ARRAY_BUFFER, Buffers[ArrayBuffer]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(vPosition);
    glVertexAttribPointer(vPosition, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*)0);

    const char*  vertexShaderCode =
        "#version 430  \n"
        ""
        "layout(location=0) in vec4 vPosition;"
        ""
        "void main()"
        "{"
        "   gl_Position = vPosition;"
        "}";

    const char* fragmentShaderCode =
        "#version 430 \r \n"
        ""
        "out vec4 fColor;"
        ""
        "void main()"
        "{"
        "   fColor = vec4(0.0, 0.0, 1.0, 1.0);"
        "}";
    GLuint vertexShaderID = glCreateShader(GL_VERTEX_SHADER);
    GLuint fragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);

    const char* adapter[1];
    adapter[0] = vertexShaderCode;
    glShaderSource(vertexShaderID, 1, adapter, 0);

    adapter[0] = fragmentShaderCode;
    glShaderSource(fragmentShaderID, 1, adapter, 0);

    glCompileShader(vertexShaderID);
    glCompileShader(fragmentShaderID);

    GLuint programID = glCreateProgram();
    glAttachShader(programID, vertexShaderID);
    glAttachShader(programID, fragmentShaderID);

    glLinkProgram(programID);
    glUseProgram(programID);

}

运行结果:

这里写图片描述

代码分析

initShader函数实现了OpenGL的初始化过程。

初始化顶点数组对象VAO

调用glGenVertexArrays分配顶点数组对象(vertex-array object)。glGenVertexArrays(NumVAOs, VAOs)表示OpenGL分配了NumVAOs个顶点数组对象,保存到数组VAOs中。glGenVertexArrays函数的完整解释如下:

 void glGenVertexArrays(GLsizei n​, GLuint *arrays​);
 返回n个未使用的对象名到数组arrays中,用作顶点数组对象。返回的名字可以用来分配更多的缓存对象,并且它们已经使用未初
 始化的顶点数组集合的默认状态进行了数值的初始化。

很多OpenGL命令都是glGen*的形式,它们负责分配不同类型的OpenGL对象的名称。这里的名称类似C语言中的指针变量,必须分配内存并且用名称引用它之后,名称才有意义。在OpenGL中,这个分配的机制叫做绑定对象(bind an object)。这通过一系列glBind形式的OpenGL函数集合去实现,本例中使用glBindVertexArray。glBindVertexArray(VAOs[Triangles])表示创建一个顶点数组对象,并与其名称(VAOs[Triangles])关联起来。

void glBindVertexArray(GLuint array​);
1、如果array非0,并且是glGenVertexArrays()所返回的,将创建一个新的顶点数组对象并且与其名称关联起来。
2、如果绑定到一个已经创建的顶点数组对象,将会激活这个VAO,并且直接影响对象中所保存的顶点数组状态。
3、如果array为0,OpenGL将不再使用程序所分配的任何VAO,并将渲染状态重设为顶点数组的默认状态。

总体上来说,在两种情况下我们需要绑定一个对象:创建对象并初始化它所对应的数据时;以及每次我们准备使用这个对象而它并不是当前绑定的对象时。

分配顶点缓存对象VBO

顶点数组对象VAO负责保存一系列顶点的数据。这些数据保存到缓存对象当中,并且由当前绑定的顶点数组对象VAO管理。我们只有一种顶点数组对象VAO类型,但是却又很多种类型的对象,并且其中一部分对象并不负责处理顶点数据。缓存对象就是OpenGL服务端(比如GPU)分配和管理的一块内存区域,并且几乎所有传入OpenGL的数据都存储在缓存对象当中。
顶点缓存对象(Vertex Buffer Objects, VBO)的初始化过程与顶点数组对象VAO的创建过程类似,不过需要有向缓存中添加数据的一个过程。首先,glGenBuffers(NumBuffers, Buffers)表示分配NumBuffers个VBO到数组Buffer中。

void glGenBuffers(GLsizei n​, GLuint * buffers​);
返回n个当前未使用的缓存对象名称,并保存到buffers数组中。返回到buffers中的名称不一定是连续的整型数据。

当分配缓存的名称之后,就可以调用glBindBuffer()来绑定它们了。由于OpenGL中有很多种不同类型的缓存对象,因此绑定一个缓存时,需要指定它所对应的类型。此例中由于是将顶点数据保存到缓存中(故为顶点缓存对象VBO),因此使用GL_ARRAY_BUFFER类型。缓存对象的类型现在共有8中,分别用于不同的OpenGL功能实现,具体以后介绍。

void glBindBuffer(GLenum target​, GLuint buffer​);
1、如果是第一次绑定buffer,且它是一个非零的无符号整数,那么将创建一个与该名称相对应的新缓存对象。
2、如果绑定到一个已经创建的缓存对象,那么它将称为当前被激活的缓存对象。
3、如果绑定的buffer值为0,那么OpenGL将不再对当前target应用任何缓存对象。

初始化顶点缓存对象之后,我们需要把顶点数据从对象传输到缓存对象中,这一步通过glBufferData ()完成的。它主要有两个任务:分配顶点数据所需的存储空间,然后将数据从应用程序的数组中拷贝到OpenGL服务器的内存中。

void glBufferData(GLenum target​, GLsizeiptr size​, const GLvoid * data​, GLenum usage​);
在OpenGL服务端内存中分配size个存储单元(通常为byte),用于存储数据或索引。如果当前绑定的对象已经存在了关联的数据,
那么会首先删除这些数据。target设置数据类型。size表示存储数据的总数量。data要么是一个客户端内存的指针,以便初始化缓
存对象,要么是NULL,将保留size大小的未初始化的数据,以备后用。usage用于设置分配数据后的读取和写入方式。

glEnableVertexAttribArray和glVertexAttribPointer指定了顶点着色器的变量与我们存储在缓存对象中数据的关系。这也就是我们所说的着色管线装配的过程,即将应用程序与着色器之间,以及不同着色阶段之间的数据通道连接起来。
为了输入顶点着色器的数据,也就是OpenGL将要处理的所有顶点数据,需要在着色器中声明一个in 变量,然后使用glVertexAttribPointer()将它关联到一个顶点属性数组。

void glVertexAttribPointer(GLuint index​, GLint size​, GLenum type​, GLboolean normalized​, GLsizei
            stride​, const GLvoid * pointer​);
设置index(着色器中的数学位置)位置对应的数据值。pointer表示缓存对象中,从起始位置开始计算的数组数据的偏移值(假设
起始位置为0),使用基本的系统单位(byte)。size表示每个顶点需要更新的分量数目。type指定了数组中每个元素的数据类型
(比如GL_FLOAT)。normalized设置顶点数据在存储前是否需要进一步归一化。stride是数组中每两个元素之间的大小偏移
量(byte)。如果stride为0,那么数据应该紧密地封装在一起。

glVertexAttribPointer(vPosition, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*)0)中各个参数的设置及其意义如下图所示。
这里写图片描述
后面是在OpenGL中使用可编程着色器(shader),我们会在下一篇详细介绍。

使用OpenGL进行渲染

渲染的工作我们在paintGL函数中进行。首先我们要清除帧缓存的数据再进行渲染。清除的工作由glClear()完成。如果要改变清除颜色的数值,可以使用glClearColor()。后面两行代码的工作就是选择我们准备绘制的顶点数据,然后请求进行绘制。首先调用glBindVertexArray()来选择作为顶点数据使用的顶点数组。然后调用glDrawArrays()来实现顶点数据向OpenGL管线的传输。

void glDrawArrays(GLenum mode​, GLint first​, GLsizei count​);
使用当前绑定的顶点数组元素来建立一系列的几何图元。mode设置构建图元的类型(比如GL_TRIANGLES),起始位置为first,而
结束位置为first+count-1。

其他

MyQGLWidget 继承自QOpenGLFunctions_4_3_Compatibility,其实也可以继承自QOpenGLFunctions_4_3_Core。QOpenGLFunctions_4_3_Core使用的OpenGL 43.版本的核心模式(core profile),这个模式可以确保使用的只是OpenGL的最新特性。QOpenGLFunctions_4_3_Compatibility使用的是兼容模式,这种模式自OpenGL 1.0版本以来的所有特性都可以使用。

代码下载
OpenGL学习系列导航

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值