Assets¶
Freya manages meshes, textures, and PBR materials through pool services.
These pools are process-wide singletons on a shared Device. Multiple windows / renderers reuse the same mesh, texture, and material IDs. Scene state (instances, lights, cull) is per-window — see Core multi-window.
MeshPool¶
auto meshPool = serviceProvider->GetService<fra::MeshPool>();
// Static models: geometry + imported PBR materials (file / embedded textures,
// packed MR, glTF factors). Override per submesh when needed (e.g. lamp bulb).
auto model = meshPool->CreateModelFromFile("./Resources/Models/Helmet.glb");
for (const auto& part : model)
{
instances.push_back({ .meshId = part.meshId,
.materialId = part.materialId, .entityId = id++ });
}
// Skinned: submeshes + materials, joints/weights, shared skeleton/clips.
fra::SkinnedModel fox =
meshPool->CreateSkinnedModelFromFile("./Resources/Models/Fox.glb");
for (const auto& part : fox.submeshes)
{
instances.push_back({ .meshId = part.meshId,
.materialId = part.materialId, .entityId = id++,
.boneOffset = 0, .boneCount = fox.skeleton.JointCount() });
}
// Full stack (AnimGraph, bake, CPU/GPU skin, LOD, look/IK): see Animation.
renderer->UploadBoneMatrices(fra::EvaluateSkeletonPose(
fox.skeleton, fox.clips[0], timeSec));
// From memory (already CPU-side Freya vertices + uint32 indices)
std::uint32_t meshId = meshPool->CreateMesh(vertices, indices);
Static meshes leave Vertex::joints/weights at defaults and SceneInstanceUpload::boneOffset = fra::kNoSkin. Skinned draws use a second/prev bone palette for TAA velocity. Full animation docs: Animation.
Draw submission goes through Renderer::UploadSceneInstances (preferred) or the legacy Draw / DrawInstanced helpers.
TexturePool¶
auto texturePool = serviceProvider->GetService<fra::TexturePool>();
std::uint32_t fromFile =
texturePool->CreateTextureFromFile("./Resources/Textures/albedo.png");
// RGBA8 (or other channel count) already in memory
std::vector<std::uint8_t> rgba = /* ... */;
std::uint32_t fromMemory = texturePool->CreateTextureFromMemory(
rgba.data(), width, height, /*channels=*/4);
Both paths create a mipmapped image and linear anisotropic sampler.
MaterialPool¶
auto materialPool = serviceProvider->GetService<fra::MaterialPool>();
std::uint32_t material = materialPool->Create(fra::MaterialCreateInfo {
.albedo = albedoId,
.normal = normalId,
.roughness = roughnessId, // or packed ORM / metallicRoughness
.emissive = emissiveId,
.metalness = metalnessId, // ignored when packedMetallicRoughness
.occlusion = aoId, // .r; packed ORM uses roughness.r if unset
.albedoFactor = { 1.f, 1.f, 1.f, 1.f },
.roughnessFactor = 1.f,
.metalnessFactor = 1.f,
.emissiveFactor = { 1.f, 1.f, 1.f },
.aoFactor = 1.f, // multiplies sampled AO into G-buffer
.alphaCutoff = 0.f, // Mask: discard when alpha < cutoff
.alphaMode = fra::AlphaMode::Opaque, // Opaque | Mask | Blend
.clearcoat = 0.f, // deferred GGX clearcoat weight
.clearcoatRoughness = 0.03f, // glTF-style default
.transmission = 0.f, // >0: OIT + screen-space refraction
.ior = 1.5f, // refraction bend (glass ≈ 1.5)
.packedMetallicRoughness = false, // G=rough, B=metal (glTF)
.unlit = false, // skip lighting (emissive only)
.doubleSided = false, // G-buffer flips back-face N
.receiveShadows = true,
});
// albedo, normal, roughness, emissive, metalness, occlusion — missing slots skip.
std::uint32_t fromFiles = materialPool->CreateFromTextureFiles({
"./Resources/Textures/albedo.png",
"./Resources/Textures/normal.png",
"./Resources/Textures/roughness.png",
"./Resources/Textures/emissive.png",
"./Resources/Textures/metalness.png",
"./Resources/Textures/ao.png",
});
materialPool->Update(material, updatedCreateInfo);
AlphaMode::Opaque / Mask stay in the deferred MDI camera cull. Mask uses alphaCutoff cutout in the G-buffer and in the shadow pass (bindless albedo alpha), so foliage holes do not cast solid shadows. AlphaMode::Blend is filtered into the Weighted Blended OIT pass (CullMode::Translucent); use albedoFactor.a (and albedo alpha) for coverage, and typically castShadows = false on glass instances. transmission (>0) enables physical glass: OIT samples opaque HDR with a screen-space IOR bend and adds split-sum IBL; glTF KHR_materials_transmission / KHR_materials_ior map on import.
clearcoat (>0) enables a second dielectric GGX lobe in deferred lighting (F0=0.04). The weight is stored in G-buffer PBR.a. When coated, PBR.b holds clearcoatRoughness so the lighting pass does not need a material SSBO; otherwise PBR.b is material AO.
G-buffer / OIT bindless set 1:
| Binding | Content |
|---|---|
| 0 | sampler2D uTextures[] (bindless heap) |
| 1 | MaterialGPU SSBO |
The lighting fullscreen pass uses only set 0 (G-buffer, lights, IBL, shadows). G-buffer albedo.a stores an 8-bit material ID (id / 255); PostProcess::BindMaterial masks against that channel (IDs ≥ 256 alias).
Empty texture optionals use engine fallbacks (white or black). Alpha cutout samples albedo alpha × albedoFactor.a in the G-buffer and shadow passes (Mask). Packed metallic-roughness maps (glTF): roughness from .g, metal from .b; AO from occlusion .r or the packed map .r when no separate occlusion texture is set. unlit skips deferred/OIT analytic lights (emissive still writes scene color). doubleSided is lit on back-faces via no-cull G-buffer + flipped N.
Vertex¶
struct Vertex
{
glm::vec3 position;
glm::vec3 color;
glm::vec3 normal;
glm::vec3 tangent;
glm::vec2 texCoord;
};
Instancing (GPU-driven MDI)¶
Prefer Scene::Upload for application code (dirty-aware retained list). RendererAdvanced::UploadSceneInstances remains for tooling / tests that build a span without a Scene. Frustum cull (compute) atomic-compacts visible instances into multi-draw indirect commands.
Contract: prefer ascending entityId (Freya sorts when needed). TAA prevModel is resolved by entityId (first frame / new ids: prev == model).
#include <Freya/Advanced.hpp>
std::vector<fra::SceneInstanceUpload> instances;
instances.push_back({ .model = M, .mesh = mesh, .material = mat,
.entityId = id, .castShadows = true });
fra::Advanced(*renderer).UploadSceneInstances(instances);
Legacy path: SetInstanceModels + Draw / DrawInstanced still works and is expanded into UploadSceneInstances internally.