48
Catlike Coding Unity C# Tutorials Rendering 7 Shadows Investigate how Unity renders shadows. Cast directional shadows. Receive directional shadows. Add support for spotlight and point light shadows. This is the seventh part of a tutorial series about rendering. The previous part covered normal mapping. Now we'll take a look at shadows. This tutorial was made with Unity 5.4.0f3. When rendering, casting a shadow is a good thing.

Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

  • Upload
    ledat

  • View
    253

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

Catlike Coding

Unity C# Tutorials

Rendering 7 Shadows

Investigate how Unity renders shadows.Cast directional shadows.Receive directional shadows.Add support for spotlight and point light shadows.

This is the seventh part of a tutorial series about rendering. The previous partcovered normal mapping. Now we'll take a look at shadows.

This tutorial was made with Unity 5.4.0f3.

When rendering, casting a shadow is a good thing.

Page 2: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

1 Directional Shadows

While our lighting shader produces fairly realistic results by now, it evaluates eachsurface fragment in isolation. It assumes that a ray of light from every light sourceeventually hits every fragment. But this is only true if those rays aren't blocked bysomething.

Some light rays get blocked.

When an object sits in between a light source and another object, it might preventpart or all of the light rays from reaching that other object. The rays that illuminatethe first object are no longer available to illuminate the second object. As as result,the second object will remain at least partially unlit. The area that is not lit lies in theshadow of the first object. To describe this, we often say that the fist object casts ashadow on the second one.

In reality, there is a transition region between fully lit and fully shadowed space,know as the penumbra. It exists because all light sources have a volume. As a result,there are regions where only part of the light source is visible, which means that theyare partially shadowed. The larger the light source, and the further away a surface isfrom its shadow caster, the larger this region is.

Page 3: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

Shadow with penumbra.

Unity doesn't support penumbra. Unity does support soft shadows, but that is ashadow filtering technique, not a simulation of penumbra.

1.1 Enabling Shadows

Without shadows, it is hard to see the spatial relationships between objects. Toillustrate this, I created a simple scene with a few stretched cubes. I placed four rowsof spheres above these cubes. The middle rows float of spheres, while the outer rowsare connected to the cubes below them via cylinders.

The objects have Unity's default while material. The scene has two directional lights,the default directional light, and a slightly weaker yellow light. These are the samelights used in previous tutorials.

Currently, the shadows are disabled project-wide. We did that in an earlier tutorial.The ambient intensity is also set to zero, which makes it easier to see the shadows.

Page 4: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

Two directional lights, no shadows, no ambient light.

Shadows are part of the project-wide quality settings, found via Edit / ProjectSettings / Quality. We'll enable them at a high quality level. This means supportingboth hard and soft shadows, using a high resolution, a stable fit projection, adistance of 150, and four cascades.

Shadow quality settings.

Make sure that both lights are set to cast soft shadows. Their resolution shoulddepend on the quality settings.

Shadow settings per light.

With both directional lights casting shadows, the spatial relationships between allobjects becomes a lot clearer. The entire scene has become both more realistic andmore interesting to look at.

Page 5: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

Scene with shadows.

Page 6: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

1.2 Shadow Mapping

How does Unity add these shadows to the scene? The standard shader apparently hassome way to determine whether a ray is blocked or not.

You could figure this out whether a point lies in a shadow, by casting a ray throughthe scene, from the light to the surface fragment. If that ray hits something before itreaches the fragment, then it is blocked. This is something that a physics enginecould do, but it would be very impractical to do so for each fragment, and per light.And then you'd have to get the results to the GPU somehow.

There are a few techniques to support real-time shadows. Each has it advantages anddisadvantages. Unity uses the most common technique nowadays, which is shadowmapping. This means that Unity stores shadow information in textures, somehow.We'll now investigate how that works.

Open the frame debugger via Window / Frame Debugger, enable it, and look at thehierarchy of rendering steps. Look at the differences between a frame without and aframe with shadows enabled.

Rendering process without vs. with shadows.

When shadows are disabled, all objects are rendered as usual. We were alreadyfamiliar with this process. But when shadows are enabled, the process becomes morecomplex. There are a few more rendering phases, and quite a lot more draw calls.Shadows are expensive!

1.3 Rendering to the Depth Texture

Page 7: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

When directional shadows are enabled, Unity begins a depth pass into the renderingprocess. The result is put into a texture that matches the screen resolution. This passrenders the entire scene, but only records the depth information of each fragment.This is the same information that is used by the GPU to determine whether afragment ends up on top or below a previously rendered fragment.

This data corresponds with a fragment's Z coordinate, in clip space. This is the spacethat defines the area that the camera can see. The depth information ends up storedas a value in the 0–1 range. When viewing the texture, nearby texels appear dark.The further away a texel is, the lighter it becomes.

Depth texture, with camera near plane set to 5.

Page 8: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

What is clip space?

It is the space that determines what the camera sees. When you select the main camerain the scene view, you will see a pyramid wire frame in front of it, which indicates what itcan see.

Camera view, with large near plane value.

In clip space, this pyramid is a regular cube. The model-view-projection matrix is usedto convert mesh vertices to this space. It is known as clip space, because everything thatends up outside of this cube gets clipped, because it isn't visible.

This information actually has nothing to do with shadows directly, but Unity will useit in a later pass.

1.4 Rendering to Shadow Maps

The next thing Unity renders is the shadow map of the first light. A little later, it willrendered the shadow map of the second light as well.

Again, the entire scene is rendered, and again only the depth information is stored ina texture. However, this time the scene is rendered from the point of the view of thelight source. Effectively, the light acts as a camera. This means that the depth valuetells us how far a ray of light traveled before it hit something. This can be used todetermine if something is shadowed!

What about normal maps?

