直接根据通过“模型视图投影”矩阵的变换得到:
/**
* @brief 窗口坐标转化为世界坐标
* @brief screenPoint 窗口坐标点
* @brief viewportRange 视口范围。 各个值依次为:左上-右下
* @brief modelViewMatrix 模型视图矩阵
* @brief projectMatrix 投影矩阵
* @brief pPointDepth 屏幕点的深度,如果不指定(为nullptr),从深度缓冲区中读取深度值
* @return 世界坐标系
* @note 注意:得到的世界坐标系在使用前要除以齐次坐标值w,
* 如果w是0,则不应使用此点。
* @code
* // sample
* ...
* auto&& worldPoint = Screen2World(...);
* if( !FuzzyIsZero( worldPoint.w ) )
* {
* glm::vec3 world3D(worldPoint);
* world3D /= worldPoint;
* /// using world3D
* }
* else
* {
* // error handler
* }
*/
glm::vec4 Screen2World(
const glm::vec2i& screenPoint,
const glm::vec4& viewportRange,
const glm::mat4& modelMatrix,
const glm::mat4& projectMatrix,
float* pPointDepth = nullptr)
{
GLfloat pointDepth(0.0f);
if( nullptr != pPointDepth )
{
pointDepth = *pPointDepth;
}
else
{
// 获取深度缓冲区中x,y的数值
glReadPixels( screenPoint.x, screenPoint.y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &pointDepth );
}
#if defined(USING_GLM_ALGORITHM)
/// 直接使用glm的"逆投影"
return glm::vec4( glm::unProject(glm::vec3(screenPoint,pointDepth),modelMatrix,projectMatrix,viewportRange);
#else
// 转换为标准设备坐标系
glm::vec4 devicePoint(
LinearMap(screenPoint.x,viewportRange.x,viewportRange.z,-1.0f,1.0f),
LinearMap(screenPoint.y,viewportRange.y,viewportRange.w,1.0f,-1.0f),
pointDepth, 1 );
// 按照opengl管线工作方式,转换到世界坐标系
return glm::inverse( projectMatrix * modelMatrix ) * devicePoint;
#endif
}
/**
* @brief 世界坐标系转换为屏幕坐标系
* @brief worldPoint 世界坐标的点坐标点
* @brief viewportRange 视口范围。 各个值依次为:左上-右下
* @brief modelViewMatrix 模型视图矩阵
* @brief projectMatrix 投影矩阵
* @brief pPointDepth 屏幕点的深度,如果不指定(为nullptr),从深度缓冲区中读取深度值
* @return 窗口坐标点
* @note 返回的窗口坐标带深度值,如果仅适用2D窗口像素坐标点,仅适用它的x,y维即可
*/
glm::vec3 World2Screen(
const glm::vec3& worldPoint,
const glm::vec4& viewportRange,
const glm::mat4& modelViewMatrix,
const glm::mat4& projectMatrix )
{
#if defined(USING_GLM_ALGORITHM)
/// 直接使用glm的"投影"
return glm::project( worldPoint, modelViewMatrix, projectMatrix, viewportRange );
#else
/// 将世界坐标转换为设备坐标
const auto& resultPoint = projectMatrix * modelViewMatrix * glm::vec4( worldPoint, 1.0f);
if( FuzzyIsZero( resultPoint.w) )
{
// 齐次坐标是0,错误
LOG_ERROR("w is zero!");
/// 其他错误处理
return glm::vec3();
}
glm::vec3 returnPoint(resultPoint);
returnPoint /= resultPoint.w;
returnPoint.x = LinearMap(returnPoint.x,-1.0f,1.0f,viewportRange.x,viewportRange.z );
returnPoint.y = LinearMap(returnPoint.y,1.0f,-1.0f,viewPortRange.y,viewPortRange.w );
return returnPoint;
#endif
}
用到的工具函数
inline constexpr float FLOAT_PRECISION_EPSILON()
{
return 1.0e-6f;
}
inline bool FuzzyIsZero( float f )
{
return std::abs( f ) < FLOAT_PRECISION_EPSILON( );
}
/**
* @brief 一维线性映射函数,将[a,b]中的点x,映射到[a1,b1]中的点x1
*
**/
float LinearMap(float x, float a, float b, float a1,float b1 )
{
auto srcDelt(b - a);
if( FuzzyIsZero( srcDelt ) )
{
// 原始范围时0,
LOG_WARN("原始范围不能为0(%f,%f)",a,b);
return a1;
}
return (b1-a1) / srcDelt * ( x-a ) + a1;
}
*其它方法
如果使用了glu库(转自nehe的教程:Using gluUnProject NeHe Productions: Using gluUnProject)
CVector3 GetOGLPos(int x, int y)
{
GLint viewport[4];
GLdouble modelview[16];
GLdouble projection[16];
GLfloat winX, winY, winZ;
GLdouble posX, posY, posZ;
glGetDoublev( GL_MODELVIEW_MATRIX, modelview );
glGetDoublev( GL_PROJECTION_MATRIX, projection );
glGetIntegerv( GL_VIEWPORT, viewport );
//转换成NDC坐标,这里的视口==窗口,如果是窗口的一部分,注意调整。
winX = (float)x;
winY = (float)viewport[3] - (float)y;
glReadPixels( x, int(winY), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ ); //获取深度值
// 窗口-->世界gluUnProject;世界-->窗口:gluProject
gluUnProject( winX, winY, winZ, modelview, projection, viewport, &posX, &posY, &posZ); //使用glu,如果高版本的opengl,3D数学库中通常包含此函数,
return CVector3(posX, posY, posZ);
}