3D数学中的多坐标系详解与实例
1. 坐标系基础概念
在3D应用开发中,我们需要处理多种不同的坐标系统,每种坐标系都有其特定用途和优势。理解这些坐标系统及其转换是掌握3D数学的基础。
1.1 坐标系的表示
坐标系通常由原点和一组基向量组成,在3D空间中一般使用三个相互垂直的轴:X轴、Y轴和Z轴。
cpp
// 基本坐标系类
class CoordinateSystem {
protected:
Point3D origin; // 原点
Vector3D xAxis; // X轴方向
Vector3D yAxis; // Y轴方向
Vector3D zAxis; // Z轴方向
public:
CoordinateSystem()
: origin(0,0,0),
xAxis(1,0,0),
yAxis(0,1,0),
zAxis(0,0,1) {}
CoordinateSystem(const Point3D& origin,
const Vector3D& xAxis,
const Vector3D& yAxis,
const Vector3D& zAxis)
: origin(origin),
xAxis(xAxis.normalize()),
yAxis(yAxis.normalize()),
zAxis(zAxis.normalize()) {}
// 获取坐标系变换矩阵
Matrix4x4 getTransformMatrix() const {
Matrix4x4 matrix;
// 旋转部分
matrix.m[0][0] = xAxis.x;
matrix.m[0][1] = xAxis.y;
matrix.m[0][2] = xAxis.z;
matrix.m[1][0] = yAxis.x;
matrix.m[1][1] = yAxis.y;
matrix.m[1][2] = yAxis.z;
matrix.m[2][0] = zAxis.x;
matrix.m[2][1] = zAxis.y;
matrix.m[2][2] = zAxis.z;
// 平移部分
matrix.m[0][3] = origin.x;
matrix.m[1][3] = origin.y;
matrix.m[2][3] = origin.z;
return matrix;
}
// 从这个坐标系转换到另一个坐标系的变换矩阵
Matrix4x4 getTransformTo(const CoordinateSystem& target) const {
Matrix4x4 thisToWorld = this->getTransformMatrix();
Matrix4x4 worldToTarget = inverseMatrix(target.getTransformMatrix());
return worldToTarget * thisToWorld;
}
};
2. 世界坐标系 (World Coordinate System)
2.1 定义和用途
世界坐标系是3D场景中的全局参考系统,所有其他坐标系最终都会参照世界坐标系。它通常是固定不变的,提供了一个共同的空间来放置和测量所有物体。
cpp
class WorldCoordinateSystem : public CoordinateSystem {
public:
WorldCoordinateSystem() : CoordinateSystem() {
// 通常采用右手坐标系,z轴向上
// 或者根据应用需求自定义
}
// 将点从任意坐标系转换到世界坐标系
Point3D transformPointToWorld(const Point3D& point, const CoordinateSystem& sourceCS) const {
Matrix4x4 transformMatrix = sourceCS.getTransformMatrix();
return transformMatrix.transformPoint(point);
}
// 将向量从任意坐标系转换到世界坐标系
Vector3D transformVectorToWorld(const Vector3D& vector, const CoordinateSystem& sourceCS) const {
Matrix4x4 transformMatrix = sourceCS.getTransformMatrix();
// 向量转换不考虑平移
return transformMatrix.transformVector(vector);
}
};
2.2 应用示例:管理全局场景
cpp
class Scene {
private:
WorldCoordinateSystem worldCS;
std::vector<GameObject*> gameObjects;
public:
Scene() {}
void addObject(GameObject* obj) {
gameObjects.push_back(obj);
}
void updateAll(float deltaTime) {
for (auto* obj : gameObjects) {
obj->update(deltaTime);
// 确保所有对象位置都相对于世界坐标系
obj->updateWorldTransform(worldCS);
}
}
void renderAll(Camera& camera) {
for (auto* obj : gameObjects) {
// 使用相机视图渲染每个对象
obj->render(camera);
}
}
};
3. 物体坐标系 (Object Coordinate System)
3.1 定义和用途
物体坐标系(也称为局部坐标系)是相对于特定物体定义的坐标系。它使得物体的操作(如旋转、缩放)更加直观。物体的所有顶点通常首先在物体坐标系中定义,然后通过变换矩阵转换到世界坐标系。
cpp
class ObjectCoordinateSystem : public CoordinateSystem {
private:
Matrix4x4 localToWorldMatrix;
Matrix4x4 worldToLocalMatrix;
bool matricesNeedUpdate;
public:
ObjectCoordinateSystem()
: CoordinateSystem(), matricesNeedUpdate(true) {}
ObjectCoordinateSystem(const Point3D& position,
const Quaternion& rotation,
const Vector3D& scale)
: CoordinateSystem(), matricesNeedUpdate(true) {
// 使用位置、旋转和缩放设置坐标系
setTransform(position, rotation, scale);
}
// 设置物体变换
void setTransform(const Point3D& position,
const Quaternion& rotation,
const Vector3D& scale) {
origin = position;
// 从四元数获取旋转轴
Matrix4x4 rotMatrix = rotation.toRotationMatrix();
// 应用缩放和旋转
xAxis = rotMatrix.transformVector(Vector3D(scale.x, 0, 0));
yAxis = rotMatrix.transformVector(Vector3D(0, scale.y, 0));
zAxis = rotMatrix.transformVector(Vector3D(0, 0, scale.z));
matricesNeedUpdate = true;
}
// 更新变换矩阵
void updateMatrices() {
if (!matricesNeedUpdate) return;
localToWorldMatrix = getTransformMatrix();
worldToLocalMatrix = inverseMatrix(localToWorldMatrix);
matricesNeedUpdate = false;
}
// 从局部坐标转换到世界坐标
Point3D localToWorld(const Point3D& localPoint) {
updateMatrices();
return localToWorldMatrix.transformPoint(localPoint);
}
// 从世界坐标转换到局部坐标
Point3D worldToLocal(const Point3D& worldPoint) {
updateMatrices();
return worldToLocalMatrix.transformPoint(worldPoint);
}
// 从局部向量转换到世界向量
Vector3D localToWorldVector(const Vector3D& localVector) {
updateMatrices();
return localToWorldMatrix.transformVector(localVector);
}
// 从世界向量转换到局部向量
Vector3D worldToLocalVector(const Vector3D& worldVector) {
updateMatrices();
return worldToLocalMatrix.transformVector(worldVector);
}
};
3.2 应用示例:变换层次结构
cpp
class Transform {
private:
Point3D position;
Quaternion rotation;
Vector3D scale;
Transform* parent;
std::vector<Transform*> children;
ObjectCoordinateSystem localCS;
Matrix4x4 localToWorldMatrix;
Matrix4x4 worldToLocalMatrix;
bool matricesNeedUpdate;
public:
Transform()
: position(0,0,0),
rotation(),
scale(1,1,1),
parent(nullptr),
matricesNeedUpdate(true) {}
// 设置父变换
void setParent(Transform* newParent) {
if (parent) {
// 从旧父级中移除
parent->removeChild(this);
}
parent = newParent;
if (parent) {
// 添加到新父级
parent->addChild(this);
}
matricesNeedUpdate = true;
}
// 添加子变换
void addChild(Transform* child) {
children.push_back(child);
}
// 移除子变换
void removeChild(Transform* child) {
auto it = std::find(children.begin(), children.end(), child);
if (it != children.end()) {
children.erase(it);
}
}
// 设置局部变换
void setLocalTransform(const Point3D& pos, const Quaternion& rot, const Vector3D& scl) {
position = pos;
rotation = rot;
scale = scl;
localCS.setTransform(position, rotation, scale);
matricesNeedUpdate = true;
// 通知所有子级它们的全局变换需要更新
for (auto* child : children) {
child->matricesNeedUpdate = true;
}
}
// 更新全局变换矩阵
void updateGlobalMatrices() {
if (!matricesNeedUpdate) return;
// 更新局部坐标系矩阵
localCS.updateMatrices();
if (parent) {
// 确保父级矩阵是最新的
parent->updateGlobalMatrices();
// 局部到世界 = 父级局部到世界 * 自身局部到父级
Matrix4x4 parentLocalToWorld = parent->getLocalToWorldMatrix();
Matrix4x4 localToParent = localCS.getTransformMatrix();
localToWorldMatrix = parentLocalToWorld * localToParent;
} else {
// 没有父级,局部空间等同于世界空间
localToWorldMatrix = localCS.getTransformMatrix();
}
// 计算逆矩阵
worldToLocalMatrix = inverseMatrix(localToWorldMatrix);
matricesNeedUpdate = false;
}
// 获取局部到世界变换矩阵
Matrix4x4 getLocalToWorldMatrix() {
updateGlobalMatrices();
return localToWorldMatrix;
}
// 获取世界到局部变换矩阵
Matrix4x4 getWorldToLocalMatrix() {
updateGlobalMatrices();
return worldToLocalMatrix;
}
// 将点从局部坐标转换到世界坐标
Point3D transformPointToWorld(const Point3D& localPoint) {
updateGlobalMatrices();
return localToWorldMatrix.transformPoint(localPoint);
}
// 将点从世界坐标转换到局部坐标
Point3D transformPointToLocal(const Point3D& worldPoint) {
updateGlobalMatrices();
return worldToLocalMatrix.transformPoint(worldPoint);
}
};
4. 相机坐标系 (Camera Coordinate System)
4.1 定义和用途
相机坐标系是以相机为原点的坐标系,通常也称为视图空间。在这个坐标系中,相机位于原点,看向特定的方向(通常是-Z轴),上方向是Y轴。
cpp
class CameraCoordinateSystem : public CoordinateSystem {
public:
CameraCoordinateSystem() : CoordinateSystem() {
// 默认相机看向-Z方向,上方向是Y轴
xAxis = Vector3D(1, 0, 0);
yAxis = Vector3D(0, 1, 0);
zAxis = Vector3D(0, 0, -1); // 注意:通常相机看向-Z方向
}
CameraCoordinateSystem(const Point3D& position,
const Point3D& target,
const Vector3D& up)
: CoordinateSystem() {
lookAt(position, target, up);
}
// 设置相机观察方向
void lookAt(const Point3D& position,
const Point3D& target,
const Vector3D& worldUp) {
origin = position;
// 计算z轴(观察方向的反方向)
zAxis = Vector3D(
position.x - target.x,
position.y - target.y,
position.z - target.z
).normalize();
// 计算x轴(右方向)
xAxis = cross(worldUp.normalize(), zAxis).normalize();
// 计算y轴(上方向)
yAxis = cross(zAxis, xAxis).normalize();
}
// 获取视图矩阵(世界到相机空间的变换)
Matrix4x4 getViewMatrix() const {
Matrix4x4 result;
// 旋转部分(使用坐标轴的转置)
result.m[0][0] = xAxis.x;
result.m[0][1] = yAxis.x;
result.m[0][2] = zAxis.x;
result.m[1][0] = xAxis.y;
result.m[1][1] = yAxis.y;
result.m[1][2] = zAxis.y;
result.m[2][0] = xAxis.z;
result.m[2][1] = yAxis.z;
result.m[2][2] = zAxis.z;
// 平移部分
result.m[0][3] = -dot(xAxis, Vector3D(origin.x, origin.y, origin.z));
result.m[1][3] = -dot(yAxis, Vector3D(origin.x, origin.y, origin.z));
result.m[2][3] = -dot(zAxis, Vector3D(origin.x, origin.y, origin.z));
return result;
}
// 世界坐标点转换到相机坐标系
Point3D worldToCamera(const Point3D& worldPoint) const {
Matrix4x4 viewMatrix = getViewMatrix();
return viewMatrix.transformPoint(worldPoint);
}
// 相机坐标点转换到世界坐标系
Point3D cameraToWorld(const Point3D& cameraPoint) const {
Matrix4x4 viewMatrix = getViewMatrix();
Matrix4x4 worldMatrix = inverseMatrix(viewMatrix);
return worldMatrix.transformPoint(cameraPoint);
}
};
4.2 投影变换
相机坐标系中的点需要进一步通过投影变换转换到裁剪空间,然后再到标准化设备坐标系(NDC)。
cpp
class ProjectionTransform {
private:
Matrix4x4 projectionMatrix;
public:
// 构建透视投影矩阵
void setPerspectiveProjection(float fovY, float aspectRatio,
float nearPlane, float farPlane) {
float tanHalfFovY = tan(fovY / 2.0f);
projectionMatrix = Matrix4x4(); // 重置为单位矩阵
projectionMatrix.m[0][0] = 1.0f / (aspectRatio * tanHalfFovY);
projectionMatrix.m[1][1] = 1.0f / tanHalfFovY;
projectionMatrix.m[2][2] = -(farPlane + nearPlane) / (farPlane - nearPlane);
projectionMatrix.m[2][3] = -(2.0f * farPlane * nearPlane) / (farPlane - nearPlane);
projectionMatrix.m[3][2] = -1.0f;
projectionMatrix.m[3][3] = 0.0f;
}
// 构建正交投影矩阵
void setOrthographicProjection(float left, float right, float bottom,
float top, float nearPlane, float farPlane) {
projectionMatrix = Matrix4x4(); // 重置为单位矩阵
float width = right - left;
float height = top - bottom;
float depth = farPlane - nearPlane;
projectionMatrix.m[0][0] = 2.0f / width;
projectionMatrix.m[1][1] = 2.0f / height;
projectionMatrix.m[2][2] = -2.0f / depth;
projectionMatrix.m[0][3] = -(right + left) / width;
projectionMatrix.m[1][3] = -(top + bottom) / height;
projectionMatrix.m[2][3] = -(farPlane + nearPlane) / depth;
}
// 获取投影矩阵
Matrix4x4 getProjectionMatrix() const {
return projectionMatrix;
}
// 相机空间点转换到裁剪空间
Point4D cameraToClip(const Point3D& cameraPoint) const {
return projectionMatrix.transformPoint4D(
Point4D(cameraPoint.x, cameraPoint.y, cameraPoint.z, 1.0f)
);
}
// 裁剪空间点转换到标准化设备坐标(NDC)
Point3D clipToNDC(const Point4D& clipPoint) const {
if (fabs(clipPoint.w) < 1e-6f) {
// 防止除以零
return Point3D(
clipPoint.x > 0 ? 1.0f : -1.0f,
clipPoint.y > 0 ? 1.0f : -1.0f,
clipPoint.z > 0 ? 1.0f : -1.0f
);
}
float invW = 1.0f / clipPoint.w;
return Point3D(
clipPoint.x * invW,
clipPoint.y * invW,
clipPoint.z * invW
);
}
};
4.3 应用示例:渲染管线
cpp
class Camera {
private:
CameraCoordinateSystem cameraCS;
ProjectionTransform projection;
int viewportWidth;
int viewportHeight;
public:
Camera(int width, int height)
: viewportWidth(width), viewportHeight(height) {
// 设置默认投影
projection.setPerspectiveProjection(
3.14159f/4.0f, // 45度视场角
(float)width / height, // 宽高比
0.1f, // 近平面
1000.0f // 远平面
);
}
void setLookAt(const Point3D& position,
const Point3D& target,
const Vector3D& up) {
cameraCS.lookAt(position, target, up);
}
// 获取视图矩阵
Matrix4x4 getViewMatrix() const {
return cameraCS.getViewMatrix();
}
// 获取投影矩阵
Matrix4x4 getProjectionMatrix() const {
return projection.getProjectionMatrix();
}
// 将世界坐标转换为屏幕坐标
Point2D worldToScreen(const Point3D& worldPoint) {
// 世界 -> 相机
Point3D cameraPoint = cameraCS.worldToCamera(worldPoint);
// 相机 -> 裁剪
Point4D clipPoint = projection.cameraToClip(cameraPoint);
// 裁剪 -> NDC
Point3D ndcPoint = projection.clipToNDC(clipPoint);
// NDC -> 屏幕
// NDC空间是[-1,1],屏幕空间是[0,width]x[0,height]
float screenX = (ndcPoint.x + 1.0f) * 0.5f * viewportWidth;
float screenY = (1.0f - ndcPoint.y) * 0.5f * viewportHeight; // Y轴翻转
return Point2D(screenX, screenY);
}
// 从屏幕坐标和深度值重建世界坐标
Point3D screenToWorld(const Point2D& screenPoint, float depth) {
// 屏幕 -> NDC
float ndcX = (2.0f * screenPoint.x / viewportWidth) - 1.0f;
float ndcY = 1.0f - (2.0f * screenPoint.y / viewportHeight); // Y轴翻转
float ndcZ = depth; // 假设depth已经在[-1,1]范围内
// NDC -> 裁剪
Matrix4x4 invProj = inverseMatrix(projection.getProjectionMatrix());
Point4D clipPoint(ndcX, ndcY, ndcZ, 1.0f);
Point4D worldPoint = invProj.transformPoint4D(clipPoint);
// 将w分量归一化回来
worldPoint.x /= worldPoint.w;
worldPoint.y /= worldPoint.w;
worldPoint.z /= worldPoint.w;
worldPoint.w = 1.0f;
// 裁剪 -> 相机 -> 世界
Matrix4x4 invView = inverseMatrix(cameraCS.getViewMatrix());
return invView.transformPoint(Point3D(worldPoint.x, worldPoint.y, worldPoint.z));
}
};
5. 惯性坐标系 (Inertial Coordinate System)
5.1 定义和用途
惯性坐标系是用于物理模拟的参考系统,在该系统中牛顿运动定律适用。通常,世界坐标系被视为惯性坐标系。
cpp
class InertialCoordinateSystem : public CoordinateSystem {
private:
Vector3D gravity; // 重力加速度
public:
InertialCoordinateSystem()
: CoordinateSystem(), gravity(0, -9.81f, 0) {} // 默认重力方向为-Y
InertialCoordinateSystem(const Vector3D& gravityDirection, float gravityStrength)
: CoordinateSystem() {
setGravity(gravityDirection, gravityStrength);
}
// 设置重力
void setGravity(const Vector3D& direction, float strength) {
gravity = direction.normalize() * strength;
}
// 获取重力加速度
Vector3D getGravity() const {
return gravity;
}
// 应用物理定律的基本方法
void applyForce(PhysicsObject& object, const Vector3D& force, float deltaTime) {
// F = ma,所以a = F/m
Vector3D acceleration = force / object.getMass();
// 更新速度:v = v0 + a*t
object.setVelocity(object.getVelocity() + acceleration * deltaTime);
// 更新位置:p = p0 + v*t
object.setPosition(object.getPosition() + object.getVelocity() * deltaTime);
}
// 应用重力
void applyGravity(PhysicsObject& object, float deltaTime) {
Vector3D gravityForce = gravity * object.getMass();
applyForce(object, gravityForce, deltaTime);
}
};
5.2 应用示例:物理模拟
cpp
class PhysicsEngine {
private:
InertialCoordinateSystem physicsCS;
std::vector<PhysicsObject*> physicsObjects;
public:
PhysicsEngine() {}
void addObject(PhysicsObject* obj) {
physicsObjects.push_back(obj);
}
void update(float deltaTime) {
for (auto* obj : physicsObjects) {
if (!obj->isStatic()) {
// 应用重力
physicsCS.applyGravity(*obj, deltaTime);
// 应用其他作用力
for (const auto& force : obj->getForces()) {
physicsCS.applyForce(*obj, force, deltaTime);
}
// 清除力