The shadow maps record the depth of the actual geometry. Normals maps add theillusion of a rough surface, and shadow maps ignore them. Thus, shadows are notaffected by normal maps.

Page 9: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

Because we're using directional lights, their cameras are orthographic. As such, thereis no perspective projection, and the exact position of the light's camera doesn'tmatter. Unity will position the camera so it sees all objects that are in view of thenormal camera.

Two shadow maps, each with four viewpoints.

Page 10: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

Actually, it turns out that Unity doesn't just render the entire scene once per light.The scene is rendered four times per light! The textures are split into four quadrants,each being rendered to from a different point of view. This happens because we haveopted to use four shadow cascades. If you were to switch to two cascades, the scenewould be rendered twice per light. And without cascades, it is only rendered once perlight. We will see why Unity does this when we look at the quality of shadows.

1.5 Collecting Shadows

We have the depth information of the scene, from the point of view of the camera. Wealso have this information from the point of view of each light. Of course this data isstored in different clip spaces, but we know the relative positions and orientations ofthese spaces. So we can convert from one space to the other. This allows us tocompare the depth measurements from both points of view. Conceptually, we havetwo vectors that should end up at the same point. If they do, both the camera andlight can see that point, and so it is lit. If the light's vector ends before reaching thepoint, then the light is blocked, which means that the point is shadowed.

What about when the scene camera can't see a point?

Those points are hidden behind other points that are closer to the camera. The scene'sdepth texture only contains the closest points. As a result, no time is wasted onevaluating hidden points.

Page 11: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

Screen-space shadows, per light.

Unity creates these textures by rendering a single quad that covers the entire view. Ituses the Hidden/Internal-ScreenSpaceShadows shader for this pass. Each fragmentsamples from the scene's and light's depth textures, makes the comparison, andrenders the final shadow value to a screen-space shadow map. Lit texels are set to 1,and shadowed texels are set to 0. At this point Unity can also perform filtering, tocreate soft shadows.

Why does Unity alternate between rendering and collecting?

Each light needs its own screen-space shadow map. But the shadow map rendered fromthe light's point of view can be reused.

Page 12: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

1.6 Sampling the Shadow Maps

Finally, Unity is finished rendering shadows. Now the scene is rendered normally,with one change. The light colors are multiplied by the values stored in their shadowmaps. This eliminates the light when it should be blocked.

Every fragment that gets rendered samples the shadow maps. Also fragments thatend up hidden behind other objects that are drawn later. So these fragments can endup receiving the shadows of the objects that end up hiding them. You can see thiswhen stepping through the frame debugger. You can also see shadows appear beforethe objects that actually cast them. Of course these mistakes only manifest whilerendering the frame. Once it is finished, the image is correct.

Patially rendered frame, containing strange shadows.

1.7 Shadow Quality

When the scene is rendered from the light's point of view, the orientation does notmatch the scene camera. So the texels of the shadow maps don't align with thetexels of the final image. The resolution of the shadow maps also ends up beingdifferent. The resolution of the final image is determined by the display settings. Theresolution of the shadow maps is determined by the shadow quality settings.

When the texels of the shadow maps end up rendered larger than those of the finalimage, they will become noticeable. The edges of the shadows will be aliased. This ismost obvious when using hard shadows.

Page 13: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

Hard vs. soft shadows.

To make this as obvious as possible, change the shadow quality settings so we onlyget hard shadows, at the lowest resolution, with no cascades.

Low quality shadows.

It is now very obvious that the shadows are textures. Also, bits of shadow areappearing in places where they shouldn't. We'll look into that later.

The closer the shadows get to the scene camera, the larger their texels become.That's because the shadow map currently covers the entire area visible to the scenecamera. We can increase the quality close to the camera, by reducing the area that iscovered by shadows, via the quality settings.

Page 14: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

Shadow distance reduced to 25.

By limiting shadows to an area close to the scene camera, we can use the sameshadow maps to cover a much smaller area. As a result, we get better shadows. Butwe lose the shadows that are further away. The shadows fade away as they approachthe maximum distance.

Ideally, we get high-quality shadows up close, while also keeping the shadows thatare far away. Because far away shadows end up rendered to a smaller screen area,those could make do with a lower-resolution shadow map. This is what shadowcascades do. When enabled, multiple shadow maps are rendered into the sametexture. Each map is for use at a certain distance.

Low resolution textures, with four cascades.

When using four cascades, the result looks a lot better, even though we're still usingthe same texture resolution. We're just using the texels much more efficiently. Thedownside is that we now have to render the scene three more times.

When rendering to the screen-space shadow maps, Unity takes care of samplingfrom the correct cascade. You can find where one cascade ends and another begins,by looking for a sudden change of the shadow texel size.

Page 15: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

You can control the range of the cascade bands via the quality settings, as portionsof the shadow distance. You can also visualize them in the scene view, by changingits Shading Mode. Instead of just Shaded, use Miscellaneous / Shadow Cascades. Thiswill render the colors of the cascades on top of the scene.

Cascade regions, adjusted to show three bands.

How do I change the scene view's display mode?

There is a dropdown list at the top left of the scene view window. By default, it is set toShaded.

The shape of the cascade bands depends on the Shadow Projection quality setting.The default is Stable Fit. In this mode, the bands are chosen based on the distance tothe camera's position. The other option is Close Fit, which uses the camera's depthinstead. This produces rectangular bands in the camera's view direction.

Close fit.

Page 16: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

This configuration allow for more efficient use of the shadow texture, which leads tohigher-quality shadows. However, the shadow projection now depends on theposition and orientation or the camera. As as result, when the camera moves orrotates, the shadow maps change as well. If you can see the shadow texels, you'llnotice that they move. This effect is known as shadow edge swimming, and can bevery obvious. That's why the other mode is the default.

Shadow swimming.

