我想做的事:
我试图弄清楚如何使相机像这样工作:
- 鼠标移动:相机旋转
- 上/下键:相机前进/后退;forward 表示相机朝向的方向
- 左/右键:相机侧向移动
- Q/E键:相机上下移动
由于我有很多代码,我会尽力解释我是如何做到的,没有太多代码。我正在处理的项目非常大,并且有一个相当大的库,其中包含许多难以理解的类和类型。
问题
我几乎已经成功地完成了这项工作,但是在以某些角度移动了一点之后,事情开始失败:当按下向上时,相机会向侧面移动等等。
下面详细解释我想到的算法。
问题是,我做错了吗?什么可能让它失败?我一整天都在调试这台相机,但还没弄清楚是什么让它失败了。
澄清
这就是我对旋转的理解:一个 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)
接下来,我生成一个变换矩阵:单位(4x4 矩阵)乘以旋转矩阵 3 次,对于 3 个坐标中的每一个(X 然后 Y 然后 Z)。接下来,我将矩阵应用于我创建的向量,并将结果添加到相机的旧位置。
但是,这种方法似乎存在问题。起初它工作得很好,但过了一会儿,当我按下向上时,它会向侧面移动,而不是它应该的方式。
实际代码
如上所述,我尝试使用尽可能少的代码。但是,如果这还不够有用,这里有一些实际代码。我尽力只选择最相关的代码。
// ... 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();
}
正如您可能看到的,我很难创建一个简单的示例,因为背后发生了很多事情,还有很多类和数据类型。
其他奖励的东西
我还上传了一个可执行文件(希望它可以工作),这样你就可以看到我在说什么问题: