ADD: BVH Selection triangle

This commit is contained in:
2025-11-19 23:33:05 +09:00
parent ac35de6104
commit 0fe7c0f975
10 changed files with 116 additions and 84 deletions

View File

@@ -8,6 +8,7 @@
#include <render/vk_materials.h>
#include <render/primitives.h>
#include <scene/tangent_space.h>
#include <scene/mesh_bvh.h>
#include <stb_image.h>
#include "asset_locator.h"
#include <core/texture_cache.h>
@@ -531,6 +532,10 @@ std::shared_ptr<MeshAsset> AssetManager::createMesh(const std::string &name,
surf.bounds = compute_bounds(vertices);
mesh->surfaces.push_back(surf);
// Build CPU-side BVH for precise ray picking over this mesh.
// This uses the same mesh-local vertex/index data as the GPU upload.
mesh->bvh = build_mesh_bvh(*mesh, vertices, indices);
_meshCache.emplace(name, mesh);
return mesh;
}

View File

@@ -438,53 +438,6 @@ void VulkanEngine::cleanup()
void VulkanEngine::draw()
{
//> frame_clear
//wait until the gpu has finished rendering the last frame. Timeout of 1 second
VK_CHECK(vkWaitForFences(_deviceManager->device(), 1, &get_current_frame()._renderFence, true, 1000000000));
// If we scheduled an ID-buffer readback in the previous frame, resolve it now.
if (_pickResultPending && _pickReadbackBuffer.buffer && _sceneManager)
{
vmaInvalidateAllocation(_deviceManager->allocator(), _pickReadbackBuffer.allocation, 0, sizeof(uint32_t));
uint32_t pickedID = 0;
if (_pickReadbackBuffer.info.pMappedData)
{
pickedID = *reinterpret_cast<const uint32_t *>(_pickReadbackBuffer.info.pMappedData);
}
if (pickedID == 0)
{
// Do not override existing raycast pick when ID buffer reports "no object".
}
else
{
RenderObject picked{};
if (_sceneManager->resolveObjectID(pickedID, picked))
{
// Fallback hit position: object origin in world space (can refine later)
glm::vec3 fallbackPos = glm::vec3(picked.transform[3]);
_lastPick.mesh = picked.sourceMesh;
_lastPick.scene = picked.sourceScene;
_lastPick.worldPos = fallbackPos;
_lastPick.worldTransform = picked.transform;
_lastPick.firstIndex = picked.firstIndex;
_lastPick.indexCount = picked.indexCount;
_lastPick.surfaceIndex = picked.surfaceIndex;
_lastPick.valid = true;
}
}
_pickResultPending = false;
}
get_current_frame()._deletionQueue.flush();
// Resolve last frame's pass timings before we clear and rebuild the graph
if (_renderGraph)
{
_renderGraph->resolve_timings();
}
get_current_frame()._frameDescriptors.clear_pools(_deviceManager->device());
//< frame_clear
_sceneManager->update_scene();
// Per-frame hover raycast based on last mouse position.
@@ -808,34 +761,39 @@ void VulkanEngine::run()
if (treatAsClick)
{
// Raycast click selection
if (_sceneManager)
{
RenderObject hitObject{};
glm::vec3 hitPos{};
if (_sceneManager->pick(releasePos, hitObject, hitPos))
{
_lastPick.mesh = hitObject.sourceMesh;
_lastPick.scene = hitObject.sourceScene;
_lastPick.worldPos = hitPos;
_lastPick.worldTransform = hitObject.transform;
_lastPick.firstIndex = hitObject.firstIndex;
_lastPick.indexCount = hitObject.indexCount;
_lastPick.surfaceIndex = hitObject.surfaceIndex;
_lastPick.valid = true;
}
else
{
_lastPick.valid = false;
}
}
// Optionally queue an ID-buffer pick at this position
if (_useIdBufferPicking)
{
// Asynchronous ID-buffer clicking: queue a pick request for this position.
// The result will be resolved at the start of a future frame from the ID buffer.
_pendingPick.active = true;
_pendingPick.windowPos = releasePos;
}
else
{
// Raycast click selection (CPU side) when ID-buffer picking is disabled.
if (_sceneManager)
{
RenderObject hitObject{};
glm::vec3 hitPos{};
if (_sceneManager->pick(releasePos, hitObject, hitPos))
{
_lastPick.mesh = hitObject.sourceMesh;
_lastPick.scene = hitObject.sourceScene;
_lastPick.worldPos = hitPos;
_lastPick.worldTransform = hitObject.transform;
_lastPick.firstIndex = hitObject.firstIndex;
_lastPick.indexCount = hitObject.indexCount;
_lastPick.surfaceIndex = hitObject.surfaceIndex;
_lastPick.valid = true;
_lastPickObjectID = hitObject.objectID;
}
else
{
_lastPick.valid = false;
_lastPickObjectID = 0;
}
}
}
}
else
{
@@ -881,6 +839,58 @@ void VulkanEngine::run()
_swapchainManager->resize_swapchain(_window);
}
// Begin frame: wait for the GPU, resolve pending ID-buffer picks,
// and clear per-frame resources before building UI and recording commands.
VK_CHECK(vkWaitForFences(_deviceManager->device(), 1, &get_current_frame()._renderFence, true, 1000000000));
if (_pickResultPending && _pickReadbackBuffer.buffer && _sceneManager)
{
vmaInvalidateAllocation(_deviceManager->allocator(), _pickReadbackBuffer.allocation, 0, sizeof(uint32_t));
uint32_t pickedID = 0;
if (_pickReadbackBuffer.info.pMappedData)
{
pickedID = *reinterpret_cast<const uint32_t *>(_pickReadbackBuffer.info.pMappedData);
}
if (pickedID == 0)
{
// No object under cursor in ID buffer: clear last pick.
_lastPick.valid = false;
_lastPickObjectID = 0;
}
else
{
_lastPickObjectID = pickedID;
RenderObject picked{};
if (_sceneManager->resolveObjectID(pickedID, picked))
{
// Fallback hit position: object origin in world space (can refine later)
glm::vec3 fallbackPos = glm::vec3(picked.transform[3]);
_lastPick.mesh = picked.sourceMesh;
_lastPick.scene = picked.sourceScene;
_lastPick.worldPos = fallbackPos;
_lastPick.worldTransform = picked.transform;
_lastPick.firstIndex = picked.firstIndex;
_lastPick.indexCount = picked.indexCount;
_lastPick.surfaceIndex = picked.surfaceIndex;
_lastPick.valid = true;
}
else
{
_lastPick.valid = false;
_lastPickObjectID = 0;
}
}
_pickResultPending = false;
}
get_current_frame()._deletionQueue.flush();
if (_renderGraph)
{
_renderGraph->resolve_timings();
}
get_current_frame()._frameDescriptors.clear_pools(_deviceManager->device());
// imgui new frame
ImGui_ImplVulkan_NewFrame();
@@ -918,7 +928,7 @@ void VulkanEngine::init_frame_resources()
_frames[i].init(_deviceManager.get(), frame_sizes);
}
// Allocate a small readback buffer for ID-buffer picking (single uint32 pixel)
// Allocate a small readback buffer for ID-buffer picking (single uint32 pixel).
_pickReadbackBuffer = _resourceManager->create_buffer(
sizeof(uint32_t),
VK_BUFFER_USAGE_TRANSFER_DST_BIT,

View File

@@ -127,6 +127,7 @@ public:
uint32_t surfaceIndex = 0;
bool valid = false;
} _lastPick;
uint32_t _lastPickObjectID = 0;
struct PickRequest
{

View File

@@ -540,6 +540,8 @@ static void ui_scene(VulkanEngine *eng)
ImGui::Text("Opaque draws: %zu", dc.OpaqueSurfaces.size());
ImGui::Text("Transp draws: %zu", dc.TransparentSurfaces.size());
ImGui::Checkbox("Use ID-buffer picking", &eng->_useIdBufferPicking);
ImGui::Text("Picking mode: %s",
eng->_useIdBufferPicking ? "ID buffer (async, 1-frame latency)" : "CPU raycast");
ImGui::Checkbox("Debug draw mesh BVH (last pick)", &eng->_debugDrawBVH);
ImGui::Separator();
@@ -552,6 +554,9 @@ static void ui_scene(VulkanEngine *eng)
sceneName = eng->_lastPick.scene->debugName.c_str();
}
ImGui::Text("Last pick scene: %s", sceneName);
ImGui::Text("Last pick source: %s",
eng->_useIdBufferPicking ? "ID buffer" : "CPU raycast");
ImGui::Text("Last pick object ID: %u", eng->_lastPickObjectID);
ImGui::Text("Last pick mesh: %s (surface %u)", meshName, eng->_lastPick.surfaceIndex);
ImGui::Text("World pos: (%.3f, %.3f, %.3f)",
eng->_lastPick.worldPos.x,