Unity制作360全景图

Unity制作360全景图

天空盒还是球?

本来以为全景图很简单,把全景图纹理设置为“Cube”类型,弄个天空盒材质附上去,然后摄像机弄个角度旋转脚本,就完事了。。。但是,用天空盒渲染全景图的话,没有办法缩放。。
于是,还是得用球体啊。。用Blender建一个球,法线反转一下,就是让球里面的纹理是正面,然后摄像机摆到球中心,球的材质,shader选Unlit/Texture即可。

如何缩放呢?

第一直觉,以为把球体放大和缩小,就能实现缩放,呵呵。。然而并不可以,只要球体x、y、z三轴是等比例缩放,无论缩放系数如何,看到的效果是完全一样的。
所以,正确缩放的方法是,改变摄像机的位置。。但是摄像机旋转时,要绕中心点旋转,相当于,摄像机摆在一个较小的同心球的球面上,转动或者缩放该同心球,就能得到对应的缩放效果。也就是说,缩放就是改变摄像机运行轨道的半径,如下图所示:
Unity制作360全景图_第1张图片

上代码

using System;
using UnityEngine;
using UnityEngine.EventSystems;

public class Camera360Controller : MonoBehaviour
{
	// 缩放系数
    public static float ZoomRate = 0.5f;
	// 最远距离(摄像机轨道最大半径)
    public static float MaxDistance = 2f;
    // 旋转系数
    public static float RotationRate = 0.5f;
    // 最小俯仰角度限制
    public static float BottomClampAngle = -80;
    // 最大俯仰角度限制
    public static float TopClampAngle = 80;
    
    private Vector2 lastMouse;
    private float pitch;
    private float distance;
    private float lerp = 1;
    
    // 调用该函数,在0.5秒内重置摄像机初始角度和位置(回到中心点)
    public void ResetCamera()
    {
        lerp = 0;
    }

    private void FixedUpdate()
    {
    	// 在0.5秒内摄像机回到中心点
        if (lerp < 1f)
        {
            lerp = Mathf.Clamp(lerp + 2 * Time.fixedDeltaTime, 0f, 1f);  
            var trans = transform;
            trans.position = Vector3.Lerp(trans.position, Vector3.zero, lerp);
            trans.rotation = Quaternion.Lerp(trans.rotation, Quaternion.identity, lerp);
            distance = Mathf.Lerp(distance, 0, lerp);
            pitch = Mathf.Lerp(distance, 0, lerp);
        }
    }

    private void Update()
    {
    	// 摄像机在重置过程中,不接受用户其他操作
        if (lerp < 1f)
            return;
        
        // 兼顾触控屏和鼠标
        switch (Input.touchCount)
        {
            case 1:
            {
            	// 单指触控,旋转摄像机视角,手指在UI上时放弃
                var touch = Input.GetTouch(0);
                if (EventSystem.current.IsPointerOverGameObject(touch.fingerId))
                    return;
                
                if (touch.phase == TouchPhase.Moved)
                {
                    CameraRotation(touch.deltaPosition);
                }

                break;
            }
            case 2:
            {
            	// 双指触控,移动相机,缩放全景图
                var touch1 = Input.GetTouch(0);
                var touch2 = Input.GetTouch(1);

				// 如果手指在UI上,放弃
                if (EventSystem.current.IsPointerOverGameObject(touch1.fingerId) ||
                    EventSystem.current.IsPointerOverGameObject(touch2.fingerId))
                    return;

                var oldpos1 = touch1.position - touch1.deltaPosition;
                var oldpos2 = touch2.position - touch2.deltaPosition;

                float olddis = (oldpos1 - oldpos2).magnitude;
                float nowdis = (touch1.position - touch2.position).magnitude;
				
				// 计算新的摄像机轨道半径
                distance = Mathf.Clamp(distance + (nowdis - olddis) * ZoomRate, -MaxDistance, MaxDistance);
                
                var trans = transform;
                trans.position = trans.forward * distance;
                break;
            }
            default:
            {
            	// 检测鼠标操作,点在UI上,放弃
                if (EventSystem.current.IsPointerOverGameObject())
                    return;
                
                if (Input.GetMouseButtonDown(1))
                {
                    lastMouse = Input.mousePosition;
                }
                else if (Input.GetMouseButton(1))
                {
                	// 按下鼠标右键,旋转视角
                    Vector2 curMouse = Input.mousePosition;
                    CameraRotation(curMouse - lastMouse);
                    lastMouse = curMouse;
                }
                else
                {
                	// 检测鼠标滚轮,计算新的摄像机轨道半径,进行缩放
                    float w = -Input.GetAxis("Mouse ScrollWheel");
                    distance = Mathf.Clamp(distance + w * ZoomRate, -MaxDistance, MaxDistance);

                    var trans = transform;
                    trans.position = trans.forward * distance;
                }

                break;
            }
        }
    }

