From f18160276e78f860f64c45111c874e3351b44ffb Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Wed, 3 Dec 2025 23:24:18 +0300 Subject: [PATCH 1/9] New example, copy of 61_UI, updated a lot, visualizer, still not "solid angle", rest should be shader work --- 72_SolidAngleVisualizer/CMakeLists.txt | 20 + 72_SolidAngleVisualizer/README.md | 0 .../hlsl/SolidAngleVis.frag.hlsl | 175 +++ .../app_resources/hlsl/common.hlsl | 14 + 72_SolidAngleVisualizer/config.json.template | 28 + 72_SolidAngleVisualizer/include/common.hpp | 20 + 72_SolidAngleVisualizer/include/transform.hpp | 172 +++ 72_SolidAngleVisualizer/main.cpp | 1105 +++++++++++++++++ 72_SolidAngleVisualizer/pipeline.groovy | 50 + 72_SolidAngleVisualizer/src/transform.cpp | 0 CMakeLists.txt | 1 + 11 files changed, 1585 insertions(+) create mode 100644 72_SolidAngleVisualizer/CMakeLists.txt create mode 100644 72_SolidAngleVisualizer/README.md create mode 100644 72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl create mode 100644 72_SolidAngleVisualizer/app_resources/hlsl/common.hlsl create mode 100644 72_SolidAngleVisualizer/config.json.template create mode 100644 72_SolidAngleVisualizer/include/common.hpp create mode 100644 72_SolidAngleVisualizer/include/transform.hpp create mode 100644 72_SolidAngleVisualizer/main.cpp create mode 100644 72_SolidAngleVisualizer/pipeline.groovy create mode 100644 72_SolidAngleVisualizer/src/transform.cpp diff --git a/72_SolidAngleVisualizer/CMakeLists.txt b/72_SolidAngleVisualizer/CMakeLists.txt new file mode 100644 index 000000000..5d0021f61 --- /dev/null +++ b/72_SolidAngleVisualizer/CMakeLists.txt @@ -0,0 +1,20 @@ +if(NBL_BUILD_IMGUI) + set(NBL_EXTRA_SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/src/transform.cpp" + ) + + set(NBL_INCLUDE_SERACH_DIRECTORIES + "${CMAKE_CURRENT_SOURCE_DIR}/include" + ) + + list(APPEND NBL_LIBRARIES + imtestengine + imguizmo + "${NBL_EXT_IMGUI_UI_LIB}" + ) + + # TODO; Arek I removed `NBL_EXECUTABLE_PROJECT_CREATION_PCH_TARGET` from the last parameter here, doesn't this macro have 4 arguments anyway !? + nbl_create_executable_project("${NBL_EXTRA_SOURCES}" "" "${NBL_INCLUDE_SERACH_DIRECTORIES}" "${NBL_LIBRARIES}") + # TODO: Arek temporarily disabled cause I haven't figured out how to make this target yet + # LINK_BUILTIN_RESOURCES_TO_TARGET(${EXECUTABLE_NAME} nblExamplesGeometrySpirvBRD) +endif() \ No newline at end of file diff --git a/72_SolidAngleVisualizer/README.md b/72_SolidAngleVisualizer/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl b/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl new file mode 100644 index 000000000..d783a5b37 --- /dev/null +++ b/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl @@ -0,0 +1,175 @@ +#pragma wave shader_stage(fragment) + +#include "common.hlsl" + +#include + +using namespace nbl::hlsl; +using namespace ext::FullScreenTriangle; + +[[vk::push_constant]] struct PushConstants pc; + +static const float CIRCLE_RADIUS = 0.45f; + +// --- Geometry Utils --- + +// Adjacency of edges to faces +static const int2 edgeToFaces[12] = { + {4,2}, {3,4}, {2,5}, {5,3}, + {2,0}, {0,3}, {1,2}, {3,1}, + {0,4}, {5,0}, {4,1}, {1,5} +}; + +static const float3 localNormals[6] = { + float3(0, 0, -1), // Face 0 (Z-) + float3(0, 0, 1), // Face 1 (Z+) + float3(-1, 0, 0), // Face 2 (X-) + float3(1, 0, 0), // Face 3 (X+) + float3(0, -1, 0), // Face 4 (Y-) + float3(0, 1, 0) // Face 5 (Y+) +}; + +static float3 corners[8]; +static float3 faceCenters[6] = { float3(0,0,0), float3(0,0,0), float3(0,0,0), + float3(0,0,0), float3(0,0,0), float3(0,0,0) }; +static float2 projCorners[8]; + + +// Converts UV into centered, aspect-corrected NDC circle space +float2 toCircleSpace(float2 uv) +{ + float aspect = pc.viewport.z / pc.viewport.w; + float2 centered = uv - 0.5f; + centered.x *= aspect; + return centered; +} + +// Distance to a 2D line segment +float sdSegment(float2 p, float2 a, float2 b) +{ + float2 pa = p - a; + float2 ba = b - a; + float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0f, 1.0f); + return length(pa - ba * h); +} + +// TODO: Hemispherical Projection (Solid Angle / Orthographic/Lambertian Projection) +float2 project(float3 p) +{ + return normalize(p).xy; +} + +void computeCubeGeo() +{ + for (int i = 0; i < 8; i++) + { + float3 localPos = float3(i % 2, (i / 2) % 2, (i / 4) % 2) * 2.0f - 1.0f; + float3 worldPos = mul(pc.modelMatrix, float4(localPos, 1.0f)).xyz; + + corners[i] = worldPos; + + faceCenters[i/4] += worldPos / 4.0f; + faceCenters[2+i%2] += worldPos / 4.0f; + faceCenters[4+(i/2)%2] += worldPos / 4.0f; + + float3 viewPos = worldPos; + projCorners[i] = project(viewPos); + } +} + +int getVisibilityCount(int2 faces, float3 cameraPos) +{ + float3x3 rotMatrix = (float3x3)pc.modelMatrix; + float3 n_world_f1 = mul(rotMatrix, localNormals[faces.x]); + float3 n_world_f2 = mul(rotMatrix, localNormals[faces.y]); + + float3 viewVec_f1 = faceCenters[faces.x] - cameraPos; + float3 viewVec_f2 = faceCenters[faces.y] - cameraPos; + + // Face is visible if its outward normal points towards the origin (camera). + bool visible1 = dot(n_world_f1, viewVec_f1) < 0.0f; + bool visible2 = dot(n_world_f2, viewVec_f2) < 0.0f; + + // Determine Line Style: + bool isSilhouette = visible1 != visible2; // One face visible, the other hidden + bool isInner = visible1 && visible2; // Both faces visible + + int visibilityCount = 0; + if (isSilhouette) + { + visibilityCount = 1; + } + else if (isInner) + { + visibilityCount = 2; + } + + return visibilityCount; +} + +void drawLine(float2 p, int a, int b, int visibilityCount, inout float4 color, float aaWidth) +{ + if (visibilityCount > 0) + { + float3 A = corners[a]; + float3 B = corners[b]; + + float avgDepth = (length(A) + length(B)) * 0.5f; + float referenceDepth = 3.0f; + float depthScale = referenceDepth / avgDepth; + + float baseWidth = (visibilityCount == 1) ? 0.005f : 0.002f; + float intensity = (visibilityCount == 1) ? 1.0f : 0.5f; + float4 edgeColor = (visibilityCount == 1) ? float4(0.0f, 0.5f, 1.0f, 1.0f) : float4(1.0f, 0.0f, 0.0f, 1.0f); // Blue vs Red + + float width = min(baseWidth * depthScale, 0.03f); + + float dist = sdSegment(p, projCorners[a], projCorners[b]); + + float alpha = 1.0f - smoothstep(width - aaWidth, width + aaWidth, dist); + + color += edgeColor * alpha * intensity; + } +} + +void drawRing(float2 p, inout float4 color, float aaWidth) +{ + float positionLength = length(p); + + // Mask to cut off drawing outside the circle + // float circleMask = 1.0f - smoothstep(CIRCLE_RADIUS, CIRCLE_RADIUS + aaWidth, positionLength); + // color *= circleMask; + + // Add a white background circle ring + float ringWidth = 0.005f; + float ringDistance = abs(positionLength - CIRCLE_RADIUS); + float ringAlpha = 1.0f - smoothstep(ringWidth - aaWidth, ringWidth + aaWidth, ringDistance); + + // Ring color is now white + color = max(color, float4(1.0, 1.0, 1.0, 1.0) * ringAlpha); +} + +[[vk::location(0)]] float32_t4 main(SVertexAttributes vx) : SV_Target0 +{ + float3 cameraPos = float3(0, 0, 0); // Camera at origin + float2 p = toCircleSpace(vx.uv); + float4 color = float4(0, 0, 0, 0); + + computeCubeGeo(); + + float aaWidth = max(fwidth(p.x), fwidth(p.y)); + + for (int j = 0; j < 12; j++) + { + int a = j % 4 * (j < 4 ? 1 : 2) - (j / 4 == 1 ? j % 2 : 0); + int b = a + (4 >> (j / 4)); + + int2 faces = edgeToFaces[j]; + int visibilityCount = getVisibilityCount(faces, cameraPos); + drawLine(p, a, b, visibilityCount, color, aaWidth); + } + + drawRing(p, color, aaWidth); + + return color; +} \ No newline at end of file diff --git a/72_SolidAngleVisualizer/app_resources/hlsl/common.hlsl b/72_SolidAngleVisualizer/app_resources/hlsl/common.hlsl new file mode 100644 index 000000000..80368d08f --- /dev/null +++ b/72_SolidAngleVisualizer/app_resources/hlsl/common.hlsl @@ -0,0 +1,14 @@ +#ifndef _SOLID_ANGLE_VIS_COMMON_HLSL_ +#define _SOLID_ANGLE_VIS_COMMON_HLSL_ +#include "nbl/builtin/hlsl/cpp_compat.hlsl" + + + +struct PushConstants +{ + nbl::hlsl::float32_t3x4 modelMatrix; + nbl::hlsl::float32_t4 viewport; +}; + + +#endif // _SOLID_ANGLE_VIS_COMMON_HLSL_ diff --git a/72_SolidAngleVisualizer/config.json.template b/72_SolidAngleVisualizer/config.json.template new file mode 100644 index 000000000..f961745c1 --- /dev/null +++ b/72_SolidAngleVisualizer/config.json.template @@ -0,0 +1,28 @@ +{ + "enableParallelBuild": true, + "threadsPerBuildProcess" : 2, + "isExecuted": false, + "scriptPath": "", + "cmake": { + "configurations": [ "Release", "Debug", "RelWithDebInfo" ], + "buildModes": [], + "requiredOptions": [] + }, + "profiles": [ + { + "backend": "vulkan", + "platform": "windows", + "buildModes": [], + "runConfiguration": "Release", + "gpuArchitectures": [] + } + ], + "dependencies": [], + "data": [ + { + "dependencies": [], + "command": [""], + "outputs": [] + } + ] +} \ No newline at end of file diff --git a/72_SolidAngleVisualizer/include/common.hpp b/72_SolidAngleVisualizer/include/common.hpp new file mode 100644 index 000000000..2e8e985dd --- /dev/null +++ b/72_SolidAngleVisualizer/include/common.hpp @@ -0,0 +1,20 @@ +#ifndef _NBL_THIS_EXAMPLE_COMMON_H_INCLUDED_ +#define _NBL_THIS_EXAMPLE_COMMON_H_INCLUDED_ + + +#include "nbl/examples/examples.hpp" + +// the example's headers +#include "transform.hpp" +#include "nbl/builtin/hlsl/matrix_utils/transformation_matrix_utils.hlsl" + +using namespace nbl; +using namespace nbl::core; +using namespace nbl::hlsl; +using namespace nbl::system; +using namespace nbl::asset; +using namespace nbl::ui; +using namespace nbl::video; +using namespace nbl::examples; + +#endif // _NBL_THIS_EXAMPLE_COMMON_H_INCLUDED_ \ No newline at end of file diff --git a/72_SolidAngleVisualizer/include/transform.hpp b/72_SolidAngleVisualizer/include/transform.hpp new file mode 100644 index 000000000..002a9d215 --- /dev/null +++ b/72_SolidAngleVisualizer/include/transform.hpp @@ -0,0 +1,172 @@ +#ifndef _NBL_THIS_EXAMPLE_TRANSFORM_H_INCLUDED_ +#define _NBL_THIS_EXAMPLE_TRANSFORM_H_INCLUDED_ + + +#include "nbl/ui/ICursorControl.h" + +#include "nbl/ext/ImGui/ImGui.h" + +#include "imgui/imgui_internal.h" +#include "imguizmo/ImGuizmo.h" + + +struct TransformRequestParams +{ + float camDistance = 8.f; + uint8_t sceneTexDescIx = ~0; + bool useWindow = true, editTransformDecomposition = false, enableViewManipulate = false; +}; + +struct TransformReturnInfo +{ + nbl::hlsl::uint16_t2 sceneResolution = { 2048,1024 }; + bool isGizmoWindowHovered; + bool isGizmoBeingUsed; +}; + +TransformReturnInfo EditTransform(float* cameraView, const float* cameraProjection, float* matrix, const TransformRequestParams& params) +{ + static ImGuizmo::OPERATION mCurrentGizmoOperation(ImGuizmo::TRANSLATE); + static ImGuizmo::MODE mCurrentGizmoMode(ImGuizmo::LOCAL); + static bool useSnap = false; + static float snap[3] = { 1.f, 1.f, 1.f }; + static float bounds[] = { -0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f }; + static float boundsSnap[] = { 0.1f, 0.1f, 0.1f }; + static bool boundSizing = false; + static bool boundSizingSnap = false; + + if (params.editTransformDecomposition) + { + if (ImGui::IsKeyPressed(ImGuiKey_T)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + if (ImGui::IsKeyPressed(ImGuiKey_R)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + if (ImGui::IsKeyPressed(ImGuiKey_S)) + mCurrentGizmoOperation = ImGuizmo::SCALE; + if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE)) + mCurrentGizmoOperation = ImGuizmo::SCALE; + if (ImGui::RadioButton("Universal", mCurrentGizmoOperation == ImGuizmo::UNIVERSAL)) + mCurrentGizmoOperation = ImGuizmo::UNIVERSAL; + float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + ImGuizmo::DecomposeMatrixToComponents(matrix, matrixTranslation, matrixRotation, matrixScale); + ImGui::InputFloat3("Tr", matrixTranslation); + ImGui::InputFloat3("Rt", matrixRotation); + ImGui::InputFloat3("Sc", matrixScale); + ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix); + + if (mCurrentGizmoOperation != ImGuizmo::SCALE) + { + if (ImGui::RadioButton("Local", mCurrentGizmoMode == ImGuizmo::LOCAL)) + mCurrentGizmoMode = ImGuizmo::LOCAL; + ImGui::SameLine(); + if (ImGui::RadioButton("World", mCurrentGizmoMode == ImGuizmo::WORLD)) + mCurrentGizmoMode = ImGuizmo::WORLD; + } + if (ImGui::IsKeyPressed(ImGuiKey_S) && ImGui::IsKeyPressed(ImGuiKey_LeftShift)) + useSnap = !useSnap; + ImGui::Checkbox("##UseSnap", &useSnap); + ImGui::SameLine(); + + switch (mCurrentGizmoOperation) + { + case ImGuizmo::TRANSLATE: + ImGui::InputFloat3("Snap", &snap[0]); + break; + case ImGuizmo::ROTATE: + ImGui::InputFloat("Angle Snap", &snap[0]); + break; + case ImGuizmo::SCALE: + ImGui::InputFloat("Scale Snap", &snap[0]); + break; + } + ImGui::Checkbox("Bound Sizing", &boundSizing); + if (boundSizing) + { + ImGui::PushID(3); + ImGui::Checkbox("##BoundSizing", &boundSizingSnap); + ImGui::SameLine(); + ImGui::InputFloat3("Snap", boundsSnap); + ImGui::PopID(); + } + } + + ImGuiIO& io = ImGui::GetIO(); + float viewManipulateRight = io.DisplaySize.x; + float viewManipulateTop = 0; + static ImGuiWindowFlags gizmoWindowFlags = 0; + + /* + for the "useWindow" case we just render to a gui area, + otherwise to fake full screen transparent window + + note that for both cases we make sure gizmo being + rendered is aligned to our texture scene using + imgui "cursor" screen positions + */ +// TODO: this shouldn't be handled here I think + SImResourceInfo info; + info.textureID = params.sceneTexDescIx; + info.samplerIx = (uint16_t)nbl::ext::imgui::UI::DefaultSamplerIx::USER; + + TransformReturnInfo retval; + if (params.useWindow) + { + ImGui::SetNextWindowSize(ImVec2(800, 800), ImGuiCond_Appearing); + ImGui::SetNextWindowPos(ImVec2(400, 20), ImGuiCond_Appearing); + ImGui::PushStyleColor(ImGuiCol_WindowBg, (ImVec4)ImColor(0.35f, 0.3f, 0.3f)); + ImGui::Begin("Gizmo", 0, gizmoWindowFlags); + ImGuizmo::SetDrawlist(); + + ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(); + ImVec2 windowPos = ImGui::GetWindowPos(); + ImVec2 cursorPos = ImGui::GetCursorScreenPos(); + + ImGui::Image(info, contentRegionSize); + ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); + retval.sceneResolution = {contentRegionSize.x,contentRegionSize.y}; + retval.isGizmoWindowHovered = ImGui::IsWindowHovered(); + + viewManipulateRight = cursorPos.x + contentRegionSize.x; + viewManipulateTop = cursorPos.y; + + ImGuiWindow* window = ImGui::GetCurrentWindow(); + gizmoWindowFlags = (ImGui::IsWindowHovered() && ImGui::IsMouseHoveringRect(window->InnerRect.Min, window->InnerRect.Max) ? ImGuiWindowFlags_NoMove : 0); + } + else + { + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowSize(io.DisplaySize); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0)); // fully transparent fake window + ImGui::Begin("FullScreenWindow", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoInputs); + + ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(); + ImVec2 cursorPos = ImGui::GetCursorScreenPos(); + + ImGui::Image(info, contentRegionSize); + ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); + retval.sceneResolution = {contentRegionSize.x,contentRegionSize.y}; + retval.isGizmoWindowHovered = ImGui::IsWindowHovered(); + + viewManipulateRight = cursorPos.x + contentRegionSize.x; + viewManipulateTop = cursorPos.y; + } + + ImGuizmo::Manipulate(cameraView, cameraProjection, mCurrentGizmoOperation, mCurrentGizmoMode, matrix, NULL, useSnap ? &snap[0] : NULL, boundSizing ? bounds : NULL, boundSizingSnap ? boundsSnap : NULL); + retval.isGizmoBeingUsed = ImGuizmo::IsOver() || (ImGuizmo::IsUsing() && ImGui::IsMouseDown(ImGuiMouseButton_Left)); + + if(params.enableViewManipulate) + ImGuizmo::ViewManipulate(cameraView, params.camDistance, ImVec2(viewManipulateRight - 128, viewManipulateTop), ImVec2(128, 128), 0x10101010); + + ImGui::End(); + ImGui::PopStyleColor(); + + return retval; +} + +#endif // __NBL_THIS_EXAMPLE_TRANSFORM_H_INCLUDED__ \ No newline at end of file diff --git a/72_SolidAngleVisualizer/main.cpp b/72_SolidAngleVisualizer/main.cpp new file mode 100644 index 000000000..b6d723e70 --- /dev/null +++ b/72_SolidAngleVisualizer/main.cpp @@ -0,0 +1,1105 @@ +// Copyright (C) 2018-2020 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h + + +#include "common.hpp" +#include "app_resources/hlsl/common.hlsl" + +#include "nbl/ext/FullScreenTriangle/FullScreenTriangle.h" + +/* +Renders scene texture to an offscreen framebuffer whose color attachment is then sampled into a imgui window. + +Written with Nabla's UI extension and got integrated with ImGuizmo to handle scene's object translations. +*/ +class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinResourcesApplication +{ + using device_base_t = MonoWindowApplication; + using asset_base_t = BuiltinResourcesApplication; + + inline static std::string SolidAngleVisShaderPath = "app_resources/hlsl/SolidAngleVis.frag.hlsl"; +public: + inline SolidAngleVisualizer(const path& _localInputCWD, const path& _localOutputCWD, const path& _sharedInputCWD, const path& _sharedOutputCWD) + : IApplicationFramework(_localInputCWD, _localOutputCWD, _sharedInputCWD, _sharedOutputCWD), + device_base_t({ 2048,1024 }, EF_UNKNOWN, _localInputCWD, _localOutputCWD, _sharedInputCWD, _sharedOutputCWD) { + } + + inline bool onAppInitialized(smart_refctd_ptr&& system) override + { + if (!asset_base_t::onAppInitialized(smart_refctd_ptr(system))) + return false; + if (!device_base_t::onAppInitialized(smart_refctd_ptr(system))) + return false; + + m_semaphore = m_device->createSemaphore(m_realFrameIx); + if (!m_semaphore) + return logFail("Failed to Create a Semaphore!"); + + auto pool = m_device->createCommandPool(getGraphicsQueue()->getFamilyIndex(), IGPUCommandPool::CREATE_FLAGS::RESET_COMMAND_BUFFER_BIT); + for (auto i = 0u; i < MaxFramesInFlight; i++) + { + if (!pool) + return logFail("Couldn't create Command Pool!"); + if (!pool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY, { m_cmdBufs.data() + i,1 })) + return logFail("Couldn't create Command Buffer!"); + } + + const uint32_t addtionalBufferOwnershipFamilies[] = { getGraphicsQueue()->getFamilyIndex() }; + m_scene = CGeometryCreatorScene::create( + { + .transferQueue = getTransferUpQueue(), + .utilities = m_utils.get(), + .logger = m_logger.get(), + .addtionalBufferOwnershipFamilies = addtionalBufferOwnershipFamilies + }, + CSimpleDebugRenderer::DefaultPolygonGeometryPatch + ); + + // for the scene drawing pass + { + IGPURenderpass::SCreationParams params = {}; + const IGPURenderpass::SCreationParams::SDepthStencilAttachmentDescription depthAttachments[] = { + {{ + { + .format = sceneRenderDepthFormat, + .samples = IGPUImage::ESCF_1_BIT, + .mayAlias = false + }, + /*.loadOp =*/ {IGPURenderpass::LOAD_OP::CLEAR}, + /*.storeOp =*/ {IGPURenderpass::STORE_OP::STORE}, + /*.initialLayout =*/ {IGPUImage::LAYOUT::UNDEFINED}, + /*.finalLayout =*/ {IGPUImage::LAYOUT::ATTACHMENT_OPTIMAL} + }}, + IGPURenderpass::SCreationParams::DepthStencilAttachmentsEnd + }; + params.depthStencilAttachments = depthAttachments; + const IGPURenderpass::SCreationParams::SColorAttachmentDescription colorAttachments[] = { + {{ + { + .format = finalSceneRenderFormat, + .samples = IGPUImage::E_SAMPLE_COUNT_FLAGS::ESCF_1_BIT, + .mayAlias = false + }, + /*.loadOp =*/ IGPURenderpass::LOAD_OP::CLEAR, + /*.storeOp =*/ IGPURenderpass::STORE_OP::STORE, + /*.initialLayout =*/ IGPUImage::LAYOUT::UNDEFINED, + /*.finalLayout =*/ IGPUImage::LAYOUT::READ_ONLY_OPTIMAL // ImGUI shall read + }}, + IGPURenderpass::SCreationParams::ColorAttachmentsEnd + }; + params.colorAttachments = colorAttachments; + IGPURenderpass::SCreationParams::SSubpassDescription subpasses[] = { + {}, + IGPURenderpass::SCreationParams::SubpassesEnd + }; + subpasses[0].depthStencilAttachment = { {.render = {.attachmentIndex = 0,.layout = IGPUImage::LAYOUT::ATTACHMENT_OPTIMAL}} }; + subpasses[0].colorAttachments[0] = { .render = {.attachmentIndex = 0,.layout = IGPUImage::LAYOUT::ATTACHMENT_OPTIMAL} }; + params.subpasses = subpasses; + + const static IGPURenderpass::SCreationParams::SSubpassDependency dependencies[] = { + // wipe-transition of Color to ATTACHMENT_OPTIMAL and depth + { + .srcSubpass = IGPURenderpass::SCreationParams::SSubpassDependency::External, + .dstSubpass = 0, + .memoryBarrier = { + // last place where the depth can get modified in previous frame, `COLOR_ATTACHMENT_OUTPUT_BIT` is implicitly later + // while color is sampled by ImGUI + .srcStageMask = PIPELINE_STAGE_FLAGS::LATE_FRAGMENT_TESTS_BIT | PIPELINE_STAGE_FLAGS::FRAGMENT_SHADER_BIT, + // don't want any writes to be available, as we are clearing both attachments + .srcAccessMask = ACCESS_FLAGS::NONE, + // destination needs to wait as early as possible + // TODO: `COLOR_ATTACHMENT_OUTPUT_BIT` shouldn't be needed, because its a logically later stage, see TODO in `ECommonEnums.h` + .dstStageMask = PIPELINE_STAGE_FLAGS::EARLY_FRAGMENT_TESTS_BIT | PIPELINE_STAGE_FLAGS::COLOR_ATTACHMENT_OUTPUT_BIT, + // because depth and color get cleared first no read mask + .dstAccessMask = ACCESS_FLAGS::DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | ACCESS_FLAGS::COLOR_ATTACHMENT_WRITE_BIT + } + // leave view offsets and flags default + }, + { + .srcSubpass = 0, + .dstSubpass = IGPURenderpass::SCreationParams::SSubpassDependency::External, + .memoryBarrier = { + // last place where the color can get modified, depth is implicitly earlier + .srcStageMask = PIPELINE_STAGE_FLAGS::COLOR_ATTACHMENT_OUTPUT_BIT, + // only write ops, reads can't be made available, also won't be using depth so don't care about it being visible to anyone else + .srcAccessMask = ACCESS_FLAGS::COLOR_ATTACHMENT_WRITE_BIT, + // the ImGUI will sample the color, then next frame we overwrite both attachments + .dstStageMask = PIPELINE_STAGE_FLAGS::FRAGMENT_SHADER_BIT | PIPELINE_STAGE_FLAGS::EARLY_FRAGMENT_TESTS_BIT, + // but we only care about the availability-visibility chain between renderpass and imgui + .dstAccessMask = ACCESS_FLAGS::SAMPLED_READ_BIT + } + // leave view offsets and flags default + }, + IGPURenderpass::SCreationParams::DependenciesEnd + }; + params.dependencies = dependencies; + auto solidAngleRenderpassParams = params; + m_mainRenderpass = m_device->createRenderpass(std::move(params)); + if (!m_mainRenderpass) + return logFail("Failed to create Main Renderpass!"); + + m_solidAngleRenderpass = m_device->createRenderpass(std::move(solidAngleRenderpassParams)); + if (!m_solidAngleRenderpass) + return logFail("Failed to create Solid Angle Renderpass!"); + + } + + const auto& geometries = m_scene->getInitParams().geometries; + m_renderer = CSimpleDebugRenderer::create(m_assetMgr.get(), m_solidAngleRenderpass.get(), 0, { &geometries.front().get(),geometries.size() }); + // special case + { + const auto& pipelines = m_renderer->getInitParams().pipelines; + auto ix = 0u; + for (const auto& name : m_scene->getInitParams().geometryNames) + { + if (name == "Cone") + m_renderer->getGeometry(ix).pipeline = pipelines[CSimpleDebugRenderer::SInitParams::PipelineType::Cone]; + ix++; + } + } + // we'll only display one thing at a time + m_renderer->m_instances.resize(1); + + // Create graphics pipeline + { + auto loadAndCompileHLSLShader = [&](const std::string& pathToShader, const std::string& defineMacro = "") -> smart_refctd_ptr + { + IAssetLoader::SAssetLoadParams lp = {}; + lp.workingDirectory = localInputCWD; + auto assetBundle = m_assetMgr->getAsset(pathToShader, lp); + const auto assets = assetBundle.getContents(); + if (assets.empty()) + { + m_logger->log("Could not load shader: ", ILogger::ELL_ERROR, pathToShader); + std::exit(-1); + } + + auto source = smart_refctd_ptr_static_cast(assets[0]); + // The down-cast should not fail! + assert(source); + + auto compiler = make_smart_refctd_ptr(smart_refctd_ptr(m_system)); + CHLSLCompiler::SOptions options = {}; + options.stage = IShader::E_SHADER_STAGE::ESS_FRAGMENT; + options.preprocessorOptions.targetSpirvVersion = m_device->getPhysicalDevice()->getLimits().spirvVersion; + options.spirvOptimizer = nullptr; +#ifndef _NBL_DEBUG + ISPIRVOptimizer::E_OPTIMIZER_PASS optPasses = ISPIRVOptimizer::EOP_STRIP_DEBUG_INFO; + auto opt = make_smart_refctd_ptr(std::span(&optPasses, 1)); + options.spirvOptimizer = opt.get(); +#endif + options.debugInfoFlags |= IShaderCompiler::E_DEBUG_INFO_FLAGS::EDIF_LINE_BIT; + options.preprocessorOptions.sourceIdentifier = source->getFilepathHint(); + options.preprocessorOptions.logger = m_logger.get(); + options.preprocessorOptions.includeFinder = compiler->getDefaultIncludeFinder(); + + core::vector defines; + if (!defineMacro.empty()) + defines.push_back({ defineMacro, "" }); + + options.preprocessorOptions.extraDefines = defines; + + source = compiler->compileToSPIRV((const char*)source->getContent()->getPointer(), options); + + auto shader = m_device->compileShader({ source.get(), nullptr, nullptr, nullptr }); + if (!shader) + { + m_logger->log("HLSL shader creationed failed: %s!", ILogger::ELL_ERROR, pathToShader); + std::exit(-1); + } + + return shader; + }; + + auto scRes = static_cast(m_surface->getSwapchainResources()); + ext::FullScreenTriangle::ProtoPipeline fsTriProtoPPln(m_assetMgr.get(), m_device.get(), m_logger.get()); + if (!fsTriProtoPPln) + return logFail("Failed to create Full Screen Triangle protopipeline or load its vertex shader!"); + + // Load Fragment Shader + auto fragmentShader = loadAndCompileHLSLShader(SolidAngleVisShaderPath); + if (!fragmentShader) + return logFail("Failed to Load and Compile Fragment Shader: lumaMeterShader!"); + + const IGPUPipelineBase::SShaderSpecInfo fragSpec = { + .shader = fragmentShader.get(), + .entryPoint = "main" + }; + + const asset::SPushConstantRange ranges[] = { { + .stageFlags = hlsl::ShaderStage::ESS_FRAGMENT, + .offset = 0, + .size = sizeof(PushConstants) + } }; + + auto visualizationLayout = m_device->createPipelineLayout( + ranges, + nullptr, + nullptr, + nullptr, + nullptr + ); + m_visualizationPipeline = fsTriProtoPPln.createPipeline(fragSpec, visualizationLayout.get(), m_solidAngleRenderpass.get()); + if (!m_visualizationPipeline) + return logFail("Could not create Graphics Pipeline!"); + + } + + // Create ImGUI + { + auto scRes = static_cast(m_surface->getSwapchainResources()); + ext::imgui::UI::SCreationParameters params = {}; + params.resources.texturesInfo = { .setIx = 0u,.bindingIx = TexturesImGUIBindingIndex }; + params.resources.samplersInfo = { .setIx = 0u,.bindingIx = 1u }; + params.utilities = m_utils; + params.transfer = getTransferUpQueue(); + params.pipelineLayout = ext::imgui::UI::createDefaultPipelineLayout(m_utils->getLogicalDevice(), params.resources.texturesInfo, params.resources.samplersInfo, MaxImGUITextures); + params.assetManager = make_smart_refctd_ptr(smart_refctd_ptr(m_system)); + params.renderpass = smart_refctd_ptr(scRes->getRenderpass()); + params.subpassIx = 0u; + params.pipelineCache = nullptr; + interface.imGUI = ext::imgui::UI::create(std::move(params)); + if (!interface.imGUI) + return logFail("Failed to create `nbl::ext::imgui::UI` class"); + } + + // create rest of User Interface + { + auto* imgui = interface.imGUI.get(); + // create the suballocated descriptor set + { + // note that we use default layout provided by our extension, but you are free to create your own by filling ext::imgui::UI::S_CREATION_PARAMETERS::resources + const auto* layout = interface.imGUI->getPipeline()->getLayout()->getDescriptorSetLayout(0u); + auto pool = m_device->createDescriptorPoolForDSLayouts(IDescriptorPool::E_CREATE_FLAGS::ECF_UPDATE_AFTER_BIND_BIT, { &layout,1 }); + auto ds = pool->createDescriptorSet(smart_refctd_ptr(layout)); + interface.subAllocDS = make_smart_refctd_ptr(std::move(ds)); + if (!interface.subAllocDS) + return logFail("Failed to create the descriptor set"); + // make sure Texture Atlas slot is taken for eternity + { + auto dummy = SubAllocatedDescriptorSet::invalid_value; + interface.subAllocDS->multi_allocate(0, 1, &dummy); + assert(dummy == ext::imgui::UI::FontAtlasTexId); + } + // write constant descriptors, note we don't create info & write pair for the samplers because UI extension's are immutable and baked into DS layout + IGPUDescriptorSet::SDescriptorInfo info = {}; + info.desc = smart_refctd_ptr(interface.imGUI->getFontAtlasView()); + info.info.image.imageLayout = IImage::LAYOUT::READ_ONLY_OPTIMAL; + const IGPUDescriptorSet::SWriteDescriptorSet write = { + .dstSet = interface.subAllocDS->getDescriptorSet(), + .binding = TexturesImGUIBindingIndex, + .arrayElement = ext::imgui::UI::FontAtlasTexId, + .count = 1, + .info = &info + }; + if (!m_device->updateDescriptorSets({ &write,1 }, {})) + return logFail("Failed to write the descriptor set"); + } + imgui->registerListener([this]() {interface(); }); + } + + interface.camera.mapKeysToWASD(); + + onAppInitializedFinish(); + return true; + } + + // + virtual inline bool onAppTerminated() + { + SubAllocatedDescriptorSet::value_type fontAtlasDescIx = ext::imgui::UI::FontAtlasTexId; + IGPUDescriptorSet::SDropDescriptorSet dummy[1]; + interface.subAllocDS->multi_deallocate(dummy, TexturesImGUIBindingIndex, 1, &fontAtlasDescIx); + return device_base_t::onAppTerminated(); + } + + inline IQueue::SSubmitInfo::SSemaphoreInfo renderFrame(const std::chrono::microseconds nextPresentationTimestamp) override + { + // CPU events + update(nextPresentationTimestamp); + + const auto& virtualWindowRes = interface.transformReturnInfo.sceneResolution; + // TODO: check main frame buffer too + if (!m_solidAngleViewFramebuffer || m_solidAngleViewFramebuffer->getCreationParameters().width != virtualWindowRes[0] || m_solidAngleViewFramebuffer->getCreationParameters().height != virtualWindowRes[1]) + recreateFramebuffer(virtualWindowRes); + + // + const auto resourceIx = m_realFrameIx % MaxFramesInFlight; + + auto* const cb = m_cmdBufs.data()[resourceIx].get(); + cb->reset(IGPUCommandBuffer::RESET_FLAGS::RELEASE_RESOURCES_BIT); + cb->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); + // clear to black for both things + const IGPUCommandBuffer::SClearColorValue clearValue = { .float32 = {0.f,0.f,0.f,1.f} }; + if (m_solidAngleViewFramebuffer) + { + cb->beginDebugMarker("Draw Circle View Frame"); + { + const IGPUCommandBuffer::SClearDepthStencilValue farValue = { .depth = 0.f }; + const IGPUCommandBuffer::SRenderpassBeginInfo renderpassInfo = + { + .framebuffer = m_solidAngleViewFramebuffer.get(), + .colorClearValues = &clearValue, + .depthStencilClearValues = &farValue, + .renderArea = { + .offset = {0,0}, + .extent = {virtualWindowRes[0],virtualWindowRes[1]} + } + }; + beginRenderpass(cb, renderpassInfo); + } + // draw scene + { + PushConstants pc{ + .modelMatrix = hlsl::float32_t3x4(hlsl::transpose(interface.m_OBBModelMatrix)), + .viewport = { 0.f,0.f,static_cast(virtualWindowRes[0]),static_cast(virtualWindowRes[1]) } + }; + auto pipeline = m_visualizationPipeline; + cb->bindGraphicsPipeline(pipeline.get()); + cb->pushConstants(pipeline->getLayout(), hlsl::ShaderStage::ESS_FRAGMENT, 0, sizeof(PushConstants), &pc); + //cb->bindDescriptorSets(nbl::asset::EPBP_GRAPHICS, pipeline->getLayout(), 3, 1, &ds); + ext::FullScreenTriangle::recordDrawCall(cb); + } + cb->endRenderPass(); + cb->endDebugMarker(); + } + // draw main view + if (m_mainViewFramebuffer) + { + cb->beginDebugMarker("Main Scene Frame"); + { + const IGPUCommandBuffer::SClearDepthStencilValue farValue = { .depth = 0.f }; + const IGPUCommandBuffer::SRenderpassBeginInfo renderpassInfo = + { + .framebuffer = m_mainViewFramebuffer.get(), + .colorClearValues = &clearValue, + .depthStencilClearValues = &farValue, + .renderArea = { + .offset = {0,0}, + .extent = {virtualWindowRes[0],virtualWindowRes[1]} + } + }; + beginRenderpass(cb, renderpassInfo); + } + // draw scene + { + float32_t3x4 viewMatrix; + float32_t4x4 viewProjMatrix; + // TODO: get rid of legacy matrices + { + const auto& camera = interface.camera; + memcpy(&viewMatrix, camera.getViewMatrix().pointer(), sizeof(viewMatrix)); + memcpy(&viewProjMatrix, camera.getConcatenatedMatrix().pointer(), sizeof(viewProjMatrix)); + } + const auto viewParams = CSimpleDebugRenderer::SViewParams(viewMatrix, viewProjMatrix); + + // tear down scene every frame + auto& instance = m_renderer->m_instances[0]; + auto transposed = hlsl::transpose(interface.m_OBBModelMatrix); + memcpy(&instance.world, &transposed, sizeof(instance.world)); + instance.packedGeo = m_renderer->getGeometries().data();// +interface.gcIndex; + m_renderer->render(cb, viewParams); // draw the cube/OBB + + + // TODO: a better way to get identity matrix + float32_t3x4 origin = { + 0.2f,0.0f,0.0f,0.0f, + 0.0f,0.2f,0.0f,0.0f, + 0.0f,0.0f,0.2f,0.0f + }; + memcpy(&instance.world, &origin, sizeof(instance.world)); + instance.packedGeo = m_renderer->getGeometries().data() + 3; // sphere + m_renderer->render(cb, viewParams); + } + cb->endRenderPass(); + cb->endDebugMarker(); + } + { + cb->beginDebugMarker("SolidAngleVisualizer IMGUI Frame"); + { + auto scRes = static_cast(m_surface->getSwapchainResources()); + const IGPUCommandBuffer::SRenderpassBeginInfo renderpassInfo = + { + .framebuffer = scRes->getFramebuffer(device_base_t::getCurrentAcquire().imageIndex), + .colorClearValues = &clearValue, + .depthStencilClearValues = nullptr, + .renderArea = { + .offset = {0,0}, + .extent = {m_window->getWidth(),m_window->getHeight()} + } + }; + beginRenderpass(cb, renderpassInfo); + } + // draw ImGUI + { + auto* imgui = interface.imGUI.get(); + auto* pipeline = imgui->getPipeline(); + cb->bindGraphicsPipeline(pipeline); + // note that we use default UI pipeline layout where uiParams.resources.textures.setIx == uiParams.resources.samplers.setIx + const auto* ds = interface.subAllocDS->getDescriptorSet(); + cb->bindDescriptorSets(EPBP_GRAPHICS, pipeline->getLayout(), imgui->getCreationParameters().resources.texturesInfo.setIx, 1u, &ds); + // a timepoint in the future to release streaming resources for geometry + const ISemaphore::SWaitInfo drawFinished = { .semaphore = m_semaphore.get(),.value = m_realFrameIx + 1u }; + if (!imgui->render(cb, drawFinished)) + { + m_logger->log("TODO: need to present acquired image before bailing because its already acquired.", ILogger::ELL_ERROR); + return {}; + } + } + cb->endRenderPass(); + cb->endDebugMarker(); + } + cb->end(); + + IQueue::SSubmitInfo::SSemaphoreInfo retval = + { + .semaphore = m_semaphore.get(), + .value = ++m_realFrameIx, + .stageMask = PIPELINE_STAGE_FLAGS::ALL_GRAPHICS_BITS + }; + const IQueue::SSubmitInfo::SCommandBufferInfo commandBuffers[] = + { + {.cmdbuf = cb } + }; + const IQueue::SSubmitInfo::SSemaphoreInfo acquired[] = { + { + .semaphore = device_base_t::getCurrentAcquire().semaphore, + .value = device_base_t::getCurrentAcquire().acquireCount, + .stageMask = PIPELINE_STAGE_FLAGS::NONE + } + }; + const IQueue::SSubmitInfo infos[] = + { + { + .waitSemaphores = acquired, + .commandBuffers = commandBuffers, + .signalSemaphores = {&retval,1} + } + }; + + if (getGraphicsQueue()->submit(infos) != IQueue::RESULT::SUCCESS) + { + retval.semaphore = nullptr; // so that we don't wait on semaphore that will never signal + m_realFrameIx--; + } + + + m_window->setCaption("[Nabla Engine] UI App Test Demo"); + return retval; + } + +protected: + const video::IGPURenderpass::SCreationParams::SSubpassDependency* getDefaultSubpassDependencies() const override + { + // Subsequent submits don't wait for each other, but they wait for acquire and get waited on by present + const static IGPURenderpass::SCreationParams::SSubpassDependency dependencies[] = { + // don't want any writes to be available, we'll clear, only thing to worry about is the layout transition + { + .srcSubpass = IGPURenderpass::SCreationParams::SSubpassDependency::External, + .dstSubpass = 0, + .memoryBarrier = { + .srcStageMask = PIPELINE_STAGE_FLAGS::NONE, // should sync against the semaphore wait anyway + .srcAccessMask = ACCESS_FLAGS::NONE, + // layout transition needs to finish before the color write + .dstStageMask = PIPELINE_STAGE_FLAGS::COLOR_ATTACHMENT_OUTPUT_BIT, + .dstAccessMask = ACCESS_FLAGS::COLOR_ATTACHMENT_WRITE_BIT + } + // leave view offsets and flags default + }, + // want layout transition to begin after all color output is done + { + .srcSubpass = 0, + .dstSubpass = IGPURenderpass::SCreationParams::SSubpassDependency::External, + .memoryBarrier = { + // last place where the color can get modified, depth is implicitly earlier + .srcStageMask = PIPELINE_STAGE_FLAGS::COLOR_ATTACHMENT_OUTPUT_BIT, + // only write ops, reads can't be made available + .srcAccessMask = ACCESS_FLAGS::COLOR_ATTACHMENT_WRITE_BIT + // spec says nothing is needed when presentation is the destination + } + // leave view offsets and flags default + }, + IGPURenderpass::SCreationParams::DependenciesEnd + }; + return dependencies; + } + +private: + inline void update(const std::chrono::microseconds nextPresentationTimestamp) + { + auto& camera = interface.camera; + camera.setMoveSpeed(interface.moveSpeed); + camera.setRotateSpeed(interface.rotateSpeed); + + + m_inputSystem->getDefaultMouse(&mouse); + m_inputSystem->getDefaultKeyboard(&keyboard); + + struct + { + std::vector mouse{}; + std::vector keyboard{}; + } uiEvents; + + // TODO: should be a member really + static std::chrono::microseconds previousEventTimestamp{}; + + // I think begin/end should always be called on camera, just events shouldn't be fed, why? + // If you stop begin/end, whatever keys were up/down get their up/down values frozen leading to + // `perActionDt` becoming obnoxiously large the first time the even processing resumes due to + // `timeDiff` being computed since `lastVirtualUpTimeStamp` + camera.beginInputProcessing(nextPresentationTimestamp); + { + mouse.consumeEvents([&](const IMouseEventChannel::range_t& events) -> void + { + if (interface.move) + camera.mouseProcess(events); // don't capture the events, only let camera handle them with its impl + + for (const auto& e : events) // here capture + { + if (e.timeStamp < previousEventTimestamp) + continue; + + previousEventTimestamp = e.timeStamp; + uiEvents.mouse.emplace_back(e); + + //if (e.type == nbl::ui::SMouseEvent::EET_SCROLL && m_renderer) + //{ + // interface.gcIndex += int16_t(core::sign(e.scrollEvent.verticalScroll)); + // interface.gcIndex = core::clamp(interface.gcIndex, 0ull, m_renderer->getGeometries().size() - 1); + //} + } + }, + m_logger.get() + ); + keyboard.consumeEvents([&](const IKeyboardEventChannel::range_t& events) -> void + { + //if (interface.move) + camera.keyboardProcess(events); // don't capture the events, only let camera handle them with its impl + + for (const auto& e : events) // here capture + { + if (e.timeStamp < previousEventTimestamp) + continue; + + previousEventTimestamp = e.timeStamp; + uiEvents.keyboard.emplace_back(e); + } + }, + m_logger.get() + ); + } + camera.endInputProcessing(nextPresentationTimestamp); + + const auto cursorPosition = m_window->getCursorControl()->getPosition(); + + ext::imgui::UI::SUpdateParameters params = + { + .mousePosition = float32_t2(cursorPosition.x,cursorPosition.y) - float32_t2(m_window->getX(),m_window->getY()), + .displaySize = {m_window->getWidth(),m_window->getHeight()}, + .mouseEvents = uiEvents.mouse, + .keyboardEvents = uiEvents.keyboard + }; + + //interface.objectName = m_scene->getInitParams().geometryNames[interface.gcIndex]; + interface.imGUI->update(params); + } + + void recreateFramebuffer(const uint16_t2 resolution) + { + auto createImageAndView = [&](E_FORMAT format)->smart_refctd_ptr + { + auto image = m_device->createImage({ { + .type = IGPUImage::ET_2D, + .samples = IGPUImage::ESCF_1_BIT, + .format = format, + .extent = {resolution.x,resolution.y,1}, + .mipLevels = 1, + .arrayLayers = 1, + .usage = IGPUImage::EUF_RENDER_ATTACHMENT_BIT | IGPUImage::EUF_SAMPLED_BIT + } }); + if (!m_device->allocate(image->getMemoryReqs(), image.get()).isValid()) + return nullptr; + IGPUImageView::SCreationParams params = { + .image = std::move(image), + .viewType = IGPUImageView::ET_2D, + .format = format + }; + params.subresourceRange.aspectMask = isDepthOrStencilFormat(format) ? IGPUImage::EAF_DEPTH_BIT : IGPUImage::EAF_COLOR_BIT; + return m_device->createImageView(std::move(params)); + }; + + smart_refctd_ptr solidAngleView; + smart_refctd_ptr mainView; + // detect window minimization + if (resolution.x < 0x4000 && resolution.y < 0x4000) + { + solidAngleView = createImageAndView(finalSceneRenderFormat); + auto solidAngleDepthView = createImageAndView(sceneRenderDepthFormat); + m_solidAngleViewFramebuffer = m_device->createFramebuffer({ { + .renderpass = m_solidAngleRenderpass, + .depthStencilAttachments = &solidAngleDepthView.get(), + .colorAttachments = &solidAngleView.get(), + .width = resolution.x, + .height = resolution.y + } }); + + mainView = createImageAndView(finalSceneRenderFormat); + auto mainDepthView = createImageAndView(sceneRenderDepthFormat); + m_mainViewFramebuffer = m_device->createFramebuffer({ { + .renderpass = m_mainRenderpass, + .depthStencilAttachments = &mainDepthView.get(), + .colorAttachments = &mainView.get(), + .width = resolution.x, + .height = resolution.y + } }); + + } + else + { + m_solidAngleViewFramebuffer = nullptr; + m_mainViewFramebuffer = nullptr; + } + + // release previous slot and its image + interface.subAllocDS->multi_deallocate(0, static_cast(CInterface::Count), interface.renderColorViewDescIndices, { .semaphore = m_semaphore.get(),.value = m_realFrameIx }); + // + if (solidAngleView) + { + interface.subAllocDS->multi_allocate(0, static_cast(CInterface::Count), interface.renderColorViewDescIndices); + // update descriptor set + IGPUDescriptorSet::SDescriptorInfo infos[static_cast(CInterface::Count)] = {}; + infos[0].desc = solidAngleView; + infos[0].info.image.imageLayout = IGPUImage::LAYOUT::READ_ONLY_OPTIMAL; + infos[1].desc = mainView; + infos[1].info.image.imageLayout = IGPUImage::LAYOUT::READ_ONLY_OPTIMAL; + const IGPUDescriptorSet::SWriteDescriptorSet write[static_cast(CInterface::Count)] = { + {.dstSet = interface.subAllocDS->getDescriptorSet(), + .binding = TexturesImGUIBindingIndex, + .arrayElement = interface.renderColorViewDescIndices[static_cast(CInterface::ERV_SOLID_ANGLE_VIEW)], + .count = 1, + .info = &infos[static_cast(CInterface::ERV_MAIN_VIEW)] + }, + { + .dstSet = interface.subAllocDS->getDescriptorSet(), + .binding = TexturesImGUIBindingIndex, + .arrayElement = interface.renderColorViewDescIndices[static_cast(CInterface::ERV_MAIN_VIEW)], + .count = 1, + .info = &infos[1] + } + }; + m_device->updateDescriptorSets({ write, static_cast(CInterface::Count) }, {}); + } + interface.transformParams.sceneTexDescIx = interface.renderColorViewDescIndices[CInterface::ERV_MAIN_VIEW]; + } + + inline void beginRenderpass(IGPUCommandBuffer* cb, const IGPUCommandBuffer::SRenderpassBeginInfo& info) + { + cb->beginRenderPass(info, IGPUCommandBuffer::SUBPASS_CONTENTS::INLINE); + cb->setScissor(0, 1, &info.renderArea); + const SViewport viewport = { + .x = 0, + .y = 0, + .width = static_cast(info.renderArea.extent.width), + .height = static_cast(info.renderArea.extent.height) + }; + cb->setViewport(0u, 1u, &viewport); + } + + // Maximum frames which can be simultaneously submitted, used to cycle through our per-frame resources like command buffers + constexpr static inline uint32_t MaxFramesInFlight = 3u; + constexpr static inline auto sceneRenderDepthFormat = EF_D32_SFLOAT; + constexpr static inline auto finalSceneRenderFormat = EF_R8G8B8A8_SRGB; + constexpr static inline auto TexturesImGUIBindingIndex = 0u; + // we create the Descriptor Set with a few slots extra to spare, so we don't have to `waitIdle` the device whenever ImGUI virtual window resizes + constexpr static inline auto MaxImGUITextures = 2u + MaxFramesInFlight; + + // + smart_refctd_ptr m_scene; + smart_refctd_ptr m_solidAngleRenderpass; + smart_refctd_ptr m_mainRenderpass; + smart_refctd_ptr m_renderer; + smart_refctd_ptr m_solidAngleViewFramebuffer; + smart_refctd_ptr m_mainViewFramebuffer; + smart_refctd_ptr m_visualizationPipeline; + // + smart_refctd_ptr m_semaphore; + uint64_t m_realFrameIx = 0; + std::array, MaxFramesInFlight> m_cmdBufs; + // + InputSystem::ChannelReader mouse; + InputSystem::ChannelReader keyboard; + // UI stuff + struct CInterface + { + void cameraToHome() + { + core::vectorSIMDf cameraPosition(-3.0f, 3.0f, 6.0f); + core::vectorSIMDf cameraTarget(0.f, 0.f, 6.f); + const static core::vectorSIMDf up(0.f, 1.f, 0.f); + + camera.setPosition(cameraPosition); + camera.setTarget(cameraTarget); + camera.setBackupUpVector(up); + + camera.recomputeViewMatrix(); + } + + void operator()() + { + ImGuiIO& io = ImGui::GetIO(); + + // TODO: why is this a lambda and not just an assignment in a scope ? + camera.setProjectionMatrix([&]() + { + matrix4SIMD projection; + + if (isPerspective) + if (isLH) + projection = matrix4SIMD::buildProjectionMatrixPerspectiveFovLH(core::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar); + else + projection = matrix4SIMD::buildProjectionMatrixPerspectiveFovRH(core::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar); + else + { + float viewHeight = viewWidth * io.DisplaySize.y / io.DisplaySize.x; + + if (isLH) + projection = matrix4SIMD::buildProjectionMatrixOrthoLH(viewWidth, viewHeight, zNear, zFar); + else + projection = matrix4SIMD::buildProjectionMatrixOrthoRH(viewWidth, viewHeight, zNear, zFar); + } + + return projection; + }()); + + ImGuizmo::SetOrthographic(false); + ImGuizmo::BeginFrame(); + + ImGui::SetNextWindowPos(ImVec2(1024, 100), ImGuiCond_Appearing); + ImGui::SetNextWindowSize(ImVec2(256, 256), ImGuiCond_Appearing); + + // create a window and insert the inspector + ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_Appearing); + ImGui::SetNextWindowSize(ImVec2(320, 340), ImGuiCond_Appearing); + ImGui::Begin("Editor"); + + //if (ImGui::RadioButton("Full view", !transformParams.useWindow)) + // transformParams.useWindow = false; + + //ImGui::SameLine(); + + //if (ImGui::RadioButton("Window", transformParams.useWindow)) + // transformParams.useWindow = true; + + ImGui::Text("Camera"); + bool viewDirty = false; + + if (ImGui::RadioButton("LH", isLH)) + isLH = true; + + ImGui::SameLine(); + + if (ImGui::RadioButton("RH", !isLH)) + isLH = false; + + if (ImGui::RadioButton("Perspective", isPerspective)) + isPerspective = true; + + ImGui::SameLine(); + + if (ImGui::RadioButton("Orthographic", !isPerspective)) + isPerspective = false; + + ImGui::Checkbox("Enable \"view manipulate\"", &transformParams.enableViewManipulate); + //ImGui::Checkbox("Enable camera movement", &move); + ImGui::SliderFloat("Move speed", &moveSpeed, 0.1f, 10.f); + ImGui::SliderFloat("Rotate speed", &rotateSpeed, 0.1f, 10.f); + + // ImGui::Checkbox("Flip Gizmo's Y axis", &flipGizmoY); // let's not expose it to be changed in UI but keep the logic in case + + if (isPerspective) + ImGui::SliderFloat("Fov", &fov, 20.f, 150.f); + else + ImGui::SliderFloat("Ortho width", &viewWidth, 1, 20); + + ImGui::SliderFloat("zNear", &zNear, 0.1f, 100.f); + ImGui::SliderFloat("zFar", &zFar, 110.f, 10000.f); + + viewDirty |= ImGui::SliderFloat("Distance", &transformParams.camDistance, 1.f, 69.f); + + if (viewDirty || firstFrame) + { + cameraToHome(); + } + firstFrame = false; + + ImGui::Text("X: %f Y: %f", io.MousePos.x, io.MousePos.y); + if (ImGuizmo::IsUsing()) + { + ImGui::Text("Using gizmo"); + } + else + { + ImGui::Text(ImGuizmo::IsOver() ? "Over gizmo" : ""); + ImGui::SameLine(); + ImGui::Text(ImGuizmo::IsOver(ImGuizmo::TRANSLATE) ? "Over translate gizmo" : ""); + ImGui::SameLine(); + ImGui::Text(ImGuizmo::IsOver(ImGuizmo::ROTATE) ? "Over rotate gizmo" : ""); + ImGui::SameLine(); + ImGui::Text(ImGuizmo::IsOver(ImGuizmo::SCALE) ? "Over scale gizmo" : ""); + } + ImGui::Separator(); + + /* + * ImGuizmo expects view & perspective matrix to be column major both with 4x4 layout + * and Nabla uses row major matricies - 3x4 matrix for view & 4x4 for projection + + - VIEW: + + ImGuizmo + + | X[0] Y[0] Z[0] 0.0f | + | X[1] Y[1] Z[1] 0.0f | + | X[2] Y[2] Z[2] 0.0f | + | -Dot(X, eye) -Dot(Y, eye) -Dot(Z, eye) 1.0f | + + Nabla + + | X[0] X[1] X[2] -Dot(X, eye) | + | Y[0] Y[1] Y[2] -Dot(Y, eye) | + | Z[0] Z[1] Z[2] -Dot(Z, eye) | + + = transpose(nbl::core::matrix4SIMD()) + + - PERSPECTIVE [PROJECTION CASE]: + + ImGuizmo + + | (temp / temp2) (0.0) (0.0) (0.0) | + | (0.0) (temp / temp3) (0.0) (0.0) | + | ((right + left) / temp2) ((top + bottom) / temp3) ((-zfar - znear) / temp4) (-1.0f) | + | (0.0) (0.0) ((-temp * zfar) / temp4) (0.0) | + + Nabla + + | w (0.0) (0.0) (0.0) | + | (0.0) -h (0.0) (0.0) | + | (0.0) (0.0) (-zFar/(zFar-zNear)) (-zNear*zFar/(zFar-zNear)) | + | (0.0) (0.0) (-1.0) (0.0) | + + = transpose() + + * + * the ViewManipulate final call (inside EditTransform) returns world space column major matrix for an object, + * note it also modifies input view matrix but projection matrix is immutable + */ + + if (ImGui::IsKeyPressed(ImGuiKey_Home)) + { + cameraToHome(); + } + + if (ImGui::IsKeyPressed(ImGuiKey_End)) + { + m_OBBModelMatrix = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 12.0f, 1.0f + }; + } + + static struct + { + float32_t4x4 view, projection, model; + } imguizmoM16InOut; + + ImGuizmo::SetID(0u); + + // TODO: camera will return hlsl::float32_tMxN + auto view = *reinterpret_cast(camera.getViewMatrix().pointer()); + imguizmoM16InOut.view = hlsl::transpose(getMatrix3x4As4x4(view)); + + // TODO: camera will return hlsl::float32_tMxN + imguizmoM16InOut.projection = hlsl::transpose(*reinterpret_cast(camera.getProjectionMatrix().pointer())); + imguizmoM16InOut.model = m_OBBModelMatrix; + + { + if (flipGizmoY) // note we allow to flip gizmo just to match our coordinates + imguizmoM16InOut.projection[1][1] *= -1.f; // https://johannesugb.github.io/gpu-programming/why-do-opengl-proj-matrices-fail-in-vulkan/ + + transformParams.editTransformDecomposition = true; + transformReturnInfo = EditTransform(&imguizmoM16InOut.view[0][0], &imguizmoM16InOut.projection[0][0], &imguizmoM16InOut.model[0][0], transformParams); + + // TODO: camera stops when cursor hovers gizmo, but we also want to stop when gizmo is being used + move = (ImGui::IsMouseDown(ImGuiMouseButton_Left) || transformReturnInfo.isGizmoWindowHovered) && (!transformReturnInfo.isGizmoBeingUsed); + } + + // to Nabla + update camera & model matrices + // TODO: make it more nicely, extract: + // - Position by computing inverse of the view matrix and grabbing its translation + // - Target from 3rd row without W component of view matrix multiplied by some arbitrary distance value (can be the length of position from origin) and adding the position + // But then set the view matrix this way anyway, because up-vector may not be compatible + //const auto& view = camera.getViewMatrix(); + //const_cast(view) = core::transpose(imguizmoM16InOut.view).extractSub3x4(); // a hack, correct way would be to use inverse matrix and get position + target because now it will bring you back to last position & target when switching from gizmo move to manual move (but from manual to gizmo is ok) + m_OBBModelMatrix = imguizmoM16InOut.model; + + // object meta display + //{ + // ImGui::Begin("Object"); + // ImGui::Text("type: \"%s\"", objectName.data()); + // ImGui::End(); + //} + + // solid angle view window + { + ImGui::SetNextWindowSize(ImVec2(800, 800), ImGuiCond_Appearing); + ImGui::SetNextWindowPos(ImVec2(1240, 20), ImGuiCond_Appearing); + static bool isOpen = true; + ImGui::Begin("Solid angle view", &isOpen, 0); + + ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(); + ImGui::Image({ renderColorViewDescIndices[ERV_SOLID_ANGLE_VIEW] }, contentRegionSize); + ImGui::End(); + } + + // view matrices editor + { + ImGui::Begin("Matrices"); + + auto addMatrixTable = [&](const char* topText, const char* tableName, const int rows, const int columns, const float* pointer, const bool withSeparator = true) + { + ImGui::Text(topText); + if (ImGui::BeginTable(tableName, columns)) + { + for (int y = 0; y < rows; ++y) + { + ImGui::TableNextRow(); + for (int x = 0; x < columns; ++x) + { + ImGui::TableSetColumnIndex(x); + ImGui::Text("%.3f", *(pointer + (y * columns) + x)); + } + } + ImGui::EndTable(); + } + + if (withSeparator) + ImGui::Separator(); + }; + + addMatrixTable("Model Matrix", "ModelMatrixTable", 4, 4, &m_OBBModelMatrix[0][0]); + addMatrixTable("Camera View Matrix", "ViewMatrixTable", 3, 4, camera.getViewMatrix().pointer()); + addMatrixTable("Camera View Projection Matrix", "ViewProjectionMatrixTable", 4, 4, camera.getProjectionMatrix().pointer(), false); + + ImGui::End(); + } + + // Nabla Imgui backend MDI buffer info + // To be 100% accurate and not overly conservative we'd have to explicitly `cull_frees` and defragment each time, + // so unless you do that, don't use this basic info to optimize the size of your IMGUI buffer. + { + auto* streaminingBuffer = imGUI->getStreamingBuffer(); + + const size_t total = streaminingBuffer->get_total_size(); // total memory range size for which allocation can be requested + const size_t freeSize = streaminingBuffer->getAddressAllocator().get_free_size(); // max total free bloock memory size we can still allocate from total memory available + const size_t consumedMemory = total - freeSize; // memory currently consumed by streaming buffer + + float freePercentage = 100.0f * (float)(freeSize) / (float)total; + float allocatedPercentage = (float)(consumedMemory) / (float)total; + + ImVec2 barSize = ImVec2(400, 30); + float windowPadding = 10.0f; + float verticalPadding = ImGui::GetStyle().FramePadding.y; + + ImGui::SetNextWindowSize(ImVec2(barSize.x + 2 * windowPadding, 110 + verticalPadding), ImGuiCond_Always); + ImGui::Begin("Nabla Imgui MDI Buffer Info", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar); + + ImGui::Text("Total Allocated Size: %zu bytes", total); + ImGui::Text("In use: %zu bytes", consumedMemory); + ImGui::Text("Buffer Usage:"); + + ImGui::SetCursorPosX(windowPadding); + + if (freePercentage > 70.0f) + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.0f, 1.0f, 0.0f, 0.4f)); // Green + else if (freePercentage > 30.0f) + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(1.0f, 1.0f, 0.0f, 0.4f)); // Yellow + else + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(1.0f, 0.0f, 0.0f, 0.4f)); // Red + + ImGui::ProgressBar(allocatedPercentage, barSize, ""); + + ImGui::PopStyleColor(); + + ImDrawList* drawList = ImGui::GetWindowDrawList(); + + ImVec2 progressBarPos = ImGui::GetItemRectMin(); + ImVec2 progressBarSize = ImGui::GetItemRectSize(); + + const char* text = "%.2f%% free"; + char textBuffer[64]; + snprintf(textBuffer, sizeof(textBuffer), text, freePercentage); + + ImVec2 textSize = ImGui::CalcTextSize(textBuffer); + ImVec2 textPos = ImVec2 + ( + progressBarPos.x + (progressBarSize.x - textSize.x) * 0.5f, + progressBarPos.y + (progressBarSize.y - textSize.y) * 0.5f + ); + + ImVec4 bgColor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); + drawList->AddRectFilled + ( + ImVec2(textPos.x - 5, textPos.y - 2), + ImVec2(textPos.x + textSize.x + 5, textPos.y + textSize.y + 2), + ImGui::GetColorU32(bgColor) + ); + + ImGui::SetCursorScreenPos(textPos); + ImGui::Text("%s", textBuffer); + + ImGui::Dummy(ImVec2(0.0f, verticalPadding)); + + ImGui::End(); + } + ImGui::End(); + } + + smart_refctd_ptr imGUI; + + // descriptor set + smart_refctd_ptr subAllocDS; + enum E_RENDER_VIEWS : uint8_t + { + ERV_MAIN_VIEW, + ERV_SOLID_ANGLE_VIEW, + Count + }; + SubAllocatedDescriptorSet::value_type renderColorViewDescIndices[E_RENDER_VIEWS::Count] = { SubAllocatedDescriptorSet::invalid_value, SubAllocatedDescriptorSet::invalid_value }; + // + Camera camera = Camera(core::vectorSIMDf(0, 0, 0), core::vectorSIMDf(0, 0, 0), core::matrix4SIMD()); + // mutables + float32_t4x4 m_OBBModelMatrix{ + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 12.0f, 1.0f + }; + + //std::string_view objectName; + TransformRequestParams transformParams; + TransformReturnInfo transformReturnInfo; + + float fov = 90.f, zNear = 0.1f, zFar = 10000.f, moveSpeed = 1.f, rotateSpeed = 1.f; + float viewWidth = 10.f; + float camYAngle = 90.f / 180.f * 3.14159f; + float camXAngle = 0.f / 180.f * 3.14159f; + //uint16_t gcIndex = {}; // note: this is dirty however since I assume only single object in scene I can leave it now, when this example is upgraded to support multiple objects this needs to be changed + bool isPerspective = true, isLH = true, flipGizmoY = true, move = true; + bool firstFrame = true; + } interface; +}; + +NBL_MAIN_FUNC(SolidAngleVisualizer) \ No newline at end of file diff --git a/72_SolidAngleVisualizer/pipeline.groovy b/72_SolidAngleVisualizer/pipeline.groovy new file mode 100644 index 000000000..7b7c9702a --- /dev/null +++ b/72_SolidAngleVisualizer/pipeline.groovy @@ -0,0 +1,50 @@ +import org.DevshGraphicsProgramming.Agent +import org.DevshGraphicsProgramming.BuilderInfo +import org.DevshGraphicsProgramming.IBuilder + +class CUIBuilder extends IBuilder +{ + public CUIBuilder(Agent _agent, _info) + { + super(_agent, _info) + } + + @Override + public boolean prepare(Map axisMapping) + { + return true + } + + @Override + public boolean build(Map axisMapping) + { + IBuilder.CONFIGURATION config = axisMapping.get("CONFIGURATION") + IBuilder.BUILD_TYPE buildType = axisMapping.get("BUILD_TYPE") + + def nameOfBuildDirectory = getNameOfBuildDirectory(buildType) + def nameOfConfig = getNameOfConfig(config) + + agent.execute("cmake --build ${info.rootProjectPath}/${nameOfBuildDirectory}/${info.targetProjectPathRelativeToRoot} --target ${info.targetBaseName} --config ${nameOfConfig} -j12 -v") + + return true + } + + @Override + public boolean test(Map axisMapping) + { + return true + } + + @Override + public boolean install(Map axisMapping) + { + return true + } +} + +def create(Agent _agent, _info) +{ + return new CUIBuilder(_agent, _info) +} + +return this \ No newline at end of file diff --git a/72_SolidAngleVisualizer/src/transform.cpp b/72_SolidAngleVisualizer/src/transform.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/CMakeLists.txt b/CMakeLists.txt index 574925e97..fddafdac1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,7 @@ if(NBL_BUILD_EXAMPLES) add_subdirectory(70_FLIPFluids) add_subdirectory(71_RayTracingPipeline) + add_subdirectory(72_SolidAngleVisualizer) # add new examples *before* NBL_GET_ALL_TARGETS invocation, it gathers recursively all targets created so far in this subdirectory NBL_GET_ALL_TARGETS(TARGETS) From 93861bd59f85721993472e3de67f23bec6170363 Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Sat, 6 Dec 2025 21:02:46 +0300 Subject: [PATCH 2/9] Make camera account for up direction, corrected framebuffer resolutions for both views, solid angle shader now outputs correct cube vertices correctly --- .../hlsl/SolidAngleVis.frag.hlsl | 157 +++++++++++------- 72_SolidAngleVisualizer/include/transform.hpp | 2 +- 72_SolidAngleVisualizer/main.cpp | 134 ++++++++------- .../include/nbl/examples/cameras/CCamera.hpp | 50 +++--- 4 files changed, 190 insertions(+), 153 deletions(-) diff --git a/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl b/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl index d783a5b37..2ad766c8a 100644 --- a/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl +++ b/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl @@ -9,7 +9,7 @@ using namespace ext::FullScreenTriangle; [[vk::push_constant]] struct PushConstants pc; -static const float CIRCLE_RADIUS = 0.45f; +static const float CIRCLE_RADIUS = 0.75f; // --- Geometry Utils --- @@ -33,17 +33,23 @@ static float3 corners[8]; static float3 faceCenters[6] = { float3(0,0,0), float3(0,0,0), float3(0,0,0), float3(0,0,0), float3(0,0,0), float3(0,0,0) }; static float2 projCorners[8]; +static bool cornerVisible[8]; // Converts UV into centered, aspect-corrected NDC circle space float2 toCircleSpace(float2 uv) { - float aspect = pc.viewport.z / pc.viewport.w; - float2 centered = uv - 0.5f; - centered.x *= aspect; - return centered; + // Map [0,1] UV to [-1,1] + float2 p = uv * 2.0f - 1.0f; + + // Correct aspect ratio + float aspect = pc.viewport.z / pc.viewport.w; // width / height + p.x *= aspect; + + return p; } + // Distance to a 2D line segment float sdSegment(float2 p, float2 a, float2 b) { @@ -54,9 +60,18 @@ float sdSegment(float2 p, float2 a, float2 b) } // TODO: Hemispherical Projection (Solid Angle / Orthographic/Lambertian Projection) -float2 project(float3 p) +bool projectToOrthoSphere(float3 p, out float2 uv) { - return normalize(p).xy; + float3 n = normalize(p); // direction to sphere + + // hemisphere (Z > 0) + if (n.z <= 0.0) + return false; + + // orthographic projection (drop Z) + uv = n.xy; + + return true; // valid } void computeCubeGeo() @@ -66,71 +81,72 @@ void computeCubeGeo() float3 localPos = float3(i % 2, (i / 2) % 2, (i / 4) % 2) * 2.0f - 1.0f; float3 worldPos = mul(pc.modelMatrix, float4(localPos, 1.0f)).xyz; - corners[i] = worldPos; + corners[i] = worldPos.xyz; faceCenters[i/4] += worldPos / 4.0f; faceCenters[2+i%2] += worldPos / 4.0f; faceCenters[4+(i/2)%2] += worldPos / 4.0f; - float3 viewPos = worldPos; - projCorners[i] = project(viewPos); + float3 viewPos = worldPos.xyz; + cornerVisible[i] = projectToOrthoSphere(viewPos, projCorners[i]); + projCorners[i] *= CIRCLE_RADIUS; // scale to circle radius } } -int getVisibilityCount(int2 faces, float3 cameraPos) -{ - float3x3 rotMatrix = (float3x3)pc.modelMatrix; - float3 n_world_f1 = mul(rotMatrix, localNormals[faces.x]); - float3 n_world_f2 = mul(rotMatrix, localNormals[faces.y]); +// int getVisibilityCount(int2 faces, float3 cameraPos) +// { +// float3x3 rotMatrix = (float3x3)pc.modelMatrix; +// float3 n_world_f1 = mul(rotMatrix, localNormals[faces.x]); +// float3 n_world_f2 = mul(rotMatrix, localNormals[faces.y]); - float3 viewVec_f1 = faceCenters[faces.x] - cameraPos; - float3 viewVec_f2 = faceCenters[faces.y] - cameraPos; +// float3 viewVec_f1 = faceCenters[faces.x] - cameraPos; +// float3 viewVec_f2 = faceCenters[faces.y] - cameraPos; - // Face is visible if its outward normal points towards the origin (camera). - bool visible1 = dot(n_world_f1, viewVec_f1) < 0.0f; - bool visible2 = dot(n_world_f2, viewVec_f2) < 0.0f; +// // Face is visible if its outward normal points towards the origin (camera). +// bool visible1 = dot(n_world_f1, viewVec_f1) < 0.0f; +// bool visible2 = dot(n_world_f2, viewVec_f2) < 0.0f; - // Determine Line Style: - bool isSilhouette = visible1 != visible2; // One face visible, the other hidden - bool isInner = visible1 && visible2; // Both faces visible +// // Determine Line Style: +// bool isSilhouette = visible1 != visible2; // One face visible, the other hidden +// bool isInner = visible1 && visible2; // Both faces visible - int visibilityCount = 0; - if (isSilhouette) - { - visibilityCount = 1; - } - else if (isInner) - { - visibilityCount = 2; - } - - return visibilityCount; -} - -void drawLine(float2 p, int a, int b, int visibilityCount, inout float4 color, float aaWidth) -{ - if (visibilityCount > 0) - { - float3 A = corners[a]; - float3 B = corners[b]; - - float avgDepth = (length(A) + length(B)) * 0.5f; - float referenceDepth = 3.0f; - float depthScale = referenceDepth / avgDepth; - - float baseWidth = (visibilityCount == 1) ? 0.005f : 0.002f; - float intensity = (visibilityCount == 1) ? 1.0f : 0.5f; - float4 edgeColor = (visibilityCount == 1) ? float4(0.0f, 0.5f, 1.0f, 1.0f) : float4(1.0f, 0.0f, 0.0f, 1.0f); // Blue vs Red +// int visibilityCount = 0; +// if (isSilhouette) +// { +// visibilityCount = 1; +// } +// else if (isInner) +// { +// visibilityCount = 2; +// } + +// return visibilityCount; +// } + +// void drawLine(float2 p, int a, int b, int visibilityCount, inout float4 color, float aaWidth) +// { +// if (visibilityCount > 0) +// { +// float3 A = corners[a]; +// float3 B = corners[b]; + +// float avgDepth = (length(A) + length(B)) * 0.5f; +// float referenceDepth = 3.0f; +// float depthScale = referenceDepth / avgDepth; + +// float baseWidth = (visibilityCount == 1) ? 0.005f : 0.002f; +// float intensity = (visibilityCount == 1) ? 1.0f : 0.5f; +// float4 edgeColor = (visibilityCount == 1) ? float4(0.0f, 0.5f, 1.0f, 1.0f) : float4(1.0f, 0.0f, 0.0f, 1.0f); // Blue vs Red - float width = min(baseWidth * depthScale, 0.03f); +// float width = min(baseWidth * depthScale, 0.03f); - float dist = sdSegment(p, projCorners[a], projCorners[b]); +// float dist = sdSegment(p, projCorners[a], projCorners[b]); - float alpha = 1.0f - smoothstep(width - aaWidth, width + aaWidth, dist); +// float alpha = 1.0f - smoothstep(width - aaWidth, width + aaWidth, dist); - color += edgeColor * alpha * intensity; - } -} +// color += edgeColor * alpha * intensity; +// } +// } void drawRing(float2 p, inout float4 color, float aaWidth) { @@ -149,6 +165,12 @@ void drawRing(float2 p, inout float4 color, float aaWidth) color = max(color, float4(1.0, 1.0, 1.0, 1.0) * ringAlpha); } +float plotPoint(float2 uv, float2 p, float r) +{ + return step(length(uv - p), r); +} + + [[vk::location(0)]] float32_t4 main(SVertexAttributes vx) : SV_Target0 { float3 cameraPos = float3(0, 0, 0); // Camera at origin @@ -159,16 +181,25 @@ void drawRing(float2 p, inout float4 color, float aaWidth) float aaWidth = max(fwidth(p.x), fwidth(p.y)); - for (int j = 0; j < 12; j++) + float pointMask = 0.0; + for (int i=0; i<8; i++) { - int a = j % 4 * (j < 4 ? 1 : 2) - (j / 4 == 1 ? j % 2 : 0); - int b = a + (4 >> (j / 4)); - - int2 faces = edgeToFaces[j]; - int visibilityCount = getVisibilityCount(faces, cameraPos); - drawLine(p, a, b, visibilityCount, color, aaWidth); + if (cornerVisible[i]) + pointMask += plotPoint(p, projCorners[i], 0.015f); } + color += pointMask * float4(1,0,0,1); // red points + + // for (int j = 0; j < 12; j++) + // { + // int a = j % 4 * (j < 4 ? 1 : 2) - (j / 4 == 1 ? j % 2 : 0); + // int b = a + (4 >> (j / 4)); + + // // int2 faces = edgeToFaces[j]; + // // int visibilityCount = getVisibilityCount(faces, cameraPos); + // // drawLine(p, a, b, visibilityCount, color, aaWidth); + // } + drawRing(p, color, aaWidth); return color; diff --git a/72_SolidAngleVisualizer/include/transform.hpp b/72_SolidAngleVisualizer/include/transform.hpp index 002a9d215..5061ebd49 100644 --- a/72_SolidAngleVisualizer/include/transform.hpp +++ b/72_SolidAngleVisualizer/include/transform.hpp @@ -19,7 +19,7 @@ struct TransformRequestParams struct TransformReturnInfo { - nbl::hlsl::uint16_t2 sceneResolution = { 2048,1024 }; + nbl::hlsl::uint16_t2 sceneResolution = { 0, 0 }; bool isGizmoWindowHovered; bool isGizmoBeingUsed; }; diff --git a/72_SolidAngleVisualizer/main.cpp b/72_SolidAngleVisualizer/main.cpp index b6d723e70..1025eb067 100644 --- a/72_SolidAngleVisualizer/main.cpp +++ b/72_SolidAngleVisualizer/main.cpp @@ -5,7 +5,6 @@ #include "common.hpp" #include "app_resources/hlsl/common.hlsl" - #include "nbl/ext/FullScreenTriangle/FullScreenTriangle.h" /* @@ -319,10 +318,13 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR // CPU events update(nextPresentationTimestamp); - const auto& virtualWindowRes = interface.transformReturnInfo.sceneResolution; - // TODO: check main frame buffer too - if (!m_solidAngleViewFramebuffer || m_solidAngleViewFramebuffer->getCreationParameters().width != virtualWindowRes[0] || m_solidAngleViewFramebuffer->getCreationParameters().height != virtualWindowRes[1]) - recreateFramebuffer(virtualWindowRes); + { + const auto& virtualSolidAngleWindowRes = interface.solidAngleViewTransformReturnInfo.sceneResolution; + const auto& virtualMainWindowRes = interface.mainViewTransformReturnInfo.sceneResolution; + if (!m_solidAngleViewFramebuffer || m_solidAngleViewFramebuffer->getCreationParameters().width != virtualSolidAngleWindowRes[0] || m_solidAngleViewFramebuffer->getCreationParameters().height != virtualSolidAngleWindowRes[1] || + !m_mainViewFramebuffer || m_mainViewFramebuffer->getCreationParameters().width != virtualMainWindowRes[0] || m_mainViewFramebuffer->getCreationParameters().height != virtualMainWindowRes[1]) + recreateFramebuffer(); + } // const auto resourceIx = m_realFrameIx % MaxFramesInFlight; @@ -334,6 +336,7 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR const IGPUCommandBuffer::SClearColorValue clearValue = { .float32 = {0.f,0.f,0.f,1.f} }; if (m_solidAngleViewFramebuffer) { + auto creationParams = m_solidAngleViewFramebuffer->getCreationParameters(); cb->beginDebugMarker("Draw Circle View Frame"); { const IGPUCommandBuffer::SClearDepthStencilValue farValue = { .depth = 0.f }; @@ -344,7 +347,7 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR .depthStencilClearValues = &farValue, .renderArea = { .offset = {0,0}, - .extent = {virtualWindowRes[0],virtualWindowRes[1]} + .extent = {creationParams.width, creationParams.height} } }; beginRenderpass(cb, renderpassInfo); @@ -353,7 +356,7 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR { PushConstants pc{ .modelMatrix = hlsl::float32_t3x4(hlsl::transpose(interface.m_OBBModelMatrix)), - .viewport = { 0.f,0.f,static_cast(virtualWindowRes[0]),static_cast(virtualWindowRes[1]) } + .viewport = { 0.f,0.f,static_cast(creationParams.width),static_cast(creationParams.height) } }; auto pipeline = m_visualizationPipeline; cb->bindGraphicsPipeline(pipeline.get()); @@ -369,6 +372,7 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR { cb->beginDebugMarker("Main Scene Frame"); { + auto creationParams = m_mainViewFramebuffer->getCreationParameters(); const IGPUCommandBuffer::SClearDepthStencilValue farValue = { .depth = 0.f }; const IGPUCommandBuffer::SRenderpassBeginInfo renderpassInfo = { @@ -377,7 +381,7 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR .depthStencilClearValues = &farValue, .renderArea = { .offset = {0,0}, - .extent = {virtualWindowRes[0],virtualWindowRes[1]} + .extent = {creationParams.width, creationParams.height} } }; beginRenderpass(cb, renderpassInfo); @@ -404,12 +408,12 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR // TODO: a better way to get identity matrix float32_t3x4 origin = { - 0.2f,0.0f,0.0f,0.0f, - 0.0f,0.2f,0.0f,0.0f, - 0.0f,0.0f,0.2f,0.0f + 1.0f,0.0f,0.0f,0.0f, + 0.0f,1.0f,0.0f,0.0f, + 0.0f,0.0f,1.0f,0.0f }; memcpy(&instance.world, &origin, sizeof(instance.world)); - instance.packedGeo = m_renderer->getGeometries().data() + 3; // sphere + instance.packedGeo = m_renderer->getGeometries().data() + 2; // disk m_renderer->render(cb, viewParams); } cb->endRenderPass(); @@ -575,7 +579,7 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR ); keyboard.consumeEvents([&](const IKeyboardEventChannel::range_t& events) -> void { - //if (interface.move) + if (interface.move) camera.keyboardProcess(events); // don't capture the events, only let camera handle them with its impl for (const auto& e : events) // here capture @@ -606,9 +610,10 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR interface.imGUI->update(params); } - void recreateFramebuffer(const uint16_t2 resolution) + void recreateFramebuffer() { - auto createImageAndView = [&](E_FORMAT format)->smart_refctd_ptr + + auto createImageAndView = [&](const uint16_t2 resolution, E_FORMAT format)->smart_refctd_ptr { auto image = m_device->createImage({ { .type = IGPUImage::ET_2D, @@ -632,29 +637,32 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR smart_refctd_ptr solidAngleView; smart_refctd_ptr mainView; + const uint16_t2 solidAngleViewRes = interface.solidAngleViewTransformReturnInfo.sceneResolution; + const uint16_t2 mainViewRes = interface.mainViewTransformReturnInfo.sceneResolution; + // detect window minimization - if (resolution.x < 0x4000 && resolution.y < 0x4000) + if (solidAngleViewRes.x < 0x4000 && solidAngleViewRes.y < 0x4000 || + mainViewRes.x < 0x4000 && mainViewRes.y < 0x4000) { - solidAngleView = createImageAndView(finalSceneRenderFormat); - auto solidAngleDepthView = createImageAndView(sceneRenderDepthFormat); + solidAngleView = createImageAndView(solidAngleViewRes, finalSceneRenderFormat); + auto solidAngleDepthView = createImageAndView(solidAngleViewRes, sceneRenderDepthFormat); m_solidAngleViewFramebuffer = m_device->createFramebuffer({ { .renderpass = m_solidAngleRenderpass, .depthStencilAttachments = &solidAngleDepthView.get(), .colorAttachments = &solidAngleView.get(), - .width = resolution.x, - .height = resolution.y + .width = solidAngleViewRes.x, + .height = solidAngleViewRes.y } }); - mainView = createImageAndView(finalSceneRenderFormat); - auto mainDepthView = createImageAndView(sceneRenderDepthFormat); + mainView = createImageAndView(mainViewRes, finalSceneRenderFormat); + auto mainDepthView = createImageAndView(mainViewRes, sceneRenderDepthFormat); m_mainViewFramebuffer = m_device->createFramebuffer({ { .renderpass = m_mainRenderpass, .depthStencilAttachments = &mainDepthView.get(), .colorAttachments = &mainView.get(), - .width = resolution.x, - .height = resolution.y + .width = mainViewRes.x, + .height = mainViewRes.y } }); - } else { @@ -715,6 +723,13 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR // we create the Descriptor Set with a few slots extra to spare, so we don't have to `waitIdle` the device whenever ImGUI virtual window resizes constexpr static inline auto MaxImGUITextures = 2u + MaxFramesInFlight; + constexpr static inline float32_t4x4 OBBModelMatrixDefault + { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 6.0f, 1.0f + }; // smart_refctd_ptr m_scene; smart_refctd_ptr m_solidAngleRenderpass; @@ -722,7 +737,7 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR smart_refctd_ptr m_renderer; smart_refctd_ptr m_solidAngleViewFramebuffer; smart_refctd_ptr m_mainViewFramebuffer; - smart_refctd_ptr m_visualizationPipeline; + smart_refctd_ptr m_visualizationPipeline; // smart_refctd_ptr m_semaphore; uint64_t m_realFrameIx = 0; @@ -733,19 +748,6 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR // UI stuff struct CInterface { - void cameraToHome() - { - core::vectorSIMDf cameraPosition(-3.0f, 3.0f, 6.0f); - core::vectorSIMDf cameraTarget(0.f, 0.f, 6.f); - const static core::vectorSIMDf up(0.f, 1.f, 0.f); - - camera.setPosition(cameraPosition); - camera.setTarget(cameraTarget); - camera.setBackupUpVector(up); - - camera.recomputeViewMatrix(); - } - void operator()() { ImGuiIO& io = ImGui::GetIO(); @@ -773,7 +775,7 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR return projection; }()); - ImGuizmo::SetOrthographic(false); + ImGuizmo::SetOrthographic(!isPerspective); ImGuizmo::BeginFrame(); ImGui::SetNextWindowPos(ImVec2(1024, 100), ImGuiCond_Appearing); @@ -830,7 +832,12 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR if (viewDirty || firstFrame) { - cameraToHome(); + camera.setPosition(cameraIntialPosition); + camera.setTarget(cameraInitialTarget); + camera.setBackupUpVector(cameraInitialUp); + camera.setUpVector(cameraInitialUp); + + camera.recomputeViewMatrix(); } firstFrame = false; @@ -895,19 +902,15 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR * note it also modifies input view matrix but projection matrix is immutable */ - if (ImGui::IsKeyPressed(ImGuiKey_Home)) - { - cameraToHome(); - } + // No need because camera already has this functionality + // if (ImGui::IsKeyPressed(ImGuiKey_Home)) + // { + // cameraToHome(); + // } if (ImGui::IsKeyPressed(ImGuiKey_End)) { - m_OBBModelMatrix = { - 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 12.0f, 1.0f - }; + m_OBBModelMatrix = OBBModelMatrixDefault; } static struct @@ -930,10 +933,14 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR imguizmoM16InOut.projection[1][1] *= -1.f; // https://johannesugb.github.io/gpu-programming/why-do-opengl-proj-matrices-fail-in-vulkan/ transformParams.editTransformDecomposition = true; - transformReturnInfo = EditTransform(&imguizmoM16InOut.view[0][0], &imguizmoM16InOut.projection[0][0], &imguizmoM16InOut.model[0][0], transformParams); + mainViewTransformReturnInfo = EditTransform(&imguizmoM16InOut.view[0][0], &imguizmoM16InOut.projection[0][0], &imguizmoM16InOut.model[0][0], transformParams); + // MODEL: Zup -> Yup + + m_OBBModelMatrix = imguizmoM16InOut.model; // TODO: camera stops when cursor hovers gizmo, but we also want to stop when gizmo is being used - move = (ImGui::IsMouseDown(ImGuiMouseButton_Left) || transformReturnInfo.isGizmoWindowHovered) && (!transformReturnInfo.isGizmoBeingUsed); + move = (ImGui::IsMouseDown(ImGuiMouseButton_Left) || mainViewTransformReturnInfo.isGizmoWindowHovered) && (!mainViewTransformReturnInfo.isGizmoBeingUsed); + } // to Nabla + update camera & model matrices @@ -957,9 +964,12 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR ImGui::SetNextWindowSize(ImVec2(800, 800), ImGuiCond_Appearing); ImGui::SetNextWindowPos(ImVec2(1240, 20), ImGuiCond_Appearing); static bool isOpen = true; - ImGui::Begin("Solid angle view", &isOpen, 0); + ImGui::Begin("Projected Solid Angle View", &isOpen, 0); ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(); + solidAngleViewTransformReturnInfo.sceneResolution = uint16_t2(static_cast(contentRegionSize.x), static_cast(contentRegionSize.y)); + solidAngleViewTransformReturnInfo.isGizmoBeingUsed = false; // not used in this view + solidAngleViewTransformReturnInfo.isGizmoWindowHovered = false; // not used in this view ImGui::Image({ renderColorViewDescIndices[ERV_SOLID_ANGLE_VIEW] }, contentRegionSize); ImGui::End(); } @@ -1081,21 +1091,19 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR // Camera camera = Camera(core::vectorSIMDf(0, 0, 0), core::vectorSIMDf(0, 0, 0), core::matrix4SIMD()); // mutables - float32_t4x4 m_OBBModelMatrix{ - 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 12.0f, 1.0f - }; + float32_t4x4 m_OBBModelMatrix = OBBModelMatrixDefault; //std::string_view objectName; TransformRequestParams transformParams; - TransformReturnInfo transformReturnInfo; + TransformReturnInfo mainViewTransformReturnInfo; + TransformReturnInfo solidAngleViewTransformReturnInfo; + + const static inline core::vectorSIMDf cameraIntialPosition{ -3.0f, 6.0f, 3.0f }; + const static inline core::vectorSIMDf cameraInitialTarget{ 0.f, 0.0f, 3.f }; + const static inline core::vectorSIMDf cameraInitialUp{ 0.f, 0.f, 1.f }; float fov = 90.f, zNear = 0.1f, zFar = 10000.f, moveSpeed = 1.f, rotateSpeed = 1.f; float viewWidth = 10.f; - float camYAngle = 90.f / 180.f * 3.14159f; - float camXAngle = 0.f / 180.f * 3.14159f; //uint16_t gcIndex = {}; // note: this is dirty however since I assume only single object in scene I can leave it now, when this example is upgraded to support multiple objects this needs to be changed bool isPerspective = true, isLH = true, flipGizmoY = true, move = true; bool firstFrame = true; diff --git a/common/include/nbl/examples/cameras/CCamera.hpp b/common/include/nbl/examples/cameras/CCamera.hpp index 3b3cd38d8..f35cd341a 100644 --- a/common/include/nbl/examples/cameras/CCamera.hpp +++ b/common/include/nbl/examples/cameras/CCamera.hpp @@ -149,38 +149,36 @@ class Camera if(ev.type == nbl::ui::SMouseEvent::EET_MOVEMENT && mouseDown) { nbl::core::vectorSIMDf pos = getPosition(); - nbl::core::vectorSIMDf localTarget = getTarget() - pos; - - // Get Relative Rotation for localTarget in Radians - float relativeRotationX, relativeRotationY; - relativeRotationY = atan2(localTarget.X, localTarget.Z); - const double z1 = nbl::core::sqrt(localTarget.X*localTarget.X + localTarget.Z*localTarget.Z); - relativeRotationX = atan2(z1, localTarget.Y) - nbl::core::PI()/2; - - constexpr float RotateSpeedScale = 0.003f; - relativeRotationX -= ev.movementEvent.relativeMovementY * rotateSpeed * RotateSpeedScale * -1.0f; - float tmpYRot = ev.movementEvent.relativeMovementX * rotateSpeed * RotateSpeedScale * -1.0f; + nbl::core::vectorSIMDf upVector = getUpVector(); + nbl::core::vectorSIMDf forward = nbl::core::normalize(getTarget() - pos); + + nbl::core::vectorSIMDf right = nbl::core::normalize(nbl::core::cross(forward, upVector)); + nbl::core::vectorSIMDf up = nbl::core::normalize(nbl::core::cross(right, forward)); + + constexpr float RotateSpeedScale = 0.003f; + float pitchDelta = ev.movementEvent.relativeMovementY * rotateSpeed * RotateSpeedScale * -1.0f; + float yawDelta = ev.movementEvent.relativeMovementX * rotateSpeed * RotateSpeedScale * -1.0f; if (leftHanded) - relativeRotationY -= tmpYRot; - else - relativeRotationY += tmpYRot; + yawDelta = -yawDelta; - const double MaxVerticalAngle = nbl::core::radians(88.0f); + // Clamp pitch BEFORE applying rotation + const float MaxVerticalAngle = nbl::core::radians(88.0f); + float currentPitch = asin(nbl::core::dot(forward, upVector).X); + float newPitch = nbl::core::clamp(currentPitch + pitchDelta, -MaxVerticalAngle, MaxVerticalAngle); + pitchDelta = newPitch - currentPitch; - if (relativeRotationX > MaxVerticalAngle*2 && relativeRotationX < 2 * nbl::core::PI()-MaxVerticalAngle) - relativeRotationX = 2 * nbl::core::PI()-MaxVerticalAngle; - else - if (relativeRotationX > MaxVerticalAngle && relativeRotationX < 2 * nbl::core::PI()-MaxVerticalAngle) - relativeRotationX = MaxVerticalAngle; + // Create rotation quaternions using axis-angle method + nbl::core::quaternion pitchRot = nbl::core::quaternion::fromAngleAxis(pitchDelta, right); + nbl::core::quaternion yawRot = nbl::core::quaternion::fromAngleAxis(yawDelta, upVector); + nbl::core::quaternion combinedRot = yawRot * pitchRot; - localTarget.set(0,0, nbl::core::max(1.f, nbl::core::length(pos)[0]), 1.f); + // Apply to forward vector + forward = nbl::core::normalize(combinedRot.transformVect(forward)); - nbl::core::matrix3x4SIMD mat; - mat.setRotation(nbl::core::quaternion(relativeRotationX, relativeRotationY, 0)); - mat.transformVect(localTarget); - - setTarget(localTarget + pos); + // Set new target + float targetDistance = nbl::core::length(getTarget() - pos).X; + setTarget(pos + forward * targetDistance); } } } From adb15edd201e82cbc9ed3526bbfccfc67ccdf4ff Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Sun, 7 Dec 2025 00:12:56 +0300 Subject: [PATCH 3/9] sphere arc "cube edge" in solid angle view, more reliable resizing of windows --- .../hlsl/SolidAngleVis.frag.hlsl | 218 ++++++++---------- 72_SolidAngleVisualizer/main.cpp | 24 +- 2 files changed, 107 insertions(+), 135 deletions(-) diff --git a/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl b/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl index 2ad766c8a..badf1e4be 100644 --- a/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl +++ b/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl @@ -32,8 +32,7 @@ static const float3 localNormals[6] = { static float3 corners[8]; static float3 faceCenters[6] = { float3(0,0,0), float3(0,0,0), float3(0,0,0), float3(0,0,0), float3(0,0,0), float3(0,0,0) }; -static float2 projCorners[8]; -static bool cornerVisible[8]; + // Converts UV into centered, aspect-corrected NDC circle space @@ -46,32 +45,7 @@ float2 toCircleSpace(float2 uv) float aspect = pc.viewport.z / pc.viewport.w; // width / height p.x *= aspect; - return p; -} - - -// Distance to a 2D line segment -float sdSegment(float2 p, float2 a, float2 b) -{ - float2 pa = p - a; - float2 ba = b - a; - float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0f, 1.0f); - return length(pa - ba * h); -} - -// TODO: Hemispherical Projection (Solid Angle / Orthographic/Lambertian Projection) -bool projectToOrthoSphere(float3 p, out float2 uv) -{ - float3 n = normalize(p); // direction to sphere - - // hemisphere (Z > 0) - if (n.z <= 0.0) - return false; - - // orthographic projection (drop Z) - uv = n.xy; - - return true; // valid + return p * CIRCLE_RADIUS; } void computeCubeGeo() @@ -86,121 +60,121 @@ void computeCubeGeo() faceCenters[i/4] += worldPos / 4.0f; faceCenters[2+i%2] += worldPos / 4.0f; faceCenters[4+(i/2)%2] += worldPos / 4.0f; - - float3 viewPos = worldPos.xyz; - cornerVisible[i] = projectToOrthoSphere(viewPos, projCorners[i]); - projCorners[i] *= CIRCLE_RADIUS; // scale to circle radius } } -// int getVisibilityCount(int2 faces, float3 cameraPos) -// { -// float3x3 rotMatrix = (float3x3)pc.modelMatrix; -// float3 n_world_f1 = mul(rotMatrix, localNormals[faces.x]); -// float3 n_world_f2 = mul(rotMatrix, localNormals[faces.y]); - -// float3 viewVec_f1 = faceCenters[faces.x] - cameraPos; -// float3 viewVec_f2 = faceCenters[faces.y] - cameraPos; - -// // Face is visible if its outward normal points towards the origin (camera). -// bool visible1 = dot(n_world_f1, viewVec_f1) < 0.0f; -// bool visible2 = dot(n_world_f2, viewVec_f2) < 0.0f; - -// // Determine Line Style: -// bool isSilhouette = visible1 != visible2; // One face visible, the other hidden -// bool isInner = visible1 && visible2; // Both faces visible - -// int visibilityCount = 0; -// if (isSilhouette) -// { -// visibilityCount = 1; -// } -// else if (isInner) -// { -// visibilityCount = 2; -// } - -// return visibilityCount; -// } - -// void drawLine(float2 p, int a, int b, int visibilityCount, inout float4 color, float aaWidth) -// { -// if (visibilityCount > 0) -// { -// float3 A = corners[a]; -// float3 B = corners[b]; - -// float avgDepth = (length(A) + length(B)) * 0.5f; -// float referenceDepth = 3.0f; -// float depthScale = referenceDepth / avgDepth; - -// float baseWidth = (visibilityCount == 1) ? 0.005f : 0.002f; -// float intensity = (visibilityCount == 1) ? 1.0f : 0.5f; -// float4 edgeColor = (visibilityCount == 1) ? float4(0.0f, 0.5f, 1.0f, 1.0f) : float4(1.0f, 0.0f, 0.0f, 1.0f); // Blue vs Red - -// float width = min(baseWidth * depthScale, 0.03f); - -// float dist = sdSegment(p, projCorners[a], projCorners[b]); - -// float alpha = 1.0f - smoothstep(width - aaWidth, width + aaWidth, dist); - -// color += edgeColor * alpha * intensity; -// } -// } - -void drawRing(float2 p, inout float4 color, float aaWidth) +float4 drawRing(float2 p, float aaWidth) { float positionLength = length(p); - - // Mask to cut off drawing outside the circle - // float circleMask = 1.0f - smoothstep(CIRCLE_RADIUS, CIRCLE_RADIUS + aaWidth, positionLength); - // color *= circleMask; // Add a white background circle ring - float ringWidth = 0.005f; + float ringWidth = 0.01f; float ringDistance = abs(positionLength - CIRCLE_RADIUS); float ringAlpha = 1.0f - smoothstep(ringWidth - aaWidth, ringWidth + aaWidth, ringDistance); - // Ring color is now white - color = max(color, float4(1.0, 1.0, 1.0, 1.0) * ringAlpha); + return ringAlpha.xxxx; } -float plotPoint(float2 uv, float2 p, float r) +// Check if a face on the hemisphere is visible from camera at origin +bool isFaceVisible(float3 faceCenter, float3 faceNormal) { - return step(length(uv - p), r); + // Face is visible if normal points toward camera (at origin) + float3 viewVec = -normalize(faceCenter); // Vector from face to camera + return dot(faceNormal, viewVec) > 0.0f; } +int getEdgeVisibility(int edgeIdx, float3 cameraPos) +{ + int2 faces = edgeToFaces[edgeIdx]; + + // Transform normals to world space + float3x3 rotMatrix = (float3x3)pc.modelMatrix; + float3 n_world_f1 = mul(rotMatrix, localNormals[faces.x]); + float3 n_world_f2 = mul(rotMatrix, localNormals[faces.y]); + + bool visible1 = isFaceVisible(faceCenters[faces.x], n_world_f1); + bool visible2 = isFaceVisible(faceCenters[faces.y], n_world_f2); + + // Silhouette: exactly one face visible + if (visible1 != visible2) return 1; + + // Inner edge: both faces visible + if (visible1 && visible2) return 2; + + // Hidden edge: both faces hidden + return 0; +} + +// Draw great circle arc in fragment shader +float4 drawGreatCircleArc(float3 fragPos, int2 edgeVerts, int visibility, float aaWidth) +{ + if (visibility == 0) return float4(0,0,0,0); // Hidden edge + + float3 v0 = normalize(corners[edgeVerts.x]); + float3 v1 = normalize(corners[edgeVerts.y]); + float3 p = normalize(fragPos); // Current point on hemisphere + + // Great circle plane normal + float3 arcNormal = normalize(cross(v0, v1)); + + // Distance to great circle + float dist = abs(dot(p, arcNormal)); + + // Check if point is within arc bounds + float dotMid = dot(v0, v1); + bool onArc = (dot(p, v0) >= dotMid) && (dot(p, v1) >= dotMid); + + if (!onArc) return float4(0,0,0,0); + + // Depth-based width scaling + float avgDepth = (length(corners[edgeVerts.x]) + length(corners[edgeVerts.y])) * 0.5f; + float depthScale = 3.0f / avgDepth; + + float baseWidth = (visibility == 1) ? 0.01f : 0.005f; + float width = min(baseWidth * depthScale, 0.02f); + + float alpha = 1.0f - smoothstep(width - aaWidth, width + aaWidth, dist); + + float4 edgeColor = (visibility == 1) ? + float4(0.0f, 0.5f, 1.0f, 1.0f) : // Silhouette: blue + float4(1.0f, 0.0f, 0.0f, 1.0f); // Inner: red + + float intensity = (visibility == 1) ? 1.0f : 0.5f; + return edgeColor * alpha * intensity; +} [[vk::location(0)]] float32_t4 main(SVertexAttributes vx) : SV_Target0 { - float3 cameraPos = float3(0, 0, 0); // Camera at origin - float2 p = toCircleSpace(vx.uv); + float3 cameraPos = float3(0, 0, 0); float4 color = float4(0, 0, 0, 0); - - computeCubeGeo(); + float2 p = toCircleSpace(vx.uv); - float aaWidth = max(fwidth(p.x), fwidth(p.y)); - - float pointMask = 0.0; - for (int i=0; i<8; i++) + // Convert 2D disk position to 3D hemisphere position + // p is in range [-CIRCLE_RADIUS, CIRCLE_RADIUS] + float2 normalized = p / CIRCLE_RADIUS; // Now in range [-1, 1] + float r2 = dot(normalized, normalized); + + if (r2 > 1.0f) + discard; + + // Convert UV to 3D position on hemisphere + float3 spherePos = normalize(float3(normalized.x, normalized.y, sqrt(1 - r2))); + + computeCubeGeo(); // Your existing function + + float aaWidth = length(float2(ddx(p.x), ddy(p.y))); + + // Draw edges as great circle arcs + for (int j = 0; j < 12; j++) { - if (cornerVisible[i]) - pointMask += plotPoint(p, projCorners[i], 0.015f); + int a = j % 4 * (j < 4 ? 1 : 2) - (j / 4 == 1 ? j % 2 : 0); + int b = a + (4 >> (j / 4)); + + int visibility = getEdgeVisibility(j, cameraPos); + color += drawGreatCircleArc(spherePos, int2(a, b), visibility, aaWidth); } - - color += pointMask * float4(1,0,0,1); // red points - - // for (int j = 0; j < 12; j++) - // { - // int a = j % 4 * (j < 4 ? 1 : 2) - (j / 4 == 1 ? j % 2 : 0); - // int b = a + (4 >> (j / 4)); - - // // int2 faces = edgeToFaces[j]; - // // int visibilityCount = getVisibilityCount(faces, cameraPos); - // // drawLine(p, a, b, visibilityCount, color, aaWidth); - // } - - drawRing(p, color, aaWidth); - + + color += drawRing(p, aaWidth); + return color; } \ No newline at end of file diff --git a/72_SolidAngleVisualizer/main.cpp b/72_SolidAngleVisualizer/main.cpp index 1025eb067..8fb8bf144 100644 --- a/72_SolidAngleVisualizer/main.cpp +++ b/72_SolidAngleVisualizer/main.cpp @@ -323,7 +323,7 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR const auto& virtualMainWindowRes = interface.mainViewTransformReturnInfo.sceneResolution; if (!m_solidAngleViewFramebuffer || m_solidAngleViewFramebuffer->getCreationParameters().width != virtualSolidAngleWindowRes[0] || m_solidAngleViewFramebuffer->getCreationParameters().height != virtualSolidAngleWindowRes[1] || !m_mainViewFramebuffer || m_mainViewFramebuffer->getCreationParameters().width != virtualMainWindowRes[0] || m_mainViewFramebuffer->getCreationParameters().height != virtualMainWindowRes[1]) - recreateFramebuffer(); + recreateFramebuffers(); } // @@ -402,10 +402,9 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR auto& instance = m_renderer->m_instances[0]; auto transposed = hlsl::transpose(interface.m_OBBModelMatrix); memcpy(&instance.world, &transposed, sizeof(instance.world)); - instance.packedGeo = m_renderer->getGeometries().data();// +interface.gcIndex; + instance.packedGeo = m_renderer->getGeometries().data(); // cube // +interface.gcIndex; m_renderer->render(cb, viewParams); // draw the cube/OBB - // TODO: a better way to get identity matrix float32_t3x4 origin = { 1.0f,0.0f,0.0f,0.0f, @@ -536,7 +535,6 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR camera.setMoveSpeed(interface.moveSpeed); camera.setRotateSpeed(interface.rotateSpeed); - m_inputSystem->getDefaultMouse(&mouse); m_inputSystem->getDefaultKeyboard(&keyboard); @@ -610,7 +608,7 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR interface.imGUI->update(params); } - void recreateFramebuffer() + void recreateFramebuffers() { auto createImageAndView = [&](const uint16_t2 resolution, E_FORMAT format)->smart_refctd_ptr @@ -671,30 +669,30 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR } // release previous slot and its image - interface.subAllocDS->multi_deallocate(0, static_cast(CInterface::Count), interface.renderColorViewDescIndices, { .semaphore = m_semaphore.get(),.value = m_realFrameIx }); + interface.subAllocDS->multi_deallocate(0, static_cast(CInterface::Count), interface.renderColorViewDescIndices, { .semaphore = m_semaphore.get(),.value = m_realFrameIx + 1 }); // - if (solidAngleView) + if (solidAngleView && mainView) { interface.subAllocDS->multi_allocate(0, static_cast(CInterface::Count), interface.renderColorViewDescIndices); // update descriptor set IGPUDescriptorSet::SDescriptorInfo infos[static_cast(CInterface::Count)] = {}; - infos[0].desc = solidAngleView; + infos[0].desc = mainView; infos[0].info.image.imageLayout = IGPUImage::LAYOUT::READ_ONLY_OPTIMAL; - infos[1].desc = mainView; + infos[1].desc = solidAngleView; infos[1].info.image.imageLayout = IGPUImage::LAYOUT::READ_ONLY_OPTIMAL; const IGPUDescriptorSet::SWriteDescriptorSet write[static_cast(CInterface::Count)] = { {.dstSet = interface.subAllocDS->getDescriptorSet(), .binding = TexturesImGUIBindingIndex, - .arrayElement = interface.renderColorViewDescIndices[static_cast(CInterface::ERV_SOLID_ANGLE_VIEW)], + .arrayElement = interface.renderColorViewDescIndices[static_cast(CInterface::ERV_MAIN_VIEW)], .count = 1, .info = &infos[static_cast(CInterface::ERV_MAIN_VIEW)] }, { .dstSet = interface.subAllocDS->getDescriptorSet(), .binding = TexturesImGUIBindingIndex, - .arrayElement = interface.renderColorViewDescIndices[static_cast(CInterface::ERV_MAIN_VIEW)], + .arrayElement = interface.renderColorViewDescIndices[static_cast(CInterface::ERV_SOLID_ANGLE_VIEW)], .count = 1, - .info = &infos[1] + .info = &infos[static_cast(CInterface::ERV_SOLID_ANGLE_VIEW)] } }; m_device->updateDescriptorSets({ write, static_cast(CInterface::Count) }, {}); @@ -728,7 +726,7 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 6.0f, 1.0f + 0.0f, 0.0f, 3.0f, 1.0f }; // smart_refctd_ptr m_scene; From 008e2ee154b6cf5ba725752a3f1b4dac5d37ff42 Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Sun, 7 Dec 2025 00:29:22 +0300 Subject: [PATCH 4/9] Scaling by pressing G to prevent conflict with WASD camera movement, also added Q and E for moving up and down --- 72_SolidAngleVisualizer/include/transform.hpp | 4 +++- common/include/nbl/examples/cameras/CCamera.hpp | 9 ++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/72_SolidAngleVisualizer/include/transform.hpp b/72_SolidAngleVisualizer/include/transform.hpp index 5061ebd49..639c0fa3a 100644 --- a/72_SolidAngleVisualizer/include/transform.hpp +++ b/72_SolidAngleVisualizer/include/transform.hpp @@ -35,13 +35,15 @@ TransformReturnInfo EditTransform(float* cameraView, const float* cameraProjecti static bool boundSizing = false; static bool boundSizingSnap = false; + ImGui::Text("Press T/R/G to change gizmo mode"); + if (params.editTransformDecomposition) { if (ImGui::IsKeyPressed(ImGuiKey_T)) mCurrentGizmoOperation = ImGuizmo::TRANSLATE; if (ImGui::IsKeyPressed(ImGuiKey_R)) mCurrentGizmoOperation = ImGuizmo::ROTATE; - if (ImGui::IsKeyPressed(ImGuiKey_S)) + if (ImGui::IsKeyPressed(ImGuiKey_G)) mCurrentGizmoOperation = ImGuizmo::SCALE; if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) mCurrentGizmoOperation = ImGuizmo::TRANSLATE; diff --git a/common/include/nbl/examples/cameras/CCamera.hpp b/common/include/nbl/examples/cameras/CCamera.hpp index f35cd341a..e5f077e46 100644 --- a/common/include/nbl/examples/cameras/CCamera.hpp +++ b/common/include/nbl/examples/cameras/CCamera.hpp @@ -39,6 +39,8 @@ class Camera enum E_CAMERA_MOVE_KEYS : uint8_t { ECMK_MOVE_FORWARD = 0, + ECMK_MOVE_UP, + ECMK_MOVE_DOWN, ECMK_MOVE_BACKWARD, ECMK_MOVE_LEFT, ECMK_MOVE_RIGHT, @@ -47,6 +49,8 @@ class Camera inline void mapKeysToWASD() { + keysMap[ECMK_MOVE_UP] = nbl::ui::EKC_E; + keysMap[ECMK_MOVE_DOWN] = nbl::ui::EKC_Q; keysMap[ECMK_MOVE_FORWARD] = nbl::ui::EKC_W; keysMap[ECMK_MOVE_BACKWARD] = nbl::ui::EKC_S; keysMap[ECMK_MOVE_LEFT] = nbl::ui::EKC_A; @@ -211,7 +215,7 @@ class Camera assert(timeDiff >= 0); // handle camera movement - for (const auto logicalKey : { ECMK_MOVE_FORWARD, ECMK_MOVE_BACKWARD, ECMK_MOVE_LEFT, ECMK_MOVE_RIGHT }) + for (const auto logicalKey : { ECMK_MOVE_FORWARD, ECMK_MOVE_UP, ECMK_MOVE_DOWN, ECMK_MOVE_BACKWARD, ECMK_MOVE_LEFT, ECMK_MOVE_RIGHT }) { const auto code = keysMap[logicalKey]; @@ -275,6 +279,9 @@ class Camera up = nbl::core::normalize(backupUpVector); } + pos += up * perActionDt[E_CAMERA_MOVE_KEYS::ECMK_MOVE_UP] * moveSpeed * MoveSpeedScale; + pos -= up * perActionDt[E_CAMERA_MOVE_KEYS::ECMK_MOVE_DOWN] * moveSpeed * MoveSpeedScale; + nbl::core::vectorSIMDf strafevect = localTarget; if (leftHanded) strafevect = nbl::core::cross(strafevect, up); From 4290f4ab26360fbf8dac4c45c395fc4a20faf6e3 Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Sun, 7 Dec 2025 16:33:09 +0300 Subject: [PATCH 5/9] better clipping of arcs behind the hemisphere --- .../app_resources/hlsl/SolidAngleVis.frag.hlsl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl b/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl index badf1e4be..c12c007a0 100644 --- a/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl +++ b/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl @@ -114,6 +114,10 @@ float4 drawGreatCircleArc(float3 fragPos, int2 edgeVerts, int visibility, float float3 v1 = normalize(corners[edgeVerts.y]); float3 p = normalize(fragPos); // Current point on hemisphere + // Skip fragment if not in front of hemisphere or edge if both endpoints are behind horizon + if (p.z < 0.0f || (v0.z < 0.0f && v1.z < 0.0f)) + return float4(0,0,0,0); + // Great circle plane normal float3 arcNormal = normalize(cross(v0, v1)); From ba068c44c08a777bb6794b3e0f019cbdc3605480 Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Mon, 8 Dec 2025 08:47:02 +0300 Subject: [PATCH 6/9] WIP quick push for shader code --- .../hlsl/SolidAngleVis.frag.hlsl | 154 +++++++++++++++--- 1 file changed, 135 insertions(+), 19 deletions(-) diff --git a/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl b/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl index c12c007a0..7c96a8316 100644 --- a/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl +++ b/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl @@ -20,6 +20,25 @@ static const int2 edgeToFaces[12] = { {0,4}, {5,0}, {4,1}, {1,5} }; +//float3(i % 2, (i / 2) % 2, (i / 4) % 2) * 2.0f - 1.0f +static const float3 constCorners[8] = { + float3(-1, -1, -1), // 0 + float3( 1, -1, -1), // 1 + float3(-1, 1, -1), // 2 + float3( 1, 1, -1), // 3 + float3(-1, -1, 1), // 4 + float3( 1, -1, 1), // 5 + float3(-1, 1, 1), // 6 + float3( 1, 1, 1) // 7 +}; + +// All 12 edges of the cube (vertex index pairs) +static const int2 allEdges[12] = { + {0, 1}, {2, 3}, {4, 5}, {6, 7}, // Edges along X axis + {0, 2}, {1, 3}, {4, 6}, {5, 7}, // Edges along Y axis + {0, 4}, {1, 5}, {2, 6}, {3, 7} // Edges along Z axis +}; + static const float3 localNormals[6] = { float3(0, 0, -1), // Face 0 (Z-) float3(0, 0, 1), // Face 1 (Z+) @@ -34,6 +53,30 @@ static float3 faceCenters[6] = { float3(0,0,0), float3(0,0,0), float3(0,0,0), float3(0,0,0), float3(0,0,0), float3(0,0,0) }; +static const float3 colorLUT[8] = { + float3(0, 0, 0), // 0: Black + float3(1, 0, 0), // 1: Red + float3(0, 1, 0), // 2: Green + float3(1, 1, 0), // 3: Yellow + float3(0, 0, 1), // 4: Blue + float3(1, 0, 1), // 5: Magenta + float3(0, 1, 1), // 6: Cyan + float3(1, 1, 1) // 7: White +}; + + + +// Vertices are ordered CCW relative to the camera view. +static const int silhouettes[8][6] = { + {2, 3, 1, 5, 4, 6}, // 0: Black + {6, 7, 5, 1, 0, 2}, // 1: Red + {7, 6, 4, 0, 1, 3}, // 2: Green + {3, 7, 5, 4, 0, 2}, // 3: Yellow + {3, 2, 0, 4, 5, 7}, // 4: Cyan + {1, 3, 7, 6, 4, 0}, // 5: Magenta + {0, 1, 5, 7, 6, 2}, // 6: White + {4, 6, 2, 3, 1, 5} // 7: Gray +}; // Converts UV into centered, aspect-corrected NDC circle space float2 toCircleSpace(float2 uv) @@ -52,7 +95,7 @@ void computeCubeGeo() { for (int i = 0; i < 8; i++) { - float3 localPos = float3(i % 2, (i / 2) % 2, (i / 4) % 2) * 2.0f - 1.0f; + float3 localPos = constCorners[i]; //float3(i % 2, (i / 2) % 2, (i / 4) % 2) * 2.0f - 1.0f; float3 worldPos = mul(pc.modelMatrix, float4(localPos, 1.0f)).xyz; corners[i] = worldPos.xyz; @@ -72,7 +115,7 @@ float4 drawRing(float2 p, float aaWidth) float ringDistance = abs(positionLength - CIRCLE_RADIUS); float ringAlpha = 1.0f - smoothstep(ringWidth - aaWidth, ringWidth + aaWidth, ringDistance); - return ringAlpha.xxxx; + return ringAlpha * float4(1, 1, 1, 1); } // Check if a face on the hemisphere is visible from camera at origin @@ -105,7 +148,7 @@ int getEdgeVisibility(int edgeIdx, float3 cameraPos) return 0; } -// Draw great circle arc in fragment shader +// Draw great circle arc in fragment shader with horizon clipping float4 drawGreatCircleArc(float3 fragPos, int2 edgeVerts, int visibility, float aaWidth) { if (visibility == 0) return float4(0,0,0,0); // Hidden edge @@ -114,8 +157,12 @@ float4 drawGreatCircleArc(float3 fragPos, int2 edgeVerts, int visibility, float float3 v1 = normalize(corners[edgeVerts.y]); float3 p = normalize(fragPos); // Current point on hemisphere - // Skip fragment if not in front of hemisphere or edge if both endpoints are behind horizon - if (p.z < 0.0f || (v0.z < 0.0f && v1.z < 0.0f)) + // HORIZON CLIPPING: Current fragment must be on front hemisphere + if (p.z < 0.0f) + return float4(0,0,0,0); + + // HORIZON CLIPPING: Skip edge if both endpoints are behind horizon + if (v0.z < 0.0f && v1.z < 0.0f) return float4(0,0,0,0); // Great circle plane normal @@ -149,36 +196,105 @@ float4 drawGreatCircleArc(float3 fragPos, int2 edgeVerts, int visibility, float [[vk::location(0)]] float32_t4 main(SVertexAttributes vx) : SV_Target0 { - float3 cameraPos = float3(0, 0, 0); float4 color = float4(0, 0, 0, 0); float2 p = toCircleSpace(vx.uv); // Convert 2D disk position to 3D hemisphere position - // p is in range [-CIRCLE_RADIUS, CIRCLE_RADIUS] - float2 normalized = p / CIRCLE_RADIUS; // Now in range [-1, 1] + float2 normalized = p / CIRCLE_RADIUS; float r2 = dot(normalized, normalized); - if (r2 > 1.0f) - discard; - // Convert UV to 3D position on hemisphere float3 spherePos = normalize(float3(normalized.x, normalized.y, sqrt(1 - r2))); - computeCubeGeo(); // Your existing function + computeCubeGeo(); + + float3 obbCenter = mul(pc.modelMatrix, float4(0, 0, 0, 1)).xyz; + + float3 viewDir = obbCenter; + + // Is this correct? + float dotX = dot(viewDir, float3(pc.modelMatrix[0][0], pc.modelMatrix[1][0], pc.modelMatrix[2][0])); + float dotY = dot(viewDir, float3(pc.modelMatrix[0][1], pc.modelMatrix[1][1], pc.modelMatrix[2][1])); + float dotZ = dot(viewDir, float3(pc.modelMatrix[0][2], pc.modelMatrix[1][2], pc.modelMatrix[2][2])); + + // Determine octant from ray direction signs + int octant = (dotX >= 0 ? 4 : 0) + + (dotY >= 0 ? 2 : 0) + + (dotZ >= 0 ? 1 : 0); + + if (all(vx.uv >= float2(0.49f, 0.49f) ) && all(vx.uv <= float2(0.51f, 0.51f))) + { + return float4(colorLUT[octant], 1.0f); + } + + float aaWidth = length(float2(ddx(vx.uv.x), ddy(vx.uv.y))); - float aaWidth = length(float2(ddx(p.x), ddy(p.y))); + + // Draw the 6 silhouette edges + for (int i = 0; i < 6; i++) + { + int v0Idx = silhouettes[octant][i]; + int v1Idx = silhouettes[octant][(i + 1) % 6]; + + float4 edgeContribution = drawGreatCircleArc(spherePos, int2(v0Idx, v1Idx), 1, aaWidth); + color += float4(colorLUT[i] * edgeContribution.a, edgeContribution.a); + } - // Draw edges as great circle arcs - for (int j = 0; j < 12; j++) + // Draw the remaining edges (non-silhouette) in a different color + float3 hiddenEdgeColor = float3(0.3, 0.3, 0.3); // Gray color for hidden edges + + for (int i = 0; i < 12; i++) { - int a = j % 4 * (j < 4 ? 1 : 2) - (j / 4 == 1 ? j % 2 : 0); - int b = a + (4 >> (j / 4)); + int2 edge = allEdges[i]; + + // Check if this edge is already drawn as a silhouette edge + bool isSilhouette = false; + for (int j = 0; j < 6; j++) + { + int v0 = silhouettes[octant][j]; + int v1 = silhouettes[octant][(j + 1) % 6]; + + if ((edge.x == v0 && edge.y == v1) || (edge.x == v1 && edge.y == v0)) + { + isSilhouette = true; + break; + } + } - int visibility = getEdgeVisibility(j, cameraPos); - color += drawGreatCircleArc(spherePos, int2(a, b), visibility, aaWidth); + // Only draw if it's not a silhouette edge + if (!isSilhouette) + { + float4 edgeContribution = drawGreatCircleArc(spherePos, edge, 1, aaWidth); + color += float4(hiddenEdgeColor * edgeContribution.a, edgeContribution.a); + } + } + + // Draw corner labels for debugging + for (int i = 0; i < 8; i++) + { + float3 corner = normalize(corners[i]); + float2 cornerPos = corner.xy; + // Project corner onto 2D circle space + + // Distance from current fragment to corner + float dist = length(spherePos.xy - cornerPos); + + // Draw a small colored dot at the corner + float dotSize = 0.03f; + float dotAlpha = 1.0f - smoothstep(dotSize - aaWidth, dotSize + aaWidth, dist); + + if (dotAlpha > 0.0f) + { + float brightness = float(i) / 7.0f; + float3 dotColor = colorLUT[i]; + color += float4(dotColor * dotAlpha, dotAlpha); + } } color += drawRing(p, aaWidth); + + // if (r2 > 1.1f) + // color.a = 0.0f; // Outside circle, make transparent return color; } \ No newline at end of file From 91ae8657dee9b4de82c81b97b23b83d3824a6011 Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Tue, 9 Dec 2025 00:20:01 +0300 Subject: [PATCH 7/9] Fixed main camera aspect ratio, added 27 configurations for cube silhouette --- .../hlsl/SolidAngleVis.frag.hlsl | 248 ++++++++++++------ 72_SolidAngleVisualizer/include/transform.hpp | 2 +- 72_SolidAngleVisualizer/main.cpp | 9 +- 3 files changed, 167 insertions(+), 92 deletions(-) diff --git a/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl b/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl index 7c96a8316..fa0805356 100644 --- a/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl +++ b/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl @@ -53,29 +53,84 @@ static float3 faceCenters[6] = { float3(0,0,0), float3(0,0,0), float3(0,0,0), float3(0,0,0), float3(0,0,0), float3(0,0,0) }; -static const float3 colorLUT[8] = { +static const float3 colorLUT[27] = { + // Row 1: Pure and bright colors float3(0, 0, 0), // 0: Black - float3(1, 0, 0), // 1: Red - float3(0, 1, 0), // 2: Green - float3(1, 1, 0), // 3: Yellow - float3(0, 0, 1), // 4: Blue - float3(1, 0, 1), // 5: Magenta - float3(0, 1, 1), // 6: Cyan - float3(1, 1, 1) // 7: White + float3(1, 1, 1), // 1: White + float3(0.5, 0.5, 0.5), // 2: Gray + + // Row 2: Primary colors + float3(1, 0, 0), // 3: Red + float3(0, 1, 0), // 4: Green + float3(0, 0, 1), // 5: Blue + + // Row 3: Secondary colors + float3(1, 1, 0), // 6: Yellow + float3(1, 0, 1), // 7: Magenta + float3(0, 1, 1), // 8: Cyan + + // Row 4: Orange family + float3(1, 0.5, 0), // 9: Orange + float3(1, 0.65, 0), // 10: Light Orange + float3(0.8, 0.4, 0), // 11: Dark Orange + + // Row 5: Pink/Rose family + float3(1, 0.4, 0.7), // 12: Pink + float3(1, 0.75, 0.8), // 13: Light Pink + float3(0.7, 0.1, 0.3), // 14: Deep Rose + + // Row 6: Purple/Violet family + float3(0.5, 0, 0.5), // 15: Purple + float3(0.6, 0.4, 0.8), // 16: Light Purple + float3(0.3, 0, 0.5), // 17: Indigo + + // Row 7: Green variations + float3(0, 0.5, 0), // 18: Dark Green + float3(0.5, 1, 0), // 19: Lime + float3(0, 0.5, 0.25), // 20: Forest Green + + // Row 8: Blue variations + float3(0, 0, 0.5), // 21: Navy + float3(0.3, 0.7, 1), // 22: Sky Blue + float3(0, 0.4, 0.6), // 23: Teal + + // Row 9: Earth tones + float3(0.6, 0.4, 0.2), // 24: Brown + float3(0.8, 0.7, 0.3), // 25: Tan/Beige + float3(0.4, 0.3, 0.1) // 26: Dark Brown }; // Vertices are ordered CCW relative to the camera view. -static const int silhouettes[8][6] = { - {2, 3, 1, 5, 4, 6}, // 0: Black - {6, 7, 5, 1, 0, 2}, // 1: Red - {7, 6, 4, 0, 1, 3}, // 2: Green - {3, 7, 5, 4, 0, 2}, // 3: Yellow - {3, 2, 0, 4, 5, 7}, // 4: Cyan - {1, 3, 7, 6, 4, 0}, // 5: Magenta - {0, 1, 5, 7, 6, 2}, // 6: White - {4, 6, 2, 3, 1, 5} // 7: Gray +static const int silhouettes[27][7] = { + {6, 1, 3, 2, 6, 4, 5}, // 0: Black + {6, 2, 6, 4, 5, 7, 3}, // 1: White + {6, 0, 4, 5, 7, 3, 2}, // 2: Gray + {6, 1, 3, 7, 6, 4, 5,}, // 3: Red + {4, 4, 5, 7, 6, -1, -1}, // 4: Green + {6, 0, 4, 5, 7, 6, 2}, // 5: Blue + {6, 0, 1, 3, 7, 6, 4}, // 6: Yellow + {6, 0, 1, 5, 7, 6, 4}, // 7: Magenta + {6, 0, 1, 5, 7, 6, 2}, // 8: Cyan + {6, 1, 3, 2, 6, 7, 5}, // 9: Orange + {4, 2, 6, 7, 3, -1, -1}, // 10: Light Orange + {6, 0, 4, 6, 7, 3, 2}, // 11: Dark Orange + {4, 1, 3, 7, 5, -1, -1}, // 12: Pink + {6, 0, 4, 6, 7, 3, 2}, // 13: Light Pink + {4, 0, 4, 6, 2, -1, -1}, // 14: Deep Rose + {6, 0, 1, 3, 7, 5, 4}, // 15: Purple + {4, 0, 1, 5, 4, -1, -1}, // 16: Light Purple + {6, 0, 1, 5, 4, 6, 2}, // 17: Indigo + {6, 0, 2, 6, 7, 5, 1}, // 18: Dark Green + {6, 0, 2, 6, 7, 3, 1}, // 19: Lime + {6, 0, 4, 6, 7, 3, 1}, // 20: Forest Green + {6, 0, 2, 3, 7, 5, 1}, // 21: Navy + {4, 0, 2, 3, 1, -1, -1}, // 22: Sky Blue + {6, 0, 4, 6, 2, 3, 1}, // 23: Teal + {6, 0, 2, 3, 7, 5, 4}, // 24: Brown + {6, 0, 2, 3, 1, 5, 4}, // 25: Tan/Beige + {6, 1, 5, 4, 6, 2, 3} // 26: Dark Brown }; // Converts UV into centered, aspect-corrected NDC circle space @@ -106,6 +161,33 @@ void computeCubeGeo() } } +float4 drawCorners(float3 spherePos, float aaWidth) +{ + float4 color = float4(0,0,0,0); + // Draw corner labels for debugging + for (int i = 0; i < 8; i++) + { + float3 corner = normalize(corners[i]); + float2 cornerPos = corner.xy; + // Project corner onto 2D circle space + + // Distance from current fragment to corner + float dist = length(spherePos.xy - cornerPos); + + // Draw a small colored dot at the corner + float dotSize = 0.03f; + float dotAlpha = 1.0f - smoothstep(dotSize - aaWidth, dotSize + aaWidth, dist); + + if (dotAlpha > 0.0f) + { + float brightness = float(i) / 7.0f; + float3 dotColor = colorLUT[i]; + color += float4(dotColor * dotAlpha, dotAlpha); + } + } + return color; +} + float4 drawRing(float2 p, float aaWidth) { float positionLength = length(p); @@ -194,54 +276,11 @@ float4 drawGreatCircleArc(float3 fragPos, int2 edgeVerts, int visibility, float return edgeColor * alpha * intensity; } -[[vk::location(0)]] float32_t4 main(SVertexAttributes vx) : SV_Target0 +float4 drawHiddenEdges(float3 spherePos, int configIndex, float aaWidth) { - float4 color = float4(0, 0, 0, 0); - float2 p = toCircleSpace(vx.uv); - - // Convert 2D disk position to 3D hemisphere position - float2 normalized = p / CIRCLE_RADIUS; - float r2 = dot(normalized, normalized); - - // Convert UV to 3D position on hemisphere - float3 spherePos = normalize(float3(normalized.x, normalized.y, sqrt(1 - r2))); - - computeCubeGeo(); - - float3 obbCenter = mul(pc.modelMatrix, float4(0, 0, 0, 1)).xyz; - - float3 viewDir = obbCenter; - - // Is this correct? - float dotX = dot(viewDir, float3(pc.modelMatrix[0][0], pc.modelMatrix[1][0], pc.modelMatrix[2][0])); - float dotY = dot(viewDir, float3(pc.modelMatrix[0][1], pc.modelMatrix[1][1], pc.modelMatrix[2][1])); - float dotZ = dot(viewDir, float3(pc.modelMatrix[0][2], pc.modelMatrix[1][2], pc.modelMatrix[2][2])); - - // Determine octant from ray direction signs - int octant = (dotX >= 0 ? 4 : 0) + - (dotY >= 0 ? 2 : 0) + - (dotZ >= 0 ? 1 : 0); - - if (all(vx.uv >= float2(0.49f, 0.49f) ) && all(vx.uv <= float2(0.51f, 0.51f))) - { - return float4(colorLUT[octant], 1.0f); - } - - float aaWidth = length(float2(ddx(vx.uv.x), ddy(vx.uv.y))); - - - // Draw the 6 silhouette edges - for (int i = 0; i < 6; i++) - { - int v0Idx = silhouettes[octant][i]; - int v1Idx = silhouettes[octant][(i + 1) % 6]; - - float4 edgeContribution = drawGreatCircleArc(spherePos, int2(v0Idx, v1Idx), 1, aaWidth); - color += float4(colorLUT[i] * edgeContribution.a, edgeContribution.a); - } - + float4 color = float4(0,0,0,0); // Draw the remaining edges (non-silhouette) in a different color - float3 hiddenEdgeColor = float3(0.3, 0.3, 0.3); // Gray color for hidden edges + float3 hiddenEdgeColor = float3(0.3, 0.3, 0); // dark yellow color for hidden edges for (int i = 0; i < 12; i++) { @@ -249,12 +288,14 @@ float4 drawGreatCircleArc(float3 fragPos, int2 edgeVerts, int visibility, float // Check if this edge is already drawn as a silhouette edge bool isSilhouette = false; - for (int j = 0; j < 6; j++) + int vertexCount = silhouettes[configIndex][0]; + // Draw the 6 silhouette edges + for (int i = 0; i < vertexCount; i++) { - int v0 = silhouettes[octant][j]; - int v1 = silhouettes[octant][(j + 1) % 6]; + int v0Idx = silhouettes[configIndex][i + 1]; + int v1Idx = silhouettes[configIndex][((i + 1) % vertexCount) + 1]; - if ((edge.x == v0 && edge.y == v1) || (edge.x == v1 && edge.y == v0)) + if ((edge.x == v0Idx && edge.y == v1Idx) || (edge.x == v1Idx && edge.y == v0Idx)) { isSilhouette = true; break; @@ -268,33 +309,66 @@ float4 drawGreatCircleArc(float3 fragPos, int2 edgeVerts, int visibility, float color += float4(hiddenEdgeColor * edgeContribution.a, edgeContribution.a); } } + return color; +} - // Draw corner labels for debugging - for (int i = 0; i < 8; i++) +[[vk::location(0)]] float32_t4 main(SVertexAttributes vx) : SV_Target0 +{ + float4 color = float4(0, 0, 0, 0); + float2 p = toCircleSpace(vx.uv); + + // Convert 2D disk position to 3D hemisphere position + float2 normalized = p / CIRCLE_RADIUS; + float r2 = dot(normalized, normalized); + float aaWidth = length(float2(ddx(vx.uv.x), ddy(vx.uv.y))); + + if (all(vx.uv >= float2(0.49f, 0.49f) ) && all(vx.uv <= float2(0.51f, 0.51f))) { - float3 corner = normalize(corners[i]); - float2 cornerPos = corner.xy; - // Project corner onto 2D circle space - - // Distance from current fragment to corner - float dist = length(spherePos.xy - cornerPos); - - // Draw a small colored dot at the corner - float dotSize = 0.03f; - float dotAlpha = 1.0f - smoothstep(dotSize - aaWidth, dotSize + aaWidth, dist); + return float4(colorLUT[configIndex], 1.0f); + } + + // Convert UV to 3D position on hemisphere + float3 spherePos = normalize(float3(normalized.x, normalized.y, sqrt(1 - r2))); + + computeCubeGeo(); + + // Get OBB center in world space + float3 obbCenter = mul(pc.modelMatrix, float4(0, 0, 0, 1)).xyz; + + float3x3 rotMatrix = (float3x3)pc.modelMatrix; + float3 proj = mul(obbCenter, rotMatrix); // Get all 3 projections at once + + // Get squared column lengths + float lenSqX = dot(rotMatrix[0], rotMatrix[0]); + float lenSqY = dot(rotMatrix[1], rotMatrix[1]); + float lenSqZ = dot(rotMatrix[2], rotMatrix[2]); + + int3 region = int3( + proj.x < -lenSqX ? 0 : (proj.x > lenSqX ? 2 : 1), + proj.y < -lenSqY ? 0 : (proj.y > lenSqY ? 2 : 1), + proj.z < -lenSqZ ? 0 : (proj.z > lenSqZ ? 2 : 1) + ); + + int configIndex = region.x + region.y * 3 + region.z * 9; // 0-26 + + int vertexCount = silhouettes[configIndex][0]; + for (int i = 0; i < vertexCount; i++) + { + int v0Idx = silhouettes[configIndex][i + 1]; + int v1Idx = silhouettes[configIndex][((i + 1) % vertexCount) + 1]; - if (dotAlpha > 0.0f) - { - float brightness = float(i) / 7.0f; - float3 dotColor = colorLUT[i]; - color += float4(dotColor * dotAlpha, dotAlpha); - } + float4 edgeContribution = drawGreatCircleArc(spherePos, int2(v0Idx, v1Idx), 1, aaWidth); + color += float4(colorLUT[i] * edgeContribution.a, edgeContribution.a); } + color += drawHiddenEdges(spherePos, configIndex, aaWidth); + + color += drawCorners(spherePos, aaWidth); + color += drawRing(p, aaWidth); - // if (r2 > 1.1f) - // color.a = 0.0f; // Outside circle, make transparent + if (r2 > 1.1f) + color.a = 0.0f; // Outside circle, make transparent return color; } \ No newline at end of file diff --git a/72_SolidAngleVisualizer/include/transform.hpp b/72_SolidAngleVisualizer/include/transform.hpp index 639c0fa3a..105b2f757 100644 --- a/72_SolidAngleVisualizer/include/transform.hpp +++ b/72_SolidAngleVisualizer/include/transform.hpp @@ -19,7 +19,7 @@ struct TransformRequestParams struct TransformReturnInfo { - nbl::hlsl::uint16_t2 sceneResolution = { 0, 0 }; + nbl::hlsl::uint16_t2 sceneResolution = { 1, 1 }; bool isGizmoWindowHovered; bool isGizmoBeingUsed; }; diff --git a/72_SolidAngleVisualizer/main.cpp b/72_SolidAngleVisualizer/main.cpp index 8fb8bf144..5f73797a6 100644 --- a/72_SolidAngleVisualizer/main.cpp +++ b/72_SolidAngleVisualizer/main.cpp @@ -753,16 +753,17 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR // TODO: why is this a lambda and not just an assignment in a scope ? camera.setProjectionMatrix([&]() { - matrix4SIMD projection; + const auto& sceneRes = mainViewTransformReturnInfo.sceneResolution; + matrix4SIMD projection; if (isPerspective) if (isLH) - projection = matrix4SIMD::buildProjectionMatrixPerspectiveFovLH(core::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar); + projection = matrix4SIMD::buildProjectionMatrixPerspectiveFovLH(core::radians(fov), sceneRes.x / sceneRes.y, zNear, zFar); else - projection = matrix4SIMD::buildProjectionMatrixPerspectiveFovRH(core::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar); + projection = matrix4SIMD::buildProjectionMatrixPerspectiveFovRH(core::radians(fov), sceneRes.x / sceneRes.y, zNear, zFar); else { - float viewHeight = viewWidth * io.DisplaySize.y / io.DisplaySize.x; + float viewHeight = viewWidth * sceneRes.y / sceneRes.x; if (isLH) projection = matrix4SIMD::buildProjectionMatrixOrthoLH(viewWidth, viewHeight, zNear, zFar); From 0124cc9c0ad83d4a38f1e8ac3ddcdf56125740ac Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Tue, 9 Dec 2025 00:30:34 +0300 Subject: [PATCH 8/9] Shader fixes, bast uint16 resolutionf to float --- .../app_resources/hlsl/SolidAngleVis.frag.hlsl | 16 +++++++++------- 72_SolidAngleVisualizer/main.cpp | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl b/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl index fa0805356..ec30c2b64 100644 --- a/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl +++ b/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl @@ -322,10 +322,7 @@ float4 drawHiddenEdges(float3 spherePos, int configIndex, float aaWidth) float r2 = dot(normalized, normalized); float aaWidth = length(float2(ddx(vx.uv.x), ddy(vx.uv.y))); - if (all(vx.uv >= float2(0.49f, 0.49f) ) && all(vx.uv <= float2(0.51f, 0.51f))) - { - return float4(colorLUT[configIndex], 1.0f); - } + // Convert UV to 3D position on hemisphere float3 spherePos = normalize(float3(normalized.x, normalized.y, sqrt(1 - r2))); @@ -350,7 +347,7 @@ float4 drawHiddenEdges(float3 spherePos, int configIndex, float aaWidth) ); int configIndex = region.x + region.y * 3 + region.z * 9; // 0-26 - + int vertexCount = silhouettes[configIndex][0]; for (int i = 0; i < vertexCount; i++) { @@ -367,8 +364,13 @@ float4 drawHiddenEdges(float3 spherePos, int configIndex, float aaWidth) color += drawRing(p, aaWidth); - if (r2 > 1.1f) - color.a = 0.0f; // Outside circle, make transparent + if (all(vx.uv >= float2(0.49f, 0.49f) ) && all(vx.uv <= float2(0.51f, 0.51f))) + { + return float4(colorLUT[configIndex], 1.0f); + } + + // if (r2 > 1.1f) + // color.a = 0.0f; // Outside circle, make transparent return color; } \ No newline at end of file diff --git a/72_SolidAngleVisualizer/main.cpp b/72_SolidAngleVisualizer/main.cpp index 5f73797a6..85685e705 100644 --- a/72_SolidAngleVisualizer/main.cpp +++ b/72_SolidAngleVisualizer/main.cpp @@ -753,7 +753,7 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR // TODO: why is this a lambda and not just an assignment in a scope ? camera.setProjectionMatrix([&]() { - const auto& sceneRes = mainViewTransformReturnInfo.sceneResolution; + const auto& sceneRes = float16_t2(mainViewTransformReturnInfo.sceneResolution); matrix4SIMD projection; if (isPerspective) From a35eddd1bd83fbf636e820b59c6eef939ed09668 Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Tue, 9 Dec 2025 00:44:42 +0300 Subject: [PATCH 9/9] Better color for non-silhouette edges --- .../app_resources/hlsl/SolidAngleVis.frag.hlsl | 2 +- 72_SolidAngleVisualizer/main.cpp | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl b/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl index ec30c2b64..51cb1946d 100644 --- a/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl +++ b/72_SolidAngleVisualizer/app_resources/hlsl/SolidAngleVis.frag.hlsl @@ -280,7 +280,7 @@ float4 drawHiddenEdges(float3 spherePos, int configIndex, float aaWidth) { float4 color = float4(0,0,0,0); // Draw the remaining edges (non-silhouette) in a different color - float3 hiddenEdgeColor = float3(0.3, 0.3, 0); // dark yellow color for hidden edges + float3 hiddenEdgeColor = float3(0.1, 0.1, 0.1); // dark yellow color for hidden edges for (int i = 0; i < 12; i++) { diff --git a/72_SolidAngleVisualizer/main.cpp b/72_SolidAngleVisualizer/main.cpp index 85685e705..e9266520d 100644 --- a/72_SolidAngleVisualizer/main.cpp +++ b/72_SolidAngleVisualizer/main.cpp @@ -933,9 +933,6 @@ class SolidAngleVisualizer final : public MonoWindowApplication, public BuiltinR transformParams.editTransformDecomposition = true; mainViewTransformReturnInfo = EditTransform(&imguizmoM16InOut.view[0][0], &imguizmoM16InOut.projection[0][0], &imguizmoM16InOut.model[0][0], transformParams); - // MODEL: Zup -> Yup - - m_OBBModelMatrix = imguizmoM16InOut.model; // TODO: camera stops when cursor hovers gizmo, but we also want to stop when gizmo is being used move = (ImGui::IsMouseDown(ImGuiMouseButton_Left) || mainViewTransformReturnInfo.isGizmoWindowHovered) && (!mainViewTransformReturnInfo.isGizmoBeingUsed);