OpenGL 屏幕坐标系与世界坐标系的相互转换

11 篇文章 2 订阅

直接根据通过“模型视图投影”矩阵的变换得到:


/**
 * @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);
}
  • 7
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值