Initial push
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
<Solution>
|
||||
<Project Path="WpfOpenGLSubwindowingClasses/WpfOpenGLSubwindowingClasses.csproj" />
|
||||
</Solution>
|
||||
@@ -0,0 +1,141 @@
|
||||
using OpenTK.Mathematics;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace WpfOpenGLSubwindowingClasses.Camera
|
||||
{
|
||||
public static class OrbitCamera
|
||||
{
|
||||
public static Matrix4 View;
|
||||
public static Matrix4 Projection;
|
||||
public static Matrix4 ViewMatrix;
|
||||
|
||||
public static Vector3 Target = Vector3.Zero;
|
||||
public static float OrbitRadius;
|
||||
|
||||
public static double Yaw = -90.0f;
|
||||
public static double Pitch = 0.0f;
|
||||
|
||||
public static float MouseSensitivity = 0.1f;
|
||||
public static float ScrollSpeed = 0.1f;
|
||||
public static float Zoom = 45.0f;
|
||||
|
||||
public static int Width, Height;
|
||||
public static float Near = 0.1f, Far = 1000f;
|
||||
|
||||
public static Vector3 Position = Vector3.Zero;
|
||||
public static Vector3 Front = new Vector3(0.0f, 0.0f, -1.0f);
|
||||
public static Vector3 Up = Vector3.UnitY;
|
||||
public static Vector3 Right = Vector3.UnitX;
|
||||
public static Vector3 WorldUp = Vector3.UnitY;
|
||||
|
||||
private static double _lastX = 0.0f;
|
||||
private static double _lastY = 0.0f;
|
||||
|
||||
public static float MinOrbitRadius = 0.1f;
|
||||
public static float MaxOrbitRadius = 10000f;
|
||||
|
||||
public static bool IsLeftMousePressed;
|
||||
|
||||
public static void Initialize(Vector3 initialPosition, Vector3 worldUp)
|
||||
{
|
||||
Target = Vector3.Zero;
|
||||
WorldUp = worldUp;
|
||||
|
||||
OrbitRadius = (initialPosition - Target).Length;
|
||||
if (OrbitRadius < 0.1f) OrbitRadius = 0.1f;
|
||||
|
||||
var toCamera = Vector3.Normalize(initialPosition - Target);
|
||||
|
||||
Pitch = MathHelper.RadiansToDegrees(MathF.Asin(toCamera.Y));
|
||||
Pitch = Math.Clamp(Pitch, -89.0f, 89.0f);
|
||||
|
||||
Yaw = MathHelper.RadiansToDegrees(MathF.Atan2(toCamera.Z, toCamera.X));
|
||||
|
||||
RecalculateCameraPositionAndVectors();
|
||||
}
|
||||
|
||||
public static void RecalculateCameraPositionAndVectors()
|
||||
{
|
||||
float yawRad = MathHelper.DegreesToRadians((float)Yaw);
|
||||
float pitchRad = MathHelper.DegreesToRadians((float)Pitch);
|
||||
|
||||
var dir = new Vector3(
|
||||
MathF.Cos(yawRad) * MathF.Cos(pitchRad),
|
||||
MathF.Sin(pitchRad),
|
||||
MathF.Sin(yawRad) * MathF.Cos(pitchRad)
|
||||
);
|
||||
|
||||
Position = Target - dir * OrbitRadius;
|
||||
|
||||
Front = Vector3.Normalize(Target - Position);
|
||||
Right = Vector3.Normalize(Vector3.Cross(Front, WorldUp));
|
||||
Up = Vector3.Normalize(Vector3.Cross(Right, Front));
|
||||
|
||||
UpdateView();
|
||||
}
|
||||
|
||||
public static void UpdateView() => View = Matrix4.LookAt(Position, Target, Up);
|
||||
public static void UpdateProjection(float fov, float aspectRatio) => Projection = Matrix4.CreatePerspectiveFieldOfView(fov, aspectRatio, Near, Far);
|
||||
|
||||
public static void MouseDownEvent(object _, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed) IsLeftMousePressed = true;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
public static void MouseMoveEvent(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (sender is not IInputElement control) return;
|
||||
var point = e.GetPosition(control);
|
||||
|
||||
if (IsLeftMousePressed) ProcessMouseMovement(point.X - _lastX, point.Y - _lastY);
|
||||
|
||||
_lastX = point.X;
|
||||
_lastY = point.Y;
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
public static void MouseUpEvent(object _, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Released) IsLeftMousePressed = false;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
public static void MouseWheelEvent(object _, MouseWheelEventArgs e)
|
||||
{
|
||||
ProcessMouseScroll(e.Delta);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
public static void MouseLostFocusEvent(object _, RoutedEventArgs __) => IsLeftMousePressed = false;
|
||||
|
||||
private static void ProcessMouseMovement(double xoff, double yoff, bool constrainPitch = true)
|
||||
{
|
||||
xoff *= MouseSensitivity;
|
||||
yoff *= MouseSensitivity;
|
||||
Yaw += xoff;
|
||||
Pitch -= yoff;
|
||||
|
||||
if (Yaw > 360.0) Yaw -= 360.0;
|
||||
else if (Yaw < 0.0) Yaw += 360.0;
|
||||
|
||||
if (constrainPitch)
|
||||
{
|
||||
if (Pitch > 89.0f) Pitch = 89.0f;
|
||||
if (Pitch < -89.0f) Pitch = -89.0f;
|
||||
}
|
||||
|
||||
RecalculateCameraPositionAndVectors();
|
||||
}
|
||||
|
||||
private static void ProcessMouseScroll(float yoff)
|
||||
{
|
||||
float steps = yoff / 120f;
|
||||
OrbitRadius = Math.Clamp(OrbitRadius - steps * ScrollSpeed, MinOrbitRadius, MaxOrbitRadius);
|
||||
RecalculateCameraPositionAndVectors();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using Assimp;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using OpenTK.Mathematics;
|
||||
|
||||
namespace WpfOpenGLSubwindowingClasses.ModelHandling;
|
||||
|
||||
public sealed class AxisLines : IDisposable
|
||||
{
|
||||
private int _vao, _vbo, _count;
|
||||
|
||||
public void Create(float length = 1f)
|
||||
{
|
||||
float L = length;
|
||||
float[] data = new float[]
|
||||
{
|
||||
// X (red)
|
||||
0,0,0, 1,0,0, L,0,0, 1,0,0,
|
||||
// Y (green)
|
||||
0,0,0, 0,1,0, 0,L,0, 0,1,0,
|
||||
// Z (blue)
|
||||
0,0,0, 0,0,1, 0,0,-L, 0,0,1,
|
||||
};
|
||||
|
||||
_count = data.Length / 6;
|
||||
|
||||
_vao = GL.GenVertexArray();
|
||||
_vbo = GL.GenBuffer();
|
||||
|
||||
GL.BindVertexArray(_vao);
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, _vbo);
|
||||
GL.BufferData(BufferTarget.ArrayBuffer, data.Length * sizeof(float), data, BufferUsageHint.StaticDraw);
|
||||
|
||||
int stride = 6 * sizeof(float);
|
||||
GL.EnableVertexAttribArray(0);
|
||||
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, 0);
|
||||
GL.EnableVertexAttribArray(1);
|
||||
GL.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, stride, 3 * sizeof(float));
|
||||
|
||||
GL.BindVertexArray(0);
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
|
||||
}
|
||||
|
||||
public void Draw(Shader.Shader shader, Matrix4 view, Matrix4 proj, Matrix4 model, float lineWidth = 2f, bool depthTest = true)
|
||||
{
|
||||
if (_vao == 0) return;
|
||||
if (!depthTest) GL.Disable(EnableCap.DepthTest);
|
||||
|
||||
GL.LineWidth(lineWidth);
|
||||
|
||||
shader.Use();
|
||||
|
||||
shader.SetMatrix4("view", view);
|
||||
shader.SetMatrix4("proj", proj);
|
||||
shader.SetMatrix4("model", model);
|
||||
|
||||
GL.BindVertexArray(_vao);
|
||||
GL.DrawArrays(OpenTK.Graphics.OpenGL4.PrimitiveType.Lines, 0, _count);
|
||||
GL.BindVertexArray(0);
|
||||
|
||||
if (!depthTest) GL.Enable(EnableCap.DepthTest);
|
||||
GL.LineWidth(1f);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_vbo != 0) GL.DeleteBuffer(_vbo);
|
||||
if (_vao != 0) GL.DeleteVertexArray(_vao);
|
||||
_vbo = _vao = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
using Assimp;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using OpenTK.Mathematics;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using PrimitiveType = OpenTK.Graphics.OpenGL4.PrimitiveType;
|
||||
|
||||
namespace WpfOpenGLSubwindowingClasses.ModelHandling
|
||||
{
|
||||
public sealed class Mesh : IDisposable
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct Vertex(Vector3 pos, Vector3 normal, Vector3 color, Vector2 uv)
|
||||
{
|
||||
public Vector3 Position = pos;
|
||||
public Vector3 Normal = normal;
|
||||
public Vector3 Color = color;
|
||||
public Vector2 TexCoord = uv;
|
||||
}
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public Vertex[] Vertices { get; set; } = Array.Empty<Vertex>();
|
||||
public int[] Indices { get; set; } = Array.Empty<int>();
|
||||
public string? DiffuseTexturePath { get; set; }
|
||||
public int DiffuseTextureHandle { get; set; } = 0;
|
||||
public string? SpecularTexturePath { get; set; }
|
||||
public int SpecularTextureHandle { get; set; } = 0;
|
||||
|
||||
public int MaterialIndex { get; set; } = -1;
|
||||
|
||||
private int _vao;
|
||||
private int _vbo;
|
||||
private int _ebo;
|
||||
|
||||
public Color4 AmbientColor { get; set; } = new(0.2f, 0.2f, 0.2f, 1.0f);
|
||||
public Color4 DiffuseColor { get; set; } = new(0.8f, 0.8f, 0.8f, 1.0f);
|
||||
public Color4 SpecularColor { get; set; } = new(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
public float Shininess { get; set; } = 32.0f;
|
||||
|
||||
public bool HasGpuBuffers => _vao != 0;
|
||||
|
||||
public void CreateGpuBuffers()
|
||||
{
|
||||
if (HasGpuBuffers || Vertices.Length == 0 || Indices.Length == 0)
|
||||
return;
|
||||
|
||||
_vao = GL.GenVertexArray();
|
||||
_vbo = GL.GenBuffer();
|
||||
_ebo = GL.GenBuffer();
|
||||
|
||||
GL.BindVertexArray(_vao);
|
||||
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, _vbo);
|
||||
GL.BufferData(BufferTarget.ArrayBuffer, Vertices.Length * Unsafe.SizeOf<Vertex>(), Vertices, BufferUsageHint.StaticDraw);
|
||||
|
||||
GL.BindBuffer(BufferTarget.ElementArrayBuffer, _ebo);
|
||||
GL.BufferData(BufferTarget.ElementArrayBuffer, Indices.Length * sizeof(int), Indices, BufferUsageHint.StaticDraw);
|
||||
|
||||
int stride = Unsafe.SizeOf<Vertex>();
|
||||
|
||||
var posOff = (int)Marshal.OffsetOf<Vertex>(nameof(Vertex.Position));
|
||||
var nrmOff = (int)Marshal.OffsetOf<Vertex>(nameof(Vertex.Normal));
|
||||
var colOff = (int)Marshal.OffsetOf<Vertex>(nameof(Vertex.Color));
|
||||
var uvOff = (int)Marshal.OffsetOf<Vertex>(nameof(Vertex.TexCoord));
|
||||
|
||||
// position (vec3) @ location 0
|
||||
GL.EnableVertexAttribArray(0);
|
||||
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, posOff);
|
||||
|
||||
// normal (vec3) @ location 1
|
||||
GL.EnableVertexAttribArray(1);
|
||||
GL.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, stride, nrmOff);
|
||||
|
||||
// color (vec3) @ location 2
|
||||
GL.EnableVertexAttribArray(2);
|
||||
GL.VertexAttribPointer(2, 3, VertexAttribPointerType.Float, false, stride, colOff);
|
||||
|
||||
// texcoord (vec2) @ location 3
|
||||
GL.EnableVertexAttribArray(3);
|
||||
GL.VertexAttribPointer(3, 2, VertexAttribPointerType.Float, false, stride, uvOff);
|
||||
|
||||
GL.BindVertexArray(0);
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
|
||||
}
|
||||
|
||||
public void Draw(Shader.Shader shader, Matrix4 M, Vector3 translation, OpenTK.Mathematics.Quaternion rotation, Vector3 scale, PrimitiveType? drawModeOverride = null)
|
||||
{
|
||||
if (!HasGpuBuffers) return;
|
||||
if (drawModeOverride == null) drawModeOverride = PrimitiveType.Triangles;
|
||||
|
||||
var T = Matrix4.CreateTranslation(translation);
|
||||
var R = Matrix4.CreateFromQuaternion(rotation);
|
||||
var S = Matrix4.CreateScale(scale);
|
||||
|
||||
// activate shader in beforehand -> activation call each mesh is redundant
|
||||
|
||||
shader.SetMatrix4("translation", T);
|
||||
shader.SetMatrix4("rotation", R);
|
||||
shader.SetMatrix4("scale", S);
|
||||
shader.SetMatrix4("model", M);
|
||||
shader.SetVector4("material_ambient", (Vector4)AmbientColor);
|
||||
shader.SetVector4("material_diffuse", (Vector4)DiffuseColor);
|
||||
shader.SetVector4("material_specular", (Vector4)SpecularColor);
|
||||
shader.SetFloat("material_shininess", Shininess);
|
||||
|
||||
// Textur-Handling
|
||||
bool hasDiffuseTexture = DiffuseTextureHandle != 0;
|
||||
shader.SetBool("hasDiffuseTexture", hasDiffuseTexture);
|
||||
|
||||
if (hasDiffuseTexture)
|
||||
{
|
||||
GL.ActiveTexture(TextureUnit.Texture0);
|
||||
GL.BindTexture(TextureTarget.Texture2D, DiffuseTextureHandle);
|
||||
shader.SetInt("diffuse0", 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.ActiveTexture(TextureUnit.Texture0);
|
||||
GL.BindTexture(TextureTarget.Texture2D, 0);
|
||||
}
|
||||
|
||||
// Specular Texture Handling
|
||||
bool hasSpecularTexture = SpecularTextureHandle != 0;
|
||||
shader.SetBool("hasSpecularTexture", hasSpecularTexture);
|
||||
|
||||
if (hasSpecularTexture)
|
||||
{
|
||||
GL.ActiveTexture(TextureUnit.Texture1);
|
||||
GL.BindTexture(TextureTarget.Texture2D, SpecularTextureHandle);
|
||||
shader.SetInt("specular0", 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.ActiveTexture(TextureUnit.Texture1);
|
||||
GL.BindTexture(TextureTarget.Texture2D, 0);
|
||||
}
|
||||
|
||||
GL.BindVertexArray(_vao);
|
||||
GL.DrawElements(drawModeOverride.Value, Indices.Length, DrawElementsType.UnsignedInt, 0);
|
||||
GL.BindVertexArray(0);
|
||||
|
||||
// Opt: unbind tex
|
||||
GL.ActiveTexture(TextureUnit.Texture0);
|
||||
GL.BindTexture(TextureTarget.Texture2D, 0);
|
||||
GL.ActiveTexture(TextureUnit.Texture1);
|
||||
GL.BindTexture(TextureTarget.Texture2D, 0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_vao != 0) { GL.DeleteVertexArray(_vao); _vao = 0; }
|
||||
if (_vbo != 0) { GL.DeleteBuffer(_vbo); _vbo = 0; }
|
||||
if (_ebo != 0) { GL.DeleteBuffer(_ebo); _ebo = 0; }
|
||||
if (DiffuseTextureHandle != 0) { GL.DeleteTexture(DiffuseTextureHandle); DiffuseTextureHandle = 0; }
|
||||
if (SpecularTextureHandle != 0) { GL.DeleteTexture(SpecularTextureHandle); SpecularTextureHandle = 0; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using OpenTK.Mathematics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WpfOpenGLSubwindowingClasses.ModelHandling
|
||||
{
|
||||
public class Model(string path, List<Mesh> meshes) : IDisposable
|
||||
{
|
||||
public string Path { get; set; } = path;
|
||||
public List<Mesh> Meshes { get; set; } = meshes;
|
||||
public OpenTK.Mathematics.Vector3 CenterOffset { get; private set; } = OpenTK.Mathematics.Vector3.Zero;
|
||||
public Matrix4 ModelMatrix { get; private set; } = Matrix4.Identity;
|
||||
|
||||
public OpenTK.Mathematics.Vector3 Translation { get; set; } = OpenTK.Mathematics.Vector3.Zero;
|
||||
public OpenTK.Mathematics.Quaternion Rotation { get; set; } = OpenTK.Mathematics.Quaternion.Identity;
|
||||
public OpenTK.Mathematics.Vector3 Scale { get; set; } = OpenTK.Mathematics.Vector3.One;
|
||||
|
||||
public void ComputeCenterOffset(OpenTK.Mathematics.Vector3 target = default)
|
||||
{
|
||||
var (min, max) = ComputeGlobalBounds();
|
||||
var currentCenter = (min + max) * 0.5f;
|
||||
CenterOffset = target - currentCenter;
|
||||
// ModelMatrix = Matrix4.CreateTranslation(CenterOffset);
|
||||
Translation = CenterOffset;
|
||||
}
|
||||
|
||||
public void CenterModelAroundVec(OpenTK.Mathematics.Vector3? targetCenter = null)
|
||||
{
|
||||
var c = OpenTK.Mathematics.Vector3.Zero;
|
||||
if (targetCenter is not null) c = targetCenter.Value;
|
||||
|
||||
var (min, max) = ComputeGlobalBounds();
|
||||
var currentCenter = (min + max) * 0.5f;
|
||||
var offset = c - currentCenter;
|
||||
|
||||
foreach (var mesh in Meshes)
|
||||
{
|
||||
var verts = mesh.Vertices;
|
||||
|
||||
for (int i = 0; i < verts.Length; i++)
|
||||
{
|
||||
var v = verts[i];
|
||||
verts[i] = new Mesh.Vertex(v.Position + offset, v.Normal, v.Color, v.TexCoord);
|
||||
}
|
||||
|
||||
mesh.Vertices = verts;
|
||||
}
|
||||
}
|
||||
|
||||
public (OpenTK.Mathematics.Vector3 min, OpenTK.Mathematics.Vector3 max) ComputeGlobalBounds()
|
||||
{
|
||||
var min = new OpenTK.Mathematics.Vector3(float.MaxValue);
|
||||
var max = new OpenTK.Mathematics.Vector3(float.MinValue);
|
||||
|
||||
foreach (var mesh in Meshes)
|
||||
{
|
||||
foreach (var v in mesh.Vertices)
|
||||
{
|
||||
min = OpenTK.Mathematics.Vector3.ComponentMin(min, v.Position);
|
||||
max = OpenTK.Mathematics.Vector3.ComponentMax(max, v.Position);
|
||||
}
|
||||
}
|
||||
|
||||
return (min, max);
|
||||
}
|
||||
|
||||
public void ApplyUniformScale(float? s = null)
|
||||
{
|
||||
if (s == null) s = ComputeScaleToMaxExtent(this, 5f);
|
||||
if (s <= 0f) return;
|
||||
|
||||
foreach (var mesh in Meshes)
|
||||
{
|
||||
var verts = mesh.Vertices;
|
||||
for (int i = 0; i < verts.Length; i++)
|
||||
{
|
||||
var v = verts[i];
|
||||
verts[i] = new Mesh.Vertex(v.Position * (float)s, v.Normal, v.Color, v.TexCoord);
|
||||
}
|
||||
mesh.Vertices = verts;
|
||||
}
|
||||
}
|
||||
|
||||
static float ComputeScaleToMaxExtent(Model model, float targetMaxExtent)
|
||||
{
|
||||
var (min, max) = model.ComputeGlobalBounds();
|
||||
var extent = max - min;
|
||||
float maxExtent = Math.Max(extent.X, Math.Max(extent.Y, extent.Z));
|
||||
if (maxExtent <= 0f) return 1f;
|
||||
return targetMaxExtent / maxExtent;
|
||||
}
|
||||
|
||||
public void Draw(Shader.Shader shader, PrimitiveType? drawModeOverride = null)
|
||||
{
|
||||
if (drawModeOverride == null) drawModeOverride = PrimitiveType.Triangles;
|
||||
foreach (var mesh in Meshes) mesh.Draw(shader, Matrix4.Identity, Translation, Rotation, Scale, drawModeOverride);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach(var mesh in Meshes)
|
||||
{
|
||||
mesh.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+309
@@ -0,0 +1,309 @@
|
||||
using Assimp;
|
||||
using Assimp.Configs;
|
||||
using OpenTK.Mathematics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.MemoryMappedFiles;
|
||||
using System.Numerics;
|
||||
using System.Windows.Documents;
|
||||
|
||||
using Vector2 = OpenTK.Mathematics.Vector2;
|
||||
using Vector3 = OpenTK.Mathematics.Vector3;
|
||||
|
||||
namespace WpfOpenGLSubwindowingClasses.ModelHandling
|
||||
{
|
||||
public sealed class ModelLoader(bool logging = false, bool logToDebug = false)
|
||||
{
|
||||
public bool Logging { get; set; } = logging;
|
||||
public bool LogToDebug { get; set; } = logToDebug;
|
||||
|
||||
public Model Load(string objPath, bool loadTextures = true)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(objPath))
|
||||
throw new ArgumentException("Pfad ist leer.", nameof(objPath));
|
||||
if (!File.Exists(objPath))
|
||||
throw new FileNotFoundException("Obj-Datei nicht gefunden.", objPath);
|
||||
string baseDir = Path.GetDirectoryName(objPath)!;
|
||||
|
||||
var ctx = new AssimpContext();
|
||||
ctx.SetConfig(new Assimp.Configs.FBXImportCamerasConfig(false));
|
||||
ctx.SetConfig(new NormalSmoothingAngleConfig(60.0f));
|
||||
|
||||
var flags =
|
||||
PostProcessSteps.Triangulate |
|
||||
PostProcessSteps.JoinIdenticalVertices |
|
||||
PostProcessSteps.GenerateSmoothNormals |
|
||||
PostProcessSteps.CalculateTangentSpace |
|
||||
PostProcessSteps.FlipUVs |
|
||||
PostProcessSteps.ImproveCacheLocality |
|
||||
PostProcessSteps.OptimizeMeshes |
|
||||
PostProcessSteps.PreTransformVertices
|
||||
;
|
||||
|
||||
Scene? scene = null;
|
||||
try
|
||||
{
|
||||
/*
|
||||
var mmf = MemoryMappedFile.CreateFromFile(objPath);
|
||||
var stream = mmf.CreateViewStream();
|
||||
scene = ctx.ImportFileFromStream(stream, flags);
|
||||
*/
|
||||
scene = ctx.ImportFile(objPath, flags);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine("Assimp Importer Failed: " + e);
|
||||
}
|
||||
|
||||
if (scene == null || scene.MeshCount == 0)
|
||||
throw new InvalidOperationException("Import has no meshes.");
|
||||
|
||||
var result = new List<Mesh>(scene.MeshCount);
|
||||
|
||||
for (int m = 0; m < scene.MeshCount; m++)
|
||||
{
|
||||
var aMesh = scene.Meshes[m];
|
||||
var mesh = new Mesh
|
||||
{
|
||||
Name = string.IsNullOrEmpty(aMesh.Name) ? $"Mesh_{m}" : aMesh.Name,
|
||||
MaterialIndex = aMesh.MaterialIndex
|
||||
};
|
||||
|
||||
// Vertices
|
||||
var vertices = new Mesh.Vertex[aMesh.VertexCount];
|
||||
for (int i = 0; i < aMesh.VertexCount; i++)
|
||||
{
|
||||
var pos = ToVec3(aMesh.Vertices[i]);
|
||||
var normal = aMesh.HasNormals ? ToVec3(aMesh.Normals[i]) : Vector3.UnitY;
|
||||
|
||||
Vector2 uv = Vector2.Zero;
|
||||
if (aMesh.HasTextureCoords(0))
|
||||
{
|
||||
var t = aMesh.TextureCoordinateChannels[0][i];
|
||||
uv = new Vector2(t.X, t.Y);
|
||||
}
|
||||
|
||||
Vector3 vertexColor = Vector3.One;
|
||||
if (aMesh.HasVertexColors(0))
|
||||
{
|
||||
var assimpColor = aMesh.VertexColorChannels[0][i];
|
||||
vertexColor = new Vector3(assimpColor.X, assimpColor.Y, assimpColor.Z);
|
||||
}
|
||||
|
||||
vertices[i] = new Mesh.Vertex(pos, normal, vertexColor, uv);
|
||||
}
|
||||
|
||||
// Indices
|
||||
var indices = new List<int>(aMesh.FaceCount * 3);
|
||||
for (int f = 0; f < aMesh.FaceCount; f++)
|
||||
{
|
||||
var face = aMesh.Faces[f];
|
||||
|
||||
if (face.IndexCount == 3)
|
||||
{
|
||||
indices.Add(face.Indices[0]);
|
||||
indices.Add(face.Indices[1]);
|
||||
indices.Add(face.Indices[2]);
|
||||
}
|
||||
else if (face.IndexCount > 3)
|
||||
{
|
||||
for (int k = 1; k < face.IndexCount - 1; k++)
|
||||
{
|
||||
indices.Add(face.Indices[0]);
|
||||
indices.Add(face.Indices[k]);
|
||||
indices.Add(face.Indices[k + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mesh.Vertices = vertices;
|
||||
mesh.Indices = indices.ToArray();
|
||||
|
||||
// Material
|
||||
if (aMesh.MaterialIndex >= 0 && aMesh.MaterialIndex < scene.MaterialCount)
|
||||
{
|
||||
var mat = scene.Materials[aMesh.MaterialIndex];
|
||||
|
||||
// Diffuse (Kd)
|
||||
if (mat.HasColorDiffuse)
|
||||
{
|
||||
var c = mat.ColorDiffuse;
|
||||
mesh.DiffuseColor = new(c.X, c.Y, c.Z, c.W);
|
||||
Log($"Material '{mat.Name}' Diffuse Color (Kd): {mesh.DiffuseColor}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"Material '{mat.Name}' has no Diffuse Color (Kd), using default.");
|
||||
}
|
||||
|
||||
// Ambient (Ka)
|
||||
if (mat.HasColorAmbient)
|
||||
{
|
||||
var c = mat.ColorAmbient;
|
||||
mesh.AmbientColor = new(c.X, c.Y, c.Z, c.W);
|
||||
Log($"Material '{mat.Name}' Ambient Color (Ka): {mesh.AmbientColor}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"Material '{mat.Name}' has no Ambient Color (Ka), using default.");
|
||||
}
|
||||
|
||||
// Specular (Ks)
|
||||
if (mat.HasColorSpecular)
|
||||
{
|
||||
var c = mat.ColorSpecular;
|
||||
mesh.SpecularColor = new(c.X, c.Y, c.Z, c.W);
|
||||
Log($"Material '{mat.Name}' Specular Color (Ks): {mesh.SpecularColor}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"Material '{mat.Name}' has no Specular Color (Ks), using default.");
|
||||
}
|
||||
|
||||
// Shininess (Ns)
|
||||
if (mat.HasShininess)
|
||||
{
|
||||
mesh.Shininess = mat.Shininess;
|
||||
Log($"Material '{mat.Name}' Shininess (Ns): {mesh.Shininess}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"Material '{mat.Name}' has no Shininess (Ns), using default.");
|
||||
}
|
||||
|
||||
// Load textures from files
|
||||
string? diffusePath = TryResolveTexturePath(mat, TextureType.Diffuse, baseDir);
|
||||
if (diffusePath != null)
|
||||
{
|
||||
mesh.DiffuseTexturePath = diffusePath;
|
||||
if (loadTextures)
|
||||
{
|
||||
try
|
||||
{
|
||||
Texture diffuseTexture = TextureManager.LoadTextureFromFile(diffusePath);
|
||||
mesh.DiffuseTextureHandle = diffuseTexture.Handle;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"Error loading diffuse texture '{diffusePath}': {ex.Message}");
|
||||
mesh.DiffuseTextureHandle = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string? specularPath = TryResolveTexturePath(mat, TextureType.Specular, baseDir);
|
||||
if (specularPath != null)
|
||||
{
|
||||
mesh.SpecularTexturePath = specularPath;
|
||||
if (loadTextures) {
|
||||
try
|
||||
{
|
||||
Texture specularTexture = TextureManager.LoadTextureFromFile(specularPath);
|
||||
mesh.SpecularTextureHandle = specularTexture.Handle;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"Error loading specular texture '{specularPath}': {ex.Message}");
|
||||
mesh.SpecularTextureHandle = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
return new(objPath, result);
|
||||
}
|
||||
|
||||
private static OpenTK.Mathematics.Vector3 ToVec3(System.Numerics.Vector3 v) => new OpenTK.Mathematics.Vector3(v.X, v.Y, v.Z);
|
||||
|
||||
private static string? TryResolveTexturePath(Material mat, TextureType type, string baseDir, bool logging = false, bool logToDebug = false)
|
||||
{
|
||||
Log($"--- Resolving texture for type: {type} ---", logging, logToDebug);
|
||||
|
||||
if (mat.GetMaterialTextureCount(type) <= 0)
|
||||
{
|
||||
Log($"No textures of type {type} found in material.", logging, logToDebug);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (mat.GetMaterialTexture(type, 0, out TextureSlot slot))
|
||||
{
|
||||
var raw = slot.FilePath;
|
||||
Log($"Raw texture path from material: '{raw}'", logging, logToDebug);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
{
|
||||
Log($"Raw texture path is null or whitespace.", logging, logToDebug);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (raw.StartsWith("*"))
|
||||
{
|
||||
Log($"Raw texture path starts with an asterisk (Assimp internal reference).", logging, logToDebug);
|
||||
return null; // Assimp internal reference, not a file path
|
||||
}
|
||||
|
||||
var candidate1 = Path.IsPathRooted(raw) ? raw : Path.Combine(baseDir, raw);
|
||||
Log($"Candidate 1 path: '{candidate1}'", logging, logToDebug);
|
||||
if (File.Exists(candidate1))
|
||||
{
|
||||
Log($"Found texture at candidate 1: '{candidate1}'", logging, logToDebug);
|
||||
return candidate1;
|
||||
}
|
||||
Log($"Candidate 1 not found.", logging, logToDebug);
|
||||
|
||||
var normalized = raw.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar);
|
||||
var candidate2 = Path.Combine(baseDir, normalized);
|
||||
Log($"Candidate 2 path (normalized): '{candidate2}'", logging, logToDebug);
|
||||
if (File.Exists(candidate2))
|
||||
{
|
||||
Log($"Found texture at candidate 2: '{candidate2}'", logging, logToDebug);
|
||||
return candidate2;
|
||||
}
|
||||
Log($"Candidate 2 not found.", logging, logToDebug);
|
||||
|
||||
var fileName = Path.GetFileName(raw);
|
||||
Log($"Searching for file name '{fileName}' in '{baseDir}' and subdirectories.", logging, logToDebug);
|
||||
try
|
||||
{
|
||||
foreach (var f in Directory.EnumerateFiles(baseDir, "*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
if (string.Equals(Path.GetFileName(f), fileName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Log($"Found texture by filename search: '{f}'", logging, logToDebug);
|
||||
return f;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"Error during directory enumeration for texture: {ex.Message}", logging, logToDebug);
|
||||
}
|
||||
Log($"File name '{fileName}' not found in '{baseDir}' or subdirectories.", logging, logToDebug);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"Failed to get material texture slot for type {type}.", logging, logToDebug);
|
||||
}
|
||||
|
||||
Log($"--- Failed to resolve texture path for type: {type} ---", logging, logToDebug);
|
||||
return null;
|
||||
}
|
||||
|
||||
void Log(string message)
|
||||
{
|
||||
if(Logging && !LogToDebug) Console.WriteLine(message);
|
||||
else if(Logging && LogToDebug) Debug.WriteLine(message);
|
||||
}
|
||||
|
||||
static void Log(string message, bool logging, bool logToDebug)
|
||||
{
|
||||
if (logging && !logToDebug) Console.WriteLine(message);
|
||||
else if (logging && logToDebug) Debug.WriteLine(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using StbImageSharp; // NEU: Import für StbImageSharp
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace WpfOpenGLSubwindowingClasses.ModelHandling
|
||||
{
|
||||
public class Texture : IDisposable
|
||||
{
|
||||
public int Handle { get; private set; }
|
||||
public string? Path { get; private set; }
|
||||
|
||||
public Texture(int glHandle, string? path)
|
||||
{
|
||||
Handle = glHandle;
|
||||
Path = path;
|
||||
}
|
||||
|
||||
public static Texture LoadFromFile(string path, bool logging = false, bool logToDebug = false)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
Log($"Error: Texture file not found at '{path}'", logging, logToDebug);
|
||||
return new Texture(0, path);
|
||||
}
|
||||
|
||||
int handle = 0;
|
||||
|
||||
try
|
||||
{
|
||||
handle = GL.GenTexture();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or element is obsolete
|
||||
if (e is ExecutionEngineException)
|
||||
{
|
||||
Log("This is exception should not be thrown by runtime anymore -> unexpected!", logging, logToDebug);
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
GL.ActiveTexture(TextureUnit.Texture0);
|
||||
GL.BindTexture(TextureTarget.Texture2D, handle);
|
||||
|
||||
StbImage.stbi_set_flip_vertically_on_load(1);
|
||||
|
||||
ImageResult? image = null;
|
||||
|
||||
try
|
||||
{
|
||||
using var fs = File.OpenRead(path);
|
||||
image = ImageResult.FromStream(fs, ColorComponents.RedGreenBlueAlpha);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"Error loading image with StbImageSharp from '{path}': {ex.Message}", logging, logToDebug);
|
||||
GL.DeleteTexture(handle);
|
||||
return new Texture(0, path);
|
||||
}
|
||||
finally
|
||||
{
|
||||
StbImage.stbi_set_flip_vertically_on_load(0);
|
||||
}
|
||||
|
||||
if (image == null)
|
||||
{
|
||||
Log($"Error: StbImageSharp returned null for '{path}'", logging, logToDebug);
|
||||
GL.DeleteTexture(handle);
|
||||
return new Texture(0, path);
|
||||
}
|
||||
|
||||
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, image.Width, image.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, image.Data);
|
||||
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.LinearMipmapLinear);
|
||||
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.GenerateMipmap(GenerateMipmapTarget.Texture2D);
|
||||
|
||||
return new Texture(handle, path);
|
||||
}
|
||||
|
||||
public void Use(TextureUnit unit)
|
||||
{
|
||||
GL.ActiveTexture(unit);
|
||||
GL.BindTexture(TextureTarget.Texture2D, Handle);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Handle != 0)
|
||||
{
|
||||
GL.DeleteTexture(Handle);
|
||||
Handle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void Log(string message, bool logging, bool logToDebug)
|
||||
{
|
||||
if (logging && !logToDebug) Console.WriteLine(message);
|
||||
else if (logging && logToDebug) Debug.WriteLine(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WpfOpenGLSubwindowingClasses.ModelHandling
|
||||
{
|
||||
public static class TextureManager
|
||||
{
|
||||
static private List<Texture> _textures = new();
|
||||
public static IReadOnlyList<Texture> Textures => _textures;
|
||||
|
||||
public static Texture LoadTextureFromFile(string path)
|
||||
{
|
||||
var tex = Textures.FirstOrDefault(x => x.Path == path);
|
||||
if (tex is not null) return tex;
|
||||
|
||||
tex = Texture.LoadFromFile(path);
|
||||
AddTexture(tex);
|
||||
|
||||
return tex;
|
||||
}
|
||||
|
||||
public static void AddTexture(Texture texture)
|
||||
{
|
||||
if(_textures.Any(x => x.Path == texture.Path)) return;
|
||||
if (!_textures.Contains(texture)) _textures.Add(texture);
|
||||
}
|
||||
|
||||
public static void RemoveTexture(Texture texture) => _textures.Remove(texture);
|
||||
public static void RemoveTexture(string path)
|
||||
{
|
||||
var tex = _textures.FirstOrDefault(x => x.Path == path);
|
||||
if (tex is null) return;
|
||||
_textures.Remove(tex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using OpenTK.Graphics.Wgl;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using WpfOpenGLSubwindowingClasses.Interop;
|
||||
using WpfOpenGLSubwindowingClasses.Renderer;
|
||||
using Wgl = WpfOpenGLSubwindowingClasses.Interop.Wgl;
|
||||
|
||||
namespace WpfOpenGLSubwindowingClasses.Controls
|
||||
{
|
||||
public class OpenGlHwndHost : HwndHost
|
||||
{
|
||||
private IntPtr _hwnd;
|
||||
private IntPtr _hdc;
|
||||
private IntPtr _hglrc;
|
||||
private bool _initializedBindings;
|
||||
private double _dpiX = 1.0, _dpiY = 1.0;
|
||||
|
||||
private static IntPtr s_sharedMasterCtx = IntPtr.Zero;
|
||||
|
||||
public OpenGlHwndHost() { } // for xaml
|
||||
|
||||
public OpenGLInstance Instance
|
||||
{
|
||||
get => (OpenGLInstance)GetValue(InstanceProperty);
|
||||
set => SetValue(InstanceProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty InstanceProperty =
|
||||
DependencyProperty.Register(nameof(Instance), typeof(OpenGLInstance),
|
||||
typeof(OpenGlHwndHost),
|
||||
new PropertyMetadata(null, OnInstanceChanged));
|
||||
|
||||
public bool ShareResourcesWithFirst
|
||||
{
|
||||
get => (bool)GetValue(ShareResourcesWithFirstProperty);
|
||||
set => SetValue(ShareResourcesWithFirstProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ShareResourcesWithFirstProperty =
|
||||
DependencyProperty.Register(nameof(ShareResourcesWithFirst), typeof(bool),
|
||||
typeof(OpenGlHwndHost), new PropertyMetadata(true));
|
||||
|
||||
public bool DebugContext { get; set; } = false;
|
||||
|
||||
const int WS_CHILD = 0x40000000;
|
||||
const int WS_VISIBLE = 0x10000000;
|
||||
|
||||
public OpenGlHwndHost(OpenGLInstance instance) : this()
|
||||
{
|
||||
Instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
protected override HandleRef BuildWindowCore(HandleRef hwndParent)
|
||||
{
|
||||
var dpi = VisualTreeHelper.GetDpi(Application.Current.MainWindow);
|
||||
_dpiX = dpi.DpiScaleX;
|
||||
_dpiY = dpi.DpiScaleY;
|
||||
|
||||
_hwnd = Wgl.CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE,
|
||||
0, 0, 1, 1,
|
||||
hwndParent.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
|
||||
|
||||
_hdc = Wgl.GetDC(_hwnd);
|
||||
if (_hdc == IntPtr.Zero) throw new InvalidOperationException("GetDC failed.");
|
||||
|
||||
if (!Wgl.ApplyDefaultPixelFormat(_hdc))
|
||||
throw new InvalidOperationException("SetPixelFormat failed.");
|
||||
|
||||
IntPtr share = (ShareResourcesWithFirst && s_sharedMasterCtx != IntPtr.Zero) ? s_sharedMasterCtx : IntPtr.Zero;
|
||||
|
||||
_hglrc = Wgl.CreateBestContext(_hdc, shareWith: share, debug: DebugContext, coreProfile: true);
|
||||
if (_hglrc == IntPtr.Zero)
|
||||
throw new InvalidOperationException("wglCreateContext/CreateBestContext failed.");
|
||||
|
||||
if (s_sharedMasterCtx == IntPtr.Zero && ShareResourcesWithFirst)
|
||||
s_sharedMasterCtx = _hglrc;
|
||||
|
||||
Wgl.wglMakeCurrent(_hdc, _hglrc);
|
||||
|
||||
if (!_initializedBindings)
|
||||
{
|
||||
GL.LoadBindings(new WglBindingsContext());
|
||||
_initializedBindings = true;
|
||||
}
|
||||
|
||||
var size = GetPixelSize();
|
||||
InstanceResize(size.width, size.height);
|
||||
|
||||
CompositionTarget.Rendering += OnRendering;
|
||||
|
||||
return new HandleRef(this, _hwnd);
|
||||
}
|
||||
|
||||
protected override void DestroyWindowCore(HandleRef hwnd)
|
||||
{
|
||||
CompositionTarget.Rendering -= OnRendering;
|
||||
|
||||
Wgl.wglMakeCurrent(IntPtr.Zero, IntPtr.Zero);
|
||||
|
||||
if (_hglrc != IntPtr.Zero)
|
||||
{
|
||||
Wgl.wglDeleteContext(_hglrc);
|
||||
_hglrc = IntPtr.Zero;
|
||||
}
|
||||
|
||||
if (_hdc != IntPtr.Zero)
|
||||
{
|
||||
Wgl.ReleaseDC(_hwnd, _hdc);
|
||||
_hdc = IntPtr.Zero;
|
||||
}
|
||||
|
||||
if (hwnd.Handle != IntPtr.Zero)
|
||||
{
|
||||
Wgl.DestroyWindow(hwnd.Handle);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Instance?.Dispose();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
|
||||
{
|
||||
base.OnRenderSizeChanged(sizeInfo);
|
||||
var size = GetPixelSize();
|
||||
InstanceResize(size.width, size.height);
|
||||
}
|
||||
|
||||
private void OnRendering(object sender, EventArgs e)
|
||||
{
|
||||
if (_hdc == IntPtr.Zero || _hglrc == IntPtr.Zero) return;
|
||||
|
||||
Wgl.wglMakeCurrent(_hdc, _hglrc);
|
||||
Instance.Render();
|
||||
Wgl.SwapBuffers(_hdc);
|
||||
Wgl.wglMakeCurrent(IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
|
||||
private (int width, int height) GetPixelSize()
|
||||
{
|
||||
int w = (int)Math.Max(1, Math.Round(ActualWidth * _dpiX));
|
||||
int h = (int)Math.Max(1, Math.Round(ActualHeight * _dpiY));
|
||||
return (w, h);
|
||||
}
|
||||
|
||||
private void InstanceResize(int pixelWidth, int pixelHeight)
|
||||
{
|
||||
try
|
||||
{
|
||||
Instance.EnqueueOnGlThread(() =>
|
||||
{
|
||||
Instance.Resize(pixelWidth, pixelHeight);
|
||||
});
|
||||
}
|
||||
catch { /* ignore */ }
|
||||
}
|
||||
|
||||
private static void OnInstanceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var host = (OpenGlHwndHost)d;
|
||||
if (host._hwnd != IntPtr.Zero && host.Instance != null)
|
||||
{
|
||||
var size = host.GetPixelSize();
|
||||
host.InstanceResize(size.width, size.height);
|
||||
}
|
||||
}
|
||||
|
||||
public IntPtr Hwnd => _hwnd;
|
||||
public IntPtr Hglrc => _hglrc;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
using Assimp;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using OpenTK.Mathematics;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using WpfOpenGLSubwindowingClasses.Camera;
|
||||
using WpfOpenGLSubwindowingClasses.ModelHandling;
|
||||
using WpfOpenGLSubwindowingClasses.Shader;
|
||||
using PrimitiveType = OpenTK.Graphics.OpenGL4.PrimitiveType;
|
||||
using Quaternion = OpenTK.Mathematics.Quaternion;
|
||||
|
||||
namespace WpfOpenGLSubwindowingClasses.Renderer
|
||||
{
|
||||
public class OpenGLInstance(string modelVertexShaderPath, string modelFragmentShaderPath, string axesVertexShaderPath, string axesFragmentShaderPath)
|
||||
{
|
||||
private bool _initialized;
|
||||
private bool _disposeRequested;
|
||||
|
||||
private string _modelVertexShaderPath = modelVertexShaderPath;
|
||||
private string _modelFragmentShaderPath = modelFragmentShaderPath;
|
||||
private string _axesVertexShaderPath = axesVertexShaderPath;
|
||||
private string _axesFragmentShaderPath = axesFragmentShaderPath;
|
||||
|
||||
private Shader.Shader? _shader;
|
||||
private Shader.Shader? _colorShader;
|
||||
private AxisLines _axisLines = new();
|
||||
public bool DisplayAxisLines { get; set; } = false;
|
||||
|
||||
PrimitiveType? DrawModeOverride = null;
|
||||
|
||||
List<WpfOpenGLSubwindowingClasses.ModelHandling.Model> Models = new();
|
||||
|
||||
public CancellationToken CancellationToken { get; set; }
|
||||
public Color4 BackgroundColor { get; set; } = Color4.White;
|
||||
|
||||
readonly ConcurrentQueue<Action> _glQueue = new();
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
if (_initialized) return;
|
||||
|
||||
GL.ClearColor(.0f, .0f, .0f, .0f);
|
||||
GL.Enable(EnableCap.DepthTest);
|
||||
GL.Enable(EnableCap.Multisample);
|
||||
GL.Disable(EnableCap.CullFace);
|
||||
|
||||
// Shader
|
||||
_shader = new Shader.Shader(_modelVertexShaderPath, _modelFragmentShaderPath);
|
||||
_shader.Use();
|
||||
|
||||
_colorShader = new Shader.Shader(_axesVertexShaderPath, _axesFragmentShaderPath);
|
||||
_axisLines.Create(5f);
|
||||
|
||||
OrbitCamera.Initialize(new Vector3(-5, -3, 5), Vector3.UnitY);
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
public void EnqueueOnGlThread(Action a) => _glQueue.Enqueue(a);
|
||||
|
||||
public void Render()
|
||||
{
|
||||
if (_disposeRequested || CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_initialized) Initialize();
|
||||
if (_shader == null ||_colorShader == null) return;
|
||||
|
||||
while (_glQueue.TryDequeue(out var a)) a();
|
||||
|
||||
_shader.Use();
|
||||
|
||||
GL.Viewport(0, 0, OrbitCamera.Width, OrbitCamera.Height);
|
||||
|
||||
_shader.SetMatrix4("view", OrbitCamera.View);
|
||||
_shader.SetMatrix4("proj", OrbitCamera.Projection);
|
||||
_shader.SetVector3("camPos", OrbitCamera.Position);
|
||||
|
||||
_shader.SetVector4("lightColor", new Vector4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
|
||||
var lightPos = OrbitCamera.Position - OrbitCamera.Front * 20f;
|
||||
_shader.SetVector3("lightPos", lightPos);
|
||||
|
||||
GL.ClearColor(BackgroundColor);
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
||||
|
||||
foreach(var model in Models) model.Draw(_shader, DrawModeOverride);
|
||||
|
||||
if (DisplayAxisLines) _axisLines.Draw(_colorShader, OrbitCamera.View, OrbitCamera.Projection, Matrix4.Identity, 1f);
|
||||
}
|
||||
|
||||
public void SetPolygonMode(PrimitiveType type) => DrawModeOverride = type;
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_disposeRequested = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_initialized) return;
|
||||
|
||||
GL.BindVertexArray(0);
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
|
||||
|
||||
_shader?.Dispose();
|
||||
_shader = null;
|
||||
|
||||
_colorShader?.Dispose();
|
||||
_colorShader = null;
|
||||
|
||||
_axisLines?.Dispose();
|
||||
|
||||
foreach (var model in Models) model.Dispose();
|
||||
Models.Clear();
|
||||
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
public void Resize(int width, int height)
|
||||
{
|
||||
OrbitCamera.Width = width;
|
||||
OrbitCamera.Height = height;
|
||||
|
||||
float aspect = (width > 0 && height > 0) ? (float)width / height : 1f;
|
||||
float fovY = MathHelper.DegreesToRadians(OrbitCamera.Zoom);
|
||||
OrbitCamera.UpdateProjection(fovY, aspect);
|
||||
|
||||
}
|
||||
|
||||
public void LoadModel(string path, bool replaceWhenLoaded = false)
|
||||
{
|
||||
if (Models.Any(x => x.Path == path)) return;
|
||||
|
||||
var loader = new ModelLoader();
|
||||
WpfOpenGLSubwindowingClasses.ModelHandling.Model? model = null;
|
||||
try
|
||||
{
|
||||
model = loader.Load(path);
|
||||
}
|
||||
catch { return; }
|
||||
if (model is null) return;
|
||||
|
||||
model.CenterModelAroundVec();
|
||||
model.ComputeCenterOffset();
|
||||
model.ApplyUniformScale(ComputeScaleToTargetRadius(model, 2f));
|
||||
model.Rotation = Quaternion.FromAxisAngle(Vector3.UnitX, MathHelper.DegreesToRadians(-90f));
|
||||
|
||||
if (replaceWhenLoaded) ClearModels();
|
||||
|
||||
Models.Add(model);
|
||||
|
||||
EnqueueOnGlThread(() =>
|
||||
{
|
||||
foreach (var m in model.Meshes) m.CreateGpuBuffers();
|
||||
});
|
||||
|
||||
FocusCameraToModel(model);
|
||||
}
|
||||
|
||||
public async void LoadModelAsync(string path, bool replaceWhenLoaded = false)
|
||||
{
|
||||
if (Models.Any(x => x.Path == path)) return;
|
||||
|
||||
var loader = new ModelLoader();
|
||||
WpfOpenGLSubwindowingClasses.ModelHandling.Model? model;
|
||||
try
|
||||
{
|
||||
model = await Task.Run(() => loader.Load(path, loadTextures: false)); // load textures later on ui thread
|
||||
}
|
||||
catch { return; }
|
||||
if (model is null) return;
|
||||
|
||||
model.CenterModelAroundVec();
|
||||
model.ComputeCenterOffset();
|
||||
model.ApplyUniformScale(ComputeScaleToTargetRadius(model, 2f));
|
||||
model.Rotation = Quaternion.FromAxisAngle(Vector3.UnitX, MathHelper.DegreesToRadians(-90f));
|
||||
|
||||
if (replaceWhenLoaded) ClearModels();
|
||||
|
||||
Models.Add(model);
|
||||
|
||||
EnqueueOnGlThread(() =>
|
||||
{
|
||||
foreach (var m in model.Meshes)
|
||||
{
|
||||
m.CreateGpuBuffers();
|
||||
|
||||
// Load images on UI Thread so GL is not throwing errors
|
||||
if (!string.IsNullOrEmpty(m.DiffuseTexturePath) && m.DiffuseTextureHandle == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var diffuseTexture = Texture.LoadFromFile(m.DiffuseTexturePath);
|
||||
m.DiffuseTextureHandle = diffuseTexture.Handle;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error loading diffuse texture '{m.DiffuseTexturePath}': {ex.Message}");
|
||||
m.DiffuseTextureHandle = 0;
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrEmpty(m.SpecularTexturePath) && m.SpecularTextureHandle == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var specularTexture = TextureManager.LoadTextureFromFile(m.SpecularTexturePath);
|
||||
m.SpecularTextureHandle = specularTexture.Handle;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error loading specular texture '{m.SpecularTexturePath}': {ex.Message}");
|
||||
m.SpecularTextureHandle = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
FocusCameraToModel(model);
|
||||
}
|
||||
|
||||
public void ClearModels()
|
||||
{
|
||||
foreach(var model in Models) model.Dispose();
|
||||
Models.Clear();
|
||||
}
|
||||
|
||||
static float ComputeScaleToTargetRadius(WpfOpenGLSubwindowingClasses.ModelHandling.Model model, float targetRadius)
|
||||
{
|
||||
var (min, max) = model.ComputeGlobalBounds();
|
||||
var center = (min + max) * 0.5f;
|
||||
float r = 0f;
|
||||
foreach (var m in model.Meshes)
|
||||
foreach (var v in m.Vertices)
|
||||
r = Math.Max(r, (v.Position - center).Length);
|
||||
return r > 0f ? targetRadius / r : 1f;
|
||||
}
|
||||
|
||||
public void FocusCameraToModel(WpfOpenGLSubwindowingClasses.ModelHandling.Model model)
|
||||
{
|
||||
var (min, max) = model.ComputeGlobalBounds();
|
||||
var center = (min + max) * 0.5f;
|
||||
|
||||
float r = 0f;
|
||||
foreach (var m in model.Meshes)
|
||||
foreach (var v in m.Vertices)
|
||||
r = Math.Max(r, (v.Position - center).Length);
|
||||
|
||||
OrbitCamera.Target = center;
|
||||
|
||||
float aspect = (OrbitCamera.Width > 0 && OrbitCamera.Height > 0) ? (float)OrbitCamera.Width / OrbitCamera.Height : 1f;
|
||||
float fovY = MathHelper.DegreesToRadians(OrbitCamera.Zoom);
|
||||
float fovX = 2f * MathF.Atan(MathF.Tan(fovY / 2f) * aspect);
|
||||
|
||||
float dY = r / MathF.Tan(fovY / 2f);
|
||||
float dX = r / MathF.Tan(fovX / 2f);
|
||||
float dist = MathF.Max(dX, dY) * 1.2f;
|
||||
|
||||
OrbitCamera.MinOrbitRadius = r * 1.05f;
|
||||
OrbitCamera.MaxOrbitRadius = r * 50f;
|
||||
OrbitCamera.OrbitRadius = Math.Clamp(dist, OrbitCamera.MinOrbitRadius, OrbitCamera.MaxOrbitRadius);
|
||||
|
||||
/* no override
|
||||
float near = Math.Max(0.05f, OrbitCamera.OrbitRadius - 2f * r);
|
||||
float far = OrbitCamera.OrbitRadius + 4f * r;
|
||||
near = Math.Max(0.01f, Math.Min(near, far * 0.5f));
|
||||
|
||||
OrbitCamera.Near = near;
|
||||
OrbitCamera.Far = far;
|
||||
*/
|
||||
|
||||
OrbitCamera.RecalculateCameraPositionAndVectors();
|
||||
OrbitCamera.UpdateProjection(fovY, aspect);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using OpenTK.Mathematics;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace WpfOpenGLSubwindowingClasses.Shader
|
||||
{
|
||||
public class Shader : IDisposable
|
||||
{
|
||||
public int Program;
|
||||
private bool disposedValue = false;
|
||||
|
||||
public bool Logging { get; set; } = false;
|
||||
public bool LogToDebug { get; set; } = false;
|
||||
|
||||
public Shader(string vertexPath, string fragmentPath, bool logging = false, bool logToDebug = false)
|
||||
{
|
||||
Logging = logging;
|
||||
LogToDebug = logToDebug;
|
||||
|
||||
string VertexShaderSource = File.ReadAllText(vertexPath);
|
||||
string FragmentShaderSource = File.ReadAllText(fragmentPath);
|
||||
|
||||
(int VertexShader, int FragmentShader) = GenerateAndBindShaders(VertexShaderSource, FragmentShaderSource);
|
||||
|
||||
if (CompileShaders(VertexShader, FragmentShader) == 0) throw new Exception("Failed to compile shaders");
|
||||
if (LinkShadersToProgram(VertexShader, FragmentShader) == 0) throw new Exception("Failed to link Shaders to Program");
|
||||
|
||||
Cleanup(VertexShader, FragmentShader);
|
||||
}
|
||||
|
||||
public void SetMatrix4(string name, Matrix4 value) => GL.UniformMatrix4(GL.GetUniformLocation(Program, name), false, ref value);
|
||||
public void SetVector3(string name, Vector3 value) => GL.Uniform3(GL.GetUniformLocation(Program, name), value);
|
||||
public void SetVector4(string name, Vector4 value) => GL.Uniform4(GL.GetUniformLocation(Program, name), value);
|
||||
public void SetColor4(string name, Color4 value) => GL.Uniform4(GL.GetUniformLocation(Program, name), value);
|
||||
public void SetFloat(string name, float value) => GL.Uniform1(GL.GetUniformLocation(Program, name), value);
|
||||
public void SetInt(string name, int value) => GL.Uniform1(GL.GetUniformLocation(Program, name), value);
|
||||
public void SetBool(string name, bool value) => GL.Uniform1(GL.GetUniformLocation(Program, name), value ? 1 : 0);
|
||||
|
||||
private (int, int) GenerateAndBindShaders(string VertexShaderSource, string FragmentShaderSource)
|
||||
{
|
||||
int VertexShader, FragmentShader;
|
||||
|
||||
// Generate and Bind
|
||||
VertexShader = GL.CreateShader(ShaderType.VertexShader);
|
||||
GL.ShaderSource(VertexShader, VertexShaderSource);
|
||||
|
||||
FragmentShader = GL.CreateShader(ShaderType.FragmentShader);
|
||||
GL.ShaderSource(FragmentShader, FragmentShaderSource);
|
||||
|
||||
return (VertexShader, FragmentShader);
|
||||
}
|
||||
|
||||
private int CompileShaders(int VertexShader, int FragmentShader)
|
||||
{
|
||||
// Compile
|
||||
GL.CompileShader(VertexShader);
|
||||
|
||||
GL.GetShader(VertexShader, ShaderParameter.CompileStatus, out int success1);
|
||||
if (success1 == 0)
|
||||
{
|
||||
string infoLog = GL.GetShaderInfoLog(VertexShader);
|
||||
Log(infoLog);
|
||||
return 0;
|
||||
}
|
||||
|
||||
GL.CompileShader(FragmentShader);
|
||||
|
||||
GL.GetShader(FragmentShader, ShaderParameter.CompileStatus, out int success2);
|
||||
if (success2 == 0)
|
||||
{
|
||||
string infoLog = GL.GetShaderInfoLog(FragmentShader);
|
||||
Log(infoLog);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private int LinkShadersToProgram(int VertexShader, int FragmentShader)
|
||||
{
|
||||
Program = GL.CreateProgram();
|
||||
|
||||
GL.AttachShader(Program, VertexShader);
|
||||
GL.AttachShader(Program, FragmentShader);
|
||||
|
||||
GL.LinkProgram(Program);
|
||||
|
||||
GL.GetProgram(Program, GetProgramParameterName.LinkStatus, out int success);
|
||||
if (success == 0)
|
||||
{
|
||||
string infoLog = GL.GetProgramInfoLog(Program);
|
||||
Log(infoLog);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private void Cleanup(int VertexShader, int FragmentShader)
|
||||
{
|
||||
GL.DetachShader(Program, VertexShader);
|
||||
GL.DetachShader(Program, FragmentShader);
|
||||
GL.DeleteShader(FragmentShader);
|
||||
GL.DeleteShader(VertexShader);
|
||||
}
|
||||
|
||||
public void Use() => GL.UseProgram(Program);
|
||||
|
||||
public virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
GL.DeleteProgram(Program);
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
~Shader()
|
||||
{
|
||||
if (disposedValue == false)
|
||||
{
|
||||
Log("GPU Resource leak! Did you forget to call Dispose()?");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
void Log(string message)
|
||||
{
|
||||
if (Logging && !LogToDebug) Console.WriteLine(message);
|
||||
else if (Logging && LogToDebug) Debug.WriteLine(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
#version 410 core
|
||||
|
||||
in DATA {
|
||||
vec3 Normal;
|
||||
vec3 color;
|
||||
vec2 texCoord;
|
||||
vec3 WorldPos;
|
||||
} data_in;
|
||||
|
||||
vec2 texCoord = data_in.texCoord;
|
||||
vec3 Normal = data_in.Normal;
|
||||
vec3 crntPos = data_in.WorldPos;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
uniform sampler2D diffuse0;
|
||||
uniform sampler2D specular0;
|
||||
|
||||
uniform vec4 material_ambient;
|
||||
uniform vec4 material_diffuse;
|
||||
uniform vec4 material_specular;
|
||||
uniform float material_shininess;
|
||||
|
||||
uniform bool hasDiffuseTexture;
|
||||
uniform bool hasSpecularTexture;
|
||||
|
||||
uniform vec4 lightColor;
|
||||
uniform vec3 lightPos;
|
||||
uniform vec3 camPos;
|
||||
|
||||
vec4 directLight()
|
||||
{
|
||||
vec3 final_ambient_color;
|
||||
vec3 final_diffuse_color;
|
||||
vec3 final_specular_color;
|
||||
|
||||
if(hasDiffuseTexture){
|
||||
final_diffuse_color = texture(diffuse0, texCoord).rgb;
|
||||
final_ambient_color = material_ambient.rgb;
|
||||
}
|
||||
else{
|
||||
final_ambient_color = material_ambient.rgb;
|
||||
final_diffuse_color = material_diffuse.rgb;
|
||||
}
|
||||
|
||||
if(hasSpecularTexture){
|
||||
final_specular_color = texture(specular0, texCoord).rgb;
|
||||
}
|
||||
else {
|
||||
final_specular_color = material_specular.rgb;
|
||||
}
|
||||
|
||||
// ambient lighting
|
||||
float ambientStrength = 0.20f;
|
||||
vec3 ambient = ambientStrength * final_ambient_color;
|
||||
|
||||
// diffuse lighting
|
||||
vec3 normal = normalize(Normal);
|
||||
vec3 lightDirection = normalize(lightPos - crntPos);
|
||||
float diff = max(dot(normal, lightDirection), 0.0f);
|
||||
vec3 diffuse = diff * final_diffuse_color;
|
||||
|
||||
// specular lighting
|
||||
vec3 specular = vec3(0.0f);
|
||||
if(diff > 0.0f){
|
||||
float specularStrength = 0.50f;
|
||||
vec3 viewDirection = normalize(camPos - crntPos);
|
||||
vec3 reflectionDirection = reflect(-lightDirection, normal);
|
||||
|
||||
// Blinn-Phong Model (Halway Vector)
|
||||
vec3 halfwayDir = normalize(lightDirection + viewDirection);
|
||||
float spec = pow(max(dot(normal, halfwayDir), 0.0f), material_shininess);
|
||||
specular = specularStrength * spec * final_specular_color;
|
||||
}
|
||||
|
||||
return vec4(ambient + diffuse + specular, 1.0f) * lightColor;
|
||||
}
|
||||
|
||||
vec4 noLight()
|
||||
{
|
||||
vec3 final_color;
|
||||
|
||||
if(hasDiffuseTexture){
|
||||
final_color = texture(diffuse0, texCoord).rgb;
|
||||
}
|
||||
else{
|
||||
final_color = material_diffuse.rgb;
|
||||
}
|
||||
|
||||
return vec4(final_color, 1.0f); // Rückgabe der Farbe mit voller Deckkraft
|
||||
}
|
||||
|
||||
void main(){
|
||||
outColor = directLight();
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#version 410 core
|
||||
|
||||
layout (location = 0) in vec3 aPos;
|
||||
layout (location = 1) in vec3 aNormal;
|
||||
layout (location = 2) in vec3 aColor;
|
||||
layout (location = 3) in vec2 aTexCoord;
|
||||
|
||||
out DATA {
|
||||
vec3 Normal;
|
||||
vec3 color;
|
||||
vec2 texCoord;
|
||||
vec3 WorldPos;
|
||||
} data_out;
|
||||
|
||||
uniform mat4 model;
|
||||
// uniform mat4 camMatrix;
|
||||
uniform mat4 view;
|
||||
uniform mat4 proj;
|
||||
uniform mat4 translation;
|
||||
uniform mat4 rotation;
|
||||
uniform mat4 scale;
|
||||
|
||||
void main() {
|
||||
mat4 M = translation * rotation * scale * model;
|
||||
vec4 worldPos = M * vec4(aPos, 1.0);
|
||||
gl_Position = proj * view * worldPos;
|
||||
data_out.WorldPos = worldPos.xyz;
|
||||
data_out.Normal = mat3(transpose(inverse(M))) * aNormal;
|
||||
data_out.color = aColor;
|
||||
data_out.texCoord = vec2(aTexCoord.x, 1.0 - aTexCoord.y);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#version 410 core
|
||||
|
||||
in vec3 vColor;
|
||||
|
||||
out vec4 FragColor;
|
||||
|
||||
void main(){
|
||||
FragColor = vec4(vColor, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#version 410 core
|
||||
|
||||
layout(location=0) in vec3 aPos;
|
||||
layout(location=1) in vec3 aColor;
|
||||
|
||||
uniform mat4 view;
|
||||
uniform mat4 proj;
|
||||
uniform mat4 model;
|
||||
|
||||
out vec3 vColor;
|
||||
|
||||
void main(){
|
||||
vColor = aColor;
|
||||
gl_Position = proj * view * model * vec4(aPos,1.0);
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace WpfOpenGLSubwindowingClasses.Interop
|
||||
{
|
||||
internal static class Wgl
|
||||
{
|
||||
// PIXELFORMATDESCRIPTOR
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct PIXELFORMATDESCRIPTOR
|
||||
{
|
||||
public ushort nSize;
|
||||
public ushort nVersion;
|
||||
public uint dwFlags;
|
||||
public byte iPixelType;
|
||||
public byte cColorBits;
|
||||
public byte cRedBits;
|
||||
public byte cRedShift;
|
||||
public byte cGreenBits;
|
||||
public byte cGreenShift;
|
||||
public byte cBlueBits;
|
||||
public byte cBlueShift;
|
||||
public byte cAlphaBits;
|
||||
public byte cAlphaShift;
|
||||
public byte cAccumBits;
|
||||
public byte cAccumRedBits;
|
||||
public byte cAccumGreenBits;
|
||||
public byte cAccumBlueBits;
|
||||
public byte cAccumAlphaBits;
|
||||
public byte cDepthBits;
|
||||
public byte cStencilBits;
|
||||
public byte cAuxBuffers;
|
||||
public byte iLayerType;
|
||||
public byte bReserved;
|
||||
public uint dwLayerMask;
|
||||
public uint dwVisibleMask;
|
||||
public uint dwDamageMask;
|
||||
}
|
||||
|
||||
// PFD Flags
|
||||
public const uint PFD_DRAW_TO_WINDOW = 0x00000004;
|
||||
public const uint PFD_SUPPORT_OPENGL = 0x00000020;
|
||||
public const uint PFD_DOUBLEBUFFER = 0x00000001;
|
||||
public const byte PFD_TYPE_RGBA = 0;
|
||||
public const byte PFD_MAIN_PLANE = 0;
|
||||
|
||||
// Window Styles
|
||||
public const int WS_CHILD = 0x40000000;
|
||||
public const int WS_VISIBLE = 0x10000000;
|
||||
public const int WS_CLIPSIBLINGS = 0x04000000;
|
||||
public const int WS_CLIPCHILDREN = 0x02000000;
|
||||
|
||||
// WGL_ARB_create_context constants
|
||||
public const int WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
|
||||
public const int WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
|
||||
public const int WGL_CONTEXT_FLAGS_ARB = 0x2094;
|
||||
public const int WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
|
||||
public const int WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x0001;
|
||||
public const int WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x0002;
|
||||
public const int WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
|
||||
public const int WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
|
||||
|
||||
// Win32/GDI
|
||||
[DllImport("user32.dll")] public static extern IntPtr GetDC(IntPtr hWnd);
|
||||
[DllImport("user32.dll")] public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
|
||||
[DllImport("gdi32.dll")] public static extern int ChoosePixelFormat(IntPtr hdc, ref PIXELFORMATDESCRIPTOR pfd);
|
||||
[DllImport("gdi32.dll")] public static extern bool SetPixelFormat(IntPtr hdc, int format, ref PIXELFORMATDESCRIPTOR pfd);
|
||||
[DllImport("gdi32.dll")] public static extern bool SwapBuffers(IntPtr hdc);
|
||||
|
||||
// WGL core
|
||||
[DllImport("opengl32.dll")] public static extern IntPtr wglCreateContext(IntPtr hdc);
|
||||
[DllImport("opengl32.dll")] public static extern bool wglMakeCurrent(IntPtr hdc, IntPtr hglrc);
|
||||
[DllImport("opengl32.dll")] public static extern bool wglDeleteContext(IntPtr hglrc);
|
||||
[DllImport("opengl32.dll")] public static extern bool wglShareLists(IntPtr hglrcSrc, IntPtr hglrcDst);
|
||||
[DllImport("opengl32.dll")] public static extern IntPtr wglGetProcAddress(string name);
|
||||
|
||||
// Win32 window creation
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
public static extern IntPtr CreateWindowEx(
|
||||
int exStyle,
|
||||
string className,
|
||||
string windowName,
|
||||
int style,
|
||||
int x, int y, int width, int height,
|
||||
IntPtr parent, IntPtr menu, IntPtr instance, IntPtr param);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool DestroyWindow(IntPtr hwnd);
|
||||
|
||||
// Kernel32 for function address fallback
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
|
||||
public static extern IntPtr LoadLibrary(string lpFileName);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true)]
|
||||
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
|
||||
|
||||
// Convenience: Make/Done Current
|
||||
public static bool MakeCurrent(IntPtr hdc, IntPtr hglrc) => wglMakeCurrent(hdc, hglrc);
|
||||
public static void DoneCurrent() => wglMakeCurrent(IntPtr.Zero, IntPtr.Zero);
|
||||
public static bool DeleteContext(IntPtr hglrc) => wglDeleteContext(hglrc);
|
||||
|
||||
// Set a reasonable default PixelFormat (RGBA, double-buffer, depth/stencil)
|
||||
public static bool ApplyDefaultPixelFormat(IntPtr hdc, int colorBits = 24, int depthBits = 24, int stencilBits = 8)
|
||||
{
|
||||
var pfd = new PIXELFORMATDESCRIPTOR
|
||||
{
|
||||
nSize = (ushort)Marshal.SizeOf<PIXELFORMATDESCRIPTOR>(),
|
||||
nVersion = 1,
|
||||
dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
|
||||
iPixelType = PFD_TYPE_RGBA,
|
||||
cColorBits = (byte)colorBits,
|
||||
cDepthBits = (byte)depthBits,
|
||||
cStencilBits = (byte)stencilBits,
|
||||
iLayerType = PFD_MAIN_PLANE
|
||||
};
|
||||
|
||||
int format = ChoosePixelFormat(hdc, ref pfd);
|
||||
if (format == 0) return false;
|
||||
return SetPixelFormat(hdc, format, ref pfd);
|
||||
}
|
||||
|
||||
// Delegates for WGL extensions
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||
private delegate IntPtr wglCreateContextAttribsARB_t(IntPtr hdc, IntPtr hShareContext, int[] attribList);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||
private delegate bool wglSwapIntervalEXT_t(int interval);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||
private delegate int wglGetSwapIntervalEXT_t();
|
||||
|
||||
private static wglSwapIntervalEXT_t? s_swapIntervalEXT;
|
||||
private static wglGetSwapIntervalEXT_t? s_getSwapIntervalEXT;
|
||||
|
||||
private static void EnsureSwapIntervalDelegates()
|
||||
{
|
||||
if (s_swapIntervalEXT == null)
|
||||
{
|
||||
var p = wglGetProcAddress("wglSwapIntervalEXT");
|
||||
if (p != IntPtr.Zero)
|
||||
s_swapIntervalEXT = (wglSwapIntervalEXT_t)Marshal.GetDelegateForFunctionPointer(p, typeof(wglSwapIntervalEXT_t));
|
||||
}
|
||||
if (s_getSwapIntervalEXT == null)
|
||||
{
|
||||
var p = wglGetProcAddress("wglGetSwapIntervalEXT");
|
||||
if (p != IntPtr.Zero)
|
||||
s_getSwapIntervalEXT = (wglGetSwapIntervalEXT_t)Marshal.GetDelegateForFunctionPointer(p, typeof(wglGetSwapIntervalEXT_t));
|
||||
}
|
||||
}
|
||||
|
||||
// Try to set VSync (interval 0=off, 1=on)
|
||||
public static bool TrySetSwapInterval(int interval)
|
||||
{
|
||||
EnsureSwapIntervalDelegates();
|
||||
return s_swapIntervalEXT != null && s_swapIntervalEXT(interval);
|
||||
}
|
||||
|
||||
// Get current swap interval (-1 if unavailable)
|
||||
public static int GetSwapInterval()
|
||||
{
|
||||
EnsureSwapIntervalDelegates();
|
||||
return s_getSwapIntervalEXT != null ? s_getSwapIntervalEXT() : -1;
|
||||
}
|
||||
|
||||
// Create a "best" OpenGL context:
|
||||
// - tries wglCreateContextAttribsARB for core/modern versions
|
||||
// - falls back to legacy if needed
|
||||
// - optionally shares resources with 'shareWith' via wglShareLists
|
||||
public static IntPtr CreateBestContext(IntPtr hdc, IntPtr shareWith = default, bool debug = false, bool coreProfile = true)
|
||||
{
|
||||
// Create temp legacy context to load the ARB function (requires current context)
|
||||
IntPtr temp = wglCreateContext(hdc);
|
||||
if (temp != IntPtr.Zero) wglMakeCurrent(hdc, temp);
|
||||
|
||||
IntPtr proc = wglGetProcAddress("wglCreateContextAttribsARB");
|
||||
IntPtr realCtx = IntPtr.Zero;
|
||||
|
||||
if (proc != IntPtr.Zero)
|
||||
{
|
||||
var createAttribs = (wglCreateContextAttribsARB_t)Marshal.GetDelegateForFunctionPointer(proc, typeof(wglCreateContextAttribsARB_t));
|
||||
int flags = 0;
|
||||
if (debug) flags |= WGL_CONTEXT_DEBUG_BIT_ARB;
|
||||
if (coreProfile) flags |= WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB;
|
||||
|
||||
// Try descending versions
|
||||
int[][] versions =
|
||||
{
|
||||
new[]{4,6}, new[]{4,5}, new[]{4,4}, new[]{4,3}, new[]{4,2}, new[]{4,1}, new[]{4,0},
|
||||
new[]{3,3}, new[]{3,2}
|
||||
};
|
||||
|
||||
foreach (var v in versions)
|
||||
{
|
||||
int[] attribs =
|
||||
{
|
||||
WGL_CONTEXT_MAJOR_VERSION_ARB, v[0],
|
||||
WGL_CONTEXT_MINOR_VERSION_ARB, v[1],
|
||||
WGL_CONTEXT_FLAGS_ARB, flags,
|
||||
WGL_CONTEXT_PROFILE_MASK_ARB, coreProfile ? WGL_CONTEXT_CORE_PROFILE_BIT_ARB : WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB,
|
||||
0
|
||||
};
|
||||
realCtx = createAttribs(hdc, shareWith, attribs);
|
||||
if (realCtx != IntPtr.Zero) break;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to legacy
|
||||
if (realCtx == IntPtr.Zero)
|
||||
{
|
||||
realCtx = wglCreateContext(hdc);
|
||||
if (realCtx == IntPtr.Zero) return IntPtr.Zero;
|
||||
if (shareWith != IntPtr.Zero)
|
||||
wglShareLists(shareWith, realCtx);
|
||||
}
|
||||
|
||||
// Cleanup temp
|
||||
if (temp != IntPtr.Zero)
|
||||
{
|
||||
wglMakeCurrent(IntPtr.Zero, IntPtr.Zero);
|
||||
wglDeleteContext(temp);
|
||||
}
|
||||
|
||||
return realCtx;
|
||||
}
|
||||
|
||||
// Helper to create a child window using the built-in "STATIC" class.
|
||||
// This is suitable as a render target HWND for OpenGL.
|
||||
public static IntPtr CreateChildWindow(IntPtr parent, int x, int y, int width, int height, bool visible = true)
|
||||
{
|
||||
int style = WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | (visible ? WS_VISIBLE : 0);
|
||||
return CreateWindowEx(
|
||||
0,
|
||||
"STATIC",
|
||||
"",
|
||||
style,
|
||||
x, y, width, height,
|
||||
parent, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics.Wgl;
|
||||
using System;
|
||||
using WpfOpenGLSubwindowingClasses.Interop;
|
||||
|
||||
namespace WpfOpenGLSubwindowingClasses.Interop
|
||||
{
|
||||
internal sealed class WglBindingsContext : IBindingsContext
|
||||
{
|
||||
private readonly IntPtr _opengl32 = Wgl.LoadLibrary("opengl32.dll");
|
||||
|
||||
public IntPtr GetProcAddress(string procName)
|
||||
{
|
||||
var ptr = Wgl.wglGetProcAddress(procName);
|
||||
if (ptr == IntPtr.Zero && _opengl32 != IntPtr.Zero)
|
||||
ptr = Wgl.GetProcAddress(_opengl32, procName);
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Shaders\BasicShader.frag" />
|
||||
<None Remove="Shaders\BasicShader.vert" />
|
||||
<None Remove="Shaders\ColorShader.frag" />
|
||||
<None Remove="Shaders\ColorShader.vert" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Shaders\BasicShader.frag">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Shaders\BasicShader.vert">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Shaders\ColorShader.frag">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Shaders\ColorShader.vert">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AssimpNetter" Version="6.0.2.1" />
|
||||
<PackageReference Include="OpenTK" Version="4.9.4" />
|
||||
<PackageReference Include="StbImageSharp" Version="2.30.15" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user