Unity2017是明确不支持GPU Instancing的,如果在材质上勾选了enable GPU Instancing后,如下图所示,在Frame Debugger中,我们能清楚的看到所有的游戏对象没有合批,提示Objects are lightmapped。
所以为了让Unity2017支持GPU Instancing我们就不能使用游戏对象的模式,必须采用Graphics.DrawMeshInstanced接口来绘制。具体使用方法可以看我之前的文章Unity3D研究院GPU Instancing实战(九十七)
并且参与烘焙的材质我们需要手动修改,不能使用默认的standard着色器,原因就是我们要把每个物体烘焙过的LightmapScaleOffset传进去。如果你对LightmapIndex、LightmapScaleOffset、UV2之间的关系还不太了解,可以看我之前的文章。Unity3D研究院之LightmapIndex、LightmapScaleOffset、UV2的关系(一百零六)
首先需要改造standard着色器,在ps结构体里声明float2 uv1 : TEXCOORD1。
1 2 3 4 5 6 7 8 9 |
struct v2f { float2 uv0 : TEXCOORD0; float2 uv1 : TEXCOORD1; float4 pos : SV_POSITION; UNITY_VERTEX_INPUT_INSTANCE_ID }; |
接着需要在vs里返回和LightmapScaleOffset计算后的uv2,也就是上面定义的float2 uv1 : TEXCOORD1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
v2f vert(appdata IN) { v2f o; UNITY_SETUP_INSTANCE_ID(IN); UNITY_TRANSFER_INSTANCE_ID(IN, o); o.uv0 = TRANSFORM_TEX(IN.uv0, _MainTex); o.pos = UnityObjectToClipPos(IN.pos); //IN.uv1.xy表示每个mesh的uv2 //unity_LightmapST.xy表示LightmapScaleOffset.xy也就是Tiling x Tiling y //unity_LightmapST.zw表示LightmapScaleOffset.zw Offset x Offset y o.uv1 = IN.uv1.xy * unity_LightmapST.xy + unity_LightmapST.zw; return o; } |
最后在PS里就可以采样lightmap的颜色了。
1 2 3 4 5 6 7 8 9 |
fixed4 frag(v2f IN) : SV_Target { UNITY_SETUP_INSTANCE_ID(IN); fixed4 col; col.rgb = DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, IN.uv1.xy)); return col; } |
以上代码修改完毕,我们就可以开始烘焙场景了,如下图所示,右侧我们能看到现在还游戏对象模式。
接着我们要使用Graphics.DrawMeshInstanced来进行绘制,按照上面我们修改的shader其实只需要将unity_LightmapST传入即可,但是很遗憾的说我们不能直接传入。如下图所示,首先指定Custom材质,然后在指定一张特定的烘焙贴图,因为实际环境中可能会有多张烘焙贴图的情况。
Custom.shader,请大家注意//—add—中的内容。
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
Shader "Mobile/Custom" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} //---add--- _LightmapST("_LightmapST",Vector)=(0,0,0,0) //---add--- } SubShader { Tags { "RenderType"="Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_instancing #include "UnityCG.cginc" sampler2D _MainTex; float4 _MainTex_ST; struct appdata { float3 pos : POSITION; float3 uv0 : TEXCOORD0; float3 uv1 : TEXCOORD1; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float2 uv0 : TEXCOORD0; //---add--- float2 uv1 : TEXCOORD1; //---add--- float4 pos : SV_POSITION; UNITY_VERTEX_INPUT_INSTANCE_ID }; //将MeshRenderer中的LightmapScaleOffset分别传入_LightmapST中 UNITY_INSTANCING_BUFFER_START(Props) UNITY_DEFINE_INSTANCED_PROP(fixed4, _LightmapST) UNITY_INSTANCING_BUFFER_END(Props) v2f vert(appdata IN) { v2f o; UNITY_SETUP_INSTANCE_ID(IN); UNITY_TRANSFER_INSTANCE_ID(IN, o); o.uv0 = TRANSFORM_TEX(IN.uv0, _MainTex); //---add--- //取出每一个物体的LightmapScaleOffset重新算UV2 fixed4 l = UNITY_ACCESS_INSTANCED_PROP(Props, _LightmapST); o.uv1 = IN.uv1.xy * l.xy + l.zw; //---add--- o.pos = UnityObjectToClipPos(IN.pos); return o; } fixed4 frag(v2f IN) : SV_Target { UNITY_SETUP_INSTANCE_ID(IN); fixed4 col; //在这里采样lightmap col.rgb = DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, IN.uv1.xy)); return col; } ENDCG } } } |
最后就是Custom.cs
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
using System.Collections.Generic; using UnityEngine; public class Custom : MonoBehaviour { public Material material; public Texture2D lightmapTexture; MeshFilter meshfilter; List<Matrix4x4> materix = new List<Matrix4x4>(); List<Vector4> lightmapOffset = new List<Vector4>(); MaterialPropertyBlock block; void Start() { //获取节点下的所有MeshRenderer foreach (var item in GetComponentsInChildren<MeshRenderer>()) { //隐藏它 item.gameObject.SetActive(false); if (meshfilter == null) { //取出第一个mesh,因为我们是做例子可以肯定保证下面的mesh都是一样的 meshfilter = item.GetComponent<MeshFilter>(); } //保存每个物体的矩阵以及lightmapScaleOffset materix.Add(item.localToWorldMatrix); lightmapOffset.Add(item.lightmapScaleOffset); } //启动LIGHTMAP_ON宏 material.EnableKeyword("LIGHTMAP_ON"); //为了避免GC所以只new一次MaterialPropertyBlock block = new MaterialPropertyBlock(); //设置具体用哪张烘焙贴图 block.SetTexture("unity_Lightmap", lightmapTexture); //将每个物体的lightmapScaleOffset传入shader中 block.SetVectorArray("_LightmapST", lightmapOffset.ToArray()); } void Update() { //一次绘制 Graphics.DrawMeshInstanced(meshfilter.sharedMesh, 0, material, materix, block); } } |
运行游戏,如下图所示,游戏对象都已经全部隐藏,通过Graphics.DrawMeshInstanced 一次绘制完毕,并且设置了正确的烘焙贴图。
OK,我们在来看看Unity2018和Unity2019,从Unity2018开始Lightmap已经支持GPUInstancing了,如果是游戏对象的方式烘焙完场景就直接显示lightmap了,但是为了极致的性能优化,Graphics.DrawMeshInstanced 的效率明显还是要高。
——————-以下方法在真机上不行但是编辑器上可行,所以目前来看只能用上面的方法//——————-
在Unity2019中我们打开了LWRP了,此时在去修改Shader就太麻烦了,不过还好我们可以直接传入unity_LightmapST,这样就不需要改代码了。
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 |
void Start() { foreach (var item in GetComponentsInChildren<MeshRenderer>()) { if(meshfilter == null) { meshfilter = item.GetComponent<MeshFilter>(); } materix.Add(item.localToWorldMatrix); lightmapOffset.Add(item.lightmapScaleOffset); item.gameObject.SetActive(false); } material.EnableKeyword("LIGHTMAP_ON"); block = new MaterialPropertyBlock(); block.SetTexture("unity_Lightmap", lightmapTexture); //unity2019可以在这直接传入unity_LightmapST block.SetVectorArray("unity_LightmapST", lightmapOffset.ToArray()); } void Update() { //一次渲染 Graphics.DrawMeshInstanced(meshfilter.sharedMesh, 0, material, materix, block); } |
如下图所示,在Unity2019中lightmap已经渲染出来了。
如果Unity2019没有用LWRP,unity会计算球谐光照,需要改下代码。主要是LightProbeUsage.CustomProvided,因为CustomProvider没有提供球谐,所以这样球谐就都没有值了,所以计算就对了。
1 2 3 |
Graphics.DrawMeshInstanced(meshfilter.sharedMesh, 0, material, materix, block,ShadowCastingMode.Off,false,0,Camera.main,LightProbeUsage.CustomProvided); |
总之现在项目都在用LWRP了,用上面的方法肯定就没错了。
最后在补充一下,SRP提供了SRPBatcher,它可以对不同Mesh相同材质进行“合并”,你也可以理解合批批次,它比GPUInstancing还要好,因为后者需要同时满足相同Mesh相同材质才能合批。
如果游戏对象情况下勾选了enable GPU insatncing会优先启动SRPBatcher。
非游戏对象情况,Graphics.DrawMeshInstanced绘制出来的,才会优先启用GPUInstancing。
还有ECS提供了无游戏对象的渲染,其实也存在Lightmap的问题,按照上面的做法也同样可以显示正确的Lightmap ,还有Hybird也是同样的套路。
- 本文固定链接: https://www.xuanyusong.com/archives/4640
- 转载请注明: 雨松MOMO 于 雨松MOMO程序研究院 发表
使用不同光照贴图的对象,是不能使用Graphics.DrawMeshInstanced渲染的吗
我也想问这个问题,这样好像同对象,但在不同两张光照贴图上 只能使用分开渲染了
可以使用 Texture2DArray
block.SetTexture(“unity_Lightmap”, lightmapTexture); 中的lightmapTexture 怎么赋值
用了custom的shader 材质球直接黑了 是哪里有问题吗
我也是 用这个shader拖进材质球 变黑了
materiall.EnableKeyword(“SHADOWS_SHADOWMASK”);
block.SetTexture(“unity_ShadowMask”, LightmapSettings.lightmaps[renderer.lightmapIndex].shadowMask);
如果需要烘焙阴影的话,建议加上这两句
大佬 要是多张shadowmask怎么处理啊
material打ab包的情况下,使用Graphics.DrawMeshInstanced无法渲染,请问有遇到过这样的问题吗?
bake完后lightmapScaleOffset数据会被自动写入么
对的