Skip to content

Commit

Permalink
EXRExporter using tinyexr
Browse files Browse the repository at this point in the history
  • Loading branch information
jan-van-bergen committed Jan 17, 2022
1 parent fc1c157 commit 54b6612
Show file tree
Hide file tree
Showing 16 changed files with 8,752 additions and 37 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -351,8 +351,9 @@ healthchecksdb
# Dear ImGui configuration
imgui.ini

# PPM is used for debug output of images
# Image Output
*.ppm
*.exr

# CUDA PTX assembly
*.ptx
Expand Down
2 changes: 2 additions & 0 deletions Pathtracer.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@
<ClCompile Include="Src\Device\CUDAContext.cpp" />
<ClCompile Include="Src\Device\CUDAMemory.cpp" />
<ClCompile Include="Src\Device\CUDAModule.cpp" />
<ClCompile Include="Src\Exporters\EXRExporter.cpp" />
<ClCompile Include="Src\Exporters\PPMExporter.cpp" />
<ClCompile Include="Src\Input.cpp" />
<ClCompile Include="Src\Main.cpp" />
Expand Down Expand Up @@ -292,6 +293,7 @@
<ClInclude Include="Src\Device\CUDAKernel.h" />
<ClInclude Include="Src\Device\CUDAMemory.h" />
<ClInclude Include="Src\Device\CUDAModule.h" />
<ClInclude Include="Src\Exporters\EXRExporter.h" />
<ClInclude Include="Src\Exporters\PPMExporter.h" />
<ClInclude Include="Src\Input.h" />
<ClInclude Include="Src\Math\AABB.h" />
Expand Down
6 changes: 6 additions & 0 deletions Pathtracer.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@
<ClCompile Include="Src\Exporters\PPMExporter.cpp">
<Filter>Exporters</Filter>
</ClCompile>
<ClCompile Include="Src\Exporters\EXRExporter.cpp">
<Filter>Exporters</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="Math">
Expand Down Expand Up @@ -391,5 +394,8 @@
<ClInclude Include="Src\Exporters\PPMExporter.h">
<Filter>Exporters</Filter>
</ClInclude>
<ClInclude Include="Src\Exporters\EXRExporter.h">
<Filter>Exporters</Filter>
</ClInclude>
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion Src/Args.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ static void parse_args(const Array<StringView> & args) {
Option { "H"_sv, "height"_sv, "Sets the height of the window"_sv, 1, [](const Array<StringView> & args, size_t i) { cpu_config.initial_height = parse_arg_int(args[i + 1]); } },
Option { "b"_sv, "bounce"_sv, "Sets the number of pathtracing bounces"_sv, 1, [](const Array<StringView> & args, size_t i) { gpu_config.num_bounces = Math::clamp(parse_arg_int(args[i + 1]), 0, MAX_BOUNCES - 1); } },
Option { "N"_sv, "samples"_sv, "Sets a target number of samples to use"_sv, 1, [](const Array<StringView> & args, size_t i) { cpu_config.output_sample_index = parse_arg_int(args[i + 1]); } },
Option { "o"_sv, "output"_sv, "Sets path to output file. Supported formats: ppm"_sv, 1, [](const Array<StringView> & args, size_t i) { cpu_config.output_name = args[i + 1].start; } },
Option { "o"_sv, "output"_sv, "Sets path to output file. Supported formats: ppm"_sv, 1, [](const Array<StringView> & args, size_t i) { cpu_config.output_filename = args[i + 1].start; } },

Option { "s"_sv, "scene"_sv, "Sets path to scene file. Supported formats: Mitsuba XML, OBJ, and PLY"_sv, 1, [](const Array<StringView> & args, size_t i) { cpu_config.scene_filenames.push_back(args[i + 1]); } },
Option { "S"_sv, "sky"_sv, "Sets path to sky file. Supported formats: HDR"_sv, 1, [](const Array<StringView> & args, size_t i) { cpu_config.sky_filename = args[i + 1]; } },
Expand Down
9 changes: 8 additions & 1 deletion Src/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@

inline GPUConfig gpu_config = { };

enum struct OutputFormat {
EXR,
PPM
};

enum struct MipmapFilterType {
BOX,
LANCZOS,
Expand All @@ -26,8 +31,10 @@ struct CPUConfig {
Array<String> scene_filenames;
String sky_filename;

OutputFormat screenshot_format = OutputFormat::PPM;

int output_sample_index = INVALID;
String output_name = "render.ppm"_sv;
String output_filename = "render.ppm"_sv;

bool bvh_force_rebuild = false;
bool enable_bvh_optimization = false;
Expand Down
57 changes: 57 additions & 0 deletions Src/Exporters/EXRExporter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include "EXRExporter.h"

#define TINYEXR_IMPLEMENTATION
#include <tinyexr.h>

#include "Core/IO.h"

#include "Util/Util.h"

void EXRExporter::save(const String & filename, int pitch, int width, int height, const Array<Vector3> & data) {
Array<float> data_rgb[3] = {
Array<float>(width * height),
Array<float>(width * height),
Array<float>(width * height)
};

for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
data_rgb[0][x + (height - 1 - y) * width] = data[x + y * pitch].x;
data_rgb[1][x + (height - 1 - y) * width] = data[x + y * pitch].y;
data_rgb[2][x + (height - 1 - y) * width] = data[x + y * pitch].z;
}
}

const float * data_bgr[3] = {
data_rgb[2].data(), // B
data_rgb[1].data(), // G
data_rgb[0].data() // R
};

EXRImage image = { };
image.num_channels = 3;
image.images = Util::bit_cast<unsigned char **>(&data_bgr[0]);
image.width = width;
image.height = height;

EXRHeader header = { };
header.num_channels = 3;

EXRChannelInfo channels[3] = { };
header.channels = channels;
memcpy(header.channels[0].name, "B", 2);
memcpy(header.channels[1].name, "G", 2);
memcpy(header.channels[2].name, "R", 2);

int pixel_types[3] = { TINYEXR_PIXELTYPE_FLOAT, TINYEXR_PIXELTYPE_FLOAT, TINYEXR_PIXELTYPE_FLOAT };
int requested_pixel_types[3] = { TINYEXR_PIXELTYPE_HALF, TINYEXR_PIXELTYPE_HALF, TINYEXR_PIXELTYPE_HALF};
header.pixel_types = pixel_types;
header.requested_pixel_types = requested_pixel_types;

const char * error_string = nullptr;
int error_code = SaveEXRImageToFile(&image, &header, filename.data(), &error_string);

if (error_code != TINYEXR_SUCCESS) {
IO::print("EXRExporter: Failed to export {}\n{}\n"_sv, filename, error_string);
}
}
9 changes: 9 additions & 0 deletions Src/Exporters/EXRExporter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma once
#include "Core/Array.h"
#include "Core/String.h"

#include "Math/Vector3.h"

namespace EXRExporter {
void save(const String & filename, int pitch, int width, int height, const Array<Vector3> & data);
}
2 changes: 1 addition & 1 deletion Src/Exporters/PPMExporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ void PPMExporter::save(const String & filename, int pitch, int width, int height
fopen_s(&file, filename.data(), "wb");

if (!file) {
IO::print("Failed to export '{}'!\n"_sv, filename);
IO::print("PPMExporter: Failed to export '{}'!\n"_sv, filename);
return;
}

Expand Down
55 changes: 39 additions & 16 deletions Src/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "Input.h"
#include "Window.h"

#include "Exporters/EXRExporter.h"
#include "Exporters/PPMExporter.h"

#include "Util/Util.h"
Expand Down Expand Up @@ -49,7 +50,7 @@ struct Timing {
static int last_pixel_query_x;
static int last_pixel_query_y;

static void capture_screen(const Window & window, const String & filename);
static void capture_screen(const Window & window, const Pathtracer & pathtracer, const String & filename);
static void calc_timing();
static void draw_gui(Window & window, Pathtracer & pathtracer);

Expand Down Expand Up @@ -97,12 +98,19 @@ int main(int num_args, char ** args) {
window.render_framebuffer();

if (pathtracer.sample_index == cpu_config.output_sample_index) {
capture_screen(window, cpu_config.output_name);
capture_screen(window, pathtracer, cpu_config.output_filename);
break; // Exit game loop and terimate
}
if (Input::is_key_pressed(SDL_SCANCODE_P)) {
String screenshot_name = Format().format("screenshot_{}.ppm"_sv, pathtracer.sample_index);
capture_screen(window, screenshot_name);
StringView ext = { };
switch (cpu_config.screenshot_format) {
case OutputFormat::EXR: ext = "exr"_sv; break;
case OutputFormat::PPM: ext = "ppm"_sv; break;
default: ASSERT(false);
}

String screenshot_name = Format().format("screenshot_{}.{}"_sv, pathtracer.sample_index, ext);
capture_screen(window, pathtracer, screenshot_name);

timing.time_of_last_screenshot = timing.now;
}
Expand Down Expand Up @@ -138,22 +146,35 @@ int main(int num_args, char ** args) {
window.swap();
}

if (pathtracer.sample_index < cpu_config.output_sample_index) {
capture_screen(window, cpu_config.output_name);
}

CUDAContext::free();

return EXIT_SUCCESS;
}

static void capture_screen(const Window & window, const String & filename) {
static void capture_screen(const Window & window, const Pathtracer & pathtracer, const String & filename) {
ScopeTimer timer("Screenshot"_sv);

int window_pitch = 0;
Array<Vector3> data = window.read_frame_buffer(window_pitch);
using Exporter = void (*)(const String & filename, int pitch, int width, int height, const Array<Vector3> & data);
Exporter exporter = nullptr;

bool hdr = false;

PPMExporter::save(filename, window_pitch, window.width, window.height, data);
StringView file_extension = Util::get_file_extension(filename.view());
if (file_extension == "ppm"_sv) {
exporter = PPMExporter::save;
hdr = false;
} else if (file_extension == "exr"_sv) {
exporter = EXRExporter::save;
hdr = true;
} else {
IO::print("WARNING: Unsupported output file extension: {}!\n"_sv, file_extension);
return;
}

int pitch = 0;
Array<Vector3> data = window.read_frame_buffer(hdr, pitch);

exporter(filename, pitch, window.width, window.height, data);
}

static void calc_timing() {
Expand Down Expand Up @@ -313,6 +334,8 @@ static void draw_gui(Window & window, Pathtracer & pathtracer) {
case BVHType::BVH8: ImGui::TextUnformatted("BVH: BVH8"); break;
}

ImGui::Combo("Output Format", reinterpret_cast<int *>(&cpu_config.screenshot_format), "EXR\0PPM\0");

bool invalidated_config = ImGui::SliderInt("Num Bounces", &gpu_config.num_bounces, 0, MAX_BOUNCES);

float fov = Math::rad_to_deg(pathtracer.scene.camera.fov);
Expand Down Expand Up @@ -539,12 +562,12 @@ static void draw_gui(Window & window, Pathtracer & pathtracer) {
aabb_corners[i] = Matrix4::transform(pathtracer.scene.camera.view_projection, aabb_corners[i]);
}

auto draw_line_clipped = [draw_list, &window, near = pathtracer.scene.camera.near](Vector4 a, Vector4 b, ImColor colour, float thickness = 1.0f) {
if (a.z < near && b.z < near) return;
auto draw_line_clipped = [draw_list, &window, near_plane = pathtracer.scene.camera.near_plane](Vector4 a, Vector4 b, ImColor colour, float thickness = 1.0f) {
if (a.z < near_plane && b.z < near_plane) return;

// Clip against near plane only
if (a.z < near) a = Math::lerp(a, b, Math::inv_lerp(near, a.z, b.z));
if (b.z < near) b = Math::lerp(a, b, Math::inv_lerp(near, a.z, b.z));
if (a.z < near_plane) a = Math::lerp(a, b, Math::inv_lerp(near_plane, a.z, b.z));
if (b.z < near_plane) b = Math::lerp(a, b, Math::inv_lerp(near_plane, a.z, b.z));

// Clip space to NDC to Window coordinates
ImVec2 a_window = { (0.5f + 0.5f * a.x / a.w) * window.width, (0.5f - 0.5f * a.y / a.w) * window.height };
Expand Down
10 changes: 5 additions & 5 deletions Src/Math/Matrix4.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,29 +75,29 @@ struct alignas(16) Matrix4 {
return result;
}

inline static Matrix4 perspective(float fov, float aspect, float near, float far) {
inline static Matrix4 perspective(float fov, float aspect, float near_plane, float far_plane) {
float tan_half_fov = tanf(0.5f * fov);

Matrix4 result;
result(0, 0) = 1.0f / tan_half_fov;
result(1, 1) = 1.0f / (aspect * tan_half_fov);
result(2, 2) = -(far + near) / (far - near);
result(2, 2) = -(far_plane + near_plane) / (far_plane - near_plane);
result(3, 2) = -1.0f;
result(2, 3) = -2.0f * (far * near) / (far - near);
result(2, 3) = -2.0f * (far_plane * near_plane) / (far_plane - near_plane);
result(3, 3) = 0.0f;

return result;
}

inline static Matrix4 perspective_infinite(float fov, float aspect, float near) {
inline static Matrix4 perspective_infinite(float fov, float aspect, float near_plane) {
float tan_half_fov = tanf(0.5f * fov);

Matrix4 result;
result(0, 0) = 1.0f / (aspect * tan_half_fov);
result(1, 1) = 1.0f / tan_half_fov;
result(2, 2) = -1.0f;
result(3, 2) = -1.0f;
result(2, 3) = -2.0f * near;
result(2, 3) = -2.0f * near_plane;
result(3, 3) = 0.0f;

return result;
Expand Down
2 changes: 1 addition & 1 deletion Src/Pathtracer/Camera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ void Camera::recalibrate() {
y_axis = Vector3(0.0f, 1.0f, 0.0f);

// Projection matrix (for rasterization)
projection = Matrix4::perspective(fov, half_height / half_width, near, far);
projection = Matrix4::perspective(fov, half_height / half_width, near_plane, far_plane);

// See equation 30 of "Texture Level of Detail Strategies for Real-Time Ray Tracing"
pixel_spread_angle = atanf(2.0f * tan_half_fov * inv_width);
Expand Down
10 changes: 5 additions & 5 deletions Src/Pathtracer/Camera.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ struct Camera {
float aperture_radius = 0.1f;
float focal_distance = 10.0f;

float near;
float far;
float near_plane;
float far_plane;

float screen_width;
float screen_height;
Expand All @@ -30,10 +30,10 @@ struct Camera {

bool moved;

Camera(float fov, float near = 0.1f, float far = 300.0f) {
Camera(float fov, float near_plane = 0.1f, float far_plane = 300.0f) {
set_fov(fov);
this->near = near;
this->far = far;
this->near_plane = near_plane;
this->far_plane = far_plane;
}

void resize(int width, int height);
Expand Down
2 changes: 1 addition & 1 deletion Src/Pathtracer/Pathtracer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ void Pathtracer::resize_init(unsigned frame_buffer_handle, int width, int height

// Create Frame Buffers
ptr_frame_buffer_albedo = CUDAMemory::malloc<float4>(screen_pitch * height);
cuda_module.get_global("frame_buffer_albedo") .set_value(ptr_frame_buffer_albedo);
cuda_module.get_global("frame_buffer_albedo").set_value(ptr_frame_buffer_albedo);

ptr_frame_buffer_direct = CUDAMemory::malloc<float4>(screen_pitch * height);
ptr_frame_buffer_indirect = CUDAMemory::malloc<float4>(screen_pitch * height);
Expand Down
17 changes: 13 additions & 4 deletions Src/Window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,15 +207,24 @@ void Window::swap() {
}
}

Array<Vector3> Window::read_frame_buffer(int & window_pitch) const {
Array<Vector3> Window::read_frame_buffer(bool hdr, int & pitch) const {
int pack_alignment = 0;
glGetIntegerv(GL_PACK_ALIGNMENT, &pack_alignment);

window_pitch = int(Math::round_up(width * sizeof(Vector3), size_t(pack_alignment)) / sizeof(Vector3));
Array<Vector3> data(window_pitch * height);
pitch = int(Math::round_up(width * sizeof(Vector3), size_t(pack_alignment)) / sizeof(Vector3));
Array<Vector3> data(pitch * height);

glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT);
glReadPixels(0, 0, width, height, GL_RGB, GL_FLOAT, data.data());

if (hdr) {
// For HDR output we use the frame_buffer Texture,
// since this is the raw output of the Pathtracer
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_FLOAT, data.data());
} else {
// For LDR output we use the Window's actual frame buffer,
// since this has been tonemapped and gamma corrected
glReadPixels(0, 0, width, height, GL_RGB, GL_FLOAT, data.data());
}

return data;
}
2 changes: 1 addition & 1 deletion Src/Window.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ struct Window {

void swap();

Array<Vector3> read_frame_buffer(int & window_pitch) const;
Array<Vector3> read_frame_buffer(bool hdr, int & pitch) const;

std::function<void(unsigned frame_buffer_handle, int width, int height)> resize_handler;
};
Loading

0 comments on commit 54b6612

Please sign in to comment.