本文基于krpano1.20.9版本
导读
krpano是什么
krpano是一个可以实现定制化全景项目的根据,很多在线可视化全景平台都是基于krpano开发而来。它本身不开源,商用需要付费购买,否则会有水印,购买方式读者可在官网自行了解。
本文目标
本文会从零开始做一个完整的全景项目,希望读者跟随步骤完成后能够了解krpano的基本用法,不对krpano的语法做深入讲解。
在线预览:全景demo
源代码可以直接用控制台查看下载
如何使用krpano
krpano文件介绍
从官网下载krpano后,可以看到文件包里有如下文件:
其中docu文件夹是krpano的文档,templates提供了一系列模板,viewer则是存放例子的地方。双击krpano Testing Server.exe打开后,可以看到一个弹窗,提供了本地服务地址,点击进入后打开可以看到与上图相同的结构,点击documentation.html可以查看文档,点击examples.html可以看到所有的案例,这些读者可自行浏览研究。
我们的重点在于最下面的.bat文件,这几个文件是用来生成krpano所需要的全景图的。可以看到下面一共6个文件,其中第一个是生成平面图的,最后一个是生成vr所需要的图的,这两个我们都暂且忽视,重点的是中间的4个。
首先分析一下它们的意思,不难发现MAKE PANO和MAKE VTOUR是两种不同的类型,它们分别表示生成单个场景和生成一组场景,后面括号里的NORMAL和MULTIRES表示生成的全景图的类型,NORMAL表示生成6个面的全景图,MULTIRES表示生成更加细分的全景图,读者自行操作一下即可发现其中区别。
如何生成全景图
首先,我从网络上找了两张全景图素材
选中他们,拖动到MAKE VTOUR (NORMAL) droplet.bat上,就会在当前文件夹下生成一个叫vtour的文件夹,文件夹目录结构如下
运行tour_testingserver.exe即可启动服务,可以看到浏览器自动打开的页面上已经有了基础的功能:缩放、跳转、全屏。
我们从看一下它进行了什么操作,首先在
tour.html文件中可以看到它引用了一个叫tour.js的文件,实际上就是krpano.js,然后在底下进行了初始化
// 重点看第二行和第三行
embedpano({
swf:"tour.swf",
xml:"tour.xml", // 此处为配置文件,也是krpano所有功能配置的来源
target:"pano", // 这个"pano"表示在设置给krpano的容器的id,可以看到html里面有一个id为pano的div
html5:"auto",
mobilescale:1.0,
passQueryParameters:true
});
如上所述,krpano最重要的配置文件就是tour.xml,那么我们首先简单了解一下里面的内容,可以看到类似如下结构。
<krpano version="1.20.7" title="Virtual Tour">
<include url="skin/vtourskin.xml" />
<skin_settings maps="false"
...
/>
...
<action name="startup" autorun="onstart">
if(startscene === null OR !scene[get(startscene)], copy(startscene,scene[0].name); );
loadscene(get(startscene), null, MERGE);
if(startactions !== null, startactions() );
</action>
<scene name="scene_view1" title="view1" onstart="" thumburl="panos/view1.tiles/thumb.jpg" lat="" lng="" heading="">
...
</scene>
...
</krpano>
首先说明一下,最外层是由krpano标签包起来的,中间部分的scene表示场景,可以看到它引用目录里生成的全景文件。
由action标签包裹的这一段表示启动时自动加载第一个场景。
<include url="skin/vtourskin.xml" />表示引入其他xml,这边是引入了krpano默认的一个皮肤模板,而下面的skin_settings对此进行了一些配置,参数非常多,这次的例子暂且用不上,所以我们暂时将这一段减去。
于是我们的xml目前变成了这样的结构:
<krpano version="1.20.7" title="Virtual Tour">
<action name="startup" autorun="onstart">
if(startscene === null OR !scene[get(startscene)], copy(startscene,scene[0].name); );
loadscene(get(startscene), null, MERGE);
if(startactions !== null, startactions() );
</action>
<scene name="scene_view1" title="view1" onstart="" thumburl="panos/view1.tiles/thumb.jpg" lat="" lng="" heading="">
<view hlookat="0.0" vlookat="0.0" fovtype="MFOV" fov="120" maxpixelzoom="2.0" fovmin="70" fovmax="140" limitview="auto" />
<preview url="panos/view1.tiles/preview.jpg" />
<image>
<cube url="panos/view1.tiles/pano_%s.jpg" />
</image>
</scene>
<scene name="scene_view2" title="view2" onstart="" thumburl="panos/view2.tiles/thumb.jpg" lat="" lng="" heading="">
<view hlookat="0.0" vlookat="0.0" fovtype="MFOV" fov="120" maxpixelzoom="2.0" fovmin="70" fovmax="140" limitview="auto" />
<preview url="panos/view2.tiles/preview.jpg" />
<image>
<cube url="panos/view2.tiles/pano_%s.jpg" />
</image>
</scene>
</krpano>
这时页面上只剩下一个全景场景了,那么接下来我们开始定义自己想要的功能。 先列出来我们要实现的功能
- 在场景上选择合适的位置,自定义有动画的热点
- 点击热点能够跳转场景
- 点击热点能和js进行交互
- 热点位移动画
- 热点上有说明文字
- 实现小行星开场和陀螺仪
添加热点
我们要实现的第一个功能是在场景里添加一个热点,首先在第一个scene中添加一个hotspot标签
<hotspot
name="area1"
url="skin/vtourskin_hotspot.png" />
这时可以看到一个箭头图形出现在了屏幕中间,但是我们希望的是能够自定义它的位置,那么给它加上ath和atv属性,其中ath表示水平坐标,ath表示垂直坐标
<hotspot
name="area1" ath="-0.736" atv="35.471"
url="skin/vtourskin_hotspot.png" />
问题又来了,慢慢调整数字显然不够方面,直接把热点移动到想要的位置不是更好吗?当然这是可行的。
首先在krpano标签下面加下面的action:
<action name="draghotspot">
spheretoscreen(ath, atv, hotspotcenterx, hotspotcentery, calc(mouse.stagex LT stagewidth/2 ? 'l' : 'r'));
sub(drag_adjustx, mouse.stagex, hotspotcenterx);
sub(drag_adjusty, mouse.stagey, hotspotcentery);
asyncloop(pressed,
sub(dx, mouse.stagex, drag_adjustx);
sub(dy, mouse.stagey, drag_adjusty);
screentosphere(dx, dy, ath, atv);
print_hotspot_pos();
);
</action>
<action name="print_hotspot_pos"><![CDATA[
copy(print_ath, ath);
copy(print_atv, atv);
roundval(print_ath, 3);
roundval(print_atv, 3);
calc(plugin[hotspot_pos_info].html, '<hotspot name="' + name + '"[br] ...[br] ath="' + print_ath + '"[br] atv="' + print_atv + '"[br] ...[br] />');
]]></action>
然后在scene标签下面加这一段:
<plugin name="hotspot_pos_info"
type="text"
html="drag the hotspots..."
css="font-family:Courier;font-size: 25px;"
padding="8"
align="lefttop" x="10" y="10"
width="600"
height="200"
enabled="true"
/>
同时在hotspot标签上加上相关方法:
<hotspot
name="area1" ath="-360" atv="35.471"
url="skin/vtourskin_hotspot.png"
ondown="draghotspot()"
/>
这时可以发现场景左上角有一个白色方框,拖动热点时上面会显示热点坐标,于是我们就可以很方便的将热点定义到自己想要的位置。
代码解释:第一个action draghotspot用来做热点的拖动,第二个action print_hotspot_pos用来配置左上角的方框,在场景里通过plugin标签创建一个插件,并且向print_hotspot_pos传递参数进行输出。
帧动画
接下来我们给热点加上动画,首先我们需要一个序列帧图片,这里就用krpano官方例子里的图片
首先将这张图放到当前项目的skin文件夹,然后将热点的图片引用地址指向这张图:
<hotspot
name="area1" ath="-360" atv="35.471"
url="skin/arrow.png"
ondown="draghotspot()"
/>
这时候页面中直接展示了这张图,如何让它有动效呢,这里又要借助action了,在页面里加入如下代码:
<action name="do_crop_animation" scope="local" args="framewidth, frameheight, framerate">
<!-- define local variables -->
calc(local.xframes, (caller.imagewidth /framewidth) BOR 0);
calc(local.frames, xframes * ((caller.imageheight / frameheight) BOR 0));
def(local.frame, integer, 0);
<!-- set the first frame -->
calc(caller.crop, '0|0|' + framewidth + '|' + frameheight);
<!-- do the animation -->
setinterval(
calc('crop_anim_' + caller.name),
calc(1.0 / framerate),
if(caller.loaded,
inc(frame);
if(
frame GE frames, if(
caller.onlastframe !== null,
callwith(caller, onlastframe())
);
set(frame,0);
);
mod(xpos, frame, xframes);
div(ypos, frame, xframes);
Math.floor(ypos);
mul(xpos, framewidth);
mul(ypos, frameheight);
calc(caller.crop, xpos + '|' + ypos + '|' + framewidth + '|' + frameheight);
,
<!-- stop the interval when the hotspot gets removed -->
clearinterval(calc('crop_anim_' + caller.name));
);
);
</action>
同时热点修改如下:
<hotspot
name="area1" ath="-360" atv="35.471"
onloaded="do_crop_animation(200, 100, 24);"
url="skin/arrow.png"
ondown="draghotspot()"
/>
就可以在页面中看到一个旋转的箭头了。
场景跳转
上面的操作只是在单个场景中进行,实际开发中一般是有多个场景的,场景间也能自由的跳转,所以我们接下来就实现点击热点进行跳转的功能。 首先继续加入新的action:
<action name="skin_loadscene" scope="local" args="newscenenameorindex, blendmode">
if(webvr.isenabled AND scene.count GT 1,
set(hotspot[skin_webvr_prev_scene].visible, false);
set(hotspot[skin_webvr_next_scene].visible, false);
);
calc(layer[skin_thumbborder].parent, 'skin_thumb_' + scene[get(newscenenameorindex)].index);
<!-- layer[skin_thumbs].scrolltocenter(get(scene[get(newscenenameorindex)].thumbx), get(scene[get(newscenenameorindex)].thumby)); -->
loadscene(get(scene[get(newscenenameorindex)].name), null, get(skin_settings.loadscene_flags), get(blendmode));
</action>
<action name="skin_gotoscene" scope="local" args="newscene">
if(scene[get(newscene)],
copy(cursceneindex, scene[get(xml.scene)].index);
copy(newsceneindex, scene[get(newscene)].index);
skin_loadscene(get(newsceneindex), 'ZOOMBLEND(1.0, 1.0, easeInOutSine)')) );
);
</action>
从action的命名可以直观地了解到它们的含义,第一个是加载场景,第二个是场景的切换。 我们给热点加上点击事件:
<hotspot
name="area1" ath="-360" atv="35.471"
onloaded="do_crop_animation(64, 64, 60);"
url="skin/arrow.png"
ondown="draghotspot()"
onclick="skin_gotoscene('scene_view2')"
/>
这时候点击热点,可以看到场景以淡入淡出的方式切换了。
给热点添加文字
有时候热点需要附带一些文字,如果将文字做在热点上,热点多的时候需要很多图片,显然是比较麻烦的,直接将文字分离出来是更好的选择。
<action name="add_all_the_time_tooltip">
txtadd(tooltipname, 'tooltip_', get(name));
addhotspot(get(tooltipname));
set(hotspot[get(tooltipname)].url,'%SWFPATH%/plugins/textfield.swf');
set(hotspot[get(tooltipname)].align,top);
set(hotspot[get(tooltipname)].edge,bottom);
if(device.mobile,
set(hotspot[get(tooltipname)].scale,0.5);
);
set(hotspot[get(tooltipname)].atv,get(hotspot[get(name)].atv));
set(hotspot[get(tooltipname)].ath,get(hotspot[get(name)].ath));
set(hotspot[get(tooltipname)].autowidth,true);
set(hotspot[get(tooltipname)].autoheight,true);
set(hotspot[get(tooltipname)].vcenter,true);
set(hotspot[get(tooltipname)].background,true);
set(hotspot[get(tooltipname)].bgroundedge,10);
set(hotspot[get(tooltipname)].backgroundcolor,0xccc);
set(hotspot[get(tooltipname)].roundedge,5);
set(hotspot[get(tooltipname)].backgroundalpha,1);
set(hotspot[get(tooltipname)].padding,5);
set(hotspot[get(tooltipname)].border,false);
set(hotspot[get(tooltipname)].glow,0);
set(hotspot[get(tooltipname)].zorder,99999);
set(hotspot[get(tooltipname)].glowcolor,0xFFFFFF);
if(device.mobile,
set(hotspot[get(tooltipname)].css,'text-align:center; color:#FFFFFF; font-size:28px; text-shadow: 1px 1px 1px #000;');
,
set(hotspot[get(tooltipname)].css,'text-align:center; color:#FFFFFF; font-size:28px; text-shadow: 1px 1px 1px #000;');
);
set(hotspot[get(tooltipname)].textshadow,0);
set(hotspot[get(tooltipname)].textshadowrange,6.0);
set(hotspot[get(tooltipname)].textshadowangle,90);
if(text == '' OR text === null,
copy(hotspot[get(tooltipname)].html,scene[get(linkscene)].title),
copy(hotspot[get(tooltipname)].html,text)
);
set(hotspot[get(tooltipname)].enabled,false);
if(hotspot[get(name)].typeid == '1',
if(device.mobile,
set(hotspot[get(tooltipname)].oy,100);
,
set(hotspot[get(tooltipname)].oy,100);
);
,
if(device.mobile,
set(hotspot[get(tooltipname)].oy,100);
,
set(hotspot[get(tooltipname)].oy,100);
);
);
</action>
然后给热点添加属性和事件:
<hotspot
name="area1" ath="-360" atv="35.471"
onloaded="do_crop_animation(64, 64, 60);add_all_the_time_tooltip"
url="skin/arrow.png"
ondown="draghotspot()"
text="热点描述"
onclick="skin_gotoscene('scene_view2')"
/>
可以看到页面显示了一个蓝底白字带圆角的框,参数可以在action里面修改。
视角限制
不难发现,目前所做的全景图,可以上下拖动看到最顶部和最底部,一般来说看场景时,我们希望用户是在一个相对平视的范围内,同时也不希望用户缩放过大的范围,所以要对场景进行一些配置去限制这个范围。
可以看到我们当前的view配置如下:
<view
hlookat="0.0"
vlookat="0.0"
fovtype="MFOV"
fov="120"
maxpixelzoom="2.0"
fovmin="70"
fovmax="140"
limitview="auto" />
其中hlootat和vlookat分别表示初始显示的水平角度和垂直角度,取值分别在-180到180,-90到90之间,fov表示视角范围,取值在0到179。我们想要限制上下浏览的范围,需要设置vlookatmin和vlookatmax,修改代码如下:
<view
hlookat="0.0"
vlookat="0.0"
vlookatmin="-70"
vlookatmax="80"
fovtype="MFOV"
fov="120"
maxpixelzoom="2.0"
fovmin="70"
fovmax="140"
limitview="auto" />
这时拖拽页面,发现限制并没有生效,这是因为除了设置这两个值外,我们还需要修改limitview的值,将limitview的值设为fullrange,就可以发现视角限制成功了。
krpano和页面的交互
上面的操作都是在xml文件里进行的,那么问题来了,如果想要点击热点,让页面出现一个弹窗,或者是进行更复杂的dom操作呢?krpano也提供了xml和js交互的方法:jscall
我们首先在tour.html随便里面添加一个方法:
function showTips () {
alert('这是一个弹窗')
}
接下来在场景里再添加一个热点,注意,同一个场景下hotspot的name属性不能重复
<hotspot
name="area2" ath="-320" atv="35.471"
url="skin/vtourskin_hotspot.png"
onclick="jscall(showTips())"
/>
点击这个热点,页面上弹出弹窗,这个功能就完成了。
小行星开场动画
小行星开场动画也是一个常用的功能,首先一样是添加代码,可以看到,这段代码里有两个action,一个event,第一个action是小行星动画的主体,第二个action是用来控制热点的显示,以免刚进入场景时就显示热点,event是表示文件夹加载后执行小行星动画。
<action name="skin_setup_littleplanetintro" scope="local">
<!-- skin_hideskin(instant); -->
set_hotspot_visible(false);
set(global.lpinfo, scene=get(xml.scene), hlookat=get(view.hlookat), vlookat=get(view.vlookat), fov=get(view.fov), fovmax=get(view.fovmax), limitview=get(view.limitview));
set(view, fovmax=170, limitview=lookto, vlookatmin=90, vlookatmax=90);
lookat(calc(global.lpinfo.hlookat - 180), 90, 150, 1, 0, 0);
set(events[lp_events].onloadcomplete,
delayedcall(0.5,
if(lpinfo.scene === xml.scene,
set(control.usercontrol, off);
set(view, limitview=get(lpinfo.limitview), vlookatmin=null, view.vlookatmax=null);
tween(view.hlookat|view.vlookat|view.fov|view.distortion, calc('' + lpinfo.hlookat + '|' + lpinfo.vlookat + '|' + lpinfo.fov + '|' + 0.0), 3.0, easeOutQuad);
delayedcall(3.0,
set(control.usercontrol, all);
<!-- skin_update_bouncinglimits_control(); -->
tween(view.fovmax, get(lpinfo.fovmax));
<!-- skin_deeplinking_update_url(); -->
delete(global.lpinfo);
set_hotspot_visible(true);
<!-- skin_showskin(); -->
if(skin_settings.thumbs AND skin_settings.thumbs_opened, skin_showthumbs() );
);
,
delete(global.lpinfo);
);
);
);
</action>
<events name="lp_events_" keep="true"
onxmlcomplete="set(events[lp_events_].onxmlcomplete,null);
if(device.webgl OR device.flash,
skin_setup_littleplanetintro();
); "
/>
<action name="set_hotspot_visible">
for(set(i,0),i LT hotspot.count,inc(i),
if(%1 == false,
if(hotspot[get(i)].visible == true,
set(hotspot[get(i)].mark,true);set(hotspot[get(i)].visible,%1);
);
,
if(hotspot[get(i)].mark == true OR hotspot[get(i)].mark2 == true,
set(hotspot[get(i)].visible,%1);
);
);
);
</action>
陀螺仪
做手机端的项目,有时需要用到陀螺仪功能,krpano的陀螺仪使用非常方便,在代码中加入:
<plugin name="skin_gyro" keep="true" url="./plugins/gyro2.js" softstart="1.0" enabled="true" />
<!-- 下面的action用来控制陀螺仪的开和关 -->
<action name="switch_gryo">
switch(plugin[skin_gyro].enabled);
</action>
这样在安卓手机上就可以使用陀螺仪,但是在ios13及以上需要获取用户授权,所以需要判断一下,一般来说,陀螺仪功能默认关闭,用户点击时再去做相应的判断,还有一点要注意,使用陀螺仪授权的网站必须有https证书。
这里给出示例代码,完整代码可以看github。
```js
var ua = navigator.userAgent
if (!!ua.match(/\(i[^;]+;( U;)?( )?CPU.+Mac OS X/i)) {
if (
typeof window.DeviceMotionEvent !== "undefined" &&
typeof window.DeviceMotionEvent.requestPermission === "function"
) {
// (optional) Do something before API request prompt.
window.DeviceMotionEvent.requestPermission()
.then(function (response) {
// (optional) Do something after API prompt dismissed.
if (response == "granted") {
... // 开启陀螺仪
} else {
... // 授权失败
}
})
.catch(function (e) {
...
});
} else {
var str = ua.toLowerCase().match(/cpu iphone os (.*?) like mac os/)
var v = str[1].split('_')[0] / 1
if (v < 13) {
// ios版本低于13直接开启陀螺仪
} else {
// ...
}
}
} else {
...
}
位移动画
有时候我们希望热点有一些简单的位移动画,不需要用序列帧实现,增加一个action:
<action name="float_animation">
def(val, number, 0);
def(max, number, 0.1);
def(flag, boolean, true);
setinterval(
calc('val' + caller.name),
0.05,
inc(val, 0.01);
if (flag,
set(ath, calc(ath + val));
set(atv, calc(atv + val));
,
set(ath, calc(ath - val));
set(atv, calc(atv - val));
);
if (val GE max, set(flag, calc(!get(flag)));set(val, 0));
);
</action>
同时给热点加上这个action
<hotspot
name="area2" ath="-320" atv="35.471"
onloaded="float_animation"
url="skin/vtourskin_hotspot.png"
onclick="jscall(showTips())"
/>
可以看到热点有了轻微的浮动效果,那么这段代码是怎么实现的呢,下面逐步解释一下。
首先可以看到这个action里面有几个函数:
def setinterval calc set inc if,这些是krpano里面自带的函数,在官网有所有的函数、变量等,这边针对用到的几个函数做一下解释。
def(variable, type, value*)
定义变量
variable:变量名。
type: 变量类型,有boolean,number(数字),integer/int(整数),uint(无符号整数),string,object,array
setinterval(id,delay,actions)
创建定时器 id:每个interval需要的唯一id
delay:间隔秒数
actions:执行函数
calc(variable, expression)
由于这里没发像js里一样自由使用运算,所以大部分计算要使用calc完成
variable:变量
expression:表达式
set(variable, value)
给变量赋值
inc(variable, byvalue*, max*, min*)
变量增加或减少某个值,可定义上限和下限
byvalue:增/减的值
if(condition, then-actions, else-actions*)
if函数,语法如下
if (
条件, 执行函数a,
else-if条件, 执行函数b,
...(其他条件和执行函数)
执行函数c
)
if可能有点难理解,举个例子,在js里面这样的一段代码:
if (number == 1) {
fnA();
fnB();
} else if (number == 2) {
fnC();
} else {
fnD();
}
在krpano里是这样的:
if (number == 1, fnA();fnB(),
number == 2, fnC(),
fnD()
);
那么再回头看一下这个action就很清楚了,首先我们定义了三个变量,让它每0.05帧加0.01,利用flag来增或减,实现ath和atv的变化(这两个值是自动获取的)。
如果用js来写,相当于这样:
var time = 0;
var max = 0.1;
var flag = true;
setInterval(function () {
time += 0.01;
if (flag) {
ath = ath + time;
atv = atv + time;
} else {
ath = ath - time;
atv = atv - time;
}
if (time >= max) {
flag = !flag;
time = 0;
}
}, 1000)
结语
对于krpano的入门指南就到这里啦,如有写错或不合理的地方,欢迎指正。