OpenGL能做的事情太多了!很多程序也看起来很复杂。很多人感觉OpenGL晦涩难懂,原因大多是被OpenGL里面各种语句搞得头大,一会gen一下,一会bind一下,一会又active一下。搞到最后都不知道自己在干嘛,更有可能因为某一步的顺序错误导致最后渲染出错,又或者觉得记下这些操作的顺序是非常烦人的一件事。那么,OpenGL为什么会长成这个样子呢?这篇文章旨在通过一个最简单的OpenGL程序开始,让我们能够“看懂”它,“记住”这些操作顺序。
我们先来解释一下OpenGL为什么会涉及这么多操作顺序。这是因为,和我们现在使用的C++、C#这种面向对象的语言不同,OpenGL中的大多数函数使用了一种基于状态的方法,大多数OpenGL对象都需要在使用前把该对象绑定到context上。这里有两个新名词——OpenGL对象和Context。
Context
Context是一个非常抽象的概念,我们姑且把它理解成一个包含了所有OpenGL状态的对象。如果我们把一个Context销毁了,那么OpenGL也不复存在。
OpenGL对象
我们可以把OpenGL对象理解成一个状态的集合,它负责管理它下属的所有状态。当然,除了状态,OpenGL对象还会存储其他数据。注意。这些状态和上述context中的状态并不重合,只有在把一个OpenGL对象绑定到context上时,OpenGL对象的各种状态才会映射到context的状态。因此,这时如果我们改变了context的状态,那么也会影响这个对象,而相反地,依赖这些context状态的函数也会使用存储在这个对象上的数据。
下面看一个实例:
源码下载地址:https://download.csdn.net/download/cqltbe131421/10502444
#include "mainwidget.h"
#include <QMouseEvent>
#include <math.h>
#include <QDebug>
MainWidget::MainWidget(QWidget *parent) :
QOpenGLWidget(parent),
geometries(0),
texture(0),
angularSpeed(0)
{
}
MainWidget::~MainWidget()
{
// 删除纹理时确保上下文是当前的
// 缓冲器。
makeCurrent();
delete texture;
delete geometries;
doneCurrent();
}
void MainWidget::mousePressEvent(QMouseEvent *e)
{
//保存鼠标按下位置
mousePressPosition = QVector2D(e->localPos());
}
void MainWidget::mouseMoveEvent(QMouseEvent *e)
{
// 鼠标释放位置-鼠标按下位置
QVector2D diff = QVector2D(e->localPos()) - mousePressPosition;
//旋转轴垂直于鼠标位置差
// vector
QVector3D n = QVector3D(diff.y(), diff.x(), 0.0).normalized();
//相对于鼠标扫描长度加快角速度
qreal acc = diff.length() / 100.0;
// 计算新的旋转轴作为加权和
rotationAxis = (rotationAxis * angularSpeed + n * acc).normalized();
// 提高角速度
angularSpeed = acc;
// 如果加等的话会更快
// angularSpeed += acc;
rotation = QQuaternion::fromAxisAndAngle(rotationAxis, angularSpeed) * rotation;
update();
}
void MainWidget::timerEvent(QTimerEvent *)
{
// 降低角速度(摩擦)
angularSpeed *= 0.99;
// 当停止旋转速度都低于阈值
if (angularSpeed < 0.01) {
angularSpeed = 0.0;
} else {
//更新轮换
rotation = QQuaternion::fromAxisAndAngle(rotationAxis, angularSpeed) * rotation;
// 请求更新
update();
}
}
void MainWidget::initializeGL()
{
initializeOpenGLFunctions();//初始化gl
glClearColor(0, 0, 0, 1);//清除颜色
initShaders();//初始化着色器
initTextures();//初始化纹理
// 启用深度缓冲区
glEnable(GL_DEPTH_TEST);
// 启用背面剔除
glEnable(GL_CULL_FACE);
geometries = new GeometryEngine;//绘图矩阵
// 使用QBasic定时器,因为它比QTIMER快
timer.start(12, this);
}
void MainWidget::initShaders()
{
// 编译顶点着色器,添加资源
if (!program.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/vshader.glsl")){
close();
}
//编译片段着色器,添加资源
if (!program.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/fshader.glsl")){
close();
}
//链接着色器管道
if (!program.link()){
close();
}
// 绑定着色器管道以供使用
if (!program.bind())
close();
}
void MainWidget::initTextures()
{
// 加载图像
texture = new QOpenGLTexture(QImage(":/test.png").mirrored());
// 设置纹理最小化的最近过滤模式
texture->setMinificationFilter(QOpenGLTexture::Nearest);
//纹理放大的双线性滤波模式
texture->setMagnificationFilter(QOpenGLTexture::Linear);
// 重复纹理贴图坐标
// f.ex. texture coordinate (1.1, 1.2) is same as (0.1, 0.2)
texture->setWrapMode(QOpenGLTexture::Repeat);
}
void MainWidget::resizeGL(int w, int h)
{
//calculate方面比
qreal aspect = qreal(w) / qreal(h ? h : 1);
// 设近平面为3,远平面为7,视场为45度
const qreal zNear = 3.0, zFar = 7.0, fov = 45.0;
// 复位投影
projection.setToIdentity();
// 集合透视投影
projection.perspective(fov, aspect, zNear, zFar);
}
void MainWidget::paintGL()
{
// 清除的颜色和深度缓冲器
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
texture->bind();//绑定
// 计算模型视图变换
QMatrix4x4 matrix;
matrix.translate(0.0, 0.0, -5.0);//平移
matrix.rotate(rotation);//旋转
//集合模型视图投影矩阵
program.setUniformValue("mvp_matrix", projection * matrix);
// 使用纹理单元,包含cube.png 0
program.setUniformValue("texture", 0);
//绘制立方体几何图形
geometries->drawCubeGeometry(&program);
}
#include "geometryengine.h"
#include <QVector2D>
#include <QVector3D>
struct VertexData
{
QVector3D position;
QVector2D texCoord;
};
//! [0]
GeometryEngine::GeometryEngine()
: indexBuf(QOpenGLBuffer::IndexBuffer)
{
initializeOpenGLFunctions();//初始化
// Generate 2 VBOs
arrayBuf.create();//创建
indexBuf.create();//创建
// Initializes cube geometry and transfers it to VBOs
initCubeGeometry();
}
GeometryEngine::~GeometryEngine()
{
arrayBuf.destroy();//释放
indexBuf.destroy();
}
void GeometryEngine::initCubeGeometry()
{
// 对于立方体,我们只需要8个顶点,但是我们必须
// 因为纹理坐标不同,所以每个面都有顶点。
VertexData vertices[] = {
// Vertex data for face 0
{QVector3D(-1.0f, -1.0f, 1.0f), QVector2D(0.0f, 0.0f)}, // v0
{QVector3D( 1.0f, -1.0f, 1.0f), QVector2D(0.33f, 0.0f)}, // v1
{QVector3D(-1.0f, 1.0f, 1.0f), QVector2D(0.0f, 0.5f)}, // v2
{QVector3D( 1.0f, 1.0f, 1.0f), QVector2D(0.33f, 0.5f)}, // v3
// Vertex data for face 1
{QVector3D( 1.0f, -1.0f, 1.0f), QVector2D( 0.0f, 0.5f)}, // v4
{QVector3D( 1.0f, -1.0f, -1.0f), QVector2D(0.33f, 0.5f)}, // v5
{QVector3D( 1.0f, 1.0f, 1.0f), QVector2D(0.0f, 1.0f)}, // v6
{QVector3D( 1.0f, 1.0f, -1.0f), QVector2D(0.33f, 1.0f)}, // v7
// Vertex data for face 2
{QVector3D( 1.0f, -1.0f, -1.0f), QVector2D(0.66f, 0.5f)}, // v8
{QVector3D(-1.0f, -1.0f, -1.0f), QVector2D(1.0f, 0.5f)}, // v9
{QVector3D( 1.0f, 1.0f, -1.0f), QVector2D(0.66f, 1.0f)}, // v10
{QVector3D(-1.0f, 1.0f, -1.0f), QVector2D(1.0f, 1.0f)}, // v11
// Vertex data for face 3
{QVector3D(-1.0f, -1.0f, -1.0f), QVector2D(0.66f, 0.0f)}, // v12
{QVector3D(-1.0f, -1.0f, 1.0f), QVector2D(1.0f, 0.0f)}, // v13
{QVector3D(-1.0f, 1.0f, -1.0f), QVector2D(0.66f, 0.5f)}, // v14
{QVector3D(-1.0f, 1.0f, 1.0f), QVector2D(1.0f, 0.5f)}, // v15
// Vertex data for face 4
{QVector3D(-1.0f, -1.0f, -1.0f), QVector2D(0.33f, 0.0f)}, // v16
{QVector3D( 1.0f, -1.0f, -1.0f), QVector2D(0.66f, 0.0f)}, // v17
{QVector3D(-1.0f, -1.0f, 1.0f), QVector2D(0.33f, 0.5f)}, // v18
{QVector3D( 1.0f, -1.0f, 1.0f), QVector2D(0.66f, 0.5f)}, // v19
// Vertex data for face 5
{QVector3D(-1.0f, 1.0f, 1.0f), QVector2D(0.33f, 0.5f)}, // v20
{QVector3D( 1.0f, 1.0f, 1.0f), QVector2D(0.66f, 0.5f)}, // v21
{QVector3D(-1.0f, 1.0f, -1.0f), QVector2D(0.33f, 1.0f)}, // v22
{QVector3D( 1.0f, 1.0f, -1.0f), QVector2D(0.66f, 1.0f)} // v23
};
// Indices for drawing cube faces using triangle strips.
// Triangle strips can be connected by duplicating indices
// between the strips. If connecting strips have opposite
// vertex order then last index of the first strip and first
// index of the second strip needs to be duplicated. If
// connecting strips have same vertex order then only last
// index of the first strip needs to be duplicated.
GLushort indices[] = {
0, 1, 2, 3, 3, // Face 0 - triangle strip ( v0, v1, v2, v3)
4, 4, 5, 6, 7, 7, // Face 1 - triangle strip ( v4, v5, v6, v7)
8, 8, 9, 10, 11, 11, // Face 2 - triangle strip ( v8, v9, v10, v11)
12, 12, 13, 14, 15, 15, // Face 3 - triangle strip (v12, v13, v14, v15)
16, 16, 17, 18, 19, 19, // Face 4 - triangle strip (v16, v17, v18, v19)
20, 20, 21, 22, 23 // Face 5 - triangle strip (v20, v21, v22, v23)
};
// Transfer vertex data to VBO 0
arrayBuf.bind();
arrayBuf.allocate(vertices, 24 * sizeof(VertexData));
// Transfer index data to VBO 1
indexBuf.bind();
indexBuf.allocate(indices, 34 * sizeof(GLushort));
}
void GeometryEngine::drawCubeGeometry(QOpenGLShaderProgram *program)
{
// 告诉OpenGL使用哪些VBOS
arrayBuf.bind();
indexBuf.bind();
//位置偏移
quintptr offset = 0;
// 告诉OpenGL可编程流水线如何定位顶点位置数据
int vertexLocation = program->attributeLocation("a_position");
program->enableAttributeArray(vertexLocation);
program->setAttributeBuffer(vertexLocation, GL_FLOAT, offset, 3, sizeof(VertexData));
// 纹理坐标偏移
offset += sizeof(QVector3D);
// 告诉OpenGL可编程流水线如何定位顶点纹理坐标数据
int texcoordLocation = program->attributeLocation("a_texcoord");
program->enableAttributeArray(texcoordLocation);
program->setAttributeBuffer(texcoordLocation, GL_FLOAT, offset, 2, sizeof(VertexData));
// 使用VBO 1索引绘制立方体几何图形
glDrawElements(GL_TRIANGLE_STRIP, 34, GL_UNSIGNED_SHORT, 0);
}