烘焙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。
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的信息的。
前几天我在看Mesh合并的时候,Mesh.CombineMeshes()第4个参数如果为true表示同时合并Lightmap,所以我就在想合并前每个MeshRender的LightmapScaleOffset都是不同的,但是合并完以后怎么给他设置正确的LightmapScaleOffset呢?后来发现根本不用设置LightmapScaleOffset只需要设置LightmapIndex就可以的,顺着这个问题我就在想Unity烘焙贴图的原理。
现在终于搞明白了,Unity会通过LightmapIndex、LightmapScaleOffset在图形API中修正正确的UV2,Mesh.CombineMeshes()之所以能做到这一点是因为提前把正确的UV2赋给Mesh了,既然已经合并Mesh其实也不存在多个共享的需求。
下面我们在代码中重现这个步骤。
先烘焙一下场景,如下图所示,请大家记住CUBE正确的烘焙信息。
然后在Hierarchy中选择这个CUBE对象,然后执行Tools/SetUV2把烘焙过的LightmapScaleOffset重新对每个Mesh的UV2赋值,并且保存文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#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中了。
如下图所示,我们创建一个新的CUBE,把MeshFilter中的mesh换成刚刚生成包含完整UV2的新Mesh。
此时肯定是没有烘焙信息,因为我们还没有指定LightmapIndex,由于我这个例子只烘焙 出了一张烘焙贴图,所以代码我们这样写。
1 2 3 4 5 6 |
private void Awake() { GetComponent<MeshRenderer>().lightmapIndex = 0; } |
如下图所示,运行起来我们在看,不需要在设置LightmapScaleOffset依然显示了正确的Lightmap信息。
知道了原理,我们还是要慎用,因为这样就无法共享mesh了,对应打包肯定会增加包体大小的。
- 本文固定链接: https://www.xuanyusong.com/archives/4633
- 转载请注明: 雨松MOMO 于 雨松MOMO程序研究院 发表
如果是多个mesh 共用一个灯光贴图,如何用代码把每个mesh的贴图讯息正确的赋予对应mesh的uv2上呢?