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.

This tutorial was made with Unity 5.5.0f3.

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 .

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.

To make this actually work, add shader features for _RENDERING_CUTOUT and _SMOOTHNESS_ALBEDO to the shadow caster pass of My First Lighting Shader.

Now we can retrieve the alpha value in the fragment program, and use it to clip when in Cutout rendering mode.

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.

Pass the UV coordinates on to the the interpolators in the vertex program, when needed.

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.

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 .

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.

Do the same for the fragment program. Then get rid of the old conditional programs.

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.

First, move the definition of Interpolators out of the conditional block. Then make the light vector conditional instead.

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.

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.

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.

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.

#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.

#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.

#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. 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. Dither patterns used by 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. 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. 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. First, rename Interpolators to InterpolatorsVertex and adjust MyShadowVertexProgram accordingly. Do not adjust 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 . 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 }; Do we need position at all in the fragment program? The vertex program needs to output its transformed position, but we don't have to access it in our fragment program. So technically we could leave it out of the struct. However, because all other fields of the struct are conditional, that could lead to an empty struct. The compiler can't always handle those, so we keep the position in there to prevent errors.

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 . 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. 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. #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. 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. Changing dither patterns.