这是用户在 2025-6-9 13:46 为 https://catlikecoding.com/unity/tutorials/rendering/part-17/ 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?
Catlike Coding
published 2017-06-30  发布于 2017-06-30

Rendering 17

Mixed Lighting


渲染 17 混合光照
  • Bake only indirect light.
    只烘焙间接光。
  • Mix baked and realtime shadows.
    混合烘焙和实时阴影。
  • Deal with code changes and bugs.
    处理代码更改和错误。
  • Support subtractive lighting.
    支持减法光照。

This is part 17 of a tutorial series about rendering. Last time, we added support for static lighting via lightmaps. Now we follow up with combining features of both baked and realtime lighting.
这是渲染教程系列的第 17 部分。上次我们通过光照贴图添加了对静态光照的支持。现在我们继续结合烘焙光照和实时光照的特性。

This tutorial was made with Unity 5.6.0.
本教程使用 Unity 5.6.0 制作。

Mixing baked and realtime lighting.
混合烘焙光照和实时光照。

Baking Indirect Light  烘焙间接光照

Lightmaps allow us to compute lighting ahead of time. This reduces the amount of work that the GPU has to do in realtime, at the cost of texture memory. Besides that, it also adds indirect lighting. But as we saw last time, there are limitations. First, specular lighting cannot be baked. Second, baked lights only influence dynamic objects via light probes. And third, baked lights do not cast realtime shadows.
光照贴图允许我们提前计算光照。这减少了 GPU 在实时渲染时所需完成的工作量,但代价是会消耗纹理内存。除此之外,它还增加了间接光照。但正如我们上次所看到的,它也存在局限性。首先,镜面光照无法烘焙。其次,烘焙光照只能通过光照探头影响动态物体。第三,烘焙光照无法投射实时阴影。

You can see the difference between fully realtime and fully baked lighting in the screenshots below. It is the scene from the previous tutorial, except that I have made all spheres dynamic and relocated a few. Everything else is static. This is using the forward rendering path.
你可以在下面的截图中看到完全实时光照和完全烘焙光照之间的区别。这是上一个教程中的场景,但我将所有球体都设为动态,并重新放置了几个。其他一切都是静态的。这里使用的是前向渲染路径。

realtime baked
Fully realtime and fully baked lighting.
完全实时光照与完全烘焙光照。

I haven't adjusted the light probes, so their positions make less sense now that there is less static geometry. The resulting probe lighting is a bit off now, which makes it easier to notice when it's being used.
我没有调整光照探头,因此现在静态几何体较少,光照探头的位置也就不太合理了。由此产生的光照探头照明现在有点偏差,这使得在使用时更容易被注意到。

Mixed Mode  混合模式

Indirect light is the one thing that baked lighting has that realtime lighting lacks, because it requires a lightmap. As indirect light can add a lot of realism to a scene, it would be nice if we could combine it with realtime lighting. This is possible, although of course this means that shading becomes more expensive. It requires the Lighting Mode of Mixed Lighting to be set to Baked Indirect.
间接光是烘焙照明所拥有而实时照明所缺乏的唯一要素,因为它需要一张光照贴图。由于间接光可以为场景增添许多真实感,因此如果能将其与实时照明结合起来将是极好的。这是可行的,尽管这当然意味着着色会变得更加昂贵。这需要将 Lighting ModeMixed Lighting 设置为 Baked Indirect

Mixed lighting, baked indirect.
混合照明,烘焙间接光。

We already switched to this mode in the previous tutorial, but back then we only worked with fully baked lights. As a result, the mode for mixed lighting didn't make a difference. To make use of mixed lighting, a light's Mode has to be set to Mixed.
我们已经在上一个教程中切换到此模式,但当时我们只使用了完全烘焙的光照。因此,混合光照模式并没有什么区别。要使用混合光照,光源的 Mode 必须设置为 Mixed

Mixed-mode main light.  混合模式主光源。

After turning the main directional light into a mixed light, two things will happen. First, Unity will bake the lightmap again. This time, it will only store the indirect light, so the resulting lightmap will be much darker than before.
将主平行光转换为混合光后,会发生两件事。首先,Unity 将再次烘焙光照贴图。这次,它只会存储间接光,因此生成的光照贴图将比以前暗得多。

all indirect
Fully baked vs. indirect-only lightmaps.
完全烘焙与仅间接光照贴图。

Second, everything will be lit as if the main light was set to realtime, with one difference. The lightmap is used to add indirect light to static objects, instead of spherical harmonics or probes. The dynamic objects still use light probes for their indirect light.
其次,所有物体都将按照主光源设置为实时光源的方式进行光照,但有一个区别。光照贴图用于为静态物体添加间接光照,而不是球谐函数或光照探头。动态物体仍然使用光照探头进行间接光照。

Mixed lighting, realtime direct plus baked indirect.
混合光照:实时直射光照与烘焙的间接光照。

We didn't have to change our shader to support this, because the forward base pass already combines lightmapped data and the main directional light. Additional lights get additive passes, as usual. When using the deferred rendering path, the main light will simply gets a pass as well.
我们无需改变着色器即可支持此功能,因为前向基准通道已将光照贴图数据和主定向光结合在一起。附加光照仍像往常一样采用叠加通道。当使用延迟渲染路径时,主光照也同样会获得一个通道。

Upgrading Our Shader  升级我们的着色器

Initially, everything appears to work fine. However, it turns out that shadow fading no longer works correct for the directional light. The shadows are cut off, which is easiest to see by greatly reducing the shadow distance.
起初,一切似乎都运行良好。然而,事实证明,阴影淡出功能不再对定向光照正确工作。阴影被截断了,通过大幅缩短阴影距离最容易看出这一点。

standard custom
Shadow fading, standard vs. our shader.
阴影渐隐,标准着色器 vs. 我们的着色器。

