Mixed Lighting Lightmap & Shader in Unity | Unity中混合光照Lightmap研究

Unity中有三种灯光模式:Realtime Lighting, Mixed Lighting, Baked Lighting. 第一种是实时计算的,第二种是实时与烘焙混合的,第三种所有灯光都是烘焙的。

在Mixed Lighting中处理Lightmap的同时还要考虑实时灯光,Shader上会复杂一点。并且Mixed Lighting中有三种模式:ShadowMask, Bake Indirect和Subtractive,三种模式的Lightmap定义不一样,这就很尴尬了。那么首先介绍一下这几种模式:

Bake Indirect

烘焙直接阴影,只烘焙间接光,在Shadow Distance(Project Settings>Quality>Shadows里面设置)之内的阴影都是实时计算的(Shadow Map)。
所谓间接光,就是物体之间互相反射的那些光,也包含AO。Unity用的Enlighten系统用的Radiosity辐射度算法计算间接光。
所谓Shadow Map就是以灯光视角depth test计算一下渲染到的像素能不能被看到,它比较费啦因为多一个pass。

Dynamic Receiver
Static Receiver
Shadow Distance内
Shadow Distance外
Shadow Distance内
Shadow Distance外
Dynamic Caster
Shadow Map实时阴影
Shadow Map实时阴影
Static Caster
Shadow Map实时阴影
Shadow Map实时阴影

优点:

  • 有实时阴影
  • 有间接光照

缺点:

  • 性能消耗大
  • Shadow Distance外没阴影

所以高端机可以用一下吧,中低端就算了

Subtractive

这是唯一一个把Mixed Lighiting的阴影烘焙进Lightmap的模式

 

Dynamic Receiver
Static Receiver
Shadow Distance内
Shadow Distance外
Shadow Distance内
Shadow Distance外
Dynamic Caster
Shadow Map实时阴影
主光ShadowMap
Static Caster
Light Probes
Light Probes
Lightmap
Lightmap

他这个模式呢静态阴影是Lightmap读出来的,动态的阴影不是,所以就存在一个阴影颜色混合的问题。选项里有一个Shadow Color,大概能些许解决这个问题。
优点:

  • 静态物体效果好
  • 节省性能

缺点:

  • 不支持实时直接光,静态物体没有高光(?)
  • Baked Lighting对动态物体是用Light Probe方式做的,效果比较差了
  • 费资源
  • 动态静态阴影混合可能有问题

Shadowmask

它会多一张Shadowmask的图,这个图里可以存4个灯光造成的阴影。

Dynamic Receiver
Static Receiver
Shadow Distance内
Shadow Distance外
Shadow Distance内
Shadow Distance外
Dynamic Caster
Shadow Map实时阴影
主光ShadowMap
Static Caster
Light Probes
Light Probes
Shadow Mask
Shadow Mask

优点:

  • 静态动态阴影混合比较容易
  • 静态物体也有实时高光

缺点:

  • shadowmask只有四个光
  • lightprob精度低
  • 多一张图多内存

有一点疑惑的地方,shadowmask多几个灯光的阴影有什么好处?
可能混合实时和动态阴影时,可以多一点阴影混合效果好吧,如果shadowmask只有一个阴影,但有两个投射阴影的灯光,那么第二个阴影会烘焙在lightmap里,这样混合阴影就会遇到和subtractive一样的问题。
另外就是Shadowmask模式下烘焙的lightmap和Baked Indirect一样,是间接光。

Shader采集Lightmap

同一个场景烘焙,左1为Shadowmask的lightmap,左2为Shadowmask那张阴影图,左3为subtractive模式的lightmap
lightmap很明显不一样,shadowmask这个是间接光,黑一点

Subtractive

这个模式是传统模式,阴影就在贴图里,直接采样lightmap就行了
一般这样:


half4 bakedTex = UNITY_SAMPLE_TEX2D(unity_Lightmap, lightmapUV.xy);
half3 decodeColor = DecodeLightmap( bakedTex  );
col *= decodeColor;
return col;

