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

Rendering 12

Semitransparent Shadows


渲染 12 半透明阴影
  • Support cutout shadows.  支持镂空阴影。
  • Use dithering.  使用抖动。
  • Approximate semitransparent shadow.
    近似半透明阴影。
  • Toggle between semitransparent and cutout shadows.
    在半透明和镂空阴影之间切换。

This is part 12 of a tutorial series about rendering. In the previous part, we made it possible to render semitransparent surfaces, but we didn't cover their shadows yet. Now we'll take care of that.
这是渲染系列教程的第 12 部分。在上一部分中,我们实现了半透明表面的渲染,但尚未涉及它们的阴影。现在我们将处理这个问题。

This tutorial was made with Unity 5.5.0f3.
本教程基于 Unity 5.5.0f3 制作。

When objects fade, so do their shadows.
当物体淡出时,它们的阴影也会随之淡出。

Cutout Shadows  镂空阴影

Currently, the shadows of our transparent materials are always cast as if they were solid, because that's what our shader assumes. As a result, the shadows might appear very strange, until you realize that you're seeing the shadows of a solid object. In the case of directional shadows, this can also lead to invisible geometry blocking shadows.
目前,我们透明材质的阴影总是以实体形式投射,因为我们的着色器就是这样假设的。结果是,阴影可能看起来非常奇怪,直到你意识到你看到的是一个实体物体的阴影。在定向阴影的情况下,这还可能导致不可见的几何体阻挡阴影。

opaque cutput
Opaque and cutout rendering mode, same directional shadows.
不透明和镂空渲染模式,相同的定向阴影。

In the case of spotlight or point light shadows, you'll simply get a solid shadow.
对于聚光灯或点光源阴影,你只会得到一个实心阴影。

Solid spotlight shadow.  实心聚光灯阴影。

Refactoring My Shadows  重构我的阴影

In order to take transparency into account, we have to access the alpha value in the shadow caster shader pass. This means that we'll need to sample the albedo texture. However, this is not needed when using the opaque rendering mode. So we're going to need multiple shader variants for our shadows.
为了考虑透明度,我们必须在阴影投射器着色器通道中访问 alpha 值。这意味着我们需要采样反照率纹理。然而,当使用不透明渲染模式时,这不是必需的。因此,我们的阴影需要多个着色器变体。

Right now we have two versions of our shadow programs. One version for cube shadow maps, which is required for point lights, and one for the other light types. Now we need to mix in even more variants. To make this easier, we're going to rewrite our My Shadow include file. We'll use interpolators for all variants, and create a single vertex and fragment program.
现在我们有两种版本的阴影程序。一种用于立方体阴影贴图(点光源需要),另一种用于其他光源类型。现在我们需要混合更多变体。为了方便起见,我们将重写我们的 My Shadow 包含文件。我们将对所有变体使用插值器,并创建一个单独的顶点和片段程序。

First, move the definition of Interpolators out of the conditional block. Then make the light vector conditional instead.
首先,将 Interpolators 的定义移出条件块。然后让光矢量变成有条件的。

struct VertexData {
	float4 position : POSITION;
	float3 normal : NORMAL;
};

struct Interpolators {
	float4 position : SV_POSITION;
	#if defined(SHADOWS_CUBE)
		float3 lightVec : TEXCOORD0;
	#endif
};

Next, write a new vertex program, which contains copies of the two different versions. The non-cube code has to be slightly adjusted to work with the new Interpolators output.
接下来,编写一个新的顶点程序,其中包含两个不同版本的副本。非立方体代码必须稍微调整才能与新的 Interpolators 输出一起工作。

Interpolators MyShadowVertexProgram (VertexData v) {
	Interpolators i;
	#if defined(SHADOWS_CUBE)
		i.position = UnityObjectToClipPos(v.position);
		i.lightVec =
			mul(unity_ObjectToWorld, v.position).xyz - _LightPositionRange.xyz;
	#else
		i.position = UnityClipSpaceShadowCasterPos(v.position.xyz, v.normal);
		i.position = UnityApplyLinearShadowBias(i.position);
	#endif
	return i;
}

Do the same for the fragment program. Then get rid of the old conditional programs.
对片元程序做同样的处理。然后去掉旧的条件程序。