While Unity has had a mixed lighting mode for a long time, it actually became nonfunctional in Unity 5. A new mixed lighting mode was added in Unity 5.6, which is what we are using now. When this new mode was added, the code behind the UNITY_LIGHT_ATTENUATION macro was changed. We didn't notice this when using fully baked or realtime lighting, but we have to update our code to work with the new approach for mixed lighting. As this has been a recent big change, we have to be on our guard for bugs.
Unity 长期以来都有一种混合光照模式,但它实际上在 Unity 5 中失效了。Unity 5.6 中添加了一种新的混合光照模式,也就是我们现在正在使用的这种。当添加这种新模式时, UNITY_LIGHT_ATTENUATION 宏背后的代码发生了变化。在使用完全烘焙或实时光照时我们没有注意到这一点,但我们必须更新我们的代码以适应混合光照的新方法。由于这是一个最近发生的重大变化,我们必须警惕可能出现的错误。

The first thing we have to change is to no longer use the SHADOW_COORDS macro to define the interpolator for shadow coordinates. We have to use new UNITY_SHADOW_COORDS macro instead.
我们首先要做的改变是,不再使用宏` SHADOW_COORDS `来定义用于阴影坐标的插值器。我们必须改用新的宏` UNITY_SHADOW_COORDS `。

struct Interpolators {
	…

//	SHADOW_COORDS(5)
	UNITY_SHADOW_COORDS(5)

	…
};

Likewise, TRANSFER_SHADOW should be replaced with UNITY_TRANSFER_SHADOW.
同理, TRANSFER_SHADOW 应该替换为 UNITY_TRANSFER_SHADOW

Interpolators MyVertexProgram (VertexData v) {
	…

//	TRANSFER_SHADOW(i);
	UNITY_TRANSFER_SHADOW(i);

	…
}

However, this will produce a compiler error, because that macro requires an additional parameter. Since Unity 5.6, only the screen-space coordinates for directional shadows are put in an interpolator. Shadows coordinates for point lights and spotlights are now computed in the fragment program. What's new is that lightmap coordinates are used to a shadowmasks in some cases, which we'll cover later. For this to work, the macro has to be provided with data from the second UV channel, which contains the lightmap coordinates.
然而,这会产生一个编译器错误,因为该宏需要一个额外的参数。自 Unity 5.6 起,只有方向光的屏幕空间阴影坐标被放入插值器中。点光源和聚光灯的阴影坐标现在在片元程序中计算。新变化是,在某些情况下,光照贴图坐标用于阴影遮罩,这我们稍后会介绍。为了实现这一点,必须向宏提供来自第二个 UV 通道的数据,该通道包含光照贴图坐标。

	UNITY_TRANSFER_SHADOW(i, v.uv1);

This once again produces a compiler error. When this happens, it's because UNITY_SHADOW_COORDS in some cases incorrectly creates an interpolator, even though it is not actually needed. In that case, TRANSFER_SHADOW doesn't initialize it, which leads to the error. This bug is in versions 5.6.0, up to at least 5.6.2 and the 2017.1.0 beta.
这会再次产生一个编译器错误。发生这种情况是因为 UNITY_SHADOW_COORDS 在某些情况下错误地创建了插值器,即使它实际上并不需要。在这种情况下, TRANSFER_SHADOW 不会对其进行初始化,从而导致错误。此错误存在于 5.6.0 版本,直到至少 5.6.2 和 2017.1.0 测试版。

The bug usually goes unnoticed, because Unity's standard shader uses the UNITY_INITIALIZE_OUTPUT macro to fully initialize its interpolators structure. Because we didn't use that macro, we found the bug. To work around it, use the UNITY_INITIALIZE_OUTPUT macro to initialize our interpolators as well. That way, our code will compile with and without the bug.
这个 bug 通常不会被发现,因为 Unity 的标准着色器使用 UNITY_INITIALIZE_OUTPUT 宏来完全初始化其插值器结构。由于我们没有使用该宏,我们发现了这个 bug。为了解决这个问题,我们也可以使用 UNITY_INITIALIZE_OUTPUT 宏来初始化我们的插值器。这样,我们的代码无论有没有这个 bug 都能编译。

Interpolators MyVertexProgram (VertexData v) {
	Interpolators i;
	UNITY_INITIALIZE_OUTPUT(Interpolators, i);
	…
}

Fading Shadows Ourselves
渐隐阴影

We are now correctly using the new macros, but the shadows of our main light still don't fade out like they should. It turns out that UNITY_LIGHT_ATTENUATION does not perform this fading when both directional shadows and lightmaps are used at the same time, which is the case for the mixed-mode main directional light. So we must do it manually.
我们现在正确使用了新的宏,但主光的阴影仍未像预期那样渐隐。事实证明,当同时使用定向阴影和光照贴图时(混合模式主方向光就属于这种情况), UNITY_LIGHT_ATTENUATION 不会执行这种渐隐。因此我们必须手动完成。

We already have code to perform shadow fading, for our deferred lighting shader. Copy the relevant code fragment from MyDeferredShading to a new function in My Lighting. The only real difference is that we have to construct viewZ from the view vector and the view matrix. Only the Z component is needed, so we don't need to perform a full matrix multiplication.
我们已经有代码来执行阴影渐隐,用于我们的延迟光照着色器。将 MyDeferredShading 中相关的代码片段复制到 My Lighting 中的一个新函数。唯一真正的区别是,我们必须从视图向量和视图矩阵构造 viewZ 。只需要 Z 分量,所以我们不需要执行完整的矩阵乘法。

