Implement Mesh partitioning

This commit is contained in:
2026-09-27 17:48:22 +02:00
parent 11426fe8fb
commit 850a6f601b
21 changed files with 1146638 additions and 181 deletions

103
README.md
View File

@@ -28,75 +28,72 @@ This is the current state of the engine architecture:
```mermaid
flowchart TB
subgraph Application
Renderer
Scene
Camera
Framebuffer
end
subgraph Infrastructure["Infrastructure"]
direction LR
subgraph World
Entity
Transform
Mesh
Material
LightEnvironment
end
subgraph SceneData["Scene Data"]
Vertices
Indices
Shader
Texture
UniformBuffer
DirectionalLight
PointLight
SpotLight
subgraph Platform
direction TB
Window --> GLFW
Window --> glad
Window --> ImGui["Dear ImGui"]
end
subgraph Loading
Loader
tinyobjloader
stb_image
direction TB
Loader --> tinyobjloader
Loader --> stb_image
end
end
subgraph Platform
Window
GLFW
glad
ImGui["Dear ImGui"]
end
Renderer --> Scene
subgraph Application
direction LR
Framebuffer
Renderer --> Camera
Renderer --> UniformBuffer
Renderer --> Scene
Renderer -->|Updates| UniformBuffer
end
subgraph World
direction LR
subgraph SceneGraph["Scene Graph"]
direction TB
Scene --> Entity
Scene --> LightEnvironment
Entity --> Transform
Entity --> Mesh
Entity --> Material
Mesh --> Vertices
Mesh --> Indices
Material --> Shader
Material --> Texture
Shader --> UniformBuffer
Entity -.->|Optional override| Material
Mesh -->|Partitions| SubMesh
SubMesh -->|Owns| Material
end
subgraph Lighting
direction TB
Scene --> LightEnvironment
LightEnvironment --> DirectionalLight
LightEnvironment --> PointLight
LightEnvironment --> SpotLight
end
end
Loader --> tinyobjloader
tinyobjloader --> Entity
subgraph SceneData["Scene Data"]
direction LR
Texture --> stb_image
subgraph Geometry
direction TB
Mesh --> Vertices
Mesh --> Indices
end
Window --> GLFW
Window --> glad
Window --> ImGui
subgraph Resources
direction TB
Material --> Shader
Material --> Texture
UniformBuffer -->|Global data| Shader
end
end
tinyobjloader -->|Generates| Entity
Texture -.->|Decodes via| stb_image
classDef application fill:#7c3aed,stroke:#4c1d95,color:#fff
classDef world fill:#2563eb,stroke:#1e3a8a,color:#fff
@@ -105,8 +102,8 @@ flowchart TB
classDef platform fill:#64748b,stroke:#334155,color:#fff
class Renderer,Scene,Camera,Framebuffer application
class Entity,Transform,Mesh,Material,LightEnvironment world
class Vertices,Indices,Shader,Texture,UniformBuffer,DirectionalLight,PointLight,SpotLight data
class Entity,Transform,Mesh,SubMesh,Material,LightEnvironment world
class UniformBuffer,Shader,Texture,Vertices,Indices,DirectionalLight,PointLight,SpotLight data
class Loader,tinyobjloader,stb_image loading
class Window,GLFW,glad,ImGui platform
```

View File

@@ -0,0 +1,313 @@
# Blender MTL File: 'Audi_R8_2017.blend'
# Material Count: 31
newmtl AirBackOutput
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl AudiLogoChrome
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl Axe
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl BlackGlossy
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.050000 0.050000 0.050000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl BodyMaterials(00297F)
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.000000 0.160784 0.498039
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl Bolt(Black)
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.050000 0.050000 0.050000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl Brake
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.100000 0.100000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl BrakeDisc
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.100000 0.100000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl CarbonFibre
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.150000 0.150000 0.150000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl Centre(Black)
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.050000 0.050000 0.050000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl FarLight
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl FrontHighlightChrome
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl GlassBackHighlight
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.100000 0.100000 0.120000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl GlassFrontHighlight
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.100000 0.100000 0.120000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl Ground
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl HoneyComb
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.050000 0.050000 0.050000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl InteriorFrontHighlight
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl LightBackHighlight
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl LightFrontHighlight
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl LightSource
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl Mirrors
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl MirrorsLightSouces
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl NumberPlateLight
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl NumbrePlateAudiR8
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
map_Kd NumernSchildAudiR8.png
newmtl Ring(Light)
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl RoofCanvas
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl Tyre
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.030000 0.030000 0.030000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl WheelsChrome
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl WindscreenGlassBlack
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.050000 0.050000 0.050000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl WindscreenGlassGrey
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.100000 0.100000 0.120000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl undercarriage
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.050000 0.050000 0.050000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2

