(本文是LearnOpenGL的学习笔记, 教程中文翻译地址https://learnopengl-cn.github.io/(备用地址https://learnopengl-cn.readthedocs.io/zh/latest/),写于 2022-04-04)
0.前言
在之前的图形绘制操作中,都是将模型顶点坐标转换为屏幕坐标进行显示。但在进行如人机交互时,可能需要将屏幕坐标转换到世界坐标,比如点击物体进行选中,如果采用射线拾取法,就需要将屏幕坐标系的点转到世界坐标系去。
1.知识点
先复习下顶点的坐标转换流程,图1来自LearnOpenGL,图2来自OpenGL编程指南
顶点坐标通过模型矩阵、视图矩阵、投影矩阵转换到裁剪空间。 转换到裁剪空间后经过GPU的裁剪处理就可以得到最终需要被渲染的物体顶点坐标了,接下来GPU会做透视除法(xyz都除以w)将顶点转换到标准化设备坐标(Normalized Device Coordinate, NDC)中,透视除法将裁剪空间中顶点的4个分量都除以w分量,就从裁剪空间转换到了NDC了,NDC是一个长宽高取值范围为[-1,1]的立方体。将顶点坐标都转换到NDC后,下一步就是屏幕映射,这个就取决于最终要渲染到的窗体分辨率。
现在要做的就是将上面的步骤反过来走一遍。
先是将屏幕坐标转为NDC坐标:
因为屏幕只有一个xy平面,所以z值任取[-1,1]一点。(两个不同z值的点连成一条线,和场景中的物体做相交检测后,可以获取到离屏幕最近的一个面的z值)
NDC到裁剪空间,加一个为1的w分量作为齐次坐标。
剩下就是裁剪坐标到世界坐标,直接用mvp转换矩阵的逆矩阵计算。
2.代码实现
github链接(MyRayPick类):https://github.com/gongjianbo/OpenGLwithQtWidgets
float ndc_x = 2.0f * pos.x() / width() - 1.0f;
float ndc_y = 1.0f - (2.0f * pos.y() / height());
float ndc_z = 1.0f;
//view视图矩阵和projection投影矩阵取渲染时的值
QVector3D ndc_ray = QVector3D(ndc_x, ndc_y, ndc_z);
QVector4D clip_ray = QVector4D(ndc_ray, 1.0f);
QVector4D eye_ray = projection.inverted() * clip_ray;
QVector4D world_ray = view.inverted() * eye_ray;
//转笛卡尔坐标
if(world_ray.w() != 0.0f){
world_ray /= world_ray.w();
}
3.参考
LearnOpenGL:坐标系统 - LearnOpenGL CN
书籍:《OpenGL编程指南》(原书第九版)第五章