汤锴 14:36:36
发一下拾取算法:
得到鼠标点击处的屏幕坐标,求得世界坐标系下通过视点和鼠标点击点的一条射线,该射线如果与场景的三角形相交,则拾取该三角形。
从数学角度来看,只要得到世界坐标系下,该射线的方向矢量和出射点,就具备了判断射线与三角面是否相交的条件。
projPt.x = (screenPt.x-screenWidth/2)/screenWidth*2;
projPt.y = (screenPt.y-screenHeight/2)/screenHeight*2;
projPt.z =0;
viewPt.x = projPt.x*Zn/ ProjMatrix._m11;
viewPt.y = projPt.y*Zn/ ProjMatrix._m22;
viewPt.z = Zn;
DIRview.x = (2*screenPt.x/screenWidth-1)/projMatrix._m11;
DIRview.y = (2*screenPt.y/screenHeight-1)/projMatrix._m22;
DIRview.z = 1;
DIRworld = DIRview * inverse_ViewMatrix,其中inverse_ViewMatrix为ViewMatrix的逆矩阵。
观察点在世界坐标系中的表示为:
OriginWorld = (inverse_ViewMatrix._41,
inverse_ViewMatrix._42,
inverse_ViewMatrix._43,
1);
判断射线与三角面是否相交的条件就完全具备了。
刚才和connan聊到这个,感觉会对大家有用(特别是做编辑器的),于是在这里发一下
小河马 14:41:34
DX里面提供一个方法,Vector3.Unproject // Transform points to world space
用这个函数就不用自己写了
conan 14:42:13
找个例子给我看看
汤锴 14:42:30
Unproject 是从 projection space -> view space 吧,我推荐不要跳跃步骤
先 screen -> proj,再 proj -> view, 再求 view下的观察矢量,再变换到 world,这样不容易出错
小河马 14:42:47
private void DoPicking(short mouseX, short mouseY)
{
// Clamp mouse coordinates to viewport
if (mouseX < 0) mouseX = 0;
if (mouseY < 0) mouseY = 0;
if (mouseX > this.sampleFramework.Device.Viewport.Width) mouseX = (short)this.sampleFramework.Device.Viewport.Width;
if (mouseY > this.sampleFramework.Device.Viewport.Height) mouseY = (short)this.sampleFramework.Device.Viewport.Height;
// Put mouse coordinates in screenspace Vector3's. These are the points
// defining our ray for picking, which we'll transform back to world space
Vector3 near = new Vector3(mouseX, mouseY, 0);
Vector3 far = new Vector3(mouseX, mouseY, 1);
// Transform points to world space
near.Unproject(this.sampleFramework.Device.Viewport, camera.ProjectionMatrix, camera.ViewMatrix, scannerWorldMatrix * Matrix.RotationY(scannerRotationTimer));
far.Unproject(this.sampleFramework.Device.Viewport, camera.ProjectionMatrix, camera.ViewMatrix, scannerWorldMatrix * Matrix.RotationY(scannerRotationTimer));
小河马 14:43:32
dx里有个自带的例子,就是picking的,里面有完整实现
汤锴 14:43:45
dx8的例子嘛
后面去掉了
小河马 14:44:05
是啊,DX9 2007年4月的就没了
汤锴 14:44:35
2004就没有了
汤锴 14:45:43
用这个方法,没有自己推导一编,更能清楚问题的本质
conan 14:45:56
unproject得到的结果是不是世界坐标?
汤锴 14:46:09
看它的注释是这样
汤锴 14:46:27
问题是unproject把所有的实现细节都隐藏了,不利于学习
而且这是一个DX-Only的函数
conan 14:47:00
是的
小河马 14:47:09
public void Unproject (
Viewport viewport,
Matrix projection,
Matrix view,
Matrix world
)
Projects the current vector from screen space into object space.
conan 14:47:11
就是那个拟运算
以前我知道
汤锴 14:47:28
它用到了viewport,这肯定对应
projPt.x = (screenPt.x-screenWidth/2)/screenWidth*2;
projPt.y = (screenPt.y-screenHeight/2)/screenHeight*2;
projPt.z =0;
这一步
camera.ProjectionMatrix用来计算:
viewPt.x = projPt.x*Zn/ ProjMatrix._m11;
viewPt.y = projPt.y*Zn/ ProjMatrix._m22;
viewPt.z = Zn;
conan 14:48:12
zn?
camera.ViewMatrix是用来变换 Dir到world坐标系
conan 14:48:48
Zn指什么?
汤锴 14:48:54
Zn = Z near
Zf = Z far
小河马 14:48:56
public static Vector3 Project(
Vector3 v,
object viewport,
Matrix projection,
Matrix view,
Matrix world
); Projects a vector from object space into screen space.
14:50:29
Project其实就是一次从 object -> screen 的完整的顶点坐标变换流程
Unproject就是对应的逆变换
不过这里做法稍有区别,DX例子是把pick放在object也就是本地坐标系作的
嗯,这样确实更好
小河马 14:50:58
unproject是屏幕到世界
汤锴 14:51:02
都是变换到本地坐标系的
本地坐标系和世界坐标系有区别。Unproject把 inverse_worldmatrix 也乘算了,这样pick就不必在world space下进行
不过,对于很多场景文件来说,world space就是object space
conan 14:52:20
near和far对应的远近剪彩?
汤锴 14:52:29
嗯
小河马 14:52:52
z near = 0; z far = 1,一般是这样的吗
汤锴 14:53:22
什么坐标系下的z near? 如果是 homo space 下,是,而且必然是
小河马 14:53:36
世界坐标系下的呢
相机坐标系下的呢
汤锴 14:54:11
如果是world下,那就要看你的比例尺了,不过一般没人定这么小的比例尺吧!
我的z near world = 0.1f, z far world = 1024.f
这个根据具体游戏不同而异
小河马 14:54:38
哦,z near和z far都是在初始化相机时设置的吧/
conan 14:54:44
是的
汤锴 14:54:47
嗯,你可以这样做,其实这个函数内部做的运算,就是我贴的那一堆东西。
conan 14:55:56
TK我把照相机放到0,-10,-10的位置鼠标知道的地方好像不对
汤锴 14:56:43
那你那段代码肯定有问题,你把好几步计算合到一起了,我也看不出来哪里有错
除非我数学推导化建一遍才知道,反正公式都有了,你自己化简吧
还是那句话,我推荐不要跳跃步骤
要么,你就用 unproject
要么,就一步一步自己计算,不要合并
小河马 14:58:10
我想问个初级问题,就是,这些矩阵运算,都是怎么推导出来的啊?比如,为什么2D到3D,必须要这么转换啊!
肯定有数学证明吧!
我想看下数学证明,否则只能死记硬背这些转换步骤
汤锴 14:58:32
矩阵计算的前身是三角函数计算
矩阵只不过为了级联运算的方便
那你自己推导一下 旋转平移缩放矩阵吧
然后再去推导 投影矩阵
conan 15:04:24
OriginWorld = (inverse_ViewMatrix._41,
inverse_ViewMatrix._42,
inverse_ViewMatrix._43,
1);
这个我求到了
关键是从照相机出发,观察的那点的位置
这需要中间那个向量
我要球的是鼠标点到的那点在世界坐标的位置
汤锴 15:05:18
那个向量就是DIRworld
你如果只是要做拾取,有了这个向量,就不用再求拾取点在世界坐标系下的坐标了
conan 15:05:40
那个是照相机的位置阿?
汤锴 15:06:08
不是,照相机的位置,就是视点的位置,就是OriginWorld
conan 15:07:10
DIRworld就是照相机到鼠标直到的点在世界坐标的向量?
汤锴 15:07:40
对
小河马 15:07:41
射线的射出点就是 OriginWorld吗?
汤锴 15:08:17
对
汤锴 15:08:30
这些都是定义在world下的
小河马 15:08:37
那你怎么说 OriginWorld是相机的位置。。。
拾取时和相机没关系吧
汤锴 15:09:25
摄像机在世界坐标系下的位置,就是view space的原点在world space下的坐标啊!!!
拾取射线就是从view space的原点发出来的
小河马 15:10:15
哦,我知道了。其实主要是确定那个射线向量
汤锴 15:12:04
我这中间还是跳跃了几步,我完整写出来:
DIRview = (viewPt.x-0,viewPt.y-0,viewPt.z-0)= (projPt.x/projMatrix._m11,projPt.y/projMatrix._m22,1)
所以才有
DIRview.x = (2*screenPt.x/screenWidth-1)/projMatrix._m11;
DIRview.y = (2*screenPt.y/screenHeight-1)/projMatrix._m22;
DIRview.z = 1;
conan 15:45:59
TK DIRworld酸出来不对
D3DXVec3Transform(&DIRworld,&DIRview,&inverse_ViewMatrix);
OriginWorld是正确的
汤锴 15:50:09
你如何知道不对的呢
conan 15:50:30
我的照相机在0,-10-10
向量球出来应该是0,10,10
结果是0,-10,-10
汤锴 16:05:53
哦,确实 DIRview.y公式错了,少了个负号
conan 14:59:49
你这个 Vector3 near = new Vector3(mouseX, mouseY, 0);
后面的0,1代表什么?
汤锴 15:00:08
0,1是homo space下的Zn, Zf
小河马 15:00:22
homo space是什么?
汤锴 15:00:46
perspective divide 之后的空间,接在 projection space 后面
也就是
v.x /= v.w
v.y /= v.w...之后的空间
conan 15:01:21
由屏幕坐标 x,y
v.w是按照DX的ProjMatrix写的,我自己的是v.z,perspective divide这一步其实就是“近大远小”
homo space的形状是一个砍掉一半的立方体(把z-砍掉了)
小河马 15:02:51
晕。。你当初是怎么弄明白这些的啊?要看什么书?
汤锴 15:03:14
我自己实现了一遍DX的流水线,就清楚了
参考DX自带的文档
只不过它多做了一步,变换到object了