Document and demonstrate prepared mesh LOD visibility
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
if(TARGET faset_render)
|
||||
add_executable(faset_p2_visibility_example
|
||||
"${PROJECT_SOURCE_DIR}/examples/renderer/p2_visibility.cpp")
|
||||
target_link_libraries(faset_p2_visibility_example PRIVATE faset_render)
|
||||
endif()
|
||||
|
||||
if(TARGET faset_render AND BUILD_TESTING)
|
||||
add_executable(faset_render_gpu_visibility_tests
|
||||
"${PROJECT_SOURCE_DIR}/tests/render_gpu_visibility_tests.cpp")
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# GPU visibility and prepared mesh LOD
|
||||
|
||||
Faset can draw opaque static triangle meshes through GPU frustum culling and,
|
||||
optionally, two-pass HZB occlusion. The original direct renderer remains available
|
||||
for comparison. In the Editor, build with `FASET_DEBUG_IMGUI=ON`, press **F12**,
|
||||
and choose **Direct**, **GPU frustum**, or **GPU occlusion** in Diagnostics. **Path:
|
||||
active** confirms that the selected GPU path actually ran. See
|
||||
[developer diagnostics](diagnostics.md) for the HZB preview and counters.
|
||||
|
||||
The current GPU path groups instances with the same mesh and texture into fixed
|
||||
indirect draw bins. A previous-frame HZB can defer an object, but the current-frame
|
||||
post pass checks it again before the final image. Sprites, editor UI, transparent
|
||||
meshes, and shadow casters keep their existing ordered or shadow paths. This option
|
||||
does not require ray tracing or mesh shaders.
|
||||
|
||||
## Supply prepared LODs from C++
|
||||
|
||||
`DrawItem::mesh` is LOD 0. Set `lod_meshes[0]` to a prepared LOD 1 mesh,
|
||||
`lod_meshes[1]` to LOD 2, and so on. These are actual `Mesh` objects with a
|
||||
complete triangle list; Faset does not simplify a source mesh automatically.
|
||||
Keep a stable `instance_key` across frames. Give different scene objects different
|
||||
keys, and retain the same shared mesh objects instead of rebuilding them every
|
||||
frame. For a manually constructed `Snapshot`, supply both `projection` and
|
||||
`view_projection`; set `view_id` for each camera and `camera_cut` when switching
|
||||
shots. The Player's scene extraction fills the camera fields and instance keys.
|
||||
|
||||
```cpp
|
||||
faset::render::DrawItem draw;
|
||||
draw.mesh = prepared_high_detail;
|
||||
draw.lod_meshes = {prepared_medium_detail, prepared_low_detail};
|
||||
draw.instance_key = "scene/object-id/primitive-id";
|
||||
draw.model = faset::render::transform({0, 0, 0});
|
||||
snapshot.draws.push_back(std::move(draw));
|
||||
```
|
||||
|
||||
LOD selection uses projected screen size. The first transition is 192 pixels;
|
||||
each additional level halves that threshold. A 12% hysteresis band avoids
|
||||
switching back and forth near a boundary. Missing levels fall back to an available
|
||||
mesh. Changing level invalidates that instance's temporal occlusion state while
|
||||
preserving its logical key. `FrameStats::lod_counts` reports selected levels 0–3
|
||||
(higher levels are included in the final bucket).
|
||||
|
||||
For a runnable C++ example, build `faset_p2_visibility_example` and capture the
|
||||
same scene in any mode:
|
||||
|
||||
```sh
|
||||
cmake --build --preset linux-debug --target faset_p2_visibility_example
|
||||
build/linux-debug/faset_p2_visibility_example occlusion /tmp/p2-visibility.ppm
|
||||
build/linux-debug/faset_p2_visibility_example direct /tmp/p2-direct.ppm
|
||||
```
|
||||
|
||||
The example creates a fine cube and a prepared coarse tetrahedron in
|
||||
[`examples/renderer/p2_visibility.cpp`](https://github.com/emil28092005/Faset_Engine/blob/main/examples/renderer/p2_visibility.cpp).
|
||||
Imported GLB geometry can be supplied as prepared levels by C++ integration,
|
||||
but automatic LOD generation and assigning a GLB's alternate meshes as LODs in
|
||||
the Inspector are not implemented yet.
|
||||
|
||||
## Measure before choosing a mode
|
||||
|
||||
The GPU route saves per-instance CPU vertex transformation and direct draw
|
||||
submission, but its compute passes and indirect rendering have a cost. Keep the
|
||||
same scene, resolution and camera path when comparing modes. Hide Diagnostics for
|
||||
an ordinary Player measurement: opening it enables GPU counter readback, while
|
||||
**Show HZB** adds a separate image copy. The renderer currently captures and
|
||||
waits for every frame even without this overlay. Read
|
||||
[profiling and measurements](profiling.md) and the
|
||||
[P2 acceptance study](https://github.com/emil28092005/Faset_Engine/blob/main/docs/studies/19-p2-gpu-visibility-acceptance.md)
|
||||
for the measured scope and limitations.
|
||||
@@ -0,0 +1,87 @@
|
||||
#include <faset/render/renderer.hpp>
|
||||
|
||||
#include <exception>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
using namespace faset::render;
|
||||
|
||||
namespace {
|
||||
std::shared_ptr<const Mesh> coarse_mesh() {
|
||||
auto mesh = std::make_shared<Mesh>();
|
||||
mesh->vertices = {
|
||||
Vertex{{-1, -1, -1}, {-1, -1, -1}},
|
||||
Vertex{{1, -1, -1}, {1, -1, -1}},
|
||||
Vertex{{0, 1, 0}, {0, 1, 0}},
|
||||
Vertex{{0, -1, 1}, {0, -1, 1}},
|
||||
};
|
||||
mesh->indices = {0, 2, 1, 0, 1, 3, 1, 2, 3, 2, 0, 3};
|
||||
return mesh;
|
||||
}
|
||||
|
||||
VisibilityMode mode_from_argument(std::string_view argument) {
|
||||
if (argument == "direct")
|
||||
return VisibilityMode::Direct;
|
||||
if (argument == "frustum")
|
||||
return VisibilityMode::GpuFrustum;
|
||||
if (argument == "occlusion")
|
||||
return VisibilityMode::GpuOcclusion;
|
||||
throw std::invalid_argument("mode must be direct, frustum, or occlusion");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
if (argc > 3)
|
||||
throw std::invalid_argument("usage: faset_p2_visibility_example "
|
||||
"[direct|frustum|occlusion] [capture.ppm]");
|
||||
RendererConfig config;
|
||||
config.width = 640;
|
||||
config.height = 360;
|
||||
config.headless = true;
|
||||
config.visibility_mode = argc > 1 ? mode_from_argument(argv[1])
|
||||
: VisibilityMode::GpuOcclusion;
|
||||
config.visibility_diagnostics = true;
|
||||
Renderer renderer(config);
|
||||
|
||||
Snapshot frame;
|
||||
frame.view_id = "p2-lod-example-camera";
|
||||
frame.eye = {0, 2, 12};
|
||||
frame.projection = perspective(.9f, float(config.width) / config.height, .1f, 100);
|
||||
frame.view_projection = multiply(frame.projection,
|
||||
look_at(frame.eye, {0, 0, 0}));
|
||||
const auto prepared_coarse = coarse_mesh();
|
||||
for (int i = 0; i < 9; ++i) {
|
||||
DrawItem item;
|
||||
item.mesh = cube_mesh();
|
||||
item.lod_meshes = {prepared_coarse};
|
||||
item.instance_key = "example/cube/" + std::to_string(i);
|
||||
item.model = transform({float(i - 4) * 1.8f, 0, i % 2 ? -5.f : 0.f});
|
||||
item.color = {0.28f + .07f * i, .6f, .8f, 1};
|
||||
item.cast_shadow = false;
|
||||
frame.draws.push_back(std::move(item));
|
||||
}
|
||||
|
||||
// The second and later frames have compatible HZB history for this view.
|
||||
for (int i = 0; i < 3; ++i)
|
||||
renderer.render(frame);
|
||||
const auto& stats = renderer.stats();
|
||||
renderer.capture(argc > 2 ? std::filesystem::path(argv[2])
|
||||
: std::filesystem::path("p2-visibility.ppm"));
|
||||
std::cout << "GPU path: " << (stats.gpu_visibility_active ? "active" : "unavailable")
|
||||
<< ", bins: " << stats.gpu_bins
|
||||
<< ", visible: " << stats.gpu_visible_instances
|
||||
<< ", LOD0: " << stats.lod_counts[0]
|
||||
<< ", LOD1: " << stats.lod_counts[1]
|
||||
<< ", validation errors: " << stats.validation_errors << '\n';
|
||||
return stats.validation_errors ? 1 : 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ nav:
|
||||
- Editor workspace: editor/workspace.md
|
||||
- Scene templates: editor/templates.md
|
||||
- Assets and Blender: editor/assets.md
|
||||
- GPU visibility and mesh LOD: editor/visibility-lod.md
|
||||
- Build, Play, and export: editor/export.md
|
||||
- Profiling and measurements: editor/profiling.md
|
||||
- Optional developer diagnostics: editor/diagnostics.md
|
||||
|
||||
Reference in New Issue
Block a user