	// 旋转摄像机
    private void CameraRotation(Vector2 delta)
    {
        var trans = transform;
        var rot = trans.localEulerAngles;
        pitch = ClampAngle(pitch + delta.y * RotationRate, BottomClampAngle, TopClampAngle );
        rot.x = pitch;
        rot.y -= delta.x * RotationRate;
        trans.localEulerAngles = rot;

        trans.position = trans.forward * distance;
    }

	// 角度裁剪,保持-360~360之间。
    private static float ClampAngle(float lfAngle, float lfMin, float lfMax)
    {
        switch (lfAngle)
        {
            case < -360f:
                lfAngle += 360f;
                break;
            case > 360f:
                lfAngle -= 360f;
                break;
        }

        return Mathf.Clamp(lfAngle, lfMin, lfMax);
    }
}

其他小功能

  • 根据配置文件读取地面箭头指示器
  • 根据配置文件读取墙面热点
private void LoadSceneData()
{
    XmlElement node = Config.GetNode("Scenes");
    if (node != null)
    {
        foreach (XmlNode child in node.ChildNodes)
        {
            if( child.NodeType != XmlNodeType.Element )
                continue;
            if( child is not XmlElement element )
                continue;
            if(string.Compare(element.Name, "Room", StringComparison.OrdinalIgnoreCase) != 0 )
                continue;
            
            string key = element.GetAttributeString("name");
            if( string.IsNullOrWhiteSpace(key) || _rooms.ContainsKey(key))
                continue;

            string hdr = element.GetAttributeString("hdr");
            if(!TextureFileLoader.IsPictureFile(hdr))
                continue;
            string thum = element.GetAttributeString("thum");
            if(!TextureFileLoader.IsPictureFile(thum))
                continue;

            Texture2D htex = TextureFileLoader.GetTexture(hdr);
            if(htex == null)
                continue;
            Texture2D ttex = TextureFileLoader.GetTexture(thum);
            if( ttex==null)
                continue;

            RoomThumItem item = Instantiate(_thumItemPrefab, _roomListContent);
            item.key = key;
            item.texture = ttex;

            GameObject indicator = null;
            GameObject hotpoints = null;
            
            foreach (XmlNode arrowsNode in element.ChildNodes)
            {
                if (arrowsNode.NodeType != XmlNodeType.Element)
                    continue;
                if (arrowsNode is not XmlElement arrowElement)
                    continue;
                if (string.Compare("Indicator", arrowElement.Name, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    string dir = arrowElement.GetAttributeString("type", "F").ToUpper();
                    Vector3 pos = arrowElement.GetAttribute("pos", new Vector3(-3, -2, 0));
                    var angle = arrowElement.GetAttribute("rot", 0f);
                    var enter = arrowElement.GetAttributeString("enter");
                    
                    var arrow = dir switch
                    {
                        "F" => Instantiate(_forwordArrowPrefab),
                        "L" => Instantiate(_leftArrowPrefab),
                        "R" => Instantiate(_rightArrowPrefab),
                        _ => null
                    };
                    
                    if (arrow != null)
                    {
                        if (indicator == null)
                        {
                            indicator = new GameObject($"R_{key}")
                            {
                                transform =
                                {
                                    position = Vector3.zero
                                }
                            };
                        }

                        arrow.target = enter;
                        var arrTrans = arrow.transform;
                        arrTrans.position = pos;
                        arrTrans.eulerAngles = new Vector3(0, angle, 0);
                        arrTrans.SetParent(indicator.transform);
                    }
                }

                if (string.Compare("Hotpoint", arrowElement.Name, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    var hpos = arrowElement.GetAttribute("pos", Vector3.zero);
                    var url = arrowElement.GetAttributeString("url");

                    if (!TextureFileLoader.IsPictureFile(url) && !TextureFileLoader.IsVideoFile(url))
                        continue;
                    
                    if (hotpoints == null)
                    {
                        hotpoints = Instantiate(_HPContentsPrefab, _hotpointsContent);
                    }

                    var hp = Instantiate(_hotPointPrefab, hotpoints.transform);
                    hp.title = arrowElement.GetAttributeString("title", "None Caption");
                    hp.worldPos = hpos;
                    hp.url = url;
                }
            }
            
            if (indicator != null)
                indicator.SetActive(false);
                
            if(hotpoints != null)
                hotpoints.SetActive(false);

            _rooms.Add(key, new SceneNode()
            {
                texture = htex,
                arrows = indicator,
                hotpoints = hotpoints
            });
        }
    }

    return startNode;
}

配置文件如下:


<C360>
  <StartPage rotaSpeed="10" rotaRate="2" />
  <Camera rotaRate="0.1" zoomRate="-1" maxDistance="3" minAngle="-80" maxAngle="80" switchTime="1.5" />
  <Scenes default="01">
    <Room name="01" hdr="e:/temp/zt/01.jpg" thum="e:/temp/zt/t01.png">
      <Indicator type="F" pos="-3,-0.9,-1.6" rot="-90" enter="02" />
    Room>
    <Room name="02" hdr="e:/temp/zt/02.jpg" thum="e:/temp/zt/t02.png">
      <Indicator type="R" pos="0.12,-0.9,3.7" enter="03" />
      <Hotpoint title="智慧城市" pos="5,0.9,0.2" url="e:/temp/back.mp4" />
      <Hotpoint title="管廊百科" pos="1.4,0.5,-5" url="e:/temp/back.mp4" />
      <Hotpoint title="发展历程" pos="-4,-0.8,-2.5" url="e:/temp/back.mp4" />
      <Hotpoint title="政策指引" pos="-5,0.3,0.22" url="e:/temp/back.mp4" />
      <Hotpoint title="建设意义" pos="-4,-0.8,2.8" url="e:/temp/back.mp4" />
    Room>
    <Room name="03" hdr="e:/temp/zt/03.jpg" thum="e:/temp/zt/t03.png">
      <Indicator type="F" pos="-0.5,-1,4" enter="04" />
      <Indicator type="R" pos="1,-1.6,4" enter="05" />
      <Indicator type="F" pos="3,-3,-0.5" rot="90" enter="02" />
      <Hotpoint title="经济便捷" pos="0.1,0.38,-5" url="e:/temp/back.mp4" />
      <Hotpoint title="功能分级" pos="-4.5,0.3,-1" url="e:/temp/back.mp4" />
      <Hotpoint title="先进技术" pos="-4,0.5,2.2" url="e:/temp/back.mp4" />
    Room>
    <Room name="04" hdr="e:/temp/zt/04.jpg" thum="e:/temp/zt/t04.png">
      <Indicator type="F" pos="3.3,-3,-1.36" rot="90" enter="05" />
      <Indicator type="L" pos="1.25,-1,-3.5" rot="180" enter="02" />
      <Hotpoint title="先进技术" pos="-5,0.3,-4" url="e:/temp/back.mp4" />
      <Hotpoint title="筑梦未来" pos="-4,0.3,3" url="e:/temp/back.mp4" />
    Room>
    <Room name="05" hdr="e:/temp/zt/05.jpg" thum="e:/temp/zt/t05.png">
      <Indicator type="L" pos="0.3,-1,-4" rot="180" enter="03" />
      <Hotpoint title="智慧运维" pos="-4,0,-0.2" url="e:/temp/back.mp4" />
    Room>
  Scenes>
C360>

读取XML文件的配置,已经封装好了一个类,请参看
Unity配置文件封装

你可能感兴趣的:(Unity,unity,游戏引擎,全景图,360图,天空球)