在很多情况下,我们为了提高渲染效率,一般都会让美术同学在制作场景时,设置场景相关节点的lightmap static属性,提前给整个场景烘培出静态的光照贴图lightmap,烘培的数据保存在场景目录下的LightmapSnapshot文件中,主要包括的数据有:
lightmaps:烘培出的光照贴图数组;
gameobject uid:被烘培的gameobject的唯一标识;
renderer的lightmapIndex:被烘培的gameobject的renderer组件所指向的光照贴图数组的索引;
renderer的lightmapScaleOffset:被烘培的gameobject的renderer组件所指向的光照贴图用于采样的区域坐标和宽高;
这个文件目前没有相关api读写,如果你想烘培完场景之后,把场景里面的gameobjet抽出来做成prefab,等切换完场景之后再用于动态加载是不可行的,因为抽出来的prefab在Instantiate之后将会是一个新的gameobject,uid自然和LightmapSnapshot文件里面记录的不一样,导致找不到对应的光照数据而造成模型没光照变暗或渲染错乱。
还有一种比较常见的需求是,在游戏运行时,通过更换光照贴图数据,营造场景在不同时间或季节的光照氛围,例如白天和黑夜等。
so,就算场景烘培完之后,我们还是要“动”它。
做法大概是,既然LightmapSnapshot文件我们不能动,那就把上面提到的光照数据保存到我们可以控制的文件里面,例如prefab。
首先,给场景根节点挂一个自定义组件,用于保存烘培出的光照贴图数组和烘培模式:
using UnityEngine ;
using System . Collections ;
[ ExecuteInEditMode ]
public class SceneLightMapSetting : MonoBehaviour {
public Texture2D [ ] lightmapFar , lightmapNear ;
public LightmapsMode mode ;
public void SaveSettings ( )
{
mode = LightmapSettings . lightmapsMode ;
lightmapFar = null ;
lightmapNear = null ;
if ( LightmapSettings . lightmaps != null && LightmapSettings . lightmaps . Length > 0 )
{
int l = LightmapSettings . lightmaps . Length ;
lightmapFar = new Texture2D [ l ] ;
lightmapNear = new Texture2D [ l ] ;
for ( int i = 0 ; i < l ; i ++ )
{
lightmapFar [ i ] = LightmapSettings . lightmaps [ i ] . lightmapFar ;
lightmapNear [ i ] = LightmapSettings . lightmaps [ i ] . lightmapNear ;
}
}
RendererLightMapSetting [ ] savers = Transform . FindObjectsOfType < RendererLightMapSetting > ( ) ;
foreach ( RendererLightMapSetting s in savers )
{
s . SaveSettings ( ) ;
}
}
public void LoadSettings ( )
{
LightmapSettings . lightmapsMode = mode ;
int l1 = ( lightmapFar == null ) ? 0 : lightmapFar . Length ;
int l2 = ( lightmapNear == null ) ? 0 : lightmapNear . Length ;
int l = ( l1 < l2 ) ? l2 : l1 ;
LightmapData [ ] lightmaps = null ;
if ( l > 0 )
{
lightmaps = new LightmapData [ l ] ;
for ( int i = 0 ; i < l ; i ++ )
{
lightmaps [ i ] = new LightmapData ( ) ;
if ( i < l1 )
lightmaps [ i ] . lightmapFar = lightmapFar [ i ] ;
if ( i < l2 )
lightmaps [ i ] . lightmapNear = lightmapNear [ i ] ;
}
LightmapSettings . lightmaps = lightmaps ;
}
}
void OnEnable ( )
{
#if UNITY_EDITOR
UnityEditor . Lightmapping . completed += SaveSettings ;
#endif
}
void OnDisable ( )
{
#if UNITY_EDITOR
UnityEditor . Lightmapping . completed -= SaveSettings ;
#endif
}
void Awake ( ) {
if ( Application . isPlaying ) {
LoadSettings ( ) ;
}
}
}
再给场景里面被烘培的gameobject挂一组件,用于保存光照贴图数组的索引和光照贴图的区域坐标和宽高数据:
using UnityEngine ;
using System . Collections ;
[ ExecuteInEditMode ]
public class RendererLightMapSetting : MonoBehaviour {
public int lightmapIndex ;
public Vector4 lightmapScaleOffset ;
public void SaveSettings ( )
{
if ( ! IsLightMapGo ( gameObject ) ) {
return ;
}
Renderer renderer = GetComponent < Renderer > ( ) ;
lightmapIndex = renderer . lightmapIndex ;
lightmapScaleOffset = renderer . lightmapScaleOffset ;
}
public void LoadSettings ( )
{
if ( ! IsLightMapGo ( gameObject ) ) {
return ;
}
Renderer renderer = GetComponent < Renderer > ( ) ;
renderer . lightmapIndex = lightmapIndex ;
renderer . lightmapScaleOffset = lightmapScaleOffset ;
}
public static bool IsLightMapGo ( GameObject go ) {
if ( go == null ) {
return false ;
}
Renderer renderer = go . GetComponent < Renderer > ( ) ;
if ( renderer == null ) {
return false ;
}
return true ;
}
void Awake ( ) {
if ( Application . isPlaying ) {
LoadSettings ( ) ;
}
}
}
如果手动挂上面两个脚本的话,繁琐且容易出错,so,写工具接口,用工具扫场景挂脚本:
using UnityEngine ;
using UnityEditor ;
using System . IO ;
using System . Text ;
using System . Linq ;
using System . Collections ;
using System . Collections . Generic ;
using System ;
public class SceneTools
{
static string [ ] _sNeedAssetType = new string [ 1 ] {
".unity" ,
} ;
[ MenuItem ( "场景相关/1. 保存场景光照贴图信息" , false , 111 ) ]
public static void SaveSceneMapLightSetting ( )
{
UnityEngine . Object [ ] selObjs = Selection . GetFiltered ( typeof ( UnityEngine . Object ) , SelectionMode . DeepAssets ) ;
if ( selObjs == null || selObjs . Length == 0 ) {
Debug . LogError ( "请到\"Scenes\" 目录下选中需要保存场景光照贴图信息的场景!" ) ;
return ;
}
string assetPath ;
UnityEngine . Object assetObj = null ;
GameObject gameObj = null ;
SceneLightMapSetting slms = null ;
RendererLightMapSetting rlms = null ;
bool needSave = false ;
for ( int i = 0 ; i < selObjs . Length ; ++ i ) {
needSave = false ;
assetPath = AssetDatabase . GetAssetPath ( selObjs [ i ] ) ;
foreach ( string extName in _sNeedAssetType ) {
if ( assetPath . EndsWith ( extName ) ) {
needSave = true ;
break ;
}
}
if ( ! needSave )
continue ;
assetObj = AssetDatabase . LoadAssetAtPath ( assetPath , typeof ( UnityEngine . Object ) ) as UnityEngine . Object ;
EditorApplication . OpenScene ( assetPath ) ;
gameObj = GameObject . Find ( "scene_root" ) ;
if ( gameObj == null ) {
Debug . LogError ( "不合法的场景:场景没有scene_root根节点!!" ) ;
continue ;
}
slms = gameObj . GetComponent < SceneLightMapSetting > ( ) ;
if ( slms == null ) {
slms = gameObj . AddComponent < SceneLightMapSetting > ( ) ;
}
Renderer [ ] savers = Transform . FindObjectsOfType < Renderer > ( ) ;
foreach ( Renderer s in savers )
{
if ( s . lightmapIndex != - 1 ) {
rlms = s . gameObject . GetComponent < RendererLightMapSetting > ( ) ;
if ( rlms == null ) {
rlms = s . gameObject . AddComponent < RendererLightMapSetting > ( ) ;
}
}
}
slms . SaveSettings ( ) ;
EditorApplication . SaveScene ( ) ;
Debug . Log ( string . Format ( "场景{0}的光照贴图信息保存完成" , assetObj . name ) ) ;
}
EditorApplication . SaveAssets ( ) ;
}
}
没截图从觉得少点什么,来,上图,先打开一个被烘培过的场景,场景根节点为scene_root,被烘培的gameobjet是地板、一颗小草、带logo的立方体:
然后使用工具给场景里面的相关节点挂脚本存数据:
把场景根节点scene_root拖出来做成prefab,然后删掉场景里面的scene_root,再把scene_root.prefab拖进场景里面:
此时模型是丢失了光照数据的,原因截图已说明。接着运行场景:
模型的光照又回来啦,原因截图已说明。