1
0

SunShadowOverlay.cs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. using System.Numerics;
  2. using Content.Shared.Light.Components;
  3. using Robust.Client.Graphics;
  4. using Robust.Shared.Enums;
  5. using Robust.Shared.Map;
  6. using Robust.Shared.Map.Components;
  7. using Robust.Shared.Physics;
  8. using Robust.Shared.Prototypes;
  9. namespace Content.Client.Light;
  10. public sealed class SunShadowOverlay : Overlay
  11. {
  12. public override OverlaySpace Space => OverlaySpace.BeforeLighting;
  13. [Dependency] private readonly IClyde _clyde = default!;
  14. [Dependency] private readonly IEntityManager _entManager = default!;
  15. [Dependency] private readonly IMapManager _mapManager = default!;
  16. [Dependency] private readonly IPrototypeManager _protoManager = default!;
  17. private readonly EntityLookupSystem _lookup;
  18. private readonly SharedTransformSystem _xformSys;
  19. private readonly HashSet<Entity<SunShadowCastComponent>> _shadows = new();
  20. private IRenderTexture? _blurTarget;
  21. private IRenderTexture? _target;
  22. public SunShadowOverlay()
  23. {
  24. IoCManager.InjectDependencies(this);
  25. _xformSys = _entManager.System<SharedTransformSystem>();
  26. _lookup = _entManager.System<EntityLookupSystem>();
  27. ZIndex = AfterLightTargetOverlay.ContentZIndex + 1;
  28. }
  29. private List<Entity<MapGridComponent>> _grids = new();
  30. protected override void Draw(in OverlayDrawArgs args)
  31. {
  32. var viewport = args.Viewport;
  33. var eye = viewport.Eye;
  34. if (eye == null)
  35. return;
  36. _grids.Clear();
  37. _mapManager.FindGridsIntersecting(args.MapId,
  38. args.WorldBounds.Enlarged(SunShadowComponent.MaxLength),
  39. ref _grids);
  40. var worldHandle = args.WorldHandle;
  41. var mapId = args.MapId;
  42. var worldBounds = args.WorldBounds;
  43. var targetSize = viewport.LightRenderTarget.Size;
  44. if (_target?.Size != targetSize)
  45. {
  46. _target = _clyde
  47. .CreateRenderTarget(targetSize,
  48. new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb),
  49. name: "sun-shadow-target");
  50. if (_blurTarget?.Size != targetSize)
  51. {
  52. _blurTarget = _clyde
  53. .CreateRenderTarget(targetSize, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "sun-shadow-blur");
  54. }
  55. }
  56. var lightScale = viewport.LightRenderTarget.Size / (Vector2)viewport.Size;
  57. var scale = viewport.RenderScale / (Vector2.One / lightScale);
  58. foreach (var grid in _grids)
  59. {
  60. if (!_entManager.TryGetComponent(grid.Owner, out SunShadowComponent? sun))
  61. {
  62. continue;
  63. }
  64. var direction = sun.Direction;
  65. var alpha = Math.Clamp(sun.Alpha, 0f, 1f);
  66. // Nowhere to cast to so ignore it.
  67. if (direction.Equals(Vector2.Zero) || alpha == 0f)
  68. continue;
  69. // Feature todo: dynamic shadows for mobs and trees. Also ideally remove the fake tree shadows.
  70. // TODO: Jittering still not quite perfect
  71. var expandedBounds = worldBounds.Enlarged(direction.Length() + 0.01f);
  72. _shadows.Clear();
  73. // Draw shadow polys to stencil
  74. args.WorldHandle.RenderInRenderTarget(_target,
  75. () =>
  76. {
  77. var invMatrix =
  78. _target.GetWorldToLocalMatrix(eye, scale);
  79. var indices = new Vector2[PhysicsConstants.MaxPolygonVertices * 2];
  80. // Go through shadows in range.
  81. // For each one we:
  82. // - Get the original vertices.
  83. // - Extrapolate these along the sun direction.
  84. // - Combine the above into 1 single polygon to draw.
  85. // Note that this is range-limited for accuracy; if you set it too high it will clip through walls or other undesirable entities.
  86. // This is probably not noticeable most of the time but if you want something "accurate" you'll want to code a solution.
  87. // Ideally the CPU would have its own shadow-map copy that we could just ray-cast each vert into though
  88. // You might need to batch verts or the likes as this could get expensive.
  89. _lookup.GetEntitiesIntersecting(mapId, expandedBounds, _shadows);
  90. foreach (var ent in _shadows)
  91. {
  92. var xform = _entManager.GetComponent<TransformComponent>(ent.Owner);
  93. var (worldPos, worldRot) = _xformSys.GetWorldPositionRotation(xform);
  94. // Need no rotation on matrix as sun shadow direction doesn't care.
  95. var worldMatrix = Matrix3x2.CreateTranslation(worldPos);
  96. var renderMatrix = Matrix3x2.Multiply(worldMatrix, invMatrix);
  97. var pointCount = ent.Comp.Points.Length;
  98. Array.Copy(ent.Comp.Points, indices, pointCount);
  99. for (var i = 0; i < pointCount; i++)
  100. {
  101. // Update point based on entity rotation.
  102. indices[i] = worldRot.RotateVec(indices[i]);
  103. // Add the offset point by the sun shadow direction.
  104. indices[pointCount + i] = indices[i] + direction;
  105. }
  106. var points = PhysicsHull.ComputePoints(indices, pointCount * 2);
  107. worldHandle.SetTransform(renderMatrix);
  108. worldHandle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, points, Color.White);
  109. }
  110. },
  111. Color.Transparent);
  112. // Slightly blur it just to avoid aliasing issues on the later viewport-wide blur.
  113. _clyde.BlurRenderTarget(viewport, _target, _blurTarget!, eye, 1f);
  114. // Draw stencil (see roofoverlay).
  115. args.WorldHandle.RenderInRenderTarget(viewport.LightRenderTarget,
  116. () =>
  117. {
  118. var invMatrix =
  119. viewport.LightRenderTarget.GetWorldToLocalMatrix(eye, scale);
  120. worldHandle.SetTransform(invMatrix);
  121. var maskShader = _protoManager.Index<ShaderPrototype>("Mix").Instance();
  122. worldHandle.UseShader(maskShader);
  123. worldHandle.DrawTextureRect(_target.Texture, worldBounds, Color.Black.WithAlpha(alpha));
  124. }, null);
  125. }
  126. }
  127. }