diff --git a/Content/Shaders/Editor/Grid.flax b/Content/Shaders/Editor/Grid.flax new file mode 100644 index 000000000..78d0e2618 --- /dev/null +++ b/Content/Shaders/Editor/Grid.flax @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb948b8313e458d0a368794877ebd94b4c59790f4068395450ed474a347cb2bb +size 4632 diff --git a/Source/Editor/Gizmo/GridGizmo.cs b/Source/Editor/Gizmo/GridGizmo.cs index 74a16449e..1c3f86043 100644 --- a/Source/Editor/Gizmo/GridGizmo.cs +++ b/Source/Editor/Gizmo/GridGizmo.cs @@ -2,6 +2,7 @@ using System; using FlaxEngine; +using System.Runtime.InteropServices; namespace FlaxEditor.Gizmo { @@ -15,91 +16,119 @@ namespace FlaxEditor.Gizmo [HideInEditor] private sealed class Renderer : PostProcessEffect { - private IntPtr _debugDrawContext; + [StructLayout(LayoutKind.Sequential)] + private struct Data + { + public Matrix WorldMatrix; + public Matrix ViewProjectionMatrix; + public Float4 GridColor; + public Float3 ViewPos; + public float Far; + public Float3 Padding; + public float GridSize; + } + + private static readonly uint[] _triangles = + { + 0, 2, 1, // Face front + 1, 3, 0, + }; + + private GPUBuffer[] _vbs = new GPUBuffer[1]; + private GPUBuffer _vertexBuffer; + private GPUBuffer _indexBuffer; + private GPUPipelineState _psGrid; + private Shader _shader; public Renderer() { - Order = -100; UseSingleTarget = true; - Location = PostProcessEffectLocation.BeforeForwardPass; + Location = PostProcessEffectLocation.Default; + _shader = FlaxEngine.Content.LoadAsyncInternal("Shaders/Editor/Grid"); } ~Renderer() { - if (_debugDrawContext != IntPtr.Zero) - { - DebugDraw.FreeContext(_debugDrawContext); - _debugDrawContext = IntPtr.Zero; - } + Destroy(ref _psGrid); + Destroy(ref _vertexBuffer); + Destroy(ref _indexBuffer); + _shader = null; } - public override void Render(GPUContext context, ref RenderContext renderContext, GPUTexture input, GPUTexture output) + public override unsafe void Render(GPUContext context, ref RenderContext renderContext, GPUTexture input, GPUTexture output) { + if (_shader == null) + return; Profiler.BeginEventGPU("Editor Grid"); - if (_debugDrawContext == IntPtr.Zero) - _debugDrawContext = DebugDraw.AllocateContext(); - DebugDraw.SetContext(_debugDrawContext); - DebugDraw.UpdateContext(_debugDrawContext, 1.0f / Mathf.Max(Engine.FramesPerSecond, 1)); - - var viewPos = (Vector3)renderContext.View.Position; - var plane = new Plane(Vector3.Zero, Vector3.UnitY); - var dst = CollisionsHelper.DistancePlanePoint(ref plane, ref viewPos); - var options = Editor.Instance.Options.Options; - float space = options.Viewport.ViewportGridScale, size; - if (dst <= 500.0f) + Vector3 camPos = renderContext.View.WorldPosition; + float gridSize = renderContext.View.Far + 20000; + + // Lazy-init resources + if (_vertexBuffer == null) { - size = 8000; + _vertexBuffer = new GPUBuffer(); + var desc = GPUBufferDescription.Vertex(sizeof(Float3), 4); + _vertexBuffer.Init(ref desc); } - else if (dst <= 2000.0f) + if (_indexBuffer == null) { - space *= 2; - size = 8000; + _indexBuffer = new GPUBuffer(); + fixed (uint* ptr = _triangles) + { + var desc = GPUBufferDescription.Index(sizeof(uint), _triangles.Length, new IntPtr(ptr)); + _indexBuffer.Init(ref desc); + } } - else + if (_psGrid == null) { - space *= 20; - size = 100000; + _psGrid = new GPUPipelineState(); + var desc = GPUPipelineState.Description.Default; + desc.BlendMode = BlendingMode.AlphaBlend; + desc.CullMode = CullMode.TwoSided; + desc.VS = _shader.GPU.GetVS("VS_Grid"); + desc.PS = _shader.GPU.GetPS("PS_Grid"); + _psGrid.Init(ref desc); } - float bigLineIntensity = 0.8f; - Color bigColor = Color.Gray * bigLineIntensity; - Color color = bigColor * 0.8f; - int count = (int)(size / space); - int midLine = count / 2; - int bigLinesMod = count / 8; - - Vector3 start = new Vector3(0, 0, size * -0.5f); - Vector3 end = new Vector3(0, 0, size * 0.5f); - - for (int i = 0; i <= count; i++) + // Update vertices of the plane + // TODO: perf this operation in a Vertex Shader + var vertices = new Float3[] { - start.X = end.X = i * space + start.Z; - Color lineColor = color; - if (i == midLine) - lineColor = Color.Blue * bigLineIntensity; - else if (i % bigLinesMod == 0) - lineColor = bigColor; - DebugDraw.DrawLine(start, end, lineColor); + new Float3(-gridSize + camPos.X, 0, -gridSize + camPos.Z), + new Float3(gridSize + camPos.X, 0, gridSize + camPos.Z), + new Float3(-gridSize + camPos.X, 0, gridSize + camPos.Z), + new Float3(gridSize + camPos.X, 0, -gridSize + camPos.Z), + }; + fixed (Float3* ptr = vertices) + { + context.UpdateBuffer(_vertexBuffer, new IntPtr(ptr), (uint)(sizeof(Float3) * vertices.Length)); } - start = new Vector3(size * -0.5f, 0, 0); - end = new Vector3(size * 0.5f, 0, 0); - - for (int i = 0; i <= count; i++) + // Update constant buffer data + var cb = _shader.GPU.GetCB(0); + if (cb != IntPtr.Zero) { - start.Z = end.Z = i * space + start.X; - Color lineColor = color; - if (i == midLine) - lineColor = Color.Red * bigLineIntensity; - else if (i % bigLinesMod == 0) - lineColor = bigColor; - DebugDraw.DrawLine(start, end, lineColor); + var data = new Data(); + Matrix.Multiply(ref renderContext.View.View, ref renderContext.View.Projection, out var viewProjection); + data.WorldMatrix = Matrix.Identity; + Matrix.Transpose(ref viewProjection, out data.ViewProjectionMatrix); + data.ViewPos = renderContext.View.WorldPosition; + data.GridColor = options.Viewport.ViewportGridColor; + data.Far = renderContext.View.Far; + data.GridSize = options.Viewport.ViewportGridViewDistance; + context.UpdateCB(cb, new IntPtr(&data)); } - DebugDraw.Draw(ref renderContext, input.View(), null, true); - DebugDraw.SetContext(IntPtr.Zero); + // Draw geometry using custom Pixel Shader and Vertex Shader + context.BindCB(0, cb); + context.BindIB(_indexBuffer); + _vbs[0] = _vertexBuffer; + context.BindVB(_vbs); + context.SetState(_psGrid); + context.SetRenderTarget(renderContext.Buffers.DepthBuffer.View(), input.View()); + context.DrawIndexed((uint)_triangles.Length); Profiler.EndEventGPU(); } diff --git a/Source/Editor/Options/ViewportOptions.cs b/Source/Editor/Options/ViewportOptions.cs index 9b9705a0e..454315b91 100644 --- a/Source/Editor/Options/ViewportOptions.cs +++ b/Source/Editor/Options/ViewportOptions.cs @@ -129,5 +129,19 @@ namespace FlaxEditor.Options [DefaultValue(50.0f), Limit(25.0f, 500.0f, 5.0f)] [EditorDisplay("Defaults"), EditorOrder(220), Tooltip("The default editor viewport grid scale.")] public float ViewportGridScale { get; set; } = 50.0f; + + /// + /// Gets or sets the view distance you can see the grid. + /// + [DefaultValue(2500.0f)] + [EditorDisplay("Grid"), EditorOrder(300), Tooltip("The maximum distance you will be able to see the grid.")] + public float ViewportGridViewDistance { get; set; } = 2500.0f; + + /// + /// Gets or sets the grid color. + /// + [DefaultValue(typeof(Color), "0.5,0.5,0.5,1.0")] + [EditorDisplay("Grid"), EditorOrder(310), Tooltip("The color for the viewport grid.")] + public Color ViewportGridColor { get; set; } = new Color(0.5f, 0.5f, 0.5f, 1.0f); } } diff --git a/Source/Shaders/Editor/Grid.shader b/Source/Shaders/Editor/Grid.shader new file mode 100644 index 000000000..f8b0b0b81 --- /dev/null +++ b/Source/Shaders/Editor/Grid.shader @@ -0,0 +1,151 @@ +// Implementation based on: +// "The Best Darn Grid Shader (Yet)", Medium, Oct 2023 +// Ben Golus +// https://bgolus.medium.com/the-best-darn-grid-shader-yet-727f9278b9d8#3e73 + +#define USE_FORWARD true; + +#include "./Flax/Common.hlsl" + +META_CB_BEGIN(0, Data) +float4x4 WorldMatrix; +float4x4 ViewProjectionMatrix; +float4 GridColor; +float3 ViewPos; +float Far; +float3 Padding; +float GridSize; +META_CB_END + +// Geometry data passed to the vertex shader +struct ModelInput +{ + float3 Position : POSITION; +}; + +// Interpolants passed from the vertex shader +struct VertexOutput +{ + float4 Position : SV_Position; + float2 TexCoord : TEXCOORD0; + float3 WorldPosition : TEXCOORD1; +}; + +// Interpolants passed to the pixel shader +struct PixelInput +{ + float4 Position : SV_Position; + noperspective float2 TexCoord : TEXCOORD0; + float3 WorldPosition : TEXCOORD1; +}; + +// Vertex shader function for grid rendering +META_VS(true, FEATURE_LEVEL_ES2) +META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 1, ALIGN, PER_VERTEX, 0, true) +VertexOutput VS_Grid(ModelInput input) +{ + VertexOutput output; + output.WorldPosition = mul(float4(input.Position.xyz, 1), WorldMatrix).xyz; + output.Position = mul(float4(input.Position.xyz, 1), ViewProjectionMatrix); + return output; +} + +float invLerp(float from, float to, float value) +{ + return (value - from) / (to - from); +} + +float remap(float origFrom, float origTo, float targetFrom, float targetTo, float value) +{ + float rel = invLerp(origFrom, origTo, value); + return lerp(targetFrom, targetTo, rel); +} + +float ddLength(float a) +{ + return length(float2(ddx(a), ddy(a))); +} + +float GetLine(float pos, float scale, float thickness) +{ + float lineWidth = thickness; + float coord = (pos * 0.01) * scale; + + float2 uvDDXY = float2(ddx(coord), ddy(coord)); + + float deriv = float(length(uvDDXY.xy)); + float drawWidth = clamp(lineWidth, deriv, 0.5); + float lineAA = deriv * 1.5; + float gridUV = abs(coord); + float grid2 = smoothstep(drawWidth + lineAA, drawWidth - lineAA, gridUV); + grid2 *= saturate(lineWidth / drawWidth); + grid2 = lerp(grid2, lineWidth, saturate(deriv * 2.0 - 1.0)); + + float grid = lerp(grid2, 1.0, grid2); + return grid; +} + +float GetGrid(float3 pos, float scale, float thickness) +{ + float lineWidth = thickness; + float2 coord = (pos.xz * 0.01) * scale; + + float4 uvDDXY = float4(ddx(coord), ddy(coord)); + + float2 deriv = float2(length(uvDDXY.xz), length(uvDDXY.yw)); + float2 drawWidth = clamp(lineWidth, deriv, 0.5); + float2 lineAA = deriv * 1.5; + float2 gridUV = 1.0 - abs(frac(coord) * 2.0 - 1.0); + float2 grid2 = smoothstep(drawWidth + lineAA, drawWidth - lineAA, gridUV); + grid2 *= saturate(lineWidth / drawWidth); + grid2 = lerp(grid2, lineWidth, saturate(deriv * 2.0 - 1.0)); + + float grid = lerp(grid2.x, 1.0, grid2.y); + return grid; +} + +float4 GetColor(float3 pos, float scale) +{ + float dist = 1 - saturate(distance(float3(ViewPos.x, 0, ViewPos.z), pos) / GridSize); + + // Line width + float g1LW = 0.01; + // Major line Z + float l1 = GetLine(pos.x, 1, g1LW * 2); + // Major line X + float l2 = GetLine(pos.z, 1, g1LW); + + // Main grid + float g1 = GetGrid(pos, 1, g1LW * 0.8); + float g2 = GetGrid(pos, 2, g1LW * 0.4); + float g3 = GetGrid(pos, 0.1, g1LW * 2); + + float camFadeLarge = clamp(invLerp(2500, 4000, abs(ViewPos.y)),0, g3); + g3 *= camFadeLarge; + + float g4 = GetGrid(pos, 10, g1LW); + float camFadeTiny = clamp(invLerp(150, 100, abs(ViewPos.y)), 0, g4); + g4 *= camFadeTiny; + + float grid = 0; + grid = max(l1, l2); + grid = max(grid, g1); + grid = max(grid, g2); + grid = max(grid, g3); + grid = max(grid, g4); + + float4 color = grid * GridColor; + color = lerp(color, float4(1,0,0,1), l2); + color = lerp(color, float4(0,0,1,1), l1); + color *= dist; + return color; +} + +// Pixel shader function for grid rendering +META_PS(true, FEATURE_LEVEL_ES2) +float4 PS_Grid(PixelInput input) : SV_Target +{ + float4 color = GetColor(input.WorldPosition, 1); + return color; +}