地球仪经线纬线上的经度纬度始终跟随摄像机

一.在做APP的AR项目中开发“认识地球仪”模块时,要实现在地球仪上显示经线和纬线,与经度和纬度,实现效果如下:

1.点击“认识经度”按钮,会在地球仪上依次显示经线和经度,手指在屏幕上左右滑动来查看不同视角。
2.点击“认识纬度”按钮,会在地球仪上依次显示纬线和纬度,手指在屏幕上左右滑动来查看不同视角。

可能有人已经看出来了,旋转地球的时候有些角度会被挡住。对,我就是为了解决它,使其变的更优雅。接着看下图:

3
4


这样的话,不管你怎么旋转视角查看地球仪,线上的角度始终跟随摄像机,可见视角区域始终可以看到相应角度,实在不能够被显示的也会渐隐消失来尽量优雅,这样是不是比上面体验更好,更优雅呢。

优雅的同时也确实增加了不少开发工作量,开发起来特别考验开发者对3D几何向量知识的掌握,下面就来介绍一下我的思路和实现:

图1,2实现起来就比较简单了,一般unity开发者都能搞定,就是把3d字体(Unity 3d Text)摆放到固定位置就可以了,就不做细赘述了。

我们来主要看图3,4,角度跟随摄像机是如何实现的? 为便于理解后面的程序实现,还是要介绍一下需求和一些相关的地理知识(若有地理术语使用不当的地方请多包涵,因着重是从产品需求和程序开发方面来介绍的):

经度,经度的话就是标记地球上经线所在位置的角度(就是经线上显示度数),经线之间相隔15°共24条。度数显示在线的中间位置(也是纬度0°的位置)。如图,当摄像机与地球仪平行时正好看到的是经纬度都是0°的位置(通俗的说也就是在球的半腰位置,也就是在摄像机视野中间),问题?这个时候随着摄像机向上看的话,显示经线上的角度变得越来越靠下最后就被球体档住了,如图

纬度,纬度的话就是标记地球仪上纬线位置的角度,纬线之间也是相隔15°共11条(11个环),每条纬线的度数显示在纬线经度0°的位置,如下图,摄像机中心区域显示的正好是经纬0°位置,问题,当左右移动摄像机某个角度,纬线上的度数也会被挡住(描述问题跟经度遇到情况一样所有下面就只截一张图)。


以上这些提到的问题就是需要改善的地方,下面是具体实现:

参考下图1,2,首先是在场景中的地球仪下创建空物体WeftCtrl,在再WeftCtrl下面创建三个子物体分别:

DegreeGroup 用来存放角度位置的空物体,也可以是Unity 3d Text 物体。注意:因为后面用到的是UI, 所以这个下面是只带Transform的空物体;

Center 地球仪中心的位置

Axle 用于计算角度参考的轴位置

1.

2.

3.

图3,生成对应纬度UI,其实第一部分git图看到的效果就是用UI,好处是不管视角怎么反转,纬度文字都是正常的,而用3d字体就会出现旋转字体倾斜,用UI的话会更好。不过仅需要考虑一点,要把3D空间坐标点映射到屏幕UI点上(后面也会贴转换的代码,网上这样的代码也有有很多可以借鉴);

DegreeGroup 下面的物体是通过代码动态生成的,生成纬度代码片段:

//在编辑模式“[ExecuteInEditMode]”下执行一次,生成经纬度对应物体,然后保存场景文件,以后运行场景就不需要再创建了,好处是场景加载时减速cpu计算量;

void CreateWeftDegree(){

UIContainer.RemoveAllChild();

degreeGroup.RemoveAllChild();

degreeFades = new DegreeFade[setDegreeFadeLen];

int _degree_i = 0;

var prefabs = Resources.Load("Prefabs/Degree");

CreateDegreePoint((pos,degree)=>{

//Create Empty object

GameObject newGo = new GameObject();

newGo.name = "degree_"+degree;

newGo.transform.localPosition = pos;

newGo.transform.SetParent(degreeGroup,false);

newGo.transform.localScale = Vector3.one*0.02f;

//create UI object

GameObject UIGo = Instantiate(prefabs) as GameObject;

UIGo.transform.SetParent(UIContainer,false);

UIGo.name = newGo.name;

degree = degree<0?degree*-1:degree;

UIGo.GetComponent().text = degree+"°";

DegreeFade df = UIGo.AddComponent();

df.followerTransform = newGo.transform;

degreeFades[_degree_i++] = df;

ScreenGameObjectFollower follower = UIGo.AddComponent();

follower.UICamera = mainCamera;

follower.followObject = newGo;

UIGo.SetActive(false);

});

}

//计算纬度

protected override void CreateDegreePoint(System.Action fun){

float stepDegree = 15;

setDegreeFadeLen = 11;

Vector3 pos;

var size = gameObject.GetComponent().sharedMesh.bounds.size;

var scale = transform.localScale;

var radius = (size.x * scale.x)/2;

for(int i=0;i

if(i<6){

pos = GetEarthPointBy(radius,0,i*stepDegree)/transform.localScale.x;

fun(pos,i*stepDegree);

}else{

pos = GetEarthPointBy(radius,0,(5-i)*stepDegree)/transform.localScale.x;

fun(pos,(5-i)*stepDegree);

}

}

}

//计算经度

