Files
parkan-playground/NResUI/Rendering/Viewport/ViewportRenderer.cs
T

317 lines
9.9 KiB
C#
Raw Normal View History

2026-06-05 20:33:21 +03:00
using System.Numerics;
2026-06-05 20:47:57 +03:00
using NResUI.Rendering.Viewport.Meshes;
2026-06-05 20:33:21 +03:00
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;
2026-06-05 21:12:25 +03:00
public GL Gl => _gl;
2026-06-05 20:33:21 +03:00
private ShaderProgram? _meshShader;
2026-06-05 20:47:57 +03:00
private GpuMesh? _unitWireBoxMesh;
private GpuMesh? _axesMesh;
2026-06-06 00:42:07 +03:00
private int _modelLocation;
private int _mvpLocation;
private int _lightDirectionLocation;
2026-06-06 21:26:56 +03:00
private int _useTextureLocation;
private int _texture0Location;
2026-06-10 01:38:44 +03:00
private int _colorAddLocation;
2026-06-05 20:33:21 +03:00
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);
2026-06-06 00:42:07 +03:00
_gl.Disable(EnableCap.CullFace);
2026-06-05 20:33:21 +03:00
_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);
2026-06-05 20:47:57 +03:00
DrawDebugOverlays(scene, sceneRotation, view, projection);
2026-06-05 20:33:21 +03:00
2026-06-05 20:47:57 +03:00
_gl.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill);
2026-06-05 20:33:21 +03:00
_framebuffer.Resolve();
_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;
2026-06-06 21:26:56 +03:00
var model = grid.LocalTransform * sceneRotation;
2026-06-05 20:33:21 +03:00
_gl.Disable(EnableCap.StencilTest);
2026-06-05 20:47:57 +03:00
_gl.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill);
2026-06-05 20:33:21 +03:00
2026-06-06 01:24:09 +03:00
DrawMesh(grid.Mesh, model, view, projection);
2026-06-06 21:26:56 +03:00
2026-06-05 20:33:21 +03:00
}
private void DrawScene(
ViewportScene scene,
Matrix4x4 sceneRotation,
Matrix4x4 view,
Matrix4x4 projection)
{
2026-06-10 01:38:44 +03:00
if (_meshShader == null)
2026-06-05 20:33:21 +03:00
throw new InvalidOperationException("Viewport renderer is not initialized.");
_gl.Disable(EnableCap.StencilTest);
_meshShader.Use();
2026-06-05 20:47:57 +03:00
_gl.PolygonMode(
TriangleFace.FrontAndBack,
scene.Debug.Wireframe ? PolygonMode.Line : PolygonMode.Fill);
2026-06-05 20:33:21 +03:00
foreach (var piece in scene.Pieces)
{
2026-06-10 01:38:44 +03:00
var colorAdd = piece.Id == scene.SelectedPieceId
? new Vector3(0.22f, 0.16f, 0.02f)
: Vector3.Zero;
DrawPiece(piece, sceneRotation, view, projection, colorAdd);
2026-06-05 20:47:57 +03:00
}
2026-06-05 20:33:21 +03:00
2026-06-05 20:47:57 +03:00
_gl.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill);
2026-06-05 20:33:21 +03:00
}
2026-06-05 20:47:57 +03:00
private void DrawDebugOverlays(
ViewportScene scene,
2026-06-05 20:33:21 +03:00
Matrix4x4 sceneRotation,
Matrix4x4 view,
Matrix4x4 projection)
{
2026-06-05 20:47:57 +03:00
if (_meshShader == null || _unitWireBoxMesh == null || _axesMesh == null)
throw new InvalidOperationException("Viewport debug resources are not initialized.");
2026-06-05 20:33:21 +03:00
2026-06-05 20:47:57 +03:00
_gl.Disable(EnableCap.StencilTest);
_gl.Disable(EnableCap.DepthTest);
_gl.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill);
_gl.LineWidth(2.0f);
2026-06-05 20:33:21 +03:00
_meshShader.Use();
2026-06-05 20:47:57 +03:00
if (scene.Debug.ShowOriginAxes)
DrawMesh(_axesMesh, Matrix4x4.CreateScale(1.25f) * sceneRotation, view, projection);
if (scene.Debug.ShowPieceOrigins)
{
foreach (var piece in scene.Pieces)
{
var axesModel = Matrix4x4.CreateScale(0.45f) * piece.LocalTransform * sceneRotation;
DrawMesh(_axesMesh, axesModel, view, projection);
}
}
if (scene.Debug.ShowSelectedBounds && scene.SelectedPiece != null)
{
var selectedPiece = scene.SelectedPiece;
var boundsModel = BuildLocalBoundsModel(selectedPiece.BoundsMin, selectedPiece.BoundsMax) *
selectedPiece.LocalTransform *
sceneRotation;
DrawMesh(_unitWireBoxMesh, boundsModel, view, projection);
}
if (scene.Debug.ShowSceneBounds && scene.TryGetSceneWorldBounds(out var sceneBounds))
{
var sceneBoundsModel = BuildLocalBoundsModel(sceneBounds.Min, sceneBounds.Max) * sceneRotation;
DrawMesh(_unitWireBoxMesh, sceneBoundsModel, view, projection);
}
_gl.LineWidth(1.0f);
_gl.Enable(EnableCap.DepthTest);
}
private void DrawPiece(
ViewportPiece piece,
Matrix4x4 sceneRotation,
Matrix4x4 view,
2026-06-10 01:38:44 +03:00
Matrix4x4 projection,
Vector3 colorAdd)
2026-06-05 20:47:57 +03:00
{
var model = piece.LocalTransform * sceneRotation;
2026-06-06 21:26:56 +03:00
foreach (var mesh in piece.Meshes)
2026-06-10 01:38:44 +03:00
DrawMesh(mesh, model, view, projection, colorAdd);
2026-06-05 20:33:21 +03:00
}
2026-06-05 20:47:57 +03:00
private void DrawMesh(
GpuMesh mesh,
Matrix4x4 model,
Matrix4x4 view,
2026-06-10 01:38:44 +03:00
Matrix4x4 projection,
Vector3? colorAdd = null)
2026-06-05 20:47:57 +03:00
{
if (_meshShader == null)
throw new InvalidOperationException("Viewport mesh shader is not initialized.");
var mvp = model * view * projection;
2026-06-06 00:42:07 +03:00
var lightDirection = Vector3.Normalize(new Vector3(-0.35f, -0.75f, -0.55f));
2026-06-05 20:47:57 +03:00
_meshShader.Use();
2026-06-06 00:42:07 +03:00
_meshShader.SetMatrix4(_mvpLocation, mvp);
_meshShader.SetMatrix4(_modelLocation, model);
_meshShader.SetVector3(_lightDirectionLocation, lightDirection);
2026-06-10 01:38:44 +03:00
_meshShader.SetVector3(_colorAddLocation, colorAdd ?? Vector3.Zero);
2026-06-06 00:42:07 +03:00
2026-06-06 21:26:56 +03:00
if (mesh.Material.HasTexture)
{
_gl.ActiveTexture(TextureUnit.Texture0);
_gl.BindTexture(TextureTarget.Texture2D, mesh.Material.TextureHandle);
_meshShader.SetInt(_texture0Location, 0);
_meshShader.SetInt(_useTextureLocation, 1);
}
else
{
_gl.BindTexture(TextureTarget.Texture2D, 0);
_meshShader.SetInt(_useTextureLocation, 0);
}
2026-06-05 20:47:57 +03:00
mesh.Draw();
}
private static Matrix4x4 BuildLocalBoundsModel(Vector3 boundsMin, Vector3 boundsMax)
{
var center = (boundsMin + boundsMax) * 0.5f;
var size = boundsMax - boundsMin;
return Matrix4x4.CreateScale(size * 0.5f) * Matrix4x4.CreateTranslation(center);
}
2026-06-05 20:33:21 +03:00
private void EnsureInitialized()
{
if (_initialized)
return;
CreateShaders();
2026-06-05 20:47:57 +03:00
_unitWireBoxMesh = PrimitiveMeshes.CreateUnitWireBox(_gl);
_axesMesh = PrimitiveMeshes.CreateAxes(_gl);
2026-06-05 20:33:21 +03:00
_initialized = true;
}
private void CreateShaders()
{
const string meshVertexShaderSource = """
#version 330 core
2026-06-06 00:42:07 +03:00
2026-06-05 20:33:21 +03:00
layout (location = 0) in vec3 aPosition;
layout (location = 1) in vec3 aColor;
2026-06-06 00:42:07 +03:00
layout (location = 2) in vec3 aNormal;
2026-06-06 21:26:56 +03:00
layout (location = 3) in vec2 aTexCoord;
2026-06-06 00:42:07 +03:00
uniform mat4 uModel;
2026-06-05 20:33:21 +03:00
uniform mat4 uMvp;
2026-06-06 00:42:07 +03:00
2026-06-05 20:33:21 +03:00
out vec3 vColor;
2026-06-06 00:42:07 +03:00
out vec3 vNormalWorld;
2026-06-06 21:26:56 +03:00
out vec2 vTexCoord;
2026-06-06 00:42:07 +03:00
2026-06-05 20:33:21 +03:00
void main()
{
vColor = aColor;
2026-06-06 00:42:07 +03:00
vNormalWorld = mat3(transpose(inverse(uModel))) * aNormal;
2026-06-06 21:26:56 +03:00
vTexCoord = aTexCoord;
2026-06-05 20:33:21 +03:00
gl_Position = uMvp * vec4(aPosition, 1.0);
}
""";
const string meshFragmentShaderSource = """
#version 330 core
2026-06-06 00:42:07 +03:00
2026-06-05 20:33:21 +03:00
in vec3 vColor;
2026-06-06 00:42:07 +03:00
in vec3 vNormalWorld;
2026-06-06 21:26:56 +03:00
in vec2 vTexCoord;
2026-06-06 00:42:07 +03:00
uniform vec3 uLightDirectionWorld;
2026-06-10 01:38:44 +03:00
uniform vec3 uColorAdd;
2026-06-06 21:26:56 +03:00
uniform bool uUseTexture;
uniform sampler2D uTexture0;
2026-06-06 00:42:07 +03:00
2026-06-05 20:33:21 +03:00
out vec4 FragColor;
2026-06-06 00:42:07 +03:00
2026-06-05 20:33:21 +03:00
void main()
{
2026-06-06 00:42:07 +03:00
vec3 normal = normalize(vNormalWorld);
// Useful when backface display is enabled.
if (!gl_FrontFacing)
normal = -normal;
vec3 lightDir = normalize(-uLightDirectionWorld);
float diffuse = max(dot(normal, lightDir), 0.0);
float lighting = 0.35 + diffuse * 0.65;
2026-06-06 21:26:56 +03:00
vec4 texel = uUseTexture ? texture(uTexture0, vTexCoord) : vec4(1.0);
if (texel.a < 0.05)
discard;
2026-06-10 01:38:44 +03:00
vec3 litColor = vColor * texel.rgb * lighting;
FragColor = vec4(clamp(litColor + uColorAdd, 0.0, 1.0), texel.a);
2026-06-05 20:33:21 +03:00
}
""";
_meshShader = new ShaderProgram(_gl, meshVertexShaderSource, meshFragmentShaderSource, "Viewport mesh");
2026-06-06 00:42:07 +03:00
_modelLocation = _meshShader.GetUniformLocation("uModel");
_mvpLocation = _meshShader.GetUniformLocation("uMvp");
_lightDirectionLocation = _meshShader.GetUniformLocation("uLightDirectionWorld");
2026-06-06 21:26:56 +03:00
_useTextureLocation = _meshShader.GetUniformLocation("uUseTexture");
_texture0Location = _meshShader.GetUniformLocation("uTexture0");
2026-06-10 01:38:44 +03:00
_colorAddLocation = _meshShader.GetUniformLocation("uColorAdd");
2026-06-05 20:33:21 +03:00
}
}