Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AssetEditor/Themes/Controls.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -4125,7 +4125,7 @@
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid x:Name="templateRoot" SnapsToDevicePixels="true">
<Border x:Name="mainBorder" Background="{DynamicResource Window.Static.Border}" Margin="0" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1">
<Border x:Name="innerBorder" BorderBrush="{TemplateBinding BorderBrush}" Background="{Binding Background}" Margin="-1" Opacity="0"/>
<Border x:Name="innerBorder" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" Margin="-1" Opacity="0"/>
</Border>
<ContentPresenter x:Name="contentPresenter" ContentSource="Header" Focusable="False" HorizontalAlignment="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
</Grid>
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
6 changes: 6 additions & 0 deletions GameWorld/ContentProject/Content/Content.mgcb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
157 changes: 157 additions & 0 deletions GameWorld/ContentProject/Content/Shaders/GridShader.fx
Original file line number Diff line number Diff line change
@@ -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();
}
}
52 changes: 52 additions & 0 deletions GameWorld/View3D/Components/Grid/GridComponent.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// 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().
/// </summary>
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;
}
}
}
60 changes: 60 additions & 0 deletions GameWorld/View3D/Components/Grid/GridRenderItem.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
33 changes: 0 additions & 33 deletions GameWorld/View3D/Components/GridComponent.cs

This file was deleted.

1 change: 1 addition & 0 deletions GameWorld/View3D/DependencyInjectionContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 1 addition & 3 deletions GameWorld/View3D/Services/MeshBuilderService.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
4 changes: 3 additions & 1 deletion GameWorld/View3D/Services/ResourceLibary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public enum ShaderTypes
BasicEffect,
GeometryInstance,
Glow,
BloomFilter
BloomFilter,
Grid
}

public class ResourceLibrary
Expand Down Expand Up @@ -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<TextureCube>("textures\\phazer\\DiffuseAmbientLightCubeMap");
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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),
Expand Down
Loading
Loading