無限大地圖:lightmap拆分


  無縫地圖涉及到地形、物件的分塊加載,同樣,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, int bakeTag)
        {
            LightmapParameters lp = new LightmapParameters(); lp.bakedLightmapTag = bakeTag; AssetDatabase.CreateAsset(lp, path); AssetDatabase.ImportAsset(path); } public static void SetRenderLightmapParameters(Renderer renderer, string giparamsFile) { SerializedObject so = new SerializedObject(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 static Vector2[] GetMeshUV2(Mesh m)
        {
    //如果不存在uv2,則使用uv代替
            var uv2 = m.uv2; if (uv2 == null || uv2.Length == 0) uv2 = m.uv; return uv2; } //計算uv的范圍 public static Vector4 GetBounds(Vector2[] uv, Renderer r) { if (uv != null) { var __uv = new Vector2[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 = new Vector4(minx, miny, maxx, maxy); return bounds; } return Vector4.zero; } //通過與LightmapScaleOffset計算出原始uv范圍對應Lightmap貼圖中的范圍 public static Vector4 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); return scaleBounds; } //這里就是扣圖了 public static Texture2D 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 = new Texture2D(blockW, blockH); tex2d.SetPixels(colors); tex2d.Apply(); return tex2d; } //從整lightmap貼圖中扣指定物件的光照貼圖 public static Texture2D PickLightmap(GameObject go, out Vector4 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; return PickTexture(tex, scaleBounds); }

 

以上代碼,重點是uv2和LightmapScaleOffset的計算,算出一個矩形范圍。這里把圖扣出來了,然后是把若干摳出來的圖合並到一個新的Texture。合並圖集用到unity自帶的接口, public Rect[] PackTextures(Texture2D[] textures, int padding, int maximumAtlasSize);
public static void CombineRendererInfos2(List<RendererInfo> rdInfoList, out Rect bound, out Rect[] packedRects)
        {
            packedRects = new Rect[0];

            //計算總面積
            int areaAll = 0;
            for(int i = 0; i < rdInfoList.Count; ++i)
            {
                areaAll += (rdInfoList[i].MyLightmap.width * rdInfoList[i].MyLightmap.height);
            }

            //計算最接近這個面積的寬高尺寸
            int size = 128;
            while(true)
            {
                if (size * size > areaAll)
                    break;
                size *= 2;
            }

            bound = new Rect(0, 0, size, size);

            if (areaAll == 0)
                return;

            while (true)
            {
                List<Rect> rectList = new List<Rect>();
                List<Texture2D> texList = new List<Texture2D>();
                rectList.Add(bound);
                for (int i = 0; i < rdInfoList.Count; ++i)
                {
                    Texture2D tex = new Texture2D(rdInfoList[i].MyLightmap.width, rdInfoList[i].MyLightmap.height);
                    texList.Add(tex);
                }

                Texture2D combined = new Texture2D((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 (int i = 0; i < rdInfoList.Count; ++i)
                    {
                        Vector2 pos = new Vector2(packedRects[i].x, packedRects[i].y);
                        {
                            rdInfoList[i].Position = pos;
                            rdInfoList[i].Position.x *= bound.width;
                            rdInfoList[i].Position.y *= bound.height;

                            //為了消除接縫黑邊
                            var ignorPixels = 1/1f;
                            var blockW = rdInfoList[i].MyLightmap.width;
                            var blockH = rdInfoList[i].MyLightmap.height;
                            var texWidth = bound.width;
                            var texHeight = bound.height;
                            var sourceBounds = rdInfoList[i].OldBound;

                            var scaleUVX = (blockW - ignorPixels) / (texWidth * (sourceBounds.z - sourceBounds.x));
                            var offsetUVX = (pos.x) / (float)texWidth - scaleUVX * sourceBounds.x;

                            var scaleUVY = (blockH - ignorPixels) / (texHeight * (sourceBounds.w - sourceBounds.y));
                            var offsetUVY = (pos.y) / (float)texHeight - scaleUVY * sourceBounds.y;

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

 

上面重點是合並到新的lightmap后,需要重新計算Renderer的LightmapScaleOffset
var 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.y
 
rdInfoList[i].MyLightmapScaleInfo = new Vector4(scaleUVX, scaleUVY, offsetUVX, offsetUVY);
 
實際上就是前面通過uv與LightmapScaleOffset計算一個范圍的反推過程,現在是已知最終范圍和原始uv,計算新的LightmapScaleOffset,原理還是很簡單的。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM