原文: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. MyEclipse中修改servlet模板

    1.在MyEclipse目录下搜索com.genuitec.eclipse.wizards,得到搜索结果 com.genuitec.eclipse.wizards_8.4.100.me20091213 ...

  2. Linux 新建用户和组命令

    用户的角色是通过UID和GID识别的. UID用户ID:相当于各为的身份证,在系统中是唯一的 GID组ID:相当于各为的家庭或者你们的学校. 1.新建用户及设置密码命令如下: useradd [参数] ...

  3. 铁乐学python_Day44_IO多路复用

    目录 IO模型介绍 阻塞IO(blocking IO) 非阻塞IO(non-blocking IO) 多路复用IO(IO multiplexing) 异步IO(Asynchronous I/O) IO ...

  4. [MongoDB]------windos远程服务器部署连接

    1.连接前的准备 这里就省略了服务器上安装的操作,跟上一节是一样的流程. 连接到远程服务器,首先需要到远程服务器上在mongoDb安装根目录下的bin文件夹(默认安装目录是C:\Program Fil ...

  5. java Calendar日历类

    ~Calendar类是一个抽象类,为特定瞬间与一组诸如YEAR,MONTH,DAY_OF_MONTH,HOUR等日历字段之间的转换提供了一些方 法,并为操作日历字段(例如获得下星期的日期)提供了一些方 ...

  6. Hibernate入门步骤及概念

    1.什么是Hibernate Hibernate是一个开发源代码的对象关系映射框架,它对JDBC进行非常轻量级的对象封装,使得程序员可以随心所欲地使用对象编程思维来操纵数据库.Hibernate可以应 ...

  7. 【收集】Python 微优化

    1. 第二种方式可以节省寻找result的append属性的时间, 但会降低代码可读性和可维护性 # The way we're used to seeing it: result.append(&q ...

  8. Java并发:Executor与连接池

    概述 首先来说一说java连接池中常用到的几个类:Executor,ExecutorService,ScheduledExecutorService Executor 执行已经提交的任务对象.此接口提 ...

  9. jdk1.5-jdk1.9的主要区别

    jdk1.5相对以前jdk版本主要新增功能 1.自动拆箱和装箱 其中基本数据类型的包装类有:Double,Float,Long,Integer,Short,Character和Boolean 2.提供 ...

  10. 函数的类型:函数也是类型 (*)->*

    函数的类型:函数也是类型 (*)->* 函数类型作为类型可以定义变量,使得函数变量具有可替代性,这个是高阶函数的编程基础. 使用函数的类型可以定义函数的变量,并用函数给这个变量赋值: 每一个函数 ...