OBJ로 만든 GIS 데이터를 다운로드하여 Unity에 표시

5355 단어 GISUnity

소개



12/3에 만든 OBJ 파일을 Unity에 표시해 봅니다. OBJ 파일은 Unity Editor가 표준으로 읽을 수있는 형식이므로,
그대로 Asset으로 로드하면 특별히 문제가 되지 않습니다. 적당한 머티리얼만 설정해 버리면 게임내의 에셋으로서 사용할 수 있습니다.

그렇다면 흥미롭지 않으므로 여기에서는 런타임에 다운로드하여 현장에 구축한다는 것을 해보겠습니다.
그렇다고는 해도, 이것도 동적으로 메쉬의 작성과, OBJ의 퍼스등의 합작 기술이므로 큰 일 없네요.
이 근처는 느슨하게 흘려 가고 싶은 느낌으로.

다운로드하고 표시



다운로드



UnityWebRequest에서 가져옵니다. 샘플 코드 그대로 감.
    IEnumerator objLoad()
    {
        UnityWebRequest req = UnityWebRequest.Get("URL to FILE/test.obj");
        yield return req.SendWebRequest();

여담입니다만, github의 gist는, 이런 Web 액세스의 테스트용의 파일 두는 장소로서 편리하네요. 이번에도 그런 느낌으로 시험하고 있습니다.

OBJ 퍼스



한 줄씩 읽어 갑니다.
정점 좌표와 정점 인덱스를 그대로 읽어 배열에 격납합니다.
        List<Vector3> vertices = new List<Vector3>();
        List<int> indices = new List<int>();

        if(req.isHttpError || req.isNetworkError){
            Debug.Log(req.error);
        } else {
            var strs = req.downloadHandler.text.Split('\n');
            foreach(string str in strs){
                if(str.StartsWith("g")){

                } else if(str.StartsWith("v")){
                    var tokens = str.Split(' ');
                    vertices.Add(new Vector3(float.Parse(tokens[1]), float.Parse(tokens[2]), float.Parse(tokens[3])));
                } else if(str.StartsWith("f")){
                    var tokens = str.Split(' ');
                    indices.Add(int.Parse(tokens[1])-1);
                    indices.Add(int.Parse(tokens[2])-1);
                    indices.Add(int.Parse(tokens[3])-1);
                }
            }
        }
        Debug.Log(vertices.Count);
        Debug.Log(indices.Count);

만약을 위해, 마지막으로 읽어들인 정점수와 인덱스수를 디버그 로그했습니다.
이런 것을 파서라고 말할 수 있을까 하는 정도 간단하네요.
덧붙여서, 인덱스의 read로 -1 하고 있는 것은, OBJ 파일이 1 오리진으로, Unity가 0 오리진이기 때문입니다.
처음에는 여기에서 멈췄다.
배열이 1부터 시작하는 것이 이상하다고 이야기는 있을지도 모릅니다만, 세상 그러한 언어도 있거나 합니다.
예를 들면 Lua라든지군요.
또, 옛 N88-BASIC에는, 배열의 오리진을 0,1로 변경하는 OPTION BASE란 명령이 있었습니다. VB에도 있는 것 같습니다.

정점 설정



방금 저장한 정점 좌표와 정점 인덱스의 배열을 설정합니다.
        var meshFilter = gameObject.GetComponent<MeshFilter>();
        var mesh = new Mesh();

        mesh.SetVertices(vertices);
        mesh.SetIndices(indices.ToArray(),MeshTopology.Triangles,0);
        mesh.RecalculateNormals();
        mesh.RecalculateBounds();
        meshFilter.mesh = mesh;
    }

마지막으로, 법선과 바운딩 박스의 재계산도 시킵니다.
이것을 같은 GameObject에 첨부된 MeshFilter 컴퍼넌트에 투입해 끝입니다.

코루틴 호출



테스트이므로, 빨리 Start로부터 코루틴 호출해 버려, 표시시켜 버립니다.
    void Start()
    {
        StartCoroutine(objLoad());
    }

전체 스크립트


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;

public class OBJImport : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        StartCoroutine(objLoad());
    }

    // Update is called once per frame
    void Update()
    {

    }

    IEnumerator objLoad()
    {
        UnityWebRequest req = UnityWebRequest.Get("https://gist.githubusercontent.com/oho-sugu/875b568ee5bcfdaabbd08d313f07fb0d/raw/682a8439c9ec3296d7e90e30b3dc9aa6e4e53202/test.obj");
        yield return req.SendWebRequest();

        List<Vector3> vertices = new List<Vector3>();
        List<int> indices = new List<int>();

        if(req.isHttpError || req.isNetworkError){
            Debug.Log(req.error);
        } else {
            var strs = req.downloadHandler.text.Split('\n');
            foreach(string str in strs){
                if(str.StartsWith("g")){

                } else if(str.StartsWith("v")){
                    var tokens = str.Split(' ');
                    vertices.Add(new Vector3(float.Parse(tokens[1]), float.Parse(tokens[2]), float.Parse(tokens[3])));
                } else if(str.StartsWith("f")){
                    var tokens = str.Split(' ');
                    indices.Add(int.Parse(tokens[1])-1);
                    indices.Add(int.Parse(tokens[2])-1);
                    indices.Add(int.Parse(tokens[3])-1);
                }
            }
        }
        Debug.Log(vertices.Count);
        Debug.Log(indices.Count);

        var meshFilter = gameObject.GetComponent<MeshFilter>();
        var mesh = new Mesh();

        mesh.SetVertices(vertices);
        mesh.SetIndices(indices.ToArray(),MeshTopology.Triangles,0);
        mesh.RecalculateNormals();
        mesh.RecalculateBounds();
        meshFilter.mesh = mesh;
    }
}

이것을 적당히 히에라루키에 만든 Cube 나름대로 부착하여 실행합시다.

결과





중간에 높은 빌딩군이 롯폰기 힐즈군요.
이제 힐즈 GO를 만들 수 있습니다

도전



파일 사이즈도 커지므로, 바이너리 포맷을 생각하는 것이 좋다고 생각합니다.

좋은 웹페이지 즐겨찾기