mirror of
https://github.com/sampletext32/ParkanPlayground.git
synced 2026-08-15 15:07:48 +04:00
gl viewport
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace NResUI.Rendering.Viewport.Meshes;
|
||||
|
||||
public sealed class GpuMesh
|
||||
{
|
||||
private readonly GL _gl;
|
||||
|
||||
public uint VertexArrayObject { get; }
|
||||
public uint VertexBufferObject { get; }
|
||||
public uint ElementBufferObject { get; }
|
||||
public uint IndexCount { get; }
|
||||
public PrimitiveType PrimitiveType { get; }
|
||||
|
||||
public GpuMesh(
|
||||
GL gl,
|
||||
uint vertexArrayObject,
|
||||
uint vertexBufferObject,
|
||||
uint elementBufferObject,
|
||||
uint indexCount,
|
||||
PrimitiveType primitiveType = PrimitiveType.Triangles)
|
||||
{
|
||||
_gl = gl;
|
||||
VertexArrayObject = vertexArrayObject;
|
||||
VertexBufferObject = vertexBufferObject;
|
||||
ElementBufferObject = elementBufferObject;
|
||||
IndexCount = indexCount;
|
||||
PrimitiveType = primitiveType;
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
_gl.BindVertexArray(VertexArrayObject);
|
||||
_gl.DrawElements(PrimitiveType, IndexCount, DrawElementsType.UnsignedInt, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace NResUI.Rendering.Viewport.Meshes;
|
||||
|
||||
public static unsafe class PrimitiveMeshes
|
||||
{
|
||||
public static GpuMesh CreateCube(GL gl)
|
||||
{
|
||||
// position.xyz, color.rgb
|
||||
float[] vertices =
|
||||
{
|
||||
// front
|
||||
-1, -1, 1, 1, 0, 0,
|
||||
1, -1, 1, 0, 1, 0,
|
||||
1, 1, 1, 0, 0, 1,
|
||||
-1, 1, 1, 1, 1, 0,
|
||||
|
||||
// back
|
||||
-1, -1, -1, 1, 0, 1,
|
||||
1, -1, -1, 0, 1, 1,
|
||||
1, 1, -1, 1, 1, 1,
|
||||
-1, 1, -1, 0.5f, 0.5f, 0.5f,
|
||||
};
|
||||
|
||||
uint[] indices =
|
||||
{
|
||||
// front
|
||||
0, 1, 2,
|
||||
2, 3, 0,
|
||||
|
||||
// right
|
||||
1, 5, 6,
|
||||
6, 2, 1,
|
||||
|
||||
// back
|
||||
5, 4, 7,
|
||||
7, 6, 5,
|
||||
|
||||
// left
|
||||
4, 0, 3,
|
||||
3, 7, 4,
|
||||
|
||||
// top
|
||||
3, 2, 6,
|
||||
6, 7, 3,
|
||||
|
||||
// bottom
|
||||
4, 5, 1,
|
||||
1, 0, 4,
|
||||
};
|
||||
|
||||
return CreateIndexedMesh(gl, vertices, indices, PrimitiveType.Triangles);
|
||||
}
|
||||
|
||||
public static GpuMesh CreateWorldGrid(
|
||||
GL gl,
|
||||
int halfExtent = 20,
|
||||
int minorStep = 1,
|
||||
int majorStep = 5)
|
||||
{
|
||||
if (halfExtent <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(halfExtent));
|
||||
|
||||
if (minorStep <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(minorStep));
|
||||
|
||||
if (majorStep <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(majorStep));
|
||||
|
||||
var vertices = new List<float>();
|
||||
var indices = new List<uint>();
|
||||
|
||||
void AddVertex(float x, float y, float z, float r, float g, float b)
|
||||
{
|
||||
vertices.Add(x);
|
||||
vertices.Add(y);
|
||||
vertices.Add(z);
|
||||
vertices.Add(r);
|
||||
vertices.Add(g);
|
||||
vertices.Add(b);
|
||||
}
|
||||
|
||||
void AddLine(
|
||||
float x0, float y0, float z0,
|
||||
float x1, float y1, float z1,
|
||||
float r, float g, float b)
|
||||
{
|
||||
var startIndex = (uint)(vertices.Count / 6);
|
||||
|
||||
AddVertex(x0, y0, z0, r, g, b);
|
||||
AddVertex(x1, y1, z1, r, g, b);
|
||||
|
||||
indices.Add(startIndex);
|
||||
indices.Add(startIndex + 1);
|
||||
}
|
||||
|
||||
const float y = 0.0f;
|
||||
|
||||
for (var i = -halfExtent; i <= halfExtent; i += minorStep)
|
||||
{
|
||||
var isAxis = i == 0;
|
||||
var isMajor = i % majorStep == 0;
|
||||
|
||||
var brightness = isMajor ? 0.38f : 0.24f;
|
||||
var r = brightness;
|
||||
var g = brightness;
|
||||
var b = brightness;
|
||||
|
||||
// Lines parallel to X. The Z axis itself is blue.
|
||||
if (isAxis)
|
||||
{
|
||||
AddLine(-halfExtent, y, i, halfExtent, y, i, 0.25f, 0.45f, 1.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddLine(-halfExtent, y, i, halfExtent, y, i, r, g, b);
|
||||
}
|
||||
|
||||
// Lines parallel to Z. The X axis itself is red.
|
||||
if (isAxis)
|
||||
{
|
||||
AddLine(i, y, -halfExtent, i, y, halfExtent, 1.0f, 0.25f, 0.25f);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddLine(i, y, -halfExtent, i, y, halfExtent, r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
return CreateIndexedMesh(gl, vertices.ToArray(), indices.ToArray(), PrimitiveType.Lines);
|
||||
}
|
||||
|
||||
private static GpuMesh CreateIndexedMesh(
|
||||
GL gl,
|
||||
float[] vertices,
|
||||
uint[] indices,
|
||||
PrimitiveType primitiveType)
|
||||
{
|
||||
var vao = gl.GenVertexArray();
|
||||
var vbo = gl.GenBuffer();
|
||||
var ebo = gl.GenBuffer();
|
||||
|
||||
gl.BindVertexArray(vao);
|
||||
|
||||
gl.BindBuffer(BufferTargetARB.ArrayBuffer, vbo);
|
||||
fixed (float* vertexPtr = vertices)
|
||||
{
|
||||
gl.BufferData(
|
||||
BufferTargetARB.ArrayBuffer,
|
||||
(nuint)(vertices.Length * sizeof(float)),
|
||||
vertexPtr,
|
||||
BufferUsageARB.StaticDraw);
|
||||
}
|
||||
|
||||
gl.BindBuffer(BufferTargetARB.ElementArrayBuffer, ebo);
|
||||
fixed (uint* indexPtr = indices)
|
||||
{
|
||||
gl.BufferData(
|
||||
BufferTargetARB.ElementArrayBuffer,
|
||||
(nuint)(indices.Length * sizeof(uint)),
|
||||
indexPtr,
|
||||
BufferUsageARB.StaticDraw);
|
||||
}
|
||||
|
||||
const uint stride = 6 * sizeof(float);
|
||||
|
||||
gl.EnableVertexAttribArray(0);
|
||||
gl.VertexAttribPointer(
|
||||
0,
|
||||
3,
|
||||
VertexAttribPointerType.Float,
|
||||
false,
|
||||
stride,
|
||||
null);
|
||||
|
||||
gl.EnableVertexAttribArray(1);
|
||||
gl.VertexAttribPointer(
|
||||
1,
|
||||
3,
|
||||
VertexAttribPointerType.Float,
|
||||
false,
|
||||
stride,
|
||||
(void*)(3 * sizeof(float)));
|
||||
|
||||
gl.BindVertexArray(0);
|
||||
|
||||
return new GpuMesh(gl, vao, vbo, ebo, (uint)indices.Length, primitiveType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace NResUI.Rendering.Viewport.OpenGL;
|
||||
|
||||
public sealed class MsaaFramebuffer
|
||||
{
|
||||
private readonly GL _gl;
|
||||
private readonly uint _samples;
|
||||
|
||||
private uint _msaaFramebuffer;
|
||||
private uint _msaaColorRenderbuffer;
|
||||
private uint _msaaDepthStencilRenderbuffer;
|
||||
|
||||
private uint _resolveFramebuffer;
|
||||
private uint _resolveColorTexture;
|
||||
|
||||
private int _width;
|
||||
private int _height;
|
||||
|
||||
public uint ColorTexture => _resolveColorTexture;
|
||||
|
||||
public MsaaFramebuffer(GL gl, uint samples = 4)
|
||||
{
|
||||
_gl = gl;
|
||||
_samples = samples;
|
||||
}
|
||||
|
||||
public void EnsureSize(int width, int height)
|
||||
{
|
||||
width = Math.Max(1, width);
|
||||
height = Math.Max(1, height);
|
||||
|
||||
if (_msaaFramebuffer != 0 && _width == width && _height == height)
|
||||
return;
|
||||
|
||||
_width = width;
|
||||
_height = height;
|
||||
|
||||
if (_msaaFramebuffer == 0)
|
||||
{
|
||||
_msaaFramebuffer = _gl.GenFramebuffer();
|
||||
_msaaColorRenderbuffer = _gl.GenRenderbuffer();
|
||||
_msaaDepthStencilRenderbuffer = _gl.GenRenderbuffer();
|
||||
|
||||
_resolveFramebuffer = _gl.GenFramebuffer();
|
||||
_resolveColorTexture = _gl.GenTexture();
|
||||
}
|
||||
|
||||
_gl.BindRenderbuffer(RenderbufferTarget.Renderbuffer, _msaaColorRenderbuffer);
|
||||
_gl.RenderbufferStorageMultisample(
|
||||
RenderbufferTarget.Renderbuffer,
|
||||
_samples,
|
||||
InternalFormat.Rgba8,
|
||||
(uint)width,
|
||||
(uint)height);
|
||||
|
||||
_gl.BindRenderbuffer(RenderbufferTarget.Renderbuffer, _msaaDepthStencilRenderbuffer);
|
||||
_gl.RenderbufferStorageMultisample(
|
||||
RenderbufferTarget.Renderbuffer,
|
||||
_samples,
|
||||
InternalFormat.Depth24Stencil8,
|
||||
(uint)width,
|
||||
(uint)height);
|
||||
|
||||
_gl.BindFramebuffer(FramebufferTarget.Framebuffer, _msaaFramebuffer);
|
||||
|
||||
_gl.FramebufferRenderbuffer(
|
||||
FramebufferTarget.Framebuffer,
|
||||
FramebufferAttachment.ColorAttachment0,
|
||||
RenderbufferTarget.Renderbuffer,
|
||||
_msaaColorRenderbuffer);
|
||||
|
||||
_gl.FramebufferRenderbuffer(
|
||||
FramebufferTarget.Framebuffer,
|
||||
FramebufferAttachment.DepthStencilAttachment,
|
||||
RenderbufferTarget.Renderbuffer,
|
||||
_msaaDepthStencilRenderbuffer);
|
||||
|
||||
var msaaStatus = _gl.CheckFramebufferStatus(FramebufferTarget.Framebuffer);
|
||||
if (msaaStatus != GLEnum.FramebufferComplete)
|
||||
throw new InvalidOperationException($"MSAA viewport framebuffer is incomplete: {msaaStatus}");
|
||||
|
||||
_gl.BindTexture(TextureTarget.Texture2D, _resolveColorTexture);
|
||||
_gl.TexImage2D(
|
||||
TextureTarget.Texture2D,
|
||||
0,
|
||||
InternalFormat.Rgba8,
|
||||
(uint)width,
|
||||
(uint)height,
|
||||
0,
|
||||
PixelFormat.Rgba,
|
||||
PixelType.UnsignedByte,
|
||||
null);
|
||||
|
||||
_gl.TexParameter(
|
||||
TextureTarget.Texture2D,
|
||||
TextureParameterName.TextureMinFilter,
|
||||
(int)TextureMinFilter.Linear);
|
||||
|
||||
_gl.TexParameter(
|
||||
TextureTarget.Texture2D,
|
||||
TextureParameterName.TextureMagFilter,
|
||||
(int)TextureMagFilter.Linear);
|
||||
|
||||
_gl.TexParameter(
|
||||
TextureTarget.Texture2D,
|
||||
TextureParameterName.TextureWrapS,
|
||||
(int)TextureWrapMode.ClampToEdge);
|
||||
|
||||
_gl.TexParameter(
|
||||
TextureTarget.Texture2D,
|
||||
TextureParameterName.TextureWrapT,
|
||||
(int)TextureWrapMode.ClampToEdge);
|
||||
|
||||
_gl.BindFramebuffer(FramebufferTarget.Framebuffer, _resolveFramebuffer);
|
||||
|
||||
_gl.FramebufferTexture2D(
|
||||
FramebufferTarget.Framebuffer,
|
||||
FramebufferAttachment.ColorAttachment0,
|
||||
TextureTarget.Texture2D,
|
||||
_resolveColorTexture,
|
||||
0);
|
||||
|
||||
var resolveStatus = _gl.CheckFramebufferStatus(FramebufferTarget.Framebuffer);
|
||||
if (resolveStatus != GLEnum.FramebufferComplete)
|
||||
throw new InvalidOperationException($"Resolve viewport framebuffer is incomplete: {resolveStatus}");
|
||||
|
||||
_gl.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
|
||||
_gl.BindTexture(TextureTarget.Texture2D, 0);
|
||||
_gl.BindRenderbuffer(RenderbufferTarget.Renderbuffer, 0);
|
||||
}
|
||||
|
||||
public void BindForRender()
|
||||
{
|
||||
_gl.BindFramebuffer(FramebufferTarget.Framebuffer, _msaaFramebuffer);
|
||||
_gl.Viewport(0, 0, (uint)_width, (uint)_height);
|
||||
}
|
||||
|
||||
public void Resolve()
|
||||
{
|
||||
_gl.BindFramebuffer(FramebufferTarget.ReadFramebuffer, _msaaFramebuffer);
|
||||
_gl.BindFramebuffer(FramebufferTarget.DrawFramebuffer, _resolveFramebuffer);
|
||||
|
||||
_gl.BlitFramebuffer(
|
||||
0, 0, _width, _height,
|
||||
0, 0, _width, _height,
|
||||
ClearBufferMask.ColorBufferBit,
|
||||
BlitFramebufferFilter.Nearest);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using System.Numerics;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace NResUI.Rendering.Viewport.OpenGL;
|
||||
|
||||
public sealed unsafe class ShaderProgram
|
||||
{
|
||||
private readonly GL _gl;
|
||||
|
||||
public uint Handle { get; }
|
||||
|
||||
public ShaderProgram(GL gl, string vertexShaderSource, string fragmentShaderSource, string debugName)
|
||||
{
|
||||
_gl = gl;
|
||||
|
||||
var vertexShader = CompileShader(ShaderType.VertexShader, vertexShaderSource, debugName);
|
||||
var fragmentShader = CompileShader(ShaderType.FragmentShader, fragmentShaderSource, debugName);
|
||||
|
||||
Handle = _gl.CreateProgram();
|
||||
_gl.AttachShader(Handle, vertexShader);
|
||||
_gl.AttachShader(Handle, fragmentShader);
|
||||
_gl.LinkProgram(Handle);
|
||||
|
||||
_gl.GetProgram(Handle, ProgramPropertyARB.LinkStatus, out var linked);
|
||||
if (linked == 0)
|
||||
{
|
||||
var log = _gl.GetProgramInfoLog(Handle);
|
||||
throw new InvalidOperationException($"{debugName} shader link failed: {log}");
|
||||
}
|
||||
|
||||
_gl.DetachShader(Handle, vertexShader);
|
||||
_gl.DetachShader(Handle, fragmentShader);
|
||||
_gl.DeleteShader(vertexShader);
|
||||
_gl.DeleteShader(fragmentShader);
|
||||
}
|
||||
|
||||
public void Use()
|
||||
{
|
||||
_gl.UseProgram(Handle);
|
||||
}
|
||||
|
||||
public int GetUniformLocation(string name)
|
||||
{
|
||||
return _gl.GetUniformLocation(Handle, name);
|
||||
}
|
||||
|
||||
public void SetMatrix4(int location, Matrix4x4 matrix)
|
||||
{
|
||||
_gl.UniformMatrix4(location, 1, false, (float*)&matrix);
|
||||
}
|
||||
|
||||
public void SetVector4(int location, Vector4 value)
|
||||
{
|
||||
_gl.Uniform4(location, value.X, value.Y, value.Z, value.W);
|
||||
}
|
||||
|
||||
private uint CompileShader(ShaderType type, string source, string debugName)
|
||||
{
|
||||
var shader = _gl.CreateShader(type);
|
||||
_gl.ShaderSource(shader, source);
|
||||
_gl.CompileShader(shader);
|
||||
|
||||
_gl.GetShader(shader, ShaderParameterName.CompileStatus, out var compiled);
|
||||
if (compiled == 0)
|
||||
{
|
||||
var log = _gl.GetShaderInfoLog(shader);
|
||||
throw new InvalidOperationException($"{debugName} {type} compile failed: {log}");
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace NResUI.Rendering.Viewport;
|
||||
|
||||
public sealed class ViewportCamera
|
||||
{
|
||||
public float YawDegrees { get; set; } = 35.0f;
|
||||
public float PitchDegrees { get; set; } = 25.0f;
|
||||
public float Distance { get; set; } = 4.0f;
|
||||
|
||||
public Matrix4x4 GetSceneRotationMatrix()
|
||||
{
|
||||
return Matrix4x4.CreateRotationY(ToRadians(YawDegrees)) *
|
||||
Matrix4x4.CreateRotationX(ToRadians(PitchDegrees));
|
||||
}
|
||||
|
||||
public Matrix4x4 GetViewMatrix()
|
||||
{
|
||||
return Matrix4x4.CreateLookAt(
|
||||
new Vector3(0.0f, 0.0f, Distance),
|
||||
Vector3.Zero,
|
||||
Vector3.UnitY);
|
||||
}
|
||||
|
||||
public Matrix4x4 GetProjectionMatrix(float aspectRatio)
|
||||
{
|
||||
return Matrix4x4.CreatePerspectiveFieldOfView(
|
||||
ToRadians(60.0f),
|
||||
aspectRatio,
|
||||
0.01f,
|
||||
100.0f);
|
||||
}
|
||||
|
||||
public static float ToRadians(float degrees)
|
||||
{
|
||||
return degrees * MathF.PI / 180.0f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.Numerics;
|
||||
using NResUI.Rendering.Viewport.Meshes;
|
||||
|
||||
namespace NResUI.Rendering.Viewport;
|
||||
|
||||
public sealed class ViewportGrid
|
||||
{
|
||||
public GpuMesh Mesh { get; }
|
||||
public bool IsVisible { get; set; } = true;
|
||||
public Matrix4x4 LocalTransform { get; set; } = Matrix4x4.Identity;
|
||||
|
||||
public ViewportGrid(GpuMesh mesh)
|
||||
{
|
||||
Mesh = mesh;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Numerics;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace NResUI.Rendering.Viewport;
|
||||
|
||||
public sealed class ViewportInputController
|
||||
{
|
||||
private const float MouseRotationSpeed = 0.25f;
|
||||
private const float MouseZoomSpeed = 0.35f;
|
||||
|
||||
private bool _isRotatingViewport;
|
||||
|
||||
public void Handle(
|
||||
ViewportScene scene,
|
||||
ViewportCamera camera,
|
||||
bool viewportHovered,
|
||||
Vector2 imageMin,
|
||||
Vector2 imageSize)
|
||||
{
|
||||
var io = ImGui.GetIO();
|
||||
|
||||
if (viewportHovered && ImGui.IsMouseClicked(ImGuiMouseButton.Middle))
|
||||
_isRotatingViewport = true;
|
||||
|
||||
if (!ImGui.IsMouseDown(ImGuiMouseButton.Middle))
|
||||
_isRotatingViewport = false;
|
||||
|
||||
if (_isRotatingViewport)
|
||||
{
|
||||
camera.YawDegrees += io.MouseDelta.X * MouseRotationSpeed;
|
||||
camera.PitchDegrees += io.MouseDelta.Y * MouseRotationSpeed;
|
||||
camera.PitchDegrees = Math.Clamp(camera.PitchDegrees, -89.0f, 89.0f);
|
||||
}
|
||||
|
||||
if (viewportHovered && Math.Abs(io.MouseWheel) > float.Epsilon)
|
||||
{
|
||||
camera.Distance -= io.MouseWheel * MouseZoomSpeed;
|
||||
camera.Distance = Math.Clamp(camera.Distance, 1.5f, 40.0f);
|
||||
}
|
||||
|
||||
if (viewportHovered && ImGui.IsMouseClicked(ImGuiMouseButton.Left))
|
||||
{
|
||||
var mouse = ImGui.GetMousePos();
|
||||
var localMouse = mouse - imageMin;
|
||||
|
||||
scene.SelectedPieceId = ViewportSelection.PickPiece(
|
||||
scene,
|
||||
camera,
|
||||
localMouse,
|
||||
imageSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Numerics;
|
||||
using NResUI.Rendering.Viewport.Meshes;
|
||||
|
||||
namespace NResUI.Rendering.Viewport;
|
||||
|
||||
public sealed class ViewportPiece
|
||||
{
|
||||
public int Id { get; }
|
||||
public string Name { get; }
|
||||
public GpuMesh Mesh { get; }
|
||||
|
||||
public Matrix4x4 LocalTransform { get; set; }
|
||||
|
||||
public Vector3 BoundsMin { get; }
|
||||
public Vector3 BoundsMax { get; }
|
||||
|
||||
public ViewportPiece(
|
||||
int id,
|
||||
string name,
|
||||
GpuMesh mesh,
|
||||
Matrix4x4 localTransform,
|
||||
Vector3 boundsMin,
|
||||
Vector3 boundsMax)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
Mesh = mesh;
|
||||
LocalTransform = localTransform;
|
||||
BoundsMin = boundsMin;
|
||||
BoundsMax = boundsMax;
|
||||
}
|
||||
|
||||
public static ViewportPiece CreateUnitCube(int id, string name, GpuMesh mesh)
|
||||
{
|
||||
return new ViewportPiece(
|
||||
id,
|
||||
name,
|
||||
mesh,
|
||||
Matrix4x4.Identity,
|
||||
new Vector3(-1.0f, -1.0f, -1.0f),
|
||||
new Vector3(1.0f, 1.0f, 1.0f));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
using System.Numerics;
|
||||
using NResUI.Rendering.Viewport.OpenGL;
|
||||
using Silk.NET.OpenGL;
|
||||
using Silk.NET.Windowing;
|
||||
|
||||
namespace NResUI.Rendering.Viewport;
|
||||
|
||||
public sealed class ViewportRenderer
|
||||
{
|
||||
private readonly GL _gl;
|
||||
private readonly IWindow _window;
|
||||
private readonly MsaaFramebuffer _framebuffer;
|
||||
|
||||
private ShaderProgram? _meshShader;
|
||||
private ShaderProgram? _outlineShader;
|
||||
|
||||
private int _meshMvpLocation;
|
||||
private int _outlineMvpLocation;
|
||||
private int _outlineColorLocation;
|
||||
|
||||
private bool _initialized;
|
||||
|
||||
public ViewportRenderer(GL gl, IWindow window)
|
||||
{
|
||||
_gl = gl;
|
||||
_window = window;
|
||||
_framebuffer = new MsaaFramebuffer(gl, samples: 4);
|
||||
}
|
||||
|
||||
public uint Render(int width, int height, ViewportScene scene, ViewportCamera camera)
|
||||
{
|
||||
EnsureInitialized();
|
||||
|
||||
width = Math.Max(1, width);
|
||||
height = Math.Max(1, height);
|
||||
|
||||
_framebuffer.EnsureSize(width, height);
|
||||
_framebuffer.BindForRender();
|
||||
|
||||
_gl.Enable(EnableCap.DepthTest);
|
||||
_gl.Enable(EnableCap.CullFace);
|
||||
_gl.CullFace(TriangleFace.Back);
|
||||
|
||||
_gl.ClearColor(0.12f, 0.13f, 0.15f, 1.0f);
|
||||
_gl.ClearStencil(0);
|
||||
_gl.Clear((uint)(
|
||||
ClearBufferMask.ColorBufferBit |
|
||||
ClearBufferMask.DepthBufferBit |
|
||||
ClearBufferMask.StencilBufferBit));
|
||||
|
||||
var aspect = width / (float)Math.Max(1, height);
|
||||
var sceneRotation = camera.GetSceneRotationMatrix();
|
||||
var view = camera.GetViewMatrix();
|
||||
var projection = camera.GetProjectionMatrix(aspect);
|
||||
|
||||
DrawGrid(scene, sceneRotation, view, projection);
|
||||
DrawScene(scene, sceneRotation, view, projection);
|
||||
|
||||
_framebuffer.Resolve();
|
||||
|
||||
_gl.Disable(EnableCap.CullFace);
|
||||
_gl.Disable(EnableCap.DepthTest);
|
||||
_gl.Disable(EnableCap.StencilTest);
|
||||
_gl.UseProgram(0);
|
||||
_gl.BindVertexArray(0);
|
||||
_gl.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
|
||||
|
||||
var framebufferSize = _window.FramebufferSize;
|
||||
_gl.Viewport(0, 0, (uint)framebufferSize.X, (uint)framebufferSize.Y);
|
||||
|
||||
return _framebuffer.ColorTexture;
|
||||
}
|
||||
|
||||
|
||||
private void DrawGrid(
|
||||
ViewportScene scene,
|
||||
Matrix4x4 sceneRotation,
|
||||
Matrix4x4 view,
|
||||
Matrix4x4 projection)
|
||||
{
|
||||
if (_meshShader == null)
|
||||
throw new InvalidOperationException("Viewport mesh shader is not initialized.");
|
||||
|
||||
var grid = scene.Grid;
|
||||
if (grid == null || !grid.IsVisible)
|
||||
return;
|
||||
|
||||
var model = grid.LocalTransform * sceneRotation;
|
||||
var mvp = model * view * projection;
|
||||
|
||||
_gl.Disable(EnableCap.StencilTest);
|
||||
_gl.Disable(EnableCap.CullFace);
|
||||
|
||||
_meshShader.Use();
|
||||
_meshShader.SetMatrix4(_meshMvpLocation, mvp);
|
||||
grid.Mesh.Draw();
|
||||
|
||||
_gl.Enable(EnableCap.CullFace);
|
||||
}
|
||||
|
||||
private void DrawScene(
|
||||
ViewportScene scene,
|
||||
Matrix4x4 sceneRotation,
|
||||
Matrix4x4 view,
|
||||
Matrix4x4 projection)
|
||||
{
|
||||
if (_meshShader == null || _outlineShader == null)
|
||||
throw new InvalidOperationException("Viewport renderer is not initialized.");
|
||||
|
||||
_gl.Disable(EnableCap.StencilTest);
|
||||
_meshShader.Use();
|
||||
|
||||
foreach (var piece in scene.Pieces)
|
||||
{
|
||||
if (piece.Id == scene.SelectedPieceId)
|
||||
continue;
|
||||
|
||||
DrawPiece(piece, sceneRotation, view, projection);
|
||||
}
|
||||
|
||||
var selectedPiece = scene.SelectedPiece;
|
||||
if (selectedPiece == null)
|
||||
return;
|
||||
|
||||
_gl.Enable(EnableCap.StencilTest);
|
||||
_gl.StencilMask(0xFF);
|
||||
_gl.StencilFunc(StencilFunction.Always, 1, 0xFF);
|
||||
_gl.StencilOp(StencilOp.Keep, StencilOp.Keep, StencilOp.Replace);
|
||||
|
||||
_meshShader.Use();
|
||||
DrawPiece(selectedPiece, sceneRotation, view, projection);
|
||||
|
||||
_gl.StencilFunc(StencilFunction.Notequal, 1, 0xFF);
|
||||
_gl.StencilMask(0x00);
|
||||
_gl.Disable(EnableCap.DepthTest);
|
||||
|
||||
DrawPieceOutline(selectedPiece, sceneRotation, view, projection);
|
||||
|
||||
_gl.Enable(EnableCap.DepthTest);
|
||||
_gl.StencilMask(0xFF);
|
||||
_gl.Disable(EnableCap.StencilTest);
|
||||
}
|
||||
|
||||
private void DrawPiece(
|
||||
ViewportPiece piece,
|
||||
Matrix4x4 sceneRotation,
|
||||
Matrix4x4 view,
|
||||
Matrix4x4 projection)
|
||||
{
|
||||
if (_meshShader == null)
|
||||
throw new InvalidOperationException("Viewport mesh shader is not initialized.");
|
||||
|
||||
var model = piece.LocalTransform * sceneRotation;
|
||||
var mvp = model * view * projection;
|
||||
|
||||
_meshShader.Use();
|
||||
_meshShader.SetMatrix4(_meshMvpLocation, mvp);
|
||||
piece.Mesh.Draw();
|
||||
}
|
||||
|
||||
private void DrawPieceOutline(
|
||||
ViewportPiece piece,
|
||||
Matrix4x4 sceneRotation,
|
||||
Matrix4x4 view,
|
||||
Matrix4x4 projection)
|
||||
{
|
||||
if (_outlineShader == null)
|
||||
throw new InvalidOperationException("Viewport outline shader is not initialized.");
|
||||
|
||||
const float outlineScale = 1.06f;
|
||||
|
||||
var model = Matrix4x4.CreateScale(outlineScale) * piece.LocalTransform * sceneRotation;
|
||||
var mvp = model * view * projection;
|
||||
|
||||
_outlineShader.Use();
|
||||
_outlineShader.SetMatrix4(_outlineMvpLocation, mvp);
|
||||
_outlineShader.SetVector4(_outlineColorLocation, new Vector4(1.0f, 0.82f, 0.15f, 1.0f));
|
||||
|
||||
piece.Mesh.Draw();
|
||||
}
|
||||
|
||||
private void EnsureInitialized()
|
||||
{
|
||||
if (_initialized)
|
||||
return;
|
||||
|
||||
CreateShaders();
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
private void CreateShaders()
|
||||
{
|
||||
const string meshVertexShaderSource = """
|
||||
#version 330 core
|
||||
|
||||
layout (location = 0) in vec3 aPosition;
|
||||
layout (location = 1) in vec3 aColor;
|
||||
|
||||
uniform mat4 uMvp;
|
||||
|
||||
out vec3 vColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
vColor = aColor;
|
||||
gl_Position = uMvp * vec4(aPosition, 1.0);
|
||||
}
|
||||
""";
|
||||
|
||||
const string meshFragmentShaderSource = """
|
||||
#version 330 core
|
||||
|
||||
in vec3 vColor;
|
||||
out vec4 FragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
FragColor = vec4(vColor, 1.0);
|
||||
}
|
||||
""";
|
||||
|
||||
const string outlineVertexShaderSource = """
|
||||
#version 330 core
|
||||
|
||||
layout (location = 0) in vec3 aPosition;
|
||||
|
||||
uniform mat4 uMvp;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = uMvp * vec4(aPosition, 1.0);
|
||||
}
|
||||
""";
|
||||
|
||||
const string outlineFragmentShaderSource = """
|
||||
#version 330 core
|
||||
|
||||
uniform vec4 uColor;
|
||||
out vec4 FragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
FragColor = uColor;
|
||||
}
|
||||
""";
|
||||
|
||||
_meshShader = new ShaderProgram(_gl, meshVertexShaderSource, meshFragmentShaderSource, "Viewport mesh");
|
||||
_outlineShader = new ShaderProgram(_gl, outlineVertexShaderSource, outlineFragmentShaderSource, "Viewport outline");
|
||||
|
||||
_meshMvpLocation = _meshShader.GetUniformLocation("uMvp");
|
||||
_outlineMvpLocation = _outlineShader.GetUniformLocation("uMvp");
|
||||
_outlineColorLocation = _outlineShader.GetUniformLocation("uColor");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using NResUI.Rendering.Viewport.Meshes;
|
||||
|
||||
namespace NResUI.Rendering.Viewport;
|
||||
|
||||
public sealed class ViewportScene
|
||||
{
|
||||
private readonly List<ViewportPiece> _pieces = new();
|
||||
|
||||
public IReadOnlyList<ViewportPiece> Pieces => _pieces;
|
||||
|
||||
public ViewportGrid? Grid { get; set; }
|
||||
|
||||
public int SelectedPieceId { get; set; } = -1;
|
||||
|
||||
public ViewportPiece? SelectedPiece
|
||||
{
|
||||
get
|
||||
{
|
||||
if (SelectedPieceId < 0)
|
||||
return null;
|
||||
|
||||
foreach (var piece in _pieces)
|
||||
{
|
||||
if (piece.Id == SelectedPieceId)
|
||||
return piece;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddPiece(ViewportPiece piece)
|
||||
{
|
||||
_pieces.Add(piece);
|
||||
}
|
||||
|
||||
public void ClearSelection()
|
||||
{
|
||||
SelectedPieceId = -1;
|
||||
}
|
||||
|
||||
public static ViewportScene CreateDefaultCubeScene(GpuMesh cubeMesh, GpuMesh gridMesh)
|
||||
{
|
||||
var scene = new ViewportScene
|
||||
{
|
||||
Grid = new ViewportGrid(gridMesh)
|
||||
};
|
||||
|
||||
scene.AddPiece(ViewportPiece.CreateUnitCube(0, "Cube", cubeMesh));
|
||||
|
||||
return scene;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace NResUI.Rendering.Viewport;
|
||||
|
||||
public static class ViewportSelection
|
||||
{
|
||||
public static int PickPiece(
|
||||
ViewportScene scene,
|
||||
ViewportCamera camera,
|
||||
Vector2 localMouse,
|
||||
Vector2 viewportSize)
|
||||
{
|
||||
if (viewportSize.X <= 1.0f || viewportSize.Y <= 1.0f)
|
||||
return -1;
|
||||
|
||||
var aspect = viewportSize.X / viewportSize.Y;
|
||||
var fovRadians = ViewportCamera.ToRadians(60.0f);
|
||||
var tanHalfFov = MathF.Tan(fovRadians * 0.5f);
|
||||
|
||||
var ndcX = (2.0f * localMouse.X / viewportSize.X) - 1.0f;
|
||||
var ndcY = 1.0f - (2.0f * localMouse.Y / viewportSize.Y);
|
||||
|
||||
var rayOriginWorld = new Vector3(0.0f, 0.0f, camera.Distance);
|
||||
var rayDirectionWorld = Vector3.Normalize(new Vector3(
|
||||
ndcX * aspect * tanHalfFov,
|
||||
ndcY * tanHalfFov,
|
||||
-1.0f));
|
||||
|
||||
var sceneRotation = camera.GetSceneRotationMatrix();
|
||||
|
||||
var bestPieceId = -1;
|
||||
var bestDistance = float.PositiveInfinity;
|
||||
|
||||
foreach (var piece in scene.Pieces)
|
||||
{
|
||||
var model = piece.LocalTransform * sceneRotation;
|
||||
|
||||
if (!Matrix4x4.Invert(model, out var inverseModel))
|
||||
continue;
|
||||
|
||||
var rayOriginLocal = Vector3.Transform(rayOriginWorld, inverseModel);
|
||||
var rayDirectionLocal = Vector3.Normalize(
|
||||
Vector3.TransformNormal(rayDirectionWorld, inverseModel));
|
||||
|
||||
if (IntersectRayAabb(
|
||||
rayOriginLocal,
|
||||
rayDirectionLocal,
|
||||
piece.BoundsMin,
|
||||
piece.BoundsMax,
|
||||
out var hitDistance) &&
|
||||
hitDistance < bestDistance)
|
||||
{
|
||||
bestDistance = hitDistance;
|
||||
bestPieceId = piece.Id;
|
||||
}
|
||||
}
|
||||
|
||||
return bestPieceId;
|
||||
}
|
||||
|
||||
private static bool IntersectRayAabb(
|
||||
Vector3 rayOrigin,
|
||||
Vector3 rayDirection,
|
||||
Vector3 boundsMin,
|
||||
Vector3 boundsMax,
|
||||
out float hitDistance)
|
||||
{
|
||||
hitDistance = 0.0f;
|
||||
|
||||
var tMin = 0.0f;
|
||||
var tMax = float.PositiveInfinity;
|
||||
|
||||
if (!IntersectSlab(rayOrigin.X, rayDirection.X, boundsMin.X, boundsMax.X, ref tMin, ref tMax) ||
|
||||
!IntersectSlab(rayOrigin.Y, rayDirection.Y, boundsMin.Y, boundsMax.Y, ref tMin, ref tMax) ||
|
||||
!IntersectSlab(rayOrigin.Z, rayDirection.Z, boundsMin.Z, boundsMax.Z, ref tMin, ref tMax))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
hitDistance = tMin;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IntersectSlab(
|
||||
float origin,
|
||||
float direction,
|
||||
float min,
|
||||
float max,
|
||||
ref float tMin,
|
||||
ref float tMax)
|
||||
{
|
||||
const float epsilon = 1e-6f;
|
||||
|
||||
if (MathF.Abs(direction) < epsilon)
|
||||
return origin >= min && origin <= max;
|
||||
|
||||
var invDirection = 1.0f / direction;
|
||||
var t1 = (min - origin) * invDirection;
|
||||
var t2 = (max - origin) * invDirection;
|
||||
|
||||
if (t1 > t2)
|
||||
(t1, t2) = (t2, t1);
|
||||
|
||||
tMin = MathF.Max(tMin, t1);
|
||||
tMax = MathF.Min(tMax, t2);
|
||||
|
||||
return tMin <= tMax;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user