如何在3D世界中正确旋转相机? [英] How do I rotate my camera correctly in my 3D world?

查看:59
本文介绍了如何在3D世界中正确旋转相机?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在学习3D编程,目前我正在从事FPS相机风格的业余项目.我创建了那些我应该使用的矩阵,但是我在查看如何将所有内容连接到摄像机旋转方面遇到了麻烦.所以,我有一个相机类:

get_World_To_View矩阵

  mat4f rotMatrix = mat4f :: rotation(-rotation.z,-rotation.x,-rotation.y);rotMatrix.transpose();返回rotMatrix * mat4f :: translation(-position); 

get_ViewToWorld矩阵

 返回mat4f :: translation(position)* mat4f :: rotation(-rotation.z,-rotation.x,-rotation.y); 

get_ProjectionMatrix:

 返回mat4f :: projection(vfov,Aspect,zNear,zFar); 

vector3 for get_forward

  mat4f ViewToWorld = mat4f :: translation(position)* mat4f :: rotation(-rotation.z,-rotation.x,-rotation.y);vec4f forward = ViewToWorld * vec4f(0,0,-1,0);返回forward.xyz(); 

和get_rightwards:

  mat4f ViewToWorld = mat4f :: translation(position)* mat4f :: rotation(-rotation.z,-rotation.x,-rotation.y);vec4f向右= ViewToWorld * vec4f(-1,0,0,0);返回rightways.xyz(); 

从此以后,我想我需要一个实际上可以旋转相机的功能,但是我尝试了几件事,但我无法真正理解应该如何将其迷惑.

我渲染了两个矩阵:get_WorldToView和get_ProjectionMatrix,我可以使用WASD键移动.有没有人提示我如何考虑我的RotateCamera()函数?我错过了一些非常重要的东西吗?我对编程很陌生,而且仍然很难看到"我面前的逻辑.

