Games101中讲到渲染过程中往往会使用透视投影,产生一种“近大远小”的更符合视觉习惯的效果。这篇文章即应用透视矩阵写一个demo,加深理解。
一样的,demo还是使用C实现,使用OpenCV作为辅助以及显示
1、首先、透视矩阵的计算结果如下,具体推导过程可查看Games101系列的课程
double perspMatrix[16] = { 0 };
double n = 300;
double f = 800;
perspMatrix[0] = n;
perspMatrix[5] = n;
perspMatrix[10] = n + f;
perspMatrix[11] = -n * f;
perspMatrix[14] = 1;
2、demo中画了9个排列正确的长方体,代码如下
// 定义需要展示的数据 -- 1个3D长方体
const double w = 60, l = 80, h = 500;
const double centerPos[3] = { 250, 250, 500 };
const int yCnt = 3, xCnt = 3;
double pts[8][3] = // 定义原始的点坐标
{
{-w / 2, -l / 2, -h / 2},
{w / 2, -l / 2, -h / 2},
{w / 2, l / 2, -h / 2},
{-w / 2, l / 2, -h / 2},
{-w / 2, -l / 2, h / 2},
{w / 2, -l / 2, h / 2},
{w / 2, l / 2, h / 2},
{-w / 2, l / 2, h / 2},
//
};
int line[12][2] = // 定义线及点的关系
{
{0, 1},
{1, 2},
{2, 3},
{3, 0},
{4, 5},
{5, 6},
{6, 7},
{7, 4},
{0, 4},
{1, 5},
{2, 6},
{3, 7}
};
unsigned char lineColor[12][3] =
{
{0, 0, 255},
{0, 0, 255},
{0, 0, 255},
{0, 0, 255},
{0, 255, 0},
{0, 255, 0},
{0, 255, 0},
{0, 255, 0},
{255, 0, 0},
{255, 0, 0},
{255, 0, 0},
{255, 0, 0}
};
//====================================
// 定义yCnt x xCnt个长方体
double ptDatas[yCnt * xCnt][8][3];
for (int yy = 0; yy < yCnt; ++yy)
{
for (int xx = 0; xx < xCnt; ++xx)
{
for (int i = 0; i < 8; ++i)
{
ptDatas[yy * xCnt + xx][i][0] = pts[i][0] + (-xCnt + 1 + xx * 2) * w;
ptDatas[yy * xCnt + xx][i][1] = pts[i][1] + (-yCnt + 1 + yy * 2) * l;
ptDatas[yy * xCnt + xx][i][2] = pts[i][2];
// 根据旋转矩阵,重新计算点的坐标
// TransPoint(ptDatas[yy * xCnt + xx][i], rotateM, ptDatas[yy * xCnt + xx][i]);
}
}
}
3、画出这9个长方体
unsigned char bkColor[3] = { 212, 148, 0};
_mainMatImg =
cv::Mat(600, 800, CV_8UC3,
cv::Scalar(bkColor[0], bkColor[1], bkColor[2]));
//
bool ifPerspective = true; // 是否透视投影,否则即为正交(Orthographic)投影
for (int yy = 0; yy < yCnt; ++yy)
{
for (int xx = 0; xx < xCnt; ++xx)
{
if (ifPerspective) // 如果使用透视投影
{
// 将长方体的8个顶点运用透视矩阵算出新的坐标
for (int i = 0; i < 8; ++i)
{
TransPoint(ptDatas[yy * xCnt + xx][i], perspMatrix, ptDatas[yy * xCnt + xx][i]);
}
}
for (int i = 0; i < 12; ++i)
{
cv::line(_mainMatImg,
cv::Point(ptDatas[yy * xCnt + xx][line[i][0]][0] + _mainMatImg.cols / 2, ptDatas[yy * xCnt + xx][line[i][0]][1] + _mainMatImg.rows / 2),
cv::Point(ptDatas[yy * xCnt + xx][line[i][1]][0] + _mainMatImg.cols / 2, ptDatas[yy * xCnt + xx][line[i][1]][1] + _mainMatImg.rows / 2),
cv::Scalar(lineColor[i][0], lineColor[i][1], lineColor[i][2], 255)
);
}
}
}
绘制结果如下,可以看到长方体距离观察更远的平面被缩小了
以下是不是用透视投影(即平行投影)的结果,因为没有近大远小的效果,所以前后两个面互相遮挡,只能看到9个矩形(长方体的正面)
可以将其进行旋转(具体怎么对点进行旋转,后续有机会再写一篇文章),效果更为立体一点,对如下
后记:
以下是将透视变换应用于更复杂的模型中的效果对比,可以看到这个人的脚远离观察者,右边就有近大远小的视觉效果,而左边的如果细看的话,会觉得脚显得有点太大,有点别扭
最后,感谢闫令琪和Games101