Add wall-based ambient occlusion (#38276)
* Add wall ambient occlusion * wawawewa * Work * cvars * Comment to make slart happy
This commit is contained in:
137
Content.Client/Light/AmbientOcclusionOverlay.cs
Normal file
137
Content.Client/Light/AmbientOcclusionOverlay.cs
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Content.Shared.CCVar;
|
||||||
|
using Content.Shared.Maps;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Shared.Configuration;
|
||||||
|
using Robust.Shared.Enums;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Client.Light;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies ambient-occlusion to the viewport.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class AmbientOcclusionOverlay : Overlay
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IClyde _clyde = default!;
|
||||||
|
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
|
||||||
|
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||||
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||||
|
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
|
||||||
|
|
||||||
|
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowEntities;
|
||||||
|
|
||||||
|
private IRenderTexture? _aoTarget;
|
||||||
|
|
||||||
|
// Couldn't figure out a way to avoid this so if you can then please do.
|
||||||
|
private IRenderTexture? _aoStencilTarget;
|
||||||
|
|
||||||
|
public AmbientOcclusionOverlay()
|
||||||
|
{
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
ZIndex = AfterLightTargetOverlay.ContentZIndex + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Draw(in OverlayDrawArgs args)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* tl;dr
|
||||||
|
* - we draw a black square on each "ambient occlusion" entity.
|
||||||
|
* - we blur this.
|
||||||
|
* - We apply it to the viewport.
|
||||||
|
*
|
||||||
|
* We do this while ignoring lighting because it will wash out the actual effect.
|
||||||
|
* In 3D ambient occlusion is more complicated due top having to calculate normals but in 2D
|
||||||
|
* we don't have a concept of depth / corners necessarily.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var viewport = args.Viewport;
|
||||||
|
var mapId = args.MapId;
|
||||||
|
var worldBounds = args.WorldBounds;
|
||||||
|
var worldHandle = args.WorldHandle;
|
||||||
|
var color = Color.FromHex(_cfgManager.GetCVar(CCVars.AmbientOcclusionColor));
|
||||||
|
var distance = _cfgManager.GetCVar(CCVars.AmbientOcclusionDistance);
|
||||||
|
//var color = Color.Red;
|
||||||
|
var target = viewport.RenderTarget;
|
||||||
|
var lightScale = target.Size / (Vector2) viewport.Size;
|
||||||
|
var scale = viewport.RenderScale / (Vector2.One / lightScale);
|
||||||
|
var maps = _entManager.System<SharedMapSystem>();
|
||||||
|
var lookups = _entManager.System<EntityLookupSystem>();
|
||||||
|
var query = _entManager.System<OccluderSystem>();
|
||||||
|
var xformSystem = _entManager.System<SharedTransformSystem>();
|
||||||
|
var invMatrix = args.Viewport.GetWorldToLocalMatrix();
|
||||||
|
|
||||||
|
if (_aoTarget?.Texture.Size != target.Size)
|
||||||
|
{
|
||||||
|
_aoTarget?.Dispose();
|
||||||
|
_aoTarget = _clyde.CreateRenderTarget(target.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "ambient-occlusion-target");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_aoStencilTarget?.Texture.Size != target.Size)
|
||||||
|
{
|
||||||
|
_aoStencilTarget?.Dispose();
|
||||||
|
_aoStencilTarget = _clyde.CreateRenderTarget(target.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "ambient-occlusion-stencil-target");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the texture data to the texture.
|
||||||
|
args.WorldHandle.RenderInRenderTarget(_aoTarget,
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
worldHandle.UseShader(_proto.Index<ShaderPrototype>("unshaded").Instance());
|
||||||
|
var invMatrix = _aoTarget.GetWorldToLocalMatrix(viewport.Eye!, scale);
|
||||||
|
|
||||||
|
foreach (var entry in query.QueryAabb(mapId, worldBounds))
|
||||||
|
{
|
||||||
|
DebugTools.Assert(entry.Component.Enabled);
|
||||||
|
var matrix = xformSystem.GetWorldMatrix(entry.Transform);
|
||||||
|
var localMatrix = Matrix3x2.Multiply(matrix, invMatrix);
|
||||||
|
|
||||||
|
worldHandle.SetTransform(localMatrix);
|
||||||
|
// 4 pixels
|
||||||
|
worldHandle.DrawRect(Box2.UnitCentered.Enlarged(distance / EyeManager.PixelsPerMeter), Color.White);
|
||||||
|
}
|
||||||
|
}, Color.Transparent);
|
||||||
|
|
||||||
|
_clyde.BlurRenderTarget(viewport, _aoTarget, _aoTarget, viewport.Eye!, 14f);
|
||||||
|
|
||||||
|
// Need to do stencilling after blur as it will nuke it.
|
||||||
|
// Draw stencil for the grid so we don't draw in space.
|
||||||
|
args.WorldHandle.RenderInRenderTarget(_aoStencilTarget,
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
// Don't want lighting affecting it.
|
||||||
|
worldHandle.UseShader(_proto.Index<ShaderPrototype>("unshaded").Instance());
|
||||||
|
|
||||||
|
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds))
|
||||||
|
{
|
||||||
|
var transform = xformSystem.GetWorldMatrix(grid.Owner);
|
||||||
|
var worldToTextureMatrix = Matrix3x2.Multiply(transform, invMatrix);
|
||||||
|
var tiles = maps.GetTilesEnumerator(grid.Owner, grid, worldBounds);
|
||||||
|
worldHandle.SetTransform(worldToTextureMatrix);
|
||||||
|
while (tiles.MoveNext(out var tileRef))
|
||||||
|
{
|
||||||
|
if (tileRef.IsSpace(_tileDefManager))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var bounds = lookups.GetLocalBounds(tileRef, grid.TileSize);
|
||||||
|
worldHandle.DrawRect(bounds, Color.White);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}, Color.Transparent);
|
||||||
|
|
||||||
|
// Draw the stencil texture to depth buffer.
|
||||||
|
worldHandle.UseShader(_proto.Index<ShaderPrototype>("StencilMask").Instance());
|
||||||
|
worldHandle.DrawTextureRect(_aoStencilTarget!.Texture, worldBounds);
|
||||||
|
|
||||||
|
// Draw the Blurred AO texture finally.
|
||||||
|
worldHandle.UseShader(_proto.Index<ShaderPrototype>("StencilEqualDraw").Instance());
|
||||||
|
worldHandle.DrawTextureRect(_aoTarget!.Texture, worldBounds, color);
|
||||||
|
|
||||||
|
args.WorldHandle.SetTransform(Matrix3x2.Identity);
|
||||||
|
args.WorldHandle.UseShader(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,51 @@
|
|||||||
|
using Content.Shared.CCVar;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Shared.Configuration;
|
||||||
|
|
||||||
namespace Content.Client.Light.EntitySystems;
|
namespace Content.Client.Light.EntitySystems;
|
||||||
|
|
||||||
public sealed class PlanetLightSystem : EntitySystem
|
public sealed class PlanetLightSystem : EntitySystem
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
|
||||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables / disables the ambient occlusion overlay.
|
||||||
|
/// </summary>
|
||||||
|
public bool AmbientOcclusion
|
||||||
|
{
|
||||||
|
get => _ambientOcclusion;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_ambientOcclusion == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_ambientOcclusion = value;
|
||||||
|
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
_overlayMan.AddOverlay(new AmbientOcclusionOverlay());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_overlayMan.RemoveOverlay<AmbientOcclusionOverlay>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _ambientOcclusion;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<GetClearColorEvent>(OnClearColor);
|
SubscribeLocalEvent<GetClearColorEvent>(OnClearColor);
|
||||||
|
|
||||||
|
_cfgManager.OnValueChanged(CCVars.AmbientOcclusion, val =>
|
||||||
|
{
|
||||||
|
AmbientOcclusion = val;
|
||||||
|
}, true);
|
||||||
|
|
||||||
_overlayMan.AddOverlay(new BeforeLightTargetOverlay());
|
_overlayMan.AddOverlay(new BeforeLightTargetOverlay());
|
||||||
_overlayMan.AddOverlay(new RoofOverlay(EntityManager));
|
_overlayMan.AddOverlay(new RoofOverlay(EntityManager));
|
||||||
_overlayMan.AddOverlay(new TileEmissionOverlay(EntityManager));
|
_overlayMan.AddOverlay(new TileEmissionOverlay(EntityManager));
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ public sealed class RoofOverlay : Overlay
|
|||||||
worldHandle.RenderInRenderTarget(target,
|
worldHandle.RenderInRenderTarget(target,
|
||||||
() =>
|
() =>
|
||||||
{
|
{
|
||||||
|
var invMatrix = target.GetWorldToLocalMatrix(eye, scale);
|
||||||
|
|
||||||
for (var i = 0; i < _grids.Count; i++)
|
for (var i = 0; i < _grids.Count; i++)
|
||||||
{
|
{
|
||||||
var grid = _grids[i];
|
var grid = _grids[i];
|
||||||
@@ -69,8 +71,6 @@ public sealed class RoofOverlay : Overlay
|
|||||||
if (!_entManager.TryGetComponent(grid.Owner, out ImplicitRoofComponent? roof))
|
if (!_entManager.TryGetComponent(grid.Owner, out ImplicitRoofComponent? roof))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var invMatrix = target.GetWorldToLocalMatrix(eye, scale);
|
|
||||||
|
|
||||||
var gridMatrix = _xformSystem.GetWorldMatrix(grid.Owner);
|
var gridMatrix = _xformSystem.GetWorldMatrix(grid.Owner);
|
||||||
var matty = Matrix3x2.Multiply(gridMatrix, invMatrix);
|
var matty = Matrix3x2.Multiply(gridMatrix, invMatrix);
|
||||||
|
|
||||||
@@ -94,13 +94,13 @@ public sealed class RoofOverlay : Overlay
|
|||||||
worldHandle.RenderInRenderTarget(target,
|
worldHandle.RenderInRenderTarget(target,
|
||||||
() =>
|
() =>
|
||||||
{
|
{
|
||||||
|
var invMatrix = target.GetWorldToLocalMatrix(eye, scale);
|
||||||
|
|
||||||
foreach (var grid in _grids)
|
foreach (var grid in _grids)
|
||||||
{
|
{
|
||||||
if (!_entManager.TryGetComponent(grid.Owner, out RoofComponent? roof))
|
if (!_entManager.TryGetComponent(grid.Owner, out RoofComponent? roof))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var invMatrix = target.GetWorldToLocalMatrix(eye, scale);
|
|
||||||
|
|
||||||
var gridMatrix = _xformSystem.GetWorldMatrix(grid.Owner);
|
var gridMatrix = _xformSystem.GetWorldMatrix(grid.Owner);
|
||||||
var matty = Matrix3x2.Multiply(gridMatrix, invMatrix);
|
var matty = Matrix3x2.Multiply(gridMatrix, invMatrix);
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
<ui:OptionDropDown Name="DropDownLightingQuality" Title="{Loc 'ui-options-lighting-label'}" />
|
<ui:OptionDropDown Name="DropDownLightingQuality" Title="{Loc 'ui-options-lighting-label'}" />
|
||||||
<CheckBox Name="ViewportLowResCheckBox" Text="{Loc 'ui-options-vp-low-res'}" />
|
<CheckBox Name="ViewportLowResCheckBox" Text="{Loc 'ui-options-vp-low-res'}" />
|
||||||
<CheckBox Name="ParallaxLowQualityCheckBox" Text="{Loc 'ui-options-parallax-low-quality'}" />
|
<CheckBox Name="ParallaxLowQualityCheckBox" Text="{Loc 'ui-options-parallax-low-quality'}" />
|
||||||
|
<CheckBox Name="AmbientOcclusionCheckBox" Text="{Loc 'ui-options-ambient-occlusion'}" />
|
||||||
|
|
||||||
<!-- Interface -->
|
<!-- Interface -->
|
||||||
<Label Text="{Loc 'ui-options-interface-label'}" StyleClasses="LabelKeyText"/>
|
<Label Text="{Loc 'ui-options-interface-label'}" StyleClasses="LabelKeyText"/>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ public sealed partial class GraphicsTab : Control
|
|||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
|
|
||||||
Control.AddOptionCheckBox(CVars.DisplayVSync, VSyncCheckBox);
|
Control.AddOptionCheckBox(CVars.DisplayVSync, VSyncCheckBox);
|
||||||
|
Control.AddOptionCheckBox(CCVars.AmbientOcclusion, AmbientOcclusionCheckBox);
|
||||||
Control.AddOption(new OptionFullscreen(Control, _cfg, FullscreenCheckBox));
|
Control.AddOption(new OptionFullscreen(Control, _cfg, FullscreenCheckBox));
|
||||||
Control.AddOption(new OptionLightingQuality(Control, _cfg, DropDownLightingQuality));
|
Control.AddOption(new OptionLightingQuality(Control, _cfg, DropDownLightingQuality));
|
||||||
|
|
||||||
|
|||||||
21
Content.Shared/CCVar/CCVars.Lighting.cs
Normal file
21
Content.Shared/CCVar/CCVars.Lighting.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Robust.Shared.Configuration;
|
||||||
|
|
||||||
|
namespace Content.Shared.CCVar;
|
||||||
|
|
||||||
|
public sealed partial class CCVars
|
||||||
|
{
|
||||||
|
public static readonly CVarDef<bool> AmbientOcclusion =
|
||||||
|
CVarDef.Create("light.ambient_occlusion", true, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Distance in world-pixels of ambient occlusion.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly CVarDef<string> AmbientOcclusionColor =
|
||||||
|
CVarDef.Create("light.ambient_occlusion_color", "#04080FAA", CVar.CLIENTONLY);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Distance in world-pixels of ambient occlusion.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly CVarDef<float> AmbientOcclusionDistance =
|
||||||
|
CVarDef.Create("light.ambient_occlusion_distance", 4f, CVar.CLIENTONLY);
|
||||||
|
}
|
||||||
@@ -98,6 +98,7 @@ ui-options-vp-vertical-fit-tooltip = When enabled, the main viewport will ignore
|
|||||||
will cause the viewport to be cut off on the horizontal axis.
|
will cause the viewport to be cut off on the horizontal axis.
|
||||||
ui-options-vp-low-res = Low-resolution viewport
|
ui-options-vp-low-res = Low-resolution viewport
|
||||||
ui-options-parallax-low-quality = Low-quality Parallax (background)
|
ui-options-parallax-low-quality = Low-quality Parallax (background)
|
||||||
|
ui-options-ambient-occlusion = Show Ambient Occlusion
|
||||||
ui-options-fps-counter = Show FPS counter
|
ui-options-fps-counter = Show FPS counter
|
||||||
ui-options-vp-width = Viewport width:
|
ui-options-vp-width = Viewport width:
|
||||||
ui-options-hud-layout = HUD layout:
|
ui-options-hud-layout = HUD layout:
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
op: Replace
|
op: Replace
|
||||||
func: Always
|
func: Always
|
||||||
|
|
||||||
|
# Draws to the stencil buffer if the alpha is not set to 0.
|
||||||
- type: shader
|
- type: shader
|
||||||
id: StencilMask
|
id: StencilMask
|
||||||
kind: source
|
kind: source
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
op: Replace
|
op: Replace
|
||||||
func: Always
|
func: Always
|
||||||
|
|
||||||
|
# Draws if the texture in the stencil buffer is not equal to white.
|
||||||
- type: shader
|
- type: shader
|
||||||
id: StencilDraw
|
id: StencilDraw
|
||||||
kind: canvas
|
kind: canvas
|
||||||
@@ -23,3 +25,12 @@
|
|||||||
ref: 1
|
ref: 1
|
||||||
op: Keep
|
op: Keep
|
||||||
func: NotEqual
|
func: NotEqual
|
||||||
|
|
||||||
|
# Draws if the texture in the stencil buffer is equal to white.
|
||||||
|
- type: shader
|
||||||
|
id: StencilEqualDraw
|
||||||
|
kind: canvas
|
||||||
|
stencil:
|
||||||
|
ref: 1
|
||||||
|
op: Keep
|
||||||
|
func: Equal
|
||||||
|
|||||||
Reference in New Issue
Block a user