feat: add camera culling masks

This commit is contained in:
2026-04-01 01:42:06 +08:00
parent 51736253e3
commit 0fe02fd1b4
9 changed files with 81 additions and 3 deletions

View File

@@ -46,6 +46,9 @@ public:
CameraClearMode GetClearMode() const { return m_clearMode; }
void SetClearMode(CameraClearMode value) { m_clearMode = value; }
uint32_t GetCullingMask() const { return m_cullingMask; }
void SetCullingMask(uint32_t value) { m_cullingMask = value; }
const Math::Color& GetClearColor() const { return m_clearColor; }
void SetClearColor(const Math::Color& value) { m_clearColor = value; }
@@ -61,6 +64,7 @@ private:
float m_depth = 0.0f;
bool m_primary = true;
CameraClearMode m_clearMode = CameraClearMode::Auto;
uint32_t m_cullingMask = 0xFFFFFFFFu;
Math::Color m_clearColor = Math::Color(0.192f, 0.302f, 0.475f, 1.0f);
};

View File

@@ -30,6 +30,8 @@ public:
uint64_t GetUUID() const { return m_uuid; }
const std::string& GetName() const { return m_name; }
void SetName(const std::string& name) { m_name = name; }
uint8_t GetLayer() const { return m_layer; }
void SetLayer(uint8_t layer) { m_layer = std::min<uint8_t>(layer, 31u); }
void DetachFromParent();
@@ -217,6 +219,7 @@ private:
std::string m_name;
bool m_activeSelf = true;
bool m_started = false;
uint8_t m_layer = 0;
GameObject* m_parent = nullptr;
std::vector<GameObject*> m_children;

View File

@@ -67,6 +67,7 @@ private:
void ExtractVisibleItems(
Components::GameObject* gameObject,
const Math::Vector3& cameraPosition,
uint32_t cullingMask,
std::vector<VisibleRenderItem>& visibleItems) const;
};

View File