Don't Stable Fit shadows also depend on the camera position?

They do, but Unity can align the maps so that when the camera position changes, thetexels appear motionless. Of course the cascade bands do move, so the transition pointsbetween the bands change. But if you don't notice the bands, you also don't notice thatthey move.

1.8 Shadow Acne

When we used low quality hard shadows, we saw bits of shadow appear where theyshouldn't. Unfortunately, this can happen regardless of the quality settings.

Each texel in the shadow map represents the point where a light ray hit a surface.However, texels aren't single points. They end up covering a larger area. And they arealigned with the light direction, not with the surface. As a result of this, they can endup sticking in, through, and out of surfaces like dark shards. As parts of the texelsend up poking out of the surfaces that cast the shadow, the surface appears toshadow itself. This is known as shadow acne.

Page 17: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

Shadow map causes acne.

Another source of shadow acne is numerical precision limitations. These limitationscan cause incorrect result when very small distances are involved.

Severe acne, when using no biases at all.

One way to prevent this problem is by adding a depth offset when rendering theshadow maps. This bias is added to the distance from the light to the shadow castingsurface, pushes the shadows into the surfaces.

Biased shadow map.

The shadow bias is configured per light, and is set to 0.05 by default.

Page 18: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

Shadow settings per light.

A low bias can produce shadow acne, but a large bias introduces another problem.As the shadow-casting objects are pushed away from the lights, so are theirshadows. As a result, the shadows will not be perfectly aligned with the objects. Thisisn't so bad when using a small bias. But too large a bias can make it seem likeshadows are disconnected from the objects that cast them. This effect is known aspeter panning.

Large bias causes peter panning.

Besides this distance bias, there is also a Normal Bias. This is a subtler adjustment ofthe shadow casters. This bias pushes the vertices of the shadow casters inwards,along their normals. This also reduces self-shadowing, but it also makes theshadows smaller and can cause holes to appear in the shadows.

What are the best bias settings?

There are no best settings. Unfortunately, you'll have to experiment. Unity's defaultsettings might work, but they can also produce unacceptable results. Different qualitysettings can also produce different results.

1.9 Anti-Aliasing

Page 19: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

Do you have anti-aliasing enabled in the quality settings? If you have, then you mighthave spotted another problem of shadow maps. They do not mix with the standardanti-aliasing approach.

Aliasing when using anti-aliasing.

When you enable anti-aliasing in the quality settings, Unity will use multi-samplinganti-aliasing, MSAA. It removes the aliasing at triangle edges by performing somesuper-sampling along those edges. The details don't matter here. What matters isthat when Unity renders the screen-space shadow maps, it does so with a singlequad that covers the entire view. As a result, there are no triangle edges, and thusMSAA does not affect the screen-space shadow map. MSAA does work for the finalimage, but the shadow values are taken straight from the screen-space shadow map.This becomes very obvious when a light surface next to a darker surface isshadowed. The edge between the light and dark geometry is anti-aliased, while theshadow edge isn't.

Page 20: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

No AA vs. MSAA 4× vs. FXAA.

Anti-aliasing methods that rely on image post-processing – like FXAA – don't havethis problem, because they are applied after the entire scene has been rendered.

Does this mean that I cannot combine MSAA with directional shadows?

You can, but you will experience the problem described above. In some cases, it mightnot be noticeable. For example, when all surface colors are roughly the same, theartifacts will be subtle. You still get aliased shadow edges, of course.

Page 21: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

2 Casting Shadows

Now that we know how Unity creates shadows for directional lights, it is time tosupport add support for them to our own shader. Currently, My First Lighting Shaderneither casts nor received shadows.

Let's deal with casting shadows first. I have changed the spheres and cylinders in theexample scene so they use our material. So now they no longer cast shadows.

No shadows are cast by our material.

We know that Unity renders the scene multiple times for directional shadows. Oncefor the depth pass, and once per light, for each shadow map cascade. The screen-space shadow map is a screen-space effect and doesn't concern us.

To support all relevant passes, we have to add a pass to our shader, with its lightmode set to ShadowCaster. Because we are only interested in the depth values, it willbe a lot simpler than our other passes.

Page 22: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

SubShader {

Pass { Tags { "LightMode" = "ForwardBase" }

… }

Pass { Tags { "LightMode" = "ForwardAdd" }

… }

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

CGPROGRAM

#pragma target 3.0

#pragma vertex MyShadowVertexProgram #pragma fragment MyShadowFragmentProgram

#include "My Shadows.cginc"

ENDCG } }

Let's give the shadow programs their own include file, named My Shadows.cginc.They are very simple. The vertex program converts the position from object space toclip space as usual, and does nothing else. The fragment program actually doesn'tneed to do anything, so just return zero. The GPU records the depth value for us.

Page 23: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

#if !defined(MY_SHADOWS_INCLUDED)#define MY_SHADOWS_INCLUDED

#include "UnityCG.cginc"

struct VertexData { float4 position : POSITION;};

float4 MyShadowVertexProgram (VertexData v) : SV_POSITION { return mul(UNITY_MATRIX_MVP, v.position);}

half4 MyShadowFragmentProgram () : SV_TARGET { return 0;}

#endif

This is already enough to cast shadows directional.

Casting shadows.

2.1 Bias

We also have to support the shadow bias. During the depth pass, the biases are zero,but when rendering the shadow maps, the biases correspond to the light settings. Wedo so by applying the depth bias to the position in the vertex shader, in clip space.

To support the depth bias, we can use the UnityApplyLinearShadowBias function, whichis defined in UnityCG.

float4 MyShadowVertexProgram (VertexData v) : SV_POSITION { float4 position = mul(UNITY_MATRIX_MVP, v.position); return UnityApplyLinearShadowBias(position);}

Page 24: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

