Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
ShadowMapRenderer.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 
4 using System;
5 using System.Collections.Generic;
6 
7 using SiliconStudio.Core;
8 using SiliconStudio.Core.Mathematics;
9 using SiliconStudio.Paradox.DataModel;
10 using SiliconStudio.Paradox.Effects.Modules.Processors;
11 using SiliconStudio.Paradox.Effects.Modules.Shadowmap;
12 using SiliconStudio.Paradox.Engine;
13 using SiliconStudio.Paradox.EntityModel;
14 using SiliconStudio.Paradox.Graphics;
15 
16 namespace SiliconStudio.Paradox.Effects.Modules.Renderers
17 {
18  /// <summary>
19  /// Handles shadow mapping.
20  /// </summary>
22  {
23  #region Constants
24 
25  //TODO: dependant of the PostEffectVsmBlur shader. We should have a way to set everything at the same time
26  private const float VsmBlurSize = 4.0f;
27 
28  #endregion
29 
30  #region Private static members
31 
32  /// <summary>
33  /// Base points for frustrum corners.
34  /// </summary>
35  private static readonly Vector3[] FrustrumBasePoints =
36  {
37  new Vector3(-1.0f,-1.0f,-1.0f), new Vector3(1.0f,-1.0f,-1.0f), new Vector3(-1.0f,1.0f,-1.0f), new Vector3(1.0f,1.0f,-1.0f),
38  new Vector3(-1.0f,-1.0f, 1.0f), new Vector3(1.0f,-1.0f, 1.0f), new Vector3(-1.0f,1.0f, 1.0f), new Vector3(1.0f,1.0f, 1.0f),
39  };
40 
41  /// <summary>
42  /// The various UP vectors to try.
43  /// </summary>
44  private static readonly Vector3[] VectorUps = new[] { Vector3.UnitZ, Vector3.UnitY, Vector3.UnitX };
45 
46  internal static readonly ParameterKey<ShadowMapReceiverInfo[]> Receivers = ParameterKeys.New(new ShadowMapReceiverInfo[1]);
47  internal static readonly ParameterKey<ShadowMapReceiverVsmInfo[]> ReceiversVsm = ParameterKeys.New(new ShadowMapReceiverVsmInfo[1]);
48  internal static readonly ParameterKey<ShadowMapCascadeReceiverInfo[]> LevelReceivers = ParameterKeys.New(new ShadowMapCascadeReceiverInfo[1]);
49  internal static readonly ParameterKey<int> ShadowMapLightCount = ParameterKeys.New(0);
50 
51  #endregion
52 
53  #region Private members
54 
55  // Storage for temporary variables
56  private Vector3[] points = new Vector3[8];
57  private Vector3[] directions = new Vector3[4];
58 
59  private Effect vsmHorizontalBlur;
60 
61  private Effect vsmVerticalBlur;
62 
63  // rectangles to blur for each shadow map
64  private HashSet<ShadowMapTexture> shadowMapTexturesToBlur = new HashSet<ShadowMapTexture>();
65 
66  #endregion
67 
68  #region Constructor
69 
70  public ShadowMapRenderer(IServiceRegistry services, RenderPipeline recursivePipeline) : base(services, recursivePipeline)
71  {
72  // Build blur effects for VSM
73  vsmHorizontalBlur = EffectSystem.LoadEffect("HorizontalVsmBlur");
74  vsmVerticalBlur = EffectSystem.LoadEffect("VerticalVsmBlur");
75  }
76 
77  #endregion
78 
79  #region Methods
80 
81  protected override void OnRendering(RenderContext context)
82  {
83  // get the lightprocessor
84  var entitySystem = Services.GetServiceAs<EntitySystem>();
85  var lightProcessor = entitySystem.GetProcessor<LightShadowProcessor>();
86  if (lightProcessor == null)
87  return;
88 
89  var graphicsDevice = context.GraphicsDevice;
90 
91  // Get View and Projection matrices
92  Matrix view, projection;
93  Pass.Parameters.Get(TransformationKeys.View, out view);
94  Pass.Parameters.Get(TransformationKeys.Projection, out projection);
95 
96  // Clear shadow map textures
97  foreach (var shadowMapTexture in lightProcessor.ActiveShadowMapTextures)
98  {
99  // Clear and set render target
100  graphicsDevice.Clear(shadowMapTexture.ShadowMapDepthBuffer, DepthStencilClearOptions.DepthBuffer);
101 
102  if (shadowMapTexture.IsVarianceShadowMap)
103  graphicsDevice.Clear(shadowMapTexture.ShadowMapRenderTarget, Color4.White);
104 
105  // Reset shadow map allocator
106  shadowMapTexture.GuillotinePacker.Clear(shadowMapTexture.ShadowMapDepthTexture.Width, shadowMapTexture.ShadowMapDepthTexture.Height);
107  }
108 
109  ShadowMapFilterType filterBackup;
110  var hasFilter = graphicsDevice.Parameters.ContainsKey(ShadowMapParameters.FilterType);
111  filterBackup = graphicsDevice.Parameters.Get(ShadowMapParameters.FilterType);
112 
113  if (lightProcessor.ActiveShadowMaps.Count > 0)
114  {
115  // Compute frustum-dependent variables (common for all shadow maps)
116  Matrix inverseView, inverseProjection;
117  Matrix.Invert(ref projection, out inverseProjection);
118  Matrix.Invert(ref view, out inverseView);
119 
120  // Transform Frustum corners in View Space (8 points) - algorithm is valid only if the view matrix does not do any kind of scale/shear transformation
121  for (int i = 0; i < 8; ++i)
122  Vector3.TransformCoordinate(ref FrustrumBasePoints[i], ref inverseProjection, out points[i]);
123 
124  // Compute frustum edge directions
125  for (int i = 0; i < 4; i++)
126  directions[i] = Vector3.Normalize(points[i + 4] - points[i]);
127 
128  // Prepare and render shadow maps
129  foreach (var shadowMap in lightProcessor.ActiveShadowMaps)
130  {
131  Vector3.Normalize(ref shadowMap.LightDirection, out shadowMap.LightDirectionNormalized);
132 
133  // Compute shadow map infos
134  ComputeShadowMap(shadowMap, ref inverseView);
135 
136  if (shadowMap.Filter == ShadowMapFilterType.Variance)
137  graphicsDevice.SetRenderTarget(shadowMap.Texture.ShadowMapDepthBuffer, shadowMap.Texture.ShadowMapRenderTarget);
138  else
139  graphicsDevice.SetRenderTarget(shadowMap.Texture.ShadowMapDepthBuffer);
140 
141  // set layers
142  context.Parameters.Set(RenderingParameters.ActiveRenderLayer, shadowMap.Layers);
143 
144  // Render each cascade
145  for (int i = 0; i < shadowMap.CascadeCount; ++i)
146  {
147  var cascade = shadowMap.Cascades[i];
148 
149  // Override with current shadow map parameters
150  graphicsDevice.Parameters.Set(ShadowMapKeys.DistanceMax, shadowMap.LightType == LightType.Directional ? shadowMap.ShadowFarDistance : shadowMap.ShadowFarDistance - shadowMap.ShadowNearDistance);
151  graphicsDevice.Parameters.Set(LightKeys.LightDirection, shadowMap.LightDirectionNormalized);
152  graphicsDevice.Parameters.Set(ShadowMapCasterBaseKeys.shadowLightOffset, cascade.ReceiverInfo.Offset);
153 
154  // We computed ViewProjection, so let's use View = Identity & Projection = ViewProjection
155  // (ideally we should override ViewProjection dynamic)
156  graphicsDevice.Parameters.Set(TransformationKeys.View, Matrix.Identity);
157  graphicsDevice.Parameters.Set(TransformationKeys.Projection, cascade.ViewProjCaster);
158 
159  // Prepare viewport
160  var cascadeTextureCoord = cascade.CascadeTextureCoords;
161  var viewPortCoord = new Vector4(
162  cascadeTextureCoord.X * shadowMap.Texture.ShadowMapDepthTexture.Width,
163  cascadeTextureCoord.Y * shadowMap.Texture.ShadowMapDepthTexture.Height,
164  cascadeTextureCoord.Z * shadowMap.Texture.ShadowMapDepthTexture.Width,
165  cascadeTextureCoord.W * shadowMap.Texture.ShadowMapDepthTexture.Height);
166 
167  // Set viewport
168  graphicsDevice.SetViewport(new Viewport((int)viewPortCoord.X, (int)viewPortCoord.Y, (int)(viewPortCoord.Z - viewPortCoord.X), (int)(viewPortCoord.W - viewPortCoord.Y)));
169 
170  if (shadowMap.Filter == ShadowMapFilterType.Variance)
171  shadowMapTexturesToBlur.Add(shadowMap.Texture);
172 
173  graphicsDevice.Parameters.Set(ShadowMapParameters.FilterType, shadowMap.Filter);
174  base.OnRendering(context);
175  }
176 
177  // reset layers
178  context.Parameters.Reset(RenderingParameters.ActiveRenderLayer);
179  }
180 
181  // Reset parameters
182  graphicsDevice.Parameters.Reset(ShadowMapKeys.DistanceMax);
183  graphicsDevice.Parameters.Reset(LightKeys.LightDirection);
184  graphicsDevice.Parameters.Reset(ShadowMapCasterBaseKeys.shadowLightOffset);
185  graphicsDevice.Parameters.Reset(TransformationKeys.View);
186  graphicsDevice.Parameters.Reset(TransformationKeys.Projection);
187  if (hasFilter)
188  graphicsDevice.Parameters.Set(ShadowMapParameters.FilterType, filterBackup);
189  else
190  graphicsDevice.Parameters.Reset(ShadowMapParameters.FilterType);
191  }
192 
193  foreach (var shadowMap in shadowMapTexturesToBlur)
194  {
195  graphicsDevice.SetDepthStencilState(graphicsDevice.DepthStencilStates.None);
196  graphicsDevice.SetRasterizerState(graphicsDevice.RasterizerStates.CullNone);
197 
198  // TODO: use next post effect instead
199  graphicsDevice.SetRenderTarget(shadowMap.ShadowMapDepthBuffer, shadowMap.IntermediateBlurRenderTarget);
200  vsmHorizontalBlur.Parameters.Set(TexturingKeys.Texture0, shadowMap.ShadowMapTargetTexture);
201  vsmHorizontalBlur.Parameters.Set(TexturingKeys.Sampler, GraphicsDevice.SamplerStates.LinearClamp);
202  graphicsDevice.DrawQuad(vsmHorizontalBlur);
203 
204  // TODO: use next post effect instead
205  graphicsDevice.SetRenderTarget(shadowMap.ShadowMapDepthBuffer, shadowMap.ShadowMapRenderTarget);
206  vsmVerticalBlur.Parameters.Set(TexturingKeys.Texture0, shadowMap.IntermediateBlurTexture);
207  vsmVerticalBlur.Parameters.Set(TexturingKeys.Sampler, GraphicsDevice.SamplerStates.LinearClamp);
208  graphicsDevice.DrawQuad(vsmVerticalBlur);
209  }
210 
211  shadowMapTexturesToBlur.Clear();
212  }
213 
214  private void ComputeShadowMap(ShadowMap shadowMap, ref Matrix inverseView)
215  {
216  float shadowDistribute = 1.0f / shadowMap.CascadeCount;
217  float znear = shadowMap.ShadowNearDistance;
218  float zfar = shadowMap.ShadowFarDistance;
219 
220  var boudingBoxVectors = new Vector3[8];
221  var direction = Vector3.Normalize(shadowMap.LightDirectionNormalized);
222 
223  // Fake value
224  // It will be setup by next loop
225  Vector3 side = Vector3.UnitX;
226  Vector3 up = Vector3.UnitX;
227 
228  // Select best Up vector
229  // TODO: User preference?
230  foreach (var vectorUp in VectorUps)
231  {
232  if (Vector3.Dot(direction, vectorUp) < (1.0 - 0.0001))
233  {
234  side = Vector3.Normalize(Vector3.Cross(vectorUp, direction));
235  up = Vector3.Normalize(Vector3.Cross(direction, side));
236  break;
237  }
238  }
239 
240  // Prepare cascade list (allocate it if not done yet)
241  var cascades = shadowMap.Cascades;
242  if (cascades == null)
243  cascades = shadowMap.Cascades = new ShadowMapCascadeInfo[shadowMap.CascadeCount];
244 
245  for (int cascadeLevel = 0; cascadeLevel < shadowMap.CascadeCount; ++cascadeLevel)
246  {
247  // Compute caster view and projection matrices
248  var shadowMapView = Matrix.Zero;
249  var shadowMapProjection = Matrix.Zero;
250  if (shadowMap.LightType == LightType.Directional)
251  {
252  // Compute cascade split (between znear and zfar)
253  float k0 = (float)(cascadeLevel + 0) / shadowMap.CascadeCount;
254  float k1 = (float)(cascadeLevel + 1) / shadowMap.CascadeCount;
255  float min = (float)(znear * Math.Pow(zfar / znear, k0)) * (1.0f - shadowDistribute) + (znear + (zfar - znear) * k0) * shadowDistribute;
256  float max = (float)(znear * Math.Pow(zfar / znear, k1)) * (1.0f - shadowDistribute) + (znear + (zfar - znear) * k1) * shadowDistribute;
257 
258  // Compute frustum corners
259  for (int j = 0; j < 4; j++)
260  {
261  boudingBoxVectors[j * 2 + 0] = points[j] + directions[j] * min;
262  boudingBoxVectors[j * 2 + 1] = points[j] + directions[j] * max;
263  }
264  var boundingBox = BoundingBox.FromPoints(boudingBoxVectors);
265 
266  // Compute bounding box center & radius
267  // Note: boundingBox is computed in view space so the computation of the radius is only correct when the view matrix does not do any kind of scale/shear transformation
268  var radius = (boundingBox.Maximum - boundingBox.Minimum).Length() * 0.5f;
269  var target = Vector3.TransformCoordinate(boundingBox.Center, inverseView);
270 
271  // Snap camera to texel units (so that shadow doesn't jitter when light doesn't change direction but camera is moving)
272  var shadowMapHalfSize = shadowMap.ShadowMapSize * 0.5f;
273  float x = (float)Math.Ceiling(Vector3.Dot(target, up) * shadowMapHalfSize / radius) * radius / shadowMapHalfSize;
274  float y = (float)Math.Ceiling(Vector3.Dot(target, side) * shadowMapHalfSize / radius) * radius / shadowMapHalfSize;
275  float z = Vector3.Dot(target, direction);
276  //target = up * x + side * y + direction * R32G32B32_Float.Dot(target, direction);
277  target = up * x + side * y + direction * z;
278 
279  // Compute caster view and projection matrices
280  shadowMapView = Matrix.LookAtRH(target - direction * zfar * 0.5f, target + direction * zfar * 0.5f, up); // View;
281  shadowMapProjection = Matrix.OrthoOffCenterRH(-radius, radius, -radius, radius, znear, zfar); // Projection
282  // near and far are not correctly set but the shader will rewrite the z value
283  // on the hand, the offset is correct
284 
285  // Compute offset
286  Matrix shadowVInverse;
287  Matrix.Invert(ref shadowMapView, out shadowVInverse);
288  cascades[cascadeLevel].ReceiverInfo.Offset = new Vector3(shadowVInverse.M41, shadowVInverse.M42, shadowVInverse.M43);
289  }
290  else if (shadowMap.LightType == LightType.Spot)
291  {
292  shadowMapView = Matrix.LookAtRH(shadowMap.LightPosition, shadowMap.LightPosition + shadowMap.LightDirection, up);
293  shadowMapProjection = Matrix.PerspectiveFovRH(shadowMap.Fov, 1, znear, zfar);
294 
295  // Set offset
296  cascades[cascadeLevel].ReceiverInfo.Offset = shadowMap.LightPosition + znear * shadowMap.LightDirectionNormalized;
297  }
298 
299  // Allocate shadow map area
300  var shadowMapRectangle = new Rectangle();
301  if (!shadowMap.Texture.GuillotinePacker.Insert(shadowMap.ShadowMapSize, shadowMap.ShadowMapSize, ref shadowMapRectangle))
302  throw new InvalidOperationException("Not enough space to allocate all shadow maps.");
303 
304  var cascadeTextureCoords = new Vector4(
305  (float)shadowMapRectangle.Left / (float)shadowMap.Texture.ShadowMapDepthTexture.Width,
306  (float)shadowMapRectangle.Top / (float)shadowMap.Texture.ShadowMapDepthTexture.Height,
307  (float)shadowMapRectangle.Right / (float)shadowMap.Texture.ShadowMapDepthTexture.Width,
308  (float)shadowMapRectangle.Bottom / (float)shadowMap.Texture.ShadowMapDepthTexture.Height);
309 
310  // Copy texture coords without border
311  cascades[cascadeLevel].CascadeTextureCoords = cascadeTextureCoords;
312 
313  // Add border (avoid using edges due to bilinear filtering and blur)
314  var boderSizeU = VsmBlurSize / shadowMap.Texture.ShadowMapDepthTexture.Width;
315  var boderSizeV = VsmBlurSize / shadowMap.Texture.ShadowMapDepthTexture.Height;
316  cascadeTextureCoords.X += boderSizeU;
317  cascadeTextureCoords.Y += boderSizeV;
318  cascadeTextureCoords.Z -= boderSizeU;
319  cascadeTextureCoords.W -= boderSizeV;
320 
321  float leftX = (float)shadowMap.ShadowMapSize / (float)shadowMap.Texture.ShadowMapDepthTexture.Width * 0.5f;
322  float leftY = (float)shadowMap.ShadowMapSize / (float)shadowMap.Texture.ShadowMapDepthTexture.Height * 0.5f;
323  float centerX = 0.5f * (cascadeTextureCoords.X + cascadeTextureCoords.Z);
324  float centerY = 0.5f * (cascadeTextureCoords.Y + cascadeTextureCoords.W);
325 
326  // Compute caster view proj matrix
327  Matrix.Multiply(ref shadowMapView, ref shadowMapProjection, out cascades[cascadeLevel].ViewProjCaster);
328 
329  // Compute receiver view proj matrix
330  // TODO: Optimize adjustment matrix computation
331  Matrix adjustmentMatrix = Matrix.Scaling(leftX, -leftY, 0.5f) * Matrix.Translation(centerX, centerY, 0.5f);
332  Matrix.Multiply(ref cascades[cascadeLevel].ViewProjCaster, ref adjustmentMatrix, out cascades[cascadeLevel].ReceiverInfo.ViewProjReceiver);
333 
334  // Copy texture coords with border
335  cascades[cascadeLevel].ReceiverInfo.CascadeTextureCoordsBorder = cascadeTextureCoords;
336  }
337  }
338 
339  #endregion
340  }
341 }
Key of an effect parameter.
Definition: ParameterKey.cs:15
static void Dot(ref Vector3 left, ref Vector3 right, out float result)
Calculates the dot product of two vectors.
Definition: Vector3.cs:585
int ShadowMapSize
Gets or sets the size of the shadow map (default: 1024)
Definition: ShadowMap.cs:36
ShadowMapRenderer(IServiceRegistry services, RenderPipeline recursivePipeline)
_In_ size_t _In_ DXGI_FORMAT _In_ size_t _In_ float size_t y
Definition: DirectXTexP.h:191
Represents a three dimensional mathematical vector.
Definition: Vector3.cs:42
A service registry is a IServiceProvider that provides methods to register and unregister services...
static void Translation(ref Vector3 value, out Matrix result)
Creates a translation matrix using the specified offsets.
Definition: Matrix.cs:2672
Represents a ShadowMap.
Definition: ShadowMap.cs:11
This Renderer recursively render another RenderPass.
Represents a four dimensional mathematical vector.
Definition: Vector4.cs:42
int Height
The height of this texture view.
Definition: Texture.cs:53
Thread-local storage context used during rendering.
Manage a collection of entities.
Definition: EntitySystem.cs:22
Defines the window dimensions of a render-target surface onto which a 3D volume projects.
Definition: Viewport.cs:14
int Width
The width of this texture view.
Definition: Texture.cs:48
System.Windows.Shapes.Rectangle Rectangle
Definition: ColorPicker.cs:16
SiliconStudio.Core.Mathematics.Vector3 Vector3
void Normalize()
Converts the vector into a unit vector.
Definition: Vector3.cs:216
static void TransformCoordinate(ref Vector3 coordinate, ref Matrix transform, out Vector3 result)
Performs a coordinate transformation using the given SiliconStudio.Core.Mathematics.Matrix.
Definition: Vector3.cs:1188
Defines an entry point for mesh instantiation and recursive rendering.
_In_ size_t _In_ DXGI_FORMAT _In_ size_t _In_ float size_t size_t z
Definition: DirectXTexP.h:191
Represents a 4x4 mathematical matrix.
Definition: Matrix.cs:47