float4 MyShadowFragmentProgram (Interpolators i) : SV_TARGET {
	#if defined(SHADOWS_CUBE)
		float depth = length(i.lightVec) + unity_LightShadowBias.x;
		depth *= _LightPositionRange.w;
		return UnityEncodeCubeShadowDepth(depth);
	#else
		return 0;
	#endif
}

//#if defined(SHADOWS_CUBE)
//	…
//#endif

Clipping Shadow Fragments
裁剪阴影片元

We'll take care of cutout shadows first. We cut holes in the shadows by discarding fragments, like we do for the Coutout rendering mode in the other rendering passes. For this we need the material's tint, albedo texture, and alpha cutoff settings. Add variables for them to the top of My Shadows.
我们将首先处理镂空阴影。我们通过丢弃片段在阴影中“剪孔”,就像我们在其他渲染通道中对 Coutout 渲染模式所做的那样。为此,我们需要材质的色调、反照率纹理和 alpha 裁剪设置。将它们的变量添加到 My Shadows 的顶部。

#include "UnityCG.cginc"

float4 _Tint;
sampler2D _MainTex;
float4 _MainTex_ST;
float _AlphaCutoff;

So we have to sample the albedo texture when we're using Cutout rendering mode. Actually, we must only do this when we're not using the albedo's alpha value to determine smoothness. When these conditions are met, we have to pass the UV coordinates to the fragment program. We'll define SHADOWS_NEED_UV as 1 when these conditions are met. This way, we can conveniently use #if SHADOWS_NEED_UV.
所以,当使用 Cutout 渲染模式时,我们必须采样反照率纹理。实际上,我们仅在不使用反照率的 alpha 值来确定平滑度时才必须这样做。满足这些条件时,我们必须将 UV 坐标传递给片段程序。满足这些条件时,我们将 SHADOWS_NEED_UV 定义为 1。这样,我们就可以方便地使用 #if SHADOWS_NEED_UV

#include "UnityCG.cginc"

#if defined(_RENDERING_CUTOUT) && !defined(_SMOOTHNESS_ALBEDO)
	#define SHADOWS_NEED_UV 1
#endif

Add the UV coordinates to the vertex input data. We don't need to make that conditional. Then conditionally add the UV to the interpolators.
将 UV 坐标添加到顶点输入数据中。我们无需使其成为条件性的。然后有条件地将 UV 添加到插值器中。

struct VertexData {
	float4 position : POSITION;
	float3 normal : NORMAL;
	float2 uv : TEXCOORD0;
};

struct Interpolators {
	float4 position : SV_POSITION;
	#if SHADOWS_NEED_UV
		float2 uv : TEXCOORD0;
	#endif
	#if defined(SHADOWS_CUBE)
		float3 lightVec : TEXCOORD1;
	#endif
};

Pass the UV coordinates on to the the interpolators in the vertex program, when needed.
需要时,在顶点程序中将 UV 坐标传递给插值器。

Interpolators MyShadowVertexProgram (VertexData v) {
	…

	#if SHADOWS_NEED_UV
		i.uv = TRANSFORM_TEX(v.uv, _MainTex);
	#endif
	return i;
}

Copy the GetAlpha method from My Lighting to My Shadows. Here, whether the texture is sampled has to depend on SHADOWS_NEED_UV. So check for that instead of whether _SMOOTHNESS_ALBEDO is defined. I marked the difference.
My Lighting 中的 GetAlpha 方法复制到 My Shadows 。这里,纹理是否采样必须取决于 SHADOWS_NEED_UV 。因此,请检查这个而不是检查 _SMOOTHNESS_ALBEDO 是否已定义。我已标记出区别。

float GetAlpha (Interpolators i) {
	float alpha = _Tint.a;
	#if SHADOWS_NEED_UV
		alpha *= tex2D(_MainTex, i.uv.xy).a;
	#endif
	return alpha;
}

Now we can retrieve the alpha value in the fragment program, and use it to clip when in Cutout rendering mode.
现在我们可以在片段程序中检索 Alpha 值,并在 Cutout 渲染模式下使用它进行裁剪。

float4 MyShadowFragmentProgram (Interpolators i) : SV_TARGET {
	float alpha = GetAlpha(i);
	#if defined(_RENDERING_CUTOUT)
		clip(alpha - _AlphaCutoff);
	#endif
	
	…
}

