diff --git a/assets/shaders/camera.slang b/assets/shaders/camera.slang new file mode 100644 index 0000000..fc6166a --- /dev/null +++ b/assets/shaders/camera.slang @@ -0,0 +1,9 @@ +#ifndef CAMERA_SLANG +#define CAMERA_SLANG + +struct camera_data { + float4x4 view; + float4x4 projection; +}; + +#endif diff --git a/assets/shaders/render_mesh.slang b/assets/shaders/render_mesh.slang new file mode 100644 index 0000000..b514189 --- /dev/null +++ b/assets/shaders/render_mesh.slang @@ -0,0 +1,31 @@ +#include "camera.slang" + +[[vk_binding(0, 0)]] ConstantBuffer camera; + +struct input_vertex +{ + [[vk::location(0)]] float3 pos : POSITION; + [[vk::location(1)]] float3 normal : NORMAL; + [[vk::location(2)]] float2 texcoord : TEXCOORD; +}; + +struct post_transform_vertex { + float4 position : SV_Position; + float3 normal : NORMAL; + float2 texcoord : TEXCOORD; +}; + +[shader("vertex")] +post_transform_vertex vert_main(input_vertex vertex) { + post_transform_vertex output; + output.position = mul(camera.projection, mul(camera.view, float4(vertex.pos, 1.0f))); + output.normal = vertex.normal; + output.texcoord = vertex.texcoord; + + return output; +} + +[shader("fragment")] +float4 frag_main(post_transform_vertex input) : SV_Target { + return float4(input.normal * 0.5f + 0.5f, 1.0f); +} diff --git a/source/game/engine.cpp b/source/game/engine.cpp index fd53159..fe3b91f 100644 --- a/source/game/engine.cpp +++ b/source/game/engine.cpp @@ -1,8 +1,13 @@ #include "engine.hpp" #include +#include +#include #include +#include "glm/ext/matrix_clip_space.hpp" +#include + namespace { [[nodiscard]] auto load_scene_to_renderer(trb::render::renderer &renderer, trb::game::scene &scene) { @@ -18,6 +23,85 @@ namespace { std::views::transform(upload_mesh_node) | std::ranges::to>(); } + +[[nodiscard]] std::pair +bounds_of(const trb::game::scene &scene) { + auto min = glm::vec3(std::numeric_limits::infinity()); + auto max = glm::vec3(-std::numeric_limits::infinity()); + + const auto is_mesh_node = [](const auto &v) { + return std::holds_alternative(v); + }; + + for (const auto &v : scene.nodes | std::views::filter(is_mesh_node)) { + const auto &mesh = std::get(v); + for (const auto [position, normal, uv] : mesh.vertices) { + min = glm::min(min, position); + max = glm::max(max, position); + } + } + + return std::make_pair(min, max); +} + +struct level_view { + glm::mat4 view; + glm::mat4 projection; +}; +[[nodiscard]] level_view calculate_camera_matrices(glm::vec3 min, glm::vec3 max, + float aspectRatio, + float zoom = 1.0f) { + // 1. Calculate the X/Z center. We ignore the Y center per your requirement. + glm::vec3 center = + glm::vec3((min.x + max.x) * 0.5f, 0.0f, (min.z + max.z) * 0.5f); + + // 2. Position the camera. + // For 45 degrees in Y-up: we move 'distance' up (Y) and 'distance' back (Z). + float distance = 100.0f; + glm::vec3 cameraPos = center + glm::vec3(0.0f, distance, distance); + + // 3. View Matrix + // Up is (0, 1, 0). Camera looks at the X/Z center. + glm::mat4 view = glm::lookAt(cameraPos, center, glm::vec3(0.0f, 1.0f, 0.0f)); + + // 4. Determine Ortho Bounds + // We must check all corners because at 45 degrees, the Y-height + // of objects affects their projected X/Y position on screen. + glm::vec3 corners[8] = {{min.x, min.y, min.z}, {max.x, min.y, min.z}, + {min.x, max.y, min.z}, {max.x, max.y, min.z}, + {min.x, min.y, max.z}, {max.x, min.y, max.z}, + {min.x, max.y, max.z}, {max.x, max.y, max.z}}; + + float vMinX = FLT_MAX, vMaxX = -FLT_MAX; + float vMinY = FLT_MAX, vMaxY = -FLT_MAX; + + for (const auto &corner : corners) { + glm::vec4 vSpace = view * glm::vec4(corner, 1.0f); + vMinX = glm::min(vMinX, vSpace.x); + vMaxX = glm::max(vMaxX, vSpace.x); + vMinY = glm::min(vMinY, vSpace.y); + vMaxY = glm::max(vMaxY, vSpace.y); + } + + // 5. Apply Zoom and Aspect Ratio + float width = (vMaxX - vMinX) / zoom; + float height = (vMaxY - vMinY) / zoom; + float midX = (vMinX + vMaxX) * 0.5f; + float midY = (vMinY + vMaxY) * 0.5f; + + if (aspectRatio > (width / height)) { + width = height * aspectRatio; + } else { + height = width / aspectRatio; + } + + glm::mat4 projection = + glm::ortho(midX - width * 0.5f, midX + width * 0.5f, midY - height * 0.5f, + midY + height * 0.5f, -1000.0f, 1000.0f); + + return {view, projection}; +} + } // namespace namespace trb::game { @@ -30,7 +114,10 @@ engine::engine() void engine::run() { while (!display.should_close()) { try { - renderer.render(); + const auto [map_min, map_max] = ::bounds_of(level_border); + const auto [view, projection] = + ::calculate_camera_matrices(map_min, map_max, 1920.0f / 1080.0f); + renderer.render(level_border_render_ids, view, projection); } catch (const vuk::VkException &ex) { if (ex.what() != std::string("Out of date.")) { std::rethrow_exception(std::current_exception()); diff --git a/source/game/model.cpp b/source/game/model.cpp index 0cea544..3d76c5f 100644 --- a/source/game/model.cpp +++ b/source/game/model.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include namespace { @@ -15,6 +17,9 @@ namespace { assert(position_it != it->attributes.end()); assert(it->indicesAccessor.has_value()); + auto *normal_it = it->findAttribute("NORMAL"); + assert(normal_it != it->attributes.end()); + const auto index = std::distance(mesh.primitives.begin(), it); auto &primitive = meshes.emplace_back(); @@ -22,8 +27,7 @@ namespace { throw std::runtime_error("only triangular meshes are supported"); } - // TODO: Materials, texcoords - + auto positions = std::vector(); { auto &position_accessor = asset.accessors[position_it->accessorIndex]; if (!position_accessor.bufferViewIndex.has_value()) @@ -32,7 +36,7 @@ namespace { fastgltf::iterateAccessorWithIndex( asset, position_accessor, [&](fastgltf::math::fvec3 pos, std::size_t idx) { - primitive.positions.emplace_back(pos.x(), pos.y(), pos.z()); + positions.emplace_back(pos.x(), pos.y(), pos.z()); }); } @@ -47,6 +51,29 @@ namespace { fastgltf::copyFromAccessor(asset, index_accessor, primitive.indices.data()); } + + auto normals = std::vector(); + { + auto &normal_accessor = asset.accessors[normal_it->accessorIndex]; + if (!normal_accessor.bufferViewIndex.has_value()) { + throw std::runtime_error( + "no index buffer was specified for mesh primitive"); + } + + fastgltf::iterateAccessorWithIndex( + asset, normal_accessor, + [&](fastgltf::math::fvec3 pos, std::size_t idx) { + normals.emplace_back(pos.x(), pos.y(), pos.z()); + }); + } + + for (const auto [position, normal] : std::views::zip(positions, normals)) { + primitive.vertices.push_back({ + .position = position, + .normal = normal, + .uv = glm::vec2(), + }); + } } return meshes; diff --git a/source/game/model.hpp b/source/game/model.hpp index 73ec286..6be011e 100644 --- a/source/game/model.hpp +++ b/source/game/model.hpp @@ -9,8 +9,14 @@ namespace trb::game { +struct vertex { + glm::vec3 position; + glm::vec3 normal; + glm::vec2 uv; +}; + struct mesh_node { - std::vector positions; + std::vector vertices; std::vector indices; }; diff --git a/source/render/renderer.cpp b/source/render/renderer.cpp index 6bd84cc..07468bd 100644 --- a/source/render/renderer.cpp +++ b/source/render/renderer.cpp @@ -22,6 +22,59 @@ #include #include +[[nodiscard]] auto render_mesh() { + return vuk::make_pass( + "render mesh", [](vuk::CommandBuffer &command_buffer, + VUK_BA(vuk::eMemoryRead) camera_buffer, + VUK_BA(vuk::eIndirectRead) indirect_buffer, + VUK_BA(vuk::eVertexRead) vertex_buffer, + VUK_BA(vuk::eIndexRead) index_buffer, + VUK_IA(vuk::eColorWrite) color_target) { + auto attributes = std::vector(); + attributes.push_back({ + .location = 0, + .binding = 0, + .format = vuk::Format::eR32G32B32Sfloat, + .offset = 0, + }); + attributes.push_back({ + .location = 1, + .binding = 0, + .format = vuk::Format::eR32G32B32Sfloat, + .offset = sizeof(glm::vec3), + }); + attributes.push_back({ + .location = 2, + .binding = 0, + .format = vuk::Format::eR32G32Sfloat, + .offset = sizeof(glm::vec3) + sizeof(glm::vec3), + }); + + command_buffer.bind_graphics_pipeline("mesh") + .set_dynamic_state(vuk::DynamicStateFlagBits::eScissor | + vuk::DynamicStateFlagBits::eViewport) + .set_viewport(0, vuk::Rect2D::framebuffer()) + .set_scissor(0, vuk::Rect2D::framebuffer()) + .set_rasterization({ + //.polygonMode = vuk::PolygonMode::eLine, + }) + /* +.set_depth_stencil({ +.depthTestEnable = false, +.depthWriteEnable = false, +.depthCompareOp = vuk::CompareOp::eGreater, +})*/ + .set_color_blend(color_target, {}) + .bind_vertex_buffer(0, vertex_buffer, attributes, sizeof(float) * 8) + .bind_index_buffer(index_buffer, vuk::IndexType::eUint32) + .bind_buffer(0, 0, camera_buffer) + .draw_indexed_indirect(1, indirect_buffer); + + return std::make_tuple(std::move(camera_buffer), + std::move(color_target)); + }); +} + namespace trb::render { std::uint32_t renderer::next_mesh_id() { @@ -63,7 +116,10 @@ renderer::create_graphics_pipeline(std::string_view module_name) { "frag_main"); } -void renderer::create_pipelines() {} +void renderer::create_pipelines() { + context.runtime.create_named_pipeline( + "mesh", create_graphics_pipeline("render_mesh.slang")); +} vuk::Value renderer::get_swap_target() { auto imported_swapchain = vuk::acquire_swapchain(context.swapchain); @@ -83,11 +139,34 @@ renderer::vuk_frame_resources renderer::get_next_frame_resources() { } renderer::renderer(ui::display *display) - : context(display), rng(std::random_device()()) {} + : context(display), rng(std::random_device()()) { + create_pipelines(); +} -void renderer::render() { +void renderer::render(std::span meshes, glm::mat4 view, + glm::mat4 projection) { auto [frame_allocator, frame_target] = get_next_frame_resources(); + struct { + glm::mat4 view; + glm::mat4 projection; + } camera_data{ + .view = view, + .projection = projection, + }; + auto [camera_buffer, camera_future] = vuk::create_buffer( + frame_allocator, vuk::MemoryUsage::eCPUtoGPU, + vuk::DomainFlagBits::eTransferOperation, std::span(&camera_data, 1)); + + for (const auto id : meshes) { + auto &mesh = registered_meshes.at(id); + std::tie(camera_future, frame_target) = render_mesh()( + camera_future, + vuk::make_constant("mesh indirect command", *mesh.indirect_command), + vuk::make_constant("mesh vertices", *mesh.vertices), + vuk::make_constant("mesh indices", *mesh.indices), frame_target); + } + auto entire_thing = vuk::enqueue_presentation(frame_target); entire_thing.submit(frame_allocator, context.compiler); } @@ -97,7 +176,7 @@ std::uint32_t renderer::register_mesh(const game::mesh_node &mesh) { auto [vertex_buffer, vertex_future] = vuk::create_buffer( context.superframe_allocator, vuk::MemoryUsage::eCPUtoGPU, - vuk::DomainFlagBits::eTransferOperation, std::span(mesh.positions)); + vuk::DomainFlagBits::eTransferOperation, std::span(mesh.vertices)); auto [index_buffer, index_future] = vuk::create_buffer( context.superframe_allocator, vuk::MemoryUsage::eCPUtoGPU, diff --git a/source/render/renderer.hpp b/source/render/renderer.hpp index 53f093a..bc10383 100644 --- a/source/render/renderer.hpp +++ b/source/render/renderer.hpp @@ -56,7 +56,8 @@ private: public: explicit renderer(ui::display *display); - void render(); + void render(std::span meshes, glm::mat4 view, + glm::mat4 projection); [[nodiscard]] std::uint32_t register_mesh(const game::mesh_node &mesh); };