diff --git a/.gitignore b/.gitignore index dd0a3e9..e292b14 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,7 @@ Makefile # OS files .DS_Store Thumbs.db + +# Some testing scripts for me qwq +cornell_box_build.sh +cornell_box_metal_sphere_build.sh diff --git a/examples/cornell_box b/examples/cornell_box index 92b5450..4db3aaa 100644 Binary files a/examples/cornell_box and b/examples/cornell_box differ diff --git a/examples/cornell_box.cpp b/examples/cornell_box.cpp index 465f4c3..e62a33d 100644 --- a/examples/cornell_box.cpp +++ b/examples/cornell_box.cpp @@ -1,580 +1,583 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace are; - -// Window dimensions -const uint WINDOW_WIDTH = 800; -const uint WINDOW_HEIGHT = 800; - -// Global state -GLFWwindow *g_window = nullptr; -std::unique_ptr g_renderer = nullptr; -std::unique_ptr g_scene = nullptr; -std::shared_ptr g_camera = nullptr; // Keep a direct reference to camera - -// --- Camera Control State --- -Vec3 g_cameraPos = Vec3(0.0f, 0.0f, 4.5f); -Vec3 g_cameraTarget = Vec3(0.0f, 0.0f, 0.0f); -Vec3 g_cameraUp = Vec3(0.0f, 1.0f, 0.0f); -Vec3 g_worldUp = Vec3(0.0f, 1.0f, 0.0f); - -// Euler Angles -float g_yaw = -90.0f; // Initialized to look along -Z (standard OpenGL) -float g_pitch = 0.0f; - -// Control settings -float g_moveSpeed = 2.5f; -float g_mouseSensitivity = 0.1f; -bool g_firstMouse = true; -double g_lastX = WINDOW_WIDTH / 2.0; -double g_lastY = WINDOW_HEIGHT / 2.0; - -// Time -float g_deltaTime = 0.0f; -float g_lastFrame = 0.0f; - -// GLFW error callback -void glfw_error_callback(int error, const char *description) { - ARE_LOG_ERROR("GLFW Error " + std::to_string(error) + ": " + std::string(description)); -} - -/// @brief Create a quad mesh -std::shared_ptr create_quad(const Vec3 &v0, const Vec3 &v1, const Vec3 &v2, const Vec3 &v3, - const Vec3 &normal, uint material_id) { - auto mesh = std::make_shared(); - - std::vector vertices = { - { v0, normal, Vec2(0.0f, 0.0f), Vec3(1.0f, 0.0f, 0.0f) }, - { v1, normal, Vec2(1.0f, 0.0f), Vec3(1.0f, 0.0f, 0.0f) }, - { v2, normal, Vec2(1.0f, 1.0f), Vec3(1.0f, 0.0f, 0.0f) }, - { v3, normal, Vec2(0.0f, 1.0f), Vec3(1.0f, 0.0f, 0.0f) } - }; - - std::vector indices = { 0, 1, 2, 0, 2, 3 }; - - mesh->set_vertices(vertices); - mesh->set_indices(indices); - mesh->set_material(material_id); - - return mesh; -} - -/// @brief Create a box mesh -std::shared_ptr create_box(const Vec3 &min, const Vec3 &max, uint material_id) { - auto mesh = std::make_shared(); - - std::vector vertices = { - // Front face - { { min.x, min.y, max.z }, { 0.0f, 0.0f, 1.0f }, { 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } }, - { { max.x, min.y, max.z }, { 0.0f, 0.0f, 1.0f }, { 1.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } }, - { { max.x, max.y, max.z }, { 0.0f, 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f, 0.0f } }, - { { min.x, max.y, max.z }, { 0.0f, 0.0f, 1.0f }, { 0.0f, 1.0f }, { 1.0f, 0.0f, 0.0f } }, - - // Back face - { { max.x, min.y, min.z }, { 0.0f, 0.0f, -1.0f }, { 0.0f, 0.0f }, { -1.0f, 0.0f, 0.0f } }, - { { min.x, min.y, min.z }, { 0.0f, 0.0f, -1.0f }, { 1.0f, 0.0f }, { -1.0f, 0.0f, 0.0f } }, - { { min.x, max.y, min.z }, { 0.0f, 0.0f, -1.0f }, { 1.0f, 1.0f }, { -1.0f, 0.0f, 0.0f } }, - { { max.x, max.y, min.z }, { 0.0f, 0.0f, -1.0f }, { 0.0f, 1.0f }, { -1.0f, 0.0f, 0.0f } }, - - // Top face - { { min.x, max.y, max.z }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } }, - { { max.x, max.y, max.z }, { 0.0f, 1.0f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } }, - { { max.x, max.y, min.z }, { 0.0f, 1.0f, 0.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f, 0.0f } }, - { { min.x, max.y, min.z }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 1.0f }, { 1.0f, 0.0f, 0.0f } }, - - // Bottom face - { { min.x, min.y, min.z }, { 0.0f, -1.0f, 0.0f }, { 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } }, - { { max.x, min.y, min.z }, { 0.0f, -1.0f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } }, - { { max.x, min.y, max.z }, { 0.0f, -1.0f, 0.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f, 0.0f } }, - { { min.x, min.y, max.z }, { 0.0f, -1.0f, 0.0f }, { 0.0f, 1.0f }, { 1.0f, 0.0f, 0.0f } }, - - // Right face - { { max.x, min.y, max.z }, { 1.0f, 0.0f, 0.0f }, { 0.0f, 0.0f }, { 0.0f, 0.0f, -1.0f } }, - { { max.x, min.y, min.z }, { 1.0f, 0.0f, 0.0f }, { 1.0f, 0.0f }, { 0.0f, 0.0f, -1.0f } }, - { { max.x, max.y, min.z }, { 1.0f, 0.0f, 0.0f }, { 1.0f, 1.0f }, { 0.0f, 0.0f, -1.0f } }, - { { max.x, max.y, max.z }, { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f }, { 0.0f, 0.0f, -1.0f } }, - - // Left face - { { min.x, min.y, min.z }, { -1.0f, 0.0f, 0.0f }, { 0.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }, - { { min.x, min.y, max.z }, { -1.0f, 0.0f, 0.0f }, { 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }, - { { min.x, max.y, max.z }, { -1.0f, 0.0f, 0.0f }, { 1.0f, 1.0f }, { 0.0f, 0.0f, 1.0f } }, - { { min.x, max.y, min.z }, { -1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f }, { 0.0f, 0.0f, 1.0f } } - }; - - std::vector indices = { - 0, 1, 2, 0, 2, 3, // Front - 4, 5, 6, 4, 6, 7, // Back - 8, 9, 10, 8, 10, 11, // Top - 12, 13, 14, 12, 14, 15, // Bottom - 16, 17, 18, 16, 18, 19, // Right - 20, 21, 22, 20, 22, 23 // Left - }; - - mesh->set_vertices(vertices); - mesh->set_indices(indices); - mesh->set_material(material_id); - - return mesh; -} - -/// @brief Create a sphere mesh -std::shared_ptr create_sphere(float radius, uint segments, uint rings, uint material_id) { - auto mesh = std::make_shared(); - - std::vector vertices; - std::vector indices; - - for (uint ring = 0; ring <= rings; ++ring) { - float theta = ring * glm::pi() / rings; - float sin_theta = sin(theta); - float cos_theta = cos(theta); - - for (uint seg = 0; seg <= segments; ++seg) { - float phi = seg * 2.0f * glm::pi() / segments; - float x = cos(phi) * sin_theta; - float y = cos_theta; - float z = sin(phi) * sin_theta; - - Vec3 pos = Vec3(x, y, z) * radius; - Vec3 normal = Vec3(x, y, z); - Vec2 uv = Vec2((float)seg / segments, (float)ring / rings); - Vec3 tangent = Vec3(-sin(phi), 0.0f, cos(phi)); - - vertices.push_back({ pos, normal, uv, tangent }); - } - } - - for (uint ring = 0; ring < rings; ++ring) { - for (uint seg = 0; seg < segments; ++seg) { - uint current = ring * (segments + 1) + seg; - indices.push_back(current); - indices.push_back(current + segments + 1); - indices.push_back(current + 1); - indices.push_back(current + 1); - indices.push_back(current + segments + 1); - indices.push_back(current + segments + 2); - } - } - - mesh->set_vertices(vertices); - mesh->set_indices(indices); - mesh->set_material(material_id); - mesh->compute_tangents(); - - return mesh; -} - -/// @brief Setup Cornell Box scene -void setup_cornell_box() { - g_scene = std::make_unique(); - - // Create materials - // 0: White diffuse - auto white_material = std::make_shared(); - white_material->set_albedo(Vec3(0.73f, 0.73f, 0.73f)); - white_material->set_type(MaterialType::DIFFUSE); - uint white_id = g_scene->add_material(white_material); - - // 1: Red diffuse (left wall) - auto red_material = std::make_shared(); - red_material->set_albedo(Vec3(0.65f, 0.05f, 0.05f)); - red_material->set_type(MaterialType::DIFFUSE); - uint red_id = g_scene->add_material(red_material); - - // 2: Green diffuse (right wall) - auto green_material = std::make_shared(); - green_material->set_albedo(Vec3(0.12f, 0.45f, 0.15f)); - green_material->set_type(MaterialType::DIFFUSE); - uint green_id = g_scene->add_material(green_material); - - // 3: Light emissive - auto light_material = std::make_shared(); - light_material->set_albedo(Vec3(1.0f, 1.0f, 1.0f)); - light_material->set_emission(Vec3(15.0f, 15.0f, 15.0f)); - light_material->set_type(MaterialType::EMISSIVE); - uint light_id = g_scene->add_material(light_material); - - // 4: Metal (for one box) - auto metal_material = std::make_shared(); - metal_material->set_albedo(Vec3(0.95f, 0.93f, 0.88f)); - metal_material->set_metallic(1.0f); - metal_material->set_roughness(0.0f); - metal_material->set_type(MaterialType::METAL); - uint metal_id = g_scene->add_material(metal_material); - - // 5: Glass/Dielectric (refraction) - auto glass_material = std::make_shared(); - glass_material->set_albedo(Vec3(1.0f, 1.0f, 1.0f)); - glass_material->set_ior(1.5f); - glass_material->set_roughness(0.0f); - glass_material->set_type(MaterialType::DIELECTRIC); - uint glass_id = g_scene->add_material(glass_material); - - // 6: Yellow emissive sphere - auto emissive_sphere_mat = std::make_shared(); - emissive_sphere_mat->set_albedo(Vec3(1.0f, 0.8f, 0.2f)); - emissive_sphere_mat->set_emission(Vec3(5.0f, 4.0f, 1.0f)); - emissive_sphere_mat->set_type(MaterialType::EMISSIVE); - uint emissive_sphere_id = g_scene->add_material(emissive_sphere_mat); - - // Create room (Cornell Box) - float room_size = 2.0f; - - // Floor (white) - auto floor = create_quad( - Vec3(-room_size, -room_size, -room_size), - Vec3(room_size, -room_size, -room_size), - Vec3(room_size, -room_size, room_size), - Vec3(-room_size, -room_size, room_size), - Vec3(0.0f, 1.0f, 0.0f), - white_id); - floor->upload_to_gpu(); - g_scene->add_mesh(floor); - - // Ceiling (white) - auto ceiling = create_quad( - Vec3(-room_size, room_size, room_size), - Vec3(room_size, room_size, room_size), - Vec3(room_size, room_size, -room_size), - Vec3(-room_size, room_size, -room_size), - Vec3(0.0f, -1.0f, 0.0f), - white_id); - ceiling->upload_to_gpu(); - g_scene->add_mesh(ceiling); - - // Back wall (white) - auto back_wall = create_quad( - Vec3(-room_size, -room_size, -room_size), - Vec3(-room_size, room_size, -room_size), - Vec3(room_size, room_size, -room_size), - Vec3(room_size, -room_size, -room_size), - Vec3(0.0f, 0.0f, 1.0f), - white_id); - back_wall->upload_to_gpu(); - g_scene->add_mesh(back_wall); - - // Left wall (red) - auto left_wall = create_quad( - Vec3(-room_size, -room_size, room_size), - Vec3(-room_size, room_size, room_size), - Vec3(-room_size, room_size, -room_size), - Vec3(-room_size, -room_size, -room_size), - Vec3(1.0f, 0.0f, 0.0f), - red_id); - left_wall->upload_to_gpu(); - g_scene->add_mesh(left_wall); - - // Right wall (green) - auto right_wall = create_quad( - Vec3(room_size, -room_size, -room_size), - Vec3(room_size, room_size, -room_size), - Vec3(room_size, room_size, room_size), - Vec3(room_size, -room_size, room_size), - Vec3(-1.0f, 0.0f, 0.0f), - green_id); - right_wall->upload_to_gpu(); - g_scene->add_mesh(right_wall); - - // Area light on ceiling - float light_size = 0.5f; - auto area_light = create_quad( - Vec3(-light_size, room_size - 0.01f, -light_size), - Vec3(light_size, room_size - 0.01f, -light_size), - Vec3(light_size, room_size - 0.01f, light_size), - Vec3(-light_size, room_size - 0.01f, light_size), - Vec3(0.0f, -1.0f, 0.0f), - light_id); - area_light->upload_to_gpu(); - g_scene->add_mesh(area_light); - - // Tall box (white, left side) - auto tall_box = create_box(Vec3(-0.7f, -room_size, -0.7f), Vec3(-0.2f, 0.6f, -0.2f), white_id); - tall_box->upload_to_gpu(); - g_scene->add_mesh(tall_box); - - // Short box (metal, right side) - auto short_box = create_box(Vec3(0.2f, -room_size, 0.2f), Vec3(0.9f, -0.4f, 0.9f), glass_id); - short_box->upload_to_gpu(); - g_scene->add_mesh(short_box); - - // // Glass sphere (dielectric/refraction test) - // auto glass_sphere = create_sphere(0.4f, 32, 16, glass_id); - // glass_sphere->set_position(Vec3(-0.5f, -0.6f, 0.0f)); - // glass_sphere->upload_to_gpu(); - // g_scene->add_mesh(glass_sphere); - // - // // Yellow emissive sphere (emission test) - // auto emissive_sphere = create_sphere(0.25f, 32, 16, emissive_sphere_id); - // emissive_sphere->set_position(Vec3(0.5f, -0.75f, 0.3f)); - // emissive_sphere->upload_to_gpu(); - // g_scene->add_mesh(emissive_sphere); - - // Setup camera - g_camera = std::make_shared(); - g_camera->set_position(g_cameraPos); - g_camera->set_target(g_cameraTarget); - g_camera->set_up(g_cameraUp); - g_camera->set_perspective(45.0f, static_cast(WINDOW_WIDTH) / WINDOW_HEIGHT, 0.1f, 100.0f); - g_scene->set_camera(g_camera); - - // Add point light - auto light = std::make_shared(); - light->set_type(LightType::POINT); - light->set_position(Vec3(0.0f, 1.8f, 0.0f)); - light->set_color(Vec3(1.0f, 1.0f, 1.0f)); - light->set_intensity(10.0f); - light->set_range(10.0f); - g_scene->add_light(light); - - ARE_LOG_INFO("Cornell Box scene created"); -} - -/// @brief Initialize GLFW and create window -bool init_window() { - glfwSetErrorCallback(glfw_error_callback); - - if (!glfwInit()) { - ARE_LOG_ERROR("Failed to initialize GLFW"); - return false; - } - - ARE_LOG_INFO("GLFW initialized successfully"); - - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); - glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); - glfwWindowHint(GLFW_SAMPLES, 0); - - g_window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Aurora - Cornell Box", nullptr, nullptr); - - if (!g_window) { - ARE_LOG_ERROR("Failed to create GLFW window"); - glfwTerminate(); - return false; - } - - glfwMakeContextCurrent(g_window); - glfwSwapInterval(1); - - if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { - ARE_LOG_ERROR("Failed to initialize GLAD"); - return false; - } - - return true; -} - -// --- Input Processing --- -void process_input() { - // Calculate delta time - float currentFrame = glfwGetTime(); - g_deltaTime = currentFrame - g_lastFrame; - g_lastFrame = currentFrame; - - float velocity = g_moveSpeed * g_deltaTime; - bool camera_changed = false; - - // 1. Mouse Rotation (Left Button Hold) - if (glfwGetMouseButton(g_window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) { - double xpos, ypos; - glfwGetCursorPos(g_window, &xpos, &ypos); - - if (g_firstMouse) { - g_lastX = xpos; - g_lastY = ypos; - g_firstMouse = false; - } - - float xoffset = xpos - g_lastX; - float yoffset = g_lastY - ypos; // Reversed since y-coordinates go from bottom to top - g_lastX = xpos; - g_lastY = ypos; - - // Only update if mouse actually moved - if (xoffset != 0.0f || yoffset != 0.0f) { - xoffset *= g_mouseSensitivity; - yoffset *= g_mouseSensitivity; - - g_yaw += xoffset; - g_pitch += yoffset; - - // Constrain pitch - if (g_pitch > 89.0f) - g_pitch = 89.0f; - if (g_pitch < -89.0f) - g_pitch = -89.0f; - - camera_changed = true; - } - } else { - g_firstMouse = true; // Reset when released - } - - // 2. Calculate Direction Vectors - glm::vec3 front; - front.x = cos(glm::radians(g_yaw)) * cos(glm::radians(g_pitch)); - front.y = sin(glm::radians(g_pitch)); - front.z = sin(glm::radians(g_yaw)) * cos(glm::radians(g_pitch)); - glm::vec3 frontNorm = glm::normalize(front); - - glm::vec3 rightNorm = glm::normalize(glm::cross(frontNorm, glm::vec3(g_worldUp.x, g_worldUp.y, g_worldUp.z))); - - // 3. Keyboard Movement (WASD) - glm::vec3 pos = glm::vec3(g_cameraPos.x, g_cameraPos.y, g_cameraPos.z); - - if (glfwGetKey(g_window, GLFW_KEY_W) == GLFW_PRESS) { - pos += frontNorm * velocity; - camera_changed = true; - } - if (glfwGetKey(g_window, GLFW_KEY_S) == GLFW_PRESS) { - pos -= frontNorm * velocity; - camera_changed = true; - } - if (glfwGetKey(g_window, GLFW_KEY_A) == GLFW_PRESS) { - pos -= rightNorm * velocity; - camera_changed = true; - } - if (glfwGetKey(g_window, GLFW_KEY_D) == GLFW_PRESS) { - pos += rightNorm * velocity; - camera_changed = true; - } - - // 4. Apply changes to Scene Camera and Notify Renderer - if (camera_changed) { - g_cameraPos = Vec3(pos.x, pos.y, pos.z); - - // Target = Position + Front - Vec3 newTarget = g_cameraPos + Vec3(frontNorm.x, frontNorm.y, frontNorm.z); - - g_camera->set_position(g_cameraPos); - g_camera->set_target(newTarget); - - // CRITICAL: Notify renderer to reset accumulation - g_renderer->notify_scene_changed(*g_scene); - } -} - -/// @brief Main render loop -void render_loop() { - ARE_LOG_INFO("Entering render loop..."); - - int frame_count = 0; - double fps_time = glfwGetTime(); - g_lastFrame = glfwGetTime(); // Initialize for delta time - - while (!glfwWindowShouldClose(g_window)) { - // Process input at the start of the frame - process_input(); - - // Render - RenderStats stats = g_renderer->render(*g_scene); - - // Swap buffers - glfwSwapBuffers(g_window); - glfwPollEvents(); - - // Calculate FPS - frame_count++; - double current_time = glfwGetTime(); - double delta = current_time - fps_time; - - if (delta >= 1.0) { - double fps = frame_count / delta; - std::string title = "Aurora - Cornell Box | FPS: " + std::to_string((int)fps) + " | Frame: " + std::to_string((int)stats.frame_time_ms_) + "ms"; - glfwSetWindowTitle(g_window, title.c_str()); - - frame_count = 0; - fps_time = current_time; - } - - // ESC to exit - if (glfwGetKey(g_window, GLFW_KEY_ESCAPE) == GLFW_PRESS) { - glfwSetWindowShouldClose(g_window, true); - } - } - - ARE_LOG_INFO("Exiting render loop"); -} - -/// @brief Cleanup -void cleanup() { - ARE_LOG_INFO("Cleaning up..."); - - if (g_renderer) { - g_renderer->shutdown(); - g_renderer.reset(); - } - - g_scene.reset(); - - if (g_window) { - glfwDestroyWindow(g_window); - glfwTerminate(); - } - - ARE_LOG_INFO("Cleanup complete"); -} - -int main() { - ARE_LOG_INFO("==========================================="); - ARE_LOG_INFO("Aurora Rendering Engine - Cornell Box Demo"); - ARE_LOG_INFO("==========================================="); - - if (!init_window()) { - cleanup(); - ARE_LOG_ERROR("Failed to initialize window"); - Logger::shutdown(); - return -1; - } - - ARE_LOG_INFO("Setting up Cornell Box scene..."); - setup_cornell_box(); - - ARE_LOG_INFO("Initializing renderer..."); - RendererConfig config; - config.output_width = WINDOW_WIDTH; - config.output_height = WINDOW_HEIGHT; - config.rt_config.samples_per_pixel = 1; - config.rt_config.max_depth = 4; - config.rt_config.enable_accumulation = true; - config.enable_denoising = false; - - g_renderer = std::make_unique(config); - if (!g_renderer->initialize()) { - ARE_LOG_ERROR("Failed to initialize renderer"); - cleanup(); - Logger::shutdown(); - return -1; - } - - ARE_LOG_INFO("==========================================="); - ARE_LOG_INFO("Renderer initialized successfully!"); - ARE_LOG_INFO("Controls:"); - ARE_LOG_INFO(" WASD - Move Camera"); - ARE_LOG_INFO(" Hold Left Mouse Button - Rotate Camera"); - ARE_LOG_INFO(" ESC - Exit"); - ARE_LOG_INFO("==========================================="); - - render_loop(); - cleanup(); - - ARE_LOG_INFO("Cornell Box demo finished"); - Logger::shutdown(); - - return 0; -} +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace are; + +// Window dimensions +const uint WINDOW_WIDTH = 800; +const uint WINDOW_HEIGHT = 800; + +// Global state +GLFWwindow *g_window = nullptr; +std::unique_ptr g_renderer = nullptr; +std::unique_ptr g_scene = nullptr; +std::shared_ptr g_camera = nullptr; // Keep a direct reference to camera + +// --- Camera Control State --- +Vec3 g_cameraPos = Vec3(0.0f, 0.0f, 4.5f); +Vec3 g_cameraTarget = Vec3(0.0f, 0.0f, 0.0f); +Vec3 g_cameraUp = Vec3(0.0f, 1.0f, 0.0f); +Vec3 g_worldUp = Vec3(0.0f, 1.0f, 0.0f); + +// Euler Angles +float g_yaw = -90.0f; // Initialized to look along -Z (standard OpenGL) +float g_pitch = 0.0f; + +// Control settings +float g_moveSpeed = 2.5f; +float g_mouseSensitivity = 0.1f; +bool g_firstMouse = true; +double g_lastX = WINDOW_WIDTH / 2.0; +double g_lastY = WINDOW_HEIGHT / 2.0; + +// Time +float g_deltaTime = 0.0f; +float g_lastFrame = 0.0f; + +// GLFW error callback +void glfw_error_callback(int error, const char *description) { + ARE_LOG_ERROR("GLFW Error " + std::to_string(error) + ": " + std::string(description)); +} + +/// @brief Create a quad mesh +std::shared_ptr create_quad(const Vec3 &v0, const Vec3 &v1, const Vec3 &v2, const Vec3 &v3, + const Vec3 &normal, uint material_id) { + auto mesh = std::make_shared(); + + std::vector vertices = { + { v0, normal, Vec2(0.0f, 0.0f), Vec3(1.0f, 0.0f, 0.0f) }, + { v1, normal, Vec2(1.0f, 0.0f), Vec3(1.0f, 0.0f, 0.0f) }, + { v2, normal, Vec2(1.0f, 1.0f), Vec3(1.0f, 0.0f, 0.0f) }, + { v3, normal, Vec2(0.0f, 1.0f), Vec3(1.0f, 0.0f, 0.0f) } + }; + + std::vector indices = { 0, 1, 2, 0, 2, 3 }; + + mesh->set_vertices(vertices); + mesh->set_indices(indices); + mesh->set_material(material_id); + + return mesh; +} + +/// @brief Create a box mesh +std::shared_ptr create_box(const Vec3 &min, const Vec3 &max, uint material_id) { + auto mesh = std::make_shared(); + + std::vector vertices = { + // Front face + { { min.x, min.y, max.z }, { 0.0f, 0.0f, 1.0f }, { 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } }, + { { max.x, min.y, max.z }, { 0.0f, 0.0f, 1.0f }, { 1.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } }, + { { max.x, max.y, max.z }, { 0.0f, 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f, 0.0f } }, + { { min.x, max.y, max.z }, { 0.0f, 0.0f, 1.0f }, { 0.0f, 1.0f }, { 1.0f, 0.0f, 0.0f } }, + + // Back face + { { max.x, min.y, min.z }, { 0.0f, 0.0f, -1.0f }, { 0.0f, 0.0f }, { -1.0f, 0.0f, 0.0f } }, + { { min.x, min.y, min.z }, { 0.0f, 0.0f, -1.0f }, { 1.0f, 0.0f }, { -1.0f, 0.0f, 0.0f } }, + { { min.x, max.y, min.z }, { 0.0f, 0.0f, -1.0f }, { 1.0f, 1.0f }, { -1.0f, 0.0f, 0.0f } }, + { { max.x, max.y, min.z }, { 0.0f, 0.0f, -1.0f }, { 0.0f, 1.0f }, { -1.0f, 0.0f, 0.0f } }, + + // Top face + { { min.x, max.y, max.z }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } }, + { { max.x, max.y, max.z }, { 0.0f, 1.0f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } }, + { { max.x, max.y, min.z }, { 0.0f, 1.0f, 0.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f, 0.0f } }, + { { min.x, max.y, min.z }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 1.0f }, { 1.0f, 0.0f, 0.0f } }, + + // Bottom face + { { min.x, min.y, min.z }, { 0.0f, -1.0f, 0.0f }, { 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } }, + { { max.x, min.y, min.z }, { 0.0f, -1.0f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } }, + { { max.x, min.y, max.z }, { 0.0f, -1.0f, 0.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f, 0.0f } }, + { { min.x, min.y, max.z }, { 0.0f, -1.0f, 0.0f }, { 0.0f, 1.0f }, { 1.0f, 0.0f, 0.0f } }, + + // Right face + { { max.x, min.y, max.z }, { 1.0f, 0.0f, 0.0f }, { 0.0f, 0.0f }, { 0.0f, 0.0f, -1.0f } }, + { { max.x, min.y, min.z }, { 1.0f, 0.0f, 0.0f }, { 1.0f, 0.0f }, { 0.0f, 0.0f, -1.0f } }, + { { max.x, max.y, min.z }, { 1.0f, 0.0f, 0.0f }, { 1.0f, 1.0f }, { 0.0f, 0.0f, -1.0f } }, + { { max.x, max.y, max.z }, { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f }, { 0.0f, 0.0f, -1.0f } }, + + // Left face + { { min.x, min.y, min.z }, { -1.0f, 0.0f, 0.0f }, { 0.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }, + { { min.x, min.y, max.z }, { -1.0f, 0.0f, 0.0f }, { 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }, + { { min.x, max.y, max.z }, { -1.0f, 0.0f, 0.0f }, { 1.0f, 1.0f }, { 0.0f, 0.0f, 1.0f } }, + { { min.x, max.y, min.z }, { -1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f }, { 0.0f, 0.0f, 1.0f } } + }; + + std::vector indices = { + 0, 1, 2, 0, 2, 3, // Front + 4, 5, 6, 4, 6, 7, // Back + 8, 9, 10, 8, 10, 11, // Top + 12, 13, 14, 12, 14, 15, // Bottom + 16, 17, 18, 16, 18, 19, // Right + 20, 21, 22, 20, 22, 23 // Left + }; + + mesh->set_vertices(vertices); + mesh->set_indices(indices); + mesh->set_material(material_id); + + return mesh; +} + +/// @brief Create a sphere mesh +std::shared_ptr create_sphere(float radius, uint segments, uint rings, uint material_id) { + auto mesh = std::make_shared(); + + std::vector vertices; + std::vector indices; + + for (uint ring = 0; ring <= rings; ++ring) { + float theta = ring * glm::pi() / rings; + float sin_theta = sin(theta); + float cos_theta = cos(theta); + + for (uint seg = 0; seg <= segments; ++seg) { + float phi = seg * 2.0f * glm::pi() / segments; + float x = cos(phi) * sin_theta; + float y = cos_theta; + float z = sin(phi) * sin_theta; + + Vec3 pos = Vec3(x, y, z) * radius; + Vec3 normal = Vec3(x, y, z); + Vec2 uv = Vec2((float)seg / segments, (float)ring / rings); + Vec3 tangent = Vec3(-sin(phi), 0.0f, cos(phi)); + + vertices.push_back({ pos, normal, uv, tangent }); + } + } + + for (uint ring = 0; ring < rings; ++ring) { + for (uint seg = 0; seg < segments; ++seg) { + uint current = ring * (segments + 1) + seg; + indices.push_back(current); + indices.push_back(current + segments + 1); + indices.push_back(current + 1); + indices.push_back(current + 1); + indices.push_back(current + segments + 1); + indices.push_back(current + segments + 2); + } + } + + mesh->set_vertices(vertices); + mesh->set_indices(indices); + mesh->set_material(material_id); + mesh->compute_tangents(); + + return mesh; +} + +/// @brief Setup Cornell Box scene +void setup_cornell_box() { + g_scene = std::make_unique(); + + // Create materials + // 0: White diffuse + auto white_material = std::make_shared(); + white_material->set_albedo(Vec3(0.73f, 0.73f, 0.73f)); + white_material->set_type(MaterialType::DIFFUSE); + uint white_id = g_scene->add_material(white_material); + + // 1: Red diffuse (left wall) + auto red_material = std::make_shared(); + red_material->set_albedo(Vec3(0.65f, 0.05f, 0.05f)); + red_material->set_type(MaterialType::DIFFUSE); + uint red_id = g_scene->add_material(red_material); + + // 2: Green diffuse (right wall) + auto green_material = std::make_shared(); + green_material->set_albedo(Vec3(0.12f, 0.45f, 0.15f)); + green_material->set_type(MaterialType::DIFFUSE); + uint green_id = g_scene->add_material(green_material); + + // 3: Light emissive + auto light_material = std::make_shared(); + light_material->set_albedo(Vec3(1.0f, 1.0f, 1.0f)); + light_material->set_emission(Vec3(15.0f, 15.0f, 15.0f)); + light_material->set_type(MaterialType::EMISSIVE); + uint light_id = g_scene->add_material(light_material); + + // 4: Metal (for one box) + auto metal_material = std::make_shared(); + metal_material->set_albedo(Vec3(0.95f, 0.93f, 0.88f)); + metal_material->set_metallic(1.0f); + metal_material->set_roughness(0.0f); + metal_material->set_type(MaterialType::METAL); + uint metal_id = g_scene->add_material(metal_material); + + // 5: Glass/Dielectric (refraction) + auto glass_material = std::make_shared(); + glass_material->set_albedo(Vec3(1.0f, 1.0f, 1.0f)); + glass_material->set_ior(1.5f); + glass_material->set_roughness(0.0f); + glass_material->set_type(MaterialType::DIELECTRIC); + uint glass_id = g_scene->add_material(glass_material); + + // 6: Yellow emissive sphere + auto emissive_sphere_mat = std::make_shared(); + emissive_sphere_mat->set_albedo(Vec3(1.0f, 0.8f, 0.2f)); + emissive_sphere_mat->set_emission(Vec3(5.0f, 4.0f, 1.0f)); + emissive_sphere_mat->set_type(MaterialType::EMISSIVE); + uint emissive_sphere_id = g_scene->add_material(emissive_sphere_mat); + + // Create room (Cornell Box) + float room_size = 2.0f; + + // Floor (white) + auto floor = create_quad( + Vec3(-room_size, -room_size, -room_size), + Vec3(room_size, -room_size, -room_size), + Vec3(room_size, -room_size, room_size), + Vec3(-room_size, -room_size, room_size), + Vec3(0.0f, 1.0f, 0.0f), + white_id); + floor->upload_to_gpu(); + g_scene->add_mesh(floor); + + // Ceiling (white) + auto ceiling = create_quad( + Vec3(-room_size, room_size, room_size), + Vec3(room_size, room_size, room_size), + Vec3(room_size, room_size, -room_size), + Vec3(-room_size, room_size, -room_size), + Vec3(0.0f, -1.0f, 0.0f), + white_id); + ceiling->upload_to_gpu(); + g_scene->add_mesh(ceiling); + + // Back wall (white) + auto back_wall = create_quad( + Vec3(-room_size, -room_size, -room_size), + Vec3(-room_size, room_size, -room_size), + Vec3(room_size, room_size, -room_size), + Vec3(room_size, -room_size, -room_size), + Vec3(0.0f, 0.0f, 1.0f), + white_id); + back_wall->upload_to_gpu(); + g_scene->add_mesh(back_wall); + + // Left wall (red) + auto left_wall = create_quad( + Vec3(-room_size, -room_size, room_size), + Vec3(-room_size, room_size, room_size), + Vec3(-room_size, room_size, -room_size), + Vec3(-room_size, -room_size, -room_size), + Vec3(1.0f, 0.0f, 0.0f), + red_id); + left_wall->upload_to_gpu(); + g_scene->add_mesh(left_wall); + + // Right wall (green) + auto right_wall = create_quad( + Vec3(room_size, -room_size, -room_size), + Vec3(room_size, room_size, -room_size), + Vec3(room_size, room_size, room_size), + Vec3(room_size, -room_size, room_size), + Vec3(-1.0f, 0.0f, 0.0f), + green_id); + right_wall->upload_to_gpu(); + g_scene->add_mesh(right_wall); + + // Area light on ceiling + float light_size = 0.5f; + auto area_light = create_quad( + Vec3(-light_size, room_size - 0.01f, -light_size), + Vec3(light_size, room_size - 0.01f, -light_size), + Vec3(light_size, room_size - 0.01f, light_size), + Vec3(-light_size, room_size - 0.01f, light_size), + Vec3(0.0f, -1.0f, 0.0f), + light_id); + area_light->upload_to_gpu(); + g_scene->add_mesh(area_light); + + // Tall box (white, left side) + auto tall_box = create_box(Vec3(-0.7f, -room_size, -0.7f), Vec3(-0.2f, 0.6f, -0.2f), white_id); + tall_box->upload_to_gpu(); + g_scene->add_mesh(tall_box); + + // Short box (metal, right side) + auto short_box = create_box(Vec3(0.2f, -room_size, 0.2f), Vec3(0.9f, -0.4f, 0.9f), emissive_sphere_id); + short_box->upload_to_gpu(); + g_scene->add_mesh(short_box); + + // // Glass sphere (dielectric/refraction test) + // auto glass_sphere = create_sphere(0.4f, 32, 16, glass_id); + // glass_sphere->set_position(Vec3(-0.5f, -0.6f, 0.0f)); + // glass_sphere->upload_to_gpu(); + // g_scene->add_mesh(glass_sphere); + // + // // Yellow emissive sphere (emission test) + // auto emissive_sphere = create_sphere(0.25f, 32, 16, emissive_sphere_id); + // emissive_sphere->set_position(Vec3(0.5f, -0.75f, 0.3f)); + // emissive_sphere->upload_to_gpu(); + // g_scene->add_mesh(emissive_sphere); + + // Setup camera + g_camera = std::make_shared(); + g_camera->set_position(g_cameraPos); + g_camera->set_target(g_cameraTarget); + g_camera->set_up(g_cameraUp); + g_camera->set_perspective(45.0f, static_cast(WINDOW_WIDTH) / WINDOW_HEIGHT, 0.1f, 100.0f); + g_scene->set_camera(g_camera); + + // Add point light + auto light = std::make_shared(); + light->set_type(LightType::POINT); + light->set_position(Vec3(0.0f, 1.8f, 0.0f)); + light->set_color(Vec3(1.0f, 1.0f, 1.0f)); + light->set_intensity(10.0f); + light->set_range(10.0f); + g_scene->add_light(light); + + ARE_LOG_INFO("Cornell Box scene created"); +} + +/// @brief Initialize GLFW and create window +bool init_window() { + glfwSetErrorCallback(glfw_error_callback); + + if (!glfwInit()) { + ARE_LOG_ERROR("Failed to initialize GLFW"); + return false; + } + + ARE_LOG_INFO("GLFW initialized successfully"); + + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); + glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); + glfwWindowHint(GLFW_SAMPLES, 0); + + g_window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Aurora - Cornell Box", nullptr, nullptr); + + if (!g_window) { + ARE_LOG_ERROR("Failed to create GLFW window"); + glfwTerminate(); + return false; + } + + glfwMakeContextCurrent(g_window); + glfwSwapInterval(1); + + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { + ARE_LOG_ERROR("Failed to initialize GLAD"); + return false; + } + + return true; +} + +// --- Input Processing --- +void process_input() { + // Calculate delta time + float currentFrame = glfwGetTime(); + g_deltaTime = currentFrame - g_lastFrame; + g_lastFrame = currentFrame; + + float velocity = g_moveSpeed * g_deltaTime; + bool camera_changed = false; + + // 1. Mouse Rotation (Left Button Hold) + if (glfwGetMouseButton(g_window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) { + double xpos, ypos; + glfwGetCursorPos(g_window, &xpos, &ypos); + + if (g_firstMouse) { + g_lastX = xpos; + g_lastY = ypos; + g_firstMouse = false; + } + + float xoffset = xpos - g_lastX; + float yoffset = g_lastY - ypos; // Reversed since y-coordinates go from bottom to top + g_lastX = xpos; + g_lastY = ypos; + + // Only update if mouse actually moved + if (xoffset != 0.0f || yoffset != 0.0f) { + xoffset *= g_mouseSensitivity; + yoffset *= g_mouseSensitivity; + + g_yaw += xoffset; + g_pitch += yoffset; + + // Constrain pitch + if (g_pitch > 89.0f) + g_pitch = 89.0f; + if (g_pitch < -89.0f) + g_pitch = -89.0f; + + camera_changed = true; + } + } else { + g_firstMouse = true; // Reset when released + } + + // 2. Calculate Direction Vectors + glm::vec3 front; + front.x = cos(glm::radians(g_yaw)) * cos(glm::radians(g_pitch)); + front.y = sin(glm::radians(g_pitch)); + front.z = sin(glm::radians(g_yaw)) * cos(glm::radians(g_pitch)); + glm::vec3 frontNorm = glm::normalize(front); + + glm::vec3 rightNorm = glm::normalize(glm::cross(frontNorm, glm::vec3(g_worldUp.x, g_worldUp.y, g_worldUp.z))); + + // 3. Keyboard Movement (WASD) + glm::vec3 pos = glm::vec3(g_cameraPos.x, g_cameraPos.y, g_cameraPos.z); + + if (glfwGetKey(g_window, GLFW_KEY_W) == GLFW_PRESS) { + pos += frontNorm * velocity; + camera_changed = true; + } + if (glfwGetKey(g_window, GLFW_KEY_S) == GLFW_PRESS) { + pos -= frontNorm * velocity; + camera_changed = true; + } + if (glfwGetKey(g_window, GLFW_KEY_A) == GLFW_PRESS) { + pos -= rightNorm * velocity; + camera_changed = true; + } + if (glfwGetKey(g_window, GLFW_KEY_D) == GLFW_PRESS) { + pos += rightNorm * velocity; + camera_changed = true; + } + + // 4. Apply changes to Scene Camera and Notify Renderer + if (camera_changed) { + g_cameraPos = Vec3(pos.x, pos.y, pos.z); + + // Target = Position + Front + Vec3 newTarget = g_cameraPos + Vec3(frontNorm.x, frontNorm.y, frontNorm.z); + + g_camera->set_position(g_cameraPos); + g_camera->set_target(newTarget); + + // CRITICAL: Notify renderer to reset accumulation + g_renderer->notify_scene_changed(*g_scene); + } +} + +/// @brief Main render loop +void render_loop() { + ARE_LOG_INFO("Entering render loop..."); + + int frame_count = 0; + double fps_time = glfwGetTime(); + g_lastFrame = glfwGetTime(); // Initialize for delta time + + while (!glfwWindowShouldClose(g_window)) { + // Process input at the start of the frame + process_input(); + + // Render + RenderStats stats = g_renderer->render(*g_scene); + + // Swap buffers + glfwSwapBuffers(g_window); + glfwPollEvents(); + + // Calculate FPS + frame_count++; + double current_time = glfwGetTime(); + double delta = current_time - fps_time; + + if (delta >= 1.0) { + double fps = frame_count / delta; + std::string title = "Aurora - Cornell Box | FPS: " + std::to_string((int)fps) + " | Frame: " + std::to_string((int)stats.frame_time_ms_) + "ms"; + glfwSetWindowTitle(g_window, title.c_str()); + + frame_count = 0; + fps_time = current_time; + } + + // ESC to exit + if (glfwGetKey(g_window, GLFW_KEY_ESCAPE) == GLFW_PRESS) { + glfwSetWindowShouldClose(g_window, true); + } + } + + ARE_LOG_INFO("Exiting render loop"); +} + +/// @brief Cleanup +void cleanup() { + ARE_LOG_INFO("Cleaning up..."); + + if (g_renderer) { + g_renderer->shutdown(); + g_renderer.reset(); + } + + g_scene.reset(); + + if (g_window) { + glfwDestroyWindow(g_window); + glfwTerminate(); + } + + ARE_LOG_INFO("Cleanup complete"); +} + +int main() { + ARE_LOG_INFO("==========================================="); + ARE_LOG_INFO("Aurora Rendering Engine - Cornell Box Demo"); + ARE_LOG_INFO("==========================================="); + + if (!init_window()) { + cleanup(); + ARE_LOG_ERROR("Failed to initialize window"); + Logger::shutdown(); + return -1; + } + + ARE_LOG_INFO("Setting up Cornell Box scene..."); + setup_cornell_box(); + + ARE_LOG_INFO("Initializing renderer..."); + RendererConfig config; + config.output_width = WINDOW_WIDTH; + config.output_height = WINDOW_HEIGHT; + config.rt_config.samples_per_pixel = 1; + config.rt_config.max_depth = 4; + config.rt_config.enable_accumulation = true; + config.enable_denoising = false; + + config.sr_config.enabled = true; + config.sr_config.scaling = 4; + + g_renderer = std::make_unique(config); + if (!g_renderer->initialize()) { + ARE_LOG_ERROR("Failed to initialize renderer"); + cleanup(); + Logger::shutdown(); + return -1; + } + + ARE_LOG_INFO("==========================================="); + ARE_LOG_INFO("Renderer initialized successfully!"); + ARE_LOG_INFO("Controls:"); + ARE_LOG_INFO(" WASD - Move Camera"); + ARE_LOG_INFO(" Hold Left Mouse Button - Rotate Camera"); + ARE_LOG_INFO(" ESC - Exit"); + ARE_LOG_INFO("==========================================="); + + render_loop(); + cleanup(); + + ARE_LOG_INFO("Cornell Box demo finished"); + Logger::shutdown(); + + return 0; +} diff --git a/examples/cornell_box_metal_sphere b/examples/cornell_box_metal_sphere index 84cfe9e..b6e94e1 100644 Binary files a/examples/cornell_box_metal_sphere and b/examples/cornell_box_metal_sphere differ diff --git a/examples/cornell_box_metal_sphere.cpp b/examples/cornell_box_metal_sphere.cpp index e97e4cd..dad24d8 100644 --- a/examples/cornell_box_metal_sphere.cpp +++ b/examples/cornell_box_metal_sphere.cpp @@ -536,6 +536,10 @@ int main() { config.rt_config.enable_accumulation = true; config.enable_denoising = false; + + config.sr_config.enabled = true; + config.sr_config.scaling = 4.0; + g_renderer = std::make_unique(config); if (!g_renderer->initialize()) { ARE_LOG_ERROR("Failed to initialize renderer"); diff --git a/include/core/raytracer.h b/include/core/raytracer.h index c6a2a32..76cc4ca 100644 --- a/include/core/raytracer.h +++ b/include/core/raytracer.h @@ -16,114 +16,62 @@ namespace are { // Compute shader based ray tracer class RayTracer { public: - /* - * @brief Constructor - * @param width Output width - * @param height Output height - * @param config Ray tracer configuration - */ RayTracer(uint width, uint height, const RayTracerConfig &config); - - // Destructor ~RayTracer(); - /* - * @brief Initialize ray tracer - * @return True if initialization succeeded - */ bool initialize(const std::shared_ptr &shader); - - // Release resources void release(); /* * @brief Trace rays using G-Buffer as input - * @param scene Scene data - * @param gbuffer G-Buffer containing geometry information - * @param output_texture Output texture for ray traced result + * @param scene Scene data + * @param gbuffer G-Buffer containing geometry information + * @param output_image Low-resolution output (W/block × H/block in SR mode) + * @param sr_scaling Pixel ratio for super resolution (1 = disabled, e.g. 4 = 4× fewer pixels) + * @param sr_jitter Jitter frame index 0..scaling-1 + * @param sr_accum Full-resolution accumulation texture (SR mode only, 0 = disabled) */ - void trace(const Scene &scene, const GBuffer &gbuffer, TextureHandle output_texture); + void trace(const Scene &scene, const GBuffer &gbuffer, TextureHandle output_image, + uint sr_scaling = 1, uint sr_jitter = 0, TextureHandle sr_accum = 0); - /* - * @brief Resize output - * @param width New width - * @param height New height - */ void resize(uint width, uint height); - - // Reset accumulation buffer void reset_accumulation(); - /* - * @brief Get current configuration - * @return Current configuration - */ const RayTracerConfig &get_config() const { return config_; } - - /* - * @brief Update configuration - * @param config New configuration - */ void set_config(const RayTracerConfig &config); - - /* - * @brief Rebuild BVH from scene - * @param scene Scene to build BVH from - * @return True if build succeeded - */ bool rebuild_bvh(const Scene &scene); private: - uint width_; - uint height_; + uint width_, height_; RayTracerConfig config_; - // Scene data hash for change detection uint materials_hash_; uint lights_hash_; - // Texture arrays for PBR materials - GLuint texture_arrays_[6]; // albedo, normal, metallic, roughness, ao, emission - uint texture_array_sizes_[6]; // Number of textures in each array - - // Texture array caching (content hash based) - uint texture_config_hash_; // Hash of entire texture configuration - uint texture_slot_hashes_[6]; // Hash per slot for incremental rebuild - bool texture_arrays_dirty_; // Dirty flag for texture arrays + GLuint texture_arrays_[6]; + uint texture_array_sizes_[6]; + uint texture_config_hash_; + uint texture_slot_hashes_[6]; + bool texture_arrays_dirty_; std::shared_ptr compute_shader_; TextureHandle accumulation_texture_; BufferHandle material_buffer_; BufferHandle light_buffer_; - // BVH related std::unique_ptr bvh_; Buffer bvh_node_buffer_; - Buffer bvh_triangle_buffer_; ///< Compact triangle data (intersection only) - Buffer bvh_attr_buffer_; ///< Triangle attributes (fetched on hit) + Buffer bvh_triangle_buffer_; + Buffer bvh_attr_buffer_; bool bvh_built_; uint frame_count_; bool initialized_; - /* - * @brief Upload scene data to GPU buffers - * @param scene Scene to upload - */ void upload_scene_data_(const Scene &scene); - - /* - * @brief Bind G-Buffer textures to compute shader - * @param gbuffer G-Buffer to bind - */ void bind_gbuffer_(const GBuffer &gbuffer); - - /* - * @brief Build texture arrays from scene materials - * @param scene Scene containing materials - */ void build_texture_arrays_(const Scene &scene); }; diff --git a/include/core/renderer.h b/include/core/renderer.h index d32cef2..3b715e0 100644 --- a/include/core/renderer.h +++ b/include/core/renderer.h @@ -7,6 +7,7 @@ #include "core/raytracer.h" #include "core/screen_blit.h" #include "core/shader_manager.h" +#include "core/super_resolution.h" #include "scene/scene.h" #include "utils/config.h" #include @@ -73,11 +74,11 @@ private: std::unique_ptr shader_manager_; std::unique_ptr screen_blit_; std::unique_ptr denoiser_; + std::unique_ptr super_resolution_; TextureHandle rt_output_texture_; bool initialized_; - uint frame_count_; }; } // namespace are diff --git a/include/core/shader_manager.h b/include/core/shader_manager.h index b2bebfa..6ec7c07 100644 --- a/include/core/shader_manager.h +++ b/include/core/shader_manager.h @@ -86,12 +86,21 @@ public: return screen_blit_shader_; } + /* + * @brief Get super resolution shader + * @return Super resolution shader (nullptr if not loaded) + */ + const std::shared_ptr &get_super_resolution_shader() const { + return super_resolution_shader_; + } + private: std::unordered_map> shader_cache_; std::shared_ptr gbuffer_shader_; std::shared_ptr raytracing_shader_; std::shared_ptr denoise_shader_; std::shared_ptr screen_blit_shader_; + std::shared_ptr super_resolution_shader_; bool initialized_; diff --git a/include/core/super_resolution.h b/include/core/super_resolution.h new file mode 100644 index 0000000..49ab392 --- /dev/null +++ b/include/core/super_resolution.h @@ -0,0 +1,137 @@ +#ifndef ARE_INCLUDE_CORE_SUPER_RESOLUTION_H +#define ARE_INCLUDE_CORE_SUPER_RESOLUTION_H + +#include "basic/types.h" +#include "resource/shader.h" +#include "utils/config.h" +#include + +namespace are { + +// Super resolution sparse ray tracing system +// Renders a subset of pixels each frame via a jitter pattern and +// accumulates across multiple frames inside the RT compute shader. +class SuperResolution { +public: + /* + * @brief Constructor + * @param full_width Full-resolution output width + * @param full_height Full-resolution output height + * @param config Super resolution configuration (enabled, scaling) + */ + SuperResolution(uint full_width, uint full_height, const SuperResolutionConfig &config); + + // Destructor + ~SuperResolution(); + + /* + * @brief Allocate textures and bind the upscale compute shader + * @param shader Super-resolution compute shader (super_resolution.comp) + * @return True if successful + */ + bool initialize(const std::shared_ptr &shader); + + // Release all GPU resources + void release(); + + /* + * @brief Low-resolution texture written by RayTracer each frame. + * Dimensions are W/scaling × H/scaling. + * @return Texture handle + */ + TextureHandle get_low_res_rt_texture() const { + return low_res_rt_texture_; + } + + /* + * @brief Full-resolution accumulation buffer (binding 4 in the RT shader). + * RGB = running average colour, A = 1.0 once sampled. + * @return Texture handle + */ + TextureHandle get_accumulated_rt_texture() const { + return accumulated_rt_texture_; + } + + /* + * @brief Run the upscale compute pass: tonemap accumulated RT, output final image. + * Unrendered pixels (alpha == 0) appear black. + * @return Upscaled output texture handle (full resolution) + */ + TextureHandle upscale(); + + /* + * @brief Advance to the next jitter frame within the current cycle. + * Cycles from 0 to scaling-1, wrapping back to 0. + */ + void advance_jitter_frame(); + + /* + * @brief Reset jitter frame to 0 and clear the accumulation texture to black. + * Called on scene changes and initialisation. + */ + void reset_accumulation(); + + /* + * @brief Resize all internal textures for a new output resolution + * @param full_width New full-resolution width + * @param full_height New full-resolution height + */ + void resize(uint full_width, uint full_height); + + /* + * @brief Current jitter frame index (0 .. scaling-1) + * @return Jitter frame index + */ + uint get_current_jitter_frame() const { + return current_jitter_frame_; + } + + /* + * @brief Read-only access to the active configuration + * @return Current SuperResolutionConfig + */ + const SuperResolutionConfig &get_config() const { + return config_; + } + + /* + * @brief Low-resolution dispatch width (full_width / sqrt(scaling)) + * @return Width in pixels + */ + uint get_low_res_width() const { + return low_res_w_; + } + + /* + * @brief Low-resolution dispatch height (full_height / sqrt(scaling)) + * @return Height in pixels + */ + uint get_low_res_height() const { + return low_res_h_; + } + +private: + uint full_width_, full_height_, low_res_w_, low_res_h_; + SuperResolutionConfig config_; + uint current_jitter_frame_; + + TextureHandle low_res_rt_texture_; // W/block × H/block – per-frame RT output + TextureHandle accumulated_rt_texture_; // W × H – running average (binding 4) + TextureHandle upscaled_texture_; // W × H – final tonemapped output + + std::shared_ptr compute_shader_; + bool initialized_ = false; + + // Create / recreate all internal textures + void create_textures_(); + + // sqrt(scaling) – the block side length in pixels + uint compute_block_size_() const; + + // Clears accumulated_rt_texture_ to (0,0,0,0) via FBO colour clear + void clear_accumulation_texture_() const; +}; + +} // namespace are + +#endif // ARE_INCLUDE_CORE_SUPER_RESOLUTION_H diff --git a/include/utils/config.h b/include/utils/config.h index 6945ac6..c776b3a 100644 --- a/include/utils/config.h +++ b/include/utils/config.h @@ -1,9 +1,9 @@ #ifndef ARE_INCLUDE_UTILS_CONFIG_H #define ARE_INCLUDE_UTILS_CONFIG_H +#include "basic/types.h" #include #include -#include "basic/types.h" namespace are { @@ -17,6 +17,12 @@ struct RayTracerConfig { bool use_bvh = true; }; +// Super resolution configuration +struct SuperResolutionConfig { + bool enabled = false; // Enable the super resolution mode + uint scaling = 4; // Pixel ratio: how many times fewer pixels rendered per frame + // scaling=4 → 1/4 pixels, 2×2 blocks, 4 jitter positions +}; // Configuration struct for renderer struct RendererConfig { @@ -24,8 +30,7 @@ struct RendererConfig { uint output_height; RayTracerConfig rt_config; bool enable_denoising; - bool enable_sr; // Enable the super resolution mode - double sr_scaling; // The magnification of super-resolution + SuperResolutionConfig sr_config; }; } // namespace are diff --git a/shaders/include/tonemap.glsl b/shaders/include/tonemap.glsl new file mode 100644 index 0000000..5393691 --- /dev/null +++ b/shaders/include/tonemap.glsl @@ -0,0 +1,6 @@ +// ACES Filmic Tone Mapping — shared between ray tracing and upscale passes. +// Converts HDR linear radiance to LDR display colour [0, 1]. +vec3 aces_tonemap(vec3 x) { + float a = 2.51, b = 0.03, c = 2.43, d = 0.59, e = 0.14; + return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0); +} diff --git a/shaders/postprocess/denoiser.comp b/shaders/postprocess/denoiser.comp index f9f886b..e3c36d8 100644 --- a/shaders/postprocess/denoiser.comp +++ b/shaders/postprocess/denoiser.comp @@ -6,9 +6,9 @@ layout(binding = 0, rgba32f) uniform readonly image2D u_input; layout(binding = 1, rgba32f) uniform writeonly image2D u_output; layout(binding = 2, rgba32f) uniform readonly image2D u_history; -uniform int u_radius; // 1 => 3x3, 2 => 5x5 -uniform float u_temporal_weight; // 0 = no temporal, 1 = full history -uniform bool u_has_history; // Whether history texture is valid +uniform int u_radius; // 1 => 3x3, 2 => 5x5 +uniform float u_temporal_weight; // 0 = no temporal, 1 = full history +uniform bool u_has_history; // Whether history texture is valid // Gaussian weight based on distance float gaussian_weight(float dist, float sigma) { @@ -22,10 +22,10 @@ void main() { if (p.x >= size.x || p.y >= size.y) return; vec3 center_color = imageLoad(u_input, p).rgb; - + // Sigma values for bilateral filter - float sigma_space = float(u_radius); // spatial sigma - float sigma_color = 0.3; // color sigma (adjust for more/less smoothing) + float sigma_space = float(u_radius); // spatial sigma + float sigma_color = 0.3; // color sigma (adjust for more/less smoothing) vec3 sum = vec3(0.0); float weight_sum = 0.0; @@ -34,33 +34,33 @@ void main() { for (int dx = -u_radius; dx <= u_radius; ++dx) { ivec2 q = clamp(p + ivec2(dx, dy), ivec2(0), size - ivec2(1)); vec3 sample_color = imageLoad(u_input, q).rgb; - + // Spatial weight (Gaussian based on distance) float spatial_dist = length(vec2(float(dx), float(dy))); float spatial_weight = gaussian_weight(spatial_dist, sigma_space); - + // Color weight (Gaussian based on color difference) float color_dist = length(sample_color - center_color); float color_weight = gaussian_weight(color_dist, sigma_color); - + // Combined bilateral weight float weight = spatial_weight * color_weight; - + sum += sample_color * weight; weight_sum += weight; } } vec3 denoised_color = sum / max(weight_sum, 1e-6); - + // Temporal accumulation: blend with history if (u_has_history && u_temporal_weight > 0.0) { vec3 history_color = imageLoad(u_history, p).rgb; - + // Simple exponential moving average // temporal_weight controls how much history to keep (higher = smoother but more ghosting) vec3 final_color = mix(denoised_color, history_color, u_temporal_weight); - + imageStore(u_output, p, vec4(final_color, 1.0)); } else { imageStore(u_output, p, vec4(denoised_color, 1.0)); diff --git a/shaders/postprocess/super_resolution.comp b/shaders/postprocess/super_resolution.comp new file mode 100644 index 0000000..4facf96 --- /dev/null +++ b/shaders/postprocess/super_resolution.comp @@ -0,0 +1,22 @@ +#version 430 core + +#include "../include/tonemap.glsl" + +layout(local_size_x = 16, local_size_y = 16) in; + +// Binding 1 – full-res accumulation buffer (RGB = colour, A = 1.0 once sampled) +layout(binding = 1, rgba32f) uniform readonly image2D u_accumulated_rt; + +// Binding 2 – final output +layout(binding = 2, rgba32f) uniform writeonly image2D u_output; + +// ── Upscale: tonemap accumulated RT or output black for unreached pixels ── +void main() { + ivec2 p = ivec2(gl_GlobalInvocationID.xy); + ivec2 s = imageSize(u_output); + if (p.x >= s.x || p.y >= s.y) return; + + vec4 acc = imageLoad(u_accumulated_rt, p); + vec3 col = (acc.a > 0.0) ? aces_tonemap(acc.rgb) : vec3(0.0); + imageStore(u_output, p, vec4(col, 1.0)); +} diff --git a/shaders/raytracing/raytracing.comp b/shaders/raytracing/raytracing.comp index 779c011..74eb747 100644 --- a/shaders/raytracing/raytracing.comp +++ b/shaders/raytracing/raytracing.comp @@ -1,36 +1,41 @@ #version 430 core -// Include shared modules #include "../include/common.glsl" #include "../include/structs.glsl" #include "../include/math.glsl" #include "../include/rng.glsl" #include "../include/sobol.glsl" #include "../include/sampling.glsl" +#include "../include/tonemap.glsl" -// Workgroup size layout(local_size_x = 16, local_size_y = 16) in; -// G-Buffer inputs layout(binding = 0, rgba32f) uniform readonly image2D g_position; -layout(binding = 1, rg32f) uniform readonly image2D g_normal; // Octahedral encoded +layout(binding = 1, rg32f) uniform readonly image2D g_normal; layout(binding = 5, rgba32f) uniform readonly image2D g_material; -layout(binding = 6, r32ui) uniform readonly uimage2D g_material_id; +layout(binding = 6, r32ui) uniform readonly uimage2D g_material_id; layout(binding = 2, rgba32f) uniform readonly image2D g_texcoord; layout(binding = 7, rgba32f) uniform readonly image2D g_tangent; -// Output layout(binding = 3, rgba32f) uniform image2D output_image; layout(binding = 4, rgba32f) uniform image2D accumulation_image; -// SSBO bindings -layout(std430, binding = 0) readonly buffer MaterialBuffer { Material materials[]; }; -layout(std430, binding = 1) readonly buffer LightBuffer { Light lights[]; }; -layout(std430, binding = 2) readonly buffer BVHNodeBuffer { BVHNodeGpu bvh_nodes[]; }; -layout(std430, binding = 3) readonly buffer TriangleBuffer { TriangleCompactGpu bvh_tris[]; }; -layout(std430, binding = 4) readonly buffer AttrBuffer { TriangleAttrGpu bvh_attrs[]; }; +layout(std430, binding = 0) readonly buffer MaterialBuffer { + Material materials[]; +}; +layout(std430, binding = 1) readonly buffer LightBuffer { + Light lights[]; +}; +layout(std430, binding = 2) readonly buffer BVHNodeBuffer { + BVHNodeGpu bvh_nodes[]; +}; +layout(std430, binding = 3) readonly buffer TriangleBuffer { + TriangleCompactGpu bvh_tris[]; +}; +layout(std430, binding = 4) readonly buffer AttrBuffer { + TriangleAttrGpu bvh_attrs[]; +}; -// Uniforms uniform uint u_frame_count; uniform uint u_samples_per_pixel; uniform uint u_max_depth; @@ -41,7 +46,13 @@ uniform bool u_use_bvh; uniform uint u_bvh_node_count; uniform bool u_enable_textures; -// Texture arrays +uniform uint u_sr_enabled; +uniform uint u_sr_scaling; // pixel ratio, e.g. 4 → 4× fewer pixels +uniform uint u_sr_block; // sqrt(scaling), block side length in pixels +uniform uint u_sr_jitter; // frame index within one jitter cycle (0 .. scaling-1) +uniform uint u_sr_full_width; +uniform uint u_sr_full_height; + layout(binding = 10) uniform sampler2DArray u_texture_albedo_array; layout(binding = 11) uniform sampler2DArray u_texture_normal_array; layout(binding = 12) uniform sampler2DArray u_texture_metallic_array; @@ -49,32 +60,24 @@ layout(binding = 13) uniform sampler2DArray u_texture_roughness_array; layout(binding = 14) uniform sampler2DArray u_texture_ao_array; layout(binding = 15) uniform sampler2DArray u_texture_emission_array; -// Include material, BVH, and lighting modules (needs uniform declarations above) #include "../include/material.glsl" #include "../include/bvh.glsl" #include "../include/lighting.glsl" -// Sobol sampling state struct SobolState { - uint sample_index; // Which sample (0, 1, 2, ...) - uint dimension; // Current dimension being used - uint scramble; // Seed for Owen scrambling + uint sample_index; + uint dimension; + uint scramble; }; -// Initialize Sobol state SobolState init_sobol(uint pixel_index, uint frame, uint sample_idx) { SobolState state; - // Sample index combines frame and sample number for temporal variation - // Add +1 to avoid degenerate index 0 (Sobol at index 0 produces all zeros before scrambling) - // Add pixel_index offset to ensure spatial variation within same frame state.sample_index = sample_idx + frame * 1024u + pixel_index + 1u; state.dimension = 0u; - // Use pixel index for per-pixel Owen scrambling state.scramble = pcg_hash(pixel_index + frame * 668265263u); return state; } -// Get next Sobol float in [0, 1) float sobol_next(inout SobolState state) { float value; if (state.dimension < 16u) { @@ -87,219 +90,166 @@ float sobol_next(inout SobolState state) { return value; } -// Sobol-based GGX half vector sampling vec3 sobol_ggx_half_vector(float roughness, vec3 N, inout SobolState state) { float a = roughness * roughness; float a2 = a * a; - float u1 = clamp(sobol_next(state), 0.001, 0.999); float u2 = sobol_next(state); - - float cos_theta = sqrt((1.0 - u1) / ((a2 - 1.0) * u1 + 1.0)); - cos_theta = clamp(cos_theta, 0.0, 1.0); - float sin_theta = sqrt(max(0.0, 1.0 - cos_theta * cos_theta)); - float phi = 2.0 * PI * u2; - - vec3 H_tangent = vec3(sin_theta * cos(phi), sin_theta * sin(phi), cos_theta); + float ct = sqrt((1.0 - u1) / ((a2 - 1.0) * u1 + 1.0)); + ct = clamp(ct, 0.0, 1.0); + float st = sqrt(max(0.0, 1.0 - ct * ct)); + float ph = 2.0 * PI * u2; + vec3 Ht = vec3(st * cos(ph), st * sin(ph), ct); vec3 up = (abs(N.y) < 0.999) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0); vec3 T = normalize(cross(up, N)); vec3 B = cross(N, T); - mat3 onb = mat3(T, B, N); - return onb * H_tangent; + return mat3(T, B, N) * Ht; } -// Sobol-based diffuse scattering with cosine-weighted importance sampling ScatterResult scatter_diffuse_sobol(Ray ray_in, HitInfo hit, Material mat, inout SobolState state) { ScatterResult r; r.scattered = true; r.attenuation = mat.albedo; - - // Cosine-weighted importance sampling for Lambertian BRDF - float r_sqrt = sqrt(sobol_next(state)); - float phi = 2.0 * PI * sobol_next(state); - float x = r_sqrt * cos(phi); - float y = r_sqrt * sin(phi); + float rs = sqrt(sobol_next(state)); + float ph = 2.0 * PI * sobol_next(state); + float x = rs * cos(ph), y = rs * sin(ph); float z = sqrt(max(0.0, 1.0 - x * x - y * y)); - vec3 up = (abs(hit.normal.y) < 0.999) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0); vec3 T = normalize(cross(up, hit.normal)); vec3 B = cross(hit.normal, T); - mat3 onb = mat3(T, B, hit.normal); - vec3 dir = onb * vec3(x, y, z); - + vec3 dir = mat3(T, B, hit.normal) * vec3(x, y, z); if (near_zero(dir)) dir = hit.normal; - r.scattered_ray.origin = hit.position + hit.normal * EPSILON; r.scattered_ray.direction = dir; return r; } -// Sobol-based metal scattering (GGX) ScatterResult scatter_metal_sobol(Ray ray_in, HitInfo hit, Material mat, inout SobolState state) { ScatterResult r; - vec3 V = normalize(-ray_in.direction); - vec3 N = hit.normal; float roughness = clamp(mat.roughness, 0.04, 1.0); - - vec3 H = sobol_ggx_half_vector(roughness, N, state); - if (dot(H, N) < 0.0) H = -H; - + vec3 H = sobol_ggx_half_vector(roughness, hit.normal, state); + if (dot(H, hit.normal) < 0.0) H = -H; vec3 L = reflect(-V, H); - - float NdotL = dot(N, L); - if (NdotL <= 0.0) { + if (dot(hit.normal, L) <= 0.0) { r.scattered = false; r.attenuation = vec3(0.0); return r; } - float HdotV = max(dot(H, V), 0.001); vec3 F = fresnel_schlick(HdotV, mat.albedo); - r.attenuation = clamp(F, vec3(0.0), vec3(1.0)); r.scattered = true; - r.scattered_ray.origin = hit.position + N * EPSILON; + r.scattered_ray.origin = hit.position + hit.normal * EPSILON; r.scattered_ray.direction = L; return r; } -// Sobol-based dielectric scattering ScatterResult scatter_dielectric_sobol(Ray ray_in, HitInfo hit, Material mat, inout SobolState state) { ScatterResult r; r.scattered = true; r.attenuation = vec3(1.0); - - vec3 unit_dir = normalize(ray_in.direction); - float cos_theta = dot(-unit_dir, hit.normal); - float sin_theta = sqrt(max(0.0, 1.0 - cos_theta * cos_theta)); - - bool entering = cos_theta > 0.0; - float eta = entering ? (1.0 / mat.ior) : mat.ior; - vec3 normal = entering ? hit.normal : -hit.normal; - - float sin_theta_t = eta * sin_theta; - bool total_internal_reflection = sin_theta_t >= 1.0; - - float f = fresnel_dielectric(cos_theta, mat.ior); - + vec3 ud = normalize(ray_in.direction); + float ct = dot(-ud, hit.normal); + float st = sqrt(max(0.0, 1.0 - ct * ct)); + bool ent = ct > 0.0; + float eta = ent ? (1.0 / mat.ior) : mat.ior; + vec3 N = ent ? hit.normal : -hit.normal; + float st_t = eta * st; + float f = fresnel_dielectric(ct, mat.ior); vec3 dir; - if (total_internal_reflection || sobol_next(state) < f) { - dir = reflect_vector(unit_dir, normal); - } else { - dir = refract_vector(unit_dir, normal, eta); - } - + if (st_t >= 1.0 || sobol_next(state) < f) + dir = reflect_vector(ud, N); + else + dir = refract_vector(ud, N, eta); r.scattered_ray.origin = hit.position + dir * EPSILON; r.scattered_ray.direction = dir; return r; } -// Sobol-based scatter dispatcher ScatterResult scatter_ray_sobol(Ray ray_in, HitInfo hit, Material mat, inout SobolState state) { if (mat.type == MATERIAL_DIFFUSE) return scatter_diffuse_sobol(ray_in, hit, mat, state); if (mat.type == MATERIAL_METAL) return scatter_metal_sobol(ray_in, hit, mat, state); if (mat.type == MATERIAL_DIELECTRIC) return scatter_dielectric_sobol(ray_in, hit, mat, state); - ScatterResult r; r.scattered = false; r.attenuation = vec3(0.0); return r; } -// Generate camera ray (center pixel, no jitter) Ray generate_camera_ray(ivec2 pixel_coords, ivec2 image_size) { vec2 uv = (vec2(pixel_coords) + vec2(0.5)) / vec2(image_size); vec2 ndc = uv * 2.0 - 1.0; - - vec4 p_near = u_inv_view_projection * vec4(ndc, 0.0, 1.0); - vec4 p_far = u_inv_view_projection * vec4(ndc, 1.0, 1.0); - vec3 near_ws = p_near.xyz / p_near.w; - vec3 far_ws = p_far.xyz / p_far.w; - + vec4 pn = u_inv_view_projection * vec4(ndc, 0.0, 1.0); + vec4 pf = u_inv_view_projection * vec4(ndc, 1.0, 1.0); Ray r; - r.origin = near_ws; - r.direction = normalize(far_ws - near_ws); + r.origin = pn.xyz / pn.w; + r.direction = normalize(pf.xyz / pf.w - r.origin); return r; } -// Path tracing with G-Buffer acceleration for primary ray (Sobol sampling) vec3 trace_path_sobol(ivec2 pixel_coords, ivec2 image_size, inout SobolState sobol) { Ray ray = generate_camera_ray(pixel_coords, image_size); - vec3 radiance = vec3(0.0); vec3 throughput = vec3(1.0); - // Depth 0: try G-Buffer hit first HitInfo hit0 = trace_primary_gbuffer(ray, pixel_coords); if (hit0.hit) { Material mat0 = fetch_material(hit0.material_id); - - if (hit0.material_type >= 0) { - mat0.type = hit0.material_type; - } - + if (hit0.material_type >= 0) mat0.type = hit0.material_type; apply_material_textures(mat0, hit0.normal, hit0.texcoord, hit0.tangent); - radiance += throughput * mat0.emission; - ScatterResult sc0 = scatter_ray_sobol(ray, hit0, mat0, sobol); if (!sc0.scattered) return radiance; - throughput *= sc0.attenuation; ray = sc0.scattered_ray; } - // Subsequent bounces: BVH for (uint depth = (hit0.hit ? 1u : 0u); depth < u_max_depth; ++depth) { HitInfo hit = trace_ray_bvh(ray); if (!hit.hit) { radiance += throughput * environment_color(ray.direction); break; } - Material mat = fetch_material(hit.material_id); apply_material_textures(mat, hit.normal, hit.texcoord, hit.tangent); - radiance += throughput * mat.emission; - ScatterResult sc = scatter_ray_sobol(ray, hit, mat, sobol); if (!sc.scattered) break; - throughput *= sc.attenuation; - if (depth > 3u) { - float p = max(throughput.r, max(throughput.g, throughput.b)); + float p = max(max(throughput.r, throughput.g), throughput.b); p = clamp(p, 0.0, 0.95); if (p < RR_THRESHOLD || sobol_next(sobol) > p) break; throughput /= p; } - ray = sc.scattered_ray; - if (all(lessThan(throughput, vec3(EPSILON)))) break; } - return radiance; } -// ACES Filmic Tone Mapping -vec3 aces_tonemap(vec3 x) { - float a = 2.51; - float b = 0.03; - float c = 2.43; - float d = 0.59; - float e = 0.14; - return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0); -} - void main() { - ivec2 pixel_coords = ivec2(gl_GlobalInvocationID.xy); - ivec2 image_size = imageSize(output_image); + ivec2 rt_coord = ivec2(gl_GlobalInvocationID.xy); + ivec2 output_size = imageSize(output_image); + if (rt_coord.x >= output_size.x || rt_coord.y >= output_size.y) return; + + ivec2 pixel_coords; + ivec2 image_size; + + if (u_sr_enabled != 0u) { + uint jx = u_sr_jitter % u_sr_block; + uint jy = u_sr_jitter / u_sr_block; + pixel_coords = rt_coord * int(u_sr_block) + ivec2(int(jx), int(jy)); + image_size = ivec2(int(u_sr_full_width), int(u_sr_full_height)); + } else { + pixel_coords = rt_coord; + image_size = output_size; + } + if (pixel_coords.x >= image_size.x || pixel_coords.y >= image_size.y) return; uint pixel_index = uint(pixel_coords.x) + uint(pixel_coords.y) * uint(image_size.x); - uint seed = init_seed(pixel_coords, image_size, u_frame_count); vec3 color = vec3(0.0); uint spp = max(u_samples_per_pixel, 1u); @@ -309,19 +259,26 @@ void main() { color += trace_path_sobol(pixel_coords, image_size, sobol); } color /= float(spp); - color = clamp(color, vec3(0.0), vec3(100.0)); - vec3 accumulation_color = color; - - if (u_enable_accumulation && u_frame_count > 0u) { - vec3 accumulated = imageLoad(accumulation_image, pixel_coords).rgb; - float w = 1.0 / float(u_frame_count + 1u); - accumulation_color = mix(accumulated, color, w); + // ── Super resolution: per‑cycle running average inside the RT shader ── + if (u_sr_enabled != 0u) { + vec4 old = imageLoad(accumulation_image, pixel_coords); + uint cyc = u_frame_count / u_sr_scaling; + float w = 1.0 / float(cyc + 1u); + vec3 acc = mix(old.rgb, color, w); + imageStore(accumulation_image, pixel_coords, vec4(acc, 1.0)); + return; } - - vec3 output_color = aces_tonemap(accumulation_color); - imageStore(accumulation_image, pixel_coords, vec4(accumulation_color, 1.0)); - imageStore(output_image, pixel_coords, vec4(output_color, 1.0)); + // ── Standard non‑SR accumulation ───────────────────────────────────── + vec3 acc_color = color; + if (u_enable_accumulation && u_frame_count > 0u) { + vec3 old = imageLoad(accumulation_image, rt_coord).rgb; + float w = 1.0 / float(u_frame_count + 1u); + acc_color = mix(old, color, w); + } + vec3 out_color = aces_tonemap(acc_color); + imageStore(accumulation_image, rt_coord, vec4(acc_color, 1.0)); + imageStore(output_image, rt_coord, vec4(out_color, 1.0)); } diff --git a/src/core/raytracer.cpp b/src/core/raytracer.cpp index ea8495d..0722eb9 100644 --- a/src/core/raytracer.cpp +++ b/src/core/raytracer.cpp @@ -2,6 +2,7 @@ #include "basic/constants.h" #include "resource/resource_manager.h" #include "utils/logger.h" +#include #include namespace are { @@ -17,38 +18,21 @@ namespace { return h; } - // Compute hash of texture pointers for a specific slot uint compute_slot_texture_hash(const std::vector> &textures) { if (textures.empty()) return 0u; - // Hash the raw pointers to detect texture set changes std::vector ptrs; ptrs.reserve(textures.size()); - for (const auto &t : textures) { + for (const auto &t : textures) ptrs.push_back(t.get()); - } return fnv1a_hash_bytes(ptrs.data(), ptrs.size() * sizeof(void *)); } } // namespace RayTracer::RayTracer(uint width, uint height, const RayTracerConfig &config) - : width_(width) - , height_(height) - , config_(config) - , materials_hash_(0u) - , lights_hash_(0u) - , texture_config_hash_(0u) - , texture_arrays_dirty_(true) - , accumulation_texture_(INVALID_HANDLE) - , material_buffer_(INVALID_HANDLE) - , light_buffer_(INVALID_HANDLE) - , bvh_(nullptr) - , bvh_built_(false) - , frame_count_(0) - , initialized_(false) { - for (int i = 0; i < 6; ++i) { + : width_(width), height_(height), config_(config), materials_hash_(0u), lights_hash_(0u), texture_config_hash_(0u), texture_arrays_dirty_(true), accumulation_texture_(INVALID_HANDLE), material_buffer_(INVALID_HANDLE), light_buffer_(INVALID_HANDLE), bvh_(nullptr), bvh_built_(false), frame_count_(0), initialized_(false) { + for (int i = 0; i < 6; ++i) texture_slot_hashes_[i] = 0u; - } } RayTracer::~RayTracer() { @@ -60,15 +44,9 @@ bool RayTracer::initialize(const std::shared_ptr &shader) { ARE_LOG_WARN("RayTracer already initialized"); return true; } - ResourceManager &rm = ResourceManager::instance(); - compute_shader_ = shader; - - // Create accumulation texture accumulation_texture_ = rm.create_texture(width_, height_, TextureFormat::RGBA32F); - - // Create shader storage buffers BufferDescription ssbo_desc; ssbo_desc.type = BufferType::SHADER_STORAGE_BUFFER; ssbo_desc.usage = BufferUsage::DYNAMIC_DRAW; @@ -76,108 +54,85 @@ bool RayTracer::initialize(const std::shared_ptr &shader) { ssbo_desc.data = nullptr; material_buffer_ = rm.create_buffer(ssbo_desc); light_buffer_ = rm.create_buffer(ssbo_desc); - - // Initialize texture arrays (empty for now) for (int i = 0; i < 6; i++) { texture_arrays_[i] = 0; texture_array_sizes_[i] = 0; } - - // Initialize BVH if enabled - if (config_.use_bvh) { + if (config_.use_bvh) bvh_ = std::make_unique(); - } - initialized_ = true; - ARE_LOG_INFO("RayTracer initialized successfully"); + ARE_LOG_INFO("RayTracer initialized (" + std::to_string(width_) + "x" + std::to_string(height_) + ")"); return true; } void RayTracer::release() { if (!initialized_) return; - ResourceManager &rm = ResourceManager::instance(); - if (accumulation_texture_ != INVALID_HANDLE) { rm.destroy_texture(accumulation_texture_); accumulation_texture_ = INVALID_HANDLE; } - - // Release texture arrays for (int i = 0; i < 6; i++) { if (texture_arrays_[i] != 0) { rm.destroy_texture_array(texture_arrays_[i]); texture_arrays_[i] = 0; } } - if (material_buffer_ != INVALID_HANDLE) { rm.destroy_buffer(material_buffer_); material_buffer_ = INVALID_HANDLE; } - if (light_buffer_ != INVALID_HANDLE) { rm.destroy_buffer(light_buffer_); light_buffer_ = INVALID_HANDLE; } - bvh_node_buffer_.release(); bvh_triangle_buffer_.release(); bvh_attr_buffer_.release(); - bvh_.reset(); bvh_built_ = false; - initialized_ = false; ARE_LOG_INFO("RayTracer released"); } bool RayTracer::rebuild_bvh(const Scene &scene) { - if (!config_.use_bvh) { - ARE_LOG_WARN("BVH is disabled in configuration"); + if (!config_.use_bvh) return false; - } - - if (!bvh_) { + if (!bvh_) bvh_ = std::make_unique(); - } - - ARE_LOG_INFO("Building BVH for ray tracing..."); - if (!bvh_->build(scene.get_meshes())) { ARE_LOG_ERROR("Failed to build BVH"); return false; } - if (!bvh_->upload_to_gpu(bvh_node_buffer_, bvh_triangle_buffer_, bvh_attr_buffer_)) { ARE_LOG_ERROR("Failed to upload BVH to GPU"); return false; } - bvh_built_ = true; reset_accumulation(); - ARE_LOG_INFO("BVH built and uploaded successfully"); return true; } -void RayTracer::trace(const Scene &scene, const GBuffer &gbuffer, TextureHandle output_texture) { +void RayTracer::trace(const Scene &scene, const GBuffer &gbuffer, TextureHandle output_image, + uint sr_scaling, uint sr_jitter, TextureHandle sr_accum) { if (!initialized_) { ARE_LOG_ERROR("RayTracer not initialized"); return; } - if (!compute_shader_->is_valid()) { - ARE_LOG_ERROR("Ray tracing compute shader not loaded"); + ARE_LOG_ERROR("Compute shader not loaded"); return; } - - // Build BVH if enabled and not built yet - if (config_.use_bvh && !bvh_built_) { + if (config_.use_bvh && !bvh_built_) rebuild_bvh(scene); - } - // Build texture arrays BEFORE uploading materials (so indices are available) + uint sr_enabled = (sr_scaling > 1) ? 1u : 0u; + uint sr_block = sr_enabled ? static_cast(std::sqrt(static_cast(sr_scaling))) : 1u; + uint dispatch_w = sr_enabled ? (width_ / sr_block) : width_; + uint dispatch_h = sr_enabled ? (height_ / sr_block) : height_; + + // ── texture arrays / scene data ──────────────────────────────────────── const auto &materials = scene.get_materials(); bool has_textures = false; for (const auto &mat : materials) { @@ -188,40 +143,28 @@ void RayTracer::trace(const Scene &scene, const GBuffer &gbuffer, TextureHandle } if (has_textures) { build_texture_arrays_(scene); - - // Bind texture arrays - glActiveTexture(GL_TEXTURE10); - glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[0]); - glActiveTexture(GL_TEXTURE11); - glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[1]); - glActiveTexture(GL_TEXTURE12); - glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[2]); - glActiveTexture(GL_TEXTURE13); - glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[3]); - glActiveTexture(GL_TEXTURE14); - glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[4]); - glActiveTexture(GL_TEXTURE15); - glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[5]); + for (int slot = 0; slot < 6; slot++) { + glActiveTexture(GL_TEXTURE10 + slot); + glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[slot]); + } } - - // Upload scene data (materials now have correct texture indices) upload_scene_data_(scene); - // Use compute shader - if (!compute_shader_ || !compute_shader_->is_valid()) { - ARE_LOG_ERROR("Ray tracing compute shader not set or invalid"); - return; - } compute_shader_->use(); - // Bind G-Buffer textures + // ── G‑buffer images ──────────────────────────────────────────────────── bind_gbuffer_(gbuffer); - // Bind output and accumulation textures - glBindImageTexture(3, output_texture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F); - glBindImageTexture(4, accumulation_texture_, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F); + // ── output image (binding 3) ────────────────────────────────────────── + glBindImageTexture(3, output_image, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F); - // Bind BVH buffers if enabled + // ── accumulation image (binding 4) ──────────────────────────────────── + // SR: caller provides the full‑res accumulation texture + // non‑SR: use ray‑tracer's own accumulation texture + TextureHandle accum_tex = sr_enabled ? sr_accum : accumulation_texture_; + glBindImageTexture(4, accum_tex, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F); + + // ── BVH ──────────────────────────────────────────────────────────────── if (config_.use_bvh && bvh_built_) { bvh_node_buffer_.bind_base(2); bvh_triangle_buffer_.bind_base(3); @@ -232,55 +175,47 @@ void RayTracer::trace(const Scene &scene, const GBuffer &gbuffer, TextureHandle compute_shader_->set_bool("u_use_bvh", false); } - // Set uniforms + // ── uniforms ─────────────────────────────────────────────────────────── compute_shader_->set_uint("u_frame_count", frame_count_); compute_shader_->set_uint("u_samples_per_pixel", config_.samples_per_pixel); compute_shader_->set_uint("u_max_depth", config_.max_depth); compute_shader_->set_uint("u_light_count", static_cast(scene.get_lights().size())); - compute_shader_->set_bool("u_enable_accumulation", config_.enable_accumulation); - - // Enable/disable textures based on material usage + compute_shader_->set_bool("u_enable_accumulation", sr_enabled ? false : config_.enable_accumulation); compute_shader_->set_bool("u_enable_textures", has_textures); - // Set camera data const Camera &camera = scene.get_camera(); - Mat4 inv_vp = glm::inverse(camera.get_view_projection_matrix()); - compute_shader_->set_mat4("u_inv_view_projection", inv_vp); + compute_shader_->set_mat4("u_inv_view_projection", glm::inverse(camera.get_view_projection_matrix())); - // Dispatch compute shader - uint num_groups_x = (width_ + COMPUTE_GROUP_SIZE_X - 1) / COMPUTE_GROUP_SIZE_X; - uint num_groups_y = (height_ + COMPUTE_GROUP_SIZE_Y - 1) / COMPUTE_GROUP_SIZE_Y; + compute_shader_->set_uint("u_sr_enabled", sr_enabled); + compute_shader_->set_uint("u_sr_scaling", sr_scaling); + compute_shader_->set_uint("u_sr_block", sr_block); + compute_shader_->set_uint("u_sr_jitter", sr_jitter); + compute_shader_->set_uint("u_sr_full_width", width_); + compute_shader_->set_uint("u_sr_full_height", height_); + // ── dispatch ─────────────────────────────────────────────────────────── + uint num_groups_x = (dispatch_w + COMPUTE_GROUP_SIZE_X - 1) / COMPUTE_GROUP_SIZE_X; + uint num_groups_y = (dispatch_h + COMPUTE_GROUP_SIZE_Y - 1) / COMPUTE_GROUP_SIZE_Y; glDispatchCompute(num_groups_x, num_groups_y, 1); - - // Memory barrier glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); - // Increment frame count for accumulation - if (config_.enable_accumulation) { + if (config_.enable_accumulation || sr_enabled) frame_count_++; - } } void RayTracer::resize(uint width, uint height) { if (width == width_ && height == height_) return; - + ARE_LOG_DEBUG("RayTracer resize: " + std::to_string(width_) + "x" + std::to_string(height_) + " -> " + std::to_string(width) + "x" + std::to_string(height)); width_ = width; height_ = height; - - if (initialized_) { - ResourceManager &rm = ResourceManager::instance(); - - // Recreate accumulation texture - if (accumulation_texture_ != INVALID_HANDLE) { - rm.destroy_texture(accumulation_texture_); - } - - accumulation_texture_ = rm.create_texture(width_, height_, TextureFormat::RGBA32F); - - reset_accumulation(); - } + if (!initialized_) + return; + ResourceManager &rm = ResourceManager::instance(); + if (accumulation_texture_ != INVALID_HANDLE) + rm.destroy_texture(accumulation_texture_); + accumulation_texture_ = rm.create_texture(width_, height_, TextureFormat::RGBA32F); + reset_accumulation(); } void RayTracer::reset_accumulation() { @@ -289,10 +224,8 @@ void RayTracer::reset_accumulation() { void RayTracer::set_config(const RayTracerConfig &config) { bool bvh_changed = (config.use_bvh != config_.use_bvh); - config_ = config; reset_accumulation(); - if (bvh_changed) { if (config_.use_bvh && !bvh_) { bvh_ = std::make_unique(); @@ -305,66 +238,49 @@ void RayTracer::set_config(const RayTracerConfig &config) { } void RayTracer::upload_scene_data_(const Scene &scene) { - // Upload materials (on change only) const auto &materials = scene.get_materials(); if (!materials.empty()) { - // Aligned to match GLSL std430 layout (vec3 = vec4 = 16 bytes) struct MaterialData { alignas(16) Vec3 albedo; alignas(16) Vec3 emission; - float metallic; - float roughness; + float metallic, roughness; int type; - float ior; - float ao; - float padding1; + float ior, ao, padding1; uint texture_handles[6]; }; - - std::vector material_data; - material_data.reserve(materials.size()); - + std::vector md; + md.reserve(materials.size()); for (const auto &mat : materials) { - MaterialData data {}; - data.albedo = mat->get_albedo(); - data.metallic = mat->get_metallic(); - data.emission = mat->get_emission(); - data.roughness = mat->get_roughness(); - data.type = static_cast(mat->get_type()); - data.ior = mat->get_ior(); - data.ao = 1.0f; // default: no AO - - // Texture array indices (0 = no texture, 1+ = index into array) - data.texture_handles[0] = mat->get_texture_index(TextureSlot::ALBEDO); - data.texture_handles[1] = mat->get_texture_index(TextureSlot::NORMAL); - data.texture_handles[2] = mat->get_texture_index(TextureSlot::METALLIC); - data.texture_handles[3] = mat->get_texture_index(TextureSlot::ROUGHNESS); - data.texture_handles[4] = mat->get_texture_index(TextureSlot::AO); - data.texture_handles[5] = mat->get_texture_index(TextureSlot::EMISSION); - - material_data.push_back(data); + MaterialData d {}; + d.albedo = mat->get_albedo(); + d.metallic = mat->get_metallic(); + d.emission = mat->get_emission(); + d.roughness = mat->get_roughness(); + d.type = static_cast(mat->get_type()); + d.ior = mat->get_ior(); + d.ao = 1.0f; + d.texture_handles[0] = mat->get_texture_index(TextureSlot::ALBEDO); + d.texture_handles[1] = mat->get_texture_index(TextureSlot::NORMAL); + d.texture_handles[2] = mat->get_texture_index(TextureSlot::METALLIC); + d.texture_handles[3] = mat->get_texture_index(TextureSlot::ROUGHNESS); + d.texture_handles[4] = mat->get_texture_index(TextureSlot::AO); + d.texture_handles[5] = mat->get_texture_index(TextureSlot::EMISSION); + md.push_back(d); } - - uint h = fnv1a_hash_bytes(material_data.data(), material_data.size() * sizeof(MaterialData)); + uint h = fnv1a_hash_bytes(md.data(), md.size() * sizeof(MaterialData)); if (h != materials_hash_) { materials_hash_ = h; - glBindBuffer(GL_SHADER_STORAGE_BUFFER, material_buffer_); - glBufferData(GL_SHADER_STORAGE_BUFFER, - material_data.size() * sizeof(MaterialData), - material_data.data(), GL_DYNAMIC_DRAW); + glBufferData(GL_SHADER_STORAGE_BUFFER, md.size() * sizeof(MaterialData), md.data(), GL_DYNAMIC_DRAW); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, material_buffer_); - - reset_accumulation(); // materials changed => invalidate accumulation + reset_accumulation(); } else { - // Still ensure bound (in case other code changed bindings) glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, material_buffer_); } } else { materials_hash_ = 0u; } - // Upload lights (on change only) const auto &lights = scene.get_lights(); if (!lights.empty()) { struct LightData { @@ -377,33 +293,26 @@ void RayTracer::upload_scene_data_(const Scene &scene) { Vec2 spot_angles; Vec2 padding; }; - - std::vector light_data; - light_data.reserve(lights.size()); - - for (const auto &light : lights) { - LightData data {}; - data.position = light->get_position(); - data.type = static_cast(light->get_type()); - data.direction = light->get_direction(); - data.intensity = light->get_intensity(); - data.color = light->get_color(); - data.range = light->get_range(); - data.spot_angles = Vec2(light->get_inner_angle(), light->get_outer_angle()); - light_data.push_back(data); + std::vector ld; + ld.reserve(lights.size()); + for (const auto &l : lights) { + LightData d {}; + d.position = l->get_position(); + d.type = static_cast(l->get_type()); + d.direction = l->get_direction(); + d.intensity = l->get_intensity(); + d.color = l->get_color(); + d.range = l->get_range(); + d.spot_angles = Vec2(l->get_inner_angle(), l->get_outer_angle()); + ld.push_back(d); } - - uint h = fnv1a_hash_bytes(light_data.data(), light_data.size() * sizeof(LightData)); + uint h = fnv1a_hash_bytes(ld.data(), ld.size() * sizeof(LightData)); if (h != lights_hash_) { lights_hash_ = h; - glBindBuffer(GL_SHADER_STORAGE_BUFFER, light_buffer_); - glBufferData(GL_SHADER_STORAGE_BUFFER, - light_data.size() * sizeof(LightData), - light_data.data(), GL_DYNAMIC_DRAW); + glBufferData(GL_SHADER_STORAGE_BUFFER, ld.size() * sizeof(LightData), ld.data(), GL_DYNAMIC_DRAW); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, light_buffer_); - - reset_accumulation(); // lights changed => invalidate accumulation + reset_accumulation(); } else { glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, light_buffer_); } @@ -414,29 +323,20 @@ void RayTracer::upload_scene_data_(const Scene &scene) { void RayTracer::bind_gbuffer_(const GBuffer &gbuffer) { glBindImageTexture(0, gbuffer.get_texture(GBUFFER_POSITION), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F); - glBindImageTexture(1, gbuffer.get_texture(GBUFFER_NORMAL), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RG32F); // Octahedral encoded - + glBindImageTexture(1, gbuffer.get_texture(GBUFFER_NORMAL), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RG32F); glBindImageTexture(5, gbuffer.get_texture(GBUFFER_MATERIAL), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F); glBindImageTexture(6, gbuffer.get_texture(GBUFFER_MATERIAL_ID), 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI); - - // Texcoord glBindImageTexture(2, gbuffer.get_texture(GBUFFER_TEXCOORD), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F); - - // Tangent glBindImageTexture(7, gbuffer.get_texture(GBUFFER_TANGENT), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F); } void RayTracer::build_texture_arrays_(const Scene &scene) { const auto &materials = scene.get_materials(); - - // Collect all textures for each slot std::vector> textures[6]; - for (const auto &mat : materials) { for (int slot = 0; slot < 6; slot++) { auto tex = mat->get_texture(static_cast(slot)); if (tex && tex->is_valid()) { - // Check if texture already added (use set for O(1) lookup) bool found = false; for (const auto &t : textures[slot]) { if (t.get() == tex.get()) { @@ -444,92 +344,62 @@ void RayTracer::build_texture_arrays_(const Scene &scene) { break; } } - if (!found) { + if (!found) textures[slot].push_back(tex); - } } } } - - // Compute hash for each slot and check if rebuild is needed bool any_slot_dirty = false; uint new_slot_hashes[6]; for (int slot = 0; slot < 6; slot++) { new_slot_hashes[slot] = compute_slot_texture_hash(textures[slot]); - if (new_slot_hashes[slot] != texture_slot_hashes_[slot]) { + if (new_slot_hashes[slot] != texture_slot_hashes_[slot]) any_slot_dirty = true; - } } - - // If no slots changed, skip entire rebuild if (!any_slot_dirty && !texture_arrays_dirty_) { - // Still need to bind existing arrays - for (int slot = 0; slot < 6; slot++) { + for (int slot = 0; slot < 6; slot++) if (texture_arrays_[slot] != 0) { glActiveTexture(GL_TEXTURE10 + slot); glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[slot]); } - } return; } - ResourceManager &rm = ResourceManager::instance(); - - // Build arrays only for dirty slots for (int slot = 0; slot < 6; slot++) { - // Skip if this slot hasn't changed if (new_slot_hashes[slot] == texture_slot_hashes_[slot] && !texture_arrays_dirty_) { - // Bind existing array if (texture_arrays_[slot] != 0) { glActiveTexture(GL_TEXTURE10 + slot); glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[slot]); } continue; } - - // Destroy previous texture array if exists if (texture_arrays_[slot] != 0) { rm.destroy_texture_array(texture_arrays_[slot]); texture_arrays_[slot] = 0; } - if (textures[slot].empty()) { texture_array_sizes_[slot] = 0; texture_slot_hashes_[slot] = 0u; continue; } - texture_array_sizes_[slot] = static_cast(textures[slot].size()); - - // Create texture array using ResourceManager TextureArrayDescription desc; desc.textures = textures[slot]; desc.filter = TextureFilter::LINEAR; desc.wrap = TextureWrap::REPEAT; - texture_arrays_[slot] = rm.create_texture_array(desc); - - // Set texture index on all materials using each texture for (size_t i = 0; i < textures[slot].size(); i++) { - // Index is i+1 because 0 means "no texture" in the shader uint32_t array_index = static_cast(i) + 1; - for (const auto &mat : materials) { - if (mat->get_texture(static_cast(slot)).get() == textures[slot][i].get()) { + for (const auto &mat : materials) + if (mat->get_texture(static_cast(slot)).get() == textures[slot][i].get()) mat->set_texture_index(static_cast(slot), array_index); - } - } } - - // Update slot hash texture_slot_hashes_[slot] = new_slot_hashes[slot]; - - // Bind the newly created array if (texture_arrays_[slot] != 0) { glActiveTexture(GL_TEXTURE10 + slot); glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[slot]); } } - texture_arrays_dirty_ = false; } diff --git a/src/core/renderer.cpp b/src/core/renderer.cpp index 590cbca..db5b81d 100644 --- a/src/core/renderer.cpp +++ b/src/core/renderer.cpp @@ -9,8 +9,7 @@ namespace are { Renderer::Renderer(const RendererConfig &config) : config_(config) , rt_output_texture_(INVALID_HANDLE) - , initialized_(false) - , frame_count_(0) { + , initialized_(false) { } Renderer::~Renderer() { @@ -40,10 +39,7 @@ bool Renderer::initialize() { } // Initialize ray tracer - RayTracerConfig rt_config = config_.rt_config; - - // Initialize ray tracer - raytracer_ = std::make_unique(config_.output_width, config_.output_height, rt_config); + raytracer_ = std::make_unique(config_.output_width, config_.output_height, config_.rt_config); const auto &rt_shader = shader_manager_->get_raytracing_shader(); if (!raytracer_->initialize(rt_shader)) { ARE_LOG_ERROR("Failed to initialize ray tracer"); @@ -65,6 +61,16 @@ bool Renderer::initialize() { return false; } + // Initialize super resolution if enabled + if (config_.sr_config.enabled) { + super_resolution_ = std::make_unique(config_.output_width, config_.output_height, config_.sr_config); + const auto &sr_shader = shader_manager_->get_super_resolution_shader(); + if (!super_resolution_->initialize(sr_shader)) { + ARE_LOG_ERROR("Failed to initialize super resolution"); + return false; + } + } + // Create ray tracing output texture (reused every frame) ResourceManager &rm = ResourceManager::instance(); rt_output_texture_ = rm.create_texture(config_.output_width, config_.output_height, TextureFormat::RGBA32F); @@ -92,6 +98,7 @@ void Renderer::shutdown() { gbuffer_.reset(); shader_manager_.reset(); denoiser_.reset(); + super_resolution_.reset(); initialized_ = false; ARE_LOG_INFO("Aurora Rendering Engine shut down"); @@ -124,21 +131,36 @@ RenderStats Renderer::render(const Scene &scene, TextureHandle output_texture) { // Phase 2: Ray tracing pass auto raytrace_start = std::chrono::high_resolution_clock::now(); - // Use output texture if provided, otherwise use internal texture - TextureHandle rt_output = (output_texture != 0) ? output_texture : rt_output_texture_; + TextureHandle rt_output; + if (config_.sr_config.enabled && super_resolution_) { + auto &sr = *super_resolution_; + uint jitt = sr.get_current_jitter_frame(); + rt_output = sr.get_low_res_rt_texture(); - raytracer_->trace(scene, *gbuffer_, rt_output); + raytracer_->trace(scene, *gbuffer_, rt_output, + config_.sr_config.scaling, jitt, + sr.get_accumulated_rt_texture()); + } else { + rt_output = (output_texture != 0) ? output_texture : rt_output_texture_; + raytracer_->trace(scene, *gbuffer_, rt_output); + } auto raytrace_end = std::chrono::high_resolution_clock::now(); stats.raytrace_time_ms_ = std::chrono::duration(raytrace_end - raytrace_start).count(); - // Phase 3: Denoise texture - TextureHandle final_output = rt_output; - - if (config_.enable_denoising && denoiser_) { - // Use temporal accumulation with weight 0.1 (10% blend of new frame) - float temporal_weight = 0.1f; - final_output = denoiser_->denoise(rt_output, 1, temporal_weight); + // Phase 3: Post-processing and output + TextureHandle final_output; + if (config_.sr_config.enabled && super_resolution_) { + // Denoising intentionally skipped — cross‑cycle accumulation provides temporal smoothing + auto &sr = *super_resolution_; + final_output = sr.upscale(); + sr.advance_jitter_frame(); + } else { + final_output = rt_output; + if (config_.enable_denoising && denoiser_) { + float temporal_weight = 0.1f; + final_output = denoiser_->denoise(final_output, 1, temporal_weight); + } } // Phase 4: Blit to screen if output is default framebuffer @@ -156,11 +178,6 @@ RenderStats Renderer::render(const Scene &scene, TextureHandle output_texture) { stats.triangle_count_ += mesh->get_indices().size() / 3; } - // Estimate ray count (very rough) - stats.ray_count_ = config_.output_width * config_.output_height * config_.rt_config.samples_per_pixel * config_.rt_config.max_depth; - - frame_count_++; - return stats; } @@ -184,6 +201,10 @@ void Renderer::resize(uint width, uint height) { raytracer_->resize(width, height); denoiser_->resize(width, height); + if (super_resolution_) { + super_resolution_->resize(width, height); + } + ARE_LOG_INFO("Renderer resized to " + std::to_string(width) + "x" + std::to_string(height)); } } @@ -200,6 +221,15 @@ void Renderer::set_config(const RendererConfig &config) { // Update ray tracer config raytracer_->set_config(config_.rt_config); + + // Handle SR enable/disable + if (config_.sr_config.enabled && !super_resolution_) { + super_resolution_ = std::make_unique(config_.output_width, config_.output_height, config_.sr_config); + const auto &sr_shader = shader_manager_->get_super_resolution_shader(); + super_resolution_->initialize(sr_shader); + } else if (!config_.sr_config.enabled && super_resolution_) { + super_resolution_.reset(); + } } } @@ -211,6 +241,11 @@ void Renderer::notify_scene_changed(const Scene &scene) { if (denoiser_) { denoiser_->reset_history(); } + + // Reset super resolution accumulation on scene change + if (super_resolution_) { + super_resolution_->reset_accumulation(); + } } } // namespace are diff --git a/src/core/shader_manager.cpp b/src/core/shader_manager.cpp index 976d334..6e0eff9 100644 --- a/src/core/shader_manager.cpp +++ b/src/core/shader_manager.cpp @@ -39,6 +39,7 @@ void ShaderManager::release() { screen_blit_shader_.reset(); raytracing_shader_.reset(); denoise_shader_.reset(); + super_resolution_shader_.reset(); initialized_ = false; ARE_LOG_INFO("ShaderManager released"); @@ -94,7 +95,7 @@ std::shared_ptr ShaderManager::get_shader(const std::string &name) const bool ShaderManager::load_builtin_shaders_() { // Load G-buffer shader - ARE_LOG_INFO("Loading G-buffer shaders.."); + ARE_LOG_INFO("Loading G-buffer shaders..."); gbuffer_shader_ = std::make_shared(); if (!gbuffer_shader_->load("shaders/gbuffer/gbuffer.vert", "shaders/gbuffer/gbuffer.frag")) { ARE_LOG_ERROR("Failed to load G-Buffer shader"); @@ -132,6 +133,16 @@ bool ShaderManager::load_builtin_shaders_() { shader_cache_["denoise"] = denoise_shader_; ARE_LOG_INFO("Denoise shader loaded successfully"); + // Load super resolution shader + ARE_LOG_INFO("Loading super resolution compute shader..."); + super_resolution_shader_ = std::make_shared(); + if (!super_resolution_shader_->load_compute("shaders/postprocess/super_resolution.comp")) { + ARE_LOG_ERROR("Failed to load super resolution shader"); + return false; + } + shader_cache_["super_resolution"] = super_resolution_shader_; + ARE_LOG_INFO("Super resolution shader loaded successfully"); + return true; } diff --git a/src/core/super_resolution.cpp b/src/core/super_resolution.cpp new file mode 100644 index 0000000..0a8944d --- /dev/null +++ b/src/core/super_resolution.cpp @@ -0,0 +1,133 @@ +#include "core/super_resolution.h" +#include "basic/constants.h" +#include "resource/resource_manager.h" +#include "utils/logger.h" +#include +#include + +namespace are { + +uint SuperResolution::compute_block_size_() const { + return static_cast(std::sqrt(static_cast(config_.scaling))); +} + +SuperResolution::SuperResolution(uint full_width, uint full_height, const SuperResolutionConfig &config) + : full_width_(full_width), full_height_(full_height), config_(config), current_jitter_frame_(0), low_res_rt_texture_(INVALID_HANDLE), accumulated_rt_texture_(INVALID_HANDLE), upscaled_texture_(INVALID_HANDLE) { + uint block = compute_block_size_(); + low_res_w_ = full_width_ / block; + low_res_h_ = full_height_ / block; +} + +SuperResolution::~SuperResolution() { + release(); +} + +bool SuperResolution::initialize(const std::shared_ptr &shader) { + if (initialized_) { + ARE_LOG_WARN("SuperResolution already initialized"); + return true; + } + if (!shader || !shader->is_valid()) { + ARE_LOG_ERROR("Invalid shader"); + return false; + } + compute_shader_ = shader; + create_textures_(); + initialized_ = true; + ARE_LOG_INFO("SuperResolution initialized: " + std::to_string(full_width_) + "x" + std::to_string(full_height_) + " scaling_px=" + std::to_string(config_.scaling) + " lowres=" + std::to_string(low_res_w_) + "x" + std::to_string(low_res_h_)); + return true; +} + +void SuperResolution::release() { + if (!initialized_) + return; + ResourceManager &rm = ResourceManager::instance(); + if (low_res_rt_texture_ != INVALID_HANDLE) { + rm.destroy_texture(low_res_rt_texture_); + low_res_rt_texture_ = INVALID_HANDLE; + } + if (accumulated_rt_texture_ != INVALID_HANDLE) { + rm.destroy_texture(accumulated_rt_texture_); + accumulated_rt_texture_ = INVALID_HANDLE; + } + if (upscaled_texture_ != INVALID_HANDLE) { + rm.destroy_texture(upscaled_texture_); + upscaled_texture_ = INVALID_HANDLE; + } + initialized_ = false; + ARE_LOG_INFO("SuperResolution released"); +} + +TextureHandle SuperResolution::upscale() { + if (!initialized_ || !compute_shader_) + return INVALID_HANDLE; + + compute_shader_->use(); + glBindImageTexture(1, accumulated_rt_texture_, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F); + glBindImageTexture(2, upscaled_texture_, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F); + + uint gx = (full_width_ + COMPUTE_GROUP_SIZE_X - 1) / COMPUTE_GROUP_SIZE_X; + uint gy = (full_height_ + COMPUTE_GROUP_SIZE_Y - 1) / COMPUTE_GROUP_SIZE_Y; + glDispatchCompute(gx, gy, 1); + glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); + return upscaled_texture_; +} + +void SuperResolution::advance_jitter_frame() { + current_jitter_frame_ = (current_jitter_frame_ + 1) % config_.scaling; +} + +void SuperResolution::reset_accumulation() { + current_jitter_frame_ = 0; + clear_accumulation_texture_(); + ARE_LOG_DEBUG("SuperResolution accumulation reset (frame " + std::to_string(current_jitter_frame_) + ")"); +} + +void SuperResolution::resize(uint full_width, uint full_height) { + if (full_width == full_width_ && full_height == full_height_) + return; + full_width_ = full_width; + full_height_ = full_height; + uint block = compute_block_size_(); + low_res_w_ = full_width_ / block; + low_res_h_ = full_height_ / block; + if (!initialized_) + return; + + ResourceManager &rm = ResourceManager::instance(); + if (low_res_rt_texture_ != INVALID_HANDLE) { + rm.destroy_texture(low_res_rt_texture_); + } + if (accumulated_rt_texture_ != INVALID_HANDLE) { + rm.destroy_texture(accumulated_rt_texture_); + } + if (upscaled_texture_ != INVALID_HANDLE) { + rm.destroy_texture(upscaled_texture_); + } + create_textures_(); + ARE_LOG_INFO("SuperResolution resized to " + std::to_string(full_width) + "x" + std::to_string(full_height)); +} + +void SuperResolution::create_textures_() { + ResourceManager &rm = ResourceManager::instance(); + low_res_rt_texture_ = rm.create_texture(low_res_w_, low_res_h_, TextureFormat::RGBA32F); + accumulated_rt_texture_ = rm.create_texture(full_width_, full_height_, TextureFormat::RGBA32F); + upscaled_texture_ = rm.create_texture(full_width_, full_height_, TextureFormat::RGBA32F); + reset_accumulation(); +} + +void SuperResolution::clear_accumulation_texture_() const { + // FBO-based clear — far more efficient than a full compute dispatch. + // The texture is attached as a colour attachment and cleared to (0,0,0,0). + GLuint fbo; + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + accumulated_rt_texture_, 0); + const GLfloat clear_color[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + glClearBufferfv(GL_COLOR, 0, clear_color); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &fbo); +} + +} // namespace are