Mixed Lighting Lightmap & Shader in Unity | Unity中混合光照Lightmap研究
Sep 2, 2017
在MixedLight模式,Lightmap有三个模式:Shadowmask,Bake Indirect和Subtractive
Bake Indirect 不烘焙直接阴影,只烘焙间接光,在Shadow Distance(Project Settings>Quality>Shadows里面设置)之内的阴影都是实时计算的(Shadow Map)。
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之外没有阴影
对shader来说,阴影就直接用TRANSFER_SHADOW啊,SHADOW_ATTENUATION出来的就行了。采样Lightmap就多了点间接光。
Subtractive 这个模式呢是唯一一种把阴影烘焙进贴图的模式,
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
以上是接受者和投影者生成阴影的方式区别。他还有一个特殊的地方是实时阴影的颜色可以调的2333
优点是静态物体的效果很好
缺点是不支持实时直接光,所以静态物体没有高光;light prob阴影比较差;动态静态阴影混合会有问题。
Shadowmask 这个把阴影信息存到一张贴图里了,这个贴图里可以存四个灯光阴影(RGBA四个通道)
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
优点:动态物体的阴影永远是实时的;混合动态和静态阴影比较容易,静态物体也有实时高光的哦
缺点:只有四个光咯,LightProbs精度低,多内存
Bake Indirect模式不必多说,像实时一样采样阴影就可以了。
Subtractive模式是传统的模式,阴影就在贴图里,直接采样一下lightmap就可以了。
一般这样:
1 2 3 4 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 // 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 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 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里只是间接光,所以不是乘进本色而是加进实时计算的光照。
所以对于Substactive来说:
1 2 3 4 5 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);
也就是说直接把lightmap乘进diffuse就行了
对于ShadowMask和Bake Indirect来说:
1 2 3 4 5 6 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);
这时lightmap是加进diffuse而不是乘进的,至于那个4.4的系数,是试验出来的,我也感到很诧异。 按理来说DecodeLightmap乘以2就可以了。这个不太清楚原理。
ShadowMask烘焙,左为自己写的shader,右为Mobile/Diffuse
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 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 } } } http://catlikecoding.com/unity/tutorials/rendering/part-17/