请尽可能清楚:我在Main.cpp(更新)中有一个函数,可以像这样输入.

  If(mousedeltaX!= 0.0f || mousedeltaY!= 0.0f){//调用旋转相机的函数.} 

该功能我想如何思考.

当我使用WASD键移动时,我只是调用一个函数Move(),将位置+ =设置为具有正确的x,y,z方向乘以camera_velocity的vector3,因此与旋转无关本身.

解决方案

我想演示如何通过对摄像机的4×4矩阵进行连续更改来简单地实现摄像机运动.

因此,相机矩阵是视图矩阵的逆矩阵.摄像头矩阵代表摄像头相对于世界原点的坐标(位置,方向),而视图矩阵代表相反的–相对于相机原点的世界位置.后者是将3d内容映射到屏幕时进行渲染所需的转换.但是,人类(没有自我中心的干扰)被用来看待自己与世界的关系.因此,我认为对相机矩阵的操作更加直观.

左3d视图显示第一人称相机,右视图从上至下用红色三角形标记第一人称相机的位置/方向.

最初将摄影机矩阵设置为单位矩阵,该矩阵向y方向的仰角较小,以从地面上方出现–x-z平面.

  • x轴指向右.
  • y轴指向上.
  • z轴指向屏幕外.

因此,视线矢量是负z轴.

因此,向前移动可以通过在翻译中添加负z值来实现.

照相机向上矢量是y轴.

因此,绕y轴正向旋转可实现向左旋转,而绕负轴可向右旋转.

现在,如果照相机已经转向,那么如何才能向前考虑转向的视线呢?

诀窍是将平移应用于z轴,但应用于相机的局部坐标系.

使用矩阵执行此操作,您只需要正确的乘法顺序即可.

  void moveObs(QMatrix4x4& matCam,//相机矩阵double v,//速度(向前,向后)double rot)//旋转(向左,向右){QMatrix4x4 matFwd;matFwd.translate(0,0,-v);//前进/后退:-z是视线QMatrix4x4 matRot;matRot.rotate(rot,0,1,0);//向左/向右转:y是camera-up-vectormatCam * = matRot * matFwd;} 

我使用了 QMatrix4x4 ,因为这就是我手头上的东西.在

Im triyng to learn 3d programming and Im currently working on a FPS camera-style for a hobby project. I've created those matrices I believe I should use but Im having trouble seeing how to connect everything to the camera rotation. So, I have a camera Class with:

get_World_To_View matrix

    mat4f rotMatrix = mat4f::rotation(-rotation.z, -rotation.x, -rotation.y);
    rotMatrix.transpose();
    return rotMatrix * mat4f::translation(-position);

get_ViewToWorld matrix

return mat4f::translation(position) * mat4f::rotation(-rotation.z, -rotation.x, -rotation.y);

get_ProjectionMatrix:

return mat4f::projection(vfov, aspect, zNear, zFar);

vector3 for get_forward

mat4f ViewToWorld = mat4f::translation(position) * mat4f::rotation(-rotation.z, -rotation.x, - 
rotation.y);
vec4f forward = ViewToWorld * vec4f(0, 0, -1, 0);
return forward.xyz();

and get_rightwards:

mat4f ViewToWorld = mat4f::translation(position) * mat4f::rotation(-rotation.z, -rotation.x, - 
rotation.y);
vec4f rightways = ViewToWorld * vec4f(-1, 0, 0, 0);
return rightways.xyz();

From here on Im thinking that a need a function that actually rotate my camera, but I've tried several things but I cant really understand how it should be puzzeled together.

I render my two matrices: get_WorldToView and get_ProjectionMatrix and Im able to move around with the WASD keys. Does anyone have a tip for how I should think for my RotateCamera()-function? Am I missing something very important? Im quite new to programming and Im still having a hard time "seeing" the logic before me.

So to be as clear as I can: I have a function in Main.cpp (Update) for input that works like.

If(mousedeltaX != 0.0f || mousedeltaY != 0.0f)
{
   // Call a function that rotate the camera.
}

Its that function I want some help on how to think.

When I move with the WASD keys I just call a function Move() that sets the position += to the vector3 with the correct x,y,z direction multiplied by camera_velocity, so that have ofcause nothing to do with the rotation itself.

解决方案

I want to demonstrate how camera motion can be simply achieved applying continuous changes to the 4×4 matrix for the camera.

Thereby the camera matrix is the inverse of the view matrix. While the camera matrix represents coordinates (position, orientation) of the camera relative to world origin, the view matrix represents the opposite – the position of world relative to camera origin. The latter is a needed transformation for rendering when 3d contents is mapped to the screen. However, humans (without egocentrical disturbance) are used to see themselves in relation to world. Hence, I consider manipulation of camera matrix more intuitive.

The left 3d view shows the first-person-camera, the right a view from top where the position/orientation of first-person-camera is remarked by the red triangle.

The camera matrix is initially set to identity matrix with a small elevation into y direction to appear above from ground – the x-z plane.

  • The x-axis points to right.
  • The y-axis points up.
  • The z-axis points out of screen.

So, the line-of-sight vector is the negative z-axis.

Hence, moving forward can be achieved adding negative z-values to translation.

The camera-up vector is the y-axis.

Hence, turning to left can be achieved with a positive rotation about y-axis, turning to right with a negative.

Now, if the camera has been turned how can moving forward consider that turned line-of-sight?

The trick is to apply the translation to the z-axis but in the local coordinate system of the camera.

Doing this with matrices, you just need the correct order for multiplications.

void moveObs(
  QMatrix4x4 &matCam, // camera matrix
  double v, // speed (forwards, backwards)
  double rot) // rotate (left, right)
{
  QMatrix4x4 matFwd; matFwd.translate(0, 0, -v); // moving forwards / backwards: -z is line-of-sight
  QMatrix4x4 matRot; matRot.rotate(rot, 0, 1, 0); // turning left / right: y is camera-up-vector
  matCam *= matRot * matFwd;
}

I used QMatrix4x4 as this was what I had at hand. It shouldn't be that different in other APIs like glm or DirectXMath as all of them are based on the same mathematical basics.

(Though, you have always to check whether the specific API exposes the matrix row-major or column major: Matrix array order of OpenGL Vs DirectX.)

I must admit that I'm fellow of the OpenGL community, ignoring Direct3D mostly. Hence, I didn't feel able to prepare an MCVE in Direct3D but made one in OpenGL. I used the Qt framework which provides a lot of things out of the box to keep the sample as compact as possible. (That's not quite easy for 3d programming as well as for GUI programming and especially not for the combination of both.)

The (complete) source code testQOpenGLWidgetNav.cc:

#include <QtWidgets>

/* This function is periodically called to move the observer
 * (aka player, aka first person camera).
 */
void moveObs(
  QMatrix4x4 &matCam, // camera matrix
  double v, // speed (forwards, backwards)
  double rot) // rotate (left, right)
{
  QMatrix4x4 matFwd; matFwd.translate(0, 0, -v); // moving forwards / backwards: -z is line-of-sight
  QMatrix4x4 matRot; matRot.rotate(rot, 0, 1, 0); // turning left / right: y is camera-up-vector
  matCam *= matRot * matFwd;
}

class OpenGLWidget: public QOpenGLWidget, public QOpenGLFunctions {

  private:
    QMatrix4x4 &_matCam, _matProj, _matView, *_pMatObs;
    QOpenGLShaderProgram *_pGLPrg;
    GLuint _coordAttr;

  public:
    OpenGLWidget(QMatrix4x4 &matCam, QMatrix4x4 *pMatObs = nullptr):
      QOpenGLWidget(),
      _matCam(matCam), _pMatObs(pMatObs), _pGLPrg(nullptr)
    { }

    QSize sizeHint() const override { return QSize(256, 256); }

  protected:
    virtual void initializeGL() override
    {
      initializeOpenGLFunctions();
      glClearColor(0.525f, 0.733f, 0.851f, 1.0f);
    }

    virtual void resizeGL(int w, int h) override
    {
      _matProj.setToIdentity();
      _matProj.perspective(45.0f, GLfloat(w) / h, 0.01f, 100.0f);
    }

    virtual void paintGL() override;

  private:
    void drawTriStrip(const GLfloat *coords, size_t nCoords, const QMatrix4x4 &mat, const QColor &color);
};

static const char *vertexShaderSource =
  "# version 330\n"
  "layout (location = 0) in vec3 coord;\n"
  "uniform mat4 mat;\n"
  "void main() {\n"
  "  gl_Position = mat * vec4(coord, 1.0);\n"
  "}\n";

static const char *fragmentShaderSource =
  "#version 330\n"
  "uniform vec4 color;\n"
  "out vec4 colorFrag;\n"
  "void main() {\n"
  "  colorFrag = color;\n"
  "}\n";

const GLfloat u = 0.5; // base unit
const GLfloat coordsGround[] = {
  -15 * u, 0, +15 * u,
  +15 * u, 0, +15 * u,
  -15 * u, 0, -15 * u,
  +15 * u, 0, -15 * u,
};
const size_t sizeCoordsGround = sizeof coordsGround / sizeof *coordsGround;
const GLfloat coordsCube[] = {
  -u, +u, +u,
  -u, -u, -u,
  -u, -u, +u,
  +u, -u, +u,
  -u, +u, +u,
  +u, +u, +u,
  +u, +u, -u,
  +u, -u, +u,
  +u, -u, -u,
  -u, -u, -u,
  +u, +u, -u,
  -u, +u, -u,
  -u, +u, +u,
  -u, -u, -u
};
const size_t sizeCoordsCube = sizeof coordsCube / sizeof *coordsCube;
const GLfloat coordsObs[] = {
  -u, 0, +u,
  +u, 0, +u,
   0, 0, -u
};
const size_t sizeCoordsObs = sizeof coordsObs / sizeof *coordsObs;

void OpenGLWidget::paintGL()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_CULL_FACE);
  _matView = _matCam.inverted();
  // create shader program if not yet done
  if (!_pGLPrg) {
    _pGLPrg = new QOpenGLShaderProgram(this);
    _pGLPrg->addShaderFromSourceCode(QOpenGLShader::Vertex,
      vertexShaderSource);
    _pGLPrg->addShaderFromSourceCode(QOpenGLShader::Fragment,
      fragmentShaderSource);
    _pGLPrg->link();
    _coordAttr = _pGLPrg->attributeLocation("coord");
  }
  _pGLPrg->bind();
  // render scene
  const QColor colors[] = {
    Qt::white, Qt::green, Qt::blue,
    Qt::black, Qt::darkRed, Qt::darkGreen, Qt::darkBlue,
    Qt::cyan, Qt::magenta, Qt::yellow, Qt::gray,
    Qt::darkCyan, Qt::darkMagenta, Qt::darkYellow, Qt::darkGray
  };
  QMatrix4x4 matModel;
  drawTriStrip(coordsGround, sizeCoordsGround, matModel, Qt::lightGray);
  const size_t nColors = sizeof colors / sizeof *colors;
  for (int x = -2, i = 0; x <= 2; ++x) {
    for (int z = -2; z <= 2; ++z, ++i) {
      if (!x && !z) continue;
      matModel.setToIdentity();
      matModel.translate(x * 5 * u, u, z * 5 * u);
      drawTriStrip(coordsCube, sizeCoordsCube, matModel, colors[i++ % nColors]);
    }
  }
  // draw cam
  if (_pMatObs) drawTriStrip(coordsObs, sizeCoordsObs, *_pMatObs, Qt::red);
  // done
  _pGLPrg->release();
}

void OpenGLWidget::drawTriStrip(const GLfloat *coords, size_t sizeCoords, const QMatrix4x4 &matModel, const QColor &color)
{
  _pGLPrg->setUniformValue("mat", _matProj * _matView * matModel);
  _pGLPrg->setUniformValue("color",
    QVector4D(color.redF(), color.greenF(), color.blueF(), 1.0));
  const size_t nVtcs = sizeCoords / 3;
  glVertexAttribPointer(_coordAttr, 3, GL_FLOAT, GL_FALSE, 0, coords);
  glEnableVertexAttribArray(0);
  glDrawArrays(GL_TRIANGLE_STRIP, 0, nVtcs);
  glDisableVertexAttribArray(0);
}

struct ToolButton: QToolButton {
  ToolButton(const char *text): QToolButton()
  {
    setText(QString::fromUtf8(text));
    setCheckable(true);
    QFont qFont = font();
    qFont.setPointSize(2 * qFont.pointSize());
    setFont(qFont);
  }
};

struct MatrixView: QGridLayout {
  QLabel qLbls[4][4];
  MatrixView();
  void setText(const QMatrix4x4 &mat);
};

MatrixView::MatrixView()
{
  QColor colors[4] = { Qt::red, Qt::darkGreen, Qt::blue, Qt::black };
  for (int j = 0; j < 4; ++j) {
    for (int i = 0; i < 4; ++i) {
      QLabel &qLbl = qLbls[i][j];
      qLbl.setAlignment(Qt::AlignCenter);
      if (i < 3) {
        QPalette qPalette = qLbl.palette();
        qPalette.setColor(QPalette::WindowText, colors[j]);
        qLbl.setPalette(qPalette);
      }
      addWidget(&qLbl, i, j, Qt::AlignCenter);
    }
  }
}

void MatrixView::setText(const QMatrix4x4 &mat)
{
  for (int j = 0; j < 4; ++j) {
    for (int i = 0; i < 4; ++i) {
      qLbls[i][j].setText(QString().number(mat.row(i)[j], 'f', 3));
    }
  }
}

const char *const Up = "\342\206\221", *const Down = "\342\206\223";
const char *const Left = "\342\206\266", *const Right = "\342\206\267";

int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  // setup GUI
  QWidget qWinMain;
  QHBoxLayout qHBox;
  QMatrix4x4 matCamObs; // position/orientation of observer
  matCamObs.setToIdentity();
  matCamObs.translate(0, 0.7, 0);
  OpenGLWidget qGLViewObs(matCamObs); // observer view
  qHBox.addWidget(&qGLViewObs, 1);
  QVBoxLayout qVBox;
  QGridLayout qGrid;
  ToolButton qBtnUp(Up), qBtnLeft(Left), qBtnDown(Down), qBtnRight(Right);
  qGrid.addWidget(&qBtnUp, 0, 1);
  qGrid.addWidget(&qBtnLeft, 1, 0);
  qGrid.addWidget(&qBtnDown, 1, 1);
  qGrid.addWidget(&qBtnRight, 1, 2);
  qVBox.addLayout(&qGrid);
  qVBox.addWidget(new QLabel(), 1); // spacer
  qVBox.addWidget(new QLabel("<b>Camera Matrix:</b>"));
  MatrixView qMatView;
  qMatView.setText(matCamObs);
  qVBox.addLayout(&qMatView);
  QMatrix4x4 matCamMap; // position/orientation of "god" cam.
  matCamMap.setToIdentity();
  matCamMap.translate(0, 15, 0);
  matCamMap.rotate(-90, 1, 0, 0);
  OpenGLWidget qGLViewMap(matCamMap, &matCamObs); // overview
  qVBox.addWidget(&qGLViewMap);
  qHBox.addLayout(&qVBox);
  qWinMain.setLayout(&qHBox);
  qWinMain.show();
  qWinMain.resize(720, 400);
  // setup animation
  const double v = 0.5, rot = 15.0; // linear speed, rot. speed
  const double dt = 0.05; // target 20 fps
  QTimer qTimer;
  qTimer.setInterval(dt * 1000 /* ms */);
  QObject::connect(&qTimer, &QTimer::timeout,
    [&]() {
      // fwd and turn are "tristate" vars. with value 0, -1, or +1
      const int fwd = (int)qBtnUp.isChecked() - (int)qBtnDown.isChecked();
      const int turn = (int)qBtnLeft.isChecked() - (int)qBtnRight.isChecked();
      moveObs(matCamObs, v * dt * fwd, rot * dt * turn);
      qGLViewObs.update(); qGLViewMap.update(); qMatView.setText(matCamObs);
    });
  qTimer.start();
  // runtime loop
  return app.exec();
}

and the CMakeLists.txt from which I prepared my VisualStudio solution:

project(QOpenGLWidgetNav)

cmake_minimum_required(VERSION 3.10.0)

set_property(GLOBAL PROPERTY USE_FOLDERS ON)
#set(CMAKE_CXX_STANDARD 17)
#set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

find_package(Qt5Widgets CONFIG REQUIRED)

include_directories("${CMAKE_SOURCE_DIR}")

add_executable(testQOpenGLWidgetNav
  testQOpenGLWidgetNav.cc)

target_link_libraries(testQOpenGLWidgetNav
  Qt5::Widgets)

Demo Output:

这篇关于如何在3D世界中正确旋转相机?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