zoukankan      html  css  js  c++  java
  • 无限大地图: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,原理还是很简单的。
     
  • 相关阅读:
    [Vue warn]: Avoid mutating a prop directly since the value will be overwritten
    修改git地址
    Clipboard使用
    Electron as GUI of Python
    VUE AXIOS封装三
    Vue-Cli3/Webpack / i18n / image / CDN vue项目优化总结
    echarts 折线图点击高亮
    vue中 左侧导航条 多个toggleClass
    js中异常处理
    Element UI disableddate设置动态时间,结束日期不可大于开始日期以及时间点
  • 原文地址:https://www.cnblogs.com/fishyu/p/7703812.html
Copyright © 2011-2022 走看看