原文:http://blog.csdn.net/bluekitty/article/details/6070828

3D应用程序中,我们可以通过鼠标进行空间中物体的旋转和视角的变换等,而鼠标的移动是2D的(只有x,y坐标的变化),鼠标的这个2D移动是如何反映到3D空间中的旋转呢?这就要进行2D坐标和3D坐标的转换。

DXUT的CD3DArcBall类有一个成员函数ScreenToVector负责这个转换,下面是这个函数的实现:

D3DXVECTOR3 CD3DArcBall::ScreenToVector( float fScreenPtX, float fScreenPtY )
{
// Scale to screen
FLOAT x = -( fScreenPtX - m_Offset.x - m_nWidth / ) / ( m_fRadius * m_nWidth / );
FLOAT y = ( fScreenPtY - m_Offset.y - m_nHeight / ) / ( m_fRadius * m_nHeight / ); FLOAT z = 0.0f;
FLOAT mag = x * x + y * y; if( mag > 1.0f )
{
FLOAT scale = 1.0f / sqrtf( mag );
x *= scale;
y *= scale;
}
else
z = sqrtf( 1.0f - mag ); // Return vector
return D3DXVECTOR3( x, y, z );
}

变量说明:

m_Offset:POINT类型,D3D视口起始的偏移量,一般D3D视口都是独占并铺满一个窗口,所以一般为(0,0)。

m_fRadius:球的半径,这个半径一般为1。

m_nWidth,m_nHeight:视口的宽高,没有偏移的话,就是窗口的ClientWidth和ClientHeight。

这里简单的介绍一下ArcBall,想象有一个位于空间原点的球体,这个球的半径是1,它被x轴所在的纵向平面一切2半,一半位于z轴的正半轴,另一半在负半轴,在z正半轴的那个半球就是ArcBall(注意D3D是左手坐标系)。之所以定义半径为1,就是使所有位于这个球面上的向量的模都是1,是归一化的向量,便于后续计算,所以这个球也可以叫做归一化的球(实际可能没这个叫法,但就是这个意思)。

回到ScreenToVector这个函数,它实际是把屏幕平面坐标fScreenPtX 和fScreenPtY投影到这个球上以完成2D到3D的转换(逆投影),它的2个参数是float fScreenPtX, float fScreenPtY,很好理解,就是转化为float的屏幕坐标。前两行代码:

FLOAT x = -( fScreenPtX - m_Offset.x - m_nWidth / 2 ) / ( m_fRadius * m_nWidth / 2 );
    FLOAT y = ( fScreenPtY - m_Offset.y - m_nHeight / 2 ) / ( m_fRadius * m_nHeight / 2 );

对于一般情况(没有偏移,球是归一化的球),这两行代码可以简化成这样,

FLOAT x = -( fScreenPtX- m_nWidth / 2 ) / ( m_nWidth / 2 );
    FLOAT y = ( fScreenPtY- m_nHeight / 2 ) / ( m_nHeight / 2 );

这两行代码乍一看不是很好理解,可以看出x和y的范围是-1到1,这有什么意义?记得刚才说的那个球被x轴所在的纵向平面一切为2么,实际上就是把那个切面上的圆的一个内接矩形当做你的屏幕了,如下图(图画的比较烂,稍微有点走样)

红色代表切面的那个圆,蓝色表示你的屏幕,屏幕的中心(实际是窗口的中心,这里假定是全屏显示的)和空间原点是重合的,那两行代码所求出的x,y值就是蓝色区域(也可以说是红色圆区域内,为什么后面有说明)内的点的坐标。当屏幕坐标fScreenPtX和fScreenPtY增大的时候,投影到空间坐标中那个蓝色矩形的点的坐标是如何变化的呢?应该的情况是x随fScreenPtX增大而增大,GDI坐标的y轴方向和空间坐标的y轴方向是相反的,所以y随fScreenPtY的增大而减小,但求的值正好相反,x是减小的(第一行代码将结果加了负号),而y是增大的,最终的结果接就是,当屏幕上的点向右下移动的时候,转换的空间坐标的点实际是在向左上移动。这和观察变换一摸一样(观察变换是世界变换的逆向变换),所以这个球用来模拟摄像机的位置非常合适。

继续往下看,这段代码

FLOAT z = 0.0f;
FLOAT mag = x * x + y * y; if( mag > 1.0f )
{
FLOAT scale = 1.0f / sqrtf( mag );
x *= scale;
y *= scale;
}
else
z = sqrtf( 1.0f - mag );

很明显是在求z值,但首先必须对x和y进行修正,必须进行修正的原因就是向量模的定义:向量的模代表了向量的长度,它的值是根号(x*x+y*y+z*z),即|v|^2=x^2+y^2+z^2,这个公式很容易用勾股定律推出来。因为我们求的向量终点都在那个球面上,所以v的模在这里就是m_fRadius也就是1,即x*x+y*y+z*z=1,很明显x*x+y*y不能大于1,代码中用变量mag存贮x*x+y*y,当mag>1的时候就必须修正xy的值使它们的平方和等于1,这时z=0,当mag<1的时候,z = sqrtf( 1.0f - mag )。最后函数返回一个对应平面坐标的向量。

现在回过头来整个再看一遍代码就可以用另外一种方式来解释这个变换,可以通过将2D的点逆投影到3D球体的表面来进行变换,窗口是2D矩形,它被放入空间后变形为一个圆形,窗口上的每一个点都被转化成这个圆范围内的点,这里是有变形的,例如,根据上面的计算,窗口左上角对应的空间坐标是(0.707,-0.707,0)(注意是空间坐标的反方向,值是根号2除以2),那我要问你窗口左边中点对应空间坐标的x坐标是什么?用GDI的方式考虑,因为x方向上没有变化,只是点垂直向下移动了,所以横坐标没有变化,但对应空间坐标的x是0.707么?试一试就知道了。

