Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
504 views
in Technique[技术] by (71.8m points)

c++ - How do I rotate my camera correctly in my 3D world?

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.

Snapshot of testQOpenGLWidgetNav

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
"
  "layout (location = 0) in vec3 coord;
"
  "uniform mat4 mat;
"
  "void main() {
"
  "  gl_Position = mat * vec4(coord, 1.0);
"
  "}
";

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

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 = "342206221", *const Down = "342206223";
const char *const Left = "342206266", *const Right = "342206267";

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 R

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...