float FadeShadows (Interpolators i, float attenuation) {
	float viewZ =
		dot(_WorldSpaceCameraPos - i.worldPos, UNITY_MATRIX_V[2].xyz);
	float shadowFadeDistance =
		UnityComputeShadowFadeDistance(i.worldPos, viewZ);
	float shadowFade = UnityComputeShadowFade(shadowFadeDistance);
	attenuation = saturate(attenuation + shadowFade);
	return attenuation;
}

This manual fading must be done after using UNITY_LIGHT_ATTENUATION.
此手动淡化操作必须在使用了 UNITY_LIGHT_ATTENUATION 之后执行。

UnityLight CreateLight (Interpolators i) {
	…

		UNITY_LIGHT_ATTENUATION(attenuation, i, i.worldPos.xyz);
		attenuation = FadeShadows(i, attenuation);

	…
}

But only when UNITY_LIGHT_ATTENUATION decides to skip fading. This is the case when HANDLE_SHADOWS_BLENDING_IN_GI is defined in the UnityShadowLibrary include file. So FadeShadows should only do anything when HANDLE_SHADOWS_BLENDING_IN_GI is defined.
但是只有当 UNITY_LIGHT_ATTENUATION 决定跳过淡入淡出时。当 HANDLE_SHADOWS_BLENDING_IN_GIUnityShadowLibrary 包含文件中定义时,就是这种情况。所以 FadeShadows 应该只在定义了 HANDLE_SHADOWS_BLENDING_IN_GI 的情况下才执行任何操作。

float FadeShadows (Interpolators i, float attenuation) {
	#if HANDLE_SHADOWS_BLENDING_IN_GI
		// UNITY_LIGHT_ATTENUATION doesn't fade shadows for us.
		float viewZ =
			dot(_WorldSpaceCameraPos - i.worldPos, UNITY_MATRIX_V[2].xyz);
		float shadowFadeDistance =
			UnityComputeShadowFadeDistance(i.worldPos, viewZ);
		float shadowFade = UnityComputeShadowFade(shadowFadeDistance);
		attenuation = saturate(attenuation + shadowFade);
	#endif
	return attenuation;
}

Finally, our shadows again fade as they should.
最后,我们的阴影再次如期消散。

unitypackage

Using a Shadowmask  使用阴影遮罩

Baked-indirect mixed-mode lights are quite expensive. They require as much work as realtime lights, plus lightmaps for indirect light. Most significant compared to fully-baked lights is the addition of realtime shadows. Fortunately, there is a way to still bake shadows into lightmaps, in combination with realtime shading. To enable this, change the mixed lighting mode to Shadowmask.
烘焙-间接混合模式灯光相当昂贵。它们需要像实时灯光一样多的工作,再加上用于间接光照的光照贴图。与完全烘焙的灯光相比,最显著的区别是增加了实时阴影。幸运的是,有一种方法仍然可以将阴影烘焙到光照贴图中,并结合实时着色。要启用此功能,请将混合照明模式更改为 Shadowmask

Shadowmask mode.  阴影遮罩模式。

In this mode, both the indirect lighting and the shadow attenuation for mixed lights are stored in lightmaps. The shadows are stored in a separate map, known as a shadowmask. When using only the main directional light, everything that's illuminated will show up red in the shadowmask. It's red because the shadow information is stored in the texture's R channel. Actually, shadows for up to four lights can be stored in the map, as it has four channels.
在此模式下,混合灯光的间接照明和阴影衰减都存储在光照贴图中。阴影存储在一个单独的贴图中,称为阴影遮罩。当只使用主定向灯光时,所有被照亮的东西在阴影遮罩中都将显示为红色。它是红色的,因为阴影信息存储在纹理的 R 通道中。实际上,最多可以存储四个灯光的阴影,因为它有四个通道。

Baked intensity and shadowmask.
烘焙强度和阴影遮罩。

After Unity has created the shadowmask, the shadows cast by static objects will disappear. Only the light probes still take them into consideration. Shadows of dynamic objects are unaffected.
Unity 创建阴影遮罩后,静态物体投射的阴影将消失。只有光照探头仍会考虑这些阴影。动态物体的阴影不受影响。

No baked shadows.  无烘焙阴影。

Sampling the Shadowmask  采样阴影遮罩

To get the baked shadows back, we have to sample the shadowmask. Unity's macro already does that for point lights and spotlights, but we have to include it in our FadeShadows function as well. We can use the UnitySampleBakedOcclusion function from UnityShadowLibrary for this. It requires the lightmap UV coordinates and world position as arguments.
要让烘焙阴影重新出现,我们必须对阴影遮罩进行采样。Unity 的宏已经为点光源和聚光灯完成了这项工作,但我们也必须将其包含在我们的 FadeShadows 函数中。我们可以使用 UnityShadowLibrary 中的 UnitySampleBakedOcclusion 函数来实现。它需要光照贴图 UV 坐标和世界位置作为参数。

float FadeShadows (Interpolators i, float attenuation) {
	#if HANDLE_SHADOWS_BLENDING_IN_GI
float bakedAttenuation =
			UnitySampleBakedOcclusion(i.lightmapUV, i.worldPos);
		attenuation = saturate(attenuation + shadowFade);
	#endif
	return attenuation;
}

UnitySampleBakedOcclusion provides us with the baked shadow attenuation when a shadowmask is used, and simply 1 in all other cases. Now we have to combine this with the attenuation that we already have and then fade the shadows. The UnityMixRealtimeAndBakedShadows function does all this for us.
UnitySampleBakedOcclusion 在使用阴影蒙版时提供烘焙阴影衰减,在所有其他情况下仅提供 1。现在我们必须将其与我们已有的衰减结合起来,然后淡化阴影。 UnityMixRealtimeAndBakedShadows 函数为我们完成了所有这些。

		float bakedAttenuation =
			UnitySampleBakedOcclusion(i.lightmapUV, i.worldPos);