To make this actually work, add shader features for _RENDERING_CUTOUT and _SMOOTHNESS_ALBEDO to the shadow caster pass of My First Lighting Shader.
为了使其真正生效,请将 _RENDERING_CUTOUT_SMOOTHNESS_ALBEDO 的着色器功能添加到 My First Lighting Shader 的阴影投射器通道中。

		Pass {
			Tags {
				"LightMode" = "ShadowCaster"
			}

			CGPROGRAM

			#pragma target 3.0

			#pragma shader_feature _RENDERING_CUTOUT
			#pragma shader_feature _SMOOTHNESS_ALBEDO
			
			…
		}
directional spotlight
Cutout shadows, directional and spotlight.
镂空阴影,方向光和聚光灯。

Refactoring My Lighting  重构光照

Before we move on, let's tweak My Lighting a bit as well. Notice how we've used UnityObjectToClipPos to transform the vertex position in My Shadows. We can use this function in My Lighting as well, instead of performing a matrix multiplication ourselves. The UnityObjectToClipPos function also performs this multiplication, but uses the constant value 1 as the fourth position coordinate, instead of relying on the mesh data.
在继续之前,我们也将对 My Lighting 进行一些调整。请注意,我们已使用 UnityObjectToClipPos 来变换 My Shadows 中的顶点位置。我们也可以在 My Lighting 中使用此函数,而不是自己执行矩阵乘法。 UnityObjectToClipPos 函数也执行此乘法,但使用常量值 1 作为第四个位置坐标,而不是依赖于网格数据。

Interpolators MyVertexProgram (VertexData v) {
	Interpolators i;
//	i.pos = mul(UNITY_MATRIX_MVP, v.vertex);
	i.pos = UnityObjectToClipPos(v.vertex);
	…
}

The data supplied via the mesh is always 1, but the shader compiler doesn't know this. As a result, using a constant is more efficient. Beginning with version 5.6, Unity will give a performance warning when using an unoptimized multiplication with UNITY_MATRIX_MVP.
通过网格提供的数据始终为 1,但着色器编译器不知道这一点。因此,使用常量效率更高。从 5.6 版本开始,当使用未经优化的乘法与 UNITY_MATRIX_MVP 时,Unity 将给出性能警告。

unitypackage

Partial Shadows  部分阴影

To also support shadows for the Fade and Transprant rendering modes, we have to add their keywords to the shader feature of or shadow caster pass. Like the other passes, the rendering feature now has four possible states.
为了支持 FadeTransprant 渲染模式下的阴影,我们必须将它们的关键字添加到阴影投射通道的着色器功能中。像其他通道一样,渲染功能现在有四种可能的状态。

#pragma shader_feature _ _RENDERING_CUTOUT _RENDERING_FADE _RENDERING_TRANSPARENT

These two modes are semitransparent instead of cutout. So their shadows should be semitransparent as well. Let's define a convenient SHADOWS_SEMITRANSPARENT macro in My Shadows when this is the case.
这两种模式是半透明而不是镂空。所以它们的阴影也应该是半透明的。在这种情况下,我们可以在 My Shadows 中定义一个方便的 SHADOWS_SEMITRANSPARENT 宏。

#if defined(_RENDERING_FADE) || defined(_RENDERING_TRANSPARENT)
	#define SHADOWS_SEMITRANSPARENT 1
#endif

Now we have to adjust the definition of SHADOWS_NEED_UV, so it also gets defined in the case of semitransparent shadows.
现在我们必须调整 D 的定义,使其在半透明阴影的情况下也能被定义。

#if SHADOWS_SEMITRANSPARENT || defined(_RENDERING_CUTOUT)
	#if !defined(_SMOOTHNESS_ALBEDO)
		#define SHADOWS_NEED_UV 1
	#endif
#endif

Dithering  抖动

Shadow maps contain the distance to surfaces that block light. Either the light is blocked at some distance, or it is not. Hence, there is no way to specify that light is partially blocked by semitransparent surfaces.
阴影贴图包含阻挡光线的表面的距离。光线要么在某个距离被阻挡,要么不被阻挡。因此,无法指定光线被半透明表面部分阻挡。

What we can do, is clip part of the shadow surface. That's what we do for cutout shadows. But instead of clipping based on a threshold, we could clip fragments uniformly. For example, if a surface lets half the light through, we could clip every other fragment, using a checkerboard pattern. Overall, the resulting shadow will appear half as strong as a full shadow.
我们能做的是裁剪部分阴影表面。这正是我们对镂空阴影所做的。但与其基于阈值裁剪,我们可以均匀地裁剪片段。例如,如果一个表面允许一半的光线通过,我们可以使用棋盘格图案裁剪每隔一个片段。总体而言,生成的阴影将显得只有完全阴影的一半强度。

