Unity3D에서 원하는 곳의 CubeMap을 생성하는 방법입니다.

 

C#으로 큐브맵을 생성하는 스크립트를 작성할 것이다.

메뉴에 버튼을 생성해 팝업 에디트 창을 띄울 것이기 때문에, Editor라는 폴더에 스크립트 파일을 넣어야 한다.


소스(한쪽면만 제대로 찍히는 문제가)

using UnityEngine;
using UnityEditor;
using System.Collections;

//스크립트를 팝업 타입의 에디터 창으로 만들기 위해, ScriptableWizard 클래스로부터 상속받도록 만들어야한다.
//ScriptableWizard는 스크립트에서 사용할수 있는 low-level 함수를 제공할 것이다.
public class GenerateStaticCubemap : ScriptableWizard {
 //새 큐브 맵과 그 위치를 저장할 수 있도록 public 변수를 추가한다.
 public Transform renderPosition;
 public Cubemap cubemap;

 //이 함수는 마법사를 처음 열 때나, 사용자에 의해 GUI가 변경될 때마다 호출된다.
 //그러므로 사용자가 작업을 위해 마법사에게 애셋을 제공했는지 확인 할 수 있는 좋은 함수다.
 //만약 변수가 큐브 맵이나 트랜스폼을 갖고 있지 않다면, Boolean 타입인 isValid를 false로 설정하고 이후 함수에 접근하지 못하게 한다.
 void OnWizardUpdate()
 {
  helpString = "Select transform to render" + "from and cubemap to render into";
  if(renderPosition != null && cubemap != null)
  {
   isValid = true;
  }
  else
  {
   isValid = false;
  }
 }

  //만약 Boolean 타입인 isValid가 true라면, 마법사는 OnWizardCreate()함수를 호출할 것이다.
  //이 함수는 새 카메라를 생성한 뒤, 제공된 트랜스폼을 이용해 위치를 지정하고 RenderToCubemap() 함수를 사용해 큐브맵을 리턴한다.

  void OnWizardCreate()
  {
   //랜더링을 위한 임시 카메라를 생성한다.
   GameObject go = new GameObject("CubeCam", typeof(Camera));

   //카메라를 랜더링 위치에 놓는다.
   go.transform.position = renderPosition.position;
   go.transform.rotation = Quaternion.identity;

   //큐브맵을 랜더링 한다.
   go.camera.RenderToCubemap(cubemap);

   //임시 카메라를 제거한다.

   //DestoryImmediate가 에러 발생하여 일단 숨김
   //DestoryImmediate(go);
  }

  //마지막으로, 유니티 에디터의 메뉴 옵션에서 마법사를 활성화하기 위해 호출해야한다.
  //GenerateStaticCubemap 클래스에 다음 코드를 입력한다.

  [MenuItem("JWShader/Render Cubemap")]
  static void RenderCubemap()
  {
   ScriptableWizard.DisplayWizard ("Render CubeMap", typeof(GenerateStaticCubemap), "Render!");
  }


}
 

소스(6방향 모두 제대로 나오는 스크립트)

/*
* This confidential and proprietary software may be used only as
* authorised by a licensing agreement from ARM Limited
* (C) COPYRIGHT 2014 ARM Limited
* ALL RIGHTS RESERVED
* The entire notice above must be reproduced on all authorised
* copies and copies may only be made to the extent permitted
* by a licensing agreement from ARM Limited.
*/

using UnityEngine;
using UnityEditor;
using System.IO;

/**
* This script must be placed in the Editor folder.
* The script renders the scene into a cubemap and optionally
* saves each cubemap image individually.
* The script is available in the Editor mode from the
 * Game Object menu as "Bake Cubemap" option.
* Be sure the camera far plane is enough to render the scene.
*/

public class GenerateCubeMap : ScriptableWizard
{
 
