如何在3D空间中正确移动相机? [英] How can I move the camera correctly in 3D space?

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

问题描述

我试图弄清楚如何使相机像这样工作:

I am trying to figure out how to make the camera work like this:

  • 鼠标移动:相机旋转
  • 上/下键:相机向前/向后移动;向前意味着相机所面对的方向
  • 左/右键:相机向侧面移动
  • Q/E键:摄像机上下移动

由于我有很多代码,所以我会尽力尝试解释我是如何做到的,而无需太多代码.我正在从事的项目非常大,并且有一个很大的库,其中包含许多类和类型,这使得它很难理解.

Since I have a lot of code, I will do my best to try to explain how I did it, without too much code. The project I'm working on is very large, and has a pretty big library with many classes and types that would make it hard to understand.

我几乎设法完成了这项工作,但是在稍微倾斜一些角度之后,事情开始失败:按下向上键时,相机向侧面移动,依此类推.

I have managed to almost get this working, however after moving around a little, at some angles, things start failing: when pressing Up, the camera moves sideways and so on.

我想到的算法将在下面详细解释.

The algorithm I thought of is explained in detail below.

问题是,我做错了吗?什么会使它失败?我一整天都在尝试调试这台相机,但没有弄清楚是什么原因导致的.

The question is, am I doing things wrong? What could make it fail? I tried debugging this camera the entire day, and haven't figured out what makes it fail.

  • 这就是我对 rotation 的理解:3D向量(可能不正确地称为向量),其中每个分量表示对象围绕其旋转的轴.例如,X值就是对象绕X轴旋转的角度.因为我在OpenGL中工作,所以旋转值将为 degrees (不是弧度).

  • This is how I understood rotation: a 3D vector (maybe improperly called vector), where each component means the axis around which the object rotates. For example, the X value would be how much the object rotates around the X axis. Because I am working in OpenGL, rotation values will be in degrees (not radians).

渲染照相机时,我只是平移了照相机的位置,但是带有相反的符号.

When rendering the camera, I simply translate the camera position, but with opposite sign.

轮换使用相同:

glRotatef(-currentCamera->Rotation().X, 1.0f, 0, 0);
glRotatef(-currentCamera->Rotation().Y, 0, 1.0f, 0);
glRotatef(-currentCamera->Rotation().Z, 0, 0, 1.0f);
glTranslatef(-currentCamera->Position().X, -currentCamera->Position().Y, -currentCamera->Position().Z);

我尝试了什么(没用):

