krpano全景入门指南

16,930 阅读12分钟

krpano官网

本文基于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 PANOMAKE VTOUR是两种不同的类型,它们分别表示生成单个场景和生成一组场景,后面括号里的NORMALMULTIRES表示生成的全景图的类型,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" />

这时可以看到一个箭头图形出现在了屏幕中间,但是我们希望的是能够自定义它的位置,那么给它加上athatv属性,其中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, '&lt;hotspot name="' + name + '"[br]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...[br]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ath="' + print_ath + '"[br]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;atv="' + print_atv + '"[br]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...[br]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/&gt;');
  ]]></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" />

其中hlootatvlookat分别表示初始显示的水平角度和垂直角度,取值分别在-180到180,-90到90之间,fov表示视角范围,取值在0到179。我们想要限制上下浏览的范围,需要设置vlookatminvlookatmax,修改代码如下:

    <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('这是一个弹窗')
}

接下来在场景里再添加一个热点,注意,同一个场景下hotspotname属性不能重复

    <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来增或减,实现athatv的变化(这两个值是自动获取的)。

如果用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的入门指南就到这里啦,如有写错或不合理的地方,欢迎指正。

参考资料:
krpano.com/docu
www.krpano360.com