 public Transform renderPosition;
 public Cubemap cubemap;
 // Camera settings.
 public int cameraDepth = 24;
 public LayerMask cameraLayerMask = -1;
 public Color cameraBackgroundColor;
 public float cameraNearPlane = 0.1f;
 public float cameraFarPlane = 2500.0f;
 public bool cameraUseOcclusion = true;
 // Cubemap settings.
 public FilterMode cubemapFilterMode = FilterMode.Trilinear;
 // Quality settings.
 public int antiAliasing = 4;
 
 public bool createIndividualImages = false;
 
 // The folder where individual cubemap images will be saved
 static string imageDirectory = "Assets/CubemapImages";
 static string[] cubemapImage
 = new string[]{"front+Z", "right+X", "back-Z", "left-X", "top+Y", "bottom-Y"};
 static Vector3[] eulerAngles = new Vector3[]{new Vector3(0.0f,0.0f,0.0f),
  new Vector3(0.0f,-90.0f,0.0f), new Vector3(0.0f,180.0f,0.0f),
  new Vector3(0.0f,90.0f,0.0f), new Vector3(-90.0f,0.0f,0.0f),
  new Vector3(90.0f,0.0f,0.0f)};
 
 void OnWizardUpdate()
 {
  helpString = "Set the position to render from and the cubemap to bake.";
  if(renderPosition != null && cubemap != null)
  {
   isValid = true;
  }
  else
  {
   isValid = false;
  }
 }
 
 void OnWizardCreate ()
 {
  
  // Create temporary camera for rendering.
  GameObject go = new GameObject( "CubemapCam", typeof(Camera) );
  // Camera settings.
  go.camera.depth = cameraDepth;
  go.camera.backgroundColor = cameraBackgroundColor;
  go.camera.cullingMask = cameraLayerMask;
  go.camera.nearClipPlane = cameraNearPlane;
  go.camera.farClipPlane = cameraFarPlane;
  go.camera.useOcclusionCulling = cameraUseOcclusion;
  // Cubemap settings
  cubemap.filterMode = cubemapFilterMode;
  // Set antialiasing
  QualitySettings.antiAliasing = antiAliasing;
  
  // Place the camera on the render position.
  go.transform.position = renderPosition.position;
  go.transform.rotation = Quaternion.identity;
  
  // Bake the cubemap
  go.camera.RenderToCubemap(cubemap);
  
  // Rendering individual images
  if(createIndividualImages)
  {
   if (!Directory.Exists(imageDirectory))
   {
    Directory.CreateDirectory(imageDirectory);
   }
   
   RenderIndividualCubemapImages(go);
  }
  
  
  // Destroy the camera after rendering.
  DestroyImmediate(go);
 }
 
 void RenderIndividualCubemapImages(GameObject go)
 {
  go.camera.backgroundColor = Color.black;
  go.camera.clearFlags = CameraClearFlags.Skybox;
  go.camera.fieldOfView = 90;   
  go.camera.aspect = 1.0f;
  
  go.transform.rotation = Quaternion.identity;
  
  //Render individual images       
  for (int camOrientation = 0; camOrientation < eulerAngles.Length ; camOrientation++)
  {
   string imageName = Path.Combine(imageDirectory, cubemap.name + "_"
                                   + cubemapImage[camOrientation] + ".png");
   go.camera.transform.eulerAngles = eulerAngles[camOrientation];
   RenderTexture renderTex = new RenderTexture(cubemap.height,
                                               cubemap.height, cameraDepth);
   go.camera.targetTexture = renderTex;
   go.camera.Render();
   RenderTexture.active = renderTex;
   
   Texture2D img = new Texture2D(cubemap.height, cubemap.height,
                                 TextureFormat.RGB24, false);
   img.ReadPixels(new Rect(0, 0, cubemap.height, cubemap.height), 0, 0);
   
   RenderTexture.active = null;
   GameObject.DestroyImmediate(renderTex);
   
   byte[] imgBytes = img.EncodeToPNG();
   File.WriteAllBytes(imageName, imgBytes);
   
   AssetDatabase.ImportAsset(imageName, ImportAssetOptions.ForceUpdate);
  }
  
  AssetDatabase.Refresh();
 }
 
