diff options
Diffstat (limited to 'src/rendering/render_path.cpp')
| -rw-r--r-- | src/rendering/render_path.cpp | 94 |
1 files changed, 94 insertions, 0 deletions
diff --git a/src/rendering/render_path.cpp b/src/rendering/render_path.cpp index 115d182..8cb0328 100644 --- a/src/rendering/render_path.cpp +++ b/src/rendering/render_path.cpp @@ -1,10 +1,18 @@ #include "render_path.h" #include "scene/scene.h" +#include "core/log.h" + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" #include <glm/gtc/matrix_transform.hpp> #include <algorithm> #include <cmath> +#include <filesystem> +#include <ctime> +#include <cstdio> +#include <vector> namespace Donut { @@ -90,4 +98,90 @@ namespace Donut // Backend-specific HDRI/GPU resource cleanup is the device's job (see e.g. // the OpenGL device clearing the HDRIManager texture cache on shutdown). } + + // Portable Float Map: a raw RGB float image (the actual physical values). Its + // raster is bottom-up; -1.0 scale flags little-endian. + static auto write_pfm(const std::string& path, int w, int h, const std::vector<float>& rgba) -> bool + { + std::FILE* f = std::fopen(path.c_str(), "wb"); + if (!f) return false; + std::fprintf(f, "PF\n%d %d\n-1.0\n", w, h); + std::vector<float> rgb((size_t)w * 3); + for (int y = h - 1; y >= 0; --y) + { + const float* src = &rgba[(size_t)y * w * 4]; + for (int x = 0; x < w; ++x) { rgb[x*3+0] = src[x*4+0]; rgb[x*3+1] = src[x*4+1]; rgb[x*3+2] = src[x*4+2]; } + std::fwrite(rgb.data(), sizeof(float), (size_t)w * 3, f); + } + std::fclose(f); + return true; + } + + // CSV grid of the scalar value (the R channel), one image row per text line. + static auto write_csv(const std::string& path, int w, int h, const std::vector<float>& rgba) -> bool + { + std::FILE* f = std::fopen(path.c_str(), "wb"); + if (!f) return false; + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) std::fprintf(f, x ? ",%g" : "%g", rgba[((size_t)y * w + x) * 4]); + std::fputc('\n', f); + } + std::fclose(f); + return true; + } + + auto RenderPath::export_frame(Scene& scene, const ExportConfig& cfg) -> int + { + if (!m_device) return 0; + const int w = std::max(cfg.width, 1), h = std::max(cfg.height, 1); + const bool raw = cfg.format != ExportFormat::Png; // Pfm / Csv want physical values + auto target = m_device->create_render_target( + w, h, raw ? RHI::Format::RGBA32F : RHI::Format::RGBA8, RHI::Format::None, RHI::Filter::Nearest); + + // Full-quality geodesic view from the sim camera (moving = false = settled). + GeodesicView gv; + glm::vec3 pos, fwd; + if (scene.sim_camera.get_camera_mode() == CameraMode::FPS) + { pos = scene.sim_camera.get_position(); fwd = scene.sim_camera.get_forward_direction(); } + else + { pos = scene.sim_camera.get_orbital_position(); fwd = glm::normalize(scene.sim_camera.get_orbital_target() - pos); } + glm::vec3 right = glm::normalize(glm::cross(fwd, glm::vec3(0, 1, 0))); + gv.position = pos; gv.right = right; gv.up = glm::cross(right, fwd); gv.forward = fwd; + gv.tan_half_fov = (float)tan(glm::radians(scene.black_hole.fov_degrees * 0.5f)); + gv.aspect = (float)w / (float)h; + gv.moving = false; + gv.time = 0.0f; + + std::error_code ec; std::filesystem::create_directories(cfg.directory, ec); + char stamp[32]; std::time_t t = std::time(nullptr); + std::strftime(stamp, sizeof(stamp), "%Y%m%d_%H%M%S", std::localtime(&t)); + const char* ext = cfg.format == ExportFormat::Pfm ? "pfm" : cfg.format == ExportFormat::Csv ? "csv" : "png"; + + int written = 0; + std::vector<uint8_t> px8; std::vector<float> pxf; + auto do_channel = [&](bool enabled, int channel, const char* name) + { + if (!enabled) return; + m_device->run_offscreen([&](RHI::CommandList& cmd) { + m_black_hole_renderer->render_export(cmd, target.get(), gv, scene.black_hole, + scene.objects, m_cubemap.get(), channel, raw); + }); + std::string path = cfg.directory + "/donut_" + name + "_" + stamp + "." + ext; + bool ok; + if (cfg.format == ExportFormat::Png) + { m_device->read_render_target(target.get(), px8); ok = stbi_write_png(path.c_str(), w, h, 4, px8.data(), w * 4) != 0; } + else + { + m_device->read_render_target_float(target.get(), pxf); + ok = (cfg.format == ExportFormat::Pfm) ? write_pfm(path, w, h, pxf) : write_csv(path, w, h, pxf); + } + if (ok) { DONUT_INFO("Exported {}", path); ++written; } else DONUT_ERROR("Export failed: {}", path); + }; + do_channel(cfg.color, 0, "color"); + do_channel(cfg.redshift, 1, "redshift"); + do_channel(cfg.temperature, 2, "temperature"); + do_channel(cfg.impact, 3, "impact"); + return written; + } } |
