Help us understand the problem. What is going on with this article?

簡単に地形生成してUNITYに持ってくる!

簡単に地形生成してUNITYに持ってくる!

こういうソフトがありまして。
https://3dnchu.com/archives/terresculptor-2-free/
リアルな地形を生成するソフトです。

image.png

TerreSculptor2というフリーのソフトなんですが。
UE4に取り込むフローは書いてあるけど、UNITYに取り込むフローは書いてない!

ということで作りました。

下記のファイルを作って、Editorフォルダ内に入れてください。

TSMapImporter.cs
//#define TSMAP_SUPPORT_COMPRESS

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;
using UnityEngine;
using UnityEditor;
#if TSMAP_SUPPORT_COMPRESS
using Ionic.Zlib;
#endif

public class TSMapImporter
{
    [MenuItem("Assets/ImportTSMap")]
    public static void ImportTSMap()
    {
        string folderPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments);
        var openPath = EditorUtility.OpenFilePanel("open TerreSculptor map", folderPath, "TSmap");
        var td = importTSMap(openPath);

        // create object
        if (td != null)
        {
            //            Terrain.CreateTerrainGameObject(td);

            int instanceID = Selection.activeInstanceID;
            string path = AssetDatabase.GetAssetPath(instanceID);
            string fullPath = System.IO.Path.GetFullPath(path);

            var exportFileName = EditorUtility.SaveFilePanelInProject("save Terrain data", "TerrainFromTS", "asset", "Terrain data", fullPath);
            AssetDatabase.CreateAsset(td, exportFileName);
            AssetDatabase.SaveAssets();
        }
    }

    static TerrainData importTSMap(string openPath)
    {
        string tsName = Path.GetFileNameWithoutExtension(openPath);
        using (var fs = new FileStream(openPath, FileMode.Open))
        {
            using (var br = new BinaryReader(fs))
            {
                var headFileType = br.ReadBytes(16);    // TerreSculptor
                var headDataType = br.ReadBytes(16);    // TSmap
                var version = br.ReadInt32();   // 0x0100
                var width = br.ReadInt32();   // width
                var length = br.ReadInt32();   // length
                var isCompress = br.ReadInt32();// isCompress 1:true 0:false
                br.ReadBytes(16);   // ?

                // check header string
                string strFileType = Encoding.UTF8.GetString(headFileType).TrimEnd('\0');
                string strDataType = Encoding.UTF8.GetString(headDataType).TrimEnd('\0');
                if ((strFileType == "TerreSculptor")
                    && (strDataType == "TSmap"))
                {
                    float[,] heightMap = new float[width + 1, length + 1];
                    float maxHeight = 0f;
                    if (isCompress != 0)
                    {
#if TSMAP_SUPPORT_COMPRESS
                        // 圧縮バージョン
                        int numUnknown = br.ReadInt32();
                        int numUnknown2 = br.ReadInt32();
                        int numUnknown3 = br.ReadInt16();

                        // ZIP展開
                        int zipLen = (int)(fs.Length - fs.Position);
                        byte[] buf = new byte[width * length * 4];
                        using (DeflateStream s = new DeflateStream(fs, CompressionMode.Decompress))
                        {
                            s.Read(buf, 0, buf.Length);
                        }

                        // ハイトの設定
                        for (int y = 0; y < length; ++y)
                        {
                            for (int x = 0; x < width; ++x)
                            {
                                int idx = (x + (y * width))*4;
                                heightMap[x, y] = BitConverter.ToSingle(buf,idx);
                                maxHeight = Mathf.Max(maxHeight, heightMap[x, y]);
                            }
                        }
#else
                        Debug.Log("NOT SUPPORT COMPRESS TSMAP");
                        return null;
#endif
                    }
                    else
                    {
                        // ハイトの設定
                        for (int y = 0; y < length; ++y)
                        {
                            for (int x = 0; x < width; ++x)
                            {
                                heightMap[x, y] = br.ReadSingle();
                                maxHeight = Mathf.Max(maxHeight, heightMap[x, y]);
                            }
                        }
                    }
                    // 正規化する
                    for (int y = 0; y < length; ++y)
                    {
                        for (int x = 0; x < width; ++x)
                        {
                            heightMap[x, y] /= maxHeight;
                        }
                    }
                    // add 1 pixel
                    // UNITYは、2のべき乗の数+1でハイトマップが作られるので合わせる
                    for (int x = 0; x < width; ++x)
                    {
                        heightMap[x, length] = heightMap[x, length - 1];
                    }
                    for (int x = 0; x < length + 1; ++x)
                    {
                        heightMap[width, x] = heightMap[width - 1, x];
                    }

                    // modifyResolution
                    int heightRes = Mathf.ClosestPowerOfTwo(Mathf.Max(width, length)) + 1;
                    if ((heightMap.GetLength(0) != heightRes)
                        || (heightMap.GetLength(1) != heightRes))
                    {
                        heightMap = modifyResolution(heightMap, heightRes, heightRes);
                    }

                    // スケール(適当)
                    float scale = 10f / (heightRes-1);

                    // create TerrainData
                    TerrainData td = new TerrainData();
                    td.size = new Vector3(width * scale, maxHeight, length * scale);
                    td.heightmapResolution = heightRes;
                    td.SetDetailResolution(width, 16);
                    td.SetHeights(0, 0, heightMap);

                    return td;
                }
            }
        }
        return null;
    }

    // 解像度を変更
    static float[,] modifyResolution(float[,] src,int targetWidth,int targetLength)
    {
        int srcW = src.GetLength(0);
        int srcL = src.GetLength(1);
        float[,] dst = new float[targetWidth, targetLength];
        for(int y=0;y<targetLength;++y)
        {
            for (int x = 0; x < targetWidth; ++x)
            {
                float fx = (x * srcW) / (float)targetWidth;
                float fy = (y * srcL) / (float)targetLength;
                int x0 = Mathf.Min(Mathf.CeilToInt(fx), srcW - 1);
                int y0 = Mathf.Min(Mathf.CeilToInt(fy), srcL - 1);
                int x1 = Mathf.Min(x0 + 1, srcW-1);
                int y1 = Mathf.Min(y0 + 1, srcL-1);
                float ax = fx - x0;
                float ay = fy - y0;
                // four sample
                var s0 = Mathf.Lerp(src[x0, y0],src[x1, y0], fx);
                var s1 = Mathf.Lerp(src[x0, y1], src[x1, y1], fx);
                dst[x, y] = Mathf.Lerp(s0, s1, fy);
            }
        }
        return dst;
    }
}