1145920
assets/audi_r8/Audi_R8_2017.obj Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

View File

@@ -0,0 +1,3 @@
Model by hermes25, from: https://free3d.com/3d-model/audi-r8-spyder-22646.html
Modified material for easier lighting and diffuse colors

76
assets/mtl_converter.py Normal file
View File

@@ -0,0 +1,76 @@
import argparse
import re
import os
import sys
COLOR_RULES = {
"black": "0.05 0.05 0.05", "dark": "0.15 0.15 0.15", "grey": "0.5 0.5 0.5",
"white": "0.9 0.9 0.9", "chrome": "0.8 0.8 0.8", "metal": "0.7 0.7 0.7",
"silver": "0.75 0.75 0.75", "gold": "0.8 0.7 0.2", "copper": "0.7 0.4 0.2",
"glass": "0.1 0.1 0.12", "window": "0.1 0.1 0.12", "mirror": "0.9 0.9 0.9",
"tyre": "0.03 0.03 0.03", "tire": "0.03 0.03 0.03", "rubber": "0.03 0.03 0.03",
"wood": "0.4 0.25 0.1", "carbon": "0.15 0.15 0.15", "plastic": "0.8 0.8 0.8",
"red": "0.8 0.1 0.1", "green": "0.1 0.8 0.1", "blue": "0.1 0.1 0.8",
"yellow": "0.8 0.8 0.1"
}
def hex_to_kd(hex_str):
r, g, b = int(hex_str[0:2], 16), int(hex_str[2:4], 16), int(hex_str[4:6], 16)
return f"{r/255.0:.6f} {g/255.0:.6f} {b/255.0:.6f}"
def get_color(name):
match = re.search(r'\(([A-Fa-f0-9]{6})\)', name)
if match:
return hex_to_kd(match.group(1))
name_lower = name.lower()
for kw, kd in COLOR_RULES.items():
if kw in name_lower: return kd
return None
def main():
parser = argparse.ArgumentParser(description="Auto-fix missing Kd colors in MTL files based on material names.")
parser.add_argument("input", help="Path to the input .mtl file")
parser.add_argument("-o", "--output", help="Path to output file (default: <input>_fixed.mtl)")
parser.add_argument("-w", "--overwrite", action="store_true", help="Overwrite the input file directly")
args = parser.parse_args()
if not os.path.exists(args.input):
print(f"Error: {args.input} not found.")
sys.exit(1)
with open(args.input, 'r') as f:
lines = f.readlines()
out_lines = []
current_mat = ""
changes = 0
for line in lines:
if line.startswith("newmtl "):
current_mat = line.split()[1].strip()
out_lines.append(line)
elif line.startswith("Kd ") and current_mat:
new_kd = get_color(current_mat)
if new_kd:
out_lines.append(f"Kd {new_kd}\n")
changes += 1
else:
out_lines.append(line)
else:
out_lines.append(line)
out_path = args.input if args.overwrite else (args.output or args.input.replace(".mtl", "_fixed.mtl"))
with open(out_path, 'w') as f:
f.writelines(out_lines)
print(f"Done! {changes} materials updated. Saved to {out_path}")
if __name__ == "__main__":
main()

View File

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

Before

Width:  |  Height:  |  Size: 181 KiB

After

Width:  |  Height:  |  Size: 181 KiB

View File

Before

Width:  |  Height:  |  Size: 457 KiB

After

Width:  |  Height:  |  Size: 457 KiB

View File

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 141 KiB

View File

Before

Width:  |  Height:  |  Size: 231 B

After

Width:  |  Height:  |  Size: 231 B

View File

Before

Width:  |  Height:  |  Size: 251 KiB

After

Width:  |  Height:  |  Size: 251 KiB

View File

@@ -14,9 +14,9 @@ class Entity
public:
Transform transform{};
Rc<Mesh> mesh;
Rc<Material> material;
Rc<Material> material{}; // Optional material override
Entity(Rc<Mesh> mesh, Rc<Material> material)
Entity(Rc<Mesh> mesh, Rc<Material> material = nullptr)
: mesh(mesh)
, material(material) {}
};

View File

@@ -34,87 +34,129 @@ Opt<Entity> load_obj(const std::string& file_path, Rc<Shader> shader)
std::string err;
fs::path parent_path = fs::path{file_path}.parent_path();
parent_path += fs::path::preferred_separator;
std::string base_dir = parent_path.string();
std::string base_dir = !parent_path.empty()
? parent_path.string() + fs::path::preferred_separator
: "";
bool ok = tinyobj::LoadObj(&attrib, &shapes, &materials, &err,
file_path.c_str(), base_dir.c_str());
if (!ok || !err.empty()) {
std::cerr << "ERROR: LOAD_OBJ: Could not load " << file_path << std::endl << err;
if (!ok) {
std::cerr << "ERROR: LOAD_OBJ: Could not load " << file_path << ": " << err;
return std::nullopt;
}
// Build the unified mesh
std::vector<Rc<Material>> loaded_materials;
loaded_materials.reserve(materials.size());
// Load materials
for (const auto& mtl : materials) {
auto mat = make_rc<Material>();
mat->shader = shader;
mat->shininess = mtl.shininess;
mat->diffuse_color = glm::vec3{mtl.diffuse[0], mtl.diffuse[1], mtl.diffuse[2]};
mat->specular_color = glm::vec3{mtl.specular[0], mtl.specular[1], mtl.specular[2]};
if (!mtl.diffuse_texname.empty()) {
mat->diffuse_map = Texture::create(base_dir + mtl.diffuse_texname);
if (!mat->diffuse_map) {
std::cerr << "WARNING: LOAD_OBJ: Could not create diffuse texture: "
<< mtl.diffuse_texname << std::endl;
}
}
if (!mtl.specular_texname.empty()) {
mat->specular_map = Texture::create(base_dir + mtl.specular_texname);
if (!mat->specular_map) {
std::cerr << "WARNING: LOAD_OBJ: Could not create specular texture: "
<< mtl.specular_texname << std::endl;
}
}
loaded_materials.push_back(mat);
}
// Default material fallback for faces without a material (-1)
auto default_material = make_rc<Material>();
default_material->shader = shader;
// Map faces/indices grouped by material id
std::vector<Vertex> vertices;
std::vector<GLuint> indices;
std::unordered_map<tinyobj::index_t, GLuint,
VertexIndexHash, VertexIndexEqual> unique_vertices;
std::unordered_map<tinyobj::index_t, GLuint, VertexIndexHash, VertexIndexEqual> unique_vertices;
// Group index buffers: material_id -> list of index references
std::unordered_map<int, std::vector<GLuint>> mat_to_indices;
for (const auto& shape : shapes) {
for (const auto& index : shape.mesh.indices) {
if (unique_vertices.count(index) == 0) {
size_t index_offset = 0;
for (size_t f = 0; f < shape.mesh.num_face_vertices.size(); ++f) {
size_t fv = static_cast<size_t>(shape.mesh.num_face_vertices[f]);
int mat_id = shape.mesh.material_ids[f];
for (size_t v = 0; v < fv; ++v) {
tinyobj::index_t idx = shape.mesh.indices[index_offset + v];
if (unique_vertices.count(idx) == 0) {
Vertex vertex{};
vertex.position = {
attrib.vertices[3 * index.vertex_index + 0],
attrib.vertices[3 * index.vertex_index + 1],
attrib.vertices[3 * index.vertex_index + 2]
attrib.vertices[3 * idx.vertex_index + 0],
attrib.vertices[3 * idx.vertex_index + 1],
attrib.vertices[3 * idx.vertex_index + 2]
};
if (index.normal_index >= 0) {
if (idx.normal_index >= 0) {
vertex.normal = {
attrib.normals[3 * index.normal_index + 0],
attrib.normals[3 * index.normal_index + 1],
attrib.normals[3 * index.normal_index + 2]
attrib.normals[3 * idx.normal_index + 0],
attrib.normals[3 * idx.normal_index + 1],
attrib.normals[3 * idx.normal_index + 2]
};
}
if (index.texcoord_index >= 0) {
if (idx.texcoord_index >= 0) {
vertex.texture_coords = {
attrib.texcoords[2 * index.texcoord_index + 0],
1.0f - attrib.texcoords[2 * index.texcoord_index + 1]
attrib.texcoords[2 * idx.texcoord_index + 0],
1.0f - attrib.texcoords[2 * idx.texcoord_index + 1]
};
}
unique_vertices[index] = static_cast<GLuint>(vertices.size());
unique_vertices[idx] = static_cast<GLuint>(vertices.size());
vertices.push_back(vertex);
}
indices.push_back(unique_vertices[index]);
mat_to_indices[mat_id].push_back(unique_vertices[idx]);
}
index_offset += fv;
}
}
auto mesh = Mesh::create(vertices, indices);
// Assemble unified index buffer and SubMesh definitions
std::vector<GLuint> all_indices;
std::vector<Mesh::SubMesh> submeshes;
for (const auto& [mat_id, indices] : mat_to_indices) {
Mesh::SubMesh submesh{};
submesh.index_offset = static_cast<GLuint>(all_indices.size());
submesh.index_count = static_cast<GLuint>(indices.size());
if (0 <= mat_id && mat_id < static_cast<int>(loaded_materials.size())) {
submesh.material = loaded_materials[mat_id];
} else {
submesh.material = default_material;
}
all_indices.insert(all_indices.end(), indices.begin(), indices.end());
submeshes.push_back(submesh);
}
auto mesh = Mesh::create(vertices, all_indices, submeshes);
if (!mesh) {
std::cerr << "ERROR: LOAD_OBJ: Could not create mesh" << std::endl;
return std::nullopt;
}
auto material = make_rc<Material>();
material->shader = shader; // Assigned required shader
// If the .obj file had an .mtl file attached to it
if (!materials.empty()) {
// Take only the first material found
const tinyobj::material_t& mtl = materials[0];
if (!mtl.diffuse_texname.empty()) {
material->diffuse_map = Texture::create(base_dir + mtl.diffuse_texname);
if (!material->diffuse_map) {
std::cerr << "WARNING: LOAD_OBJ: Could not create diffuse texture" << std::endl;
}
}
if (!mtl.specular_texname.empty()) {
material->specular_map = Texture::create(base_dir + mtl.specular_texname);
if (!material->specular_map) {
std::cerr << "WARNING: LOAD_OBJ: Could not create specular texture" << std::endl;
}
}
material->shininess = mtl.shininess;
}
return Entity{mesh, material};
return Entity{mesh};
}
} // namespace Fan

View File

@@ -71,15 +71,15 @@ int main()
auto shader = Shader::create("./src/shaders/default.vert", "./src/shaders/default.frag");
auto debug_shader = Shader::create("./src/shaders/debug.vert", "./src/shaders/debug.frag", false);
// auto car = load_obj("./assets/AudiR8Spyder_2017/3D Models/Audi_R8_2017.obj", shader);
auto car = load_obj("./assets/audi_r8/Audi_R8_2017.obj", shader);
auto backpack = load_obj("./assets/backpack/backpack.obj", shader);
auto cube_mesh = Mesh::create(get_cube_vertices());
auto cube_texture = Texture::create("./assets/container.jpg");
auto cube_texture = Texture::create("./assets/textures/container.jpg");
auto default_mesh = Mesh::create(get_cube_vertices());
auto default_texture = Texture::create("./assets/debugempty.png");
auto default_texture = Texture::create("./assets/textures/debugempty.png");
auto framebuffer = FrameBuffer::create(window->get_width(), window->get_height());
@@ -94,17 +94,17 @@ int main()
/******************** Scene construction ********************/
// Car scene
// Rc<Scene> car_scene = make_rc<Scene>();
// user_data.scenes.push_back(car_scene);
//
// car_scene->add_entity(*car);
//
// PointLight light_white1;
// light_white1.position = glm::vec3(0.0f, 3.0f, 3.0f);
// light_white1.diffuse = glm::vec3(2.0f, 2.0f, 2.0f); // Bright white
// light_white1.specular = glm::vec3(2.0f, 2.0f, 2.0f); // White highlights
//
// car_scene->light_environment.point_lights.push_back(light_white1);
Rc<Scene> car_scene = make_rc<Scene>();
user_data.scenes.push_back(car_scene);
car_scene->add_entity(*car);
PointLight light_white1;
light_white1.position = glm::vec3(0.0f, 3.0f, 3.0f);
light_white1.diffuse = glm::vec3(1.0f, 1.0f, 1.0f); // Bright white
light_white1.specular = glm::vec3(1.0f, 1.0f, 1.0f); // White highlights
car_scene->light_environment.point_lights.push_back(light_white1);
// Backpack scene
Rc<Scene> backpack_scene = make_rc<Scene>();

View File

@@ -7,24 +7,27 @@ void Material::bind() const
{
shader->bind();
// Diffuse (slot 0)
if (diffuse_map) {
diffuse_map->bind(0);
} else {
Texture::unbind(0);
}
shader->set_int("u_material.diffuse", 0);
// Specular (slot 1)
if (specular_map) {
specular_map->bind(1);
} else {
Texture::unbind(1);
}
shader->set_int("u_material.specular", 1);
// Shininess
shader->set_vec3("u_material.diffuse_color", diffuse_color);
shader->set_vec3("u_material.specular_color", specular_color);
shader->set_float("u_material.shininess", shininess);
// Bind diffuse map to texture unit 0
if (diffuse_map) {
shader->set_int("u_material.use_diffuse_map", 1);
diffuse_map->bind(0);
shader->set_int("u_material.diffuse", 0);
} else {
shader->set_int("u_material.use_diffuse_map", 0);
}
// Bind specular map to texture unit 1
if (specular_map) {
shader->set_int("u_material.use_specular_map", 1);
specular_map->bind(1);
shader->set_int("u_material.specular", 1);
} else {
shader->set_int("u_material.use_specular_map", 0);
}
}
} // namespace Fan

View File

@@ -12,6 +12,9 @@ struct Material
Rc<Shader> shader;
Rc<Texture> diffuse_map;
Rc<Texture> specular_map;
glm::vec3 diffuse_color{1.0f, 1.0f, 1.0f};
glm::vec3 specular_color{1.0f, 1.0f, 1.0f};
float shininess{32.0f};
void bind() const;

View File

@@ -7,7 +7,8 @@ namespace Fan
{
Rc<Mesh> Mesh::create(const std::vector<Vertex>& vertices,
const std::vector<GLuint>& indices/* = {}*/)
const std::vector<GLuint>& indices/* = {}*/,
const std::vector<SubMesh>& submeshes/* = {}*/)
{
GLuint VBO{}, VAO{}, EBO{};
bool has_indices = !indices.empty();
@@ -38,7 +39,18 @@ Rc<Mesh> Mesh::create(const std::vector<Vertex>& vertices,
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
return make_rc<Mesh>(vertices, indices, has_indices, VBO, VAO, EBO);
// Submesh setup
std::vector<SubMesh> final_submeshes = submeshes;
// If no submeshes were explicitly defined, create one default submesh spanning the entire mesh
if (final_submeshes.empty()) {
SubMesh default_submesh{};
default_submesh.index_offset = 0;
default_submesh.index_count = indices.empty() ? vertices.size() : indices.size();
final_submeshes.push_back(default_submesh);
}
return make_rc<Mesh>(vertices, indices, final_submeshes, has_indices, VBO, VAO, EBO);
}
Mesh::~Mesh()
@@ -51,6 +63,7 @@ Mesh::~Mesh()
Mesh::Mesh(Mesh&& other) noexcept
: m_vertices(std::move(other.m_vertices))
, m_indices(std::move(other.m_indices))
, m_submeshes(std::move(other.m_submeshes))
, m_has_indices(std::exchange(other.m_has_indices, false))
, m_VBO(std::exchange(other.m_VBO, 0))
, m_VAO(std::exchange(other.m_VAO, 0))
@@ -64,6 +77,7 @@ Mesh& Mesh::operator=(Mesh&& other) noexcept
std::swap(m_vertices, other.m_vertices);
std::swap(m_indices, other.m_indices);
std::swap(m_submeshes, other.m_submeshes);
std::swap(m_has_indices, other.m_has_indices);
std::swap(m_VBO, other.m_VBO);
std::swap(m_VAO, other.m_VAO);
@@ -72,15 +86,34 @@ Mesh& Mesh::operator=(Mesh&& other) noexcept
return *this;
}
const std::vector<Mesh::SubMesh>& Mesh::get_submeshes() const
{
return m_submeshes;
}
void Mesh::draw() const
{
glBindVertexArray(m_VAO);
if (m_has_indices) {
glDrawElements(GL_TRIANGLES, m_indices.size(), GL_UNSIGNED_INT, 0);
} else {
glDrawArrays(GL_TRIANGLES, 0, m_vertices.size());
for (const auto& submesh : m_submeshes) {
draw_submesh(submesh.index_offset, submesh.index_count);
}
glBindVertexArray(0);
}
void Mesh::draw_submesh(GLuint index_offset, GLuint index_count) const
{
glBindVertexArray(m_VAO);
if (m_has_indices) {
glDrawElements(GL_TRIANGLES, index_count, GL_UNSIGNED_INT,
reinterpret_cast<const void*>(index_offset * sizeof(GLuint)));
} else {
glDrawArrays(GL_TRIANGLES, index_offset, index_count);
}
glBindVertexArray(0);
}
} // namespace Fan

View File

@@ -5,6 +5,7 @@
#include <glad/gl.h>
#include "vertex.hpp"
#include "material.hpp"
#include "utils.hpp"
namespace Fan
@@ -13,14 +14,24 @@ namespace Fan
class Mesh
{
public:
struct SubMesh
{
GLuint index_offset{};
GLuint index_count{};
Rc<Material> material{};
};
static Rc<Mesh> create(const std::vector<Vertex>& vertices,
const std::vector<GLuint>& indices = {});
const std::vector<GLuint>& indices = {},
const std::vector<SubMesh>& submeshes = {});
Mesh(const std::vector<Vertex>& vertices,
const std::vector<GLuint>& indices,
const std::vector<SubMesh>& submeshes,
bool has_indices,
GLuint VBO, GLuint VAO, GLuint EBO)
: m_vertices(vertices)
, m_indices(indices)
, m_submeshes(submeshes)
, m_has_indices(has_indices)
, m_VBO(VBO)
, m_VAO(VAO)
@@ -32,11 +43,15 @@ public:
Mesh(Mesh&& other) noexcept;
Mesh& operator=(Mesh&& other) noexcept;
const std::vector<SubMesh>& get_submeshes() const;
void draw() const;
void draw_submesh(GLuint index_offset, GLuint index_count) const;
private:
std::vector<Vertex> m_vertices{};
std::vector<GLuint> m_indices{};
std::vector<SubMesh> m_submeshes{};
bool m_has_indices{};
GLuint m_VBO{};

View File

@@ -49,31 +49,65 @@ void Renderer::draw_scene(const Scene& scene, const Camera& camera,
{
update_global_data(scene, camera, screen_width, screen_height);
// Sort Entities: Shader -> Material -> Entities
using RenderQueue = std::unordered_map<Shader*, std::unordered_map<Material*, std::vector<const Entity*>>>;
struct DrawCommand
{
const Transform* transform;
const Mesh* mesh;
GLuint index_offset;
GLuint index_count;
};
// Sort Entities: Shader -> Material -> SubMesh Draw Commands
using RenderQueue = std::unordered_map<Shader*, std::unordered_map<Material*, std::vector<DrawCommand>>>;
RenderQueue render_queue;
for (const auto& entity : scene.get_entities()) {
if (!entity.mesh || !entity.material || !entity.material->shader) {
// Ignore empty entities
continue;
}
if (!entity.mesh) { continue; }
// If Entity has an explicit material override
if (entity.material) {
if (!entity.material->shader) { continue; }
Shader* shader_ptr = entity.material->shader.get();
Material* material_ptr = entity.material.get();
render_queue[shader_ptr][material_ptr].push_back(&entity);
for (const auto& submesh : entity.mesh->get_submeshes()) {
render_queue[shader_ptr][material_ptr].push_back({
&entity.transform,
entity.mesh.get(),
submesh.index_offset,
submesh.index_count
});
}
// Draw using the sorted queue
// Otherwise use SubMesh's native material
} else {
for (const auto& submesh : entity.mesh->get_submeshes()) {
if (!submesh.material || !submesh.material->shader) { continue; }
Shader* shader_ptr = submesh.material->shader.get();
Material* material_ptr = submesh.material.get();
render_queue[shader_ptr][material_ptr].push_back({
&entity.transform,
entity.mesh.get(),
submesh.index_offset,
submesh.index_count
});
}
}
}
// Execute sorted queue for minimal state switching
for (const auto& [shader, materials] : render_queue) {
shader->bind();
for (const auto& [material, entities] : materials) {
for (const auto& [material, draw_command] : materials) {
material->bind();
for (const auto& entity : entities) {
shader->set_mat4("u_model", entity->transform.get_model_matrix());
entity->mesh->draw();
for (const auto& cmd: draw_command) {
shader->set_mat4("u_model", cmd.transform->get_model_matrix());
cmd.mesh->draw_submesh(cmd.index_offset, cmd.index_count);
}
}
}

View File

@@ -3,6 +3,10 @@
struct Material {
sampler2D diffuse;
sampler2D specular;
int use_diffuse_map;
int use_specular_map;
vec3 diffuse_color;
vec3 specular_color;
float shininess;
};
@@ -59,6 +63,20 @@ in vec2 texture_coords;
out vec4 frag_color;
vec3 get_diffuse() {
if (u_material.use_diffuse_map == 1) {
return vec3(texture(u_material.diffuse, texture_coords)) * u_material.diffuse_color;
}
return u_material.diffuse_color;
}
vec3 get_specular() {
if (u_material.use_specular_map == 1) {
return vec3(texture(u_material.specular, texture_coords)) * u_material.specular_color;
}
return u_material.specular_color;
}
vec3 calculate_directional_light(DirLight light, vec3 normal, vec3 view_dir);
vec3 calculate_point_light(PointLight light, vec3 normal, vec3 frag_position, vec3 view_dir);
vec3 calculate_spot_light(SpotLight light, vec3 normal, vec3 frag_position, vec3 view_dir);
@@ -93,9 +111,9 @@ vec3 calculate_directional_light(DirLight light, vec3 normal, vec3 view_dir)
vec3 reflect_dir = reflect(-light_dir, normal);
float spec = pow(max(dot(view_dir, reflect_dir), 0.0), u_material.shininess);
vec3 ambient = light.ambient.xyz * vec3(texture(u_material.diffuse, texture_coords));
vec3 diffuse = light.diffuse.xyz * diff * vec3(texture(u_material.diffuse, texture_coords));
vec3 specular = light.specular.xyz * spec * vec3(texture(u_material.specular, texture_coords));
vec3 ambient = light.ambient.xyz * get_diffuse();
vec3 diffuse = light.diffuse.xyz * diff * get_diffuse();
vec3 specular = light.specular.xyz * spec * get_specular();
return ambient + diffuse + specular;
}
@@ -110,9 +128,9 @@ vec3 calculate_point_light(PointLight light, vec3 normal, vec3 frag_position, ve
float dist = length(light.position.xyz - frag_position);
float attenuation = 1.0 / (light.attenuation.x + light.attenuation.y * dist + light.attenuation.z * (dist * dist));
vec3 ambient = light.ambient.xyz * vec3(texture(u_material.diffuse, texture_coords));
vec3 diffuse = light.diffuse.xyz * diff * vec3(texture(u_material.diffuse, texture_coords));
vec3 specular = light.specular.xyz * spec * vec3(texture(u_material.specular, texture_coords));
vec3 ambient = light.ambient.xyz * get_diffuse();
vec3 diffuse = light.diffuse.xyz * diff * get_diffuse();
vec3 specular = light.specular.xyz * spec * get_specular();
return (ambient + diffuse + specular) * attenuation;
}
@@ -131,9 +149,9 @@ vec3 calculate_spot_light(SpotLight light, vec3 normal, vec3 frag_position, vec3
float epsilon = light.cut_offs.x - light.cut_offs.y;
float intensity = clamp((theta - light.cut_offs.y) / epsilon, 0.0, 1.0);
vec3 ambient = light.ambient.xyz * vec3(texture(u_material.diffuse, texture_coords));
vec3 diffuse = light.diffuse.xyz * diff * vec3(texture(u_material.diffuse, texture_coords));
vec3 specular = light.specular.xyz * spec * vec3(texture(u_material.specular, texture_coords));
vec3 ambient = light.ambient.xyz * get_diffuse();
vec3 diffuse = light.diffuse.xyz * diff * get_diffuse();
vec3 specular = light.specular.xyz * spec * get_specular();
return (ambient + diffuse + specular) * attenuation * intensity;
}