lightmap拆分无限大地图

fish_yu    2018-8-27

无缝地图涉及到地形、物件的分块加载,同样,lightmap也需要动态加载。而场景烘焙时,所有物件都是一起烘焙的,那怎么把某些物件指定烘焙到某一张lightmap贴图中?网上找了很久,也没有看到具体的实现方式,还是要自己从头造车,结合网上的一些理论,经过实践,分享2个方法1. 设置自定义LightmapParameters,设置Bake tag,相同tag的物件,会烘焙到同一张lightmap中。Terrain_1_1是新创建的LightmapParameters,替换掉默认的Pamameters

2. 通过计算Renderer的uv,从完整Lightmap贴图中抠出来指定某个范围的贴图,然后与想要合并到一起的其他贴图生成一个新的lightmap贴图第一个方法是最省事的,但是有一个问题,LightmapParameters看起来只有手动设置,Unity没有提供api,貌似不能用代码来实现自动化。那么只能使用黑科技了(反编译UnityEditor.dll得来的方法)

 //创建LightmapParameters资源文件
        public static void CreateLightmapParameterFile(string path, intbakeTag)
        {
            LightmapParameters lp = newLightmapParameters();
            lp.bakedLightmapTag =bakeTag;
            AssetDatabase.CreateAsset(lp, path);
            AssetDatabase.ImportAsset(path);
        }

        public static void SetRenderLightmapParameters(Renderer renderer, stringgiparamsFile)
        {
            SerializedObject so = newSerializedObject(renderer);
            var sp = so.FindProperty("m_LightmapParameters");
            sp.objectReferenceValue = AssetDatabase.LoadAssetAtPath(giparamsFile, typeof(LightmapParameters));
            so.ApplyModifiedProperties();
        }

重点在so.FindProperty("m_LightmapParameters");这是一个不对外的成员变量,通过反射来获取,然后设置objectReferenceValue ,关联之前创建的LightmapParameters文件。后面就简单了,自己实现吧

下面是第二个方法,Renderer里面有个变量lightmapScaleOffset,这个变量记录了光照uv的缩放和偏移量,通过计算可以得到该Renderer在lightmap完整贴图中的范围,然后就能扣出来这里总结一下流程a:通过uv2和Renderer的LightmapScaleOffset计算Lightmap贴图中的占用范围(bound)b:抠图,打包图集,获得新的范围(bound)c:已知新的bound和原始uv2,计算新的LightmapScaleOffset

//获取uv2
        public staticVector2[] GetMeshUV2(Mesh m)
        {
    //如果不存在uv2,则使用uv代替
            var uv2 =m.uv2;
            if (uv2 == null || uv2.Length == 0)
                uv2 =m.uv;

            returnuv2;
        }

       //计算uv的范围
        public staticVector4 GetBounds(Vector2[] uv, Renderer r)
        {
            if (uv != null)
            {
                var __uv = newVector2[uv.Length];
                Array.Copy(uv, __uv, uv.Length);
                uv =__uv;
                var minx = float.MaxValue;
                var miny = float.MaxValue;
                var maxx = float.MinValue;
                var maxy = float.MinValue;
                for (var _j = 0; _j < uv.Length; ++_j)
                {
                    var _uv =uv[_j];
                    if (_uv.x <minx)
                    {
                        minx =_uv.x;
                    }
                    if (_uv.y <miny)
                    {
                        miny =_uv.y;
                    }
                    if (_uv.x >maxx)
                    {
                        maxx =_uv.x;
                    }
                    if (_uv.y >maxy)
                    {
                        maxy =_uv.y;
                    }
                }
                var bounds = newVector4(minx, miny, maxx, maxy);
                returnbounds;
            }

            returnVector4.zero;
        }

//通过与LightmapScaleOffset计算出原始uv范围对应Lightmap贴图中的范围
        public staticVector4 CalcBoundWithLightmapScaleOffset(Vector4 sourceBounds, Vector4 lightmapScaleOffset)
        {
            var scaleBounds = new Vector4(sourceBounds.x * lightmapScaleOffset.x +lightmapScaleOffset.z,
                                     sourceBounds.y * lightmapScaleOffset.y +lightmapScaleOffset.w,
                                     sourceBounds.z * lightmapScaleOffset.x +lightmapScaleOffset.z,
                                     sourceBounds.w * lightmapScaleOffset.y +lightmapScaleOffset.w);

            returnscaleBounds;
        }

//这里就是扣图了
public staticTexture2D PickTexture(Texture2D sourceTex, Vector4 bounds)
        {
            var blockW = (int)((bounds.z - bounds.x) *sourceTex.width);
            var blockH = (int)((bounds.w - bounds.y) *sourceTex.height);
            int startX = (int)(bounds.x *sourceTex.width);
            int startY = (int)(bounds.y *sourceTex.height);

            //startY = (tex.height - startY - blockH);
            if (blockH == 0 || blockW == 0)
                return null;

            var colors =sourceTex.GetPixels(startX, startY, blockW, blockH);
            Texture2D tex2d = newTexture2D(blockW, blockH);
            tex2d.SetPixels(colors);
            tex2d.Apply();
            returntex2d;
        }

