diff --git a/CMakeLists.txt b/CMakeLists.txt index a911884..e673fb7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,6 @@ cmake_minimum_required(VERSION 3.7) + option(VERSION_STRING "Project version string" "0.0.0") project(antkeeper VERSION ${VERSION_STRING} LANGUAGES CXX) diff --git a/src/render/context.hpp b/src/render/context.hpp index 36b98d6..523a23c 100644 --- a/src/render/context.hpp +++ b/src/render/context.hpp @@ -30,6 +30,7 @@ namespace scene { class camera; class collection; + class object_base; } namespace render { @@ -80,6 +81,9 @@ struct context /// Subframe interpolation factor. float alpha; + + /// List of objects visible to the active camera. + std::list visible_objects; }; } // namespace render diff --git a/src/render/renderer.cpp b/src/render/renderer.cpp index 84a58e8..9e987a0 100644 --- a/src/render/renderer.cpp +++ b/src/render/renderer.cpp @@ -51,11 +51,15 @@ renderer::renderer() // Allocate skinning palette skinning_palette = new float4x4[MATERIAL_PASS_MAX_BONE_COUNT]; + + // Construct culling stage + culling_stage = new render::culling_stage(); } renderer::~renderer() { delete[] skinning_palette; + delete culling_stage; } void renderer::render(float t, float dt, float alpha, const scene::collection& collection) const @@ -114,21 +118,15 @@ void renderer::render(float t, float dt, float alpha, const scene::collection& c ctx.view_projection = ctx.projection * ctx.view; ctx.exposure = std::exp2(-camera->get_exposure_tween().interpolate(alpha)); + // Execute culling stage + culling_stage->execute(ctx); + // Create render queue render::queue queue; - // Get camera culling volume - ctx.camera_culling_volume = camera->get_culling_mask(); - if (!ctx.camera_culling_volume) - ctx.camera_culling_volume = &camera->get_world_bounds(); - // Queue render operations for each visible scene object - for (const scene::object_base* object: *objects) + for (const scene::object_base* object: ctx.visible_objects) { - // Skip inactive objects - if (!object->is_active()) - continue; - // Process object process_object(ctx, queue, object); } @@ -163,15 +161,6 @@ void renderer::process_model_instance(const render::context& ctx, render::queue& if (!model) return; - // Get object culling volume - const geom::bounding_volume* object_culling_volume = model_instance->get_culling_mask(); - if (!object_culling_volume) - object_culling_volume = &model_instance->get_world_bounds(); - - // Perform view-frustum culling - if (!ctx.camera_culling_volume->intersects(*object_culling_volume)) - return; - const std::vector* instance_materials = model_instance->get_materials(); const std::vector* groups = model->get_groups(); @@ -213,15 +202,6 @@ void renderer::process_model_instance(const render::context& ctx, render::queue& void renderer::process_billboard(const render::context& ctx, render::queue& queue, const scene::billboard* billboard) const { - // Get object culling volume - const geom::bounding_volume* object_culling_volume = billboard->get_culling_mask(); - if (!object_culling_volume) - object_culling_volume = &billboard->get_world_bounds(); - - // Perform view-frustum culling - if (!ctx.camera_culling_volume->intersects(*object_culling_volume)) - return; - math::transform billboard_transform = billboard->get_transform_tween().interpolate(ctx.alpha); billboard_op.material = billboard->get_material(); billboard_op.depth = ctx.clip_near.signed_distance(float3(billboard_transform.translation)); @@ -261,15 +241,6 @@ void renderer::process_lod_group(const render::context& ctx, render::queue& queu void renderer::process_text(const render::context& ctx, render::queue& queue, const scene::text* text) const { - // Get object culling volume - const geom::bounding_volume* object_culling_volume = text->get_culling_mask(); - if (!object_culling_volume) - object_culling_volume = &text->get_world_bounds(); - - // Perform view-frustum culling - if (!ctx.camera_culling_volume->intersects(*object_culling_volume)) - return; - text->render(ctx, queue); } diff --git a/src/render/renderer.hpp b/src/render/renderer.hpp index 6a611a6..03b2f2b 100644 --- a/src/render/renderer.hpp +++ b/src/render/renderer.hpp @@ -23,6 +23,7 @@ #include "render/operation.hpp" #include "render/context.hpp" #include "render/queue.hpp" +#include "render/stage/culling-stage.hpp" #include "gl/vertex-array.hpp" namespace scene @@ -70,6 +71,8 @@ private: mutable render::operation billboard_op; float4x4* skinning_palette; + + render::culling_stage* culling_stage; }; } // namespace render diff --git a/src/render/stage.hpp b/src/render/stage.hpp new file mode 100644 index 0000000..9a4c2cb --- /dev/null +++ b/src/render/stage.hpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_RENDER_STAGE_HPP +#define ANTKEEPER_RENDER_STAGE_HPP + +#include "render/context.hpp" + +namespace render { + +/** + * Abstract base class for a single stage in a render pipeline. + */ +class stage +{ +public: + /// Constructs a render stage. + stage() = default; + + /// Destructs a render stage. + virtual ~stage() = default; + + /** + * Executes the render stage. + * + * @param ctx Render context. + */ + virtual void execute(render::context& ctx) = 0; +}; + +} // namespace render + +#endif // ANTKEEPER_RENDER_STAGE_HPP diff --git a/src/render/stage/culling-stage.cpp b/src/render/stage/culling-stage.cpp new file mode 100644 index 0000000..b08811a --- /dev/null +++ b/src/render/stage/culling-stage.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#include "render/stage/culling-stage.hpp" +#include "scene/camera.hpp" +#include "scene/collection.hpp" +#include +#include +#include + +namespace render { + +void culling_stage::execute(render::context& ctx) +{ + // Get list of all objects in the collection + const std::list& objects = *(ctx.collection->get_objects()); + + // Get camera culling volume + ctx.camera_culling_volume = ctx.camera->get_culling_mask(); + if (!ctx.camera_culling_volume) + ctx.camera_culling_volume = &ctx.camera->get_world_bounds(); + + // Clear set of visible objects + ctx.visible_objects.clear(); + + // Construct mutex to guard set of visible objects + std::mutex mutex; + + // For each object in the scene collection + std::for_each + ( + std::execution::par_unseq, + std::begin(objects), + std::end(objects), + [&](scene::object_base* object) + { + // Ignore inactive objects and cameras + if (!object->is_active() || object->get_object_type_id() == scene::camera::object_type_id) + return; + + // Cull object if it doesn't share any common layers with the camera + //if (!(object->get_layer_mask() & camera_layer_mask)) + // return; + + // Get object culling volume + const geom::bounding_volume* object_culling_volume = object->get_culling_mask(); + if (!object_culling_volume) + object_culling_volume = &object->get_world_bounds(); + + // Cull object if it's outside of the camera culling volume + if (!ctx.camera_culling_volume->intersects(*object_culling_volume)) + return; + + // Insert object into set of visible objects + std::lock_guard guard(mutex); + ctx.visible_objects.push_back(object); + } + ); +} + +} // namespace render diff --git a/src/render/stage/culling-stage.hpp b/src/render/stage/culling-stage.hpp new file mode 100644 index 0000000..9f616f4 --- /dev/null +++ b/src/render/stage/culling-stage.hpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_RENDER_CULLING_STAGE_HPP +#define ANTKEEPER_RENDER_CULLING_STAGE_HPP + +#include "render/stage.hpp" + +namespace render { + +/** + * Builds a set of scene objects visible to the current camera and stores it in the render context. + */ +class culling_stage: public stage +{ +public: + /// Constructs a culling stage. + culling_stage() = default; + + /// Destructs a culling stage. + virtual ~culling_stage() = default; + + /// @copydoc render::stage::execute(render::context&) + virtual void execute(render::context& ctx) final; +}; + +} // namespace render + +#endif // ANTKEEPER_RENDER_CULLING_STAGE_HPP