diff --git a/CUE4Parse b/CUE4Parse index 2e3274d9..54a95bf8 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit 2e3274d96eb02b4b376f9f44942da81593130d28 +Subproject commit 54a95bf82efd13cd9e4423f182296f0c0127ccaf diff --git a/FModel/FModel.csproj b/FModel/FModel.csproj index 36d889d9..557f6aec 100644 --- a/FModel/FModel.csproj +++ b/FModel/FModel.csproj @@ -105,6 +105,8 @@ + + @@ -126,6 +128,8 @@ + + @@ -222,6 +226,8 @@ + + diff --git a/FModel/Resources/default.frag b/FModel/Resources/default.frag index 2a392bb5..ae2968b0 100644 --- a/FModel/Resources/default.frag +++ b/FModel/Resources/default.frag @@ -2,6 +2,7 @@ #define PI 3.1415926535897932384626433832795 #define MAX_UV_COUNT 8 +#define MAX_LIGHT_COUNT 100 in vec3 fPos; in vec3 fNormal; @@ -46,7 +47,19 @@ struct Parameters float UVScale; }; +struct Light { + vec4 Color; + vec3 Position; + float Intensity; + + float Constant; + float Linear; + float Quadratic; +}; + uniform Parameters uParameters; +uniform Light uLights[MAX_LIGHT_COUNT]; +uniform int uNumLights; uniform int uNumTexCoords; uniform vec3 uViewPos; uniform vec3 uViewDir; @@ -175,6 +188,27 @@ void main() if (!bVertexColors[1]) { result += CalcPBRLight(layer, normals); + + vec3 lights = vec3(uNumLights > 0 ? 0 : 1); + for (int i = 0; i < uNumLights; i++) + { + float distance = length(uLights[i].Position - fPos); + float attenuation = 1.0 / (1.0 + uLights[i].Linear * distance + uLights[i].Quadratic * (distance * distance)); + vec3 intensity = uLights[i].Color.rgb * uLights[i].Intensity; + lights += result * intensity * attenuation; + +// float attenuation = 0.0; +// float theta = dot(normalize(uLights[i].Position - fPos), normalize(-uLights[i].Direction)); +// if(theta > uLights[i].ConeAngle) +// { +// float distanceToLight = length(uLights[i].Position - fPos); +// attenuation = 1.0 / (1.0 + uLights[i].Attenuation * pow(distanceToLight, 2)); +// } +// +// vec3 intensity = uLights[i].Color.rgb * uLights[i].Intensity; +// lights += result * intensity * attenuation; + } + result *= lights; // use * to darken the scene, + to lighten it } result = result / (result + vec3(1.0f)); diff --git a/FModel/Resources/light.frag b/FModel/Resources/light.frag new file mode 100644 index 00000000..26b943f2 --- /dev/null +++ b/FModel/Resources/light.frag @@ -0,0 +1,16 @@ +#version 330 + +uniform sampler2D uIcon; +uniform vec4 uColor; + +in vec2 fTexCoords; + +out vec4 FragColor; + +void main() +{ + vec4 color = uColor * texture(uIcon, fTexCoords); + if (color.a < 0.1) discard; + + FragColor = uColor; +} diff --git a/FModel/Resources/light.vert b/FModel/Resources/light.vert new file mode 100644 index 00000000..14516356 --- /dev/null +++ b/FModel/Resources/light.vert @@ -0,0 +1,15 @@ +#version 330 core + +layout (location = 0) in vec3 vPos; +layout (location = 9) in mat4 vInstanceMatrix; + +uniform mat4 uView; +uniform mat4 uProjection; + +out vec2 fTexCoords; + +void main() +{ + gl_Position = uProjection * uView * vInstanceMatrix * vec4(inverse(mat3(uView)) * vPos, 1.0); + fTexCoords = -vPos.xy; +} diff --git a/FModel/Resources/pointlight.png b/FModel/Resources/pointlight.png new file mode 100644 index 00000000..7360e2b1 Binary files /dev/null and b/FModel/Resources/pointlight.png differ diff --git a/FModel/Resources/spotlight.png b/FModel/Resources/spotlight.png new file mode 100644 index 00000000..83b0e048 Binary files /dev/null and b/FModel/Resources/spotlight.png differ diff --git a/FModel/Views/SettingsView.xaml b/FModel/Views/SettingsView.xaml index dd224932..dfae0bd7 100644 --- a/FModel/Views/SettingsView.xaml +++ b/FModel/Views/SettingsView.xaml @@ -372,7 +372,7 @@ IsChecked="{Binding SaveMaterials, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"/> - + diff --git a/FModel/Views/Snooper/Cache.cs b/FModel/Views/Snooper/Cache.cs index a85f0603..c7e0bbeb 100644 --- a/FModel/Views/Snooper/Cache.cs +++ b/FModel/Views/Snooper/Cache.cs @@ -13,6 +13,8 @@ public class Cache : IDisposable { public readonly Dictionary Models; public readonly Dictionary Textures; + public readonly List Lights; + public readonly Dictionary Icons; private ETexturePlatform _platform; @@ -22,12 +24,18 @@ public class Cache : IDisposable { Models = new Dictionary(); Textures = new Dictionary(); - Icons = new Dictionary(); + Lights = new List(); + + Icons = new Dictionary + { + ["material"] = new ("materialicon"), + ["noimage"] = new ("T_Placeholder_Item_Image"), + ["pointlight"] = new ("pointlight"), + ["spotlight"] = new ("spotlight"), + }; + _platform = UserSettings.Default.OverridedPlatform; _game = Services.ApplicationService.ApplicationView.CUE4Parse.Game; - - Icons["material"] = new Texture("materialicon"); - Icons["noimage"] = new Texture("T_Placeholder_Item_Image"); } public bool TryGetCachedModel(UStaticMesh o, out Model model) @@ -72,6 +80,11 @@ public class Cache : IDisposable if (model.IsSetup) continue; model.Setup(this); } + + foreach (var light in Lights) + { + light.Setup(); + } } public void DisposeModels() @@ -94,10 +107,16 @@ public class Cache : IDisposable } } - public void Dispose() + public void Reset() { DisposeModels(); Models.Clear(); + Lights.Clear(); + } + + public void Dispose() + { + Reset(); DisposeTextures(); Textures.Clear(); diff --git a/FModel/Views/Snooper/Camera.cs b/FModel/Views/Snooper/Camera.cs index 0afd2de4..0f783ce0 100644 --- a/FModel/Views/Snooper/Camera.cs +++ b/FModel/Views/Snooper/Camera.cs @@ -129,7 +129,7 @@ public class Camera if (ImGui.BeginTable("camera_editor", 2)) { SnimGui.Layout("Speed");ImGui.PushID(1); - ImGui.DragFloat("", ref Speed, _step, _zero, _infinite, "%.2f s/m", _clamp); + ImGui.DragFloat("", ref Speed, _step, _zero, _infinite, "%.2f m/s", _clamp); ImGui.PopID();SnimGui.Layout("Far Plane");ImGui.PushID(2); ImGui.DragFloat("", ref Far, 0.1f, 0.1f, Far * 2f, "%.2f m", _clamp); ImGui.PopID(); diff --git a/FModel/Views/Snooper/Light.cs b/FModel/Views/Snooper/Light.cs new file mode 100644 index 00000000..1ab6209d --- /dev/null +++ b/FModel/Views/Snooper/Light.cs @@ -0,0 +1,95 @@ +using System; +using System.Numerics; +using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Objects.Core.Math; +using OpenTK.Graphics.OpenGL4; + +namespace FModel.Views.Snooper; + +public abstract class Light : IDisposable +{ + private int _handle; + + private BufferObject _ebo; + private BufferObject _vbo; + private BufferObject _matrixVbo; + private VertexArrayObject _vao; + + public readonly uint[] Indices = { 0, 1, 2, 3, 4, 5 }; + public readonly float[] Vertices = { + 1f, 1f, 0f, + -1f, -1f, 0f, + -1f, 1f, 0f, + -1f, -1f, 0f, + 1f, 1f, 0f, + 1f, -1f, 0 + }; + public Texture Icon; + public readonly Transform Transform; + + public readonly Vector4 Color; + public readonly float Intensity; + public readonly float Linear; + public readonly float Quadratic; + + public Light(Texture icon, UObject light, FVector position) + { + var p = light.GetOrDefault("RelativeLocation", FVector.ZeroVector); + var r = light.GetOrDefault("RelativeRotation", FRotator.ZeroRotator); + + Transform = Transform.Identity; + Transform.Scale = new FVector(0.25f); + Transform.Position = position + r.RotateVector(p.ToMapVector()) * Constants.SCALE_DOWN_RATIO; + + Icon = icon; + + Color = light.GetOrDefault("LightColor", new FColor(0xFF, 0xFF, 0xFF, 0xFF)); + Intensity = light.GetOrDefault("Intensity", 1.0f); + + var radius = light.GetOrDefault("AttenuationRadius", 0.0f) * Constants.SCALE_DOWN_RATIO; + Linear = 4.5f / radius; + Quadratic = 75.0f / MathF.Pow(radius, 2); + } + + public void SetupInstances() + { + var instanceMatrix = new [] {Transform.Matrix}; + _matrixVbo = new BufferObject(instanceMatrix, BufferTarget.ArrayBuffer); + _vao.BindInstancing(); // VertexAttributePointer + } + + public void Setup() + { + _handle = GL.CreateProgram(); + + _ebo = new BufferObject(Indices, BufferTarget.ElementArrayBuffer); + _vbo = new BufferObject(Vertices, BufferTarget.ArrayBuffer); + _vao = new VertexArrayObject(_vbo, _ebo); + + _vao.VertexAttributePointer(0, 3, VertexAttribPointerType.Float, 3, 0); // position + SetupInstances(); + } + + public abstract void Render(int i, Shader shader); + + public void Render(Shader shader) + { + // GL.Disable(EnableCap.DepthTest); + _vao.Bind(); + + Icon?.Bind(TextureUnit.Texture0); + shader.SetUniform("uIcon", 0); + shader.SetUniform("uColor", Color); + + GL.DrawArrays(PrimitiveType.Triangles, 0, Indices.Length); + // GL.Enable(EnableCap.DepthTest); + } + + public void Dispose() + { + _ebo?.Dispose(); + _vbo?.Dispose(); + _vao?.Dispose(); + GL.DeleteProgram(_handle); + } +} diff --git a/FModel/Views/Snooper/Material.cs b/FModel/Views/Snooper/Material.cs index c7650b6d..06a2ae50 100644 --- a/FModel/Views/Snooper/Material.cs +++ b/FModel/Views/Snooper/Material.cs @@ -83,7 +83,7 @@ public class Material : IDisposable } { // colors - DiffuseColor = FillColors(numTexCoords, Diffuse, CMaterialParams2.DiffuseColors, new Vector4(0.5f)); + DiffuseColor = FillColors(numTexCoords, Diffuse, CMaterialParams2.DiffuseColors, Vector4.One); EmissiveColor = FillColors(numTexCoords, Emissive, CMaterialParams2.EmissiveColors, Vector4.One); } @@ -208,10 +208,15 @@ public class Material : IDisposable private const float _zero = 0.000001f; // doesn't actually work if _infinite is used as max value /shrug private const float _infinite = 0.0f; private const ImGuiSliderFlags _clamp = ImGuiSliderFlags.AlwaysClamp; - public void ImGuiParameters() + private void PushStyle() { ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(8, 3)); ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, new Vector2(0, 1)); + } + + public void ImGuiParameters() + { + PushStyle(); if (ImGui.BeginTable("parameters", 2)) { SnimGui.Layout("Emissive Multiplier");ImGui.PushID(1); @@ -253,7 +258,40 @@ public class Material : IDisposable } } - public IntPtr? GetSelectedTexture() + public void ImGuiTextures(Dictionary icons, Model model) + { + PushStyle(); + if (ImGui.BeginTable("material_textures", 2)) + { + SnimGui.Layout("Channel");ImGui.PushID(1); ImGui.BeginDisabled(model.NumTexCoords < 2); + ImGui.DragInt("", ref SelectedChannel, _step, 0, model.NumTexCoords - 1, "UV %i", ImGuiSliderFlags.AlwaysClamp); + ImGui.EndDisabled();ImGui.PopID();SnimGui.Layout("Type");ImGui.PushID(2); + ImGui.Combo("texture_type", ref SelectedTexture, "Diffuse\0Normals\0Specular\0Ambient Occlusion\0Emissive\0"); + ImGui.PopID(); + + switch (SelectedTexture) + { + case 0: + SnimGui.Layout("Color");ImGui.PushID(3); + ImGui.ColorEdit4("", ref DiffuseColor[SelectedChannel], ImGuiColorEditFlags.NoAlpha); + ImGui.PopID(); + break; + case 4: + SnimGui.Layout("Color");ImGui.PushID(3); + ImGui.ColorEdit4("", ref EmissiveColor[SelectedChannel], ImGuiColorEditFlags.NoAlpha); + ImGui.PopID(); + break; + } + + ImGui.EndTable(); + } + ImGui.PopStyleVar(2); + + ImGui.Image(GetSelectedTexture() ?? icons["noimage"].GetPointer(), new Vector2(ImGui.GetContentRegionAvail().X), Vector2.Zero, Vector2.One, Vector4.One, new Vector4(1.0f, 1.0f, 1.0f, 0.25f)); + ImGui.Spacing(); + } + + private IntPtr? GetSelectedTexture() { return SelectedTexture switch { diff --git a/FModel/Views/Snooper/PointLight.cs b/FModel/Views/Snooper/PointLight.cs new file mode 100644 index 00000000..bad35c25 --- /dev/null +++ b/FModel/Views/Snooper/PointLight.cs @@ -0,0 +1,21 @@ +using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Objects.Core.Math; + +namespace FModel.Views.Snooper; + +public class PointLight : Light +{ + public PointLight(Texture icon, UObject point, FVector position) : base(icon, point, position) + { + + } + + public override void Render(int i, Shader shader) + { + shader.SetUniform($"uLights[{i}].Color", Color); + shader.SetUniform($"uLights[{i}].Position", Transform.Position); + shader.SetUniform($"uLights[{i}].Intensity", Intensity); + shader.SetUniform($"uLights[{i}].Linear", Linear); + shader.SetUniform($"uLights[{i}].Quadratic", Quadratic); + } +} diff --git a/FModel/Views/Snooper/Renderer.cs b/FModel/Views/Snooper/Renderer.cs index 5c66ca55..79e97c86 100644 --- a/FModel/Views/Snooper/Renderer.cs +++ b/FModel/Views/Snooper/Renderer.cs @@ -24,9 +24,11 @@ public class Renderer : IDisposable private readonly Grid _grid; private Shader _shader; private Shader _outline; + private Shader _light; public bool ShowSkybox; public bool ShowGrid; + public bool ShowLights; public int VertexColor; public PickingTexture Picking { get; } @@ -49,6 +51,7 @@ public class Renderer : IDisposable public Camera Load(CancellationToken cancellationToken, UObject export) { + ShowLights = false; return export switch { UStaticMesh st => LoadStaticMesh(st), @@ -86,6 +89,7 @@ public class Renderer : IDisposable _shader = new Shader(); _outline = new Shader("outline"); + _light = new Shader("light"); Picking.Setup(); Cache.Setup(); @@ -101,17 +105,28 @@ public class Renderer : IDisposable _shader.Render(viewMatrix, cam.Position, cam.Direction, projMatrix); for (int i = 0; i < 6; i++) - { _shader.SetUniform($"bVertexColors[{i}]", i == VertexColor); - } - // render pass + // render model pass foreach (var model in Cache.Models.Values) { if (!model.Show) continue; model.Render(_shader); } + { // light pass + var uNumLights = Cache.Lights.Count; + _shader.SetUniform("uNumLights", ShowLights ? uNumLights : 0); + + if (ShowLights) + for (int i = 0; i < uNumLights; i++) + Cache.Lights[i].Render(i, _shader); + + _light.Render(viewMatrix, projMatrix); + for (int i = 0; i < uNumLights; i++) + Cache.Lights[i].Render(_light); + } + // outline pass if (Cache.Models.TryGetValue(Settings.SelectedModel, out var selected) && selected.Show) { @@ -188,6 +203,7 @@ public class Renderer : IDisposable Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{original.Name} ... {i}/{length}"); WorldCamera(actor, ref cam); + // WorldLight(actor); WorldMesh(actor, transform); AdditionalWorlds(actor, transform.Matrix, cancellationToken); } @@ -209,6 +225,14 @@ public class Renderer : IDisposable 0.01f, far * 25f, Math.Max(5f, far / 10f)); } + private void WorldLight(UObject actor) + { + if (!actor.TryGetValue(out FPackageIndex lightComponent, "LightComponent") || + lightComponent.Load() is not { } lightObject) return; + + Cache.Lights.Add(new PointLight(Cache.Icons["pointlight"], lightObject, FVector.ZeroVector)); + } + private void WorldMesh(UObject actor, Transform transform) { if (!actor.TryGetValue(out FPackageIndex staticMeshComponent, "StaticMeshComponent", "Mesh") || @@ -274,6 +298,17 @@ public class Renderer : IDisposable } Cache.Models[guid] = model; } + + if (actor.TryGetValue(out FPackageIndex treasureLight, "TreasureLight", "PointLight") && + treasureLight.TryLoad(out var tl) && tl.Template.TryLoad(out tl)) + { + Cache.Lights.Add(new PointLight(Cache.Icons["pointlight"], tl, t.Position)); + } + if (actor.TryGetValue(out FPackageIndex spotLight, "SpotLight") && + spotLight.TryLoad(out var sl) && sl.Template.TryLoad(out sl)) + { + Cache.Lights.Add(new SpotLight(Cache.Icons["spotlight"], sl, t.Position)); + } } private void AdditionalWorlds(UObject actor, Matrix4x4 relation, CancellationToken cancellationToken) @@ -308,6 +343,7 @@ public class Renderer : IDisposable _grid?.Dispose(); _shader?.Dispose(); _outline?.Dispose(); + _light?.Dispose(); Picking?.Dispose(); Cache?.Dispose(); } diff --git a/FModel/Views/Snooper/SnimGui.cs b/FModel/Views/Snooper/SnimGui.cs index b32eafa1..4a41a3c5 100644 --- a/FModel/Views/Snooper/SnimGui.cs +++ b/FModel/Views/Snooper/SnimGui.cs @@ -63,7 +63,9 @@ public class SnimGui ImGui.Checkbox("", ref s.Renderer.ShowSkybox); ImGui.PopID();Layout("Grid");ImGui.PushID(2); ImGui.Checkbox("", ref s.Renderer.ShowGrid); - ImGui.PopID();Layout("Vertex Colors");ImGui.PushID(3); + ImGui.PopID();Layout("Lights");ImGui.PushID(3); + ImGui.Checkbox("", ref s.Renderer.ShowLights); + ImGui.PopID();Layout("Vertex Colors");ImGui.PushID(4); ImGui.Combo("vertex_colors", ref s.Renderer.VertexColor, "Default\0Diffuse Only\0Colors\0Normals\0Tangent\0Texture Coordinates\0"); ImGui.PopID(); @@ -441,22 +443,7 @@ public class SnimGui ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); if (ImGui.CollapsingHeader("Textures")) { - ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(8, 3)); - ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, new Vector2(0, 1)); - if (ImGui.BeginTable("material_textures", 2)) - { - Layout("Channel");ImGui.PushID(1); ImGui.BeginDisabled(model.NumTexCoords < 2); - ImGui.DragInt("", ref material.SelectedChannel, 1, 0, model.NumTexCoords - 1, "UV %i", ImGuiSliderFlags.AlwaysClamp); - ImGui.EndDisabled();ImGui.PopID();Layout("Type");ImGui.PushID(2); - ImGui.Combo("texture_type", ref material.SelectedTexture, - "Diffuse\0Normals\0Specular\0Ambient Occlusion\0Emissive\0"); - ImGui.PopID(); - ImGui.EndTable(); - } - ImGui.PopStyleVar(2); - - ImGui.Image(material.GetSelectedTexture() ?? icons["noimage"].GetPointer(), new Vector2(ImGui.GetContentRegionAvail().X), Vector2.Zero, Vector2.One, Vector4.One, new Vector4(1.0f, 1.0f, 1.0f, 0.25f)); - ImGui.Spacing(); + material.ImGuiTextures(icons, model); } ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); diff --git a/FModel/Views/Snooper/Snooper.cs b/FModel/Views/Snooper/Snooper.cs index 9d72b865..d5d5718f 100644 --- a/FModel/Views/Snooper/Snooper.cs +++ b/FModel/Views/Snooper/Snooper.cs @@ -49,8 +49,7 @@ public class Snooper : GameWindow if (clear) { _previousSpeed = 0f; - Renderer.Cache.DisposeModels(); - Renderer.Cache.Models.Clear(); + Renderer.Cache.Reset(); Renderer.Settings.Reset(); Renderer.Save(); } diff --git a/FModel/Views/Snooper/SpotLight.cs b/FModel/Views/Snooper/SpotLight.cs new file mode 100644 index 00000000..8f1ad9ef --- /dev/null +++ b/FModel/Views/Snooper/SpotLight.cs @@ -0,0 +1,32 @@ +using System; +using System.Numerics; +using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Objects.Core.Math; + +namespace FModel.Views.Snooper; + +public class SpotLight : PointLight +{ + public Vector3 Direction; // ??? + public float Attenuation; + public float ConeAngle; + + public SpotLight(Texture icon, UObject spot, FVector position) : base(icon, spot, position) + { + // var p = spot.GetOrDefault("RelativeLocation", FVector.ZeroVector); + // var r = spot.GetOrDefault("RelativeRotation", FRotator.ZeroRotator); + + // Direction = position + r.UnrotateVector(p.ToMapVector()) * Constants.SCALE_DOWN_RATIO; + Attenuation = spot.GetOrDefault("AttenuationRadius", 0.0f) * Constants.SCALE_DOWN_RATIO; + ConeAngle = (spot.GetOrDefault("InnerConeAngle", 50f) + spot.GetOrDefault("OuterConeAngle", 60f)) / 2f; + ConeAngle = MathF.Cos(Helper.DegreesToRadians(ConeAngle)); + } + + public new void Render(int i, Shader shader) + { + base.Render(i, shader); + // shader.SetUniform($"uLights[{i}].Direction", Direction); + // shader.SetUniform($"uLights[{i}].Attenuation", Attenuation); + // shader.SetUniform($"uLights[{i}].ConeAngle", ConeAngle); + } +} diff --git a/FModel/Views/Snooper/VertexArrayObject.cs b/FModel/Views/Snooper/VertexArrayObject.cs index f2bc3b53..ef27c198 100644 --- a/FModel/Views/Snooper/VertexArrayObject.cs +++ b/FModel/Views/Snooper/VertexArrayObject.cs @@ -36,6 +36,11 @@ public class VertexArrayObject : IDisposable where TVer GL.BindVertexArray(_handle); } + public void Unbind() + { + GL.BindVertexArray(0); + } + public unsafe void BindInstancing() { Bind(); @@ -58,11 +63,6 @@ public class VertexArrayObject : IDisposable where TVer Unbind(); } - public void Unbind() - { - GL.BindVertexArray(0); - } - public void Dispose() { GL.DeleteVertexArray(_handle);