protected override void CreateDegreePoint(System.Action fun){

float stepDegree = 15;

setDegreeFadeLen=24;

Vector3 pos;

var size = gameObject.GetComponent().sharedMesh.bounds.size;

var scale = transform.localScale;

var radius = (size.x * scale.x)/2;

for(int i=0;i

if(i<13){

pos = GetEarthPointBy(radius,i*stepDegree,0)/transform.localScale.x;

fun(pos,i*stepDegree);

}else{

pos = GetEarthPointBy(radius,(12-i)*stepDegree,0)/transform.localScale.x;

fun(pos,(12-i)*stepDegree);

}

}

}

上面这两个方法是用来创建经度和纬度在地球仪上显示的位置和UI显示的度数,下面是经纬度跟随摄像机实现部分:

下图显示初始化效果,坐标系分别是摄像机和地球仪位置,设定“地球仪的坐标系”和“摄像机的坐标系”垂直方向一致。

因为是有AR功能,地球仪旋转被AR依赖,我只能在地球仪物体下创建WeftCtrl和WarpCtrl,分别调整他们和摄像机坐标系垂直方向一致,同样也可以达到目的。

接着调整WeftCtrl坐标系和摄像坐标系朝向相同,中心点调整到地球仪中心位置(0,0,0),在WeftCtrl物体下创建子物体Axle,调整Axle自身到Y轴的(0,0.5,0)位置。有了WeftCtrl中心位置、Axle位置、Camera位置,就可以计算WeftCtrl到Axle差向量“A”和WeftCtrl到Camera差向量“B”, 然后用Vector3f.Angle(A,B)求出两个差向量的角度---当然也可以用几何公式向量叉乘来计算。Unity已经提供现成API使用会更方便。

实现经度跟随摄像机就只需要下面核心的两个方法:

第一个OnUpdate()是摄像机视角有产生变化时调用的,而不是在脚本的Update中一直在调用,避免放update下一直调用,消耗性能。 

计算经过地球一圈的所有点位置(计算360个点),每度点与摄像机距离,根据距离排序找出最近距离点的度数,根据最近的度数通过GetEarthPointBy()计算每个经度的位置。

public override void OnUpdate(Vector3 cameraPosition){

var angle = 360;

var stepAngle = 1;

var len = angle/stepAngle;

float[] distances = new float[angle/stepAngle];

float Radius = Vector3.Distance(Axle.position,center.position);

int j=0;

for(int i= 0;i

if(i>=len/2){

j = (angle-i)*stepAngle*-1;

}else{

j = i*stepAngle;

}

distances[i]=Vector3.Distance(cameraPosition,(degreeGroup.position + degreeGroup.rotation*(GetEarthPointBy(Radius,j,0))));

}

距离摄像机最近的纬度

int idx =  ArrayUtility.FindSize(distances,(t,min)=>{

return t < min ;

});

if(idx>=len/2){

idx = (angle-idx)*stepAngle*-1;

}else{

idx *=stepAngle;

}

ArrayUtility.Foreach(degreeFades,(df)=>{

df.OnUpdate(degreeGroup.position,cameraPosition);

var parent = df.followerTransform.parent;

if(df.gameObject.name.Equals("S") || df.gameObject.name.Equals("N"))

return ;

df.followerTransform.position = parent.position + parent.rotation*GetEarthPointBy(Radius,idx,df.angle);

});

}

第二个OnUpdate()是判断度数不在摄像机可见范围内时计算渐隐消失,回到可见区再渐显;

public void OnUpdate (Vector3 earthPosition,Vector3 cameraPositon) {

根据当前纬度位置、地球中心位置、摄像机位置求出夹角

var degree = Vector3.Angle(followerTransform.position-earthPosition,cameraPositon-earthPosition);

var distance = Vector3.Distance(followerTransform.position,cameraPositon);

SetTextSize(calculateTextSize(distance));

判断夹角控制纬度文字渐隐、渐显

if(degree >= 70 && !isFade){

isFade = true;

FadeOut();

}else if(degree <= 70 & isFade){

isFade=false;

FadeIn();

}

}

纬度计算跟上面部分类似,也是两个方法:

public override void OnUpdate (Vector3 cameraPosition) {

通过Y轴位置、地球中心位置、摄像机位置,计算他们的夹角

var angle = Vector3.Angle(Axle.position-center.position,cameraPosition-center.position);

if(angle>=90){

angle = (90 - (180 - angle))*-1;

if(angle < -60)

angle = -60;

}else if(angle < 90 && angle >= 0){

angle = 90-angle;

if(angle > 60)

angle = 60;

}

float radius = Vector3.Distance(degreeGroup.GetChild(0).position,degreeGroup.position);

ArrayUtility.Foreach(degreeFades,(df)=>{

df.OnUpdate(degreeGroup.position,cameraPosition);

var parent = df.followerTransform.parent;

df.followerTransform.position = parent.position + parent.rotation*GetEarthPointBy(radius,df.angle,angle);

});

}

public void OnUpdate (Vector3 earthPosition,Vector3 cameraPositon) {

根据当前纬度位置、地球中心位置、摄像机位置求出夹角

var degree = Vector3.Angle(followerTransform.position-earthPosition,cameraPositon-earthPosition);

var distance = Vector3.Distance(followerTransform.position,cameraPositon);

SetTextSize(calculateTextSize(distance));

判断夹角控制纬度文字渐隐、渐显

if(degree >= 70 && !isFade){

isFade = true;

FadeOut();

}else if(degree <= 70 & isFade){

isFade=false;

FadeIn();

}

}

在计算纬度时没有采取通过和摄像机夹角开判断,是因为场景中物体嵌套层级太多,不太方便计算。

判断距离开计算会更简单方便。

这是在简书上的第一篇博客,请大家多支持,描述不清楚点多多指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容