我正在试图弄清楚如何让相机像这样工作:
由于我有很多代码,我会尽力解释我是如何做到的,没有太多代码。我正在处理的项目非常庞大,并且有一个非常大的库,其中包含许多类和类型,这使得它很难理解。
我已经成功地完成了这项工作,但是在稍微移动一点之后,事情开始失败:当按下Up时,相机会侧向移动等等。
我想到的算法将在下面详细解释。
问题是,我做错了吗?什么可能使它失败?我整天都试着调试这台相机,但还没弄清楚是什么让它失败了。
这就是我理解 rotation 的方式:一个3D矢量(可能是不正确地称为矢量),其中每个组件表示对象围绕其旋转的轴。例如,X值将是对象围绕X轴旋转的程度。因为我在OpenGL中工作,所以旋转值将是度(不是弧度)。
渲染相机时,我只是翻译相机位置,但符号相反。
同样适用于轮换:
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结果。)
使用转换矩阵。
当用户按下任意键时,会生成一个3d矢量:
+X = right; -X = left
+Y = top; -Y = bottom
+Z = backward (towards camera); -Z = forward (away from camera)
接下来,我生成一个变换矩阵:对于3个坐标中的每个坐标(X然后是Y,然后是Z),身份(4x4矩阵)乘以旋转矩阵3次。接下来,我将矩阵应用于我创建的矢量,然后将结果添加到摄像机的旧位置。
然而,这种方法似乎存在问题。起初它的效果很好,但过了一段时间,当我按下它时,它会侧向移动而不是应该的方式。
正如我上面所说,我尝试使用尽可能少的代码。但是,如果这不够有用,这里有一些实际的代码。我尽力选择最相关的代码。
// ... 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();
}
正如您可能看到的,我很难创建一个简单的示例,因为背后发生了很多事情,以及如此多的类和数据类型。
我还上传了一个可执行文件(希望它有效),这样你就可以看到我在说什么问题了:
答案 0 :(得分:10)
我认为这与“相机矩阵”(相机的世界空间位置)之间有点混淆,而它的逆矩阵是“视图矩阵”(从世界空间转换为视图空间的矩阵) )。
首先,一点背景。
你从相机的世界空间位置开始,它是X,Y和Z旋转。如果这台相机只是我们放置在场景中的典型物体,我们会将其设置为:
glTranslate(camX, camY, camZ);
glRotate(x);
glRotate(y);
glRotate(z);
这些操作一起创建了我将定义为“CameraToWorldMatrix”的矩阵,或“从相机空间转换到世界空间的矩阵”。
但是,当我们处理视图矩阵时,我们不希望从相机空间转换到世界空间。对于视图矩阵,我们希望将坐标从世界空间转换为相机空间(逆操作)。所以我们的视图矩阵实际上是一个“WorldToCameraMatrix”。
你采用“CameraToWorldMatrix”的“逆”的方式是以相反的顺序执行所有操作(你接近这样做,但是命令略微混淆了)。
上述矩阵的反函数为:
glRotate(-z);
glRotate(-y);
glRotate(-x);
glTranslate(-camX, -camY, -camZ);
这几乎就是你所拥有的,但你把订单搞砸了。
在您的代码中:
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,然后翻译”。
然而,当你反过来时,你得到的东西与你使用的“WorldToCameraMatrix”不同,它是(平移,然后围绕z旋转,然后围绕y旋转,然后围绕x旋转)。
因为你的视图矩阵和相机矩阵实际上没有定义相同的东西,所以它们会失去同步而你会得到奇怪的行为。