6

Unity3D研究院之LightmapIndex、LightmapScaleOffset、UV2的关系(一百零六)

 3 years ago
source link: https://www.xuanyusong.com/archives/4633
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Unity3D研究院之LightmapIndex、LightmapScaleOffset、UV2的关系(一百零六)

烘焙Lightmap以后unity会自动给参与烘焙的所有mesh添加uv2的属性,例如,三角形每个顶点都会有UV2它记录着这个每个顶点对应Lightmap图中的UV值,这样拥有3个顶点的三角形面就可以通过UV2在Lightmap中线性采样烘焙颜色了。

LightmapIndex:

所以每个MeshRenderer 需要传入一个LightmapIndex也就是采样哪张Lightmap烘焙图。

LightmapScaleOffset:

由于Mesh的位置摆放的不同,那么即使相同的Mesh的它们烘焙出来UV2肯定是不一样的。我们知道Unity是个大黑盒,我们是不能访问图形API的,渲染一个Mesh我们唯一能做的就是送入Mesh信息,如果相同的Mesh烘焙出来的UV2不一样,那么它将变成多个Mesh文件了,这样送到GPU中无疑是浪费资源(SET PASS CALL 包体大小都会有影响),所以Unity想了个非常好的办法就是LightmapScaleOffset。

如下图所示,我们可以看到每个MeshRenderer中都包含了Tiling x Tiling y Offset x Offset y。

Unity3D研究院之LightmapIndex、LightmapScaleOffset、UV2的关系(一百零六) - 雨松MOMO程序研究院 - 1

Offset x Offset y 表示Mesh每个顶点的UV从Lightmap图的Offset x Offset y 处开始采样,还没完上图中我们还可以设置Scale in Lightmap,也就是物体所占烘焙贴图的比例系数,这样光有 Offset x Offset y是不行的,还需要乘以Tiling x Tiling y 。

举个例子,比如现在场景里有两个Cube,由于它们需要共享mesh所以它们的UV2烘焙完以后也是一致的(相对的偏移),如果这两个Cube分别设置了不同的Scale in Lightmap系数,那么它们反应的烘焙贴图上的区域就不同的,那么相同UV2和Offset x Offset y 是无法计算出正确的采样偏移的,所以就需要乘以Tiling x Tiling y  。

如下图所示,FBX在导出的时候需要勾选Generate Lightmap UVs就是是否生成UV2了,换句话说UV2在模型导入的时候就已经设置好了,烘焙不烘焙是改变不了UV2的信息的。

Unity3D研究院之LightmapIndex、LightmapScaleOffset、UV2的关系(一百零六) - 雨松MOMO程序研究院 - 2

前几天我在看Mesh合并的时候,Mesh.CombineMeshes()第4个参数如果为true表示同时合并Lightmap,所以我就在想合并前每个MeshRender的LightmapScaleOffset都是不同的,但是合并完以后怎么给他设置正确的LightmapScaleOffset呢?后来发现根本不用设置LightmapScaleOffset只需要设置LightmapIndex就可以的,顺着这个问题我就在想Unity烘焙贴图的原理。

现在终于搞明白了,Unity会通过LightmapIndex、LightmapScaleOffset在图形API中修正正确的UV2,Mesh.CombineMeshes()之所以能做到这一点是因为提前把正确的UV2赋给Mesh了,既然已经合并Mesh其实也不存在多个共享的需求。

下面我们在代码中重现这个步骤。

先烘焙一下场景,如下图所示,请大家记住CUBE正确的烘焙信息。

Unity3D研究院之LightmapIndex、LightmapScaleOffset、UV2的关系(一百零六) - 雨松MOMO程序研究院 - 3

然后在Hierarchy中选择这个CUBE对象,然后执行Tools/SetUV2把烘焙过的LightmapScaleOffset重新对每个Mesh的UV2赋值,并且保存文件。

#if UNITY_EDITOR
    [MenuItem("Tools/SetUV2")]
    private static void SetUV2()
        if (Selection.activeTransform)
            Mesh mesh = Instantiate <Mesh>(Selection.activeTransform.GetComponent<MeshFilter>().sharedMesh);
            Vector4 lightmapOffsetAndScale = Selection.activeTransform.GetComponent<MeshRenderer>().lightmapScaleOffset;
            //Mesh的UV2重新赋值
            Vector2[] modifiedUV2s = mesh.uv2;
            for (int i = 0; i < mesh.uv2.Length; i++)
                modifiedUV2s[i] = new Vector2(mesh.uv2[i].x * lightmapOffsetAndScale.x +
                lightmapOffsetAndScale.z, mesh.uv2[i].y * lightmapOffsetAndScale.y +
                lightmapOffsetAndScale.w);
            mesh.uv2 = modifiedUV2s;
            //mesh写到文件中
            AssetDatabase.CreateAsset(mesh, "Assets/newMesh.asset");
            Selection.activeTransform.GetComponent<MeshFilter>().sharedMesh = mesh;
#endif

如下图所示,最正确的烘焙UV2已经保存在Mesh中了。

Unity3D研究院之LightmapIndex、LightmapScaleOffset、UV2的关系(一百零六) - 雨松MOMO程序研究院 - 4

如下图所示,我们创建一个新的CUBE,把MeshFilter中的mesh换成刚刚生成包含完整UV2的新Mesh。

Unity3D研究院之LightmapIndex、LightmapScaleOffset、UV2的关系(一百零六) - 雨松MOMO程序研究院 - 5

此时肯定是没有烘焙信息,因为我们还没有指定LightmapIndex,由于我这个例子只烘焙 出了一张烘焙贴图,所以代码我们这样写。

    private void Awake()
        GetComponent<MeshRenderer>().lightmapIndex = 0;

如下图所示,运行起来我们在看,不需要在设置LightmapScaleOffset依然显示了正确的Lightmap信息。

Unity3D研究院之LightmapIndex、LightmapScaleOffset、UV2的关系(一百零六) - 雨松MOMO程序研究院 - 6

知道了原理,我们还是要慎用,因为这样就无法共享mesh了,对应打包肯定会增加包体大小的。

作者:雨松MOMO
专注移动互联网,Unity3D游戏开发
捐 赠写博客不易,如果您想请我喝一杯星巴克的话?就进来看吧!

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK