Skip to content

Commit 1084fa6

Browse files
zoalasaurusrawrPerkseyThomasMiz
authored
Added a tutorial for model loading based on the model loading tutorial from LearnOpenGL (#1161)
* Starting work to add the model loading tutorial from LearnOpenGL * Checking in model loading tutorial project along with a model file * Renamed cube.obj to cube.model to get around gitignore * Added Assimp package to get Assimp native libs to load. * Updated render loop to fix aspect ratio issue * Added dispose to model and mesh and removed unused boilerplate * Apply suggestions from code review Co-authored-by: ThomasMiz <[email protected]> Co-authored-by: Dylan Perks <[email protected]> Co-authored-by: ThomasMiz <[email protected]>
1 parent 30e26d0 commit 1084fa6

File tree

16 files changed

+882
-7
lines changed

16 files changed

+882
-7
lines changed

Silk.NET.sln

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -506,23 +506,25 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Direct3D11 Tutorials", "Dir
506506
EndProject
507507
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tutorial 1.2 - Hello quad", "examples\CSharp\Direct3D11 Tutorials\Tutorial 1.2 - Hello quad\Tutorial 1.2 - Hello quad.csproj", "{F86248D7-5012-4820-ADBE-BCE8B701581D}"
508508
EndProject
509-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebGPUTest", "src\Lab\Experiments\WebGPUTest\WebGPUTest.csproj", "{534FCB74-0E82-42AF-AF94-48EE634F37A8}"
509+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebGPUTest", "src\Lab\Experiments\WebGPUTest\WebGPUTest.csproj", "{534FCB74-0E82-42AF-AF94-48EE634F37A8}"
510510
EndProject
511511
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebGPU", "WebGPU", "{AA6EDF22-8128-476C-97E0-0CEA4689624E}"
512512
EndProject
513-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.WebGPU", "src\WebGPU\Silk.NET.WebGPU\Silk.NET.WebGPU.csproj", "{3FA488D6-239F-4509-A7F9-5CFE6B2517CD}"
513+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Silk.NET.WebGPU", "src\WebGPU\Silk.NET.WebGPU\Silk.NET.WebGPU.csproj", "{3FA488D6-239F-4509-A7F9-5CFE6B2517CD}"
514514
EndProject
515515
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{D218E3C8-44C7-472F-B147-3C9DA8B0C2EC}"
516516
EndProject
517-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.WebGPU.Extensions.WGPU", "src\WebGPU\Extensions\Silk.NET.WebGPU.Extensions.WGPU\Silk.NET.WebGPU.Extensions.WGPU.csproj", "{C66E285B-4DED-46FD-95A9-7B376C680412}"
517+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Silk.NET.WebGPU.Extensions.WGPU", "src\WebGPU\Extensions\Silk.NET.WebGPU.Extensions.WGPU\Silk.NET.WebGPU.Extensions.WGPU.csproj", "{C66E285B-4DED-46FD-95A9-7B376C680412}"
518518
EndProject
519-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebGPUTriangle", "src\Lab\Experiments\WebGPUTriangle\WebGPUTriangle.csproj", "{1F16CA6E-9A7E-452E-BE37-1201E506B661}"
519+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebGPUTriangle", "src\Lab\Experiments\WebGPUTriangle\WebGPUTriangle.csproj", "{1F16CA6E-9A7E-452E-BE37-1201E506B661}"
520520
EndProject
521-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.WebGPU.Extensions.Dawn", "src\WebGPU\Extensions\Silk.NET.WebGPU.Extensions.Dawn\Silk.NET.WebGPU.Extensions.Dawn.csproj", "{3DADBCD8-5C5E-487C-8616-55A56BC351EF}"
521+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Silk.NET.WebGPU.Extensions.Dawn", "src\WebGPU\Extensions\Silk.NET.WebGPU.Extensions.Dawn\Silk.NET.WebGPU.Extensions.Dawn.csproj", "{3DADBCD8-5C5E-487C-8616-55A56BC351EF}"
522522
EndProject
523-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.WebGPU.Extensions.Disposal", "src\WebGPU\Extensions\Silk.NET.WebGPU.Extensions.Disposal\Silk.NET.WebGPU.Extensions.Disposal.csproj", "{1314FEAF-71F7-42A8-881F-091384CE601B}"
523+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Silk.NET.WebGPU.Extensions.Disposal", "src\WebGPU\Extensions\Silk.NET.WebGPU.Extensions.Disposal\Silk.NET.WebGPU.Extensions.Disposal.csproj", "{1314FEAF-71F7-42A8-881F-091384CE601B}"
524524
EndProject
525-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebGPUTexturedQuad", "src\Lab\Experiments\WebGPUTexturedQuad\WebGPUTexturedQuad.csproj", "{5DD2A122-34CB-45B9-8D89-42DB5CE9FD17}"
525+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebGPUTexturedQuad", "src\Lab\Experiments\WebGPUTexturedQuad\WebGPUTexturedQuad.csproj", "{5DD2A122-34CB-45B9-8D89-42DB5CE9FD17}"
526+
EndProject
527+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tutorial 4.1 - Model Loading", "examples\CSharp\OpenGL Tutorials\Tutorial 4.1 - Model Loading\Tutorial 4.1 - Model Loading.csproj", "{7C91F372-8716-42F6-BB5A-6C94B7FC0513}"
526528
EndProject
527529
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.WGL", "src\OpenGL\Silk.NET.WGL\Silk.NET.WGL.csproj", "{456D740A-3612-4CFB-BB5B-AF7E88EFBA26}"
528530
EndProject
@@ -3181,6 +3183,18 @@ Global
31813183
{5DD2A122-34CB-45B9-8D89-42DB5CE9FD17}.Release|x64.Build.0 = Release|Any CPU
31823184
{5DD2A122-34CB-45B9-8D89-42DB5CE9FD17}.Release|x86.ActiveCfg = Release|Any CPU
31833185
{5DD2A122-34CB-45B9-8D89-42DB5CE9FD17}.Release|x86.Build.0 = Release|Any CPU
3186+
{7C91F372-8716-42F6-BB5A-6C94B7FC0513}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
3187+
{7C91F372-8716-42F6-BB5A-6C94B7FC0513}.Debug|Any CPU.Build.0 = Debug|Any CPU
3188+
{7C91F372-8716-42F6-BB5A-6C94B7FC0513}.Debug|x64.ActiveCfg = Debug|Any CPU
3189+
{7C91F372-8716-42F6-BB5A-6C94B7FC0513}.Debug|x64.Build.0 = Debug|Any CPU
3190+
{7C91F372-8716-42F6-BB5A-6C94B7FC0513}.Debug|x86.ActiveCfg = Debug|Any CPU
3191+
{7C91F372-8716-42F6-BB5A-6C94B7FC0513}.Debug|x86.Build.0 = Debug|Any CPU
3192+
{7C91F372-8716-42F6-BB5A-6C94B7FC0513}.Release|Any CPU.ActiveCfg = Release|Any CPU
3193+
{7C91F372-8716-42F6-BB5A-6C94B7FC0513}.Release|Any CPU.Build.0 = Release|Any CPU
3194+
{7C91F372-8716-42F6-BB5A-6C94B7FC0513}.Release|x64.ActiveCfg = Release|Any CPU
3195+
{7C91F372-8716-42F6-BB5A-6C94B7FC0513}.Release|x64.Build.0 = Release|Any CPU
3196+
{7C91F372-8716-42F6-BB5A-6C94B7FC0513}.Release|x86.ActiveCfg = Release|Any CPU
3197+
{7C91F372-8716-42F6-BB5A-6C94B7FC0513}.Release|x86.Build.0 = Release|Any CPU
31843198
{456D740A-3612-4CFB-BB5B-AF7E88EFBA26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31853199
{456D740A-3612-4CFB-BB5B-AF7E88EFBA26}.Debug|Any CPU.Build.0 = Debug|Any CPU
31863200
{456D740A-3612-4CFB-BB5B-AF7E88EFBA26}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -3615,6 +3629,7 @@ Global
36153629
{3DADBCD8-5C5E-487C-8616-55A56BC351EF} = {D218E3C8-44C7-472F-B147-3C9DA8B0C2EC}
36163630
{1314FEAF-71F7-42A8-881F-091384CE601B} = {D218E3C8-44C7-472F-B147-3C9DA8B0C2EC}
36173631
{5DD2A122-34CB-45B9-8D89-42DB5CE9FD17} = {39B598E9-44BA-4A61-A1BB-7C543734DBA6}
3632+
{7C91F372-8716-42F6-BB5A-6C94B7FC0513} = {20A4A2D1-D699-4D71-AA97-950154638576}
36183633
{456D740A-3612-4CFB-BB5B-AF7E88EFBA26} = {0E9C83A8-A413-4921-8F39-59519BFF939B}
36193634
{B6A87FA1-0A71-434E-A808-C93072584E6D} = {CF69D5C3-4ACE-4458-BA5A-0E9A3B294CDC}
36203635
{1D073CF7-A0D9-49B0-8A2C-B01EAD1B5D45} = {CF69D5C3-4ACE-4458-BA5A-0E9A3B294CDC}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using Silk.NET.OpenGL;
2+
using System;
3+
4+
namespace Tutorial
5+
{
6+
public class BufferObject<TDataType> : IDisposable
7+
where TDataType : unmanaged
8+
{
9+
private uint _handle;
10+
private BufferTargetARB _bufferType;
11+
private GL _gl;
12+
13+
public unsafe BufferObject(GL gl, Span<TDataType> data, BufferTargetARB bufferType)
14+
{
15+
_gl = gl;
16+
_bufferType = bufferType;
17+
18+
_handle = _gl.GenBuffer();
19+
Bind();
20+
fixed (void* d = data)
21+
{
22+
_gl.BufferData(bufferType, (nuint) (data.Length * sizeof(TDataType)), d, BufferUsageARB.StaticDraw);
23+
}
24+
}
25+
26+
public void Bind()
27+
{
28+
_gl.BindBuffer(_bufferType, _handle);
29+
}
30+
31+
public void Dispose()
32+
{
33+
_gl.DeleteBuffer(_handle);
34+
}
35+
}
36+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
3+
namespace Tutorial
4+
{
5+
public static class MathHelper
6+
{
7+
public static float DegreesToRadians(float degrees)
8+
{
9+
return MathF.PI / 180f * degrees;
10+
}
11+
}
12+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using Silk.NET.OpenGL;
7+
8+
namespace Tutorial
9+
{
10+
public class Mesh : IDisposable
11+
{
12+
public Mesh(GL gl, float[] vertices, uint[] indices, List<Texture> textures)
13+
{
14+
GL = gl;
15+
Vertices = vertices;
16+
Indices = indices;
17+
Textures = textures;
18+
SetupMesh();
19+
}
20+
21+
public float[] Vertices { get; private set; }
22+
public uint[] Indices { get; private set; }
23+
public IReadOnlyList<Texture> Textures { get; private set; }
24+
public VertexArrayObject<float, uint> VAO { get; set; }
25+
public BufferObject<float> VBO { get; set; }
26+
public BufferObject<uint> EBO { get; set; }
27+
public GL GL { get; }
28+
29+
public unsafe void SetupMesh()
30+
{
31+
EBO = new BufferObject<uint>(GL, Indices, BufferTargetARB.ElementArrayBuffer);
32+
VBO = new BufferObject<float>(GL, Vertices, BufferTargetARB.ArrayBuffer);
33+
VAO = new VertexArrayObject<float, uint>(GL, VBO, EBO);
34+
VAO.VertexAttributePointer(0, 3, VertexAttribPointerType.Float, 5, 0);
35+
VAO.VertexAttributePointer(1, 2, VertexAttribPointerType.Float, 5, 3);
36+
}
37+
38+
public void Bind()
39+
{
40+
VAO.Bind();
41+
}
42+
43+
public void Dispose()
44+
{
45+
Textures = null;
46+
VAO.Dispose();
47+
VBO.Dispose();
48+
EBO.Dispose();
49+
}
50+
}
51+
}
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Silk.NET.Assimp;
5+
using Silk.NET.OpenGL;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
using System.Numerics;
10+
using AssimpMesh = Silk.NET.Assimp.Mesh;
11+
12+
namespace Tutorial
13+
{
14+
public class Model : IDisposable
15+
{
16+
public Model(GL gl, string path, bool gamma = false)
17+
{
18+
var assimp = Silk.NET.Assimp.Assimp.GetApi();
19+
_assimp = assimp;
20+
_gl = gl;
21+
LoadModel(path);
22+
}
23+
24+
private readonly GL _gl;
25+
private Assimp _assimp;
26+
private List<Texture> _texturesLoaded = new List<Texture>();
27+
public string Directory { get; protected set; } = string.Empty;
28+
public List<Mesh> Meshes { get; protected set; } = new List<Mesh>();
29+
30+
private unsafe void LoadModel(string path)
31+
{
32+
var scene = _assimp.ImportFile(path, (uint)PostProcessSteps.Triangulate);
33+
34+
if (scene == null || scene->MFlags == Silk.NET.Assimp.Assimp.SceneFlagsIncomplete || scene->MRootNode == null)
35+
{
36+
var error = _assimp.GetErrorStringS();
37+
throw new Exception(error);
38+
}
39+
40+
Directory = path;
41+
42+
ProcessNode(scene->MRootNode, scene);
43+
}
44+
45+
private unsafe void ProcessNode(Node* node, Scene* scene)
46+
{
47+
for (var i = 0; i < node->MNumMeshes; i++)
48+
{
49+
var mesh = scene->MMeshes[node->MMeshes[i]];
50+
Meshes.Add(ProcessMesh(mesh, scene));
51+
52+
}
53+
54+
for (var i = 0; i < node->MNumChildren; i++)
55+
{
56+
ProcessNode(node->MChildren[i], scene);
57+
}
58+
}
59+
60+
private unsafe Mesh ProcessMesh(AssimpMesh* mesh, Scene* scene)
61+
{
62+
// data to fill
63+
List<Vertex> vertices = new List<Vertex>();
64+
List<uint> indices = new List<uint>();
65+
List<Texture> textures = new List<Texture>();
66+
67+
// walk through each of the mesh's vertices
68+
for (uint i = 0; i < mesh->MNumVertices; i++)
69+
{
70+
Vertex vertex = new Vertex();
71+
vertex.BoneIds = new int[Vertex.MAX_BONE_INFLUENCE];
72+
vertex.Weights = new float[Vertex.MAX_BONE_INFLUENCE];
73+
Vector3 vector = new Vector3(); // we declare a placeholder vector since assimp uses its own vector class that doesn't directly convert to glm's vec3 class so we transfer the data to this placeholder glm::vec3 first.
74+
// positions
75+
vector.X = mesh->MVertices[i].X;
76+
vector.Y = mesh->MVertices[i].Y;
77+
vector.Z = mesh->MVertices[i].Z;
78+
vertex.Position = vector;
79+
// normals
80+
81+
if (mesh->MNormals != null)
82+
{
83+
vector.X = mesh->MNormals[i].X;
84+
vector.Y = mesh->MNormals[i].Y;
85+
vector.Z = mesh->MNormals[i].Z;
86+
vertex.Normal = vector;
87+
}
88+
// texture coordinates
89+
if (mesh->MTextureCoords[0] != null) // does the mesh contain texture coordinates?
90+
{
91+
Vector2 vec = new Vector2();
92+
// a vertex can contain up to 8 different texture coordinates. We thus make the assumption that we won't
93+
// use models where a vertex can have multiple texture coordinates so we always take the first set (0).
94+
vec.X = mesh->MTextureCoords[0][i].X;
95+
vec.Y = mesh->MTextureCoords[0][i].Y;
96+
vertex.TexCoords = vec;
97+
// tangent
98+
if (mesh->MTangents != null)
99+
{
100+
vector.X = mesh->MTangents[i].X;
101+
vector.Y = mesh->MTangents[i].Y;
102+
vector.Z = mesh->MTangents[i].Z;
103+
vertex.Tangent = vector;
104+
}
105+
// bitangent
106+
if (mesh->MBitangents != null)
107+
{
108+
vector.X = mesh->MBitangents[i].X;
109+
vector.Y = mesh->MBitangents[i].Y;
110+
vector.Z = mesh->MBitangents[i].Z;
111+
vertex.Bitangent = vector;
112+
}
113+
}
114+
else
115+
vertex.TexCoords = new Vector2(0.0f, 0.0f);
116+
117+
vertices.Add(vertex);
118+
}
119+
// now wak through each of the mesh's faces (a face is a mesh its triangle) and retrieve the corresponding vertex indices.
120+
for (uint i = 0; i < mesh->MNumFaces; i++)
121+
{
122+
Face face = mesh->MFaces[i];
123+
// retrieve all indices of the face and store them in the indices vector
124+
for (uint j = 0; j < face.MNumIndices; j++)
125+
indices.Add(face.MIndices[j]);
126+
}
127+
// process materials
128+
Material* material = scene->MMaterials[mesh->MMaterialIndex];
129+
// we assume a convention for sampler names in the shaders. Each diffuse texture should be named
130+
// as 'texture_diffuseN' where N is a sequential number ranging from 1 to MAX_SAMPLER_NUMBER.
131+
// Same applies to other texture as the following list summarizes:
132+
// diffuse: texture_diffuseN
133+
// specular: texture_specularN
134+
// normal: texture_normalN
135+
136+
// 1. diffuse maps
137+
var diffuseMaps = LoadMaterialTextures(material, TextureType.Diffuse, "texture_diffuse");
138+
if (diffuseMaps.Any())
139+
textures.AddRange(diffuseMaps);
140+
// 2. specular maps
141+
var specularMaps = LoadMaterialTextures(material, TextureType.Specular, "texture_specular");
142+
if (specularMaps.Any())
143+
textures.AddRange(specularMaps);
144+
// 3. normal maps
145+
var normalMaps = LoadMaterialTextures(material, TextureType.Height, "texture_normal");
146+
if (normalMaps.Any())
147+
textures.AddRange(normalMaps);
148+
// 4. height maps
149+
var heightMaps = LoadMaterialTextures(material, TextureType.Ambient, "texture_height");
150+
if (heightMaps.Any())
151+
textures.AddRange(heightMaps);
152+
153+
// return a mesh object created from the extracted mesh data
154+
var result = new Mesh(_gl, BuildVertices(vertices), BuildIndices(indices), textures);
155+
return result;
156+
}
157+
158+
private unsafe List<Texture> LoadMaterialTextures(Material* mat, TextureType type, string typeName)
159+
{
160+
var textureCount = _assimp.GetMaterialTextureCount(mat, type);
161+
List<Texture> textures = new List<Texture>();
162+
for (uint i = 0; i < textureCount; i++)
163+
{
164+
AssimpString path;
165+
_assimp.GetMaterialTexture(mat, type, i, &path, null, null, null, null, null, null);
166+
bool skip = false;
167+
for (int j = 0; j < _texturesLoaded.Count; j++)
168+
{
169+
if (_texturesLoaded[j].Path == path)
170+
{
171+
textures.Add(_texturesLoaded[j]);
172+
skip = true;
173+
break;
174+
}
175+
}
176+
if (!skip)
177+
{
178+
var texture = new Texture(_gl, Directory, type);
179+
texture.Path = path;
180+
textures.Add(texture);
181+
_texturesLoaded.Add(texture);
182+
}
183+
}
184+
return textures;
185+
}
186+
187+
private float[] BuildVertices(List<Vertex> vertexCollection)
188+
{
189+
var vertices = new List<float>();
190+
191+
foreach (var vertex in vertexCollection)
192+
{
193+
vertices.Add(vertex.Position.X);
194+
vertices.Add(vertex.Position.Y);
195+
vertices.Add(vertex.Position.Z);
196+
vertices.Add(vertex.TexCoords.X);
197+
vertices.Add(vertex.TexCoords.Y);
198+
}
199+
200+
return vertices.ToArray();
201+
}
202+
203+
private uint[] BuildIndices(List<uint> indices)
204+
{
205+
return indices.ToArray();
206+
}
207+
208+
public void Dispose()
209+
{
210+
foreach (var mesh in Meshes)
211+
{
212+
mesh.Dispose();
213+
}
214+
215+
_texturesLoaded = null;
216+
}
217+
}
218+
}

0 commit comments

Comments
 (0)