How does UnityApplyLinearShadowBias work?

It increases the Z coordinate in clip space. What complicates this is that it are workingwith homogeneous coordinates. It has to compensate for the perspective projection, sothat the offset doesn't vary with distance from the camera. It must also make sure thatthe result doesn't go out of bounds.

float4 UnityApplyLinearShadowBias (float4 clipPos) { clipPos.z += saturate(unity_LightShadowBias.x / clipPos.w); float clamped = max(clipPos.z, clipPos.w * UNITY_NEAR_CLIP_VALUE); clipPos.z = lerp(clipPos.z, clamped, unity_LightShadowBias.y); return clipPos;}

To also support the normal bias, we have to move the vertex position based on thenormal Vector. So we have to add the normal to our vertex data. Then we can use theUnityClipSpaceShadowCasterPos function to apply the bias. This function is also definedin UnityCG.

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

float4 MyShadowVertexProgram (VertexData v) : SV_POSITION { float4 position = UnityClipSpaceShadowCasterPos(v.position.xyz, v.normal); return UnityApplyLinearShadowBias(position);}

Page 25: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

How does UnityClipSpaceShadowCasterPos work?

It converts the position to world space, applies the normal bias, then converts to clipspace. The exact offset depends on the angle between the normal and light direction,and the shadow texel size.

float4 UnityClipSpaceShadowCasterPos (float3 vertex, float3 normal) { float4 clipPos; // Important to match MVP transform precision exactly while rendering // into the depth texture, so branch on normal bias being zero. if (unity_LightShadowBias.z != 0.0) { float3 wPos = mul(unity_ObjectToWorld, float4(vertex,1)).xyz; float3 wNormal = UnityObjectToWorldNormal(normal); float3 wLight = normalize(UnityWorldSpaceLightDir(wPos));

// apply normal offset bias (inset position along the normal) // bias needs to be scaled by sine between normal and light direction // (http://the-witness.net/news/2013/09/shadow-mapping-summary-part-1/) // // unity_LightShadowBias.z contains user-specified normal offset amount // scaled by world space texel size.

float shadowCos = dot(wNormal, wLight); float shadowSine = sqrt(1 - shadowCos * shadowCos); float normalBias = unity_LightShadowBias.z * shadowSine;

wPos -= wNormal * normalBias;

clipPos = mul(UNITY_MATRIX_VP, float4(wPos, 1)); } else { clipPos = UnityObjectToClipPos(vertex); } return clipPos;}

The UnityObjectToClipPos function just performs the model-view-projection matrixmultiplication, with a caveat when using stereoscopic rendering.

// Tranforms position from object to homogenous spaceinline float4 UnityObjectToClipPos( in float3 pos) {#ifdef UNITY_USE_PREMULTIPLIED_MATRICES return mul(UNITY_MATRIX_MVP, float4(pos, 1.0));#else // More efficient than computing M*VP matrix product return mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(pos, 1.0)));#endif}

Our shader is now a fully functional shadow caster.

Page 26: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

3 Receiving Shadows

The second part of the process is receiving shadows. All objects in the test scenenow use our material.

No shadows are received, when only using our material.

Let's first concern ourselves with the shadows of the main directional light only.Because this light is included in the base pass, we have to adjust that one.

When the main directional light casts shadows, Unity will look for a shader variantthat has the SHADOWS_SCREEN keyword enabled. So we have to create two variants of ourbase pass, one with and one without this keyword. This works the same as for theVERTEXLIGHT_ON keyword.

#pragma multi_compile _ SHADOWS_SCREEN #pragma multi_compile _ VERTEXLIGHT_ON

The pass now has two multi-compile directives, each for a single keyword. As aresult, there are four possible variants. One with no keywords, one for each keyword,and one with both keywords.

// Snippet #0 platforms ffffffff:SHADOWS_SCREEN VERTEXLIGHT_ON

4 keyword variants used in scene:

<no keywords defined>VERTEXLIGHT_ONSHADOWS_SCREENSHADOWS_SCREEN VERTEXLIGHT_ON

Page 27: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

After adding the multi-compile pragma, the shader compiler will complain about anonexistent _ShadowCoord. This happens because the UNITY_LIGHT_ATTENUATION macrobehaves differently when shadows are in play. To quickly fix this, open the MyLighting.cginc file and just set the attenuation to 1 when we have shadows.

UnityLight CreateLight (Interpolators i) { UnityLight light;

#if defined(POINT) || defined(POINT_COOKIE) || defined(SPOT) light.dir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos); #else light.dir = _WorldSpaceLightPos0.xyz; #endif

#if defined(SHADOWS_SCREEN) float attenuation = 1; #else UNITY_LIGHT_ATTENUATION(attenuation, 0, i.worldPos); #endif

light.color = _LightColor0.rgb * attenuation; light.ndotl = DotClamped(i.normal, light.dir); return light;}

3.1 Sampling Shadows

To get to the shadows, we have to sample the screen-space shadow map. To do this,we need to know the screen-space texture coordinates. Like other texturecoordinates, we'll pass them from the vertex shader to the fragment shader. So weneed use an additional interpolator when supporting shadows. We'll begin by justpassing along the homogeneous clip-space position, so we need a float4.

Page 28: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

