diff options
| -rw-r--r-- | Makefile.am | 16 | ||||
| -rw-r--r-- | src/Makefile.am | 1 | ||||
| -rw-r--r-- | src/ctrl/keyboard.hpp | 2 | ||||
| -rw-r--r-- | src/ctrl/mouse.hpp | 2 | ||||
| -rw-r--r-- | src/engine.cpp | 249 | ||||
| -rw-r--r-- | src/fb/chfb.cpp | 7 | ||||
| -rw-r--r-- | src/fb/chfb.hpp | 5 | ||||
| -rw-r--r-- | src/fb/pixfb.cpp | 9 | ||||
| -rw-r--r-- | src/fb/pixfb.hpp | 5 | ||||
| -rw-r--r-- | src/math/mat4.hpp | 1 | ||||
| -rw-r--r-- | src/math/quat.hpp | 26 | ||||
| -rw-r--r-- | src/math/utils.hpp | 24 | ||||
| -rw-r--r-- | src/math/vector.hpp | 26 | ||||
| -rw-r--r-- | src/o3d/camera.hpp | 10 | ||||
| -rw-r--r-- | src/o3d/deriv_vertex.hpp | 4 | ||||
| -rw-r--r-- | src/o3d/mesh.cpp | 39 | ||||
| -rw-r--r-- | src/o3d/mesh.hpp | 3 | ||||
| -rw-r--r-- | src/o3d/vertex.hpp | 9 | ||||
| -rw-r--r-- | src/o3d/vertex_data.hpp | 5 | ||||
| -rw-r--r-- | src/obj_parser.cpp | 149 | ||||
| -rw-r--r-- | src/renderer.cpp | 13 | ||||
| -rw-r--r-- | src/renderer.hpp | 7 | ||||
| -rw-r--r-- | src/shaders/shader.slang | 34 | ||||
| -rw-r--r-- | src/vulkan_utils.hpp | 14 |
24 files changed, 412 insertions, 248 deletions
diff --git a/Makefile.am b/Makefile.am index 0e3ddf4..3297551 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3,9 +3,21 @@ SUBDIRS = src ACLOCAL_AMFLAGS = -Im4 --install assetsdir = $(datarootdir)/assets -assets_DATA = ../assets/suzanne.obj +assets_DATA = ../assets/suzanne.obj ../assets/viking_room.obj EXTRA_DIST += $(assets_DATA) texturesdir = $(assetsdir)/textures -textures_DATA = ../assets/textures/texture.jpg +textures_DATA = ../assets/textures/texture.jpg ../assets/textures/viking_room.png EXTRA_DIST += $(textures_DATA) + +# assets origin: +# - assets/suzanne.obj +# taken from Blender +# - assets/texture.jpg +# comes from here https://pixabay.com/photos/statue-sculpture-figure-1275469/, modified by the +# author(s) of the vulkan tutorials (see +# https://docs.vulkan.org/tutorial/latest/06_Texture_mapping/00_Images.html#_loading_an_image) +# - assets/viking_room.obj & assets/textures/viking_room.png +# comes from here https://sketchfab.com/3d-models/viking-room-a49f1b8e4f5c4ecf9e1fe7d81915ad38, +# modified by the author(s) of the vulkan tutorials (see +# https://docs.vulkan.org/tutorial/latest/08_Loading_models.html#_sample_mesh) diff --git a/src/Makefile.am b/src/Makefile.am index 805db1f..e8f98a1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -11,6 +11,7 @@ engine_SOURCES = \ vulkan_utils.hpp \ stb_image.c \ fb/chfb.hpp fb/chfb.cpp fb/pixfb.hpp fb/pixfb.cpp \ + math/utils.hpp \ math/vector.hpp \ math/mat4.hpp \ math/quat.hpp \ diff --git a/src/ctrl/keyboard.hpp b/src/ctrl/keyboard.hpp index f7bdf3d..dc8e259 100644 --- a/src/ctrl/keyboard.hpp +++ b/src/ctrl/keyboard.hpp @@ -4,8 +4,6 @@ #include <cstdint> #include "math/vector.hpp" -using engine::math::Vector2; - namespace engine::controllers { enum class KeyboardKey { diff --git a/src/ctrl/mouse.hpp b/src/ctrl/mouse.hpp index 5c7af80..3b45b4d 100644 --- a/src/ctrl/mouse.hpp +++ b/src/ctrl/mouse.hpp @@ -10,7 +10,7 @@ class Mouse { public: constexpr Mouse(MouseMotionCallback mouse_motion_cb) : mouse_motion_cb{mouse_motion_cb} {} - void mouse_motion_event(Vector2 rel) const & { + void mouse_motion_event(engine::math::Vector2 rel) const & { mouse_motion_cb(rel); } diff --git a/src/engine.cpp b/src/engine.cpp index bde3058..aed0970 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -55,6 +55,7 @@ using engine::fb::CharacterFrameBuffer, engine::fb::PixelFrameBuffer, engine::o3d::Scene, + engine::o3d::Object3D, engine::o3d::Mesh, engine::o3d::Triangle, engine::o3d::Camera, @@ -82,9 +83,10 @@ enum class GameType { plane, suzanne, plane_and_suzanne, + test, }; -constexpr GameType game_type { GameType::plane_and_suzanne }; +constexpr GameType game_type { GameType::test }; static void print_usage(std::ostream& output_stream) { output_stream << "Usage: ./engine [-htg] [--help] [--term] [--graphical]\n" @@ -139,7 +141,7 @@ static void scene_main(const Matrix4& final_transform_mat, Scene& scene, if (kb.is_down(KeyboardKey::fw) || kb.is_down(KeyboardKey::key_left) || kb.is_down(KeyboardKey::bw) || kb.is_down(KeyboardKey::key_right)) movement.normalize(); - scene.camera.transform.loc += movement.rot(Quaternion::rot_y(ry)) * movement_speed * ellapsed_time; + scene.camera.transform.loc += Quaternion::rot_y(ry).rot(movement) * movement_speed * ellapsed_time; scene.camera.transform.rot = Quaternion::euler_zxy(rx, ry, 0.f); scene.camera.fov = (kb.is_down(KeyboardKey::zoom) ? 40.f : 80.f) * PI / 180.f; @@ -152,7 +154,7 @@ static void scene_main(const Matrix4& final_transform_mat, Scene& scene, auto proj_mat = final_transform_mat * Matrix4::perspective(scene.camera.fov, static_cast<float>(surface_width) / static_cast<float>(surface_height), .5f, 12.f); - render_and_present_frame(scene.camera.transform.to_inverse_mat4(), proj_mat, time, ellapsed_time); + render_and_present_frame(scene.camera.transform.to_inverse_mat4(), proj_mat, scene.camera.transform.to_mat4(), time, ellapsed_time); } } @@ -162,7 +164,7 @@ static void render_software(Renderer<FrameBuffer>& renderer, const Matrix4& fina PollEventsFn poll_events, UpdateRendererSizeFn update_renderer_size, PresentFrameFn present_frame) { scene_main(final_transform_mat, scene, // update_surface_size - [&]() { + [&] { update_renderer_size(); return std::tuple { renderer.width(), renderer.height() }; }, @@ -170,25 +172,33 @@ static void render_software(Renderer<FrameBuffer>& renderer, const Matrix4& fina poll_events, // render_and_present_frame - [&](const Matrix4& view_mat, const Matrix4& proj_mat, float /* time */, float /* ellapsed_time */) { - auto proj_view_mat = proj_mat * view_mat; + [&](const Matrix4& /* view_mat */, const Matrix4& /* proj_mat */, const Matrix4& /* inv_view_mat */, float /* time */, float /* ellapsed_time */) { + // TODO: remove renderer.clear(); - for (const auto& obj : scene.objs) { - auto model_mat = obj.transform.to_mat4(); - auto final_mat = proj_view_mat * model_mat; - const auto& mesh = obj.mesh; - std::vector<Vector4> vertices; - std::vector<VertexData> vertices_data; - for (const auto& vertex : mesh.vertices) { - vertices.push_back(final_mat * Vector4 { vertex, 1.f }); - vertices_data.push_back(VertexData((model_mat * vertex).xyz())); - } - for (const auto& triangle_indices : mesh.indices) { - [&]<std::size_t... j>(std::integer_sequence<std::size_t, j...>) { - renderer.draw_triangle({{vertices[triangle_indices[j][0]], mesh.normals[triangle_indices[j][1]], vertices_data[triangle_indices[j][0]]}...}); - }(std::make_integer_sequence<std::size_t, 3>()); - } - } + // auto proj_view_mat = proj_mat * view_mat; + // renderer.clear(); + // for (const auto& obj : scene.objs) { + // auto model_mat = obj.transform.to_mat4(); + // auto final_mat = proj_view_mat * model_mat; + // const auto& mesh = obj.mesh; + // std::vector<Vector4> vertices; + // std::vector<Vector3> normals; + // std::vector<VertexData> vertices_data; + // for (const auto& vertex : mesh.vertices) { + // Vector4 vertex4 { vertex, 1.f }; + // vertices.push_back(final_mat * vertex4); + // vertices_data.push_back(VertexData((model_mat * vertex4).xyz())); + // } + // for (const auto& normal : mesh.normals) + // normals.push_back((model_mat * Vector4 { normal, 0.f }).xyz()); + // for (const auto& triangle_indices : mesh.indices) { + // [&]<std::size_t... j>(std::index_sequence<j...>) { + // renderer.draw_triangle( + // {{vertices[triangle_indices[j][0]], normals[triangle_indices[j][1]], vertices_data[triangle_indices[j][0]]}...} + // ); + // }(std::make_index_sequence<3>()); + // } + // } present_frame(); } ); @@ -208,10 +218,10 @@ static int main_term(Scene& scene) { set_escdelay(0); curs_set(0); - auto renderer = [&]() { + auto renderer = [&] { int w, h; getmaxyx(stdscr, h, w); - return Renderer<CharacterFrameBuffer> { CharacterFrameBuffer { static_cast<unsigned int>(w), static_cast<unsigned int>(h) } }; + return Renderer { CharacterFrameBuffer { static_cast<unsigned int>(w), static_cast<unsigned int>(h) } }; }(); render_software(renderer, Matrix4::scale(Vector3(2.f, 1.f, 1.f)), scene, @@ -289,14 +299,14 @@ static int main_term(Scene& scene) { }, // update_renderer_size - [&]() { + [&] { int w, h; getmaxyx(stdscr, h, w); renderer.resize(static_cast<unsigned int>(w), static_cast<unsigned int>(h)); }, // present_frame - [&]() { + [&] { mvaddnstr(0, 0, renderer.fb.chars(), renderer.width() * renderer.height()); } ); @@ -331,6 +341,7 @@ constexpr bool enable_validation_layers = true; constexpr size_t max_frames_in_flight = 2; constexpr bool show_fps = false; +constexpr bool transparent_window = false; struct PhysicalDeviceEntry { uint32_t idx; @@ -347,7 +358,7 @@ enum class GraphicalRendererMode { }; static bool check_validation_layer_support() { - auto avail_layers = [&]() { + auto avail_layers = [&] { uint32_t avail_layers_count; if (VkResult res = vkEnumerateInstanceLayerProperties(&avail_layers_count, nullptr); res != VK_SUCCESS) { std::cerr << "failed to enumerate instance layer properties, error code: " << string_VkResult(res) << std::endl; @@ -477,7 +488,7 @@ static uint32_t find_mem_type(VkPhysicalDevice physical_device, uint32_t type_fi static std::tuple<VkBuffer, VkDeviceMemory> create_buf(VkPhysicalDevice physical_device, VkDevice device, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags mem_flags) { - auto buf = [&]() { + auto buf = [&] { VkBufferCreateInfo buf_ci { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .pNext = nullptr, @@ -499,7 +510,7 @@ create_buf(VkPhysicalDevice physical_device, VkDevice device, VkDeviceSize size, VkMemoryRequirements buf_mem_requirements; vkGetBufferMemoryRequirements(device, buf, &buf_mem_requirements); - auto buf_device_mem = [&]() { + auto buf_device_mem = [&] { VkMemoryAllocateInfo buf_mem_ai { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .pNext = nullptr, @@ -522,7 +533,7 @@ create_buf(VkPhysicalDevice physical_device, VkDevice device, VkDeviceSize size, } static VkCommandBuffer begin_single_time_cmds(VkDevice device, VkCommandPool cmd_pool) { - auto cmd_buf = [&]() { + auto cmd_buf = [&] { VkCommandBufferAllocateInfo cmd_buf_ai { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .pNext = nullptr, @@ -612,7 +623,7 @@ static void copy_buf_to_img(VkCommandBuffer cmd_buf, VkBuffer src, VkImage dst, static std::tuple<VkImage, VkDeviceMemory> create_img(VkPhysicalDevice physical_device, VkDevice device, uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags mem_flags) { - auto texture_img = [&]() { + auto texture_img = [&] { VkImageCreateInfo img_ci { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .pNext = nullptr, @@ -639,7 +650,7 @@ create_img(VkPhysicalDevice physical_device, VkDevice device, uint32_t width, ui return texture_img; }(); - auto texture_img_device_mem = [&]() { + auto texture_img_device_mem = [&] { VkMemoryRequirements mem_requirements; vkGetImageMemoryRequirements(device, texture_img, &mem_requirements); VkMemoryAllocateInfo mem_ai { @@ -668,7 +679,7 @@ static std::tuple<VkImage, VkDeviceMemory, VkImageView> create_depth_resources(VkPhysicalDevice physical_device, VkDevice device, VkExtent2D swapchain_extent, VkFormat depth_format) { auto [depth_img, depth_img_device_mem] = create_img(physical_device, device, swapchain_extent.width, swapchain_extent.height, depth_format, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - auto depth_img_view = [&]() { + auto depth_img_view = [&] { VkImageViewCreateInfo img_view_ci { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .pNext = {}, @@ -743,6 +754,10 @@ static int main_graphical(Scene& scene) { // init window glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + if (transparent_window) { + glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); + glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); + } bool fb_resized = false; @@ -777,7 +792,7 @@ static int main_graphical(Scene& scene) { exit(EXIT_FAILURE); } - auto instance = [&]() { + auto instance = [&] { VkApplicationInfo app_info { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pNext = nullptr, @@ -788,7 +803,7 @@ static int main_graphical(Scene& scene) { .apiVersion = VK_API_VERSION_1_4, }; - auto instance_exts = [&]() { + auto instance_exts = [&] { std::vector<const char*> instance_exts; { uint32_t glfw_extension_count; @@ -800,7 +815,7 @@ static int main_graphical(Scene& scene) { return instance_exts; }(); - auto avail_instance_exts = [&]() { + auto avail_instance_exts = [&] { uint32_t avail_instance_exts_count; if (VkResult res = vkEnumerateInstanceExtensionProperties(nullptr, &avail_instance_exts_count, nullptr); res != VK_SUCCESS) { std::cerr << "failed to enumerate instance extension properties, error code: " << string_VkResult(res) << std::endl; @@ -863,7 +878,7 @@ static int main_graphical(Scene& scene) { } // create window surface - auto surface = [&]() { + auto surface = [&] { VkSurfaceKHR surface; if (VkResult res = glfwCreateWindowSurface(instance, window, nullptr, &surface); res != VK_SUCCESS) { std::cerr << "failed to create window surface, error code: " << string_VkResult(res) << std::endl; @@ -873,10 +888,10 @@ static int main_graphical(Scene& scene) { }(); // select physical device and queues - auto [physical_device, graphics_queue_family_index, present_queue_family_index, physical_device_props, physical_device_features, depth_format] = [&]() { + auto [physical_device, graphics_queue_family_index, present_queue_family_index, physical_device_props, physical_device_features, depth_format] = [&] { std::multimap<unsigned, PhysicalDeviceEntry> physical_devices; - auto avail_physical_devices = [&]() { + auto avail_physical_devices = [&] { uint32_t avail_physical_devices_count; if (VkResult res = vkEnumeratePhysicalDevices(instance, &avail_physical_devices_count, nullptr); res != VK_SUCCESS) { std::cerr << "failed to enumerate physical devices, error code: " << string_VkResult(res) << std::endl; @@ -900,7 +915,7 @@ static int main_graphical(Scene& scene) { for (uint32_t i = 0; i < avail_physical_devices.size(); i++) { const auto& avail_physical_device = avail_physical_devices[i]; - auto physical_device_props = [&]() { + auto physical_device_props = [&] { VkPhysicalDeviceProperties2 physical_device_props{}; physical_device_props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; vkGetPhysicalDeviceProperties2(avail_physical_device, &physical_device_props); @@ -910,14 +925,14 @@ static int main_graphical(Scene& scene) { std::cout << " " << (i + 1) << ". " << physical_device_props.properties.deviceName << ":" << std::endl; std::cout << " apiVersion: " << engine::vk::api { physical_device_props.properties.apiVersion } << std::endl; - auto physical_device_features = [&]() { + auto physical_device_features = [&] { VkPhysicalDeviceFeatures2 physical_device_features{}; physical_device_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; vkGetPhysicalDeviceFeatures2(avail_physical_device, &physical_device_features); return physical_device_features; }(); - auto physical_device_ext_props = [&]() { + auto physical_device_ext_props = [&] { uint32_t physical_device_ext_props_count; if (VkResult res = vkEnumerateDeviceExtensionProperties(avail_physical_device, nullptr, &physical_device_ext_props_count, nullptr); res != VK_SUCCESS) { @@ -941,7 +956,7 @@ static int main_graphical(Scene& scene) { std::cout << " " << ext << std::endl; } - auto queue_family_properties = [&]() { + auto queue_family_properties = [&] { uint32_t queue_family_properties_count; vkGetPhysicalDeviceQueueFamilyProperties2(avail_physical_device, &queue_family_properties_count, nullptr); std::vector<VkQueueFamilyProperties2> queue_family_properties(queue_family_properties_count); @@ -975,7 +990,7 @@ static int main_graphical(Scene& scene) { std::cout << "none"; std::cout << std::endl; - auto score = [&]() { + auto score = [&] { if (VK_API_VERSION_VARIANT(physical_device_props.properties.apiVersion) != 0 || physical_device_props.properties.apiVersion < VK_API_VERSION_1_4) return std::optional<unsigned> {}; @@ -1051,7 +1066,7 @@ static int main_graphical(Scene& scene) { best->second.present_queue_family_index, best->second.props, best->second.features, best->second.depth_format }; }(); - auto device = [&]() { + auto device = [&] { // TODO: really weird way of making a single structure if // graphics_queue_family_index == present_queue_family_index // here, in this cas, we create both *CreateInfo, and then tell VkDeviceCreateInfo that @@ -1135,7 +1150,7 @@ static int main_graphical(Scene& scene) { return device; }(); - auto [graphics_queue, present_queue] = [&]() { + auto [graphics_queue, present_queue] = [&] { const auto map_family_to_queue = [&](uint32_t queue_family_index) { VkQueue queue; vkGetDeviceQueue(device, queue_family_index, 0, &queue); @@ -1166,13 +1181,13 @@ static int main_graphical(Scene& scene) { VkDeviceMemory depth_img_device_mem; VkImageView depth_img_view; - const auto destroy_swapchain = [&]() { + const auto destroy_swapchain = [&] { for (auto it_img_view = swapchain_img_views.rbegin(); it_img_view != swapchain_img_views.rend(); ++it_img_view) vkDestroyImageView(device, *it_img_view, nullptr); vkDestroySwapchainKHR(device, swapchain, nullptr); }; - const auto recreate_swapchain = [&]() { + const auto recreate_swapchain = [&] { bool is_first_swapchain = (swapchain == nullptr); if (!is_first_swapchain) { if (VkResult res = vkDeviceWaitIdle(device); res != VK_SUCCESS) { @@ -1185,7 +1200,7 @@ static int main_graphical(Scene& scene) { // TODO: should probably use version 2 of theses functions, but glfwCreateWindowSurface // return version 1, so for now we will use version 1 - auto surface_capabilities = [&]() { + auto surface_capabilities = [&] { VkSurfaceCapabilitiesKHR surface_capabilities; if (VkResult res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &surface_capabilities); res != VK_SUCCESS) { std::cerr << "failed to get physical device surface capabilities, error code: " << string_VkResult(res) << std::endl; @@ -1202,8 +1217,16 @@ static int main_graphical(Scene& scene) { exit(EXIT_FAILURE); } } + { + VkCompositeAlphaFlagsKHR required_composite_alpha = + (transparent_window ? VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR : VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR); + if ((surface_capabilities.supportedCompositeAlpha & required_composite_alpha) != required_composite_alpha) { + std::cerr << "required composite alpha flags not present" << std::endl; + exit(EXIT_FAILURE); + } + } - swapchain_extent = [&]() { + swapchain_extent = [&] { if (surface_capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) return surface_capabilities.currentExtent; int width, height; @@ -1220,8 +1243,8 @@ static int main_graphical(Scene& scene) { }; }(); - surface_format = [&]() { - auto surface_formats = [&]() { + surface_format = [&] { + auto surface_formats = [&] { uint32_t surface_formats_count; if (VkResult res = vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &surface_formats_count, nullptr); res != VK_SUCCESS) { std::cerr << "failed to get physical device surface formats, error code: " << string_VkResult(res) << std::endl; @@ -1246,9 +1269,9 @@ static int main_graphical(Scene& scene) { exit(EXIT_FAILURE); }(); - [&]() { - auto present_mode = [&]() { - auto present_modes = [&]() { + [&] { + auto present_mode = [&] { + auto present_modes = [&] { uint32_t present_modes_count; if (VkResult res = vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &present_modes_count, nullptr); res != VK_SUCCESS) { std::cerr << "failed to get physical device present modes, error code: " << string_VkResult(res) << std::endl; @@ -1293,7 +1316,7 @@ static int main_graphical(Scene& scene) { .queueFamilyIndexCount = {}, .pQueueFamilyIndices = {}, .preTransform = surface_capabilities.currentTransform, - .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + .compositeAlpha = (transparent_window ? VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR : VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR), .presentMode = present_mode, .clipped = VK_TRUE, .oldSwapchain = {}, @@ -1316,7 +1339,7 @@ static int main_graphical(Scene& scene) { } }(); - [&]() { + [&] { uint32_t swapchain_imgs_count; if (VkResult res = vkGetSwapchainImagesKHR(device, swapchain, &swapchain_imgs_count, nullptr); res != VK_SUCCESS) { std::cerr << "failed to get swapchain images, error code: " << string_VkResult(res) << std::endl; @@ -1329,7 +1352,7 @@ static int main_graphical(Scene& scene) { } }(); - [&]() { + [&] { swapchain_img_views.resize(swapchain_imgs.size()); for (uint32_t i = 0; i < swapchain_imgs.size(); i++) { VkImageViewCreateInfo img_view_ci { @@ -1361,10 +1384,10 @@ static int main_graphical(Scene& scene) { recreate_swapchain(); - auto renderer_mode = GraphicalRendererMode::software; + auto renderer_mode = GraphicalRendererMode::hardware; // create descriptor set layout - auto descriptor_set_layout = [&]() { + auto descriptor_set_layout = [&] { std::array descriptor_set_layout_bindings { VkDescriptorSetLayoutBinding { .binding = 0, @@ -1397,10 +1420,10 @@ static int main_graphical(Scene& scene) { }(); // create pipeline - auto [pl_layout, graphics_pl] = [&]() { + auto [pl_layout, graphics_pl] = [&] { // reading shader file - auto shader_module = [&]() { - auto shader_code = [&]() { + auto shader_module = [&] { + auto shader_code = [&] { const char* shader_file_name = SHADERSDIR "/shader.spv"; std::ifstream shader_file(shader_file_name, std::ios::ate | std::ios::binary); if (!shader_file.is_open()) { @@ -1429,7 +1452,7 @@ static int main_graphical(Scene& scene) { return shader_module; }(); - auto [pl_layout, graphics_pl] = [&]() { + auto [pl_layout, graphics_pl] = [&] { std::array pl_shader_stage_cis { VkPipelineShaderStageCreateInfo { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, @@ -1563,7 +1586,7 @@ static int main_graphical(Scene& scene) { .blendConstants = { 0.f, 0.f, 0.f, 0.f }, }; - auto pl_layout = [&]() { + auto pl_layout = [&] { VkPipelineLayoutCreateInfo pl_layout_ci { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .pNext = nullptr, @@ -1628,7 +1651,7 @@ static int main_graphical(Scene& scene) { }(); // create command pool - auto cmd_pool = [&]() { + auto cmd_pool = [&] { VkCommandPoolCreateInfo cmd_pool_ci { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .pNext = nullptr, @@ -1649,9 +1672,9 @@ static int main_graphical(Scene& scene) { std::tie(depth_img, depth_img_device_mem, depth_img_view) = create_depth_resources(physical_device, device, swapchain_extent, depth_format); // create texture image - auto [texture_img, texture_img_device_mem] = [&]() { + auto [texture_img, texture_img_device_mem] = [&] { int w, h, channels; - stbi_uc* pixels = stbi_load(DATADIR "/assets/textures/texture.jpg", &w, &h, &channels, STBI_rgb_alpha); + stbi_uc* pixels = stbi_load(DATADIR "/assets/textures/viking_room.png", &w, &h, &channels, STBI_rgb_alpha); // TODO: we're also using this size as the host pixels buffer size, I don't know if mixing // the two will cause problems later. Right now they are the same, so it doesn't @@ -1708,7 +1731,7 @@ static int main_graphical(Scene& scene) { // TODO: this is the same code, with minor differences, than the one that create swapchain image // views, so maybe we should create a function. The thing is, this code only call a single // function with a single create-info, so I'm not sure if it would make sense - auto texture_img_view = [&]() { + auto texture_img_view = [&] { VkImageViewCreateInfo img_view_ci { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .pNext = {}, @@ -1734,7 +1757,7 @@ static int main_graphical(Scene& scene) { }(); // create texture sampler - auto sampler = [&]() { + auto sampler = [&] { VkSamplerCreateInfo sampler_ci { .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .pNext = nullptr, @@ -1765,7 +1788,7 @@ static int main_graphical(Scene& scene) { }(); // create vertex buffers - auto [vertex_bufs, vertex_buf_device_mems] = [&]() { + auto [vertex_bufs, vertex_buf_device_mems] = [&] { std::vector<VkBuffer> vertex_bufs; std::vector<VkDeviceMemory> vertex_buf_device_mems; for (const auto& vertices : meshes_vertices) { @@ -1817,7 +1840,7 @@ static int main_graphical(Scene& scene) { // TODO: this code is pretty much a duplicate with minor differences of the vertex_bufs one. We // should probably factor it out in a function. Also, every TODOs were remove for this one, but // they still hold - auto [index_bufs, index_buf_device_mems] = [&]() { + auto [index_bufs, index_buf_device_mems] = [&] { std::vector<VkBuffer> index_bufs; std::vector<VkDeviceMemory> index_buf_device_mems; for (const auto& indices : meshes_indices) { @@ -1861,7 +1884,7 @@ static int main_graphical(Scene& scene) { }(); // create uniform buffers - auto [uniform_bufs, uniform_buf_device_mems, uniform_buf_mems] = [&]() { + auto [uniform_bufs, uniform_buf_device_mems, uniform_buf_mems] = [&] { constexpr VkDeviceSize uniform_buf_size = sizeof(engine::vk::UniformBufferObject); std::vector<VkBuffer> uniform_bufs(max_frames_in_flight * scene.objs.size(), nullptr); std::vector<VkDeviceMemory> uniform_buf_device_mems(max_frames_in_flight * scene.objs.size(), nullptr); @@ -1879,7 +1902,7 @@ static int main_graphical(Scene& scene) { }(); // create descriptor pool - auto descriptor_pool = [&]() { + auto descriptor_pool = [&] { std::array descriptor_pool_sizes { VkDescriptorPoolSize { .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, @@ -1912,7 +1935,7 @@ static int main_graphical(Scene& scene) { // TODO: right now, we just send every descriptors every frame and for every object, without // taking into account update frequencies. For example we should have only one set for the // camera, shared between every objects, etc - auto descriptor_sets = [&]() { + auto descriptor_sets = [&] { std::vector<VkDescriptorSetLayout> layouts(max_frames_in_flight * scene.objs.size(), descriptor_set_layout); VkDescriptorSetAllocateInfo descriptor_set_ai { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, @@ -1974,7 +1997,7 @@ static int main_graphical(Scene& scene) { }(); // create command buffers - auto cmd_bufs = [&]() { + auto cmd_bufs = [&] { VkCommandBufferAllocateInfo cmd_buf_ai { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .pNext = nullptr, @@ -1991,10 +2014,10 @@ static int main_graphical(Scene& scene) { }(); // create sync objects - auto [sems_present_complete, sems_render_finished] = [&]() { + auto [sems_present_complete, sems_render_finished] = [&] { // TODO: remove duplicate code - auto sems_present_complete = [&]() { + auto sems_present_complete = [&] { std::array<VkSemaphore, max_frames_in_flight> sems_present_complete; size_t num = 0; for (auto& sem : sems_present_complete) { @@ -2011,7 +2034,7 @@ static int main_graphical(Scene& scene) { return sems_present_complete; }(); - auto sems_render_finished = [&]() { + auto sems_render_finished = [&] { std::vector<VkSemaphore> sems_render_finished(swapchain_imgs.size()); size_t num = 0; for (auto& sem : sems_render_finished) { @@ -2050,7 +2073,7 @@ static int main_graphical(Scene& scene) { } std::cout << " ]" << std::endl; - auto fences_in_flight = [&]() { + auto fences_in_flight = [&] { std::array<VkFence, max_frames_in_flight> fences_in_flight; size_t num = 0; for (auto& fence_in_flight : fences_in_flight) { @@ -2087,7 +2110,7 @@ static int main_graphical(Scene& scene) { std::array<VkDeviceMemory, max_frames_in_flight> software_renderer_buf_device_mems; std::array<void*, max_frames_in_flight> software_renderer_buf_mems; - auto destroy_software_renderer_bufs = [&]() { + auto destroy_software_renderer_bufs = [&] { for (size_t i = max_frames_in_flight; i > 0; i--) { vkUnmapMemory(device, software_renderer_buf_device_mems[i - 1]); vkFreeMemory(device, software_renderer_buf_device_mems[i - 1], nullptr); @@ -2095,7 +2118,7 @@ static int main_graphical(Scene& scene) { } }; - auto recreate_software_renderer_bufs = [&]() { + auto recreate_software_renderer_bufs = [&] { if (!first_software_renderer_buf_creation) { destroy_software_renderer_bufs(); if (software_renderer.width() != swapchain_extent.width || software_renderer.height() != swapchain_extent.height) @@ -2125,7 +2148,7 @@ static int main_graphical(Scene& scene) { scene_main(Matrix4::idty(), scene, // update_surface_size - [&]() { + [&] { return std::tuple { swapchain_extent.width, swapchain_extent.height }; }, @@ -2219,7 +2242,7 @@ static int main_graphical(Scene& scene) { }, // render_and_present_frame - [&](const Matrix4& view_mat, const Matrix4& proj_mat, float /* time */, float ellapsed_time) { + [&](const Matrix4& view_mat, const Matrix4& proj_mat, const Matrix4& inv_view_mat, float /* time */, float ellapsed_time) { if (show_fps) std::cout << "\rfps: " << (1.f / ellapsed_time) << " "; @@ -2246,9 +2269,6 @@ static int main_graphical(Scene& scene) { } } - // TODO: this is where we set uniform buffers for the hardware renderer, and where we - // render for the software renderer. We should do approximately the same thing in the - // same place for both renderers switch (renderer_mode) { case GraphicalRendererMode::hardware: { @@ -2256,20 +2276,24 @@ static int main_graphical(Scene& scene) { for (size_t i = 0; i < scene.objs.size(); i++) { // TODO: should use push constants engine::vk::UniformBufferObject ubo { - .model = scene.objs[i].transform.to_mat4(), - .view = view_mat, - .proj = proj_mat, - .camera_rot = Matrix4::from_quaternion(scene.camera.transform.rot), - .camera_loc = scene.camera.transform.loc, + .model = scene.objs[i].transform.to_mat4(), + .view = view_mat, + .proj = proj_mat, + .inv_view = inv_view_mat, }; memcpy(uniform_buf_mems[frame_idx * scene.objs.size() + i], &ubo, sizeof(engine::vk::UniformBufferObject)); } } break; case GraphicalRendererMode::software: + break; + } + + switch (renderer_mode) { + case GraphicalRendererMode::hardware: + break; + case GraphicalRendererMode::software: { - // TODO: this is a copy-paste of the code in render_software, which is obviously - // bad auto proj_view_mat = proj_mat * view_mat; software_renderer.clear(); for (const auto& obj : scene.objs) { @@ -2277,17 +2301,27 @@ static int main_graphical(Scene& scene) { auto final_mat = proj_view_mat * model_mat; const auto& mesh = obj.mesh; std::vector<Vector4> vertices; + std::vector<Vector3> normals; std::vector<VertexData> vertices_data; for (const auto& vertex : mesh.vertices) { - vertices.push_back(final_mat * Vector4 { vertex, 1.f }); - vertices_data.push_back(VertexData((model_mat * vertex).xyz())); + Vector4 vertex4 { vertex, 1.f }; + vertices.push_back(final_mat * vertex4); + vertices_data.push_back(VertexData((model_mat * vertex4).xyz())); } + for (const auto& normal : mesh.normals) + normals.push_back((model_mat * Vector4 { normal, 0.f }).xyz()); for (const auto& triangle_indices : mesh.indices) { - [&]<std::size_t... j>(std::integer_sequence<std::size_t, j...>) { - software_renderer.draw_triangle( - {{vertices[triangle_indices[j][0]], mesh.normals[triangle_indices[j][1]], vertices_data[triangle_indices[j][0]]}...} - ); - }(std::make_integer_sequence<std::size_t, 3>()); + software_renderer.draw_triangle([&]<size_t... j>(std::index_sequence<j...>) constexpr -> engine::o3d::Triangle { + return { + { + vertices[triangle_indices[j][0]], + normals [triangle_indices[j][1]], + mesh.uvs[triangle_indices[j][2]], + vertices_data[triangle_indices[j][0]] + } + ... + }; + }(std::make_index_sequence<3>())); } } memcpy(software_renderer_buf_mems[frame_idx], software_renderer.fb.pixels(), swapchain_extent.width * swapchain_extent.height * 4); @@ -2380,7 +2414,7 @@ static int main_graphical(Scene& scene) { } { - VkClearValue clear_color_val { .color { .float32 = { 0.f, 0.f, 0.f, 1.f } } }; + VkClearValue clear_color_val { .color { .float32 = { 0.f, 0.f, 0.f, (transparent_window ? 0.f : 1.f) } } }; VkClearValue clear_depth_val { .depthStencil { .depth = 1.f, .stencil = 0 } }; VkRenderingAttachmentInfo rendering_color_info { .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO, @@ -2760,6 +2794,17 @@ int main(int argc, char *argv[]) { } }, }; + case GameType::test: + return { + { + engine::parse_object(DATADIR "/assets/viking_room.obj"), + { + Vector3(0.f, .5f, 0.f), + Quaternion::look_towards({ -1.f, 0.f, 0.f }, { 0.f, 0.f, 1.f }).conjugate(), + Vector3(1.f, 1.f, 1.f), + } + }, + }; } std::unreachable(); }() diff --git a/src/fb/chfb.cpp b/src/fb/chfb.cpp index 9f293ed..7341015 100644 --- a/src/fb/chfb.cpp +++ b/src/fb/chfb.cpp @@ -8,6 +8,7 @@ using namespace engine::fb; using + engine::math::Vector2, engine::math::Vector3, engine::o3d::VertexData, engine::o3d::Camera; @@ -31,10 +32,8 @@ void CharacterFrameBuffer::clear() { char brightness_chars[] = " `.-':_,^=;><+!rc*/z?sLTv)J7(|Fi{C}fI31tlu[neoZ5Yxjya]2ESwqkP6h9d4VpOGbUAKXHm8RD#$Bg0MNWQ%&@"; extern Camera* camera; -void CharacterFrameBuffer::draw_point(int x, int y, const Vector3& loc, const VertexData& vd, const Vector3& normal) { - (void) loc; - (void) vd; - auto v = normal.rot(camera->transform.rot.conjugate()); +void CharacterFrameBuffer::draw_point(int x, int y, const Vector3& /* loc */, const Vector3& normal, const Vector2& /* uv */, const VertexData& /* vd */) { + auto v = camera->transform.rot.conjugate().rot(normal).normalize(); float light = .1f + (v.z < 0.f ? 0.f : v.z) * .9f; std::uint32_t c = (int) (light * static_cast<float>(sizeof(brightness_chars) - 1)); chars_vector[x + y * w] = brightness_chars[c]; diff --git a/src/fb/chfb.hpp b/src/fb/chfb.hpp index 7f6857b..1748abf 100644 --- a/src/fb/chfb.hpp +++ b/src/fb/chfb.hpp @@ -7,14 +7,13 @@ namespace engine::fb { -using engine::math::Vector3, engine::o3d::VertexData; - class CharacterFrameBuffer { public: CharacterFrameBuffer(unsigned int w, unsigned int h); void resize(unsigned int w, unsigned int h); void clear(); - void draw_point(int x, int y, const Vector3& loc, const VertexData& vd, const Vector3& normal); + void draw_point(int x, int y, const engine::math::Vector3& loc, + const engine::math::Vector3& normal, const engine::math::Vector2& uv, const engine::o3d::VertexData& vd); constexpr unsigned int width() const & { return w; diff --git a/src/fb/pixfb.cpp b/src/fb/pixfb.cpp index b38ec30..c629adb 100644 --- a/src/fb/pixfb.cpp +++ b/src/fb/pixfb.cpp @@ -8,6 +8,7 @@ using namespace engine::fb; using + engine::math::Vector2, engine::math::Vector3, engine::o3d::VertexData, engine::o3d::Camera; @@ -29,17 +30,15 @@ void PixelFrameBuffer::clear() { extern Camera* camera; -void PixelFrameBuffer::draw_point(int x, int y, const Vector3& loc, const VertexData& vd, const Vector3& normal) { - (void) loc; - (void) vd; +void PixelFrameBuffer::draw_point(int x, int y, const Vector3& /* loc */, const Vector3& normal, const Vector2& /* uv */, const VertexData& vd) { auto u = vd.world_loc - camera->transform.loc; float u_len_sq = u.length_squared(); u = u.normalize(); - float attenuation = Vector3(0.f, 0.f, -1.f).rot(camera->transform.rot).dot(u); + float attenuation = camera->transform.rot.rot({ 0.f, 0.f, -1.f }).dot(u); if (attenuation < .7f) attenuation = 0.f; else if (attenuation > .8f) attenuation = 1.f; else attenuation = (attenuation - .7f) / .1f; - float light = -normal.dot(u) / u_len_sq; + float light = -normal.normalize().dot(u) / u_len_sq; if (light < 0.f) light = 0.f; float final_light = .003f + light * attenuation * .8f * .997f; if (final_light > 1.f) final_light = 1.f; diff --git a/src/fb/pixfb.hpp b/src/fb/pixfb.hpp index 92fa604..954f6e1 100644 --- a/src/fb/pixfb.hpp +++ b/src/fb/pixfb.hpp @@ -8,14 +8,13 @@ namespace engine::fb { -using engine::math::Vector3, engine::o3d::VertexData; - class PixelFrameBuffer { public: PixelFrameBuffer(unsigned int w, unsigned int h); void resize(unsigned int w, unsigned int h); void clear(); - void draw_point(int x, int y, const Vector3& loc, const VertexData& vd, const Vector3& normal); + void draw_point(int x, int y, const engine::math::Vector3& loc, + const engine::math::Vector3& normal, const engine::math::Vector2& uv, const engine::o3d::VertexData& vd); constexpr unsigned int width() const & { return w; diff --git a/src/math/mat4.hpp b/src/math/mat4.hpp index 6a215d3..b25dbd4 100644 --- a/src/math/mat4.hpp +++ b/src/math/mat4.hpp @@ -4,6 +4,7 @@ #include <array> #include <cmath> #include "math/vector.hpp" +#include "math/quat.hpp" namespace engine::math { diff --git a/src/math/quat.hpp b/src/math/quat.hpp index 51392e7..9687c98 100644 --- a/src/math/quat.hpp +++ b/src/math/quat.hpp @@ -2,6 +2,7 @@ #define MATH_QUAT_HPP #include <cmath> +#include "math/vector.hpp" namespace engine::math { @@ -30,6 +31,23 @@ struct Quaternion { return {std::cos(a / 2.f), 0.f, std::sin(a / 2.f), 0.f}; } + static constexpr Quaternion look_towards(const Vector3& dir, const Vector3& up) { + // TODO: extract common code between Matrix4::look_at and this. We should have something + // similar to a function returning a 3x3 matrix which does: + // e_x -> up.cross(-dir).normalize() + // e_y -> (-dir).cross(e_x).normalize() + // e_z -> (-dir).normalize() + Vector3 new_x = up.cross(-dir).normalize(); + Vector3 new_y = (-dir).cross(new_x).normalize(); + Vector3 new_z = (-dir).normalize(); + return { + std::sqrt(std::max(0.f, new_x.x + new_y.y + new_z.z + 1.f)) / 2.f, + std::copysign(std::sqrt(std::max(0.f, new_x.x - new_y.y - new_z.z + 1.f)) / 2.f, new_y.z - new_z.y), + std::copysign(std::sqrt(std::max(0.f, -new_x.x + new_y.y - new_z.z + 1.f)) / 2.f, new_z.x - new_x.z), + std::copysign(std::sqrt(std::max(0.f, -new_x.x - new_y.y + new_z.z + 1.f)) / 2.f, new_x.y - new_y.x), + }; + } + float w, x, y, z; constexpr Quaternion() {} @@ -71,6 +89,14 @@ struct Quaternion { constexpr Quaternion conjugate() const & { return {w, -x, -y, -z}; } + + constexpr Vector3 rot(const Vector3& v) const & { + return { + (2.f * (w * w + x * x) - 1.f) * v.x + (2.f * (x * y - w * z) ) * v.y + (2.f * (x * z + w * y) ) * v.z, + (2.f * (x * y + w * z) ) * v.x + (2.f * (w * w + y * y) - 1.f) * v.y + (2.f * (y * z - w * x) ) * v.z, + (2.f * (x * z - w * y) ) * v.x + (2.f * (y * z + w * x) ) * v.y + (2.f * (w * w + z * z) - 1.f) * v.z, + }; + } }; } diff --git a/src/math/utils.hpp b/src/math/utils.hpp new file mode 100644 index 0000000..5ec5959 --- /dev/null +++ b/src/math/utils.hpp @@ -0,0 +1,24 @@ +#ifndef MATH_UTILS_HPP +#define MATH_UTILS_HPP + +#include <array> +#include <utility> +#include "math/vector.hpp" + +namespace engine::math::utils { + +template<size_t size> struct Vector; +template<> struct Vector<2> { using type = engine::math::Vector2; }; +template<> struct Vector<3> { using type = engine::math::Vector3; }; +template<> struct Vector<4> { using type = engine::math::Vector4; }; + +template<size_t vector_size> +constexpr Vector<vector_size>::type array_to_vec(const std::array<float, vector_size>& coords) { + return [&]<size_t... i>(std::index_sequence<i...>) constexpr -> Vector<vector_size>::type { + return { coords[i] ... }; + }(std::make_index_sequence<vector_size>()); +} + +} + +#endif // MATH_UTILS_HPP diff --git a/src/math/vector.hpp b/src/math/vector.hpp index e836e0d..471f30d 100644 --- a/src/math/vector.hpp +++ b/src/math/vector.hpp @@ -2,11 +2,20 @@ #define MATH_VECTOR_HPP #include <cmath> -#include "math/quat.hpp" namespace engine::math { +struct Vector2; +constexpr Vector2 operator*(float n, const Vector2& other); +constexpr Vector2 operator/(const Vector2& other, float n); + struct Vector2 { + static constexpr size_t size = 2; + + static constexpr Vector2 bilerp(const Vector2& v1, const Vector2& v2, const Vector2& v3, float b0, float b1) { + return b0 * v1 + b1 * v2 + (1.f - b0 - b1) * v3; + } + float x, y; constexpr bool operator==(const Vector2& other) const & { @@ -77,6 +86,8 @@ constexpr Vector3 operator*(float n, const Vector3& other); constexpr Vector3 operator/(const Vector3& other, float n); struct Vector3 { + static constexpr size_t size = 3; + static constexpr Vector3 bilerp(const Vector3& v1, const Vector3& v2, const Vector3& v3, float b0, float b1) { return b0 * v1 + b1 * v2 + (1.f - b0 - b1) * v3; } @@ -126,14 +137,6 @@ struct Vector3 { }; } - constexpr Vector3 rot(const Quaternion& q) const & { - return { - (2.f * (q.w * q.w + q.x * q.x) - 1.f) * x + (2.f * (q.x * q.y - q.w * q.z) ) * y + (2.f * (q.x * q.z + q.w * q.y) ) * z, - (2.f * (q.x * q.y + q.w * q.z) ) * x + (2.f * (q.w * q.w + q.y * q.y) - 1.f) * y + (2.f * (q.y * q.z - q.w * q.x) ) * z, - (2.f * (q.x * q.z - q.w * q.y) ) * x + (2.f * (q.y * q.z + q.w * q.x) ) * y + (2.f * (q.w * q.w + q.z * q.z) - 1.f) * z, - }; - } - constexpr float dot(const Vector3& other) const & { return x * other.x + y * other.y + z * other.z; } @@ -168,15 +171,14 @@ constexpr Vector3 operator/(const Vector3& other, float n) { } struct Vector4 { + static constexpr size_t size = 4; + float x, y, z, w; constexpr Vector4() {} constexpr Vector4(float x, float y, float z, float w) : x{x}, y{y}, z{z}, w{w} {} - constexpr Vector4(float x, float y, float z) : x{x}, y{y}, z{z}, w{1.f} {} constexpr Vector4(const Vector2& v, float z, float w) : x{v.x}, y{v.y}, z{z}, w{w} {} - constexpr Vector4(const Vector2& v, float z) : x{v.x}, y{v.y}, z{z}, w{1.f} {} constexpr Vector4(const Vector3& v, float w) : x{v.x}, y{v.y}, z{v.z}, w{w} {} - constexpr Vector4(const Vector3& v) : x{v.x}, y{v.y}, z{v.z}, w{1.f} {} constexpr bool operator==(const Vector4& other) const & { return x == other.x && y == other.y && z == other.z && w == other.w; diff --git a/src/o3d/camera.hpp b/src/o3d/camera.hpp index eb10450..ff99c08 100644 --- a/src/o3d/camera.hpp +++ b/src/o3d/camera.hpp @@ -4,18 +4,14 @@ #include "math/mat4.hpp" #include "math/tform.hpp" -using - engine::math::Matrix4, - engine::math::Transform; - namespace engine::o3d { struct Camera { float fov; - Transform transform; + engine::math::Transform transform; - constexpr Matrix4 to_mat4(float aspect_ratio, float min_z, float max_z) const & { - return Matrix4::perspective(fov, aspect_ratio, min_z, max_z) * transform.to_inverse_mat4(); + constexpr engine::math::Matrix4 to_mat4(float aspect_ratio, float min_z, float max_z) const & { + return engine::math::Matrix4::perspective(fov, aspect_ratio, min_z, max_z) * transform.to_inverse_mat4(); } }; diff --git a/src/o3d/deriv_vertex.hpp b/src/o3d/deriv_vertex.hpp index 6fc32e0..5e8dc22 100644 --- a/src/o3d/deriv_vertex.hpp +++ b/src/o3d/deriv_vertex.hpp @@ -5,10 +5,8 @@ namespace engine::o3d { -using engine::math::Vector4; - struct DerivedVertex { - Vector4 vertex; + engine::math::Vector4 vertex; float b0, b1; constexpr DerivedVertex div_by_w() const & { diff --git a/src/o3d/mesh.cpp b/src/o3d/mesh.cpp index ce6da30..fc5ff78 100644 --- a/src/o3d/mesh.cpp +++ b/src/o3d/mesh.cpp @@ -4,11 +4,23 @@ #include <cstdint> #include <cstddef> #include <tuple> +#include <unordered_map> #include "math/vector.hpp" #include "vulkan_utils.hpp" using namespace engine::o3d; +template<size_t indices_size> +struct VertexIndicesHasher { + // taken from boost's container_hash + std::size_t operator()(const std::array<size_t, indices_size>& vertex_indices) const { + size_t h = 0; + for (const auto& idx : vertex_indices) + h ^= std::hash<size_t>{}(idx) + 0x9e3779b9 + (h << 6) + (h >> 2); + return h; + } +}; + Mesh Mesh::plane(float width, float height) { const float w2 = width / 2, h2 = height / 2; @@ -24,23 +36,36 @@ Mesh Mesh::plane(float width, float height) { { 0.f, +1.f, 0.f }, }, { - {{ {{ 0, 0 }}, {{ 1, 0 }}, {{ 2, 0 }} }}, - {{ {{ 2, 0 }}, {{ 3, 0 }}, {{ 0, 0 }} }}, - {{ {{ 0, 1 }}, {{ 3, 1 }}, {{ 2, 1 }} }}, - {{ {{ 2, 1 }}, {{ 1, 1 }}, {{ 0, 1 }} }}, + { 0.f, 0.f }, + { 1.f, 0.f }, + { 1.f, 1.f }, + { 0.f, 1.f }, + }, + { + {{ {{ 0, 0, 0 }}, {{ 1, 0, 1 }}, {{ 2, 0, 2 }} }}, + {{ {{ 2, 0, 2 }}, {{ 3, 0, 3 }}, {{ 0, 0, 0 }} }}, + {{ {{ 0, 1, 0 }}, {{ 3, 1, 3 }}, {{ 2, 1, 2 }} }}, + {{ {{ 2, 1, 2 }}, {{ 1, 1, 1 }}, {{ 0, 1, 0 }} }}, } }; } std::tuple<std::vector<engine::vk::Vertex>, std::vector<uint16_t>> Mesh::linearize_indices() const & { + std::unordered_map<std::array<size_t, 3>, size_t, VertexIndicesHasher<3>> unique_vertices; std::vector<engine::vk::Vertex> linearized_vertices; std::vector<uint16_t> linearized_indices; - size_t n = 0; for (const auto& triangle_indices : this->indices) { for (const auto& vertex_indices : triangle_indices) { - linearized_vertices.emplace_back(this->vertices[vertex_indices[0]], this->normals[vertex_indices[1]]); - linearized_indices.emplace_back(n++); + auto it = unique_vertices.find(vertex_indices); + if (it == unique_vertices.end()) { + linearized_vertices.emplace_back(this->vertices[vertex_indices[0]], this->normals[vertex_indices[1]], this->uvs[vertex_indices[2]]); + size_t idx = linearized_vertices.size() - 1; + unique_vertices.emplace(vertex_indices, idx); + linearized_indices.emplace_back(idx); + } else { + linearized_indices.emplace_back((*it).second); + } } } diff --git a/src/o3d/mesh.hpp b/src/o3d/mesh.hpp index 525d9bd..d1d3f1c 100644 --- a/src/o3d/mesh.hpp +++ b/src/o3d/mesh.hpp @@ -17,7 +17,8 @@ struct Mesh { std::vector<engine::math::Vector3> vertices; std::vector<engine::math::Vector3> normals; - std::vector<std::array<std::array<size_t, 2>, 3>> indices; + std::vector<engine::math::Vector2> uvs; + std::vector<std::array<std::array<size_t, 3>, 3>> indices; // TODO: find a better way to do this. This workaround is due to the fact that vulkan only // accepts a single index, not an index for each attributes diff --git a/src/o3d/vertex.hpp b/src/o3d/vertex.hpp index 1605f0d..8d02297 100644 --- a/src/o3d/vertex.hpp +++ b/src/o3d/vertex.hpp @@ -6,12 +6,11 @@ namespace engine::o3d { -using engine::math::Vector3, engine::math::Vector4; - struct Vertex { - Vector4 vertex; - Vector3 normal; - VertexData data; + engine::math::Vector4 vertex; + engine::math::Vector3 normal; + engine::math::Vector2 uv; + engine::o3d::VertexData data; }; } diff --git a/src/o3d/vertex_data.hpp b/src/o3d/vertex_data.hpp index f70100c..fa62271 100644 --- a/src/o3d/vertex_data.hpp +++ b/src/o3d/vertex_data.hpp @@ -3,9 +3,6 @@ #include "math/vector.hpp" -using - engine::math::Vector3; - namespace engine::o3d { struct VertexData { @@ -21,7 +18,7 @@ struct VertexData { }; } - Vector3 world_loc; + engine::math::Vector3 world_loc; }; } diff --git a/src/obj_parser.cpp b/src/obj_parser.cpp index d83d75f..28df871 100644 --- a/src/obj_parser.cpp +++ b/src/obj_parser.cpp @@ -6,37 +6,44 @@ #include <vector> #include <cstddef> #include <array> +#include <string_view> +#include <optional> #include "math/vector.hpp" +#include "math/utils.hpp" #include "o3d/mesh.hpp" #include "o3d/vertex_data.hpp" namespace engine { -namespace { +using math::Vector2, math::Vector3; -std::vector<std::string> split(const std::string& s, char sep) { - std::vector<std::string> res; - std::string::size_type last_ind = 0; - for (std::string::size_type ind = 0; ind < s.length(); ind++) { +template<typename CallbackFn> +static void split(const std::string_view& s, char sep, CallbackFn fn) { + size_t n = 0; + std::string_view::size_type last_ind = 0; + for (std::string_view::size_type ind = 0; ind < s.length(); ind++) { if (s[ind] == sep) { - res.push_back(s.substr(last_ind, ind - last_ind)); + if (!fn(n, s.substr(last_ind, ind - last_ind))) return; + n++; last_ind = ind + 1; } } - res.push_back(s.substr(last_ind)); - return res; + fn(n, s.substr(last_ind)); } -float parse_float(const std::string& s) { +template<typename NumType> +static std::optional<NumType> parse_num(const std::string_view& s); + +template<> +std::optional<float> parse_num<float>(const std::string_view& s) { std::string::size_type ind = 0; bool positive = true; while (ind < s.length() && (s[ind] == '+' || s[ind] == '-')) if (s[ind++] == '-') positive = !positive; - // TODO: improve error checking if (ind == s.length()) - return 0.f; + return {}; int n = 0; while (ind < s.length() && s[ind] >= '0' && s[ind] <= '9') @@ -45,9 +52,8 @@ float parse_float(const std::string& s) { if (ind == s.length()) return (positive ? 1.f : -1.f) * static_cast<float>(n); - // TODO: improve error checking if (s[ind] != '.') - return 0.f; + return {}; ind++; @@ -58,79 +64,116 @@ float parse_float(const std::string& s) { decimal_fac *= .1f; } - // TODO: improve error checking if (ind != s.length()) - return 0.f; + return {}; return (positive ? 1.f : -1.f) * (static_cast<float>(n) + decimal_part); } -float parse_int(const std::string& s) { +template<> +std::optional<int> parse_num<int>(const std::string_view& s) { std::string::size_type ind = 0; bool positive = true; while (ind < s.length() && (s[ind] == '+' || s[ind] == '-')) if (s[ind++] == '-') positive = !positive; - // TODO: improve error checking if (ind == s.length()) - return 0.f; + return {}; int n = 0; while (ind < s.length() && s[ind] >= '0' && s[ind] <= '9') n = n * 10 + static_cast<int>(s[ind++]) - static_cast<int>('0'); - // TODO: improve error checking if (ind != s.length()) - return 0.f; + return {}; return (positive ? 1.f : -1.f) * n; } +template<typename VectorType> +static constexpr std::optional<VectorType> split_to_vec(const std::string_view& s, char sep) { + std::array<float, VectorType::size> coords; + bool err = false; + split(s, sep, [&](auto n, const auto& coord_s) { + auto coord = parse_num<float>(coord_s); + if (!coord) { + err = true; + return false; + } + coords[n] = *coord; + return true; + }); + if (err) return {}; + return engine::math::utils::array_to_vec(coords); +} + +// TODO: improve this. This is a workaround to have a unwrap-like function, but we should check more +// precisely the error +template<typename Ret, size_t line_num> +[[noreturn]] +static std::optional<Ret> err_exit() { + std::cerr << "error: failed to parse .obj file (line number " << line_num << ")" << std::endl; + exit(EXIT_FAILURE); } o3d::Mesh parse_object(const std::string& obj_path) { o3d::Mesh mesh; std::ifstream obj_file(obj_path); if (!obj_file.is_open()) { - std::cerr << "file `" << obj_path << "'not found" << std::endl; // TODO: improve - std::exit(1); + std::cerr << "file `" << obj_path << "' not found" << std::endl; // TODO: improve + exit(EXIT_FAILURE); } std::string line; while (std::getline(obj_file, line)) { - if (line.length() == 0 || line[0] == '#') + std::string_view line_view { line }; + if (line_view.length() == 0 || line_view[0] == '#') continue; - if (line.rfind("o ", 0) == 0) { - // std::cout << "Object: " << line.substr(2) << std::endl; - } else if (line.rfind("v ", 0) == 0) { - auto s_coords = split(line.substr(2), ' '); - mesh.vertices.emplace_back(parse_float(s_coords[0]), parse_float(s_coords[1]), parse_float(s_coords[2])); - } else if (line.rfind("vn ", 0) == 0) { - auto s_coords = split(line.substr(3), ' '); - mesh.normals.emplace_back(parse_float(s_coords[0]), parse_float(s_coords[1]), parse_float(s_coords[2])); - } else if (line.rfind("s ", 0) == 0) { - // auto smooth = false; - // auto s_smooth = line.substr(2); - // if (s_smooth == "0" || s_smooth == "off") { - // smooth = false; - // } else if (s_smooth == "1" || s_smooth == "on") { - // smooth = true; - // } - // std::cout << "Smooth: " << std::boolalpha << smooth << std::endl; - } else if (line.rfind("f ", 0) == 0) { - std::array<std::array<std::size_t, 2>, 3> indices; - auto line_split = split(line.substr(2), ' '); - for (int i = 0; i < 3; i++) { - auto indices_s_group = split(line_split[i], '/'); - indices[i][0] = parse_int(indices_s_group[0]) - 1; - indices[i][1] = parse_int(indices_s_group[2]) - 1; - } - // std::cout << "Face:" - // << " 1: vertex: " << indices[0][0] << " normal: " << indices[0][1] - // << " 2: vertex: " << indices[1][0] << " normal: " << indices[1][1] - // << " 3: vertex: " << indices[2][0] << " normal: " << indices[2][1] - // << std::endl; + auto data_type_idx = line_view.find(" "); + if (data_type_idx == std::string_view::npos) { + std::cerr << "error: failed to parse .obj file: no argument given to data type '" << line_view << "`" << std::endl; + exit(EXIT_FAILURE); + } + auto data_type = line_view.substr(0, data_type_idx); + auto data_arg = line_view.substr(data_type_idx + 1); + if (data_type == "o") { + // TODO: implement + } else if (data_type == "v") { + mesh.vertices.push_back(*split_to_vec<Vector3>(data_arg, ' ').or_else(err_exit<Vector3, __LINE__>)); + } else if (data_type == "vn") { + mesh.normals.push_back(*split_to_vec<Vector3>(data_arg, ' ').or_else(err_exit<Vector3, __LINE__>)); + } else if (data_type == "vt") { + auto uv = *split_to_vec<Vector2>(data_arg, ' ').or_else(err_exit<Vector2, __LINE__>); + uv.y = 1.f - uv.y; + mesh.uvs.push_back(uv); + } else if (data_type == "s") { + // TODO: implement + } else if (data_type == "f") { + std::array<std::array<std::size_t, 3>, 3> indices; + bool err = false; + split(data_arg, ' ', [&](auto m, const auto& vertex_indices_s) { + split(vertex_indices_s, '/', [&](auto n, const auto& idx_s) { + // indices of texture coordinates and normals are reversed in .obj relative to the + // engine + auto idx_opt = parse_num<int>(idx_s); + if (!idx_opt) { + err = true; + return false; + } + indices[m][(n > 0 ? 3 - n : n)] = *idx_opt - 1; + return true; + }); + return !err; + }); + if (err) err_exit<int, __LINE__>(); mesh.indices.push_back(indices); + } else if (data_type == "mtllib") { + // TODO: implement + } else if (data_type == "usemtl") { + // TODO: implement + } else { + std::cerr << "error while parsing .obj file: unknown data type: " << line_view << std::endl; + exit(EXIT_FAILURE); } } return mesh; diff --git a/src/renderer.cpp b/src/renderer.cpp index 73b31f7..cf97af8 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -10,6 +10,7 @@ using namespace engine; using engine::math::Vector2, + engine::math::Vector3, engine::math::Vector4, engine::o3d::Triangle, engine::o3d::TriangleDerived, @@ -19,8 +20,7 @@ using engine::fb::PixelFrameBuffer; template<typename FrameBuffer> -template<typename FBArg> -Renderer<FrameBuffer>::Renderer(FBArg&& fb) : fb{std::forward<FBArg>(fb)} { +Renderer<FrameBuffer>::Renderer(FrameBuffer&& fb) : fb { std::forward<FrameBuffer>(fb) } { depth_buf.resize(this->fb.width() * this->fb.height()); } @@ -145,8 +145,9 @@ void Renderer<FrameBuffer>::_draw_cropped_triangle(const Triangle& root, const T loc_z }; fb.draw_point(x, y, loc, - VertexData::bilerp(root.vertex1.data, root.vertex2.data, root.vertex3.data, b0, b1), - Vector3::bilerp(root.vertex1.normal, root.vertex2.normal, root.vertex3.normal, b0, b1).normalize()); + Vector3 ::bilerp(root.vertex1.normal, root.vertex2.normal, root.vertex3.normal, b0, b1), + Vector2 ::bilerp(root.vertex1.uv, root.vertex2.uv, root.vertex3.uv, b0, b1), + VertexData::bilerp(root.vertex1.data, root.vertex2.data, root.vertex3.data, b0, b1)); } } }; @@ -155,8 +156,4 @@ void Renderer<FrameBuffer>::_draw_cropped_triangle(const Triangle& root, const T } template class Renderer<CharacterFrameBuffer>; -template Renderer<CharacterFrameBuffer>::Renderer(CharacterFrameBuffer&&); -template Renderer<CharacterFrameBuffer>::Renderer(const CharacterFrameBuffer&); template class Renderer<PixelFrameBuffer>; -template Renderer<PixelFrameBuffer>::Renderer(PixelFrameBuffer&&); -template Renderer<PixelFrameBuffer>::Renderer(const PixelFrameBuffer&); diff --git a/src/renderer.hpp b/src/renderer.hpp index ec67489..ea79fce 100644 --- a/src/renderer.hpp +++ b/src/renderer.hpp @@ -8,19 +8,16 @@ namespace engine { -using math::Vector3, o3d::Triangle; - template<typename FrameBuffer> class Renderer { public: FrameBuffer fb; - template<typename FBArg> - Renderer(FBArg&& fb); + Renderer(FrameBuffer&& fb); void resize(unsigned int w, unsigned int h); void clear(); - void draw_triangle(const Triangle& triangle); + void draw_triangle(const o3d::Triangle& triangle); constexpr unsigned int width() const & { return fb.width(); diff --git a/src/shaders/shader.slang b/src/shaders/shader.slang index e92f5aa..245df4b 100644 --- a/src/shaders/shader.slang +++ b/src/shaders/shader.slang @@ -1,12 +1,11 @@ struct VertexInput { float3 pos; float3 normal; + float2 uv; }; struct UniformBuffer { - float4x4 model, view, proj; - float4x4 camera_rot; - float3 camera_loc; + float4x4 model, view, proj, inv_view; }; ConstantBuffer<UniformBuffer> ubo; @@ -15,6 +14,7 @@ struct VertexOutput { float4 pos : SV_Position; float3 world_loc; float3 normal; + float2 uv; }; [shader("vertex")] @@ -23,7 +23,8 @@ VertexOutput vert_main(VertexInput vi) { float4 world_loc = mul(ubo.model, float4(vi.pos, 1.)); vo.pos = mul(ubo.proj, mul(ubo.view, world_loc)); vo.world_loc = world_loc.xyz; - vo.normal = vi.normal; + vo.normal = mul(ubo.model, float4(vi.normal, 0.)).xyz; + vo.uv = vi.uv; return vo; } @@ -31,16 +32,17 @@ Sampler2D texture; [shader("fragment")] float4 frag_main(VertexOutput vo) : SV_Target { - float3 u = vo.world_loc - ubo.camera_loc; - float u_len_sq = dot(u, u); - u = normalize(u); - float attenuation = dot(mul(ubo.camera_rot, float4(0., 0., -1., 0.)).xyz, u); - if (attenuation < .7) attenuation = 0.f; - else if (attenuation > .8) attenuation = 1.; - else attenuation = (attenuation - .7) / .1; - float light = -dot(vo.normal, u) / u_len_sq; - if (light < 0.) light = 0.; - float final_light = .003 + light * attenuation * .8 * .997; - if (final_light > 1.) final_light = 1.; - return float4(final_light, final_light, final_light, 1.); + // float3 u = vo.world_loc - (ubo.inv_view * float4(0., 0., 0., 1.)).xyz; + // float u_len_sq = dot(u, u); + // u = normalize(u); + // float attenuation = dot(mul(ubo.inv_view, float4(0., 0., -1., 0.)).xyz, u); + // if (attenuation < .7) attenuation = 0.f; + // else if (attenuation > .8) attenuation = 1.; + // else attenuation = (attenuation - .7) / .1; + // float light = -dot(vo.normal, u) / u_len_sq; + // if (light < 0.) light = 0.; + // float final_light = .003 + light * attenuation * .8 * .997; + // if (final_light > 1.) final_light = 1.; + // return float4(final_light, final_light, final_light, 1.); + return float4(texture.Sample(vo.uv).rgb, 1.f); } diff --git a/src/vulkan_utils.hpp b/src/vulkan_utils.hpp index c822caa..54c82c5 100644 --- a/src/vulkan_utils.hpp +++ b/src/vulkan_utils.hpp @@ -39,7 +39,7 @@ struct Vertex { }; } - static constexpr std::array<VkVertexInputAttributeDescription, 2> get_attr_descs() { + static constexpr std::array<VkVertexInputAttributeDescription, 3> get_attr_descs() { return { VkVertexInputAttributeDescription { .location = 0, @@ -53,19 +53,23 @@ struct Vertex { .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(Vertex, normal), }, + VkVertexInputAttributeDescription { + .location = 2, + .binding = 0, + .format = VK_FORMAT_R32G32_SFLOAT, + .offset = offsetof(Vertex, uv), + }, }; } engine::math::Vector3 pos; engine::math::Vector3 normal; + engine::math::Vector2 uv; }; // TODO: move to a better place. Also, see TODOs for struct Vertex struct UniformBufferObject { - alignas(16) engine::math::Matrix4 model, view, proj; - // TODO: should me Matrix3, but it isn't implemented yet - alignas(16) engine::math::Matrix4 camera_rot; - alignas(16) engine::math::Vector3 camera_loc; + alignas(16) engine::math::Matrix4 model, view, proj, inv_view; }; } |