We don't always have to use the same pattern. Depening on the alpha value, we can use a pattern with more or less holes. And if we mix these patterns, we can create smooth transitions of shadow density. Basically, we're using only two states to approximate a gradient. This technique is known as dithering.
我们并非总是要使用相同的模式。根据 alpha 值,我们可以使用孔洞更多或更少的模式。如果我们将这些模式混合使用,就可以创建阴影密度的平滑过渡。基本上,我们只用两种状态来近似一个渐变。这种技术被称为抖动。

Unity contains a dither pattern atlas that we can use. It contains 16 different patterns of 4 by 4 pixels. It starts with a completely empty pattern. Each successive pattern fills one additional pixel, until there are seven filled. Then the pattern is inverted and reverses, until all pixels are filled.
Unity 包含一个抖动模式图集,我们可以使用它。它包含 16 种不同的 4x4 像素模式。它从一个完全空白的模式开始。每个连续的模式都会多填充一个像素,直到填充七个像素。然后模式反转并倒序,直到所有像素都被填充。

Dither patterns used by Unity.
Unity 使用的抖动模式。

VPOS

To apply a dither patter to our shadow, we have to sample it. We cannot use the UV coordinates of the mesh, because those aren't uniform in shadow space. Instead, we'll need to use the screen-space coordinates of the fragment. As shadow maps are rendered from the point of view of the light, this aligns the patterns with the shadow map.
要将抖动模式应用于我们的阴影,我们必须对其进行采样。我们不能使用网格的 UV 坐标,因为这些坐标在阴影空间中不均匀。相反,我们需要使用片段的屏幕空间坐标。由于阴影图是从光源的角度渲染的,这使得模式与阴影图对齐。

The screen-space position of a fragment can be accessed in the fragment program, by adding a parameter with the VPOS semantic to it. These coordinates are not explicitly output by the vertex program, but the GPU can make them available to us.
片段的屏幕空间位置可以在片段程序中通过添加一个带有 VPOS 语义的参数来访问。这些坐标不是由顶点程序明确输出的,但 GPU 可以将其提供给我们。

Unfortunately, the VPOS and SV_POSITION semantics don't play nice. On some platforms, they end up mapped to the same position semantic. So we cannot use both at the same time in our Interpolators struct. Fortunately, we only need to use SV_POSITION in the vertex program, while VPOS is only needed in the fragment program. So we can use a separate struct for each program.
很遗憾,` VPOS ` 和 ` SV_POSITION ` 这两个语义无法很好地兼容。在某些平台上,它们最终会被映射到同一个位置语义。因此,我们不能在 `% Interpolators ` 结构体中同时使用它们。幸运的是,我们只需要在顶点程序中使用 ` SV_POSITION `,而 ` VPOS ` 只在片元程序中需要。所以,我们可以为每个程序使用一个单独的结构体。

First, rename Interpolators to InterpolatorsVertex and adjust MyShadowVertexProgram accordingly. Do not adjust MyShadowFragmentProgram.
首先,将 ` Interpolators ` 重命名为 ` InterpolatorsVertex `,并相应地调整 ` MyShadowVertexProgram `。不要调整 ` MyShadowFragmentProgram `。

struct InterpolatorsVertex {
	float4 position : SV_POSITION;
	#if SHADOWS_NEED_UV
		float2 uv : TEXCOORD0;
	#endif
	#if defined(SHADOWS_CUBE)
		float3 lightVec : TEXCOORD1;
	#endif
};

…

InterpolatorsVertex MyShadowVertexProgram (VertexData v) {
	InterpolatorsVertex i;
	#if defined(SHADOWS_CUBE)
		i.position = UnityObjectToClipPos(v.position);
		i.lightVec =
			mul(unity_ObjectToWorld, v.position).xyz - _LightPositionRange.xyz;
	#else
		i.position = UnityClipSpaceShadowCasterPos(v.position.xyz, v.normal);
		i.position = UnityApplyLinearShadowBias(i.position);
	#endif

	#if SHADOWS_NEED_UV
		i.uv = TRANSFORM_TEX(v.uv, _MainTex);
	#endif
	return i;
}

