透视投影矩阵乘法实现求助:3D顶点转屏幕坐标问题
问题解决步骤
一、先修正代码里的基础错误
1. 修复Object3D的成员定义
当前vertices和triangles是单个float类型,无法存储顶点与三角形数据,改成合适的容器:
#include <vector> #include <array> struct Object3D { // 存储8个4维齐次坐标顶点 std::vector<std::array<float, 4>> vertices = { {1.0f, 1.0f, -1.0f, 1.0f}, {1.0f, -1.0f, -1.0f, 1.0f}, {1.0f, 1.0f, 1.0f, 1.0f}, {1.0f, -1.0f, 1.0f, 1.0f}, {-1.0f, 1.0f, -1.0f, 1.0f}, {-1.0f, -1.0f, -1.0f, 1.0f}, {-1.0f, 1.0f, 1.0f, 1.0f}, {-1.0f, -1.0f, 1.0f, 1.0f} }; // 存储12个三角形的顶点索引 std::vector<std::array<int, 3>> triangles = { {4, 2, 0}, {2, 7, 3}, {6, 5, 7}, {1, 7, 5}, {0, 3, 1}, {4, 1, 5}, {4, 6, 2}, {2, 6, 7}, {6, 4, 5}, {1, 3, 7}, {0, 2, 3}, {4, 0, 1} }; };
2. 修复PerspectiveProjectionCamera的init函数
init里重新定义了局部矩阵,导致类成员变量未初始化,直接给成员赋值即可:
class PerspectiveProjectionCamera { public: float projectionMatrix[4][4]; float toScreenMatrix[4][4]; float position[3] = {0.0f, 0.0f, 5.0f}; // 默认相机位置 int hwidth; int hheight; void init(float nearplane, float farplane, int width, int height, float FOV) { float aspectRatio = static_cast<float>(height) / width; float vFOV = FOV * aspectRatio; float right = tanf(FOV / 2.0f); float left = -right; float top = tanf(vFOV / 2.0f); float bottom = -top; hwidth = width / 2; hheight = height / 2; float m00 = 2.0f / (right - left); float m11 = 2.0f / (top - bottom); float m22 = (farplane + nearplane) / (farplane - nearplane); float m32 = -2.0f * nearplane * farplane / (farplane - nearplane); // 直接赋值给类成员矩阵 projectionMatrix[0][0] = m00; projectionMatrix[0][1] = 0.0f; projectionMatrix[0][2] = 0.0f; projectionMatrix[0][3] = 0.0f; projectionMatrix[1][0] = 0.0f; projectionMatrix[1][1] = m11; projectionMatrix[1][2] = 0.0f; projectionMatrix[1][3] = 0.0f; projectionMatrix[2][0] = 0.0f; projectionMatrix[2][1] = 0.0f; projectionMatrix[2][2] = m22; projectionMatrix[2][3] = 1.0f; projectionMatrix[3][0] = 0.0f; projectionMatrix[3][1] = 0.0f; projectionMatrix[3][2] = m32; projectionMatrix[3][3] = 0.0f; toScreenMatrix[0][0] = static_cast<float>(width) / 2.0f; toScreenMatrix[0][1] = 0.0f; toScreenMatrix[0][2] = 0.0f; toScreenMatrix[0][3] = 0.0f; toScreenMatrix[1][0] = 0.0f; toScreenMatrix[1][1] = -static_cast<float>(height) / 2.0f; toScreenMatrix[1][2] = 0.0f; toScreenMatrix[1][3] = 0.0f; toScreenMatrix[2][0] = 0.0f; toScreenMatrix[2][1] = 0.0f; toScreenMatrix[2][2] = 1.0f; toScreenMatrix[2][3] = 0.0f; toScreenMatrix[3][0] = static_cast<float>(width) / 2.0f; toScreenMatrix[3][1] = static_cast<float>(height) / 2.0f; toScreenMatrix[3][2] = 0.0f; toScreenMatrix[3][3] = 1.0f; } private: // 改用float减少计算开销 float vFOV; float right; float left; float top; float bottom; float m00; float m11; float m22; float m32; };
二、正确的顶点变换流程与矩阵乘法实现
1. 变换顺序纠正
原公式projectionMatrix * (vertices+cameraPos) * toScreenMatrix完全错误,正确的3D顶点到屏幕坐标变换顺序为:
世界空间顶点 → 视图空间(顶点 - 相机位置)→ 裁剪空间(投影矩阵乘视图顶点)→ 归一化设备坐标(透视除法)→ 屏幕坐标(屏幕矩阵乘NDC顶点)
顶点用列向量时,矩阵乘法顺序为从右到左:屏幕坐标 = toScreenMatrix * projectionMatrix * 视图空间顶点(透视除法需放在投影之后、屏幕矩阵之前)
2. 矩阵乘4维向量的实现
用inline函数减少调用开销:
// 4x4矩阵乘4维列向量,结果存入out inline void multiplyMatrixVector(const float mat[4][4], const float vec[4], float out[4]) { out[0] = mat[0][0] * vec[0] + mat[0][1] * vec[1] + mat[0][2] * vec[2] + mat[0][3] * vec[3]; out[1] = mat[1][0] * vec[0] + mat[1][1] * vec[1] + mat[1][2] * vec[2] + mat[1][3] * vec[3]; out[2] = mat[2][0] * vec[0] + mat[2][1] * vec[1] + mat[2][2] * vec[2] + mat[2][3] * vec[3]; out[3] = mat[3][0] * vec[0] + mat[3][1] * vec[1] + mat[3][2] * vec[2] + mat[3][3] * vec[3]; }
3. 完整顶点变换代码
// 输入世界空间顶点与相机,输出屏幕坐标(x,y) void transformVertexToScreen(const float worldVertex[4], const PerspectiveProjectionCamera& cam, float& screenX, float& screenY) { // 1. 转换到视图空间:世界顶点 - 相机位置,保持w=1 float viewVertex[4]; viewVertex[0] = worldVertex[0] - cam.position[0]; viewVertex[1] = worldVertex[1] - cam.position[1]; viewVertex[2] = worldVertex[2] - cam.position[2]; viewVertex[3] = 1.0f; // 2. 投影矩阵变换,得到裁剪空间坐标 float clipVertex[4]; multiplyMatrixVector(cam.projectionMatrix, viewVertex, clipVertex); // 3. 透视除法,得到NDC坐标 if (clipVertex[3] == 0.0f) clipVertex[3] = 1e-6f; // 避免除以0 float ndcVertex[4]; ndcVertex[0] = clipVertex[0] / clipVertex[3]; ndcVertex[1] = clipVertex[1] / clipVertex[3]; ndcVertex[2] = clipVertex[2] / clipVertex[3]; ndcVertex[3] = 1.0f; // 4. 屏幕矩阵变换,得到最终屏幕坐标 float screenVertex[4]; multiplyMatrixVector(cam.toScreenMatrix, ndcVertex, screenVertex); screenX = screenVertex[0]; screenY = screenVertex[1]; }
三、十万三角形的性能优化建议
- 减少内存访问开销:将Object3D的vertices和triangles改为连续数组(如
float vertices[8*4];),比vector内存访问更高效;或提前预分配vector容量。 - 循环内复用变量:遍历三角形时,提前声明临时变量并复用,避免频繁创建销毁。
- SIMD指令优化:使用x86的SSE/AVX指令集批量处理顶点,一次处理2-4个顶点的变换,大幅提升计算速度。
- 多线程并行处理:将三角形分成多个批次,用std::thread或OpenMP分配给多核CPU并行处理,充分利用硬件资源。
- 提前剔除不可见三角形:变换前做视锥体剔除,跳过完全在相机视锥体外部的三角形,减少无效计算。
内容的提问来源于stack exchange,提问作者user21956960
相关产品推荐
相关产品推荐

