유니티에서 라이트 매핑은 씬정보( 라이트 정보 )에 따라 라이트 맵을 생성하기 때문에
동일한 모델이라도 씬이 다를 경우에는 라이트 맵 정보가 공유 되지 않는다.
씬별로 라이트 맵을 따로 만드는 방법이 있지만 라이트 맵 용량이 크기 때문에 부담이 되는 경우가 많다.
한 씬에서 제작한 라이트 맵을 다른 씬에서 공유 하고 싶다면 생성된 라이트 맵 정보를 프리팹에 저장을 하는 기능이 필요한데
유니티에서 기능을 제공하지는 않는다.
다음과 같은 코드를 사용하면 프리팹 별로 라이트 맵을 세팅 할 수 있다.
생성된 라이트 맵을 프리팹에 저장하는 기능.
PrefabLightmapData.cs
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.SceneManagement;
[ExecuteAlways]
public class PrefabLightmapData : MonoBehaviour
{
[System.Serializable]
struct RendererInfo
{
public Renderer renderer;
public int lightmapIndex;
public Vector4 lightmapOffsetScale;
}
[System.Serializable]
struct LightInfo
{
public Light light;
public int lightmapBaketype;
public int mixedLightingMode;
}
[SerializeField]
RendererInfo[] m_RendererInfo;
[SerializeField]
Texture2D[] m_Lightmaps;
[SerializeField]
Texture2D[] m_LightmapsDir;
[SerializeField]
Texture2D[] m_ShadowMasks;
[SerializeField]
LightInfo[] m_LightInfo;
void Awake()
{
Init();
}
void Init()
{
if (m_RendererInfo == null || m_RendererInfo.Length == 0)
return;
var lightmaps = LightmapSettings.lightmaps;
int[] offsetsindexes = new int[m_Lightmaps.Length];
int counttotal = lightmaps.Length;
List<LightmapData> combinedLightmaps = new List<LightmapData>();
for (int i = 0; i < m_Lightmaps.Length; i++)
{
bool exists = false;
for (int j = 0; j < lightmaps.Length; j++)
{
if (m_Lightmaps[i] == lightmaps[j].lightmapColor)
{
exists = true;
offsetsindexes[i] = j;
}
}
if (!exists)
{
offsetsindexes[i] = counttotal;
var newlightmapdata = new LightmapData
{
lightmapColor = m_Lightmaps[i],
lightmapDir = m_LightmapsDir.Length == m_Lightmaps.Length ? m_LightmapsDir[i] : default(Texture2D),
shadowMask = m_ShadowMasks.Length == m_Lightmaps.Length ? m_ShadowMasks[i] : default(Texture2D),
};
combinedLightmaps.Add(newlightmapdata);
counttotal += 1;
}
}
var combinedLightmaps2 = new LightmapData[counttotal];
lightmaps.CopyTo(combinedLightmaps2, 0);
combinedLightmaps.ToArray().CopyTo(combinedLightmaps2, lightmaps.Length);
bool directional=true;
foreach(Texture2D t in m_LightmapsDir)
{
if (t == null)
{
directional = false;
break;
}
}
LightmapSettings.lightmapsMode = (m_LightmapsDir.Length == m_Lightmaps.Length && directional) ? LightmapsMode.CombinedDirectional : LightmapsMode.NonDirectional;
ApplyRendererInfo(m_RendererInfo, offsetsindexes, m_LightInfo);
LightmapSettings.lightmaps = combinedLightmaps2;
}
void OnEnable()
{
SceneManager.sceneLoaded += OnSceneLoaded;
}
// called second
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
Init();
}
// called when the game is terminated
void OnDisable()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
}
static void ApplyRendererInfo(RendererInfo[] infos, int[] lightmapOffsetIndex, LightInfo[] lightsInfo)
{
for (int i = 0; i < infos.Length; i++)
{
var info = infos[i];
info.renderer.lightmapIndex = lightmapOffsetIndex[info.lightmapIndex];
info.renderer.lightmapScaleOffset = info.lightmapOffsetScale;
// You have to release shaders.
Material[] mat = info.renderer.sharedMaterials;
for (int j = 0; j < mat.Length; j++)
{
if (mat[j] != null && Shader.Find(mat[j].shader.name) != null)
mat[j].shader = Shader.Find(mat[j].shader.name);
}
}
for (int i = 0; i < lightsInfo.Length; i++)
{
LightBakingOutput bakingOutput = new LightBakingOutput();
bakingOutput.isBaked = true;
bakingOutput.lightmapBakeType = (LightmapBakeType)lightsInfo[i].lightmapBaketype;
bakingOutput.mixedLightingMode = (MixedLightingMode)lightsInfo[i].mixedLightingMode;
lightsInfo[i].light.bakingOutput = bakingOutput;
}
}
#if UNITY_EDITOR
[UnityEditor.MenuItem("Assets/Bake Prefab Lightmaps")]
static void GenerateLightmapInfo()
{
if (UnityEditor.Lightmapping.giWorkflowMode != UnityEditor.Lightmapping.GIWorkflowMode.OnDemand)
{
Debug.LogError("ExtractLightmapData requires that you have baked you lightmaps and Auto mode is disabled.");
return;
}
UnityEditor.Lightmapping.Bake();
PrefabLightmapData[] prefabs = FindObjectsOfType<PrefabLightmapData>();
foreach (var instance in prefabs)
{
var gameObject = instance.gameObject;
var rendererInfos = new List<RendererInfo>();
var lightmaps = new List<Texture2D>();
var lightmapsDir = new List<Texture2D>();
var shadowMasks = new List<Texture2D>();
var lightsInfos = new List<LightInfo>();
GenerateLightmapInfo(gameObject, rendererInfos, lightmaps, lightmapsDir, shadowMasks, lightsInfos);
instance.m_RendererInfo = rendererInfos.ToArray();
instance.m_Lightmaps = lightmaps.ToArray();
instance.m_LightmapsDir = lightmapsDir.ToArray();
instance.m_LightInfo = lightsInfos.ToArray();
instance.m_ShadowMasks = shadowMasks.ToArray();
#if UNITY_2018_3_OR_NEWER
var targetPrefab = PrefabUtility.GetCorrespondingObjectFromOriginalSource(instance.gameObject) as GameObject;
if (targetPrefab != null)
{
GameObject root = PrefabUtility.GetOutermostPrefabInstanceRoot(instance.gameObject);
if (root != null)
{
GameObject rootPrefab = PrefabUtility.GetCorrespondingObjectFromSource(instance.gameObject);
string rootPath = AssetDatabase.GetAssetPath(rootPrefab);
PrefabUtility.UnpackPrefabInstanceAndReturnNewOutermostRoots(root, PrefabUnpackMode.OutermostRoot);
try
{
PrefabUtility.ApplyPrefabInstance(instance.gameObject, InteractionMode.AutomatedAction);
}
catch { }
finally
{
PrefabUtility.SaveAsPrefabAssetAndConnect(root, rootPath, InteractionMode.AutomatedAction);
}
}
else
{
PrefabUtility.ApplyPrefabInstance(instance.gameObject, InteractionMode.AutomatedAction);
}
}
#else
var targetPrefab = UnityEditor.PrefabUtility.GetPrefabParent(gameObject) as GameObject;
if (targetPrefab != null)
{
UnityEditor.PrefabUtility.ReplacePrefab(gameObject, targetPrefab);
}
#endif
}
}
static void GenerateLightmapInfo(GameObject root, List<RendererInfo> rendererInfos, List<Texture2D> lightmaps, List<Texture2D> lightmapsDir, List<Texture2D> shadowMasks, List<LightInfo> lightsInfo)
{
var renderers = root.GetComponentsInChildren<MeshRenderer>();
foreach (MeshRenderer renderer in renderers)
{
if (renderer.lightmapIndex != -1)
{
RendererInfo info = new RendererInfo();
info.renderer = renderer;
if (renderer.lightmapScaleOffset != Vector4.zero)
{
if(renderer.lightmapIndex < 0 || renderer.lightmapIndex == 0xFFFE) continue;
info.lightmapOffsetScale = renderer.lightmapScaleOffset;
Texture2D lightmap = LightmapSettings.lightmaps[renderer.lightmapIndex].lightmapColor;
Texture2D lightmapDir = LightmapSettings.lightmaps[renderer.lightmapIndex].lightmapDir;
Texture2D shadowMask = LightmapSettings.lightmaps[renderer.lightmapIndex].shadowMask;
info.lightmapIndex = lightmaps.IndexOf(lightmap);
if (info.lightmapIndex == -1)
{
info.lightmapIndex = lightmaps.Count;
lightmaps.Add(lightmap);
lightmapsDir.Add(lightmapDir);
shadowMasks.Add(shadowMask);
}
rendererInfos.Add(info);
}
}
}
var lights = root.GetComponentsInChildren<Light>(true);
foreach (Light l in lights)
{
LightInfo lightInfo = new LightInfo();
lightInfo.light = l;
lightInfo.lightmapBaketype = (int)l.lightmapBakeType;
#if UNITY_2020_1_OR_NEWER
lightInfo.mixedLightingMode = (int)UnityEditor.Lightmapping.lightingSettings.mixedBakeMode;
#elif UNITY_2018_1_OR_NEWER
lightInfo.mixedLightingMode = (int)UnityEditor.LightmapEditorSettings.mixedBakeMode;
#else
lightInfo.mixedLightingMode = (int)l.bakingOutput.lightmapBakeType;
#endif
lightsInfo.Add(lightInfo);
}
}
#endif
}
먼저 씬에 라이트와 라이트 맵 정보와 모델을 세팅 하고 라이트맵 창에서 라이트 맵을 생성 한뒤에
Prefab의 최상단에 PrefabLightmapData.cs Compenet를 추가 한다.
에디터 메뉴에서 Asset - > Bake Prefab Light Map 를 누르면 라이트 맵을 다시 생성한 뒤
PrefabLightmapData.cs 에 라이트 맵 정보가 연결 된다.
해당 프리팹을 다른씬에서 로드해도 라이트 맵이 잘 적용 되는 것을 볼 수 있다.
'그래픽스' 카테고리의 다른 글
라이트 매핑( Light Mapping ) #2 (0) | 2023.03.03 |
---|---|
라이트 매핑 ( Light Mapping ) #1 (0) | 2023.03.03 |
Screen Space Ambient Occlusion( SSAO ) 기법 (0) | 2023.03.02 |
Anti Aliasing 기법( MSAA, SSAA, FXAA, SMAA ) (0) | 2023.02.27 |