├── .gitignore ├── .gitmodules ├── .travis.yml ├── .ycm_extra_conf.py ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── res ├── DejaVuSans.ttf ├── DejaVuSansMono-Bold.ttf ├── DejaVuSansMono.ttf └── gradient.png ├── tests ├── test_camera.cpp └── test_packing.cpp ├── trac0r ├── aabb.cpp ├── aabb.hpp ├── camera.cpp ├── camera.hpp ├── filtering.hpp ├── flat_structure.cpp ├── flat_structure.hpp ├── intersection_info.hpp ├── intersections.hpp ├── light_vertex.hpp ├── material.hpp ├── random.hpp ├── ray.hpp ├── renderer.cpp ├── renderer.hpp ├── renderer_aux.cl ├── renderer_aux.cpp ├── scene.cpp ├── scene.hpp ├── shape.cpp ├── shape.hpp ├── timer.hpp ├── triangle.hpp └── utils.hpp └── viewer ├── main.cpp ├── viewer.cpp └── viewer.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | 30 | # Python cache 31 | *.pyc 32 | 33 | # Build dirs 34 | external/ 35 | build/ 36 | build-web/ 37 | 38 | # Misc 39 | *.plist 40 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/cppformat"] 2 | path = external/cppformat 3 | url = https://github.com/cppformat/cppformat.git 4 | [submodule "external/glm"] 5 | path = external/glm 6 | url = https://github.com/g-truc/glm.git 7 | [submodule "external/sdl2_gfx"] 8 | path = external/sdl2_gfx 9 | url = https://github.com/svn2github/sdl2_gfx.git 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | language: cpp 4 | services: 5 | - docker 6 | 7 | before_install: 8 | - docker pull pritunl/archlinux 9 | 10 | matrix: 11 | include: 12 | - env: COMPILER="gcc" 13 | - env: COMPILER="clang" 14 | - env: COMPILER="emscripten" 15 | allow_failures: 16 | - env: COMPILER="emscripten" 17 | 18 | script: 19 | PACKAGES='base-devel emscripten sdl2 sdl2_gfx sdl2_image sdl2_mixer sdl2_ttf cmake clang python'; 20 | if [[ $COMPILER = 'gcc' ]]; then 21 | docker run -v ${TRAVIS_BUILD_DIR}:/tmp pritunl/archlinux bash -c "pacman -Syu --noconfirm ${PACKAGES}; cd /tmp; make"; 22 | elif [[ $COMPILER = 'clang' ]]; then 23 | docker run -v ${TRAVIS_BUILD_DIR}:/tmp pritunl/archlinux bash -c "pacman -Syu --noconfirm ${PACKAGES}; cd /tmp; make clang"; 24 | elif [[ $COMPILER = 'emscripten' ]]; then 25 | docker run -v ${TRAVIS_BUILD_DIR}:/tmp pritunl/archlinux bash -c "pacman -Syu --noconfirm ${PACKAGES}; cd /tmp; make web; make web"; 26 | fi 27 | -------------------------------------------------------------------------------- /.ycm_extra_conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | import ycm_core 3 | 4 | # These are the compilation flags that will be used in case there's no 5 | # compilation database set (by default, one is not set). 6 | # CHANGE THIS LIST OF FLAGS. YES, THIS IS THE DROID YOU HAVE BEEN LOOKING FOR. 7 | flags = [ 8 | '-Wall', 9 | '-Wextra', 10 | '-Werror', 11 | '-fexceptions', 12 | '-DNDEBUG', 13 | # THIS IS IMPORTANT! Without a "-std=" flag, clang won't know which 14 | # language to use when compiling headers. So it will guess. Badly. So C++ 15 | # headers will be compiled as C headers. You don't want that so ALWAYS specify 16 | # a "-std=". 17 | # For a C project, you would set this to something like 'c99' instead of 18 | # 'c++11'. 19 | '-std=c++14', 20 | # ...and the same thing goes for the magic -x option which specifies the 21 | # language that the files to be compiled are written in. This is mostly 22 | # relevant for c++ headers. 23 | # For a C project, you would set this to 'c' instead of 'c++'. 24 | '-x', 25 | 'c++', 26 | '-Iexternal', 27 | '-I/usr/include/SDL2', 28 | '-Itrac0r', 29 | '-I', 30 | '.', 31 | ] 32 | 33 | 34 | # Set this to the absolute path to the folder (NOT the file!) containing the 35 | # compile_commands.json file to use that instead of 'flags'. See here for 36 | # more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html 37 | # 38 | # Most projects will NOT need to set this to anything; you can just change the 39 | # 'flags' list of compilation flags. Notice that YCM itself uses that approach. 40 | compilation_database_folder = '' 41 | 42 | if os.path.exists( compilation_database_folder ): 43 | database = ycm_core.CompilationDatabase( compilation_database_folder ) 44 | else: 45 | database = None 46 | 47 | SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ] 48 | 49 | def DirectoryOfThisScript(): 50 | return os.path.dirname( os.path.abspath( __file__ ) ) 51 | 52 | 53 | def MakeRelativePathsInFlagsAbsolute( flags, working_directory ): 54 | if not working_directory: 55 | return list( flags ) 56 | new_flags = [] 57 | make_next_absolute = False 58 | path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ] 59 | for flag in flags: 60 | new_flag = flag 61 | 62 | if make_next_absolute: 63 | make_next_absolute = False 64 | if not flag.startswith( '/' ): 65 | new_flag = os.path.join( working_directory, flag ) 66 | 67 | for path_flag in path_flags: 68 | if flag == path_flag: 69 | make_next_absolute = True 70 | break 71 | 72 | if flag.startswith( path_flag ): 73 | path = flag[ len( path_flag ): ] 74 | new_flag = path_flag + os.path.join( working_directory, path ) 75 | break 76 | 77 | if new_flag: 78 | new_flags.append( new_flag ) 79 | return new_flags 80 | 81 | 82 | def IsHeaderFile( filename ): 83 | extension = os.path.splitext( filename )[ 1 ] 84 | return extension in [ '.h', '.hxx', '.hpp', '.hh' ] 85 | 86 | 87 | def GetCompilationInfoForFile( filename ): 88 | # The compilation_commands.json file generated by CMake does not have entries 89 | # for header files. So we do our best by asking the db for flags for a 90 | # corresponding source file, if any. If one exists, the flags for that file 91 | # should be good enough. 92 | if IsHeaderFile( filename ): 93 | basename = os.path.splitext( filename )[ 0 ] 94 | for extension in SOURCE_EXTENSIONS: 95 | replacement_file = basename + extension 96 | if os.path.exists( replacement_file ): 97 | compilation_info = database.GetCompilationInfoForFile( 98 | replacement_file ) 99 | if compilation_info.compiler_flags_: 100 | return compilation_info 101 | return None 102 | return database.GetCompilationInfoForFile( filename ) 103 | 104 | 105 | def FlagsForFile( filename, **kwargs ): 106 | if database: 107 | # Bear in mind that compilation_info.compiler_flags_ does NOT return a 108 | # python list, but a "list-like" StringVec object 109 | compilation_info = GetCompilationInfoForFile( filename ) 110 | if not compilation_info: 111 | return None 112 | 113 | final_flags = MakeRelativePathsInFlagsAbsolute( 114 | compilation_info.compiler_flags_, 115 | compilation_info.compiler_working_dir_ ) 116 | 117 | else: 118 | relative_to = DirectoryOfThisScript() 119 | final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to ) 120 | 121 | return { 122 | 'flags': final_flags, 123 | 'do_cache': True 124 | } 125 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | project(trac0r) 3 | include(ExternalProject) 4 | 5 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") 6 | find_package(OpenMP) 7 | elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") 8 | find_library(OpenMP NAMES omp) 9 | if (OpenMP) 10 | set(OpenMP_CXX_FLAGS -fopenmp=libomp) 11 | endif() 12 | endif() 13 | 14 | set(trac0r_flags -O3 -g ${OpenMP_CXX_FLAGS} -march=native -mtune=native -Wall -Wextra -pedantic -Werror -std=c++14 -Wno-unused-parameter) 15 | #set(trac0r_flags -O0 -g ${OpenMP_CXX_FLAGS} -Wall -Wextra -pedantic -Werror -std=c++14 -Wno-unused-parameter) 16 | set(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} ${OpenMP_CXX_FLAGS}") 17 | 18 | file(GLOB cppformat_src external/cppformat/fmt/*.cc) 19 | add_library(cppformat ${cppformat_src}) 20 | 21 | file(GLOB sdl2_gfx_src external/sdl2_gfx/*.c) 22 | add_library(sdl2_gfx ${sdl2_gfx_src}) 23 | 24 | file(GLOB trac0r_library_src trac0r/*.cpp) 25 | file(GLOB trac0r_viewer_src viewer/*.cpp) 26 | 27 | add_library(trac0r_library ${trac0r_library_src}) 28 | add_executable(trac0r_viewer ${trac0r_viewer_src}) 29 | add_executable(trac0r_test_camera tests/test_camera.cpp) 30 | add_executable(trac0r_test_packing tests/test_packing.cpp) 31 | 32 | target_compile_options(trac0r_library PUBLIC ${trac0r_flags}) 33 | target_compile_options(trac0r_viewer PUBLIC ${trac0r_flags}) 34 | target_compile_options(trac0r_test_camera PUBLIC ${trac0r_flags}) 35 | target_compile_options(trac0r_test_packing PUBLIC ${trac0r_flags}) 36 | 37 | if(${BENCHMARK}) 38 | add_definitions("-DBENCHMARK") 39 | endif() 40 | 41 | if(${OPENCL}) 42 | find_package(OpenCL) 43 | add_definitions("-DOPENCL") 44 | endif() 45 | 46 | if(${EMSCRIPTEN}) 47 | set(CROSS_COMPILING ON) 48 | add_definitions("-O3 -s USE_SDL=2 -s USE_SDL_IMAGE=2 -s USE_SDL_TTF=2") 49 | set(CMAKE_CXX_LINK_FLAGS "-O3 -s USE_SDL=2 -s USE_SDL_IMAGE=2 -s USE_SDL_TTF=2 -s TOTAL_MEMORY=32000000 --emrun --preload-file ../res -o index.html") 50 | else() 51 | include(FindPkgConfig) 52 | pkg_check_modules(SDL2 REQUIRED sdl2 SDL2_image SDL2_ttf) 53 | endif() 54 | 55 | include_directories(SYSTEM 56 | ${SDL2_INCLUDE_DIRS} 57 | external/cppformat 58 | external/glm 59 | external/sdl2_gfx 60 | ${CMAKE_SOURCE_DIR} 61 | ${OpenCL_INCLUDE_DIRS} 62 | ) 63 | 64 | target_link_libraries(trac0r_library 65 | cppformat 66 | ${OpenCL_LIBRARIES} 67 | ) 68 | 69 | target_link_libraries(trac0r_viewer 70 | trac0r_library 71 | sdl2_gfx 72 | ${SDL2_LIBRARIES} 73 | ) 74 | 75 | target_link_libraries(trac0r_test_camera trac0r_library) 76 | target_link_libraries(trac0r_test_packing trac0r_library) 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Sven-Hendrik Haase 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: web run default clean clang webrun 2 | 3 | default: gcc 4 | 5 | gcc: 6 | mkdir -p build 7 | cd build; CXX=g++ cmake ..; make -j 8 | 9 | gcc-opencl: 10 | mkdir -p build 11 | cd build; CXX=g++ cmake -DOPENCL=1 ..; make -j 12 | 13 | clang: 14 | mkdir -p build 15 | cd build; CXX=clang++ cmake ..; make -j 16 | 17 | clang-opencl: 18 | mkdir -p build 19 | cd build; CXX=clang++ cmake -DOPENCL=1 ..; make -j 20 | 21 | benchmark: default 22 | build/trac0r_viewer -b1 23 | build/trac0r_viewer -b2 24 | build/trac0r_viewer -b3 25 | build/trac0r_viewer -b4 26 | build/trac0r_viewer -b5 27 | 28 | memcheck: default 29 | valgrind --leak-check=full build/trac0r_viewer 30 | 31 | cachecheck: default 32 | perf stat -r 5 -B -e cache-references,cache-misses build/trac0r_viewer 33 | 34 | run: default 35 | build/trac0r_viewer 36 | 37 | web: 38 | mkdir -p build-web 39 | cd build-web; /usr/lib/emscripten/emcmake cmake ..; make 40 | 41 | webrun: 42 | emrun build-web/index.html 43 | 44 | webpublish: web 45 | cp build-web/index.* ~/prj/trac0r-pages 46 | cd ~/prj/trac0r-pages; git commit -am "Update"; git push 47 | 48 | clean: 49 | rm -rf build 50 | rm -rf build-web 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # trac0r 2 | 3 | [![Build Status](https://travis-ci.org/svenstaro/trac0r.svg)](https://travis-ci.org/svenstaro/trac0r) 4 | 5 | A fast real time physically based renderer using bidirectional Monte Carlo path tracing and the Metropolis light transport. 6 | 7 | ![](https://i.imgur.com/FfEE3DO.png) 8 | ![](https://i.imgur.com/QuGYuFr.png) 9 | ![](https://i.imgur.com/RkAnsi3.jpg) 10 | ![](https://i.imgur.com/NJFpewW.png) 11 | -------------------------------------------------------------------------------- /res/DejaVuSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svenstaro/trac0r/75ff6c39a02ecc96e2516e9f38c49c03d90c552e/res/DejaVuSans.ttf -------------------------------------------------------------------------------- /res/DejaVuSansMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svenstaro/trac0r/75ff6c39a02ecc96e2516e9f38c49c03d90c552e/res/DejaVuSansMono-Bold.ttf -------------------------------------------------------------------------------- /res/DejaVuSansMono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svenstaro/trac0r/75ff6c39a02ecc96e2516e9f38c49c03d90c552e/res/DejaVuSansMono.ttf -------------------------------------------------------------------------------- /res/gradient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svenstaro/trac0r/75ff6c39a02ecc96e2516e9f38c49c03d90c552e/res/gradient.png -------------------------------------------------------------------------------- /tests/test_camera.cpp: -------------------------------------------------------------------------------- 1 | #include "trac0r/camera.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | 11 | int main(int argc, char *argv[]) { 12 | (void)argc; 13 | (void)argv; 14 | 15 | glm::vec3 cam_pos = {0, 0.31, -1.2}; 16 | glm::vec3 cam_dir = {0, 0, 1}; 17 | cam_dir = glm::rotateY(cam_dir, 0.6f); 18 | cam_dir = glm::rotateX(cam_dir, 0.6f); 19 | glm::vec3 world_up = {0, 1, 0}; 20 | 21 | using Camera = trac0r::Camera; 22 | Camera camera(cam_pos, cam_dir, world_up, 90.f, 0.001, 100.f, 800, 600); 23 | 24 | fmt::print("Canvas Center Pos {}\n", glm::to_string(Camera::canvas_center_pos(camera))); 25 | fmt::print("Canvas Canvas Width/Height {}/{}\n", Camera::canvas_width(camera), camera.canvas_height(camera)); 26 | fmt::print("Canvas Dir X {}\n", glm::to_string(Camera::canvas_dir_x(camera))); 27 | fmt::print("Canvas Dir Y {}\n\n", glm::to_string(Camera::canvas_dir_y(camera))); 28 | 29 | glm::i32vec2 ss1 {600, 100}; 30 | 31 | auto cs1 = Camera::screenspace_to_camspace(camera, ss1.x, ss1.y); 32 | auto ws1 = Camera::camspace_to_worldspace(camera, cs1); 33 | fmt::print("Converting screen space to cam space: "); 34 | fmt::print("{} -> {}\n", glm::to_string(ss1), glm::to_string(cs1)); 35 | fmt::print("Converting cam space to world space on canvas: "); 36 | fmt::print("{} -> {}\n\n", glm::to_string(cs1), glm::to_string(ws1)); 37 | 38 | auto cs2 = Camera::worldspace_to_camspace(camera, ws1); 39 | auto ss2 = Camera::camspace_to_screenspace(camera, cs2); 40 | fmt::print("Converting world space on canvas BACK to cam space: "); 41 | fmt::print("{} -> {}\n", glm::to_string(ws1), glm::to_string(cs2)); 42 | fmt::print("Converting cam space BACK to screen space: "); 43 | fmt::print("{} -> {}\n", glm::to_string(cs2), glm::to_string(ss2)); 44 | 45 | return 0; 46 | } 47 | -------------------------------------------------------------------------------- /tests/test_packing.cpp: -------------------------------------------------------------------------------- 1 | #include "trac0r/utils.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | int main(int argc, char *argv[]) { 9 | (void)argc; 10 | (void)argv; 11 | 12 | auto unpacked = glm::vec4{0.5, 0.5, 0.5, 0.5}; 13 | for (int i = 0; i < 10000; ++i) { 14 | auto packed = trac0r::pack_color_argb(unpacked); 15 | unpacked = trac0r::unpack_color_argb_to_vec4(packed); 16 | } 17 | 18 | std::cout << glm::to_string(unpacked) << std::endl; 19 | 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /trac0r/aabb.cpp: -------------------------------------------------------------------------------- 1 | #include "aabb.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace trac0r { 10 | 11 | bool AABB::is_null(const AABB &aabb) { 12 | bool min_null = glm::isNull(aabb.m_min, glm::epsilon()); 13 | bool max_null = glm::isNull(aabb.m_max, glm::epsilon()); 14 | return min_null && max_null; 15 | } 16 | 17 | glm::vec3 AABB::min(const AABB &aabb) { 18 | return aabb.m_min; 19 | } 20 | 21 | glm::vec3 AABB::max(const AABB &aabb) { 22 | return aabb.m_max; 23 | } 24 | 25 | glm::vec3 AABB::diagonal(const AABB &aabb) { 26 | if (!is_null(aabb)) 27 | return aabb.m_max - aabb.m_min; 28 | else 29 | return glm::vec3(0); 30 | } 31 | 32 | glm::vec3 AABB::center(const AABB &aabb) { 33 | if (!is_null(aabb)) 34 | return aabb.m_min + (diagonal(aabb) * 0.5f); 35 | else 36 | return glm::vec3(0); 37 | } 38 | 39 | std::array AABB::vertices(AABB &aabb) { 40 | std::array result; 41 | result[0] = {aabb.m_min.x, aabb.m_min.y, aabb.m_min.z}; // lower left front 42 | result[1] = {aabb.m_max.x, aabb.m_min.y, aabb.m_min.z}; // lower right front 43 | result[2] = {aabb.m_min.x, aabb.m_max.y, aabb.m_min.z}; // upper left front 44 | result[3] = {aabb.m_max.x, aabb.m_max.y, aabb.m_min.z}; // upper right front 45 | result[4] = {aabb.m_min.x, aabb.m_min.y, aabb.m_max.z}; // lower left back 46 | result[5] = {aabb.m_max.x, aabb.m_min.y, aabb.m_max.z}; // lower right back 47 | result[6] = {aabb.m_min.x, aabb.m_max.y, aabb.m_max.z}; // upper left back 48 | result[7] = {aabb.m_max.x, aabb.m_max.y, aabb.m_max.z}; // upper right back 49 | return result; 50 | } 51 | 52 | void AABB::extend(AABB &aabb, glm::vec3 &point) { 53 | if (!is_null(aabb)) { 54 | aabb.m_min = glm::min(point, aabb.m_min); 55 | aabb.m_max = glm::max(point, aabb.m_max); 56 | } else { 57 | aabb.m_min = point; 58 | aabb.m_max = point; 59 | } 60 | } 61 | 62 | bool AABB::overlaps(const AABB &first, const AABB &second) { 63 | if (is_null(first) || is_null(second)) 64 | return false; 65 | 66 | return first.m_max.x > second.m_min.x && first.m_min.x < second.m_max.x && 67 | first.m_max.y > second.m_min.y && first.m_min.y < second.m_max.y && 68 | first.m_max.z > second.m_min.z && first.m_min.z < second.m_max.z; 69 | } 70 | 71 | void AABB::reset(AABB &aabb) { 72 | aabb.m_min = glm::vec3(0); 73 | aabb.m_max = glm::vec3(0); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /trac0r/aabb.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AABB_HPP 2 | #define AABB_HPP 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace trac0r { 9 | 10 | class AABB { 11 | public: 12 | static bool is_null(const AABB &aabb); 13 | static glm::vec3 min(const AABB &aabb); 14 | static glm::vec3 max(const AABB &aabb); 15 | static glm::vec3 diagonal(const AABB &aabb); 16 | static glm::vec3 center(const AABB &aabb); 17 | static std::array vertices(AABB &aabb); 18 | static void extend(AABB &aabb, glm::vec3 &point); 19 | static bool overlaps(const AABB &first, const AABB &second); 20 | static void reset(AABB &aabb); 21 | 22 | private: 23 | glm::vec3 m_min; 24 | glm::vec3 m_max; 25 | }; 26 | } 27 | 28 | #endif /* end of include guard: AABB_HPP */ 29 | -------------------------------------------------------------------------------- /trac0r/camera.cpp: -------------------------------------------------------------------------------- 1 | #include "camera.hpp" 2 | 3 | #include "random.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | namespace trac0r { 14 | 15 | Camera::Camera() { 16 | } 17 | 18 | Camera::Camera(glm::vec3 pos, glm::vec3 dir, glm::vec3 world_up, float vertical_fov_degrees, 19 | float near_plane_dist, float far_plane_dist, int screen_width, int screen_height) 20 | : m_pos(pos), m_dir(dir), m_world_up(world_up), m_near_plane_dist(near_plane_dist), 21 | m_far_plane_dist(far_plane_dist), m_screen_width(screen_width), 22 | m_screen_height(screen_height) { 23 | 24 | set_vertical_fov(*this, vertical_fov_degrees); 25 | rebuild(*this); 26 | } 27 | 28 | glm::vec3 Camera::pos(const Camera &camera) { 29 | return camera.m_pos; 30 | } 31 | 32 | void Camera::set_pos(Camera &camera, glm::vec3 pos) { 33 | camera.m_pos = pos; 34 | rebuild(camera); 35 | } 36 | 37 | glm::vec3 Camera::dir(const Camera &camera) { 38 | return camera.m_dir; 39 | } 40 | 41 | void Camera::set_dir(Camera &camera, glm::vec3 dir) { 42 | camera.m_dir = dir; 43 | rebuild(camera); 44 | } 45 | 46 | glm::vec3 Camera::world_up(const Camera &camera) { 47 | return camera.m_world_up; 48 | } 49 | 50 | void Camera::set_world_up(Camera &camera, glm::vec3 world_up) { 51 | camera.m_world_up = world_up; 52 | rebuild(camera); 53 | } 54 | 55 | glm::vec3 Camera::right(const Camera &camera) { 56 | return camera.m_right; 57 | } 58 | 59 | glm::vec3 Camera::up(const Camera &camera) { 60 | return camera.m_up; 61 | } 62 | 63 | float Camera::near_plane_dist(const Camera &camera) { 64 | return camera.m_near_plane_dist; 65 | } 66 | 67 | void Camera::set_near_plane_dist(Camera &camera, float dist) { 68 | camera.m_near_plane_dist = dist; 69 | rebuild(camera); 70 | } 71 | 72 | float Camera::far_plane_dist(const Camera &camera) { 73 | return camera.m_far_plane_dist; 74 | } 75 | 76 | void Camera::set_far_plane_dist(Camera &camera, float dist) { 77 | camera.m_far_plane_dist = dist; 78 | rebuild(camera); 79 | } 80 | 81 | int Camera::screen_width(const Camera &camera) { 82 | return camera.m_screen_width; 83 | } 84 | 85 | int Camera::screen_height(const Camera &camera) { 86 | return camera.m_screen_height; 87 | } 88 | 89 | float Camera::vertical_fov(const Camera &camera) { 90 | return camera.m_vertical_fov; 91 | } 92 | 93 | void Camera::set_vertical_fov(Camera &camera, float degrees) { 94 | // See https://en.wikipedia.org/wiki/Field_of_view_in_video_games 95 | camera.m_vertical_fov = glm::radians(degrees); 96 | camera.m_horizontal_fov = 97 | 2 * glm::atan(glm::tan(vertical_fov(camera) / 2) * 98 | ((float)screen_width(camera) / (float)screen_height(camera))); 99 | } 100 | 101 | float Camera::horizontal_fov(const Camera &camera) { 102 | return camera.m_horizontal_fov; 103 | } 104 | 105 | float Camera::aspect_ratio(const Camera &camera) { 106 | return screen_width(camera) / screen_height(camera); 107 | } 108 | 109 | float Camera::canvas_width(const Camera &camera) { 110 | return camera.m_canvas_width; 111 | } 112 | 113 | float Camera::canvas_height(const Camera &camera) { 114 | return camera.m_canvas_height; 115 | } 116 | 117 | glm::vec3 Camera::canvas_center_pos(const Camera &camera) { 118 | return camera.m_canvas_center_pos; 119 | } 120 | 121 | glm::vec3 Camera::canvas_dir_x(const Camera &camera) { 122 | return camera.m_canvas_dir_x; 123 | } 124 | 125 | glm::vec3 Camera::canvas_dir_y(const Camera &camera) { 126 | return camera.m_canvas_dir_y; 127 | } 128 | 129 | glm::vec2 Camera::pixel_size(const Camera &camera) { 130 | return {1.f / screen_width(camera), 1.f / screen_height(camera)}; 131 | } 132 | 133 | glm::vec2 Camera::screenspace_to_camspace(const Camera &camera, unsigned x, unsigned y) { 134 | auto rel_x = (x - screen_width(camera) / 2.f) / screen_width(camera); 135 | auto rel_y = (y - screen_height(camera) / 2.f) / screen_height(camera); 136 | return {rel_x, rel_y}; 137 | } 138 | 139 | glm::i32vec2 Camera::camspace_to_screenspace(const Camera &camera, glm::vec2 coords) { 140 | int screen_x = 141 | glm::round(0.5f * (screen_width(camera) - 2.f * screen_width(camera) * coords.x)); 142 | int screen_y = 143 | glm::round(0.5f * (screen_height(camera) - 2.f * screen_height(camera) * coords.y)); 144 | return {screen_x, screen_y}; 145 | } 146 | 147 | glm::vec3 Camera::camspace_to_worldspace(const Camera &camera, glm::vec2 rel_pos) { 148 | auto worldspace = canvas_center_pos(camera) + (rel_pos.x * -canvas_dir_x(camera)) + 149 | (rel_pos.y * -canvas_dir_y(camera)); 150 | return worldspace; 151 | } 152 | 153 | glm::vec2 Camera::worldspace_to_camspace(const Camera &camera, glm::vec3 world_pos_on_canvas) { 154 | auto canvas_center_to_point = world_pos_on_canvas - canvas_center_pos(camera); 155 | 156 | // Manually calculate angle between the positive y-axis on the canvas and the world point 157 | auto ay = canvas_center_to_point; 158 | auto by = up(camera); 159 | auto cy = glm::cross(ay, by); 160 | auto angle1y = glm::atan(glm::dot(ay, by), glm::length(cy)); 161 | 162 | // Manually calculate angle between the positive x-axis on the canvas and the world point 163 | auto ax = canvas_center_to_point; 164 | auto bx = right(camera); 165 | auto cx = glm::cross(ax, bx); 166 | auto angle1x = glm::atan(glm::length(cx), glm::dot(ax, bx)); 167 | 168 | auto angle2x = glm::pi() - (glm::half_pi() + angle1x); 169 | auto len = glm::length(canvas_center_to_point); 170 | auto x = glm::sin(angle2x) * len; 171 | auto y = glm::sin(angle1y) * len; 172 | auto rel_x = -(2 * x) / canvas_width(camera); 173 | auto rel_y = -(2 * y) / canvas_height(camera); 174 | return {rel_x, -rel_y}; 175 | } 176 | 177 | glm::vec3 Camera::worldpoint_to_worldspace(const Camera &camera, glm::vec3 world_point) { 178 | auto ray_to_cam = pos(camera) - world_point; 179 | float dist = 0; 180 | bool collided = glm::intersectRayPlane(world_point, glm::normalize(ray_to_cam), 181 | canvas_center_pos(camera), dir(camera), dist); 182 | if (collided) { 183 | return world_point + glm::normalize(ray_to_cam) * dist; 184 | } else { 185 | return glm::vec3(0); 186 | } 187 | } 188 | 189 | void Camera::rebuild(Camera &camera) { 190 | camera.m_right = glm::normalize(glm::cross(camera.m_world_up, camera.m_dir)); 191 | camera.m_up = glm::cross(camera.m_dir, right(camera)); 192 | camera.m_canvas_width = 2 * glm::tan(horizontal_fov(camera) / 2) * near_plane_dist(camera); 193 | camera.m_canvas_height = 2 * glm::tan(vertical_fov(camera) / 2) * near_plane_dist(camera); 194 | camera.m_canvas_center_pos = pos(camera) + dir(camera) * near_plane_dist(camera); 195 | camera.m_canvas_dir_x = 196 | glm::normalize(glm::cross(dir(camera), up(camera))) * (canvas_width(camera) / 2); 197 | camera.m_canvas_dir_y = glm::normalize(up(camera)) * (canvas_height(camera) / 2); 198 | } 199 | 200 | Ray Camera::pixel_to_ray(const Camera &camera, unsigned x, unsigned y) { 201 | glm::vec2 rel_pos = Camera::screenspace_to_camspace(camera, x, y); 202 | 203 | // Subpixel sampling / antialiasing 204 | glm::vec2 pixel_size = Camera::pixel_size(camera); 205 | glm::vec2 jitter = {rand_range(-pixel_size.x / 2.f, pixel_size.x / 2.f), 206 | rand_range(-pixel_size.y / 2.f, pixel_size.y / 2.f)}; 207 | rel_pos += jitter; 208 | 209 | glm::vec3 world_pos = Camera::camspace_to_worldspace(camera, rel_pos); 210 | glm::vec3 ray_dir = glm::normalize(world_pos - Camera::pos(camera)); 211 | 212 | return Ray{world_pos, ray_dir}; 213 | } 214 | 215 | } 216 | -------------------------------------------------------------------------------- /trac0r/camera.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CAMERA_HPP 2 | #define CAMERA_HPP 3 | 4 | #include "trac0r/ray.hpp" 5 | 6 | #include 7 | 8 | namespace trac0r { 9 | 10 | class Camera { 11 | public: 12 | Camera(); 13 | Camera(glm::vec3 pos, glm::vec3 dir, glm::vec3 world_up, float vertical_fov, 14 | float near_plane_dist, float far_plane_dist, int screen_width, int screen_height); 15 | 16 | static glm::vec3 pos(const Camera &camera); 17 | static void set_pos(Camera &camera, glm::vec3 pos); 18 | 19 | static glm::vec3 dir(const Camera &camera); 20 | static void set_dir(Camera &camera, glm::vec3 dir); 21 | 22 | static glm::vec3 world_up(const Camera &camera); 23 | static void set_world_up(Camera &camera, glm::vec3 dir); 24 | 25 | static glm::vec3 right(const Camera &camera); 26 | static glm::vec3 up(const Camera &camera); 27 | 28 | static float near_plane_dist(const Camera &camera); 29 | static void set_near_plane_dist(Camera &camera, float dist); 30 | 31 | static float far_plane_dist(const Camera &camera); 32 | static void set_far_plane_dist(Camera &camera, float dist); 33 | 34 | static int screen_width(const Camera &camera); 35 | static int screen_height(const Camera &camera); 36 | 37 | static float vertical_fov(const Camera &camera); 38 | static void set_vertical_fov(Camera &camera, float degrees); 39 | 40 | static float horizontal_fov(const Camera &camera); 41 | 42 | static float aspect_ratio(const Camera &camera); 43 | 44 | static float canvas_width(const Camera &camera); 45 | static float canvas_height(const Camera &camera); 46 | static glm::vec3 canvas_center_pos(const Camera &camera); 47 | static glm::vec3 canvas_dir_x(const Camera &camera); 48 | static glm::vec3 canvas_dir_y(const Camera &camera); 49 | 50 | /** 51 | * @brief Returns the size of one pixel in camera space. 52 | * 53 | * @return Vector of (x, y) pixel size in camera space. 54 | */ 55 | static glm::vec2 pixel_size(const Camera &camera); 56 | 57 | /** 58 | * @brief Converts absolute screen space coordinates to relative camera space positions. 59 | * 60 | * @param x Pixel coordinate. Can not be larger than m_screen_width. 61 | * @param y Pixel coordinate. Can not be larger than m_screen_height. 62 | * 63 | * @return Relative camera space positions. Values will be between -1.0 and 1.0. 64 | */ 65 | static glm::vec2 screenspace_to_camspace(const Camera &camera, unsigned x, unsigned y); 66 | 67 | /** 68 | * @brief Converts relative camera space positions to absolute screen space coordinates. 69 | * 70 | * @param coords Camera space coordinates between -1.0 and 1.0. 71 | * 72 | * @return Absolute screen space coordinates. 73 | */ 74 | static glm::i32vec2 camspace_to_screenspace(const Camera &camera, glm::vec2 coords); 75 | 76 | /** 77 | * @brief Converts camera space relative positions to world space positions on the camera's 78 | * canvas. 79 | * 80 | * @param rel_pos The relative position as screenspace_to_camspace(int x, int y) would return 81 | * it. Values need to be between -1.0 and 1.0. 82 | * 83 | * @return World space coordinates on the camera's canvas. 84 | */ 85 | static glm::vec3 camspace_to_worldspace(const Camera &camera, glm::vec2 rel_pos); 86 | 87 | /** 88 | * @brief Converts a position on the camera's canvas to a camera space relative position. 89 | * 90 | * @param world_pos The world space position. 91 | * 92 | * @return The relative position in camera space. Values are between -1.0 and 1.0. 93 | */ 94 | static glm::vec2 worldspace_to_camspace(const Camera &camera, glm::vec3 world_pos); 95 | 96 | /** 97 | * @brief Converts a position in the world to a position on the camera's canvas (but still in 98 | * world space). 99 | * You then have to use worldspace_to_camspace() and then camspace_to_screenspace() to find out 100 | * which pixel 101 | * a world point falls into. 102 | * 103 | * @param world_point The world point in world space. 104 | * 105 | * @return A world space coordinate on the camera's canvas. 106 | */ 107 | static glm::vec3 worldpoint_to_worldspace(const Camera &camera, glm::vec3 world_point); 108 | 109 | /** 110 | * @brief Creates a ray that shoots through the camera's canvas at the position coresponding to 111 | * the provided pixel coordinates. 112 | * 113 | * @param x Pixel coordinate x 114 | * @param y Pixel coordinate y 115 | * 116 | * @return A new ray pointing from the sensor to this pixel in world space 117 | */ 118 | static Ray pixel_to_ray(const Camera &camera, unsigned x, unsigned y); 119 | 120 | private: 121 | static void rebuild(Camera &camera); 122 | 123 | glm::vec3 m_pos; 124 | glm::vec3 m_dir; 125 | glm::vec3 m_world_up; 126 | glm::vec3 m_right; 127 | glm::vec3 m_up; 128 | float m_canvas_width; 129 | float m_canvas_height; 130 | glm::vec3 m_canvas_center_pos; 131 | glm::vec3 m_canvas_dir_x; 132 | glm::vec3 m_canvas_dir_y; 133 | float m_near_plane_dist; 134 | float m_far_plane_dist; 135 | int m_screen_width; 136 | int m_screen_height; 137 | float m_vertical_fov; 138 | float m_horizontal_fov; 139 | }; 140 | } 141 | 142 | #endif /* end of include guard: CAMERA_HPP */ 143 | -------------------------------------------------------------------------------- /trac0r/filtering.hpp: -------------------------------------------------------------------------------- 1 | #ifndef FILTERING_HPP 2 | #define FILTERING_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include 13 | 14 | namespace trac0r { 15 | 16 | // inline void bilateral_filter(const std::vector &input, std::vector &output, 17 | // const float radius, const float sigma) { 18 | // } 19 | // } 20 | 21 | // inline void gaussian_filter(const std::vector &input, std::vector &output, 22 | 23 | inline void box_filter(const std::vector &input, const uint32_t width, 24 | const uint32_t height, std::vector &output) { 25 | // clang-format off 26 | std::vector kernel = {1.f, 1.f, 1.f, 27 | 1.f, 2.f, 1.f, 28 | 1.f, 1.f, 1.f}; 29 | // clang-format on 30 | 31 | int kernel_width = glm::sqrt(kernel.size()); 32 | int offset = kernel_width / 2; 33 | float weightsum = std::accumulate(kernel.cbegin(), kernel.cend(), 0.f); 34 | 35 | #pragma omp parallel for collapse(2) schedule(dynamic, 1024) 36 | for (uint32_t x = 1; x < width - 1; x++) { 37 | for (uint32_t y = 1; y < height - 1; y++) { 38 | glm::vec4 new_color; 39 | for (int i = 0; i < kernel_width; i++) { 40 | for (int j = 0; j < kernel_width; j++) { 41 | glm::vec4 color = unpack_color_argb_to_vec4( 42 | input[(x + i - offset) + (y + j - offset) * width]); 43 | new_color += color * kernel[i + j * kernel_width]; 44 | } 45 | } 46 | new_color /= weightsum; 47 | new_color.a = 1.f; 48 | output[x + y * width] = pack_color_argb(new_color); 49 | } 50 | } 51 | } 52 | } 53 | 54 | #endif /* end of include guard: FILTERING_HPP */ 55 | -------------------------------------------------------------------------------- /trac0r/flat_structure.cpp: -------------------------------------------------------------------------------- 1 | #include "flat_structure.hpp" 2 | #include "intersections.hpp" 3 | 4 | #include 5 | #include 6 | 7 | namespace trac0r { 8 | 9 | void FlatStructure::add_shape(FlatStructure &flatstruct, Shape &shape) { 10 | flatstruct.m_shapes.push_back(shape); 11 | } 12 | 13 | std::vector &FlatStructure::shapes(FlatStructure &flatstruct) { 14 | return flatstruct.m_shapes; 15 | } 16 | 17 | const std::vector &FlatStructure::shapes(const FlatStructure &flatstruct) { 18 | return flatstruct.m_shapes; 19 | } 20 | 21 | std::vector &FlatStructure::light_triangles(FlatStructure &flatstruct) { 22 | return flatstruct.m_light_triangles; 23 | } 24 | 25 | const std::vector &FlatStructure::light_triangles(const FlatStructure &flatstruct) { 26 | return flatstruct.m_light_triangles; 27 | } 28 | 29 | IntersectionInfo FlatStructure::intersect(const FlatStructure &flatstruct, const Ray &ray) { 30 | // TODO: We get like 2x better performance here if we loop over a flat structure of triangles 31 | // instead of looping over all shapes and for each shape over all triangles 32 | IntersectionInfo intersect_info; 33 | 34 | // Keep track of closest triangle 35 | float closest_dist = std::numeric_limits::max(); 36 | Triangle closest_triangle; 37 | for (const auto &shape : FlatStructure::shapes(flatstruct)) { 38 | if (intersect_ray_aabb(ray, Shape::aabb(shape))) { 39 | for (auto &tri : Shape::triangles(shape)) { 40 | float dist_to_intersect; 41 | bool intersected = intersect_ray_triangle(ray, tri, dist_to_intersect); 42 | if (intersected) { 43 | // Find closest triangle 44 | if (dist_to_intersect < closest_dist) { 45 | closest_dist = dist_to_intersect; 46 | closest_triangle = tri; 47 | 48 | intersect_info.m_has_intersected = true; 49 | intersect_info.m_pos = ray.m_origin + ray.m_dir * closest_dist; 50 | intersect_info.m_incoming_ray = ray; 51 | 52 | intersect_info.m_angle_between = glm::dot( 53 | closest_triangle.m_normal, intersect_info.m_incoming_ray.m_dir); 54 | 55 | intersect_info.m_normal = closest_triangle.m_normal; 56 | intersect_info.m_material = closest_triangle.m_material; 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | return intersect_info; 64 | } 65 | 66 | void FlatStructure::rebuild(FlatStructure &flatstruct) { 67 | flatstruct.m_light_triangles.clear(); 68 | for (auto &shape : FlatStructure::shapes(flatstruct)) { 69 | for (auto &tri : Shape::triangles(shape)) { 70 | // Put lights into a list for easy access 71 | if (tri.m_material.m_type == 1) { 72 | flatstruct.m_light_triangles.push_back(tri); 73 | } 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /trac0r/flat_structure.hpp: -------------------------------------------------------------------------------- 1 | #ifndef FLAT_STRUCTURE_HPP 2 | #define FLAT_STRUCTURE_HPP 3 | 4 | #include "triangle.hpp" 5 | #include "ray.hpp" 6 | #include "intersection_info.hpp" 7 | #include "shape.hpp" 8 | #include "camera.hpp" 9 | 10 | #include 11 | 12 | namespace trac0r { 13 | 14 | class FlatStructure { 15 | public: 16 | static void add_shape(FlatStructure &flatstruct, Shape &shape); 17 | static std::vector &shapes(FlatStructure &flatstruct); 18 | static const std::vector &shapes(const FlatStructure &flatstruct); 19 | static std::vector &light_triangles(FlatStructure &flatstruct); 20 | static const std::vector &light_triangles(const FlatStructure &flatstruct); 21 | static IntersectionInfo intersect(const FlatStructure &flatstruct, const Ray &ray); 22 | static void rebuild(FlatStructure &flatstruct); 23 | 24 | private: 25 | std::vector m_light_triangles; 26 | std::vector m_shapes; 27 | }; 28 | } 29 | 30 | #endif /* end of include guard: FLAT_STRUCTURE_HPP */ 31 | -------------------------------------------------------------------------------- /trac0r/intersection_info.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INTERSECTION_INFO_HPP 2 | #define INTERSECTION_INFO_HPP 3 | 4 | #include "material.hpp" 5 | #include "ray.hpp" 6 | 7 | #include 8 | 9 | namespace trac0r { 10 | 11 | struct IntersectionInfo { 12 | /** 13 | * @brief Indicates whether an intersection was found. 14 | */ 15 | bool m_has_intersected = false; 16 | 17 | /** 18 | * @brief World coordinates of intersection. 19 | */ 20 | glm::vec3 m_pos; 21 | 22 | /** 23 | * @brief Normal at the point of intersection. 24 | */ 25 | glm::vec3 m_normal; 26 | 27 | /** 28 | * @brief The angle between the incoming ray and the normal. 29 | */ 30 | float m_angle_between; 31 | 32 | /** 33 | * @brief Direction vector of incomig ray towards point of intersection. 34 | */ 35 | Ray m_incoming_ray = Ray(glm::vec3(0), glm::vec3(0)); 36 | 37 | /** 38 | * @brief Material at the point of intersection. 39 | */ 40 | Material m_material; 41 | }; 42 | } 43 | 44 | #endif /* end of include guard: INTERSECTION_INFO_HPP */ 45 | -------------------------------------------------------------------------------- /trac0r/intersections.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INTERSECTIONS_HPP 2 | #define INTERSECTIONS_HPP 3 | 4 | #include "aabb.hpp" 5 | #include "ray.hpp" 6 | #include "triangle.hpp" 7 | 8 | #include 9 | 10 | #include 11 | 12 | namespace trac0r { 13 | // From 14 | // http://ftp.cg.cs.tu-bs.de/media/publications/fast-rayaxis-aligned-bounding-box-overlap-tests-using-ray-slopes.pdf 15 | // Fast Ray/Axis-Aligned Bounding Box Overlap Tests using Ray Slopes by Martin Eisemann et al. 16 | inline bool intersect_ray_aabb_broken2(const Ray &ray, const AABB &aabb) { 17 | if (AABB::is_null(aabb)) 18 | return false; 19 | 20 | auto s_yx = ray.m_dir.x * ray.m_invdir.y; 21 | auto s_xy = ray.m_dir.y * ray.m_invdir.x; 22 | auto s_zy = ray.m_dir.y * ray.m_invdir.z; 23 | auto s_yz = ray.m_dir.z * ray.m_invdir.y; 24 | auto s_xz = ray.m_dir.x * ray.m_invdir.z; 25 | auto s_zx = ray.m_dir.z * ray.m_invdir.x; 26 | 27 | auto c_xy = ray.m_origin.y - s_xy * ray.m_origin.x; 28 | auto c_yx = ray.m_origin.x - s_yx * ray.m_origin.y; 29 | auto c_zy = ray.m_origin.y - s_zy * ray.m_origin.z; 30 | auto c_yz = ray.m_origin.z - s_yz * ray.m_origin.y; 31 | auto c_xz = ray.m_origin.z - s_xz * ray.m_origin.x; 32 | auto c_zx = ray.m_origin.x - s_zx * ray.m_origin.z; 33 | 34 | if ((ray.m_origin.x > AABB::max(aabb).x) || (ray.m_origin.y > AABB::max(aabb).y) || 35 | (ray.m_origin.z > AABB::max(aabb).z) || (s_xy * AABB::max(aabb).x - AABB::min(aabb).y + c_xy < 0) || 36 | (s_yx * AABB::max(aabb).y - AABB::min(aabb).x + c_yx < 0) || 37 | (s_zy * AABB::max(aabb).z - AABB::min(aabb).y + c_zy < 0) || 38 | (s_yz * AABB::max(aabb).y - AABB::min(aabb).z + c_yz < 0) || 39 | (s_xz * AABB::max(aabb).x - AABB::min(aabb).z + c_xz < 0) || 40 | (s_zx * AABB::max(aabb).z - AABB::min(aabb).x + c_zx < 0)) 41 | return false; 42 | 43 | return true; 44 | } 45 | 46 | // From http://tavianator.com/fast-branchless-raybounding-box-intersections-part-2-nans/ 47 | inline bool intersect_ray_aabb_broken1(const Ray &ray, const AABB &aabb) { 48 | double t1 = (AABB::min(aabb)[0] - ray.m_origin[0]) * ray.m_invdir[0]; 49 | double t2 = (AABB::max(aabb)[0] - ray.m_origin[0]) * ray.m_invdir[0]; 50 | 51 | double tmin = glm::min(t1, t2); 52 | double tmax = glm::max(t1, t2); 53 | 54 | for (int i = 1; i < 3; ++i) { 55 | t1 = (AABB::min(aabb)[i] - ray.m_origin[i]) * ray.m_invdir[i]; 56 | t2 = (AABB::max(aabb)[i] - ray.m_origin[i]) * ray.m_invdir[i]; 57 | 58 | tmin = glm::max(tmin, glm::min(t1, t2)); 59 | tmax = glm::min(tmax, glm::max(t1, t2)); 60 | } 61 | 62 | return tmax > glm::max(tmin, 0.0); 63 | } 64 | 65 | 66 | // From 67 | // http://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-box-intersection 68 | inline bool intersect_ray_aabb(const Ray &ray, const AABB &aabb) { 69 | float tmin, tmax, tymin, tymax, tzmin, tzmax; 70 | 71 | glm::vec3 bounds[2]; 72 | bounds[0] = AABB::min(aabb); 73 | bounds[1] = AABB::max(aabb); 74 | 75 | glm::i8vec3 sign; 76 | sign.x = (ray.m_invdir.x < 0); 77 | sign.y = (ray.m_invdir.y < 0); 78 | sign.z = (ray.m_invdir.z < 0); 79 | 80 | tmin = (bounds[sign.x].x - ray.m_origin.x) * ray.m_invdir.x; 81 | tmax = (bounds[1 - sign.x].x - ray.m_origin.x) * ray.m_invdir.x; 82 | tymin = (bounds[sign.y].y - ray.m_origin.y) * ray.m_invdir.y; 83 | tymax = (bounds[1 - sign.y].y - ray.m_origin.y) * ray.m_invdir.y; 84 | 85 | if ((tmin > tymax) || (tymin > tmax)) 86 | return false; 87 | if (tymin > tmin) 88 | tmin = tymin; 89 | if (tymax < tmax) 90 | tmax = tymax; 91 | 92 | tzmin = (bounds[sign.z].z - ray.m_origin.z) * ray.m_invdir.z; 93 | tzmax = (bounds[1 - sign.z].z - ray.m_origin.z) * ray.m_invdir.z; 94 | 95 | if ((tmin > tzmax) || (tzmin > tmax)) 96 | return false; 97 | if (tzmin > tmin) 98 | tmin = tzmin; 99 | if (tzmax < tmax) 100 | tmax = tzmax; 101 | 102 | return true; 103 | } 104 | 105 | // Möller-Trumbore intersection algorithm 106 | // (see https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm) 107 | inline bool intersect_ray_triangle(const Ray &ray, const Triangle &triangle, float &dist) { 108 | // Calculate edges of triangle from v0. 109 | auto e0 = triangle.m_v2 - triangle.m_v1; 110 | auto e1 = triangle.m_v3 - triangle.m_v1; 111 | 112 | // Calculate determinant to check whether the ray is in the newly calculated plane made up from 113 | // e0 and e1. 114 | auto pvec = glm::cross(ray.m_dir, e1); 115 | auto det = glm::dot(e0, pvec); 116 | 117 | // Check whether determinant is close to 0. If that is the case, the ray is in the same plane as 118 | // the triangle itself which means that they can't collide. This effectively disables backface 119 | // culling for which we would instead only check whether det < epsilon. 120 | auto epsilon = std::numeric_limits::epsilon(); 121 | if (det > -epsilon && det < epsilon) 122 | return false; 123 | 124 | auto inv_det = 1.f / det; 125 | 126 | // Calculate distance from v1 to ray origin 127 | auto tvec = ray.m_origin - triangle.m_v1; 128 | 129 | // Calculate u parameter and test bound 130 | auto u = glm::dot(tvec, pvec) * inv_det; 131 | 132 | // Check whether the intersection lies outside of the triangle 133 | if (u < 0.f || u > 1.f) 134 | return false; 135 | 136 | // Prepare to test v parameter 137 | auto qvec = glm::cross(tvec, e0); 138 | 139 | // Calculate v parameter and test bound 140 | auto v = glm::dot(ray.m_dir, qvec) * inv_det; 141 | 142 | // Check whether the intersection lies outside of the triangle 143 | if (v < 0.f || u + v > 1.f) 144 | return false; 145 | 146 | auto t = glm::dot(e1, qvec) * inv_det; 147 | 148 | if (t > epsilon) { 149 | dist = t - epsilon; 150 | return true; 151 | } 152 | 153 | // If we end up here, there was no hit 154 | return false; 155 | } 156 | } 157 | 158 | #endif /* end of include guard: INTERSECTIONS_HPP */ 159 | -------------------------------------------------------------------------------- /trac0r/light_vertex.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LIGHT_VERTEX_HPP 2 | #define LIGHT_VERTEX_HPP 3 | 4 | #include 5 | 6 | struct LightVertex { 7 | glm::vec3 m_pos{0.f}; 8 | glm::vec3 m_luminance{0.f}; 9 | }; 10 | 11 | #endif /* end of include guard: LIGHT_VERTEX_HPP */ 12 | -------------------------------------------------------------------------------- /trac0r/material.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MATERIAL_HPP 2 | #define MATERIAL_HPP 3 | 4 | #include 5 | 6 | namespace trac0r { 7 | 8 | struct Material { 9 | /** 10 | * @brief Used to determine the material type used. Refer to the table below: 11 | * m_type = 1: Emissive 12 | * m_type = 2: Diffuse 13 | * m_type = 3: Glass 14 | * m_type = 4: Glossy 15 | */ 16 | uint8_t m_type = 1; 17 | 18 | /** 19 | * @brief Used as the basic color for most materials. 20 | */ 21 | glm::vec3 m_color = {0.9f, 0.5f, 0.1f}; 22 | 23 | /** 24 | * @brief Used to determine the ratio between reflection and diffusion for some materials. Value 25 | * must be between 0.0 and 1.0. 26 | */ 27 | float m_roughness = 0.f; 28 | 29 | /** 30 | * @brief Index of refraction (IOR) is used to determine how strongly light is bent inside a 31 | * glass 32 | * material. 33 | */ 34 | float m_ior = 1.f; 35 | 36 | /** 37 | * @brief Luminous emittance provides the strength of emissive material. Values can be larger 38 | * than 1.0. 39 | */ 40 | float m_emittance = 0.f; 41 | }; 42 | } 43 | 44 | #endif /* end of include guard: MATERIAL_HPP */ 45 | -------------------------------------------------------------------------------- /trac0r/random.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RANDOM_HPP 2 | #define RANDOM_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include "utils.hpp" 13 | 14 | namespace trac0r { 15 | 16 | // From http://xorshift.di.unimi.it/xorshift64star.c 17 | inline uint64_t xorshift64star(uint64_t x) { 18 | x ^= x >> 12; // a 19 | x ^= x << 25; // b 20 | x ^= x >> 27; // c 21 | return x * 2685821657736338717LL; 22 | } 23 | 24 | // From http://xorshift.di.unimi.it/xorshift1024star.c 25 | inline int64_t xorshift1024star(uint64_t &p, std::array &s) { 26 | uint64_t s0 = s[p]; 27 | uint64_t s1 = s[p = (p + 1) & 15]; 28 | s1 ^= s1 << 31; // a 29 | s1 ^= s1 >> 11; // b 30 | s0 ^= s0 >> 30; // c 31 | return (s[p] = s0 ^ s1) * 1181783497276652981LL; 32 | } 33 | 34 | class PRNG { 35 | public: 36 | PRNG() { 37 | // Generate an initial seed based on time 38 | auto now = std::chrono::high_resolution_clock::now().time_since_epoch(); 39 | auto ns = std::chrono::duration_cast(now).count(); 40 | 41 | // Then use the result of another PRNG as our actual seed 42 | for (auto i = 0; i < 16; i++) 43 | m_seed[i] = xorshift64star(ns); 44 | } 45 | 46 | uint64_t next() { 47 | return xorshift1024star(m_p, m_seed); 48 | } 49 | 50 | private: 51 | std::array m_seed; 52 | uint64_t m_p = 0; 53 | }; 54 | 55 | template 56 | std::enable_if_t::value, T> inline rand_range(const T min, const T max) { 57 | static thread_local PRNG generator; 58 | return generator.next() % max + min; 59 | } 60 | 61 | template 62 | std::enable_if_t::value, T> inline rand_range(const T min, const T max) { 63 | static thread_local PRNG generator; 64 | return min + 65 | static_cast(generator.next()) / 66 | (static_cast(std::numeric_limits::max() / (max - min))); 67 | } 68 | 69 | /** 70 | * @brief Selects a random point on a sphere with uniform distribution. 71 | * point on a uniform. 72 | * 73 | * @return A random point on the surface of a sphere 74 | */ 75 | inline glm::vec3 uniform_sample_sphere() { 76 | glm::vec3 rand_vec = 77 | glm::vec3(rand_range(-1.f, 1.f), rand_range(-1.f, 1.f), rand_range(-1.f, 1.f)); 78 | return glm::normalize(rand_vec); 79 | } 80 | 81 | /** 82 | * @brief Given a direction vector, this will return a random uniform point on a sphere 83 | * on the hemisphere around dir. 84 | * 85 | * @param dir A vector that represents the hemisphere's center 86 | * 87 | * @return A random point the on the hemisphere 88 | */ 89 | inline glm::vec3 oriented_uniform_hemisphere_sample(glm::vec3 dir) { 90 | glm::vec3 v = uniform_sample_sphere(); 91 | return v * glm::sign(glm::dot(v, dir)); 92 | } 93 | 94 | /** 95 | * @brief Selects a random point on a sphere with uniform or cosine-weighted distribution. 96 | * 97 | * @param dir A vector around which the hemisphere will be centered 98 | * @param power 0.f means uniform distribution while 1.f means cosine-weighted 99 | * @param angle When a full hemisphere is desired, use pi/2. 0 equals perfect reflection. The value 100 | * should therefore be between 0 and pi/2. This angle is equal to half the cone width. 101 | * 102 | * @return A random point on the surface of a sphere 103 | */ 104 | inline glm::vec3 sample_hemisphere(glm::vec3 dir, float power, float angle) { 105 | // Code adapted from Mikael Hvidtfeldt Christensen's resource 106 | // at http://blog.hvidtfeldts.net/index.php/2015/01/path-tracing-3d-fractals/ 107 | // Thanks! 108 | 109 | glm::vec3 o1 = glm::normalize(ortho(dir)); 110 | glm::vec3 o2 = glm::normalize(glm::cross(dir, o1)); 111 | glm::vec2 r = glm::vec2{rand_range(0.f, 1.f), rand_range(glm::cos(angle), 1.f)}; 112 | r.x = r.x * glm::two_pi(); 113 | r.y = glm::pow(r.y, 1.f / (power + 1.f)); 114 | float oneminus = glm::sqrt(1.f - r.y * r.y); 115 | return glm::cos(r.x) * oneminus * o1 + glm::sin(r.x) * oneminus * o2 + r.y * dir; 116 | } 117 | 118 | /** 119 | * @brief Given a direction vector, this will return a random cosine-weighted point on a sphere 120 | * on the hemisphere around dir. 121 | * 122 | * @param dir A vector that represents the hemisphere's center 123 | * 124 | * @return A random point the on the hemisphere 125 | */ 126 | inline glm::vec3 oriented_cosine_weighted_hemisphere_sample(glm::vec3 dir) { 127 | return sample_hemisphere(dir, 1.f, glm::half_pi()); 128 | } 129 | 130 | /** 131 | * @brief Selects a random point on a cone with uniform distribution. 132 | * 133 | * @param dir Direction in which the cone is oriented 134 | * @param angle Angle of the cone in radians 135 | * 136 | * @return A random point on the surface of a cone 137 | */ 138 | inline glm::vec3 oriented_cosine_weighted_cone_sample(glm::vec3 dir, float angle) { 139 | return sample_hemisphere(dir, 1.f, angle); 140 | } 141 | } 142 | 143 | #endif /* end of include guard: RANDOM_HPP */ 144 | -------------------------------------------------------------------------------- /trac0r/ray.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RAY_HPP 2 | #define RAY_HPP 3 | 4 | #include 5 | 6 | struct Ray { 7 | Ray(const glm::vec3 &origin, const glm::vec3 &direction) 8 | : m_origin(origin), m_dir(direction), m_invdir(1.f / direction) { 9 | } 10 | 11 | glm::vec3 m_origin; 12 | glm::vec3 m_dir; 13 | glm::vec3 m_invdir; 14 | }; 15 | 16 | #endif /* end of include guard: RAY_HPP */ 17 | -------------------------------------------------------------------------------- /trac0r/renderer.cpp: -------------------------------------------------------------------------------- 1 | #include "renderer.hpp" 2 | #include "ray.hpp" 3 | #include "random.hpp" 4 | #include "utils.hpp" 5 | #include "timer.hpp" 6 | 7 | #include 8 | #include 9 | 10 | #include "fmt/format.h" 11 | 12 | #include 13 | #include 14 | 15 | namespace trac0r { 16 | 17 | Renderer::Renderer(const int width, const int height, const Camera &camera, const Scene &scene, 18 | bool print_perf) 19 | : m_width(width), m_height(height), m_camera(camera), m_scene(scene), m_print_perf(print_perf) { 20 | m_luminance.resize(width * height, glm::vec4{0}); 21 | 22 | #ifdef OPENCL 23 | cl::Platform::get(&m_compute_platforms); 24 | for (const auto &platform : m_compute_platforms) { 25 | std::vector devices; 26 | // platform.getDevices(CL_DEVICE_TYPE_ALL, &devices); 27 | platform.getDevices(CL_DEVICE_TYPE_GPU, &devices); 28 | for (auto &device : devices) { 29 | m_compute_devices.push_back(device); 30 | } 31 | } 32 | 33 | m_compute_context = cl::Context(m_compute_devices); 34 | for (auto &device : m_compute_devices) { 35 | m_compute_queues.emplace_back(cl::CommandQueue(m_compute_context, device)); 36 | } 37 | 38 | std::ifstream source_file("trac0r/renderer_aux.cl"); 39 | std::string source_content((std::istreambuf_iterator(source_file)), 40 | (std::istreambuf_iterator())); 41 | m_program = cl::Program(m_compute_context, source_content); 42 | // cl_int result = m_program.build(); 43 | cl_int result = m_program.build("-cl-fast-relaxed-math"); 44 | // cl_int result = m_program.build("-cl-nv-verbose"); 45 | auto build_log = m_program.getBuildInfo(m_compute_devices[0]); 46 | fmt::print("{}\n", build_log); 47 | if (result != CL_SUCCESS) 48 | exit(1); 49 | m_kernel = cl::Kernel(m_program, "renderer_trace_camera_ray", &result); 50 | if (result != CL_SUCCESS) { 51 | fmt::print("{}\n", opencl_error_string(result)); 52 | exit(1); 53 | } 54 | #endif 55 | } 56 | 57 | std::vector &Renderer::render(bool scene_changed, int stride_x, int stride_y) { 58 | #ifdef OPENCL 59 | struct DevicePRNG { 60 | cl_ulong m_seed[16]; 61 | cl_ulong m_p; 62 | }; 63 | 64 | struct DeviceMaterial { 65 | cl_uchar m_type; 66 | cl_float3 m_color; 67 | cl_float m_roughness; 68 | cl_float m_ior; 69 | cl_float m_emittance; 70 | }; 71 | 72 | struct DeviceTriangle { 73 | cl_float3 m_v1; 74 | cl_float3 m_v2; 75 | cl_float3 m_v3; 76 | DeviceMaterial m_material; 77 | cl_float3 m_normal; 78 | cl_float3 m_centroid; 79 | cl_float m_area; 80 | }; 81 | 82 | struct DeviceAABB { 83 | cl_float3 m_min; 84 | cl_float3 m_max; 85 | }; 86 | 87 | struct DeviceShape { 88 | DeviceAABB m_aabb; 89 | uint32_t m_triangle_index_start; 90 | uint32_t m_triangle_index_end; 91 | }; 92 | 93 | struct DeviceCamera { 94 | cl_float3 m_pos; 95 | cl_float3 m_dir; 96 | cl_float3 m_world_up; 97 | cl_float3 m_right; 98 | cl_float3 m_up; 99 | cl_float m_canvas_width; 100 | cl_float m_canvas_height; 101 | cl_float3 m_canvas_center_pos; 102 | cl_float3 m_canvas_dir_x; 103 | cl_float3 m_canvas_dir_y; 104 | cl_float m_near_plane_dist; 105 | cl_float m_far_plane_dist; 106 | cl_int m_screen_width; 107 | cl_int m_screen_height; 108 | cl_float2 m_pixel_size; 109 | cl_float m_vertical_fov; 110 | cl_float m_horizontal_fov; 111 | }; 112 | 113 | const size_t image_size = m_width * m_height; 114 | 115 | Timer timer; 116 | 117 | std::vector host_output; 118 | host_output.resize(image_size); 119 | 120 | // Init dev_output 121 | cl::Buffer dev_output_buf(m_compute_context, CL_MEM_WRITE_ONLY, image_size * sizeof(cl_float4)); 122 | 123 | // Init dev_prng 124 | DevicePRNG dev_prng; 125 | auto now = std::chrono::high_resolution_clock::now().time_since_epoch(); 126 | auto ns = std::chrono::duration_cast(now).count(); 127 | for (auto i = 0; i < 16; i++) 128 | dev_prng.m_seed[i] = xorshift64star(ns); 129 | dev_prng.m_p = 0; 130 | cl::Buffer dev_prng_buf(m_compute_context, CL_MEM_READ_WRITE, sizeof(dev_prng)); 131 | m_compute_queues[0].enqueueWriteBuffer(dev_prng_buf, CL_TRUE, 0, sizeof(dev_prng), &dev_prng); 132 | 133 | // Init dev_camera 134 | DeviceCamera dev_camera; 135 | dev_camera.m_pos = { 136 | {Camera::pos(m_camera).x, Camera::pos(m_camera).y, Camera::pos(m_camera).z}}; 137 | dev_camera.m_dir = { 138 | {Camera::dir(m_camera).x, Camera::dir(m_camera).y, Camera::dir(m_camera).z}}; 139 | dev_camera.m_world_up = { 140 | {Camera::world_up(m_camera).x, Camera::world_up(m_camera).y, Camera::world_up(m_camera).z}}; 141 | dev_camera.m_right = { 142 | {Camera::right(m_camera).x, Camera::right(m_camera).y, Camera::right(m_camera).z}}; 143 | dev_camera.m_up = {{Camera::up(m_camera).x, Camera::up(m_camera).y, Camera::up(m_camera).z}}; 144 | dev_camera.m_canvas_width = Camera::canvas_width(m_camera); 145 | dev_camera.m_canvas_height = Camera::canvas_height(m_camera); 146 | dev_camera.m_canvas_center_pos = {{Camera::canvas_center_pos(m_camera).x, 147 | Camera::canvas_center_pos(m_camera).y, 148 | Camera::canvas_center_pos(m_camera).z}}; 149 | dev_camera.m_canvas_dir_x = {{Camera::canvas_dir_x(m_camera).x, 150 | Camera::canvas_dir_x(m_camera).y, 151 | Camera::canvas_dir_x(m_camera).z}}; 152 | dev_camera.m_canvas_dir_y = {{Camera::canvas_dir_y(m_camera).x, 153 | Camera::canvas_dir_y(m_camera).y, 154 | Camera::canvas_dir_y(m_camera).z}}; 155 | dev_camera.m_near_plane_dist = Camera::near_plane_dist(m_camera); 156 | dev_camera.m_far_plane_dist = Camera::far_plane_dist(m_camera); 157 | dev_camera.m_screen_width = Camera::screen_width(m_camera); 158 | dev_camera.m_screen_height = Camera::screen_height(m_camera); 159 | dev_camera.m_pixel_size = {{Camera::pixel_size(m_camera).x, Camera::pixel_size(m_camera).y}}; 160 | dev_camera.m_vertical_fov = Camera::vertical_fov(m_camera); 161 | dev_camera.m_horizontal_fov = Camera::horizontal_fov(m_camera); 162 | cl::Buffer dev_camera_buf(m_compute_context, CL_MEM_READ_ONLY, sizeof(dev_camera)); 163 | m_compute_queues[0].enqueueWriteBuffer(dev_camera_buf, CL_TRUE, 0, sizeof(dev_camera), 164 | &dev_camera); 165 | 166 | // Init dev_flatstruct 167 | // DeviceFlatStructure dev_flatstruct; 168 | std::vector dev_triangles; 169 | std::vector dev_shapes; 170 | auto &accel_struct = Scene::accel_struct(m_scene); 171 | for (auto &shape : FlatStructure::shapes(accel_struct)) { 172 | DeviceAABB dev_aabb; 173 | dev_aabb.m_min = {{AABB::min(Shape::aabb(shape)).x, AABB::min(Shape::aabb(shape)).y, 174 | AABB::min(Shape::aabb(shape)).z}}; 175 | dev_aabb.m_max = {{AABB::max(Shape::aabb(shape)).x, AABB::max(Shape::aabb(shape)).y, 176 | AABB::max(Shape::aabb(shape)).z}}; 177 | DeviceShape dev_shape; 178 | dev_shape.m_aabb = dev_aabb; 179 | dev_shape.m_triangle_index_start = dev_triangles.size(); 180 | 181 | for (auto &tri : Shape::triangles(shape)) { 182 | DeviceMaterial dev_mat; 183 | dev_mat.m_type = tri.m_material.m_type; 184 | dev_mat.m_color = { 185 | {tri.m_material.m_color.r, tri.m_material.m_color.g, tri.m_material.m_color.b}}; 186 | dev_mat.m_roughness = tri.m_material.m_roughness; 187 | dev_mat.m_ior = tri.m_material.m_ior; 188 | dev_mat.m_emittance = tri.m_material.m_emittance; 189 | DeviceTriangle dev_tri; 190 | dev_tri.m_v1 = {{tri.m_v1.x, tri.m_v1.y, tri.m_v1.z}}; 191 | dev_tri.m_v2 = {{tri.m_v2.x, tri.m_v2.y, tri.m_v2.z}}; 192 | dev_tri.m_v3 = {{tri.m_v3.x, tri.m_v3.y, tri.m_v3.z}}; 193 | dev_tri.m_material = dev_mat; 194 | dev_tri.m_normal = {{tri.m_normal.x, tri.m_normal.y, tri.m_normal.z}}; 195 | dev_tri.m_centroid = {{tri.m_centroid.x, tri.m_centroid.y, tri.m_centroid.z}}; 196 | dev_tri.m_area = tri.m_area; 197 | dev_triangles.push_back(dev_tri); 198 | } 199 | 200 | dev_shape.m_triangle_index_end = dev_triangles.size(); 201 | dev_shapes.push_back(dev_shape); 202 | } 203 | 204 | cl::Buffer dev_triangles_buf(m_compute_context, CL_MEM_READ_ONLY, 205 | sizeof(DeviceTriangle) * dev_triangles.size()); 206 | m_compute_queues[0].enqueueWriteBuffer(dev_triangles_buf, CL_TRUE, 0, 207 | sizeof(DeviceTriangle) * dev_triangles.size(), 208 | &dev_triangles[0]); 209 | 210 | cl::Buffer dev_shapes_buf(m_compute_context, CL_MEM_READ_ONLY, 211 | sizeof(DeviceShape) * dev_shapes.size()); 212 | m_compute_queues[0].enqueueWriteBuffer(dev_shapes_buf, CL_TRUE, 0, 213 | sizeof(DeviceShape) * dev_shapes.size(), &dev_shapes[0]); 214 | 215 | if (m_print_perf) 216 | m_last_frame_buffer_write_time = timer.elapsed(); 217 | 218 | m_kernel.setArg(0, dev_output_buf); 219 | m_kernel.setArg(1, m_width); 220 | m_kernel.setArg(2, m_max_camera_subpath_depth); 221 | m_kernel.setArg(3, dev_prng_buf); 222 | m_kernel.setArg(4, dev_camera_buf); 223 | m_kernel.setArg(5, dev_triangles_buf); 224 | m_kernel.setArg(6, static_cast(dev_triangles.size())); 225 | m_kernel.setArg(7, dev_shapes_buf); 226 | m_kernel.setArg(8, static_cast(dev_shapes.size())); 227 | cl::Event event; 228 | 229 | cl::Device device = m_compute_queues[0].getInfo(); 230 | auto preferred_work_size_multiple = 231 | m_kernel.getWorkGroupInfo(device); 232 | auto max_work_group_size = m_kernel.getWorkGroupInfo(device); 233 | auto local_mem_size = m_kernel.getWorkGroupInfo(device); 234 | // auto private_mem_size = m_kernel.getWorkGroupInfo(device); 235 | auto private_mem_size = 236 | 0; // We do this because pocl currently doesn't implement this and will exit(1) 237 | auto device_name = device.getInfo(); 238 | auto local_work_size_x = preferred_work_size_multiple; 239 | auto local_work_size_y = 2; 240 | auto global_work_size = m_width * m_height; 241 | auto work_group_size = local_work_size_x * local_work_size_y; 242 | 243 | if (m_print_perf) { 244 | fmt::print( 245 | " Executing kernel ({}x{}/{}x{}, global work items: {}, items per work group: " 246 | "{}, total work groups: {}) on device {}\n", 247 | m_width, m_height, local_work_size_x, local_work_size_y, global_work_size, 248 | local_work_size_x * local_work_size_y, global_work_size / work_group_size, device_name); 249 | fmt::print(" Max work group size: {}, Local mem size used by kernel: {} KB, minimum " 250 | "private mem " 251 | "size per work item: {} KB\n", 252 | 253 | max_work_group_size, local_mem_size / 1024, private_mem_size / 1024); 254 | } 255 | 256 | cl_int result = m_compute_queues[0].enqueueNDRangeKernel( 257 | m_kernel, cl::NDRange(0, 0), cl::NDRange(m_width, m_height), 258 | cl::NDRange(local_work_size_x, local_work_size_y), nullptr, &event); 259 | 260 | if (result != CL_SUCCESS) { 261 | fmt::print("{}\n", opencl_error_string(result)); 262 | exit(1); 263 | } 264 | 265 | // Wait for kernel to finish computing 266 | event.wait(); 267 | 268 | if (m_print_perf) 269 | m_last_frame_kernel_run_time = timer.elapsed(); 270 | 271 | // Transfer data from GPU back to CPU (TODO In later versions, just expose it to the OpenGL 272 | // buffer 273 | // and render it directly in order to get rid of this transfer) 274 | m_compute_queues[0].enqueueReadBuffer(dev_output_buf, CL_TRUE, 0, 275 | image_size * sizeof(cl_float4), &host_output[0]); 276 | 277 | // Accumulate energy 278 | // TODO Do this in opencl 279 | for (uint32_t x = 0; x < m_width; x += stride_x) { 280 | for (uint32_t y = 0; y < m_height; y += stride_y) { 281 | auto lol = reinterpret_cast(&host_output[y * m_width + x]); 282 | if (scene_changed) 283 | m_luminance[y * m_width + x] = *lol; 284 | else 285 | m_luminance[y * m_width + x] += *lol; 286 | } 287 | } 288 | 289 | if (m_print_perf) 290 | m_last_frame_buffer_read_time = timer.elapsed(); 291 | #else 292 | Timer timer; 293 | 294 | // TODO Make OpenMP simd option work 295 | #pragma omp parallel for collapse(2) schedule(dynamic, 1024) 296 | // Reverse path tracing part: Trace a ray through every camera pixel 297 | for (uint32_t x = 0; x < m_width; x += stride_x) { 298 | for (uint32_t y = 0; y < m_height; y += stride_y) { 299 | Ray ray = Camera::pixel_to_ray(m_camera, x, y); 300 | glm::vec4 new_color = trace_camera_ray(ray, m_max_camera_subpath_depth, m_scene); 301 | if (scene_changed) 302 | m_luminance[y * m_width + x] = new_color; 303 | else 304 | m_luminance[y * m_width + x] += new_color; 305 | } 306 | } 307 | 308 | if (m_print_perf) 309 | fmt::print(" {:<15} {:>10.3f} ms\n", "Path tracing", timer.elapsed()); 310 | #endif 311 | 312 | return m_luminance; 313 | } 314 | 315 | void Renderer::print_sysinfo() const { 316 | auto count_shapes = 0; 317 | auto count_triangles = 0; 318 | for (const auto shape : FlatStructure::shapes(Scene::accel_struct(m_scene))) { 319 | count_shapes++; 320 | for (const auto triangle : Shape::triangles(shape)) { 321 | (void)triangle; 322 | count_triangles++; 323 | } 324 | } 325 | fmt::print("Scene has {} triangles and {} Shapes\n", count_triangles, count_shapes); 326 | #ifdef OPENCL 327 | fmt::print("Rendering on OpenCL\n"); 328 | std::vector platforms; 329 | cl::Platform::get(&platforms); 330 | for (const auto &platform : platforms) { 331 | std::vector devices; 332 | platform.getDevices(CL_DEVICE_TYPE_GPU, &devices); 333 | 334 | if (devices.size() > 0) { 335 | auto platform_name = platform.getInfo(); 336 | auto platform_version = platform.getInfo(); 337 | fmt::print(" {} ({})\n", platform_name, platform_version); 338 | for (size_t i = 0; i < devices.size(); i++) { 339 | auto device_name = devices[i].getInfo(); 340 | auto device_version = devices[i].getInfo(); 341 | auto driver_version = devices[i].getInfo(); 342 | auto max_compute_units = devices[i].getInfo(); 343 | auto max_work_item_dimensions = 344 | devices[i].getInfo(); 345 | auto max_work_item_sizes = devices[i].getInfo(); 346 | auto max_work_group_size = devices[i].getInfo(); 347 | auto max_mem_alloc_size = devices[i].getInfo(); 348 | auto max_parameter_size = devices[i].getInfo(); 349 | auto global_mem_cacheline_size = 350 | devices[i].getInfo(); 351 | auto global_mem_cache_size = devices[i].getInfo(); 352 | auto global_mem_size = devices[i].getInfo(); 353 | auto max_constant_buffer_size = 354 | devices[i].getInfo(); 355 | auto max_constant_args = devices[i].getInfo(); 356 | auto local_mem_size = devices[i].getInfo(); 357 | auto preferred_work_size_multiple = 358 | m_kernel.getWorkGroupInfo( 359 | devices[i]); 360 | fmt::print(" Device {}: {}, OpenCL version: {}, driver version: {}\n", i, 361 | device_name, device_version, driver_version); 362 | fmt::print(" Compute units: {}, Max work item dim: {}, Max work item sizes: " 363 | "{}/{}/{}, Max work group size: {}\n", 364 | max_compute_units, max_work_item_dimensions, max_work_item_sizes[0], 365 | max_work_item_sizes[1], max_work_item_sizes[2], max_work_group_size); 366 | fmt::print(" Max mem alloc size: {} MB, Max parameter size: {} B\n", 367 | max_mem_alloc_size / (1024 * 1024), max_parameter_size); 368 | fmt::print( 369 | " Global mem cacheline size: {} B, Global mem cache size: {} KB, Global " 370 | "mem size: {} MB\n", 371 | global_mem_cacheline_size, global_mem_cache_size / 1024, 372 | global_mem_size / (1024 * 1024)); 373 | fmt::print(" Max constant buffer size: {} KB, Max constant args: {}, Local mem " 374 | "size: {} KB\n", 375 | max_constant_buffer_size / 1024, max_constant_args, 376 | local_mem_size / 1024); 377 | fmt::print(" Preferred work group size multiple: {}\n", 378 | preferred_work_size_multiple); 379 | fmt::print("\n"); 380 | } 381 | } 382 | } 383 | #else 384 | fmt::print("Rendering on OpenMP\n"); 385 | auto threads = std::thread::hardware_concurrency(); 386 | fmt::print(" OpenMP ({} threads)\n", threads); 387 | #endif 388 | } 389 | 390 | void Renderer::print_last_frame_timings() const { 391 | #ifdef OPENCL 392 | fmt::print(" {:<15} {:>12.3f} ms\n", "Buffer write to device", 393 | m_last_frame_buffer_write_time); 394 | fmt::print(" {:<15} {:>20.3f} ms\n", "Kernel run time", m_last_frame_kernel_run_time); 395 | fmt::print(" {:<15} {:>15.3f} ms\n", "Buffer read to host", m_last_frame_buffer_read_time); 396 | #endif 397 | } 398 | } 399 | -------------------------------------------------------------------------------- /trac0r/renderer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RENDERER_HPP 2 | #define RENDERER_HPP 3 | 4 | #include "camera.hpp" 5 | #include "scene.hpp" 6 | #include "light_vertex.hpp" 7 | 8 | #ifdef OPENCL 9 | #include 10 | #endif 11 | 12 | #include 13 | #include 14 | 15 | namespace trac0r { 16 | 17 | class Renderer { 18 | public: 19 | Renderer(const int width, const int height, const Camera &camera, const Scene &scene, 20 | bool print_perf); 21 | static glm::vec4 trace_camera_ray(const Ray &ray, const unsigned max_depth, const Scene &scene); 22 | std::vector &render(bool screen_changed, int stride_x, int stride_y); 23 | void print_sysinfo() const; 24 | void print_last_frame_timings() const; 25 | 26 | private: 27 | const uint32_t m_max_camera_subpath_depth = 10; 28 | 29 | /** 30 | * @brief We accumulate our "photons" into here for each pixel 31 | */ 32 | std::vector m_luminance; 33 | 34 | const uint32_t m_width; 35 | const uint32_t m_height; 36 | const Camera &m_camera; 37 | const Scene &m_scene; 38 | bool m_print_perf = false; 39 | 40 | #ifdef OPENCL 41 | double m_last_frame_buffer_write_time; 42 | double m_last_frame_kernel_run_time; 43 | double m_last_frame_buffer_read_time; 44 | std::vector m_compute_platforms; 45 | std::vector m_compute_devices; 46 | cl::Context m_compute_context; 47 | std::vector m_compute_queues; 48 | cl::Program m_program; 49 | cl::Kernel m_kernel; 50 | #endif 51 | }; 52 | } 53 | 54 | #endif /* end of include guard: RENDERER_HPP */ 55 | -------------------------------------------------------------------------------- /trac0r/renderer_aux.cl: -------------------------------------------------------------------------------- 1 | #define EPSILON 0.00001 2 | 3 | typedef struct PRNG { 4 | ulong m_seed[16]; 5 | ulong m_p; 6 | } PRNG; 7 | 8 | typedef struct Camera { 9 | float3 m_pos; 10 | float3 m_dir; 11 | float3 m_world_up; 12 | float3 m_right; 13 | float3 m_up; 14 | float m_canvas_width; 15 | float m_canvas_height; 16 | float3 m_canvas_center_pos; 17 | float3 m_canvas_dir_x; 18 | float3 m_canvas_dir_y; 19 | float m_near_plane_dist; 20 | float m_far_plane_dist; 21 | int m_screen_width; 22 | int m_screen_height; 23 | float2 m_pixel_size; 24 | float m_vertical_fov; 25 | float m_horizontal_fov; 26 | } Camera; 27 | 28 | typedef struct Material { 29 | uchar m_type; 30 | float3 m_color; 31 | float m_roughness; 32 | float m_ior; 33 | float m_emittance; 34 | } Material; 35 | 36 | typedef struct AABB { 37 | float3 m_min; 38 | float3 m_max; 39 | } AABB; 40 | 41 | typedef struct Shape { 42 | AABB m_aabb; 43 | uint m_triangle_index_start; 44 | uint m_triangle_index_end; 45 | } Shape; 46 | 47 | typedef struct Triangle { 48 | float3 m_v1; 49 | float3 m_v2; 50 | float3 m_v3; 51 | Material m_material; 52 | float3 m_normal; 53 | float3 m_centroid; 54 | float m_area; 55 | } Triangle; 56 | 57 | // typedef struct FlatStructure { 58 | // Triangle *m_triangles; 59 | // unsigned m_num_triangles; 60 | // } FlatStructure; 61 | 62 | typedef struct Ray { 63 | float3 m_origin; 64 | float3 m_dir; 65 | float3 m_invdir; 66 | } Ray; 67 | 68 | typedef struct IntersectionInfo { 69 | bool m_has_intersected; 70 | float3 m_pos; 71 | float3 m_normal; 72 | float m_angle_between; 73 | Ray m_incoming_ray; 74 | Material m_material; 75 | } IntersectionInfo; 76 | 77 | // From http://xorshift.di.unimi.it/xorshift1024star.c 78 | inline ulong xorshift1024star(__global PRNG *prng) { 79 | ulong s0 = prng->m_seed[prng->m_p]; 80 | ulong s1 = prng->m_seed[prng->m_p = (prng->m_p + 1) & 15]; 81 | s1 ^= s1 << 31; // a 82 | s1 ^= s1 >> 11; // b 83 | s0 ^= s0 >> 30; // c 84 | return (prng->m_seed[prng->m_p] = s0 ^ s1) * 1181783497276652981L * get_global_id(0) * 85 | get_global_id(1); 86 | } 87 | 88 | inline Ray ray_construct(float3 origin, float3 direction) { 89 | Ray new_ray; 90 | new_ray.m_origin = origin; 91 | new_ray.m_dir = direction; 92 | new_ray.m_invdir = 1.f / direction; 93 | return new_ray; 94 | } 95 | 96 | inline float rand_range(__global PRNG *prng, const float min, const float max) { 97 | return min + ((float)xorshift1024star(prng)) / (float)(ULONG_MAX / (max - min)); 98 | } 99 | 100 | inline float3 uniform_sample_sphere(__global PRNG *prng) { 101 | float3 rand_vec = (float3)(rand_range(prng, -1.f, 1.f), rand_range(prng, -1.f, 1.f), 102 | rand_range(prng, -1.f, 1.f)); 103 | return fast_normalize(rand_vec); 104 | } 105 | 106 | inline float3 oriented_oriented_hemisphere_sample(__global PRNG *prng, const float3 dir) { 107 | float3 v = uniform_sample_sphere(prng); 108 | return v * sign(dot(v, dir)); 109 | } 110 | 111 | inline float3 ortho(float3 v) { 112 | // Awesome branchless function for finding an orthogonal vector in 3D space by 113 | // http://lolengine.net/blog/2013/09/21/picking-orthogonal-vector-combing-coconuts 114 | // 115 | // Their "boring" branching is commented here for completeness: 116 | // return glm::abs(v.x) > glm::abs(v.z) ? glm::vec3(-v.y, v.x, 0.0) : glm::vec3(0.0, -v.z, v.y); 117 | 118 | float flo; // We don't really care about the floor but fract needs it 119 | float k = fract(fabs(v.x) + 0.5f, &flo); 120 | return (float3)(-v.y, v.x - k * v.z, k * v.y); 121 | } 122 | 123 | inline float3 sample_hemisphere(__global PRNG *prng, float3 dir, float power, float angle) { 124 | // Code adapted from Mikael Hvidtfeldt Christensen's resource 125 | // at http://blog.hvidtfeldts.net/index.php/2015/01/path-tracing-3d-fractals/ 126 | // Thanks! 127 | 128 | float3 o1 = fast_normalize(ortho(dir)); 129 | float3 o2 = fast_normalize(cross(dir, o1)); 130 | float2 r = (float2)(rand_range(prng, 0.f, 1.f), rand_range(prng, native_cos(angle), 1.f)); 131 | r.x = r.x * M_PI_F * 2.f; 132 | r.y = native_powr(r.y, 1.f / (power + 1.f)); 133 | float oneminus = sqrt(1.f - r.y * r.y); 134 | return native_cos(r.x) * oneminus * o1 + native_sin(r.x) * oneminus * o2 + r.y * dir; 135 | } 136 | 137 | inline float3 oriented_cosine_weighted_hemisphere_sample(__global PRNG *prng, float3 dir) { 138 | return sample_hemisphere(prng, dir, 1.f, M_PI_2_F); 139 | } 140 | 141 | inline float3 oriented_cosine_weighted_cone_sample(__global PRNG *prng, float3 dir, float angle) { 142 | return sample_hemisphere(prng, dir, 1.f, angle); 143 | } 144 | 145 | inline float3 reflect(float3 incident, float3 normal) { 146 | return incident - 2.f * normal * dot(normal, incident); 147 | } 148 | 149 | inline float3 refract(float3 incident, float3 normal, float eta) { 150 | float k = 1.f - eta * eta * (1.f - dot(normal, incident) * dot(normal, incident)); 151 | if (k < 0.f) 152 | return (float3)(0.f); 153 | else 154 | return eta * incident - (eta * dot(normal, incident) + native_sqrt(k)) * normal; 155 | } 156 | 157 | inline bool AABB_is_null(const AABB *aabb) { 158 | bool min_null = fast_length(aabb->m_min) == 0; 159 | bool max_null = fast_length(aabb->m_max) == 0; 160 | return min_null && max_null; 161 | } 162 | 163 | inline float3 AABB_min(__global AABB *aabb) { 164 | return aabb->m_min; 165 | } 166 | 167 | inline float3 AABB_max(__global AABB *aabb) { 168 | return aabb->m_max; 169 | } 170 | 171 | inline float3 AABB_diagonal(const AABB *aabb) { 172 | return aabb->m_max - aabb->m_min; 173 | } 174 | 175 | inline float3 AABB_center(const AABB *aabb) { 176 | return aabb->m_min + (AABB_diagonal(aabb) * 0.5f); 177 | } 178 | 179 | inline void AABB_extend(AABB *aabb, float3 point) { 180 | aabb->m_min = min(point, aabb->m_min); 181 | aabb->m_max = max(point, aabb->m_max); 182 | } 183 | 184 | inline bool AABB_overlaps(const AABB *first, const AABB *second) { 185 | return first->m_max.x > second->m_min.x && first->m_min.x < second->m_max.x && 186 | first->m_max.y > second->m_min.y && first->m_min.y < second->m_max.y && 187 | first->m_max.z > second->m_min.z && first->m_min.z < second->m_max.z; 188 | } 189 | 190 | inline void AABB_reset(AABB *aabb) { 191 | aabb->m_min = 0; 192 | aabb->m_max = 0; 193 | } 194 | 195 | inline float2 Camera_screenspace_to_camspace(__constant const Camera *camera, unsigned x, 196 | unsigned y) { 197 | float rel_x = -(x - camera->m_screen_width / 2.f) / camera->m_screen_width; 198 | float rel_y = -(y - camera->m_screen_height / 2.f) / camera->m_screen_height; 199 | return (float2)(rel_x, rel_y); 200 | } 201 | 202 | inline int2 Camera_camspace_to_screenspace(__constant const Camera *camera, int2 coords) { 203 | int screen_x = round(0.5f * (camera->m_screen_width - 2.f * camera->m_screen_width * coords.x)); 204 | int screen_y = 205 | round(0.5f * (camera->m_screen_height - 2.f * camera->m_screen_height * coords.y)); 206 | return (int2)(screen_x, screen_y); 207 | } 208 | 209 | inline float3 Camera_camspace_to_worldspace(__constant const Camera *camera, float2 rel_pos) { 210 | float3 worldspace = camera->m_canvas_center_pos + (rel_pos.x * camera->m_canvas_dir_x) + 211 | (rel_pos.y * camera->m_canvas_dir_y); 212 | return worldspace; 213 | } 214 | 215 | // float2 Camera_worldspace_to_camspace(const Camera *camera, float3 world_pos_on_canvas) { 216 | // float3 canvas_center_to_point = world_pos_on_canvas - Camera_canvas_center_pos(camera); 217 | // 218 | // // Manually calculate angle between the positive y-axis on the canvas and the world point 219 | // float3 ay = canvas_center_to_point; 220 | // float3 by = Camera_up(camera); 221 | // float3 cy = cross(ay, by); 222 | // float angle1y = atan(dot(ay, by), length(cy)); 223 | // 224 | // // Manually calculate angle between the positive x-axis on the canvas and the world point 225 | // float3 ax = canvas_center_to_point; 226 | // float3 bx = Camera_right(camera); 227 | // float3 cx = cross(ax, bx); 228 | // float angle1x = atan(length(cx), dot(ax, bx)); 229 | // 230 | // float angle2x = glm_pi() - (glm_half_pi() + angle1x); 231 | // float len = length(canvas_center_to_point); 232 | // float x = sin(angle2x) * len; 233 | // float y = sin(angle1y) * len; 234 | // float rel_x = -(2 * x) / Camera_canvas_width(camera); 235 | // float rel_y = -(2 * y) / Camera_canvas_height(camera); 236 | // return (float2)(rel_x, -rel_y); 237 | // } 238 | 239 | // glm_vec3 Camera_worldpoint_to_worldspace(const Camera *camera, glm_vec3 world_point) { 240 | // auto ray_to_cam = pos(camera) - world_point; 241 | // float dist = 0; 242 | // bool collided = glm_intersectRayPlane(world_point, glm_normalize(ray_to_cam), 243 | // canvas_center_pos(camera), dir(camera), dist); 244 | // if (collided) { 245 | // return world_point + glm_normalize(ray_to_cam) * dist; 246 | // } else { 247 | // return glm_vec3(0); 248 | // } 249 | // } 250 | 251 | inline Ray Camera_pixel_to_ray(__global PRNG *prng, __constant Camera *camera, uint x, uint y) { 252 | float2 rel_pos = Camera_screenspace_to_camspace(camera, x, y); 253 | 254 | // Subpixel sampling / antialiasing 255 | float2 jitter = {rand_range(prng, -camera->m_pixel_size.x / 2.f, camera->m_pixel_size.x / 2.f), 256 | rand_range(prng, -camera->m_pixel_size.y / 2.f, camera->m_pixel_size.y / 2.f)}; 257 | rel_pos += jitter; 258 | 259 | float3 world_pos = Camera_camspace_to_worldspace(camera, rel_pos); 260 | float3 ray_dir = fast_normalize(world_pos - camera->m_pos); 261 | 262 | return ray_construct(world_pos, ray_dir); 263 | } 264 | 265 | // From 266 | // http://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-box-intersection 267 | inline bool intersect_ray_aabb(const Ray *ray, __global AABB *aabb) { 268 | float tmin, tmax, tymin, tymax, tzmin, tzmax; 269 | 270 | float3 bounds[2]; 271 | bounds[0] = AABB_min(aabb); 272 | bounds[1] = AABB_max(aabb); 273 | 274 | char3 sign; 275 | sign.x = (ray->m_invdir.x < 0); 276 | sign.y = (ray->m_invdir.y < 0); 277 | sign.z = (ray->m_invdir.z < 0); 278 | 279 | tmin = (bounds[sign.x].x - ray->m_origin.x) * ray->m_invdir.x; 280 | tmax = (bounds[1 - sign.x].x - ray->m_origin.x) * ray->m_invdir.x; 281 | tymin = (bounds[sign.y].y - ray->m_origin.y) * ray->m_invdir.y; 282 | tymax = (bounds[1 - sign.y].y - ray->m_origin.y) * ray->m_invdir.y; 283 | 284 | if ((tmin > tymax) || (tymin > tmax)) 285 | return false; 286 | if (tymin > tmin) 287 | tmin = tymin; 288 | if (tymax < tmax) 289 | tmax = tymax; 290 | 291 | tzmin = (bounds[sign.z].z - ray->m_origin.z) * ray->m_invdir.z; 292 | tzmax = (bounds[1 - sign.z].z - ray->m_origin.z) * ray->m_invdir.z; 293 | 294 | if ((tmin > tzmax) || (tzmin > tmax)) 295 | return false; 296 | if (tzmin > tmin) 297 | tmin = tzmin; 298 | if (tzmax < tmax) 299 | tmax = tzmax; 300 | 301 | return true; 302 | } 303 | 304 | // Möller-Trumbore intersection algorithm 305 | // (see https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm) 306 | inline bool intersect_ray_triangle(const Ray *ray, __global const Triangle *triangle, float *dist) { 307 | // Calculate edges of triangle from v0. 308 | float3 e0 = triangle->m_v2 - triangle->m_v1; 309 | float3 e1 = triangle->m_v3 - triangle->m_v1; 310 | 311 | // Calculate determinant to check whether the ray is in the newly calculated plane made up from 312 | // e0 and e1. 313 | float3 pvec = cross(ray->m_dir, e1); 314 | float det = dot(e0, pvec); 315 | 316 | // Check whether determinant is close to 0. If that is the case, the ray is in the same plane as 317 | // the triangle itself which means that they can't collide. This effectively disables backface 318 | // culling for which we would instead only check whether det < epsilon. 319 | if (det > -EPSILON && det < EPSILON) 320 | return false; 321 | 322 | float inv_det = 1.f / det; 323 | 324 | // Calculate distance from v1 to ray origin 325 | float3 tvec = ray->m_origin - triangle->m_v1; 326 | 327 | // Calculate u parameter and test bound 328 | float u = dot(tvec, pvec) * inv_det; 329 | 330 | // Check whether the intersection lies outside of the triangle 331 | if (u < 0.f || u > 1.f) 332 | return false; 333 | 334 | // Prepare to test v parameter 335 | float3 qvec = cross(tvec, e0); 336 | 337 | // Calculate v parameter and test bound 338 | float v = dot(ray->m_dir, qvec) * inv_det; 339 | 340 | // Check whether the intersection lies outside of the triangle 341 | if (v < 0.f || u + v > 1.f) 342 | return false; 343 | 344 | float t = dot(e1, qvec) * inv_det; 345 | 346 | if (t > EPSILON) { 347 | *dist = t - EPSILON; 348 | return true; 349 | } 350 | 351 | // If we end up here, there was no hit 352 | return false; 353 | } 354 | 355 | inline IntersectionInfo Scene_intersect(__global Triangle *triangles, const uint num_triangles, 356 | __global Shape *shapes, const uint num_shapes, Ray *ray) { 357 | IntersectionInfo intersect_info; // TODO use proper constructor 358 | intersect_info.m_has_intersected = false; 359 | 360 | // Keep track of closest triangle 361 | float closest_dist = FLT_MAX; 362 | Triangle closest_triangle; 363 | for (unsigned s = 0; s < num_shapes; s++) { 364 | __global Shape *shape = &(shapes[s]); 365 | if (intersect_ray_aabb(ray, &(shape->m_aabb))) { 366 | for (unsigned i = shape->m_triangle_index_start; i < shape->m_triangle_index_end; i++) { 367 | float dist_to_intersect; 368 | __global Triangle *tri = &(triangles[i]); 369 | bool intersected = intersect_ray_triangle(ray, tri, &dist_to_intersect); 370 | if (intersected) { 371 | // Find closest triangle 372 | if (dist_to_intersect < closest_dist) { 373 | closest_dist = dist_to_intersect; 374 | closest_triangle = *tri; 375 | 376 | intersect_info.m_has_intersected = true; 377 | intersect_info.m_pos = ray->m_origin + ray->m_dir * closest_dist; 378 | intersect_info.m_incoming_ray = *ray; 379 | intersect_info.m_angle_between = 380 | dot(closest_triangle.m_normal, intersect_info.m_incoming_ray.m_dir); 381 | intersect_info.m_normal = closest_triangle.m_normal; 382 | intersect_info.m_material = closest_triangle.m_material; 383 | } 384 | } 385 | } 386 | } 387 | } 388 | 389 | return intersect_info; 390 | } 391 | 392 | __kernel void renderer_trace_camera_ray(__write_only __global float4 *output, const uint width, 393 | const uint max_depth, __global PRNG *prng, 394 | __constant Camera *camera, __global Triangle *triangles, 395 | const uint num_triangles, __global Shape *shapes, 396 | const uint num_shapes) { 397 | uint x = get_global_id(0); 398 | uint y = get_global_id(1); 399 | uint index = y * width + x; 400 | 401 | Ray next_ray = Camera_pixel_to_ray(prng, camera, x, y); 402 | float3 return_color = (float3)(0.f); 403 | float3 luminance = (float3)(1.f); 404 | size_t depth = 0; 405 | 406 | // We'll run until terminated by Russian Roulette 407 | while (true) { 408 | // Russian Roulette 409 | float continuation_probability = 1.f - (1.f / (max_depth - depth)); 410 | // float continuation_probability = (luminance.x + luminance.y + luminance.z) / 3.f; 411 | if (rand_range(prng, 0.f, 1.f) >= continuation_probability) { 412 | break; 413 | } 414 | depth++; 415 | 416 | IntersectionInfo intersect_info = Scene_intersect(triangles, num_triangles, shapes, num_shapes, &next_ray); 417 | if (intersect_info.m_has_intersected) { 418 | // Emitter Material 419 | if (intersect_info.m_material.m_type == 1) { 420 | return_color = luminance * intersect_info.m_material.m_color * 421 | intersect_info.m_material.m_emittance / continuation_probability; 422 | break; 423 | } 424 | 425 | // Diffuse Material 426 | else if (intersect_info.m_material.m_type == 2) { 427 | // Find normal in correct direction 428 | intersect_info.m_normal = 429 | intersect_info.m_normal * -sign(intersect_info.m_angle_between); 430 | intersect_info.m_angle_between = 431 | intersect_info.m_angle_between * -sign(intersect_info.m_angle_between); 432 | 433 | // Find new random direction for diffuse reflection 434 | 435 | // We're using importance sampling for this since it converges much faster than 436 | // uniform sampling 437 | // See http://blog.hvidtfeldts.net/index.php/2015/01/path-tracing-3d-fractals/ and 438 | // http://www.rorydriscoll.com/2009/01/07/better-sampling/ and 439 | // https://pathtracing.wordpress.com/2011/03/03/cosine-weighted-hemisphere/ 440 | float3 new_ray_dir = 441 | oriented_cosine_weighted_hemisphere_sample(prng, intersect_info.m_normal); 442 | luminance *= intersect_info.m_material.m_color; 443 | 444 | // For completeness, this is what it looks like with uniform sampling: 445 | // glm::vec3 new_ray_dir = 446 | // oriented_uniform_hemisphere_sample(intersect_info.m_normal); 447 | // float cos_theta = glm::dot(new_ray_dir, intersect_info.m_normal); 448 | // luminance *= 2.f * intersect_info.m_material.m_color * cos_theta; 449 | 450 | // Make a new ray 451 | next_ray = ray_construct(intersect_info.m_pos, new_ray_dir); 452 | } 453 | 454 | // Glass Material 455 | else if (intersect_info.m_material.m_type == 3) { 456 | // This code is mostly taken from TomCrypto's Lambda 457 | float n1, n2; 458 | 459 | if (intersect_info.m_angle_between > 0) { 460 | // Ray in inside the object 461 | n1 = intersect_info.m_material.m_ior; 462 | n2 = 1.0003f; 463 | 464 | intersect_info.m_normal = -intersect_info.m_normal; 465 | } else { 466 | // Ray is outside the object 467 | n1 = 1.0003f; 468 | n2 = intersect_info.m_material.m_ior; 469 | 470 | intersect_info.m_angle_between = -intersect_info.m_angle_between; 471 | } 472 | 473 | float n = n1 / n2; 474 | 475 | float cos_t = 1.f - pown(n, 2) * (1.f - pown(intersect_info.m_angle_between, 2)); 476 | 477 | float3 new_ray_dir; 478 | // Handle total internal reflection 479 | if (cos_t < 0.f) { 480 | new_ray_dir = intersect_info.m_incoming_ray.m_dir - 481 | (2.f * intersect_info.m_angle_between * intersect_info.m_normal); 482 | next_ray = ray_construct(intersect_info.m_pos, new_ray_dir); 483 | break; 484 | } 485 | 486 | cos_t = native_sqrt(cos_t); 487 | 488 | // Fresnel coefficients 489 | float r1 = n1 * intersect_info.m_angle_between - n2 * cos_t; 490 | float r2 = n1 * intersect_info.m_angle_between + n2 * cos_t; 491 | float r3 = n2 * intersect_info.m_angle_between - n1 * cos_t; 492 | float r4 = n2 * intersect_info.m_angle_between + n1 * cos_t; 493 | float r = pown(r1 / r2, 2) + pown(r3 / r4, 2) * 0.5f; 494 | 495 | if (rand_range(prng, 0.f, 1.f) < r) { 496 | // Reflection 497 | new_ray_dir = intersect_info.m_incoming_ray.m_dir - 498 | (2.f * intersect_info.m_angle_between * intersect_info.m_normal); 499 | luminance *= intersect_info.m_material.m_color; 500 | } else { 501 | // Refraction 502 | new_ray_dir = intersect_info.m_incoming_ray.m_dir * (n1 / n2) + 503 | intersect_info.m_normal * 504 | ((n1 / n2) * intersect_info.m_angle_between - cos_t); 505 | luminance *= 1.f; 506 | } 507 | 508 | // Make a new ray 509 | next_ray = ray_construct(intersect_info.m_pos, new_ray_dir); 510 | } 511 | 512 | // Glossy Material 513 | else if (intersect_info.m_material.m_type == 4) { 514 | // Find normal in correct direction 515 | intersect_info.m_normal = 516 | intersect_info.m_normal * -sign(intersect_info.m_angle_between); 517 | intersect_info.m_angle_between = 518 | intersect_info.m_angle_between * -sign(intersect_info.m_angle_between); 519 | 520 | float real_roughness = intersect_info.m_material.m_roughness * M_PI_F; 521 | 522 | // Find new direction for reflection 523 | float3 reflected_dir = 524 | intersect_info.m_incoming_ray.m_dir - 525 | (2.f * intersect_info.m_angle_between * intersect_info.m_normal); 526 | 527 | // Find new random direction on cone for glossy reflection 528 | float3 new_ray_dir = 529 | oriented_cosine_weighted_cone_sample(prng, reflected_dir, real_roughness); 530 | 531 | luminance *= intersect_info.m_material.m_color; 532 | 533 | // Make a new ray 534 | next_ray = ray_construct(intersect_info.m_pos, new_ray_dir); 535 | } 536 | } else { 537 | break; 538 | } 539 | } 540 | 541 | output[index] = (float4)(return_color.x, return_color.y, return_color.z, 1.f); 542 | } 543 | -------------------------------------------------------------------------------- /trac0r/renderer_aux.cpp: -------------------------------------------------------------------------------- 1 | #include "renderer.hpp" 2 | 3 | #include "random.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | namespace trac0r { 12 | // #pragma omp declare simd // TODO make this work 13 | glm::vec4 Renderer::trace_camera_ray(const Ray &ray, const unsigned max_depth, const Scene &scene) { 14 | Ray next_ray = ray; 15 | glm::vec3 return_color{0.f}; 16 | glm::vec3 luminance{1.f}; 17 | size_t depth = 0; 18 | 19 | // We'll run until terminated by Russian Roulette 20 | while (true) { 21 | // Russian Roulette 22 | float continuation_probability = 1.f - (1.f / (max_depth - depth)); 23 | // float continuation_probability = (luminance.x + luminance.y + luminance.z) / 3.f; 24 | if (rand_range(0.f, 1.0f) >= continuation_probability) { 25 | break; 26 | } 27 | depth++; 28 | 29 | // TODO Refactor out all of the material BRDFs into the material class so we don't duplicate 30 | // them 31 | auto intersect_info = Scene::intersect(scene, next_ray); 32 | if (intersect_info.m_has_intersected) { 33 | // Emitter Material 34 | if (intersect_info.m_material.m_type == 1) { 35 | return_color = luminance * intersect_info.m_material.m_color * 36 | intersect_info.m_material.m_emittance / continuation_probability; 37 | break; 38 | } 39 | 40 | // Diffuse Material 41 | else if (intersect_info.m_material.m_type == 2) { 42 | // Find normal in correct direction 43 | intersect_info.m_normal = 44 | intersect_info.m_normal * -glm::sign(intersect_info.m_angle_between); 45 | intersect_info.m_angle_between = 46 | intersect_info.m_angle_between * -glm::sign(intersect_info.m_angle_between); 47 | 48 | // Find new random direction for diffuse reflection 49 | 50 | // We're using importance sampling for this since it converges much faster than 51 | // uniform sampling 52 | // See http://blog.hvidtfeldts.net/index.php/2015/01/path-tracing-3d-fractals/ and 53 | // http://www.rorydriscoll.com/2009/01/07/better-sampling/ and 54 | // https://pathtracing.wordpress.com/2011/03/03/cosine-weighted-hemisphere/ 55 | glm::vec3 new_ray_dir = 56 | oriented_cosine_weighted_hemisphere_sample(intersect_info.m_normal); 57 | luminance *= intersect_info.m_material.m_color; 58 | 59 | // For completeness, this is what it looks like with uniform sampling: 60 | // glm::vec3 new_ray_dir = 61 | // oriented_uniform_hemisphere_sample(intersect_info.m_normal); 62 | // float cos_theta = glm::dot(new_ray_dir, intersect_info.m_normal); 63 | // luminance *= 2.f * intersect_info.m_material.m_color * cos_theta; 64 | 65 | // Make a new ray 66 | next_ray = Ray{intersect_info.m_pos, new_ray_dir}; 67 | } 68 | 69 | // Glass Material 70 | else if (intersect_info.m_material.m_type == 3) { 71 | // This code is mostly taken from TomCrypto's Lambda 72 | float n1, n2; 73 | 74 | if (intersect_info.m_angle_between > 0) { 75 | // Ray in inside the object 76 | n1 = intersect_info.m_material.m_ior; 77 | n2 = 1.0003f; 78 | 79 | intersect_info.m_normal = -intersect_info.m_normal; 80 | } else { 81 | // Ray is outside the object 82 | n1 = 1.0003f; 83 | n2 = intersect_info.m_material.m_ior; 84 | 85 | intersect_info.m_angle_between = -intersect_info.m_angle_between; 86 | } 87 | 88 | float n = n1 / n2; 89 | 90 | float cos_t = 91 | 1.f - glm::pow(n, 2) * (1.f - glm::pow(intersect_info.m_angle_between, 2)); 92 | 93 | glm::vec3 new_ray_dir; 94 | // Handle total internal reflection 95 | if (cos_t < 0.f) { 96 | new_ray_dir = intersect_info.m_incoming_ray.m_dir - 97 | (2.f * intersect_info.m_angle_between * intersect_info.m_normal); 98 | next_ray = Ray{intersect_info.m_pos, new_ray_dir}; 99 | break; 100 | } 101 | 102 | cos_t = glm::sqrt(cos_t); 103 | 104 | // Fresnel coefficients 105 | float r1 = n1 * intersect_info.m_angle_between - n2 * cos_t; 106 | float r2 = n1 * intersect_info.m_angle_between + n2 * cos_t; 107 | float r3 = n2 * intersect_info.m_angle_between - n1 * cos_t; 108 | float r4 = n2 * intersect_info.m_angle_between + n1 * cos_t; 109 | float r = glm::pow(r1 / r2, 2) + glm::pow(r3 / r4, 2) * 0.5f; 110 | 111 | if (rand_range(0.f, 1.f) < r) { 112 | // Reflection 113 | new_ray_dir = intersect_info.m_incoming_ray.m_dir - 114 | (2.f * intersect_info.m_angle_between * intersect_info.m_normal); 115 | luminance *= intersect_info.m_material.m_color; 116 | } else { 117 | // Refraction 118 | new_ray_dir = intersect_info.m_incoming_ray.m_dir * (n1 / n2) + 119 | intersect_info.m_normal * 120 | ((n1 / n2) * intersect_info.m_angle_between - cos_t); 121 | luminance *= 1.f; 122 | } 123 | 124 | // Make a new ray 125 | next_ray = Ray{intersect_info.m_pos, new_ray_dir}; 126 | } 127 | 128 | // Glossy Material 129 | else if (intersect_info.m_material.m_type == 4) { 130 | // Find normal in correct direction 131 | intersect_info.m_normal = 132 | intersect_info.m_normal * -glm::sign(intersect_info.m_angle_between); 133 | intersect_info.m_angle_between = 134 | intersect_info.m_angle_between * -glm::sign(intersect_info.m_angle_between); 135 | 136 | float real_roughness = 137 | intersect_info.m_material.m_roughness * glm::half_pi(); 138 | 139 | // Find new direction for reflection 140 | glm::vec3 reflected_dir = 141 | intersect_info.m_incoming_ray.m_dir - 142 | (2.f * intersect_info.m_angle_between * intersect_info.m_normal); 143 | 144 | // Find new random direction on cone for glossy reflection 145 | glm::vec3 new_ray_dir = 146 | oriented_cosine_weighted_cone_sample(reflected_dir, real_roughness); 147 | 148 | luminance *= intersect_info.m_material.m_color; 149 | 150 | // Make a new ray 151 | next_ray = Ray{intersect_info.m_pos, new_ray_dir}; 152 | } 153 | } else { 154 | break; 155 | } 156 | } 157 | 158 | return glm::vec4(return_color, 1.f); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /trac0r/scene.cpp: -------------------------------------------------------------------------------- 1 | #include "scene.hpp" 2 | #include "utils.hpp" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | namespace trac0r { 12 | 13 | void Scene::add_shape(Scene &scene, Shape &shape) { 14 | FlatStructure::add_shape(Scene::accel_struct(scene), shape); 15 | } 16 | 17 | IntersectionInfo Scene::intersect(const Scene &scene, const Ray &ray) { 18 | return FlatStructure::intersect(accel_struct(scene), ray); 19 | } 20 | 21 | void Scene::rebuild(Scene &scene) { 22 | FlatStructure::rebuild(Scene::accel_struct(scene)); 23 | } 24 | 25 | FlatStructure &Scene::accel_struct(Scene &scene) { 26 | return scene.m_accel_struct; 27 | } 28 | 29 | const FlatStructure &Scene::accel_struct(const Scene &scene) { 30 | return scene.m_accel_struct; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /trac0r/scene.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SCENE_HPP 2 | #define SCENE_HPP 3 | 4 | #include "ray.hpp" 5 | #include "shape.hpp" 6 | #include "camera.hpp" 7 | #include "intersection_info.hpp" 8 | #include "flat_structure.hpp" 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | namespace trac0r { 16 | 17 | class Scene { 18 | public: 19 | static void add_shape(Scene &scene, Shape &shape); 20 | static IntersectionInfo intersect(const Scene &scene, const Ray &ray); 21 | static void rebuild(Scene &scene); 22 | static const FlatStructure &accel_struct(const Scene &scene); 23 | static FlatStructure &accel_struct(Scene &scene); 24 | 25 | private: 26 | FlatStructure m_accel_struct; 27 | }; 28 | } 29 | 30 | #endif /* end of include guard: SCENE_HPP*/ 31 | -------------------------------------------------------------------------------- /trac0r/shape.cpp: -------------------------------------------------------------------------------- 1 | #include "shape.hpp" 2 | 3 | #include "glm/gtx/rotate_vector.hpp" 4 | 5 | #include "utils.hpp" 6 | 7 | namespace trac0r { 8 | 9 | const glm::vec3 Shape::pos(const Shape &shape) { 10 | return shape.m_pos; 11 | } 12 | 13 | void Shape::set_pos(Shape &shape, glm::vec3 new_pos) { 14 | shape.m_pos = new_pos; 15 | } 16 | 17 | const glm::vec3 Shape::orientation(const Shape &shape) { 18 | return shape.m_orientation; 19 | } 20 | 21 | void Shape::set_orientation(Shape &shape, glm::vec3 new_orientation) { 22 | shape.m_orientation = new_orientation; 23 | } 24 | 25 | const glm::vec3 Shape::scale(const Shape &shape) { 26 | return shape.m_scale; 27 | } 28 | 29 | void Shape::set_scale(Shape &shape, glm::vec3 new_scale) { 30 | shape.m_scale = new_scale; 31 | } 32 | 33 | AABB &Shape::aabb(Shape &shape) { 34 | return shape.m_aabb; 35 | } 36 | 37 | const AABB &Shape::aabb(const Shape &shape) { 38 | return shape.m_aabb; 39 | } 40 | 41 | std::vector &Shape::triangles(Shape &shape) { 42 | return shape.m_triangles; 43 | } 44 | 45 | const std::vector &Shape::triangles(const Shape &shape) { 46 | return shape.m_triangles; 47 | } 48 | 49 | void Shape::add_triangle(Shape &shape, const Triangle triangle) { 50 | shape.m_triangles.push_back(triangle); 51 | } 52 | 53 | Shape Shape::make_box(glm::vec3 pos, glm::vec3 orientation, glm::vec3 size, Material material) { 54 | Shape new_shape; 55 | Shape::set_pos(new_shape, pos); 56 | Shape::set_orientation(new_shape, orientation); 57 | Shape::set_scale(new_shape, size); 58 | 59 | auto p1 = glm::vec3{-0.5f, 0.5f, -0.5f}; 60 | auto p2 = glm::vec3{-0.5f, -0.5f, -0.5f}; 61 | auto p3 = glm::vec3{0.5f, -0.5f, -0.5f}; 62 | auto p4 = glm::vec3{0.5f, 0.5f, -0.5f}; 63 | auto p5 = glm::vec3{-0.5f, 0.5f, 0.5f}; 64 | auto p6 = glm::vec3{-0.5f, -0.5f, 0.5f}; 65 | auto p7 = glm::vec3{0.5f, -0.5f, 0.5f}; 66 | auto p8 = glm::vec3{0.5f, 0.5f, 0.5f}; 67 | 68 | // front face 69 | auto t1 = Triangle(p2, p1, p3, material); 70 | auto t2 = Triangle(p1, p4, p3, material); 71 | 72 | // right face 73 | auto t3 = Triangle(p4, p3, p8, material); 74 | auto t4 = Triangle(p3, p7, p8, material); 75 | 76 | // left face 77 | auto t5 = Triangle(p1, p2, p6, material); 78 | auto t6 = Triangle(p5, p1, p6, material); 79 | 80 | // back face 81 | auto t7 = Triangle(p5, p6, p8, material); 82 | auto t8 = Triangle(p6, p7, p8, material); 83 | 84 | // top face 85 | auto t9 = Triangle(p5, p1, p4, material); 86 | auto t10 = Triangle(p8, p4, p5, material); 87 | 88 | // bottom face 89 | auto t11 = Triangle(p2, p3, p6, material); 90 | auto t12 = Triangle(p3, p7, p6, material); 91 | 92 | Shape::add_triangle(new_shape, t1); 93 | Shape::add_triangle(new_shape, t2); 94 | Shape::add_triangle(new_shape, t3); 95 | Shape::add_triangle(new_shape, t4); 96 | Shape::add_triangle(new_shape, t5); 97 | Shape::add_triangle(new_shape, t6); 98 | Shape::add_triangle(new_shape, t7); 99 | Shape::add_triangle(new_shape, t8); 100 | Shape::add_triangle(new_shape, t9); 101 | Shape::add_triangle(new_shape, t10); 102 | Shape::add_triangle(new_shape, t11); 103 | Shape::add_triangle(new_shape, t12); 104 | 105 | rebuild(new_shape); 106 | 107 | return new_shape; 108 | } 109 | 110 | Shape Shape::make_icosphere(glm::vec3 pos, glm::vec3 orientation, float radius, size_t iterations, 111 | Material material) { 112 | Shape new_shape; 113 | Shape::set_pos(new_shape, pos); 114 | Shape::set_orientation(new_shape, orientation); 115 | Shape::set_scale(new_shape, glm::vec3(radius, radius, radius)); 116 | 117 | float t = 0.5 + glm::sqrt(5) / 2.f; 118 | 119 | triangles(new_shape).push_back(Triangle{{-1, t, 0}, {-t, 0, 1}, {0, 1, t}, material}); 120 | triangles(new_shape).push_back(Triangle{{-1, t, 0}, {0, 1, t}, {1, t, 0}, material}); 121 | triangles(new_shape).push_back(Triangle{{-1, t, 0}, {1, t, 0}, {0, 1, -t}, material}); 122 | triangles(new_shape).push_back(Triangle{{-1, t, 0}, {0, 1, -t}, {-t, 0, -1}, material}); 123 | triangles(new_shape).push_back(Triangle{{-1, t, 0}, {-t, 0, -1}, {-t, 0, 1}, material}); 124 | 125 | triangles(new_shape).push_back(Triangle{{1, t, 0}, {0, 1, t}, {t, 0, 1}, material}); 126 | triangles(new_shape).push_back(Triangle{{0, 1, t}, {-t, 0, 1}, {0, -1, t}, material}); 127 | triangles(new_shape).push_back(Triangle{{-t, 0, 1}, {-t, 0, -1}, {-1, -t, 0}, material}); 128 | triangles(new_shape).push_back(Triangle{{-t, 0, -1}, {0, 1, -t}, {0, -1, -t}, material}); 129 | triangles(new_shape).push_back(Triangle{{0, 1, -t}, {1, t, 0}, {t, 0, -1}, material}); 130 | 131 | triangles(new_shape).push_back(Triangle{{1, -t, 0}, {t, 0, 1}, {0, -1, t}, material}); 132 | triangles(new_shape).push_back(Triangle{{1, -t, 0}, {0, -1, t}, {-1, -t, 0}, material}); 133 | triangles(new_shape).push_back(Triangle{{1, -t, 0}, {-1, -t, 0}, {0, -1, -t}, material}); 134 | triangles(new_shape).push_back(Triangle{{1, -t, 0}, {0, -1, -t}, {t, 0, -1}, material}); 135 | triangles(new_shape).push_back(Triangle{{1, -t, 0}, {t, 0, -1}, {t, 0, 1}, material}); 136 | 137 | triangles(new_shape).push_back(Triangle{{0, -1, t}, {t, 0, 1}, {0, 1, t}, material}); 138 | triangles(new_shape).push_back(Triangle{{-1, -t, 0}, {0, -1, t}, {-t, 0, 1}, material}); 139 | triangles(new_shape).push_back(Triangle{{0, -1, -t}, {-1, -t, 0}, {-t, 0, -1}, material}); 140 | triangles(new_shape).push_back(Triangle{{t, 0, -1}, {0, -1, -t}, {0, 1, -t}, material}); 141 | triangles(new_shape).push_back(Triangle{{t, 0, 1}, {t, 0, -1}, {1, t, 0}, material}); 142 | 143 | for (size_t i = 0; i < iterations; i++) { 144 | std::vector new_triangles; 145 | for (const auto &tri : triangles(new_shape)) { 146 | glm::vec3 a = glm::normalize(get_middle_point(tri.m_v1, tri.m_v2)); 147 | glm::vec3 b = glm::normalize(get_middle_point(tri.m_v2, tri.m_v3)); 148 | glm::vec3 c = glm::normalize(get_middle_point(tri.m_v3, tri.m_v1)); 149 | 150 | new_triangles.push_back(Triangle{glm::normalize(tri.m_v1), a, c, material}); 151 | new_triangles.push_back(Triangle{glm::normalize(tri.m_v2), b, a, material}); 152 | new_triangles.push_back(Triangle{glm::normalize(tri.m_v3), c, b, material}); 153 | new_triangles.push_back(Triangle{a, b, c, material}); 154 | } 155 | 156 | new_shape.m_triangles = new_triangles; 157 | } 158 | 159 | rebuild(new_shape); 160 | 161 | return new_shape; 162 | } 163 | 164 | Shape Shape::make_plane(glm::vec3 pos, glm::vec3 orientation, glm::vec2 size, Material material) { 165 | Shape new_shape; 166 | Shape::set_pos(new_shape, pos); 167 | Shape::set_orientation(new_shape, orientation); 168 | Shape::set_scale(new_shape, glm::vec3(size.x, 0, size.y)); 169 | 170 | auto p1 = glm::vec3{-0.5f, 0, 0.5f}; 171 | auto p2 = glm::vec3{-0.5f, 0, -0.5f}; 172 | auto p3 = glm::vec3{0.5f, 0, -0.5f}; 173 | auto p4 = glm::vec3{0.5f, 0, 0.5f}; 174 | 175 | auto triangle_left = Triangle(p2, p1, p3, material); 176 | auto triangle_right = Triangle(p1, p4, p3, material); 177 | 178 | Shape::add_triangle(new_shape, triangle_left); 179 | Shape::add_triangle(new_shape, triangle_right); 180 | 181 | rebuild(new_shape); 182 | 183 | return new_shape; 184 | } 185 | 186 | void Shape::rebuild(Shape &shape) { 187 | glm::mat4 translation = glm::translate(Shape::pos(shape)); 188 | glm::mat4 rotation_x = glm::rotate(Shape::orientation(shape).x, glm::vec3{1, 0, 0}); 189 | glm::mat4 rotation_y = glm::rotate(Shape::orientation(shape).y, glm::vec3{0, 1, 0}); 190 | glm::mat4 rotation_z = glm::rotate(Shape::orientation(shape).z, glm::vec3{0, 0, 1}); 191 | glm::mat4 scale = glm::scale(Shape::scale(shape)); 192 | glm::mat4 model = translation * rotation_x * rotation_y * rotation_z * scale; 193 | 194 | for (auto &tri : triangles(shape)) { 195 | tri.m_v1 = glm::vec3(model * glm::vec4(tri.m_v1, 1)); 196 | tri.m_v2 = glm::vec3(model * glm::vec4(tri.m_v2, 1)); 197 | tri.m_v3 = glm::vec3(model * glm::vec4(tri.m_v3, 1)); 198 | tri.rebuild(); 199 | } 200 | 201 | auto &aabb = Shape::aabb(shape); 202 | AABB::reset(aabb); 203 | for (auto &tri : Shape::triangles(shape)) { 204 | AABB::extend(aabb, tri.m_v1); 205 | AABB::extend(aabb, tri.m_v2); 206 | AABB::extend(aabb, tri.m_v3); 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /trac0r/shape.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SHAPE_HPP 2 | #define SHAPE_HPP 3 | 4 | #include "aabb.hpp" 5 | #include "triangle.hpp" 6 | #include "material.hpp" 7 | 8 | #include 9 | #include 10 | 11 | namespace trac0r { 12 | 13 | class Shape { 14 | public: 15 | static const glm::vec3 pos(const Shape &shape); 16 | static void set_pos(Shape &shape, glm::vec3 new_pos); 17 | 18 | static const glm::vec3 orientation(const Shape &shape); 19 | static void set_orientation(Shape &shape, glm::vec3 new_orientation); 20 | 21 | static const glm::vec3 scale(const Shape &shape); 22 | static void set_scale(Shape &shape, glm::vec3 new_scale); 23 | 24 | static AABB &aabb(Shape &shape); 25 | static const AABB &aabb(const Shape &shape); 26 | 27 | static std::vector &triangles(Shape &shape); 28 | static const std::vector &triangles(const Shape &shape); 29 | 30 | static void add_triangle(Shape &shape, const Triangle triangle); 31 | 32 | static Shape make_box(glm::vec3 pos, glm::vec3 orientation, glm::vec3 size, Material material); 33 | 34 | static Shape make_icosphere(glm::vec3 pos, glm::vec3 orientation, float radius, 35 | size_t iterations, Material material); 36 | 37 | static Shape make_plane(glm::vec3 pos, glm::vec3 orientation, glm::vec2 size, 38 | Material material); 39 | 40 | protected: 41 | glm::vec3 m_pos; 42 | glm::vec3 m_orientation; 43 | glm::vec3 m_scale; 44 | AABB m_aabb; 45 | std::vector m_triangles; 46 | 47 | private: 48 | static void rebuild(Shape &shape); 49 | }; 50 | } 51 | 52 | #endif /* end of include guard: SHAPE_HPP */ 53 | -------------------------------------------------------------------------------- /trac0r/timer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef TIMER_HPP 2 | #define TIMER_HPP 3 | 4 | #include 5 | 6 | class Timer { 7 | public: 8 | Timer() { 9 | m_start = std::chrono::steady_clock::now(); 10 | } 11 | 12 | template 13 | double elapsed() { 14 | auto delta = peek(); 15 | reset(); 16 | return delta; 17 | } 18 | 19 | template 20 | double peek() { 21 | auto end = std::chrono::steady_clock::now(); 22 | auto delta = std::chrono::duration(end - m_start); 23 | return delta.count(); 24 | } 25 | 26 | void reset() { 27 | m_start = std::chrono::steady_clock::now(); 28 | } 29 | 30 | private: 31 | std::chrono::time_point m_start; 32 | }; 33 | 34 | #endif /* end of include guard: TIMER_HPP */ 35 | -------------------------------------------------------------------------------- /trac0r/triangle.hpp: -------------------------------------------------------------------------------- 1 | #ifndef TRIANGLE_HPP 2 | #define TRIANGLE_HPP 3 | 4 | #include "material.hpp" 5 | #include "random.hpp" 6 | 7 | #include 8 | #include 9 | 10 | namespace trac0r { 11 | 12 | struct Triangle { 13 | Triangle() { 14 | } 15 | 16 | Triangle(glm::vec3 v1, glm::vec3 v2, glm::vec3 v3, Material material) 17 | : m_v1(v1), m_v2(v2), m_v3(v3), m_material(material) { 18 | rebuild(); 19 | } 20 | 21 | // rebuild cached geometry data 22 | void rebuild() { 23 | m_normal = glm::triangleNormal(m_v1, m_v2, m_v3); 24 | 25 | m_centroid.x = m_v1.x + m_v2.x + m_v3.x; 26 | m_centroid.y = m_v1.y + m_v2.y + m_v3.y; 27 | m_centroid.z = m_v1.z + m_v2.z + m_v3.z; 28 | m_centroid /= 3; 29 | 30 | // Heron's formula 31 | auto a = glm::length(m_v1 - m_v2); 32 | auto b = glm::length(m_v2 - m_v3); 33 | auto c = glm::length(m_v3 - m_v1); 34 | auto s = (a + b + c) / 2.f; 35 | m_area = glm::sqrt(s * (s - a) * (s - b) * (s - c)); 36 | } 37 | 38 | static inline glm::vec3 random_point(const Triangle &triangle) { 39 | return triangle.m_v1 + rand_range(0.f, 1.f) * (triangle.m_v2 - triangle.m_v1) + 40 | rand_range(0.f, 1.f) * (triangle.m_v3 - triangle.m_v1); 41 | } 42 | 43 | glm::vec3 m_v1; 44 | glm::vec3 m_v2; 45 | glm::vec3 m_v3; 46 | Material m_material; 47 | glm::vec3 m_normal; 48 | glm::vec3 m_centroid; 49 | float m_area; 50 | }; 51 | } 52 | 53 | #endif /* end of include guard: TRIANGLE_HPP */ 54 | -------------------------------------------------------------------------------- /trac0r/utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_HPP 2 | #define UTILS_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #ifdef OPENCL 13 | #include 14 | #endif 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | namespace trac0r { 22 | 23 | /** 24 | * @brief Calculate Mean Square Error of two frames 25 | * 26 | * @param frame1 The first frame to compare against 27 | * @param frame2 The second frame to compare against the first one 28 | * 29 | * @return Mean square error as a scalar 30 | */ 31 | inline float mse(std::vector frame1, std::vector frame2) { 32 | glm::vec4 error{0.f}; 33 | for (size_t n = 0; n < frame1.size(); n++) { 34 | glm::vec4 difference = frame1[n] - frame2[n]; 35 | error += difference * difference; 36 | } 37 | 38 | // We'll use only RGB channels because A is always 1.f and thusly not meaningful 39 | float mean = (error.r + error.g + error.b) / 3.f; 40 | return mean / frame1.size(); 41 | } 42 | 43 | /** 44 | * @brief Peak signal-to-noise ratio 45 | * 46 | * @param frame1 The first frame to compare against 47 | * @param frame2 The second frame to compare against the first one 48 | * 49 | * @return 50 | */ 51 | inline float psnr(std::vector frame1, std::vector frame2) { 52 | return 10.f * glm::log(1.f / mse(frame1, frame2), 10.f); 53 | } 54 | 55 | /** 56 | * @brief Returns a vector orthogonal to a given vector in 3D space. 57 | * 58 | * @param v The vector to find an orthogonal vector for 59 | * 60 | * @return A vector orthogonal to the given vector 61 | */ 62 | inline glm::vec3 ortho(glm::vec3 v) { 63 | // Awesome branchless function for finding an orthogonal vector in 3D space by 64 | // http://lolengine.net/blog/2013/09/21/picking-orthogonal-vector-combing-coconuts 65 | // 66 | // Their "boring" branching is commented here for completeness: 67 | // return glm::abs(v.x) > glm::abs(v.z) ? glm::vec3(-v.y, v.x, 0.0) : glm::vec3(0.0, -v.z, v.y); 68 | 69 | float k = glm::fract(glm::abs(v.x) + 0.5f); 70 | return glm::vec3(-v.y, v.x - k * v.z, k * v.y); 71 | } 72 | 73 | inline glm::vec3 get_middle_point(glm::vec3 v1, glm::vec3 v2) { 74 | return (v1 - v2) / 2.f + v2; 75 | } 76 | 77 | #ifdef OPENCL 78 | inline std::string opencl_error_string(cl_int error) { 79 | switch (error) { 80 | // run-time and JIT compiler errors 81 | case 0: 82 | return "CL_SUCCESS"; 83 | case -1: 84 | return "CL_DEVICE_NOT_FOUND"; 85 | case -2: 86 | return "CL_DEVICE_NOT_AVAILABLE"; 87 | case -3: 88 | return "CL_COMPILER_NOT_AVAILABLE"; 89 | case -4: 90 | return "CL_MEM_OBJECT_ALLOCATION_FAILURE"; 91 | case -5: 92 | return "CL_OUT_OF_RESOURCES"; 93 | case -6: 94 | return "CL_OUT_OF_HOST_MEMORY"; 95 | case -7: 96 | return "CL_PROFILING_INFO_NOT_AVAILABLE"; 97 | case -8: 98 | return "CL_MEM_COPY_OVERLAP"; 99 | case -9: 100 | return "CL_IMAGE_FORMAT_MISMATCH"; 101 | case -10: 102 | return "CL_IMAGE_FORMAT_NOT_SUPPORTED"; 103 | case -11: 104 | return "CL_BUILD_PROGRAM_FAILURE"; 105 | case -12: 106 | return "CL_MAP_FAILURE"; 107 | case -13: 108 | return "CL_MISALIGNED_SUB_BUFFER_OFFSET"; 109 | case -14: 110 | return "CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST"; 111 | case -15: 112 | return "CL_COMPILE_PROGRAM_FAILURE"; 113 | case -16: 114 | return "CL_LINKER_NOT_AVAILABLE"; 115 | case -17: 116 | return "CL_LINK_PROGRAM_FAILURE"; 117 | case -18: 118 | return "CL_DEVICE_PARTITION_FAILED"; 119 | case -19: 120 | return "CL_KERNEL_ARG_INFO_NOT_AVAILABLE"; 121 | 122 | // compile-time errors 123 | case -30: 124 | return "CL_INVALID_VALUE"; 125 | case -31: 126 | return "CL_INVALID_DEVICE_TYPE"; 127 | case -32: 128 | return "CL_INVALID_PLATFORM"; 129 | case -33: 130 | return "CL_INVALID_DEVICE"; 131 | case -34: 132 | return "CL_INVALID_CONTEXT"; 133 | case -35: 134 | return "CL_INVALID_QUEUE_PROPERTIES"; 135 | case -36: 136 | return "CL_INVALID_COMMAND_QUEUE"; 137 | case -37: 138 | return "CL_INVALID_HOST_PTR"; 139 | case -38: 140 | return "CL_INVALID_MEM_OBJECT"; 141 | case -39: 142 | return "CL_INVALID_IMAGE_FORMAT_DESCRIPTOR"; 143 | case -40: 144 | return "CL_INVALID_IMAGE_SIZE"; 145 | case -41: 146 | return "CL_INVALID_SAMPLER"; 147 | case -42: 148 | return "CL_INVALID_BINARY"; 149 | case -43: 150 | return "CL_INVALID_BUILD_OPTIONS"; 151 | case -44: 152 | return "CL_INVALID_PROGRAM"; 153 | case -45: 154 | return "CL_INVALID_PROGRAM_EXECUTABLE"; 155 | case -46: 156 | return "CL_INVALID_KERNEL_NAME"; 157 | case -47: 158 | return "CL_INVALID_KERNEL_DEFINITION"; 159 | case -48: 160 | return "CL_INVALID_KERNEL"; 161 | case -49: 162 | return "CL_INVALID_ARG_INDEX"; 163 | case -50: 164 | return "CL_INVALID_ARG_VALUE"; 165 | case -51: 166 | return "CL_INVALID_ARG_SIZE"; 167 | case -52: 168 | return "CL_INVALID_KERNEL_ARGS"; 169 | case -53: 170 | return "CL_INVALID_WORK_DIMENSION"; 171 | case -54: 172 | return "CL_INVALID_WORK_GROUP_SIZE"; 173 | case -55: 174 | return "CL_INVALID_WORK_ITEM_SIZE"; 175 | case -56: 176 | return "CL_INVALID_GLOBAL_OFFSET"; 177 | case -57: 178 | return "CL_INVALID_EVENT_WAIT_LIST"; 179 | case -58: 180 | return "CL_INVALID_EVENT"; 181 | case -59: 182 | return "CL_INVALID_OPERATION"; 183 | case -60: 184 | return "CL_INVALID_GL_OBJECT"; 185 | case -61: 186 | return "CL_INVALID_BUFFER_SIZE"; 187 | case -62: 188 | return "CL_INVALID_MIP_LEVEL"; 189 | case -63: 190 | return "CL_INVALID_GLOBAL_WORK_SIZE"; 191 | case -64: 192 | return "CL_INVALID_PROPERTY"; 193 | case -65: 194 | return "CL_INVALID_IMAGE_DESCRIPTOR"; 195 | case -66: 196 | return "CL_INVALID_COMPILER_OPTIONS"; 197 | case -67: 198 | return "CL_INVALID_LINKER_OPTIONS"; 199 | case -68: 200 | return "CL_INVALID_DEVICE_PARTITION_COUNT"; 201 | 202 | // extension errors 203 | case -1000: 204 | return "CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR"; 205 | case -1001: 206 | return "CL_PLATFORM_NOT_FOUND_KHR"; 207 | case -1002: 208 | return "CL_INVALID_D3D10_DEVICE_KHR"; 209 | case -1003: 210 | return "CL_INVALID_D3D10_RESOURCE_KHR"; 211 | case -1004: 212 | return "CL_D3D10_RESOURCE_ALREADY_ACQUIRED_KHR"; 213 | case -1005: 214 | return "CL_D3D10_RESOURCE_NOT_ACQUIRED_KHR"; 215 | default: 216 | return "Unknown OpenCL error"; 217 | } 218 | } 219 | #endif 220 | 221 | inline SDL_Texture *make_text(SDL_Renderer *renderer, TTF_Font *font, std::string text, 222 | const SDL_Color &color) { 223 | auto text_surface = TTF_RenderText_Blended(font, text.c_str(), color); 224 | auto text_tex = SDL_CreateTextureFromSurface(renderer, text_surface); 225 | SDL_FreeSurface(text_surface); 226 | 227 | return text_tex; 228 | } 229 | 230 | inline void render_text(SDL_Renderer *renderer, SDL_Texture *texture, int pos_x, int pos_y) { 231 | int tex_width; 232 | int tex_height; 233 | 234 | SDL_QueryTexture(texture, 0, 0, &tex_width, &tex_height); 235 | SDL_Rect rect{pos_x, pos_y, tex_width, tex_height}; 236 | SDL_RenderCopy(renderer, texture, 0, &rect); 237 | } 238 | 239 | inline uint32_t pack_color_argb(uint8_t a, uint8_t r, uint8_t g, uint8_t b) { 240 | uint32_t new_color = a << 24 | r << 16 | g << 8 | b; 241 | return new_color; 242 | } 243 | 244 | inline uint32_t pack_color_argb(glm::i8vec4 color) { 245 | uint32_t new_color = color.a << 24 | color.r << 16 | color.g << 8 | color.b; 246 | return new_color; 247 | } 248 | 249 | inline uint32_t pack_color_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { 250 | uint32_t packed_color = r << 24 | g << 16 | b << 8 | a; 251 | return packed_color; 252 | } 253 | 254 | inline uint32_t pack_color_rgba(glm::i8vec4 color) { 255 | uint32_t packed_color = color.r << 24 | color.g << 16 | color.b << 8 | color.a; 256 | return packed_color; 257 | } 258 | 259 | inline uint32_t pack_color_rgba(glm::vec4 color) { 260 | uint32_t packed_color = 261 | static_cast(glm::round(glm::clamp(color.r, 0.f, 1.f) * 255)) << 24 | 262 | static_cast(glm::round(glm::clamp(color.g, 0.f, 1.f) * 255)) << 16 | 263 | static_cast(glm::round(glm::clamp(color.b, 0.f, 1.f) * 255)) << 8 | 264 | static_cast(glm::round(glm::clamp(color.a, 0.f, 1.f) * 255)); 265 | return packed_color; 266 | } 267 | 268 | inline uint32_t pack_color_argb(glm::vec4 color) { 269 | uint32_t packed_color = 270 | static_cast(glm::round(glm::clamp(color.a, 0.f, 1.f) * 255)) << 24 | 271 | static_cast(glm::round(glm::clamp(color.r, 0.f, 1.f) * 255)) << 16 | 272 | static_cast(glm::round(glm::clamp(color.g, 0.f, 1.f) * 255)) << 8 | 273 | static_cast(glm::round(glm::clamp(color.b, 0.f, 1.f) * 255)); 274 | return packed_color; 275 | } 276 | 277 | inline glm::i8vec4 unpack_color_rgba_to_i8vec4(uint32_t packed_color_rgba) { 278 | glm::i8vec4 unpacked_color; 279 | unpacked_color.r = packed_color_rgba >> 24 & 0xFF; 280 | unpacked_color.g = packed_color_rgba >> 16 & 0xFF; 281 | unpacked_color.b = packed_color_rgba >> 8 & 0xFF; 282 | unpacked_color.a = packed_color_rgba & 0xFF; 283 | return unpacked_color; 284 | } 285 | 286 | inline glm::i8vec4 unpack_color_argb_to_i8vec4(uint32_t packed_color_argb) { 287 | glm::i8vec4 unpacked_color; 288 | unpacked_color.a = packed_color_argb >> 24 & 0xFF; 289 | unpacked_color.r = packed_color_argb >> 16 & 0xFF; 290 | unpacked_color.g = packed_color_argb >> 8 & 0xFF; 291 | unpacked_color.b = packed_color_argb & 0xFF; 292 | return unpacked_color; 293 | } 294 | 295 | inline glm::vec4 unpack_color_rgbb_to_vec4(uint32_t packed_color_rgba) { 296 | glm::i8vec4 unpacked_color; 297 | unpacked_color.r = (packed_color_rgba >> 24 & 0xFF) / 255.f; 298 | unpacked_color.g = (packed_color_rgba >> 16 & 0xFF) / 255.f; 299 | unpacked_color.b = (packed_color_rgba >> 8 & 0xFF) / 255.f; 300 | unpacked_color.a = (packed_color_rgba & 0xFF) / 255.f; 301 | return unpacked_color; 302 | } 303 | 304 | inline glm::vec4 unpack_color_argb_to_vec4(uint32_t packed_color_argb) { 305 | glm::vec4 unpacked_color; 306 | unpacked_color.a = (packed_color_argb >> 24 & 0xFF) / 255.f; 307 | unpacked_color.r = (packed_color_argb >> 16 & 0xFF) / 255.f; 308 | unpacked_color.g = (packed_color_argb >> 8 & 0xFF) / 255.f; 309 | unpacked_color.b = (packed_color_argb & 0xFF) / 255.f; 310 | return unpacked_color; 311 | } 312 | } 313 | 314 | #endif /* end of include guard: UTILS_HPP */ 315 | -------------------------------------------------------------------------------- /viewer/main.cpp: -------------------------------------------------------------------------------- 1 | #ifdef __EMSCRIPTEN__ 2 | #include 3 | #endif 4 | 5 | #include "viewer.hpp" 6 | 7 | void mainloop(void *args) { 8 | auto viewer = static_cast(args); 9 | viewer->mainloop(); 10 | } 11 | 12 | int main(int argc, char *argv[]) { 13 | Viewer viewer; 14 | 15 | if (viewer.init(argc, argv) != 0) 16 | return 1; 17 | 18 | #ifdef __EMSCRIPTEN__ 19 | emscripten_set_main_loop_arg(mainloop, &viewer, 0, true); 20 | #else 21 | while (viewer.is_running()) { 22 | mainloop(&viewer); 23 | } 24 | #endif 25 | 26 | return 0; 27 | } 28 | -------------------------------------------------------------------------------- /viewer/viewer.cpp: -------------------------------------------------------------------------------- 1 | #include "viewer.hpp" 2 | 3 | #include "trac0r/shape.hpp" 4 | #include "trac0r/utils.hpp" 5 | #include "trac0r/flat_structure.hpp" 6 | #include "trac0r/filtering.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | #ifdef OPENCL 19 | #include 20 | #endif 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | using Camera = trac0r::Camera; 27 | using Scene = trac0r::Scene; 28 | using FlatStructure = trac0r::FlatStructure; 29 | using AABB = trac0r::AABB; 30 | using Shape = trac0r::Shape; 31 | 32 | Viewer::~Viewer() { 33 | TTF_CloseFont(m_font); 34 | TTF_Quit(); 35 | SDL_DestroyTexture(m_render_tex); 36 | SDL_DestroyRenderer(m_render); 37 | SDL_DestroyWindow(m_window); 38 | IMG_Quit(); 39 | SDL_Quit(); 40 | } 41 | 42 | int Viewer::init(int argc, char *argv[]) { 43 | fmt::print("Start init\n"); 44 | 45 | for (auto i = 0; i < argc; i++) { 46 | std::string argv_str(argv[i]); 47 | if (argv_str == "-b1") { 48 | m_benchmark_mode = 1; 49 | m_max_frames = 500; 50 | } else if (argv_str == "-b2") { 51 | m_benchmark_mode = 2; 52 | m_max_frames = 250; 53 | } else if (argv_str == "-b3") { 54 | m_benchmark_mode = 3; 55 | m_max_frames = 125; 56 | } else if (argv_str == "-b4") { 57 | m_benchmark_mode = 4; 58 | m_max_frames = 50; 59 | } else if (argv_str == "-b5") { 60 | m_benchmark_mode = 5; 61 | m_max_frames = 25; 62 | } 63 | 64 | // Save image after n frames and quit 65 | auto found = argv_str.find("-f"); 66 | if (found != std::string::npos) { 67 | m_benchmark_mode = -1; 68 | m_max_frames = std::stoi(argv_str.substr(found + 2, argv_str.length())); 69 | } 70 | } 71 | 72 | if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) != 0) { 73 | std::cerr << "SDL_Init error: " << SDL_GetError() << std::endl; 74 | return 1; 75 | } 76 | 77 | m_window = 78 | SDL_CreateWindow("trac0r", 100, 100, m_screen_width, m_screen_height, SDL_WINDOW_SHOWN); 79 | if (m_window == nullptr) { 80 | std::cerr << "SDL_CreateWindow error: " << SDL_GetError() << std::endl; 81 | SDL_Quit(); 82 | return 1; 83 | } 84 | 85 | // m_render = SDL_CreateRenderer(m_window, -1, SDL_RENDERER_ACCELERATED | 86 | // SDL_RENDERER_PRESENTVSYNC); 87 | m_render = SDL_CreateRenderer(m_window, -1, SDL_RENDERER_ACCELERATED); 88 | m_render_tex = SDL_CreateTexture(m_render, SDL_PIXELFORMAT_ARGB8888, 89 | SDL_TEXTUREACCESS_STREAMING, m_screen_width, m_screen_height); 90 | m_pixels.resize(m_screen_width * m_screen_height, 0); 91 | 92 | if (m_render == nullptr) { 93 | SDL_DestroyWindow(m_window); 94 | std::cerr << "SDL_CreateRenderer error: " << SDL_GetError() << std::endl; 95 | SDL_Quit(); 96 | return 1; 97 | } 98 | 99 | if (TTF_Init() != 0) { 100 | std::cerr << "SDL_ttf could not initialize! SDL_ttf error: " << SDL_GetError() << std::endl; 101 | return 1; 102 | } 103 | 104 | auto font_path = "res/DejaVuSansMono-Bold.ttf"; 105 | m_font = TTF_OpenFont(font_path, 14); 106 | if (m_font == nullptr) { 107 | std::cerr << "SDL_ttf could not open '" << font_path << "'" << std::endl; 108 | return 1; 109 | } 110 | 111 | // Setup scene 112 | setup_scene(); 113 | m_renderer = std::make_unique(m_screen_width, m_screen_height, m_camera, 114 | m_scene, m_print_perf); 115 | m_renderer->print_sysinfo(); 116 | 117 | fmt::print("Finish init\n"); 118 | 119 | return 0; 120 | } 121 | 122 | void Viewer::setup_scene() { 123 | trac0r::Material emissive{1, {1.f, 0.93f, 0.85f}, 0.f, 1.f, 15.f}; 124 | trac0r::Material default_material{2, {0.740063, 0.742313, 0.733934}}; 125 | trac0r::Material diffuse_red{2, {0.366046, 0.0371827, 0.0416385}}; 126 | trac0r::Material diffuse_green{2, {0.162928, 0.408903, 0.0833759}}; 127 | // trac0r::Material glass{3, {0.5f, 0.5f, 0.9f}, 0.0f, 1.51714f}; 128 | // trac0r::Material glossy{4, {1.f, 1.f, 1.f}, 0.09f}; 129 | auto wall_left = trac0r::Shape::make_plane({-0.5f, 0.4f, 0}, {0, 0, -glm::half_pi()}, 130 | {1, 1}, diffuse_red); 131 | auto wall_right = trac0r::Shape::make_plane({0.5f, 0.4f, 0}, {0, 0, glm::half_pi()}, 132 | {1, 1}, diffuse_green); 133 | auto wall_back = trac0r::Shape::make_plane({0, 0.4f, 0.5}, {-glm::half_pi(), 0, 0}, 134 | {1, 1}, default_material); 135 | auto wall_top = 136 | trac0r::Shape::make_plane({0, 0.9f, 0}, {glm::pi(), 0, 0}, {1, 1}, default_material); 137 | auto wall_bottom = 138 | trac0r::Shape::make_plane({0, -0.1f, 0}, {0, 0, 0}, {1, 1}, default_material); 139 | auto lamp = trac0r::Shape::make_plane({0, 0.85f, -0.1}, {0, 0, 0}, {0.4, 0.4}, emissive); 140 | auto box1 = trac0r::Shape::make_box({0.3f, 0.1f, 0.1f}, {0, 0.6f, 0}, {0.2f, 0.5f, 0.2f}, 141 | default_material); 142 | auto box2 = 143 | trac0r::Shape::make_box({-0.2f, 0.15f, 0.1f}, {0, -0.5f, 0}, {0.3f, 0.6f, 0.3f}, default_material); 144 | if (m_benchmark_mode > 0) { 145 | auto sphere1 = trac0r::Shape::make_icosphere({0.f, 0.1f, -0.3f}, {0, 0, 0}, 0.15f, 146 | m_benchmark_mode - 1, default_material); 147 | Scene::add_shape(m_scene, sphere1); 148 | } else { 149 | auto sphere1 = 150 | trac0r::Shape::make_icosphere({0.f, 0.1f, -0.3f}, {0, 0, 0}, 0.15f, 1, default_material); 151 | auto sphere2 = 152 | trac0r::Shape::make_icosphere({0.3f, 0.45f, 0.1f}, {0, 0, 0}, 0.15f, 1, default_material); 153 | Scene::add_shape(m_scene, sphere1); 154 | Scene::add_shape(m_scene, sphere2); 155 | } 156 | 157 | Scene::add_shape(m_scene, wall_left); 158 | Scene::add_shape(m_scene, wall_right); 159 | Scene::add_shape(m_scene, wall_back); 160 | Scene::add_shape(m_scene, wall_top); 161 | Scene::add_shape(m_scene, wall_bottom); 162 | Scene::add_shape(m_scene, lamp); 163 | Scene::add_shape(m_scene, box1); 164 | Scene::add_shape(m_scene, box2); 165 | 166 | glm::vec3 cam_pos = {0, 0.31, -1.2}; 167 | glm::vec3 cam_dir = {0, 0, 1}; 168 | glm::vec3 world_up = {0, 1, 0}; 169 | 170 | m_camera = 171 | Camera(cam_pos, cam_dir, world_up, 90.f, 0.001, 100.f, m_screen_width, m_screen_height); 172 | } 173 | 174 | void Viewer::mainloop() { 175 | Timer timer; 176 | Timer total; 177 | 178 | if (m_print_perf) 179 | fmt::print("Rendering frame {}\n", m_frame); 180 | m_scene_changed = false; 181 | m_frame++; 182 | 183 | int current_time = SDL_GetTicks(); 184 | double dt = (current_time - m_last_frame_time) / 1000.0; 185 | m_last_frame_time = current_time; 186 | auto fps = 1. / dt; 187 | 188 | // Input 189 | SDL_Event e; 190 | while (SDL_PollEvent(&e)) { 191 | if (e.type == SDL_QUIT) { 192 | shutdown(); 193 | } 194 | 195 | if (e.type == SDL_KEYDOWN) { 196 | if (e.key.keysym.sym == SDLK_ESCAPE) { 197 | shutdown(); 198 | } 199 | if (e.key.keysym.sym == SDLK_b) { 200 | m_debug = !m_debug; 201 | } 202 | if (e.key.keysym.sym == SDLK_n) { 203 | m_print_perf = !m_print_perf; 204 | } 205 | if (e.key.keysym.sym == SDLK_1) { 206 | m_stride_x = 1; 207 | m_stride_y = 1; 208 | m_scene_changed = true; 209 | } 210 | if (e.key.keysym.sym == SDLK_2) { 211 | m_stride_x = 2; 212 | m_stride_y = 2; 213 | m_scene_changed = true; 214 | } 215 | if (e.key.keysym.sym == SDLK_3) { 216 | m_stride_x = 4; 217 | m_stride_y = 4; 218 | m_scene_changed = true; 219 | } 220 | } 221 | 222 | if (e.type == SDL_MOUSEBUTTONDOWN) { 223 | if (e.button.button == SDL_BUTTON_RIGHT) { 224 | if (m_look_mode) { 225 | m_look_mode = false; 226 | SDL_SetRelativeMouseMode(SDL_FALSE); 227 | } else if (!m_look_mode) { 228 | m_look_mode = true; 229 | SDL_SetRelativeMouseMode(SDL_TRUE); 230 | } 231 | } 232 | } 233 | } 234 | 235 | float cam_speed = .8f * dt; 236 | 237 | // Left/right 238 | const uint8_t *keystates = SDL_GetKeyboardState(0); 239 | if (keystates[SDL_SCANCODE_A]) { 240 | m_scene_changed = true; 241 | Camera::set_pos(m_camera, Camera::pos(m_camera) - Camera::right(m_camera) * cam_speed); 242 | } else if (keystates[SDL_SCANCODE_D]) { 243 | Camera::set_pos(m_camera, Camera::pos(m_camera) + Camera::right(m_camera) * cam_speed); 244 | m_scene_changed = true; 245 | } 246 | 247 | // Up/down 248 | if (keystates[SDL_SCANCODE_SPACE]) { 249 | m_scene_changed = true; 250 | Camera::set_pos(m_camera, Camera::pos(m_camera) + glm::vec3{0, 1, 0} * cam_speed); 251 | } else if (keystates[SDL_SCANCODE_LCTRL]) { 252 | m_scene_changed = true; 253 | Camera::set_pos(m_camera, Camera::pos(m_camera) - glm::vec3{0, 1, 0} * cam_speed); 254 | } 255 | 256 | // Roll 257 | if (keystates[SDL_SCANCODE_Q]) { 258 | m_scene_changed = true; 259 | Camera::set_world_up(m_camera, glm::rotateZ(Camera::up(m_camera), 0.1f)); 260 | } else if (keystates[SDL_SCANCODE_E]) { 261 | m_scene_changed = true; 262 | Camera::set_world_up(m_camera, glm::rotateZ(Camera::up(m_camera), -0.1f)); 263 | } 264 | 265 | // Front/back 266 | if (keystates[SDL_SCANCODE_W]) { 267 | m_scene_changed = true; 268 | Camera::set_pos(m_camera, Camera::pos(m_camera) + Camera::dir(m_camera) * cam_speed); 269 | } else if (keystates[SDL_SCANCODE_S]) { 270 | m_scene_changed = true; 271 | Camera::set_pos(m_camera, Camera::pos(m_camera) - Camera::dir(m_camera) * cam_speed); 272 | } 273 | 274 | // Rendering here 275 | int width; 276 | int height; 277 | SDL_GetWindowSize(m_window, &width, &height); 278 | 279 | glm::ivec2 mouse_pos; 280 | if (m_look_mode) { 281 | // Yaw 282 | SDL_GetRelativeMouseState(&(mouse_pos.x), &(mouse_pos.y)); 283 | if (mouse_pos.x != 0) { 284 | m_scene_changed = true; 285 | Camera::set_dir(m_camera, glm::rotateY(Camera::dir(m_camera), mouse_pos.x * 0.001f)); 286 | } 287 | 288 | // Pitch 289 | if (mouse_pos.y != 0) { 290 | m_scene_changed = true; 291 | Camera::set_dir(m_camera, 292 | glm::rotate(Camera::dir(m_camera), mouse_pos.y * 0.001f, 293 | glm::cross(Camera::up(m_camera), Camera::dir(m_camera)))); 294 | } 295 | 296 | } else if (!m_look_mode) { 297 | SDL_GetMouseState(&(mouse_pos.x), &(mouse_pos.y)); 298 | } 299 | 300 | if (m_scene_changed) { 301 | m_samples_accumulated = 0; 302 | } 303 | 304 | if (m_print_perf) 305 | fmt::print(" {:<15} {:>10.3f} ms\n", "Input handling", timer.elapsed()); 306 | 307 | Scene::rebuild(m_scene); 308 | 309 | if (m_print_perf) 310 | fmt::print(" {:<15} {:=10.3f} ms\n", "Scene rebuild", timer.elapsed()); 311 | 312 | const auto luminance = m_renderer->render(m_scene_changed, m_stride_x, m_stride_y); 313 | if (m_print_perf) { 314 | m_renderer->print_last_frame_timings(); 315 | } 316 | 317 | m_samples_accumulated += 1; 318 | 319 | timer.reset(); 320 | 321 | // This striding is just for speeding up 322 | // We're basically drawing really big pixels here 323 | #pragma omp parallel for collapse(2) schedule(dynamic, 1024) 324 | for (auto x = 0; x < width; x += m_stride_x) { 325 | for (auto y = 0; y < height; y += m_stride_y) { 326 | glm::vec4 color = luminance[y * width + x] / static_cast(m_samples_accumulated); 327 | for (auto u = 0; u < m_stride_x; u++) { 328 | for (auto v = 0; v < m_stride_y; v++) { 329 | m_pixels[(y + v) * width + (x + u)] = trac0r::pack_color_argb(color); 330 | } 331 | } 332 | } 333 | } 334 | 335 | if (m_print_perf) 336 | fmt::print(" {:<15} {:>10.3f} ms\n", "Pixel transfer", timer.elapsed()); 337 | 338 | // std::vector m_pixels_filtered; 339 | // m_pixels_filtered.resize(m_screen_width * m_screen_height, 0); 340 | // trac0r::box_filter(m_pixels, width, height, m_pixels_filtered); 341 | // m_pixels = m_pixels_filtered; 342 | // 343 | // if (m_print_perf) 344 | // fmt::print(" {:<15} {:>10.3f} ms\n", "Image filtering", timer.elapsed()); 345 | 346 | SDL_RenderClear(m_render); 347 | SDL_UpdateTexture(m_render_tex, 0, m_pixels.data(), width * sizeof(uint32_t)); 348 | SDL_RenderCopy(m_render, m_render_tex, 0, 0); 349 | 350 | if (m_debug) { 351 | // Lots of debug info 352 | glm::vec2 mouse_rel_pos = 353 | Camera::screenspace_to_camspace(m_camera, mouse_pos.x, mouse_pos.y); 354 | glm::vec3 mouse_canvas_pos = Camera::camspace_to_worldspace(m_camera, mouse_rel_pos); 355 | 356 | auto fps_debug_info = "FPS: " + std::to_string(int(fps)); 357 | auto scene_changing_info = "Samples : " + std::to_string(m_samples_accumulated); 358 | scene_changing_info += " Scene Changing: " + std::to_string(m_scene_changed); 359 | auto cam_look_debug_info = "Cam Look Mode: " + std::to_string(m_look_mode); 360 | auto cam_pos_debug_info = "Cam Pos: " + glm::to_string(Camera::pos(m_camera)); 361 | auto cam_dir_debug_info = "Cam Dir: " + glm::to_string(Camera::dir(m_camera)); 362 | auto cam_up_debug_info = "Cam Up: " + glm::to_string(Camera::up(m_camera)); 363 | auto cam_fov_debug_info = 364 | "Cam FOV (H/V): " + 365 | std::to_string(int(glm::degrees(Camera::horizontal_fov(m_camera)))) + "/"; 366 | cam_fov_debug_info += std::to_string(int(glm::degrees(Camera::vertical_fov(m_camera)))); 367 | auto cam_canvas_center_pos_info = 368 | "Cam Canvas Center: " + glm::to_string(Camera::canvas_center_pos(m_camera)); 369 | auto mouse_pos_screen_info = "Mouse Pos Screen Space: " + glm::to_string(mouse_pos); 370 | auto mouse_pos_relative_info = "Mouse Pos Cam Space: " + glm::to_string(mouse_rel_pos); 371 | auto mouse_pos_canvas_info = 372 | "Mouse Pos Canvas World Space: " + glm::to_string(mouse_canvas_pos); 373 | 374 | auto fps_debug_tex = 375 | trac0r::make_text(m_render, m_font, fps_debug_info, {200, 100, 100, 200}); 376 | auto scene_changing_tex = 377 | trac0r::make_text(m_render, m_font, scene_changing_info, {200, 100, 100, 200}); 378 | auto cam_look_debug_tex = 379 | trac0r::make_text(m_render, m_font, cam_look_debug_info, {200, 100, 100, 200}); 380 | auto cam_pos_debug_tex = 381 | trac0r::make_text(m_render, m_font, cam_pos_debug_info, {200, 100, 100, 200}); 382 | auto cam_dir_debug_tex = 383 | trac0r::make_text(m_render, m_font, cam_dir_debug_info, {200, 100, 100, 200}); 384 | auto cam_up_debug_tex = 385 | trac0r::make_text(m_render, m_font, cam_up_debug_info, {200, 100, 100, 200}); 386 | auto cam_fov_debug_tex = 387 | trac0r::make_text(m_render, m_font, cam_fov_debug_info, {200, 100, 100, 200}); 388 | auto cam_canvas_center_pos_tex = 389 | trac0r::make_text(m_render, m_font, cam_canvas_center_pos_info, {200, 100, 100, 200}); 390 | auto mouse_pos_screen_tex = 391 | trac0r::make_text(m_render, m_font, mouse_pos_screen_info, {200, 100, 100, 200}); 392 | auto mouse_pos_relative_tex = 393 | trac0r::make_text(m_render, m_font, mouse_pos_relative_info, {200, 100, 100, 200}); 394 | auto mouse_pos_canvas_tex = 395 | trac0r::make_text(m_render, m_font, mouse_pos_canvas_info, {200, 100, 100, 200}); 396 | 397 | trac0r::render_text(m_render, fps_debug_tex, 10, 10); 398 | trac0r::render_text(m_render, scene_changing_tex, 10, 25); 399 | trac0r::render_text(m_render, cam_look_debug_tex, 10, 40); 400 | trac0r::render_text(m_render, cam_pos_debug_tex, 10, 55); 401 | trac0r::render_text(m_render, cam_dir_debug_tex, 10, 70); 402 | trac0r::render_text(m_render, cam_up_debug_tex, 10, 85); 403 | trac0r::render_text(m_render, cam_fov_debug_tex, 10, 100); 404 | trac0r::render_text(m_render, cam_canvas_center_pos_tex, 10, 115); 405 | trac0r::render_text(m_render, mouse_pos_screen_tex, 10, 130); 406 | trac0r::render_text(m_render, mouse_pos_relative_tex, 10, 145); 407 | trac0r::render_text(m_render, mouse_pos_canvas_tex, 10, 160); 408 | 409 | // Let's draw some debug to the display (such as AABBs) 410 | if (m_debug) { 411 | auto &accel_struct = Scene::accel_struct(m_scene); 412 | for (auto &shape : FlatStructure::shapes(accel_struct)) { 413 | auto &aabb = Shape::aabb(shape); 414 | const auto &verts = AABB::vertices(aabb); 415 | std::array pairs; 416 | pairs[0] = {0, 1}; 417 | pairs[1] = {1, 3}; 418 | pairs[2] = {2, 3}; 419 | pairs[3] = {0, 2}; 420 | pairs[4] = {4, 5}; 421 | pairs[5] = {5, 7}; 422 | pairs[6] = {6, 7}; 423 | pairs[7] = {4, 6}; 424 | pairs[8] = {0, 4}; 425 | pairs[9] = {1, 5}; 426 | pairs[10] = {2, 6}; 427 | pairs[11] = {3, 7}; 428 | for (auto pair : pairs) { 429 | auto ws1 = Camera::worldpoint_to_worldspace(m_camera, verts[pair[0]]); 430 | auto ss1 = glm::i32vec2(0); 431 | if (ws1 != glm::vec3(0)) { 432 | auto cs1 = Camera::worldspace_to_camspace(m_camera, ws1); 433 | ss1 = Camera::camspace_to_screenspace(m_camera, cs1); 434 | } 435 | auto ws2 = Camera::worldpoint_to_worldspace(m_camera, verts[pair[1]]); 436 | auto ss2 = glm::i32vec2(0); 437 | if (ws2 != glm::vec3(0)) { 438 | auto cs2 = Camera::worldspace_to_camspace(m_camera, ws2); 439 | ss2 = Camera::camspace_to_screenspace(m_camera, cs2); 440 | } 441 | if (ss1 != glm::i32vec2(0) && ss2 != glm::i32vec2(0)) { 442 | aalineRGBA(m_render, ss1.x, ss1.y, ss2.x, ss2.y, 255, 255, 0, 200); 443 | } 444 | } 445 | } 446 | } 447 | 448 | SDL_DestroyTexture(fps_debug_tex); 449 | SDL_DestroyTexture(scene_changing_tex); 450 | SDL_DestroyTexture(cam_look_debug_tex); 451 | SDL_DestroyTexture(cam_pos_debug_tex); 452 | SDL_DestroyTexture(cam_dir_debug_tex); 453 | SDL_DestroyTexture(cam_up_debug_tex); 454 | SDL_DestroyTexture(cam_fov_debug_tex); 455 | SDL_DestroyTexture(cam_canvas_center_pos_tex); 456 | SDL_DestroyTexture(mouse_pos_screen_tex); 457 | SDL_DestroyTexture(mouse_pos_relative_tex); 458 | SDL_DestroyTexture(mouse_pos_canvas_tex); 459 | } 460 | 461 | SDL_RenderPresent(m_render); 462 | 463 | if (m_print_perf) { 464 | fmt::print(" {:<15} {:>10.3f} ms\n", "Rendering", timer.elapsed()); 465 | fmt::print(" {:<15} {:>10.3f} ms\n", "=> Budget", 1000.f / 60.f - total.peek()); 466 | fmt::print(" {:<15} {:>10.3f} ms\n\n", "=> Total", total.peek()); 467 | } 468 | 469 | m_frame_total += total.elapsed(); 470 | if (m_benchmark_mode < 0 && m_max_frames != 0 && m_frame > m_max_frames) { 471 | auto filename = std::string("trac0r-") + std::to_string(m_max_frames) + std::string(".bmp"); 472 | SDL_Surface *sshot = SDL_CreateRGBSurface(0, m_screen_width, m_screen_height, 32, 473 | 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000); 474 | SDL_RenderReadPixels(m_render, NULL, SDL_PIXELFORMAT_ARGB8888, sshot->pixels, sshot->pitch); 475 | SDL_SaveBMP(sshot, filename.c_str()); 476 | SDL_FreeSurface(sshot); 477 | shutdown(); 478 | } else if (m_benchmark_mode > 0 && m_max_frames != 0 && m_frame > m_max_frames) { 479 | fmt::print("Benchmark results:\n"); 480 | fmt::print(" {:<15} {:>10}\n", "Frames rendered", m_max_frames); 481 | fmt::print(" {:<15} {:>10.3f} ms\n", "Total runtime", m_frame_total); 482 | fmt::print(" {:<15} {:>10.3f} ms\n", "Avg. frame", m_frame_total / m_max_frames); 483 | fmt::print(" {:<15} {:>10.3f} FPS\n", "Avg. FPS", 484 | 1.f / ((m_frame_total / 1000.f) / m_max_frames)); 485 | shutdown(); 486 | } 487 | } 488 | 489 | SDL_Renderer *Viewer::renderer() { 490 | return m_render; 491 | } 492 | 493 | SDL_Window *Viewer::window() { 494 | return m_window; 495 | } 496 | 497 | bool Viewer::is_running() { 498 | return m_running; 499 | } 500 | 501 | void Viewer::shutdown() { 502 | m_running = false; 503 | } 504 | -------------------------------------------------------------------------------- /viewer/viewer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef VIEWER_HPP 2 | #define VIEWER_HPP 3 | 4 | #include "trac0r/camera.hpp" 5 | #include "trac0r/triangle.hpp" 6 | #include "trac0r/renderer.hpp" 7 | #include "trac0r/scene.hpp" 8 | #include "trac0r/timer.hpp" 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | // Right-handed coordinate system where Y is up and Z is depth 18 | 19 | class Viewer { 20 | public: 21 | ~Viewer(); 22 | 23 | int init(int argc, char *argv[]); 24 | void mainloop(); 25 | bool is_running(); 26 | void shutdown(); 27 | 28 | SDL_Renderer *renderer(); 29 | SDL_Window *window(); 30 | 31 | // Put this stuff somewhere else 32 | void setup_scene(); 33 | 34 | private: 35 | bool m_scene_changed = false; 36 | int m_samples_accumulated = 0; 37 | glm::vec3 m_scene_up = {0, 1, 0}; 38 | bool m_running = true; 39 | bool m_look_mode = false; 40 | int m_last_frame_time = 0; 41 | bool m_debug = false; 42 | bool m_print_perf = false; 43 | int m_stride_x = 1; 44 | int m_stride_y = 1; 45 | int m_frame = 0; 46 | int m_screen_width = 800; 47 | int m_screen_height = 640; 48 | 49 | /** 50 | * @brief Used for benchmarking 51 | */ 52 | int m_benchmark_mode = 0; 53 | int m_max_frames = 0; 54 | float m_frame_total = 0.f; 55 | 56 | SDL_Renderer *m_render; 57 | SDL_Window *m_window; 58 | SDL_Texture *m_render_tex; 59 | TTF_Font *m_font; 60 | 61 | /** 62 | * @brief This is where we put our normalized intensities 63 | */ 64 | std::vector m_pixels; 65 | 66 | trac0r::Camera m_camera; 67 | trac0r::Scene m_scene; 68 | std::unique_ptr m_renderer; 69 | }; 70 | 71 | #endif /* end of include guard: VIEWER_HPP */ 72 | --------------------------------------------------------------------------------