Unity: ส่งออก Terrian เป็นไฟล์ Obj

Step 1.
ใช้ Unity สร้างวัตถุ Terrian ขึ้นมา

Step 2.
ในโฟลเดอร์ "Assets" ให้สร้างโฟลเดอร์ชื่อว่า "Editor" ขึ้นมา

Step 3.
สร้าง C# Script ชื่อว่า "ExportTerrain.cs" ไว้ที่
Assets>Editor>ExportTerrain.cs

Step 4.
แทนที่สคริปต์ของ ExportTerrain.cs ด้วยโค้ดด้านล่างนี้

// Converted from UnityScript to C# at http://www.M2H.nl/files/js_to_c.php - by Mike Hergaarden
// C # manual conversion work by Yun Kyu Choi

using UnityEngine;
using UnityEditor;
using System;
using System.Collections;
using System.IO;
using System.Text;

enum SaveFormat { Triangles, Quads }
enum SaveResolution { Full=0, Half, Quarter, Eighth, Sixteenth }

class ExportTerrain : EditorWindow
{
   SaveFormat saveFormat = SaveFormat.Triangles;
   SaveResolution saveResolution = SaveResolution.Half;

   static TerrainData terrain;
   static Vector3 terrainPos;

   int tCount;
   int counter;
   int totalCount;
   int progressUpdateInterval = 10000;

   [MenuItem("Terrain/Export To Obj...")]
   static void Init()
   {
      terrain = null;
      Terrain terrainObject = Selection.activeObject as Terrain;
      if (!terrainObject)
      {
         terrainObject = Terrain.activeTerrain;
      }
      if (terrainObject)
      {
         terrain = terrainObject.terrainData;
         terrainPos = terrainObject.transform.position;
      }

      EditorWindow.GetWindow<ExportTerrain>().Show();
   }

   void OnGUI()
   {
      if (!terrain)
      {
         GUILayout.Label("No terrain found");
         if (GUILayout.Button("Cancel"))
         {
            EditorWindow.GetWindow<ExportTerrain>().Close();
         }
         return;
      }
      saveFormat = (SaveFormat) EditorGUILayout.EnumPopup("Export Format", saveFormat);

      saveResolution = (SaveResolution) EditorGUILayout.EnumPopup("Resolution", saveResolution);

      if (GUILayout.Button("Export"))
      {
         Export();
      }
   }