Then create a new Interpolators struct for use in the fragment program. It is a copy of the other struct, except that it should contain UNITY_VPOS_TYPE vpos : VPOS instead of float4 positions : SV_POSITION when semitransparent shadows are needed. The UNITY_VPOS_TYPE macro is defined in HLSLSupport. It's usually a float4, except for Direct3D 9, which needs it to be a float2.
然后,创建一个新的 Interpolators 结构体以供片段程序使用。它是另一个结构体的副本,不同之处在于,当需要半透明阴影时,它应包含 UNITY_VPOS_TYPE vpos : VPOS 而不是 float4 positions : SV_POSITIONUNITY_VPOS_TYPE 宏定义在 HLSLSupport 中。通常它是一个 float4 ,但对于 Direct3D 9 而言,它需要是一个 float2

struct InterpolatorsVertex {
	…
}

struct Interpolators {
	#if SHADOWS_SEMITRANSPARENT
		UNITY_VPOS_TYPE vpos : VPOS;
	#else
		float4 positions : SV_POSITION;
	#endif
	
	#if SHADOWS_NEED_UV
		float2 uv : TEXCOORD0;
	#endif
	#if defined(SHADOWS_CUBE)
		float3 lightVec : TEXCOORD1;
	#endif
};

Dithering  抖动

To access Unity's dither pattern texture, add a _DitherMaskLOD variable to My Shadows. The different patterns are stored in layers of a 3D texture, so its type has to be sampler3D instead of sampler2D.
如需访问 Unity 的抖动模式纹理,请将 _DitherMaskLOD 变量添加到 My Shadows 。不同的模式存储在 3D 纹理的层中,因此其类型必须是 sampler3D 而不是 sampler2D

sampler3D _DitherMaskLOD;

Sample this texture in MyShadowFragmentProgram, if we need semitransparent shadows. This is done via the tex3D function, which requires 3D coordinates. The third coordinate should be in the 0–1 range and is used to select a 3D slice. As there are 16 patterns, the Z coordinate of the first pattern is 0, the coordinate for the second pattern is 0.0625, the third is 0.128, and so on. Let's begin by always choosing the second pattern.
如果我们需要半透明阴影,请在 MyShadowFragmentProgram 中采样此纹理。这是通过 tex3D 函数完成的,该函数需要 3D 坐标。第三个坐标应在 0-1 范围内,用于选择 3D 切片。由于有 16 种模式,第一个模式的 Z 坐标为 0,第二个模式的坐标为 0.0625,第三个模式为 0.128,依此类推。让我们从始终选择第二种模式开始。

float4 MyShadowFragmentProgram (Interpolators i) : SV_TARGET {
	float alpha = GetAlpha(i);
	#if defined(_RENDERING_CUTOUT)
		clip(alpha - _AlphaCutoff);
	#endif

	#if SHADOWS_SEMITRANSPARENT
		tex3D(_DitherMaskLOD, float3(i.vpos.xy, 0.0625));
	#endif
	
	…
}

The alpha channel of the dither texture is zero when a fragment should be discarded. So subtract a small value from it and use that to clip.
当片段应该被丢弃时,抖动纹理的 alpha 通道为零。因此,从中减去一个很小的值,并用它进行裁剪。

	#if SHADOWS_SEMITRANSPARENT
		float dither =
			tex3D(_DitherMaskLOD, float3(i.vpos.xy, 0.0625)).a;
		clip(dither - 0.01);
	#endif

To actually see a pattern, we have to scale it. To get a good look at it, magnify it by a factor of 100, which is done by multiplying the position by 0.01. A spotlight shadow allows us to get a good look at it.
要真正看到某种图案,我们必须对其进行缩放。为了更好地观察它,将其放大 100 倍,这通过将位置乘以 0.01 来实现。聚光灯阴影使我们能够更好地观察它。

		tex3D(_DitherMaskLOD, float3(i.vpos.xy * 0.01, 0.0625)).a;
Uniform dithering, in fade mode.
在淡出模式下的统一抖动。

You can inspect all 16 dither patterns by increasing the Z coordinate in steps of 0.0625. The shadows get fully clipped at 0, and are fully rendered at 0.9375.
你可以通过以 0.0625 为步长增加 Z 坐标来检查所有 16 种抖动模式。阴影在 0 时完全被裁剪,在 0.9375 时完全渲染。

