├── LightProbeRuntime.cs ├── PrefabLightmapData.cs └── README.md /LightProbeRuntime.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEngine.Rendering; 5 | 6 | public class LightProbeRuntime : MonoBehaviour 7 | { 8 | public Color m_Ambient; 9 | Light[] m_Lights; 10 | 11 | // Start is called before the first frame update 12 | IEnumerator Start() 13 | { 14 | yield return null; 15 | 16 | m_Lights = FindObjectsOfType(); 17 | SphericalHarmonicsL2[] bakedProbes = LightmapSettings.lightProbes.bakedProbes; 18 | Vector3[] probePositions = LightmapSettings.lightProbes.positions; 19 | int probeCount = LightmapSettings.lightProbes.count; 20 | 21 | // Clear all probes 22 | for (int i = 0; i < probeCount; i++) 23 | bakedProbes[i].Clear(); 24 | 25 | // Add ambient light to all probes 26 | for (int i = 0; i < probeCount; i++) 27 | bakedProbes[i].AddAmbientLight(m_Ambient); 28 | 29 | // Add directional and point lights' contribution to all probes 30 | foreach (Light l in m_Lights) 31 | { 32 | if (l.type == LightType.Directional) 33 | { 34 | for (int i = 0; i < probeCount; i++) 35 | bakedProbes[i].AddDirectionalLight(-l.transform.forward, l.color, l.intensity); 36 | } 37 | else if (l.type == LightType.Point) 38 | { 39 | for (int i = 0; i < probeCount; i++) 40 | SHAddPointLight(probePositions[i], l.transform.position, l.range, l.color, l.intensity, ref bakedProbes[i]); 41 | } 42 | } 43 | LightmapSettings.lightProbes.bakedProbes = bakedProbes; 44 | } 45 | 46 | void SHAddPointLight(Vector3 probePosition, Vector3 position, float range, Color color, float intensity, ref SphericalHarmonicsL2 sh) 47 | { 48 | // From the point of view of an SH probe, point light looks no different than a directional light, 49 | // just attenuated and coming from the right direction. 50 | Vector3 probeToLight = position - probePosition; 51 | float attenuation = 1.0F / (1.0F + 25.0F * probeToLight.sqrMagnitude / (range * range)); 52 | sh.AddDirectionalLight(probeToLight.normalized, color, intensity * attenuation); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /PrefabLightmapData.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEditor; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using UnityEngine.SceneManagement; 6 | 7 | [ExecuteAlways] 8 | public class PrefabLightmapData : MonoBehaviour 9 | { 10 | [Tooltip("Reassigns shaders when applying the baked lightmaps. Might conflict with some shaders like transparent HDRP.")] 11 | public bool releaseShaders = true; 12 | 13 | [System.Serializable] 14 | struct RendererInfo 15 | { 16 | public Renderer renderer; 17 | public int lightmapIndex; 18 | public Vector4 lightmapOffsetScale; 19 | } 20 | [System.Serializable] 21 | struct LightInfo 22 | { 23 | public Light light; 24 | public int lightmapBaketype; 25 | public int mixedLightingMode; 26 | } 27 | 28 | [SerializeField] 29 | RendererInfo[] m_RendererInfo; 30 | [SerializeField] 31 | Texture2D[] m_Lightmaps; 32 | [SerializeField] 33 | Texture2D[] m_LightmapsDir; 34 | [SerializeField] 35 | Texture2D[] m_ShadowMasks; 36 | [SerializeField] 37 | LightInfo[] m_LightInfo; 38 | 39 | 40 | void Awake() 41 | { 42 | Init(); 43 | } 44 | 45 | void Init() 46 | { 47 | if (m_RendererInfo == null || m_RendererInfo.Length == 0) 48 | return; 49 | 50 | var lightmaps = LightmapSettings.lightmaps; 51 | int[] offsetsindexes = new int[m_Lightmaps.Length]; 52 | int counttotal = lightmaps.Length; 53 | List combinedLightmaps = new List(); 54 | 55 | for (int i = 0; i < m_Lightmaps.Length; i++) 56 | { 57 | bool exists = false; 58 | for (int j = 0; j < lightmaps.Length; j++) 59 | { 60 | 61 | if (m_Lightmaps[i] == lightmaps[j].lightmapColor) 62 | { 63 | exists = true; 64 | offsetsindexes[i] = j; 65 | 66 | } 67 | 68 | } 69 | if (!exists) 70 | { 71 | offsetsindexes[i] = counttotal; 72 | var newlightmapdata = new LightmapData 73 | { 74 | lightmapColor = m_Lightmaps[i], 75 | lightmapDir = m_LightmapsDir.Length == m_Lightmaps.Length ? m_LightmapsDir[i] : default(Texture2D), 76 | shadowMask = m_ShadowMasks.Length == m_Lightmaps.Length ? m_ShadowMasks[i] : default(Texture2D), 77 | }; 78 | 79 | combinedLightmaps.Add(newlightmapdata); 80 | 81 | counttotal += 1; 82 | 83 | 84 | } 85 | 86 | } 87 | 88 | var combinedLightmaps2 = new LightmapData[counttotal]; 89 | 90 | lightmaps.CopyTo(combinedLightmaps2, 0); 91 | combinedLightmaps.ToArray().CopyTo(combinedLightmaps2, lightmaps.Length); 92 | 93 | bool directional=true; 94 | 95 | foreach(Texture2D t in m_LightmapsDir) 96 | { 97 | if (t == null) 98 | { 99 | directional = false; 100 | break; 101 | } 102 | } 103 | 104 | LightmapSettings.lightmapsMode = (m_LightmapsDir.Length == m_Lightmaps.Length && directional) ? LightmapsMode.CombinedDirectional : LightmapsMode.NonDirectional; 105 | ApplyRendererInfo(m_RendererInfo, offsetsindexes, m_LightInfo); 106 | LightmapSettings.lightmaps = combinedLightmaps2; 107 | } 108 | 109 | void OnEnable() 110 | { 111 | 112 | SceneManager.sceneLoaded += OnSceneLoaded; 113 | 114 | } 115 | 116 | // called second 117 | void OnSceneLoaded(Scene scene, LoadSceneMode mode) 118 | { 119 | Init(); 120 | } 121 | 122 | // called when the game is terminated 123 | void OnDisable() 124 | { 125 | SceneManager.sceneLoaded -= OnSceneLoaded; 126 | } 127 | 128 | 129 | 130 | void ApplyRendererInfo(RendererInfo[] infos, int[] lightmapOffsetIndex, LightInfo[] lightsInfo) 131 | { 132 | for (int i = 0; i < infos.Length; i++) 133 | { 134 | var info = infos[i]; 135 | 136 | info.renderer.lightmapIndex = lightmapOffsetIndex[info.lightmapIndex]; 137 | info.renderer.lightmapScaleOffset = info.lightmapOffsetScale; 138 | 139 | if (releaseShaders) 140 | { 141 | // You have to release shaders. 142 | Material[] mat = info.renderer.sharedMaterials; 143 | for (int j = 0; j < mat.Length; j++) 144 | { 145 | if (mat[j] != null && Shader.Find(mat[j].shader.name) != null) 146 | { 147 | mat[j].shader = Shader.Find(mat[j].shader.name); 148 | } 149 | 150 | } 151 | } 152 | 153 | } 154 | 155 | for (int i = 0; i < lightsInfo.Length; i++) 156 | { 157 | LightBakingOutput bakingOutput = new LightBakingOutput(); 158 | bakingOutput.isBaked = true; 159 | bakingOutput.lightmapBakeType = (LightmapBakeType)lightsInfo[i].lightmapBaketype; 160 | bakingOutput.mixedLightingMode = (MixedLightingMode)lightsInfo[i].mixedLightingMode; 161 | 162 | lightsInfo[i].light.bakingOutput = bakingOutput; 163 | 164 | } 165 | 166 | 167 | } 168 | 169 | #if UNITY_EDITOR 170 | [UnityEditor.MenuItem("Assets/Bake Prefab Lightmaps")] 171 | static void GenerateLightmapInfo() 172 | { 173 | if (UnityEditor.Lightmapping.giWorkflowMode != UnityEditor.Lightmapping.GIWorkflowMode.OnDemand) 174 | { 175 | Debug.LogError("ExtractLightmapData requires that you have baked you lightmaps and Auto mode is disabled."); 176 | return; 177 | } 178 | UnityEditor.Lightmapping.Bake(); 179 | 180 | PrefabLightmapData[] prefabs = FindObjectsOfType(); 181 | 182 | foreach (var instance in prefabs) 183 | { 184 | var gameObject = instance.gameObject; 185 | var rendererInfos = new List(); 186 | var lightmaps = new List(); 187 | var lightmapsDir = new List(); 188 | var shadowMasks = new List(); 189 | var lightsInfos = new List(); 190 | 191 | GenerateLightmapInfo(gameObject, rendererInfos, lightmaps, lightmapsDir, shadowMasks, lightsInfos); 192 | 193 | instance.m_RendererInfo = rendererInfos.ToArray(); 194 | instance.m_Lightmaps = lightmaps.ToArray(); 195 | instance.m_LightmapsDir = lightmapsDir.ToArray(); 196 | instance.m_LightInfo = lightsInfos.ToArray(); 197 | instance.m_ShadowMasks = shadowMasks.ToArray(); 198 | #if UNITY_2018_3_OR_NEWER 199 | var targetPrefab = PrefabUtility.GetCorrespondingObjectFromOriginalSource(instance.gameObject) as GameObject; 200 | if (targetPrefab != null) 201 | { 202 | GameObject root = PrefabUtility.GetOutermostPrefabInstanceRoot(instance.gameObject);// 根结点 203 | //如果当前预制体是是某个嵌套预制体的一部分(IsPartOfPrefabInstance) 204 | if (root != null) 205 | { 206 | GameObject rootPrefab = PrefabUtility.GetCorrespondingObjectFromSource(instance.gameObject); 207 | string rootPath = AssetDatabase.GetAssetPath(rootPrefab); 208 | //打开根部预制体 209 | PrefabUtility.UnpackPrefabInstanceAndReturnNewOutermostRoots(root, PrefabUnpackMode.OutermostRoot); 210 | try 211 | { 212 | //Apply各个子预制体的改变 213 | PrefabUtility.ApplyPrefabInstance(instance.gameObject, InteractionMode.AutomatedAction); 214 | } 215 | catch { } 216 | finally 217 | { 218 | //重新更新根预制体 219 | PrefabUtility.SaveAsPrefabAssetAndConnect(root, rootPath, InteractionMode.AutomatedAction); 220 | } 221 | } 222 | else 223 | { 224 | PrefabUtility.ApplyPrefabInstance(instance.gameObject, InteractionMode.AutomatedAction); 225 | } 226 | } 227 | #else 228 | var targetPrefab = UnityEditor.PrefabUtility.GetPrefabParent(gameObject) as GameObject; 229 | if (targetPrefab != null) 230 | { 231 | //UnityEditor.Prefab 232 | UnityEditor.PrefabUtility.ReplacePrefab(gameObject, targetPrefab); 233 | } 234 | #endif 235 | } 236 | 237 | 238 | } 239 | 240 | static void GenerateLightmapInfo(GameObject root, List rendererInfos, List lightmaps, List lightmapsDir, List shadowMasks, List lightsInfo) 241 | { 242 | var renderers = root.GetComponentsInChildren(); 243 | foreach (MeshRenderer renderer in renderers) 244 | { 245 | if (renderer.lightmapIndex != -1) 246 | { 247 | RendererInfo info = new RendererInfo(); 248 | info.renderer = renderer; 249 | 250 | if (renderer.lightmapScaleOffset != Vector4.zero) 251 | { 252 | //1ibrium's pointed out this issue : https://docs.unity3d.com/ScriptReference/Renderer-lightmapIndex.html 253 | if(renderer.lightmapIndex < 0 || renderer.lightmapIndex == 0xFFFE) continue; 254 | info.lightmapOffsetScale = renderer.lightmapScaleOffset; 255 | 256 | Texture2D lightmap = LightmapSettings.lightmaps[renderer.lightmapIndex].lightmapColor; 257 | Texture2D lightmapDir = LightmapSettings.lightmaps[renderer.lightmapIndex].lightmapDir; 258 | Texture2D shadowMask = LightmapSettings.lightmaps[renderer.lightmapIndex].shadowMask; 259 | 260 | info.lightmapIndex = lightmaps.IndexOf(lightmap); 261 | if (info.lightmapIndex == -1) 262 | { 263 | info.lightmapIndex = lightmaps.Count; 264 | lightmaps.Add(lightmap); 265 | lightmapsDir.Add(lightmapDir); 266 | shadowMasks.Add(shadowMask); 267 | } 268 | 269 | rendererInfos.Add(info); 270 | } 271 | 272 | } 273 | } 274 | 275 | var lights = root.GetComponentsInChildren(true); 276 | 277 | foreach (Light l in lights) 278 | { 279 | LightInfo lightInfo = new LightInfo(); 280 | lightInfo.light = l; 281 | lightInfo.lightmapBaketype = (int)l.lightmapBakeType; 282 | #if UNITY_2020_1_OR_NEWER 283 | lightInfo.mixedLightingMode = (int)UnityEditor.Lightmapping.lightingSettings.mixedBakeMode; 284 | #elif UNITY_2018_1_OR_NEWER 285 | lightInfo.mixedLightingMode = (int)UnityEditor.LightmapEditorSettings.mixedBakeMode; 286 | #else 287 | lightInfo.mixedLightingMode = (int)l.bakingOutput.lightmapBakeType; 288 | #endif 289 | lightsInfo.Add(lightInfo); 290 | 291 | } 292 | } 293 | #endif 294 | 295 | } 296 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PrefabLightmapping 2 | Script for saving lightmapping data to prefabs. Used through the Assets tab in Unity. Place your prefabs in the scene with this script at the root. Set up your lighting and in the editor go to Assets->Bake Prefab Lightmaps. After the bake is processed you can now spawn your prefabs in different scenes and they will use the lightmapping from the original scene. 3 | 4 | Remember that if you are not instantiating your prefabs at runtime you should remove the static flag from the GameObjects, otherwise static batching will mess with uvs and the lightmap won't work properly. 5 | 6 | If you find problems when building make sure to check your graphics settings under Project Settings, as shader stripping might be the cause of the issue. Try playing with the option "Lightmap Modes" and setting it to Custom if it's not working. 7 | 8 | ![Graphics Settings](https://user-images.githubusercontent.com/13970424/60190570-7dd05680-97f8-11e9-991f-f54b816a577f.png) 9 | 10 | *There is also an issue with Probuilder Objects so make sure to bake those meshes down so you don't use Probuilder objects in the prefabs. 11 | 12 | ** I added an optional checkbox called "Release Shaders" that is true by default, this makes the system reassign the shaders when applying the lightmaps. In some cases this is not necessary and it might conflict with certain shader keywords, if you notice some of your materials losing shader features you can uncheck this and that might fix your specific issue. It is true by default. 13 | 14 | Original idea came from Joachim_Ante in the Unity forums 15 | 16 | Feel free to use this project in all commercial and personal projects ;) 17 | 18 | # Runtime Light Probes 19 | 20 | I added a new script that can be used to use lightprobes with your lightmapped prefabs. It needs a special setup though. You need to create a uniform lightprobe group in the scene you are going to instantiate prefabs at, and then do an "empty" bake in that scene. Then this script (LightProbeRuntime.cs) should be placed in that scene where you instantiate prefabs, and it will wait a frame to calculate the light volume contribution from the lights (only point lights for now) in the scene (so for this to work properly you need to have those lights be a part of the prefabs rooms/assets you bake, so this script finds the lights and adds their "would be" contribution to the probes). You can easily tweak this to call it at will if you prefer to recalculate probes at different times. 21 | --------------------------------------------------------------------------------