feat: add click to select
This commit is contained in:
@@ -11,7 +11,7 @@ struct InstanceData {
|
||||
mat4 transform;
|
||||
VertexBuffer vertexBuffer;
|
||||
uint materialID;
|
||||
uint padding;
|
||||
uint objectID;
|
||||
};
|
||||
|
||||
layout (buffer_reference, scalar) readonly buffer InstanceBuffer {
|
||||
|
||||
@@ -13,9 +13,23 @@ layout (location = 1) in vec2 inUV;
|
||||
layout (location = 2) in vec3 inNormal;
|
||||
layout (location = 3) in vec4 inTangent;
|
||||
layout (location = 4) flat in uint inMaterialID;
|
||||
layout (location = 5) flat in uint inObjectID;
|
||||
|
||||
layout (location = 0) out vec4 outFragColor;
|
||||
|
||||
// Stable per object, the same object keeps its color between frames and runs
|
||||
vec3 selectionColor(uint objectID)
|
||||
{
|
||||
if (objectID == 0u) {
|
||||
return vec3(0.0);
|
||||
}
|
||||
uint hash = objectID * 2654435761u;
|
||||
hash ^= hash >> 15;
|
||||
hash *= 2246822519u;
|
||||
hash ^= hash >> 13;
|
||||
return vec3(hash & 255u, (hash >> 8) & 255u, (hash >> 16) & 255u) / 255.0;
|
||||
}
|
||||
|
||||
vec3 calculateNormal(MaterialData material, uint samplerID)
|
||||
{
|
||||
vec3 n = normalize(inNormal);
|
||||
@@ -127,6 +141,10 @@ void main()
|
||||
fragColor = mix(fragColor, lightHeatmapColor(tileLightCount), 0.5);
|
||||
}
|
||||
|
||||
if (pcs.sceneData.debugSelectionView != 0) {
|
||||
fragColor = selectionColor(inObjectID);
|
||||
}
|
||||
|
||||
#if 0
|
||||
// TANGENT DEBUG
|
||||
if (inTangent == vec4(0.0)) {
|
||||
|
||||
@@ -9,6 +9,7 @@ layout (location = 1) out vec2 outUV;
|
||||
layout (location = 2) out vec3 outNormal;
|
||||
layout (location = 3) out vec4 outTangent;
|
||||
layout (location = 4) flat out uint outMaterialID;
|
||||
layout (location = 5) flat out uint outObjectID;
|
||||
|
||||
// The depth pre-pass reuses this shader, positions have to match bit for bit
|
||||
// so the main pass can depth test against it with LESS_OR_EQUAL
|
||||
@@ -34,4 +35,5 @@ void main()
|
||||
// w is the bitangent sign, the fragment shader builds the TBN from the interpolated vectors
|
||||
outTangent = vec4(mat3(instance.transform) * v.tangent.xyz, v.tangent.w);
|
||||
outMaterialID = instance.materialID;
|
||||
outObjectID = instance.objectID;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ layout (buffer_reference, scalar) readonly buffer SceneDataBuffer {
|
||||
// directional shadows
|
||||
uint shadowMapId;
|
||||
float shadowNormalOffset;
|
||||
uint padding;
|
||||
uint debugSelectionView;
|
||||
mat4 lightViewProj;
|
||||
|
||||
// spot shadows
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#version 460
|
||||
|
||||
// Same interface as mesh.vert, only the object ID is used
|
||||
layout (location = 0) in vec3 inPos;
|
||||
layout (location = 1) in vec2 inUV;
|
||||
layout (location = 2) in vec3 inNormal;
|
||||
layout (location = 3) in vec4 inTangent;
|
||||
layout (location = 4) flat in uint inMaterialID;
|
||||
layout (location = 5) flat in uint inObjectID;
|
||||
|
||||
layout (location = 0) out uint outObjectID;
|
||||
|
||||
void main()
|
||||
{
|
||||
outObjectID = inObjectID;
|
||||
}
|
||||
@@ -123,12 +123,16 @@ private:
|
||||
void drawDockSpace();
|
||||
void drawViewportWindow();
|
||||
void applyViewportResize();
|
||||
void requestSelectionPick();
|
||||
void applySelectionPick();
|
||||
[[nodiscard]] bool canPickInViewport() const { return usesEditorViewport() && (!playing || paused); }
|
||||
|
||||
ViewportRect viewportRect{};
|
||||
glm::ivec2 viewportDesiredSize{0};
|
||||
bool viewportHovered{false};
|
||||
bool viewportFocused{false};
|
||||
bool viewportMouseClaimed{false};
|
||||
bool pickSelectsParent{false};
|
||||
|
||||
void drawPlayControls();
|
||||
void applyPlayModeToggle();
|
||||
|
||||
@@ -22,7 +22,7 @@ struct GPUInstanceData {
|
||||
glm::mat4 transform;
|
||||
VkDeviceAddress vertexBuffer;
|
||||
std::uint32_t materialId;
|
||||
std::uint32_t padding;
|
||||
std::uint32_t objectId;
|
||||
};
|
||||
static_assert(sizeof(GPUInstanceData) == 80);
|
||||
|
||||
|
||||
@@ -39,6 +39,11 @@ public:
|
||||
VkDeviceSize offset = 0,
|
||||
VkDeviceSize size = VK_WHOLE_SIZE) const;
|
||||
|
||||
void invalidateAllocation(
|
||||
const GPUBuffer& buffer,
|
||||
VkDeviceSize offset = 0,
|
||||
VkDeviceSize size = VK_WHOLE_SIZE) const;
|
||||
|
||||
[[nodiscard]] VkDeviceAddress getBufferAddress(const GPUBuffer& buffer) const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -26,6 +26,8 @@ struct MeshDrawCommand {
|
||||
// skinned meshes only
|
||||
const SkinnedMesh* skinnedMesh{nullptr};
|
||||
std::uint32_t jointMatricesStartIndex{0};
|
||||
|
||||
std::uint32_t objectId{0};
|
||||
};
|
||||
|
||||
#endif //MESHDRAWCOMMAND_H
|
||||
|
||||
@@ -24,7 +24,8 @@ public:
|
||||
GfxDevice& gfxDevice,
|
||||
RenderResources& resources,
|
||||
VkFormat drawImageFormat,
|
||||
VkFormat depthImageFormat);
|
||||
VkFormat depthImageFormat,
|
||||
VkFormat selectionImageFormat);
|
||||
|
||||
void drawDepthPrepass(
|
||||
VkCommandBuffer cmd,
|
||||
@@ -32,7 +33,8 @@ public:
|
||||
const RenderResources& resources,
|
||||
const GPUBuffer& sceneDataBuffer,
|
||||
const IndirectDrawList& drawList,
|
||||
const IndirectDrawList::View& view);
|
||||
const IndirectDrawList::View& view,
|
||||
bool writeObjectIds);
|
||||
|
||||
void draw(
|
||||
VkCommandBuffer cmd,
|
||||
@@ -71,6 +73,7 @@ private:
|
||||
VkPipelineLayout m_pipelineLayout{VK_NULL_HANDLE};
|
||||
std::unique_ptr<Pipeline> m_pipeline;
|
||||
std::unique_ptr<Pipeline> m_depthPrepassPipeline;
|
||||
std::unique_ptr<Pipeline> m_selectionPrepassPipeline;
|
||||
|
||||
bool m_renderWireframe{false};
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define RENDERER_H
|
||||
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/vec2.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
|
||||
#include <destrum/Graphics/Camera.h>
|
||||
@@ -21,6 +22,7 @@
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <destrum/Graphics/Pipelines/SkinningPipeline.h>
|
||||
@@ -58,9 +60,9 @@ public:
|
||||
void draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera& camera, const SceneData& sceneData);
|
||||
void cleanup(GfxDevice& gfxDevice);
|
||||
|
||||
void drawMesh(MeshID id, const glm::mat4& transform, MaterialID materialId);
|
||||
void drawMesh(MeshID id, const glm::mat4& transform, MaterialID materialId, std::uint32_t objectId = 0);
|
||||
void drawSkinnedMesh(MeshID id, const glm::mat4& transform, MaterialID materialId, SkinnedMesh* skinnedMesh,
|
||||
std::size_t jointMatricesStartIndex);
|
||||
std::size_t jointMatricesStartIndex, std::uint32_t objectId = 0);
|
||||
void submitLight(const Light& light);
|
||||
const GPUImage& getDrawImage() const;
|
||||
|
||||
@@ -101,6 +103,31 @@ public:
|
||||
return renderLightHeatmap;
|
||||
}
|
||||
|
||||
void setRenderSelectionView(bool selectionView)
|
||||
{
|
||||
renderSelectionView = selectionView;
|
||||
}
|
||||
|
||||
bool getRenderSelectionView() const
|
||||
{
|
||||
return renderSelectionView;
|
||||
}
|
||||
|
||||
void setSelectionPassEnabled(bool enabled)
|
||||
{
|
||||
selectionPassEnabled = enabled;
|
||||
}
|
||||
|
||||
void requestSelectionPick(const glm::ivec2& pixel)
|
||||
{
|
||||
requestedPick = pixel;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<std::uint32_t> takeSelectionPick()
|
||||
{
|
||||
return std::exchange(pickResult, std::nullopt);
|
||||
}
|
||||
|
||||
void setShadowDistance(float distance)
|
||||
{
|
||||
shadowMapPipeline->setShadowDistance(distance);
|
||||
@@ -126,6 +153,7 @@ public:
|
||||
private:
|
||||
void createDrawImage(GfxDevice& gfxDevice, const glm::ivec2& drawImageSize, bool firstCreate);
|
||||
void uploadLights(const Camera& camera);
|
||||
void recordSelectionPick(VkCommandBuffer cmd, std::uint32_t frameIndex);
|
||||
[[nodiscard]] std::uint32_t assignShadowIndex(const Light& light, const Frustum& cameraFrustum);
|
||||
|
||||
RenderResources* resources = nullptr;
|
||||
@@ -142,9 +170,18 @@ private:
|
||||
|
||||
VkFormat drawImageFormat{VK_FORMAT_R16G16B16A16_SFLOAT};
|
||||
VkFormat depthImageFormat{VK_FORMAT_D32_SFLOAT};
|
||||
VkFormat selectionImageFormat{VK_FORMAT_R32_UINT};
|
||||
|
||||
ImageID drawImageId{NULL_IMAGE_ID};
|
||||
ImageID depthImageId{NULL_IMAGE_ID};
|
||||
ImageID selectionImageId{NULL_IMAGE_ID};
|
||||
|
||||
std::array<GPUBuffer, FRAMES_IN_FLIGHT> pickBuffers{};
|
||||
std::array<bool, FRAMES_IN_FLIGHT> pickPending{};
|
||||
std::optional<glm::ivec2> requestedPick;
|
||||
std::optional<std::uint32_t> pickResult;
|
||||
bool selectionPassEnabled{false};
|
||||
bool renderSelectionView{false};
|
||||
|
||||
VkSampleCountFlagBits samples{VK_SAMPLE_COUNT_1_BIT};
|
||||
|
||||
@@ -173,7 +210,7 @@ private:
|
||||
|
||||
std::uint32_t shadowMapId;
|
||||
float shadowNormalOffset;
|
||||
std::uint32_t padding;
|
||||
std::uint32_t debugSelectionView;
|
||||
glm::mat4 lightViewProj;
|
||||
|
||||
std::uint32_t spotShadowAtlasId;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <destrum/FS/AssetFS.h>
|
||||
#include <destrum/Graphics/Resources/FontCache.h>
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
#include <destrum/ObjectModel/Transform.h>
|
||||
#include <destrum/Scene/Scene.h>
|
||||
#include <destrum/Scene/SceneManager.h>
|
||||
#include <destrum/Serialization/SceneSerializer.h>
|
||||
@@ -234,6 +235,7 @@ if (!isRunning) break;
|
||||
{
|
||||
ZoneScopedN("Debug ImGui");
|
||||
drawWindowsMenu();
|
||||
applySelectionPick();
|
||||
drawViewportWindow();
|
||||
|
||||
if (m_DebugWindows.inspector)
|
||||
@@ -380,6 +382,7 @@ if (!isRunning) break;
|
||||
|
||||
{
|
||||
ZoneScopedN("customDraw");
|
||||
renderer.setSelectionPassEnabled(canPickInViewport());
|
||||
customDraw();
|
||||
}
|
||||
|
||||
@@ -549,10 +552,52 @@ void App::drawViewportWindow()
|
||||
|
||||
ImGui::SetCursorScreenPos(origin);
|
||||
drawViewportOverlay();
|
||||
requestSelectionPick();
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void App::requestSelectionPick()
|
||||
{
|
||||
if (!canPickInViewport() || !viewportHovered || viewportMouseClaimed ||
|
||||
!ImGui::IsMouseClicked(ImGuiMouseButton_Left))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const ImVec2 mouse = ImGui::GetMousePos();
|
||||
const glm::vec2 uv = (glm::vec2{mouse.x, mouse.y} - viewportRect.min) / viewportRect.size;
|
||||
if (uv.x < 0.0f || uv.y < 0.0f || uv.x >= 1.0f || uv.y >= 1.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const VkExtent3D extent = renderer.getDrawImage().extent;
|
||||
renderer.requestSelectionPick(glm::ivec2{uv * glm::vec2{static_cast<float>(extent.width), static_cast<float>(extent.height)}});
|
||||
pickSelectsParent = ImGui::GetIO().KeyAlt;
|
||||
}
|
||||
|
||||
void App::applySelectionPick()
|
||||
{
|
||||
const auto picked = renderer.takeSelectionPick();
|
||||
auto& sceneManager = SceneManager::GetInstance();
|
||||
if (!picked || sceneManager.GetActiveScenes().empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Scene& scene = sceneManager.GetCurrentScene();
|
||||
GameObject* object = scene.FindGameObject(*picked);
|
||||
if (object != nullptr && pickSelectsParent)
|
||||
{
|
||||
if (const Transform* parent = object->GetTransform().GetParent(); parent != nullptr && parent->GetOwner() != nullptr)
|
||||
{
|
||||
object = parent->GetOwner();
|
||||
}
|
||||
}
|
||||
scene.SetSelectedObject(object != nullptr ? object->GetId() : InvalidObjectId);
|
||||
}
|
||||
|
||||
void App::applyViewportResize()
|
||||
{
|
||||
if (viewportDesiredSize.x <= 0 || viewportDesiredSize.y <= 0)
|
||||
|
||||
@@ -216,9 +216,11 @@ void MeshRendererComponent::Render(const RenderContext& ctx) {
|
||||
GetTransform().GetWorldMatrix(),
|
||||
materialID,
|
||||
m_skinnedMesh.get(),
|
||||
jointMatricesStartIndex
|
||||
jointMatricesStartIndex,
|
||||
static_cast<std::uint32_t>(GetGameObject()->GetId())
|
||||
);
|
||||
} else {
|
||||
ctx.renderer.drawMesh(meshID, GetTransform().GetWorldMatrix(), materialID);
|
||||
ctx.renderer.drawMesh(meshID, GetTransform().GetWorldMatrix(), materialID,
|
||||
static_cast<std::uint32_t>(GetGameObject()->GetId()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ IndirectDrawList::View IndirectDrawList::addView(
|
||||
? dc.skinnedMesh->skinnedVertexBuffer.address
|
||||
: mesh.vertexBuffer.address,
|
||||
.materialId = dc.materialId,
|
||||
.padding = 0,
|
||||
.objectId = dc.objectId,
|
||||
});
|
||||
++commands.back().instanceCount;
|
||||
++view.instanceCount;
|
||||
|
||||
@@ -113,3 +113,15 @@ void MemoryManager::flushAllocation(
|
||||
|
||||
VK_CHECK(vmaFlushAllocation(allocator, buffer.allocation, offset, size));
|
||||
}
|
||||
|
||||
void MemoryManager::invalidateAllocation(
|
||||
const GPUBuffer& buffer,
|
||||
VkDeviceSize offset,
|
||||
VkDeviceSize size) const
|
||||
{
|
||||
if (!buffer.hostVisible || buffer.allocation == VK_NULL_HANDLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
VK_CHECK(vmaInvalidateAllocation(allocator, buffer.allocation, offset, size));
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ void MeshPipeline::init(
|
||||
GfxDevice& gfxDevice,
|
||||
RenderResources& resources,
|
||||
VkFormat drawImageFormat,
|
||||
VkFormat depthImageFormat)
|
||||
VkFormat depthImageFormat,
|
||||
VkFormat selectionImageFormat)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -31,6 +32,9 @@ void MeshPipeline::init(
|
||||
const auto fragShader =
|
||||
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/mesh.frag");
|
||||
|
||||
const auto selectionShader =
|
||||
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/selection.frag");
|
||||
|
||||
const auto bufferRange = VkPushConstantRange{
|
||||
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
.offset = 0,
|
||||
@@ -82,6 +86,27 @@ void MeshPipeline::init(
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
PipelineConfigInfo pipelineConfig{};
|
||||
Pipeline::DefaultPipelineConfigInfo(pipelineConfig);
|
||||
|
||||
pipelineConfig.name = "Mesh Selection Prepass Pipeline";
|
||||
pipelineConfig.pipelineLayout = m_pipelineLayout;
|
||||
|
||||
pipelineConfig.vertexAttributeDescriptions = {};
|
||||
pipelineConfig.vertexBindingDescriptions = {};
|
||||
|
||||
pipelineConfig.colorAttachments = {selectionImageFormat};
|
||||
pipelineConfig.depthAttachment = depthImageFormat;
|
||||
|
||||
m_selectionPrepassPipeline = std::make_unique<Pipeline>(
|
||||
gfxDevice,
|
||||
vertexShader.string(),
|
||||
selectionShader.string(),
|
||||
pipelineConfig
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
PipelineConfigInfo pipelineConfig{};
|
||||
Pipeline::DefaultPipelineConfigInfo(pipelineConfig);
|
||||
@@ -120,16 +145,18 @@ void MeshPipeline::drawDepthPrepass(
|
||||
const RenderResources& resources,
|
||||
const GPUBuffer& sceneDataBuffer,
|
||||
const IndirectDrawList& drawList,
|
||||
const IndirectDrawList::View& view)
|
||||
const IndirectDrawList::View& view,
|
||||
bool writeObjectIds)
|
||||
{
|
||||
if (!m_depthPrepassPipeline)
|
||||
const auto& pipeline = writeObjectIds ? m_selectionPrepassPipeline : m_depthPrepassPipeline;
|
||||
if (!pipeline)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
drawMeshes(
|
||||
cmd,
|
||||
*m_depthPrepassPipeline,
|
||||
*pipeline,
|
||||
renderExtent,
|
||||
resources,
|
||||
sceneDataBuffer,
|
||||
@@ -228,6 +255,7 @@ void MeshPipeline::cleanup(VkDevice device)
|
||||
{
|
||||
m_pipeline.reset();
|
||||
m_depthPrepassPipeline.reset();
|
||||
m_selectionPrepassPipeline.reset();
|
||||
|
||||
if (m_pipelineLayout != VK_NULL_HANDLE && device != VK_NULL_HANDLE)
|
||||
{
|
||||
|
||||
@@ -33,7 +33,15 @@ void GameRenderer::init(GfxDevice& gfxDevice, RenderResources& _resources, glm::
|
||||
createDrawImage(gfxDevice, drawImageSize, true);
|
||||
|
||||
meshPipeline = std::make_unique<MeshPipeline>();
|
||||
meshPipeline->init(gfxDevice, _resources, drawImageFormat, depthImageFormat);
|
||||
meshPipeline->init(gfxDevice, _resources, drawImageFormat, depthImageFormat, selectionImageFormat);
|
||||
|
||||
for (GPUBuffer& pickBuffer : pickBuffers)
|
||||
{
|
||||
pickBuffer = gfxDevice.createBuffer(
|
||||
sizeof(std::uint32_t),
|
||||
VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
VMA_MEMORY_USAGE_GPU_TO_CPU);
|
||||
}
|
||||
|
||||
skyboxPipeline = std::make_unique<SkyboxPipeline>();
|
||||
skyboxPipeline->init(gfxDevice, _resources, drawImageFormat, depthImageFormat);
|
||||
@@ -65,6 +73,15 @@ void GameRenderer::init(GfxDevice& gfxDevice, RenderResources& _resources, glm::
|
||||
|
||||
void GameRenderer::beginDrawing(GfxDevice& gfxDevice)
|
||||
{
|
||||
// This frame's fence was waited on, so a pick copied by its last use has landed
|
||||
const std::uint32_t frameIndex = gfxDevice.getCurrentFrameIndex();
|
||||
if (pickPending[frameIndex])
|
||||
{
|
||||
pickPending[frameIndex] = false;
|
||||
gfxDevice.getMemoryManager().invalidateAllocation(pickBuffers[frameIndex]);
|
||||
pickResult = *static_cast<const std::uint32_t*>(pickBuffers[frameIndex].info.pMappedData);
|
||||
}
|
||||
|
||||
meshDrawCommands.clear();
|
||||
skinningPipeline->beginDrawing(gfxDevice.getCurrentFrameIndex());
|
||||
lightCullingPipeline->beginDrawing(gfxDevice.getCurrentFrameIndex());
|
||||
@@ -207,7 +224,7 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
||||
.debugLightHeatmap = renderLightHeatmap ? 1u : 0u,
|
||||
.shadowMapId = static_cast<std::uint32_t>(shadowMapPipeline->getShadowMapId()),
|
||||
.shadowNormalOffset = shadowMapPipeline->getNormalOffset(),
|
||||
.padding = 0,
|
||||
.debugSelectionView = renderSelectionView ? 1u : 0u,
|
||||
.lightViewProj = lightViewProj,
|
||||
.spotShadowAtlasId = static_cast<std::uint32_t>(shadowMapPipeline->getSpotShadowAtlasId()),
|
||||
.spotShadowTilesPerRow = ShadowMapPipeline::SPOT_SHADOW_TILES_PER_ROW,
|
||||
@@ -288,12 +305,29 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
||||
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Depth Prepass");
|
||||
vkutil::ScopedCmdLabel label(cmd, "Depth Prepass", vkutil::labelColors::depth);
|
||||
|
||||
const auto& selectionImage = resources->getImage(selectionImageId);
|
||||
if (selectionPassEnabled)
|
||||
{
|
||||
vkutil::transitionImage(
|
||||
cmd,
|
||||
selectionImage.image,
|
||||
selectionImage.layout,
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
selectionImage.setLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
}
|
||||
|
||||
auto depthPrepassInfo = vkutil::createRenderingInfo({
|
||||
.renderExtent = drawImage.getExtent2D(),
|
||||
.colorImageView = selectionPassEnabled ? selectionImage.imageView : VK_NULL_HANDLE,
|
||||
.colorImageClearValue = glm::vec4{0.f},
|
||||
.depthImageView = depthImage.imageView,
|
||||
.depthImageClearValue = 1.f,
|
||||
});
|
||||
depthPrepassInfo.renderingInfo.pDepthAttachment = &depthPrepassInfo.depthAttachment;
|
||||
if (selectionPassEnabled)
|
||||
{
|
||||
depthPrepassInfo.renderingInfo.pColorAttachments = &depthPrepassInfo.colorAttachment;
|
||||
}
|
||||
|
||||
vkCmdBeginRendering(cmd, &depthPrepassInfo.renderingInfo);
|
||||
|
||||
@@ -303,9 +337,16 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
||||
*resources,
|
||||
sceneDataBuffer.getBuffer(),
|
||||
drawList,
|
||||
mainView);
|
||||
mainView,
|
||||
selectionPassEnabled);
|
||||
|
||||
vkCmdEndRendering(cmd);
|
||||
|
||||
if (selectionPassEnabled && requestedPick)
|
||||
{
|
||||
recordSelectionPick(cmd, gfxDevice.getCurrentFrameIndex());
|
||||
}
|
||||
requestedPick.reset();
|
||||
}
|
||||
|
||||
{
|
||||
@@ -404,6 +445,51 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
||||
}
|
||||
}
|
||||
|
||||
void GameRenderer::recordSelectionPick(VkCommandBuffer cmd, std::uint32_t frameIndex)
|
||||
{
|
||||
vkutil::ScopedCmdLabel label(cmd, "Selection Pick", vkutil::labelColors::transfer);
|
||||
|
||||
const auto& image = resources->getImage(selectionImageId);
|
||||
const VkExtent2D extent = image.getExtent2D();
|
||||
const glm::ivec2 pixel = glm::clamp(
|
||||
*requestedPick,
|
||||
glm::ivec2{0},
|
||||
glm::ivec2{static_cast<int>(extent.width) - 1, static_cast<int>(extent.height) - 1});
|
||||
|
||||
vkutil::transitionImage(cmd, image.image, image.layout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
image.setLayout(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
|
||||
const VkBufferImageCopy region{
|
||||
.bufferOffset = 0,
|
||||
.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
|
||||
.imageOffset = {pixel.x, pixel.y, 0},
|
||||
.imageExtent = {1, 1, 1},
|
||||
};
|
||||
vkCmdCopyImageToBuffer(
|
||||
cmd,
|
||||
image.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
pickBuffers[frameIndex].buffer,
|
||||
1,
|
||||
®ion);
|
||||
|
||||
const VkMemoryBarrier2 toHost{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT,
|
||||
.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_2_HOST_BIT,
|
||||
.dstAccessMask = VK_ACCESS_2_HOST_READ_BIT,
|
||||
};
|
||||
const VkDependencyInfo dependency{
|
||||
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
|
||||
.memoryBarrierCount = 1,
|
||||
.pMemoryBarriers = &toHost,
|
||||
};
|
||||
vkCmdPipelineBarrier2(cmd, &dependency);
|
||||
|
||||
pickPending[frameIndex] = true;
|
||||
}
|
||||
|
||||
void GameRenderer::draw2D(
|
||||
VkCommandBuffer cmd,
|
||||
GfxDevice& gfxDevice,
|
||||
@@ -440,6 +526,13 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
|
||||
|
||||
sceneDataBuffer.cleanup(gfxDevice);
|
||||
drawList.cleanup(gfxDevice);
|
||||
for (GPUBuffer& pickBuffer : pickBuffers)
|
||||
{
|
||||
gfxDevice.destroyBuffer(pickBuffer);
|
||||
}
|
||||
pickPending = {};
|
||||
pickResult.reset();
|
||||
requestedPick.reset();
|
||||
|
||||
// if (drawImageId != NULL_IMAGE_ID)
|
||||
// gfxDevice.destroyImage();
|
||||
@@ -449,6 +542,7 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
|
||||
|
||||
drawImageId = NULL_IMAGE_ID;
|
||||
depthImageId = NULL_IMAGE_ID;
|
||||
selectionImageId = NULL_IMAGE_ID;
|
||||
resources = nullptr;
|
||||
meshPipeline.reset();
|
||||
skyboxPipeline.reset();
|
||||
@@ -467,7 +561,7 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
|
||||
GameState::GetInstance().SetRenderer(nullptr);
|
||||
}
|
||||
|
||||
void GameRenderer::drawMesh(MeshID id, const glm::mat4& transform, MaterialID materialId)
|
||||
void GameRenderer::drawMesh(MeshID id, const glm::mat4& transform, MaterialID materialId, std::uint32_t objectId)
|
||||
{
|
||||
const auto& mesh = resources->meshes().getMesh(id);
|
||||
const auto worldBoundingSphere = edge::calculateBoundingSphereWorld(transform, mesh.boundingSphere, false);
|
||||
@@ -478,6 +572,7 @@ void GameRenderer::drawMesh(MeshID id, const glm::mat4& transform, MaterialID ma
|
||||
.transformMatrix = transform,
|
||||
.worldBoundingSphere = worldBoundingSphere,
|
||||
.materialId = materialId,
|
||||
.objectId = objectId,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -485,7 +580,8 @@ void GameRenderer::drawSkinnedMesh(MeshID id,
|
||||
const glm::mat4& transform,
|
||||
MaterialID materialId,
|
||||
SkinnedMesh* skinnedMesh,
|
||||
std::size_t jointMatricesStartIndex)
|
||||
std::size_t jointMatricesStartIndex,
|
||||
std::uint32_t objectId)
|
||||
{
|
||||
const auto& mesh = resources->meshes().getMesh(id);
|
||||
const auto worldBoundingSphere = edge::calculateBoundingSphereWorld(transform, mesh.boundingSphere, true);
|
||||
@@ -499,6 +595,7 @@ void GameRenderer::drawSkinnedMesh(MeshID id,
|
||||
.materialId = materialId,
|
||||
.skinnedMesh = skinnedMesh,
|
||||
.jointMatricesStartIndex = static_cast<std::uint32_t>(jointMatricesStartIndex),
|
||||
.objectId = objectId,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -650,4 +747,16 @@ void GameRenderer::createDrawImage(GfxDevice& gfxDevice,
|
||||
depthImageId = resources->createImage(gfxDevice, createInfo, "depth image");
|
||||
spdlog::info("Created depth image with id {}", depthImageId);
|
||||
}
|
||||
|
||||
{
|
||||
auto createInfo = vkutil::CreateImageInfo{
|
||||
.format = selectionImageFormat,
|
||||
.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||
.extent = drawImageExtent,
|
||||
.samples = noMsaa,
|
||||
};
|
||||
|
||||
resources->destroyImage(selectionImageId);
|
||||
selectionImageId = resources->createImage(gfxDevice, createInfo, "selection image");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ public:
|
||||
private:
|
||||
void setupSkybox();
|
||||
void updateViewMode();
|
||||
void focusSelection();
|
||||
void updateFocus(float dt);
|
||||
[[nodiscard]] CameraComponent* getSceneCamera() const;
|
||||
[[nodiscard]] Camera& getViewCamera();
|
||||
[[nodiscard]] bool isPlayerViewActive() const;
|
||||
@@ -57,6 +59,7 @@ private:
|
||||
|
||||
Camera camera{glm::vec3(0.f, 0.f, -5.f), glm::vec3(0, 1, 0)};
|
||||
std::optional<Camera> editorCamera;
|
||||
std::optional<glm::vec3> focusTarget;
|
||||
|
||||
std::unique_ptr<CubeMap> skyboxCubemap;
|
||||
MainSceneObjects mainScene{};
|
||||
|
||||
@@ -65,6 +65,11 @@ void LightKeeper::customUpdate(float dt)
|
||||
// after SDL events have been consumed.
|
||||
if (!isPlayerViewActive() && getSceneCamera() == nullptr)
|
||||
{
|
||||
if (InputManager::GetInstance().WasKeyPressed(SDL_SCANCODE_F))
|
||||
{
|
||||
focusSelection();
|
||||
}
|
||||
updateFocus(dt);
|
||||
camera.Update(dt);
|
||||
}
|
||||
if (InputManager::GetInstance().WasKeyPressed(SDL_SCANCODE_F11))
|
||||
|
||||
@@ -19,8 +19,12 @@
|
||||
|
||||
#include <components/FirstPersonController.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -162,6 +166,11 @@ void LightKeeper::drawDebugMenuBar()
|
||||
if (ImGui::MenuItem("Forward+ Light Heatmap", nullptr, &lightHeatmap)) {
|
||||
renderer.setRenderLightHeatmap(lightHeatmap);
|
||||
}
|
||||
|
||||
bool selectionView = renderer.getRenderSelectionView();
|
||||
if (ImGui::MenuItem("Selection Buffer", nullptr, &selectionView)) {
|
||||
renderer.setRenderSelectionView(selectionView);
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
@@ -217,6 +226,71 @@ void LightKeeper::onViewportAssetDropped(std::string_view path)
|
||||
}
|
||||
}
|
||||
|
||||
void LightKeeper::focusSelection()
|
||||
{
|
||||
GameObject* selected = SceneManager::GetInstance().GetCurrentScene().GetSelectedObject();
|
||||
if (selected == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
glm::vec3 boundsMin{std::numeric_limits<float>::max()};
|
||||
glm::vec3 boundsMax{std::numeric_limits<float>::lowest()};
|
||||
std::vector<GameObject*> pending{selected};
|
||||
while (!pending.empty()) {
|
||||
GameObject* object = pending.back();
|
||||
pending.pop_back();
|
||||
for (const Transform* child : object->GetTransform().GetChildren()) {
|
||||
if (child != nullptr && child->GetOwner() != nullptr) {
|
||||
pending.push_back(child->GetOwner());
|
||||
}
|
||||
}
|
||||
|
||||
const auto* meshRenderer = object->GetComponent<MeshRendererComponent>();
|
||||
if (meshRenderer == nullptr || meshRenderer->GetMeshID() == NULL_MESH_ID) {
|
||||
continue;
|
||||
}
|
||||
const GPUMesh& mesh = resources.meshes().getMesh(meshRenderer->GetMeshID());
|
||||
const glm::mat4 world = object->GetTransform().GetWorldMatrix();
|
||||
for (int corner = 0; corner < 8; ++corner) {
|
||||
const glm::vec3 local{
|
||||
corner & 1 ? mesh.maxPos.x : mesh.minPos.x,
|
||||
corner & 2 ? mesh.maxPos.y : mesh.minPos.y,
|
||||
corner & 4 ? mesh.maxPos.z : mesh.minPos.z,
|
||||
};
|
||||
const glm::vec3 point{world * glm::vec4{local, 1.0f}};
|
||||
boundsMin = glm::min(boundsMin, point);
|
||||
boundsMax = glm::max(boundsMax, point);
|
||||
}
|
||||
}
|
||||
|
||||
const bool hasBounds = boundsMin.x <= boundsMax.x;
|
||||
const glm::vec3 center = hasBounds ? (boundsMin + boundsMax) * 0.5f : selected->GetTransform().GetWorldPosition();
|
||||
const float radius = hasBounds ? std::max(glm::length(boundsMax - boundsMin) * 0.5f, 0.1f) : 1.0f;
|
||||
|
||||
// Fit the bounding sphere in the narrower of the two field of view angles
|
||||
const float halfFovY = glm::radians(camera.GetFOVY()) * 0.5f;
|
||||
const float halfFovX = std::atan(std::tan(halfFovY) * camera.GetAspectRatio());
|
||||
const float distance = radius / std::sin(std::min(halfFovX, halfFovY)) * 1.1f;
|
||||
focusTarget = center - camera.GetForward() * distance;
|
||||
}
|
||||
|
||||
void LightKeeper::updateFocus(float dt)
|
||||
{
|
||||
if (!focusTarget) {
|
||||
return;
|
||||
}
|
||||
if (InputManager::GetInstance().IsMouseDown(SDL_BUTTON_RIGHT)) {
|
||||
focusTarget.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
camera.m_position = glm::mix(camera.m_position, *focusTarget, 1.0f - std::exp(-12.0f * dt));
|
||||
if (glm::distance(camera.m_position, *focusTarget) < 0.01f) {
|
||||
camera.m_position = *focusTarget;
|
||||
focusTarget.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void LightKeeper::drawViewportOverlay()
|
||||
{
|
||||
if (!showTransformGizmo || isPlayerViewActive()) {
|
||||
|
||||
Reference in New Issue
Block a user