Changing dither patterns.
改变抖动模式。

Approximating Semitransparency
近似半透明

Instead of using a uniform pattern, we have to base the selection of the dither pattern on the surface's alpha value. As full opacity is reached at 0.9375, multiply the alpha value by this factor, then use it as the Z coordinate.
我们不能使用统一的图案,而必须根据表面的 Alpha 值选择抖动图案。由于在 0.9375 处达到完全不透明,因此将 Alpha 值乘以该系数,然后将其用作 Z 坐标。

			tex3D(_DitherMaskLOD, float3(i.vpos.xy * 0.01, alpha * 0.9375)).a;
Dithering based on alpha.
基于 Alpha 的抖动。

The dithering now varies based on the surface opacity. To make it look more like a true shadow, we'll have to scale down the pattern size. Unity uses a factor of 0.25, so we'll use that as well.
抖动现在会根据表面不透明度而变化。为了使其看起来更像真实的阴影,我们必须缩小图案尺寸。Unity 使用的系数是 0.25,因此我们也使用该系数。

			tex3D(_DitherMaskLOD, float3(i.vpos.xy * 0.25, alpha * 0.9375)).a;
Scaled dithering.  缩放抖动。

This looks a lot better, but it's not perfect. How obvious the dithering is depends on the resolution of the shadow map. The higher its resolution, the smaller and less obvious the patterns.
这样看起来好多了,但仍不完美。抖动的明显程度取决于阴影贴图的分辨率。分辨率越高,图案越小越不明显。

Dithering works better with soft directional shadows. The screen-space filtering smudges the dithered fragments to such a degree that they're no longer obvious. The result is something that approaches actual semitransparent shadows.
抖动更适用于柔和的定向阴影。屏幕空间过滤将抖动后的片段模糊到如此程度,以至于它们不再明显。结果接近于实际的半透明阴影。

hard soft
Hard and soft directional shadows with dithering.
带抖动的硬、柔定向阴影。

Unfortunately, dithering is not visually stable. When things move, you can get very obvious shadow swimming. Not just along the edge, but across the entire shadow!
遗憾的是,抖动在视觉上不稳定。当物体移动时,阴影会出现非常明显的“游动”现象。这不仅发生在边缘,而是整个阴影区域!

Swimming dithering.  游动的抖动。
unitypackage

Optional Semitransparent Shadows
可选的半透明阴影

Considering the limitations of semitransparent shadows, you might decide not to use them. You can entirely disable the shadows of an object via the Cast Shadows mode of its Mesh Renderer component. However, it could be that cutout shadows work just fine for a semitransparent object. For example, when a significant portion of its surface is fully opaque. So let's make it possible to choose between both types of shadows.
考虑到半透明阴影的局限性,你可能会决定不使用它们。你可以通过其组件的模式来完全禁用对象的阴影。然而,对于半透明对象来说,镂空阴影可能也适用。例如,当其表面的很大一部分完全不透明时。所以,让我们使其可以在两种阴影类型之间进行选择。

To support this choice, add a shader feature to the shadow caster pass for a new keyword, _SEMITRANSPARENT_SHADOWS.
为了支持这种选择,请为阴影投射器通道添加一个着色器特性,用于新的关键字。

			#pragma shader_feature _SEMITRANSPARENT_SHADOWS

In My Shadows, only define SHADOWS_SEMITRANSPARENT if the _SEMITRANSPARENT_SHADOWS shader keyword is set.
在中,只有在设置了着色器关键字时才定义。

#if defined(_RENDERING_FADE) || defined(_RENDERING_TRANSPARENT)
	#if defined(_SEMITRANSPARENT_SHADOWS)
		#define SHADOWS_SEMITRANSPARENT 1
	#endif
#endif

If the new shader feature is not enabled, then we should fall back to cutout shadows. We can do this by manually defining _RENDERING_CUTOUT.
如果未启用新的着色器特性,那么我们应该回退到镂空阴影。我们可以通过手动定义来做到这一点。

#if defined(_RENDERING_FADE) || defined(_RENDERING_TRANSPARENT)
	#if defined(_SEMITRANSPARENT_SHADOWS)
		#define SHADOWS_SEMITRANSPARENT 1
	#else
		#define _RENDERING_CUTOUT
	#endif