//		attenuation = saturate(attenuation + shadowFade);
		attenuation = UnityMixRealtimeAndBakedShadows(
			attenuation, bakedAttenuation, shadowFade
		);
Both realtime and shadowmask shadows.
实时阴影和投影遮罩阴影。

We now get both realtime and baked shadows on static objects, and they correctly blend. The realtime shadows still fade out beyond the shadow distance, but the baked shadows don't.
我们现在可以获得静态对象的实时阴影和烘焙阴影,并且它们能够正确融合。实时阴影在超出阴影距离后仍然会淡出,但烘焙阴影不会。

Only realtime shadows fade.
只有实时阴影会淡出。

Adding a Shadowmask G-Buffer
添加一个 Shadowmask G-Buffer

The shadowmask now works with forward rendering, but we have some work to do before it also works with the deferred rendering path. Specifically, we have to add the shadowmask information as an additional G-buffer, when needed. So add another buffer to our FragmentOutput structure when SHADOWS_SHADOWMASK is defined.
现在,阴影遮罩可与前向渲染配合使用,但在它也能与延迟渲染路径配合使用之前,我们还有一些工作要做。具体来说,我们需要在必要时将阴影遮罩信息添加为额外的 G-buffer。因此,当定义了 SHADOWS_SHADOWMASK 时,将另一个缓冲区添加到我们的 FragmentOutput 结构中。

struct FragmentOutput {
	#if defined(DEFERRED_PASS)
		float4 gBuffer0 : SV_Target0;
		float4 gBuffer1 : SV_Target1;
		float4 gBuffer2 : SV_Target2;
		float4 gBuffer3 : SV_Target3;

		#if defined(SHADOWS_SHADOWMASK)
			float4 gBuffer4 : SV_Target4;
		#endif
	#else
		float4 color : SV_Target;
	#endif
};

This is our fifth G-buffer, which is quite a lot. Not all platforms support it. Unity only supports shadowmasks when enough render targets are available, and we should do so as well.
这是我们的第五个 G 缓冲区,数量相当可观。并非所有平台都支持它。Unity 仅在有足够渲染目标可用时才支持遮罩,我们也应该这样做。

		#if defined(SHADOWS_SHADOWMASK) && (UNITY_ALLOWED_MRT_COUNT > 4)
			float4 gBuffer4 : SV_Target4;
		#endif

We simply have to store the sampled shadowmask data in the G-buffer, as we're not working with a specific light at this point. We can use the UnityGetRawBakedOcclusions function for this. It works like UnitySampleBakedOcclusion, except that it doesn't select one of the channels.
我们只需将采样后的阴影遮罩数据存储在 G-buffer 中,因为此时我们没有处理特定的光源。我们可以使用 UnityGetRawBakedOcclusions 函数来完成此操作。它的工作原理类似于 UnitySampleBakedOcclusion ,但它不选择其中一个通道。

	FragmentOutput output;
	#if defined(DEFERRED_PASS)
		#if !defined(UNITY_HDR_ON)
			color.rgb = exp2(-color.rgb);
		#endif
		output.gBuffer0.rgb = albedo;
		output.gBuffer0.a = GetOcclusion(i);
		output.gBuffer1.rgb = specularTint;
		output.gBuffer1.a = GetSmoothness(i);
		output.gBuffer2 = float4(i.normal * 0.5 + 0.5, 1);
		output.gBuffer3 = color;

		#if defined(SHADOWS_SHADOWMASK) && (UNITY_ALLOWED_MRT_COUNT > 4)
			output.gBuffer4 =
				UnityGetRawBakedOcclusions(i.lightmapUV, i.worldPos.xyz);
		#endif
	#else
		output.color = ApplyFog(color, i);
	#endif

To make this compile without lightmaps, substitute 0 for the lightmap coordinates when they're not available.
要使它在没有光照贴图的情况下也能编译,当光照贴图坐标不可用时,用 0 代替它们。

		#if defined(SHADOWS_SHADOWMASK) && (UNITY_ALLOWED_MRT_COUNT > 4)
			float2 shadowUV = 0;
			#if defined(LIGHTMAP_ON)
				shadowUV = i.lightmapUV;
			#endif
			output.gBuffer4 =
				UnityGetRawBakedOcclusions(shadowUV, i.worldPos.xyz);
		#endif

Using the Shadowmask G-Buffer
使用 Shadowmask G-Buffer

This is enough to make our shader work with the default deferred lighting shader. But to make it work with our custom shader, we have to adjust MyDeferredShading. The first step is to add a variable for the extra G-buffer.
这足以让我们的着色器与默认的延迟光照着色器配合使用。但要使其与我们的自定义着色器配合使用,我们必须调整 MyDeferredShading 。第一步是为额外的 G-buffer 添加一个变量。

sampler2D _CameraGBufferTexture0;
sampler2D _CameraGBufferTexture1;
sampler2D _CameraGBufferTexture2;
sampler2D _CameraGBufferTexture4;

Next, create a function to retrieve the appropriate shadow attenuation. If we have a shadowmask, this is done by sampling the texture and performing a saturated dot product with unity_OcclusionMaskSelector. That variable is defined in UnityShaderVariables and contains a vector for selecting the channel for the light that's currently being rendered.
接下来,创建一个函数来检索适当的阴影衰减。如果我们有阴影遮罩,这通过采样纹理并与 unity_OcclusionMaskSelector 进行饱和点积来完成。该变量在 UnityShaderVariables 中定义,并包含一个向量,用于选择当前正在渲染的光源的通道。

float GetShadowMaskAttenuation (float2 uv) {
	float attenuation = 1;
	#if defined (SHADOWS_SHADOWMASK)
		float4 mask = tex2D(_CameraGBufferTexture4, uv);
		attenuation = saturate(dot(mask, unity_OcclusionMaskSelector));
	#endif
	return attenuation;
}

