※ ここの内容は古いので、Unity 2017以降をお使いの場合はこちらを見てください。
ちょっと長いので先に要点を書いておくと、「ライトマップを再利用するためには MaterialPropertyBlock を書き換える!」です。
Unity 3.5で作られた古いプロジェクトをひっぱりだしてきて、Unity 5に移行する作業をしていたのですが、困ったことがおこりました。
このプロジェクトではライトマップを焼くためだけのシーンがあって、そこで作ったライトマップを別の複数のシーンで再利用していて、Unity 3.5ではLightmapSettingsとRendererのlightmapIndex, lightmapScaleOffsetを適切にセットしてやれば全然問題なかったのですが、Unity 5.4ではうまくいきません。
ライトマップを使いまわすコードは以下のようなものでした。これをアッセトとして保存しておいて、Load関数やAppend関数を使ってライトマップを使いまわしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
using UnityEngine; using System.Collections.Generic; public class LightmapSettingsSaveData : ScriptableObject { public Texture2D[] farLightmaps; public Texture2D[] nearLightmaps; public LightmapsMode lightmapsMode; public ColorSpace bakedColorSpace; public LightProbes lightProbes; [System.Serializable] public class LightmapIndexSaveData { public string objectName; public int lightmapIndex; public Vector4 lightmapTilingOffset; } public LightmapIndexSaveData[] lightmapIndices; public void Save() { farLightmaps = new Texture2D[LightmapSettings.lightmaps.Length]; nearLightmaps = new Texture2D[LightmapSettings.lightmaps.Length]; for (int i = 0; i < LightmapSettings.lightmaps.Length; ++i) { farLightmaps[i] = LightmapSettings.lightmaps[i].lightmapFar; nearLightmaps[i] = LightmapSettings.lightmaps[i].lightmapNear; } lightmapsMode = LightmapSettings.lightmapsMode; bakedColorSpace = LightmapSettings.bakedColorSpace; lightProbes = LightmapSettings.lightProbes; Object[] renderers = Object.FindSceneObjectsOfType(typeof(Renderer)); Dictionary<string, LightmapIndexSaveData> objects = new Dictionary<string, LightmapIndexSaveData>(); foreach (Renderer r in renderers) { if (0 <= r.lightmapIndex) { LightmapIndexSaveData index = new LightmapIndexSaveData(); index.objectName = r.name; index.lightmapIndex = r.lightmapIndex; index.lightmapTilingOffset = r.lightmapScaleOffset; if (objects.ContainsKey(r.name)) { LightmapIndexSaveData data = objects[r.name]; if (data.lightmapIndex != index.lightmapIndex || data.lightmapTilingOffset != index.lightmapTilingOffset) { Debug.LogError(r.name + " already exists."); } } else { objects.Add(r.name, index); } } } lightmapIndices = null; if (0 < objects.Count) { lightmapIndices = new LightmapIndexSaveData[objects.Count]; int n = 0; foreach (LightmapIndexSaveData v in objects.Values) { lightmapIndices[n++] = v; } } UnityEditor.LightingDataAsset asset; } public void Load() { LightmapData[] lightmaps = new LightmapData[farLightmaps.Length]; for (int i = 0; i < farLightmaps.Length; ++i) { lightmaps[i] = new LightmapData(); lightmaps[i].lightmapFar = farLightmaps[i]; lightmaps[i].lightmapNear = nearLightmaps[i]; } LightmapSettings.lightmaps = lightmaps; LightmapSettings.bakedColorSpace = bakedColorSpace; LightmapSettings.lightmapsMode = lightmapsMode; LightmapSettings.lightProbes = lightProbes; Update(); } public void Append() { int offset = LightmapSettings.lightmaps.Length; LightmapData[] mergedLightmaps = new LightmapData[offset + farLightmaps.Length]; for (int i = 0; i < offset; ++i) { mergedLightmaps[i] = LightmapSettings.lightmaps[i]; } for (int i = 0; i < farLightmaps.Length; ++i) { mergedLightmaps[offset + i] = new LightmapData(); mergedLightmaps[offset + i].lightmapFar = farLightmaps[i]; mergedLightmaps[offset + i].lightmapNear = nearLightmaps[i]; } LightmapSettings.lightmaps = mergedLightmaps; Update(); } public void Update() { if (lightmapIndices != null) { Object[] renderers = Object.FindSceneObjectsOfType(typeof(Renderer)); foreach (Renderer r in renderers) { for (int i = 0; i < lightmapIndices.Length; ++i) { if (lightmapIndices[i].objectName == r.name) { if (0 <= lightmapIndices[i].lightmapIndex && lightmapIndices[i].lightmapIndex < farLightmaps.Length) { Texture2D farTex = farLightmaps[lightmapIndices[i].lightmapIndex]; for (int j = 0; j < LightmapSettings.lightmaps.Length; ++j) { if (farTex == LightmapSettings.lightmaps[j].lightmapFar) { r.lightmapIndex = j; r.lightmapScaleOffset = lightmapIndices[i].lightmapTilingOffset; } } } else { r.lightmapIndex = -1; r.realtimeLightmapIndex = -1; } } } } } } } |
Unity 3.5では(たぶん Unity 4でも)、Editorスクリプトを使って上のスクリプトをエディタ上で適用してやれば、書き換えられた情報はシーンに保存されて、その後は何もしなくてもよかったのですが、Unity 5.4では2つの問題がありました。
- エディタ上で上のスクリプトを適用するとライトマップが有効になるが、LightmapSettingsがシーンに保存されず、シーンをリロードするとライトマップがはがされてしまう。またシーンを実行してもライトマップがはがされてしまう。
- シーンを実行後に上のスクリプトを適用してもシーンに反映されない。
どうもUnity 5ではライトマップ作成時にできる LightingDataAsset がシーンに関連付けられていて、その情報がシーンに適用されるようです。なのでLightmapSettingsはシーンに保存されず、リロードするとライトマップは消えてしまうわけです。
今のところ LightingDataAsset をスクリプトで編集することはできなさそうなので、次のような [ExecuteInEditMode] をつけたスクリプトを使って毎回ライトマップの情報をロードすることにします。
1 2 3 4 5 6 7 8 9 10 11 |
using UnityEngine; [ExecuteInEditMode] public class LightmapLoader : MonoBehaviour { public LightmapSettingsSaveData m_lightmapSaveData; void Start () { if (m_lightmapSaveData != null) { m_lightmapSaveData.Load(); } } } |
これでエディタ上ではライトマップが反映されるようになったのですが、まだシーンを実行するとライトマップがはがされる(正確にはライトマップが崩れる)という問題が残っています。
どうもライトマップの情報はシーンがロードされた時点で MaterialPropertyBlock に格納され、それ以降にライトマップの情報を書き換えてもライトマップが更新されないようです。ためしに、上記の LightmapSettingsSaveData クラスに MaterialPropertyBlock を書き換えるコードを加えたら無事ライトマップが適用されるようになりました。こんな感じでいけるかと思います。(Unity 5.4用です。Unity 5.3以前だと変数名が違うので注意!)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
LightmapData[] lightmaps = LightmapSettings.lightmaps; Object[] renderers = Object.FindSceneObjectsOfType(typeof(Renderer)); MaterialPropertyBlock properties = new MaterialPropertyBlock(); foreach (Renderer r in renderers) { if (0 <= r.lightmapIndex && r.lightmapIndex < lightmaps.Length) { r.GetPropertyBlock(properties); properties.SetTexture("unity_Lightmap", lightmaps[r.lightmapIndex].lightmapFar); properties.SetTexture("unity_LightmapInd", lightmaps[r.lightmapIndex].lightmapNear); properties.SetVector("unity_LightmapST", r.lightmapScaleOffset); r.SetPropertyBlock(properties); } } |