就可以了,注意其中DecodeLightmap时,对于LDR(一般的)的LightMap,实际上就是乘了2。对于HDR的复杂一点。这个函数在UnityCG.cginc里面

Shadowmask

首先烘焙阴影不在unity_Lightmap这个图里,在unity_ShadowMask里。UnityShaderVariables里是这么定义的:


// Main lightmap
UNITY_DECLARE_TEX2D_HALF(unity_Lightmap);
// Directional lightmap (always used with unity_Lightmap, so can share sampler)
UNITY_DECLARE_TEX2D_NOSAMPLER_HALF(unity_LightmapInd);
// Combined light masks
#if defined (SHADOWS_SHADOWMASK)
    #if defined(LIGHTMAP_ON)
        //Can share sampler if lightmap are used.
        UNITY_DECLARE_TEX2D_NOSAMPLER(unity_ShadowMask);
    #else
        UNITY_DECLARE_TEX2D(unity_ShadowMask);
    #endif
#endif

从上面看出,要用这张图要先定义SHADOWS_SHADOWMASK这个关键词,如下:

#pragma multi_compile SHADOWS_SHADOWMASK

 

另外unity_LightmapInd这张图是光线方向的图,可以采样后用来给静态物体算高光

half4 getLightDirPerPixel(float2 lightmapUV) {
    #ifdef LIGHTMAP_ON
        half4 bakedDirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_LightmapInd, unity_Lightmap, lightmapUV.xy);
        return half4(bakedDirTex.xyz-0.5h, bakedDirTex.w);
    #else
        return half4(normalize(_WorldSpaceLightPos0.xyz),1);
    #endif
}

 

要采样这个shadowmask可以直接用一个函数UnitySampleBakedOcclusion,定义在UnityShadowLibrary.cginc里面

inline fixed UnitySampleBakedOcclusion (float2 lightmapUV, float3 worldPos)
{
    #if defined (SHADOWS_SHADOWMASK)
        #if defined(LIGHTMAP_ON)
            fixed4 rawOcclusionMask = UNITY_SAMPLE_TEX2D_SAMPLER(unity_ShadowMask, unity_Lightmap, lightmapUV.xy);
        #else
            fixed4 rawOcclusionMask = fixed4(1.0, 1.0, 1.0, 1.0);
            #if UNITY_LIGHT_PROBE_PROXY_VOLUME
                if (unity_ProbeVolumeParams.x == 1.0)
                    rawOcclusionMask = LPPV_SampleProbeOcclusion(worldPos);
                else
                    rawOcclusionMask = UNITY_SAMPLE_TEX2D(unity_ShadowMask, lightmapUV.xy);
            #else
                rawOcclusionMask = UNITY_SAMPLE_TEX2D(unity_ShadowMask, lightmapUV.xy);
            #endif
        #endif
        return saturate(dot(rawOcclusionMask, unity_OcclusionMaskSelector));
    #else
        //Handle LPPV baked occlusion for subtractive mode
        #if UNITY_LIGHT_PROBE_PROXY_VOLUME && !defined(LIGHTMAP_ON) && !UNITY_STANDARD_SIMPLE
            fixed4 rawOcclusionMask = fixed4(1.0, 1.0, 1.0, 1.0);
            if (unity_ProbeVolumeParams.x == 1.0)
                rawOcclusionMask = LPPV_SampleProbeOcclusion(worldPos);
            return saturate(dot(rawOcclusionMask, unity_OcclusionMaskSelector));
        #endif
        return 1.0;
    #endif
}

 

这里用了UNITY_SAMPLE_TEX2D_SAMPLER,是直接用了unity_Lightmap的sampler。不过需要注意的是事先要定义并使用unity_Lightmap,要不然编译会去掉这个sampler然后就报错了。
上面返回的是一个attenuation值,类似于shadowmap计算得到的那个,直接乘进颜色就行,或者用它来lerp颜色也行。于是呢这就有了烘焙的阴影。

但还有一个问题,shadowmask模式下的lightmap跟Subtractive模式相比非常暗。
它和Baked Indirect比较像,lightmap里只是间接光,所以不是乘进本色而是加进实时计算的光照。

