├── .gitignore ├── APP_PRIVACY.txt ├── CMakeLists.txt ├── LICENSE.txt ├── README.rst ├── assets ├── debug.png ├── earthmap.jpg └── earthmap.jpg.h ├── cli_runner ├── CMakeLists.txt ├── Misc.cpp ├── Misc.hpp ├── ProgressBar.cpp ├── ProgressBar.hpp └── main.cpp ├── dev_tools.sh ├── experiments ├── final_keyword │ ├── final_keyword_analysis.ipynb │ └── final_keyword_test_suite_results.xlsx ├── jupyter_notebook_requirements.txt ├── noexcept_keyword │ ├── noexcept_keyword_amd_gcc_linux_book1_results.xlsx │ ├── noexcept_keyword_analysis.ipynb │ ├── noexcept_keyword_analysis_amd_gcc_linux_book1.ipynb │ ├── noexcept_keyword_test_suite_results-win_amd_1st_run.xlsx │ ├── noexcept_keyword_test_suite_results-win_amd_2nd_run.xlsx │ ├── noexcept_keyword_test_suite_results.xlsx │ └── noexcept_vector_search_test.cpp ├── random_unit_sampling_algorithms │ ├── .gitignore │ ├── 2d_and_3d_sampling_algorithms.ipynb │ ├── Makefile │ ├── assembly_output │ │ ├── analytical_in_unit_disk_gcc_14.2_O0.asm │ │ ├── analytical_in_unit_disk_gcc_14.2_O3.asm │ │ ├── rejection_in_unit_disk_gcc_14.2_O0.asm │ │ └── rejection_in_unit_disk_gcc_14.2_O3.asm │ ├── comparing_greedy_vs_analytical.cpp │ ├── comparing_greedy_vs_analytical.py │ ├── comparing_greedy_vs_analytical_results.xlsx │ ├── psrt_rejection_vs_analytical_test_suite_results.xlsx │ ├── rejection_vs_analytical_analysis.ipynb │ └── rust_impl │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs └── take_best_data.py ├── images ├── README.txt ├── asin_approx_no_ec.png ├── asin_approx_with_ec.png ├── asin_ground_truth.png ├── black_spots_on_render.png ├── book2_final_n10000.png ├── cornell_glass_boxes.png ├── full_size │ ├── asin_approx_no_ec.png │ ├── asin_approx_with_ec.png │ ├── asin_ground_truth.png │ ├── black_spots_on_render.png │ ├── book2_final_n10000.png │ └── cornell_glass_boxes.png └── qt_ui_macos_screenshot.png ├── large_test_cases.csv ├── multi_build.py ├── qt_ui ├── .gitignore ├── CMakeLists.txt ├── PSRayTracingRenderer.cpp ├── PSRayTracingRenderer.hpp ├── README.rst ├── Settings.cpp ├── Settings.hpp ├── UIMathHelper.cpp ├── UIMathHelper.hpp ├── Utils.cpp ├── Utils.hpp ├── android │ ├── .gitignore │ ├── AndroidManifest.xml │ ├── res │ │ ├── drawable-hdpi │ │ │ └── icon.png │ │ ├── drawable-ldpi │ │ │ └── icon.png │ │ ├── drawable-mdpi │ │ │ └── icon.png │ │ ├── drawable-xhdpi │ │ │ └── icon.png │ │ ├── drawable-xxhdpi │ │ │ └── icon.png │ │ ├── drawable-xxxhdpi │ │ │ └── icon.png │ │ └── values │ │ │ └── libs.xml │ └── src │ │ └── net │ │ └── sixteenbpp │ │ └── psraytracing │ │ └── AndroidUtils.java ├── apple │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── 100.png │ │ │ ├── 1024.png │ │ │ ├── 114.png │ │ │ ├── 120.png │ │ │ ├── 128.png │ │ │ ├── 144.png │ │ │ ├── 152.png │ │ │ ├── 16.png │ │ │ ├── 167.png │ │ │ ├── 180.png │ │ │ ├── 20.png │ │ │ ├── 256.png │ │ │ ├── 29.png │ │ │ ├── 32.png │ │ │ ├── 40.png │ │ │ ├── 50.png │ │ │ ├── 512.png │ │ │ ├── 57.png │ │ │ ├── 58.png │ │ │ ├── 60.png │ │ │ ├── 64.png │ │ │ ├── 72.png │ │ │ ├── 76.png │ │ │ ├── 80.png │ │ │ ├── 87.png │ │ │ └── Contents.json │ ├── Info.plist.in │ ├── Launch.storyboard │ ├── iOSUtils.h │ └── iOSUtils.mm ├── i18n │ ├── qt_ui_de_DE.ts │ ├── qt_ui_en_US.ts │ ├── qt_ui_ja_JP.ts │ └── qtquickcontrols2_ja_JP.conf ├── icons │ ├── app_icon_compressed.png │ ├── settings.svg │ ├── start.svg │ └── stop.svg ├── imports │ └── SixteenBPP │ │ ├── ControlsBar.qml │ │ ├── Messages.qml │ │ ├── RenderSettingsForm.qml │ │ ├── UITheme.qml │ │ ├── Views │ │ ├── AboutView.qml │ │ ├── RenderView.qml │ │ └── qmldir │ │ ├── Widgets │ │ ├── IntegerOnlyTextField.qml │ │ ├── PanZoom.qml │ │ ├── RenderDisplay.qml │ │ └── qmldir │ │ └── qmldir ├── main.cpp ├── main.qml ├── qml.qrc └── qtquickcontrols2.conf ├── render_library ├── AABB.cpp ├── AABB.hpp ├── CMakeLists.txt ├── Cameras │ ├── Camera.cpp │ ├── Camera.hpp │ ├── MotionBlurCamera.cpp │ └── MotionBlurCamera.hpp ├── ColorRGBA.cpp ├── ColorRGBA.hpp ├── Common.hpp ├── HitRecord.cpp ├── HitRecord.hpp ├── Interfaces │ ├── ICamera.hpp │ ├── IDeepCopyable.hpp │ ├── IHittable.hpp │ ├── IMaterial.hpp │ ├── IPDF.hpp │ └── ITexture.hpp ├── Materials │ ├── Dielectric.cpp │ ├── Dielectric.hpp │ ├── DiffuseLight.cpp │ ├── DiffuseLight.hpp │ ├── Isotropic.cpp │ ├── Isotropic.hpp │ ├── Lambertian.cpp │ ├── Lambertian.hpp │ ├── Metal.cpp │ ├── Metal.hpp │ ├── SurfaceNormal.cpp │ └── SurfaceNormal.hpp ├── ONB.cpp ├── ONB.hpp ├── Objects │ ├── BVHNode.cpp │ ├── BVHNode.hpp │ ├── BVHNode_MorePerformant.cpp │ ├── BVHNode_MorePerformant.hpp │ ├── Box.cpp │ ├── Box.hpp │ ├── ConstantMedium.cpp │ ├── ConstantMedium.hpp │ ├── HittableList.cpp │ ├── HittableList.hpp │ ├── MovingSphere.cpp │ ├── MovingSphere.hpp │ ├── Quad.cpp │ ├── Quad.hpp │ ├── Rectangles.cpp │ ├── Rectangles.hpp │ ├── Sphere.cpp │ ├── Sphere.hpp │ └── Transform │ │ ├── FlipFace.cpp │ │ ├── FlipFace.hpp │ │ ├── RotateY.cpp │ │ ├── RotateY.hpp │ │ ├── Translate.cpp │ │ └── Translate.hpp ├── PDFVariant.cpp ├── PDFVariant.hpp ├── PDFs │ ├── CosinePDF.cpp │ ├── CosinePDF.hpp │ ├── HittablePDF.cpp │ ├── HittablePDF.hpp │ ├── MixturePDF.cpp │ └── MixturePDF.hpp ├── Perlin.cpp ├── Perlin.hpp ├── PerlinReal.cpp ├── PerlinReal.hpp ├── RandomGenerator.cpp ├── RandomGenerator.hpp ├── Ray.cpp ├── Ray.hpp ├── RenderContext.cpp ├── RenderContext.hpp ├── RenderMethod.cpp ├── RenderMethod.hpp ├── RenderOutput.cpp ├── RenderOutput.hpp ├── RenderThread.cpp ├── RenderThread.hpp ├── RenderThreadPool.cpp ├── RenderThreadPool.hpp ├── ScatterRecord.cpp ├── ScatterRecord.hpp ├── SceneDescriptor.hpp ├── Scenes │ ├── All.hpp │ ├── Book1.cpp │ ├── Book1.hpp │ ├── Book2.cpp │ ├── Book2.hpp │ ├── Book3.cpp │ ├── Book3.hpp │ ├── Fun.cpp │ └── Fun.hpp ├── Textures │ ├── CheckerTexture.cpp │ ├── CheckerTexture.hpp │ ├── ImageTexture.cpp │ ├── ImageTexture.hpp │ ├── NoiseTexture.cpp │ ├── NoiseTexture.hpp │ ├── SolidColor.cpp │ └── SolidColor.hpp ├── Util.cpp ├── Util.hpp ├── Vec3.cpp ├── Vec3.hpp ├── render.cpp └── render.hpp ├── run_verification_tests.py ├── small_test_cases.csv └── third_party ├── cxxopts.hpp ├── pcg_extras.hpp ├── pcg_random.hpp ├── pcg_uint128.hpp ├── stb_image.c ├── stb_image.h ├── stb_image_write.c └── stb_image_write.h /.gitignore: -------------------------------------------------------------------------------- 1 | build* 2 | multi_build 3 | .vscode 4 | CMakeLists.txt.user 5 | .DS_Store 6 | .ipynb_checkpoints 7 | -------------------------------------------------------------------------------- /APP_PRIVACY.txt: -------------------------------------------------------------------------------- 1 | The mobile/tablet app version of PSRayTracing (that is available in the Google Play or Apple App stores) does not 2 | collect any data about the user or their actions in the app. Renders/images may be temporarly saved onto the device. 3 | 4 | Google Play does report what kind of device installs this app. 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | project(PSRayTracing) 4 | 5 | # Check if we're building for iOS (and put it into a more friendly variable) 6 | set(IOS FALSE) 7 | if (${CMAKE_SYSTEM_NAME} STREQUAL iOS) 8 | set(IOS TRUE) 9 | endif() 10 | 11 | # If we're building for Android or iOS, turn on the UI 12 | set(IS_MOBILE FALSE) 13 | if (ANDROID OR IOS) 14 | set(IS_MOBILE TRUE) 15 | endif() 16 | 17 | # Make a flag if we are compiling with Microsoft's compiler 18 | set(COMPILING_WITH_MSVC FALSE) 19 | if (${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC) 20 | set(COMPILING_WITH_MSVC TRUE) 21 | endif() 22 | 23 | 24 | 25 | # Configurable options 26 | option(BUILD_QT_UI "Build the Qt UI as well (requires Qt 6)" ${IS_MOBILE}) 27 | 28 | # Put all runtime binaries in the root build directory 29 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) 30 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}) # For Windows/MSVC 31 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}) # For Windows/MVSC 32 | 33 | # Always build the render library 34 | add_subdirectory(render_library) 35 | 36 | # If weren't not building for a mobile device, then build the CLI 37 | if (NOT IS_MOBILE) 38 | add_subdirectory(cli_runner) 39 | endif() 40 | 41 | # Also build the Qt UI? 42 | if (BUILD_QT_UI) 43 | add_subdirectory(qt_ui) 44 | endif() 45 | 46 | # Copy over assets to the build directory 47 | file(COPY "assets" DESTINATION ${CMAKE_BINARY_DIR}) 48 | -------------------------------------------------------------------------------- /assets/debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/assets/debug.png -------------------------------------------------------------------------------- /assets/earthmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/assets/earthmap.jpg -------------------------------------------------------------------------------- /cli_runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(PSRayTracing_CLIRunner) 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | 7 | # Get the source 8 | set(sources 9 | main.cpp 10 | 11 | Misc.hpp 12 | Misc.cpp 13 | ProgressBar.hpp 14 | ProgressBar.cpp 15 | ) 16 | set(third_party_sources 17 | ../third_party/cxxopts.hpp 18 | ../third_party/stb_image_write.h 19 | ../third_party/stb_image_write.c 20 | ) 21 | 22 | # Create the executable 23 | add_executable(PSRayTracing_CLIRunner ${sources} ${third_party_sources}) 24 | target_include_directories(PSRayTracing_CLIRunner PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) # Enable includes relative to the root 25 | set_target_properties(PSRayTracing_CLIRunner PROPERTIES 26 | OUTPUT_NAME "PSRayTracing" 27 | ) 28 | 29 | # Build it! 30 | if (NOT COMPILING_WITH_MSVC) 31 | # Turn on extra warnings (or supress them) for GCC & clang 32 | target_compile_options(PSRayTracing_CLIRunner PRIVATE -Wall -Wextra -Wconversion -Wsign-conversion -pedantic-errors) 33 | set_source_files_properties(${third_party_sources} PROPERTIES COMPILE_FLAGS -w) 34 | endif() 35 | 36 | target_link_libraries(PSRayTracing_CLIRunner 37 | PSRayTracing_StaticLibrary 38 | ) 39 | -------------------------------------------------------------------------------- /cli_runner/Misc.cpp: -------------------------------------------------------------------------------- 1 | #include "Misc.hpp" 2 | #include 3 | using namespace std; 4 | 5 | 6 | bool try_parse_size_string(const string &s, uint16_t &x, uint16_t &y) 7 | { 8 | // First see if we can split it 9 | size_t pivot = s.find('x'); 10 | if (pivot == string::npos) 11 | { 12 | // Check if capital `X` was provided 13 | pivot = s.find('X'); 14 | if (pivot == string::npos) 15 | return false; 16 | } 17 | 18 | // split 19 | const string w = s.substr(0, pivot); 20 | const string h = s.substr(pivot + 1, s.length()); 21 | 22 | // Try to parse 23 | try 24 | { 25 | x = static_cast(stoi(w)); 26 | y = static_cast(stoi(h)); 27 | } 28 | catch (const invalid_argument &err) 29 | { 30 | return false; 31 | } 32 | 33 | // Make sure we have at least 1x1 (and return success) 34 | constexpr uint16_t one = 1; 35 | x = std::max(one, x); 36 | y = std::max(one, y); 37 | return true; 38 | } 39 | -------------------------------------------------------------------------------- /cli_runner/Misc.hpp: -------------------------------------------------------------------------------- 1 | // Misculanous things for the runner 2 | 3 | #include 4 | #include 5 | 6 | /** 7 | * Try to parse a size string like "960x540". Upon success, returns `true` 8 | * `false` if not. parsed size will be put into `x` and `y`. Size is at minimum (if 9 | * sussfull parsing) is guarentteed to be 1x1 10 | */ 11 | bool try_parse_size_string(const std::string &s, uint16_t &x, uint16_t &y); 12 | -------------------------------------------------------------------------------- /cli_runner/ProgressBar.cpp: -------------------------------------------------------------------------------- 1 | #include "ProgressBar.hpp" 2 | #include 3 | 4 | using namespace std; 5 | 6 | 7 | ProgressBar::ProgressBar(const uint64_t total_ticks, const uint16_t bar_width, const string &prefix) : 8 | _total_ticks(total_ticks), 9 | _bar_width(static_cast(bar_width)), 10 | _prefix(prefix) 11 | { } 12 | 13 | 14 | // NOTE: will not always print, only if there is something to update 15 | // e.g. results are stored in a cache before printing out. If 16 | // nothing is different, then it doesn't do an output 17 | void ProgressBar::display(ostream &out) 18 | { 19 | const auto time_now = chrono::steady_clock::now(); 20 | const float progress = static_cast(_ticks) / static_cast(_total_ticks); 21 | const uint16_t pos = static_cast(progress * static_cast(_bar_width)); 22 | 23 | const auto time_elapsed = chrono::duration_cast(time_now - _start_time).count(); 24 | const auto seconds_elapsed = static_cast(time_elapsed); 25 | const string time_str = to_string(seconds_elapsed) + "s"; 26 | 27 | string buffer(_get_line_width(time_str), _empty_char); 28 | 29 | // Prefix 30 | size_t cursor = 0; 31 | if (!_prefix.empty()) 32 | { 33 | buffer.replace(cursor, _prefix.length(), _prefix); 34 | cursor += _prefix.length() + 1; 35 | } 36 | 37 | // Left bar cap 38 | buffer[cursor] = _left_bracket_char; 39 | cursor += 1; 40 | 41 | // Fill with the progress 42 | buffer.replace(cursor, pos, pos, _fill_char); 43 | 44 | // Show `>` for current location 45 | buffer.replace(cursor + pos, 1, 1, '>'); 46 | cursor += _bar_width; 47 | 48 | // Right bar cap 49 | buffer[cursor] = _right_bracket_char; 50 | cursor += 2; 51 | 52 | // Make the percentage 53 | const string percentage = to_string(static_cast(progress * 100)) + "%"; 54 | const string left_pad = string(4 - percentage.length(), ' '); 55 | 56 | // (Left pad first) 57 | buffer.replace(cursor, left_pad.length(), left_pad); 58 | cursor += left_pad.length(); 59 | 60 | // Add in the percentage 61 | buffer.replace(cursor, percentage.length(), percentage); 62 | cursor += percentage.length(); 63 | cursor += 1; 64 | 65 | // Put in the time 66 | buffer.replace(cursor, time_str.length(), time_str); 67 | 68 | // First test that the output is different from the cache 69 | if (_cache.compare(buffer) == 0) 70 | return; // It's the same, nothing new to print 71 | 72 | // Different! cache it and write out 73 | _cache = buffer; 74 | 75 | // write it to the ostream 76 | out << "\r" << buffer; // Carriage return sends the cursor back at the end 77 | out.flush(); 78 | } 79 | 80 | 81 | void ProgressBar::done(ostream &out) 82 | { 83 | display(out); 84 | out << endl; 85 | } 86 | 87 | 88 | size_t ProgressBar::_get_line_width(const string &time_str) const 89 | { 90 | size_t prefix_width = 0; 91 | if (!_prefix.empty()) 92 | prefix_width = _prefix.length() + 1; // +1 for padding 93 | 94 | const size_t middle = static_cast( 95 | 1 + // left bracket 96 | _bar_width + // bar 97 | 1 + // Right bracket 98 | 1 + // Padding 99 | 4 + // Percentage (left padding with spaces) (eg.g 100%) 100 | 1 // padding 101 | ); 102 | const size_t time_width = time_str.length(); 103 | 104 | return prefix_width + middle + time_width; 105 | } 106 | -------------------------------------------------------------------------------- /cli_runner/ProgressBar.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | // A Simple progress bar for command line interfaces 8 | // Helpful to track render progress and give an estimate to the user 9 | // 10 | // Inspired/based off of // Source: https://github.com/prakhar1989/progress-cpp/tree/7bfba0d22d19c41323aa35541618b6ebec9d737c 11 | // 12 | // TODO [features]: 13 | // - allow users to turn off time keeping and configure 14 | // - percent resolution (right now only shows a hard integer) 15 | // - maybe postfixing as well 16 | 17 | 18 | class ProgressBar { 19 | private: 20 | // Data 21 | uint64_t _ticks = 0; 22 | std::chrono::steady_clock::time_point _start_time{}; 23 | 24 | const uint64_t _total_ticks; 25 | const size_t _bar_width = 0; 26 | const std::string _prefix = ""; 27 | const char _fill_char = '='; 28 | const char _empty_char = ' '; 29 | const char _left_bracket_char = '['; 30 | const char _right_bracket_char = ']'; 31 | 32 | std::string _cache = ""; // Used to test if we need to actually update stdout 33 | 34 | public: 35 | 36 | explicit ProgressBar(const uint64_t total_ticks, const uint16_t bar_width, const std::string &prefix=""); 37 | 38 | // Start the process (start time keeping too) 39 | // Will put everything on a new line first 40 | inline void start() { 41 | _start_time = std::chrono::steady_clock::now(); 42 | } 43 | 44 | // Increment by `ticks` ticks 45 | inline void inc(const uint64_t ticks=1) { 46 | _ticks += ticks; 47 | } 48 | 49 | // Set ticks to specific amount 50 | inline void set_to(const uint64_t ticks) { 51 | _ticks = ticks; 52 | } 53 | 54 | // `out` should most likley be standard output 55 | void display(std::ostream &out); 56 | 57 | // Used to signify what we're done with using the progress bar 58 | void done(std::ostream &out); 59 | 60 | private: 61 | // Get the total width (in characters) that the line to show needs to be 62 | // It factors in things such as padding/room for timing & percentage 63 | // seconds -- how many will need to be displayed 64 | size_t _get_line_width(const std::string &time_str) const; 65 | }; 66 | -------------------------------------------------------------------------------- /dev_tools.sh: -------------------------------------------------------------------------------- 1 | # Some bash scripts/aliases that make development a bit easier 2 | 3 | export PSRT_PROJECT_ROOT=`pwd` 4 | 5 | # NOTE: these may be different for different build directories on different sytems... 6 | export ANDROID_BUILD_DIR=${PSRT_PROJECT_ROOT}/build-android-v7 7 | export LINUX_BUILD_DIR=${PSRT_PROJECT_ROOT}/build-linux-qt 8 | 9 | 10 | alias create_build_directores='mkdir ${LINUX_BUILD_DIR} && mkdir ${ANDROID_BUILD_DIR}' 11 | 12 | 13 | # TODO: this isn't propertly rebuilding when the QML files are changed. The deploy works, but not the build part... 14 | # Command to push the .apk to android via CLI 15 | alias push_to_android='adb install ${ANDROID_BUILD_DIR}/qt_ui/android-build/build/outputs/apk/debug/android-build-debug.apk' 16 | alias build_and_push_to_android='cmake --build ${ANDROID_BUILD_DIR} && push_to_android' 17 | 18 | 19 | alias build_and_run_linux_ui='cmake --build ${LINUX_BUILD_DIR} && ${LINUX_BUILD_DIR}/PSRayTracing_QtUI' 20 | 21 | -------------------------------------------------------------------------------- /experiments/final_keyword/final_keyword_test_suite_results.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/experiments/final_keyword/final_keyword_test_suite_results.xlsx -------------------------------------------------------------------------------- /experiments/jupyter_notebook_requirements.txt: -------------------------------------------------------------------------------- 1 | anyio==4.3.0 2 | argon2-cffi==23.1.0 3 | argon2-cffi-bindings==21.2.0 4 | arrow==1.3.0 5 | asttokens==2.4.1 6 | async-lru==2.0.4 7 | attrs==23.2.0 8 | Babel==2.14.0 9 | beautifulsoup4==4.12.3 10 | bleach==6.1.0 11 | certifi==2024.2.2 12 | cffi==1.16.0 13 | charset-normalizer==3.3.2 14 | comm==0.2.1 15 | contourpy==1.2.0 16 | cycler==0.12.1 17 | debugpy==1.8.1 18 | decorator==5.1.1 19 | defusedxml==0.7.1 20 | et-xmlfile==1.1.0 21 | executing==2.0.1 22 | fastjsonschema==2.19.1 23 | fonttools==4.49.0 24 | fqdn==1.5.1 25 | h11==0.14.0 26 | httpcore==1.0.4 27 | httpx==0.27.0 28 | idna==3.6 29 | ipykernel==6.29.3 30 | ipython==8.22.2 31 | isoduration==20.11.0 32 | jedi==0.19.1 33 | Jinja2==3.1.3 34 | json5==0.9.22 35 | jsonpointer==2.4 36 | jsonschema==4.21.1 37 | jsonschema-specifications==2023.12.1 38 | jupyter-events==0.9.0 39 | jupyter-lsp==2.2.4 40 | jupyter_client==8.6.0 41 | jupyter_core==5.7.1 42 | jupyter_server==2.13.0 43 | jupyter_server_terminals==0.5.2 44 | jupyterlab==4.1.4 45 | jupyterlab-vim==4.1.3 46 | jupyterlab_pygments==0.3.0 47 | jupyterlab_server==2.25.3 48 | kiwisolver==1.4.5 49 | MarkupSafe==2.1.5 50 | matplotlib==3.8.3 51 | matplotlib-inline==0.1.6 52 | mistune==3.0.2 53 | nbclient==0.9.0 54 | nbconvert==7.16.2 55 | nbformat==5.9.2 56 | nest-asyncio==1.6.0 57 | notebook==7.1.1 58 | notebook_shim==0.2.4 59 | numpy==1.26.4 60 | openpyxl==3.1.2 61 | overrides==7.7.0 62 | packaging==23.2 63 | pandas==2.2.1 64 | pandocfilters==1.5.1 65 | parso==0.8.3 66 | pexpect==4.9.0 67 | pillow==10.2.0 68 | platformdirs==4.2.0 69 | plotly==5.24.1 70 | prometheus_client==0.20.0 71 | prompt-toolkit==3.0.43 72 | psutil==5.9.8 73 | ptyprocess==0.7.0 74 | pure-eval==0.2.2 75 | pycparser==2.21 76 | Pygments==2.17.2 77 | pyparsing==3.1.2 78 | python-dateutil==2.9.0.post0 79 | python-json-logger==2.0.7 80 | pytz==2024.1 81 | PyYAML==6.0.1 82 | pyzmq==25.1.2 83 | referencing==0.33.0 84 | requests==2.31.0 85 | rfc3339-validator==0.1.4 86 | rfc3986-validator==0.1.1 87 | rpds-py==0.18.0 88 | Send2Trash==1.8.2 89 | six==1.16.0 90 | sniffio==1.3.1 91 | soupsieve==2.5 92 | stack-data==0.6.3 93 | tenacity==9.0.0 94 | terminado==0.18.0 95 | tinycss2==1.2.1 96 | tornado==6.4 97 | traitlets==5.14.1 98 | types-python-dateutil==2.8.19.20240106 99 | tzdata==2024.1 100 | uri-template==1.3.0 101 | urllib3==2.2.1 102 | wcwidth==0.2.13 103 | webcolors==1.13 104 | webencodings==0.5.1 105 | websocket-client==1.7.0 106 | -------------------------------------------------------------------------------- /experiments/noexcept_keyword/noexcept_keyword_amd_gcc_linux_book1_results.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/experiments/noexcept_keyword/noexcept_keyword_amd_gcc_linux_book1_results.xlsx -------------------------------------------------------------------------------- /experiments/noexcept_keyword/noexcept_keyword_test_suite_results-win_amd_1st_run.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/experiments/noexcept_keyword/noexcept_keyword_test_suite_results-win_amd_1st_run.xlsx -------------------------------------------------------------------------------- /experiments/noexcept_keyword/noexcept_keyword_test_suite_results-win_amd_2nd_run.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/experiments/noexcept_keyword/noexcept_keyword_test_suite_results-win_amd_2nd_run.xlsx -------------------------------------------------------------------------------- /experiments/noexcept_keyword/noexcept_keyword_test_suite_results.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/experiments/noexcept_keyword/noexcept_keyword_test_suite_results.xlsx -------------------------------------------------------------------------------- /experiments/random_unit_sampling_algorithms/.gitignore: -------------------------------------------------------------------------------- 1 | test* 2 | -------------------------------------------------------------------------------- /experiments/random_unit_sampling_algorithms/Makefile: -------------------------------------------------------------------------------- 1 | # Note that this isn't really a "good" example of a Makefile; it's meant to be 2 | # something that's quick and dirty to get the job done. It works for GCC and 3 | # clang. You may need to modify it for a different compiler version or 4 | # location. 5 | # 6 | # MSVC is a bit wonky, but kinda works. You need to use some different 7 | # (specific) commands/tagets and have the developer tools command line running. 8 | 9 | GCC_CXX=g++-14 10 | CLANG_CXX=clang++-18 11 | MSVC_CXX=cl 12 | 13 | SRC = comparing_greedy_vs_analytical.cpp 14 | 15 | # Building the tests 16 | all: build_gcc_14 build_clang_18 17 | 18 | clean: 19 | rm test_* 20 | 21 | build_gcc_14: $(SRC) 22 | $(GCC_CXX) $(SRC) -O0 -o test_gcc_14_O0 23 | $(GCC_CXX) $(SRC) -O1 -o test_gcc_14_O1 24 | $(GCC_CXX) $(SRC) -O2 -o test_gcc_14_O2 25 | $(GCC_CXX) $(SRC) -O3 -o test_gcc_14_O3 26 | $(GCC_CXX) $(SRC) -Ofast -o test_gcc_14_Ofast 27 | 28 | build_clang_18: $(SRC) 29 | $(CLANG_CXX) $(SRC) -O0 -o test_clang_18_O0 30 | $(CLANG_CXX) $(SRC) -O1 -o test_clang_18_O1 31 | $(CLANG_CXX) $(SRC) -O2 -o test_clang_18_O2 32 | $(CLANG_CXX) $(SRC) -O3 -o test_clang_18_O3 33 | $(CLANG_CXX) $(SRC) -Ofast -o test_clang_18_Ofast 34 | 35 | build_msvc: $(SRC) 36 | $(MSVC_CXX) $(SRC) /Od /Fetest_msvc_Od.exe 37 | $(MSVC_CXX) $(SRC) /Ot /Fetest_msvc_Ot.exe 38 | $(MSVC_CXX) $(SRC) /O2 /Fetest_msvc_O2.exe 39 | $(MSVC_CXX) $(SRC) /Ox /Fetest_msvc_Ox.exe 40 | 41 | # Running the tests 42 | RNG_SEED=1337 43 | NUM_ITERATIONS=500 44 | NUM_POINTS=1000000 45 | 46 | run_all_tests: 47 | ./test_gcc_14_O0 $(RNG_SEED) $(NUM_ITERATIONS) $(NUM_POINTS) 48 | ./test_gcc_14_O1 $(RNG_SEED) $(NUM_ITERATIONS) $(NUM_POINTS) 49 | ./test_gcc_14_O2 $(RNG_SEED) $(NUM_ITERATIONS) $(NUM_POINTS) 50 | ./test_gcc_14_O3 $(RNG_SEED) $(NUM_ITERATIONS) $(NUM_POINTS) 51 | ./test_gcc_14_Ofast $(RNG_SEED) $(NUM_ITERATIONS) $(NUM_POINTS) 52 | ./test_clang_18_O0 $(RNG_SEED) $(NUM_ITERATIONS) $(NUM_POINTS) 53 | ./test_clang_18_O1 $(RNG_SEED) $(NUM_ITERATIONS) $(NUM_POINTS) 54 | ./test_clang_18_O2 $(RNG_SEED) $(NUM_ITERATIONS) $(NUM_POINTS) 55 | ./test_clang_18_O3 $(RNG_SEED) $(NUM_ITERATIONS) $(NUM_POINTS) 56 | ./test_clang_18_Ofast $(RNG_SEED) $(NUM_ITERATIONS) $(NUM_POINTS) 57 | # ./test_msvc_Od $(RNG_SEED) $(NUM_ITERATIONS) $(NUM_POINTS) 58 | # ./test_msvc_Ot $(RNG_SEED) $(NUM_ITERATIONS) $(NUM_POINTS) 59 | # ./test_msvc_O2 $(RNG_SEED) $(NUM_ITERATIONS) $(NUM_POINTS) 60 | # ./test_msvc_Ox $(RNG_SEED) $(NUM_ITERATIONS) $(NUM_POINTS) 61 | -------------------------------------------------------------------------------- /experiments/random_unit_sampling_algorithms/assembly_output/analytical_in_unit_disk_gcc_14.2_O0.asm: -------------------------------------------------------------------------------- 1 | Vec2::Vec2(double, double) [base object constructor]: ; 14 instructions 2 | push rbp 3 | mov rbp, rsp 4 | mov QWORD PTR [rbp-8], rdi 5 | movsd QWORD PTR [rbp-16], xmm0 6 | movsd QWORD PTR [rbp-24], xmm1 7 | mov rax, QWORD PTR [rbp-8] 8 | movsd xmm0, QWORD PTR [rbp-16] 9 | movsd QWORD PTR [rax], xmm0 10 | mov rax, QWORD PTR [rbp-8] 11 | movsd xmm0, QWORD PTR [rbp-24] 12 | movsd QWORD PTR [rax+8], xmm0 13 | nop 14 | pop rbp 15 | ret 16 | RNG::num(double, double): ; 19 instructions 17 | push rbp 18 | mov rbp, rsp 19 | sub rsp, 48 20 | mov QWORD PTR [rbp-24], rdi 21 | movsd QWORD PTR [rbp-32], xmm0 22 | movsd QWORD PTR [rbp-40], xmm1 23 | mov rax, QWORD PTR [rbp-24] 24 | lea rdx, [rax+5000] 25 | mov rax, QWORD PTR [rbp-24] 26 | mov rsi, rax 27 | mov rdi, rdx 28 | call double std::uniform_real_distribution::operator() >(std::mersenne_twister_engine&) 29 | movq rax, xmm0 30 | mov QWORD PTR [rbp-8], rax 31 | movsd xmm0, QWORD PTR [rbp-40] 32 | subsd xmm0, QWORD PTR [rbp-32] 33 | mulsd xmm0, QWORD PTR [rbp-8] 34 | addsd xmm0, QWORD PTR [rbp-32] 35 | leave 36 | ret 37 | RNG::analytical_in_unit_disk(): ; 49 instructions 38 | push rbp 39 | mov rbp, rsp 40 | sub rsp, 64 41 | mov QWORD PTR [rbp-56], rdi 42 | movsd xmm0, QWORD PTR .LC1[rip] 43 | mov rax, QWORD PTR [rbp-56] 44 | movapd xmm1, xmm0 45 | mov rdx, QWORD PTR .LC2[rip] 46 | movq xmm0, rdx 47 | mov rdi, rax 48 | call RNG::num(double, double) ; 19 instructions 49 | movq rax, xmm0 50 | movq xmm0, rax 51 | call sqrt 52 | movq rax, xmm0 53 | mov QWORD PTR [rbp-8], rax 54 | movsd xmm0, QWORD PTR .LC3[rip] 55 | mov rax, QWORD PTR [rbp-56] 56 | movapd xmm1, xmm0 57 | mov rdx, QWORD PTR .LC2[rip] 58 | movq xmm0, rdx 59 | mov rdi, rax 60 | call RNG::num(double, double) ; 19 instructions 61 | movq rax, xmm0 62 | mov QWORD PTR [rbp-16], rax 63 | mov rax, QWORD PTR [rbp-16] 64 | movq xmm0, rax 65 | call cos 66 | movsd xmm1, QWORD PTR [rbp-8] 67 | mulsd xmm0, xmm1 68 | movsd QWORD PTR [rbp-24], xmm0 69 | mov rax, QWORD PTR [rbp-16] 70 | movq xmm0, rax 71 | call sin 72 | movsd xmm1, QWORD PTR [rbp-8] 73 | mulsd xmm0, xmm1 74 | movsd QWORD PTR [rbp-32], xmm0 75 | movsd xmm0, QWORD PTR [rbp-32] 76 | mov rdx, QWORD PTR [rbp-24] 77 | lea rax, [rbp-48] 78 | movapd xmm1, xmm0 79 | movq xmm0, rdx 80 | mov rdi, rax 81 | call Vec2::Vec2(double, double) [complete object constructor] ; 14 instructions 82 | mov rax, QWORD PTR [rbp-48] 83 | mov rdx, QWORD PTR [rbp-40] 84 | movq xmm0, rax 85 | movq xmm1, rdx 86 | leave 87 | ret 88 | 89 | -------- 90 | 91 | The above method: 92 | 93 | (49 - 3) + 19 + 19 + 14: 98 instrunctions (albiet 6 calls) 94 | -------------------------------------------------------------------------------- /experiments/random_unit_sampling_algorithms/comparing_greedy_vs_analytical_results.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/experiments/random_unit_sampling_algorithms/comparing_greedy_vs_analytical_results.xlsx -------------------------------------------------------------------------------- /experiments/random_unit_sampling_algorithms/psrt_rejection_vs_analytical_test_suite_results.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/experiments/random_unit_sampling_algorithms/psrt_rejection_vs_analytical_test_suite_results.xlsx -------------------------------------------------------------------------------- /experiments/random_unit_sampling_algorithms/rust_impl/.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target/ 3 | -------------------------------------------------------------------------------- /experiments/random_unit_sampling_algorithms/rust_impl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust_impl" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | rand = "0.8.5" 8 | rand_mt = "4.2.2" 9 | -------------------------------------------------------------------------------- /experiments/take_best_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Small utlity to read in two of our CSV `results.csv` files, and take the best run from each, 4 | # and then place it into a resulting file 5 | 6 | import sys 7 | 8 | 9 | def get_ms(line: str) -> int: 10 | # Gets the nanoseconds a test case took 11 | return int(line.split(',')[8]) 12 | 13 | 14 | def get_best_row(row_a: str, row_b: str) -> str: 15 | # Compares two (raw string) rows and gets the best runtime 16 | runtime_a = get_ms(row_a) 17 | runtime_b = get_ms(row_b) 18 | 19 | if (runtime_a < runtime_b): 20 | return row_a 21 | else: 22 | return row_b 23 | 24 | 25 | def get_lines(filename: str) -> list[str]: 26 | # Gets all the lines from a file and shoves it into a list (without trailing whitespace) 27 | lines = [] 28 | with open(filename, 'r') as file: 29 | lines = [line.rstrip() for line in file.readlines()] 30 | 31 | return lines 32 | 33 | 34 | def make_file_with_best_runtimes(filename_a: str, filename_b: str, results_filename: str) -> None: 35 | # will open two CSV files, which should be formatted the same, then will use the 36 | # results from `get_best_row()` and write that data to a new file. 37 | 38 | # Get data 39 | lines_a = get_lines(filename_a) 40 | lines_b = get_lines(filename_b) 41 | lines_best = [] 42 | 43 | # Get header 44 | header = lines_a[0] 45 | 46 | # Pair each row of data 47 | paired = zip(lines_a[1:], lines_b[1:]) 48 | 49 | # Get the best 50 | lines_best = [get_best_row(a, b) for (a, b) in paired] 51 | best_runtimes = [get_ms(x) for x in lines_best] 52 | lines_best = [f'{line}\n' for line in lines_best] # Append newline 53 | total_runtime_ms = sum(best_runtimes) 54 | 55 | # write result 56 | with open(results_filename, 'w') as file: 57 | file.write(f'{header}\n') 58 | file.writelines(lines_best) 59 | 60 | print(f'Total Runtime (ms): {total_runtime_ms}') 61 | 62 | 63 | def main(): 64 | if len(sys.argv) < 3: 65 | print('Error, need three arguments: ') 66 | sys.exit(1) 67 | 68 | # Grab paths 69 | results_file_a = sys.argv[1] 70 | results_file_b = sys.argv[2] 71 | results_file_combined = sys.argv[3] 72 | 73 | make_file_with_best_runtimes(results_file_a, results_file_b, results_file_combined) 74 | 75 | 76 | if __name__ == '__main__': 77 | main() 78 | -------------------------------------------------------------------------------- /images/README.txt: -------------------------------------------------------------------------------- 1 | These are just images that I reference in documents and want to hyperlink to. 2 | These are not meant to be used in the rendering program. If you're looking for 3 | those, check the `assets/` folder at the root of this repo. 4 | 5 | So things also fit/load nicely in the README, I've reduced their sizes. You can 6 | find the original sizes in the `full_size/` directory. 7 | -------------------------------------------------------------------------------- /images/asin_approx_no_ec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/images/asin_approx_no_ec.png -------------------------------------------------------------------------------- /images/asin_approx_with_ec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/images/asin_approx_with_ec.png -------------------------------------------------------------------------------- /images/asin_ground_truth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/images/asin_ground_truth.png -------------------------------------------------------------------------------- /images/black_spots_on_render.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/images/black_spots_on_render.png -------------------------------------------------------------------------------- /images/book2_final_n10000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/images/book2_final_n10000.png -------------------------------------------------------------------------------- /images/cornell_glass_boxes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/images/cornell_glass_boxes.png -------------------------------------------------------------------------------- /images/full_size/asin_approx_no_ec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/images/full_size/asin_approx_no_ec.png -------------------------------------------------------------------------------- /images/full_size/asin_approx_with_ec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/images/full_size/asin_approx_with_ec.png -------------------------------------------------------------------------------- /images/full_size/asin_ground_truth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/images/full_size/asin_ground_truth.png -------------------------------------------------------------------------------- /images/full_size/black_spots_on_render.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/images/full_size/black_spots_on_render.png -------------------------------------------------------------------------------- /images/full_size/book2_final_n10000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/images/full_size/book2_final_n10000.png -------------------------------------------------------------------------------- /images/full_size/cornell_glass_boxes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/images/full_size/cornell_glass_boxes.png -------------------------------------------------------------------------------- /images/qt_ui_macos_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/images/qt_ui_macos_screenshot.png -------------------------------------------------------------------------------- /qt_ui/.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | .directory 25 | *.debug 26 | Makefile* 27 | *.prl 28 | *.app 29 | moc_*.cpp 30 | ui_*.h 31 | qrc_*.cpp 32 | Thumbs.db 33 | *.res 34 | *.rc 35 | /.qmake.cache 36 | /.qmake.stash 37 | 38 | # qtcreator generated files 39 | *.pro.user* 40 | 41 | # xemacs temporary files 42 | *.flc 43 | 44 | # Vim temporary files 45 | .*.swp 46 | 47 | # Visual Studio generated files 48 | *.ib_pdb_index 49 | *.idb 50 | *.ilk 51 | *.pdb 52 | *.sln 53 | *.suo 54 | *.vcproj 55 | *vcproj.*.*.user 56 | *.ncb 57 | *.sdf 58 | *.opensdf 59 | *.vcxproj 60 | *vcxproj.* 61 | 62 | # MinGW generated files 63 | *.Debug 64 | *.Release 65 | 66 | # Python byte code 67 | *.pyc 68 | 69 | # Binaries 70 | # -------- 71 | *.dll 72 | *.exe 73 | 74 | CMakeLists.txt.user 75 | -------------------------------------------------------------------------------- /qt_ui/Settings.cpp: -------------------------------------------------------------------------------- 1 | #include "Settings.hpp" 2 | 3 | 4 | // Keys for the general section 5 | constexpr char version_key[] = "settings_version"; 6 | 7 | // Keys for the render settings 8 | constexpr char render_size_key[] = "render_settings/size"; 9 | constexpr char samples_per_pixel_key[] = "render_settings/samples_per_pixel"; 10 | constexpr char max_depth_key[] = "render_settings/max_dpeth"; 11 | constexpr char num_threads_key[] = "render_settings/num_threads"; 12 | constexpr char seed_str_key[] = "render_settings/seed_str"; 13 | constexpr char deep_copy_per_thread_key[] = "render_settings/deep_copy_per_thread"; 14 | 15 | constexpr char scene_id_key[] = "controls_bar/scene_id"; 16 | 17 | // Current version of the settings system 18 | constexpr int current_settings_system_verison = 1; 19 | 20 | 21 | Settings::Settings(const int num_concurrent_threads_supported, QObject *parent) : 22 | QObject(parent), 23 | _num_concurrent_threads_supported(num_concurrent_threads_supported), 24 | _settings_store("16BPP.net", "PSRayTracing") 25 | { 26 | // First make sure some settings file exists (by checking the version), if not, then make them 27 | const bool exists = _settings_store.contains(version_key); 28 | if (!exists) 29 | _set_to_initial_values(); 30 | } 31 | 32 | 33 | void Settings::_set_to_initial_values() 34 | { 35 | // Setup the intial settings values 36 | _settings_store.setValue(scene_id_key, "book2::final_scene"); 37 | reset_render_settings_to_default(); 38 | } 39 | 40 | 41 | Q_INVOKABLE void Settings::reset_render_settings_to_default() 42 | { 43 | const int half_core_count = _num_concurrent_threads_supported / 2; 44 | 45 | _settings_store.setValue(version_key, current_settings_system_verison); 46 | _settings_store.setValue(render_size_key, QSize(960, 540)); 47 | _settings_store.setValue(samples_per_pixel_key, 10); 48 | _settings_store.setValue(max_depth_key, 50); 49 | _settings_store.setValue(num_threads_key, std::max(half_core_count, 1)); 50 | _settings_store.setValue(seed_str_key, "ASDF"); 51 | _settings_store.setValue(deep_copy_per_thread_key, true); 52 | } 53 | 54 | 55 | QSize Settings::render_size() const 56 | { return _settings_store.value(render_size_key).toSize(); } 57 | 58 | void Settings::set_render_size(const QSize &size) 59 | { _settings_store.setValue(render_size_key, size); } 60 | 61 | 62 | int Settings::samples_per_pixel() const 63 | { return _settings_store.value(samples_per_pixel_key).toInt(); } 64 | 65 | void Settings::set_samples_per_pixel(const int num_samples) 66 | { _settings_store.setValue(samples_per_pixel_key, num_samples); } 67 | 68 | 69 | int Settings::max_depth() const 70 | { return _settings_store.value(max_depth_key).toInt(); } 71 | 72 | void Settings::set_max_depth(const int depth) 73 | { _settings_store.setValue(max_depth_key, depth); } 74 | 75 | 76 | int Settings::num_threads() const 77 | { return _settings_store.value(num_threads_key).toInt(); } 78 | 79 | void Settings::set_num_threads(const int thread_count) 80 | { _settings_store.setValue(num_threads_key, thread_count); } 81 | 82 | 83 | QString Settings::seed_str() const 84 | { return _settings_store.value(seed_str_key).toString(); } 85 | 86 | void Settings::set_seed_str(const QString &seed) 87 | { _settings_store.setValue(seed_str_key, seed); } 88 | 89 | 90 | bool Settings::deep_copy_per_thread() const 91 | { return _settings_store.value(deep_copy_per_thread_key).toBool(); } 92 | 93 | void Settings::set_deep_copy_per_thread(const bool do_deep_copy) 94 | { _settings_store.setValue(deep_copy_per_thread_key, do_deep_copy); } 95 | 96 | 97 | QString Settings::scene_id() const 98 | { return _settings_store.value(scene_id_key).toString(); } 99 | 100 | void Settings::set_scene_id(const QString &id) 101 | { _settings_store.setValue(scene_id_key, id); } 102 | -------------------------------------------------------------------------------- /qt_ui/Settings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | class Settings : public QObject 10 | { 11 | Q_OBJECT 12 | 13 | // Setting must be exposed as properties so we can talk with QML 14 | Q_PROPERTY(QSize render_size READ render_size WRITE set_render_size) 15 | Q_PROPERTY(QString scene_id READ scene_id WRITE set_scene_id) 16 | Q_PROPERTY(int samples_per_pixel READ samples_per_pixel WRITE set_samples_per_pixel) 17 | Q_PROPERTY(int max_depth READ max_depth WRITE set_max_depth) 18 | Q_PROPERTY(int num_threads READ num_threads WRITE set_num_threads) 19 | Q_PROPERTY(QString seed_str READ seed_str WRITE set_seed_str) 20 | Q_PROPERTY(bool deep_copy_per_thread READ deep_copy_per_thread WRITE set_deep_copy_per_thread) 21 | 22 | // Data 23 | int _num_concurrent_threads_supported = 1; // Note that this is device specific 24 | QSettings _settings_store; ///< The underlying object that stores all of the settings 25 | 26 | 27 | public: 28 | explicit Settings(const int num_concurrent_threads_supported, QObject *parent = nullptr); 29 | ~Settings() = default; 30 | 31 | private: 32 | void _set_to_initial_values(); ///< Sets the intial values for all settings 33 | 34 | public: 35 | Q_INVOKABLE void reset_render_settings_to_default(); ///< Resets just the render settings 36 | 37 | /*== the render settings ==*/ 38 | QSize render_size() const; 39 | void set_render_size(const QSize &size); 40 | 41 | int samples_per_pixel() const; 42 | void set_samples_per_pixel(const int num_samples); 43 | 44 | int max_depth() const; 45 | void set_max_depth(const int depth); 46 | 47 | int num_threads() const; 48 | void set_num_threads(const int thread_count); 49 | 50 | QString seed_str() const; 51 | void set_seed_str(const QString &seed); 52 | 53 | bool deep_copy_per_thread() const; 54 | void set_deep_copy_per_thread(const bool do_deep_copy); 55 | 56 | // Scene Id is not considered a render setting 57 | QString scene_id() const; 58 | void set_scene_id(const QString &id); 59 | }; 60 | -------------------------------------------------------------------------------- /qt_ui/UIMathHelper.cpp: -------------------------------------------------------------------------------- 1 | #include "UIMathHelper.hpp" 2 | 3 | UIMathHelper::UIMathHelper(QObject *parent) : 4 | QObject(parent) 5 | { } 6 | 7 | UIMathHelper::~UIMathHelper() = default; 8 | 9 | 10 | QRectF UIMathHelper::compute_viewport_for_zoom( 11 | const QRectF &viewport_rect, 12 | const QPointF &zoom_focus, 13 | const qreal zoom_previous, 14 | const qreal zoom_current 15 | ) { 16 | // If we are zoomed out (or at 100%), then we just set the top-left to zero so it centers the image 17 | const bool is_one = qFuzzyCompare(zoom_current, 1.0); 18 | if ((zoom_current < 1.0) || (is_one)) 19 | return QRectF(QPointF(0, 0), viewport_rect.size()); 20 | 21 | // Figure out the viewport, in raw pixel sizes 22 | const QSizeF viewport_pixel_size = viewport_rect.size() / zoom_current; 23 | 24 | // Compute dimensions of previous viewport (in "non-zoomed-in space") 25 | const QPointF top_left_previous = viewport_rect.topLeft() / zoom_previous; 26 | const QSizeF size_previous = viewport_pixel_size / zoom_previous; 27 | const QRectF viewport_previous(top_left_previous, size_previous); 28 | 29 | // Compute dimensions for current viewport (in "non-zoomed-in space") 30 | const QSizeF size_current = viewport_pixel_size / zoom_current; 31 | const QPointF viewport_top_left = viewport_rect.topLeft() / zoom_current; 32 | 33 | // Center the two viewports 34 | QRectF viewport_current(viewport_top_left, size_current); 35 | // viewport_current.moveCenter(viewport_previous.center()); 36 | 37 | // Compute the focal point (in a range of [0.0, 1.0] in the pixel space) 38 | const QPointF zoom_focus_percent( 39 | zoom_focus.x() / viewport_pixel_size.width(), 40 | zoom_focus.y() / viewport_pixel_size.height() 41 | ); 42 | 43 | // The focal points of the two viewport rects need to overlap. Then we compute the top-left point 44 | // we need for the current viewport 45 | auto f = [](const qreal p, const qreal s, const qreal d1, const qreal d2) 46 | { return p + (s * (d1 - d2)); }; 47 | const QPointF tl( 48 | f(viewport_previous.x(), zoom_focus_percent.x(), viewport_previous.width(), viewport_current.width()), 49 | f(viewport_previous.y(), zoom_focus_percent.y(), viewport_previous.height(), viewport_current.height()) 50 | ); 51 | viewport_current.moveTopLeft(tl); 52 | 53 | // Make sure it's still in bounds 54 | const qreal w = viewport_pixel_size.width(); 55 | const qreal h = viewport_pixel_size.height(); 56 | if (viewport_current.left() < 0) 57 | viewport_current.moveLeft(0); 58 | if (viewport_current.top() < 0) 59 | viewport_current.moveTop(0); 60 | if (viewport_current.right() > w) 61 | viewport_current.moveRight(w); 62 | if (viewport_current.bottom() > h) 63 | viewport_current.moveBottom(h); 64 | 65 | // Return a rectangle back, with the zoom applied 66 | return QRectF( 67 | zoom_current * viewport_current.topLeft(), 68 | viewport_rect.size() 69 | ); 70 | } 71 | -------------------------------------------------------------------------------- /qt_ui/UIMathHelper.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | 7 | /** 8 | * UIMathHelper is an object that houses methods for doing math for the UI. 9 | * 10 | * E.g. doing the computations for zoom in/out and positioning. QRect has a 11 | * nice API for math operations, but Qml's `rect` type doesn't. So this 12 | * class exists so we can leverage that more advanced API in Qml. 13 | * 14 | * It would be best to treat this object as a singleton (at least in Qml land). 15 | */ 16 | class UIMathHelper : public QObject 17 | { 18 | Q_OBJECT 19 | 20 | public: 21 | explicit UIMathHelper(QObject *parent = nullptr); 22 | ~UIMathHelper(); 23 | 24 | /// Computes the viewport that should be used when zooming in or out, so things look nice and centered. 25 | Q_INVOKABLE static QRectF compute_viewport_for_zoom( 26 | const QRectF ¤t_viewport, // Value could be zoomed 27 | const QPointF &zoom_focus, // Value is in "widget space" 28 | const qreal zoom_before, // value is a percentage eg. `0.5 = 50%` 29 | const qreal zoom_after // Value is a percentage eg. `0.5 = 50%` 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /qt_ui/Utils.cpp: -------------------------------------------------------------------------------- 1 | #include "Utils.hpp" 2 | #include 3 | #include 4 | 5 | #if defined(Q_OS_ANDROID) 6 | #include 7 | #elif defined(Q_OS_IOS) 8 | #include "apple/iOSUtils.h" 9 | #endif 10 | 11 | 12 | #if defined(Q_OS_ANDROID) 13 | QSize android_get_device_resolution_in_pixels() 14 | { 15 | // Call our Java/Android code 16 | const QJniObject dim = QJniObject::callStaticObjectMethod( 17 | "net/sixteenbpp/psraytracing/AndroidUtils", 18 | "get_device_resolution_in_pixels", 19 | "(Landroid/content/Context;)Landroid/graphics/Point;", 20 | QNativeInterface::QAndroidApplication::context() 21 | ); 22 | 23 | // Get the result 24 | return QSize( 25 | dim.getField("x"), 26 | dim.getField("y") 27 | ); 28 | } 29 | #endif 30 | 31 | 32 | QSize utils::compute_true_screen_resolution() 33 | { 34 | // This is a Qt (6.2.1) quirk (for now...): 35 | // 36 | // Since Qt, Qml's Screen, QScreen don't provide a method to get the actual physical 37 | // amount of pixels that are the true screen resolution. So instead for certain 38 | // platforms we need to do different things. 39 | // 40 | // Part of this issue stems from high-dpi displays and Qt trying to make apps 41 | // scale up well. I have an Android LGQ7+ with a native resolution of `2160x1080`, 42 | // But Qml' Screen (and QScreen) is reporting it to be something like `823x411`. 43 | // 44 | // But also other APIs now have a "safe area" and those APIs that you used to querey 45 | // the screen resolution now returns the "safe area" resolution. 46 | // 47 | // Desktop OSs (Windows, Mac, Linux): 48 | // It seems like we can use the `QScreen` API as is. I haven't tested this on 49 | // macOS Retina displays or the new MacBook pro that has the "notch". I haven't 50 | // seen any issues with non-HighDPI displays. This also worked with a Windows 2-in-1 51 | // that can rotate. 52 | // 53 | // Android: 54 | // The QScreen API is returning a scalled value, as well as one that falls in the 55 | // "safe area" of the screen. It doesn't include the back/home/menu buttons that 56 | // appear at the bottom, or the time/date/battery status bar at the top. For this 57 | // We need to do an Android JNI call. The code that I wrote is unfortunately depcreated 58 | // since Android API 30, but I'm using it as I haven't seen any substition code 59 | // that could be used instead. 60 | // 61 | // iOS: 62 | // The major quirk here is that you need to set a "Launch Screen" for your app or 63 | // it will not be the correct resolution, nor will any of the APIs to query the screen 64 | // resolution work property. This includes both Qt's QScreen and the Apple UIKit 65 | // ones (!!). I have an iPad Pro 2021 (12.9 in). When I didn't have a Launch Screen 66 | // set, both APIs were giving me a value that was 2/3 of the true native resolution; 67 | // Once I had one set, then both using QScreen or UIKit worked as expected. 68 | // 69 | // This also wasn't documented anywhere, I had to go searching through Google and 70 | // forums posts to figure this out. While the Apple Human Interface guidlines do state 71 | // that an app needs to have a launch screen set, it isn't obvious to anyone that 72 | // this also sets what resolution your app will run in. The UIKit documentation for 73 | // polling for the native resolution doesn't say anything, nor does the Qt stuff. 74 | // 75 | // 76 | // While I stated above that QScreen will work with iOS (from initial testing), I'm 77 | // keeping in the iOS native calls to serve as an example of how to add them to a 78 | // cross platform Qt app. 79 | 80 | 81 | #if defined(Q_OS_ANDROID) 82 | return android_get_device_resolution_in_pixels(); 83 | #elif defined(Q_OS_IOS) 84 | return ios_utils::get_device_resolution_in_pixels(); 85 | #else 86 | // Fallback (or Desktop OSes) 87 | // TODO test on macOS Retina, and those with a notch 88 | const QScreen *screen = QGuiApplication::primaryScreen(); 89 | const QSizeF dim(screen->size()); 90 | return (screen->devicePixelRatio() * dim).toSize(); 91 | #endif 92 | } 93 | -------------------------------------------------------------------------------- /qt_ui/Utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace utils 6 | { 7 | /** Computes the actual screen resoltuion (in physical pixels) */ 8 | QSize compute_true_screen_resolution(); 9 | } 10 | -------------------------------------------------------------------------------- /qt_ui/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle 2 | build.grade 3 | gradlew 4 | gradlew.bat 5 | gradle.properties 6 | -------------------------------------------------------------------------------- /qt_ui/android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /qt_ui/android/res/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/android/res/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /qt_ui/android/res/drawable-ldpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/android/res/drawable-ldpi/icon.png -------------------------------------------------------------------------------- /qt_ui/android/res/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/android/res/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /qt_ui/android/res/drawable-xhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/android/res/drawable-xhdpi/icon.png -------------------------------------------------------------------------------- /qt_ui/android/res/drawable-xxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/android/res/drawable-xxhdpi/icon.png -------------------------------------------------------------------------------- /qt_ui/android/res/drawable-xxxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/android/res/drawable-xxxhdpi/icon.png -------------------------------------------------------------------------------- /qt_ui/android/res/values/libs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /qt_ui/android/src/net/sixteenbpp/psraytracing/AndroidUtils.java: -------------------------------------------------------------------------------- 1 | package net.sixteenbpp.psraytracing; 2 | 3 | import android.content.Context; 4 | import android.graphics.Point; 5 | import android.util.DisplayMetrics; 6 | 7 | 8 | public class AndroidUtils 9 | { 10 | /** 11 | * This is an Android native method we need to call to get the physical screen size 12 | * (in pixels) of our device. 13 | * 14 | * \a context must be the active application context. 15 | */ 16 | public static Point get_device_resolution_in_pixels(Context context) 17 | { 18 | // NOTE: This API is deprecated in android API 30 and above. 19 | // But I don't see any other way of getting the "real size" (easily) in the 20 | // new recommended API 21 | Point dim = new Point(); 22 | context.getDisplay().getRealSize(dim); 23 | return dim; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/128.png -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/16.png -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/256.png -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/32.png -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/512.png -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/64.png -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /qt_ui/apple/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/apple/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /qt_ui/apple/Info.plist.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | 8 | CFBundleDisplayName 9 | ${MACOSX_BUNDLE_EXECUTABLE_NAME} 10 | 11 | CFBundleExecutable 12 | ${MACOSX_BUNDLE_EXECUTABLE_NAME} 13 | 14 | CFBundleIconFile 15 | 16 | 17 | CFBundleIdentifier 18 | ${MACOSX_BUNDLE_GUI_IDENTIFIER} 19 | 20 | CFBundleInfoDictionaryVersion 21 | 6.0 22 | 23 | CFBundleName 24 | ${MACOSX_BUNDLE_EXECUTABLE_NAME} 25 | 26 | CFBundlePackageType 27 | APPL 28 | 29 | CFBundleShortVersionString 30 | ${MACOSX_BUNDLE_SHORT_VERSION_STRING} 31 | 32 | CFBundleSignature 33 | ???? 34 | 35 | CFBundleVersion 36 | ${MACOSX_BUNDLE_BUNDLE_VERSION} 37 | 38 | 40 | UILaunchStoryboardName 41 | Launch 42 | 43 | 45 | UILaunchScreen 46 | 47 | UIImageName 48 | Launch Screen 49 | 50 | 51 | UISupportedInterfaceOrientations 52 | 53 | UIInterfaceOrientationPortrait 54 | UIInterfaceOrientationPortraitUpsideDown 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /qt_ui/apple/Launch.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /qt_ui/apple/iOSUtils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ios_utils 6 | { 7 | /** Calls some native iOS stuff to get the actual screen resolution of the device */ 8 | QSize get_device_resolution_in_pixels(); 9 | } 10 | -------------------------------------------------------------------------------- /qt_ui/apple/iOSUtils.mm: -------------------------------------------------------------------------------- 1 | #include "iOSUtils.h" 2 | #import 3 | #import 4 | 5 | 6 | // I want to note that We could have used the QScreen API here instead, but I'd like to keep an example 7 | // in this codebase of how to do iOS native calls. 8 | 9 | 10 | QSize ios_utils::get_device_resolution_in_pixels() 11 | { 12 | // `nativeBounds` on the `mainScreen` is the property we need to access to get the true pixel size, 13 | // but for this to work correctly you will need to add a "Launch Screen" to the app. Else, this 14 | // will report an incorrect scaled (down) value. 15 | const CGRect dim = [UIScreen mainScreen].nativeBounds; 16 | 17 | QSize native_size( 18 | static_cast(dim.size.width), 19 | static_cast(dim.size.height) 20 | ); 21 | 22 | // `nativeBounds` returns the value as if the device in Portrait mode. We Need to check the orientation 23 | // and manually "rotate" the size above if we are in landcape 24 | 25 | if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) 26 | native_size.transpose(); 27 | 28 | return native_size; 29 | } 30 | -------------------------------------------------------------------------------- /qt_ui/i18n/qtquickcontrols2_ja_JP.conf: -------------------------------------------------------------------------------- 1 | [Controls] 2 | Style=Basic 3 | 4 | [Basic] 5 | Font\PointSize=15 6 | -------------------------------------------------------------------------------- /qt_ui/icons/app_icon_compressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/define-private-public/PSRayTracing/a54b6817ac0fd5715cbfb2b81356bc42c6778d8b/qt_ui/icons/app_icon_compressed.png -------------------------------------------------------------------------------- /qt_ui/icons/settings.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /qt_ui/icons/start.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /qt_ui/icons/stop.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /qt_ui/imports/SixteenBPP/UITheme.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | 3 | import QtQuick 4 | import QtQuick.Window 5 | 6 | 7 | // These are themeing constants that are used throughout the app. 8 | // These are things such as colour choices, images, etc. It's possible 9 | // in the future we want to be able to theme the app, so centralizing 10 | // where all of the colors go makes a lot of sense 11 | QtObject { 12 | id: root 13 | 14 | readonly property real _scaling_factor: Math.min(1.8, Screen.devicePixelRatio) 15 | 16 | readonly property string settings_icon: 'qrc:/settings_icon' 17 | readonly property string start_icon: 'qrc:/start_icon' 18 | readonly property string stop_icon: 'qrc:/stop_icon' 19 | 20 | readonly property color white_color: '#FFFFFF' 21 | readonly property color black_color: '#000000' 22 | readonly property color render_display_background_color: '#CCCCCC' 23 | readonly property color scrollbar_color: '#565656' 24 | readonly property color pan_zoom_controls_background_color: '#222222' 25 | readonly property color pan_zoom_controls_border_color: '#CCCCCC' 26 | 27 | readonly property real title_font_size_pt: _scaling_factor * 18 28 | readonly property real about_font_size_pt: _scaling_factor * _about_font_size_base 29 | 30 | // TODO document better 31 | // This is for smartphone's and small screen, If they have a screen width less than this 32 | // we adjust the layout for that kind of display 33 | readonly property int min_width_threshold: 700 34 | 35 | // Depending upon the language, we need to make tha bout text a bit bigger (maybe) 36 | readonly property real _about_font_size_base: { 37 | var is_japanese = Qt.locale().name.startsWith('ja'); // Most likely `ja_JP`, but I once got `ja_US`... 38 | return (is_japanese ? 15 : 12); 39 | } 40 | 41 | // readonly property bool width_is_small: Screen.width <= min_width_threshold 42 | 43 | /** Checks to see if a given width is considered "small" for the theme */ 44 | function is_width_small(width) 45 | { return (width < root.min_width_threshold); } 46 | } 47 | -------------------------------------------------------------------------------- /qt_ui/imports/SixteenBPP/Views/AboutView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls 3 | import QtQuick.Layouts 4 | import QtQuick.Window 5 | import SixteenBPP 6 | import SixteenBPP.Widgets 7 | 8 | 9 | // The "About Page" for the app. We explain whaqt this is for and 10 | // give some thanks to those who helped us along the way 11 | Item { 12 | id: root 13 | 14 | signal close_button_clicked(); 15 | 16 | QtObject { 17 | id: __p 18 | 19 | readonly property bool width_is_small: UITheme.is_width_small(root.width) 20 | readonly property real padding_amount: (width_is_small ? 7.5 : 25) 21 | 22 | // The width of the large whitespace that's to the right/left of the scroller 23 | readonly property real left_right_scroller_padding: (((root.width - scroller.width) / 2) - (2 * padding_amount)) 24 | 25 | // Used to check if there isn't enough room for the close button to in that whitespace off to the right/left 26 | readonly property bool place_close_button_bellow_scroller: (left_right_scroller_padding < close_button.width); 27 | } 28 | 29 | Component.onCompleted: { 30 | // Foward signals 31 | close_button.clicked.connect(root.close_button_clicked); 32 | 33 | // // Debugging info 34 | // console.log('w: ' + Screen.width); 35 | // console.log('h: ' + Screen.height); 36 | // console.log('dpr: ' + Screen.devicePixelRatio); 37 | } 38 | 39 | // Title of the form 40 | Text { 41 | id: title 42 | 43 | anchors.margins: __p.padding_amount 44 | anchors.horizontalCenter: parent.horizontalCenter 45 | anchors.top: parent.top 46 | 47 | text: Messages.about_ps_raytracing 48 | font.pointSize: UITheme.title_font_size_pt 49 | } 50 | 51 | ScrollView { 52 | id: scroller 53 | 54 | anchors.margins: __p.padding_amount 55 | anchors.left: parent.left 56 | anchors.right: parent.right 57 | anchors.top: title.bottom 58 | anchors.bottom: (__p.place_close_button_bellow_scroller ? close_button.top : parent.bottom) 59 | 60 | clip: true 61 | ScrollBar.horizontal.interactive: true 62 | ScrollBar.vertical.interactive: true 63 | 64 | Flickable { 65 | width: parent.width 66 | height: parent.height 67 | 68 | contentWidth: parent.width 69 | contentHeight: layout.height 70 | 71 | ColumnLayout { 72 | id: layout 73 | 74 | width: Math.min(UITheme.min_width_threshold, root.width) - (2 * __p.padding_amount); 75 | anchors.horizontalCenter: parent.horizontalCenter 76 | 77 | // Main body of the about page 78 | Text { 79 | Layout.fillWidth: true 80 | 81 | text: Messages.about_contents 82 | wrapMode: Text.WordWrap 83 | font.pointSize: UITheme.about_font_size_pt 84 | 85 | // Opens the hyperlinks when pressed 86 | onLinkActivated: (link) => Qt.openUrlExternally(link) 87 | } 88 | 89 | // App Icon 90 | Image { 91 | Layout.alignment: Qt.AlignHCenter 92 | 93 | source: 'qrc:/app_icon_compressed' 94 | sourceSize.width: ((2 / 3) * layout.width) 95 | mipmap: true // Smoother scaling 96 | fillMode: Image.PreserveAspectFit 97 | } 98 | 99 | // Info about the app 100 | Text { 101 | Layout.fillWidth: true 102 | 103 | text: Messages.info_contents 104 | wrapMode: Text.WordWrap 105 | font.pointSize: UITheme.about_font_size_pt 106 | } 107 | } 108 | } 109 | } 110 | 111 | 112 | // Pressing this button should go back to the Render View 113 | // Note that this adjusts its location (via anchors) if the screen width is too small 114 | Button { 115 | id: close_button 116 | 117 | text: Messages.close 118 | 119 | anchors.margins: __p.padding_amount 120 | anchors.rightMargin: (__p.place_close_button_bellow_scroller ? 0 : __p.padding_amount) 121 | anchors.right: (__p.place_close_button_bellow_scroller ? scroller.right : parent.right); 122 | anchors.bottom: parent.bottom 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /qt_ui/imports/SixteenBPP/Views/RenderView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls 3 | import SixteenBPP 4 | import SixteenBPP.Widgets 5 | 6 | // This is also the "main view" of the application. It's where we can perform a render, 7 | // adjust settings, and go into other views from 8 | Item { 9 | id: root 10 | 11 | signal about_button_clicked() 12 | 13 | state: 'normal' 14 | property bool in_fullscreen_mode: false 15 | 16 | Component.onCompleted: { 17 | // Forward the clicking of the "About" button 18 | render_settings_form.about_button_clicked.connect(root.about_button_clicked); 19 | } 20 | 21 | // The rendeer result (that's displayed) 22 | RenderDisplay { 23 | id: render_display 24 | 25 | anchors.left: parent.left 26 | anchors.right: parent.right 27 | anchors.top: parent.top 28 | 29 | onRender_double_tapped: root.handle_render_double_tapped(); 30 | } 31 | 32 | // The bar that's bellow at the bottom 33 | ControlsBar { 34 | id: controls_bar 35 | 36 | anchors.left: parent.left 37 | anchors.right: parent.right 38 | anchors.bottom: parent.bottom 39 | anchors.margins: 10 40 | 41 | onOpen_render_settings_clicked: render_settings_popup.open() 42 | 43 | onRender_button_clicked: { 44 | if (g_renderer.in_progress()) 45 | root.stop_active_render(); 46 | else 47 | root.start_render(); 48 | } 49 | } 50 | 51 | // This popup start hidden off screen to transition in 52 | Drawer { 53 | id: render_settings_popup 54 | 55 | readonly property real padding_amount: 25 56 | 57 | width: parent.width 58 | height: root.height - (2 * render_settings_popup.padding_amount) 59 | 60 | // We want this popup to be modal. 61 | // The user must press the "OK" button (int the content item) to close it 62 | modal: true 63 | focus: true 64 | interactive: false 65 | closePolicy: Popup.NoAutoClose 66 | edge: Qt.TopEdge 67 | 68 | // This is the actual form that shows up. It has an "OK" button that prevents the user 69 | // from closing the popup until all input has been validated. 70 | contentItem: RenderSettingsForm { 71 | id: render_settings_form 72 | 73 | anchors.centerIn: parent 74 | 75 | onOk_button_clicked: render_settings_popup.close(); 76 | onAbout_button_clicked: render_settings_popup.close(); 77 | } 78 | } 79 | 80 | states: [ 81 | // normal state, show control bar and render/main area 82 | State { 83 | name: 'normal' 84 | when: !root.in_fullscreen_mode 85 | 86 | PropertyChanges { target: controls_bar; visible: true; } 87 | AnchorChanges { target: render_display; anchors.bottom: controls_bar.top; } 88 | }, 89 | 90 | // State for when the user wants the render fullscreen'd 91 | // No control bar, render is front and centered 92 | // (and hopefully zoomed in w/ pan controls) 93 | State { 94 | name: 'fullscreen_render' 95 | when: root.in_fullscreen_mode 96 | 97 | PropertyChanges { target: controls_bar; visible: false; } 98 | AnchorChanges { target: render_display; anchors.bottom: parent.bottom; } 99 | } 100 | ] 101 | 102 | 103 | function start_render() { 104 | // Setup the render job 105 | g_renderer.start_render( 106 | render_settings_form.render_size, 107 | controls_bar.selected_scene_id, 108 | render_settings_form.samples_per_pixel, 109 | render_settings_form.max_depth, 110 | render_settings_form.num_threads, 111 | render_settings_form.seed_str, 112 | render_settings_form.deep_copy_per_thread 113 | ); 114 | } 115 | 116 | 117 | function stop_active_render() { 118 | // Only do this if we're rendering (force stop) 119 | if (g_renderer.in_progress()) 120 | g_renderer.stop_active_render(); 121 | } 122 | 123 | 124 | function handle_render_double_tapped() { 125 | // If a render has completed, then double tapping is enabled, which means the image can be fullscreen'd 126 | root.in_fullscreen_mode = !root.in_fullscreen_mode; 127 | } 128 | 129 | // Function to call showing the render settings popup 130 | function show_render_settings() 131 | { render_settings_popup.open(); } 132 | } 133 | 134 | -------------------------------------------------------------------------------- /qt_ui/imports/SixteenBPP/Views/qmldir: -------------------------------------------------------------------------------- 1 | module SixteenBPP.Views 2 | 3 | AboutView AboutView.qml 4 | RenderView RenderView.qml 5 | -------------------------------------------------------------------------------- /qt_ui/imports/SixteenBPP/Widgets/IntegerOnlyTextField.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls 3 | 4 | // This is a TextField that only allows for integer input 5 | TextField { 6 | id: root 7 | 8 | property alias lower_bound: validator.bottom 9 | property alias upper_bound: validator.top 10 | 11 | validator: IntValidator { 12 | id: validator 13 | } 14 | 15 | // "No predicted" is required so we can get changed signals to fire continuously 16 | inputMethodHints: Qt.ImhDigitsOnly | Qt.ImhNoPredictiveText 17 | 18 | // Will parse the text value to an integer 19 | function value_as_int() { 20 | return parseInt(root.text); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /qt_ui/imports/SixteenBPP/Widgets/RenderDisplay.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls 3 | import QtQuick.Layouts 4 | import SixteenBPP 5 | import SixteenBPP.Widgets 6 | 7 | 8 | Rectangle { 9 | id: root 10 | state: 'startup' 11 | 12 | color: UITheme.render_display_background_color 13 | 14 | signal render_double_tapped 15 | 16 | QtObject { 17 | id: __p 18 | 19 | function show_render() 20 | { 21 | // First need to clear out the image then load it again 22 | render_image.image_source = '' 23 | render_image.image_source = 'file:///' + g_renderer.render_file_path; 24 | } 25 | } 26 | 27 | onStateChanged: { 28 | // If the state becomes "complete", then we have a render to show 29 | if (state == 'show_render') 30 | __p.show_render(); 31 | } 32 | 33 | 34 | StackLayout { 35 | id: stack_layout 36 | 37 | anchors.fill: parent 38 | 39 | currentIndex: 0 40 | 41 | // Text message 42 | Item { 43 | width: parent.width 44 | height: parent.height 45 | 46 | Text { 47 | id: display_msg 48 | 49 | anchors.centerIn: parent 50 | 51 | text: Messages.instructions 52 | horizontalAlignment: Text.AlignHCenter 53 | font.pointSize: 14 54 | } 55 | } 56 | 57 | // The display for the rendered image (in a PanZoom control) 58 | PanZoom { 59 | id: render_image 60 | visible: false 61 | 62 | // Make our images appear more "pixelly" when zoomed in 63 | // TODO does this effect it when the image is too large? (e.g. zooming to 50%, does this look bad) 64 | image_smooth: false 65 | 66 | // Required to work (see docs for `PanZoom`) 67 | width: parent.width 68 | height: parent.height 69 | 70 | // Bubble up the double tap signal? 71 | onDouble_tapped: { 72 | // Bubble up the signal & toggle the pan/zoom functionality 73 | root.render_double_tapped(); 74 | render_image.pan_zoom_enabled = !render_image.pan_zoom_enabled; 75 | } 76 | } 77 | } 78 | 79 | states: [ 80 | // Show instructions 81 | State { 82 | name: 'startup' 83 | when: (g_renderer.status == PSRayTracingRenderer.Status.Idle) 84 | PropertyChanges { target: stack_layout; currentIndex: 0 } 85 | PropertyChanges { target: display_msg; text: Messages.instructions } 86 | }, 87 | 88 | // Render in progress 89 | State { 90 | name: 'render_in_progress' 91 | when: (g_renderer.status == PSRayTracingRenderer.Status.InProgress) 92 | PropertyChanges { target: stack_layout; currentIndex: 0 } 93 | PropertyChanges { 94 | target: display_msg 95 | text: Messages.render_in_progress_fmt.arg(g_renderer.render_time_str).arg(g_renderer.render_progress_str) 96 | } 97 | }, 98 | 99 | State { 100 | name: 'stopping_render' 101 | when: (g_renderer.status == PSRayTracingRenderer.Status.StoppingRender) 102 | PropertyChanges { target: stack_layout; currentIndex: 0 } 103 | PropertyChanges { target: display_msg; text: Messages.stopping_active_render } 104 | }, 105 | 106 | // Show completed render 107 | State { 108 | name: 'show_render' 109 | when: (g_renderer.status == PSRayTracingRenderer.Status.Complete) 110 | PropertyChanges { target: stack_layout; currentIndex: 1 } 111 | } 112 | ] 113 | } 114 | -------------------------------------------------------------------------------- /qt_ui/imports/SixteenBPP/Widgets/qmldir: -------------------------------------------------------------------------------- 1 | module SixteenBPP.Widgets 2 | 3 | IntegerOnlyTextField IntegerOnlyTextField.qml 4 | PanZoom PanZoom.qml 5 | RenderDisplay RenderDisplay.qml 6 | -------------------------------------------------------------------------------- /qt_ui/imports/SixteenBPP/qmldir: -------------------------------------------------------------------------------- 1 | module SixteenBPP 2 | 3 | # Constants 4 | singleton Messages Messages.qml 5 | singleton UITheme UITheme.qml 6 | 7 | # Controls/UI things 8 | ControlsBar ControlsBar.qml 9 | IntegerOnlyTextField IntegerOnlyTextField.qml 10 | RenderDisplay RenderDisplay.qml 11 | RenderSettingsForm RenderSettingsForm.qml 12 | -------------------------------------------------------------------------------- /qt_ui/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "UIMathHelper.hpp" 6 | #include "PSRayTracingRenderer.hpp" 7 | #include "Settings.hpp" 8 | 9 | 10 | int main(int argc, char *argv[]) 11 | { 12 | qmlRegisterType("SixteenBPP", 1, 0, "PSRayTracingRenderer"); 13 | qmlRegisterType( "SixteenBPP", 1, 0, "UIMathHelper"); 14 | qmlRegisterType( "SixteenBPP", 1, 0, "Settings"); 15 | 16 | // QLocale::setDefault(QLocale::Japanese); 17 | 18 | QGuiApplication app(argc, argv); 19 | QQmlApplicationEngine engine; 20 | 21 | // Load up any translations we have (for the system locale) 22 | QTranslator translator; 23 | const QStringList uiLanguages = QLocale::system().uiLanguages(); 24 | for (const QString &locale : uiLanguages) { 25 | const QString baseName = "qt_ui_" + QLocale(locale).name(); 26 | if (translator.load(":/i18n/" + baseName)) { 27 | app.installTranslator(&translator); 28 | break; 29 | } 30 | } 31 | 32 | // Setup some globals that communicate between Qml & C++ 33 | auto render_engine = new PSRayTracingRenderer(&app); 34 | auto settings = new Settings(render_engine->num_concurrent_threads_supported(), &app); 35 | auto ui_math_helper = new UIMathHelper(&app); 36 | engine.rootContext()->setContextProperties({ 37 | {"g_renderer", QVariant::fromValue(render_engine)}, 38 | {"g_settings", QVariant::fromValue(settings)}, 39 | {"g_ui_math_helper", QVariant::fromValue(ui_math_helper)}, 40 | }); 41 | 42 | const QUrl url(QStringLiteral("qrc:/main.qml")); 43 | QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, 44 | &app, [url](QObject *obj, const QUrl &objUrl) { 45 | if (!obj && url == objUrl) 46 | QCoreApplication::exit(-1); 47 | }, Qt::QueuedConnection); 48 | engine.addImportPath(":imports/"); 49 | engine.load(url); 50 | 51 | return app.exec(); 52 | } 53 | -------------------------------------------------------------------------------- /qt_ui/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls 3 | import QtQuick.Window 4 | import QtQuick.Layouts 5 | import SixteenBPP 6 | import SixteenBPP.Views 7 | 8 | 9 | Window { 10 | id: root 11 | 12 | // TODO reset after done with dev 13 | minimumWidth: 350//640 14 | minimumHeight: 200//480 15 | width: 960 16 | height: 540 17 | visible: true 18 | title: Messages.app_window_title 19 | 20 | // This is neededed: On Android when the user pressed the Back button, Qml 21 | // will close the app. Instead, we'd like it to go back to the previous view. 22 | // Only if that isn't possible, then close the app. 23 | onClosing: function(close) { 24 | if (stack_view.depth > 1) { 25 | close.accepted = false; 26 | stack_view.pop(); 27 | } else { 28 | // If we're closing the window, first kill any active renders, 29 | // Unfortunately the process could hang until a RenderTask/scanline has completed (this depends on the sample count per pixel) 30 | render_view.stop_active_render(); 31 | } 32 | } 33 | 34 | // Controller for all of the views 35 | StackView { 36 | id: stack_view 37 | anchors.fill: parent 38 | initialItem: render_view 39 | // initialItem: about_view 40 | 41 | RenderView { 42 | id: render_view 43 | 44 | visible: false 45 | width: parent.width 46 | height: parent.height 47 | 48 | // If the about button is clicked, put on the "About" View 49 | onAbout_button_clicked: stack_view.push(about_view); 50 | } 51 | 52 | AboutView { 53 | id: about_view; 54 | 55 | visible: false 56 | width: parent.width 57 | height: parent.height 58 | 59 | // If the close button is clicked, pop this view (thus going back to the render view) 60 | // The "RenderSettings" popover should show again (prevents bug #12), also since the 61 | // Render settings are the only way to get to the About page. 62 | onClose_button_clicked: 63 | { 64 | stack_view.pop() 65 | render_view.show_render_settings(); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /qt_ui/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | qtquickcontrols2.conf 5 | 6 | 7 | imports/SixteenBPP/qmldir 8 | imports/SixteenBPP/ControlsBar.qml 9 | imports/SixteenBPP/Messages.qml 10 | imports/SixteenBPP/RenderSettingsForm.qml 11 | imports/SixteenBPP/UITheme.qml 12 | 13 | imports/SixteenBPP/Views/qmldir 14 | imports/SixteenBPP/Views/AboutView.qml 15 | imports/SixteenBPP/Views/RenderView.qml 16 | 17 | imports/SixteenBPP/Widgets/qmldir 18 | imports/SixteenBPP/Widgets/IntegerOnlyTextField.qml 19 | imports/SixteenBPP/Widgets/PanZoom.qml 20 | imports/SixteenBPP/Widgets/RenderDisplay.qml 21 | 22 | 23 | icons/settings.svg 24 | icons/start.svg 25 | icons/stop.svg 26 | icons/app_icon_compressed.png 27 | 28 | 29 | 30 | 31 | i18n/qtquickcontrols2_ja_JP.conf 32 | 33 | 34 | -------------------------------------------------------------------------------- /qt_ui/qtquickcontrols2.conf: -------------------------------------------------------------------------------- 1 | [Controls] 2 | Style=Basic 3 | 4 | [Basic] 5 | Font\PointSize=12 6 | -------------------------------------------------------------------------------- /render_library/AABB.cpp: -------------------------------------------------------------------------------- 1 | #include "AABB.hpp" 2 | #include 3 | #include 4 | #include 5 | #include "Ray.hpp" 6 | 7 | 8 | AABB::AABB(const Vec3 &a, const Vec3 &b) NOEXCEPT : 9 | min(a), 10 | max(b) 11 | { } 12 | 13 | AABB AABB::surrounding(const AABB &box0, const AABB &box1) NOEXCEPT { 14 | const Vec3 small( 15 | std::min(box0.min.x, box1.min.x), 16 | std::min(box0.min.y, box1.min.y), 17 | std::min(box0.min.z, box1.min.z) 18 | ); 19 | 20 | const Vec3 big( 21 | std::max(box0.max.x, box1.max.x), 22 | std::max(box0.max.y, box1.max.y), 23 | std::max(box0.max.z, box1.max.z) 24 | ); 25 | 26 | return AABB(small, big); 27 | } 28 | 29 | bool AABB::hit(const Ray &r, rreal tmin, rreal tmax) const NOEXCEPT { 30 | #ifdef USE_BOOK_AABB_HIT 31 | // Becuase of how I built my Vec3 object, I need to write out 32 | // each axis... 33 | //*== Andrew Kensler (Pixar) method ==*/ 34 | rreal inv_d, t0, t1; 35 | 36 | // X-axis 37 | inv_d = static_cast(1) / r.direction.x; 38 | t0 = (min.x - r.origin.x) * inv_d; 39 | t1 = (max.x - r.origin.x) * inv_d; 40 | if (inv_d < 0) 41 | std::swap(t0, t1); 42 | tmin = (t0 > tmin) ? t0 : tmin; 43 | tmax = (t1 < tmax) ? t1 : tmax; 44 | if (tmax <= tmin) 45 | return false; 46 | 47 | // Y-axis 48 | inv_d = static_cast(1) / r.direction.y; 49 | t0 = (min.y - r.origin.y) * inv_d; 50 | t1 = (max.y - r.origin.y) * inv_d; 51 | if (inv_d < 0) 52 | std::swap(t0, t1); 53 | tmin = (t0 > tmin) ? t0 : tmin; 54 | tmax = (t1 < tmax) ? t1 : tmax; 55 | if (tmax <= tmin) 56 | return false; 57 | 58 | // Z-axis 59 | inv_d = static_cast(1) / r.direction.z; 60 | t0 = (min.z - r.origin.z) * inv_d; 61 | t1 = (max.z - r.origin.z) * inv_d; 62 | if (inv_d < 0) 63 | std::swap(t0, t1); 64 | tmin = (t0 > tmin) ? t0 : tmin; 65 | tmax = (t1 < tmax) ? t1 : tmax; 66 | if (tmax <= tmin) 67 | return false; 68 | 69 | return true; 70 | #else 71 | // This method is a bit faster because of modern CPUs (vector instructions) 72 | // and newer compilers (doing auto-vectorization) 73 | 74 | // Adapted from: https://medium.com/@bromanz/another-view-on-the-classic-ray-aabb-intersection-algorithm-for-bvh-traversal-41125138b525 75 | const rreal t0[3] = { 76 | (min.x - r.origin.x) / r.direction.x, 77 | (min.y - r.origin.y) / r.direction.y, 78 | (min.z - r.origin.z) / r.direction.z, 79 | }; 80 | const rreal t1[3] = { 81 | (max.x - r.origin.x) / r.direction.x, 82 | (max.y - r.origin.y) / r.direction.y, 83 | (max.z - r.origin.z) / r.direction.z, 84 | }; 85 | 86 | const rreal t_smaller[3] = { 87 | std::min(t0[0], t1[0]), 88 | std::min(t0[1], t1[1]), 89 | std::min(t0[2], t1[2]) 90 | }; 91 | const rreal t_larger[3] = { 92 | std::max(t0[0], t1[0]), 93 | std::max(t0[1], t1[1]), 94 | std::max(t0[2], t1[2]) 95 | }; 96 | 97 | tmin = std::max({tmin, t_smaller[0], t_smaller[1], t_smaller[2]}); 98 | tmax = std::min({tmax, t_larger[0], t_larger[1], t_larger[2]}); 99 | 100 | return (tmin < tmax); 101 | #endif 102 | } 103 | -------------------------------------------------------------------------------- /render_library/AABB.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.hpp" 4 | #include "Vec3.hpp" 5 | 6 | class Ray; 7 | 8 | 9 | // An axis-aligned bounding box 10 | class AABB { 11 | public: 12 | // Data 13 | Vec3 min; 14 | Vec3 max; 15 | 16 | public: 17 | explicit AABB() NOEXCEPT = default; 18 | explicit AABB(const Vec3 &a, const Vec3 &b) NOEXCEPT ; 19 | static AABB surrounding(const AABB &box0, const AABB &box1) NOEXCEPT ; 20 | 21 | bool hit(const Ray &r, rreal tmin, rreal tmax) const NOEXCEPT ; 22 | 23 | std::string str() const { 24 | return "AABB(min=" + min.str() + 25 | ", max=" + max.str() + ")"; 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /render_library/Cameras/Camera.cpp: -------------------------------------------------------------------------------- 1 | #include "Camera.hpp" 2 | #include 3 | #include "Util.hpp" 4 | #include "Ray.hpp" 5 | #include "RandomGenerator.hpp" 6 | 7 | using namespace std; 8 | using namespace util; 9 | 10 | 11 | Camera::Camera( 12 | const Vec3 &look_from, const Vec3 &look_at, const Vec3 &v_up, 13 | const rreal vfov, const rreal aspect_ratio, 14 | const rreal aperature, 15 | const rreal focus_dist 16 | ) NOEXCEPT { 17 | const rreal theta = degrees_to_radians(vfov); 18 | const rreal h = std::tan(theta / 2); 19 | const rreal viewport_height = 2 * h; 20 | const rreal viewport_width = aspect_ratio * viewport_height; 21 | 22 | _w = (look_from - look_at).unit_vector(); 23 | _u = v_up.cross(_w).unit_vector(); 24 | _v = _w.cross(_u); 25 | 26 | _origin = look_from; 27 | _horizontal = focus_dist * viewport_width * _u; 28 | _vertical = focus_dist * viewport_height * _v; 29 | _lower_left_corner = _origin - (_horizontal / static_cast(2)) - (_vertical / static_cast(2)) - (focus_dist *_w); 30 | 31 | _lens_radius = aperature / 2; 32 | } 33 | 34 | shared_ptr Camera::deep_copy() const NOEXCEPT { 35 | return make_shared(*this); 36 | } 37 | 38 | Ray Camera::get_ray(RandomGenerator &rng, const rreal s, const rreal t) const NOEXCEPT { 39 | const Vec3 rd = _lens_radius * rng.get_in_unit_disk(); 40 | const Vec3 offset = (_u * rd.x) + (_v * rd.y); 41 | 42 | const Vec3 ray_dir = _lower_left_corner 43 | + (s * _horizontal) 44 | + (t * _vertical) 45 | - _origin - offset; 46 | 47 | return Ray(_origin + offset, ray_dir); 48 | } 49 | -------------------------------------------------------------------------------- /render_library/Cameras/Camera.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Interfaces/ICamera.hpp" 4 | #include "Vec3.hpp" 5 | 6 | 7 | // A standard postionable camera with focus (and blur) 8 | class Camera : public ICamera { 9 | protected: 10 | // Data 11 | Vec3 _origin; 12 | Vec3 _lower_left_corner; 13 | Vec3 _horizontal; 14 | Vec3 _vertical; 15 | Vec3 _u, _v, _w; // Used for camera's local coordinates and determine the focal planes 16 | rreal _lens_radius; 17 | 18 | public: 19 | explicit Camera( 20 | const Vec3 &look_from, 21 | const Vec3 &look_at, 22 | const Vec3 &v_up, 23 | const rreal vfov, // Vertical field of view 24 | const rreal aspect_ratio, 25 | const rreal aperature, 26 | const rreal focus_dist 27 | ) NOEXCEPT; 28 | 29 | virtual std::shared_ptr deep_copy() const NOEXCEPT override; 30 | 31 | virtual Ray get_ray(RandomGenerator &rng, const rreal s, const rreal t) const NOEXCEPT override; 32 | }; 33 | -------------------------------------------------------------------------------- /render_library/Cameras/MotionBlurCamera.cpp: -------------------------------------------------------------------------------- 1 | #include "MotionBlurCamera.hpp" 2 | #include 3 | #include "RandomGenerator.hpp" 4 | #include "Ray.hpp" 5 | 6 | 7 | MotionBlurCamera::MotionBlurCamera( 8 | const Vec3 &look_from, 9 | const Vec3 &look_at, 10 | const Vec3 &v_up, 11 | const rreal vfov, 12 | const rreal aspect_ratio, 13 | const rreal aperature, 14 | const rreal focus_dist, 15 | const rreal t0, const rreal t1 16 | ) NOEXCEPT : 17 | Camera(look_from, look_at, v_up, vfov, aspect_ratio, aperature, focus_dist), 18 | _time0(t0), _time1(t1) 19 | { } 20 | 21 | std::shared_ptr MotionBlurCamera::deep_copy() const NOEXCEPT { 22 | return std::make_shared(*this); 23 | } 24 | 25 | Ray MotionBlurCamera::get_ray(RandomGenerator &rng, const rreal s, const rreal t) const NOEXCEPT { 26 | const Vec3 rd = _lens_radius * rng.get_in_unit_disk(); 27 | const Vec3 offset = (_u * rd.x) + (_v * rd.y); 28 | 29 | const Vec3 ray_dir = _lower_left_corner 30 | + (s * _horizontal) 31 | + (t * _vertical) 32 | - _origin - offset; 33 | 34 | return Ray(_origin + offset, ray_dir, rng.get_real(_time0, _time1)); 35 | } 36 | -------------------------------------------------------------------------------- /render_library/Cameras/MotionBlurCamera.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Camera.hpp" 4 | 5 | 6 | // A camera that supports motion blue 7 | // TODO need to refactor (maybe) for FINAL keyword 8 | class MotionBlurCamera FINAL : public Camera { 9 | private: 10 | // Data 11 | rreal _time0, _time1; // Shutter open/close times 12 | 13 | public: 14 | explicit MotionBlurCamera( 15 | const Vec3 &look_from, 16 | const Vec3 &look_at, 17 | const Vec3 &v_up, 18 | const rreal vfov, // Vertical field of view 19 | const rreal aspect_ratio, 20 | const rreal aperature, 21 | const rreal focus_dist, 22 | const rreal t0, const rreal t1 23 | ) NOEXCEPT; 24 | 25 | std::shared_ptr deep_copy() const NOEXCEPT override; 26 | 27 | Ray get_ray(RandomGenerator &rng, const rreal s, const rreal t) const NOEXCEPT override; 28 | }; 29 | -------------------------------------------------------------------------------- /render_library/ColorRGBA.cpp: -------------------------------------------------------------------------------- 1 | #include "ColorRGBA.hpp" 2 | -------------------------------------------------------------------------------- /render_library/ColorRGBA.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Vec3.hpp" 4 | 5 | // A simple pixel (represented by floats) 6 | 7 | struct ColorRGBA { 8 | rreal r = 0; 9 | rreal g = 0; 10 | rreal b = 0; 11 | rreal a = 1; // Starts opaque 12 | 13 | 14 | inline static ColorRGBA from_Vec3(const Vec3 &v, const rreal alpha=1) NOEXCEPT { 15 | ColorRGBA clr; 16 | clr.r = v.x; 17 | clr.g = v.y; 18 | clr.b = v.z; 19 | clr.a = alpha; 20 | 21 | return clr; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /render_library/Common.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // A collection of things (most likely defines or datatypes) that should be common across the project 4 | #include 5 | 6 | // adding `noexcept` to your functions usually results in a perf. boost. It's here as a toggable flag 7 | // to show the effects of it happening (or not) 8 | #ifdef USE_NOEXCEPT 9 | #define NOEXCEPT noexcept 10 | #else 11 | #define NOEXCEPT 12 | #endif 13 | 14 | 15 | #ifdef USE_FINAL 16 | #define FINAL final 17 | #else 18 | #define FINAL 19 | #endif 20 | 21 | 22 | #ifdef USE_SINGLE_PRECISION_REAL 23 | // Use floats (singles) for the maths 24 | using rreal = float; 25 | #else 26 | // Use doubles for the maths 27 | using rreal = double; 28 | #endif 29 | 30 | 31 | constexpr auto Infinity = std::numeric_limits::infinity(); 32 | constexpr auto Pi = static_cast(3.1415926535897932385); 33 | constexpr rreal HalfPi = Pi / static_cast(2); 34 | constexpr rreal TwoPi = static_cast(2) * Pi; 35 | constexpr rreal ThreeHalvesPi = static_cast(3.0 / 2.0) * Pi; 36 | constexpr rreal InvTwoPi = static_cast(1) / TwoPi; 37 | constexpr rreal TwoOverPi = static_cast(2) / Pi; 38 | -------------------------------------------------------------------------------- /render_library/HitRecord.cpp: -------------------------------------------------------------------------------- 1 | #include "HitRecord.hpp" 2 | -------------------------------------------------------------------------------- /render_library/HitRecord.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Vec3.hpp" 5 | #include "Ray.hpp" 6 | #include "Interfaces/IMaterial.hpp" 7 | 8 | 9 | struct HitRecord FINAL : public IDeepCopyable { 10 | Vec3 p = Vec3(); 11 | Vec3 normal = Vec3(); 12 | 13 | #ifdef USE_BOOK_MAT_PTR 14 | // The book by default uses a shared pointer, which is reference counted and slow 15 | std::shared_ptr mat_ptr; 16 | #else 17 | // Instead, we can use a raw pointer, which is more performant 18 | IMaterial *mat_ptr; 19 | #endif // USE_BOOK_MAT_PTR 20 | 21 | rreal t = 0; // Time 22 | rreal u = 0; // Texture coordinate 23 | rreal v = 0; // Texture coordinate 24 | bool front_face = false; 25 | 26 | inline virtual std::shared_ptr deep_copy() const NOEXCEPT override { 27 | auto hr = std::make_shared(*this); 28 | hr->set_mat_ptr(mat_ptr->deep_copy()); 29 | 30 | return hr; 31 | } 32 | 33 | inline void set_face_normal(const Ray &r, const Vec3 &outward_normal) NOEXCEPT { 34 | front_face = (r.direction.dot(outward_normal) < 0); 35 | normal = front_face ? outward_normal : -outward_normal; 36 | } 37 | 38 | inline void set_mat_ptr(const std::shared_ptr &mp) NOEXCEPT { 39 | #ifdef USE_BOOK_MAT_PTR 40 | // Using the book? simply can set it 41 | mat_ptr = mp; 42 | #else 43 | // Else, use the `get()` method 44 | mat_ptr = mp.get(); 45 | #endif // USE_BOOK_MAT_PTR 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /render_library/Interfaces/ICamera.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.hpp" 4 | #include "Interfaces/IDeepCopyable.hpp" 5 | 6 | class Ray; 7 | class RandomGenerator; 8 | 9 | // An interface so we can have multiple types of cameras 10 | class ICamera : public IDeepCopyable { 11 | public: 12 | virtual ~ICamera() NOEXCEPT = default; 13 | 14 | virtual Ray get_ray(RandomGenerator &rng, const rreal s, const rreal t) const NOEXCEPT = 0; 15 | 16 | virtual std::shared_ptr deep_copy() const NOEXCEPT = 0; 17 | }; 18 | -------------------------------------------------------------------------------- /render_library/Interfaces/IDeepCopyable.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.hpp" 4 | #include 5 | 6 | 7 | /*! 8 | Implementing this means that a deep copy of the object (including pointers) can be 9 | produced by the method `deep_copy()` 10 | */ 11 | template 12 | class IDeepCopyable { 13 | public: 14 | virtual ~IDeepCopyable() NOEXCEPT = default; 15 | 16 | virtual std::shared_ptr deep_copy() const NOEXCEPT = 0; 17 | }; 18 | -------------------------------------------------------------------------------- /render_library/Interfaces/IHittable.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "HitRecord.hpp" 4 | #include "IDeepCopyable.hpp" 5 | class AABB; 6 | class RandomGenerator; 7 | 8 | 9 | class IHittable : public IDeepCopyable { 10 | public: 11 | virtual ~IHittable() NOEXCEPT = default; 12 | 13 | virtual std::shared_ptr deep_copy() const NOEXCEPT = 0; 14 | 15 | virtual bool hit(RandomGenerator &rng, const Ray &r, const rreal t_min, const rreal t_max, HitRecord &rec) const NOEXCEPT = 0; 16 | virtual bool bounding_box(const rreal t0, const rreal t1, AABB &output_box) const NOEXCEPT = 0; 17 | virtual rreal pdf_value(RandomGenerator &rng, const Vec3 &origin, const Vec3 &v) const NOEXCEPT = 0; 18 | virtual Vec3 random(RandomGenerator &rng, const Vec3 &origin) const NOEXCEPT = 0; 19 | }; 20 | -------------------------------------------------------------------------------- /render_library/Interfaces/IMaterial.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.hpp" 4 | #include "Vec3.hpp" 5 | #include "Ray.hpp" 6 | #include "RenderMethod.hpp" 7 | #include "Interfaces/IDeepCopyable.hpp" 8 | 9 | struct HitRecord; 10 | struct ScatterRecord; 11 | class RandomGenerator; 12 | class IPDF; 13 | 14 | 15 | class IMaterial : public IDeepCopyable { 16 | public: 17 | virtual ~IMaterial() NOEXCEPT = default; 18 | 19 | virtual std::shared_ptr deep_copy() const NOEXCEPT = 0; 20 | 21 | virtual bool scatter( 22 | RandomGenerator &rng, // In 23 | const Ray &r_in, // In 24 | const HitRecord &h_rec, // In 25 | ScatterRecord &s_rec, // Out 26 | const RenderMethod method // In; this argument doesn't exist in any of the books, but it's here so we can support rendering all three books at the same time 27 | ) const NOEXCEPT = 0; 28 | 29 | virtual rreal scattering_pdf( 30 | const Ray &r_in, 31 | const HitRecord &h_rec, 32 | const Ray &scattered 33 | ) const NOEXCEPT = 0; 34 | 35 | virtual Vec3 emitted( 36 | const Ray &r_in, 37 | const HitRecord &h_rec, 38 | const rreal u, 39 | const rreal v, 40 | const Vec3 &p, 41 | const RenderMethod method // this argument doesn't exist in any of the books, but it's here so we can support rendering all three books at the same time 42 | ) const NOEXCEPT = 0; 43 | }; 44 | -------------------------------------------------------------------------------- /render_library/Interfaces/IPDF.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Vec3.hpp" 4 | class RandomGenerator; 5 | 6 | 7 | // Interface for defining a Probabily Distribution Function 8 | class IPDF { 9 | public: 10 | virtual ~IPDF() NOEXCEPT = default; 11 | 12 | virtual rreal value(RandomGenerator &rng, const Vec3 &direction) const NOEXCEPT = 0; 13 | virtual Vec3 generate(RandomGenerator &rng) const NOEXCEPT = 0; 14 | }; 15 | -------------------------------------------------------------------------------- /render_library/Interfaces/ITexture.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Vec3.hpp" 4 | #include "Interfaces/IDeepCopyable.hpp" 5 | 6 | class ITexture : public IDeepCopyable { 7 | public: 8 | virtual ~ITexture() NOEXCEPT = default; 9 | 10 | virtual std::shared_ptr deep_copy() const NOEXCEPT = 0; 11 | 12 | virtual Vec3 value(const rreal u, const rreal v, const Vec3 &p) const NOEXCEPT = 0; 13 | }; 14 | -------------------------------------------------------------------------------- /render_library/Materials/Dielectric.cpp: -------------------------------------------------------------------------------- 1 | #include "Materials/Dielectric.hpp" 2 | #include 3 | #include "ScatterRecord.hpp" 4 | #include "HitRecord.hpp" 5 | #include "Ray.hpp" 6 | #include "Util.hpp" 7 | #include "RandomGenerator.hpp" 8 | 9 | using namespace std; 10 | 11 | 12 | Dielectric::Dielectric(const rreal refractive_index) NOEXCEPT : 13 | _refractive_index(refractive_index) 14 | { } 15 | 16 | shared_ptr Dielectric::deep_copy() const NOEXCEPT { 17 | return make_shared(*this); 18 | } 19 | 20 | bool Dielectric::scatter( 21 | RandomGenerator &rng, 22 | const Ray &r_in, 23 | const HitRecord &h_rec, 24 | ScatterRecord &s_rec, 25 | const RenderMethod method 26 | ) const NOEXCEPT { 27 | s_rec.attenuation = Vec3(1.0); 28 | 29 | if (method == RenderMethod::Book3) 30 | { 31 | s_rec.is_specular = true; 32 | 33 | #ifdef USE_BOOK_PDF_MANAGEMENT 34 | s_rec.pdf_ptr = nullptr; 35 | #else 36 | s_rec.pdf = monostate(); 37 | #endif 38 | } 39 | 40 | const rreal etai_over_etat = h_rec.front_face ? (static_cast(1) / _refractive_index) : _refractive_index; 41 | const Vec3 unit_direction = r_in.direction.unit_vector(); 42 | const rreal cos_theta = std::fmin((-unit_direction).dot(h_rec.normal), static_cast(1)); 43 | const rreal sin_theta = util::sqrt(1 - (cos_theta * cos_theta)); 44 | 45 | #ifdef USE_BOOK_DIELECTRIC_SCATTER 46 | if ((etai_over_etat * sin_theta) > 1) { 47 | // Must reflect 48 | s_rec.ray = Ray(h_rec.p, unit_direction.reflect(h_rec.normal), r_in.time); 49 | return true; 50 | } 51 | 52 | // Check case for when at an angle a dielectric becomes mirrorlike 53 | const rreal reflect_probe = util::schlick(cos_theta, etai_over_etat); 54 | if (rng.get_real() < reflect_probe) { 55 | s_rec.ray = Ray(h_rec.p, unit_direction.reflect(h_rec.normal), r_in.time); 56 | return true; 57 | } 58 | 59 | // instead refract 60 | s_rec.ray = Ray(h_rec.p, unit_direction.refract(h_rec.normal, etai_over_etat), r_in.time); 61 | #else 62 | // Like in many other sections, we are trying to have the compiler break up these computation 63 | // into vector instructions for us, and reduce the amounts of possible branching 64 | 65 | // Check for reflections 66 | const bool reflect_case_one = ((etai_over_etat * sin_theta) > 1); 67 | const rreal reflect_probe = util::schlick(cos_theta, etai_over_etat); 68 | const bool reflect_case_two = (rng.get_real() < reflect_probe); // This is a case whre the dielectric becomes mirrorlike 69 | const bool reflect = (reflect_case_one || reflect_case_two); 70 | 71 | // Choose either to reflect or refract 72 | const Vec3 reflect_dir = unit_direction.reflect(h_rec.normal); 73 | const Vec3 refract_dir = unit_direction.refract(h_rec.normal, etai_over_etat); 74 | const Vec3 direction = (reflect ? reflect_dir : refract_dir); 75 | 76 | s_rec.ray = Ray(h_rec.p, direction, r_in.time); 77 | #endif 78 | 79 | return true; 80 | } 81 | 82 | rreal Dielectric::scattering_pdf( 83 | [[maybe_unused]] const Ray &r_in, 84 | [[maybe_unused]] const HitRecord &h_rec, 85 | [[maybe_unused]] const Ray &scattered 86 | ) const NOEXCEPT 87 | { return 0; } 88 | 89 | Vec3 Dielectric::emitted( 90 | [[maybe_unused]] const Ray &r_in, 91 | [[maybe_unused]] const HitRecord &h_rec, 92 | [[maybe_unused]] const rreal u, 93 | [[maybe_unused]] const rreal v, 94 | [[maybe_unused]] const Vec3 &p, 95 | [[maybe_unused]] const RenderMethod method 96 | ) const NOEXCEPT { 97 | return Vec3(0); 98 | } 99 | -------------------------------------------------------------------------------- /render_library/Materials/Dielectric.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Interfaces/IMaterial.hpp" 4 | 5 | 6 | // E.g. a glass like (refracting) material 7 | class Dielectric FINAL : public IMaterial { 8 | public: 9 | // Data 10 | rreal _refractive_index; 11 | 12 | public: 13 | explicit Dielectric(const rreal refractive_index) NOEXCEPT; 14 | 15 | std::shared_ptr deep_copy() const NOEXCEPT override; 16 | 17 | bool scatter( 18 | RandomGenerator &rng, 19 | const Ray &r_in, 20 | const HitRecord &h_rec, 21 | ScatterRecord &s_rec, 22 | const RenderMethod method 23 | ) const NOEXCEPT override; 24 | 25 | virtual rreal scattering_pdf( 26 | const Ray &r_in, 27 | const HitRecord &h_rec, 28 | const Ray &scattered 29 | ) const NOEXCEPT override; 30 | 31 | virtual Vec3 emitted( 32 | const Ray &r_in, 33 | const HitRecord &h_rec, 34 | const rreal u, 35 | const rreal v, 36 | const Vec3 &p, 37 | const RenderMethod method 38 | ) const NOEXCEPT override; 39 | }; 40 | -------------------------------------------------------------------------------- /render_library/Materials/DiffuseLight.cpp: -------------------------------------------------------------------------------- 1 | #include "Materials/DiffuseLight.hpp" 2 | #include "HitRecord.hpp" 3 | #include "ScatterRecord.hpp" 4 | #include "Textures/SolidColor.hpp" 5 | 6 | using namespace std; 7 | 8 | 9 | DiffuseLight::DiffuseLight(const shared_ptr &a) NOEXCEPT : 10 | _emit(a) 11 | { } 12 | 13 | DiffuseLight::DiffuseLight(const Vec3 &clr) NOEXCEPT : 14 | _emit(make_shared(clr)) 15 | { } 16 | 17 | shared_ptr DiffuseLight::deep_copy() const NOEXCEPT { 18 | // Do deep copy 19 | auto dl = make_shared(*this); 20 | dl->_emit = _emit->deep_copy(); 21 | 22 | return dl; 23 | } 24 | 25 | bool DiffuseLight::scatter( 26 | [[maybe_unused]] RandomGenerator &rng, 27 | [[maybe_unused]] const Ray &r_in, 28 | [[maybe_unused]] const HitRecord &h_rec, 29 | [[maybe_unused]] ScatterRecord &s_rec, 30 | [[maybe_unused]] const RenderMethod method 31 | ) const NOEXCEPT { 32 | return false; 33 | } 34 | 35 | rreal DiffuseLight::scattering_pdf( 36 | [[maybe_unused]] const Ray &r_in, 37 | [[maybe_unused]] const HitRecord &h_rec, 38 | [[maybe_unused]] const Ray &scattered 39 | ) const NOEXCEPT 40 | { return 0; } 41 | 42 | Vec3 DiffuseLight::emitted( 43 | [[maybe_unused]] const Ray &r_in, 44 | const HitRecord &h_rec, 45 | const rreal u, 46 | const rreal v, 47 | const Vec3 &p, 48 | const RenderMethod method 49 | ) const NOEXCEPT { 50 | Vec3 emission(0); // Default emission is pitch black 51 | 52 | if (method == RenderMethod::Books1And2) 53 | { 54 | emission = _emit->value(u, v, p); 55 | } 56 | else 57 | { 58 | // Book 3 59 | if (h_rec.front_face) 60 | emission = _emit->value(u, v, p); 61 | } 62 | 63 | return emission; 64 | } 65 | -------------------------------------------------------------------------------- /render_library/Materials/DiffuseLight.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Interfaces/IMaterial.hpp" 4 | #include "Interfaces/ITexture.hpp" 5 | #include 6 | 7 | 8 | class DiffuseLight FINAL : public IMaterial { 9 | private: 10 | // Data 11 | std::shared_ptr _emit; 12 | 13 | public: 14 | explicit DiffuseLight(const std::shared_ptr &a) NOEXCEPT; 15 | explicit DiffuseLight(const Vec3 &clr) NOEXCEPT; 16 | 17 | std::shared_ptr deep_copy() const NOEXCEPT override; 18 | 19 | bool scatter( 20 | RandomGenerator &rng, 21 | const Ray &r_in, 22 | const HitRecord &h_rec, 23 | ScatterRecord &s_rec, 24 | const RenderMethod method 25 | ) const NOEXCEPT override; 26 | 27 | virtual rreal scattering_pdf( 28 | const Ray &r_in, 29 | const HitRecord &h_rec, 30 | const Ray &scattered 31 | ) const NOEXCEPT override; 32 | 33 | virtual Vec3 emitted( 34 | const Ray &r_in, 35 | const HitRecord &h_rec, 36 | const rreal u, 37 | const rreal v, 38 | const Vec3 &p, 39 | const RenderMethod method 40 | ) const NOEXCEPT override; 41 | }; 42 | -------------------------------------------------------------------------------- /render_library/Materials/Isotropic.cpp: -------------------------------------------------------------------------------- 1 | #include "Materials/Isotropic.hpp" 2 | #include "Textures/SolidColor.hpp" 3 | #include "HitRecord.hpp" 4 | #include "ScatterRecord.hpp" 5 | #include "RandomGenerator.hpp" 6 | using namespace std; 7 | 8 | 9 | Isotropic::Isotropic(const Vec3 &clr) NOEXCEPT : 10 | _albedo(make_shared(clr)) 11 | { } 12 | 13 | Isotropic::Isotropic(const shared_ptr &albedo) NOEXCEPT : 14 | _albedo(albedo) 15 | { } 16 | 17 | shared_ptr Isotropic::deep_copy() const NOEXCEPT { 18 | // Do deep copy 19 | auto iso = make_shared(*this); 20 | iso->_albedo = _albedo->deep_copy(); 21 | 22 | return iso; 23 | } 24 | 25 | bool Isotropic::scatter( 26 | RandomGenerator &rng, 27 | const Ray &r_in, 28 | const HitRecord &h_rec, 29 | ScatterRecord &s_rec, 30 | const RenderMethod method 31 | ) const NOEXCEPT { 32 | s_rec.ray = Ray(h_rec.p, rng.get_in_unit_sphere(), r_in.time); 33 | s_rec.attenuation = _albedo->value(h_rec.u, h_rec.v, h_rec.p); 34 | 35 | if (method == RenderMethod::Book3) 36 | { 37 | // Note: This wasn't in the book's code, so I had to add it here. 38 | // 39 | // If you look at the github for the book 3's reference code, you'll see they just `#if 0 ... #endf` 40 | // the entire `Isotropic::scatter()` method. 41 | s_rec.is_specular = false; 42 | 43 | #ifdef USE_BOOK_PDF_MANAGEMENT 44 | s_rec.pdf_ptr = nullptr; // TODO should this be a Cosine PDF? 45 | #else 46 | s_rec.pdf = monostate(); 47 | #endif 48 | } 49 | 50 | return true; 51 | } 52 | 53 | rreal Isotropic::scattering_pdf( 54 | [[maybe_unused]] const Ray &r_in, 55 | [[maybe_unused]] const HitRecord &h_rec, 56 | [[maybe_unused]] const Ray &scattered 57 | ) const NOEXCEPT 58 | { return 0; } 59 | 60 | Vec3 Isotropic::emitted( 61 | [[maybe_unused]] const Ray &r_in, 62 | [[maybe_unused]] const HitRecord &h_rec, 63 | [[maybe_unused]] const rreal u, 64 | [[maybe_unused]] const rreal v, 65 | [[maybe_unused]] const Vec3 &p, 66 | [[maybe_unused]] const RenderMethod method 67 | ) const NOEXCEPT { 68 | return Vec3(0); 69 | } 70 | -------------------------------------------------------------------------------- /render_library/Materials/Isotropic.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Interfaces/IMaterial.hpp" 4 | #include 5 | class ITexture; 6 | 7 | 8 | class Isotropic FINAL : public IMaterial { 9 | private: 10 | // Data 11 | std::shared_ptr _albedo; 12 | 13 | public: 14 | explicit Isotropic(const Vec3 &clr) NOEXCEPT; 15 | explicit Isotropic(const std::shared_ptr &albedo) NOEXCEPT; 16 | 17 | std::shared_ptr deep_copy() const NOEXCEPT override; 18 | 19 | bool scatter( 20 | RandomGenerator &rng, 21 | const Ray &r_in, 22 | const HitRecord &h_rec, 23 | ScatterRecord &s_rec, 24 | const RenderMethod method 25 | ) const NOEXCEPT override; 26 | 27 | virtual rreal scattering_pdf( 28 | const Ray &r_in, 29 | const HitRecord &h_rec, 30 | const Ray &scattered 31 | ) const NOEXCEPT override; 32 | 33 | virtual Vec3 emitted( 34 | const Ray &r_in, 35 | const HitRecord &h_rec, 36 | const rreal u, 37 | const rreal v, 38 | const Vec3 &p, 39 | const RenderMethod method 40 | ) const NOEXCEPT override; 41 | }; 42 | -------------------------------------------------------------------------------- /render_library/Materials/Lambertian.cpp: -------------------------------------------------------------------------------- 1 | #include "Materials/Lambertian.hpp" 2 | #include "HitRecord.hpp" 3 | #include "ScatterRecord.hpp" 4 | #include "Textures/SolidColor.hpp" 5 | #include "RandomGenerator.hpp" 6 | #include "ONB.hpp" 7 | #include "PDFs/CosinePDF.hpp" 8 | 9 | using namespace std; 10 | 11 | 12 | Lambertian::Lambertian(const Vec3 &solid_color) NOEXCEPT : 13 | Lambertian(make_shared(solid_color)) 14 | { } 15 | 16 | Lambertian::Lambertian(const shared_ptr &tex) NOEXCEPT : 17 | _albedo(tex) 18 | { } 19 | 20 | shared_ptr Lambertian::deep_copy() const NOEXCEPT { 21 | // Do deep copy 22 | auto lamb = make_shared(*this); 23 | lamb->_albedo = _albedo->deep_copy(); 24 | 25 | return lamb; 26 | } 27 | 28 | bool Lambertian::scatter( 29 | RandomGenerator &rng, 30 | const Ray &r_in, 31 | const HitRecord &h_rec, 32 | ScatterRecord &s_rec, 33 | const RenderMethod method 34 | ) const NOEXCEPT { 35 | if (method == RenderMethod::Books1And2) 36 | { 37 | const Vec3 scatter_direction = h_rec.normal + rng.get_unit_vector(); 38 | s_rec.ray = Ray(h_rec.p, scatter_direction, r_in.time); 39 | } 40 | else 41 | { 42 | // Book 3 43 | s_rec.is_specular = false; 44 | 45 | #ifdef USE_BOOK_PDF_MANAGEMENT 46 | s_rec.pdf_ptr = make_shared(h_rec.normal); 47 | #else 48 | s_rec.pdf = CosinePDF(h_rec.normal); 49 | #endif 50 | } 51 | 52 | s_rec.attenuation = _albedo->value(h_rec.u, h_rec.v, h_rec.p); 53 | 54 | return true; 55 | } 56 | 57 | rreal Lambertian::scattering_pdf( 58 | [[maybe_unused]] const Ray &r_in, 59 | const HitRecord &h_rec, 60 | const Ray &scattered 61 | ) const NOEXCEPT { 62 | const rreal cosine = h_rec.normal.dot(scattered.direction.unit_vector()); 63 | return (cosine < 0) ? 0 : (cosine / Pi); 64 | } 65 | 66 | Vec3 Lambertian::emitted( 67 | [[maybe_unused]] const Ray &r_in, 68 | [[maybe_unused]] const HitRecord &h_rec, 69 | [[maybe_unused]] const rreal u, 70 | [[maybe_unused]] const rreal v, 71 | [[maybe_unused]] const Vec3 &p, 72 | [[maybe_unused]] const RenderMethod method 73 | ) const NOEXCEPT { 74 | return Vec3(0); 75 | } 76 | -------------------------------------------------------------------------------- /render_library/Materials/Lambertian.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Interfaces/IMaterial.hpp" 4 | #include 5 | class ITexture; 6 | 7 | 8 | class Lambertian FINAL : public IMaterial { 9 | private: 10 | // Data 11 | std::shared_ptr _albedo; 12 | 13 | public: 14 | explicit Lambertian(const Vec3 &solid_color) NOEXCEPT; 15 | explicit Lambertian(const std::shared_ptr &tex) NOEXCEPT; 16 | 17 | std::shared_ptr deep_copy() const NOEXCEPT override; 18 | 19 | bool scatter( 20 | RandomGenerator &rng, 21 | const Ray &r_in, 22 | const HitRecord &h_rec, 23 | ScatterRecord &s_rec, 24 | const RenderMethod method 25 | ) const NOEXCEPT override; 26 | 27 | virtual rreal scattering_pdf( 28 | const Ray &r_in, 29 | const HitRecord &h_rec, 30 | const Ray &scattered 31 | ) const NOEXCEPT override; 32 | 33 | virtual Vec3 emitted( 34 | const Ray &r_in, 35 | const HitRecord &h_rec, 36 | const rreal u, 37 | const rreal v, 38 | const Vec3 &p, 39 | const RenderMethod method 40 | ) const NOEXCEPT override; 41 | }; 42 | -------------------------------------------------------------------------------- /render_library/Materials/Metal.cpp: -------------------------------------------------------------------------------- 1 | #include "Materials/Metal.hpp" 2 | #include "Ray.hpp" 3 | #include "HitRecord.hpp" 4 | #include "ScatterRecord.hpp" 5 | #include "RandomGenerator.hpp" 6 | 7 | using namespace std; 8 | 9 | 10 | Metal::Metal(const Vec3 &albedo, const rreal fuzz) NOEXCEPT : 11 | _albedo(albedo), 12 | _fuzz(std::min(fuzz, static_cast(1))) 13 | { } 14 | 15 | shared_ptr Metal::deep_copy() const NOEXCEPT { 16 | return make_shared(*this); 17 | } 18 | 19 | bool Metal::scatter( 20 | RandomGenerator &rng, 21 | const Ray &r_in, 22 | const HitRecord &h_rec, 23 | ScatterRecord &s_rec, 24 | const RenderMethod method 25 | ) const NOEXCEPT { 26 | const Vec3 reflected = r_in.direction.unit_vector().reflect(h_rec.normal); 27 | s_rec.ray = Ray(h_rec.p, reflected + (_fuzz * rng.get_in_unit_sphere()), r_in.time); 28 | s_rec.attenuation = _albedo; 29 | 30 | // By default metal will always scatter (i.e. for book 3) 31 | bool do_scatter = true; 32 | 33 | if (method == RenderMethod::Books1And2) 34 | { 35 | // Though in books 1 & 2, we check the reflected ray vs. the hit normal 36 | do_scatter = (s_rec.ray.direction.dot(h_rec.normal) > 0); 37 | } 38 | else 39 | { 40 | s_rec.is_specular = true; 41 | 42 | #ifdef USE_BOOK_PDF_MANAGEMENT 43 | s_rec.pdf_ptr = nullptr; 44 | #else 45 | s_rec.pdf = monostate(); 46 | #endif 47 | } 48 | 49 | return do_scatter; 50 | } 51 | 52 | rreal Metal::scattering_pdf( 53 | [[maybe_unused]] const Ray &r_in, 54 | [[maybe_unused]] const HitRecord &h_rec, 55 | [[maybe_unused]] const Ray &scattered 56 | ) const NOEXCEPT 57 | { return 0; } 58 | 59 | Vec3 Metal::emitted( 60 | [[maybe_unused]] const Ray &r_in, 61 | [[maybe_unused]] const HitRecord &h_rec, 62 | [[maybe_unused]] const rreal u, 63 | [[maybe_unused]] const rreal v, 64 | [[maybe_unused]] const Vec3 &p, 65 | [[maybe_unused]] const RenderMethod method 66 | ) const NOEXCEPT { 67 | return Vec3(0); 68 | } 69 | -------------------------------------------------------------------------------- /render_library/Materials/Metal.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Interfaces/IMaterial.hpp" 4 | 5 | 6 | class Metal FINAL : public IMaterial { 7 | public: 8 | // Data 9 | Vec3 _albedo; 10 | rreal _fuzz; 11 | 12 | public: 13 | explicit Metal(const Vec3 &albedo, const rreal fuzz) NOEXCEPT; 14 | 15 | std::shared_ptr deep_copy() const NOEXCEPT override; 16 | 17 | bool scatter( 18 | RandomGenerator &rng, 19 | const Ray &r_in, 20 | const HitRecord &h_rec, 21 | ScatterRecord &s_rec, 22 | const RenderMethod method 23 | ) const NOEXCEPT override; 24 | 25 | virtual rreal scattering_pdf( 26 | const Ray &r_in, 27 | const HitRecord &h_rec, 28 | const Ray &scattered 29 | ) const NOEXCEPT override; 30 | 31 | virtual Vec3 emitted( 32 | const Ray &r_in, 33 | const HitRecord &h_rec, 34 | const rreal u, 35 | const rreal v, 36 | const Vec3 &p, 37 | const RenderMethod method 38 | ) const NOEXCEPT override; 39 | }; 40 | -------------------------------------------------------------------------------- /render_library/Materials/SurfaceNormal.cpp: -------------------------------------------------------------------------------- 1 | #include "Materials/SurfaceNormal.hpp" 2 | #include 3 | #include "HitRecord.hpp" 4 | #include "ScatterRecord.hpp" 5 | #include "RandomGenerator.hpp" 6 | #include "PDFs/CosinePDF.hpp" 7 | 8 | using namespace std; 9 | 10 | 11 | SurfaceNormal::SurfaceNormal(const rreal brightness_, const rreal saturation_) NOEXCEPT : 12 | brightness(brightness_), 13 | saturation(saturation_) 14 | { } 15 | 16 | shared_ptr SurfaceNormal::deep_copy() const NOEXCEPT { 17 | return make_shared(*this); 18 | } 19 | 20 | bool SurfaceNormal::scatter( 21 | RandomGenerator &rng, 22 | const Ray &r_in, 23 | const HitRecord &h_rec, 24 | ScatterRecord &s_rec, 25 | const RenderMethod method 26 | ) const NOEXCEPT { 27 | const Vec3 scatter_direction = h_rec.normal + rng.get_unit_vector(); 28 | const Vec3 a = (saturation * h_rec.normal) + Vec3(static_cast(brightness)); 29 | 30 | s_rec.attenuation = Vec3(a.x * a.x, a.y * a.y, a.z * a.z); 31 | s_rec.ray = Ray(h_rec.p, scatter_direction, r_in.time); 32 | 33 | if (method == RenderMethod::Book3) 34 | { 35 | // Since this material is very close to Lambertian, we use a cosine PDF 36 | s_rec.is_specular = false; 37 | 38 | #ifdef USE_BOOK_PDF_MANAGEMENT 39 | s_rec.pdf_ptr = make_shared(h_rec.normal); 40 | #else 41 | s_rec.pdf = CosinePDF(h_rec.normal); 42 | #endif 43 | } 44 | 45 | return true; 46 | } 47 | 48 | rreal SurfaceNormal::scattering_pdf( 49 | [[maybe_unused]] const Ray &r_in, 50 | const HitRecord &h_rec, 51 | const Ray &scattered 52 | ) const NOEXCEPT { 53 | // Since this material is very close to a Lambertian, we use a cosine PDF 54 | const rreal cosine = h_rec.normal.dot(scattered.direction.unit_vector()); 55 | return (cosine < 0) ? 0 : (cosine / Pi); 56 | } 57 | 58 | Vec3 SurfaceNormal::emitted( 59 | [[maybe_unused]] const Ray &r_in, 60 | [[maybe_unused]] const HitRecord &h_rec, 61 | [[maybe_unused]] const rreal u, 62 | [[maybe_unused]] const rreal v, 63 | [[maybe_unused]] const Vec3 &p, 64 | [[maybe_unused]] const RenderMethod method 65 | ) const NOEXCEPT { 66 | return Vec3(0); 67 | } 68 | -------------------------------------------------------------------------------- /render_library/Materials/SurfaceNormal.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Interfaces/IMaterial.hpp" 4 | 5 | 6 | // This is a material that shows the normal of a object's surface 7 | class SurfaceNormal FINAL : public IMaterial { 8 | public: 9 | // Data 10 | rreal brightness = static_cast(0.6); 11 | rreal saturation = 0.75; 12 | 13 | public: 14 | explicit SurfaceNormal() NOEXCEPT = default; 15 | explicit SurfaceNormal(const rreal brightness_, const rreal saturation_) NOEXCEPT; 16 | 17 | std::shared_ptr deep_copy() const NOEXCEPT override; 18 | 19 | bool scatter( 20 | RandomGenerator &rng, 21 | const Ray &r_in, 22 | const HitRecord &h_rec, 23 | ScatterRecord &s_rec, 24 | const RenderMethod method 25 | ) const NOEXCEPT override; 26 | 27 | rreal scattering_pdf( 28 | const Ray &r_in, 29 | const HitRecord &h_rec, 30 | const Ray &scattered 31 | ) const NOEXCEPT override; 32 | 33 | Vec3 emitted( 34 | const Ray &r_in, 35 | const HitRecord &h_rec, 36 | const rreal u, 37 | const rreal v, 38 | const Vec3 &p, 39 | const RenderMethod method 40 | ) const NOEXCEPT override; 41 | }; 42 | -------------------------------------------------------------------------------- /render_library/ONB.cpp: -------------------------------------------------------------------------------- 1 | #include "ONB.hpp" 2 | 3 | using namespace std; 4 | 5 | 6 | void ONB::build_from_w(const Vec3 &n) NOEXCEPT { 7 | axis[2] = n.unit_vector(); 8 | 9 | const Vec3 a = (fabs(w().x) > 0.9) ? Vec3(0, 1, 0) : Vec3(1, 0, 0); 10 | 11 | axis[1] = w().cross(a).unit_vector(); 12 | axis[0] = w().cross(v()); 13 | } 14 | -------------------------------------------------------------------------------- /render_library/ONB.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Vec3.hpp" 4 | 5 | // An Orthonormal Basis utility class 6 | class ONB { 7 | public: 8 | // Data 9 | Vec3 axis[3]; 10 | 11 | inline Vec3 operator[](const int i) const NOEXCEPT { return axis[i]; } 12 | 13 | inline Vec3 u() const NOEXCEPT { return axis[0]; } 14 | inline Vec3 v() const NOEXCEPT { return axis[1]; } 15 | inline Vec3 w() const NOEXCEPT { return axis[2]; } 16 | 17 | inline Vec3 local(const rreal a, const rreal b, const rreal c) const NOEXCEPT { 18 | return (a * u()) + 19 | (b * v()) + 20 | (c * w()); 21 | } 22 | 23 | inline Vec3 local(const Vec3 &a) const NOEXCEPT { 24 | return (a.x * u()) + 25 | (a.y * v()) + 26 | (a.z * w()); 27 | } 28 | 29 | void build_from_w(const Vec3 &n) NOEXCEPT; 30 | }; -------------------------------------------------------------------------------- /render_library/Objects/BVHNode.cpp: -------------------------------------------------------------------------------- 1 | #include "Objects/BVHNode.hpp" 2 | #include 3 | #include 4 | #include "Objects/HittableList.hpp" 5 | #include "RandomGenerator.hpp" 6 | 7 | using namespace std; 8 | 9 | 10 | BVHNode::BVHNode(RandomGenerator &rng, const HittableList &list, const rreal time0, const rreal time1) : 11 | BVHNode(rng, list._objects, 0, list._objects.size(), time0, time1) 12 | { } 13 | 14 | BVHNode::BVHNode( 15 | RandomGenerator &rng, 16 | vector> objects, 17 | const size_t start, 18 | const size_t end, 19 | const rreal time0, 20 | const rreal time1 21 | ) { 22 | // Compare along a random axis 23 | const int axis = rng.get_int(0, 2); 24 | auto comparator = box_x_compare; 25 | if (axis == 1) 26 | comparator = box_y_compare; 27 | else if (axis == 2) 28 | comparator = box_z_compare; 29 | 30 | // Split up objects 31 | const size_t object_span = end - start; 32 | if (object_span == 1) 33 | _left = _right = objects[start]; 34 | else if (object_span == 2) { 35 | shared_ptr a = objects[start]; 36 | shared_ptr b = objects[start + 1]; 37 | if (comparator(a, b)) { 38 | _left = a; 39 | _right = b; 40 | } else { 41 | _left = b; 42 | _right = a; 43 | } 44 | } else { 45 | sort( 46 | next(objects.begin(), static_cast(start)), 47 | next(objects.begin(), static_cast(end)), 48 | comparator 49 | ); 50 | 51 | const size_t mid = start + (object_span / 2); 52 | _left = make_shared(rng, objects, start, mid, time0, time1); 53 | _right = make_shared(rng, objects, mid, end, time0, time1); 54 | } 55 | 56 | AABB box_left, box_right; 57 | 58 | const bool not_box_left = !_left->bounding_box(time0, time1, box_left); 59 | const bool not_box_right = !_right->bounding_box(time0, time1, box_right); 60 | if (not_box_left || not_box_right) 61 | throw runtime_error("No bounding box in BVHNode constructor"); 62 | 63 | _box = AABB::surrounding(box_left, box_right); 64 | } 65 | 66 | shared_ptr BVHNode::deep_copy() const NOEXCEPT { 67 | auto node = make_shared(*this); 68 | node->_left = _left->deep_copy(); 69 | node->_right = _right->deep_copy(); 70 | 71 | return node; 72 | } 73 | 74 | bool BVHNode::hit( 75 | RandomGenerator &rng, 76 | const Ray &r, 77 | const rreal t_min, 78 | const rreal t_max, 79 | HitRecord &rec 80 | ) const NOEXCEPT { 81 | // If it didn't hit our box, then it didn't hit any children 82 | if (!_box.hit(r, t_min, t_max)) 83 | return false; 84 | 85 | const bool hit_left = _left->hit(rng, r, t_min, t_max, rec); 86 | const bool hit_right = _right->hit(rng, r, t_min, (hit_left ? rec.t : t_max), rec); 87 | 88 | return (hit_left || hit_right); 89 | } 90 | 91 | bool BVHNode::bounding_box( 92 | [[maybe_unused]] const rreal t0, 93 | [[maybe_unused]] const rreal t1, 94 | AABB &output_box 95 | ) const NOEXCEPT { 96 | output_box = _box; 97 | return true; 98 | } 99 | 100 | bool box_compare(const std::shared_ptr &a, const std::shared_ptr &b, const Vec3::Axis &axis) { 101 | AABB box_a, box_b; 102 | 103 | // TODO what about time parameters? 104 | if (!a->bounding_box(0, 0, box_a) || !b->bounding_box(0, 0, box_b)) 105 | throw runtime_error("No bounding box in BVHNode constructor [box_compare]"); 106 | 107 | // original implementation used indexing here 108 | // but that is because of how the Vec3 class was structured 109 | switch (axis) { 110 | case Vec3::Axis::X: return (box_a.min.x < box_b.min.x); 111 | case Vec3::Axis::Y: return (box_a.min.y < box_b.min.y); 112 | case Vec3::Axis::Z: return (box_a.min.z < box_b.min.z); 113 | } 114 | 115 | // Should never hit this case 116 | throw runtime_error("Proper Vec3::Axis value was never provided"); 117 | } 118 | -------------------------------------------------------------------------------- /render_library/Objects/BVHNode.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Interfaces/IHittable.hpp" 4 | #include 5 | #include "AABB.hpp" 6 | class HittableList; 7 | 8 | 9 | class BVHNode FINAL : public IHittable { 10 | private: 11 | // Data 12 | std::shared_ptr _left = nullptr; 13 | std::shared_ptr _right = nullptr; 14 | AABB _box; 15 | 16 | public: 17 | explicit BVHNode() = default; 18 | explicit BVHNode(RandomGenerator &rng, const HittableList &list, const rreal time0, const rreal time1); 19 | explicit BVHNode( 20 | RandomGenerator &rng, 21 | std::vector> objects, 22 | const size_t start, 23 | const size_t end, 24 | const rreal time0, 25 | const rreal time1 26 | ); 27 | 28 | std::shared_ptr deep_copy() const NOEXCEPT override; 29 | 30 | bool hit(RandomGenerator &rng, const Ray &r, const rreal t_min, const rreal t_max, HitRecord &rec) const NOEXCEPT override; 31 | bool bounding_box(const rreal t0, const rreal t1, AABB &output_box) const NOEXCEPT override; 32 | 33 | rreal pdf_value( 34 | [[maybe_unused]] RandomGenerator &rng, 35 | [[maybe_unused]] const Vec3 &origin, 36 | [[maybe_unused]] const Vec3 &v 37 | ) const NOEXCEPT override { 38 | return 0; 39 | } 40 | 41 | Vec3 random( 42 | [[maybe_unused]] RandomGenerator &rng, 43 | [[maybe_unused]] const Vec3 &origin 44 | ) const NOEXCEPT override { 45 | return Vec3(1, 0, 0); 46 | } 47 | }; 48 | 49 | // Box comparisons 50 | bool box_compare(const std::shared_ptr &a, const std::shared_ptr &b, const Vec3::Axis &axis); 51 | 52 | inline bool box_x_compare(const std::shared_ptr &a, const std::shared_ptr &b) { 53 | return box_compare(a, b, Vec3::Axis::X); 54 | } 55 | 56 | inline bool box_y_compare(const std::shared_ptr &a, const std::shared_ptr &b) { 57 | return box_compare(a, b, Vec3::Axis::Y); 58 | } 59 | 60 | inline bool box_z_compare(const std::shared_ptr &a, const std::shared_ptr &b) { 61 | return box_compare(a, b, Vec3::Axis::Z); 62 | } 63 | -------------------------------------------------------------------------------- /render_library/Objects/Box.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Interfaces/IHittable.hpp" 4 | #include "Objects/HittableList.hpp" 5 | #include "AABB.hpp" 6 | class BVHNode; 7 | class RandomGenerator; 8 | 9 | 10 | class Box FINAL : public IHittable { 11 | private: 12 | // Data 13 | Vec3 _box_min = Vec3(0); 14 | Vec3 _box_max = Vec3(0); 15 | 16 | #ifdef USE_BOOK_BOX 17 | // The book's implementation uses a `HittableList` to store objects 18 | HittableList _sides{}; 19 | #else 20 | // In out implementation, we need to store an AABB and the material pointer ourselves 21 | AABB _aabb; 22 | std::shared_ptr _mat; 23 | #endif // USE_BOOK_BOX 24 | 25 | public: 26 | explicit Box(const Vec3 &point_min, const Vec3 &point_max, const std::shared_ptr &mat) NOEXCEPT; 27 | 28 | std::shared_ptr deep_copy() const NOEXCEPT override; 29 | 30 | bool hit(RandomGenerator &rng, const Ray &r, const rreal t_min, const rreal t_max, HitRecord &rec) const NOEXCEPT override; 31 | bool bounding_box(const rreal t0, const rreal t1, AABB &output_box) const NOEXCEPT override; 32 | 33 | rreal pdf_value( 34 | [[maybe_unused]] RandomGenerator &rng, 35 | [[maybe_unused]] const Vec3 &origin, 36 | [[maybe_unused]] const Vec3 &v 37 | ) const NOEXCEPT override { 38 | return 0; 39 | } 40 | 41 | Vec3 random( 42 | [[maybe_unused]] RandomGenerator &rng, 43 | [[maybe_unused]] const Vec3 &origin 44 | ) const NOEXCEPT override { 45 | return Vec3(1, 0, 0); 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /render_library/Objects/ConstantMedium.cpp: -------------------------------------------------------------------------------- 1 | #include "Objects/ConstantMedium.hpp" 2 | #include 3 | #include "Materials/Isotropic.hpp" 4 | #include "Interfaces/ITexture.hpp" 5 | #include "RandomGenerator.hpp" 6 | 7 | using namespace std; 8 | 9 | 10 | ConstantMedium::ConstantMedium( 11 | const shared_ptr &boundary, 12 | const rreal density, 13 | const shared_ptr &phase 14 | ) NOEXCEPT : 15 | _boundary(boundary), 16 | _phase_function(make_shared(phase)), 17 | _neg_inv_intensity(static_cast(-1) / density) 18 | { } 19 | 20 | ConstantMedium::ConstantMedium( 21 | const shared_ptr &boundary, 22 | const rreal density, 23 | const Vec3 &clr 24 | ) NOEXCEPT : 25 | _boundary(boundary), 26 | _phase_function(make_shared(clr)), 27 | _neg_inv_intensity(static_cast(-1) / density) 28 | { } 29 | 30 | std::shared_ptr ConstantMedium::deep_copy() const NOEXCEPT { 31 | auto cm = make_shared(*this); 32 | cm->_boundary = _boundary->deep_copy(); 33 | cm->_phase_function = _phase_function->deep_copy(); 34 | 35 | return cm; 36 | } 37 | 38 | bool ConstantMedium::hit(RandomGenerator &rng, const Ray &r, const rreal t_min, const rreal t_max, HitRecord &rec) const NOEXCEPT { 39 | HitRecord rec1, rec2; 40 | 41 | if (!_boundary->hit(rng, r, -Infinity, Infinity, rec1)) 42 | return false; 43 | 44 | if (!_boundary->hit(rng, r, rec1.t + static_cast(0.0001), Infinity, rec2)) 45 | return false; 46 | 47 | rec1.t = max(rec1.t, t_min); 48 | rec2.t = min(rec2.t, t_max); 49 | 50 | if (rec1.t >= rec2.t) 51 | return false; 52 | 53 | rec1.t = max(rec1.t, static_cast(0)); 54 | 55 | const rreal ray_length = r.direction.length(); 56 | const rreal distance_inside_boundary = (rec2.t - rec1.t) * ray_length; 57 | const rreal hit_distance = _neg_inv_intensity * log(rng.get_real()); 58 | 59 | if (hit_distance > distance_inside_boundary) 60 | return false; 61 | 62 | rec.t = rec1.t + (hit_distance / ray_length); 63 | rec.p = r.at(rec.t); 64 | 65 | rec.normal = Vec3(1, 0, 0); // Arbitrary 66 | rec.front_face = true; // Also arbitrary 67 | rec.set_mat_ptr(_phase_function); 68 | 69 | return true; 70 | } 71 | 72 | bool ConstantMedium::bounding_box(const rreal t0, const rreal t1, AABB &output_box) const NOEXCEPT { 73 | return _boundary->bounding_box(t0, t1, output_box); 74 | } 75 | -------------------------------------------------------------------------------- /render_library/Objects/ConstantMedium.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Interfaces/IHittable.hpp" 4 | class IMaterial; 5 | class ITexture; 6 | 7 | 8 | class ConstantMedium FINAL : public IHittable { 9 | private: 10 | // Data 11 | std::shared_ptr _boundary = nullptr; 12 | std::shared_ptr _phase_function = nullptr; 13 | rreal _neg_inv_intensity = 0; 14 | 15 | public: 16 | explicit ConstantMedium( 17 | const std::shared_ptr &boundary, 18 | const rreal density, 19 | const std::shared_ptr &phase 20 | ) NOEXCEPT; 21 | 22 | explicit ConstantMedium( 23 | const std::shared_ptr &boundary, 24 | const rreal density, 25 | const Vec3 &clr 26 | ) NOEXCEPT; 27 | 28 | std::shared_ptr deep_copy() const NOEXCEPT override; 29 | 30 | bool hit(RandomGenerator &rng, const Ray &r, const rreal t_min, const rreal t_max, HitRecord &rec) const NOEXCEPT override; 31 | bool bounding_box(const rreal t0, const rreal t1, AABB &output_box) const NOEXCEPT override; 32 | 33 | rreal pdf_value( 34 | [[maybe_unused]] RandomGenerator &rng, 35 | [[maybe_unused]] const Vec3 &origin, 36 | [[maybe_unused]] const Vec3 &v 37 | ) const NOEXCEPT override { 38 | return 0; 39 | } 40 | 41 | Vec3 random( 42 | [[maybe_unused]] RandomGenerator &rng, 43 | [[maybe_unused]] const Vec3 &origin 44 | ) const NOEXCEPT override { 45 | return Vec3(1, 0, 0); 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /render_library/Objects/HittableList.cpp: -------------------------------------------------------------------------------- 1 | #include "Objects/HittableList.hpp" 2 | #include "AABB.hpp" 3 | #include "RandomGenerator.hpp" 4 | 5 | using namespace std; 6 | 7 | 8 | HittableList::HittableList(const shared_ptr &object) NOEXCEPT { 9 | add(object); 10 | } 11 | 12 | shared_ptr HittableList::deep_copy() const NOEXCEPT { 13 | auto hl = make_shared(*this); 14 | 15 | // Need to clear out the list and then do a deep copy on all objects 16 | hl->clear(); 17 | for (const auto &obj : _objects) 18 | hl->add(obj->deep_copy()); 19 | 20 | return hl; 21 | } 22 | 23 | void HittableList::clear() NOEXCEPT { 24 | _objects.clear(); 25 | } 26 | 27 | void HittableList::add(const shared_ptr &object) NOEXCEPT { 28 | _objects.push_back(object); 29 | } 30 | 31 | bool HittableList::hit(RandomGenerator &rng, const Ray &r, const rreal t_min, const rreal t_max, HitRecord &rec) const NOEXCEPT { 32 | HitRecord temp_rec{}; 33 | bool hit_anything = false; 34 | rreal closest_so_far = t_max; 35 | 36 | // TODO possible to sort objects in some manor to gain speed bump? 37 | 38 | for (const auto & obj : _objects) { 39 | if (obj->hit(rng, r, t_min, closest_so_far, temp_rec)) { 40 | hit_anything = true; 41 | closest_so_far = temp_rec.t; 42 | rec = temp_rec; 43 | } 44 | } 45 | 46 | return hit_anything; 47 | } 48 | 49 | bool HittableList::bounding_box( 50 | const rreal t0, 51 | const rreal t1, 52 | AABB &output_box 53 | ) const NOEXCEPT { 54 | if (_objects.empty()) 55 | return false; 56 | 57 | // summate all of the boxes 58 | AABB temp_box; 59 | bool first_box = true; 60 | 61 | for (const auto &obj : _objects) { 62 | // TODO [possible bug], what if a hittable list also contains an empty hittable list?, but in the parent list, there are more elements after the (empty) child list 63 | if (!obj->bounding_box(t0, t1, temp_box)) 64 | return false; 65 | 66 | output_box = first_box ? temp_box : AABB::surrounding(output_box, temp_box); 67 | first_box = false; 68 | } 69 | 70 | return true; 71 | } 72 | 73 | rreal HittableList::pdf_value(RandomGenerator &rng, const Vec3 &origin, const Vec3 &v) const NOEXCEPT { 74 | // `rreal` is an alias for `double` 75 | const rreal weight = 1.0 / static_cast(_objects.size()); 76 | rreal sum = 0; 77 | 78 | for (const shared_ptr &obj : _objects) 79 | sum += (weight * obj->pdf_value(rng, origin, v)); 80 | 81 | return sum; 82 | } 83 | 84 | Vec3 HittableList::random(RandomGenerator &rng, const Vec3 &origin) const NOEXCEPT { 85 | const auto int_size = static_cast(_objects.size()); 86 | const auto index = static_cast(rng.get_int(0, int_size - 1)); 87 | return _objects[index]->random(rng, origin); 88 | } 89 | -------------------------------------------------------------------------------- /render_library/Objects/HittableList.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "Common.hpp" 6 | #include "Interfaces/IHittable.hpp" 7 | 8 | 9 | class HittableList FINAL : public IHittable { 10 | public: 11 | // Data 12 | std::vector> _objects; 13 | 14 | public: 15 | explicit HittableList() NOEXCEPT = default; 16 | explicit HittableList(const std::shared_ptr &object) NOEXCEPT; 17 | 18 | std::shared_ptr deep_copy() const NOEXCEPT override; 19 | 20 | void clear() NOEXCEPT; 21 | void add(const std::shared_ptr &object) NOEXCEPT; 22 | 23 | bool hit(RandomGenerator &rng, const Ray &r, const rreal t_min, const rreal t_max, HitRecord &rec) const NOEXCEPT override; 24 | bool bounding_box(const rreal t0, const rreal t1, AABB &output_box) const NOEXCEPT override; 25 | rreal pdf_value(RandomGenerator &rng, const Vec3 &origin, const Vec3 &v) const NOEXCEPT override; 26 | Vec3 random(RandomGenerator &rng, const Vec3 &origin) const NOEXCEPT override; 27 | }; 28 | -------------------------------------------------------------------------------- /render_library/Objects/MovingSphere.cpp: -------------------------------------------------------------------------------- 1 | #include "Objects/MovingSphere.hpp" 2 | #include 3 | #include "Ray.hpp" 4 | #include "AABB.hpp" 5 | 6 | using namespace std; 7 | 8 | // TODO [refactoring] 9 | // - this shares a lot of common code with the `Sphere`, see if things can be unified 10 | // TODO [bug, feature] 11 | // - this doesn support textures (UV) like the regular sphere does look at at the `hit` function of the other 12 | 13 | 14 | MovingSphere::MovingSphere( 15 | const Vec3 ¢er0, const Vec3 ¢er1, 16 | const rreal time0, const rreal time1, 17 | const rreal r, 18 | const shared_ptr &mat_ptr 19 | ) NOEXCEPT : 20 | _center0(center0), _center1(center1), 21 | _time0(time0), _time1(time1), 22 | _radius(r), 23 | _mat_ptr(mat_ptr) 24 | { } 25 | 26 | std::shared_ptr MovingSphere::deep_copy() const NOEXCEPT { 27 | auto ms = make_shared(*this); 28 | ms->_mat_ptr = _mat_ptr->deep_copy(); 29 | 30 | return ms; 31 | } 32 | 33 | bool MovingSphere::hit( 34 | [[maybe_unused]] RandomGenerator &rng, 35 | const Ray &r, 36 | const rreal t_min, 37 | const rreal t_max, 38 | HitRecord &rec 39 | ) const NOEXCEPT { 40 | const Vec3 oc = r.origin - center(r.time); 41 | const Vec3 ray_dir = r.direction; 42 | const rreal a = ray_dir.length_squared(); 43 | const rreal half_b = oc.dot(ray_dir); 44 | const rreal c = oc.length_squared() - (_radius * _radius); 45 | const rreal discriminant = (half_b * half_b) - (a * c); 46 | 47 | #ifdef USE_BOOK_SPHERE_HIT 48 | if (discriminant > 0) { 49 | const rreal root = util::sqrt(discriminant); 50 | 51 | rreal temp = (-half_b - root) / a; 52 | if ((temp < t_max) && (temp > t_min)) { 53 | rec.t = temp; 54 | rec.p = r.at(rec.t); 55 | const Vec3 outward_normal = (rec.p - center(r.time)) / _radius; 56 | rec.set_face_normal(r, outward_normal); 57 | rec.mat_ptr = _mat_ptr; 58 | return true; 59 | } 60 | 61 | temp = (-half_b + root) / a; 62 | if ((temp < t_max) && (temp > t_min)) { 63 | rec.t = temp; 64 | rec.p = r.at(rec.t); 65 | const Vec3 outward_normal = (rec.p - center(r.time)) / _radius; 66 | rec.set_face_normal(r, outward_normal); 67 | rec.mat_ptr = _mat_ptr; 68 | return true; 69 | } 70 | } 71 | #else 72 | if (discriminant > 0) { 73 | const rreal root = util::sqrt(discriminant); 74 | const rreal temp1 = (-half_b - root) / a; 75 | const rreal temp2 = (-half_b + root) / a; 76 | const bool hit1 = (temp1 < t_max) && (temp1 > t_min); 77 | const bool hit2 = (temp2 < t_max) && (temp2 > t_min); 78 | 79 | if (hit1) { 80 | const Vec3 p1 = r.at(temp1); 81 | const Vec3 on1 = (p1 - center(r.time)) / _radius; // on = outward normal 82 | // sphere_get_uv(on1, rec.u, rec.v); 83 | 84 | rec.t = temp1; 85 | rec.p = p1; 86 | rec.set_face_normal(r, on1); 87 | rec.set_mat_ptr(_mat_ptr); 88 | return true; 89 | } 90 | 91 | if (hit2) { 92 | const Vec3 p2 = r.at(temp2); 93 | const Vec3 on2 = (p2 - center(r.time)) / _radius; 94 | // sphere_get_uv(on2, rec.u, rec.v); 95 | 96 | rec.t = temp2; 97 | rec.p = p2; 98 | rec.set_face_normal(r, on2); 99 | rec.set_mat_ptr(_mat_ptr); 100 | return true; 101 | } 102 | } 103 | #endif 104 | 105 | return false; 106 | } 107 | 108 | bool MovingSphere::bounding_box(const rreal t0, const rreal t1, AABB &output_box) const NOEXCEPT { 109 | const Vec3 r(abs(_radius)); 110 | const Vec3 c0 = center(t0); 111 | const Vec3 c1 = center(t1); 112 | 113 | const AABB box0(c0 - r, c0 + r); 114 | const AABB box1(c1 - r, c1 + r); 115 | 116 | output_box = AABB::surrounding(box0, box1); 117 | return true; 118 | } 119 | 120 | Vec3 MovingSphere::center(const rreal time) const NOEXCEPT { 121 | return _center0 + (((time - _time0) / (_time1 - _time0)) * (_center1 - _center0)); 122 | } 123 | -------------------------------------------------------------------------------- /render_library/Objects/MovingSphere.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Interfaces/IHittable.hpp" 4 | 5 | 6 | class MovingSphere FINAL : public IHittable { 7 | private: 8 | // Data 9 | Vec3 _center0 = Vec3(), _center1 = Vec3(); 10 | rreal _time0 = 0, _time1 = 0; 11 | rreal _radius = 0; 12 | std::shared_ptr _mat_ptr = nullptr; 13 | 14 | public: 15 | explicit MovingSphere() NOEXCEPT = default; 16 | explicit MovingSphere( 17 | const Vec3 ¢er0, const Vec3 ¢er1, 18 | const rreal time0, const rreal time1, 19 | const rreal r, 20 | const std::shared_ptr &mat_ptr 21 | ) NOEXCEPT; 22 | 23 | std::shared_ptr deep_copy() const NOEXCEPT override; 24 | 25 | bool hit(RandomGenerator &rng, const Ray &r, const rreal t_min, const rreal t_max, HitRecord &rec) const NOEXCEPT override; 26 | bool bounding_box(const rreal t0, const rreal t1, AABB &output_box) const NOEXCEPT override; 27 | 28 | rreal pdf_value( 29 | [[maybe_unused]] RandomGenerator &rng, 30 | [[maybe_unused]] const Vec3 &origin, 31 | [[maybe_unused]] const Vec3 &v 32 | ) const NOEXCEPT override { 33 | return 0; 34 | } 35 | 36 | Vec3 random( 37 | [[maybe_unused]] RandomGenerator &rng, 38 | [[maybe_unused]] const Vec3 &origin 39 | ) const NOEXCEPT override { 40 | return Vec3(1, 0, 0); 41 | } 42 | 43 | Vec3 center(const rreal time) const NOEXCEPT; 44 | }; 45 | -------------------------------------------------------------------------------- /render_library/Objects/Quad.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Interfaces/IHittable.hpp" 4 | #include "AABB.hpp" 5 | 6 | 7 | class Quad FINAL : public IHittable 8 | { 9 | private: 10 | Vec3 _Q = Vec3(); // Acts as a point of origin for the quad 11 | Vec3 _u = Vec3(); 12 | Vec3 _v = Vec3(); 13 | Vec3 _w = Vec3(); 14 | Vec3 _normal = Vec3(); 15 | rreal _area = 0; 16 | rreal _D = 0; 17 | AABB _bounding_box = AABB(); 18 | std::shared_ptr _mat_ptr = nullptr; 19 | 20 | public: 21 | explicit Quad(const Vec3 &origin, const Vec3 &u, const Vec3 &v, const std::shared_ptr &material) NOEXCEPT; 22 | 23 | std::shared_ptr deep_copy() const NOEXCEPT override; 24 | 25 | bool hit(RandomGenerator &rng, const Ray &r, const rreal t_min, const rreal t_max, HitRecord &rec) const NOEXCEPT override; 26 | bool bounding_box(const rreal t0, const rreal t1, AABB &output_box) const NOEXCEPT override; 27 | rreal pdf_value(RandomGenerator &rng, const Vec3 &origin, const Vec3 &v) const NOEXCEPT override; 28 | Vec3 random(RandomGenerator &rng, const Vec3 &origin) const NOEXCEPT override; 29 | 30 | private: 31 | void _set_bounding_box(); 32 | bool _is_interior(const rreal a, const rreal b, HitRecord &rec) const; // New method from v4 series 33 | }; 34 | -------------------------------------------------------------------------------- /render_library/Objects/Rectangles.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Interfaces/IHittable.hpp" 4 | #include "AABB.hpp" 5 | class IMaterial; 6 | 7 | 8 | //*== Three types of axis-aligned rectangles 9 | 10 | class XYRect FINAL : public IHittable { 11 | private: 12 | // Data 13 | rreal _x0, _x1; 14 | rreal _y0, _y1; 15 | rreal _k; 16 | std::shared_ptr _mat_ptr; 17 | 18 | public: 19 | explicit XYRect( 20 | const rreal x0, 21 | const rreal x1, 22 | const rreal y0, 23 | const rreal y1, 24 | const rreal k, 25 | std::shared_ptr mat 26 | ) NOEXCEPT; 27 | 28 | std::shared_ptr deep_copy() const NOEXCEPT override; 29 | 30 | bool hit(RandomGenerator &rng, const Ray &r, const rreal t_min, const rreal t_max, HitRecord &rec) const NOEXCEPT override; 31 | bool bounding_box(const rreal t0, const rreal t1, AABB &output_box) const NOEXCEPT override; 32 | rreal pdf_value(RandomGenerator &rng, const Vec3 &origin, const Vec3 &v) const NOEXCEPT override; 33 | Vec3 random(RandomGenerator &rng, const Vec3 &origin) const NOEXCEPT override; 34 | }; 35 | 36 | 37 | class XZRect FINAL : public IHittable { 38 | private: 39 | // Data 40 | rreal _x0, _x1; 41 | rreal _z0, _z1; 42 | rreal _k; 43 | std::shared_ptr _mat_ptr; 44 | 45 | public: 46 | explicit XZRect( 47 | const rreal x0, 48 | const rreal x1, 49 | const rreal z0, 50 | const rreal z1, 51 | const rreal k, 52 | std::shared_ptr mat 53 | ) NOEXCEPT; 54 | 55 | std::shared_ptr deep_copy() const NOEXCEPT override; 56 | 57 | bool hit(RandomGenerator &rng, const Ray &r, const rreal t_min, const rreal t_max, HitRecord &rec) const NOEXCEPT override; 58 | bool bounding_box(const rreal t0, const rreal t1, AABB &output_box) const NOEXCEPT override; 59 | rreal pdf_value(RandomGenerator &rng, const Vec3 &origin, const Vec3 &v) const NOEXCEPT override; 60 | Vec3 random(RandomGenerator &rng, const Vec3 &origin) const NOEXCEPT override; 61 | }; 62 | 63 | 64 | class YZRect FINAL : public IHittable { 65 | private: 66 | // Data 67 | rreal _y0, _y1; 68 | rreal _z0, _z1; 69 | rreal _k; 70 | std::shared_ptr _mat_ptr; 71 | 72 | public: 73 | explicit YZRect( 74 | const rreal y0, 75 | const rreal y1, 76 | const rreal z0, 77 | const rreal z1, 78 | const rreal k, 79 | std::shared_ptr mat 80 | ) NOEXCEPT; 81 | 82 | std::shared_ptr deep_copy() const NOEXCEPT override; 83 | 84 | bool hit(RandomGenerator &rng, const Ray &r, const rreal t_min, const rreal t_max, HitRecord &rec) const NOEXCEPT override; 85 | bool bounding_box(const rreal t0, const rreal t1, AABB &output_box) const NOEXCEPT override; 86 | rreal pdf_value(RandomGenerator &rng, const Vec3 &origin, const Vec3 &v) const NOEXCEPT override; 87 | Vec3 random(RandomGenerator &rng, const Vec3 &origin) const NOEXCEPT override; 88 | }; 89 | -------------------------------------------------------------------------------- /render_library/Objects/Sphere.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Interfaces/IHittable.hpp" 4 | 5 | 6 | class Sphere FINAL : public IHittable { 7 | private: 8 | // Data 9 | Vec3 _center = Vec3(); 10 | rreal _radius = 0; 11 | std::shared_ptr _mat_ptr = nullptr; 12 | 13 | public: 14 | explicit Sphere() NOEXCEPT = default; 15 | explicit Sphere(const Vec3 &cen, const rreal r, const std::shared_ptr &mat) NOEXCEPT; 16 | 17 | std::shared_ptr deep_copy() const NOEXCEPT override; 18 | 19 | bool hit(RandomGenerator &rng, const Ray &r, const rreal t_min, const rreal t_max, HitRecord &rec) const NOEXCEPT override; 20 | bool bounding_box(const rreal t0, const rreal t1, AABB &output_box) const NOEXCEPT override; 21 | rreal pdf_value(RandomGenerator &rng, const Vec3 &origin, const Vec3 &v) const NOEXCEPT override; 22 | Vec3 random(RandomGenerator &rng, const Vec3 &origin) const NOEXCEPT override; 23 | }; 24 | -------------------------------------------------------------------------------- /render_library/Objects/Transform/FlipFace.cpp: -------------------------------------------------------------------------------- 1 | #include "FlipFace.hpp" 2 | 3 | using namespace std; 4 | 5 | 6 | shared_ptr FlipFace::deep_copy() const NOEXCEPT { 7 | return make_shared(_obj->deep_copy()); 8 | } 9 | 10 | bool FlipFace::hit(RandomGenerator &rng, const Ray &r, const rreal t_min, const rreal t_max, HitRecord &rec) const NOEXCEPT { 11 | if (!_obj->hit(rng, r, t_min, t_max, rec)) 12 | return false; 13 | 14 | rec.front_face = !rec.front_face; 15 | return true; 16 | } 17 | 18 | bool FlipFace::bounding_box(const rreal t0, const rreal t1, AABB &output_box) const NOEXCEPT { 19 | return _obj->bounding_box(t0, t1, output_box); 20 | } 21 | -------------------------------------------------------------------------------- /render_library/Objects/Transform/FlipFace.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Interfaces/IHittable.hpp" 4 | 5 | 6 | class FlipFace FINAL : public IHittable { 7 | private: 8 | // Data 9 | std::shared_ptr _obj; 10 | 11 | public: 12 | explicit FlipFace(const std::shared_ptr &obj) NOEXCEPT : 13 | _obj(obj) 14 | { } 15 | 16 | std::shared_ptr deep_copy() const NOEXCEPT override; 17 | 18 | bool hit(RandomGenerator &rng, const Ray &r, const rreal t_min, const rreal t_max, HitRecord &rec) const NOEXCEPT override; 19 | bool bounding_box(const rreal t0, const rreal t1, AABB &output_box) const NOEXCEPT override; 20 | 21 | rreal pdf_value( 22 | [[maybe_unused]] RandomGenerator &rng, 23 | [[maybe_unused]] const Vec3 &origin, 24 | [[maybe_unused]] const Vec3 &v 25 | ) const NOEXCEPT override { 26 | return 0; 27 | } 28 | 29 | Vec3 random( 30 | [[maybe_unused]] RandomGenerator &rng, 31 | [[maybe_unused]] const Vec3 &origin 32 | ) const NOEXCEPT override { 33 | return Vec3(1, 0, 0); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /render_library/Objects/Transform/RotateY.cpp: -------------------------------------------------------------------------------- 1 | #include "Objects/Transform/RotateY.hpp" 2 | #include "Util.hpp" 3 | 4 | using namespace std; 5 | 6 | 7 | RotateY::RotateY(const shared_ptr &obj, const rreal angle) NOEXCEPT : 8 | _obj(obj) 9 | { 10 | const rreal radians = util::degrees_to_radians(angle); 11 | _sin_theta = util::sin(radians); 12 | _cos_theta = util::cos(radians); 13 | _has_box = _obj->bounding_box(0, 1, _bbox); 14 | 15 | Vec3 lower(Infinity); 16 | Vec3 upper(-Infinity); 17 | 18 | for (int i = 0; i < 2; i++) { 19 | for (int j = 0; j < 2; j++) { 20 | for (int k = 0; k < 2; k++) { 21 | const auto ri = static_cast(i); 22 | const auto rj = static_cast(j); 23 | const auto rk = static_cast(k); 24 | 25 | const rreal x = (ri * _bbox.max.x) + ((1 - ri) * _bbox.min.x); 26 | const rreal y = (rj * _bbox.max.y) + ((1 - rj) * _bbox.min.y); 27 | const rreal z = (rk * _bbox.max.z) + ((1 - rk) * _bbox.min.z); 28 | 29 | const rreal x_prime = (_cos_theta * x) + (_sin_theta * z); 30 | const rreal z_prime = (-_sin_theta * x) + (_cos_theta * z); 31 | 32 | const Vec3 tester(x_prime, y, z_prime); 33 | 34 | // Have to expand things out here compared to the original book code since I don't use elements 35 | lower.x = min(lower.x, tester.x); 36 | upper.x = max(upper.x, tester.x); 37 | lower.y = min(lower.y, tester.y); 38 | upper.y = max(upper.y, tester.y); 39 | lower.z = min(lower.z, tester.z); 40 | upper.z = max(upper.z, tester.z); 41 | } 42 | } 43 | } 44 | 45 | _bbox = AABB(lower, upper); 46 | } 47 | 48 | std::shared_ptr RotateY::deep_copy() const NOEXCEPT { 49 | auto trans = make_shared(*this); 50 | trans->_obj = _obj->deep_copy(); 51 | 52 | return trans; 53 | } 54 | 55 | bool RotateY::hit(RandomGenerator &rng, const Ray &r, const rreal t_min, const rreal t_max, HitRecord &rec) const NOEXCEPT { 56 | // Need to modify the direction and orientation of the ray 57 | 58 | Vec3 o_prime( 59 | (_cos_theta * r.origin.x) - (_sin_theta * r.origin.z), 60 | r.origin.y, 61 | (_sin_theta * r.origin.x) + (_cos_theta * r.origin.z) 62 | ); 63 | 64 | Vec3 d_prime( 65 | (_cos_theta * r.direction.x) - (_sin_theta * r.direction.z), 66 | r.direction.y, 67 | (_sin_theta * r.direction.x) + (_cos_theta * r.direction.z) 68 | ); 69 | 70 | Ray r_rotated(o_prime, d_prime, r.time); 71 | 72 | // Hit check 73 | if (!_obj->hit(rng, r_rotated, t_min, t_max, rec)) 74 | return false; 75 | 76 | // Now need to rotate the hit record 77 | Vec3 p_prime( 78 | ( _cos_theta * rec.p.x) + (_sin_theta * rec.p.z), 79 | rec.p.y, 80 | (-_sin_theta * rec.p.x) + (_cos_theta * rec.p.z) 81 | ); 82 | 83 | Vec3 n_prime( 84 | ( _cos_theta * rec.normal.x) + (_sin_theta * rec.normal.z), 85 | rec.normal.y, 86 | (-_sin_theta * rec.normal.x) + (_cos_theta * rec.normal.z) 87 | ); 88 | 89 | rec.p = p_prime; 90 | rec.set_face_normal(r_rotated, n_prime); 91 | 92 | return true; 93 | } 94 | 95 | bool RotateY::bounding_box( 96 | [[maybe_unused]] const rreal t0, 97 | [[maybe_unused]] const rreal t1, 98 | AABB &output_box 99 | ) const NOEXCEPT { 100 | output_box = _bbox; 101 | return _has_box; 102 | } 103 | -------------------------------------------------------------------------------- /render_library/Objects/Transform/RotateY.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Interfaces/IHittable.hpp" 4 | #include "AABB.hpp" 5 | 6 | 7 | class RotateY FINAL : public IHittable { 8 | private: 9 | // Data 10 | std::shared_ptr _obj = nullptr; 11 | rreal _sin_theta = 0, _cos_theta = 0; 12 | bool _has_box = false; 13 | AABB _bbox{}; 14 | 15 | public: 16 | explicit RotateY(const std::shared_ptr &obj, const rreal angle) NOEXCEPT; 17 | 18 | std::shared_ptr deep_copy() const NOEXCEPT override; 19 | 20 | bool hit(RandomGenerator &rng, const Ray &r, const rreal t_min, const rreal t_max, HitRecord &rec) const NOEXCEPT override; 21 | bool bounding_box(const rreal t0, const rreal t1, AABB &output_box) const NOEXCEPT override; 22 | 23 | rreal pdf_value( 24 | [[maybe_unused]] RandomGenerator &rng, 25 | [[maybe_unused]] const Vec3 &origin, 26 | [[maybe_unused]] const Vec3 &v 27 | ) const NOEXCEPT override { 28 | return 0; 29 | } 30 | 31 | Vec3 random( 32 | [[maybe_unused]] RandomGenerator &rng, 33 | [[maybe_unused]] const Vec3 &origin 34 | ) const NOEXCEPT override { 35 | return Vec3(1, 0, 0); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /render_library/Objects/Transform/Translate.cpp: -------------------------------------------------------------------------------- 1 | #include "Objects/Transform/Translate.hpp" 2 | #include "AABB.hpp" 3 | 4 | using namespace std; 5 | 6 | 7 | Translate::Translate(const shared_ptr &obj, const Vec3 &displacement) NOEXCEPT : 8 | _obj(obj), 9 | _offset(displacement) 10 | { } 11 | 12 | std::shared_ptr Translate::deep_copy() const NOEXCEPT { 13 | auto trans = make_shared(*this); 14 | trans->_obj = _obj->deep_copy(); 15 | 16 | return trans; 17 | } 18 | 19 | bool Translate::hit( 20 | [[maybe_unused]] RandomGenerator &rng, 21 | const Ray &r, 22 | const rreal t_min, 23 | const rreal t_max, 24 | HitRecord &rec 25 | ) const NOEXCEPT { 26 | const Ray moved_r(r.origin - _offset, r.direction, r.time); 27 | if (!_obj->hit(rng, moved_r, t_min, t_max, rec)) 28 | return false; 29 | 30 | rec.p += _offset; 31 | rec.set_face_normal(moved_r, rec.normal); 32 | 33 | return true; 34 | } 35 | 36 | bool Translate::bounding_box(const rreal t0, const rreal t1, AABB &output_box) const NOEXCEPT { 37 | if (!_obj->bounding_box(t0, t1, output_box)) 38 | return false; 39 | 40 | output_box = AABB( 41 | output_box.min + _offset, 42 | output_box.max + _offset 43 | ); 44 | 45 | return true; 46 | } 47 | -------------------------------------------------------------------------------- /render_library/Objects/Transform/Translate.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Interfaces/IHittable.hpp" 4 | 5 | 6 | class Translate FINAL : public IHittable { 7 | private: 8 | // Data 9 | std::shared_ptr _obj = nullptr; 10 | Vec3 _offset = Vec3(0); 11 | 12 | public: 13 | explicit Translate(const std::shared_ptr &obj, const Vec3 &displacement) NOEXCEPT; 14 | 15 | std::shared_ptr deep_copy() const NOEXCEPT override; 16 | 17 | bool hit(RandomGenerator &rng, const Ray &r, const rreal t_min, const rreal t_max, HitRecord &rec) const NOEXCEPT override; 18 | bool bounding_box(const rreal t0, const rreal t1, AABB &output_box) const NOEXCEPT override; 19 | 20 | rreal pdf_value( 21 | [[maybe_unused]] RandomGenerator &rng, 22 | [[maybe_unused]] const Vec3 &origin, 23 | [[maybe_unused]] const Vec3 &v 24 | ) const NOEXCEPT override { 25 | return 0; 26 | } 27 | 28 | Vec3 random( 29 | [[maybe_unused]] RandomGenerator &rng, 30 | [[maybe_unused]] const Vec3 &origin 31 | ) const NOEXCEPT override { 32 | return Vec3(1, 0, 0); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /render_library/PDFVariant.cpp: -------------------------------------------------------------------------------- 1 | #include "PDFVariant.hpp" 2 | -------------------------------------------------------------------------------- /render_library/PDFVariant.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "PDFs/CosinePDF.hpp" 5 | #include "PDFs/HittablePDF.hpp" 6 | #include "PDFs/MixturePDF.hpp" 7 | 8 | 9 | /** 10 | * PDFVariant is variant container, that well, contains sublcasses of IPDF. 11 | * 12 | * It's indended purpose is to be used to save us from having functions return a pointer 13 | * to a dynamically allocated probability distributtion function. In the book's architecture 14 | * There is quite a bit amount of dynamically allocated memory being created during render time. 15 | * That's not really good for performance. Using a typesafe container might possibly take up more 16 | * memory, but it lets the compiler better predict how that memory is going to be used, and thus 17 | * optimize more for it. 18 | * 19 | * Also, due to rules of forward delcrations and whatnot (and that PDFVariant is used in MixturePDF) 20 | * We have to put some includes below the `using` statement. 21 | */ 22 | using PDFVariant = std::variant; 23 | -------------------------------------------------------------------------------- /render_library/PDFs/CosinePDF.cpp: -------------------------------------------------------------------------------- 1 | #include "PDFs/CosinePDF.hpp" 2 | #include "RandomGenerator.hpp" 3 | 4 | rreal CosinePDF::value( 5 | [[maybe_unused]] RandomGenerator &rng, 6 | const Vec3 &direction 7 | ) const NOEXCEPT { 8 | const rreal cosine = direction.unit_vector().dot(_uvw.w()); 9 | return (cosine <= 0) ? 0 : (cosine / Pi); 10 | } 11 | 12 | Vec3 CosinePDF::generate(RandomGenerator &rng) const NOEXCEPT { 13 | return _uvw.local(rng.get_cosine_direction()); 14 | } 15 | -------------------------------------------------------------------------------- /render_library/PDFs/CosinePDF.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Interfaces/IPDF.hpp" 4 | #include "ONB.hpp" 5 | 6 | 7 | class CosinePDF FINAL : public IPDF { 8 | private: 9 | // Data 10 | ONB _uvw; 11 | 12 | public: 13 | explicit CosinePDF(const Vec3 &w) NOEXCEPT { 14 | _uvw.build_from_w(w); 15 | } 16 | 17 | rreal value(RandomGenerator &rng, const Vec3 &direction) const NOEXCEPT override; 18 | Vec3 generate(RandomGenerator &rng) const NOEXCEPT override; 19 | }; 20 | -------------------------------------------------------------------------------- /render_library/PDFs/HittablePDF.cpp: -------------------------------------------------------------------------------- 1 | #include "PDFs/HittablePDF.hpp" 2 | #include "RandomGenerator.hpp" 3 | 4 | rreal HittablePDF::value(RandomGenerator &rng, const Vec3 &direction) const NOEXCEPT { 5 | // This is slighty different from the book, to handle a case where a `null` was provided 6 | if (_obj) 7 | return _obj->pdf_value(rng, _origin, direction); 8 | 9 | return 0; 10 | } 11 | 12 | Vec3 HittablePDF::generate(RandomGenerator &rng) const NOEXCEPT { 13 | // This is slighty different from the book, to handle a case where a `null` was provided 14 | if (_obj) 15 | return _obj->random(rng, _origin); 16 | 17 | return Vec3(1, 0, 0); 18 | } 19 | -------------------------------------------------------------------------------- /render_library/PDFs/HittablePDF.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Interfaces/IPDF.hpp" 4 | #include "Interfaces/IHittable.hpp" 5 | 6 | 7 | class HittablePDF FINAL : public IPDF { 8 | private: 9 | // Data 10 | Vec3 _origin; 11 | std::shared_ptr _obj; 12 | 13 | public: 14 | explicit HittablePDF(const std::shared_ptr &object, Vec3 &origin) NOEXCEPT : 15 | _origin(origin), 16 | _obj(object) 17 | { } 18 | 19 | rreal value(RandomGenerator &rng, const Vec3 &direction) const NOEXCEPT override; 20 | Vec3 generate(RandomGenerator &rng) const NOEXCEPT override; 21 | }; 22 | -------------------------------------------------------------------------------- /render_library/PDFs/MixturePDF.cpp: -------------------------------------------------------------------------------- 1 | #include "PDFs/MixturePDF.hpp" 2 | #include "RandomGenerator.hpp" 3 | 4 | 5 | rreal MixturePDF::value( 6 | RandomGenerator &rng, 7 | const Vec3 &direction 8 | ) const NOEXCEPT { 9 | // This is different from the book, as this handles a case where a PDF could be null 10 | const rreal a_val = _a ? (0.5 * _a->value(rng, direction)) : 0; 11 | const rreal b_val = _b ? (0.5 * _b->value(rng, direction)) : 0; 12 | 13 | return a_val + b_val; 14 | } 15 | 16 | Vec3 MixturePDF::generate(RandomGenerator &rng) const NOEXCEPT { 17 | // This is different from the book, as this handles a case where a PDF could be null 18 | const bool use_a = (rng.get_real() < 0.5); 19 | 20 | if (use_a) 21 | return _a ? _a->generate(rng) : Vec3(1, 0, 0); 22 | 23 | return _b ? _b->generate(rng) : Vec3(1, 0, 0); 24 | } 25 | -------------------------------------------------------------------------------- /render_library/PDFs/MixturePDF.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Interfaces/IPDF.hpp" 4 | #include 5 | 6 | 7 | // A PDF that mixes two others 8 | class MixturePDF FINAL : public IPDF { 9 | private: 10 | // Data 11 | #ifdef USE_BOOK_PDF_MANAGEMENT 12 | std::shared_ptr _a; 13 | std::shared_ptr _b; 14 | #else 15 | // Shared pointers are slow, so on our PDFVariant change, we use raw pointers instead 16 | const IPDF *_a = nullptr; 17 | const IPDF *_b = nullptr; 18 | #endif 19 | 20 | public: 21 | explicit MixturePDF( 22 | // (Putting pre-preprocessor macro in the arguments...) 23 | // (Yes, this is disgusting, and I am sorry...) 24 | #ifdef USE_BOOK_PDF_MANAGEMENT 25 | const std::shared_ptr &a, 26 | const std::shared_ptr &b 27 | #else 28 | const IPDF *a, 29 | const IPDF *b 30 | #endif 31 | ) NOEXCEPT : 32 | _a(a), 33 | _b(b) 34 | { } 35 | 36 | rreal value(RandomGenerator &rng, const Vec3 &direction) const NOEXCEPT override; 37 | Vec3 generate(RandomGenerator &rng) const NOEXCEPT override; 38 | }; 39 | -------------------------------------------------------------------------------- /render_library/Perlin.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Vec3.hpp" 4 | #include 5 | #include 6 | class RandomGenerator; 7 | 8 | 9 | // How many random points to create 10 | constexpr int _PerlinPointCount = 256; 11 | 12 | 13 | // A simple perlin noise generator, useing our `Vec3` type 14 | class Perlin { 15 | private: 16 | // Data 17 | #ifdef USE_BOOK_PERLIN 18 | // The book uses dynamic memory for these objects 19 | Vec3 *_ranvec; 20 | int *_perm_x; 21 | int *_perm_y; 22 | int *_perm_z; 23 | #else // USE_BOOK_PERLIN 24 | // Out implementation uses std::array (and stack memory) 25 | std::array _ranvec; 26 | std::array _perm_x; 27 | std::array _perm_y; 28 | std::array _perm_z; 29 | #endif // USE_BOOK_PERLIN 30 | 31 | 32 | public: 33 | explicit Perlin(RandomGenerator &rng) NOEXCEPT; 34 | 35 | #ifdef USE_BOOK_PERLIN 36 | ~Perlin() NOEXCEPT; 37 | #endif 38 | 39 | private: 40 | #ifdef USE_BOOK_PERLIN 41 | static int *_perlin_generate_perm(RandomGenerator &rng) NOEXCEPT; // Returns dynamically allocated memory 42 | #endif 43 | 44 | static void _permute(RandomGenerator &rng, int *p, int n) NOEXCEPT; 45 | 46 | rreal _perlin_interp(const Vec3 c[2][2][2], const rreal u, const rreal v, const rreal w) const NOEXCEPT; 47 | 48 | public: 49 | rreal noise(const Vec3 &p) const NOEXCEPT; 50 | rreal turb(const Vec3 &p, const uint8_t depth=7) const NOEXCEPT; 51 | }; 52 | -------------------------------------------------------------------------------- /render_library/PerlinReal.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Vec3.hpp" 4 | #include 5 | class RandomGenerator; 6 | 7 | 8 | // How many random points to create 9 | constexpr int _PerlinRealPointCount = 256; 10 | 11 | 12 | // TODO [refactoring] 13 | // - There are also some other third-party noise implementations, they might be more worth 14 | // while to use 15 | 16 | // A simple perlin noise generator, using our `rreal` type 17 | class PerlinReal { 18 | public: 19 | enum class InterpolationType { 20 | None, 21 | TriLinear, 22 | TriLinearHermiteCubic 23 | }; 24 | 25 | private: 26 | // Data 27 | InterpolationType _interp_method; 28 | 29 | #ifdef USE_BOOK_PERLIN 30 | // The book uses dynamic memory for these objects 31 | rreal *_ranreal; 32 | int *_perm_x; 33 | int *_perm_y; 34 | int *_perm_z; 35 | #else // USE_BOOK_PERLIN 36 | std::array _ranreal; 37 | std::array _perm_x; 38 | std::array _perm_y; 39 | std::array _perm_z; 40 | #endif // USE_BOOK_PERLIN 41 | 42 | public: 43 | explicit PerlinReal(RandomGenerator &rng, const InterpolationType interpMethod) NOEXCEPT; 44 | 45 | #ifdef USE_BOOK_PERLIN 46 | ~PerlinReal() NOEXCEPT; 47 | #endif 48 | 49 | private: 50 | #ifdef USE_BOOK_PERLIN 51 | static int *_perlin_generate_perm(RandomGenerator &rng) NOEXCEPT; // Returns dynamically allocated memory 52 | #endif 53 | 54 | static void _permute(RandomGenerator &rng, int *p, int n) NOEXCEPT; 55 | 56 | rreal _noise_no_interp(const Vec3 &p) const NOEXCEPT; 57 | rreal _noise_trilinear_interp(const Vec3 &p, const bool hermitian_smoothing) const NOEXCEPT; 58 | 59 | inline rreal _noise_trilinear_interp(const Vec3 &p) const NOEXCEPT { 60 | return _noise_trilinear_interp(p, false); 61 | } 62 | 63 | inline rreal _noise_trilinear_interp_smooth(const Vec3 &p) const NOEXCEPT { 64 | return _noise_trilinear_interp(p, true); 65 | } 66 | 67 | public: 68 | inline rreal noise(const Vec3 &p) const NOEXCEPT { 69 | // Figure out which interpolation method should be used 70 | if (_interp_method == InterpolationType::TriLinear) 71 | return _noise_trilinear_interp(p); 72 | else if (_interp_method == InterpolationType::TriLinearHermiteCubic) 73 | return _noise_trilinear_interp_smooth(p); 74 | 75 | return _noise_no_interp(p); // Default do no interpolation 76 | } 77 | }; 78 | -------------------------------------------------------------------------------- /render_library/RandomGenerator.cpp: -------------------------------------------------------------------------------- 1 | #include "RandomGenerator.hpp" 2 | -------------------------------------------------------------------------------- /render_library/Ray.cpp: -------------------------------------------------------------------------------- 1 | #include "Ray.hpp" 2 | -------------------------------------------------------------------------------- /render_library/Ray.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.hpp" 4 | #include "Vec3.hpp" 5 | 6 | 7 | class Ray { 8 | public: 9 | // Data 10 | Vec3 origin; 11 | Vec3 direction; 12 | rreal time; 13 | 14 | public: 15 | explicit Ray() NOEXCEPT = default; 16 | explicit Ray(const Vec3 &origin_, const Vec3 &direction_, const rreal time_=0) NOEXCEPT : 17 | origin(origin_), 18 | direction(direction_), 19 | time(time_) 20 | { } 21 | 22 | inline Vec3 at(const rreal t) const NOEXCEPT { 23 | return origin + (t * direction); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /render_library/RenderContext.cpp: -------------------------------------------------------------------------------- 1 | #include "RenderContext.hpp" 2 | -------------------------------------------------------------------------------- /render_library/RenderContext.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Vec3.hpp" 5 | #include "RenderMethod.hpp" 6 | class IHittable; 7 | class ICamera; 8 | 9 | 10 | // The general context of what needs to be rendered 11 | // Treat this as a read-only structure 12 | struct RenderContext 13 | { 14 | // Data 15 | std::shared_ptr scene = nullptr; // Objects to render 16 | std::shared_ptr lights = nullptr; // Light sources in the scene 17 | std::shared_ptr camera = nullptr; // Camera to render with 18 | Vec3 background = Vec3(0); // Background of scene, affects the illumination 19 | uint16_t width = 1; // Render image width 20 | uint16_t height = 1; // Render image height 21 | RenderMethod render_method = RenderMethod::Books1And2; // The style of rendering to use (e.g. book 3 uses PDFs) 22 | bool deep_copy_per_thread = true; // If there should be a of the scene & camera per each thread 23 | }; 24 | -------------------------------------------------------------------------------- /render_library/RenderMethod.cpp: -------------------------------------------------------------------------------- 1 | #include "RenderMethod.hpp" 2 | -------------------------------------------------------------------------------- /render_library/RenderMethod.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | /** 5 | * Books 1 & 2 share the same method of rendering, but book 3 has a different model (e.g. the use of PDFs), which 6 | * makes rendering, well, different. To be able to support both ways, we need an enumeration so materials can know 7 | * if they should be using book 1/2 's model, or book 3's. 8 | */ 9 | enum class RenderMethod 10 | { 11 | Books1And2 = 0, 12 | Book3 13 | }; 14 | -------------------------------------------------------------------------------- /render_library/RenderOutput.cpp: -------------------------------------------------------------------------------- 1 | #include "RenderOutput.hpp" 2 | #include "ColorRGBA.hpp" 3 | #include "Util.hpp" 4 | 5 | using namespace std; 6 | using namespace util; 7 | 8 | 9 | // Write an entire scanline of pixels to the image 10 | void write_rgb_scanline(const RenderOutput &rd, vector &img, const uint16_t row, const vector &pixels) NOEXCEPT { 11 | uint32_t loc = rd.row_byte_size * row; 12 | 13 | for (const ColorRGBA &rgba: pixels) { 14 | // Convert the RGBA to the pixel and write it 15 | img[loc++] = static_cast(256 * clamp(rgba.r, 0.0, static_cast(0.999))); 16 | img[loc++] = static_cast(256 * clamp(rgba.g, 0.0, static_cast(0.999))); 17 | img[loc++] = static_cast(256 * clamp(rgba.b, 0.0, static_cast(0.999))); 18 | img[loc++] = static_cast(256 * clamp(rgba.a, 0.0, static_cast(0.999))); 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /render_library/RenderOutput.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.hpp" 4 | #include 5 | #include 6 | #include 7 | struct ColorRGBA; 8 | 9 | 10 | // Small description of the image render 11 | // TODO [refeactoring] maybe have this hold the pixel data as well? 12 | // then we could abstract away the stbi stuffs 13 | struct RenderOutput { 14 | const uint16_t width; 15 | const uint16_t height; 16 | const uint8_t num_channels; 17 | 18 | // Caches of other values 19 | const uint32_t row_byte_size; 20 | const size_t total_byte_size; 21 | 22 | explicit RenderOutput(const uint16_t width_, const uint16_t height_, const uint8_t num_channels_=4) NOEXCEPT : 23 | width(width_), 24 | height(height_), 25 | num_channels(num_channels_), 26 | row_byte_size(width * num_channels), 27 | total_byte_size(row_byte_size * height) 28 | { } 29 | }; 30 | 31 | void write_rgb_scanline(const RenderOutput &ro, std::vector &img, const uint16_t row, const std::vector &pixels) NOEXCEPT; 32 | -------------------------------------------------------------------------------- /render_library/RenderThread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Based off of work here: 4 | // https://www.bfilipek.com/2019/12/threading-loopers-cpp17.html 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "Vec3.hpp" 15 | #include "ColorRGBA.hpp" 16 | #include "RenderContext.hpp" 17 | class IHittable; 18 | class ICamera; 19 | class Ray; 20 | class RandomGenerator; 21 | struct RenderOutput; 22 | 23 | 24 | 25 | /** A thread object to queue up render work */ 26 | class RenderThread 27 | { 28 | public: 29 | /*== Data Structures ==*/ 30 | /** 31 | * A job of what to render, right now a single RenderThread will work on a scanline at a time. 32 | * Keep this as a read-only structure 33 | */ 34 | struct Task 35 | { 36 | uint16_t scanline = 0; ///< Which no. scanline of the render to work on 37 | uint16_t max_ray_depth = 50; ///< Maximum number of ray bouncies 38 | uint32_t samples_per_pixel = 1; ///< How many times to sample a pixel 39 | 40 | std::string rng_seed; ///< Seed for RNG for this task, can be any fun phrase you want! 41 | }; 42 | 43 | /** 44 | * Right now this is per-scanline, if we make it per-pixel, or allow sub scanlines it will need to be 45 | * more robust. See the above `Task`. 46 | */ 47 | struct Result 48 | { 49 | uint16_t scanline = 0; ///< The scanline that was rendered 50 | std::vector data{}; ///< pixels 51 | }; 52 | 53 | private: 54 | /*== Data ==*/ 55 | RenderContext _r_ctx; 56 | 57 | // Threading 58 | std::thread _thread; 59 | std::atomic_bool _running; 60 | std::atomic_bool _stop_requested; 61 | 62 | // Work queuing 63 | std::mutex _task_queue_mutex; 64 | std::queue _task_queue; 65 | 66 | // Results 67 | std::mutex _result_queue_mutex; 68 | std::queue _result_queue; 69 | std::atomic_uint16_t _completed_task_count; 70 | 71 | // Metering 72 | std::atomic_uint64_t _total_queued_pixels; // How many pixels in total will be worked on 73 | std::atomic_uint64_t _pixels_completed; // How many pixels have been completed thus far 74 | 75 | public: 76 | explicit RenderThread(const RenderContext &render_context) NOEXCEPT; 77 | explicit RenderThread(RenderThread &&other) NOEXCEPT; 78 | 79 | bool run() NOEXCEPT; 80 | 81 | // Queue a task to turn, returns false if failed to queue 82 | bool add_task(const Task &task) NOEXCEPT; 83 | 84 | // Try to get a result, returns false if no result was placed in `result` 85 | bool retrive_result(Result &result) NOEXCEPT; 86 | 87 | inline uint16_t num_tasks_completed() const NOEXCEPT { 88 | return _completed_task_count.load(); 89 | } 90 | 91 | inline uint64_t total_pixel_count() const NOEXCEPT { 92 | return _total_queued_pixels.load(); 93 | } 94 | 95 | inline uint64_t num_pixels_completed() const NOEXCEPT { 96 | return _pixels_completed.load(); 97 | } 98 | 99 | inline bool running() const NOEXCEPT { 100 | return _running.load(); 101 | } 102 | 103 | inline void request_stop() NOEXCEPT { 104 | _stop_requested.store(true); 105 | } 106 | 107 | inline void stop_and_try_join() NOEXCEPT { 108 | request_stop(); 109 | if (_thread.joinable()) 110 | _thread.join(); 111 | } 112 | 113 | private: 114 | // == Thread functions 115 | void _thread_main_loop() NOEXCEPT; // Main loop of the thread 116 | std::vector _render_scanline(const Task &task) NOEXCEPT; // Called in the main loop, where rendering actually takes place 117 | 118 | // Attempts to get a task from the work queue 119 | // return `true` if a task was pulled off. It's then placed into `task`. 120 | // if `false`, then one wasn't pulled off. `task` shouldn't be modified then 121 | bool _get_next_task(Task &task) NOEXCEPT; 122 | 123 | // Place the result of a render into a queue for the thread owner to receive 124 | bool _store_result(const uint16_t scanline, const std::vector &data) NOEXCEPT; 125 | }; 126 | -------------------------------------------------------------------------------- /render_library/RenderThreadPool.cpp: -------------------------------------------------------------------------------- 1 | #include "RenderThreadPool.hpp" 2 | #include "RandomGenerator.hpp" 3 | #include "RenderOutput.hpp" 4 | using namespace std; 5 | 6 | 7 | RenderThreadPool::RenderThreadPool(const RenderContext &render_context, const uint16_t num_threads) NOEXCEPT : 8 | _r_ctx(render_context) 9 | { 10 | // Create the threads 11 | for (uint16_t i = 0; i < num_threads; i++) 12 | _threads.push_back(RenderThread(_r_ctx)); 13 | } 14 | 15 | void RenderThreadPool::setup_render(const string &main_rng_seed, const uint32_t samples_per_pixel, const uint16_t max_ray_depth) 16 | { 17 | RandomGenerator rng(main_rng_seed); 18 | 19 | // Set which threads will render which scanlines 20 | RenderThread::Task task; 21 | task.samples_per_pixel = samples_per_pixel; 22 | task.max_ray_depth = max_ray_depth; 23 | 24 | const uint16_t height_max = _r_ctx.height - 1; 25 | auto row = static_cast(height_max); 26 | bool added = false; 27 | 28 | while (row >= 0) 29 | { 30 | // Setup the task 31 | task.scanline = static_cast(row--); 32 | task.rng_seed = rng.get_random_string(16); 33 | 34 | // Pick which thread to add it to 35 | added = _threads[static_cast(row) % _threads.size()].add_task(task); 36 | if (!added) 37 | throw runtime_error("Wasn't able to add render task"); 38 | } 39 | } 40 | 41 | void RenderThreadPool::start_render() 42 | { 43 | // Start all threads 44 | bool started = false; 45 | for (RenderThread &rt : _threads) 46 | { 47 | started = rt.run(); 48 | if (!started) 49 | throw runtime_error("Wasn't able to start render thread"); 50 | } 51 | } 52 | 53 | uint64_t RenderThreadPool::total_pixel_count() const NOEXCEPT 54 | { 55 | uint64_t total = 0; 56 | for (const RenderThread &rt: _threads) 57 | total += rt.total_pixel_count(); 58 | 59 | return total; 60 | } 61 | 62 | uint64_t RenderThreadPool::num_pixels_completed() const NOEXCEPT 63 | { 64 | uint64_t completed = 0; 65 | for (const RenderThread &rt: _threads) 66 | completed += rt.num_pixels_completed(); 67 | 68 | return completed; 69 | } 70 | 71 | // Checks if the render is done by looking at the task counts 72 | bool RenderThreadPool::render_completed() const NOEXCEPT 73 | { 74 | uint16_t tasks_done = 0; 75 | for (const RenderThread &rt: _threads) 76 | tasks_done += rt.num_tasks_completed(); 77 | 78 | // the total number of tasks is equal to the height of the render 79 | return (tasks_done >= _r_ctx.height); 80 | } 81 | 82 | void RenderThreadPool::shutdown_and_wait() NOEXCEPT 83 | { 84 | // Cleanly tell the threads to shutdown and wait for them all 85 | for (RenderThread &rt: _threads) 86 | rt.request_stop(); 87 | 88 | for (RenderThread &rt: _threads) 89 | rt.stop_and_try_join(); 90 | } 91 | 92 | // Write the results of the rendering to a byte buffer 93 | void RenderThreadPool::retreive_render(const RenderOutput &render_desc, vector &dest) NOEXCEPT 94 | { 95 | RenderThread::Result result; 96 | bool got_scanline = false; 97 | 98 | // Loop through all the threads 99 | for (RenderThread &rt : _threads) 100 | { 101 | // And loop through all of their results 102 | got_scanline = rt.retrive_result(result); 103 | while (got_scanline) 104 | { 105 | write_rgb_scanline(render_desc, dest, result.scanline, result.data); 106 | 107 | // Try to get the next 108 | got_scanline = rt.retrive_result(result); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /render_library/RenderThreadPool.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "RenderContext.hpp" 5 | #include "RenderThread.hpp" 6 | struct RenderOutput; 7 | 8 | 9 | // A class that manages multiple render threads, and getting everying done for you 10 | class RenderThreadPool 11 | { 12 | private: 13 | // Data 14 | RenderContext _r_ctx; 15 | std::vector _threads; 16 | 17 | public: 18 | explicit RenderThreadPool(const RenderContext &render_context, const uint16_t num_threads) NOEXCEPT; 19 | 20 | void setup_render(const std::string &main_rng_seed, const uint32_t samples_per_pixel, const uint16_t max_ray_depth); 21 | void start_render(); 22 | void shutdown_and_wait() NOEXCEPT; 23 | 24 | uint64_t total_pixel_count() const NOEXCEPT; 25 | uint64_t num_pixels_completed() const NOEXCEPT; 26 | bool render_completed() const NOEXCEPT; 27 | void retreive_render(const RenderOutput &render_desc, std::vector &dest) NOEXCEPT; 28 | }; 29 | -------------------------------------------------------------------------------- /render_library/ScatterRecord.cpp: -------------------------------------------------------------------------------- 1 | #include "ScatterRecord.hpp" 2 | -------------------------------------------------------------------------------- /render_library/ScatterRecord.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Ray.hpp" 5 | #include "Interfaces/IPDF.hpp" 6 | #include "PDFVariant.hpp" 7 | 8 | 9 | struct ScatterRecord { 10 | // TODO I think we can reorder these parameters, do a measuring (and compare with books' code on GitHub) 11 | Ray ray; ///< In books 1 & 2, this argument was called `scattered`, in book 3 is was called `specular_ray`. 12 | bool is_specular = false; 13 | Vec3 attenuation; 14 | 15 | #ifdef USE_BOOK_PDF_MANAGEMENT 16 | std::shared_ptr pdf_ptr = nullptr; 17 | #else 18 | PDFVariant pdf; 19 | #endif 20 | }; 21 | -------------------------------------------------------------------------------- /render_library/SceneDescriptor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "Vec3.hpp" 6 | #include "RenderMethod.hpp" 7 | 8 | class IHittable; 9 | class ICamera; 10 | 11 | 12 | // A Scene descriptor is a simple object that stores the defnition of 13 | // how a scene should look. It contains objects as well as camera 14 | // configurations 15 | struct SceneDescriptor { 16 | // Data 17 | std::shared_ptr scene; 18 | std::shared_ptr lights; ///< This could be a list of lights, or a singular one; note that this is meant for use with book3 style scenes/renders 19 | std::vector> cameras; 20 | Vec3 background = Vec3(0); 21 | RenderMethod render_method = RenderMethod::Books1And2; ///< With style of rendering to use; e.g. Book 3 uses PDFs 22 | }; 23 | 24 | // That sky blue background that was used for all things in the first book and a half 25 | const Vec3 sky_blue(static_cast(0.7), static_cast(0.8), 1); 26 | -------------------------------------------------------------------------------- /render_library/Scenes/Book1.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.hpp" 4 | #include "SceneDescriptor.hpp" 5 | 6 | // Scenes from Book 1 (Ray Tracing in one Weekend) of the Peter Shirley Raytracing minioooks 7 | 8 | 9 | namespace Scenes { 10 | namespace Book1 { 11 | SceneDescriptor surface_normal_sphere(const rreal aspect_ratio); 12 | SceneDescriptor grey_sphere(const rreal aspect_ratio); 13 | SceneDescriptor shiny_metal_spheres(const rreal aspect_ratio); 14 | SceneDescriptor fuzzy_metal_spheres(const rreal aspect_ratio); 15 | SceneDescriptor two_glass_one_metal_spheres(const rreal aspect_ratio); 16 | SceneDescriptor glass_blue_metal_spheres(const rreal aspect_ratio); 17 | SceneDescriptor blue_red_spheres(const rreal aspect_ratio); 18 | 19 | // This one has multiple cameras. They are as follows: 20 | // 0: standard view 21 | // 1: far view, 22 | // 2: close view 23 | // 3: close & fuzzy view 24 | SceneDescriptor hollow_glass_blue_metal_spheres(const rreal aspect_ratio); 25 | 26 | SceneDescriptor final_scene(const rreal aspect_ratio); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /render_library/Scenes/Book2.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.hpp" 4 | #include "SceneDescriptor.hpp" 5 | #include "PerlinReal.hpp" 6 | #include "Textures/NoiseTexture.hpp" 7 | 8 | // Scenes from Book 2 (Ray Tracing the Next Week) of the Peter Shirley Raytracing minioooks 9 | 10 | 11 | namespace Scenes { 12 | namespace Book2 { 13 | // These have blurry (motion) objects in them 14 | SceneDescriptor bouncing_spheres(const rreal aspect_ratio, bool use_bvh_node=false); 15 | SceneDescriptor bouncing_spheres_checkered_floor(const rreal aspect_ratio, bool use_bvh_node=false); 16 | 17 | SceneDescriptor two_checkered_spheres(const rreal aspect_ratio); 18 | 19 | // There are many different versions of the two perlin spheres 20 | SceneDescriptor perlin_sphere(const rreal aspect_ratio, const PerlinReal::InterpolationType interpType); // Uses the Perlin with the `reals` 21 | SceneDescriptor perlin_sphere(const rreal aspect_ratio, const NoiseTexture::NoiseStyle noise_style); // Uses the Perlin with the `Vec3` 22 | 23 | SceneDescriptor earth(const rreal aspect_ratio); 24 | SceneDescriptor simple_light(const rreal aspect_ratio, const bool overhead_sphere_light=false); 25 | 26 | SceneDescriptor quads(const rreal aspect_ratio); 27 | 28 | // There are a few variations on the cornel box we have, so we need to use an enum to differentaite 29 | enum class CornellBoxConfiguration { 30 | Empty = 0, 31 | TwoBoxes, 32 | TwoRotatedBoxes, 33 | TwoSmokeBoxes, 34 | }; 35 | SceneDescriptor cornell_box(const rreal aspect_ratio, const CornellBoxConfiguration config); 36 | 37 | SceneDescriptor final_scene(const rreal aspect_ratio); 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /render_library/Scenes/Book3.cpp: -------------------------------------------------------------------------------- 1 | #include "Book3.hpp" 2 | #include "Cameras/MotionBlurCamera.hpp" 3 | #include "Materials/Lambertian.hpp" 4 | #include "Materials/Metal.hpp" 5 | #include "Materials/Dielectric.hpp" 6 | #include "Materials/DiffuseLight.hpp" 7 | #include "Objects/Sphere.hpp" 8 | #include "Objects/HittableList.hpp" 9 | #include "Objects/Rectangles.hpp" 10 | #include "Objects/Box.hpp" 11 | #include "Objects/Transform/Translate.hpp" 12 | #include "Objects/Transform/RotateY.hpp" 13 | #include "Objects/Transform/FlipFace.hpp" 14 | using namespace std; 15 | 16 | 17 | namespace Scenes { namespace Book3 { 18 | 19 | //==== Scene setup helpers ==== 20 | // (avoid that duplicate code) 21 | 22 | 23 | SceneDescriptor cornell_box(const rreal aspect_ratio, const CornellBoxConfiguration config) { 24 | const rreal light_intensity = 15; 25 | 26 | const auto red = make_shared(Vec3(static_cast(0.65), static_cast(0.05), static_cast(0.05))); 27 | const auto white = make_shared(Vec3(static_cast(0.73))); 28 | const auto green = make_shared(Vec3(static_cast(0.12), static_cast(0.45), static_cast(0.15))); 29 | const auto light = make_shared(Vec3(static_cast(light_intensity))); 30 | const auto aluminium = make_shared(Vec3(0.8, 0.85, 0.88), 0); 31 | 32 | // The light source(s) 33 | HittableList lights; 34 | const auto ceiling_light = make_shared(213, 343, 227, 332, 554, light); 35 | lights.add(ceiling_light); 36 | 37 | // The walls (and light) 38 | HittableList world; 39 | world.add(make_shared( 0, 555, 0, 555, 555, green)); // Left 40 | world.add(make_shared( 0, 555, 0, 555, 0, red)); // Right 41 | world.add(make_shared(ceiling_light)); 42 | world.add(make_shared( 0, 555, 0, 555, 0, white)); // Floor 43 | world.add(make_shared( 0, 555, 0, 555, 555, white)); // Roof 44 | world.add(make_shared( 0, 555, 0, 555, 555, white)); // Back 45 | 46 | // default Material of the left item should be white (else, a mirror) 47 | shared_ptr left_mat = white; 48 | if ((config == CornellBoxConfiguration::TwoBoxesOneMirror) || (config == CornellBoxConfiguration::OneMirrorBoxOneGlassSphere)) 49 | left_mat = aluminium; 50 | 51 | // Left item is always a box 52 | shared_ptr left_item = make_shared(Vec3(0), Vec3(165, 330, 165), left_mat); 53 | left_item = make_shared(left_item, 15); 54 | left_item = make_shared(left_item, Vec3(265, 0, 295)); 55 | 56 | // Right item is either a box or a sphere 57 | shared_ptr right_item; 58 | if ((config == CornellBoxConfiguration::TwoBoxes) || (config == CornellBoxConfiguration::TwoBoxesOneMirror)) { 59 | // default right item should be a box 60 | right_item = make_shared(Vec3(0), Vec3(165), white); 61 | right_item = make_shared(right_item, -18); 62 | right_item = make_shared(right_item, Vec3(130, 0, 65)); 63 | } else { 64 | // Else, it 's a (glass) sphere 65 | const auto glass = make_shared(1.5); 66 | const Vec3 loc(190, 90, 190); 67 | const rreal r = 90; 68 | right_item = make_shared(loc, r, glass); 69 | 70 | // This also requires an extra light 71 | lights.add(make_shared(loc, r, light)); 72 | } 73 | 74 | world.add(left_item); 75 | world.add(right_item); 76 | 77 | SceneDescriptor sd{}; 78 | sd.render_method = RenderMethod::Book3; 79 | sd.scene = make_shared(world); 80 | sd.lights = make_shared(lights); 81 | 82 | const Vec3 look_from(278, 278, -800); 83 | const Vec3 look_at(278, 278, 0); 84 | const Vec3 v_up(0, 1, 0); 85 | const rreal dist_to_focus = 10;//(look_from - look_at).length(); 86 | const rreal aperture = 0; 87 | sd.cameras.push_back(make_shared(look_from, look_at, v_up, 40, aspect_ratio, aperture, dist_to_focus, 0, 1)); 88 | 89 | return sd; 90 | } 91 | 92 | }} // Scenes::Book3 93 | -------------------------------------------------------------------------------- /render_library/Scenes/Book3.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.hpp" 4 | #include "SceneDescriptor.hpp" 5 | 6 | // Scenes from Book e (Ray Tracing the Rest of your life) of the Peter Shirley Raytracing minioooks 7 | 8 | 9 | namespace Scenes { 10 | namespace Book3 { 11 | // There are a few variations on the cornel box we have, so we need to use an enum to differentaite 12 | enum class CornellBoxConfiguration { 13 | TwoBoxes = 0, 14 | TwoBoxesOneMirror, 15 | OneBoxOneGlassSphere, 16 | OneMirrorBoxOneGlassSphere 17 | }; 18 | 19 | SceneDescriptor cornell_box(const rreal aspect_ratio, const CornellBoxConfiguration config); 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /render_library/Scenes/Fun.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.hpp" 4 | #include "SceneDescriptor.hpp" 5 | 6 | // Just some personal fun scenes, I also use them for testing 7 | 8 | namespace Scenes { 9 | namespace Fun { 10 | // A multi-layered glass sphere with a blue core, a normal surface sphere, and some fuzzy gold glass 11 | SceneDescriptor three_spheres(const rreal aspect_ratio); 12 | 13 | // One of the famous images from Turner Whitted's "An improved illumination model for shaded display" (1980) 14 | // With a mirror sphere, a glass one, and the red/yell checkboard floor 15 | SceneDescriptor whitted_1980(const rreal aspect_ratio); 16 | 17 | // A cornel box filled with glass cubes 18 | SceneDescriptor cornell_glass_boxes(const rreal aspect_ratio); 19 | 20 | // Spheres, that are in a wavy-pattern 21 | SceneDescriptor wave_of_spheres(const rreal aspect_ratio); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /render_library/Textures/CheckerTexture.cpp: -------------------------------------------------------------------------------- 1 | #include "CheckerTexture.hpp" 2 | #include "SolidColor.hpp" 3 | #include "Util.hpp" 4 | 5 | using namespace std; 6 | 7 | 8 | CheckerTexture::CheckerTexture(const shared_ptr &even, const shared_ptr &odd) NOEXCEPT : 9 | _even(even), 10 | _odd(odd) 11 | { } 12 | 13 | CheckerTexture::CheckerTexture(const Vec3 &even_clr, const Vec3 &odd_clr) NOEXCEPT : 14 | CheckerTexture( 15 | make_shared(even_clr), 16 | make_shared(odd_clr) 17 | ) 18 | { } 19 | 20 | shared_ptr CheckerTexture::deep_copy() const NOEXCEPT { 21 | // Make the deepy copy 22 | auto ct = make_shared(*this); 23 | ct->_even = _even->deep_copy(); 24 | ct->_odd = _odd->deep_copy(); 25 | 26 | return ct; 27 | } 28 | 29 | Vec3 CheckerTexture::value(const rreal u, const rreal v, const Vec3 &p) const NOEXCEPT { 30 | const rreal sines = util::sin(x_frequency * p.x) 31 | * util::sin(y_frequency * p.y) 32 | * util::sin(z_frequency * p.z); 33 | if (sines < 0) 34 | return _odd->value(u, v, p); 35 | else 36 | return _even->value(u, v, p); 37 | } 38 | -------------------------------------------------------------------------------- /render_library/Textures/CheckerTexture.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Interfaces/ITexture.hpp" 4 | #include 5 | 6 | 7 | class CheckerTexture FINAL : public ITexture { 8 | private: 9 | // Data 10 | std::shared_ptr _even = nullptr; 11 | std::shared_ptr _odd = nullptr; 12 | 13 | public: 14 | // Added by my so the user can tweak the fequency 15 | rreal x_frequency = 10; 16 | rreal y_frequency = 10; 17 | rreal z_frequency = 10; 18 | 19 | public: 20 | explicit CheckerTexture( 21 | const std::shared_ptr &even, 22 | const std::shared_ptr &odd 23 | ) NOEXCEPT; 24 | 25 | explicit CheckerTexture(const Vec3 &even_clr, const Vec3 &odd_clr) NOEXCEPT; 26 | 27 | std::shared_ptr deep_copy() const NOEXCEPT override; 28 | 29 | Vec3 value(const rreal u, const rreal v, const Vec3 &p) const NOEXCEPT override; 30 | }; 31 | -------------------------------------------------------------------------------- /render_library/Textures/ImageTexture.cpp: -------------------------------------------------------------------------------- 1 | #include "ImageTexture.hpp" 2 | #include "../third_party/stb_image.h" 3 | #include "Util.hpp" 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | 10 | const int BytesPerPixel = 3; 11 | 12 | 13 | 14 | ImageTexture::ImageTexture(const uint8_t *image_data, const int width, const int height, const int num_channels) : 15 | _width(width), 16 | _height(height), 17 | _bytes_per_scanline(num_channels * width) 18 | { 19 | // Copy the pixels into the image buffer 20 | const auto num_bytes = static_cast(width * height * num_channels); 21 | _img_data.assign(image_data, image_data + num_bytes); 22 | } 23 | 24 | 25 | // Note: I'm not too happy with how "duplicate" the two static `load_*()` methods are below, but I think it's 26 | // best to leave them like that for simplicty's scake 27 | 28 | 29 | shared_ptr ImageTexture::load_from_file(const char *filename) { 30 | int num_channels = BytesPerPixel; 31 | int width, height; 32 | 33 | // Load image data 34 | uint8_t *tmp = stbi_load(filename, &width, &height, &num_channels, num_channels); 35 | if (!tmp) 36 | throw std::runtime_error("Error, couldn't load texture image from file"); 37 | 38 | // Create texture 39 | auto texture = make_shared(tmp, width, height, num_channels); 40 | 41 | // Cleanup 42 | stbi_image_free(tmp); 43 | 44 | return texture; 45 | } 46 | 47 | 48 | shared_ptr ImageTexture::load_from_memory_buffer(const uint8_t *data, const int data_length) { 49 | int num_channels = BytesPerPixel; 50 | int width, height; 51 | 52 | // Load image data 53 | uint8_t *tmp = stbi_load_from_memory(data, data_length, &width, &height, &num_channels, num_channels); 54 | if (!tmp) 55 | throw std::runtime_error("Error, couldn't load texture image from memory buffer"); 56 | 57 | // Create texture 58 | auto texture = make_shared(tmp, width, height, num_channels); 59 | 60 | // Cleanup 61 | stbi_image_free(tmp); 62 | 63 | return texture; 64 | } 65 | 66 | 67 | shared_ptr ImageTexture::deep_copy() const NOEXCEPT { 68 | return make_shared(*this); 69 | } 70 | 71 | Vec3 ImageTexture::value( 72 | const rreal u, 73 | const rreal v, 74 | [[maybe_unused]] const Vec3 &p 75 | ) const NOEXCEPT { 76 | 77 | // Clamp input texture coordinates to [0,1] x [1,0] 78 | const rreal u2 = util::clamp(u, 0, 1); 79 | const rreal v2 = 1 - util::clamp(v, 0, 1); // Flip V to image coordinates 80 | 81 | auto i = static_cast(u2 * static_cast(_width)); 82 | auto j = static_cast(v2 * static_cast(_height)); 83 | 84 | // Clamp integer mapping, since actual coordinates should be less than 1.0 85 | i = std::min(i, _width - 1); 86 | j = std::min(j, _height - 1); 87 | 88 | const auto colour_scale = static_cast(1.0 / 255.0); 89 | const auto offset = static_cast((j * _bytes_per_scanline) + (i * BytesPerPixel)); 90 | 91 | return Vec3( 92 | colour_scale * _img_data[offset + 0], 93 | colour_scale * _img_data[offset + 1], 94 | colour_scale * _img_data[offset + 2] 95 | ); 96 | } 97 | -------------------------------------------------------------------------------- /render_library/Textures/ImageTexture.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Interfaces/ITexture.hpp" 4 | #include 5 | #include 6 | 7 | 8 | class ImageTexture FINAL : public ITexture { 9 | private: 10 | // Data 11 | std::vector _img_data; 12 | int _width = 0, _height = 0; 13 | int _bytes_per_scanline = 0; 14 | 15 | public: 16 | /** Creates an image texture with the raw pixel data passed into \a image_data. */ 17 | explicit ImageTexture(const uint8_t *image_data, const int width, const int height, const int num_channels); 18 | 19 | /** Loads an image texture from the given path at \a filename */ 20 | static std::shared_ptr load_from_file(const char *filename); 21 | 22 | /** 23 | * Loads an image texture from a memory buffer (e.g. an embedded resource) 24 | * 25 | * \a data is not the raw pixel data, but encoded in a format like JPEG, PNG, etc. 26 | */ 27 | static std::shared_ptr load_from_memory_buffer(const uint8_t *data, const int data_length); 28 | 29 | std::shared_ptr deep_copy() const NOEXCEPT override; 30 | 31 | Vec3 value(const rreal u, const rreal v, const Vec3 &p) const NOEXCEPT override; 32 | }; 33 | -------------------------------------------------------------------------------- /render_library/Textures/NoiseTexture.cpp: -------------------------------------------------------------------------------- 1 | #include "NoiseTexture.hpp" 2 | #include "Perlin.hpp" 3 | #include "PerlinReal.hpp" 4 | #include "Util.hpp" 5 | 6 | using namespace std; 7 | 8 | 9 | NoiseTexture::NoiseTexture(const std::shared_ptr &noise) NOEXCEPT : 10 | _noise(noise) 11 | { } 12 | 13 | NoiseTexture::NoiseTexture(const std::shared_ptr &noise, const rreal scale) NOEXCEPT : 14 | _scale(scale), 15 | _noise(noise) 16 | { } 17 | 18 | NoiseTexture::NoiseTexture(const std::shared_ptr &noise) NOEXCEPT : 19 | _noise_real(noise) 20 | { } 21 | 22 | NoiseTexture::NoiseTexture(const std::shared_ptr &noise, const rreal scale) NOEXCEPT : 23 | _scale(scale), 24 | _noise_real(noise) 25 | { } 26 | 27 | shared_ptr NoiseTexture::deep_copy() const NOEXCEPT { 28 | // Deep copy all shared pointers 29 | auto nt = make_shared(*this); 30 | 31 | // TODO: if using the Book's perlin implementation, I think this would fail since there is no compiler 32 | // implemented copy constructors, test it! 33 | if (_noise) 34 | nt->_noise = make_shared(*_noise); 35 | if (_noise_real) 36 | nt->_noise_real = make_shared(*_noise_real); 37 | 38 | return nt; 39 | } 40 | 41 | Vec3 NoiseTexture::value( 42 | [[maybe_unused]] const rreal u, 43 | [[maybe_unused]] const rreal v, 44 | const Vec3 &p 45 | ) const NOEXCEPT { 46 | // First default to using the vector based perlin noise 47 | if (_noise) { 48 | switch (style) { 49 | case NoiseStyle::Normal: return Vec3(0.5) * (1 + _noise->noise(_scale * p)); // Non-turbulent 50 | case NoiseStyle::Turbulent: return Vec3(_noise->turb(_scale * p)); 51 | case NoiseStyle::Marble: return Vec3(0.5) * (1 + util::sin((_scale * p.z) + (10 * _noise->turb(p)))); 52 | case NoiseStyle::Book2FinalScene: return Vec3(0.5) * (1 + util::sin((_scale * p.x) + (5 * _noise->turb(_scale * p)))); 53 | } 54 | 55 | // Use the non-turbulence version 56 | } 57 | 58 | // Default, use the `real` based noise 59 | return Vec3(_noise_real->noise(_scale * p)); 60 | } 61 | -------------------------------------------------------------------------------- /render_library/Textures/NoiseTexture.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Interfaces/ITexture.hpp" 4 | #include 5 | class PerlinReal; 6 | class Perlin; 7 | 8 | 9 | // TODO [feature] 10 | // - coloured noise? right now it's only return a greyscale. 11 | // we could call the `noise.noise()` function three times and get something different 12 | 13 | class NoiseTexture FINAL : public ITexture { 14 | public: 15 | enum class NoiseStyle { 16 | Normal = 0, 17 | Turbulent, 18 | Marble, 19 | Book2FinalScene, 20 | }; 21 | 22 | private: 23 | // Data 24 | rreal _scale = 1; 25 | std::shared_ptr _noise = nullptr; // Can only be one of these two 26 | std::shared_ptr _noise_real = nullptr; 27 | 28 | public: 29 | // options for regular `Perlin` 30 | NoiseStyle style = NoiseStyle::Marble; 31 | 32 | explicit NoiseTexture(const std::shared_ptr &noise) NOEXCEPT; 33 | explicit NoiseTexture(const std::shared_ptr &noise, const rreal scale) NOEXCEPT; 34 | explicit NoiseTexture(const std::shared_ptr &noise) NOEXCEPT; 35 | explicit NoiseTexture(const std::shared_ptr &noise, const rreal scale) NOEXCEPT; 36 | 37 | std::shared_ptr deep_copy() const NOEXCEPT override; 38 | 39 | Vec3 value(const rreal u, const rreal v, const Vec3 &p) const NOEXCEPT override; 40 | }; 41 | -------------------------------------------------------------------------------- /render_library/Textures/SolidColor.cpp: -------------------------------------------------------------------------------- 1 | #include "SolidColor.hpp" 2 | 3 | using namespace std; 4 | 5 | 6 | Vec3 SolidColor::value( 7 | [[maybe_unused]] const rreal u, 8 | [[maybe_unused]] const rreal v, 9 | [[maybe_unused]] const Vec3 &p 10 | ) const NOEXCEPT { 11 | return _colour_value; 12 | } 13 | 14 | shared_ptr SolidColor::deep_copy() const NOEXCEPT { 15 | return make_shared(*this); 16 | } 17 | -------------------------------------------------------------------------------- /render_library/Textures/SolidColor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Interfaces/ITexture.hpp" 4 | 5 | 6 | class SolidColor FINAL : public ITexture { 7 | private: 8 | // Data 9 | Vec3 _colour_value = Vec3(0); // Default is black 10 | 11 | public: 12 | explicit SolidColor(const Vec3 &c) NOEXCEPT : 13 | _colour_value(c) 14 | { } 15 | 16 | explicit SolidColor(const rreal red, const rreal green, const rreal blue) NOEXCEPT : 17 | SolidColor(Vec3(red, green, blue)) 18 | { } 19 | 20 | std::shared_ptr deep_copy() const NOEXCEPT override; 21 | 22 | Vec3 value(const rreal u, const rreal v, const Vec3 &p) const NOEXCEPT override; 23 | }; 24 | -------------------------------------------------------------------------------- /render_library/Util.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.hpp" 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | // A small set of utility functions 10 | namespace util { 11 | inline rreal degrees_to_radians(const rreal degrees) NOEXCEPT { 12 | return degrees * Pi / static_cast(180); 13 | } 14 | 15 | inline rreal clamp(const rreal x, const rreal lower, const rreal upper) NOEXCEPT { 16 | #ifdef USE_BOOK_CLAMP 17 | if (x < lower) 18 | return lower; 19 | if (x > upper) 20 | return upper; 21 | 22 | return x; 23 | #else 24 | // C++17 and above has a built-in clamp function. if not, it can also be recreated with `min()` and `max()` calls 25 | // After inspecting on godbolt, using std::clamp save about one instruction. But after measuring 26 | // the effects seemed almost negligable. 27 | return std::clamp(x, lower, upper); 28 | #endif 29 | } 30 | 31 | // Note, this is actually an approximation by Christophine Schlick for determine when a material 32 | // becomes mirrorlike at an angle 33 | inline rreal schlick(const rreal cosine, const rreal refractive_index) NOEXCEPT { 34 | #ifdef USE_BOOK_SCHLICK 35 | rreal r0 = (1 - refractive_index) / (1 + refractive_index); 36 | r0 = r0 * r0; 37 | return r0 + ((1 - r0) * std::pow((1 - cosine), 5)); 38 | #else 39 | // This tries to take advantage of the compiler converting these math operations 40 | // into vectorized instructions. std::pow() can also be kinda slow compared to 41 | // writing out all of the multiplies. 42 | // https://baptiste-wicht.com/posts/2017/09/cpp11-performance-tip-when-to-use-std-pow.html 43 | const rreal alpha = 1 - cosine; 44 | const rreal beta = alpha * alpha * alpha * alpha * alpha; 45 | 46 | const rreal r0 = (1 - refractive_index) / (1 + refractive_index); 47 | const rreal r1 = r0 * r0; 48 | 49 | return r1 + ((1 - r1) * beta); 50 | #endif 51 | } 52 | 53 | #ifndef USE_BOOK_SIN_COS 54 | rreal _cos_approx_private(rreal x) NOEXCEPT; 55 | #endif 56 | 57 | inline rreal cos(const rreal x) NOEXCEPT { 58 | #ifdef USE_BOOK_SIN_COS 59 | return std::cos(x); 60 | #else 61 | return _cos_approx_private(x); 62 | #endif 63 | } 64 | 65 | inline rreal sin(const rreal x) NOEXCEPT { 66 | #ifdef USE_BOOK_SIN_COS 67 | return std::sin(x); 68 | #else 69 | // Due to trig identities, we can reuse our cosine computation (with an offset) 70 | return util::cos(HalfPi - x); 71 | #endif 72 | } 73 | 74 | 75 | #ifndef USE_BOOK_ATAN2 76 | rreal _atan2_approx_private(const rreal y, const rreal x) NOEXCEPT; 77 | #endif 78 | 79 | inline rreal atan2(const rreal y, const rreal x) NOEXCEPT { 80 | #ifdef USE_BOOK_ATAN2 81 | return std::atan2(y, x); 82 | #else 83 | return _atan2_approx_private(y, x); 84 | #endif 85 | } 86 | 87 | 88 | 89 | #ifndef USE_BOOK_ASIN 90 | rreal _asin_approx_private(const rreal x) NOEXCEPT; 91 | #endif 92 | 93 | inline rreal asin(const rreal x) NOEXCEPT { 94 | #ifdef USE_BOOK_ASIN 95 | return std::asin(x); 96 | #else 97 | return _asin_approx_private(x); 98 | #endif 99 | } 100 | 101 | 102 | #ifndef USE_BOOK_SQRT 103 | rreal _sqrt_approx_private(const rreal x) NOEXCEPT; 104 | #endif 105 | 106 | inline rreal sqrt(const rreal x) NOEXCEPT { 107 | #ifdef USE_BOOK_SQRT 108 | return std::sqrt(x); 109 | #else 110 | return _sqrt_approx_private(x); 111 | #endif 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /render_library/Vec3.cpp: -------------------------------------------------------------------------------- 1 | #include "Vec3.hpp" 2 | #include 3 | 4 | 5 | std::ostream &operator<<(std::ostream &out, const Vec3 &v) { 6 | return out << "Vec3(" << v.x << ", " << v.y << ", " << v.z << ')'; 7 | } 8 | -------------------------------------------------------------------------------- /render_library/render.hpp: -------------------------------------------------------------------------------- 1 | // This is the interface to use the PSRayTracing code as a library 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | namespace psrt { 12 | 13 | 14 | /** A descriptor of how a render should be performed */ 15 | struct RenderJob 16 | { 17 | uint16_t render_width = 960; 18 | uint16_t render_height = 540; 19 | std::string scene_id = "book2::final_scene"; 20 | uint32_t samples_per_pixel = 10; 21 | uint16_t max_depth = 50; 22 | uint16_t num_threads = 1; 23 | std::string seed_str = "ASDF"; 24 | bool deep_copy_per_thread = true; 25 | }; 26 | 27 | 28 | /** The result of a render job. */ 29 | struct Render 30 | { 31 | std::vector image_data; 32 | uint16_t render_width = 0; 33 | uint16_t render_height = 0; 34 | uint8_t num_channels = 0; 35 | float render_time_in_seconds = 0.0f; 36 | std::chrono::milliseconds render_time_in_milliseconds; 37 | 38 | bool was_succesful() const 39 | { return !image_data.empty(); } 40 | }; 41 | 42 | 43 | // The fuctions to perform a render 44 | std::future do_render(const RenderJob &job); ///< Will start a render job (if one isn't in progress) 45 | void stop_active_render(); ///< Will stop a render job (if one is in progress) 46 | float render_progress(); ///< Returns the progress of a render [0.0, 1.0]. Returns less than 0 (e.g. -1) if no render is happening 47 | bool render_in_progress(); ///< Checks to see if a render is already in progress (or not) 48 | std::vector all_scene_ids(); ///< Retrives all possible scenes that could be rendered 49 | int num_concurrent_threads_supported(); ///< Retrives the maximum number of threads that can ru concurrently 50 | 51 | 52 | } // psrt:: 53 | -------------------------------------------------------------------------------- /third_party/stb_image.c: -------------------------------------------------------------------------------- 1 | // implement STB Image here to save on compile times 2 | 3 | #define STB_IMAGE_IMPLEMENTATION 4 | #include "stb_image.h" 5 | -------------------------------------------------------------------------------- /third_party/stb_image_write.c: -------------------------------------------------------------------------------- 1 | // implement STB Image here to save on compile times 2 | 3 | #define STB_IMAGE_WRITE_IMPLEMENTATION 4 | #include "stb_image_write.h" 5 | --------------------------------------------------------------------------------