In CreateLight, we now have to also fade shadows in case of a shadowmask, even if there are no realtime shadows for the current light.
CreateLight 中,如果存在遮罩(shadowmask)而没有实时阴影,我们现在也必须淡出阴影,即使当前光源没有实时阴影。

UnityLight CreateLight (float2 uv, float3 worldPos, float viewZ) {
	…

	#if defined(SHADOWS_SHADOWMASK)
		shadowed = true;
	#endif

	if (shadowed) {
		…
	}

	…
}

To properly include the baked shadows, again use UnityMixRealtimeAndBakedShadows instead of our old fading computation.
为了正确包含烘焙阴影,再次使用 UnityMixRealtimeAndBakedShadows 而非我们旧的淡出计算。

	if (shadowed) {
		float shadowFadeDistance =
			UnityComputeShadowFadeDistance(worldPos, viewZ);
		float shadowFade = UnityComputeShadowFade(shadowFadeDistance);
//		shadowAttenuation = saturate(shadowAttenuation + shadowFade);
		shadowAttenuation = UnityMixRealtimeAndBakedShadows(
			shadowAttenuation, GetShadowMaskAttenuation(uv), shadowFade
		);

		…
	}

We now get correct baked shadows with our custom deferred lighting shader as well. Except when our optimization branch ends up being used, which skips shadow blending. That shortcut isn't possible when a shadowmask is used.
现在,即使使用我们自定义的延迟光照着色器,也能获得正确的烘焙阴影。除非我们的优化分支被使用,那样会跳过阴影混合。当使用阴影遮罩时,这种捷径是不可行的。

	if (shadowed) {
		…

		#if defined(UNITY_FAST_COHERENT_DYNAMIC_BRANCHING) && defined(SHADOWS_SOFT)
			#if !defined(SHADOWS_SHADOWMASK)
				UNITY_BRANCH
				if (shadowFade > 0.99) {
					shadowAttenuation = 1;
				}
			#endif
		#endif
	}

Distance Shadowmask Mode
距离阴影遮罩模式

While the shadowmask mode gives us good baked shadows for static objects, dynamic objects cannot benefit from them. Dynamic objects can only receive realtime shadows, and light probe data. If we want good shadows on dynamic objects, then the static objects have to cast realtime shadows as well. This is what the Distance Shadowmask mixed lighting mode is for.
虽然阴影遮罩模式为我们提供了良好的静态对象烘焙阴影,但动态对象无法从中受益。动态对象只能接收实时阴影和光照探针数据。如果想让动态对象拥有良好的阴影,那么静态对象也必须投射实时阴影。这就是 Distance Shadowmask 混合光照模式的用途。

Distance Shadowmask mode.
距离阴影遮罩模式。

When using Distance Shadowmask mode, everything uses realtime shadows. At first glance, it appears to be exactly the same as the Baked Indirect mode.
当使用“ Distance Shadowmask ”模式时,所有内容都使用实时阴影。乍一看,它似乎与“ Baked Indirect ”模式完全相同。

Realtime shadows on everything.
所有内容都使用实时阴影。

However, there is still a shadowmask. In this mode, the baked shadows and light probes are used beyond the shadow distance. So this is the most expensive mode, equal to Baked Indirect up to the shadow distance, and Shadowmask beyond that.
然而,仍然存在阴影遮罩。在此模式下,烘焙的阴影和光照探头在阴影距离之外仍被使用。因此,这是最昂贵的模式,在阴影距离之内与“ Baked Indirect ”模式相同,而在阴影距离之外则与“ Shadowmask ”模式相同。

Realtime nearby, shadowmask and probes further away.
实时阴影适用于近距离,而更远距离则使用阴影遮罩和探针。

We already support this mode, because we're using UnityMixRealtimeAndBakedShadows. To correctly blend between fully realtime and baked shadows, it simply fades realtime shadows as usual, then takes the minimum of that and the baked shadows.
我们已经支持这种模式,因为我们正在使用 UnityMixRealtimeAndBakedShadows 。为了正确混合完全实时阴影和烘焙阴影,它会像往常一样使实时阴影逐渐淡出,然后取该结果与烘焙阴影的最小值。

Multiple Lights  多光源

Because the shadowmask has four channels, it can support up to four overlapping light volumes at once. For example, here is a screenshot with lightmaps of the scene with three additional spotlights. I lowered the intensity of the main light so it's easier to see the spotlights.
由于阴影遮罩有四个通道,它最多可以同时支持四个重叠的光照体积。例如,这是一个包含三个额外聚光灯的场景光照贴图截图。我降低了主光源的强度,以便更容易看到聚光灯。

scene maps
Four lights, all mixed.
四个光源,全部混合。

The main directional light's shadows are still stored in the R channel. You can also see the shadows of the spotlights that are stored in the G and B channels. The last spotlight's shadows are stored in the A channel, which is not visible.
主平行光的阴影仍存储在 R 通道中。你还可以看到存储在 G 和 B 通道中的聚光灯的阴影。最后一个聚光灯的阴影存储在 A 通道中,但不可见。

When light volumes do not overlap, they can use the same channel to store their shadow data. So you can have as many mixed lights as you want. But you have to make sure that at most four light volumes end up overlapping each other. If there are too many mixed lights influencing the same area, then some will fall back to fully baked mode. To illustrate this, below is a screenshot with lightmaps after adding one more spotlight. One of them has turned into a baked light, which you can clearly see in the intensity map.
当光源体积不重叠时,它们可以使用同一通道存储其阴影数据。因此,你可以混合任意数量的光源。但你必须确保最多只有四个光源体积重叠。如果混合光源过多地影响同一区域,则某些光源将回退到完全烘焙模式。为说明这一点,下面是添加了一个聚光灯后的光照贴图截图。其中一个已变为烘焙光源,这在强度图中清晰可见。

