3维空间的物体比2维更真实,更酷炫;其变换大家都知道是通过矩阵转换来的;可具体到底是怎样转换的呢,网上大多都帖的一些理论,本文给出一个示例供大家学习。
本示例在Qt中显示,可以接收用户输入。同样也是开发opengl程序,步奏当然一样的。我这里将渲染器分离了出来以便能复用
在Qt中的显示我使用的是QOpenglWidget。
显示部分申明如下
#ifndef WIDGET_H
#define WIDGET_H
#include <QOpenGLWidget>
#include "matrixshader.h"
#include <QTimer>
class Widget : public QOpenGLWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
protected:
void resizeGL(int w, int h) override; //当窗口变化时的回调
void initializeGL() override; //初始化回调
void paintGL() override; //绘画回调
void wheelEvent(QWheelEvent *event) override; //鼠标滚轮回调,用于输入
private:
MatrixShader m_matxShader; //定义一个写好的渲染器
QMatrix4x4 m_proMM; //投影矩阵
QVector3D m_eye,m_target; //摄像机位置
float m_angle = 0; //水平绕y轴旋转角度
float m_curX = 0,m_curY = 0,m_curZ = 0; //物体自身3个轴向旋转角度
float m_ez = 5; //摄像机位置的初始z值
QTimer m_tm; //闹钟用于触发刷新
private slots:
void slotTimeout();
};
#endif // WIDGET_H
实现部分如下
#include "widget.h"
#include <QWheelEvent>
#include <QDebug>
Widget::Widget(QWidget *parent)
: QOpenGLWidget(parent),
m_target(0,0,-1)
{
m_tm.setInterval(60);
connect(&m_tm,SIGNAL(timeout()),this,SLOT(slotTimeout()));
m_tm.start();
}
Widget::~Widget()
{
}
void Widget::resizeGL(int w, int h)
{
m_proMM.setToIdentity();
m_proMM.perspective(60.0f,GLfloat(w)/h,0.01f,100.0f); //透视宽高比和窗体宽高比一样,可以保证物体不变形
}
void Widget::initializeGL()
{
m_matxShader.initialize(); //渲染器初始化
}
void Widget::paintGL()
{
QMatrix4x4 mvpM; //变换矩阵
mvpM.rotate(30,1,0,0); //先绕x轴旋转30度
mvpM.rotate(m_angle,0,1,0); //再绕y轴旋转m_angle角度
mvpM.translate(4,0,0); //再平移沿x轴向右平移4
mvpM.rotate(m_curX,1,0,0); //自身绕x轴旋转m_curX角度
mvpM.rotate(m_curY,0,1,0); //自身绕y轴旋转m_curY角度
mvpM.rotate(m_curZ,0,0,1); //自身绕z轴旋转m_curZ角度
QMatrix4x4 camera;
m_eye.setZ(m_ez);
camera.lookAt(m_eye,m_eye + m_target,QVector3D(0,1,0)); //设置摄像机位置为垂直向里看,头顶方向垂直向上
m_matxShader.render(QOpenGLContext::currentContext()->extraFunctions(),m_proMM,camera,mvpM); //渲染
}
void Widget::slotTimeout()
{
//每过一定时间,增加一定值
m_angle += 5;
m_curX += 5;
m_curY += 5;
m_curZ += 5;
update();
}
void Widget::wheelEvent(QWheelEvent *event)
{
//调整摄像位置
if (! event->pixelDelta().isNull()) {
m_ez += event->pixelDelta().y();
} else if (!event->angleDelta().isNull()) {
m_ez += (event->angleDelta() / 120).y();
}
event->accept();
update();
}
渲染器申明如下
#ifndef MATRIXSHADER_H
#define MATRIXSHADER_H
#include <QOpenGLShaderProgram>
#include <QOpenGLExtraFunctions>
#include <QOpenGLBuffer>
#include <QOpenGLTexture>
class MatrixShader
{
public:
MatrixShader() = default;
void initialize();
void render(QOpenGLExtraFunctions *f,QMatrix4x4 &projM,QMatrix4x4 &came,QMatrix4x4 &mvp);
private:
QOpenGLShaderProgram m_program;
QOpenGLBuffer m_vbo;
QOpenGLTexture *m_texs[6]; //正文体的6个纹理
};
#endif // MATRIXSHADER_H
实现如下
#include "matrixshader.h"
void MatrixShader::initialize()
{
//编译链接shader
m_program.addCacheableShaderFromSourceFile(QOpenGLShader::Vertex,"vsrc.vsh");
m_program.addCacheableShaderFromSourceFile(QOpenGLShader::Fragment,"fsrc.fsh");
m_program.link();
//生成顶点数据和纹理
const GLfloat coords[6][8][3] = {
{ { +1, -1, -1 }, { -1, -1, -1 }, { -1, +1, -1 }, { +1, +1, -1 } },
{ { +1, +1, -1 }, { -1, +1, -1 }, { -1, +1, +1 }, { +1, +1, +1 } },
{ { +1, -1, +1 }, { +1, -1, -1 }, { +1, +1, -1 }, { +1, +1, +1 } },
{ { -1, -1, -1 }, { -1, -1, +1 }, { -1, +1, +1 }, { -1, +1, -1 } },
{ { +1, -1, +1 }, { -1, -1, +1 }, { -1, -1, -1 }, { +1, -1, -1 } },
{ { -1, -1, +1 }, { +1, -1, +1 }, { +1, +1, +1 }, { -1, +1, +1 } }
};
for(int i = 0; i < 6; i++){
m_texs[i] = new QOpenGLTexture(QImage(QString("images/side%1.png").arg(i + 1)).mirrored());
}
QVector<GLfloat> vertData;
for (int i = 0; i < 6; ++i) {
for (int j = 0; j < 4; ++j) {
// vertex position
vertData.append(coords[i][j][0]);
vertData.append(coords[i][j][1]);
vertData.append(coords[i][j][2]);
// texture coordinate
vertData.append(j == 0 || j == 3);
vertData.append(j == 0 || j == 1);
}
}
//在opengl server端开劈数据缓冲区
m_vbo.create();
m_vbo.bind();
m_vbo.allocate(vertData.constData(),vertData.count() * sizeof(GLfloat));
}
void MatrixShader::render(QOpenGLExtraFunctions *f, QMatrix4x4 &projM, QMatrix4x4 &came, QMatrix4x4 &mvp)
{
f->glClearColor(0.0, 0.0, 0.0, 0.0);
f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
f->glEnable(GL_DEPTH_TEST);
f->glEnable(GL_CULL_FACE);
m_program.bind(); //使用m_program的一套渲染器
m_vbo.bind(); //将开避在opengl server端的内存绑定过来 以便于渲染使用
f->glActiveTexture(GL_TEXTURE0 + 2); //激活2号纹理,用于渲染使用
m_program.setUniformValue("uProjMatrix",projM);
m_program.setUniformValue("camMatrix",came);
m_program.setUniformValue("uMVPMatrix",mvp);
m_program.setUniformValue("uTexture",2); //指明栅格化着色器中的纹理单元为激活的2号
m_program.enableAttributeArray(0); //使能顶点属性
m_program.enableAttributeArray(1); //使能纹理属性
m_program.setAttributeBuffer(0,GL_FLOAT,0,3,5*sizeof(GLfloat)); //设置顶点数据
m_program.setAttributeBuffer(1,GL_FLOAT,3 * sizeof(GLfloat),2,5 * sizeof(GLfloat)); //设置纹理数据
for(int i = 0; i < 6; i++){
m_texs[i]->bind(2); //将纹理数据绑定到激活的2号纹理单元,将纹理数据传递到了激活的实际的纹理单元上
f->glDrawArrays(GL_TRIANGLE_FAN, i * 4, 4);
}
m_program.disableAttributeArray(0);
m_program.disableAttributeArray(1);
m_vbo.release();
m_program.release();
f->glDisable(GL_DEPTH_TEST);
f->glDisable(GL_CULL_FACE);
}
顶点着色器如下
#version 330
uniform mat4 uMVPMatrix;
uniform mat4 uProjMatrix;
uniform mat4 camMatrix;
layout (location = 0) in vec3 aPosition;
layout (location = 1) in vec2 aTexture;
smooth out vec4 vColor;
void main(void)
{
gl_Position = uProjMatrix * camMatrix * uMVPMatrix * vec4(aPosition,1); //注意顺序,先乘的后作用
vColor = vec4(aTexture,1,1);
}
栅格化着色器如下
#version 330
uniform sampler2D uTexture;
smooth in vec4 vColor;
out vec4 fragColor;
void main(void)
{
fragColor = texture2D(uTexture,vColor.rg);
}
运行效果如下:
1、需要显示支持opengl 3.3 compatibility context
2、可以用鼠标控制远近
3、我的设计是先绕x轴旋转固定的30度,这样看起来就是倾斜的;再绕y轴旋转m_angle角度(每过一定时间递增);再向x轴正方向平移4,这样就会在一个倾斜的平面绕y轴旋转了;然后在平移后加入自身旋转就达到上面的效果了;关于纹理这里就不多说了
本项目已在gitlab上,有兴趣的朋友可以下载;不要忘了加star哦。https://gitlab.com/gitHubwhl562916378/matrix