diff --git a/AssetEditor/Themes/Controls.xaml b/AssetEditor/Themes/Controls.xaml index 5bc6cdfba..faccf006f 100644 --- a/AssetEditor/Themes/Controls.xaml +++ b/AssetEditor/Themes/Controls.xaml @@ -4125,7 +4125,7 @@ - + diff --git a/Editors/Kitbashing/KitbasherEditor/UiCommands/OpenRenderSettingsWindowCommand.cs b/Editors/Kitbashing/KitbasherEditor/UiCommands/OpenRenderSettingsWindowCommand.cs index 4151e15d5..2cc3492c3 100644 --- a/Editors/Kitbashing/KitbasherEditor/UiCommands/OpenRenderSettingsWindowCommand.cs +++ b/Editors/Kitbashing/KitbasherEditor/UiCommands/OpenRenderSettingsWindowCommand.cs @@ -1,6 +1,6 @@ using System.Windows; using Editors.KitbasherEditor.Core.MenuBarViews; -using GameWorld.Core.Components; +using GameWorld.Core.Components.Grid; using GameWorld.Core.Components.Rendering; using GameWorld.Core.Utility.RenderSettingsDialog; using Shared.Ui.Common.MenuSystem; diff --git a/GameWorld/ContentProject/Content/Content.mgcb b/GameWorld/ContentProject/Content/Content.mgcb index f67a2af86..ccd09d02c 100644 --- a/GameWorld/ContentProject/Content/Content.mgcb +++ b/GameWorld/ContentProject/Content/Content.mgcb @@ -32,6 +32,12 @@ /processorParam:DebugMode=Auto /build:Shaders/Geometry/BasicShader.fx +#begin Shaders/GridShader.fx +/importer:EffectImporter +/processor:EffectProcessor +/processorParam:DebugMode=Auto +/build:Shaders/GridShader.fx + #begin Shaders/InstancingShader.fx /importer:EffectImporter /processor:EffectProcessor diff --git a/GameWorld/ContentProject/Content/Shaders/GridShader.fx b/GameWorld/ContentProject/Content/Shaders/GridShader.fx new file mode 100644 index 000000000..925b223df --- /dev/null +++ b/GameWorld/ContentProject/Content/Shaders/GridShader.fx @@ -0,0 +1,157 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Procedural infinite grid shader +// Renders a ground-plane quad with analytically anti-aliased grid lines using +// frac() + fwidth() + smoothstep(). This produces perfect AA at any zoom/angle. +// Based on Blender's overlay_grid approach adapted for MonoGame HLSL. +//////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#if OPENGL +#define VS_SHADERMODEL vs_3_0 +#define PS_SHADERMODEL ps_3_0 +#else +#define VS_SHADERMODEL vs_4_0 +#define PS_SHADERMODEL ps_4_0 +#endif + +float4x4 World; +float4x4 View; +float4x4 Projection; +float3 CameraPosition; +float3 GridColor; +float CameraDistance; +int IsOrthographic; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////// +// STRUCTS +//////////////////////////////////////////////////////////////////////////////////////////////////////////// + +struct VertexShaderInput +{ + float3 Position : POSITION0; +}; + +struct VertexShaderOutput +{ + float4 Position : SV_POSITION; + float3 WorldPos : TEXCOORD0; + float ViewDist : TEXCOORD1; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////// +// VERTEX SHADER +//////////////////////////////////////////////////////////////////////////////////////////////////////////// + +VertexShaderOutput GridVS(VertexShaderInput input) +{ + VertexShaderOutput output = (VertexShaderOutput)0; + + float3 worldPos = input.Position; + float4 viewPos = mul(float4(worldPos, 1.0), View); + output.Position = mul(viewPos, Projection); + output.WorldPos = worldPos; + output.ViewDist = length(viewPos.xyz); + + return output; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////// +// PIXEL SHADER - Procedural grid with analytical anti-aliasing +//////////////////////////////////////////////////////////////////////////////////////////////////////////// + +float4 GridPS(VertexShaderOutput input) : COLOR0 +{ + float2 coord = input.WorldPos.xz; + + // Screen-space derivatives for automatic LOD / anti-aliasing + float2 dv = fwidth(coord); + float2 dvHalf = dv * 0.5; + + // --- Fine grid (every 1 unit) --- + float2 gridFrac = abs(frac(coord - 0.5) - 0.5); + float2 fineLineSmooth = smoothstep(dvHalf, dvHalf * 2.0, gridFrac); + float fineLine = 1.0 - min(fineLineSmooth.x, fineLineSmooth.y); + + // --- Emphasis grid (every 5 units) --- + float2 coord5 = coord * 0.2; // coord / 5.0 + float2 dv5 = fwidth(coord5); + float2 dv5Half = dv5 * 0.5; + float2 emphasisFrac = abs(frac(coord5 - 0.5) - 0.5); + float2 emphasisSmooth = smoothstep(dv5Half, dv5Half * 2.0, emphasisFrac); + float emphasisLine = 1.0 - min(emphasisSmooth.x, emphasisSmooth.y); + + // --- Axis indicators --- + float xAxisLine = 1.0 - smoothstep(dvHalf.y, dv.y, abs(coord.y)); + float zAxisLine = 1.0 - smoothstep(dvHalf.x, dv.x, abs(coord.x)); + + // --- Distance fadeout (proportional to camera distance) --- + // Wide, gradual fade for natural appearance (Blender style) + float fadeStart = CameraDistance * 0.3; + float fadeEnd = CameraDistance * 4.5; + float dist = length(input.WorldPos.xz - CameraPosition.xz); + float distFade = 1.0 - smoothstep(fadeStart, fadeEnd, dist); + distFade = pow(distFade, 0.6); // Soften curve for more gradual falloff + + // --- Angle fadeout (grid fades when viewed nearly edge-on) --- + // Softer threshold: only fade when angle is < ~8 degrees from horizontal (0.02) + // instead of original < ~3 degrees (0.05). This keeps grid visible when camera + // is slightly below ground plane (Y ≈ 0), common after model-focused positioning. + float3 viewDir = normalize(CameraPosition - input.WorldPos); + float angleFade = smoothstep(0.02, 0.15, abs(viewDir.y)); + + // --- Compose final color and alpha --- + float combinedFade = distFade * angleFade; + + // Fine grid: subtle + float fineAlpha = fineLine * 0.25 * combinedFade; + + // Emphasis grid: stronger, uses brighter color + float emphasisAlpha = emphasisLine * 0.5 * combinedFade; + + // Axes: most prominent with dedicated colors + float xAxisAlpha = xAxisLine * 0.8 * combinedFade; + float zAxisAlpha = zAxisLine * 0.8 * combinedFade; + + // Pick the dominant contribution + float alpha = fineAlpha; + float3 color = GridColor; + + // Emphasis overrides fine + if (emphasisAlpha > alpha) + { + alpha = emphasisAlpha; + color = GridColor * 1.6; // brighter for emphasis lines + } + + // X axis = red + if (xAxisAlpha > alpha) + { + alpha = xAxisAlpha; + color = float3(0.9, 0.3, 0.3); + } + + // Z axis = blue + if (zAxisAlpha > alpha) + { + alpha = zAxisAlpha; + color = float3(0.3, 0.5, 0.9); + } + + // Discard fully transparent pixels for early-Z + if (alpha < 0.001) + discard; + + return float4(color, alpha); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////// +// TECHNIQUES +//////////////////////////////////////////////////////////////////////////////////////////////////////////// + +technique Grid +{ + pass Pass0 + { + VertexShader = compile VS_SHADERMODEL GridVS(); + PixelShader = compile PS_SHADERMODEL GridPS(); + } +} diff --git a/GameWorld/View3D/Components/Grid/GridComponent.cs b/GameWorld/View3D/Components/Grid/GridComponent.cs new file mode 100644 index 000000000..52eb9bdb2 --- /dev/null +++ b/GameWorld/View3D/Components/Grid/GridComponent.cs @@ -0,0 +1,52 @@ +using GameWorld.Core.Components.Rendering; +using GameWorld.Core.Services; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace GameWorld.Core.Components.Grid +{ + /// + /// Infinite grid component using a procedural screen-space shader. + /// Renders a camera-following ground plane quad with analytically anti-aliased + /// grid lines computed in the fragment shader via frac() + fwidth() + smoothstep(). + /// + public class GridComponent : BaseComponent, IDisposable + { + private readonly ArcBallCamera _camera; + private readonly IScopedResourceLibrary _resourceLibrary; + private readonly RenderEngineComponent _renderEngineComponent; + private Effect? _shaderEffect; + private GridRenderItem? _renderItem; + + public bool ShowGrid { get; set; } = true; + public Vector3 GridColur { get; set; } = new Vector3(0f, 0f, 0f); + + public GridComponent(ArcBallCamera camera, IScopedResourceLibrary resourceLibrary, RenderEngineComponent renderEngineComponent) + { + _camera = camera; + _resourceLibrary = resourceLibrary; + _renderEngineComponent = renderEngineComponent; + } + + public override void Initialize() + { + _shaderEffect = _resourceLibrary.GetStaticEffect(ShaderTypes.Grid); + _renderItem = new GridRenderItem(_shaderEffect); + base.Initialize(); + } + + public override void Draw(GameTime gameTime) + { + if (!ShowGrid || _shaderEffect == null || _renderItem == null) + return; + + _renderItem.Update(_camera, GridColur); + _renderEngineComponent.AddRenderItem(RenderBuckedId.Normal, _renderItem); + } + + public void Dispose() + { + _shaderEffect = null; + } + } +} diff --git a/GameWorld/View3D/Components/Grid/GridRenderItem.cs b/GameWorld/View3D/Components/Grid/GridRenderItem.cs new file mode 100644 index 000000000..42af5b1dc --- /dev/null +++ b/GameWorld/View3D/Components/Grid/GridRenderItem.cs @@ -0,0 +1,60 @@ +using CommunityToolkit.Diagnostics; +using GameWorld.Core.Components.Rendering; +using GameWorld.Core.Rendering; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace GameWorld.Core.Components.Grid +{ + public class GridRenderItem : IRenderItem + { + readonly private Effect _gridEffect; + private readonly VertexPositionTexture[] _quadVertices = new VertexPositionTexture[4]; + + private float _cameraDist; + private bool _isOrthographic; + private Vector3 _gridColur = new(0f, 0f, 0f); + + public GridRenderItem(Effect effect) + { + Guard.IsNotNull(effect); + _gridEffect = effect; + } + + public void Update(ArcBallCamera camera, Vector3 gridColur) + { + // Calculate quad size based on camera distance + _cameraDist = Vector3.Distance(camera.Position, camera.LookAt); + if (camera.CurrentProjectionType == ProjectionType.Orthographic) + _cameraDist = camera.OrthoSize; + + var halfSize = Math.Clamp(_cameraDist * 5.0f, 25f, 8000f); + + // Snap quad center to integer grid positions (camera following) + var cx = (float)Math.Round(camera.Position.X); + var cz = (float)Math.Round(camera.Position.Z); + + // Build ground plane quad at Y=0 (triangle strip order) + _quadVertices[0] = new VertexPositionTexture(new Vector3(cx - halfSize, 0, cz + halfSize), Vector2.Zero); + _quadVertices[1] = new VertexPositionTexture(new Vector3(cx + halfSize, 0, cz + halfSize), Vector2.Zero); + _quadVertices[2] = new VertexPositionTexture(new Vector3(cx - halfSize, 0, cz - halfSize), Vector2.Zero); + _quadVertices[3] = new VertexPositionTexture(new Vector3(cx + halfSize, 0, cz - halfSize), Vector2.Zero); + + _isOrthographic = camera.CurrentProjectionType == ProjectionType.Orthographic; + _gridColur = gridColur; + } + + public void Draw(GraphicsDevice device, CommonShaderParameters parameters, RenderingTechnique renderingTechnique) + { + _gridEffect.Parameters["World"].SetValue(Matrix.Identity); + _gridEffect.Parameters["View"].SetValue(parameters.View); + _gridEffect.Parameters["Projection"].SetValue(parameters.Projection); + _gridEffect.Parameters["CameraPosition"].SetValue(parameters.CameraPosition); + _gridEffect.Parameters["GridColor"].SetValue(_gridColur); + _gridEffect.Parameters["CameraDistance"].SetValue(_cameraDist); + _gridEffect.Parameters["IsOrthographic"].SetValue(_isOrthographic ? 1 : 0); + _gridEffect.Techniques["Grid"].Passes[0].Apply(); + device.DrawUserPrimitives(PrimitiveType.TriangleStrip, _quadVertices, 0, 2); + } + } +} diff --git a/GameWorld/View3D/Components/GridComponent.cs b/GameWorld/View3D/Components/GridComponent.cs deleted file mode 100644 index 946887722..000000000 --- a/GameWorld/View3D/Components/GridComponent.cs +++ /dev/null @@ -1,33 +0,0 @@ -using GameWorld.Core.Components.Rendering; -using GameWorld.Core.Rendering; -using Microsoft.Xna.Framework; -using Shared.Core.Settings; - -namespace GameWorld.Core.Components -{ - public class GridComponent : BaseComponent - { - private readonly RenderEngineComponent _renderEngineComponent; - private readonly ApplicationSettingsService _applicationSettingsService; - - public bool ShowGrid { get; set; } = true; - public Vector3 GridColur { get; set; } = new Vector3(0, 0, 0); - - public GridComponent(RenderEngineComponent renderEngineComponent, ApplicationSettingsService applicationSettingsService) - { - _renderEngineComponent = renderEngineComponent; - _applicationSettingsService = applicationSettingsService; - } - - public override void Draw(GameTime gameTime) - { - if (ShowGrid == false) - return; - - var gridSize = _applicationSettingsService.CurrentSettings.VisualEditorsGridSize; - var gridColour = new Color(GridColur); - var grid = LineHelper.CreateGrid(gridSize, gridColour); - _renderEngineComponent.AddRenderLines(grid); - } - } -} diff --git a/GameWorld/View3D/DependencyInjectionContainer.cs b/GameWorld/View3D/DependencyInjectionContainer.cs index c6ecf19a8..7e7bba446 100644 --- a/GameWorld/View3D/DependencyInjectionContainer.cs +++ b/GameWorld/View3D/DependencyInjectionContainer.cs @@ -6,6 +6,7 @@ using GameWorld.Core.Commands.Vertex; using GameWorld.Core.Components; using GameWorld.Core.Components.Gizmo; +using GameWorld.Core.Components.Grid; using GameWorld.Core.Components.Input; using GameWorld.Core.Components.Navigation; using GameWorld.Core.Components.Rendering; diff --git a/GameWorld/View3D/Services/MeshBuilderService.cs b/GameWorld/View3D/Services/MeshBuilderService.cs index 3278b2bcd..98bbaf45b 100644 --- a/GameWorld/View3D/Services/MeshBuilderService.cs +++ b/GameWorld/View3D/Services/MeshBuilderService.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using GameWorld.Core.Rendering; +using GameWorld.Core.Rendering; using GameWorld.Core.Rendering.Geometry; using Microsoft.Xna.Framework; using Shared.GameFormats.RigidModel; diff --git a/GameWorld/View3D/Services/ResourceLibary.cs b/GameWorld/View3D/Services/ResourceLibary.cs index 04726bc73..ffcfba04c 100644 --- a/GameWorld/View3D/Services/ResourceLibary.cs +++ b/GameWorld/View3D/Services/ResourceLibary.cs @@ -19,7 +19,8 @@ public enum ShaderTypes BasicEffect, GeometryInstance, Glow, - BloomFilter + BloomFilter, + Grid } public class ResourceLibrary @@ -59,6 +60,7 @@ public void Initialize(GraphicsDevice graphicsDevice, ContentManager content) LoadEffect("Shaders\\Geometry\\BasicShader", ShaderTypes.BasicEffect); LoadEffect("Shaders\\TexturePreview", ShaderTypes.TexturePreview); LoadEffect("Shaders\\LineShader", ShaderTypes.Line); + LoadEffect("Shaders\\GridShader", ShaderTypes.Grid); LoadEffect("Shaders\\InstancingShader", ShaderTypes.GeometryInstance); _pbrDiffuse = _content.Load("textures\\phazer\\DiffuseAmbientLightCubeMap"); diff --git a/GameWorld/View3D/Utility/RenderSettingsDialog/RenderSettingsWindow.xaml.cs b/GameWorld/View3D/Utility/RenderSettingsDialog/RenderSettingsWindow.xaml.cs index ade769e2b..554324b8b 100644 --- a/GameWorld/View3D/Utility/RenderSettingsDialog/RenderSettingsWindow.xaml.cs +++ b/GameWorld/View3D/Utility/RenderSettingsDialog/RenderSettingsWindow.xaml.cs @@ -1,5 +1,5 @@ using System.Windows; -using GameWorld.Core.Components; +using GameWorld.Core.Components.Grid; using GameWorld.Core.Components.Rendering; using Microsoft.Xna.Framework; using Shared.Ui.BaseDialogs.ColourPickerButton; @@ -24,7 +24,7 @@ public RenderSettingsWindow(RenderEngineComponent renderEngineComponent, SceneRe UseBigSceneCulling = renderEngineComponent.LargeSceneCulling, ShowGrid = _gridComponent.ShowGrid, - GridColour = new ColourPickerViewModel(_gridComponent.GridColur), + GridColour = new ColourPickerViewModel(_gridComponent.GridColur, ColourChanged), LightIntensity = sceneRenderParameterStore.LightIntensityMult, LightColour = new ColourPickerViewModel(sceneRenderParameterStore.LightColour, ColourChanged), diff --git a/Shared/SharedUI/Shared.Ui/BaseDialogs/MathViews/Vector2View.xaml b/Shared/SharedUI/Shared.Ui/BaseDialogs/MathViews/Vector2View.xaml index 352a10c56..1f4ea6548 100644 --- a/Shared/SharedUI/Shared.Ui/BaseDialogs/MathViews/Vector2View.xaml +++ b/Shared/SharedUI/Shared.Ui/BaseDialogs/MathViews/Vector2View.xaml @@ -23,7 +23,7 @@ Width="{Binding ActualWidth, ElementName=b}" Grid.Row="0" Grid.Column="0" HorizontalContentAlignment="Right" Text="{Binding X.TextValue, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" - MaxLength="{Binding NumbersMaxLength, ElementName=self}"> + MaxLength="{Binding NumbersMaxLength}"> @@ -34,7 +34,7 @@ Grid.Row="0" Grid.Column="1" HorizontalContentAlignment="Right" Margin="3,0,0,0" Text="{Binding Y.TextValue, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" - MaxLength="{Binding NumbersMaxLength, ElementName=self}" > + MaxLength="{Binding NumbersMaxLength}" > diff --git a/Shared/SharedUI/Shared.Ui/BaseDialogs/MathViews/Vector2ViewModel.cs b/Shared/SharedUI/Shared.Ui/BaseDialogs/MathViews/Vector2ViewModel.cs index b8080fe0e..5c07e158b 100644 --- a/Shared/SharedUI/Shared.Ui/BaseDialogs/MathViews/Vector2ViewModel.cs +++ b/Shared/SharedUI/Shared.Ui/BaseDialogs/MathViews/Vector2ViewModel.cs @@ -8,6 +8,7 @@ public partial class Vector2ViewModel : ObservableObject { private readonly Action? _valueChangedCallback; + [ObservableProperty] int _NumbersMaxLength = 20; [ObservableProperty] FloatViewModel _x; [ObservableProperty] FloatViewModel _y;