Skip to content
Draft
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@
/active_project
/test/assets/assets.db
/test/products/
_codeql_detected_source_root
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@
#include <render/adaptor/assets/MeshAsset.h>
#include <render/resource/Mesh.h>
#include <render/mesh/MeshRenderer.h>
#include <render/lod/MeshLodGroup.h>

namespace sky {

struct LodMeshAssetData {
Uuid meshUuid;
float screenSize = 0.f;
};

class StaticMeshComponent : public ComponentBase, public IAssetEvent {
public:
StaticMeshComponent() = default;
Expand Down Expand Up @@ -40,11 +46,17 @@ namespace sky {
void SetEnableMeshletConeDebug(bool enable);
bool GetEnableMeshletConeDebug() const { return debugFlags.TestBit(MeshDebugFlagBit::MESHLET_CONE); }

void SetLodMeshes(const std::vector<LodMeshAssetData> &lodMeshes);
const std::vector<LodMeshAssetData> &GetLodMeshes() const { return lodMeshAssets; }
void SetLodBias(float bias);
float GetLodBias() const { return lodBias; }

void OnAttachToWorld() override;
void OnDetachFromWorld() override;
private:
void ShutDown();
void BuildRenderer();
void BuildLodGroup();

void OnAssetLoaded() override;

Expand All @@ -59,6 +71,11 @@ namespace sky {
RDMeshPtr meshInstance;
MeshRenderer *renderer = nullptr;

std::vector<LodMeshAssetData> lodMeshAssets;
std::vector<MeshAssetPtr> lodMeshAssetPtrs;
RDMeshLodGroupPtr lodGroup;
float lodBias = 1.0f;

std::atomic_bool dirty = false;

EventBinder<IAssetEvent, Uuid> binder;
Expand Down
59 changes: 58 additions & 1 deletion runtime/render/adaptor/src/components/StaticMeshComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ namespace sky {
ar.SaveValueObject(std::string("receiveShadow"), receiveShadow);
ar.SaveValueObject(std::string("meshShading"), enableMeshShading);
ar.SaveValueObject(std::string("mesh"), meshAsset ? meshAsset->GetUuid() : Uuid());
ar.SaveValueObject(std::string("lodBias"), lodBias);

ar.Key("lodMeshes");
ar.StartArray();
for (const auto &lod : lodMeshAssets) {
ar.StartObject();
ar.SaveValueObject(std::string("mesh"), lod.meshUuid);
ar.SaveValueObject(std::string("screenSize"), lod.screenSize);
ar.EndObject();
}
ar.EndArray();

ar.EndObject();
}

Expand All @@ -51,6 +63,7 @@ namespace sky {
Uuid uuid;
ar.LoadKeyValue("mesh", uuid);
SetMeshUuid(uuid);
ar.LoadKeyValue("lodBias", lodBias);
}

void StaticMeshComponent::SetEnableMeshShading(bool enable)
Expand Down Expand Up @@ -105,6 +118,43 @@ namespace sky {
}
}

void StaticMeshComponent::SetLodMeshes(const std::vector<LodMeshAssetData> &lodMeshes)
{
lodMeshAssets = lodMeshes;
dirty.store(true);
}

void StaticMeshComponent::SetLodBias(float bias)
{
lodBias = bias;
if (lodGroup) {
lodGroup->SetLodBias(lodBias);
}
}

void StaticMeshComponent::BuildLodGroup()
{
if (lodMeshAssets.empty()) {
lodGroup = nullptr;
return;
}

auto *am = AssetManager::Get();
lodGroup = new MeshLodGroup();
lodGroup->SetLodBias(lodBias);
lodMeshAssetPtrs.clear();

for (const auto &lodData : lodMeshAssets) {
auto asset = am->LoadAsset<Mesh>(lodData.meshUuid);
if (asset) {
asset->BlockUntilLoaded();
auto meshPtr = CreateMeshFromAsset(asset);
lodGroup->AddLodMesh(meshPtr, lodData.screenSize);
lodMeshAssetPtrs.emplace_back(asset);
}
}
}

void StaticMeshComponent::BuildRenderer()
{
SKY_PROFILE_NAME("Build Static Render")
Expand All @@ -121,7 +171,14 @@ namespace sky {
}

renderer = mf->CreateStaticMesh();
renderer->SetMesh(meshInstance, enableMeshShading);

BuildLodGroup();

if (lodGroup && lodGroup->GetLodCount() > 0) {
renderer->SetLodGroup(lodGroup);
} else {
renderer->SetMesh(meshInstance, enableMeshShading);
}
}

void StaticMeshComponent::ShutDown()
Expand Down
120 changes: 120 additions & 0 deletions runtime/render/core/include/render/lod/LodGroup.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
//
// Created by blues on 2025/2/16.
//

#pragma once

#include <vector>
#include <cstdint>
#include <cmath>
#include <algorithm>
#include <core/math/Vector3.h>
#include <core/math/Matrix4.h>
#include <core/shapes/AABB.h>

namespace sky {

static constexpr uint32_t MAX_LOD_LEVEL = 8;
static constexpr uint32_t INVALID_LOD_LEVEL = ~(0U);

struct LodLevel {
float screenSize = 0.f;
};

struct LodConfig {
std::vector<LodLevel> lodLevels;
std::vector<float> distancesSq;
float lodBias = 1.0f;
};

// UE-style: extract screen-size multiplier from projection matrix
// ScreenMultiple = max(0.5 * ProjMatrix[0][0], 0.5 * ProjMatrix[1][1])
inline float GetScreenMultiple(const Matrix4 &projMatrix)
{
return std::max(0.5f * projMatrix[0][0], 0.5f * projMatrix[1][1]);
}

// UE-style: ComputeBoundsScreenSize
// ScreenSize = 2 * ScreenMultiple * SphereRadius / max(1, Distance)
inline float ComputeBoundsScreenSize(float sphereRadius, float dist, float screenMultiple)
{
float screenRadius = screenMultiple * sphereRadius / std::max(1.0f, dist);
return screenRadius * 2.0f;
}

inline float CalculateScreenSize(const AABB &worldBound, const Vector3 &viewPos, const Matrix4 &projMatrix)
{
auto center = (worldBound.min + worldBound.max) * 0.5f;
auto extent = (worldBound.max - worldBound.min) * 0.5f;
float radius = extent.Length();

auto diff = center - viewPos;
float dist = diff.Length();

float screenMultiple = GetScreenMultiple(projMatrix);
return ComputeBoundsScreenSize(radius, dist, screenMultiple);
}

// Convert a screen-size threshold to a distance threshold
// From: ScreenSize = 2 * ScreenMultiple * SphereRadius / Distance
// Solving: Distance = 2 * ScreenMultiple * SphereRadius / ScreenSize
inline float ScreenSizeToDistance(float screenSize, float sphereRadius, float screenMultiple)
{
if (screenSize <= 0.f) {
return std::numeric_limits<float>::max();
}
return (2.0f * screenMultiple * sphereRadius) / screenSize;
}

inline void PreComputeDistances(LodConfig &config, float sphereRadius, const Matrix4 &projMatrix)
{
float screenMultiple = GetScreenMultiple(projMatrix);
config.distancesSq.resize(config.lodLevels.size());
for (uint32_t i = 0; i < static_cast<uint32_t>(config.lodLevels.size()); ++i) {
float d = ScreenSizeToDistance(config.lodLevels[i].screenSize, sphereRadius, screenMultiple);
config.distancesSq[i] = d * d;
}
}

inline float CalculateDistanceSq(const AABB &worldBound, const Vector3 &viewPos)
{
auto center = (worldBound.min + worldBound.max) * 0.5f;
auto diff = center - viewPos;
return diff.x * diff.x + diff.y * diff.y + diff.z * diff.z;
}

inline uint32_t SelectLodByScreenSize(const LodConfig &config, float screenSize)
{
float biasedSize = screenSize * config.lodBias;
for (uint32_t i = 0; i < static_cast<uint32_t>(config.lodLevels.size()); ++i) {
if (biasedSize >= config.lodLevels[i].screenSize) {
return i;
}
}
return INVALID_LOD_LEVEL;
}

inline uint32_t SelectLodLevel(const LodConfig &config, float screenSize)
{
return SelectLodByScreenSize(config, screenSize);
}

inline uint32_t SelectLodLevel(const LodConfig &config, const AABB &worldBound, const Vector3 &viewPos)
{
if (config.distancesSq.empty()) {
return INVALID_LOD_LEVEL;
}

float distSq = CalculateDistanceSq(worldBound, viewPos);
float biasInv = 1.0f / std::max(config.lodBias, 0.001f);
float biasedDistSq = distSq * biasInv * biasInv;

for (uint32_t i = 0; i < static_cast<uint32_t>(config.distancesSq.size()); ++i) {
if (biasedDistSq < config.distancesSq[i]) {
return i;
}
}
return INVALID_LOD_LEVEL;
}

} // namespace sky
35 changes: 35 additions & 0 deletions runtime/render/core/include/render/lod/MeshLodGroup.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// Created by blues on 2025/2/16.
//

#pragma once

#include <render/resource/Mesh.h>
#include <render/lod/LodGroup.h>

namespace sky {

class MeshLodGroup : public RenderResource {
public:
MeshLodGroup() = default;
~MeshLodGroup() override = default;

void AddLodMesh(const RDMeshPtr &mesh, float screenSize);
void SetLodBias(float bias);
void PreComputeDistances(float sphereRadius, const Matrix4 &projMatrix);

uint32_t GetLodCount() const { return static_cast<uint32_t>(lodMeshes.size()); }
const RDMeshPtr &GetMesh(uint32_t lod) const { return lodMeshes[lod]; }

uint32_t SelectLod(const AABB &worldBound, const Vector3 &viewPos) const;

const LodConfig &GetConfig() const { return config; }

private:
LodConfig config;
std::vector<RDMeshPtr> lodMeshes;
};

using RDMeshLodGroupPtr = CounterPtr<MeshLodGroup>;

} // namespace sky
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,16 @@ namespace sky {
SkeletonMeshRenderer *CreateSkeletonMesh();
void RemoveSkeletonMesh(SkeletonMeshRenderer *mesh);

void SetMainViewName(const Name &name) { mainViewName = name; }

private:
void UpdateLod();

std::list<std::unique_ptr<MeshRenderer>> staticMeshes;

std::list<std::unique_ptr<SkeletonMeshRenderer>> skeletonMeshes;

Name mainViewName;
};

} // namespace sky
14 changes: 14 additions & 0 deletions runtime/render/core/include/render/mesh/MeshRenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#pragma once

#include <render/resource/Mesh.h>
#include <render/lod/MeshLodGroup.h>
#include <render/mesh/MeshletDebugRender.h>
#include <render/RenderPrimitive.h>
#include <core/math/Matrix4.h>
Expand All @@ -27,30 +28,43 @@ namespace sky {
void Tick();
void AttachScene(RenderScene *scn);
void SetMesh(const RDMeshPtr &mesh, bool meshShading = false);
void SetLodGroup(const RDMeshLodGroupPtr &lodGroup);
void SetDebugFlags(const MeshDebugFlags& flag);

void UpdateTransform(const Matrix4 &matrix);
void UpdateLod(const Vector3 &viewPos);

void BuildGeometry();

void SetMaterial(const RDMaterialInstancePtr &mat, uint32_t subMesh);

uint32_t GetCurrentLod() const { return currentLod; }
bool IsVisible() const { return isVisible; }
bool IsCulledByLod() const { return culledByLod; }
protected:
virtual void PrepareUBO();
virtual RDResourceGroupPtr RequestResourceGroup(MeshFeature *feature);
virtual void FillVertexFlags(RenderVertexFlags &flags) {}

void SetupDebugMeshlet();
void Reset();
void ShowPrimitives();
void HidePrimitives();

RenderScene *scene = nullptr;

RDMeshPtr mesh;
RDMeshLodGroupPtr lodGroup;
uint32_t currentLod = 0;

std::vector<std::unique_ptr<RenderMaterialPrimitive>> primitives;
std::unique_ptr<MeshletDebugRender> meshletDebug;
std::vector<RDDynamicUniformBufferPtr> meshletInfos;
RDDynamicUniformBufferPtr ubo;

bool enableMeshShading = false;
bool isVisible = true;
bool culledByLod = false;
MeshDebugFlags debugFlags;
};

Expand Down
30 changes: 30 additions & 0 deletions runtime/render/core/src/lod/MeshLodGroup.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// Created by blues on 2025/2/16.
//

#include <render/lod/MeshLodGroup.h>

namespace sky {

void MeshLodGroup::AddLodMesh(const RDMeshPtr &mesh, float screenSize)
{
lodMeshes.emplace_back(mesh);
config.lodLevels.emplace_back(LodLevel{screenSize});
}

void MeshLodGroup::SetLodBias(float bias)
{
config.lodBias = bias;
}

void MeshLodGroup::PreComputeDistances(float sphereRadius, const Matrix4 &projMatrix)
{
sky::PreComputeDistances(config, sphereRadius, projMatrix);
}

uint32_t MeshLodGroup::SelectLod(const AABB &worldBound, const Vector3 &viewPos) const
{
return SelectLodLevel(config, worldBound, viewPos);
}

} // namespace sky
Loading