scene maps
Five overlapping lights, one fully baked.
五个相互重叠的光源,其中一个已完全烘焙。

Supporting Multiple Masked Directional Lights
支持多个遮罩方向光

Unfortunately, it turns out that shadowmasks only work correctly when there is at most one mixed-mode directional light involved. Shadow fading goes wrong for additional directional lights. At least, when using the forward rendered path. Deferred rendering works fine.
不幸的是,我们发现阴影遮罩只有在最多一个混合模式方向光参与时才能正常工作。对于额外的方向光,阴影衰减会出错。至少,在使用前向渲染路径时是这样。延迟渲染则没有问题。

Incorrect fading with two directional lights.
两个方向光造成的错误渐变。

Unity's standard shader also has this problem, at least up to versions 5.6.2 and 2017.1.0f1. However, it is not an inherent limitation of the lightmapping engine. It is an oversight in the new approach used for UNITY_LIGHT_ATTENUATION. Unity uses the shadow interpolator defined via UNITY_SHADOW_COORDS to either store the screen-space coordinates for directional shadows, or the lightmap coordinates for other lights that have a shadowmask.
Unity 的标准着色器也存在这个问题,至少在 5.6.2 和 2017.1.0f1 版本中如此。然而,这并非光照贴图引擎固有的限制。这是 UNITY_LIGHT_ATTENUATION 所采用新方法中的一个疏忽。Unity 使用通过 UNITY_SHADOW_COORDS 定义的阴影插值器来存储定向阴影的屏幕空间坐标,或存储具有阴影遮罩的其他光源的光照贴图坐标。

Directional lights that use the shadowmask also need the lightmap coordinates. In case of the forward base pass, these coordinates will be included, because LIGHTMAP_ON will be defined when needed. However, LIGHTMAP_ON is never defined in additive passes. This means that additive directional lights won't have lightmap coordinates available. It turns out that UNITY_LIGHT_ATTENUATION simply uses 0 when that's the case, leading to incorrect lightmap sampling.
使用阴影遮罩的定向光源也需要光照贴图坐标。在前向基础通道中,这些坐标将包含在内,因为 LIGHTMAP_ON 在需要时将被定义。然而, LIGHTMAP_ON 从未在附加通道中定义。这意味着附加定向光源将无法获得光照贴图坐标。结果是 UNITY_LIGHT_ATTENUATION 在这种情况下简单地使用 0,导致不正确的光照贴图采样。

So we cannot rely on UNITY_LIGHT_ATTENUATION for additional directional lights that use the shadowmask. Let's make it easy to identify when this is the case. This all assumes that we're actually using screen-space directional shadows, which on some platforms isn't the case.
因此,对于使用阴影遮罩的附加定向光源,我们不能依赖 UNITY_LIGHT_ATTENUATION 。让我们更容易识别何时出现这种情况。所有这一切都假定我们确实使用了屏幕空间定向阴影,而在某些平台上并非如此。

#if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
#endif


#if !defined(LIGHTMAP_ON) && defined(SHADOWS_SCREEN)
	#if defined(SHADOWS_SHADOWMASK) && !defined(UNITY_NO_SCREENSPACE_SHADOWS)
		#define ADDITIONAL_MASKED_DIRECTIONAL_SHADOWS 1
	#endif
#endif

Next, we have to include the lightmap coordinates also when we have the additional masked directional shadows.
接下来,当我们有附加遮罩定向阴影时,我们也必须包含光照贴图坐标。

struct Interpolators {
	…

	#if defined(LIGHTMAP_ON) || ADDITIONAL_MASKED_DIRECTIONAL_SHADOWS
		float2 lightmapUV : TEXCOORD6;
	#endif
};

…

Interpolators MyVertexProgram (VertexData v) {
	…

	#if defined(LIGHTMAP_ON) || ADDITIONAL_MASKED_DIRECTIONAL_SHADOWS
		i.lightmapUV = v.uv1 * unity_LightmapST.xy + unity_LightmapST.zw;
	#endif

	…
}

With the lightmap coordinates available, we can again use our FadeShadows function to perform our own fading.
有了光照图坐标,我们可以再次使用我们的 FadeShadows 函数来进行我们自己的渐变。

float FadeShadows (Interpolators i, float attenuation) {
	#if HANDLE_SHADOWS_BLENDING_IN_GI || ADDITIONAL_MASKED_DIRECTIONAL_SHADOWS
#endif

	return attenuation;
}

However, this is still not correct, because we're feeding it bugged attenuation data. We have to bypass UNITY_LIGHT_ATTENUATION and get the baked attenuation only, which in this case we can do via the SHADOW_ATTENUATION macro.
然而,这仍然不正确,因为我们提供的是有缺陷的衰减数据。我们必须绕过 UNITY_LIGHT_ATTENUATION ,只获取烘焙衰减,在这种情况下,我们可以通过 SHADOW_ATTENUATION 宏来实现。

float FadeShadows (Interpolators i, float attenuation) {
	#if HANDLE_SHADOWS_BLENDING_IN_GI || ADDITIONAL_MASKED_DIRECTIONAL_SHADOWS
		// UNITY_LIGHT_ATTENUATION doesn't fade shadows for us.
		#if ADDITIONAL_MASKED_DIRECTIONAL_SHADOWS
			attenuation = SHADOW_ATTENUATION(i);
		#endif#endif

	return attenuation;
}
Correct fading with two directional lights.
使用两个方向光进行正确的淡入淡出。
unitypackage

Subtractive Shadows  减法阴影