#endif

Because the new shader feature isn't enabled yet, we now get cutout shadows when using the Fade or Transparent rendering mode.
由于新的着色器功能尚未启用,因此在使用 FadeTransparent 渲染模式时,我们会获得镂空阴影。

Fade rendering, with cutout shadows.
渐隐渲染,带镂空阴影。

Toggling Semitransparency
切换半透明度

To enable semitransparent shadows again, we have to add an option for it to our custom shader UI. So add a DoSemitransparentShadows method to MyLightingShaderGUI.
要再次启用半透明阴影,我们必须为它在自定义着色器 UI 中添加一个选项。所以向 MyLightingShaderGUI 添加 DoSemitransparentShadows 方法。

	void DoSemitransparentShadows () {
	}

We only need to show this option when using the Fade or Transparent rendering mode. We know which mode we're using inside DoRenderingMode. So invoke DoSemitransparentShadows at the end of this method, if needed.
我们只需在使用 FadeTransparent 渲染模式时显示此选项。我们知道在 DoRenderingMode 内部正在使用哪种模式。因此,如果需要,在此方法结束时调用 DoSemitransparentShadows

	void DoRenderingMode () {
		…

		if (mode == RenderingMode.Fade || mode == RenderingMode.Transparent) {
			DoSemitransparentShadows();
		}
	}

As this is a binary choice, we can represent it with a toggle button. Because the label Semitransparent Shadows is wider than Unity's default inspector window width, I've abbreviated it. For clarity, I gave it a tooltip that isn't abbreviated.
由于这是一个二元选择,我们可以用一个切换按钮来表示它。因为标签 Semitransparent Shadows 比 Unity 默认的检查器窗口宽度更宽,所以我将其缩写了。为了清晰起见,我给了它一个未缩写的工具提示。

	void DoSemitransparentShadows () {
		bool semitransparentShadows =
			EditorGUILayout.Toggle(
				MakeLabel("Semitransp. Shadows", "Semitransparent Shadows"),
				IsKeywordEnabled("_SEMITRANSPARENT_SHADOWS")
			);
	}
Semitransparent shadows checkbox.
半透明阴影复选框。

Like with the other keywords, check whether the user makes a change and set the keyword accordingly.
与其他关键字一样,检查用户是否进行了更改,并相应设置关键字。

	void DoSemitransparentShadows () {
		EditorGUI.BeginChangeCheck();
		bool semitransparentShadows =
			EditorGUILayout.Toggle(
				MakeLabel("Semitransp. Shadows", "Semitransparent Shadows"),
				IsKeywordEnabled("_SEMITRANSPARENT_SHADOWS")
			);
		if (EditorGUI.EndChangeCheck()) {
			SetKeyword("_SEMITRANSPARENT_SHADOWS", semitransparentShadows);
		}
	}

Showing Alpha Cutoff for Shadows
显示阴影的 Alpha 截止

When using cutout shadows, we might like to change the Alpha Cutoff threshold. Currently, it only shows up in our UI when using the Cutout rendering mode. However, it must now also be accessible in Fade and Transparent mode, when not using semitransparent shadows. We can support this by setting shouldShowAlphaCutoff to true in DoSemitransparentShadows, when appropriate.
使用镂空阴影时,我们可能想更改 Alpha Cutoff 阈值。目前,它只在使用 Cutout 渲染模式时显示在我们的 UI 中。但是,它现在必须在 FadeTransparent 模式下也可用,当不使用半透明阴影时。我们可以通过在 DoSemitransparentShadows 中适当地将 shouldShowAlphaCutoff 设置为 true 来支持这一点。

	void DoSemitransparentShadows () {
		…
		if (!semitransparentShadows) {
			shouldShowAlphaCutoff = true;
		}
	}
Alpha cutoff appears when needed.
阿尔法截止在需要时出现。

The next tutorial is Deferred Shading.
下一篇教程是《延迟着色》。

unitypackage PDF
switch theme  切换主题
contents  内容
  1. Cutout Shadows
    1. Refactoring My Shadows
    2. Clipping Shadow Fragments
    3. Refactoring My Lighting
  2. Partial Shadows
    1. Dithering
    2. VPOS
    3. Dithering
    4. Approximating Semitransparency
  3. Optional Semitransparent Shadows
    1. Toggling Semitransparency
    2. Showing Alpha Cutoff for Shadows