【问题标题】:How do I rotate my camera correctly in my 3D world?如何在 3D 世界中正确旋转相机?
【发布时间】:2020-03-09 15:47:38
【问题描述】:

我正在努力学习 3D 编程,目前正在为一个爱好项目开发 FPS 相机风格。 我已经创建了我认为应该使用的那些矩阵,但是我无法看到如何将所有内容连接到相机旋转。 所以,我有一个相机类:

get_World_To_View 矩阵

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

get_ViewToWorld 矩阵

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

get_ProjectionMatrix:

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

get_forward 的vector3

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

和 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();

从这里开始,我认为需要一个实际旋转我的相机的功能,但我尝试了几件事,但我无法真正理解它应该如何拼凑在一起。

我渲染了我的两个矩阵:get_WorldToView 和 get_ProjectionMatrix,并且我能够使用 WASD 键四处移动。 有人对我应该如何看待我的 RotateCamera() 函数有提示吗?我错过了一些非常重要的东西吗? 我对编程很陌生,我仍然很难“看到”我面前的逻辑。

所以要尽可能清楚: 我在 Main.cpp (Update) 中有一个用于输入的函数。

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

它的功能我想要一些关于如何思考的帮助。

当我使用 WASD 键移动时,我只需调用一个函数 Move(),它将位置 += 设置为 vector3,其中正确的 x、y、z 方向乘以 camera_velocity,因此与旋转无关自己。

【问题讨论】:

  • 那么,您想将旋转合并到您的视图到世界矩阵中吗?你已经考虑了一些轮换。为什么你不加入。或十二月旋转取决于鼠标增量?您对此的实际问题是什么?
  • 是的,但我不明白应该如何使用矩阵。 (我对此很陌生)我尝试过这样的函数:{get_ViewToWorld(); rotation = position + vec3f(0, v.y, 0) + (get_rightwards() * get_Forward());} 然后这样调用它:if (g_InputHandler->GetMouseDeltaX() > 0.0f) { camera->RotateCamera({ -camera_vel * dt, 0.0f, 0.0f }); } else if (g_InputHandler->GetMouseDeltaY() > 0.0f) { camera->RotateCamera({ 0.0f, camera_vel * dt, 0.0f }); }
  • 关于观察者导航:我通常区分观察者运动和头部运动。因此,对于观察者的运动,有一个主要方向,例如一个向量。观察者旋转也必须旋转这个向量。头部运动是对观察者旋转的“偏移”。它也必须旋转相机,但不会影响运动矢量。
  • 所以,如果我正确阅读了您的 sn-ps,您可以将观察者/相机的位置和旋转存储在两个三元组中,并从中计算出视图矩阵。对吗?
  • 或者这个:First Person Camera(在我的谷歌搜索中添加了 DirectX)。 ;-)

标签: c++ directx


【解决方案1】:

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

因此相机矩阵是视图矩阵的逆矩阵。相机矩阵表示相机相对于世界原点的坐标(位置、方向),而视图矩阵表示相反的——世界相对于相机原点的位置。当 3d 内容映射到屏幕时,后者是渲染所需的转换。然而,人类(没有以自我为中心的干扰)习惯于看待自己与世界的关系。因此,我认为对相机矩阵的操作更直观。

左侧 3d 视图显示第一人称摄像头,右侧是顶部视图,其中第一人称摄像头的位置/方向由红色三角形标记。

相机矩阵最初设置为单位矩阵,在 y 方向有一个小的高度,以显示在地面上方 - x-z 平面。

  • x 轴指向右侧。
  • y 轴指向上方。
  • z 轴指向屏幕外。

所以,视线向量是负 z 轴。

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

相机向上的向量是 y 轴。

因此,左转可以通过绕y轴正转实现,右转可以通过负转实现。

现在,如果相机已经转动,前进如何考虑转动的视线?

诀窍是将平移应用到 z 轴,但在相机的局部坐标系中。

用矩阵做这件事,你只需要正确的乘法顺序。

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;
}

我使用了QMatrix4x4,因为这是我手头的东西。在 glmDirectXMath 等其他 API 中应该没有什么不同,因为它们都基于相同的 mathematical basics

(不过,您必须始终检查特定 API 是否公开矩阵行优先或列优先:Matrix array order of OpenGL Vs DirectX。)

我必须承认我是 OpenGL 社区的一员,大部分时间都忽略了 Direct3D。因此,我觉得无法在 Direct3D 中准备 MCVE,但在 OpenGL 中制作了一个。我使用了Qt framework,它提供了很多开箱即用的东西,以使样本尽可能紧凑。 (对于 3D 编程和 GUI 编程来说,这并不容易,尤其是两者的结合。)

(完整)源代码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();
}

以及我从中准备 VisualStudio 解决方案的 CMakeLists.txt

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)

演示输出:

【讨论】:

  • 这很有帮助,谢谢!获得逻辑的视觉示例总是很高兴。不错的工作!这是z轴的诀窍是的。所以现在我在我的 Move() 函数中使用了我的 Get_Forward() 和 z 轴上的平移:像这样:position += position * vec3f(0, v.y, 0) + vec3f(v.x, 0,0) * -get_rightwards() + vec3f(0,0, v.z) * -get_Forward(); 感谢您的帮助!
猜你喜欢
  • 1970-01-01
  • 2014-11-21
  • 1970-01-01
  • 1970-01-01
  • 2015-06-07
  • 1970-01-01
  • 2020-02-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多