ADD: BVH Selection triangle
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
#include <render/vk_materials.h>
|
#include <render/vk_materials.h>
|
||||||
#include <render/primitives.h>
|
#include <render/primitives.h>
|
||||||
#include <scene/tangent_space.h>
|
#include <scene/tangent_space.h>
|
||||||
|
#include <scene/mesh_bvh.h>
|
||||||
#include <stb_image.h>
|
#include <stb_image.h>
|
||||||
#include "asset_locator.h"
|
#include "asset_locator.h"
|
||||||
#include <core/texture_cache.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);
|
surf.bounds = compute_bounds(vertices);
|
||||||
mesh->surfaces.push_back(surf);
|
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);
|
_meshCache.emplace(name, mesh);
|
||||||
return mesh;
|
return mesh;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -438,53 +438,6 @@ void VulkanEngine::cleanup()
|
|||||||
|
|
||||||
void VulkanEngine::draw()
|
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();
|
_sceneManager->update_scene();
|
||||||
|
|
||||||
// Per-frame hover raycast based on last mouse position.
|
// Per-frame hover raycast based on last mouse position.
|
||||||
@@ -808,7 +761,16 @@ void VulkanEngine::run()
|
|||||||
|
|
||||||
if (treatAsClick)
|
if (treatAsClick)
|
||||||
{
|
{
|
||||||
// Raycast click selection
|
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)
|
if (_sceneManager)
|
||||||
{
|
{
|
||||||
RenderObject hitObject{};
|
RenderObject hitObject{};
|
||||||
@@ -823,18 +785,14 @@ void VulkanEngine::run()
|
|||||||
_lastPick.indexCount = hitObject.indexCount;
|
_lastPick.indexCount = hitObject.indexCount;
|
||||||
_lastPick.surfaceIndex = hitObject.surfaceIndex;
|
_lastPick.surfaceIndex = hitObject.surfaceIndex;
|
||||||
_lastPick.valid = true;
|
_lastPick.valid = true;
|
||||||
|
_lastPickObjectID = hitObject.objectID;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_lastPick.valid = false;
|
_lastPick.valid = false;
|
||||||
|
_lastPickObjectID = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optionally queue an ID-buffer pick at this position
|
|
||||||
if (_useIdBufferPicking)
|
|
||||||
{
|
|
||||||
_pendingPick.active = true;
|
|
||||||
_pendingPick.windowPos = releasePos;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -881,6 +839,58 @@ void VulkanEngine::run()
|
|||||||
_swapchainManager->resize_swapchain(_window);
|
_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 new frame
|
||||||
ImGui_ImplVulkan_NewFrame();
|
ImGui_ImplVulkan_NewFrame();
|
||||||
@@ -918,7 +928,7 @@ void VulkanEngine::init_frame_resources()
|
|||||||
_frames[i].init(_deviceManager.get(), frame_sizes);
|
_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(
|
_pickReadbackBuffer = _resourceManager->create_buffer(
|
||||||
sizeof(uint32_t),
|
sizeof(uint32_t),
|
||||||
VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ public:
|
|||||||
uint32_t surfaceIndex = 0;
|
uint32_t surfaceIndex = 0;
|
||||||
bool valid = false;
|
bool valid = false;
|
||||||
} _lastPick;
|
} _lastPick;
|
||||||
|
uint32_t _lastPickObjectID = 0;
|
||||||
|
|
||||||
struct PickRequest
|
struct PickRequest
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -540,6 +540,8 @@ static void ui_scene(VulkanEngine *eng)
|
|||||||
ImGui::Text("Opaque draws: %zu", dc.OpaqueSurfaces.size());
|
ImGui::Text("Opaque draws: %zu", dc.OpaqueSurfaces.size());
|
||||||
ImGui::Text("Transp draws: %zu", dc.TransparentSurfaces.size());
|
ImGui::Text("Transp draws: %zu", dc.TransparentSurfaces.size());
|
||||||
ImGui::Checkbox("Use ID-buffer picking", &eng->_useIdBufferPicking);
|
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::Checkbox("Debug draw mesh BVH (last pick)", &eng->_debugDrawBVH);
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
@@ -552,6 +554,9 @@ static void ui_scene(VulkanEngine *eng)
|
|||||||
sceneName = eng->_lastPick.scene->debugName.c_str();
|
sceneName = eng->_lastPick.scene->debugName.c_str();
|
||||||
}
|
}
|
||||||
ImGui::Text("Last pick scene: %s", sceneName);
|
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("Last pick mesh: %s (surface %u)", meshName, eng->_lastPick.surfaceIndex);
|
||||||
ImGui::Text("World pos: (%.3f, %.3f, %.3f)",
|
ImGui::Text("World pos: (%.3f, %.3f, %.3f)",
|
||||||
eng->_lastPick.worldPos.x,
|
eng->_lastPick.worldPos.x,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public:
|
|||||||
bool rmbDown { false };
|
bool rmbDown { false };
|
||||||
|
|
||||||
// Field of view in degrees for projection
|
// Field of view in degrees for projection
|
||||||
float fovDegrees { 70.f };
|
float fovDegrees { 50.f };
|
||||||
|
|
||||||
glm::mat4 getViewMatrix();
|
glm::mat4 getViewMatrix();
|
||||||
glm::mat4 getRotationMatrix();
|
glm::mat4 getRotationMatrix();
|
||||||
|
|||||||
@@ -58,10 +58,14 @@ std::unique_ptr<MeshBVH> build_mesh_bvh(const MeshAsset &mesh,
|
|||||||
const glm::vec3 &p1 = vertices[i1].position;
|
const glm::vec3 &p1 = vertices[i1].position;
|
||||||
const glm::vec3 &p2 = vertices[i2].position;
|
const glm::vec3 &p2 = vertices[i2].position;
|
||||||
|
|
||||||
|
// BVH2 now expects triangle primitives with explicit vertices.
|
||||||
|
// Store the triangle in mesh-local space and let the library
|
||||||
|
// compute/update the AABB used for hierarchy construction.
|
||||||
PrimitiveF prim{};
|
PrimitiveF prim{};
|
||||||
prim.bounds.expand(Vec3<float>(p0.x, p0.y, p0.z));
|
prim.v0 = Vec3<float>(p0.x, p0.y, p0.z);
|
||||||
prim.bounds.expand(Vec3<float>(p1.x, p1.y, p1.z));
|
prim.v1 = Vec3<float>(p1.x, p1.y, p1.z);
|
||||||
prim.bounds.expand(Vec3<float>(p2.x, p2.y, p2.z));
|
prim.v2 = Vec3<float>(p2.x, p2.y, p2.z);
|
||||||
|
prim.updateBounds();
|
||||||
result->primitives.push_back(prim);
|
result->primitives.push_back(prim);
|
||||||
|
|
||||||
MeshBVHPrimitiveRef ref{};
|
MeshBVHPrimitiveRef ref{};
|
||||||
@@ -144,4 +148,3 @@ bool intersect_ray_mesh_bvh(const MeshBVH &bvh,
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ enum class BoundsType : uint8_t
|
|||||||
Box, // oriented box using origin/extents (default)
|
Box, // oriented box using origin/extents (default)
|
||||||
Sphere, // sphere using origin + sphereRadius
|
Sphere, // sphere using origin + sphereRadius
|
||||||
Capsule, // capsule aligned with local Y (derived from extents)
|
Capsule, // capsule aligned with local Y (derived from extents)
|
||||||
Mesh // full mesh (BVH / ray query); currently falls back to Box
|
Mesh // full mesh (BVH / ray query using mesh BVH when available)
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Bounds
|
struct Bounds
|
||||||
|
|||||||
@@ -149,7 +149,9 @@ void SceneManager::update_scene()
|
|||||||
return m;
|
return m;
|
||||||
};
|
};
|
||||||
|
|
||||||
const float fov = glm::radians(70.f);
|
// Keep projection FOV in sync with the camera so that CPU ray picking
|
||||||
|
// matches what is rendered on-screen.
|
||||||
|
const float fov = glm::radians(mainCamera.fovDegrees);
|
||||||
const float aspect = (float) _context->getSwapchain()->windowExtent().width /
|
const float aspect = (float) _context->getSwapchain()->windowExtent().width /
|
||||||
(float) _context->getSwapchain()->windowExtent().height;
|
(float) _context->getSwapchain()->windowExtent().height;
|
||||||
const float nearPlane = 0.1f;
|
const float nearPlane = 0.1f;
|
||||||
|
|||||||
@@ -89,15 +89,21 @@ namespace
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
float tHit = (tMin >= 0.0f) ? tMin : tMax;
|
// Choose the closest intersection in front of the ray origin.
|
||||||
glm::vec3 localHit = localOrigin + tHit * localDir;
|
// If the ray starts inside the box (tMin <= 0), use the exit point tMax.
|
||||||
glm::vec3 worldHit = glm::vec3(worldTransform * glm::vec4(localHit, 1.0f));
|
float tHit = tMin;
|
||||||
|
if (tHit <= 0.0f)
|
||||||
if (glm::dot(worldHit - rayOrigin, rayDir) <= 0.0f)
|
{
|
||||||
|
tHit = tMax;
|
||||||
|
}
|
||||||
|
if (tHit <= 0.0f)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
glm::vec3 localHit = localOrigin + tHit * localDir;
|
||||||
|
glm::vec3 worldHit = glm::vec3(worldTransform * glm::vec4(localHit, 1.0f));
|
||||||
|
|
||||||
outWorldHit = worldHit;
|
outWorldHit = worldHit;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
2
third_party/BVH
vendored
2
third_party/BVH
vendored
Submodule third_party/BVH updated: 2a9838e7ef...aa4fb9949b
Reference in New Issue
Block a user