Mixed lighting is nice, but it's is not as cheap as fully baked lighting. If you're targeting low-performance hardware, then mixed lighting is not feasible. Baked lighting will work, but you might really need dynamic objects to cast shadows on static objects. In that case, you can use the Subtractive mixed lighting mode.
混合照明不错,但不如完全烘焙照明便宜。如果你的目标是低性能硬件,那么混合照明是不可行的。烘焙照明可行,但你可能确实需要动态对象在静态对象上投射阴影。在这种情况下,你可以使用 Subtractive 混合照明模式。

Subtractive mode.  减色模式。

After switching to Subtractive mode, the scene will get a lot brighter. That happens because static objects now use both the fully-baked lightmap and direct lighting. Dynamic objects still use light probes and direct lighting, as usual.
切换到 Subtractive 模式后,场景会亮很多。这是因为静态物体现在同时使用完全烘焙的光照贴图和直接光照。动态物体仍然像往常一样使用光照探头和直接光照。

Static objects are lit twice.
静态物体会被照亮两次。

Subtractive mode only works with forward rendering. When using the deferred rendering path, the relevant objects will fall back to forward the forward path, like transparent objects do.
Subtractive 模式仅适用于前向渲染。在使用延迟渲染路径时,相关对象将回退到前向路径,就像透明对象一样。

Subtractive Lighting  减法式光照

The idea of Subtractive mode is that static objects are lit via lightmaps, while also factoring dynamic shadows into it. This is done by decreasing the intensity of the lightmap in shadowed areas. To do this, the shader needs to access both the lightmap and the realtime shadows. It also need to use the realtime light to figure out how much the lightmap has to be dimmed. That's why we got double lighting after switching to this mode.
Subtractive 模式的思路是静态对象通过光照贴图进行光照,同时将动态阴影也考虑在内。这是通过在阴影区域降低光照贴图的强度来实现的。为此,着色器需要同时访问光照贴图和实时阴影。它还需要使用实时光照来计算光照贴图需要调暗的程度。这就是为什么切换到此模式后,我们会出现双重光照。

Subtractive lighting is an approximation, which only works with a single directional light. So only shadows of the main directional light are supported. Also, we have to somehow know what the indirect light situation is supposed to be in dynamically shaded areas. As we're using a fully-baked lightmap, we don't have this information. Instead of including an additional lightmap with only the indirect light, Unity uses a uniform color to approximate the ambient light. This is the Realtime Shadow Color, which you can adjust in the mixed lighting section.
减法式光照是一种近似,只适用于单个定向光。因此,只支持主定向光的阴影。此外,我们必须以某种方式知道动态阴影区域的间接光照情况应该是怎样的。由于我们使用的是完全烘焙的光照贴图,我们没有这些信息。Unity 没有包含一个只有间接光照的额外光照贴图,而是使用一个统一颜色来近似环境光。这就是 Realtime Shadow Color ,你可以在混合光照部分调整它。

In the shader, we know that we should be using subtractive lighting when the LIGHTMAP_ON, SHADOWS_SCREEN, and LIGHTMAP_SHADOW_MIXING keywords are defined, while SHADOWS_SHADOWMASK isn't. Let's define SUBTRACTIVE_LIGHTING when that's the case, to make it easier to work with.
在着色器中,我们知道当定义了 LIGHTMAP_ONSHADOWS_SCREENLIGHTMAP_SHADOW_MIXING 关键字而未定义 SHADOWS_SHADOWMASK 时,应使用减色照明。在这种情况下,我们来定义 SUBTRACTIVE_LIGHTING ,以便于操作。

#if !defined(LIGHTMAP_ON) && defined(SHADOWS_SCREEN)
	#if defined(SHADOWS_SHADOWMASK) && !defined(UNITY_NO_SCREENSPACE_SHADOWS)
		#define ADDITIONAL_MASKED_DIRECTIONAL_SHADOWS 1
	#endif
#endif

#if defined(LIGHTMAP_ON) && defined(SHADOWS_SCREEN)
	#if defined(LIGHTMAP_SHADOW_MIXING) && !defined(SHADOWS_SHADOWMASK)
		#define SUBTRACTIVE_LIGHTING 1
	#endif
#endif

Before we do anything else, we have to get rid of the double lighting. This can be done by switching off the dynamic light, like we do for the deferred pass.
在进行任何操作之前,我们必须消除重复光照。这可以通过关闭动态光照来完成,就像我们在延迟渲染通道中做的那样。

UnityLight CreateLight (Interpolators i) {
	UnityLight light;

	#if defined(DEFERRED_PASS)  || SUBTRACTIVE_LIGHTING
		light.dir = float3(0, 1, 0);
		light.color = 0;
	#else
#endif

	return light;
}
Only baked lighting for static objects.
静态对象只使用烘焙光照。

Shadowing Baked Light  烘焙灯光阴影

To apply subtractive shadows, let's create a function to adjust the indirect light when needed. It usually does nothing.
要应用减法阴影,我们来创建一个函数,用于在需要时调整间接光照。通常情况下,它什么也不做。

void ApplySubtractiveLighting (
	Interpolators i, inout UnityIndirect indirectLight
) {}

This function has to be invoked after we have retrieved the lightmap data.
这个函数必须在我们检索到光照贴图数据后调用。

UnityIndirect CreateIndirectLight (Interpolators i, float3 viewDir) {
	…

	#if defined(FORWARD_BASE_PASS) || defined(DEFERRED_PASS)
		#if defined(LIGHTMAP_ON)
			indirectLight.diffuse =
				DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, i.lightmapUV));
			
			#if defined(DIRLIGHTMAP_COMBINED)
#endif

			ApplySubtractiveLighting(i, indirectLight);
		#else
			indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1)));
		#endif
#endif

	return indirectLight;
}

