From 8fb48a09ef3ac34fc2b1f7008da4142e15ab8382 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 29 Apr 2022 00:43:16 +1200 Subject: [PATCH] Improve singularity shader (#7647) * Working example * vector arrays * simplify math * max distance * max distance * PVS override * rename count --- .../Singularity/SingularityOverlay.cs | 143 ++++++------------ .../EntitySystems/SingularitySystem.cs | 13 +- .../SingularityDistortionComponent.cs | 22 +-- .../Singularity/SharedSingularitySystem.cs | 26 ++-- .../Prototypes/Entities/Objects/Fun/toys.yml | 4 +- Resources/Prototypes/Shaders/shaders.yml | 4 - Resources/Textures/Shaders/singularity.swsl | 58 ++++--- 7 files changed, 114 insertions(+), 156 deletions(-) diff --git a/Content.Client/Singularity/SingularityOverlay.cs b/Content.Client/Singularity/SingularityOverlay.cs index 401e9ee595..8bc96db6fd 100644 --- a/Content.Client/Singularity/SingularityOverlay.cs +++ b/Content.Client/Singularity/SingularityOverlay.cs @@ -1,28 +1,27 @@ -using System.Collections.Generic; -using System.Linq; using Content.Shared.Singularity.Components; using Robust.Client.Graphics; using Robust.Shared.Enums; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Map; -using Robust.Shared.Maths; using Robust.Shared.Prototypes; namespace Content.Client.Singularity { public sealed class SingularityOverlay : Overlay { - [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IEntityManager _entMan = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - private const float MaxDist = 15.0f; + /// + /// Maximum number of distortions that can be shown on screen at a time. + /// If this value is changed, the shader itself also needs to be updated. + /// + public const int MaxCount = 5; + + private const float MaxDistance = 20f; public override OverlaySpace Space => OverlaySpace.WorldSpace; public override bool RequestScreenTexture => true; private readonly ShaderInstance _shader; - private readonly Dictionary _singularities = new(); public SingularityOverlay() { @@ -30,109 +29,59 @@ namespace Content.Client.Singularity _shader = _prototypeManager.Index("Singularity").Instance().Duplicate(); } - public override bool OverwriteTargetFrameBuffer() - { - return _singularities.Count > 0; - } - protected override void Draw(in OverlayDrawArgs args) { - SingularityQuery(args.Viewport.Eye); + if (ScreenTexture == null || args.Viewport.Eye == null) + return; - var viewportWB = args.WorldAABB; // Has to be correctly handled because of the way intensity/falloff transform works so just do it. _shader?.SetParameter("renderScale", args.Viewport.RenderScale); - foreach (var instance in _singularities.Values) + + var position = new Vector2[MaxCount]; + var intensity = new float[MaxCount]; + var falloffPower = new float[MaxCount]; + int count = 0; + + var mapId = args.Viewport.Eye.Position.MapId; + + foreach (var distortion in _entMan.EntityQuery()) { + var mapPos = _entMan.GetComponent(distortion.Owner).MapPosition; + if (mapPos.MapId != mapId) + continue; + + // is the distortion in range? + if ((mapPos.Position - args.WorldAABB.ClosestPoint(mapPos.Position)).LengthSquared > MaxDistance * MaxDistance) + continue; + // To be clear, this needs to use "inside-viewport" pixels. // In other words, specifically NOT IViewportControl.WorldToScreen (which uses outer coordinates). - var tempCoords = args.Viewport.WorldToLocal(instance.CurrentMapCoords); + var tempCoords = args.Viewport.WorldToLocal(mapPos.Position); tempCoords.Y = args.Viewport.Size.Y - tempCoords.Y; - _shader?.SetParameter("positionInput", tempCoords); - if (ScreenTexture != null) - _shader?.SetParameter("SCREEN_TEXTURE", ScreenTexture); - _shader?.SetParameter("intensity", instance.Intensity); - _shader?.SetParameter("falloff", instance.Falloff); - var worldHandle = args.WorldHandle; - worldHandle.UseShader(_shader); - worldHandle.DrawRect(viewportWB, Color.White); + position[count] = tempCoords; + intensity[count] = distortion.Intensity; + falloffPower[count] = distortion.FalloffPower; + count++; + + if (count == MaxCount) + break; } - } - - //Queries all singulos on the map and either adds or removes them from the list of rendered singulos based on whether they should be drawn (in range? on the same z-level/map? singulo entity still exists?) - private void SingularityQuery(IEye? currentEye) - { - if (currentEye == null) - { - _singularities.Clear(); + if (count == 0) return; - } - var currentEyeLoc = currentEye.Position; + _shader?.SetParameter("count", count); + _shader?.SetParameter("position", position); + _shader?.SetParameter("intensity", intensity); + _shader?.SetParameter("falloffPower", falloffPower); + _shader?.SetParameter("maxDistance", MaxDistance * EyeManager.PixelsPerMeter); + _shader?.SetParameter("SCREEN_TEXTURE", ScreenTexture); - var distortions = _entityManager.EntityQuery(); - foreach (var distortion in distortions) //Add all singulos that are not added yet but qualify - { - var singuloEntity = distortion.Owner; - - if (!_singularities.Keys.Contains(singuloEntity) && SinguloQualifies(singuloEntity, currentEyeLoc)) - { - _singularities.Add(singuloEntity, new SingularityShaderInstance(_entityManager.GetComponent(singuloEntity).MapPosition.Position, distortion.Intensity, distortion.Falloff)); - } - } - - var activeShaderIds = _singularities.Keys; - foreach (var activeSingulo in activeShaderIds) //Remove all singulos that are added and no longer qualify - { - if (_entityManager.EntityExists(activeSingulo)) - { - if (!SinguloQualifies(activeSingulo, currentEyeLoc)) - { - _singularities.Remove(activeSingulo); - } - else - { - if (!_entityManager.TryGetComponent(activeSingulo, out var distortion)) - { - _singularities.Remove(activeSingulo); - } - else - { - var shaderInstance = _singularities[activeSingulo]; - shaderInstance.CurrentMapCoords = _entityManager.GetComponent(activeSingulo).MapPosition.Position; - shaderInstance.Intensity = distortion.Intensity; - shaderInstance.Falloff = distortion.Falloff; - } - } - - } - else - { - _singularities.Remove(activeSingulo); - } - } - - } - - private bool SinguloQualifies(EntityUid singuloEntity, MapCoordinates currentEyeLoc) - { - return _entityManager.GetComponent(singuloEntity).MapID == currentEyeLoc.MapId && _entityManager.GetComponent(singuloEntity).Coordinates.InRange(_entityManager, EntityCoordinates.FromMap(_entityManager, _entityManager.GetComponent(singuloEntity).ParentUid, currentEyeLoc), MaxDist); - } - - private sealed class SingularityShaderInstance - { - public Vector2 CurrentMapCoords; - public float Intensity; - public float Falloff; - - public SingularityShaderInstance(Vector2 mapCoords, float intensity, float falloff) - { - CurrentMapCoords = mapCoords; - Intensity = intensity; - Falloff = falloff; - } + var worldHandle = args.WorldHandle; + worldHandle.UseShader(_shader); + worldHandle.DrawRect(args.WorldAABB, Color.White); + worldHandle.UseShader(null); } } } diff --git a/Content.Server/Singularity/EntitySystems/SingularitySystem.cs b/Content.Server/Singularity/EntitySystems/SingularitySystem.cs index 777be02a8c..55a1e7155b 100644 --- a/Content.Server/Singularity/EntitySystems/SingularitySystem.cs +++ b/Content.Server/Singularity/EntitySystems/SingularitySystem.cs @@ -4,11 +4,9 @@ using Content.Server.Singularity.Components; using Content.Shared.Singularity; using Content.Shared.Singularity.Components; using JetBrains.Annotations; +using Robust.Server.GameStates; using Robust.Shared.Containers; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; using Robust.Shared.Map; -using Robust.Shared.Maths; using Robust.Shared.Physics; using Robust.Shared.Physics.Dynamics; @@ -20,6 +18,7 @@ namespace Content.Server.Singularity.EntitySystems [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly PVSOverrideSystem _pvs = default!; /// /// How much energy the singulo gains from destroying a tile. @@ -36,6 +35,14 @@ namespace Content.Server.Singularity.EntitySystems { base.Initialize(); SubscribeLocalEvent(OnCollide); + SubscribeLocalEvent(OnDistortionStartup); + } + + private void OnDistortionStartup(EntityUid uid, SingularityDistortionComponent component, ComponentStartup args) + { + // to avoid distortion overlay pop-in, entities with distortion ignore PVS. Really this should probably be a + // PVS range-override, but this is good enough for now. + _pvs.AddGlobalOverride(uid); } protected override bool PreventCollide(EntityUid uid, SharedSingularityComponent component, PreventCollideEvent args) diff --git a/Content.Shared/Singularity/Components/SingularityDistortionComponent.cs b/Content.Shared/Singularity/Components/SingularityDistortionComponent.cs index f9dc1aa523..5d21cdad60 100644 --- a/Content.Shared/Singularity/Components/SingularityDistortionComponent.cs +++ b/Content.Shared/Singularity/Components/SingularityDistortionComponent.cs @@ -1,11 +1,5 @@ -using System; -using Robust.Shared.GameObjects; using Robust.Shared.GameStates; -using Robust.Shared.Players; using Robust.Shared.Serialization; -using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.ViewVariables; - namespace Content.Shared.Singularity.Components { [RegisterComponent] @@ -13,10 +7,10 @@ namespace Content.Shared.Singularity.Components public sealed class SingularityDistortionComponent : Component { [DataField("intensity")] - private float _intensity = 0.25f; + private float _intensity = 31.25f; - [DataField("falloff")] - private float _falloff = 2; + [DataField("falloffPower")] + private float _falloffPower = MathF.Sqrt(2f); [ViewVariables(VVAccess.ReadWrite)] public float Intensity @@ -26,15 +20,15 @@ namespace Content.Shared.Singularity.Components } [ViewVariables(VVAccess.ReadWrite)] - public float Falloff + public float FalloffPower { - get => _falloff; - set => this.SetAndDirtyIfChanged(ref _falloff, value); + get => _falloffPower; + set => this.SetAndDirtyIfChanged(ref _falloffPower, value); } public override ComponentState GetComponentState() { - return new SingularityDistortionComponentState(Intensity, Falloff); + return new SingularityDistortionComponentState(Intensity, FalloffPower); } public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) @@ -47,7 +41,7 @@ namespace Content.Shared.Singularity.Components } Intensity = state.Intensity; - Falloff = state.Falloff; + FalloffPower = state.Falloff; } } diff --git a/Content.Shared/Singularity/SharedSingularitySystem.cs b/Content.Shared/Singularity/SharedSingularitySystem.cs index 7a40c6c42c..a318555ee5 100644 --- a/Content.Shared/Singularity/SharedSingularitySystem.cs +++ b/Content.Shared/Singularity/SharedSingularitySystem.cs @@ -22,12 +22,12 @@ namespace Content.Shared.Singularity return level switch { 0 => 9999f, - 1 => 6.4f, - 2 => 7.0f, - 3 => 8.0f, - 4 => 10.0f, - 5 => 12.0f, - 6 => 12.0f, + 1 => MathF.Sqrt(6.4f), + 2 => MathF.Sqrt(7.0f), + 3 => MathF.Sqrt(8.0f), + 4 => MathF.Sqrt(10.0f), + 5 => MathF.Sqrt(12.0f), + 6 => MathF.Sqrt(12.0f), _ => -1.0f }; } @@ -37,12 +37,12 @@ namespace Content.Shared.Singularity return level switch { 0 => 0.0f, - 1 => 2.7f, - 2 => 14.4f, - 3 => 47.2f, - 4 => 180.0f, - 5 => 600.0f, - 6 => 800.0f, + 1 => 3645f, + 2 => 103680f, + 3 => 1113920f, + 4 => 16200000f, + 5 => 180000000f, + 6 => 180000000f, _ => -1.0f }; } @@ -126,7 +126,7 @@ namespace Content.Shared.Singularity if (EntityManager.TryGetComponent(singularity.Owner, out SingularityDistortionComponent? distortion)) { - distortion.Falloff = GetFalloff(value); + distortion.FalloffPower = GetFalloff(value); distortion.Intensity = GetIntensity(value); } diff --git a/Resources/Prototypes/Entities/Objects/Fun/toys.yml b/Resources/Prototypes/Entities/Objects/Fun/toys.yml index b55ba3aad7..0f05eb99a7 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/toys.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/toys.yml @@ -618,8 +618,8 @@ sprite: Objects/Fun/toys.rsi state: singularitytoy - type: SingularityDistortion - intensity: 2 - falloff: 7 + intensity: 2000 + falloffPower: 2.6 - type: Item size: 12 sprite: Objects/Fun/toys.rsi diff --git a/Resources/Prototypes/Shaders/shaders.yml b/Resources/Prototypes/Shaders/shaders.yml index e6e12fd332..015459acb8 100644 --- a/Resources/Prototypes/Shaders/shaders.yml +++ b/Resources/Prototypes/Shaders/shaders.yml @@ -22,10 +22,6 @@ id: Singularity kind: source path: "/Textures/Shaders/singularity.swsl" - params: - positionInput: 0,0 - falloff: 5 - intensity: 5 - type: shader id: Radiation diff --git a/Resources/Textures/Shaders/singularity.swsl b/Resources/Textures/Shaders/singularity.swsl index 2752412c3d..128e286b66 100644 --- a/Resources/Textures/Shaders/singularity.swsl +++ b/Resources/Textures/Shaders/singularity.swsl @@ -1,33 +1,45 @@ -//Gravitational lensing effect. Edited from https://unionassets.com/blog/the-effect-of-the-gravitational-lens-195 to be Clyde based (based on what) +//Gravitational lensing effect. Loosely inspired by https://unionassets.com/blog/the-effect-of-the-gravitational-lens-195 to be Clyde based (based on what) uniform sampler2D SCREEN_TEXTURE; -uniform highp vec2 positionInput; uniform highp vec2 renderScale; -uniform highp float falloff; -uniform highp float intensity; - +uniform highp float maxDistance; +uniform int count; +uniform highp float[5] falloffPower; +uniform highp float[5] intensity; +uniform highp vec2[5] position; +// the `5`s in the array lengths correspond to the upper limit on the simultaneous distortion sources that can be present on screen at a time. +// If you want to change this, make sure to change all of them here, in the for loop, and, in whatever overlay assigns the uniforms +// (apparently #define is an unknown preprocessor directive) void fragment() { - highp float distanceToCenter = length((FRAGCOORD.xy - positionInput) / renderScale); - highp vec2 finalCoords = FRAGCOORD.xy - positionInput; - highp float deformation = (pow(intensity, 2.0)*500.0) / pow(distanceToCenter, pow(falloff, 0.5)); - if(deformation > 0.8) //Edit this for inward effect - deformation = pow(deformation, 0.3); - if(deformation > 0.001){ - finalCoords *= 1.0-deformation; //Change this to 1+deformation for inward effect - finalCoords += positionInput; - //float darkenCircleSize = 600; //Calculate optional darkening effect (darker the closer we are to the center of the singularity) - //float alph = (distanceToCenter-30)/(darkenCircleSize-30); - //float darkening = 0.9-(alph*0.9); + highp vec2 finalCoords = FRAGCOORD.xy; + highp vec2 delta; + highp float distance; + highp float deformation; + + for (int i = 0; i < 5 && i < count; i++) { + + delta = FRAGCOORD.xy - position[i]; + distance = length(delta / renderScale); - //Darkening effect optional (Darker the closer you are to the center) - //COLOR = mix(texture(SCREEN_TEXTURE, finalCoords*SCREEN_PIXEL_SIZE), vec4(0.0, 0.0, 0.0, 1.0), darkening); - COLOR = zTextureSpec(SCREEN_TEXTURE, finalCoords*SCREEN_PIXEL_SIZE); - } - else{ - COLOR = zTextureSpec(SCREEN_TEXTURE, FRAGCOORD.xy*SCREEN_PIXEL_SIZE); - } + deformation = intensity[i] / pow(distance, falloffPower[i]); + + // ensure deformation goes to zero at max distance + // avoids long-range single-pixel shifts that are noticeable when leaving PVS. + + if (distance >= maxDistance) { + deformation = 0; + } else { + deformation *= (1 - pow(distance/maxDistance, 4)); + } + + if(deformation > 0.8) + deformation = pow(deformation, 0.3); + finalCoords -= deformation * delta; + } + + COLOR = zTextureSpec(SCREEN_TEXTURE, finalCoords*SCREEN_PIXEL_SIZE ); }