我尝试使用简单的几何和数学,毕达哥拉斯定理和简单的三角函数,但是失败极了,所以我停止尝试使它起作用. (例如,如果旋转坐标中的任何一个为0,则为NaN结果.

What I tried (and didn't work):

I tried using simple geometry and mathematics, using Pythagoras theorem, and simple trigonometry, but it failed miserably, so I stopped trying to get this working. (e.g. NaN result if any of the rotation coordinates was 0).

使用变换矩阵.

当用户按下这些键中的任何一个时,将生成3d矢量:

When the user presses any of those keys, a 3d vector is generated:

+X = right; -X = left
+Y = top; -Y = bottom
+Z = backward (towards camera); -Z = forward (away from camera)

接下来,我生成一个转换矩阵:对于3个坐标中的每个坐标(X,然后Y,然后是Z),将恒等式(4x4矩阵)乘以旋转矩阵3次.接下来,我将矩阵应用于创建的矢量,然后将结果添加到相机的旧位置.

Next, I generate a transformation matrix: the identity (4x4 matrix) is multiplied by the rotation matrix 3 times, for each of the 3 coordinates (X then Y then Z). Next, I apply the matrix to the vector I created, and I add the result to the old position of the camera.

但是,这种方法似乎存在问题.起初它可以正常工作,但是过一会儿,当我按向上键时,它就向侧面倾斜,而不是应该的样子.

However, there seems to be a problem with this approach. At first it works just fine, but after a while, when I press Up it goes sideways instead of the way it should.

如上所述,我尝试使用尽可能少的代码.但是,如果这还不够有用,那么这里是一些实际的代码.我尽力选择了最相关的代码.

As I stated above, I tried to use as little code as possible. However if this is not helpful enough, here is some actual code. I did my best to select only the most relevant code.

// ... Many headers

// 'Camera' is a class, which, among other things, it has (things relevant here):
// * Position() getter, SetPosition() setter
// * Rotation() getter, SetRotation() setter

// The position and rotation are stored in another class (template), 'Vector3D <typename T>',
// which has X, Y and Z values. It also implements a '+' operator.

float angle; // this is for animating our little cubes
Camera* currentCamera;

// 'Matrix' is a template, which contains a 4x4 array of a generic type, which is public and
// called M. It also implements addition/subtraction operators, and multiplication. The 
// constructor memset's the array to 0.

// Generates a matrix with 1.0 on the main diagonal
Matrix<float> IdentityMatrix()
{
    Matrix<float> res;

    for (int i = 0; i < 4; i++)
        res.M[i][i] = 1.0f;

    return res;
}

// I used the OpenGL documentation about glRotate() to write this
Matrix<float> RotationMatrix (float angle, float x, float y, float z)
{
    Matrix<float> res;

    // Normalize; x, y and z must be smaller than 1
    if (abs(x) > 1 || abs(y) > 1 || abs(z) > 1)
    {
        // My own implementation of max which allows 3 parameters
        float M = Math::Max(abs(x), abs(y), abs(z)); 
        x /= M; y /= M; z /= M;
    }

    // Vars
    float s = Math::SinD(angle); // SinD and CosD convert the angle to degrees
    float c = Math::CosD(angle); // before calling the standard library sin and cos

    // Vector
    res.M[0][0] = x * x * (1 - c) + c;
    res.M[0][1] = x * y * (1 - c) - z * s;
    res.M[0][2] = x * z * (1 - c) + y * s;
    res.M[1][0] = y * x * (1 - c) + z * s;
    res.M[1][1] = y * y * (1 - c) + c;
    res.M[1][2] = y * z * (1 - c) - x * s;
    res.M[2][0] = x * z * (1 - c) - y * s;
    res.M[2][1] = y * z * (1 - c) + x * s;
    res.M[2][2] = z * z * (1 - c) + c;
    res.M[3][3] = 1.0f;

    return res;
}

// Used wikipedia for this one :)
Matrix<float> TranslationMatrix (float x, float y, float z)
{
    Matrix<float> res = IdentityMatrix();

    res.M[0][3] = x;
    res.M[1][3] = y;
    res.M[2][3] = z;

    return res;
}

Vector3D<float> ApplyMatrix (Vector3D<float> v, const Matrix<float>& m)
{
    Vector3D<float> res;

    res.X = m.M[0][0] * v.X + m.M[0][1] * v.Y + m.M[0][2] * v.Z + m.M[0][3];
    res.Y = m.M[1][0] * v.X + m.M[1][1] * v.Y + m.M[1][2] * v.Z + m.M[1][3];
    res.Z = m.M[2][0] * v.X + m.M[2][1] * v.Y + m.M[2][2] * v.Z + m.M[2][3];

    return res;
}

// Vector3D instead of x, y and z 
inline Matrix<float> RotationMatrix (float angle, Vector3D<float> v)
{
    return RotationMatrix (angle, v.X, v.Y, v.Z);
}

inline Matrix<float> TranslationMatrix (Vector3D<float> v)
{
    return TranslationMatrix (v.X, v.Y, v.Z);
}

inline Matrix<float> ScaleMatrix (Vector3D<float> v)
{
    return ScaleMatrix (v.X, v.Y, v.Z);
}


// This gets called after everything is initialized (SDL, OpenGL etc)
void OnStart()
{
    currentCamera = new Camera("camera0");
    angle = 0;
    SDL_ShowCursor(0); // Hide cursor
}

// This gets called periodically
void OnLogicUpdate()
{
    float delta = .02; // How much we move
    Vector3D<float> rot = currentCamera->Rotation();
    Vector3D<float> tr (0, 0, 0);

    Uint8* keys = SDL_GetKeyState(0);

    // Cube animation
    angle += 0.05;

    // Handle keyboard stuff
    if (keys[SDLK_LSHIFT] || keys[SDLK_RSHIFT]) delta = 0.1;
    if (keys[SDLK_LCTRL] || keys[SDLK_RCTRL]) delta = 0.008;

    if (keys[SDLK_UP] || keys[SDLK_w]) tr.Z += -delta;
    if (keys[SDLK_DOWN] || keys[SDLK_s]) tr.Z += delta;
    if (keys[SDLK_LEFT] || keys[SDLK_a]) tr.X += -delta;
    if (keys[SDLK_RIGHT] || keys[SDLK_d]) tr.X += delta;

    if (keys[SDLK_e]) tr.Y += -delta;
    if (keys[SDLK_q]) tr.Y += delta;

    if (tr != Vector3D<float>(0.0f, 0.0f, 0.0f))
    {
        Math::Matrix<float> r = Math::IdentityMatrix();
        r *= Math::RotationMatrix(rot.X, 1.0f, 0, 0);
        r *= Math::RotationMatrix(rot.Y, 0, 1.0f, 0);
        r *= Math::RotationMatrix(rot.Z, 0, 0, 1.0f);

        Vector3D<float> new_pos = Math::ApplyMatrix(tr, r);
        currentCamera->SetPosition(currentCamera->Position() + new_pos);
    }
}

// Event handler, handles mouse movement and ESCAPE exit
void OnEvent(SDL_Event* e)
{
    const float factor = -.1f;

    if (e->type == SDL_MOUSEMOTION)
    {
        // Is mouse in the center? If it is, we just moved it there, ignore
        if (e->motion.x == surface->w / 2 && e->motion.y == surface->h / 2)
            return;

        // Get delta
        float dx = e->motion.xrel;
        float dy = e->motion.yrel;

        // Make change
        currentCamera->SetRotation(currentCamera->Rotation() + World::Vector3D<float>(dy * factor, dx * factor, 0));

        // Move back to center
        SDL_WarpMouse(surface->w / 2, surface->h / 2);

    }

    else if (e->type == SDL_KEYUP)
    switch (e->key.keysym.sym)
    {
        case SDLK_ESCAPE:
            Debug::Log("Escape key pressed, will exit.");
            StopMainLoop(); // This tells the main loop to stop
            break;

        default: break;
    }
}

// Draws a cube in 'origin', and rotated at angle 'angl'
void DrawCube (World::Vector3D<float> origin, float angl)
{
    glPushMatrix();
    glTranslatef(origin.X, origin.Y, origin.Z);
    glRotatef(angl, 0.5f, 0.2f, 0.1f);

    glBegin(GL_QUADS);
        glColor3f(0.0f,1.0f,0.0f);          // green
        glVertex3f( 1.0f, 1.0f,-1.0f);          // Top Right Of The Quad (Top)
        glVertex3f(-1.0f, 1.0f,-1.0f);          // Top Left Of The Quad (Top)
        glVertex3f(-1.0f, 1.0f, 1.0f);          // Bottom Left Of The Quad (Top)
        glVertex3f( 1.0f, 1.0f, 1.0f);          // Bottom Right Of The Quad (Top)

        glColor3f(1.0f,0.5f,0.0f);          // orange
        glVertex3f( 1.0f,-1.0f, 1.0f);          // Top Right Of The Quad (Bottom)
        glVertex3f(-1.0f,-1.0f, 1.0f);          // Top Left Of The Quad (Bottom)
        glVertex3f(-1.0f,-1.0f,-1.0f);          // Bottom Left Of The Quad (Bottom)
        glVertex3f( 1.0f,-1.0f,-1.0f);          // Bottom Right Of The Quad (Bottom)

        glColor3f(1.0f,0.0f,0.0f);          // red
        glVertex3f( 1.0f, 1.0f, 1.0f);          // Top Right Of The Quad (Front)
        glVertex3f(-1.0f, 1.0f, 1.0f);          // Top Left Of The Quad (Front)
        glVertex3f(-1.0f,-1.0f, 1.0f);          // Bottom Left Of The Quad (Front)
        glVertex3f( 1.0f,-1.0f, 1.0f);          // Bottom Right Of The Quad (Front)

        glColor3f(1.0f,1.0f,0.0f);          // yellow
        glVertex3f( 1.0f,-1.0f,-1.0f);          // Bottom Left Of The Quad (Back)
        glVertex3f(-1.0f,-1.0f,-1.0f);          // Bottom Right Of The Quad (Back)
        glVertex3f(-1.0f, 1.0f,-1.0f);          // Top Right Of The Quad (Back)
        glVertex3f( 1.0f, 1.0f,-1.0f);          // Top Left Of The Quad (Back)

        glColor3f(0.0f,0.0f,1.0f);          // blue
        glVertex3f(-1.0f, 1.0f, 1.0f);          // Top Right Of The Quad (Left)
        glVertex3f(-1.0f, 1.0f,-1.0f);          // Top Left Of The Quad (Left)
        glVertex3f(-1.0f,-1.0f,-1.0f);          // Bottom Left Of The Quad (Left)
        glVertex3f(-1.0f,-1.0f, 1.0f);          // Bottom Right Of The Quad (Left)

            glColor3f(1.0f,0.0f,1.0f);          // violet
            glVertex3f( 1.0f, 1.0f,-1.0f);          // Top Right Of The Quad (Right)
            glVertex3f( 1.0f, 1.0f, 1.0f);          // Top Left Of The Quad (Right)
            glVertex3f( 1.0f,-1.0f, 1.0f);          // Bottom Left Of The Quad (Right)
            glVertex3f( 1.0f,-1.0f,-1.0f);          // Bottom Right Of The Quad (Right)

    glEnd();

    glPopMatrix();
}

// Gets called periodically
void OnRender()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();

    // Camera movement
    glRotatef(-currentCamera->Rotation().X, 1.0f, 0, 0);
    glRotatef(-currentCamera->Rotation().Y, 0, 1.0f, 0);
    glRotatef(-currentCamera->Rotation().Z, 0, 0, 1.0f);
    glTranslatef(-currentCamera->Position().X, -currentCamera->Position().Y, -currentCamera->Position().Z);

    // Draw some cubes
    for (float i = -5; i <= 5; i++)
        for (float j = -5; j <= 5; j++)
        {
            DrawCube(World::Vector3D<float>(i*3, j * 3, -5), angle + 5 * i + 5 * j);
        }

    SDL_GL_SwapBuffers();
}

您可能会看到,对于我来说,创建一个简单的示例非常困难,因为背后发生了很多事情,并且有许多类和数据类型.

As you can probably see, it is very difficult for me to create an easy example, because there are so many things happening behind, and so many classes and data types.

我还上传了一个可执行文件(希望它可以工作),以便您可以看到我在说什么问题:

I also uploaded an executable (hopefully it works), so that you can see what problem I am talking about:

https://dl.dropbox.com/u/24832466/Downloads/debug.zip

推荐答案

我认为,这与相机矩阵"(相机的世界空间位置)之间的某种混合以及它的逆矩阵有关. 视图矩阵"(从世界空间转换为视图空间的矩阵).

I believe this has to do with a bit of a mix up between the "camera matrix" (world space position of the camera), and it's inverse matrix the "view matrix" (matrix which converts from world space to view space).

首先,有一点背景知识.

First, a little background.

从摄像机的世界空间位置开始,它是X,Y和Z旋转.如果这台摄像机只是我们要放置在场景中的典型对象,我们将像这样设置它:

You're starting with a world space position of the camera, and it's X, Y, and Z rotation. If this camera was just a typical object we were placing in the scene, we would set it up like this:

glTranslate(camX, camY, camZ);
glRotate(x);
glRotate(y);
glRotate(z);

所有这些操作共同创建了一个矩阵,我将其定义为"CameraToWorldMatrix"或从摄像机空间转换为世界空间的矩阵".

All together these operations create the matrix I will define as "CameraToWorldMatrix", or "the matrix that transforms from camera space to world space".

但是,当我们处理视图矩阵时,我们不想从摄影机空间转换为世界空间.对于视图矩阵,我们希望将坐标从世界空间转换为摄影机空间(逆运算).因此,我们的视图矩阵实际上是一个"WorldToCameraMatrix".

However, when we're dealing with view matrices, we don't want to transform from camera space to world space. For the view matrix we want to transform coordinates from world space into camera space (the inverse operation). So our view matrix is really a "WorldToCameraMatrix".

采用"CameraToWorldMatrix"的逆向"方法是按照相反的顺序执行所有操作(这差不多可以完成,但是顺序有些混乱).

The way you take the "inverse" of the "CameraToWorldMatrix" would be to perform all of the operations in the reverse order (which you came close to doing, but got the order slightly mixed up).

上述矩阵的逆为:

glRotate(-z);
glRotate(-y);
glRotate(-x);
glTranslate(-camX, -camY, -camZ);

几乎拥有您所拥有的,但是您的订单却混在一起了.

Which is almost what you had, but you had the order mixed up.

在您的代码中:

Math::Matrix<float> r = Math::IdentityMatrix();
r *= Math::RotationMatrix(rot.X, 1.0f, 0, 0);
r *= Math::RotationMatrix(rot.Y, 0, 1.0f, 0);
r *= Math::RotationMatrix(rot.Z, 0, 0, 1.0f);

Vector3D<float> new_pos = Math::ApplyMatrix(tr, r);
currentCamera->SetPosition(currentCamera->Position() + new_pos);

您将"CameraToWorldMatrix"定义为首先绕X旋转,然后绕Y,然后绕Z,然后平移".

You were defining the "CameraToWorldMatrix" as "first rotate around X, then Y, then Z, then translate".

但是,当您反过来时,会得到与"WorldToCameraMatrix"所使用的东西不同的东西,即(平移,然后绕着z旋转,然后绕着y,然后绕着x旋转).

However when you inverse this, you get something different than what you were using as your "WorldToCameraMatrix", which was (translate, then rotate around z, then rotate around y, then rotate around x).

由于您的视图矩阵和相机矩阵实际上并没有定义相同的内容,因此它们不同步,并且您会得到怪异的行为.

Because your view matrix and camera matrix were not actually defining the same thing, they get out of sync and you get weird behavior.

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

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