 [MenuItem("GameObject/Bake Cubemap")]
 static void RenderCubemap ()
 {
  ScriptableWizard.DisplayWizard("Bake CubeMap", typeof(GenerateCubeMap),"Bake!");
 }
}


예제분석

새 스크립트를 생성하고 ScriptableWizard로부터 상속을 받는 클래스를 선언하며 시작한다. 이러한 상속은 유니티 3D에게 새로운 팝업창 타입의 사용자 정의 에디터를 만든다고 알려주는 역할을 한다. Editor라는 폴더에 스크립트를 넣는 이유가 바로 여기에 있다. 만약 스크립트를 Editor 폴더에 넣지 않으면, 유니티는 사용자 정의 에디터 타입 스크립트로 인식하지 않는다.


다음단계에서 선언한 변수는 큐브 맵을 생성할 위치를 저장하는 방법과 Project 탭에서 생성된 큐브 맵 GameObject 생성자를 저장하는 방법을 제공한다. 이러한 변수를 갖는 것은 새 큐브맵을 생성하는 밑거름이 된다.


그 다음에, ScriptableWizard 클래스로부터 제공된 함수인 OnWizardUpdate() 함수가 있다. 이 함수는 마법사를 처음 열 때나 마법사의 GUI요소가 변경 될 때 호출된다. 그래서 사용자가 실제로 트랜스폼과 새 큐브 맵을 입력했는지 확인하는 데 사용할 수 있따. 만약 입력했다면, isValid 변수를 true로 설정한다. 만약 입력하지 않았다면, isValid를 false로 설정한다. isValid 변수는 ScriptableWizard 클래스에서 제공한 내장 변수다. 마법사의 하단에 있는 Create 버튼을 통해 간단히 켜고 끌 수 있다. isValid 변수는 빈 트랜스폼 또는 큐브맵을 가지고 다음 함수를 실행하는 경우를 사전에 방지한다.


일단 사용자가 작업을 위해 올바른 데이터를 입력했다면, OnWizardCreate()함수로 이동할 수 있따. 큐브 맵 생성이 본질적으로 이뤄지는 곳이다. 함수는 새로운 GameObject 생성자를 만들고 Camera 타입으로 만들어졌는지 확인하는 것으로 시작한다. 그리고 제공된 트랜스폼의 위치를 이용해 카메라를 놓는다.


새 카메라를 생성해 적절한 위치에 놓았다. 이제 할 일은 RenderToCubeMap() 함수를 호출하고 사용자 정의 큐브 맵을 전달하는 것 뿐이다. 일단 함수가 실행되면, 큐브 맵의 6개 이미지가 생성되고 사용자가 제공한 큐브맵 오브젝트로 조립될 것이다.


마지막으로, 유니티의 상단 메뉴 바에 있는 도구에 접근할 수 있도록 마법사를 위한 메뉴 옵션을 만든다. 메뉴의 아이템과 함께, 실제로 메뉴를 표시하는 마법사의 static함수를 호출한다. 이 함수를 통해 유니티 에디터에서 직접 큐브맵을 생성하는 작은 도구를 만드는 과정을 완료한다.


참고사항

큐브 맵을 만들 수 있는 그 밖의 애플리케이션도 살펴보자. 리플렉션 파이프라인 또는 워크플로를 만드는데 사용할 수 있는 좋은 리소스를 제공할 것이다.


ATI CubeMapGen

http://developer.amd.com/resources/archive/archive-tool/gpu-tools-archive/cubemapgen/


HDR Light Studio Pro

http://www.hdrlightstudio.com/


소스출처

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.100140_0100_00_en/nic1408034306141.html

+ Recent posts