struct Interpolators { …

#if defined(SHADOWS_SCREEN) float4 shadowCoordinates : TEXCOORD5; #endif

#if defined(VERTEXLIGHT_ON) float3 vertexLightColor : TEXCOORD6; #endif};

Interpolators MyVertexProgram (VertexData v) { …

#if defined(SHADOWS_SCREEN) i.shadowCoordinates = i.position; #endif

ComputeVertexLightColor(i); return i;}

We can access the screen-space shadows via _ShadowMapTexture. It is defined inAutoLight when appropriate. The naive approach is to simply use the clip-space XYcoordinates of the fragment to sample this texture.

UnityLight CreateLight (Interpolators i) { …

#if defined(SHADOWS_SCREEN) float attenuation = tex2D(_ShadowMapTexture, i.shadowCoordinates.xy); #else UNITY_LIGHT_ATTENUATION(attenuation, 0, i.worldPos); #endif

…}

Sampling shadows.

Page 29: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

We're now sampling shadows, but with clip-space coordinates instead of screen-space coordinates. We do get shadows, but they end up compressed into a tinyregion at the center of the screen. We have to stretch them to cover the entirewindow.

My shadows are upside down?

That is due to API differences. We'll deal with that soon.

In clip space, all visible XY coordinates fall inside the −1–1 range, while the range forscreen-space is 0–1. The first step to solve this by having XY. Next, we also have tooffset the coordinates so they are zero at the bottom-left corner of the screen.Because we're dealing with a perspective transformation, how much we must offsetthe coordinates depends on how far away they are. In this case, the offset is equal tothe fourth homogeneous coordinate, before halving.

#if defined(SHADOWS_SCREEN) i.shadowCoordinates.xy = (i.position.xy + i.position.w) * 0.5; i.shadowCoordinates.zw = i.position.zw; #endif

Shadows in the bottom-left corner.

The projection is still not correct, because we're using homogeneous coordinates. Wehave to convert to screen-space coordinates, by dividing X and Y by W.

i.shadowCoordinates.xy = (i.position.xy + i.position.w) * 0.5 / i.position.w;

Page 30: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

Incorrect conversion.

The result gets distorted. The shadows are stretched and curved. This happensbecause we do the division before interpolation. This is incorrect, the coordinatesshould be interpolated independently, before the division. So we have to move thedivision to the fragment shader.

Interpolators MyVertexProgram (VertexData v) { …

#if defined(SHADOWS_SCREEN) i.shadowCoordinates.xy = (i.position.xy + i.position.w) * 0.5; // / i.position.w; i.shadowCoordinates.zw = i.position.zw; #endif …}

UnityLight CreateLight (Interpolators i) { …

#if defined(SHADOWS_SCREEN) float attenuation = tex2D( _ShadowMapTexture, i.shadowCoordinates.xy / i.shadowCoordinates.w ); #else UNITY_LIGHT_ATTENUATION(attenuation, 0, i.worldPos); #endif

…}

Page 31: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

How does interpolation affect division?

This is best illustrated with an example. Suppose that we're interpolating between theXW coordinate pairs (0, 1) and (1, 4). No matter how we do this, X / W starts at 0 andends at ¼. But what about halfway between those points?

If we divide before interpolating, then we end up halfway between 0 and ¼, which is ⅛.

If we divide after interpolating, then at the halfway point we end up with the coordinates(0.5, 2.5), which leads to the division 0.5 / 2.5, which is ⅕, not ⅛. So in this case theinterpolation is not linear.

Different approach, different results.

Correctly sampled shadows.

At this point, your shadows will either appear correct, or upside down. If they areflipped, it means that your graphics API – Direct3D – has the screen-space Ycoordinates go from 0 to 1 downwards, instead of upwards. To synchronize with this,flip the Y coordinate of the vertex.

Page 32: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

i.shadowCoordinates.xy = (float2(i.position.x, -i.position.y) + i.position.w) * 0.5;

Flipped shadows.

3.2 Using Unity's Code

Unity's include file provide a collection of functions and macros to help us sampleshadows. They take care of API differences and platform limitations. For example, wecan use the ComputeScreenPos function from UnityCG.

UnityCG.

#if defined(SHADOWS_SCREEN) i.shadowCoordinates = ComputeScreenPos(i.position); #endif

Page 33: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

What does ComputeScreenPos look like?

It performs the same computations that we did. The _ProjectParams.x variable is −1when the Y coordinate needs to be flipped. Also, it takes care of texture alignment whenusing Direct3D9. There's also special logic required when doing single-passstereoscopic rendering.

inline float4 ComputeNonStereoScreenPos (float4 pos) { float4 o = pos * 0.5f; #if defined(UNITY_HALF_TEXEL_OFFSET) o.xy = float2(o.x, o.y * _ProjectionParams.x) + o.w * _ScreenParams.zw; #else o.xy = float2(o.x, o.y * _ProjectionParams.x) + o.w; #endif o.zw = pos.zw; return o;}

inline float4 ComputeScreenPos (float4 pos) { float4 o = ComputeNonStereoScreenPos(pos); #ifdef UNITY_SINGLE_PASS_STEREO o.xy = TransformStereoScreenSpaceTex(o.xy, pos.w); #endif return o;}

The AutoLight include file defines three useful macros. They are SHADOW_COORDS,TRANSFER_SHADOW, and SHADOW_ATTENUATION. When shadows are enabled, these macrosperform the same work that we just did. They do nothing when there are noshadows.

SHADOW_COORDS defines the interpolator for shadow coordinates, when needed. I usesthe _ShadowCoord name, which was what the compiler complained about earlier.

struct Interpolators { … // #if defined(SHADOWS_SCREEN)// float4 shadowCoordinates : TEXCOORD5;// #endif SHADOW_COORDS(5)

…};

TRANSFER_SHADOW fills these coordinates in the vertex program.

Page 34: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

Interpolators MyVertexProgram (VertexData v) { …

// #if defined(SHADOWS_SCREEN)// i.shadowCoordinates = i.position;// #endif TRANSFER_SHADOW(i);

…}

And SHADOW_ATTENUATION uses the coordinates to sample the shadow map in thefragment program.

UnityLight CreateLight (Interpolators i) { …

#if defined(SHADOWS_SCREEN) float attenuation = SHADOW_ATTENUATION(i); #else UNITY_LIGHT_ATTENUATION(attenuation, 0, i.worldPos); #endif

…}

Actually, the UNITY_LIGHT_ATTENUATION macro already uses SHADOW_ATTENUATION. That'swhy we got that compiler error before. So we can suffice with using just that macro.The only change is that we have to use the interpolators as its second argument,while we just used zero before.

// #if defined(SHADOWS_SCREEN)// float attenuation = SHADOW_ATTENUATION(i);// #else UNITY_LIGHT_ATTENUATION(attenuation, i, i.worldPos);// #endif

After rewriting our code to use these macros, we get new compile errors. Thishappens because Unity's macros unfortunately make assumptions about the vertexdata and interpolator structures. First, it is assumed that the vertex position isnamed vertex, while we named it position. Second, it is assumed that the interpolatorposition is named pos, but we named it position.

Let's be pragmatic and adopt these names too. They're only used in a few placesanyway, so we don't have to change much.

Page 35: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

struct VertexData { float4 vertex : POSITION; …};

struct Interpolators { float4 pos : SV_POSITION; …};

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

Our shadows should work again, and this time on as many platforms as Unitysupports.

What do these macros look like?

Which macro versions you end up with depends on which shader keywords are enabled,and which features are supported. When SHADOWS_SCREEN is defined, you end up with thefollowing code.

Page 36: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

#define SHADOW_COORDS(idx1) unityShadowCoord4 _ShadowCoord : TEXCOORD##idx1;

#if defined(UNITY_NO_SCREENSPACE_SHADOWS) UNITY_DECLARE_SHADOWMAP(_ShadowMapTexture); #define TRANSFER_SHADOW(a) a._ShadowCoord = \ mul(unity_WorldToShadow[0], mul(unity_ObjectToWorld, v.vertex));

inline fixed unitySampleShadow (unityShadowCoord4 shadowCoord) {} #if defined(SHADOWS_NATIVE) fixed shadow = UNITY_SAMPLE_SHADOW(_ShadowMapTexture, shadowCoord.xyz); shadow = _LightShadowData.r + shadow * (1-_LightShadowData.r); return shadow; #else unityShadowCoord dist = SAMPLE_DEPTH_TEXTURE(_ShadowMapTexture, shadowCoord.xy);

// tegra is confused if we use _LightShadowData.x directly // with "ambiguous overloaded function reference // max(mediump float, float)" unityShadowCoord lightShadowDataX = _LightShadowData.x; unityShadowCoord threshold = shadowCoord.z; return max(dist > threshold, lightShadowDataX); #endif }#else // UNITY_NO_SCREENSPACE_SHADOWS sampler2D _ShadowMapTexture; #define TRANSFER_SHADOW(a) a._ShadowCoord = ComputeScreenPos(a.pos);

inline fixed unitySampleShadow (unityShadowCoord4 shadowCoord) { fixed shadow = tex2Dproj(_ShadowMapTexture, UNITY_PROJ_COORD(shadowCoord)).r; return shadow; }#endif

#define SHADOW_ATTENUATION(a) unitySampleShadow(a._ShadowCoord)

Note that the Z component of the shadow coordinates is only used when bothUNITY_NO_SCREENSPACE_SHADOWS and SHADOWS_NATIVE are defined.

The tex2Dproj function does the same as tex2D, but it also takes care of the XY / Wdivision. You can see this when looking at the compiled code.

Page 37: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

3.3 Multiple Shadows

The main directional light is now casting shadows, but the second directional lightstill doesn't. That's because we don't yet define SHADOWS_SCREEN in the additive pass.We could add a multi-compile statement to it, but SHADOWS_SCREEN only works fordirectional lights. To get the correct combination of keywords, change the existingmulti-compile statement to one that also includes shadows.

#pragma multi_compile_fwdadd_fullshadows

This adds four additional keywords into the mix, to support different light types.

// -----------------------------------------// Snippet #1 platforms ffffffff:DIRECTIONAL DIRECTIONAL_COOKIE POINT POINT_COOKIE SHADOWS_CUBE SHADOWS_DEPTHSHADOWS_SCREEN SHADOWS_SOFT SPOT

13 keyword variants used in scene:

POINTDIRECTIONALSPOTPOINT_COOKIEDIRECTIONAL_COOKIESHADOWS_DEPTH SPOTDIRECTIONAL SHADOWS_SCREENDIRECTIONAL_COOKIE SHADOWS_SCREENPOINT SHADOWS_CUBEPOINT_COOKIE SHADOWS_CUBESHADOWS_DEPTH SHADOWS_SOFT SPOTPOINT SHADOWS_CUBE SHADOWS_SOFTPOINT_COOKIE SHADOWS_CUBE SHADOWS_SOFT

Two directional lights casting shadows.

Page 38: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

4 Spotlight Shadows

Now that we have dealt with directional lights, let's move on to spotlights. Disablethe directional lights and add some spotlights with shadows to the scene. Surprise!Thanks to Unity's macros, spotlight shadows already work.

Two spotlights with shadows.

When looking at the frame debugger, you'll see that Unity does less work forspotlight shadows. There is no separate depth pass, and no screen-space shadowpasses. Only the shadow maps are rendered.

Rendering with spotlight shadows.

The shadow maps works the same as for directional lights. They're depth maps,rendered from the light's point of view. However, there are big differences between adirectional light and a spotlight. The spotlight has an actual position, and its lightrays aren't parallel. So the spotlight's camera has a perspective view, and cannot bemore around arbitrarily. As a result, these lights cannot support shadow cascades.

Page 39: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

Shadow map, near plane set to 4.

Although the camera setup is different, the shadow casting code is identical for bothlight types. The normal bias is only supported for directional shadows, but it's simplyset to zero for other lights.

4.1 Sampling the Shadow Map

Because spotlights don't use screen-space shadows, the sampling code has to bedifferent. But Unity's macros hide this difference from us.

What do the macros look like, for spotlights?

The shadow coordinates are found by converting the vertex positions to world space,and from there to the light's shadow space.

#if defined (SHADOWS_DEPTH) && defined (SPOT) #define SHADOW_COORDS(idx1) \ unityShadowCoord4 _ShadowCoord : TEXCOORD##idx1; #define TRANSFER_SHADOW(a) a._ShadowCoord = \ mul(unity_WorldToShadow[0], mul(unity_ObjectToWorld, v.vertex)); #define SHADOW_ATTENUATION(a) UnitySampleShadowmap(a._ShadowCoord)#endif

Page 40: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

We found directional shadows by simply sampling the screen-space shadow map.Unity took care of shadow filtering when creating that map, so we didn't need toworry about that. However, spotlights don't use screen-space shadows. So if we wantto use soft shadows, we have to do the filtering in the fragment program.

Then SHADOW_ATTENUATION macro uses the UnitySampleShadowmap function to sample theshadow map. This function is defined in UnityShadowLibrary, which AutoLightincludes. When using hard shadows, the function samples the shadow map once.When using soft shadows, it samples the map four times and averages the result. Theresult isn't as good as the filtering used for screen-space shadows, but it is a lotfaster.

Hard vs. soft spotlight shadows.

What does UnitySampleShadowmap look like?

There are two versions of this function, one for spotlights and one for point lights.Here's the one for spotlights.

_ShadowOffsets contains the offsets used for the four samples that are averaged tocreate soft shadows. In the code below, I've only shown the first of those four samples.

#if !defined(SHADOWMAPSAMPLER_DEFINED) UNITY_DECLARE_SHADOWMAP(_ShadowMapTexture);#endif

// shadow sampling offsets#if defined (SHADOWS_SOFT) float4 _ShadowOffsets[4];#endif

inline fixed UnitySampleShadowmap (float4 shadowCoord) { // DX11 feature level 9.x shader compiler (d3dcompiler_47 at least) // has a bug where trying to do more than one shadowmap sample fails // compilation with "inconsistent sampler usage". Until that is fixed, // just never compile multi-tap shadow variant on d3d11_9x. #if defined (SHADOWS_SOFT) && !defined (SHADER_API_D3D11_9X) // 4-tap shadows #if defined (SHADOWS_NATIVE) #if defined (SHADER_API_D3D9) // HLSL for D3D9, when modifying the shadow UV coordinate,

Page 41: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

// really wants to do some funky swizzles, assuming that Z // coordinate is unused in texture sampling. So force it to // do projective texture reads here, with .w being one. float4 coord = shadowCoord / shadowCoord.w; half4 shadows; shadows.x = UNITY_SAMPLE_SHADOW_PROJ( _ShadowMapTexture, coord + _ShadowOffsets[0] ); … shadows =_LightShadowData.rrrr + shadows * (1-_LightShadowData.rrrr); #else // On other platforms, // no need to do projective texture reads. float3 coord = shadowCoord.xyz / shadowCoord.w; half4 shadows; shadows.x = UNITY_SAMPLE_SHADOW( _ShadowMapTexture, coord + _ShadowOffsets[0] ); … shadows = _LightShadowData.rrrr + shadows * (1-_LightShadowData.rrrr); #endif #else float3 coord = shadowCoord.xyz / shadowCoord.w; float4 shadowVals; shadowVals.x = SAMPLE_DEPTH_TEXTURE( _ShadowMapTexture, coord + _ShadowOffsets[0].xy ); … half4 shadows = (shadowVals < coord.zzzz) ? _LightShadowData.rrrr : 1.0f; #endif

// average-4 PCF half shadow = dot(shadows, 0.25f);

#else // 1-tap shadows #if defined (SHADOWS_NATIVE) half shadow = UNITY_SAMPLE_SHADOW_PROJ(_ShadowMapTexture, shadowCoord); shadow = _LightShadowData.r + shadow * (1 - _LightShadowData.r); #else half shadow = SAMPLE_DEPTH_TEXTURE_PROJ( _ShadowMapTexture, UNITY_PROJ_COORD(shadowCoord) ) < (shadowCoord.z / shadowCoord.w) ? _LightShadowData.r : 1.0; #endif #endif return shadow;}

Page 42: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

5 Point Light Shadows

Now try some point lights. When you enable shadows for a point light, you will begreeted with a compile error. Apparently, UnityDecodeCubeShadowDepth is undefined.This error happens because UnityShadowLibrary depends on UnityCG, but doesn'texplicitly include it. So we have to make sure that UnityCG is included first. We cando this by including UnityPBSLighting before including AutoLight in My Lighting.

#include "UnityPBSLighting.cginc"#include "AutoLight.cginc"//#include "UnityPBSLighting.cginc"

It compiles, but all objects in range of the lights end up black. There is somethingwrong with the shadow maps.

An incorrect shadow map.

When you inspect the shadow maps via the frame debugger, you will discover thatnot one, but six maps are rendered per light. This happens because point lightsshine in all directions. As as result, the shadow map has to be a cube map. Cubemaps are created by rendering the scene with the camera pointing in six differentdirections, once per face of the cube. So shadows for point lights are expensive.

5.1 Casting Shadows

Page 43: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

Unfortunately, Unit doesn't use depth cube maps. Apparently, not enough platformssupport them. So we cannot rely on the fragment's depth value in My Shadows.Instead, we'll have to output the fragment's distance as the result of the fragmentprogram.

When rendering point light shadow maps, Unity looks for a shadow caster variantwith the SHADOWS_CUBE keyword be defined. The SHADOWS_DEPTH keyword is used fordirectional and spotlight shadows. To support this, add a special multi-compiledirective for shadow casters to our pass.

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

CGPROGRAM

#pragma target 3.0

#pragma multi_compile_shadowcaster #pragma vertex MyShadowVertexProgram #pragma fragment MyShadowFragmentProgram #include "My Shadows.cginc" ENDCG }

This adds the variants that we need.

// Snippet #2 platforms ffffffff:SHADOWS_CUBE SHADOWS_DEPTH

2 keyword variants used in scene:

SHADOWS_DEPTHSHADOWS_CUBE

Because points lights require such a different approach, let's create a separate set ofprogram function for them.

Page 44: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

#if defined(SHADOWS_CUBE)

#else float4 MyShadowVertexProgram (VertexData v) : SV_POSITION { float4 position = UnityClipSpaceShadowCasterPos(v.position.xyz, v.normal); return UnityApplyLinearShadowBias(position); }

half4 MyShadowFragmentProgram () : SV_TARGET { return 0; }#endif

To figure out a fragment's distance from the light, we have to construct the world-space vector from the light to the fragment. We can do so by creating these vectorsper vertex, and interpolating them. This requires an additional interpolator.

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

Interpolators MyShadowVertexProgram (VertexData v) { Interpolators i; i.position = UnityObjectToClipPos(v.position); i.lightVec = mul(unity_ObjectToWorld, v.position).xyz - _LightPositionRange.xyz; return i; } float4 MyShadowFragmentProgram (Interpolators i) : SV_TARGET { return 0; }#else

In the fragment program, we take the length of the light vector and add the bias to it.Then we divide that by light's range to fit them in the 0–1 range. The_LightPositionRange.w variable contains the inverse of its range, so we have tomultiply by this value. The result is output as a floating-point value.

float4 MyShadowFragmentProgram (Interpolators i) : SV_TARGET { float depth = length(i.lightVec) + unity_LightShadowBias.x; depth *= _LightPositionRange.w; return UnityEncodeCubeShadowDepth(depth); }

Page 45: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

What does UnityEncodeCubeShadowDepth do?

Unity prefers to use a floating-point cube map. When this is possible, this function doesnothing. When this is not possible, Unity will encode the value so it is stored in the fourchannels of an 8-bit RGBA texture.

// Encoding/decoding [0..1) floats into 8 bit/channel RGBA.// Note that 1.0 will not be encoded properly.inline float4 EncodeFloatRGBA (float v) { float4 kEncodeMul = float4(1.0, 255.0, 65025.0, 16581375.0); float kEncodeBit = 1.0 / 255.0; float4 enc = kEncodeMul * v; enc = frac(enc); enc -= enc.yzww * kEncodeBit; return enc;}

float4 UnityEncodeCubeShadowDepth (float z) { #ifdef UNITY_USE_RGBA_FOR_POINT_SHADOWS return EncodeFloatRGBA(min(z, 0.999)); #else return z; #endif}

Correct shadow map.

5.2 Sampling the Shadow Maps

Page 46: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

Now that our shadow maps are correct, the point light shadows appear. Unity'smacros take care of the sampling of those maps.

Shadowed point lights.

What do that macros look like, for point lights?

In this case, the same light vector is constructed as when casting the shadows. Thisvector is then used to sample the shadow cube map. Note that the interpolator needsonly three components, instead of four. We're not passing along homogeneouscoordinates this time.

#if defined (SHADOWS_CUBE) #define SHADOW_COORDS(idx1) \ unityShadowCoord3 _ShadowCoord : TEXCOORD##idx1; #define TRANSFER_SHADOW(a) a._ShadowCoord = \ mul(unity_ObjectToWorld, v.vertex).xyz - _LightPositionRange.xyz; #define SHADOW_ATTENUATION(a) UnitySampleShadowmap(a._ShadowCoord)#endif

In this case, UnitySampleShadowmap samples a cube map instead of a 2D texture.

Page 47: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

samplerCUBE_float _ShadowMapTexture;inline float SampleCubeDistance (float3 vec) { return UnityDecodeCubeShadowDepth(texCUBE(_ShadowMapTexture, vec));}

inline half UnitySampleShadowmap (float3 vec) { float mydist = length(vec) * _LightPositionRange.w; mydist *= 0.97; // bias

#if defined (SHADOWS_SOFT) float z = 1.0/128.0; float4 shadowVals; shadowVals.x = SampleCubeDistance(vec + float3( z, z, z)); shadowVals.y = SampleCubeDistance(vec + float3(-z,-z, z)); shadowVals.z = SampleCubeDistance(vec + float3(-z, z,-z)); shadowVals.w = SampleCubeDistance(vec + float3( z,-z,-z)); half4 shadows = (shadowVals < mydist.xxxx) ? _LightShadowData.rrrr : 1.0f; return dot(shadows,0.25); #else float dist = SampleCubeDistance(vec); return dist < mydist ? _LightShadowData.r : 1.0; #endif}

Just like with spotlight shadows, the shadow map is sampled once for hard shadows,and four times for soft shadows. The big difference is that Unity doesn't supportfiltering for the shadow cube maps. As a result, the edges of the shadows are muchharsher. So point light shadows are both expensive and aliased.

Hard vs. soft point light shadows.

How can I make nice soft lantern shadows?

Use one or more shadowed spotlights. If there are no other shadow-casting objectsnearby, you can use an unshadowed light with a cookie. That works for both spotlightsand point lights, and is a lot cheaper to render.

The next tutorial is Reflections.

Page 48: Rendering 7 Shadows - catlikecoding.com · Shadows Investigate how Unity renders shadows. Cast directional shadows. ... If that ray hits something before it reaches the fragment,

Enjoying the tutorials? Are they useful? Want more?

Please support me on Patreon!

Or make a direct donation!

made by Jasper Flick