そうすると、TerreSculptor2から書き出せるTSmapファイルを読み込んでUNITYにインポートできるようになります。

使い方

TerreSculptorからFile->Save Terrain Asを選び、TSmapファイルを書き出します。
image.png

UNITYを立ち上げ、上記のスクリプトをEditorフォルダに入れると、アセットが置いてある、Projectウィンドウで右クリックすると、ImportTSMapというメニューが追加されています。

image.png

それを実行して、TSmapファイルを選び、続いて保存先を選ぶとTerrainデータが作られます。
Terrainデータをシーン上にドラッグアンドドロップすると、地形をシーン上に配置することができます。

image.png

TSmapは圧縮フォーマットもありますが、それに対応したい場合は、DotNetZipが必要になります。
https://github.com/DinoChiesa/DotNetZip
これを入れていれば、ZIP圧縮、解凍ができるようになります。
入れた後に、一行目のdefine TSMAP_SUPPORT_COMPRESSを定義してもエラーがでなくなります。

見た目をよりよくしよう

上記のキャプチャー画像いまいちクォリティーが低く見えます。
もう少し、調整してみましょう。

image.png
Terrainのインスペクタから、Pixel Errorの値を1にしてみましょう。
そうすると、ポリゴン数が多くなりクォリティーが上がります。

シェーダーも書いてみます。

TerrainGradient.shader
Shader "Custom/TerrainGradient"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex("Albedo (RGB)", 2D) = "white" {}
        _ColorsetTex ("Colorset (RGB)", 2D) = "white" {}
        _Height("Height", Range(0,1000)) = 100.0
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic("Metallic", Range(0,1)) = 0.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _MainTex;

        sampler2D _ColorsetTex;
        half _Height;

        struct Input
        {
            float2 uv_MainTex;
            float3 worldPos;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;

        // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
        // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
        // #pragma instancing_options assumeuniformscaling
        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            c = c * tex2D(_ColorsetTex, float2(0, IN.worldPos.y / _Height));
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

_ColorsetTexに縦方向へのグラデーションテクスチャを設定すると高さに応じて色が変わるようにしました。
_Heightパラメータの操作で、高さに応じたUVの調整ができます。

ColorsetTexの画像は、下記の画像を参考に作ります。
TerraSculpterのMaterialボタンを押します。
その右のMaterial typeをColorsetにして、その下のViewボタンを押します。
ダイアログが現れるので、Colorsetを選んでPrintScreenを押すなどしてキャプチャーします。
ペイントソフトなどで、切り抜いて、サイズは1x256などにして保存します。
これをUNITYで
ColorsetTexに設定してください。

image.png

テクスチャのWrapModeはClampにしておきましょう。
UNITYでマテリアルを作って、上記シェーダーを設定し、このテクスチャを_ColorsetTexの項目に設定します。
Terrainにマテリアルを設定すると、下記のように表示されるようになりました。

image.png

少しライトや、Smoothnessなどを調整しています。

image.png

見た目も、ほぼ同じにできたのではないでしょうか。

おわりに

ファイルフォーマットが結構簡単に解析できたので、対応しました。

ということで、UE4だけでなく、UNITYでも容易に扱えます。

3dnchu様の記事でも、この記事を紹介していただけました!
https://3dnchu.com/archives/terresculptor-2-free/
ありがとうございます。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away