1 はじめに
OpenGL 自体にはカメラ (Camera) の概念はありませんが、シーン内のすべてのオブジェクトを逆方向に移動することでカメラをシミュレートし、シーンが動いているという感覚ではなく、私たちが動いているという感覚を作り出すことができます。
カメラを定義するには、ワールド空間でのカメラの位置、カメラが見ている方向、カメラの右を指すベクトル、および上を指すベクトルが必要です。
カメラの位置:
カメラの位置を取得するのは簡単です。カメラ位置は、ワールド空間内のカメラ位置を指す単純なベクトルです。正の Z 軸が画面から自分の方を向いていることを忘れないでください。カメラを後方に移動させたい場合は、正の Z 軸に沿って移動します。
QVector3D cameraPos = QVector3D( 0.0f, 0.0f, 2.0f);//摄像机位置
カメラの方向:
これはカメラがどの方向を向いているかを指します。次に、カメラをシーンの原点 (0, 0, 0) に向けてみましょう。シーン原点ベクトルからカメラ位置ベクトルを減算した結果が、カメラ ポインティング ベクトルです。カメラが Z 軸の負の方向を指していることがわかっているため、方向ベクトル (方向ベクトル) がカメラの Z 軸の正の方向を指すようにします。減算の順序を入れ替えると、カメラの正の Z 軸の方向を指すベクトルが得られます。
cameraTarget = QVector3D( 0.0f, 0.0f, 0.0f);//摄像机看到的位置
cameraDirection = QVector3D(cameraPos - cameraTarget);//摄像机的方向
cameraDirection.normalize();
右軸:
これは、カメラ空間の X 軸の正の方向を表します。正しいベクトルを取得するには、ちょっとしたトリックを使用する必要があります。まずアップ ベクトル(Up Vector) を定義します。次に、上ベクトルと2 番目のステップで得られたカメラ方向ベクトルを相互乗算します。2 つのベクトルの外積の結果は、同時に両方のベクトルに垂直になるため、(2 つのベクトルの外積の順序を入れ替えると、x 軸の正の方向を指すベクトルが得られます) 、x 軸の負の方向を指す反対のベクトルが得られます)。
up = QVector3D(0.0f, 1.0f, 0.0f);
cameraRight = QVector3D::crossProduct(up,cameraDirection);//两个向量叉乘的结果会同时垂直于两向量,因此我们会得到指向x轴正方向的那个向量
cameraRight.normalize();
上軸:
右ベクトルとカメラ方向ベクトルを外積して、カメラの上向きベクトルを取得します。
cameraUp = QVector3D::crossProduct(cameraDirection,cameraRight);
見る:
行列を使用することの優れた点の 1 つは、3 つの相互に直交する (または非線形) 軸で座標空間を定義すると、それらの 3 つの軸に平行移動ベクトルを加えた行列を作成し、その行列に Transform を乗算できることです。任意のベクトルを使用して空間を座標します。
QTime gtime;
QMatrix4x4 view;
float radius = 10.0f; //圆半径
float time = gtime.elapsed()/1000.0;
float camx = sin(time) * radius;
float camz = cos(time) * radius;
view.lookAt(QVector3D(camx,0.0,camz),cameraTarget,up);
パラメータの概要:
カメラ位置、ターゲット位置、およびワールド空間での上向きベクトルを表すベクトル(右ベクトルを計算するために使用するベクトル)。
2. 例
#ifndef MYOPENGLWIDGET_H
#define MYOPENGLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLTexture>
#include <QImage>
#include <QOpenGLShaderProgram>
#include <QVector3D>
#include <QVector>
class MyOpenGLWidget : public QOpenGLWidget,public QOpenGLFunctions_3_3_Core
{
public:
MyOpenGLWidget(QWidget *parent = nullptr);
protected:
virtual void initializeGL();
virtual void paintGL();
virtual void resizeGL(int w, int h);
private:
QOpenGLTexture *m_wall;
QOpenGLTexture *m_face;
QOpenGLShaderProgram *m_program;
QVector3D cameraPos;
QVector3D cameraTarget;
QVector3D cameraDirection;
QVector3D up;
QVector3D cameraRight;
QVector3D cameraUp;
};
#endif // MYOPENGLWIDGET_H
#include "myopenglwidget.h"
#include <QMatrix4x4>
#include <QTime>
#include <QTimer>
#include <math.h>
float vertices[] = {
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f
};
GLuint indices[] = {
0, 1, 3,
1, 2, 3
};
//顶点着色器语言
const GLchar* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 position;\n"
"layout (location = 1) in vec2 texCoord;\n"
"out vec2 outTexCoord;\n"
"uniform mat4 model;\n"
"uniform mat4 view;\n"
"uniform mat4 projection;\n"
"void main()\n"
"{\n"
"gl_Position = projection * view * model * vec4(position,1.0);\n"
"outTexCoord = texCoord;\n"
"}\n\0";
//片段着色器语言
//texture函数会使用之前设置的纹理参数对相应的颜色值进行采样
//mix按一定的比例,混合两个纹理颜色
const GLchar* fragmentShaderSource = "#version 330 core\n"
"out vec4 color;\n"
"uniform sampler2D ourTexture1;\n"
"uniform sampler2D ourTexture2;\n"
"in vec2 outTexCoord;\n"
"void main()\n"
"{\n"
"color = mix(texture(ourTexture1, outTexCoord),texture(ourTexture2, vec2(outTexCoord.x, outTexCoord.y)),0.45);\n"
"}\n\0";
GLuint VBO, VAO,EBO;
GLuint shaderProgram;
QTimer *timer;
QTime gtime;
QVector<QVector3D> cubePositions = {
QVector3D( 0.0f, 0.0f, 0.0f),
QVector3D( 2.0f, 5.0f, -15.0f),
QVector3D(-1.5f, -2.2f, -2.5f),
QVector3D(-3.8f, -2.0f, -12.3f),
QVector3D( 2.4f, -0.4f, -3.5f),
QVector3D(-1.7f, 3.0f, -7.5f),
QVector3D( 1.3f, -2.0f, -2.5f),
QVector3D( 1.5f, 2.0f, -2.5f),
QVector3D( 1.5f, 0.2f, -1.5f),
QVector3D(-1.3f, 1.0f, -1.5f)
};
MyOpenGLWidget::MyOpenGLWidget(QWidget *parent)
: QOpenGLWidget(parent)
{
timer = new QTimer();
timer->start(50);
connect(timer,&QTimer::timeout,[=]{
update();
});
gtime.start();
cameraPos = QVector3D( 0.0f, 0.0f, 2.0f);//摄像机位置
cameraTarget = QVector3D( 0.0f, 0.0f, 0.0f);//摄像机看到的位置
cameraDirection = QVector3D(cameraPos - cameraTarget);//摄像机的方向
cameraDirection.normalize();
up = QVector3D(0.0f, 1.0f, 0.0f);
cameraRight = QVector3D::crossProduct(up,cameraDirection);//两个向量叉乘的结果会同时垂直于两向量,因此我们会得到指向x轴正方向的那个向量
cameraRight.normalize();
cameraUp = QVector3D::crossProduct(cameraDirection,cameraRight);
}
void MyOpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
m_program = new QOpenGLShaderProgram();
m_program->addShaderFromSourceCode(QOpenGLShader::Vertex,vertexShaderSource);
m_program->addShaderFromSourceCode(QOpenGLShader::Fragment,fragmentShaderSource);
m_program->link();
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);//绑定VAO
glBindBuffer(GL_ARRAY_BUFFER, VBO);//顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//把顶点数据复制到缓冲的内存中GL_STATIC_DRAW :数据不会或几乎不会改变。
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glBindVertexArray(0);//解绑VAO
m_wall = new QOpenGLTexture(QImage("./container.jpg").mirrored());
m_face = new QOpenGLTexture(QImage("./awesomeface.png").mirrored());
m_program->bind();
m_program->setUniformValue("ourTexture1",0);
m_program->setUniformValue("ourTexture2",1);
//设置投影透视矩阵
QMatrix4x4 projection;
projection.perspective(60,(float)( width())/(height()),0.1,100);
m_program->setUniformValue("projection",projection);
}
void MyOpenGLWidget::paintGL()
{
glClearColor(0.2f,0.3f,0.3f,1.0f);
glEnable(GL_DEPTH_TEST);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
QMatrix4x4 model;
QMatrix4x4 view;
//随时间变化的x和z坐标
float radius = 10.0f;
float time = gtime.elapsed()/1000.0;
float camx = sin(time) * radius;
float camz = cos(time) * radius;
view.lookAt(QVector3D(camx,0.0,camz),cameraTarget,up);
m_program->bind();
glBindVertexArray(VAO);//绑定VAO
m_wall->bind(0);
m_face->bind(1);
//设置观察矩阵
m_program->setUniformValue("view",view);
foreach(auto pos , cubePositions)
{
model.setToIdentity();
model.translate(pos);
//model.rotate(time,1.0f,5.0f,3.0f);
//设置模型矩阵
m_program->setUniformValue("model",model);
glDrawArrays(GL_TRIANGLES,0,36);
}
}
void MyOpenGLWidget::resizeGL(int w, int h)
{
}
3. 移動の自由
lookat 関数は次のようになります。まず、カメラの位置を前に定義した CameraPos に設定します。方向は、現在位置に先ほど定義した方向ベクトルを加えたものです。これにより、私たちがどのように動いても、カメラは常にターゲットの方向を向くことになります。
QMatrix4x4 view;
view.lookAt(cameraPos,cameraPos + cameraFront,cameraUp);
キーボードイベント:
void MyOpenGLWidget::keyPressEvent(QKeyEvent *event)
{
qDebug()<<event->key();
cameraSpeed = 2.5 * 100 / 1000.0;
switch (event->key()) {
case Qt::Key_W:{
cameraPos += cameraSpeed * cameraFront;
}
break;
case Qt::Key_S:{
cameraPos -= cameraSpeed * cameraFront;
}
break;
case Qt::Key_A:{
cameraPos -= cameraSpeed * cameraRight;
}
break;
case Qt::Key_D:{
cameraPos += cameraSpeed * cameraRight;
}
break;
default:
break;
}
update();
}
WASDキーのいずれかを押すと、それに応じてカメラの位置が更新されます。前方または後方に移動したい場合は、位置ベクトルに方向ベクトルを加算または減算します。左右に移動したい場合は、外積を使用して右ベクトルを作成し、それに沿って移動します。これにより、カメラを操作するときにおなじみの Strafe 効果が作成されます。
4. 画角移動
ただキーボードを操作して移動するだけではあまり楽しくありません。特にまだ方向転換ができないので、動きが非常に限られています。
視野角を変更できるようにするには、マウス入力に応じて CameraFront ベクトルを変更する必要があります。
興味があれば、オイラー角についてさらに詳しく学ぶことができます。ここに直接コードがあります。
float PI = 3.1415926;
QPoint deltaPos;
void MyOpenGLWidget::mouseMoveEvent(QMouseEvent *event)
{
static float yaw = -90;
static float pitch = 0;
//上一次的位置
static QPoint lastPos(width()/2,height()/2);
//当前的位置
auto currentPos = event->pos();
//
deltaPos = currentPos-lastPos;
lastPos = currentPos;
//灵敏度
float sensitivity = 0.1f;
deltaPos *= sensitivity;
yaw += deltaPos.x();
pitch -= deltaPos.y();
if(pitch > 89.0f)
pitch = 89.0f;
if(pitch < -89.0f)
pitch = -89.0f;
cameraFront.setX(cos(yaw*PI/180.0) * cos(pitch *PI/180));
cameraFront.setY(sin(pitch*PI/180));
cameraFront.setZ(sin(yaw*PI/180) * cos(pitch *PI/180));
cameraFront.normalize();
update();
}
5. ズーム
視野(Field of View) またはfov は、私たちが見ることができるシーンの範囲を定義すると言います。視野が狭くなると、シーンによって投影される空間が狭くなり、ズームインしたように感じられます(Zoom In)。マウスのスクロール ホイールを使用してズームインします。
フレームごとに透視投影行列を GPU にアップロードする必要がありますが、今度は fov 変数を視野として使用します。
projection.perspective(fov,(float)( width())/(height()),0.1,100);
ホイールイベント:
void MyOpenGLWidget::wheelEvent(QWheelEvent *event)
{
if(fov >= 1.0f && fov <= 75.0f)
fov -= event->angleDelta().y()/120;
if(fov <= 1.0f)
fov = 1.0f;
if(fov >= 75.0f)
fov = 75.0f;
update();
}
6. 完全なソースコード