3D数学在计算机图形应用领域的详解与实例
目录
- 向量基础及应用
- 矩阵变换
- 四元数与旋转
- 射线追踪与交叉检测
- 参数曲线与曲面
- 碰撞检测
- 物理模拟
- 光照与着色
- 三维重建
- 图形渲染管线
1. 向量基础及应用
向量是3D数学的基础,用于表示位置、方向、速度等。
1.1 向量的基本操作
cpp
#include <iostream>
#include <cmath>
class Vector3 {
public:
float x, y, z;
Vector3() : x(0.0f), y(0.0f), z(0.0f) {}
Vector3(float x, float y, float z) : x(x), y(y), z(z) {}
// 向量加法
Vector3 operator+(const Vector3& v) const {
return Vector3(x + v.x, y + v.y, z + v.z);
}
// 向量减法
Vector3 operator-(const Vector3& v) const {
return Vector3(x - v.x, y - v.y, z - v.z);
}
// 标量乘法
Vector3 operator*(float scalar) const {
return Vector3(x * scalar, y * scalar, z * scalar);
}
// 点积
float dot(const Vector3& v) const {
return x * v.x + y * v.y + z * v.z;
}
// 叉积
Vector3 cross(const Vector3& v) const {
return Vector3(
y * v.z - z * v.y,
z * v.x - x * v.z,
x * v.y - y * v.x
);
}
// 向量长度
float length() const {
return std::sqrt(x * x + y * y + z * z);
}
// 单位化向量
Vector3 normalize() const {
float len = length();
if (len < 1e-6f) return Vector3(0, 0, 0);
return Vector3(x / len, y / len, z / len);
}
};
1.2 实际应用:摄像机朝向计算
cpp
// 计算摄像机朝向向量
Vector3 calculateCameraDirection(const Vector3& cameraPosition, const Vector3& targetPosition) {
return (targetPosition - cameraPosition).normalize();
}
// 计算摄像机的右向量和上向量
void calculateCameraVectors(const Vector3& forward, Vector3& right, Vector3& up) {
// 约定的世界空间上向量
Vector3 worldUp(0.0f, 1.0f, 0.0f);
// 右向量是前向量与世界上向量的叉积
right = forward.cross(worldUp).normalize();
// 上向量是右向量与前向量的叉积
up = right.cross(forward).normalize();
}
1.3 应用实例:物体跟随行为
cpp
// 游戏或模拟中的跟随行为
Vector3 calculateFollowVector(const Vector3& followerPos, const Vector3& targetPos, float maxSpeed) {
Vector3 direction = targetPos - followerPos;
float distance = direction.length();
// 如果足够接近,停止移动
if (distance < 0.1f) {
return Vector3(0, 0, 0);
}
// 计算速度,可以根据距离调整
float speed = std::min(maxSpeed, distance);
return direction.normalize() * speed;
}
2. 矩阵变换
矩阵用于表示三维空间中的变换,如平移、旋转和缩放。
2.1 4x4变换矩阵
cpp
class Matrix4x4 {
public:
float m[4][4];
Matrix4x4() {
// 初始化为单位矩阵
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
m[i][j] = (i == j) ? 1.0f : 0.0f;
}
}
}
// 矩阵乘法
Matrix4x4 operator*(const Matrix4x4& matrix) const {
Matrix4x4 result;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
result.m[i][j] = 0.0f;
for (int k = 0; k < 4; k++) {
result.m[i][j] += m[i][k] * matrix.m[k][j];
}
}
}
return result;
}
// 变换点(向量)
Vector3 transformPoint(const Vector3& point) const {
float x = m[0][0] * point.x + m[0][1] * point.y + m[0][2] * point.z + m[0][3];
float y = m[1][0] * point.x + m[1][1] * point.y + m[1][2] * point.z + m[1][3];
float z = m[2][0] * point.x + m[2][1] * point.y + m[2][2] * point.z + m[2][3];
float w = m[3][0] * point.x + m[3][1] * point.y + m[3][2] * point.z + m[3][3];
// 执行透视除法
if (fabs(w) > 1e-6f) {
return Vector3(x/w, y/w, z/w);
}
return Vector3(x, y, z);
}
// 变换方向向量(不考虑平移)
Vector3 transformDirection(const Vector3& direction) const {
float x = m[0][0] * direction.x + m[0][1] * direction.y + m[0][2] * direction.z;
float y = m[1][0] * direction.x + m[1][1] * direction.y + m[1][2] * direction.z;
float z = m[2][0] * direction.x + m[2][1] * direction.y + m[2][2] * direction.z;
return Vector3(x, y, z);
}
};
2.2 创建基本变换矩阵
cpp
// 创建平移矩阵
Matrix4x4 createTranslationMatrix(float x, float y, float z) {
Matrix4x4 result;
result.m[0][3] = x;
result.m[1][3] = y;
result.m[2][3] = z;
return result;
}
// 创建缩放矩阵
Matrix4x4 createScaleMatrix(float x, float y, float z) {
Matrix4x4 result;
result.m[0][0] = x;
result.m[1][1] = y;
result.m[2][2] = z;
return result;
}
// 创建X轴旋转矩阵
Matrix4x4 createRotationXMatrix(float angleInRadians) {
Matrix4x4 result;
float cosA = cos(angleInRadians);
float sinA = sin(angleInRadians);
result.m[1][1] = cosA;
result.m[1][2] = -sinA;
result.m[2][1] = sinA;
result.m[2][2] = cosA;
return result;
}
// 创建Y轴旋转矩阵
Matrix4x4 createRotationYMatrix(float angleInRadians) {
Matrix4x4 result;
float cosA = cos(angleInRadians);
float sinA = sin(angleInRadians);
result.m[0][0] = cosA;
result.m[0][2] = sinA;
result.m[2][0] = -sinA;
result.m[2][2] = cosA;
return result;
}
// 创建Z轴旋转矩阵
Matrix4x4 createRotationZMatrix(float angleInRadians) {
Matrix4x4 result;
float cosA = cos(angleInRadians);
float sinA = sin(angleInRadians);
result.m[0][0] = cosA;
result.m[0][1] = -sinA;
result.m[1][0] = sinA;
result.m[1][1] = cosA;
return result;
}
2.3 视图和投影矩阵
cpp
// 创建视图矩阵(摄像机位置和朝向)
Matrix4x4 createViewMatrix(const Vector3& position, const Vector3& target, const Vector3& up) {
Vector3 forward = (position - target).normalize(); // 注意:视图空间中z轴通常指向视图后方
Vector3 right = up.cross(forward).normalize();
Vector3 newUp = forward.cross(right).normalize();
Matrix4x4 result;
// 设置旋转部分
result.m[0][0] = right.x;
result.m[0][1] = right.y;
result.m[0][2] = right.z;
result.m[1][0] = newUp.x;
result.m[1][1] = newUp.y;
result.m[1][2] = newUp.z;
result.m[2][0] = forward.x;
result.m[2][1] = forward.y;
result.m[2][2] = forward.z;
// 设置平移部分
result.m[0][3] = -right.dot(position);
result.m[1][3] = -newUp.dot(position);
result.m[2][3] = -forward.dot(position);
return result;
}
// 创建透视投影矩阵
Matrix4x4 createPerspectiveMatrix(float fovY, float aspectRatio, float nearZ, float farZ) {
float tanHalfFovY = tan(fovY / 2.0f);
Matrix4x4 result;
result.m[0][0] = 1.0f / (aspectRatio * tanHalfFovY);
result.m[1][1] = 1.0f / tanHalfFovY;
result.m[2][2] = farZ / (nearZ - farZ);
result.m[2][3] = (nearZ * farZ) / (nearZ - farZ);
result.m[3][2] = -1.0f;
result.m[3][3] = 0.0f;
return result;
}
2.4 实际应用:骨骼动画的变换矩阵
cpp
struct Bone {
Matrix4x4 localTransform;
Matrix4x4 globalTransform;
int parentIndex;
};
// 计算骨骼全局变换
void calculateGlobalTransforms(std::vector<Bone>& skeleton) {
for (size_t i = 0; i < skeleton.size(); i++) {
if (skeleton[i].parentIndex == -1) {
// 根骨骼
skeleton[i].globalTransform = skeleton[i].localTransform;
} else {
// 子骨骼的全局变换是父骨骼的全局变换乘以自身的局部变换
skeleton[i].globalTransform =
skeleton[skeleton[i].parentIndex].globalTransform * skeleton[i].localTransform;
}
}
}
3. 四元数与旋转
四元数提供了一种紧凑且无万向节锁问题的旋转表示方法。
3.1 四元数的基本操作
cpp
class Quaternion {
public:
float x, y, z, w; // w是实部,x,y,z是虚部
Quaternion() : x(0.0f), y(0.0f), z(0.0f), w(1.0f) {}
Quaternion(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {}
// 四元数乘法
Quaternion operator*(const Quaternion& q) const {
return Quaternion(
w * q.x + x * q.w + y * q.z - z * q.y,
w * q.y - x * q.z + y * q.w + z * q.x,
w * q.z + x * q.y - y * q.x + z * q.w,
w * q.w - x * q.x - y * q.y - z * q.z
);
}
// 共轭
Quaternion conjugate() const {
return Quaternion(-x, -y, -z, w);
}
// 模长
float length() const {
return sqrt(x*x + y*y + z*z + w*w);
}
// 单位化
Quaternion normalize() const {
float len = length();
if (len < 1e-6f) return Quaternion(0, 0, 0, 1);
return Quaternion(x/len, y/len, z/len, w/len);
}
// 四元数转旋转矩阵
Matrix4x4 toRotationMatrix() const {
Matrix4x4 result;
float xx = x * x;
float xy = x * y;
float xz = x * z;
float xw = x * w;
float yy = y * y;
float yz = y * z;
float yw = y * w;
float zz = z * z;
float zw = z * w;
result.m[0][0] = 1.0f - 2.0f * (yy + zz);
result.m[0][1] = 2.0f * (xy - zw);
result.m[0][2] = 2.0f * (xz + yw);
result.m[1][0] = 2.0f * (xy + zw);
result.m[1][1] = 1.0f - 2.0f * (xx + zz);
result.m[1][2] = 2.0f * (yz - xw);
result.m[2][0] = 2.0f * (xz - yw);
result.m[2][1] = 2.0f * (yz + xw);
result.m[2][2] = 1.0f - 2.0f * (xx + yy);
return result;
}
};
3.2 构建旋转四元数
cpp
// 从轴角构建四元数
Quaternion fromAxisAngle(const Vector3& axis, float angleInRadians) {
Vector3 normalizedAxis = axis.normalize();
float sinHalfAngle = sin(angleInRadians * 0.5f);
return Quaternion(
normalizedAxis.x * sinHalfAngle,
normalizedAxis.y * sinHalfAngle,
normalizedAxis.z * sinHalfAngle,
cos(angleInRadians * 0.5f)
);
}
// 从欧拉角(YXZ顺序)构建四元数
Quaternion fromEulerAngles(float pitch, float yaw, float roll) {
// 将欧拉角转换为弧度
float halfPitch = pitch * 0.5f;
float halfYaw = yaw * 0.5f;
float halfRoll = roll * 0.5f;
float cosHalfPitch = cos(halfPitch);
float sinHalfPitch = sin(halfPitch);
float cosHalfYaw = cos(halfYaw);
float sinHalfYaw = sin(halfYaw);
float cosHalfRoll = cos(halfRoll);
float sinHalfRoll = sin(halfRoll);
return Quaternion(
cosHalfYaw * sinHalfPitch * cosHalfRoll + sinHalfYaw * cosHalfPitch * sinHalfRoll,
cosHalfYaw * cosHalfPitch * sinHalfRoll - sinHalfYaw * sinHalfPitch * cosHalfRoll,
sinHalfYaw * cosHalfPitch * cosHalfRoll - cosHalfYaw * sinHalfPitch * sinHalfRoll,
cosHalfYaw * cosHalfPitch * cosHalfRoll + sinHalfYaw * sinHalfPitch * sinHalfRoll
);
}
3.3 四元数插值
cpp
// 四元数球面线性插值(SLERP)
Quaternion slerp(const Quaternion& q1, const Quaternion& q2, float t) {
// 确保t在[0,1]范围内
t = std::max(0.0f, std::min(1.0f, t));
// 计算四元数点积,表示两个旋转的接近程度
float dot = q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w;
// 如果点积为负,取反其中一个四元数以确保最短路径插值
Quaternion q2Adjusted = q2;
if (dot < 0.0f) {
q2Adjusted.x = -q2.x;
q2Adjusted.y = -q2.y;
q2Adjusted.z = -q2.z;
q2Adjusted.w = -q2.w;
dot = -dot;
}
// 当四元数几乎平行时,使用线性插值以避免数值问题
if (dot > 0.9995f) {
Quaternion result = Quaternion(
q1.x + t * (q2Adjusted.x - q1.x),
q1.y + t * (q2Adjusted.y - q1.y),
q1.z + t * (q2Adjusted.z - q1.z),
q1.w + t * (q2Adjusted.w - q1.w)
);
return result.normalize();
}
// 球面线性插值
float angle = acos(dot);
float sinAngle = sin(angle);
float scale0 = sin((1.0f - t) * angle) / sinAngle;
float scale1 = sin(t * angle) / sinAngle;
return Quaternion(
scale0 * q1.x + scale1 * q2Adjusted.x,
scale0 * q1.y + scale1 * q2Adjusted.y,
scale0 * q1.z + scale1 * q2Adjusted.z,
scale0 * q1.w + scale1 * q2Adjusted.w
);
}
3.4 实际应用:角色旋转
cpp
class Character {
private:
Vector3 position;
Quaternion orientation;
public:
Character() : position(0,0,0), orientation(0,0,0,1) {}
// 按指定角度旋转角色
void rotate(float angle, const Vector3& axis) {
Quaternion rotation = fromAxisAngle(axis, angle);
orientation = rotation * orientation;
orientation = orientation.normalize(); // 防止累积误差
}
// 平滑转向目标方向
void smoothTurnTowards(const Vector3& targetDirection, float turnSpeed, float deltaTime) {
// 计算当前朝向
Vector3 forward = Vector3(0, 0, 1);
Matrix4x4 rotMatrix = orientation.toRotationMatrix();
Vector3 currentDirection = rotMatrix.transformDirection(forward);
// 如果当前方向已接近目标方向,则无需旋转
if ((targetDirection - currentDirection).length() < 0.01f) {
return;
}
// 计算旋转轴和角度
Vector3 rotationAxis = currentDirection.cross(targetDirection).normalize();
float angle = acos(currentDirection.dot(targetDirection));
// 限制旋转速度
float maxAngle = turnSpeed * deltaTime;
float actualAngle = std::min(angle, maxAngle);
// 应用旋转
Quaternion rotation = fromAxisAngle(rotationAxis, actualAngle);
orientation = rotation * orientation;
orientation = orientation.normalize();
}
};
4. 射线追踪与交叉检测
射线追踪用于检测物体间的交叉,是许多图形和游戏算法的基础。
4.1 射线的表示
cpp
class Ray {
public:
Vector3 origin; // 射线起点
Vector3 direction; // 射线方向(单位向量)
Ray() : origin(0,0,0), direction(0,0,1) {}
Ray(const Vector3& origin, const Vector3& direction)
: origin(origin), direction(direction.normalize()) {}
// 根据参数t返回射线上的点
Vector3 getPoint(float t) const {
return origin + direction * t;
}
};
4.2 射线与球体相交测试
cpp
struct Sphere {
Vector3 center;
float radius;
Sphere(const Vector3& center, float radius)
: center(center), radius(radius) {}
};
// 检测射线与球体的交点
bool rayIntersectsSphere(const Ray& ray, const Sphere& sphere, float& t1, float& t2) {
Vector3 oc = ray.origin - sphere.center;
float a = ray.direction.dot(ray.direction); // 通常为1,如果方向已归一化
float b = 2.0f * oc.dot(ray.direction);
float c = oc.dot(oc) - sphere.radius * sphere.radius;
float discriminant = b*b - 4*a*c;
if (discriminant < 0) {
// 没有实数解,射线不与球体相交
return false;
}
float sqrtDiscriminant = sqrt(discriminant);
// 两个交点距离
t1 = (-b - sqrtDiscriminant) / (2*a);
t2 = (-b + sqrtDiscriminant) / (2*a);
return true;
}
4.3 射线与三角形相交测试
cpp
struct Triangle {
Vector3 vertices[3];
Triangle(const Vector3& v1, const Vector3& v2, const Vector3& v3) {
vertices[0] = v1;
vertices[1] = v2;
vertices[2] = v3;
}
};
// 使用Möller–Trumbore算法检测射线与三角形的交点
bool rayIntersectsTriangle(const Ray& ray, const Triangle& triangle, float& t, float& u, float& v) {
const float EPSILON = 0.0000001f;
Vector3 vertex0 = triangle.vertices[0];
Vector3 vertex1 = triangle.vertices[1];
Vector3 vertex2 = triangle.vertices[2];
Vector3 edge1 = vertex1 - vertex0;
Vector3 edge2 = vertex2 - vertex0;
Vector3 h = ray.direction.cross(edge2);
float a = edge1.dot(h);
if (a > -EPSILON && a < EPSILON) {
return false; // 射线平行于三角形
}
float f = 1.0f / a;
Vector3 s = ray.origin - vertex0;
u = f * s.dot(h);
if (u < 0.0f || u > 1.0f) {
return false;
}
Vector3 q = s.cross(edge1);
v = f * ray.direction.dot(q);
if (v < 0.0f || u + v > 1.0f) {
return false;
}
// 计算t,射线交点 = 射线原点 + t * 射线方向
t = f * edge2.dot(q);
if (t > EPSILON) {
return true;
}
// 射线交点在三角形后方
return false;
}
4.4 实际应用:鼠标拾取
cpp
// 从屏幕坐标创建世界空间射线
Ray createRayFromScreenPoint(int screenX, int screenY, int screenWidth, int screenHeight,
const Matrix4x4& projectionMatrix, const Matrix4x4& viewMatrix) {
// 将屏幕坐标转换为规范化设备坐标 (-1 到 1)
float x = (2.0f * screenX) / screenWidth - 1.0f;
float y = 1.0f - (2.0f * screenY) / screenHeight;
float z = 1.0f; // 远平面
// 从投影空间转换到视图空间
Matrix4x4 invProj = inverse(projectionMatrix);
Vector3 rayNds = Vector3(x, y, z);
Vector3 rayView = invProj.transformPoint(rayNds);
rayView.z = 1.0f; // 重置z为1,因为我们只需要方向
// 从视图空间转换到世界空间
Matrix4x4 invView = inverse(viewMatrix);
Vector3 rayWorld = invView.transformPoint(rayView);
Vector3 origin = invView.transformPoint(Vector3(0, 0, 0));
Vector3 direction = (rayWorld - origin).normalize();
return Ray(origin, direction);
}
// 进行拾取测试
bool pickObject(int screenX, int screenY, const std::vector<Triangle>& triangles,
const Matrix4x4& projectionMatrix, const Matrix4x4& viewMatrix, int& pickedTriangleIndex) {
// 创建射线
Ray ray = createRayFromScreenPoint(screenX, screenY, screenWidth, screenHeight,
projectionMatrix, viewMatrix);
// 测试射线与所有三角形的交点
float closestT = FLT_MAX;
pickedTriangleIndex = -1;
for (size_t i = 0; i < triangles.size(); i++) {
float t, u, v;
if (rayIntersectsTriangle(ray, triangles[i], t, u, v)) {
if (t < closestT) {
closestT = t;
pickedTriangleIndex = i;
}
}
}
return pickedTriangleIndex != -1;
}
5. 参数曲线与曲面
参数曲线和曲面用于表示平滑的形状,广泛应用于建模和动画。
5.1 贝塞尔曲线
cpp
// 计算三次贝塞尔曲线上的点
Vector3 cubicBezierPoint(const Vector3& p0, const Vector3& p1,
const Vector3& p2, const Vector3& p3, float t) {
float u = 1.0f - t;
float tt = t * t;
float uu = u * u;
float uuu = uu * u;
float ttt = tt * t;
Vector3 point = p0 * uuu; // (1-t)^3 * P0
point = point + p1 * (3.0f * uu * t); // + 3(1-t)^2 * t * P1
point = point + p2 * (3.0f * u * tt); // + 3(1-t) * t^2 * P2
point = point + p3 * ttt; // + t^3 * P3
return point;
}
// 创建一组点,表示贝塞尔曲线
std::vector<Vector3> createBezierCurve(const Vector3& p0, const Vector3& p1,
const Vector3& p2, const Vector3& p3, int segments) {
std::vector<Vector3> points;
for (int i = 0; i <= segments; i++) {
float t = static_cast<float>(i) / segments;
points.push_back(cubicBezierPoint(p0, p1, p2, p3, t));
}
return points;
}
5.2 样条曲线
cpp
// Catmull-Rom样条曲线
Vector3 catmullRomPoint(const Vector3& p0, const Vector3& p1,
const Vector3& p2, const Vector3& p3, float t) {
float t2 = t * t;
float t3 = t2 * t;
Vector3 point = p0 * (-0.5f * t3 + t2 - 0.5f * t);
point = point + p1 * (1.5f * t3 - 2.5f * t2 + 1.0f);
point = point + p2 * (-1.5f * t3 + 2.0f * t2 + 0.5f * t);
point = point + p3 * (0.5f * t3 - 0.5f * t2);
return point;
}
// 创建一组点,表示Catmull-Rom样条曲线段
std::vector<Vector3> createCatmullRomSegment(const Vector3& p0, const Vector3& p1,
const Vector3& p2, const Vector3& p3, int segments) {
std::vector<Vector3> points;
for (int i = 0; i <= segments; i++) {
float t = static_cast<float>(i) / segments;
points.push_back(catmullRomPoint(p0, p1, p2, p3, t));
}
return points;
}
// 创建一条经过所有控制点的Catmull-Rom样条曲线
std::vector<Vector3> createCatmullRomSpline(const std::vector<Vector3>& controlPoints, int segmentsPerCurve) {
if (controlPoints.size() < 4) {
return controlPoints; // 至少需要4个控制点
}
std::vector<Vector3> curvePoints;
// 对于每个曲线段
for (size_t i = 0; i < controlPoints.size() - 3; i++) {
std::vector<Vector3> segment = createCatmullRomSegment(
controlPoints[i], controlPoints[i+1],
controlPoints[i+2], controlPoints[i+3],
segmentsPerCurve
);
// 添加当前段的点(除了最后一个点,除非是最后一段)
for (size_t j = 0; j < segment.size(); j++) {
if (j < segment.size() - 1 || i == controlPoints.size() - 4) {
curvePoints.push_back(segment[j]);
}
}
}
return curvePoints;
}
5.3 实际应用:动画路径
cpp
class PathAnimation {
private:
std::vector<Vector3> controlPoints;
std::vector<Vector3> pathPoints;
float totalDistance;
std::vector<float> distanceToParameter;
public:
PathAnimation(const std::vector<Vector3>& controlPoints, int resolution = 100)
: controlPoints(controlPoints) {
// 生成路径点
pathPoints = createCatmullRomSpline(controlPoints, resolution / controlPoints.size());
// 计算总距离和参数映射表
calculateDistanceTable();
}
// 计算距离表和参数映射
void calculateDistanceTable() {
totalDistance = 0.0f;
distanceToParameter.clear();
distanceToParameter.push_back(0.0f);
for (size_t i = 1; i < pathPoints.size(); i++) {
float segmentLength = (pathPoints[i] - pathPoints[i-1]).length();
totalDistance += segmentLength;
distanceToParameter.push_back(totalDistance);
}
// 归一化距离参数
for (size_t i = 0; i < distanceToParameter.size(); i++) {
distanceToParameter[i] /= totalDistance;
}
}
// 根据归一化参数t获取路径上的点
Vector3 getPointAtParameter(float t) {
if (pathPoints.size() <= 1) {
return pathPoints.empty() ? Vector3(0,0,0) : pathPoints[0];
}
// 将t限制在[0,1]范围内
t = std::max(0.0f, std::min(1.0f, t));
// 二分查找找到对应的段
size_t low = 0;
size_t high = distanceToParameter.size() - 1;
while (low <= high) {
size_t mid = (low + high) / 2;
if (distanceToParameter[mid] < t) {
low = mid + 1;
} else if (distanceToParameter[mid] > t) {
if (mid == 0) break;
high = mid - 1;
} else {
return pathPoints[mid];
}
}
// 线性插值获取精确位置
size_t index = high;
float localT = 0.0f;
if (index < distanceToParameter.size() - 1) {
float d0 = distanceToParameter[index];
float d1 = distanceToParameter[index + 1];
localT = (t - d0) / (d1 - d0);
}
Vector3 p0 = pathPoints[index];
Vector3 p1 = (index < pathPoints.size() - 1) ? pathPoints[index + 1] : p0;
return p0 + (p1 - p0) * localT;
}
};
6. 碰撞检测
碰撞检测在游戏和物理模拟中起着关键作用。
6.1 包围体
cpp
struct AABB { // 轴对齐包围盒
Vector3 min;
Vector3 max;
AABB() : min(0,0,0), max(0,0,0) {}
AABB(const Vector3& min, const Vector3& max) : min(min), max(max) {}
// 测试点是否在AABB内
bool containsPoint(const Vector3& point) const {
return point.x >= min.x && point.x <= max.x &&
point.y >= min.y && point.y <= max.y &&
point.z >= min.z && point.z <= max.z;
}
// 测试两个AABB是否相交
bool intersects(const AABB& other) const {
return min.x <= other.max.x && max.x >= other.min.x &&
min.y <= other.max.y && max.y >= other.min.y &&
min.z <= other.max.z && max.z >= other.min.z;
}
// 计算两个AABB的相交体积
AABB getIntersection(const AABB& other) const {
Vector3 intersectionMin(
std::max(min.x, other.min.x),
std::max(min.y, other.min.y),
std::max(min.z, other.min.z)
);
Vector3 intersectionMax(
std::min(max.x, other.max.x),
std::min(max.y, other.max.y),
std::min(max.z, other.max.z)
);
return AABB(intersectionMin, intersectionMax);
}
};
6.2 球体碰撞
cpp
// 检测两个球体是否相交
bool spheresIntersect(const Sphere& a, const Sphere& b) {
float distanceSquared = (a.center - b.center).dot(a.center - b.center);
float radiusSum = a.radius + b.radius;
return distanceSquared <= radiusSum * radiusSum;
}
// 计算两个相交球体的碰撞信息
struct CollisionInfo {
bool collision;
Vector3 normal; // 从a指向b的碰撞法线
float depth; // 穿透深度
};
CollisionInfo sphereCollisionInfo(const Sphere& a, const Sphere& b) {
CollisionInfo info;
Vector3 direction = b.center - a.center;
float distanceSquared = direction.dot(direction);
float radiusSum = a.radius + b.radius;
info.collision = distanceSquared <= radiusSum * radiusSum;
if (info.collision) {
float distance = sqrt(distanceSquared);
// 避免除以零
if (distance < 1e-6f) {
info.normal = Vector3(0, 1, 0); // 任意选择一个向上的法线
info.depth = a.radius + b.radius;
} else {
info.normal = direction * (1.0f / distance); // 单位化方向向量
info.depth = radiusSum - distance;
}
} else {
info.normal = Vector3(0, 0, 0);
info.depth = 0.0f;
}
return info;
}
6.3 分离轴定理(SAT)
cpp
struct OBB { // 有向包围盒
Vector3 center; // 中心点
Vector3 halfSize; // 半尺寸
Matrix4x4 rotation; // 旋转矩阵
// 获取OBB的8个顶点
std::vector<Vector3> getVertices() const {
std::vector<Vector3> vertices(8);
for (int i = 0; i < 8; i++) {
Vector3 direction(
((i & 1) ? 1 : -1) * halfSize.x,
((i & 2) ? 1 : -1) * halfSize.y,
((i & 4) ? 1 : -1) * halfSize.z
);
vertices[i] = center + rotation.transformDirection(direction);
}
return vertices;
}
// 获取OBB的3个轴
void getAxes(Vector3& xAxis, Vector3& yAxis, Vector3& zAxis) const {
xAxis = rotation.transformDirection(Vector3(1, 0, 0));
yAxis = rotation.transformDirection(Vector3(0, 1, 0));
zAxis = rotation.transformDirection(Vector3(0, 0, 1));
}
};
// 使用分离轴定理检测两个OBB是否相交
bool obbIntersectsSAT(const OBB& a, const OBB& b) {
Vector3 aXAxis, aYAxis, aZAxis;
Vector3 bXAxis, bYAxis, bZAxis;
a.getAxes(aXAxis, aYAxis, aZAxis);
b.getAxes(bXAxis, bYAxis, bZAxis);
// 15个需要测试的轴: 3 + 3 + 9 (轴的交叉乘积)
Vector3 axes[15] = {
aXAxis, aYAxis, aZAxis,
bXAxis, bYAxis, bZAxis,
aXAxis.cross(bXAxis), aXAxis.cross(bYAxis), aXAxis.cross(bZAxis),
aYAxis.cross(bXAxis), aYAxis.cross(bYAxis), aYAxis.cross(bZAxis),
aZAxis.cross(bXAxis), aZAxis.cross(bYAxis), aZAxis.cross(bZAxis)
};
// 获取顶点
std::vector<Vector3> verticesA = a.getVertices();
std::vector<Vector3> verticesB = b.getVertices();
// 中心偏移
Vector3 centerDifference = b.center - a.center;
// 测试每个轴
for (int i = 0; i < 15; i++) {
// 跳过接近于零的轴(交叉乘积可能为零)
if (axes[i].length() < 1e-6f) continue;
// 归一化轴
Vector3 axis = axes[i].normalize();
// 计算两个OBB在该轴上的投影
float minA = FLT_MAX, maxA = -FLT_MAX;
float minB = FLT_MAX, maxB = -FLT_MAX;
for (const auto& vertex : verticesA) {
float projection = axis.dot(vertex);
minA = std::min(minA, projection);
maxA = std::max(maxA, projection);
}
for (const auto& vertex : verticesB) {
float projection = axis.dot(vertex);
minB = std::min(minB, projection);
maxB = std::max(maxB, projection);
}
// 测试投影是否分离
if (maxA < minB || maxB < minA) {
return false; // 找到分离轴,不相交
}
}
// 没有找到分离轴,两个OBB相交
return true;
}
6.4 空间分区
cpp
// 简单的八叉树实现
class OctreeNode {
public:
AABB bounds;
std::vector<int> objectIndices; // 存储在该节点中的对象索引
OctreeNode* children[8];
bool isLeaf;
OctreeNode(const AABB& bounds) : bounds(bounds), isLeaf(true) {
for (int i = 0; i < 8; i++) {
children[i] = nullptr;
}
}
~OctreeNode() {
for (int i = 0; i < 8; i++) {
delete children[i];
}
}
// 将节点分割为8个子节点
void split() {
if (!isLeaf) return;
isLeaf = false;
Vector3 center = (bounds.min + bounds.max) * 0.5f;
// 创建8个子节点
for (int i = 0; i < 8; i++) {
Vector3 min = bounds.min;
Vector3 max = bounds.max;
if (i & 1) min.x = center.x; else max.x = center.x;
if (i & 2) min.y = center.y; else max.y = center.y;
if (i & 4) min.z = center.z; else max.z = center.z;
children[i] = new OctreeNode(AABB(min, max));
}
// 重新分配对象到子节点
for (int objIndex : objectIndices) {
insertObject(objIndex);
}
objectIndices.clear();
}
// 插入对象到适当的节点
void insertObject(int objIndex, const std::vector<AABB>& objectBounds) {
if (isLeaf) {
// 如果是叶节点,直接添加对象
objectIndices.push_back(objIndex);
// 如果对象数量超过阈值,考虑分割
if (objectIndices.size() > 8) {
split();
}
} else {
// 找到对象应该放入的子节点
for (int i = 0; i < 8; i++) {
if (children[i]->bounds.intersects(objectBounds[objIndex])) {
children[i]->insertObject(objIndex, objectBounds);
}
}
}
}
// 查询给定AABB与哪些对象相交
void query(const AABB& queryAABB, std::vector<int>& result,
const std::vector<AABB>& objectBounds) {
if (!bounds.intersects(queryAABB)) {
return; // 查询区域与当前节点不相交
}
if (isLeaf) {
// 检查叶节点中的每个对象
for (int objIndex : objectIndices) {
if (objectBounds[objIndex].intersects(queryAABB)) {
result.push_back(objIndex);
}
}
} else {
// 递归查询子节点
for (int i = 0; i < 8; i++) {
children[i]->query(queryAABB, result, objectBounds);
}
}
}
};
7. 物理模拟
物理模拟为游戏和虚拟现实提供了真实感和交互性。
7.1 质点系统
cpp
struct Particle {
Vector3 position;
Vector3 velocity;
Vector3 acceleration;
float mass;
bool isFixed; // 是否固定不动
Particle()
: position(0,0,0), velocity(0,0,0), acceleration(0,0,0),
mass(1.0f), isFixed(false) {}
Particle(const Vector3& pos, float mass = 1.0f)
: position(pos), velocity(0,0,0), acceleration(0,0,0),
mass(mass), isFixed(false) {}
// 应用力
void applyForce(const Vector3& force) {
if (!isFixed) {
acceleration = acceleration + force * (1.0f / mass);
}
}
// 更新位置和速度
void update(float deltaTime) {
if (!isFixed) {
velocity = velocity + acceleration * deltaTime;
position = position + velocity * deltaTime;
// 重置加速度
acceleration = Vector3(0, 0, 0);
}
}
};
7.2 弹簧和约束
cpp
struct Spring {
int particleIndexA;
int particleIndexB;
float restLength;
float stiffness;
float damping;
Spring(int a, int b, float restLength, float stiffness, float damping)
: particleIndexA(a), particleIndexB(b),
restLength(restLength), stiffness(stiffness), damping(damping) {}
// 应用弹簧力
void applyForce(std::vector<Particle>& particles) {
Particle& a = particles[particleIndexA];
Particle& b = particles[particleIndexB];
Vector3 delta = b.position - a.position;
float currentLength = delta.length();
if (currentLength < 1e-6f) return; // 避免除以零
// 弹簧力方向
Vector3 direction = delta * (1.0f / currentLength);
// 弹簧力大小 (Hooke's law: F = -k * x)
float displacement = currentLength - restLength;
// 计算相对速度在弹簧方向上的投影,用于阻尼
Vector3 relativeVelocity = b.velocity - a.velocity;
float dampingFactor = direction.dot(relativeVelocity) * damping;
float forceMagnitude = stiffness * displacement + dampingFactor;
Vector3 force = direction * forceMagnitude;
// 施加力(作用力与反作用力)
if (!a.isFixed) a.applyForce(force);
if (!b.isFixed) b.applyForce(force * -1.0f);
}
};
7.3 简单布料模拟
cpp
class ClothSimulation {
private:
std::vector<Particle> particles;
std::vector<Spring> springs;
int gridWidth, gridHeight;
float gravity;
public:
ClothSimulation(int width, int height, float particleSpacing,
float stiffness, float damping, float particleMass, float gravityValue = -9.8f)
: gridWidth(width), gridHeight(height), gravity(gravityValue) {
// 创建粒子网格
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
Vector3 position(x * particleSpacing, 0, y * particleSpacing);
particles.push_back(Particle(position, particleMass));
}
}
// 固定顶部两角
particles[0].isFixed = true;
particles[width-1].isFixed = true;
// 创建弹簧连接 (格子结构)
// 水平方向弹簧
for (int y = 0; y < height; y++) {
for (int x = 0; x < width - 1; x++) {
int index = y * width + x;
springs.push_back(Spring(index, index + 1, particleSpacing, stiffness, damping));
}
}
// 垂直方向弹簧
for (int y = 0; y < height - 1; y++) {
for (int x = 0; x < width; x++) {
int index = y * width + x;
springs.push_back(Spring(index, index + width, particleSpacing, stiffness, damping));
}
}
// 对角线弹簧 (增加稳定性)
for (int y = 0; y < height - 1; y++) {
for (int x = 0; x < width - 1; x++) {
int index = y * width + x;
springs.push_back(Spring(index, index + width + 1,
particleSpacing * 1.414f, stiffness * 0.7f, damping));
springs.push_back(Spring(index + 1, index + width,
particleSpacing * 1.414f, stiffness * 0.7f, damping));
}
}
}
// 更新模拟
void update(float deltaTime) {
// 施加重力
for (auto& particle : particles) {
particle.applyForce(Vector3(0, gravity * particle.mass, 0));
}
// 应用弹簧力
for (auto& spring : springs) {
spring.applyForce(particles);
}
// 更新粒子位置
for (auto& particle : particles) {
particle.update(deltaTime);
}
}
// 获取粒子位置,用于渲染
std::vector<Vector3> getParticlePositions() const {
std::vector<Vector3> positions;
for (const auto& particle : particles) {
positions.push_back(particle.position);
}
return positions;
}
// 获取布料网格的三角形索引,用于渲染
std::vector<int> getTriangleIndices() const {
std::vector<int> indices;
for (int y = 0; y < gridHeight - 1; y++) {
for (int x = 0; x < gridWidth - 1; x++) {
int topLeft = y * gridWidth + x;
int topRight = topLeft + 1;
int bottomLeft = (y + 1) * gridWidth + x;
int bottomRight = bottomLeft + 1;
// 每个格子两个三角形
indices.push_back(topLeft);
indices.push_back(bottomLeft);
indices.push_back(topRight);
indices.push_back(topRight);
indices.push_back(bottomLeft);
indices.push_back(bottomRight);
}
}
return indices;
}
};
7.4 刚体动力学
cpp
class RigidBody {
private:
Vector3 position; // 质心位置
Quaternion orientation; // 旋转四元数
Vector3 linearVelocity; // 线性速度
Vector3 angularVelocity; // 角速度
float mass; // 质量
Matrix4x4 inertiaTensor; // 惯性张量
Matrix4x4 inverseInertiaTensor; // 惯性张量的逆
// 转换为世界空间的惯性张量的逆
Matrix4x4 getWorldInverseInertiaTensor() const {
Matrix4x4 rotMatrix = orientation.toRotationMatrix();
Matrix4x4 rotMatrixT = transpose(rotMatrix);
// R * I^-1 * R^T
return rotMatrix * inverseInertiaTensor * rotMatrixT;
}
public:
RigidBody(float mass, const Matrix4x4& inertiaTensor)
: position(0,0,0), orientation(0,0,0,1),
linearVelocity(0,0,0), angularVelocity(0,0,0),
mass(mass), inertiaTensor(inertiaTensor) {
// 计算惯性张量的逆
inverseInertiaTensor = inverse(inertiaTensor);
}
// 应用力
void applyForce(const Vector3& force, const Vector3& worldPoint) {
// 线性加速度 a = F/m
Vector3 linearAcceleration = force * (1.0f / mass);
linearVelocity = linearVelocity + linearAcceleration;
// 计算力矩 τ = r × F
Vector3 torque = (worldPoint - position).cross(force);
// 计算角加速度 α = I^-1 * τ
Matrix4x4 worldInvInertia = getWorldInverseInertiaTensor();
Vector3 angularAcceleration = worldInvInertia.transformDirection(torque);
angularVelocity = angularVelocity + angularAcceleration;
}
// 更新位置和旋转
void update(float deltaTime) {
// 更新位置
position = position + linearVelocity * deltaTime;
// 更新旋转
// 将角速度转换为四元数变化率
Quaternion spin(
angularVelocity.x * 0.5f,
angularVelocity.y * 0.5f,
angularVelocity.z * 0.5f,
0
);
// 四元数微分方程: dq/dt = 0.5 * w * q
Quaternion velocityQ = spin * orientation;
// 更新方向四元数
orientation = orientation + velocityQ * deltaTime;
orientation = orientation.normalize();
}
// 获取位置和旋转
Vector3 getPosition() const { return position; }
Quaternion getOrientation() const { return orientation; }
// 从局部坐标转换到世界坐标
Vector3 localToWorld(const Vector3& localPoint) const {
Matrix4x4 rotMatrix = orientation.toRotationMatrix();
return position + rotMatrix.transformDirection(localPoint);
}
// 从世界坐标转换到局部坐标
Vector3 worldToLocal(const Vector3& worldPoint) const {
Matrix4x4 rotMatrix = orientation.toRotationMatrix();
Matrix4x4 invRotMatrix = transpose(rotMatrix);
return invRotMatrix.transformDirection(worldPoint - position);
}
};
8. 光照与着色
光照和着色使3D场景看起来更加逼真和视觉上令人愉悦。
8.1 基本光照模型
cpp
struct Material {
Vector3 ambient;
Vector3 diffuse;
Vector3 specular;
float shininess;
Material() : ambient(0.1f, 0.1f, 0.1f), diffuse(0.8f, 0.8f, 0.8f),
specular(1.0f, 1.0f, 1.0f), shininess(32.0f) {}
};
struct Light {
enum LightType { DIRECTIONAL, POINT, SPOT };
LightType type;
Vector3 position; // 点光源和聚光灯
Vector3 direction; // 方向光和聚光灯
Vector3 color;
float intensity;
// 点光源和聚光灯的衰减参数
float constant;
float linear;
float quadratic;
// 聚光灯的参数
float cutoff; // 内切角余弦值
float outerCutoff; // 外切角余弦值
Light() : type(DIRECTIONAL), position(0,0,0), direction(0,-1,0),
color(1,1,1), intensity(1.0f), constant(1.0f),
linear(0.09f), quadratic(0.032f), cutoff(cos(12.5f * 3.14159f / 180.0f)),
outerCutoff(cos(17.5f * 3.14159f / 180.0f)) {}
};
// 计算Phong光照模型
Vector3 calculatePhongLighting(const Vector3& position, const Vector3& normal, const Vector3& viewDirection,
const Material& material, const Light& light) {
Vector3 result(0, 0, 0);
// 计算环境光
Vector3 ambient = material.ambient * light.color;
// 光照方向
Vector3 lightDir;
float attenuation = 1.0f;
if (light.type == Light::DIRECTIONAL) {
lightDir = -light.direction.normalize();
} else {
// 点光源或聚光灯
lightDir = (light.position - position).normalize();
// 计算距离和衰减
float distance = (light.position - position).length();
attenuation = 1.0f / (light.constant + light.linear * distance +
light.quadratic * distance * distance);
// 聚光灯效果
if (light.type == Light::SPOT) {
float theta = lightDir.dot(-light.direction.normalize());
float epsilon = light.cutoff - light.outerCutoff;
float intensity = std::clamp((theta - light.outerCutoff) / epsilon, 0.0f, 1.0f);
attenuation *= intensity;
}
}
// 计算漫反射
float diff = std::max(normal.dot(lightDir), 0.0f);
Vector3 diffuse = material.diffuse * diff * light.color;
// 计算镜面反射
Vector3 reflectDir = reflect(-lightDir, normal);
float spec = std::pow(std::max(viewDirection.dot(reflectDir), 0.0f), material.shininess);
Vector3 specular = material.specular * spec * light.color;
// 合并结果
result = ambient + (diffuse + specular) * attenuation * light.intensity;
// 确保颜色值在[0,1]范围内
result.x = std::min(1.0f, result.x);
result.y = std::min(1.0f, result.y);
result.z = std::min(1.0f, result.z);
return result;
}
// 辅助函数:计算反射向量
Vector3 reflect(const Vector3& incident, const Vector3& normal) {
return incident - normal * (2.0f * incident.dot(normal));
}
8.2 基于物理的渲染(PBR)
cpp
struct PBRMaterial {
Vector3 albedo; // 基础颜色
float metallic; // 金属度 (0: 非金属, 1: 金属)
float roughness; // 粗糙度 (0: 光滑, 1: 粗糙)
float ao; // 环境光遮蔽
PBRMaterial() : albedo(0.8f, 0.8f, 0.8f), metallic(0.0f),
roughness(0.5f), ao(1.0f) {}
};
// PBR 着色函数
Vector3 calculatePBRLighting(const Vector3& position, const Vector3& normal, const Vector3& viewDirection,
const PBRMaterial& material, const std::vector<Light>& lights) {
Vector3 N = normal.normalize();
Vector3 V = viewDirection.normalize();
// 计算基础反射率 F0
Vector3 F0(0.04f, 0.04f, 0.04f); // 典型的非金属F0值
F0 = F0 * (1.0f - material.metallic) + material.albedo * material.metallic;
// 初始化辐射率
Vector3 Lo(0.0f, 0.0f, 0.0f);
// 计算所有光源的贡献
for (const auto& light : lights) {
// 计算每个光源的方向和强度
Vector3 L;
float attenuation = 1.0f;
Vector3 radiance = light.color * light.intensity;
if (light.type == Light::DIRECTIONAL) {
L = -light.direction.normalize();
} else {
// 点光源或聚光灯
L = (light.position - position).normalize();
// 计算距离和衰减
float distance = (light.position - position).length();
attenuation = 1.0f / (light.constant + light.linear * distance +
light.quadratic * distance * distance);
radiance = radiance * attenuation;
// 聚光灯效果
if (light.type == Light::SPOT) {
float theta = L.dot(-light.direction.normalize());
float epsilon = light.cutoff - light.outerCutoff;
float intensity = std::clamp((theta - light.outerCutoff) / epsilon, 0.0f, 1.0f);
radiance = radiance * intensity;
}
}
// 计算半角向量
Vector3 H = (V + L).normalize();
// Cook-Torrance BRDF
float NdotV = std::max(N.dot(V), 0.0f);
float NdotL = std::max(N.dot(L), 0.0f);
float NdotH = std::max(N.dot(H), 0.0f);
float HdotV = std::max(H.dot(V), 0.0f);
// 法线分布函数 (GGX)
float alpha = material.roughness * material.roughness;
float alpha2 = alpha * alpha;
float denom = NdotH * NdotH * (alpha2 - 1.0f) + 1.0f;
float D = alpha2 / (3.14159f * denom * denom);
// 几何遮蔽函数 (Smith GGX)
float k = ((material.roughness + 1.0f) * (material.roughness + 1.0f)) / 8.0f;
float G1_V = NdotV / (NdotV * (1.0f - k) + k);
float G1_L = NdotL / (NdotL * (1.0f - k) + k);
float G = G1_V * G1_L;
// 菲涅尔方程 (Schlick近似)
Vector3 F = F0 + (Vector3(1.0f, 1.0f, 1.0f) - F0) * std::pow(1.0f - HdotV, 5.0f);
// 计算镜面反射项
Vector3 numerator = D * G * F;
float denominator = 4.0f * NdotV * NdotL + 0.001f; // 防止除以零
Vector3 specular = numerator / denominator;
// 计算漫反射项
Vector3 kD = Vector3(1.0f, 1.0f, 1.0f) - F;
kD = kD * (1.0f - material.metallic); // 金属没有漫反射
// 添加到出射辐射率
Lo = Lo + (kD * material.albedo / 3.14159f + specular) * radiance * NdotL;
}
// 添加环境光
Vector3 ambient = Vector3(0.03f, 0.03f, 0.03f) * material.albedo * material.ao;
Vector3 color = ambient + Lo;
// HDR色调映射和gamma校正
color = color / (color + Vector3(1.0f, 1.0f, 1.0f)); // Reinhard算子
color = Vector3(std::pow(color.x, 1.0f/2.2f), // gamma校正 (gamma=2.2)
std::pow(color.y, 1.0f/2.2f),
std::pow(color.z, 1.0f/2.2f));
return color;
}
8.3 实际应用:渲染法线贴图
cpp
struct Vertex {
Vector3 position;
Vector3 normal;
Vector2 texCoord;
Vector3 tangent;
Vector3 bitangent;
};
// 使用法线贴图计算切线空间中的法线
Vector3 calculateTangentSpaceNormal(const Vertex& vertex, const Vector3& normalMapColor) {
// 将法线贴图颜色从[0,1]转换到[-1,1]范围
Vector3 normalTS(
normalMapColor.x * 2.0f - 1.0f,
normalMapColor.y * 2.0f - 1.0f,
normalMapColor.z * 2.0f - 1.0f
);
// 构建TBN矩阵
Matrix4x4 TBN;
TBN.m[0][0] = vertex.tangent.x;
TBN.m[0][1] = vertex.tangent.y;
TBN.m[0][2] = vertex.tangent.z;
TBN.m[1][0] = vertex.bitangent.x;
TBN.m[1][1] = vertex.bitangent.y;
TBN.m[1][2] = vertex.bitangent.z;
TBN.m[2][0] = vertex.normal.x;
TBN.m[2][1] = vertex.normal.y;
TBN.m[2][2] = vertex.normal.z;
// 将切线空间法线转换到世界空间
Vector3 worldNormal = TBN.transformDirection(normalTS);
return worldNormal.normalize();
}
// 计算顶点的切线和副切线
void calculateTangentSpace(Vertex& v1, Vertex& v2, Vertex& v3) {
Vector3 edge1 = v2.position - v1.position;
Vector3 edge2 = v3.position - v1.position;
Vector2 deltaUV1 = v2.texCoord - v1.texCoord;
Vector2 deltaUV2 = v3.texCoord - v1.texCoord;
float f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);
Vector3 tangent, bitangent;
tangent.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x);
tangent.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y);
tangent.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z);
bitangent.x = f * (-deltaUV2.x * edge1.x + deltaUV1.x * edge2.x);
bitangent.y = f * (-deltaUV2.x * edge1.y + deltaUV1.x * edge2.y);
bitangent.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z);
// 确保切线系统形成右手坐标系
Vector3 normal = v1.normal;
Vector3 T = tangent;
Vector3 B = normal.cross(T).normalize();
v1.tangent = tangent.normalize();
v2.tangent = tangent.normalize();
v3.tangent = tangent.normalize();
v1.bitangent = bitangent.normalize();
v2.bitangent = bitangent.normalize();
v3.bitangent = bitangent.normalize();
}
9. 三维重建
三维重建从图像或深度信息中构建3D模型。
9.1 从点云到网格
cpp
struct Point {
Vector3 position;
Vector3 normal;
};
// 从点云创建网格的简化算法(基于Marching Cubes的概念)
class PointCloudToMesh {
private:
std::vector<Point> pointCloud;
float voxelSize;
float isoLevel;
// 三维网格结构
struct Voxel {
bool hasPoints;
std::vector<int> pointIndices;
};
std::vector<std::vector<std::vector<Voxel>>> voxelGrid;
int gridSizeX, gridSizeY, gridSizeZ;
Vector3 minBounds, maxBounds;
public:
PointCloudToMesh(const std::vector<Point>& points, float voxelSize, float isoLevel = 0.5f)
: pointCloud(points), voxelSize(voxelSize), isoLevel(isoLevel) {
// 计算点云包围盒
calculateBounds();
// 初始化体素网格
initializeVoxelGrid();
// 将点分配到体素
assignPointsToVoxels();
}
// 计算点云的包围盒
void calculateBounds() {
if (pointCloud.empty()) return;
minBounds = maxBounds = pointCloud[0].position;
for (const auto& point : pointCloud) {
minBounds.x = std::min(minBounds.x, point.position.x);
minBounds.y = std::min(minBounds.y, point.position.y);
minBounds.z = std::min(minBounds.z, point.position.z);
maxBounds.x = std::max(maxBounds.x, point.position.x);
maxBounds.y = std::max(maxBounds.y, point.position.y);
maxBounds.z = std::max(maxBounds.z, point.position.z);
}
// 添加一些边距
const float padding = voxelSize * 2.0f;
minBounds = minBounds - Vector3(padding, padding, padding);
maxBounds = maxBounds + Vector3(padding, padding, padding);
// 计算网格尺寸
Vector3 size = maxBounds - minBounds;
gridSizeX = static_cast<int>(std::ceil(size.x / voxelSize));
gridSizeY = static_cast<int>(std::ceil(size.y / voxelSize));
gridSizeZ = static_cast<int>(std::ceil(size.z / voxelSize));
}
// 初始化体素网格
void initializeVoxelGrid() {
voxelGrid.resize(gridSizeX);
for (int x = 0; x < gridSizeX; x++) {
voxelGrid[x].resize(gridSizeY);
for (int y = 0; y < gridSizeY; y++) {
voxelGrid[x][y].resize(gridSizeZ);
for (int z = 0; z < gridSizeZ; z++) {
voxelGrid[x][y][z].hasPoints = false;
}
}
}
}
// 将点分配到体素
void assignPointsToVoxels() {
for (size_t i = 0; i < pointCloud.size(); i++) {
const auto& point = pointCloud[i];
int x = static_cast<int>((point.position.x - minBounds.x) / voxelSize);
int y = static_cast<int>((point.position.y - minBounds.y) / voxelSize);
int z = static_cast<int>((point.position.z - minBounds.z) / voxelSize);
// 确保索引在范围内
if (x >= 0 && x < gridSizeX && y >= 0 && y < gridSizeY && z >= 0 && z < gridSizeZ) {
voxelGrid[x][y][z].hasPoints = true;
voxelGrid[x][y][z].pointIndices.push_back(i);
}
}
}
// 创建网格(简化版,只提取包含点的体素面)
void createMesh(std::vector<Vector3>& vertices, std::vector<int>& indices) {
vertices.clear();
indices.clear();
for (int x = 0; x < gridSizeX; x++) {
for (int y = 0; y < gridSizeY; y++) {
for (int z = 0; z < gridSizeZ; z++) {
if (voxelGrid[x][y][z].hasPoints) {
addVoxelFaces(x, y, z, vertices, indices);
}
}
}
}
}
// 添加一个体素的可见面
void addVoxelFaces(int x, int y, int z, std::vector<Vector3>& vertices, std::vector<int>& indices) {
Vector3 voxelMin = minBounds + Vector3(x * voxelSize, y * voxelSize, z * voxelSize);
// 检查六个相邻体素,如果不包含点,则添加面
// 前面 (x+1)
if (x + 1 >= gridSizeX || !voxelGrid[x+1][y][z].hasPoints) {
addFace(voxelMin, Vector3(voxelSize, 0, 0), Vector3(0, voxelSize, 0),
Vector3(0, 0, voxelSize), false, vertices, indices);
}
// 后面 (x-1)
if (x - 1 < 0 || !voxelGrid[x-1][y][z].hasPoints) {
addFace(voxelMin, Vector3(0, 0, 0), Vector3(0, voxelSize, 0),
Vector3(0, 0, voxelSize), true, vertices, indices);
}
// 右面 (y+1)
if (y + 1 >= gridSizeY || !voxelGrid[x][y+1][z].hasPoints) {
addFace(voxelMin, Vector3(0, voxelSize, 0), Vector3(voxelSize, 0, 0),
Vector3(0, 0, voxelSize), true, vertices, indices);
}
// 左面 (y-1)
if (y - 1 < 0 || !voxelGrid[x][y-1][z].hasPoints) {
addFace(voxelMin, Vector3(0, 0, 0), Vector3(voxelSize, 0, 0),
Vector3(0, 0, voxelSize), false, vertices, indices);
}
// 上面 (z+1)
if (z + 1 >= gridSizeZ || !voxelGrid[x][y][z+1].hasPoints) {
addFace(voxelMin, Vector3(0, 0, voxelSize), Vector3(voxelSize, 0, 0),
Vector3(0, voxelSize, 0), false, vertices, indices);
}
// 下面 (z-1)
if (z - 1 < 0 || !voxelGrid[x][y][z-1].hasPoints) {
addFace(voxelMin, Vector3(0, 0, 0), Vector3(voxelSize, 0, 0),
Vector3(0, voxelSize, 0), true, vertices, indices);
}
}
// 添加一个面(两个三角形)
void addFace(const Vector3& origin, const Vector3& dirX, const Vector3& dirY,
const Vector3& dirZ, bool flipFace, std::vector<Vector3>& vertices,
std::vector<int>& indices) {
size_t baseIndex = vertices.size();
// 添加四个顶点
vertices.push_back(origin);
vertices.push_back(origin + dirX);
vertices.push_back(origin + dirX + dirY);
vertices.push_back(origin + dirY);
// 添加两个三角形
if (!flipFace) {
indices.push_back(baseIndex);
indices.push_back(baseIndex + 1);
indices.push_back(baseIndex + 2);
indices.push_back(baseIndex);
indices.push_back(baseIndex + 2);
indices.push_back(baseIndex + 3);
} else {
indices.push_back(baseIndex);
indices.push_back(baseIndex + 2);
indices.push_back(baseIndex + 1);
indices.push_back(baseIndex);
indices.push_back(baseIndex + 3);
indices.push_back(baseIndex + 2);
}
}
};
9.2 从深度图像到点云
cpp
// 从深度图像到点云
std::vector<Vector3> depthImageToPointCloud(
const std::vector<float>& depthImage, // 深度值(米)
int width, int height, // 图像尺寸
float fovX, float fovY, // 水平和垂直视场角(弧度)
float nearPlane, float farPlane // 近平面和远平面距离
) {
std::vector<Vector3> pointCloud;
pointCloud.reserve(width * height);
float aspectRatio = static_cast<float>(width) / height;
float tanHalfFovX = tan(fovX * 0.5f);
float tanHalfFovY = tan(fovY * 0.5f);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
float depth = depthImage[y * width + x];
// 跳过无效深度值
if (depth <= nearPlane || depth >= farPlane) {
continue;
}
// 计算归一化设备坐标 (NDC)
float ndcX = (2.0f * ((x + 0.5f) / width) - 1.0f) * tanHalfFovX * aspectRatio;
float ndcY = (1.0f - 2.0f * ((y + 0.5f) / height)) * tanHalfFovY;
// 转换为3D点
Vector3 point(ndcX * depth, ndcY * depth, -depth);
pointCloud.push_back(point);
}
}
return pointCloud;
}
// 从点云估计表面法线
std::vector<Point> estimateNormals(const std::vector<Vector3>& pointPositions, int kNeighbors = 20) {
std::vector<Point> pointCloud;
pointCloud.reserve(pointPositions.size());
// 初始化KD树(此处假设有KD树实现)
KDTree kdTree;
kdTree.build(pointPositions);
for (const auto& position : pointPositions) {
// 查找k个最近邻
std::vector<int> neighbors = kdTree.findKNearestNeighbors(position, kNeighbors);
// 计算质心
Vector3 centroid(0, 0, 0);
for (int idx : neighbors) {
centroid = centroid + pointPositions[idx];
}
centroid = centroid * (1.0f / neighbors.size());
// 构建协方差矩阵
float cxx = 0, cxy = 0, cxz = 0, cyy = 0, cyz = 0, czz = 0;
for (int idx : neighbors) {
Vector3 diff = pointPositions[idx] - centroid;
cxx += diff.x * diff.x;
cxy += diff.x * diff.y;
cxz += diff.x * diff.z;
cyy += diff.y * diff.y;
cyz += diff.y * diff.z;
czz += diff.z * diff.z;
}
// 对3x3协方差矩阵进行特征值分解
// 这里简化实现,实际应使用如Eigen库
// 最小特征值对应的特征向量即为表面法线
// 假设我们已经计算出法线
Vector3 normal = calculateNormalFromCovarianceMatrix(cxx, cxy, cxz, cyy, cyz, czz);
// 确保法线指向传感器
if (normal.z > 0) {
normal = normal * -1.0f;
}
// 添加点和法线
Point point;
point.position = position;
point.normal = normal;
pointCloud.push_back(point);
}
return pointCloud;
}
// 计算协方差矩阵的特征向量(简化版,实际应使用数值库)
Vector3 calculateNormalFromCovarianceMatrix(
float cxx, float cxy, float cxz, float cyy, float cyz, float czz) {
// 实际实现应该解决3x3特征值问题
// 此处返回一个随机法线代替
Vector3 randomNormal(
static_cast<float>(rand()) / RAND_MAX - 0.5f,
static_cast<float>(rand()) / RAND_MAX - 0.5f,
static_cast<float>(rand()) / RAND_MAX - 0.5f
);
return randomNormal.normalize();
}
10. 图形渲染管线
图形渲染管线是实时3D图形的核心处理流程。
10.1 简单的软件渲染器
cpp
class SoftwareRenderer {
private:
std::vector<unsigned char> colorBuffer; // 颜色缓冲区 (RGBA)
std::vector<float> depthBuffer; // 深度缓冲区
int width, height;
Matrix4x4 modelMatrix; // 模型变换
Matrix4x4 viewMatrix; // 视图变换
Matrix4x4 projectionMatrix; // 投影变换
public:
SoftwareRenderer(int width, int height)
: width(width), height(height) {
// 初始化颜色缓冲区
colorBuffer.resize(width * height * 4, 0);
// 初始化深度缓冲区(初始化为最远)
depthBuffer.resize(width * height, 1.0f);
// 初始化变换矩阵为单位矩阵
modelMatrix = Matrix4x4();
viewMatrix = Matrix4x4();
projectionMatrix = Matrix4x4();
}
// 设置变换矩阵
void setModelMatrix(const Matrix4x4& matrix) { modelMatrix = matrix; }
void setViewMatrix(const Matrix4x4& matrix) { viewMatrix = matrix; }
void setProjectionMatrix(const Matrix4x4& matrix) { projectionMatrix = matrix; }
// 清除缓冲区
void clear(const Vector3& color, float depth = 1.0f) {
for (int i = 0; i < width * height; i++) {
colorBuffer[i * 4] = static_cast<unsigned char>(color.x * 255);
colorBuffer[i * 4 + 1] = static_cast<unsigned char>(color.y * 255);
colorBuffer[i * 4 + 2] = static_cast<unsigned char>(color.z * 255);
colorBuffer[i * 4 + 3] = 255; // Alpha设为不透明
depthBuffer[i] = depth;
}
}
// 获取颜色缓冲区
const std::vector<unsigned char>& getColorBuffer() const { return colorBuffer; }
// 渲染三角形
void drawTriangle(const Vertex& v1, const Vertex& v2, const Vertex& v3,
const Material& material, const std::vector<Light>& lights) {
// 顶点着色:应用变换
Vertex tv1 = transformVertex(v1);
Vertex tv2 = transformVertex(v2);
Vertex tv3 = transformVertex(v3);
// 背面剔除(简化实现)
Vector3 e1 = tv2.position - tv1.position;
Vector3 e2 = tv3.position - tv1.position;
Vector3 normal = e1.cross(e2).normalize();
if (normal.z < 0) return; // 背面,不渲染
// 透视除法和视口变换
Vertex sv1 = perspectiveDivideAndViewportTransform(tv1);
Vertex sv2 = perspectiveDivideAndViewportTransform(tv2);
Vertex sv3 = perspectiveDivideAndViewportTransform(tv3);
// 计算包围盒
int minX = std::max(0, static_cast<int>(std::min(std::min(sv1.position.x, sv2.position.x), sv3.position.x)));
int minY = std::max(0, static_cast<int>(std::min(std::min(sv1.position.y, sv2.position.y), sv3.position.y)));
int maxX = std::min(width - 1, static_cast<int>(std::max(std::max(sv1.position.x, sv2.position.x), sv3.position.x)));
int maxY = std::min(height - 1, static_cast<int>(std::max(std::max(sv1.position.y, sv2.position.y), sv3.position.y)));
// 遍历包围盒中的每个像素
for (int y = minY; y <= maxY; y++) {
for (int x = minX; x <= maxX; x++) {
// 计算重心坐标
Vector3 bc = calculateBarycentricCoord(
Vector3(x, y, 0),
Vector3(sv1.position.x, sv1.position.y, 0),
Vector3(sv2.position.x, sv2.position.y, 0),
Vector3(sv3.position.x, sv3.position.y, 0)
);
// 如果点在三角形外,跳过
if (bc.x < 0 || bc.y < 0 || bc.z < 0) continue;
// 计算深度值
float depth = bc.x * sv1.position.z + bc.y * sv2.position.z + bc.z * sv3.position.z;
// 深度测试
int index = y * width + x;
if (depth < depthBuffer[index]) {
// 更新深度缓冲区
depthBuffer[index] = depth;
// 插值法线
Vector3 normal = (bc.x * sv1.normal + bc.y * sv2.normal + bc.z * sv3.normal).normalize();
// 插值纹理坐标
Vector2 texCoord = bc.x * sv1.texCoord + bc.y * sv2.texCoord + bc.z * sv3.texCoord;
// 计算片段位置
Vector3 worldPos = bc.x * v1.position + bc.y * v2.position + bc.z * v3.position;
// 视线方向
Vector3 viewDir = (Vector3(0, 0, 0) - worldPos).normalize(); // 假设相机在原点
// 片段着色:计算光照
Vector3 color(0, 0, 0);
for (const auto& light : lights) {
color = color + calculatePhongLighting(worldPos, normal, viewDir, material, light);
}
// 写入颜色缓冲区
colorBuffer[index * 4] = static_cast<unsigned char>(std::min(color.x * 255.0f, 255.0f));
colorBuffer[index * 4 + 1] = static_cast<unsigned char>(std::min(color.y * 255.0f, 255.0f));
colorBuffer[index * 4 + 2] = static_cast<unsigned char>(std::min(color.z * 255.0f, 255.0f));
colorBuffer[index * 4 + 3] = 255; // Alpha
}
}
}
}
private:
// 应用模型-视图-投影变换
Vertex transformVertex(const Vertex& vertex) {
Vertex transformed = vertex;
// 应用模型变换
transformed.position = modelMatrix.transformPoint(vertex.position);
transformed.normal = modelMatrix.transformDirection(vertex.normal).normalize();
// 应用视图变换
transformed.position = viewMatrix.transformPoint(transformed.position);
transformed.normal = viewMatrix.transformDirection(transformed.normal).normalize();
// 应用投影变换
transformed.position = projectionMatrix.transformPoint(transformed.position);
return transformed;
}
// 透视除法和视口变换
Vertex perspectiveDivideAndViewportTransform(const Vertex& vertex) {
Vertex result = vertex;
// 透视除法
float invW = 1.0f / vertex.position.z;
result.position.x *= invW;
result.position.y *= invW;
result.position.z *= invW;
// 视口变换
result.position.x = (result.position.x + 1.0f) * 0.5f * width;
result.position.y = (1.0f - result.position.y) * 0.5f * height;
return result;
}
// 计算重心坐标
Vector3 calculateBarycentricCoord(
const Vector3& p, const Vector3& a, const Vector3& b, const Vector3& c) {
Vector3 v0 = b - a;
Vector3 v1 = c - a;
Vector3 v2 = p - a;
float d00 = v0.dot(v0);
float d01 = v0.dot(v1);
float d11 = v1.dot(v1);
float d20 = v2.dot(v0);
float d21 = v2.dot(v1);
float denom = d00 * d11 - d01 * d01;
float v = (d11 * d20 - d01 * d21) / denom;
float w = (d00 * d21 - d01 * d20) / denom;
float u = 1.0f - v - w;
return Vector3(u, v, w);
}
};
以上代码示例涵盖了3D数学在计算机图形学、游戏开发、虚拟现实和物理模拟等领域的核心应用。实际项目中通常会使用更优化的算法和数据结构,并且会利用GPU加速来提高性能。这些基础概念和实现方法为理解和开发复杂的3D应用程序提供了坚实的基础。