From 4c82bbe2758176a34389b73e23235aa654d53129 Mon Sep 17 00:00:00 2001 From: Mars Date: Fri, 15 Nov 2024 21:25:10 -0500 Subject: [PATCH] wegh --- shaders/crosshair_fragment.glsl | 8 + shaders/crosshair_vertex.glsl | 11 + shaders/fragment.glsl | 39 ++- shaders/vertex.glsl | 20 +- src/main.cpp | 601 ++++++++++++++++++++++++++------ src/util/crosshair.hpp | 49 +++ src/util/types.hpp | 4 +- src/util/unique_image.hpp | 35 +- src/util/vertex.hpp | 85 +++-- 9 files changed, 694 insertions(+), 158 deletions(-) create mode 100644 shaders/crosshair_fragment.glsl create mode 100644 shaders/crosshair_vertex.glsl create mode 100644 src/util/crosshair.hpp diff --git a/shaders/crosshair_fragment.glsl b/shaders/crosshair_fragment.glsl new file mode 100644 index 0000000..7122ce8 --- /dev/null +++ b/shaders/crosshair_fragment.glsl @@ -0,0 +1,8 @@ +#version 450 + +layout(location = 0) in vec3 fragColor; +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} diff --git a/shaders/crosshair_vertex.glsl b/shaders/crosshair_vertex.glsl new file mode 100644 index 0000000..9f27f54 --- /dev/null +++ b/shaders/crosshair_vertex.glsl @@ -0,0 +1,11 @@ +#version 450 + +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec3 inColor; + +layout(location = 0) out vec3 fragColor; + +void main() { + gl_Position = vec4(inPosition, 0.0, 1.0); + fragColor = inColor; +} diff --git a/shaders/fragment.glsl b/shaders/fragment.glsl index 25ed3e0..e0c95cf 100644 --- a/shaders/fragment.glsl +++ b/shaders/fragment.glsl @@ -1,12 +1,47 @@ #version 450 -layout(binding = 1) uniform sampler2D texSampler; +layout(binding = 3) uniform sampler2D texSampler; + +layout(binding = 1) uniform LightInfo { + vec3 position; + vec3 color; + float ambient_strength; + float specular_strength; +} light; + +layout(binding = 2) uniform CameraInfo { + vec3 position; +} camera; layout(location = 0) in vec3 fragColor; layout(location = 1) in vec2 fragTexCoord; +layout(location = 2) in vec3 fragNormal; +layout(location = 3) in vec3 fragWorldPos; layout(location = 0) out vec4 outColor; void main() { - outColor = texture(texSampler, fragTexCoord); + // Get base color from texture + vec4 texColor = texture(texSampler, fragTexCoord); + + // Normalize vectors + vec3 normal = normalize(fragNormal); + vec3 lightDir = normalize(light.position - fragWorldPos); + vec3 viewDir = normalize(camera.position - fragWorldPos); + vec3 reflectDir = reflect(-lightDir, normal); + + // Ambient + vec3 ambient = light.ambient_strength * light.color; + + // Diffuse + float diff = max(dot(normal, lightDir), 0.0); + vec3 diffuse = diff * light.color; + + // Specular + float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0); + vec3 specular = light.specular_strength * spec * light.color; + + // Combine lighting components + vec3 lighting = (ambient + diffuse + specular); + outColor = vec4(lighting * texColor.rgb, texColor.a); } diff --git a/shaders/vertex.glsl b/shaders/vertex.glsl index 6a4ed5f..30d15f2 100644 --- a/shaders/vertex.glsl +++ b/shaders/vertex.glsl @@ -1,20 +1,28 @@ #version 450 layout(binding = 0) uniform UniformBufferObject { - mat4 model; - mat4 view; - mat4 proj; + mat4 model; + mat4 view; + mat4 proj; } ubo; layout(location = 0) in vec3 inPosition; layout(location = 1) in vec3 inColor; layout(location = 2) in vec2 inTexCoord; +layout(location = 3) in vec3 inNormal; layout(location = 0) out vec3 fragColor; layout(location = 1) out vec2 fragTexCoord; +layout(location = 2) out vec3 fragNormal; +layout(location = 3) out vec3 fragWorldPos; void main() { - gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); - fragColor = inColor; - fragTexCoord = inTexCoord; + vec4 worldPos = ubo.model * vec4(inPosition, 1.0); + gl_Position = ubo.proj * ubo.view * worldPos; + + // Transform normal to world space + fragNormal = mat3(transpose(inverse(ubo.model))) * inNormal; + fragWorldPos = worldPos.xyz; + fragColor = inColor; + fragTexCoord = inTexCoord; } diff --git a/src/main.cpp b/src/main.cpp index f3cee8f..8f8dd55 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,6 +23,7 @@ VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE // Include custom utility headers +#include "util/crosshair.hpp" // Crosshair definitions #include "util/shaders.hpp" // Compiled shader code #include "util/types.hpp" // Custom type definitions #include "util/unique_image.hpp" // Custom image handling utilities @@ -48,8 +49,10 @@ constexpr f64 CAMERA_SPEED = 1.0; constexpr i32 MAX_FRAMES_IN_FLIGHT = 2; // Shader file paths -constexpr const char* VERTEX_SHADER_PATH = "shaders/vertex.glsl"; -constexpr const char* FRAGMENT_SHADER_PATH = "shaders/fragment.glsl"; +constexpr const char* VERTEX_SHADER_PATH = "shaders/vertex.glsl"; +constexpr const char* FRAGMENT_SHADER_PATH = "shaders/fragment.glsl"; +constexpr const char* CROSSHAIR_VERTEX_SHADER_PATH = "shaders/crosshair_vertex.glsl"; +constexpr const char* CROSSHAIR_FRAGMENT_SHADER_PATH = "shaders/crosshair_fragment.glsl"; // Validation layers for debug builds #ifndef NDEBUG @@ -127,6 +130,13 @@ class VulkanApp { vk::UniquePipeline mGraphicsPipeline; ///< Graphics pipeline vk::UniquePipeline mOldPipeline; ///< Previous graphics pipeline for safe deletion + vk::UniquePipelineLayout mCrosshairPipelineLayout; + vk::UniquePipeline mCrosshairPipeline; + vk::UniqueBuffer mCrosshairVertexBuffer; + vk::UniqueDeviceMemory mCrosshairVertexBufferMemory; + vk::UniqueBuffer mCrosshairIndexBuffer; + vk::UniqueDeviceMemory mCrosshairIndexBufferMemory; + vk::UniqueCommandPool mCommandPool; ///< Command pool for allocating command buffers vk::UniqueImage mColorImage; ///< Color image @@ -154,11 +164,27 @@ class VulkanApp { std::vector mUniformBuffersMemory; ///< Memory for uniform buffers std::vector mUniformBuffersMapped; ///< Mapped uniform buffers + std::vector mLightUniformBuffers; ///< Uniform buffers for light parameters + std::vector mLightUniformBuffersMemory; ///< Memory for light uniform buffers + std::vector mLightUniformBuffersMapped; ///< Mapped light uniform buffers + + std::vector mCameraUniformBuffers; ///< Uniform buffers for camera parameters + std::vector mCameraUniformBuffersMemory; ///< Memory for camera uniform buffers + std::vector mCameraUniformBuffersMapped; ///< Mapped camera uniform buffers + vk::UniqueDescriptorPool mDescriptorPool; ///< Descriptor pool for the application vk::UniqueDescriptorPool mImGuiDescriptorPool; ///< Separate descriptor pool for ImGui std::vector mDescriptorSets; ///< Descriptor sets for binding resources - std::vector mCommandBuffers; ///< Command buffers for drawing + std::vector mCommandBuffers; ///< Command buffers for drawing commands + + // Light settings + struct { + glm::vec3 position = glm::vec3(2.0F, 2.0F, 2.0F); + glm::vec3 color = glm::vec3(1.0F, 1.0F, 1.0F); + float ambient_strength = 0.1F; + float specular_strength = 0.5F; + } mLightSettings; std::vector mImageAvailableSemaphores; ///< Signals that an image is available for rendering @@ -222,6 +248,17 @@ class VulkanApp { alignas(16) glm::mat4 proj; ///< Projection matrix }; + struct LightInfo { + alignas(16) glm::vec3 position; ///< Light position + alignas(16) glm::vec3 color; ///< Light color + alignas(4) float ambient_strength; ///< Ambient strength + alignas(4) float specular_strength; ///< Specular strength + }; + + struct CameraInfo { + alignas(16) glm::vec3 position; ///< Camera position + }; + struct Camera { glm::dvec3 position; glm::dvec3 front; @@ -231,19 +268,23 @@ class VulkanApp { f64 pitch; Camera() - : position(2.0, 2.0, 2.0), - front(glm::normalize(glm::dvec3(-2.0, -2.0, -2.0))), + : position(2.0, 2.0, 0.5), + front(glm::normalize(glm::dvec3(0.0, 1.0, 0.0))), up(0.0, 0.0, 1.0), - right(glm::normalize(glm::cross(front, up))), - yaw(-135.0) // -135 degrees to match the initial front vector - , - pitch(-35.26) // -35.26 degrees to match the initial front vector - { + right(glm::normalize(glm::cross(front, glm::dvec3(0.0, 0.0, 1.0)))), + yaw(180.0), + pitch(0.0) { updateCameraVectors(); } [[nodiscard]] fn getPosition() const -> glm::dvec3 { return position; } + [[nodiscard]] fn getFront() const -> glm::dvec3 { return front; } + + [[nodiscard]] fn getYaw() const -> f64 { return yaw; } + + [[nodiscard]] fn getPitch() const -> f64 { return pitch; } + [[nodiscard]] fn getViewMatrix() const -> glm::mat4 { return glm::lookAt(position, position + front, up); } @@ -289,6 +330,12 @@ class VulkanApp { yaw += xoffset * sensitivity; pitch += yoffset * sensitivity; + // Clamp yaw to [-180, 180] range + if (yaw > 180.0) + yaw -= 360.0; + if (yaw < -180.0) + yaw += 360.0; + // Constrain pitch to avoid camera flipping if (pitch > 89.0) pitch = 89.0; @@ -348,9 +395,20 @@ class VulkanApp { // Set window creation hints vkfw::WindowHints hints { .clientAPI = vkfw::ClientAPI::eNone }; // No OpenGL context + // Get the primary monitor and its resolution + vkfw::Monitor primaryMonitor = vkfw::getPrimaryMonitor(); + const GLFWvidmode* videoMode = primaryMonitor.getVideoMode(); + + // Calculate window position to center it + i32 xpos = (videoMode->width - WIDTH) / 2; + i32 ypos = (videoMode->height - HEIGHT) / 2; + // Create the window mWindow = vkfw::createWindowUnique(WIDTH, HEIGHT, "Vulkan", hints); + // Set window position + mWindow->setPos(xpos, ypos); + // Set the user pointer to this instance, allowing us to access it in callbacks mWindow->setUserPointer(this); @@ -359,22 +417,22 @@ class VulkanApp { // Set up mouse callback mWindow->callbacks()->on_cursor_move = - [this](const vkfw::Window& /*window*/, f64 xpos, f64 ypos) -> void { + [this](const vkfw::Window& /*window*/, f64 mouseX, f64 mouseY) -> void { if (!mCursorCaptured) return; // Skip camera movement when cursor is not captured if (mFirstMouse) { - mLastX = xpos; - mLastY = ypos; + mLastX = mouseX; + mLastY = mouseY; mFirstMouse = false; return; } - f64 xoffset = xpos - mLastX; - f64 yoffset = mLastY - ypos; // Reversed since y-coordinates range from bottom to top + f64 xoffset = mouseX - mLastX; + f64 yoffset = mLastY - mouseY; // Reversed since y-coordinates range from bottom to top - mLastX = xpos; - mLastY = ypos; + mLastX = mouseX; + mLastY = mouseY; mCamera.rotate(-xoffset, yoffset); // Invert xoffset for correct horizontal movement }; @@ -391,6 +449,7 @@ class VulkanApp { mCursorCaptured = false; window.set(vkfw::CursorMode::eNormal); } + if (key == vkfw::Key::eR && action == vkfw::KeyAction::ePress) { try { mDevice->waitIdle(); @@ -444,32 +503,36 @@ class VulkanApp { * others that must be created first. */ fn initVulkan() -> void { - createInstance(); // Create the Vulkan instance - setupDebugMessenger(); // Set up debug messaging (validation layers) - createSurface(); // Create the window surface - pickPhysicalDevice(); // Select a suitable GPU - createLogicalDevice(); // Create a logical device from the chosen GPU - createSwapChain(); // Create the swap chain for presenting images - createImageViews(); // Create image views for the swap chain images - createRenderPass(); // Set up the render pass - createDescriptorSetLayout(); // Create the descriptor set layout - createGraphicsPipeline(); // Create the graphics pipeline - createCommandPool(); // Create a command pool for allocating command buffers - createColorResources(); // Create resources for multisampling - createDepthResources(); // Create resources for depth testing - createFramebuffers(); // Create framebuffers for rendering - createTextureImage(); // Load and create the texture image - createTextureImageView(); // Create an image view for the texture - createTextureSampler(); // Create a sampler for the texture - loadModel(); // Load the 3D model - createVertexBuffer(); // Create a buffer for vertex data - createIndexBuffer(); // Create a buffer for index data - createUniformBuffers(); // Create uniform buffers for shader parameters - createDescriptorPool(); // Create a descriptor pool - createDescriptorSets(); // Allocate and update descriptor sets - createCommandBuffers(); // Create command buffers for rendering commands - createSyncObjects(); // Create synchronization objects (semaphores and fences) - initImGui(); // Initialize Dear ImGui for GUI rendering + createInstance(); // Create the Vulkan instance + setupDebugMessenger(); // Set up debug messaging (validation layers) + createSurface(); // Create the window surface + pickPhysicalDevice(); // Select a suitable GPU + createLogicalDevice(); // Create a logical device from the chosen GPU + createSwapChain(); // Create the swap chain for presenting images + createImageViews(); // Create image views for the swap chain images + createRenderPass(); // Set up the render pass + createDescriptorSetLayout(); // Create the descriptor set layout + createGraphicsPipeline(); // Create the graphics pipeline + createCrosshairPipeline(); // Create the crosshair pipeline + createCommandPool(); // Create a command pool for allocating command buffers + createColorResources(); // Create resources for multisampling + createDepthResources(); // Create resources for depth testing + createFramebuffers(); // Create framebuffers for rendering + createTextureImage(); // Load and create the texture image + createTextureImageView(); // Create an image view for the texture + createTextureSampler(); // Create a sampler for the texture + loadModel(); // Load the 3D model + createVertexBuffer(); // Create a buffer for vertex data + createIndexBuffer(); // Create a buffer for index data + createUniformBuffers(); // Create uniform buffers for shader parameters + createLightUniformBuffers(); // Create uniform buffers for light parameters + createCameraUniformBuffers(); // Create uniform buffers for camera parameters + createDescriptorPool(); // Create a descriptor pool + createDescriptorSets(); // Allocate and update descriptor sets + createCommandBuffers(); // Create command buffers for rendering commands + createCrosshairBuffers(); // Create crosshair buffers + createSyncObjects(); // Create synchronization objects (semaphores and fences) + initImGui(); // Initialize Dear ImGui for GUI rendering } /** @@ -564,6 +627,107 @@ class VulkanApp { ++frameCounter; vkfw::pollEvents(); + + // Start the ImGui frame + ImGui_ImplVulkan_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + // Create ImGui window with controls + ImGui::Begin("Controls"); + + // Camera Position + auto pos = mCamera.getPosition(); + ImGui::Text("Camera Position: (%.2f, %.2f, %.2f)", pos.x, pos.y, pos.z); + + // Camera Settings + if (ImGui::CollapsingHeader("Camera Settings", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::SliderFloat("Camera Speed", &mCameraSpeed, 0.1F, 10.0F, "%.1f"); + ImGui::SliderFloat("Field of View", &mFieldOfView, 45.0F, 120.0F, "%.1f"); + if (ImGui::Button("Reset Camera")) { + mCamera = Camera(); // Reset to default position + } + } + + // Rendering Settings + if (ImGui::CollapsingHeader("Rendering Settings", ImGuiTreeNodeFlags_DefaultOpen)) { + // Wireframe Mode + if (ImGui::Checkbox("Wireframe Mode", &mWireframeMode)) { + recreateSwapChain(); + } + + // Line Width (only in wireframe mode) + static float LineWidth = 1.0F; + if (mWireframeMode) { + if (ImGui::SliderFloat("Line Width", &LineWidth, 1.0F, 5.0F, "%.1f")) { + // TODO: Update line width in pipeline + } + } + } + + // Controls Help + if (ImGui::CollapsingHeader("Controls", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::BulletText("Use mouse to look around"); + ImGui::BulletText("WASD to move horizontally"); + ImGui::BulletText("Space/Shift to move up/down"); + ImGui::BulletText("ESC to toggle mouse capture"); + ImGui::BulletText("Tab to toggle this menu"); + } + + // Performance Metrics + if (ImGui::CollapsingHeader("Performance", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Text( + "Application average %.3f ms/frame (%.1f FPS)", + static_cast(1000.0F / ImGui::GetIO().Framerate), + static_cast(ImGui::GetIO().Framerate) + ); + + // Memory Usage + ImGui::Separator(); + ImGui::Text("Memory Usage:"); + ImGui::Text( + "Vertex Buffer: %.2f MB", + (static_cast(mVertices.size()) * static_cast(sizeof(Vertex))) / (1024.0 * 1024.0) + ); + ImGui::Text( + "Index Buffer: %.2f MB", + (static_cast(mIndices.size()) * static_cast(sizeof(uint32_t))) / (1024.0 * 1024.0) + ); + + // Camera Information + ImGui::Separator(); + ImGui::Text("Camera Information:"); + ImGui::Text( + "Position: (%.2f, %.2f, %.2f)", + mCamera.getPosition().x, + mCamera.getPosition().y, + mCamera.getPosition().z + ); + ImGui::Text( + "Front Vector: (%.2f, %.2f, %.2f)", mCamera.getFront().x, mCamera.getFront().y, mCamera.getFront().z + ); + ImGui::Text("Yaw: %.2f°, Pitch: %.2f°", mCamera.getYaw(), mCamera.getPitch()); + + // Light Controls + ImGui::Separator(); + ImGui::Text("Light Controls:"); + + // Light Position + ImGui::DragFloat3("Light Position", &mLightSettings.position.x, 0.1F, -10.0F, 10.0F); + + // Light Color + ImGui::ColorEdit3("Light Color", &mLightSettings.color.x); + + // Light Strengths + ImGui::SliderFloat("Ambient Strength", &mLightSettings.ambient_strength, 0.0F, 1.0F); + ImGui::SliderFloat("Specular Strength", &mLightSettings.specular_strength, 0.0F, 1.0F); + } + + ImGui::End(); + + // Render ImGui + ImGui::Render(); + drawFrame(); } @@ -603,13 +767,17 @@ class VulkanApp { createImageViews(); createRenderPass(); createGraphicsPipeline(); + createCrosshairPipeline(); createColorResources(); createDepthResources(); createFramebuffers(); createUniformBuffers(); + createLightUniformBuffers(); + createCameraUniformBuffers(); createDescriptorPool(); createDescriptorSets(); createCommandBuffers(); + createCrosshairBuffers(); mImagesInFlight.resize(mSwapChainImages.size(), nullptr); } @@ -981,15 +1149,36 @@ class VulkanApp { .pImmutableSamplers = nullptr, }; - vk::DescriptorSetLayoutBinding samplerLayoutBinding { + vk::DescriptorSetLayoutBinding lightLayoutBinding { .binding = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eFragment, + .pImmutableSamplers = nullptr, + }; + + vk::DescriptorSetLayoutBinding cameraLayoutBinding { + .binding = 2, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eFragment, + .pImmutableSamplers = nullptr, + }; + + vk::DescriptorSetLayoutBinding samplerLayoutBinding { + .binding = 3, .descriptorType = vk::DescriptorType::eCombinedImageSampler, .descriptorCount = 1, .stageFlags = vk::ShaderStageFlagBits::eFragment, .pImmutableSamplers = nullptr, }; - std::array bindings = { uboLayoutBinding, samplerLayoutBinding }; + std::array bindings = { + uboLayoutBinding, + lightLayoutBinding, + cameraLayoutBinding, + samplerLayoutBinding, + }; vk::DescriptorSetLayoutCreateInfo layoutInfo { .bindingCount = static_cast(bindings.size()), @@ -1033,7 +1222,7 @@ class VulkanApp { }; vk::VertexInputBindingDescription bindingDescription = Vertex::getBindingDescription(); - std::array attributeDescriptions = + std::array attributeDescriptions = Vertex::getAttributeDescriptions(); vk::PipelineVertexInputStateCreateInfo vertexInputInfo { @@ -1132,6 +1321,152 @@ class VulkanApp { mGraphicsPipeline = std::move(graphicsPipelineValue); } + fn createCrosshairPipeline() -> void { + // Create pipeline layout (no descriptor sets or push constants needed) + vk::PipelineLayoutCreateInfo pipelineLayoutInfo {}; + mCrosshairPipelineLayout = mDevice->createPipelineLayoutUnique(pipelineLayoutInfo); + + // Load shaders + auto vertShaderCode = + ShaderCompiler::getCompiledShader(CROSSHAIR_VERTEX_SHADER_PATH, shaderc_vertex_shader); + auto fragShaderCode = + ShaderCompiler::getCompiledShader(CROSSHAIR_FRAGMENT_SHADER_PATH, shaderc_fragment_shader); + + vk::UniqueShaderModule vertShaderModule = createShaderModule(vertShaderCode); + vk::UniqueShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + vk::PipelineShaderStageCreateInfo vertShaderStageInfo { .stage = vk::ShaderStageFlagBits::eVertex, + .module = vertShaderModule.get(), + .pName = "main" }; + + vk::PipelineShaderStageCreateInfo fragShaderStageInfo { .stage = vk::ShaderStageFlagBits::eFragment, + .module = fragShaderModule.get(), + .pName = "main" }; + + std::array shaderStages = { vertShaderStageInfo, fragShaderStageInfo }; + + // Vertex input + auto bindingDescription = CrosshairVertex::getBindingDescription(); + auto attributeDescriptions = CrosshairVertex::getAttributeDescriptions(); + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo { + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), + .pVertexAttributeDescriptions = attributeDescriptions.data() + }; + + // Input assembly + vk::PipelineInputAssemblyStateCreateInfo inputAssembly { .topology = vk::PrimitiveTopology::eLineList, + .primitiveRestartEnable = false }; + + // Viewport and scissor + vk::PipelineViewportStateCreateInfo viewportState { .viewportCount = 1, .scissorCount = 1 }; + + // Rasterization + vk::PipelineRasterizationStateCreateInfo rasterizer { .depthClampEnable = false, + .rasterizerDiscardEnable = false, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eNone, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = false, + .lineWidth = 1.0F }; + + // Multisampling + vk::PipelineMultisampleStateCreateInfo multisampling { .rasterizationSamples = mMsaaSamples, + .sampleShadingEnable = false }; + + // Color blending + vk::PipelineColorBlendAttachmentState colorBlendAttachment { + .blendEnable = false, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | + vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA + }; + + vk::PipelineColorBlendStateCreateInfo colorBlending { .logicOpEnable = false, + .attachmentCount = 1, + .pAttachments = &colorBlendAttachment }; + + // Dynamic state + std::array dynamicStates = { vk::DynamicState::eViewport, vk::DynamicState::eScissor }; + + vk::PipelineDynamicStateCreateInfo dynamicState { .dynamicStateCount = + static_cast(dynamicStates.size()), + .pDynamicStates = dynamicStates.data() }; + + // Depth and stencil + vk::PipelineDepthStencilStateCreateInfo depthStencil { .depthTestEnable = false, + .depthWriteEnable = false, + .depthCompareOp = vk::CompareOp::eLess, + .depthBoundsTestEnable = false, + .stencilTestEnable = false }; + + // Create the pipeline + vk::GraphicsPipelineCreateInfo pipelineInfo { .stageCount = static_cast(shaderStages.size()), + .pStages = shaderStages.data(), + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = mCrosshairPipelineLayout.get(), + .renderPass = mRenderPass.get(), + .subpass = 0 }; + + mCrosshairPipeline = mDevice->createGraphicsPipelineUnique(nullptr, pipelineInfo).value; + } + + fn createCrosshairBuffers() -> void { + // Create vertex buffer + vk::DeviceSize bufferSize = sizeof(crosshairVertices[0]) * crosshairVertices.size(); + + auto stagingBuffer = createBuffer( + bufferSize, + vk::BufferUsageFlagBits::eTransferSrc, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent + ); + + void* data = mDevice->mapMemory(stagingBuffer.second.get(), 0, bufferSize); + memcpy(data, crosshairVertices.data(), bufferSize); + mDevice->unmapMemory(stagingBuffer.second.get()); + + auto [vertexBuffer, vertexBufferMemory] = createBuffer( + bufferSize, + vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, + vk::MemoryPropertyFlagBits::eDeviceLocal + ); + + copyBuffer(stagingBuffer.first.get(), vertexBuffer.get(), bufferSize); + mCrosshairVertexBuffer = std::move(vertexBuffer); + mCrosshairVertexBufferMemory = std::move(vertexBufferMemory); + + // Create index buffer + bufferSize = sizeof(crosshairIndices[0]) * crosshairIndices.size(); + + auto stagingBufferIndices = createBuffer( + bufferSize, + vk::BufferUsageFlagBits::eTransferSrc, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent + ); + + data = mDevice->mapMemory(stagingBufferIndices.second.get(), 0, bufferSize); + memcpy(data, crosshairIndices.data(), bufferSize); + mDevice->unmapMemory(stagingBufferIndices.second.get()); + + auto [indexBuffer, indexBufferMemory] = createBuffer( + bufferSize, + vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, + vk::MemoryPropertyFlagBits::eDeviceLocal + ); + + copyBuffer(stagingBufferIndices.first.get(), indexBuffer.get(), bufferSize); + mCrosshairIndexBuffer = std::move(indexBuffer); + mCrosshairIndexBufferMemory = std::move(indexBufferMemory); + } + /** * @brief Creates framebuffers for the swap chain images. * @@ -1710,10 +2045,15 @@ class VulkanApp { attrib.vertices[static_cast((3 * index.vertex_index) + 2)], }, .color = { 1.0F, 1.0F, 1.0F }, - .tex_coord = { + .texCoord = { attrib.texcoords[static_cast((2 * index.texcoord_index) + 0)], 1.0F - attrib.texcoords[static_cast((2 * index.texcoord_index) + 1)], }, + .normal = { + attrib.normals[static_cast((3 * index.normal_index) + 0)], + attrib.normals[static_cast((3 * index.normal_index) + 1)], + attrib.normals[static_cast((3 * index.normal_index) + 2)], + }, }; if (!uniqueVertices.contains(vertex)) { @@ -1803,14 +2143,52 @@ class VulkanApp { mUniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); mUniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); - for (usize i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - std::tie(mUniformBuffers[i], mUniformBuffersMemory[i]) = createBuffer( + for (usize idx = 0; idx < MAX_FRAMES_IN_FLIGHT; idx++) { + std::tie(mUniformBuffers[idx], mUniformBuffersMemory[idx]) = createBuffer( bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent ); - mUniformBuffersMapped[i] = mDevice->mapMemory(mUniformBuffersMemory[i].get(), 0, bufferSize); + mUniformBuffersMapped[idx] = mDevice->mapMemory(mUniformBuffersMemory[idx].get(), 0, bufferSize); + } + } + + fn createLightUniformBuffers() -> void { + vk::DeviceSize bufferSize = sizeof(LightInfo); + + mLightUniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + mLightUniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + mLightUniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); + + for (usize idx = 0; idx < MAX_FRAMES_IN_FLIGHT; idx++) { + std::tie(mLightUniformBuffers[idx], mLightUniformBuffersMemory[idx]) = createBuffer( + bufferSize, + vk::BufferUsageFlagBits::eUniformBuffer, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent + ); + + mLightUniformBuffersMapped[idx] = + mDevice->mapMemory(mLightUniformBuffersMemory[idx].get(), 0, bufferSize); + } + } + + fn createCameraUniformBuffers() -> void { + vk::DeviceSize bufferSize = sizeof(CameraInfo); + + mCameraUniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + mCameraUniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + mCameraUniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); + + for (usize idx = 0; idx < MAX_FRAMES_IN_FLIGHT; idx++) { + std::tie(mCameraUniformBuffers[idx], mCameraUniformBuffersMemory[idx]) = createBuffer( + bufferSize, + vk::BufferUsageFlagBits::eUniformBuffer, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent + ); + + mCameraUniformBuffersMapped[idx] = + mDevice->mapMemory(mCameraUniformBuffersMemory[idx].get(), 0, bufferSize); } } @@ -1821,7 +2199,15 @@ class VulkanApp { * The pool is sized to accommodate the number of frames in flight. */ fn createDescriptorPool() -> void { - std::array poolSizes = { + std::array poolSizes = { + vk::DescriptorPoolSize { + .type = vk::DescriptorType::eUniformBuffer, + .descriptorCount = MAX_FRAMES_IN_FLIGHT, + }, + vk::DescriptorPoolSize { + .type = vk::DescriptorType::eUniformBuffer, + .descriptorCount = MAX_FRAMES_IN_FLIGHT, + }, vk::DescriptorPoolSize { .type = vk::DescriptorType::eUniformBuffer, .descriptorCount = MAX_FRAMES_IN_FLIGHT, @@ -1858,33 +2244,61 @@ class VulkanApp { mDescriptorSets = mDevice->allocateDescriptorSets(allocInfo); - for (usize i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo { - .buffer = mUniformBuffers[i].get(), + for (usize idx = 0; idx < MAX_FRAMES_IN_FLIGHT; idx++) { + vk::DescriptorBufferInfo uboBufferInfo { + .buffer = mUniformBuffers[idx].get(), .offset = 0, .range = sizeof(UniformBufferObject), }; + vk::DescriptorBufferInfo lightBufferInfo { + .buffer = mLightUniformBuffers[idx].get(), + .offset = 0, + .range = sizeof(LightInfo), + }; + + vk::DescriptorBufferInfo cameraBufferInfo { + .buffer = mCameraUniformBuffers[idx].get(), + .offset = 0, + .range = sizeof(CameraInfo), + }; + vk::DescriptorImageInfo imageInfo { .sampler = mTextureSampler.get(), .imageView = mTextureImageView.get(), .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal, }; - std::array descriptorWrites = { + std::array descriptorWrites = { vk::WriteDescriptorSet { - .dstSet = mDescriptorSets[i], + .dstSet = mDescriptorSets[idx], .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfo, + .pBufferInfo = &uboBufferInfo, }, vk::WriteDescriptorSet { - .dstSet = mDescriptorSets[i], + .dstSet = mDescriptorSets[idx], .dstBinding = 1, .dstArrayElement = 0, .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &lightBufferInfo, + }, + vk::WriteDescriptorSet { + .dstSet = mDescriptorSets[idx], + .dstBinding = 2, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &cameraBufferInfo, + }, + vk::WriteDescriptorSet { + .dstSet = mDescriptorSets[idx], + .dstBinding = 3, + .dstArrayElement = 0, + .descriptorCount = 1, .descriptorType = vk::DescriptorType::eCombinedImageSampler, .pImageInfo = &imageInfo, }, @@ -2170,6 +2584,23 @@ class VulkanApp { // Copy the uniform buffer object to the mapped memory memcpy(mUniformBuffersMapped[mCurrentFrame], &ubo, sizeof(ubo)); + LightInfo lightInfo { + .position = mLightSettings.position, + .color = mLightSettings.color, + .ambient_strength = mLightSettings.ambient_strength, + .specular_strength = mLightSettings.specular_strength, + }; + + // Copy the light uniform buffer object to the mapped memory + memcpy(mLightUniformBuffersMapped[mCurrentFrame], &lightInfo, sizeof(lightInfo)); + + CameraInfo cameraInfo { + .position = glm::vec3(mCamera.getPosition()), // Use actual camera position + }; + + // Copy the camera uniform buffer object to the mapped memory + memcpy(mCameraUniformBuffersMapped[mCurrentFrame], &cameraInfo, sizeof(cameraInfo)); + // Example: Add extra clones with different translations std::vector modelMatrices = { glm::translate(glm::mat4(1.0F), glm::vec3(2.0F, 0.0F, 0.0F)), glm::translate(glm::mat4(1.0F), glm::vec3(-2.0F, 0.0F, 0.0F)), @@ -2195,53 +2626,19 @@ class VulkanApp { commandBuffer.drawIndexed(static_cast(mIndices.size()), 1, 0, 0, 0); } - // Only render ImGui when cursor is not captured - if (!mCursorCaptured) { - ImGui_ImplVulkan_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); + // Draw the crosshair + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, mCrosshairPipeline.get()); - // Create ImGui window with useful controls - ImGui::Begin("Settings", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + std::array vertexBuffers = { mCrosshairVertexBuffer.get() }; + std::array offsets = { 0 }; + commandBuffer.bindVertexBuffers(0, 1, vertexBuffers.data(), offsets.data()); + commandBuffer.bindIndexBuffer(mCrosshairIndexBuffer.get(), 0, vk::IndexType::eUint16); - // Set initial window size (this will be the minimum size due to AlwaysAutoResize) - ImGui::SetWindowSize(ImVec2(400, 300), ImGuiCond_FirstUseEver); + // Draw the crosshair + commandBuffer.drawIndexed(static_cast(crosshairIndices.size()), 1, 0, 0, 0); - // Camera settings - if (ImGui::CollapsingHeader("Camera")) { - ImGui::SliderFloat("Camera Speed", &mCameraSpeed, 0.1F, 5.0F); - - glm::dvec3 pos = mCamera.getPosition(); - ImGui::Text("Position: (%.2f, %.2f, %.2f)", pos.x, pos.y, pos.z); - } - - // Rendering settings - if (ImGui::CollapsingHeader("Rendering")) { - if (ImGui::Checkbox("Wireframe Mode", &mWireframeMode)) { - // Wait for all operations to complete - mDevice->waitIdle(); - - // Store the old pipeline for deletion after the next frame - if (mGraphicsPipeline) - mOldPipeline = std::move(mGraphicsPipeline); - - // Recreate the pipeline - createGraphicsPipeline(); - } - - ImGui::SliderFloat("Field of View", &mFieldOfView, 45.0F, 120.0F); - } - - // Performance metrics - if (ImGui::CollapsingHeader("Performance")) { - const f32 framerate = ImGui::GetIO().Framerate; - ImGui::Text("%.1f FPS", static_cast(framerate)); - ImGui::Text("%.3f ms/frame", static_cast(1000.0F / framerate)); - } - - ImGui::End(); - - ImGui::Render(); + // Render ImGui if we have a draw data (ImGui::Render was called) + if (ImGui::GetDrawData()) { ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), commandBuffer); } diff --git a/src/util/crosshair.hpp b/src/util/crosshair.hpp new file mode 100644 index 0000000..8bd2bca --- /dev/null +++ b/src/util/crosshair.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include + +#define VULKAN_HPP_NO_CONSTRUCTORS +#include + +#include "types.hpp" + +struct CrosshairVertex { + vec2 pos; + vec3 color; + + static fn getBindingDescription() -> vk::VertexInputBindingDescription { + return { .binding = 0, .stride = sizeof(CrosshairVertex), .inputRate = vk::VertexInputRate::eVertex }; + } + + static fn getAttributeDescriptions() -> std::array { + return { + vk::VertexInputAttributeDescription { .location = 0, + .binding = 0, + .format = vk::Format::eR32G32Sfloat, + .offset = offsetof(CrosshairVertex, pos) }, + vk::VertexInputAttributeDescription { .location = 1, + .binding = 0, + .format = vk::Format::eR32G32B32Sfloat, + .offset = offsetof(CrosshairVertex, color) } + }; + } +}; + +// Crosshair vertices (in normalized device coordinates) +constexpr f32 CROSSHAIR_SIZE = 0.02F; +constexpr std::array crosshairVertices = { + // Horizontal line + CrosshairVertex { { -CROSSHAIR_SIZE * (9.0F / 16.0F), 0.0F }, { 1.0F, 1.0F, 1.0F } }, // Left + CrosshairVertex { { CROSSHAIR_SIZE * (9.0F / 16.0F), 0.0F }, { 1.0F, 1.0F, 1.0F } }, // Right + // Vertical line + CrosshairVertex { { 0.0F, -CROSSHAIR_SIZE }, { 1.0F, 1.0F, 1.0F } }, // Bottom + CrosshairVertex { { 0.0F, CROSSHAIR_SIZE }, { 1.0F, 1.0F, 1.0F } } // Top +}; + +// Indices for drawing the crosshair lines +constexpr std::array crosshairIndices = { + 0, + 1, // Horizontal line + 2, + 3 // Vertical line +}; diff --git a/src/util/types.hpp b/src/util/types.hpp index 57ae50f..57739d6 100644 --- a/src/util/types.hpp +++ b/src/util/types.hpp @@ -1,7 +1,7 @@ /** * @file types.hpp * @brief Core type definitions and aliases for the project. - * + * * This file provides a centralized location for type definitions used throughout * the project. It includes fixed-width integer types, floating-point types, and * commonly used GLM vector types. The type aliases are designed to improve code @@ -12,8 +12,8 @@ #include #include -#include #include +#include #define fn auto diff --git a/src/util/unique_image.hpp b/src/util/unique_image.hpp index 01ea809..7714ce2 100644 --- a/src/util/unique_image.hpp +++ b/src/util/unique_image.hpp @@ -1,13 +1,14 @@ /** * @file unique_image.hpp * @brief Provides RAII wrapper for image loading and management. - * + * * This file implements a RAII-compliant image handling class that uses stb_image * for loading various image formats. It ensures proper resource management and * provides a safe interface for image data access. */ #include +#include #define STB_IMAGE_IMPLEMENTATION #include @@ -17,7 +18,7 @@ namespace stb { /** * @brief RAII wrapper for image data loaded via stb_image. - * + * * This class provides safe resource management for loaded images, ensuring proper * cleanup of image data. It supports move semantics but prevents copying to maintain * single ownership of image resources. @@ -26,41 +27,41 @@ namespace stb { public: /** * @brief Constructs a UniqueImage by loading from file. - * + * * @param path Filesystem path to the image file. * @throws std::runtime_error If image loading fails. - * + * * Automatically loads the image data from the specified file using stb_image. * The image data is stored in RGBA format with 8 bits per channel. */ UniqueImage(const std::filesystem::path& path) { load(path.string().c_str()); } // Prevent copying to maintain single ownership - UniqueImage(const UniqueImage&) = delete; - fn operator=(const UniqueImage&) -> UniqueImage& = delete; + UniqueImage(const UniqueImage&) = delete; + fn operator=(const UniqueImage&)->UniqueImage& = delete; /** * @brief Move constructor for transferring image ownership. - * + * * @param other Source UniqueImage to move from. - * + * * Transfers ownership of image data from another UniqueImage instance, * leaving the source in a valid but empty state. */ UniqueImage(UniqueImage&& other) noexcept - : mData(other.mData), - mWidth(static_cast(other.mWidth)), - mHeight(static_cast(other.mHeight)), + : mData(other.mData), + mWidth(static_cast(other.mWidth)), + mHeight(static_cast(other.mHeight)), mChannels(static_cast(other.mChannels)) { other.mData = nullptr; } /** * @brief Move assignment operator for transferring image ownership. - * + * * @param other Source UniqueImage to move from. * @return Reference to this object. - * + * * Safely transfers ownership of image data, ensuring proper cleanup of * existing resources before the transfer. */ @@ -80,7 +81,7 @@ namespace stb { /** * @brief Destructor that ensures proper cleanup of image resources. - * + * * Automatically frees the image data using stb_image_free when the * object goes out of scope. */ @@ -92,7 +93,7 @@ namespace stb { /** * @brief Get raw pointer to image data. * @return Pointer to the raw image data in memory. - * + * * The data is stored in row-major order with either RGB or RGBA format, * depending on the source image. */ @@ -119,10 +120,10 @@ namespace stb { private: /** * @brief Internal helper function to load image data. - * + * * @param path Path to the image file. * @throws std::runtime_error If image loading fails. - * + * * Uses stb_image to load the image data, automatically detecting the * format and number of channels from the file. */ diff --git a/src/util/vertex.hpp b/src/util/vertex.hpp index 0e2ba69..dfead00 100644 --- a/src/util/vertex.hpp +++ b/src/util/vertex.hpp @@ -1,9 +1,9 @@ /** * @file vertex.hpp * @brief Defines the vertex structure and its associated utilities for 3D rendering. - * + * * This file contains the Vertex structure used for 3D model representation in the Vulkan - * graphics pipeline. It includes position, color, and texture coordinate data, along with + * graphics pipeline. It includes position, color, texture coordinate, and normal data, along with * Vulkan-specific descriptors for vertex input handling. */ @@ -18,80 +18,107 @@ #include "types.hpp" /** - * @brief Represents a vertex in 3D space with color and texture information. - * + * @brief Represents a vertex in 3D space with color, texture, and normal information. + * * This structure defines a vertex with all its attributes required for rendering, - * including position in 3D space, RGB color, and texture coordinates. It also + * including position in 3D space, RGB color, texture coordinates, and normal vector. It also * provides methods for Vulkan vertex input configuration. */ struct Vertex { - vec3 pos; ///< Position of the vertex in 3D space (x, y, z) - vec3 color; ///< RGB color values, each component in range [0, 1] - vec2 tex_coord; ///< Texture coordinates (u, v) for texture mapping + glm::vec3 pos; ///< Position of the vertex in 3D space (x, y, z) + glm::vec3 color; ///< RGB color values, each component in range [0, 1] + glm::vec2 texCoord; ///< Texture coordinates (u, v) for texture mapping + glm::vec3 normal; ///< Normal vector of the vertex /** * @brief Provides the vertex binding description for Vulkan. - * + * * @return vk::VertexInputBindingDescription Describes how to bind vertex data to GPU memory. - * + * * The binding description specifies: * - Binding index (0) * - Stride (size of one vertex) * - Input rate (per-vertex data) */ static fn getBindingDescription() -> vk::VertexInputBindingDescription { - return { .binding = 0, .stride = sizeof(Vertex), .inputRate = vk::VertexInputRate::eVertex }; + return vk::VertexInputBindingDescription { + .binding = 0, + .stride = sizeof(Vertex), + .inputRate = vk::VertexInputRate::eVertex, + }; } /** * @brief Provides attribute descriptions for vertex data interpretation. - * - * @return std::array Array of descriptions for position, color, and texture coordinates. - * + * + * @return std::array Array of descriptions for position, color, texture coordinates, and normal. + * * The attribute descriptions specify: - * - Location indices (0 for position, 1 for color, 2 for texture coordinates) + * - Location indices (0 for position, 1 for color, 2 for texture coordinates, 3 for normal) * - Binding point (0) * - Data format (R32G32B32 for vec3, R32G32 for vec2) * - Offset of each attribute in the vertex structure */ - static fn getAttributeDescriptions() -> std::array { + static fn getAttributeDescriptions() -> std::array { return { - vk::VertexInputAttributeDescription { 0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos) }, - vk::VertexInputAttributeDescription { 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) }, - vk::VertexInputAttributeDescription { 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, tex_coord) } + vk::VertexInputAttributeDescription { + .location = 0, + .binding = 0, + .format = vk::Format::eR32G32B32Sfloat, + .offset = offsetof(Vertex, pos), + }, + vk::VertexInputAttributeDescription { + .location = 1, + .binding = 0, + .format = vk::Format::eR32G32B32Sfloat, + .offset = offsetof(Vertex, color), + }, + vk::VertexInputAttributeDescription { + .location = 2, + .binding = 0, + .format = vk::Format::eR32G32Sfloat, + .offset = offsetof(Vertex, texCoord), + }, + vk::VertexInputAttributeDescription { + .location = 3, + .binding = 0, + .format = vk::Format::eR32G32B32Sfloat, + .offset = offsetof(Vertex, normal), + }, }; } /** * @brief Compares two vertices for equality. - * + * * @param other The vertex to compare with. - * @return bool True if vertices are identical in position, color, and texture coordinates. + * @return bool True if vertices are identical in position, color, texture coordinates, and normal. */ - fn operator==(const Vertex& other) const -> bool { - return pos == other.pos && color == other.color && tex_coord == other.tex_coord; + fn operator==(const Vertex& other) const->bool { + return pos == other.pos && color == other.color && texCoord == other.texCoord && normal == other.normal; } }; namespace std { /** * @brief Hash function specialization for Vertex type. - * + * * This specialization allows Vertex objects to be used as keys in unordered containers. - * The hash combines position, color, and texture coordinate data using bit operations + * The hash combines position, color, texture coordinate, and normal data using bit operations * to create a unique hash value. */ template <> struct hash { /** * @brief Computes hash value for a vertex. - * + * * @param vertex The vertex to hash. * @return size_t Hash value combining all vertex attributes. */ - fn operator()(Vertex const& vertex) const -> size_t { - return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ - (hash()(vertex.tex_coord) << 1); + fn operator()(Vertex const& vertex) const->size_t { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ + (hash()(vertex.texCoord) << 1) ^ + (hash()(vertex.normal) << 2); } }; }