@@ -34,6 +34,7 @@ void CameraComponent::Serialize(std::ostream& os) const {
os << "depth=" << m_depth << ";";
os << "primary=" << (m_primary ? 1 : 0) << ";";
os << "clearMode=" << static_cast<int>(m_clearMode) << ";";
os << "cullingMask=" << m_cullingMask << ";";
os << "clearColor=" << m_clearColor.r << "," << m_clearColor.g << "," << m_clearColor.b << "," << m_clearColor.a << ";";
}
@@ -68,6 +69,8 @@ void CameraComponent::Deserialize(std::istream& is) {
m_primary = (std::stoi(value) != 0);
} else if (key == "clearMode") {
m_clearMode = static_cast<CameraClearMode>(std::stoi(value));
} else if (key == "cullingMask") {
m_cullingMask = static_cast<uint32_t>(std::stoul(value));
} else if (key == "clearColor") {
std::replace(value.begin(), value.end(), ',', ' ');
std::istringstream ss(value);

View File

@@ -317,6 +317,7 @@ void GameObject::Destroy() {
void GameObject::Serialize(std::ostream& os) const {
os << "name=" << m_name << ";";
os << "active=" << (m_activeSelf ? "1" : "0") << ";";
os << "layer=" << static_cast<uint32_t>(m_layer) << ";";
os << "id=" << m_id << ";";
os << "uuid=" << m_uuid << ";";
os << "transform=";
@@ -346,6 +347,11 @@ void GameObject::Deserialize(std::istream& is) {
is.get(val);
m_activeSelf = (val == '1');
if (is.peek() == ';') is.get();
} else if (strcmp(key, "layer") == 0) {
uint32_t layer = 0;
is >> layer;
SetLayer(static_cast<uint8_t>(layer));
if (is.peek() == ';') is.get();
} else if (strcmp(key, "id") == 0) {
is >> m_id;
if (is.peek() == ';') is.get();

View File

@@ -64,10 +64,11 @@ RenderSceneData RenderSceneExtractor::Extract(
sceneData.cameraData = BuildCameraData(*sceneData.camera, viewportWidth, viewportHeight);
const Math::Vector3 cameraPosition = sceneData.cameraData.worldPosition;
const uint32_t cullingMask = sceneData.camera->GetCullingMask();
const std::vector<Components::GameObject*> rootGameObjects = scene.GetRootGameObjects();
for (Components::GameObject* rootGameObject : rootGameObjects) {
ExtractVisibleItems(rootGameObject, cameraPosition, sceneData.visibleItems);
ExtractVisibleItems(rootGameObject, cameraPosition, cullingMask, sceneData.visibleItems);
}
std::stable_sort(
@@ -92,10 +93,11 @@ RenderSceneData RenderSceneExtractor::ExtractForCamera(
sceneData.camera = &camera;
sceneData.cameraData = BuildCameraData(camera, viewportWidth, viewportHeight);
const Math::Vector3 cameraPosition = sceneData.cameraData.worldPosition;
const uint32_t cullingMask = camera.GetCullingMask();
const std::vector<Components::GameObject*> rootGameObjects = scene.GetRootGameObjects();
for (Components::GameObject* rootGameObject : rootGameObjects) {
ExtractVisibleItems(rootGameObject, cameraPosition, sceneData.visibleItems);
ExtractVisibleItems(rootGameObject, cameraPosition, cullingMask, sceneData.visibleItems);
}
std::stable_sort(
@@ -224,15 +226,20 @@ void RenderSceneExtractor::ExtractLighting(
void RenderSceneExtractor::ExtractVisibleItems(
Components::GameObject* gameObject,
const Math::Vector3& cameraPosition,
uint32_t cullingMask,
std::vector<VisibleRenderItem>& visibleItems) const {
if (gameObject == nullptr || !gameObject->IsActiveInHierarchy()) {
return;
}
const uint32_t gameObjectLayerMask = 1u << gameObject->GetLayer();
const bool isVisibleInCameraMask = (cullingMask & gameObjectLayerMask) != 0;
auto* meshFilter = gameObject->GetComponent<Components::MeshFilterComponent>();
auto* meshRenderer = gameObject->GetComponent<Components::MeshRendererComponent>();
if (meshFilter != nullptr &&
meshRenderer != nullptr &&
isVisibleInCameraMask &&
meshFilter->IsEnabled() &&
meshRenderer->IsEnabled()) {
Resources::Mesh* mesh = meshFilter->GetMesh();
@@ -279,7 +286,7 @@ void RenderSceneExtractor::ExtractVisibleItems(
}
for (Components::GameObject* child : gameObject->GetChildren()) {
ExtractVisibleItems(child, cameraPosition, visibleItems);
ExtractVisibleItems(child, cameraPosition, cullingMask, visibleItems);
}
}

View File

@@ -19,6 +19,7 @@ TEST(CameraComponent_Test, DefaultValues) {
EXPECT_FLOAT_EQ(camera.GetFarClipPlane(), 1000.0f);
EXPECT_TRUE(camera.IsPrimary());
EXPECT_EQ(camera.GetClearMode(), CameraClearMode::Auto);
EXPECT_EQ(camera.GetCullingMask(), 0xFFFFFFFFu);
}
TEST(CameraComponent_Test, SetterClamping) {
@@ -38,6 +39,7 @@ TEST(CameraComponent_Test, SetterClamping) {
TEST(CameraComponent_Test, SerializeRoundTripPreservesClearMode) {
CameraComponent source;
source.SetClearMode(CameraClearMode::DepthOnly);
source.SetCullingMask(0x0000000Fu);
std::stringstream stream;
source.Serialize(stream);
@@ -46,6 +48,7 @@ TEST(CameraComponent_Test, SerializeRoundTripPreservesClearMode) {
target.Deserialize(stream);
EXPECT_EQ(target.GetClearMode(), CameraClearMode::DepthOnly);
EXPECT_EQ(target.GetCullingMask(), 0x0000000Fu);
}
TEST(LightComponent_Test, DefaultValues) {

View File

@@ -4,6 +4,8 @@
#include <XCEngine/Scene/Scene.h>
#include <XCEngine/Core/Math/Vector3.h>
#include <sstream>
using namespace XCEngine::Components;
using namespace XCEngine::Math;
@@ -64,6 +66,20 @@ TEST(GameObject_Test, NamedConstructor) {
EXPECT_EQ(go.GetName(), "TestObject");
}
TEST(GameObject_Test, Layer_GetSetAndSerializeRoundTrip) {
GameObject source("LayeredObject");
source.SetLayer(7);
std::stringstream stream;
source.Serialize(stream);
GameObject target;
target.Deserialize(stream);
EXPECT_EQ(source.GetLayer(), 7u);
EXPECT_EQ(target.GetLayer(), 7u);
}
TEST(GameObject_Test, AddComponent_Single) {
GameObject go;

View File

@@ -180,6 +180,41 @@ TEST(RenderSceneExtractor_Test, ExtractsBrightestDirectionalLightAsMainLight) {
mainLightObject->GetTransform()->GetForward().Normalized() * -1.0f);
}
TEST(RenderSceneExtractor_Test, FiltersVisibleItemsByCameraCullingMask) {
Scene scene("CullingMaskScene");
GameObject* cameraObject = scene.CreateGameObject("Camera");
auto* camera = cameraObject->AddComponent<CameraComponent>();
camera->SetPrimary(true);
camera->SetCullingMask(1u << 5);
GameObject* visibleObject = scene.CreateGameObject("VisibleLayer5");
visibleObject->SetLayer(5);
auto* visibleMeshFilter = visibleObject->AddComponent<MeshFilterComponent>();
visibleObject->AddComponent<MeshRendererComponent>();
Mesh* visibleMesh = CreateTestMesh("Meshes/layer5.mesh");
visibleMeshFilter->SetMesh(visibleMesh);
GameObject* hiddenObject = scene.CreateGameObject("HiddenLayer0");
hiddenObject->SetLayer(0);
auto* hiddenMeshFilter = hiddenObject->AddComponent<MeshFilterComponent>();
hiddenObject->AddComponent<MeshRendererComponent>();
Mesh* hiddenMesh = CreateTestMesh("Meshes/layer0.mesh");
hiddenMeshFilter->SetMesh(hiddenMesh);
RenderSceneExtractor extractor;
const RenderSceneData sceneData = extractor.Extract(scene, nullptr, 800, 600);
ASSERT_EQ(sceneData.visibleItems.size(), 1u);
EXPECT_EQ(sceneData.visibleItems[0].gameObject, visibleObject);
EXPECT_EQ(sceneData.visibleItems[0].mesh, visibleMesh);
visibleMeshFilter->ClearMesh();
hiddenMeshFilter->ClearMesh();
delete visibleMesh;
delete hiddenMesh;
}
TEST(RenderSceneExtractor_Test, ExtractsSectionLevelVisibleItemsAndSortsByRenderQueue) {
Scene scene("SectionScene");