【转载】屏幕坐标向3维坐标的转化-DXUT的CD3DArcBall类的更多相关文章

  1. OpenGL屏幕二维坐标转化成三维模型坐标

    我们把OpenGL里模型的三维坐标往二维坐标的转化称为投影,则屏幕上的二维坐标往三维坐标转化则可以称为反投影,下面我们来介绍一下反投影的方法. 主要是gluUnProject函数的使用,下面是代码: ...

  2. Win窗口坐标二维坐标与OpenGl的世界坐标系的之间的相互转换

    Win窗口坐标二维坐标与OpenGl的世界坐标系的转换 几何处理管线擅长于使用视图和投影矩阵以及用于裁剪的视口把顶点的世界坐标变换为窗口坐标. 但是,在有些情况下,需要逆转这个过程.一种常见的情形是: ...

  3. 二维坐标的平移,旋转,缩放及matlab实现

    本文结合matlab 软件解释二维坐标系下的平移,旋转,缩放 首先确定点在二维坐标系下的表达方法,使用一个1*3矩阵: Pt = [x,y,1] 其中x,y 分别为点的X,Y坐标,1为对二维坐标的三维 ...

  4. UVALive 5102 Fermat Point in Quadrangle 极角排序+找距离二维坐标4个点近期的点

    题目链接:点击打开链接 题意: 给定二维坐标上的4个点 问: 找一个点使得这个点距离4个点的距离和最小 输出距离和. 思路: 若4个点不是凸4边形.则一定是端点最优. 否则就是2条对角线的交点最优,能 ...

  5. 白鹭引擎 - 本地坐标和舞台坐标的转化 ( globalToLocal, localToGlobal )

    class Main extends egret.DisplayObjectContainer { /** * Main 类构造器, 初始化的时候自动执行, ( 子类的构造函数必须调用父类的构造函数 ...

  6. 【转载】ArcBall二维控制三维旋转

    原文:http://oviliazhang.diandian.com/post/2012-05-19/40027878859 由于目前大多的显示器是二维的,要控制三维物体的旋转就显得不那么直接了.Ar ...

  7. OpenGL 获取当前屏幕坐标对应的三维坐标

    转自原文 OpenGL 获取当前屏幕坐标对应的三维坐标,使用很简单glu库中的一个函数 #include <GL/glut.h> #include <stdlib.h> #in ...

  8. 用C#控制台编写 推箱子之类的 坐标移动----之二维坐标

     //首先用枚举 列出方向  上,下,左,右(枚举的最后一位数后不用符号  否则会报错)        public enum dro    {       up = 1,        down = ...

  9. 屏幕坐标点转UGUI坐标【包含屏幕适配】

    using UnityEngine; public class ScreenToUI : MonoBehaviour { public const float UI_Width = 1366f; pu ...

随机推荐

  1. 使用eclipse遇到的unable to install breakpoint的问题

    调试一个tomcat工程,设置好断点,启动工程,结果出现了下面的错误: 继续运行,再进入断点之前,还会再度提示,但是最终会命中断点. 使用CGLIB查找关键字,了解到CGLIB是一个AOP的拦截库,想 ...

  2. C++ 的继承与虚函数 读书笔记

    一.类与类之间关系: 1.类与类之间可能会存在共性. 2.类与类之间必定会有差异. 3.为也节约开发时间和代码量,我们在设计类时可以把类的共享抽象出来形成一个基础类(基类). 4.使用基类+差异生成一 ...

  3. spring boot 在jdk 1.7下使用 commandLineRunner

    在spring boot 中有一段代码,使用的是java 1.8的语法: @Bean public CommandLineRunner commandLineRunner(ApplicationCon ...

  4. [UI] 精美UI界面欣赏[11]

    精美UI界面欣赏[11]

  5. Linux chkconfig命令详解

    chkconfig命令检查.设置系统的各种服务.这是Red Hat公司遵循GPL规则所开发的程序,它可查询操作系统在每一个执行等级中会执行哪些系统服务,其中包括各类常驻服务.谨记chkconfig不是 ...

  6. oracle PL/SQL调用Java生成Excel

    现在有个需求,  要求编写oracle存储过程生成Excel文件到指定目录,  但是oracle自己的API貌似不太给力,  所以只能通过另一种更强大的语言来实现了  ——Java.有一个Java框架 ...

  7. Centos7 apache2.4.29(httpd) 安装

    重点参考文章:https://blog.csdn.net/MrDing991124/article/details/78829184  写的很详细了,自己按着改博文走了不遍,不错! 一.配置安装环境 ...

  8. YBB.DBUtils用法

    通用数据访问类库,兼容ADO.ADO.NET方式访问. 利用ProviderFactory工厂方法,支持Oracle(不需要安装客户端).SQL Server.OleDb和ODBC等数据库访问. ht ...

  9. java Date日期类和SimpleDateFormat日期类格式

    ~Date表示特定的时间,精确到毫秒~构造方法:public Date()//构造Date对象并初始化为当前系统的时间public Date(long date) //1970-1-1 0:0:0到指 ...

  10. 解决hibernate双向关系造成的一方重复执行SQl,或者死循环的问题

    系统采用struts-json hibernate3. 在对关联表配置manytoone onetomany双向关联的时候,在执行一方的时候,会发现打印出来的SQL语句多执行了一次或者多次.经过调试, ...