From f674cc38d53ce17a649e1bba481118c4916f707b Mon Sep 17 00:00:00 2001 From: ole kristian homelien Date: Sat, 11 Apr 2026 21:37:35 +0200 Subject: [PATCH 1/4] Code --- GameWorld/ContentProject/Content/Content.mgcb | 6 + .../Content/Shaders/GridShader.fx | 157 ++++++++++++++++++ GameWorld/View3D/Components/GridComponent.cs | 142 ++++++++++++++-- 3 files changed, 290 insertions(+), 15 deletions(-) create mode 100644 GameWorld/ContentProject/Content/Shaders/GridShader.fx 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/GridComponent.cs b/GameWorld/View3D/Components/GridComponent.cs index 946887722..84422361c 100644 --- a/GameWorld/View3D/Components/GridComponent.cs +++ b/GameWorld/View3D/Components/GridComponent.cs @@ -1,33 +1,145 @@ using GameWorld.Core.Components.Rendering; using GameWorld.Core.Rendering; +using GameWorld.Core.Services; +using GameWorld.Core.Utility; using Microsoft.Xna.Framework; -using Shared.Core.Settings; +using Microsoft.Xna.Framework.Graphics; +using Serilog; +using Shared.Core.ErrorHandling; namespace GameWorld.Core.Components { - public class GridComponent : BaseComponent + /// + /// 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 RenderEngineComponent _renderEngineComponent; - private readonly ApplicationSettingsService _applicationSettingsService; + private readonly ILogger _logger = Logging.Create(); + private readonly ArcBallCamera _camera; + private readonly ResourceLibrary _resourceLibrary; + private readonly IDeviceResolver _deviceResolver; + + // Shader resources + private Effect _gridEffect; + private EffectPass _gridPass; + private EffectParameter _worldParam; + private EffectParameter _viewParam; + private EffectParameter _projectionParam; + private EffectParameter _cameraPosParam; + private EffectParameter _gridColorParam; + private EffectParameter _cameraDistParam; + private EffectParameter _isOrthoParam; + + // Reusable quad vertices (triangle strip: 4 verts = 2 triangles) + private readonly VertexPositionTexture[] _quadVertices = new VertexPositionTexture[4]; + private bool _firstRenderLogged = false; public bool ShowGrid { get; set; } = true; - public Vector3 GridColur { get; set; } = new Vector3(0, 0, 0); + // Pure black: grid lines invisible, only axis lines (red X / blue Z) visible + public Vector3 GridColur { get; set; } = new Vector3(0f, 0f, 0f); - public GridComponent(RenderEngineComponent renderEngineComponent, ApplicationSettingsService applicationSettingsService) + public GridComponent(ArcBallCamera camera, ResourceLibrary resourceLibrary, IDeviceResolver deviceResolver) { - _renderEngineComponent = renderEngineComponent; - _applicationSettingsService = applicationSettingsService; + _camera = camera; + _resourceLibrary = resourceLibrary; + _deviceResolver = deviceResolver; } - public override void Draw(GameTime gameTime) + public override void Initialize() { - if (ShowGrid == false) + // Clone the cached effect so this GridComponent has its own parameter buffers. + // The ResourceLibrary singleton caches one Effect shared by all viewports; + // without cloning, two GridComponents would overwrite each other's shader + // parameters on the same Effect object, causing incorrect rendering in + // the second viewport (KitbashEditor, animation editors, etc.). + var cachedEffect = _resourceLibrary.LoadEffect("Shaders\\GridShader", ShaderTypes.Grid); + + if (cachedEffect == null) + { + _logger.Here().Error("GridShader failed to load - effect is null"); return; - - var gridSize = _applicationSettingsService.CurrentSettings.VisualEditorsGridSize; - var gridColour = new Color(GridColur); - var grid = LineHelper.CreateGrid(gridSize, gridColour); - _renderEngineComponent.AddRenderLines(grid); + } + + _gridEffect = cachedEffect.Clone(); + + _gridPass = _gridEffect.Techniques["Grid"].Passes[0]; + _worldParam = _gridEffect.Parameters["World"]; + _viewParam = _gridEffect.Parameters["View"]; + _projectionParam = _gridEffect.Parameters["Projection"]; + _cameraPosParam = _gridEffect.Parameters["CameraPosition"]; + _gridColorParam = _gridEffect.Parameters["GridColor"]; + _cameraDistParam = _gridEffect.Parameters["CameraDistance"]; + _isOrthoParam = _gridEffect.Parameters["IsOrthographic"]; + + _logger.Here().Information($"GridComponent initialized. Effect={_gridEffect != null}, Pass={_gridPass != null}"); + } + + /// + /// Called by RenderEngineComponent between 2D clear and 3D object rendering. + /// Renders the procedural grid quad with alpha blending and depth testing. + /// + public void RenderGrid(GraphicsDevice device, CommonShaderParameters commonShaderParameters) + { + if (!ShowGrid || _gridEffect == null) + return; + + // Log first render for diagnostics + if (!_firstRenderLogged) + { + _firstRenderLogged = true; + _logger.Here().Information($"GridComponent first render: " + + $"CameraPos={_camera.Position}, CameraLookAt={_camera.LookAt}, " + + $"GridColur={GridColur}, ShowGrid={ShowGrid}, " + + $"Viewport={device.Viewport.Width}x{device.Viewport.Height}, " + + $"GridEffect={_gridEffect != null}, GridPass={_gridPass != null}, " + + $"GridColorParam={_gridColorParam != null}, CameraDistParam={_cameraDistParam != null}"); + } + + // Calculate quad size based on camera distance + float cameraDist = Vector3.Distance(_camera.Position, _camera.LookAt); + if (_camera.CurrentProjectionType == ProjectionType.Orthographic) + cameraDist = _camera.OrthoSize; + + float halfSize = Math.Clamp(cameraDist * 5.0f, 25f, 8000f); + + // Snap quad center to integer grid positions (camera following) + float cx = (float)Math.Round(_camera.Position.X); + float 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); + + // Set render state: no backface culling (visible from both sides), alpha blending, depth test + device.RasterizerState = RasterizerState.CullNone; + device.BlendState = BlendState.AlphaBlend; + device.DepthStencilState = DepthStencilState.Default; + + // Set shader parameters + _worldParam.SetValue(Matrix.Identity); + _viewParam.SetValue(commonShaderParameters.View); + _projectionParam.SetValue(commonShaderParameters.Projection); + _cameraPosParam.SetValue(commonShaderParameters.CameraPosition); + _gridColorParam.SetValue(GridColur); + _cameraDistParam.SetValue(cameraDist); + _isOrthoParam.SetValue(_camera.CurrentProjectionType == ProjectionType.Orthographic ? 1 : 0); + + _gridPass.Apply(); + device.DrawUserPrimitives(PrimitiveType.TriangleStrip, _quadVertices, 0, 2); + + // Restore default blend state + device.BlendState = BlendState.Opaque; + } + + public void Dispose() + { + // Cloned effect is owned by this component, dispose it + _gridEffect?.Dispose(); + _gridEffect = null; } } } From ccce42ac08d5add1e2168825562abe7ab044d093 Mon Sep 17 00:00:00 2001 From: ole kristian homelien Date: Sun, 12 Apr 2026 20:13:32 +0200 Subject: [PATCH 2/4] code --- .../OpenRenderSettingsWindowCommand.cs | 2 +- .../View3D/Components/Grid/GridComponent.cs | 54 +++++++ .../View3D/Components/Grid/GridRenderItem.cs | 61 ++++++++ GameWorld/View3D/Components/GridComponent.cs | 145 ------------------ .../View3D/DependencyInjectionContainer.cs | 1 + .../View3D/Services/MeshBuilderService.cs | 4 +- GameWorld/View3D/Services/ResourceLibary.cs | 4 +- .../RenderSettingsWindow.xaml.cs | 2 +- 8 files changed, 122 insertions(+), 151 deletions(-) create mode 100644 GameWorld/View3D/Components/Grid/GridComponent.cs create mode 100644 GameWorld/View3D/Components/Grid/GridRenderItem.cs delete mode 100644 GameWorld/View3D/Components/GridComponent.cs 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/View3D/Components/Grid/GridComponent.cs b/GameWorld/View3D/Components/Grid/GridComponent.cs new file mode 100644 index 000000000..240b00c96 --- /dev/null +++ b/GameWorld/View3D/Components/Grid/GridComponent.cs @@ -0,0 +1,54 @@ +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..034ab990b --- /dev/null +++ b/GameWorld/View3D/Components/Grid/GridRenderItem.cs @@ -0,0 +1,61 @@ +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; + } + + 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 84422361c..000000000 --- a/GameWorld/View3D/Components/GridComponent.cs +++ /dev/null @@ -1,145 +0,0 @@ -using GameWorld.Core.Components.Rendering; -using GameWorld.Core.Rendering; -using GameWorld.Core.Services; -using GameWorld.Core.Utility; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using Serilog; -using Shared.Core.ErrorHandling; - -namespace GameWorld.Core.Components -{ - /// - /// 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 ILogger _logger = Logging.Create(); - private readonly ArcBallCamera _camera; - private readonly ResourceLibrary _resourceLibrary; - private readonly IDeviceResolver _deviceResolver; - - // Shader resources - private Effect _gridEffect; - private EffectPass _gridPass; - private EffectParameter _worldParam; - private EffectParameter _viewParam; - private EffectParameter _projectionParam; - private EffectParameter _cameraPosParam; - private EffectParameter _gridColorParam; - private EffectParameter _cameraDistParam; - private EffectParameter _isOrthoParam; - - // Reusable quad vertices (triangle strip: 4 verts = 2 triangles) - private readonly VertexPositionTexture[] _quadVertices = new VertexPositionTexture[4]; - private bool _firstRenderLogged = false; - - public bool ShowGrid { get; set; } = true; - // Pure black: grid lines invisible, only axis lines (red X / blue Z) visible - public Vector3 GridColur { get; set; } = new Vector3(0f, 0f, 0f); - - public GridComponent(ArcBallCamera camera, ResourceLibrary resourceLibrary, IDeviceResolver deviceResolver) - { - _camera = camera; - _resourceLibrary = resourceLibrary; - _deviceResolver = deviceResolver; - } - - public override void Initialize() - { - // Clone the cached effect so this GridComponent has its own parameter buffers. - // The ResourceLibrary singleton caches one Effect shared by all viewports; - // without cloning, two GridComponents would overwrite each other's shader - // parameters on the same Effect object, causing incorrect rendering in - // the second viewport (KitbashEditor, animation editors, etc.). - var cachedEffect = _resourceLibrary.LoadEffect("Shaders\\GridShader", ShaderTypes.Grid); - - if (cachedEffect == null) - { - _logger.Here().Error("GridShader failed to load - effect is null"); - return; - } - - _gridEffect = cachedEffect.Clone(); - - _gridPass = _gridEffect.Techniques["Grid"].Passes[0]; - _worldParam = _gridEffect.Parameters["World"]; - _viewParam = _gridEffect.Parameters["View"]; - _projectionParam = _gridEffect.Parameters["Projection"]; - _cameraPosParam = _gridEffect.Parameters["CameraPosition"]; - _gridColorParam = _gridEffect.Parameters["GridColor"]; - _cameraDistParam = _gridEffect.Parameters["CameraDistance"]; - _isOrthoParam = _gridEffect.Parameters["IsOrthographic"]; - - _logger.Here().Information($"GridComponent initialized. Effect={_gridEffect != null}, Pass={_gridPass != null}"); - } - - /// - /// Called by RenderEngineComponent between 2D clear and 3D object rendering. - /// Renders the procedural grid quad with alpha blending and depth testing. - /// - public void RenderGrid(GraphicsDevice device, CommonShaderParameters commonShaderParameters) - { - if (!ShowGrid || _gridEffect == null) - return; - - // Log first render for diagnostics - if (!_firstRenderLogged) - { - _firstRenderLogged = true; - _logger.Here().Information($"GridComponent first render: " + - $"CameraPos={_camera.Position}, CameraLookAt={_camera.LookAt}, " + - $"GridColur={GridColur}, ShowGrid={ShowGrid}, " + - $"Viewport={device.Viewport.Width}x{device.Viewport.Height}, " + - $"GridEffect={_gridEffect != null}, GridPass={_gridPass != null}, " + - $"GridColorParam={_gridColorParam != null}, CameraDistParam={_cameraDistParam != null}"); - } - - // Calculate quad size based on camera distance - float cameraDist = Vector3.Distance(_camera.Position, _camera.LookAt); - if (_camera.CurrentProjectionType == ProjectionType.Orthographic) - cameraDist = _camera.OrthoSize; - - float halfSize = Math.Clamp(cameraDist * 5.0f, 25f, 8000f); - - // Snap quad center to integer grid positions (camera following) - float cx = (float)Math.Round(_camera.Position.X); - float 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); - - // Set render state: no backface culling (visible from both sides), alpha blending, depth test - device.RasterizerState = RasterizerState.CullNone; - device.BlendState = BlendState.AlphaBlend; - device.DepthStencilState = DepthStencilState.Default; - - // Set shader parameters - _worldParam.SetValue(Matrix.Identity); - _viewParam.SetValue(commonShaderParameters.View); - _projectionParam.SetValue(commonShaderParameters.Projection); - _cameraPosParam.SetValue(commonShaderParameters.CameraPosition); - _gridColorParam.SetValue(GridColur); - _cameraDistParam.SetValue(cameraDist); - _isOrthoParam.SetValue(_camera.CurrentProjectionType == ProjectionType.Orthographic ? 1 : 0); - - _gridPass.Apply(); - device.DrawUserPrimitives(PrimitiveType.TriangleStrip, _quadVertices, 0, 2); - - // Restore default blend state - device.BlendState = BlendState.Opaque; - } - - public void Dispose() - { - // Cloned effect is owned by this component, dispose it - _gridEffect?.Dispose(); - _gridEffect = null; - } - } -} 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..c3766972f 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; From 8688ebee38123411b948bb587a3999df647537fe Mon Sep 17 00:00:00 2001 From: ole kristian homelien Date: Sun, 12 Apr 2026 20:28:34 +0200 Subject: [PATCH 3/4] Code changes --- AssetEditor/Themes/Controls.xaml | 2 +- GameWorld/View3D/Components/Grid/GridComponent.cs | 2 -- GameWorld/View3D/Components/Grid/GridRenderItem.cs | 3 +-- .../Utility/RenderSettingsDialog/RenderSettingsWindow.xaml.cs | 2 +- 4 files changed, 3 insertions(+), 6 deletions(-) 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/GameWorld/View3D/Components/Grid/GridComponent.cs b/GameWorld/View3D/Components/Grid/GridComponent.cs index 240b00c96..52eb9bdb2 100644 --- a/GameWorld/View3D/Components/Grid/GridComponent.cs +++ b/GameWorld/View3D/Components/Grid/GridComponent.cs @@ -5,7 +5,6 @@ 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 @@ -19,7 +18,6 @@ public class GridComponent : BaseComponent, IDisposable private Effect? _shaderEffect; private GridRenderItem? _renderItem; - public bool ShowGrid { get; set; } = true; public Vector3 GridColur { get; set; } = new Vector3(0f, 0f, 0f); diff --git a/GameWorld/View3D/Components/Grid/GridRenderItem.cs b/GameWorld/View3D/Components/Grid/GridRenderItem.cs index 034ab990b..42af5b1dc 100644 --- a/GameWorld/View3D/Components/Grid/GridRenderItem.cs +++ b/GameWorld/View3D/Components/Grid/GridRenderItem.cs @@ -15,7 +15,6 @@ public class GridRenderItem : IRenderItem private bool _isOrthographic; private Vector3 _gridColur = new(0f, 0f, 0f); - public GridRenderItem(Effect effect) { Guard.IsNotNull(effect); @@ -42,11 +41,11 @@ public void Update(ArcBallCamera camera, Vector3 gridColur) _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); diff --git a/GameWorld/View3D/Utility/RenderSettingsDialog/RenderSettingsWindow.xaml.cs b/GameWorld/View3D/Utility/RenderSettingsDialog/RenderSettingsWindow.xaml.cs index c3766972f..554324b8b 100644 --- a/GameWorld/View3D/Utility/RenderSettingsDialog/RenderSettingsWindow.xaml.cs +++ b/GameWorld/View3D/Utility/RenderSettingsDialog/RenderSettingsWindow.xaml.cs @@ -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), From e1e6fff779c27706109e49eb6c469529c8b9b4ae Mon Sep 17 00:00:00 2001 From: ole kristian homelien Date: Sun, 12 Apr 2026 20:32:50 +0200 Subject: [PATCH 4/4] Bug fix --- .../SharedUI/Shared.Ui/BaseDialogs/MathViews/Vector2View.xaml | 4 ++-- .../Shared.Ui/BaseDialogs/MathViews/Vector2ViewModel.cs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) 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;