If there is subtractive lighting, then we have to fetch the shadow attenuation. We can simply copy the code from CreateLight.
如果存在减法照明,那么我们必须获取阴影衰减。我们可以简单地从 CreateLight 复制代码。

void ApplySubtractiveLighting (
	Interpolators i, inout UnityIndirect indirectLight
) {
	#if SUBTRACTIVE_LIGHTING
		UNITY_LIGHT_ATTENUATION(attenuation, i, i.worldPos.xyz);
		attenuation = FadeShadows(i, attenuation);
	#endif
}

Next, we have to figure out how much light we would receive, if we were using realtime lighting. We assume that this information matches what's baked in the lightmap. As the lightmap only contains diffuse light, we can suffice with computing the Lambert term for the directional light.
接下来,我们需要弄清楚如果我们使用实时光照,会接收到多少光。我们假设这些信息与光照图中烘焙的信息相符。由于光照图只包含漫反射光,我们只需计算方向光的朗伯项即可。

	#if SUBTRACTIVE_LIGHTING
		UNITY_LIGHT_ATTENUATION(attenuation, i, i.worldPos.xyz);
		attenuation = FadeShadows(i, attenuation);

		float ndotl = saturate(dot(i.normal, _WorldSpaceLightPos0.xyz));
	#endif

To arrive at the shadowed light intensity, we have to multiply the Lambert term with the attenuation. But we already have the fully unshadowed baked light. So instead we'll estimate how much light is blocked by the shadow.
要得出阴影光照强度,我们必须将朗伯项与衰减相乘。但我们已经有了完全无阴影的烘焙光照。因此,我们将估算有多少光被阴影遮挡。

		float ndotl = saturate(dot(i.normal, _WorldSpaceLightPos0.xyz));
		float3 shadowedLightEstimate =
			ndotl * (1 - attenuation) * _LightColor0.rgb;

By subtracting this estimate from the baked light, we end up with the adjusted light.
用烘焙光照减去该估算值,便得到调整后的光照。

		float3 shadowedLightEstimate =
			ndotl * (1 - attenuation) * _LightColor0.rgb;
		float3 subtractedLight = indirectLight.diffuse - shadowedLightEstimate
		indirectLight.diffuse = subtractedLight;
Subtracted light.  消减光。

This always produces solid black shadows, regardless of ambient lighting situation. To better match the scene, we can use our subtractive shadow color, which is made available via unity_ShadowColor. The shadowed areas shouldn't be darker than this color, but they could be brighter. So take the maximum of the computed light and the shadow color.
这将始终产生纯黑色的阴影,无论环境光照情况如何。为了更好地匹配场景,我们可以使用减色阴影,其可通过 unity_ShadowColor 获取。阴影区域不应比此颜色更暗,但可以更亮。因此,取计算光照和阴影颜色的最大值。

		float3 subtractedLight = indirectLight.diffuse - shadowedLightEstimate;
		subtractedLight = max(subtractedLight, unity_ShadowColor.rgb);
		indirectLight.diffuse = subtractedLight;

We must also take into consideration the possibility that the shadow strength has been set to lower than 1. To apply the shadow strength, interpolate between the shadowed and unshadowed light based on the X component of _LightShadowData.
我们还必须考虑到阴影强度可能已设置为低于 1 的情况。要应用阴影强度,请根据 _LightShadowData 的 X 分量在阴影和非阴影光之间进行插值。

		subtractedLight = max(subtractedLight, unity_ShadowColor.rgb);
		subtractedLight =
			lerp(subtractedLight, indirectLight.diffuse, _LightShadowData.x);
		indirectLight.diffuse = subtractedLight;
Colored shadows.  彩色阴影。

Because our scene has its ambient intensity set to zero, the default shadow color doesn't match the scene very well. But it makes it easy to spot the subtractive shadows, so I didn't adjust it. This also makes it obvious that the shadow color now overrides all baked shadows, which shouldn't happen. It should only affect areas that receive dynamic shadows, not brighten baked shadows. To enforce this, use the minimum of the subtractive lighting and the baked lighting.
由于我们的场景环境光强度设置为零,默认的阴影颜色与场景不是很协调。但这使得减法阴影很容易被发现,所以我没有调整它。这也使得阴影颜色现在会覆盖所有烘焙阴影变得显而易见,这不应该发生。它应该只影响接收动态阴影的区域,而不是使烘焙阴影变亮。为了强制实现这一点,请使用减法光照和烘焙光照的最小值。

//		indirectLight.diffuse = subtractedLight;
		indirectLight.diffuse = min(subtractedLight, indirectLight.diffuse);
Proper subtractive shadows.
正确的相减阴影。

We now get correct subtractive shadows, as long as we use an appropriate shadow color. But keep in mind that this is just an approximation, and it doesn't work well with multiple lights. For example, other baked lights will be shadowed incorrectly.
现在,只要我们使用合适的阴影颜色,就可以得到正确的相减阴影。但请记住,这只是一种近似,并且在多个光源的情况下效果不佳。例如,其他烘焙光源将出现错误的阴影。

Incorrect subtraction for other lights.
其他光源减法不正确。

The next tutorial is Realtime GI, Probe Volumes, LOD Groups.
下一个教程是实时 GI、探针体积、LOD 组。

unitypackage PDF
switch theme  切换主题
contents  内容
  1. Baking Indirect Light
    1. Mixed Mode
    2. Upgrading Our Shader
    3. Fading Shadows Ourselves
  2. Using a Shadowmask
    1. Sampling the Shadowmask
    2. Adding a Shadowmask G-Buffer
    3. Using the Shadowmask G-Buffer
    4. Distance Shadowmask Mode
    5. Multiple Lights
    6. Supporting Multiple Masked Directional Lights
  3. Subtractive Shadows
    1. Subtractive Lighting
    2. Shadowing Baked Light