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就行了
一般这样:
1 2 3 4 5 6 |
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里是这么定义的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// 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这个关键词,如下:
1 |
#pragma multi_compile SHADOWS_SHADOWMASK |
另外unity_LightmapInd这张图是光线方向的图,可以采样后用来给静态物体算高光
1 2 3 4 5 6 7 8 9 |
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里面
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 |
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就行了
1 2 3 4 5 6 7 |
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这个是间接光,要加进直接光
1 2 3 4 5 6 7 |
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下显示
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 |
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