所以对于Substractive模式来说,直接把lightmap乘进diffuse就行了


half4 lightmapTex = UNITY_SAMPLE_TEX2D(unity_Lightmap, lightmapUV.xy);
half atten = SHADOW_ATTENUATION(i);
half3 directDiffuse = dot(worldNormal, lightDir) * _LightColor0.rgb;
half3 diffuse = directDiffuse * lightmapTex.xyz * atten;
return half4(diffuse + specular, 1);

 

但Shadowmask这个是间接光,要加进直接光

half4 indirectColor= UNITY_SAMPLE_TEX2D(unity_Lightmap, lightmapUV.xy);//lightmap is indirect light
half bakedAtten = UnitySampleBakedOcclusion(lightmapUV.xy, worldPos);
half directAtten = SHADOW_ATTENUATION(i);
half3 directDiffuse = dot(worldNormal, lightDir) * _LightColor0.rgb;
half3 diffuse = (indirectColor.xyz * 4.4 + directDiffuse * directAtten * bakedAtten);
return hal4(diffuse + specular, 1);

至于那个4.4的系数,是试验出来的,我也感到很诧异。 按理来说DecodeLightmap乘以2就可以了。这个不太清楚原理。

自己写的shadowmask模式的shader和自带的Mobile/Diffuse的比较。

另外有个bug,自带的Mobile/Unlit(SupportLightmap)对Shadowmask不支持,还像subtractive一样乘进diffuse,就很暗了

最后放上这个shader,要注意的是没有用#if LIGHTMAP_ON和#if SHADOWS_SHADOWMASK宏控制,所以只能在lightmap下显示


Shader "lightmaptest"
{
       Properties
       {
              _myColor ("MainColor", color) = (1,1,1,1)
       }
       SubShader
       {
              Tags { "RenderType"="Opaque" }
              LOD 100
              Pass
              {
                     CGPROGRAM
                     #pragma vertex vert
                     #pragma fragment frag
                     #pragma multi_compile LIGHTMAP_ON
                     #pragma multi_compile SHADOWS_DEPTH SHADOWS_SCREEN
                     #pragma multi_compile SHADOWS_SHADOWMASK
                     
                     #include "UnityCG.cginc"
                     #include "AutoLight.cginc"
                     #include "UnityStandardCore.cginc"
                     struct appdata
                     {
                           float4 vertex : POSITION;
                           float2 uv : TEXCOORD0;
                           float4 texcoord1:TEXCOORD1;//lightmap uv
                           float3 normal:NORMAL;
                     };
                     struct v2f
                     {
                           float4 uv : TEXCOORD0;
                           float4 pos : SV_POSITION;
                           SHADOW_COORDS(1)
                           float4 worldPos : TEXCOORD2;
                           float3 worldNormal : TEXCOORD3;
                     };
                     fixed4 _myColor;
                     
                     v2f vert (appdata v)
                     {
                           v2f o;
                           o.pos = UnityObjectToClipPos(v.vertex);
                           o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                           o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
                           o.uv.zw = v.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
                           o.worldNormal = UnityObjectToWorldNormal( v.normal);
                           TRANSFER_SHADOW(o);
                           return o;
                     }
                     
                     fixed4 frag (v2f i) : SV_Target
                     {
                           half directAtten = SHADOW_ATTENUATION(i);
                           half3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
                           half3 directColor = dot(lightDir, i.worldNormal) * _LightColor0;
                           half4 indirectColor = UNITY_SAMPLE_TEX2D(unity_Lightmap, i.uv.zw);
                           fixed bakedAtten = UnitySampleBakedOcclusion(i.uv.zw, i.worldPos);
                           half3 diffuse = (indirectColor.xyz*4.4 + (directColor)  * bakedAtten * directAtten);
                           return fixed4(diffuse , 1);
                     }
                     ENDCG
              }
       }
}

参考资料:
Baked Indirect Mode | Unity Documentation
Shadowmask Mode | Unity Documentation
Subtractive Mode | Unity Documentation
Catlike Coding – Mixed Lighting

 

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注