在vr开发中,最长用的应该就是头控了,头控听起来高大上,其实原理和手触摸屏幕点击屏幕上的控件一样,只是把手换成了屏幕中心点,用手控制系统已经给我们算好了点击的屏幕坐标并计算了控件是否处于坐标上从而产生点击,但在三维坐标中应该怎样确定屏幕中心点是否与场景中的物体相交呢,这就涉及了以下几个知识点
1,光线追踪
首先看图
光源照射在现实生活中,像图中展示的一样,光线碰到物体会被挡住,后面的物体接收不到光了,类比一下,如果把光源换成人的眼睛,从眼睛发出一条射线,射线和场景中的物体相交,肯定的近处的物体最先相交,如下图所示
盆那再vr世界中,摄像机就是我们的眼睛,那光线追踪的思想也就是,通过摄像机和近平面上的点确定一条光线,根据此光线与视景体中的物体计算相交时间来确定是否选中此物体,如下图
摄像机与近平面确定射线一般将a点设置为近平面的中心,
2.碰撞检测
我们知道碰撞检测一般用于游戏开发中,即检测物体和物体是不是有接触的位置,常用的碰撞检测策略有很多,但AABB是一种很好的简化策略,原理如图
不同物体的AABB确定一个AABB需要记录6个值,Xmin、Xmax、Ymin、Ymax、Zmin、Zmax,他们代表包围盒在每个坐标轴上的最大值和最小值,碰撞检测就变成了这些值得比较
碰撞检测原理介绍了上面两个概念那在vr的头控中该怎么应用呢?
首先我们要确定眼睛注视的地方即屏幕中心在世界坐标系(即表示三维物体位置的坐标系)会涉及到坐标系的转换,即屏幕坐标转换成摄像机坐标(摄像机坐标系是以摄像机为中心建立的坐标系)
屏幕坐标系和摄像机坐标系同理我们把x,y设为屏幕中心,求出近平面的坐标后利用上图的三角形相似原理即可求出摄像机坐标系中远平面的点,但这并不是我们想要的点,所以还要将摄像机坐标系的点转换成世界坐标系(即表示三维物体坐标的坐标系)表示,,主要用到逆矩阵,关于坐标系的转换请参考坐标系转换
好了,有了物体的坐标和射线的坐标和方向,我们就可以进行碰撞检测了,思路大概是这样
先判断矩形边界框的哪个面会相交, 再检测射线与包含这个面的平面的相交性。 如果交点在盒子中,那么射线与矩形边界框相交, 否则不存在相交,代码如下
//和参数射线的相交性测试,如果不相交则返回值是一个非常大的数(大于1)
//如果相交,返回相交时间t
//t为0-1之间的值
public floatrayIntersect(
Vector3f rayStart,//射线起点
Vector3f rayDir,//射线长度和方向
Vector3f returnNormal//可选的,相交点处法向量
){
//如果未相交则返回这个大数
final floatkNoIntersection = Float.POSITIVE_INFINITY;
//检查点在矩形边界内的情况,并计算到每个面的距离
booleaninside =true;
floatxt,xn =0.0f;
if(rayStart.x
xt =min.x- rayStart.x;
if(xt>rayDir.x){returnkNoIntersection;}
xt /= rayDir.x;
inside =false;
xn = -1.0f;
}
else if(rayStart.x>max.x){
xt =max.x- rayStart.x;
if(xt
xt /= rayDir.x;
inside =false;
xn =1.0f;
}
else{
xt = -1.0f;
}
floatyt,yn =0.0f;
if(rayStart.y
yt =min.y- rayStart.y;
if(yt>rayDir.y){returnkNoIntersection;}
yt /= rayDir.y;
inside =false;
yn = -1.0f;
}
else if(rayStart.y>max.y){
yt =max.y- rayStart.y;
if(yt
yt /= rayDir.y;
inside =false;
yn =1.0f;
}
else{
yt = -1.0f;
}
floatzt,zn =0.0f;
if(rayStart.z
zt =min.z- rayStart.z;
if(zt>rayDir.z){returnkNoIntersection;}
zt /= rayDir.z;
inside =false;
zn = -1.0f;
}
else if(rayStart.z>max.z){
zt =max.z- rayStart.z;
if(zt
zt /= rayDir.z;
inside =false;
zn =1.0f;
}
else{
zt = -1.0f;
}
//是否在矩形边界框内?
if(inside){
if(returnNormal !=null){
returnNormal = rayDir.multiK(-1);
returnNormal.normalize();
}
return0.0f;
}
//选择最远的平面————发生相交的地方
intwhich =0;
floatt = xt;
if(yt>t){
which =1;
t=yt;
}
if(zt>t){
which =2;
t=zt;
}
switch(which){
case0://和yz平面相交
{
floaty=rayStart.y+rayDir.y*t;
if(ymax.y){returnkNoIntersection;}
floatz=rayStart.z+rayDir.z*t;
if(zmax.z){returnkNoIntersection;}
if(returnNormal !=null){
returnNormal.x= xn;
returnNormal.y=0.0f;
returnNormal.z=0.0f;
}
}
break;
case1://和xz平面相交
{
floatx=rayStart.x+rayDir.x*t;
if(xmax.x){returnkNoIntersection;}
floatz=rayStart.z+rayDir.z*t;
if(zmax.z){returnkNoIntersection;}
if(returnNormal !=null){
returnNormal.x=0.0f;
returnNormal.y= yn;
returnNormal.z=0.0f;
}
}
break;
case2://和xy平面相交
{
floatx=rayStart.x+rayDir.x*t;
if(xmax.x){returnkNoIntersection;}
floaty=rayStart.y+rayDir.y*t;
if(ymax.y){returnkNoIntersection;}
if(returnNormal !=null){
returnNormal.x=0.0f;
returnNormal.y=0.0f;
returnNormal.z= zn;
}
}
break;
}
returnt;//返回相交点参数值
}
网盘链接: pan.baidu.com/s/1geRT20N 密码: wtkf,里面有全书的pdf和各章的例子,我在做头控的时候是参考了其中的第15章和19章
网友评论