【转载】屏幕坐标向3维坐标的转化-DXUT的CD3DArcBall类
原文: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类的更多相关文章
- OpenGL屏幕二维坐标转化成三维模型坐标
我们把OpenGL里模型的三维坐标往二维坐标的转化称为投影,则屏幕上的二维坐标往三维坐标转化则可以称为反投影,下面我们来介绍一下反投影的方法. 主要是gluUnProject函数的使用,下面是代码: ...
- Win窗口坐标二维坐标与OpenGl的世界坐标系的之间的相互转换
Win窗口坐标二维坐标与OpenGl的世界坐标系的转换 几何处理管线擅长于使用视图和投影矩阵以及用于裁剪的视口把顶点的世界坐标变换为窗口坐标. 但是,在有些情况下,需要逆转这个过程.一种常见的情形是: ...
- 二维坐标的平移,旋转,缩放及matlab实现
本文结合matlab 软件解释二维坐标系下的平移,旋转,缩放 首先确定点在二维坐标系下的表达方法,使用一个1*3矩阵: Pt = [x,y,1] 其中x,y 分别为点的X,Y坐标,1为对二维坐标的三维 ...
- UVALive 5102 Fermat Point in Quadrangle 极角排序+找距离二维坐标4个点近期的点
题目链接:点击打开链接 题意: 给定二维坐标上的4个点 问: 找一个点使得这个点距离4个点的距离和最小 输出距离和. 思路: 若4个点不是凸4边形.则一定是端点最优. 否则就是2条对角线的交点最优,能 ...
- 白鹭引擎 - 本地坐标和舞台坐标的转化 ( globalToLocal, localToGlobal )
class Main extends egret.DisplayObjectContainer { /** * Main 类构造器, 初始化的时候自动执行, ( 子类的构造函数必须调用父类的构造函数 ...
- 【转载】ArcBall二维控制三维旋转
原文:http://oviliazhang.diandian.com/post/2012-05-19/40027878859 由于目前大多的显示器是二维的,要控制三维物体的旋转就显得不那么直接了.Ar ...
- OpenGL 获取当前屏幕坐标对应的三维坐标
转自原文 OpenGL 获取当前屏幕坐标对应的三维坐标,使用很简单glu库中的一个函数 #include <GL/glut.h> #include <stdlib.h> #in ...
- 用C#控制台编写 推箱子之类的 坐标移动----之二维坐标
//首先用枚举 列出方向 上,下,左,右(枚举的最后一位数后不用符号 否则会报错) public enum dro { up = 1, down = ...
- 屏幕坐标点转UGUI坐标【包含屏幕适配】
using UnityEngine; public class ScreenToUI : MonoBehaviour { public const float UI_Width = 1366f; pu ...
随机推荐
- 能力成熟度模型(CMM)
能力等级 特点 关键过程 第一级 基本级 软件过程是混乱无序的,对过程几乎没有定义,成功依靠的是个人的才能和经验,管理方式属于反应式 第二级 重复级 建立了基本的项目管理来跟踪进度.费用和功能特征 ...
- 使用vue做项目
使用vue做项目需要用到node.js的npm来管理包 所以我们需要先下载node.js然后通过node的npm来管理包 安装完 nodejs后 我们需要执行 npm install vue-cli ...
- RDMBorderedButton
RDMBorderedButton https://github.com/reesemclean/RDMBorderedButton 效果: 源码: RDMBorderedButton.h + RDM ...
- [C++] 用Xcode来写C++程序[6] Name visibility
用Xcode来写C++程序[6] Name visibility 此小结包括了命名空间的一些使用细节 命名空间 #include <iostream> using namespace st ...
- linux 创建新用户并增加管理员权限
1.adduser与useradd有什么区别?2.那种方式会自动创建组.用户组等信息? 3.如何新建用户具有管理员权限? $是普通管员,#是系统管理员,root用户默认是没有密码的,因此也就无法使用( ...
- Celery学习--- Celery操作之定时任务
celery支持定时任务,设定好任务的执行时间,celery就会定时自动帮你执行, 这个定时任务模块叫celery beat 文件定时执行任务 项目前提: 安装并启动Redis celery_Sche ...
- mysqlDOS命令
MySQL : 1.安装mysql服务:mysqld install 2.删除mysql服务:sc delete mysql 3.启动mysql服务:net start mysql 4.初始化设置密码 ...
- 面对对象程序设计_task2_C++视频教程
lessons about C++ 1月份的事情不该留到2月份来做,这几天看了几个地方的C++视频教程,不习惯于云课堂的话多等等,最终还是选择了慕课网上面的资源,也安下心来看了一些内容,下面附上课程详 ...
- LRU算法的精简实现(基于Java)
LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是"如果数据最近被访问过,那么将来被访问的几率也更高". impo ...
- ORACLE默认实例设置--linux
数据库实例多了之后,每次export的时候,显示的ORACLE_SID总不是自己经常用的那个,要是能让默认的自定义就好了. 现在就解释一下在linux环境中如何定义: 1.su - oracle 2. ...