//从整lightmap贴图中扣指定物件的光照贴图
        public static Texture2D PickLightmap(GameObject go, outVector4 bound)
        {
            bound =Vector4.zero;
            if (go == null)
                return null;

            var meshFilter = go.GetComponent<MeshFilter>();
            var renderer = go.GetComponent<Renderer>();
            if (meshFilter == null || renderer == null)
                return null;

            var tex =GetFullLightmap(renderer);
            var uv2s =GetMeshUV2(meshFilter.sharedMesh);
            //var bounds = GetBounds(uv2s, renderer);
            var sourceBounds =LightMapUtil.GetBounds(uv2s, renderer);
            var scaleBounds =CalcBoundWithLightmapScaleOffset(sourceBounds, renderer.lightmapScaleOffset);
            bound =sourceBounds;

            returnPickTexture(tex, scaleBounds);
        }

以上代码,重点是uv2和LightmapScaleOffset的计算,算出一个矩形范围。这里把图扣出来了,然后是把若干抠出来的图合并到一个新的Texture。合并图集用到unity自带的接口,public Rect[] PackTextures(Texture2D[] textures, int padding, int maximumAtlasSize);

publicstaticvoidCombineRendererInfos2(List<RendererInfo> rdInfoList,outRect bound,outRect[] packedRects)
        {
            packedRects=newRect[0];//计算总面积intareaAll =0;for(inti =0; i < rdInfoList.Count; ++i)
            {
                areaAll+= (rdInfoList[i].MyLightmap.width *rdInfoList[i].MyLightmap.height);
            }//计算最接近这个面积的宽高尺寸intsize =128;while(true)
            {if(size * size >areaAll)break;
                size*=2;
            }

            bound=newRect(0,0, size, size);if(areaAll ==0)return;while(true)
            {
                List<Rect> rectList =newList<Rect>();
                List<Texture2D> texList =newList<Texture2D>();
                rectList.Add(bound);for(inti =0; i < rdInfoList.Count; ++i)
                {
                    Texture2D tex=newTexture2D(rdInfoList[i].MyLightmap.width, rdInfoList[i].MyLightmap.height);
                    texList.Add(tex);
                }

                Texture2D combined=newTexture2D((int)bound.width, (int)bound.height, TextureFormat.ARGB32,false);

                packedRects= combined.PackTextures(texList.ToArray(),0, size);if(packedRects ==null|| packedRects.Length ==0|| packedRects[0].width * bound.width < texList[0].width)
                {
                    bound.width*=2;
                    bound.height*=2;
                    size*=2;
                }else{
                    bound.width=combined.width;
                    bound.height=combined.height;for(inti =0; i < rdInfoList.Count; ++i)
                    {
                        Vector2 pos=newVector2(packedRects[i].x, packedRects[i].y);
                        {
                            rdInfoList[i].Position=pos;
                            rdInfoList[i].Position.x*=bound.width;
                            rdInfoList[i].Position.y*=bound.height;//为了消除接缝黑边varignorPixels =1/1f;varblockW =rdInfoList[i].MyLightmap.width;varblockH =rdInfoList[i].MyLightmap.height;vartexWidth =bound.width;vartexHeight =bound.height;varsourceBounds =rdInfoList[i].OldBound;varscaleUVX = (blockW - ignorPixels) / (texWidth * (sourceBounds.z -sourceBounds.x));varoffsetUVX = (pos.x) / (float)texWidth - scaleUVX *sourceBounds.x;varscaleUVY = (blockH - ignorPixels) / (texHeight * (sourceBounds.w -sourceBounds.y));varoffsetUVY = (pos.y) / (float)texHeight - scaleUVY *sourceBounds.y;

                            rdInfoList[i].MyLightmapScaleInfo=newVector4(scaleUVX, scaleUVY, offsetUVX, offsetUVY);
                        }
                    }break;
                }
            }
        }

上面重点是合并到新的lightmap后,需要重新计算Renderer的LightmapScaleOffsetvar scaleUVX = (blockW) / (texWidth * (sourceBounds.z - sourceBounds.x));var offsetUVX = pos.x / (float)texWidth - scaleUVX * sourceBounds.x;var scaleUVY = (blockH) / (texHeight * (sourceBounds.w - sourceBounds.y));var offsetUVY = pos.y / (float)texHeight - scaleUVY * sourceBounds.yrdInfoList[i].MyLightmapScaleInfo = new Vector4(scaleUVX, scaleUVY, offsetUVX, offsetUVY);实际上就是前面通过uv与LightmapScaleOffset计算一个范围的反推过程,现在是已知最终范围和原始uv,计算新的LightmapScaleOffset,原理还是很简单的。






暗色背景
手机扫码阅读本页