mirror of
https://github.com/4sval/FModel.git
synced 2026-04-01 06:35:36 -05:00
instancing + uworld + fixed black normal maps
This commit is contained in:
parent
c63c1d8434
commit
a7885b1dbc
|
|
@ -1 +1 @@
|
|||
Subproject commit 19dfffa9e55d0951e7da75789e65c8c4e6e62a3e
|
||||
Subproject commit cebbff9035b292e81493f8b94c19d39394be2618
|
||||
|
|
@ -6,8 +6,8 @@ layout (location = 2) in vec2 vTexCoords;
|
|||
layout (location = 3) in vec4 vColor;
|
||||
layout (location = 4) in ivec4 vBoneIds;
|
||||
layout (location = 5) in vec4 vWeights;
|
||||
layout (location = 6) in mat4 vInstanceMatrix;
|
||||
|
||||
uniform mat4 uModel;
|
||||
uniform mat4 uView;
|
||||
uniform mat4 uProjection;
|
||||
|
||||
|
|
@ -18,10 +18,10 @@ out vec4 fColor;
|
|||
|
||||
void main()
|
||||
{
|
||||
gl_Position = uProjection * uView * uModel * vec4(vPos, 1.0);
|
||||
gl_Position = uProjection * uView * vInstanceMatrix * vec4(vPos, 1.0);
|
||||
|
||||
fPos = vec3(uModel * vec4(vPos, 1.0));
|
||||
fNormal = mat3(transpose(inverse(uModel))) * vNormal;
|
||||
fPos = vec3(vInstanceMatrix * vec4(vPos, 1.0));
|
||||
fNormal = mat3(transpose(inverse(vInstanceMatrix))) * vNormal;
|
||||
fTexCoords = vTexCoords;
|
||||
fColor = vColor;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ using CUE4Parse.UE4.Assets.Exports.Texture;
|
|||
using CUE4Parse.UE4.Assets.Exports.Wwise;
|
||||
using CUE4Parse.UE4.IO;
|
||||
using CUE4Parse.UE4.Localization;
|
||||
using CUE4Parse.UE4.Objects.Engine;
|
||||
using CUE4Parse.UE4.Oodle.Objects;
|
||||
using CUE4Parse.UE4.Readers;
|
||||
using CUE4Parse.UE4.Shaders;
|
||||
|
|
@ -749,6 +750,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
SaveAndPlaySound(Path.Combine(TabControl.SelectedTab.Directory, TabControl.SelectedTab.Header.SubstringBeforeLast('.')).Replace('\\', '/'), audioFormat, data);
|
||||
return false;
|
||||
}
|
||||
case UWorld:
|
||||
case UStaticMesh when UserSettings.Default.PreviewStaticMeshes:
|
||||
case USkeletalMesh when UserSettings.Default.PreviewSkeletalMeshes:
|
||||
case UMaterialInstance when UserSettings.Default.PreviewMaterials && !ModelIsOverwritingMaterial &&
|
||||
|
|
|
|||
|
|
@ -9,24 +9,38 @@ public class BufferObject<TDataType> : IDisposable where TDataType : unmanaged
|
|||
private BufferTargetARB _bufferType;
|
||||
private GL _gl;
|
||||
|
||||
public unsafe BufferObject(GL gl, Span<TDataType> data, BufferTargetARB bufferType)
|
||||
public BufferObject(GL gl, BufferTargetARB bufferType)
|
||||
{
|
||||
_gl = gl;
|
||||
_bufferType = bufferType;
|
||||
|
||||
_handle = _gl.GenBuffer();
|
||||
Bind();
|
||||
}
|
||||
|
||||
public unsafe BufferObject(GL gl, Span<TDataType> data, BufferTargetARB bufferType) : this(gl, bufferType)
|
||||
{
|
||||
fixed (void* d = data)
|
||||
{
|
||||
_gl.BufferData(bufferType, (nuint) (data.Length * sizeof(TDataType)), d, BufferUsageARB.StaticDraw);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void Update(int offset, TDataType data)
|
||||
{
|
||||
_gl.BufferSubData(_bufferType, offset * sizeof(TDataType), (nuint) sizeof(TDataType), data);
|
||||
}
|
||||
|
||||
public void Bind()
|
||||
{
|
||||
_gl.BindBuffer(_bufferType, _handle);
|
||||
}
|
||||
|
||||
public void Unbind()
|
||||
{
|
||||
_gl.BindBuffer(_bufferType, 0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_gl.DeleteBuffer(_handle);
|
||||
|
|
|
|||
|
|
@ -45,7 +45,13 @@ public class Camera
|
|||
//We don't want to be able to look behind us by going over our head or under our feet so make sure it stays within these bounds
|
||||
Pitch = Math.Clamp(Pitch, -89f, 89f);
|
||||
|
||||
CalculateDirection();
|
||||
var direction = Vector3.Zero;
|
||||
var yaw = Helper.DegreesToRadians(Yaw);
|
||||
var pitch = Helper.DegreesToRadians(Pitch);
|
||||
direction.X = MathF.Cos(yaw) * MathF.Cos(pitch);
|
||||
direction.Y = MathF.Sin(pitch);
|
||||
direction.Z = MathF.Sin(yaw) * MathF.Cos(pitch);
|
||||
Direction = Vector3.Normalize(direction);
|
||||
}
|
||||
|
||||
public Matrix4x4 GetViewMatrix()
|
||||
|
|
@ -57,15 +63,4 @@ public class Camera
|
|||
{
|
||||
return Matrix4x4.CreatePerspectiveFieldOfView(Helper.DegreesToRadians(Zoom), AspectRatio, Near, Far);
|
||||
}
|
||||
|
||||
public void CalculateDirection()
|
||||
{
|
||||
var direction = Vector3.Zero;
|
||||
var yaw = Helper.DegreesToRadians(Yaw);
|
||||
var pitch = Helper.DegreesToRadians(Pitch);
|
||||
direction.X = MathF.Cos(yaw) * MathF.Cos(pitch);
|
||||
direction.Y = MathF.Sin(pitch);
|
||||
direction.Z = MathF.Sin(yaw) * MathF.Cos(pitch);
|
||||
Direction = Vector3.Normalize(direction);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ public class Cube : Model
|
|||
};
|
||||
|
||||
Sections = new Section[1];
|
||||
Sections[0] = new Section(unrealMaterial.Name, 0, (uint) Indices.Length, 0, unrealMaterial);
|
||||
Sections[0] = new Section(0, (uint) Indices.Length, 0, unrealMaterial);
|
||||
|
||||
AddInstance(Transform.Identity);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion.Meshes.PSK;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
|
|
@ -13,10 +14,9 @@ public class Model : IDisposable
|
|||
|
||||
private BufferObject<uint> _ebo;
|
||||
private BufferObject<float> _vbo;
|
||||
private BufferObject<Matrix4x4> _mvbo;
|
||||
private VertexArrayObject<float, uint> _vao;
|
||||
|
||||
private Shader _shader;
|
||||
|
||||
private uint _vertexSize = 8; // Position + Normal + UV
|
||||
private const uint _faceSize = 3; // just so we don't have to do .Length
|
||||
private readonly uint[] _facesIndex = { 1, 0, 2 };
|
||||
|
|
@ -30,7 +30,8 @@ public class Model : IDisposable
|
|||
public Section[] Sections;
|
||||
public readonly List<CSkelMeshBone> Skeleton;
|
||||
|
||||
public readonly Transform Transforms = Transform.Identity;
|
||||
public int TransformsCount;
|
||||
public readonly List<Transform> Transforms;
|
||||
public readonly string[] TransformsLabels = {
|
||||
"X Location", "Y", "Z",
|
||||
"X Rotation", "Y", "Z",
|
||||
|
|
@ -43,9 +44,10 @@ public class Model : IDisposable
|
|||
{
|
||||
Name = name;
|
||||
Type = type;
|
||||
Transforms = new List<Transform>();
|
||||
}
|
||||
|
||||
public Model(string name, string type, CBaseMeshLod lod, CMeshVertex[] vertices, List<CSkelMeshBone> skeleton = null) : this(name, type)
|
||||
public Model(string name, string type, CBaseMeshLod lod, CMeshVertex[] vertices, List<CSkelMeshBone> skeleton = null, Transform transform = null) : this(name, type)
|
||||
{
|
||||
HasVertexColors = lod.VertexColors != null;
|
||||
if (HasVertexColors) _vertexSize += 4; // + Color
|
||||
|
|
@ -54,6 +56,8 @@ public class Model : IDisposable
|
|||
HasBones = Skeleton != null;
|
||||
if (HasBones) _vertexSize += 8; // + BoneIds + BoneWeights
|
||||
|
||||
_vertexSize += 16; // + InstanceMatrix
|
||||
|
||||
var sections = lod.Sections.Value;
|
||||
Sections = new Section[sections.Length];
|
||||
Indices = new uint[sections.Sum(section => section.NumFaces * _faceSize)];
|
||||
|
|
@ -110,6 +114,17 @@ public class Model : IDisposable
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
AddInstance(transform ?? Transform.Identity);
|
||||
}
|
||||
|
||||
public void AddInstance(Transform transform) => Transforms.Add(transform);
|
||||
|
||||
public void UpdateMatrix(int index)
|
||||
{
|
||||
_mvbo.Bind();
|
||||
_mvbo.Update(index, Transforms[index].Matrix);
|
||||
_mvbo.Unbind();
|
||||
}
|
||||
|
||||
public void Setup(GL gl)
|
||||
|
|
@ -118,8 +133,6 @@ public class Model : IDisposable
|
|||
|
||||
_handle = _gl.CreateProgram();
|
||||
|
||||
_shader = new Shader(_gl);
|
||||
|
||||
_ebo = new BufferObject<uint>(_gl, Indices, BufferTargetARB.ElementArrayBuffer);
|
||||
_vbo = new BufferObject<float>(_gl, Vertices, BufferTargetARB.ArrayBuffer);
|
||||
_vao = new VertexArrayObject<float, uint>(_gl, _vbo, _ebo);
|
||||
|
|
@ -130,36 +143,34 @@ public class Model : IDisposable
|
|||
_vao.VertexAttributePointer(3, 4, VertexAttribPointerType.Float, _vertexSize, 8); // color
|
||||
_vao.VertexAttributePointer(4, 4, VertexAttribPointerType.Int, _vertexSize, 12); // boneids
|
||||
_vao.VertexAttributePointer(5, 4, VertexAttribPointerType.Float, _vertexSize, 16); // boneweights
|
||||
_vao.VertexAttributePointer(6, 16, VertexAttribPointerType.Float, _vertexSize, 20); // instancematrix
|
||||
|
||||
TransformsCount = Transforms.Count;
|
||||
var instanceMatrix = new Matrix4x4[TransformsCount];
|
||||
for (var i = 0; i < instanceMatrix.Length; i++)
|
||||
instanceMatrix[i] = Transforms[i].Matrix;
|
||||
_mvbo = new BufferObject<Matrix4x4>(_gl, instanceMatrix, BufferTargetARB.ArrayBuffer);
|
||||
|
||||
for (int section = 0; section < Sections.Length; section++)
|
||||
{
|
||||
_vao.Bind();
|
||||
_vao.BindInstancing();
|
||||
_vao.Unbind();
|
||||
|
||||
Sections[section].Setup(_gl);
|
||||
}
|
||||
}
|
||||
|
||||
public void Bind(Camera camera)
|
||||
public void Bind(Shader shader)
|
||||
{
|
||||
_vao.Bind();
|
||||
|
||||
_shader.Use();
|
||||
|
||||
_shader.SetUniform("uModel", Transforms.Matrix);
|
||||
_shader.SetUniform("uView", camera.GetViewMatrix());
|
||||
_shader.SetUniform("uProjection", camera.GetProjectionMatrix());
|
||||
_shader.SetUniform("viewPos", camera.Position);
|
||||
|
||||
_shader.SetUniform("material.diffuseMap", 0);
|
||||
_shader.SetUniform("material.normalMap", 1);
|
||||
_shader.SetUniform("material.specularMap", 2);
|
||||
_shader.SetUniform("material.emissionMap", 3);
|
||||
|
||||
_shader.SetUniform("light.position", camera.Position);
|
||||
|
||||
_shader.SetUniform("display_vertex_colors", DisplayVertexColors);
|
||||
shader.SetUniform("display_vertex_colors", DisplayVertexColors);
|
||||
|
||||
var instanceCount = (uint) TransformsCount;
|
||||
for (int section = 0; section < Sections.Length; section++)
|
||||
{
|
||||
Sections[section].Bind(_shader);
|
||||
_vao.Bind();
|
||||
Sections[section].Bind(shader, instanceCount);
|
||||
_vao.Unbind();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -167,8 +178,8 @@ public class Model : IDisposable
|
|||
{
|
||||
_ebo.Dispose();
|
||||
_vbo.Dispose();
|
||||
_mvbo.Dispose();
|
||||
_vao.Dispose();
|
||||
_shader.Dispose();
|
||||
for (int section = 0; section < Sections.Length; section++)
|
||||
{
|
||||
Sections[section].Dispose();
|
||||
|
|
|
|||
|
|
@ -17,12 +17,10 @@ public class Section : IDisposable
|
|||
private GL _gl;
|
||||
|
||||
private Vector3 _ambientLight;
|
||||
private Vector3 _diffuseLight;
|
||||
private Vector3 _specularLight;
|
||||
|
||||
private readonly FGame _game;
|
||||
|
||||
public readonly string Name;
|
||||
public string Name;
|
||||
public readonly int Index;
|
||||
public readonly uint FacesCount;
|
||||
public readonly int FirstFaceIndex;
|
||||
|
|
@ -58,13 +56,18 @@ public class Section : IDisposable
|
|||
{
|
||||
if (section.Material != null && section.Material.TryLoad(out var material) && material is UMaterialInterface unrealMaterial)
|
||||
{
|
||||
Name = unrealMaterial.Name;
|
||||
unrealMaterial.GetParams(Parameters);
|
||||
SwapMaterial(unrealMaterial);
|
||||
}
|
||||
}
|
||||
|
||||
public Section(string name, int index, uint facesCount, int firstFaceIndex, UMaterialInterface unrealMaterial) : this(name, index, facesCount, firstFaceIndex)
|
||||
public Section(int index, uint facesCount, int firstFaceIndex, UMaterialInterface unrealMaterial) : this(string.Empty, index, facesCount, firstFaceIndex)
|
||||
{
|
||||
SwapMaterial(unrealMaterial);
|
||||
}
|
||||
|
||||
public void SwapMaterial(UMaterialInterface unrealMaterial)
|
||||
{
|
||||
Name = unrealMaterial.Name;
|
||||
unrealMaterial.GetParams(Parameters);
|
||||
}
|
||||
|
||||
|
|
@ -119,8 +122,6 @@ public class Section : IDisposable
|
|||
|
||||
// diffuse light is based on normal map, so increase ambient if no normal map
|
||||
_ambientLight = new Vector3(Textures[1] == null ? 1.0f : 0.2f);
|
||||
_diffuseLight = new Vector3(0.75f);
|
||||
_specularLight = new Vector3(0.5f);
|
||||
HasSpecularMap = Textures[2] != null;
|
||||
HasDiffuseColor = DiffuseColor != Vector4.Zero;
|
||||
Show = !Parameters.IsNull && !Parameters.IsTransparent;
|
||||
|
|
@ -230,7 +231,7 @@ public class Section : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
public void Bind(Shader shader)
|
||||
public void Bind(Shader shader, uint instanceCount)
|
||||
{
|
||||
for (var i = 0; i < Textures.Length; i++)
|
||||
{
|
||||
|
|
@ -247,11 +248,9 @@ public class Section : IDisposable
|
|||
shader.SetUniform("material.shininess", Parameters.MetallicValue);
|
||||
|
||||
shader.SetUniform("light.ambient", _ambientLight);
|
||||
shader.SetUniform("light.diffuse", _diffuseLight);
|
||||
shader.SetUniform("light.specular", _specularLight);
|
||||
|
||||
_gl.PolygonMode(MaterialFace.FrontAndBack, Wireframe ? PolygonMode.Line : PolygonMode.Fill);
|
||||
if (Show) _gl.DrawArrays(PrimitiveType.Triangles, FirstFaceIndex, FacesCount);
|
||||
if (Show) _gl.DrawArraysInstanced(PrimitiveType.Triangles, FirstFaceIndex, FacesCount, instanceCount);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Windows;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using FModel.Creator;
|
||||
using ImGuiNET;
|
||||
using Silk.NET.Input;
|
||||
|
|
@ -17,7 +18,6 @@ public class SnimGui : IDisposable
|
|||
private readonly ImGuiController _controller;
|
||||
private readonly GraphicsAPI _api;
|
||||
private readonly string _renderer;
|
||||
private readonly string _version;
|
||||
|
||||
private readonly Vector2 _outlinerSize;
|
||||
private readonly Vector2 _outlinerPosition;
|
||||
|
|
@ -28,7 +28,8 @@ public class SnimGui : IDisposable
|
|||
private readonly Vector2 _textureSize;
|
||||
private readonly Vector2 _texturePosition;
|
||||
private bool _viewportFocus;
|
||||
private int _selectedModel;
|
||||
private FGuid _selectedModel;
|
||||
private int _selectedInstance;
|
||||
private int _selectedSection;
|
||||
|
||||
private const ImGuiWindowFlags _noResize = ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove; // delete once we have a proper docking branch
|
||||
|
|
@ -41,13 +42,12 @@ public class SnimGui : IDisposable
|
|||
_controller = new ImGuiController(gl, window, input, fontConfig);
|
||||
_api = window.API;
|
||||
_renderer = gl.GetStringS(StringName.Renderer);
|
||||
_version = gl.GetStringS(StringName.Version);
|
||||
|
||||
var style = ImGui.GetStyle();
|
||||
var viewport = ImGui.GetMainViewport();
|
||||
var titleBarHeight = ImGui.GetFontSize() + style.FramePadding.Y * 2;
|
||||
|
||||
_outlinerSize = new Vector2(400, 250);
|
||||
_outlinerSize = new Vector2(400, 300);
|
||||
_outlinerPosition = new Vector2(viewport.WorkSize.X - _outlinerSize.X, titleBarHeight);
|
||||
_propertiesSize = _outlinerSize with { Y = viewport.WorkSize.Y - _outlinerSize.Y - titleBarHeight };
|
||||
_propertiesPosition = new Vector2(viewport.WorkSize.X - _propertiesSize.X, _outlinerPosition.Y + _outlinerSize.Y);
|
||||
|
|
@ -55,13 +55,16 @@ public class SnimGui : IDisposable
|
|||
_viewportPosition = new Vector2(0, titleBarHeight);
|
||||
_textureSize = _viewportSize with { Y = viewport.WorkSize.Y - _viewportSize.Y - titleBarHeight };
|
||||
_texturePosition = new Vector2(0, _viewportPosition.Y + _viewportSize.Y);
|
||||
_selectedModel = 0;
|
||||
_selectedModel = new FGuid();
|
||||
_selectedInstance = 0;
|
||||
_selectedSection = 0;
|
||||
|
||||
Theme(style);
|
||||
}
|
||||
|
||||
public void Construct(Vector2D<int> size, FramebufferObject framebuffer, Camera camera, IMouse mouse, IList<Model> models)
|
||||
public void Increment(FGuid guid) => _selectedModel = guid;
|
||||
|
||||
public void Construct(Vector2D<int> size, FramebufferObject framebuffer, Camera camera, IMouse mouse, IDictionary<FGuid, Model> models)
|
||||
{
|
||||
DrawDockSpace(size);
|
||||
DrawNavbar();
|
||||
|
|
@ -119,35 +122,38 @@ public class SnimGui : IDisposable
|
|||
ImGui.EndMainMenuBar();
|
||||
}
|
||||
|
||||
private void DrawOuliner(Camera camera, IList<Model> models)
|
||||
private void DrawOuliner(Camera camera, IDictionary<FGuid, Model> models)
|
||||
{
|
||||
ImGui.SetNextWindowSize(_outlinerSize, _firstUse);
|
||||
ImGui.SetNextWindowPos(_outlinerPosition, _firstUse);
|
||||
ImGui.Begin("Scene", _noResize | ImGuiWindowFlags.NoCollapse);
|
||||
|
||||
ImGui.Text($"Platform: {_api.API} {_api.Profile} {_api.Version.MajorVersion}.{_api.Version.MinorVersion}");
|
||||
ImGui.Text($"Renderer: {_renderer}");
|
||||
ImGui.Text($"Version: {_version}");
|
||||
|
||||
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
|
||||
if (ImGui.TreeNode("Collection"))
|
||||
{
|
||||
for (var i = 0; i < models.Count; i++)
|
||||
var i = 0;
|
||||
foreach (var (guid, model) in models)
|
||||
{
|
||||
var model = models[i];
|
||||
ImGui.PushID(i);
|
||||
if (ImGui.Selectable(model.Name, _selectedModel == i))
|
||||
_selectedModel = i;
|
||||
if (ImGui.Selectable(model.Name, _selectedModel == guid))
|
||||
{
|
||||
_selectedModel = guid;
|
||||
_selectedInstance = 0;
|
||||
_selectedSection = 0;
|
||||
}
|
||||
if (ImGui.BeginPopupContextItem())
|
||||
{
|
||||
if (ImGui.Selectable("Delete"))
|
||||
{
|
||||
_selectedModel--;
|
||||
models.RemoveAt(i);
|
||||
}
|
||||
models.Remove(guid);
|
||||
if (ImGui.Selectable("Copy to Clipboard"))
|
||||
Application.Current.Dispatcher.Invoke(delegate
|
||||
{
|
||||
Clipboard.SetText(model.Name);
|
||||
});
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
ImGui.PopID();
|
||||
i++;
|
||||
}
|
||||
ImGui.TreePop();
|
||||
}
|
||||
|
|
@ -166,16 +172,22 @@ public class SnimGui : IDisposable
|
|||
ImGui.End();
|
||||
}
|
||||
|
||||
private void DrawProperties(Camera camera, IList<Model> models)
|
||||
private void DrawProperties(Camera camera, IDictionary<FGuid, Model> models)
|
||||
{
|
||||
ImGui.SetNextWindowSize(_propertiesSize, _firstUse);
|
||||
ImGui.SetNextWindowPos(_propertiesPosition, _firstUse);
|
||||
ImGui.Begin("Properties", _noResize | ImGuiWindowFlags.NoCollapse);
|
||||
|
||||
if (_selectedModel < 0) return;
|
||||
var model = models[_selectedModel];
|
||||
if (!models.TryGetValue(_selectedModel, out var model)) return;
|
||||
ImGui.Text($"Type: {model.Type}");
|
||||
ImGui.Text($"Entity: {model.Name}");
|
||||
ImGui.Separator();
|
||||
if (ImGui.Button("Focus"))
|
||||
camera.Position = model.Transforms[_selectedInstance].Position;
|
||||
ImGui.SameLine();
|
||||
ImGui.BeginDisabled(model.TransformsCount < 2);
|
||||
ImGui.SliderInt("Instance", ref _selectedInstance, 0, model.TransformsCount - 1, "%i", ImGuiSliderFlags.AlwaysClamp);
|
||||
ImGui.EndDisabled();
|
||||
ImGui.BeginDisabled(!model.HasVertexColors);
|
||||
ImGui.Checkbox("Vertex Colors", ref model.DisplayVertexColors);
|
||||
ImGui.EndDisabled();
|
||||
|
|
@ -190,45 +202,46 @@ public class SnimGui : IDisposable
|
|||
var index = 0;
|
||||
|
||||
ImGui.SetNextItemWidth(width); ImGui.PushID(index);
|
||||
ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms.Position.X, speed, 0f, 0f, "%.2f m");
|
||||
ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms[_selectedInstance].Position.X, speed, 0f, 0f, "%.2f m");
|
||||
ImGui.PopID();
|
||||
|
||||
index++; ImGui.SetNextItemWidth(width); ImGui.PushID(index);
|
||||
ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms.Position.Z, speed, 0f, 0f, "%.2f m");
|
||||
ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms[_selectedInstance].Position.Y, speed, 0f, 0f, "%.2f m");
|
||||
ImGui.PopID();
|
||||
|
||||
index++; ImGui.SetNextItemWidth(width); ImGui.PushID(index);
|
||||
ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms.Position.Y, speed, 0f, 0f, "%.2f m");
|
||||
ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms[_selectedInstance].Position.Z, speed, 0f, 0f, "%.2f m");
|
||||
ImGui.PopID();
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
index++; ImGui.SetNextItemWidth(width); ImGui.PushID(index);
|
||||
ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms.Rotation.X, 1f, 0f, 0f, "%.1f°");
|
||||
ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms[_selectedInstance].Rotation.Pitch, .5f, 0f, 0f, "%.1f°");
|
||||
ImGui.PopID();
|
||||
|
||||
index++; ImGui.SetNextItemWidth(width); ImGui.PushID(index);
|
||||
ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms.Rotation.Z, 1f, 0f, 0f, "%.1f°");
|
||||
ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms[_selectedInstance].Rotation.Roll, .5f, 0f, 0f, "%.1f°");
|
||||
ImGui.PopID();
|
||||
|
||||
index++; ImGui.SetNextItemWidth(width); ImGui.PushID(index);
|
||||
ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms.Rotation.Y, 1f, 0f, 0f, "%.1f°");
|
||||
ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms[_selectedInstance].Rotation.Yaw, .5f, 0f, 0f, "%.1f°");
|
||||
ImGui.PopID();
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
index++; ImGui.SetNextItemWidth(width); ImGui.PushID(index);
|
||||
ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms.Scale.X, speed, 0f, 0f, "%.3f");
|
||||
ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms[_selectedInstance].Scale.X, speed, 0f, 0f, "%.3f");
|
||||
ImGui.PopID();
|
||||
|
||||
index++; ImGui.SetNextItemWidth(width); ImGui.PushID(index);
|
||||
ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms.Scale.Z, speed, 0f, 0f, "%.3f");
|
||||
ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms[_selectedInstance].Scale.Y, speed, 0f, 0f, "%.3f");
|
||||
ImGui.PopID();
|
||||
|
||||
index++; ImGui.SetNextItemWidth(width); ImGui.PushID(index);
|
||||
ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms.Scale.Y, speed, 0f, 0f, "%.3f");
|
||||
ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms[_selectedInstance].Scale.Z, speed, 0f, 0f, "%.3f");
|
||||
ImGui.PopID();
|
||||
|
||||
model.UpdateMatrix(_selectedInstance);
|
||||
ImGui.TreePop();
|
||||
}
|
||||
|
||||
|
|
@ -245,11 +258,11 @@ public class SnimGui : IDisposable
|
|||
|
||||
ImGui.PushID(i);
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
if (!section.Show)
|
||||
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(1, 0, 0, .5f)));
|
||||
ImGui.TableSetColumnIndex(0);
|
||||
ImGui.Text(section.Index.ToString("D"));
|
||||
ImGui.TableSetColumnIndex(1);
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGui.Selectable(section.Name, _selectedSection == i, ImGuiSelectableFlags.SpanAllColumns))
|
||||
_selectedSection = i;
|
||||
ImGui.PopID();
|
||||
|
|
@ -261,14 +274,14 @@ public class SnimGui : IDisposable
|
|||
ImGui.End();
|
||||
}
|
||||
|
||||
private void DrawTextures(IList<Model> models)
|
||||
private void DrawTextures(IDictionary<FGuid, Model> models)
|
||||
{
|
||||
ImGui.SetNextWindowSize(_textureSize, _firstUse);
|
||||
ImGui.SetNextWindowPos(_texturePosition, _firstUse);
|
||||
ImGui.Begin("Textures", _noResize | ImGuiWindowFlags.NoCollapse);
|
||||
|
||||
if (_selectedModel < 0) return;
|
||||
var section = models[_selectedModel].Sections[_selectedSection];
|
||||
if (!models.TryGetValue(_selectedModel, out var model)) return;
|
||||
var section = model.Sections[_selectedSection];
|
||||
ImGui.BeginGroup();
|
||||
ImGui.Checkbox("Show", ref section.Show);
|
||||
ImGui.Checkbox("Wireframe", ref section.Wireframe);
|
||||
|
|
@ -356,7 +369,7 @@ public class SnimGui : IDisposable
|
|||
ImGui.SetNextWindowSize(_viewportSize, _firstUse);
|
||||
ImGui.SetNextWindowPos(_viewportPosition, _firstUse);
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
|
||||
ImGui.Begin("Viewport", _noResize | flags);
|
||||
ImGui.Begin($"Viewport ({_api.API} {_api.Version.MajorVersion}.{_api.Version.MinorVersion}) ({_renderer})", _noResize | flags);
|
||||
ImGui.PopStyleVar();
|
||||
|
||||
var largest = ImGui.GetContentRegionAvail();
|
||||
|
|
@ -425,6 +438,7 @@ public class SnimGui : IDisposable
|
|||
io.ConfigDockingWithShift = true;
|
||||
|
||||
style.WindowMenuButtonPosition = ImGuiDir.Right;
|
||||
style.ScrollbarSize = 10f;
|
||||
// style.Colors[(int) ImGuiCol.Text] = new Vector4(0.95f, 0.96f, 0.98f, 1.00f);
|
||||
// style.Colors[(int) ImGuiCol.TextDisabled] = new Vector4(0.36f, 0.42f, 0.47f, 1.00f);
|
||||
// style.Colors[(int) ImGuiCol.WindowBg] = new Vector4(0.149f, 0.149f, 0.188f, 0.35f);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,11 @@ using CUE4Parse.UE4.Assets.Exports;
|
|||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using CUE4Parse.UE4.Objects.Engine;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using CUE4Parse_Conversion.Meshes;
|
||||
using ImGuiNET;
|
||||
using Silk.NET.Core;
|
||||
|
|
@ -15,9 +19,9 @@ using Silk.NET.Input;
|
|||
using Silk.NET.Maths;
|
||||
using Silk.NET.OpenGL;
|
||||
using Silk.NET.Windowing;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Advanced;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
|
||||
namespace FModel.Views.Snooper;
|
||||
|
||||
|
|
@ -34,7 +38,11 @@ public class Snooper
|
|||
private readonly FramebufferObject _framebuffer;
|
||||
private readonly Skybox _skybox;
|
||||
private readonly Grid _grid;
|
||||
private readonly List<Model> _models;
|
||||
|
||||
private Shader _shader;
|
||||
private Vector3 _diffuseLight;
|
||||
private Vector3 _specularLight;
|
||||
private readonly Dictionary<FGuid, Model> _models;
|
||||
|
||||
private Vector2D<int> _size;
|
||||
private float _previousSpeed;
|
||||
|
|
@ -80,7 +88,7 @@ public class Snooper
|
|||
_framebuffer = new FramebufferObject(_size);
|
||||
_skybox = new Skybox();
|
||||
_grid = new Grid();
|
||||
_models = new List<Model>();
|
||||
_models = new Dictionary<FGuid, Model>();
|
||||
}
|
||||
|
||||
public void Run(UObject export)
|
||||
|
|
@ -89,29 +97,139 @@ public class Snooper
|
|||
{
|
||||
case UStaticMesh st when st.TryConvert(out var mesh):
|
||||
{
|
||||
_models.Add(new Model(st.Name, st.ExportType, mesh.LODs[0], mesh.LODs[0].Verts));
|
||||
SetupCamera(mesh.BoundingBox *= Constants.SCALE_DOWN_RATIO);
|
||||
var guid = st.LightingGuid;
|
||||
if (!_models.TryGetValue(guid, out _))
|
||||
{
|
||||
_models[guid] = new Model(st.Name, st.ExportType, mesh.LODs[0], mesh.LODs[0].Verts);
|
||||
SetupCamera(mesh.BoundingBox *= Constants.SCALE_DOWN_RATIO);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case USkeletalMesh sk when sk.TryConvert(out var mesh):
|
||||
{
|
||||
_models.Add(new Model(sk.Name, sk.ExportType, mesh.LODs[0], mesh.LODs[0].Verts, mesh.RefSkeleton));
|
||||
SetupCamera(mesh.BoundingBox *= Constants.SCALE_DOWN_RATIO);
|
||||
var guid = Guid.NewGuid();
|
||||
if (!_models.TryGetValue(guid, out _))
|
||||
{
|
||||
_models[guid] = new Model(sk.Name, sk.ExportType, mesh.LODs[0], mesh.LODs[0].Verts, mesh.RefSkeleton);
|
||||
SetupCamera(mesh.BoundingBox *= Constants.SCALE_DOWN_RATIO);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UMaterialInstance mi:
|
||||
{
|
||||
_models.Add(new Cube(mi.Name, mi.ExportType, mi));
|
||||
_camera = new Camera(new Vector3(0f, 0f, 2f), Vector3.Zero, 0.01f, 0.5f * 50f, 0.5f / 2f);
|
||||
var guid = Guid.NewGuid();
|
||||
if (!_models.TryGetValue(guid, out _))
|
||||
{
|
||||
_models[guid] = new Cube(mi.Name, mi.ExportType, mi);
|
||||
SetupCamera(new FBox(new FVector(-.65f), new FVector(.65f)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UWorld wd:
|
||||
{
|
||||
var persistentLevel = wd.PersistentLevel.Load<ULevel>();
|
||||
for (var i = 0; i < persistentLevel.Actors.Length; i++)
|
||||
{
|
||||
if (persistentLevel.Actors[i].Load() is not { } actor || actor.ExportType == "LODActor" ||
|
||||
!actor.TryGetValue(out FPackageIndex staticMeshComponent, "StaticMeshComponent") ||
|
||||
staticMeshComponent.Load() is not { } staticMeshComp) continue;
|
||||
|
||||
if (!staticMeshComp.TryGetValue(out FPackageIndex staticMesh, "StaticMesh") && actor.Class is UBlueprintGeneratedClass)
|
||||
{
|
||||
foreach (var actorExp in actor.Class.Owner.GetExports())
|
||||
if (actorExp.TryGetValue(out staticMesh, "StaticMesh"))
|
||||
break;
|
||||
}
|
||||
|
||||
if (staticMesh?.Load() is not UStaticMesh m || !m.TryConvert(out var mesh))
|
||||
continue;
|
||||
|
||||
var guid = m.LightingGuid;
|
||||
var transform = new Transform
|
||||
{
|
||||
Position = staticMeshComp.GetOrDefault("RelativeLocation", FVector.ZeroVector) * Constants.SCALE_DOWN_RATIO,
|
||||
Rotation = staticMeshComp.GetOrDefault("RelativeRotation", FRotator.ZeroRotator),
|
||||
Scale = staticMeshComp.GetOrDefault("RelativeScale3D", FVector.OneVector)
|
||||
};
|
||||
// can't seem to find the problem here
|
||||
// some meshes should have their yaw reversed and others not
|
||||
transform.Rotation.Yaw = -transform.Rotation.Yaw;
|
||||
if (_models.TryGetValue(guid, out var model))
|
||||
{
|
||||
model.AddInstance(transform);
|
||||
continue;
|
||||
}
|
||||
|
||||
model = new Model(m.Name, m.ExportType, mesh.LODs[0], mesh.LODs[0].Verts, null, transform);
|
||||
if (actor.TryGetAllValues(out FPackageIndex[] textureData, "TextureData"))
|
||||
{
|
||||
for (int j = 0; j < textureData.Length; j++)
|
||||
{
|
||||
if (textureData[j].Load() is not { } textureDataIdx)
|
||||
continue;
|
||||
|
||||
if (textureDataIdx.TryGetValue(out FPackageIndex diffuse, "Diffuse") &&
|
||||
diffuse.Load() is UTexture2D diffuseTexture)
|
||||
model.Sections[j].Parameters.Diffuse = diffuseTexture;
|
||||
if (textureDataIdx.TryGetValue(out FPackageIndex normal, "Normal") &&
|
||||
normal.Load() is UTexture2D normalTexture)
|
||||
model.Sections[j].Parameters.Normal = normalTexture;
|
||||
if (textureDataIdx.TryGetValue(out FPackageIndex specular, "Specular") &&
|
||||
specular.Load() is UTexture2D specularTexture)
|
||||
model.Sections[j].Parameters.Specular = specularTexture;
|
||||
}
|
||||
}
|
||||
if (staticMeshComp.TryGetValue(out FPackageIndex[] overrideMaterials, "OverrideMaterials"))
|
||||
{
|
||||
var max = model.Sections.Length - 1;
|
||||
for (var j = 0; j < overrideMaterials.Length; j++)
|
||||
{
|
||||
if (j > max) break;
|
||||
if (overrideMaterials[j].Load() is not UMaterialInterface unrealMaterial) continue;
|
||||
model.Sections[j].SwapMaterial(unrealMaterial);
|
||||
}
|
||||
}
|
||||
|
||||
_models[guid] = model;
|
||||
}
|
||||
_camera = new Camera(new Vector3(0f, 0f, 5f), Vector3.Zero, 0.01f, 1000f, 5f);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(export));
|
||||
}
|
||||
|
||||
// because this calls Reset from an internal class we must recall GL.GetApi(_window) on load
|
||||
// so basically we can't keep our current scene and we have to setup everything again
|
||||
DoLoop();
|
||||
}
|
||||
|
||||
private void DoLoop()
|
||||
{
|
||||
if (_append) _append = false;
|
||||
_window.Run();
|
||||
// if (_window.IsInitialized)
|
||||
// {
|
||||
// if (!_window.GLContext.IsCurrent)
|
||||
// {
|
||||
// // huston we have a problem
|
||||
// // this is apparently a bug
|
||||
// }
|
||||
// _models[^1].Setup(_gl);
|
||||
// _imGui.Increment();
|
||||
// }
|
||||
// else _window.Initialize();
|
||||
//
|
||||
// while (!_window.IsClosing && _window.IsVisible)
|
||||
// {
|
||||
// _window.DoEvents();
|
||||
// if (!_window.IsClosing && _window.IsVisible)
|
||||
// _window.DoUpdate();
|
||||
// if (_window.IsClosing || !_window.IsVisible)
|
||||
// return;
|
||||
// _window.DoRender();
|
||||
// }
|
||||
//
|
||||
// _window.DoEvents();
|
||||
// if (_window.IsClosing) _window.Reset();
|
||||
}
|
||||
|
||||
private void SetupCamera(FBox box)
|
||||
|
|
@ -120,11 +238,11 @@ public class Snooper
|
|||
var center = box.GetCenter();
|
||||
var position = new Vector3(0f, center.Z, box.Max.Y * 3);
|
||||
var speed = far / 2f;
|
||||
|
||||
if (_camera == null)
|
||||
if (speed > _previousSpeed)
|
||||
{
|
||||
_camera = new Camera(position, center, 0.01f, far * 50f, speed);
|
||||
else if (speed > _previousSpeed)
|
||||
_camera.Speed = _previousSpeed = speed;
|
||||
_previousSpeed = _camera.Speed;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLoad()
|
||||
|
|
@ -145,7 +263,10 @@ public class Snooper
|
|||
_skybox.Setup(_gl);
|
||||
_grid.Setup(_gl);
|
||||
|
||||
foreach (var model in _models)
|
||||
_shader = new Shader(_gl);
|
||||
_diffuseLight = new Vector3(0.75f);
|
||||
_specularLight = new Vector3(0.5f);
|
||||
foreach (var model in _models.Values)
|
||||
{
|
||||
model.Setup(_gl);
|
||||
}
|
||||
|
|
@ -167,9 +288,24 @@ public class Snooper
|
|||
_skybox.Bind(_camera);
|
||||
_grid.Bind(_camera);
|
||||
|
||||
foreach (var model in _models)
|
||||
_shader.Use();
|
||||
|
||||
_shader.SetUniform("uView", _camera.GetViewMatrix());
|
||||
_shader.SetUniform("uProjection", _camera.GetProjectionMatrix());
|
||||
_shader.SetUniform("viewPos", _camera.Position);
|
||||
|
||||
_shader.SetUniform("material.diffuseMap", 0);
|
||||
_shader.SetUniform("material.normalMap", 1);
|
||||
_shader.SetUniform("material.specularMap", 2);
|
||||
_shader.SetUniform("material.emissionMap", 3);
|
||||
|
||||
_shader.SetUniform("light.position", _camera.Position);
|
||||
_shader.SetUniform("light.diffuse", _diffuseLight);
|
||||
_shader.SetUniform("light.specular", _specularLight);
|
||||
|
||||
foreach (var model in _models.Values)
|
||||
{
|
||||
model.Bind(_camera);
|
||||
model.Bind(_shader);
|
||||
}
|
||||
|
||||
_imGui.Construct(_size, _framebuffer, _camera, _mouse, _models);
|
||||
|
|
@ -211,6 +347,10 @@ public class Snooper
|
|||
|
||||
if (_keyboard.IsKeyPressed(Key.H))
|
||||
{
|
||||
// because we lose GLContext when the window is invisible after a few seconds (it's apparently a bug)
|
||||
// we can't use GLContext back on next load and so, for now, we basically have to reset the window
|
||||
// if we can't use GLContext, we can't generate handles, can't interact with IsVisible, State, etc
|
||||
// tldr we dispose everything but don't clear models, so the more you append, the longer it takes to load
|
||||
_append = true;
|
||||
_window.Close();
|
||||
}
|
||||
|
|
@ -223,7 +363,8 @@ public class Snooper
|
|||
_framebuffer.Dispose();
|
||||
_grid.Dispose();
|
||||
_skybox.Dispose();
|
||||
foreach (var model in _models)
|
||||
_shader.Dispose();
|
||||
foreach (var model in _models.Values)
|
||||
{
|
||||
model.Dispose();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ public class Texture : IDisposable
|
|||
|
||||
fixed (void* d = &data[0])
|
||||
{
|
||||
_gl.TexImage2D(TextureTarget.Texture2D, 0, (int) InternalFormat.Rgba, Width, Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, d);
|
||||
_gl.TexImage2D(TextureTarget.Texture2D, 0, (int) InternalFormat.Rgb, Width, Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, d);
|
||||
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int) GLEnum.LinearMipmapLinear);
|
||||
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int) GLEnum.Linear);
|
||||
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureBaseLevel, 0);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Numerics;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
|
||||
namespace FModel.Views.Snooper;
|
||||
|
||||
|
|
@ -9,16 +10,16 @@ public class Transform
|
|||
get => new ();
|
||||
}
|
||||
|
||||
public Vector3 Position = Vector3.Zero;
|
||||
public Vector3 Rotation = Vector3.Zero;
|
||||
public Vector3 Scale = Vector3.One;
|
||||
public FVector Position = FVector.ZeroVector;
|
||||
public FRotator Rotation = FRotator.ZeroRotator;
|
||||
public FVector Scale = FVector.OneVector;
|
||||
|
||||
public Matrix4x4 Matrix =>
|
||||
Matrix4x4.Identity *
|
||||
Matrix4x4.CreateFromYawPitchRoll(
|
||||
Helper.DegreesToRadians(Rotation.Y),
|
||||
Helper.DegreesToRadians(Rotation.X),
|
||||
Helper.DegreesToRadians(Rotation.Z)) *
|
||||
Helper.DegreesToRadians(Rotation.Yaw),
|
||||
Helper.DegreesToRadians(Rotation.Pitch),
|
||||
Helper.DegreesToRadians(Rotation.Roll)) *
|
||||
Matrix4x4.CreateScale(Scale) *
|
||||
Matrix4x4.CreateTranslation(Position);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace FModel.Views.Snooper;
|
||||
|
|
@ -37,6 +38,29 @@ public class VertexArrayObject<TVertexType, TIndexType> : IDisposable where TVer
|
|||
_gl.BindVertexArray(_handle);
|
||||
}
|
||||
|
||||
public unsafe void BindInstancing()
|
||||
{
|
||||
var vec4Size = (uint) sizeof(Vector4);
|
||||
_gl.EnableVertexAttribArray(6);
|
||||
_gl.VertexAttribPointer(6, 4, VertexAttribPointerType.Float, false, 4 * vec4Size, (void*)0);
|
||||
_gl.EnableVertexAttribArray(7);
|
||||
_gl.VertexAttribPointer(7, 4, VertexAttribPointerType.Float, false, 4 * vec4Size, (void*)(1 * vec4Size));
|
||||
_gl.EnableVertexAttribArray(8);
|
||||
_gl.VertexAttribPointer(8, 4, VertexAttribPointerType.Float, false, 4 * vec4Size, (void*)(2 * vec4Size));
|
||||
_gl.EnableVertexAttribArray(9);
|
||||
_gl.VertexAttribPointer(9, 4, VertexAttribPointerType.Float, false, 4 * vec4Size, (void*)(3 * vec4Size));
|
||||
|
||||
_gl.VertexAttribDivisor(6, 1);
|
||||
_gl.VertexAttribDivisor(7, 1);
|
||||
_gl.VertexAttribDivisor(8, 1);
|
||||
_gl.VertexAttribDivisor(9, 1);
|
||||
}
|
||||
|
||||
public void Unbind()
|
||||
{
|
||||
_gl.BindVertexArray(0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_gl.DeleteVertexArray(_handle);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user