   void Export()
   {
      string fileName = EditorUtility.SaveFilePanel("Export .obj file", "", "Terrain", "obj");
      int w = terrain.heightmapWidth;
      int h = terrain.heightmapHeight;
      Vector3 meshScale = terrain.size;
      int tRes = (int)Mathf.Pow(2, (int)saveResolution );
      meshScale = new Vector3(meshScale.x / (w - 1) * tRes, meshScale.y, meshScale.z / (h - 1) * tRes);
      Vector2 uvScale = new Vector2(1.0f / (w - 1), 1.0f / (h - 1));
      float[,] tData = terrain.GetHeights(0, 0, w, h);

      w = (w - 1) / tRes + 1;
      h = (h - 1) / tRes + 1;
      Vector3[] tVertices = new Vector3[w * h];
      Vector2[] tUV = new Vector2[w * h];

      int[] tPolys;

      if (saveFormat == SaveFormat.Triangles)
      {
         tPolys = new int[(w - 1) * (h - 1) * 6];
      }
      else
      {
         tPolys = new int[(w - 1) * (h - 1) * 4];
      }

      // Build vertices and UVs
      for (int y = 0; y < h; y++)
      {
         for (int x = 0; x < w; x++)
         {
            tVertices[y * w + x] = Vector3.Scale(meshScale, new Vector3(-y, tData[x * tRes, y * tRes], x)) + terrainPos;
            tUV[y * w + x] = Vector2.Scale( new Vector2(x * tRes, y * tRes), uvScale);
         }
      }

      int  index = 0;
      if (saveFormat == SaveFormat.Triangles)
      {
         // Build triangle indices: 3 indices into vertex array for each triangle
         for (int y = 0; y < h - 1; y++)
         {
            for (int x = 0; x < w - 1; x++)
            {
               // For each grid cell output two triangles
               tPolys[index++] = (y * w) + x;
               tPolys[index++] = ((y + 1) * w) + x;
               tPolys[index++] = (y * w) + x + 1;

               tPolys[index++] = ((y + 1) * w) + x;
               tPolys[index++] = ((y + 1) * w) + x + 1;
               tPolys[index++] = (y * w) + x + 1;
            }
         }
      }
      else
      {
         // Build quad indices: 4 indices into vertex array for each quad
         for (int y = 0; y < h - 1; y++)
         {
            for (int x = 0; x < w - 1; x++)
            {
               // For each grid cell output one quad
               tPolys[index++] = (y * w) + x;
               tPolys[index++] = ((y + 1) * w) + x;
               tPolys[index++] = ((y + 1) * w) + x + 1;
               tPolys[index++] = (y * w) + x + 1;
            }
         }
      }

      // Export to .obj
      StreamWriter sw = new StreamWriter(fileName);
      try
      {

         sw.WriteLine("# Unity terrain OBJ File");

         // Write vertices
         System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
         counter = tCount = 0;
         totalCount = (tVertices.Length * 2 + (saveFormat == SaveFormat.Triangles ? tPolys.Length / 3 : tPolys.Length / 4)) / progressUpdateInterval;
         for (int i = 0; i < tVertices.Length; i++)
         {
            UpdateProgress();
            StringBuilder sb = new StringBuilder("v ", 20);
            // StringBuilder stuff is done this way because it's faster than using the "{0} {1} {2}"etc. format
            // Which is important when you're exporting huge terrains.
            sb.Append(tVertices[i].x.ToString()).Append(" ").
               Append(tVertices[i].y.ToString()).Append(" ").
               Append(tVertices[i].z.ToString());
            sw.WriteLine(sb);
         }
         // Write UVs
         for (int i = 0; i < tUV.Length; i++)
         {
            UpdateProgress();
            StringBuilder sb = new StringBuilder("vt ", 22);
            sb.Append(tUV[i].x.ToString()).Append(" ").
               Append(tUV[i].y.ToString());
            sw.WriteLine(sb);
         }
         if (saveFormat == SaveFormat.Triangles)
         {
            // Write triangles
            for (int i = 0; i < tPolys.Length; i += 3)
            {
               UpdateProgress();
               StringBuilder sb = new StringBuilder("f ", 43);
               sb.Append(tPolys[i] + 1).Append("/").Append(tPolys[i] + 1).Append(" ").
                  Append(tPolys[i + 1] + 1).Append("/").Append(tPolys[i + 1] + 1).Append(" ").
                  Append(tPolys[i + 2] + 1).Append("/").Append(tPolys[i + 2] + 1);
               sw.WriteLine(sb);
            }
         }
         else
         {
            // Write quads
            for (int i = 0; i < tPolys.Length; i += 4)
            {
               UpdateProgress();
               StringBuilder sb = new StringBuilder("f ", 57);
               sb.Append(tPolys[i] + 1).Append("/").Append(tPolys[i] + 1).Append(" ").
                  Append(tPolys[i + 1] + 1).Append("/").Append(tPolys[i + 1] + 1).Append(" ").
                  Append(tPolys[i + 2] + 1).Append("/").Append(tPolys[i + 2] + 1).Append(" ").
                  Append(tPolys[i + 3] + 1).Append("/").Append(tPolys[i + 3] + 1);
               sw.WriteLine(sb);
            }
         }
      }
      catch(Exception err)
      {
         Debug.Log("Error saving file: " + err.Message);
      }
      sw.Close();

      terrain = null;
      EditorUtility.DisplayProgressBar("Saving file to disc.", "This might take a while...", 1f);
      EditorWindow.GetWindow<ExportTerrain>().Close();    
      EditorUtility.ClearProgressBar();
   }

   void UpdateProgress()
   {
      if (counter++ == progressUpdateInterval)
      {
         counter = 0;
         EditorUtility.DisplayProgressBar("Saving...", "", Mathf.InverseLerp(0, totalCount, ++tCount));
      }
   }
}


Step 5.
รอสักครู่จะพบว่าในแถบเมนูด้านบนจะมีหัวข้อ Terrian โผล่ออกมา โดยให้กดบนหัวข้อนั้นแล้วเลือกที่ "Export To Obj.."

Step 6.
จากนั้นเมื่อปรับรายละเอียดแล้วก็สั่งปุ่ม Export ได้เลย


Credit: http://wiki.unity3d.com/index.php?title=TerrainObjExporter

Post Status




3 ความคิดเห็น:

Athlons Thunderbird กล่าวว่า...

สอบถามหน่อยครับ
เราจะไม่ให้มี link archieve.html ประจำเดือนของบทความได้หรือไม่ครับ? เพราะมันชอบสร้างมาเอง เวลา search google มันจะไม่ชี้ไปที่บทความนั้นๆเลย ทำให้คนค้นหางง

Max กล่าวว่า...

ถ้าหากว่าหมายถึง Gadget ประเภท "คลังบทความ (Archive)" ก็สามารถแก้ไขได้ที่เมนูบล็อกในหัวข้อ "รูปแบบ" ครับ

Athlons Thunderbird กล่าวว่า...

แก้ไขยังไงครับ
หรือไม่ให้มันแสดง
ขอบคุณครับ

แสดงความคิดเห็น