├── .gitignore ├── CMakeLists.txt ├── LICENSE.md ├── README.md ├── cmake └── FindSDL2.cmake ├── doc ├── Filters.md ├── Geometry.md ├── Lights.md ├── Materials.md ├── Primitives.md ├── Renderers.md ├── Samplers.md ├── Textures.md └── Volumes.md ├── include ├── accelerators │ ├── bvh.h │ └── kd_point_tree.h ├── args.h ├── block_queue.h ├── cache.h ├── driver.h ├── film │ ├── camera.h │ ├── cie_vals.h │ ├── color.h │ └── render_target.h ├── filters │ ├── box_filter.h │ ├── filter.h │ ├── gaussian_filter.h │ ├── lanczos_sinc_filter.h │ ├── mitchell_filter.h │ └── triangle_filter.h ├── geometry │ ├── bbox.h │ ├── cone.h │ ├── cylinder.h │ ├── differential_geometry.h │ ├── disk.h │ ├── geometry.h │ ├── plane.h │ ├── sphere.h │ └── tri_mesh.h ├── integrator │ ├── bidir_path_integrator.h │ ├── emission_integrator.h │ ├── path_integrator.h │ ├── photon_map_integrator.h │ ├── single_scattering_integrator.h │ ├── surface_integrator.h │ ├── volume_integrator.h │ └── whitted_integrator.h ├── lights │ ├── area_light.h │ ├── light.h │ ├── occlusion_tester.h │ └── point_light.h ├── linalg │ ├── animated_transform.h │ ├── matrix4.h │ ├── point.h │ ├── quaternion.h │ ├── ray.h │ ├── transform.h │ ├── util.h │ └── vector.h ├── loaders │ ├── async_loader.h │ ├── load_filter.h │ ├── load_light.h │ ├── load_material.h │ ├── load_renderer.h │ ├── load_sampler.h │ ├── load_scene.h │ ├── load_texture.h │ └── load_volume.h ├── material │ ├── anisotropic_distribution.h │ ├── blinn_distribution.h │ ├── bsdf.h │ ├── btdf_adapter.h │ ├── bxdf.h │ ├── fresnel.h │ ├── glass_material.h │ ├── lambertian.h │ ├── material.h │ ├── matte_material.h │ ├── merl_brdf.h │ ├── merl_material.h │ ├── metal_material.h │ ├── microfacet_distribution.h │ ├── mix_material.h │ ├── oren_nayer.h │ ├── plastic_material.h │ ├── scaled_bxdf.h │ ├── specular_metal_material.h │ ├── specular_reflection.h │ ├── specular_transmission.h │ ├── torrance_sparrow.h │ └── translucent_material.h ├── memory_pool.h ├── mesh_preprocess.h ├── monte_carlo │ ├── distribution1d.h │ └── util.h ├── renderer │ └── renderer.h ├── samplers │ ├── adaptive_sampler.h │ ├── ld_sampler.h │ ├── sampler.h │ └── stratified_sampler.h ├── scene.h ├── textures │ ├── checkerboard_texture.h │ ├── constant_texture.h │ ├── image_texture.h │ ├── mipmap.h │ ├── remapped_texture.h │ ├── scale_texture.h │ ├── spherical_mapping.h │ ├── texture.h │ ├── texture_mapping.h │ ├── uv_mapping.h │ └── uv_texture.h └── volume │ ├── exponential_volume.h │ ├── geometry_volume.h │ ├── grid_volume.h │ ├── homogeneous_volume.h │ ├── varying_density_volume.h │ ├── volume.h │ └── volume_node.h ├── previewer ├── CMakeLists.txt ├── gl_core_3_3.c ├── gl_core_3_3.h ├── previewer.cpp ├── previewer.h ├── util.cpp └── util.h ├── scenes ├── cornell.xml └── smallpt_cornell.xml └── src ├── CMakeLists.txt ├── accelerators ├── CMakeLists.txt └── bvh.cpp ├── args.cpp ├── block_queue.cpp ├── driver.cpp ├── film ├── CMakeLists.txt ├── camera.cpp ├── cie_vals.cpp ├── color.cpp └── render_target.cpp ├── filters ├── CMakeLists.txt ├── box_filter.cpp ├── gaussian_filter.cpp ├── lanczos_sinc_filter.cpp ├── mitchell_filter.cpp └── triangle_filter.cpp ├── geometry ├── CMakeLists.txt ├── cone.cpp ├── cylinder.cpp ├── differential_geometry.cpp ├── disk.cpp ├── geometry.cpp ├── plane.cpp ├── sphere.cpp └── tri_mesh.cpp ├── integrator ├── CMakeLists.txt ├── bidir_path_integrator.cpp ├── emission_integrator.cpp ├── path_integrator.cpp ├── photon_map_integrator.cpp ├── single_scattering_integrator.cpp ├── surface_integrator.cpp └── whitted_integrator.cpp ├── lights ├── CMakeLists.txt ├── area_light.cpp ├── light.cpp ├── occlusion_tester.cpp └── point_light.cpp ├── linalg ├── CMakeLists.txt ├── animated_transform.cpp ├── matrix4.cpp ├── quaternion.cpp └── transform.cpp ├── loaders ├── CMakeLists.txt ├── load_filter.cpp ├── load_light.cpp ├── load_material.cpp ├── load_renderer.cpp ├── load_sampler.cpp ├── load_scene.cpp ├── load_texture.cpp └── load_volume.cpp ├── main.cpp ├── material ├── CMakeLists.txt ├── anisotropic_distribution.cpp ├── blinn_distribution.cpp ├── bsdf.cpp ├── btdf_adapter.cpp ├── bxdf.cpp ├── fresnel.cpp ├── glass_material.cpp ├── lambertian.cpp ├── matte_material.cpp ├── merl_brdf.cpp ├── merl_material.cpp ├── metal_material.cpp ├── microfacet_distribution.cpp ├── mix_material.cpp ├── oren_nayer.cpp ├── plastic_material.cpp ├── scaled_bxdf.cpp ├── specular_metal_material.cpp ├── specular_reflection.cpp ├── specular_transmission.cpp ├── torrance_sparrow.cpp └── translucent_material.cpp ├── memory_pool.cpp ├── mesh_preprocess.cpp ├── monte_carlo ├── CMakeLists.txt ├── distribution1d.cpp └── util.cpp ├── renderer ├── CMakeLists.txt └── renderer.cpp ├── samplers ├── CMakeLists.txt ├── adaptive_sampler.cpp ├── ld_sampler.cpp ├── sampler.cpp └── stratified_sampler.cpp ├── scene.cpp ├── textures ├── CMakeLists.txt ├── checkerboard_texture.cpp ├── constant_texture.cpp ├── image_texture.cpp ├── mipmap.cpp ├── remapped_texture.cpp ├── scale_texture.cpp ├── spherical_mapping.cpp ├── texture_mapping.cpp ├── uv_mapping.cpp └── uv_texture.cpp └── volume ├── CMakeLists.txt ├── exponential_volume.cpp ├── geometry_volume.cpp ├── grid_volume.cpp ├── homogeneous_volume.cpp ├── varying_density_volume.cpp ├── volume.cpp └── volume_node.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.dll 6 | build/ 7 | bin/ 8 | *.bobj 9 | external/ 10 | scenes/textures/ 11 | scenes/models/ 12 | scenes/brdfs 13 | scenes/volumes 14 | *.png 15 | *.ppm 16 | *.pgm 17 | *.bmp 18 | *.png 19 | tags 20 | 21 | # Ignored ycm compiled python file 22 | *.pyc 23 | *.ycm*.py 24 | 25 | # Swap files 26 | *.swp 27 | 28 | # external projects 29 | external 30 | 31 | # Compiled Dynamic libraries 32 | *.so 33 | 34 | # Compiled Static libraries 35 | *.lai 36 | *.la 37 | *.a 38 | 39 | *.exe 40 | *.pdb 41 | *.ilk 42 | *.tlog 43 | *.obj 44 | *.log 45 | *.lastbuildstate 46 | *.opensdf 47 | *.sdf 48 | *.suo 49 | *.out 50 | *_i.c 51 | *_p.c 52 | *.ilk 53 | *.meta 54 | *.obj 55 | *.pch 56 | *.pdb 57 | *.pgc 58 | *.pgd 59 | *.rsp 60 | *.sbr 61 | *.tlb 62 | *.tli 63 | *.tlh 64 | *.tmp 65 | *.vspscc 66 | .builds 67 | *.dotCover 68 | # Visual C++ cache files 69 | ipch/ 70 | *.aps 71 | *.ncb 72 | *.opensdf 73 | *.sdf 74 | 75 | # Visual Studio profiler 76 | *.psess 77 | *.vsp 78 | 79 | Lesson*/Debug 80 | Lesson*/Release 81 | out/ 82 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.6) 2 | project(TRAY) 3 | 4 | # Bump up warning levels appropriately for each compiler 5 | if (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU" OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") 6 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -std=c++14") 7 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS} -m64 -ggdb -DDEBUG") 8 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} -m64 -O3 -march=native -flto -fno-use-linker-plugin -DNDEBUG -fno-exceptions") 9 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -flto -fno-exceptions") 10 | elseif (${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC") 11 | if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]") 12 | string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi") 13 | else() 14 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /Zi") 15 | endif() 16 | endif() 17 | 18 | set(TRAY_INSTALL_DIR "${TRAY_SOURCE_DIR}/bin") 19 | 20 | set(tinyxml2_DIR "${TRAY_SOURCE_DIR}/external/tinyxml2/") 21 | file(DOWNLOAD "https://raw.githubusercontent.com/leethomason/tinyxml2/master/tinyxml2.cpp" 22 | "${tinyxml2_DIR}/tinyxml2.cpp") 23 | file(DOWNLOAD "https://raw.githubusercontent.com/leethomason/tinyxml2/master/tinyxml2.h" 24 | "${tinyxml2_DIR}/tinyxml2.h") 25 | # Use stb_image for wider range of image format support 26 | set(stb_image_INCLUDE_DIR "${TRAY_SOURCE_DIR}/external/stb/") 27 | file(DOWNLOAD "https://raw.githubusercontent.com/nothings/stb/master/stb_image.h" 28 | "${stb_image_INCLUDE_DIR}/stb_image.h") 29 | include_directories(include ${tinyxml2_DIR} ${stb_image_INCLUDE_DIR}) 30 | 31 | # If we're building the live previewer find SDL2 32 | if (BUILD_PREVIEWER) 33 | add_definitions(-DBUILD_PREVIEWER) 34 | # Use our modified FindSDL2 modules for windows/etc 35 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${TRAY_SOURCE_DIR}/cmake") 36 | find_package(SDL2 REQUIRED) 37 | find_package(OpenGL REQUIRED) 38 | include_directories(${SDL2_INCLUDE_DIR} ${OPENGL_INCLUDE_DIR} previewer) 39 | add_subdirectory(previewer) 40 | endif() 41 | 42 | # If user has request trilinear texture filtering 43 | if (TEX_TRILINEAR) 44 | add_definitions(-DTEX_TRILINEAR) 45 | endif() 46 | 47 | find_package(Threads) 48 | add_subdirectory(src) 49 | 50 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Will Usher 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /doc/Primitives.md: -------------------------------------------------------------------------------- 1 | Primitives 2 | === 3 | This documentation covers how to specify the various basic types that are used throughout the scene files to specify attributes such as colors, points, vectors and transformations. 4 | 5 | Points 6 | --- 7 | Points are specified by passing their coordinates as attributes. Any unspecified coordinates are assumed to be zero. 8 | 9 | ```XML 10 | 11 | 12 | ``` 13 | 14 | Vectors 15 | --- 16 | Vectors are specified identically to points but can also take an additional scaling value to multiply the components with. As with points unspecified components are assumed to be 0. 17 | 18 | ```XML 19 | 20 | 21 | ``` 22 | 23 | Colors 24 | --- 25 | Colors are specified identical to vectors but the attributes specified are the RGB components. You can also specify a grayscale color by only providing the value attribute. Examples of both are given below. 26 | 27 | ```XML 28 | 29 | 30 | 31 | ``` 32 | 33 | Transformations 34 | --- 35 | Transformations are specified by passing scale, rotation and translation parameters. The order in which the tags are read in is the order in which the transformations will be applied to the object. Both the scale and translate tags tags take the vector of scale/translation values for x, y and z. For scaling you can also specify a value attribute to have a uniform scaling applied to all axes, unspecified axes for scaling are assumed to be 1. Rotation takes an angle in degrees and a vector to rotate about, treated as described in the vector section. Some example transformations are given below. 36 | 37 | ```XML 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | ``` 55 | 56 | -------------------------------------------------------------------------------- /doc/Samplers.md: -------------------------------------------------------------------------------- 1 | Samplers 2 | === 3 | This doc covers the various samplers that can be selected. Samplers are used when finding sample positions to send rays from on the image plane and can have a big effect on image quality if they're not well distributed. 4 | 5 | Stratified Sampler 6 | --- 7 | This sampler partitions the pixel into a grid and takes jittered samples in each grid cell. It takes a single attribute to specify the number of samples per axis, spp, and will take spp \* spp samples after splitting the grid. 8 | 9 | ```XML 10 | 11 | 12 | ``` 13 | 14 | Low Discrepancy Sampler 15 | --- 16 | The low discrepency sampler chooses samples using sequences known to give well distributed samples, combining samples from a Van der Corput and Sobol sequence. The sampler also takes a parameter to select the number of samples to take per pixel which must also be a power of two. If a non-power of two number is passed it will be rounded up and a warning printed. 17 | 18 | ```XML 19 | 20 | ``` 21 | 22 | Adaptive Sampler 23 | --- 24 | The adaptive sampler determines the number of samples to take based on the variance in the contrast of samples taken previously. To choose sample positions it uses the low discrepancy sampler described previously and as such also requires power of two sample counts. The sampler takes two parameters to specify the min and max samples to take per pixel and will begin by taking min samples and use their results to determine if more samples are needed. If it's determined more are required it will step up the sample count in powers of two until reaching the max. 25 | 26 | ```XML 27 | 28 | ``` 29 | 30 | -------------------------------------------------------------------------------- /doc/Textures.md: -------------------------------------------------------------------------------- 1 | Textures 2 | === 3 | The ray tracer supports PNG (8bit channel only), BMP (non-1bpp, non-RLE), and PPM image formats for textures. Note that texture names beginning with \_\_ are reserved for generated texture names the ray tracer may need to create and should not be used. Textures can also have transformations specified which will result in a transformation being applied to the texture coordinates when looking up the texel. 4 | 5 | Checkerboard Texture 6 | --- 7 | The checkerboard texture is a regular two color checkerboard and takes two colors to be selected for the colors of the checks. Below is an example of a checkerboard with some non-uniform scaling applied. 8 | 9 | ```XML 10 | 11 | 12 | 13 | 14 | 15 | ``` 16 | 17 | Image Texture 18 | --- 19 | The image texture loads an image and applies it to the object using its texture coordinates. TRay supports PNG (8bit channel only), BMP (non-1bpp, non-RLE), and PPM image formats for textures. The name used for an image texture should be the path to the file to load, relative to the scene file. 20 | 21 | ```XML 22 | 23 | 24 | 25 | ``` 26 | 27 | UV Texture 28 | --- 29 | The UV texture just returns the texture coordinates passed in as the "color" of the texture, useful for debugging. 30 | 31 | ```XML 32 | 33 | ``` 34 | 35 | -------------------------------------------------------------------------------- /include/args.h: -------------------------------------------------------------------------------- 1 | #ifndef ARGS_H 2 | #define ARGS_h 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | /* 9 | * Some utilities for parsing command line arguments and 10 | * conveniently checking selected flags and getting parameter 11 | * values 12 | */ 13 | /* 14 | * Check if a flag was passed 15 | * @param beg The beginning of the args array 16 | * @param end One past the end of the args array 17 | * @param f The flag to check for, ex. -f 18 | * @return true if the flag was passed 19 | */ 20 | bool flag(char **beg, char **end, const std::string &f); 21 | /* 22 | * Get the parameter passed for some flag. It's best to make sure 23 | * the flag was passed before trying to retrieve its argument 24 | * if the flag isn't found T's default value will be returned 25 | * @param beg The beginning of the args array 26 | * @param end One past the end of the args array 27 | * @param f The flag to get the parameter for, ex. -f 28 | * @return The value of the parameter 29 | */ 30 | template 31 | T get_param(char **beg, char **end, const std::string &f){ 32 | char **it = std::find(beg, end, f); 33 | if (it != end && ++it != end){ 34 | std::stringstream ss; 35 | ss << *it; 36 | T t; 37 | ss >> t; 38 | return t; 39 | } 40 | return T(); 41 | } 42 | //Some quicker versions of getParam for certain types 43 | template<> 44 | std::string get_param(char **beg, char **end, const std::string &g); 45 | template<> 46 | int get_param(char **beg, char **end, const std::string &g); 47 | template<> 48 | float get_param(char **beg, char **end, const std::string &g); 49 | 50 | #endif 51 | 52 | -------------------------------------------------------------------------------- /include/block_queue.h: -------------------------------------------------------------------------------- 1 | #ifndef BLOCK_QUEUE_H 2 | #define BLOCK_QUEUE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "samplers/sampler.h" 8 | 9 | /* 10 | * A queue that hands out blocks of pixels to be worked on 11 | * by threads in the form of samplers for the block of pixels 12 | */ 13 | class BlockQueue { 14 | std::vector> samplers; 15 | //The index of the next sampler to be handed out 16 | std::atomic_uint sampler_idx, loops; 17 | //Total time and previous time we printed out timing info 18 | std::chrono::milliseconds total_time; 19 | std::chrono::time_point prev; 20 | 21 | public: 22 | /* 23 | * Create a queue of work blocks by subsampling the sampler 24 | * into blocks subsamplers 25 | */ 26 | BlockQueue(const Sampler &sampler, int bwidth, int bheight); 27 | /* 28 | * Return the next block to be worked on, returns nullptrt 29 | * when all samplers have been completed 30 | */ 31 | Sampler* get_block(); 32 | }; 33 | 34 | #endif 35 | 36 | -------------------------------------------------------------------------------- /include/cache.h: -------------------------------------------------------------------------------- 1 | #ifndef CACHE_H 2 | #define CACHE_H 3 | 4 | #include 5 | #include 6 | 7 | /* 8 | * Caches types of T, really just a hash table of name -> T 9 | */ 10 | template 11 | class Cache { 12 | std::unordered_map> cache; 13 | 14 | public: 15 | using iter = typename std::unordered_map>::iterator; 16 | using citer = typename std::unordered_map>::const_iterator; 17 | /* 18 | * Add some T to the cache 19 | */ 20 | T* add(const std::string &name, std::unique_ptr t){ 21 | auto it = cache.emplace(std::make_pair(name, std::move(t))); 22 | return it.first->second.get(); 23 | } 24 | /* 25 | * Get a non-owning pointer to some T in the cache 26 | * nullptr is returned if the name isn't found 27 | */ 28 | const T* get(const std::string &name) const { 29 | auto fnd = cache.find(name); 30 | if (fnd != cache.end()){ 31 | return fnd->second.get(); 32 | } 33 | return nullptr; 34 | } 35 | T* get(const std::string &name){ 36 | auto fnd = cache.find(name); 37 | if (fnd != cache.end()){ 38 | return fnd->second.get(); 39 | } 40 | return nullptr; 41 | } 42 | iter begin(){ 43 | return cache.begin(); 44 | } 45 | citer begin() const { 46 | return cache.begin(); 47 | } 48 | iter end(){ 49 | return cache.end(); 50 | } 51 | citer end() const { 52 | return cache.cend(); 53 | } 54 | size_t size() const { 55 | return cache.size(); 56 | } 57 | bool empty() const { 58 | return cache.empty(); 59 | } 60 | }; 61 | 62 | #endif 63 | 64 | -------------------------------------------------------------------------------- /include/driver.h: -------------------------------------------------------------------------------- 1 | #ifndef DRIVER_H 2 | #define DRIVER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "scene.h" 8 | #include "geometry/geometry.h" 9 | #include "linalg/ray.h" 10 | #include "samplers/sampler.h" 11 | #include "block_queue.h" 12 | 13 | //Status of a worker thread 14 | enum STATUS { NOT_STARTED, WORKING, DONE, CANCELED, JOINED }; 15 | 16 | /* 17 | * A worker thread that renders some subsection of the scene 18 | */ 19 | class Worker { 20 | Scene &scene; 21 | BlockQueue &queue; 22 | 23 | public: 24 | //The thread the worker is on 25 | std::thread thread; 26 | //The thread status so we can report whether we're done or should cancel 27 | std::atomic_int status; 28 | 29 | /* 30 | * Create the worker to get samplers from the sampler 31 | * and use them to render the scene 32 | */ 33 | Worker(Scene &scene, BlockQueue &queue); 34 | Worker(Worker &&w); 35 | void render(); 36 | }; 37 | 38 | /* 39 | * A driver that distributes the work of rendering the scene 40 | * among some number of threads 41 | */ 42 | class Driver { 43 | //The worker threads rendering the scene 44 | std::vector workers; 45 | Scene &scene; 46 | //Queue of blocks of pixels to be worked on 47 | BlockQueue queue; 48 | 49 | public: 50 | /* 51 | * Create a driver to render the scene with some number of worker threads 52 | * to work on the scene partitioned into blocks with the desired dimensions 53 | */ 54 | Driver(Scene &scene, int nworkers, int bwidth, int bheight); 55 | ~Driver(); 56 | void render(); 57 | bool done(); 58 | /* 59 | * Abort all threads and cancel rendering 60 | */ 61 | void cancel(); 62 | /* 63 | * Get the scene being rendered 64 | */ 65 | const Scene& get_scene() const; 66 | }; 67 | 68 | #endif 69 | 70 | -------------------------------------------------------------------------------- /include/film/camera.h: -------------------------------------------------------------------------------- 1 | #ifndef CAMERA_H 2 | #define CAMERA_H 3 | 4 | #include 5 | #include "linalg/ray.h" 6 | #include "linalg/transform.h" 7 | #include "samplers/sampler.h" 8 | 9 | class Camera { 10 | const float dof, focal_dist, open, close; 11 | //Transform to go from camera to world space, eg. camera look at matrix 12 | Transform cam_world; 13 | //Transforms from raster to camera space 14 | Transform raster_cam; 15 | //dx and dy for pixels, for computing ray differentials 16 | Vector dx, dy; 17 | 18 | public: 19 | /* 20 | * Create the camera at some position in the world. Default is looking down 21 | * the z axis but this can be changed with the cam_world transformation 22 | * xres & yres correspond to the render target resolution 23 | */ 24 | Camera(const Transform &cam_world, float fov, float dof, float focal_dist, 25 | float open, float close, int xres, int yres); 26 | /* 27 | * Generate a ray to sample the scene through x,y. Pixel coordinates should be 28 | * in raster space 29 | */ 30 | Ray generate_ray(const Sample &sample) const; 31 | /* 32 | * Generate a ray with differentials to sample the scene through x,y. 33 | * The differentials will be +1 in x and y, pixel coords passed should 34 | * be in raster space 35 | */ 36 | RayDifferential generate_raydifferential(const Sample &sample) const; 37 | }; 38 | 39 | #endif 40 | 41 | -------------------------------------------------------------------------------- /include/film/cie_vals.h: -------------------------------------------------------------------------------- 1 | #ifndef CIE_VALS_H 2 | #define CIE_VALS_H 3 | 4 | #include 5 | 6 | //We use PBRT's values for the CIE function values 7 | static const size_t NUM_CIE_SAMPLES = 471; 8 | extern const std::array CIE_X; 9 | extern const std::array CIE_Y; 10 | extern const std::array CIE_Z; 11 | extern const std::array CIE_LAMBDA; 12 | 13 | #endif 14 | 15 | -------------------------------------------------------------------------------- /include/film/render_target.h: -------------------------------------------------------------------------------- 1 | #ifndef RENDER_TARGET_H 2 | #define RENDER_TARGET_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "color.h" 9 | #include "filters/filter.h" 10 | 11 | const int FILTER_TABLE_SIZE = 16; 12 | 13 | /* 14 | * A pixel stored in the image being rendered to track pixel 15 | * luminance and weight for reconstruction 16 | * Because we need to deal with multi-thread synchronization the 17 | * rgb values and weights are stored as atomics 18 | */ 19 | struct Pixel { 20 | std::atomic r, g, b, weight; 21 | 22 | Pixel(); 23 | Pixel(const Pixel &p); 24 | }; 25 | 26 | /* 27 | * The render target where pixel data is stored for the rendered scene 28 | * along with for some reason a depth buffer is required for proj1? 29 | */ 30 | class RenderTarget { 31 | size_t width, height; 32 | std::unique_ptr filter; 33 | std::vector pixels; 34 | //Pre-computed filter values to save time when storing pixels 35 | std::array filter_table; 36 | 37 | public: 38 | /* 39 | * Create a render target with width * height pixels that will 40 | * reconstruct the image from the samples using the desired filter 41 | * Default is a box filter with single pixel extent 42 | */ 43 | RenderTarget(size_t width, size_t height, std::unique_ptr f); 44 | /* 45 | * Write a color value to the image at pixel(x, y) 46 | */ 47 | void write_pixel(float x, float y, const Colorf &c); 48 | //Save the image or depth buffer to the desired file 49 | bool save_image(const std::string &file) const; 50 | size_t get_width() const; 51 | size_t get_height() const; 52 | /* 53 | * Get a snapshot of the color buffer at the moment 54 | * stored in img 55 | */ 56 | void get_colorbuf(std::vector &img) const; 57 | 58 | private: 59 | /* 60 | * Save color data as a PPM image to the file, data should be 61 | * RGB8 data and have width * height elements 62 | */ 63 | bool save_ppm(const std::string &file, const uint8_t *data) const; 64 | /* 65 | * Save color data as a BMP image to the file, data should be 66 | * RGBA8 data and have width * height elements 67 | * The image data should be flipped appropriately already for the BMP 68 | * file format (eg. starting at bottom left) 69 | */ 70 | bool save_bmp(const std::string &file, const uint8_t *data) const; 71 | }; 72 | 73 | #endif 74 | 75 | -------------------------------------------------------------------------------- /include/filters/box_filter.h: -------------------------------------------------------------------------------- 1 | #ifndef BOX_FILTER_H 2 | #define BOX_FILTER_H 3 | 4 | #include "filter.h" 5 | 6 | /* 7 | * A simple box filter 8 | */ 9 | struct BoxFilter : Filter { 10 | BoxFilter(float w, float h); 11 | float weight(float x, float y) const override; 12 | }; 13 | 14 | #endif 15 | 16 | -------------------------------------------------------------------------------- /include/filters/filter.h: -------------------------------------------------------------------------------- 1 | #ifndef FILTER_H 2 | #define FILTER_H 3 | 4 | /* 5 | * Base filter used when reconstructing the rendered image 6 | * from samples taken of the scene 7 | * Built off the technique from PBR 8 | */ 9 | struct Filter { 10 | //The filters width, height and corresponding inverses 11 | const float w, h, inv_w, inv_h; 12 | 13 | /* 14 | * Construct a filter with the desired extent in x & y 15 | */ 16 | Filter(float w, float h) : w(w), h(h), inv_w(1 / w), inv_h(1 / h){} 17 | /* 18 | * Evaluate the weight of this filter at some position 19 | */ 20 | virtual float weight(float x, float y) const = 0; 21 | }; 22 | 23 | #endif 24 | 25 | -------------------------------------------------------------------------------- /include/filters/gaussian_filter.h: -------------------------------------------------------------------------------- 1 | #ifndef GAUSSIAN_FILTER_H 2 | #define GAUSSIAN_FILTER_H 3 | 4 | #include "filter.h" 5 | 6 | struct GaussianFilter : Filter { 7 | const float alpha, exp_x, exp_y; 8 | 9 | GaussianFilter(float w, float h, float alpha); 10 | float weight(float x, float y) const override; 11 | 12 | private: 13 | /* 14 | * Compute the 1d Gaussian value using 15 | * std::exp(-alpha * x * x) - expv 16 | * where expv should be a pre-computed value 17 | * for std::exp(-alpha * w * w) 18 | */ 19 | float gaussian_1d(float x, float expv) const; 20 | }; 21 | 22 | #endif 23 | 24 | -------------------------------------------------------------------------------- /include/filters/lanczos_sinc_filter.h: -------------------------------------------------------------------------------- 1 | #ifndef LANCZOS_SINC_FILTER 2 | #define LANCZOS_SINC_FILTER 3 | 4 | #include "filter.h" 5 | 6 | struct LanczosSincFilter : Filter { 7 | const float a; 8 | 9 | LanczosSincFilter(float w, float h, float a); 10 | float weight(float x, float y) const override; 11 | 12 | private: 13 | float lanczos_sinc1d(float x) const; 14 | }; 15 | 16 | #endif 17 | 18 | -------------------------------------------------------------------------------- /include/filters/mitchell_filter.h: -------------------------------------------------------------------------------- 1 | #ifndef MITCHELL_FILTER_H 2 | #define MITCHELL_FILTER_H 3 | 4 | #include "filter.h" 5 | 6 | struct MitchellFilter : Filter { 7 | const float b, c; 8 | 9 | MitchellFilter(float w, float h, float b, float c); 10 | float weight(float x, float y) const override; 11 | 12 | private: 13 | float mitchell_1d(float x) const; 14 | }; 15 | 16 | #endif 17 | 18 | -------------------------------------------------------------------------------- /include/filters/triangle_filter.h: -------------------------------------------------------------------------------- 1 | #ifndef TRIANGLE_FILTER_H 2 | #define TRIANGLE_FILTER_H 3 | 4 | #include "filter.h" 5 | 6 | /* 7 | * A simple triangle filter 8 | */ 9 | struct TriangleFilter : Filter { 10 | TriangleFilter(float w, float h); 11 | float weight(float x, float y) const override; 12 | }; 13 | 14 | #endif 15 | 16 | -------------------------------------------------------------------------------- /include/geometry/cone.h: -------------------------------------------------------------------------------- 1 | #ifndef CONE_H 2 | #define CONE_H 3 | 4 | #include "geometry.h" 5 | 6 | /* 7 | * A cone with some radius and height, described as cone 8 | * from [0, height] along the z axis 9 | */ 10 | class Cone : public Geometry { 11 | float radius, height; 12 | 13 | public: 14 | /* 15 | * Construct the cone with some radius and height 16 | */ 17 | Cone(float radius = 1, float height = 1); 18 | bool intersect(Ray &ray, DifferentialGeometry &dg) const override; 19 | BBox bound() const override; 20 | void refine(std::vector &prims) override; 21 | /* 22 | * Compute the surface area of the sphere 23 | */ 24 | float surface_area() const override; 25 | }; 26 | 27 | #endif 28 | 29 | -------------------------------------------------------------------------------- /include/geometry/cylinder.h: -------------------------------------------------------------------------------- 1 | #ifndef CYLINDER_H 2 | #define CYLINDER_H 3 | 4 | #include "geometry.h" 5 | 6 | /* 7 | * A cylinder with some radius and height along the z axis 8 | */ 9 | class Cylinder : public Geometry { 10 | float radius, height; 11 | 12 | public: 13 | /* 14 | * Construct the cylinder with some radius and height 15 | */ 16 | Cylinder(float radius = 1, float height = 1); 17 | bool intersect(Ray &ray, DifferentialGeometry &dg) const override; 18 | BBox bound() const override; 19 | void refine(std::vector &prims) override; 20 | /* 21 | * Compute the surface area of the sphere 22 | */ 23 | float surface_area() const override; 24 | /* 25 | * Sample a position on the geometry and return the point and normal 26 | */ 27 | Point sample(const GeomSample &gs, Normal &normal) const override; 28 | bool attach_light(const Transform &to_world) override; 29 | }; 30 | 31 | #endif 32 | 33 | -------------------------------------------------------------------------------- /include/geometry/differential_geometry.h: -------------------------------------------------------------------------------- 1 | #ifndef DIFFERENTIAL_GEOMETRY_H 2 | #define DIFFERENTIAL_GEOMETRY_H 3 | 4 | #include "linalg/point.h" 5 | #include "linalg/vector.h" 6 | #include "linalg/ray.h" 7 | 8 | class Node; 9 | class Geometry; 10 | 11 | enum HITSIDE { NONE, FRONT, BACK }; 12 | 13 | /* 14 | * Stores information about the differential piece of geometry 15 | * that was hit and a pointer to the node that was hit so we can 16 | * grab the material to shade this hit point 17 | */ 18 | struct DifferentialGeometry { 19 | Point point; 20 | //normal is the surface normal to be used for shading while 21 | //geom_normal is the true normal of the original geometry we hit 22 | Normal normal, geom_normal; 23 | const Node *node; 24 | const Geometry *geom; 25 | HITSIDE hit_side; 26 | //Various derivatives and info we need for texture mapping and filtering 27 | Vector dp_du, dp_dv, dp_dx, dp_dy; 28 | Normal dn_du, dn_dv; 29 | float u, v, du_dx, dv_dx, du_dy, dv_dy; 30 | 31 | DifferentialGeometry(); 32 | DifferentialGeometry(const Point &point, const Vector &dp_du, const Vector &dp_dv, 33 | const Normal &dn_du, const Normal &dn_dv, const Normal &geom_normal, 34 | float u, float v, const Node *node, HITSIDE hit_side); 35 | /* 36 | * Compute the (u, v) derivatives using information about the 37 | * change in pixel position from the ray differential 38 | */ 39 | void compute_differentials(const RayDifferential &r); 40 | }; 41 | 42 | #endif 43 | 44 | -------------------------------------------------------------------------------- /include/geometry/disk.h: -------------------------------------------------------------------------------- 1 | #ifndef DISK_H 2 | #define DISK_H 3 | 4 | #include "geometry.h" 5 | 6 | /* 7 | * A disk with some inner and outer radius allowing it to 8 | * have a hole in the center placed at the origin with normal 9 | * along +z 10 | */ 11 | class Disk : public Geometry { 12 | float radius, inner_radius; 13 | 14 | public: 15 | /* 16 | * Construct the disk with some radius and inner radius 17 | */ 18 | Disk(float radius = 1, float inner_radius = 0); 19 | bool intersect(Ray &ray, DifferentialGeometry &dg) const override; 20 | BBox bound() const override; 21 | void refine(std::vector &prims) override; 22 | /* 23 | * Compute the surface area of the sphere 24 | */ 25 | float surface_area() const override; 26 | /* 27 | * Sample a position on the geometry and return the point and normal 28 | */ 29 | Point sample(const GeomSample &gs, Normal &normal) const override; 30 | bool attach_light(const Transform &to_world) override; 31 | }; 32 | 33 | #endif 34 | 35 | -------------------------------------------------------------------------------- /include/geometry/plane.h: -------------------------------------------------------------------------------- 1 | #ifndef PLANE_H 2 | #define PLANE_H 3 | 4 | #include "geometry.h" 5 | 6 | /* 7 | * A plane centered at the origin spanning 8 | * [-1, -1] to [1, 1] with normal of {0, 0, 1} 9 | */ 10 | class Plane : public Geometry { 11 | public: 12 | bool intersect(Ray &ray, DifferentialGeometry &diff_geom) const override; 13 | BBox bound() const override; 14 | void refine(std::vector &prims) override; 15 | }; 16 | 17 | #endif 18 | 19 | -------------------------------------------------------------------------------- /include/geometry/sphere.h: -------------------------------------------------------------------------------- 1 | #ifndef SPHERE_H 2 | #define SPHERE_H 3 | 4 | #include 5 | #include "linalg/point.h" 6 | #include "geometry.h" 7 | 8 | /* 9 | * A simple unit sphere centered at the origin 10 | */ 11 | class Sphere : public Geometry { 12 | float radius; 13 | 14 | public: 15 | /* 16 | * Construct the sphere with some radius 17 | */ 18 | Sphere(float radius = 1); 19 | bool intersect(Ray &ray, DifferentialGeometry &diff_geom) const override; 20 | BBox bound() const override; 21 | void refine(std::vector &prims) override; 22 | /* 23 | * Compute the surface area of the sphere 24 | */ 25 | float surface_area() const override; 26 | /* 27 | * Sample a position on the geometry and return the point and normal 28 | */ 29 | Point sample(const GeomSample &gs, Normal &normal) const override; 30 | /* 31 | * Sample the shape using the probability density of the solid angle from 32 | * point p to the point on the surface 33 | */ 34 | Point sample(const Point &p, const GeomSample &gs, Normal &normal) const override; 35 | /* 36 | * Compute the pdf that the ray from p with direction w_i intersects the shape 37 | */ 38 | float pdf(const Point &p, const Vector &w_i) const override; 39 | bool attach_light(const Transform &to_world) override; 40 | }; 41 | 42 | #endif 43 | 44 | -------------------------------------------------------------------------------- /include/integrator/bidir_path_integrator.h: -------------------------------------------------------------------------------- 1 | #ifndef BIDIR_PATH_INTEGRATOR_H 2 | #define BIDIR_PATH_INTEGRATOR_H 3 | 4 | #include "surface_integrator.h" 5 | #include "renderer/renderer.h" 6 | 7 | /* 8 | * Surface integrator that uses bidirectional path tracing for computing illumination 9 | * at a a point on the surface 10 | */ 11 | class BidirPathIntegrator : public SurfaceIntegrator { 12 | const int min_depth, max_depth; 13 | 14 | /* 15 | * Struct used to store information about a vertex along a camera or light path 16 | * stores the vertex differential geometry, outgoing and incident vectors (both point out of surface) 17 | * the BSDF, specularity and throughput information 18 | */ 19 | struct PathVertex { 20 | DifferentialGeometry dg; 21 | Vector w_o, w_i; 22 | BSDF *bsdf; 23 | bool specular_bounce; 24 | int num_specular_comp; 25 | Colorf throughput; 26 | }; 27 | 28 | public: 29 | /* 30 | * Create the path tracing integrator and set the min and max depth for paths 31 | * rays are randomly stopped by Russian roulette after reaching min_depth and are stopped 32 | * at max_depth 33 | */ 34 | BidirPathIntegrator(int min_depth, int max_depth); 35 | /* 36 | * Compute the illumination at a point on a surface in the scene 37 | */ 38 | Colorf illumination(const Scene &scene, const Renderer &renderer, const RayDifferential &ray, 39 | DifferentialGeometry &dg, Sampler &sampler, MemoryPool &pool) const override; 40 | 41 | private: 42 | /* 43 | * Generate a path of length at least min_depth and at most max_depth, returning the actual 44 | * length of the path generated and filling out the path vertex array passed. There must be 45 | * room for at least max_depth vertices in the array. Weight is the starting weight of the 46 | * path, eg. weight from the camera or light source generating the ray 47 | */ 48 | int trace_path(const Scene &scene, const Renderer &renderer, const RayDifferential &ray, const Colorf &weight, 49 | Sampler &sampler, MemoryPool &pool, PathVertex *path_vertices) const; 50 | /* 51 | * Compute the luminance along the camera path using the regular camera path tracing lighting 52 | * computation but with the path vertices for path of length path_len provided in path_vertices 53 | */ 54 | Colorf camera_luminance(const Scene &scene, const Renderer &renderer, const PathVertex *path_vertices, 55 | int path_len, Sampler &sampler, MemoryPool &pool) const; 56 | /* 57 | * Compute the luminance coming back along the camera path by combining the camera and light 58 | * paths that were traced previously 59 | */ 60 | Colorf bidir_luminance(const Scene &scene, const Renderer &renderer, PathVertex *cam_path, int cam_path_len, 61 | PathVertex *light_path, int light_path_len, Sampler &sampler, MemoryPool &pool) const; 62 | }; 63 | 64 | #endif 65 | 66 | -------------------------------------------------------------------------------- /include/integrator/emission_integrator.h: -------------------------------------------------------------------------------- 1 | #ifndef EMISSION_INTEGRATOR_H 2 | #define EMISSION_INTEGRATOR_H 3 | 4 | #include "volume_integrator.h" 5 | 6 | /* 7 | * The EmissionIntegrator is a volume integrator that only 8 | * accounts for absoprtion and emission effects of the volumes 9 | * being traversed 10 | * See: PBR 11 | */ 12 | class EmissionIntegrator : public VolumeIntegrator { 13 | //Distance between samples to take of the volume when integrating 14 | float step_size; 15 | 16 | public: 17 | /* 18 | * Construct the emission integrator specifying the distance between 19 | * samples when integrating over the volumes in the scene 20 | */ 21 | EmissionIntegrator(float step_size); 22 | /* 23 | * Compute the radiance arriving at the point p along the direction w 24 | * due to participating media in the scene. Also returns the transmittance 25 | * along the ray through transmit 26 | */ 27 | virtual Colorf radiance(const Scene &scene, const Renderer &renderer, const RayDifferential &ray, 28 | Sampler &sampler, MemoryPool &pool, Colorf &transmit) const override; 29 | /* 30 | * Compute the beam transmittance for line segment along the ray from min_t to max_t 31 | */ 32 | virtual Colorf transmittance(const Scene &scene, const Renderer &renderer, const RayDifferential &ray, 33 | Sampler &sampler, MemoryPool &pool) const override; 34 | }; 35 | 36 | #endif 37 | 38 | -------------------------------------------------------------------------------- /include/integrator/path_integrator.h: -------------------------------------------------------------------------------- 1 | #ifndef PATH_INTEGRATOR_H 2 | #define PATH_INTEGRATOR_H 3 | 4 | #include "surface_integrator.h" 5 | #include "renderer/renderer.h" 6 | 7 | /* 8 | * Surface integrator that uses Path tracing for computing illumination at a point on the surface 9 | */ 10 | class PathIntegrator : public SurfaceIntegrator { 11 | const int min_depth, max_depth; 12 | 13 | public: 14 | /* 15 | * Create the path tracing integrator and set the min and max depth for paths 16 | * rays are randomly stopped by Russian roulette after reaching min_depth and are stopped 17 | * at max_depth 18 | */ 19 | PathIntegrator(int min_depth, int max_depth); 20 | /* 21 | * Compute the illumination at a point on a surface in the scene 22 | */ 23 | Colorf illumination(const Scene &scene, const Renderer &renderer, const RayDifferential &ray, 24 | DifferentialGeometry &dg, Sampler &sampler, MemoryPool &pool) const override; 25 | }; 26 | 27 | #endif 28 | 29 | -------------------------------------------------------------------------------- /include/integrator/single_scattering_integrator.h: -------------------------------------------------------------------------------- 1 | #ifndef SINGLE_SCATTERING_INTEGRATOR_H 2 | #define SINGLE_SCATTERING_INTEGRATOR_H 3 | 4 | #include "volume_integrator.h" 5 | 6 | /* 7 | * The SingleScatteringIntegrator is a volume integrator that 8 | * accounts for absoprtion and emission effects of the volumes 9 | * being traversed along with in scattering effects 10 | * See: PBR 11 | */ 12 | class SingleScatteringIntegrator : public VolumeIntegrator { 13 | //Distance between samples to take of the volume when integrating 14 | float step_size; 15 | 16 | public: 17 | /* 18 | * Construct the single scattering integrator specifying the distance between 19 | * samples when integrating over the volumes in the scene 20 | */ 21 | SingleScatteringIntegrator(float step_size); 22 | /* 23 | * Compute the radiance arriving at the point p along the direction w 24 | * due to participating media in the scene. Also returns the transmittance 25 | * along the ray through transmit 26 | */ 27 | virtual Colorf radiance(const Scene &scene, const Renderer &renderer, const RayDifferential &ray, 28 | Sampler &sampler, MemoryPool &pool, Colorf &transmit) const override; 29 | /* 30 | * Compute the beam transmittance for line segment along the ray from min_t to max_t 31 | */ 32 | virtual Colorf transmittance(const Scene &scene, const Renderer &renderer, const RayDifferential &ray, 33 | Sampler &sampler, MemoryPool &pool) const override; 34 | }; 35 | 36 | #endif 37 | 38 | 39 | -------------------------------------------------------------------------------- /include/integrator/volume_integrator.h: -------------------------------------------------------------------------------- 1 | #ifndef VOLUME_INTEGRATOR_H 2 | #define VOLUME_INTEGRATOR_H 3 | 4 | #include "samplers/sampler.h" 5 | #include "renderer/renderer.h" 6 | #include "linalg/ray.h" 7 | #include "memory_pool.h" 8 | #include "film/color.h" 9 | 10 | class Scene; 11 | 12 | /* 13 | * Interface for volume integrators to implement 14 | */ 15 | class VolumeIntegrator { 16 | public: 17 | /* 18 | * Compute the radiance arriving at the point p along the direction w 19 | * due to participating media in the scene. Also returns the transmittance 20 | * along the ray through transmit 21 | */ 22 | virtual Colorf radiance(const Scene &scene, const Renderer &renderer, const RayDifferential &ray, 23 | Sampler &sampler, MemoryPool &pool, Colorf &transmit) const = 0; 24 | /* 25 | * Compute the beam transmittance for line segment along the ray from min_t to max_t 26 | */ 27 | virtual Colorf transmittance(const Scene &scene, const Renderer &renderer, const RayDifferential &ray, 28 | Sampler &sampler, MemoryPool &pool) const = 0; 29 | }; 30 | 31 | #endif 32 | 33 | -------------------------------------------------------------------------------- /include/integrator/whitted_integrator.h: -------------------------------------------------------------------------------- 1 | #ifndef WHITTED_INTEGRATOR_H 2 | #define WHITTED_INTEGRATOR_H 3 | 4 | #include "surface_integrator.h" 5 | #include "renderer/renderer.h" 6 | 7 | /* 8 | * Surface integrator that uses Whitted recursive ray tracing algorithm 9 | * for computing illumination at a point on the surface 10 | */ 11 | class WhittedIntegrator : public SurfaceIntegrator { 12 | const int max_depth; 13 | 14 | public: 15 | /* 16 | * Create the Whitted integrator and set the max recursion depth for rays 17 | */ 18 | WhittedIntegrator(int max_depth); 19 | /* 20 | * Compute the illumination at a point on a surface in the scene 21 | */ 22 | Colorf illumination(const Scene &scene, const Renderer &renderer, const RayDifferential &ray, 23 | DifferentialGeometry &dg, Sampler &sampler, MemoryPool &pool) const override; 24 | }; 25 | 26 | #endif 27 | 28 | -------------------------------------------------------------------------------- /include/lights/area_light.h: -------------------------------------------------------------------------------- 1 | #ifndef AREA_LIGHT_H 2 | #define AREA_LIGHT_H 3 | 4 | #include 5 | #include "light.h" 6 | 7 | class Geometry; 8 | 9 | /* 10 | * Diffuse area light that can be attached to some arbitrary geometry 11 | * to create an emissive version of the object 12 | * TODO: Currently only spheres are supported 13 | */ 14 | class AreaLight : public Light { 15 | Colorf emit; 16 | Geometry *geometry; 17 | float surface_area; 18 | 19 | public: 20 | /* 21 | * Construct the area light with some transformation to be applied to 22 | * the sphere it's emitting from 23 | */ 24 | AreaLight(const Transform &to_world, const Colorf &emit, Geometry *geometry, int n_samples = 6); 25 | /* 26 | * Compute the light's emitted radiance in the outgoing direction, w 27 | * from the point on the surface with normal n 28 | */ 29 | Colorf radiance(const Point &p, const Normal &n, const Vector &w) const; 30 | /* 31 | * Sample the illumination from the light arriving at the point 32 | * returns the color along with the incident light direction for the point, 33 | * the PDF of the point that was sampled and fills out the occlusion tester 34 | * for shadow testing if the color returned wasn't black 35 | */ 36 | Colorf sample(const Point &p, const LightSample &lsample, Vector &w_i, 37 | float &pdf_val, OcclusionTester &occlusion) const override; 38 | /* 39 | * Sample a light ray from the distribution of rays leaving the light 40 | * returning the ray, normal on the light surface where the ray was emitted 41 | * and pdf 42 | */ 43 | Colorf sample(const Scene &scene, const LightSample &lsample, const std::array &b, 44 | Ray &ray, Normal &normal, float &pdf_val) const override; 45 | /* 46 | * Compute the total power emitted by the light in the scene 47 | */ 48 | Colorf power(const Scene &scene) const override; 49 | /* 50 | * Check if this light is a delta light, eg. a point or directional light 51 | */ 52 | bool delta_light() const override; 53 | /* 54 | * Compute the PDF for sampling the point with the incident direction wi 55 | */ 56 | float pdf(const Point &p, const Vector &w_i) const override; 57 | }; 58 | 59 | #endif 60 | 61 | -------------------------------------------------------------------------------- /include/lights/light.h: -------------------------------------------------------------------------------- 1 | #ifndef LIGHT_H 2 | #define LIGHT_H 3 | 4 | #include 5 | #include "cache.h" 6 | #include "linalg/transform.h" 7 | #include "film/color.h" 8 | #include "occlusion_tester.h" 9 | 10 | class Scene; 11 | 12 | /* 13 | * Struct to easily pass light samples for choosing a light and then sampling the light 14 | */ 15 | struct LightSample { 16 | std::array u; 17 | float light; 18 | }; 19 | 20 | /* 21 | * Base class for lights, stores the transformation from world space to light space 22 | * and the number of samples that should be taken when sampling this light 23 | */ 24 | class Light { 25 | protected: 26 | //Transforms from light to world space and world to light space 27 | const Transform to_world, to_light; 28 | 29 | public: 30 | //Specify number of samples to take of the light when uniformly sampling all lights 31 | const int n_samples; 32 | 33 | /* 34 | * Create the light with some transformation to world space 35 | * and desired number of samples to be taken 36 | */ 37 | Light(const Transform &to_world, int n_samples = 1); 38 | /* 39 | * Sample the illumination from the light arriving at the point 40 | * returns the color along with the incident light direction for the point, 41 | * the PDF of the point that was sampled and fills out the occlusion tester 42 | * for shadow testing if the color returned wasn't black 43 | */ 44 | virtual Colorf sample(const Point &p, const LightSample &lsample, 45 | Vector &w_i, float &pdf_val, OcclusionTester &occlusion) const = 0; 46 | /* 47 | * Sample a light ray from the distribution of rays leaving the light 48 | * returning the ray, normal on the light surface where the ray was emitted 49 | * and pdf 50 | */ 51 | virtual Colorf sample(const Scene &scene, const LightSample &lsample, 52 | const std::array &b, Ray &ray, Normal &normal, float &pdf_val) const = 0; 53 | /* 54 | * Compute the total power emitted by the light in the scene 55 | */ 56 | virtual Colorf power(const Scene &scene) const = 0; 57 | /* 58 | * Check if this light is a delta light, eg. a point or directional light 59 | */ 60 | virtual bool delta_light() const = 0; 61 | /* 62 | * Compute the PDF for sampling the point with the incident direction wi 63 | */ 64 | virtual float pdf(const Point &p, const Vector &w_i) const = 0; 65 | }; 66 | 67 | typedef Cache LightCache; 68 | 69 | #endif 70 | 71 | -------------------------------------------------------------------------------- /include/lights/occlusion_tester.h: -------------------------------------------------------------------------------- 1 | #ifndef OCCLUSION_TESTER_H 2 | #define OCCLUSION_TESTER_H 3 | 4 | #include "linalg/point.h" 5 | #include "linalg/ray.h" 6 | #include "renderer/renderer.h" 7 | #include "samplers/sampler.h" 8 | #include "memory_pool.h" 9 | 10 | class Scene; 11 | 12 | /* 13 | * A structure used to setup and test lights for occlusion 14 | */ 15 | struct OcclusionTester { 16 | Ray ray; 17 | 18 | /* 19 | * Set the occlusion test to be performed between two points 20 | */ 21 | void set_points(const Point &a, const Point &b); 22 | /* 23 | * Set the occlusion test to be performed along a ray 24 | */ 25 | void set_ray(const Point &p, const Vector &d); 26 | /* 27 | * Test if the ray or segment set is occluded by some object in the scene 28 | */ 29 | bool occluded(const Scene &scene); 30 | /* 31 | * Compute the transmittance along the ray/segment that the occlusion test 32 | * was performed on 33 | */ 34 | Colorf transmittance(const Scene &scene, const Renderer &renderer, Sampler &sampler, 35 | MemoryPool &pool); 36 | }; 37 | 38 | #endif 39 | 40 | -------------------------------------------------------------------------------- /include/lights/point_light.h: -------------------------------------------------------------------------------- 1 | #ifndef POINT_LIGHT_H 2 | #define POINT_LIGHT_H 3 | 4 | #include "film/color.h" 5 | #include "light.h" 6 | 7 | /* 8 | * Describes a simple point light 9 | */ 10 | class PointLight : public Light { 11 | const Point position; 12 | const Colorf intensity; 13 | 14 | public: 15 | /* 16 | * Create a point light with some transformation to world space 17 | * and the desired intensity 18 | */ 19 | PointLight(const Transform &to_world, const Colorf &intensity); 20 | /* 21 | * Sample the illumination from the light arriving at the point 22 | * returns the color along with the incident light direction for the point, 23 | * the PDF of the point that was sampled and fills out the occlusion tester 24 | * for shadow testing if the color returned wasn't black 25 | */ 26 | Colorf sample(const Point &p, const LightSample &lsample, Vector &w_i, 27 | float &pdf_val, OcclusionTester &occlusion) const override; 28 | /* 29 | * Sample a light ray from the distribution of rays leaving the light 30 | * returning the ray, normal on the light surface where the ray was emitted 31 | * and pdf 32 | */ 33 | Colorf sample(const Scene &scene, const LightSample &lsample, const std::array &b, 34 | Ray &ray, Normal &normal, float &pdf_val) const override; 35 | /* 36 | * Compute the total power emitted by the light in the scene 37 | */ 38 | Colorf power(const Scene &scene) const override; 39 | /* 40 | * Check if this light is a delta light, eg. a point or directional light 41 | */ 42 | bool delta_light() const override; 43 | /* 44 | * Compute the PDF for sampling the point with the incident direction wi 45 | * since there's no chance of sampling the delta distribution randomly this 46 | * just returns 0 47 | */ 48 | float pdf(const Point &p, const Vector &w_i) const override; 49 | }; 50 | 51 | #endif 52 | 53 | -------------------------------------------------------------------------------- /include/linalg/animated_transform.h: -------------------------------------------------------------------------------- 1 | #ifndef ANIMATED_TRANSFORM_H 2 | #define ANIMATED_TRANSFORM_H 3 | 4 | #include 5 | #include "linalg/transform.h" 6 | #include "linalg/quaternion.h" 7 | 8 | /* 9 | * Animated transformation based on interpolating two transforms 10 | * between the given times, based on Shoemake and Duff (1992) and 11 | * PBR 12 | */ 13 | class AnimatedTransform { 14 | const float start_time, end_time; 15 | const bool animated; 16 | Transform start_transform, end_transform; 17 | //The decomposed start and end transformations 18 | std::array translation; 19 | std::array rotation; 20 | std::array scaling; 21 | 22 | public: 23 | /* 24 | * Create a transform that interpolates between the start and 25 | * end transformations over the start and end times 26 | */ 27 | AnimatedTransform(const Transform &start_transform, float start_time, 28 | const Transform &end_transform, float end_time); 29 | /* 30 | * Compute the interpolated transform at some point 31 | */ 32 | Transform interpolate(float t) const; 33 | void interpolate(float t, Transform &transform) const; 34 | /* 35 | * Compute the bounds the BBox covers when animated with this transformation 36 | */ 37 | BBox motion_bound(const BBox &b) const; 38 | Point operator()(float t, const Point &p) const; 39 | void operator()(float t, const Point &p, Point &out) const; 40 | Vector operator()(float t, const Vector &v) const; 41 | void operator()(float t, const Vector &v, Vector &out) const; 42 | Ray operator()(const Ray &r) const; 43 | void operator()(const Ray &r, Ray &out) const; 44 | RayDifferential operator()(const RayDifferential &r) const; 45 | void operator()(const RayDifferential &r, RayDifferential &out) const; 46 | 47 | private: 48 | /* 49 | * Decompose a transformation into its translation, rotation 50 | * and scaling components as descibed by Shoemake and Duff & PBR 51 | */ 52 | static void decompose(const Matrix4 &m, Vector &trans, Quaternion &rot, 53 | Matrix4 &scale); 54 | }; 55 | 56 | #endif 57 | 58 | -------------------------------------------------------------------------------- /include/linalg/matrix4.h: -------------------------------------------------------------------------------- 1 | #ifndef MATRIX4_H 2 | #define MATRIX4_H 3 | 4 | #include 5 | #include 6 | 7 | class Matrix4 { 8 | std::array mat; 9 | 10 | public: 11 | //Initializes the matrix to the identity 12 | Matrix4(); 13 | //Create from some existing matrix, m should be row-major 14 | Matrix4(const std::array &m); 15 | Matrix4 inverse() const; 16 | Matrix4 transpose() const; 17 | //Get the value of the matrix at row i, col j 18 | inline const float& at(size_t i, size_t j) const { 19 | return mat[4 * i + j]; 20 | } 21 | inline float& at(size_t i, size_t j){ 22 | return mat[4 * i + j]; 23 | } 24 | //Get a pointer to row i of the matrix 25 | inline const float* operator[](size_t i) const { 26 | return &mat[4 * i]; 27 | } 28 | inline float* operator[](size_t i){ 29 | return &mat[4 * i]; 30 | } 31 | bool operator==(const Matrix4 &m) const; 32 | bool operator!=(const Matrix4 &m) const; 33 | Matrix4 operator+(const Matrix4 &m) const; 34 | Matrix4 operator-(const Matrix4 &m) const; 35 | Matrix4 operator*(const Matrix4 &m) const; 36 | Matrix4 operator*(float s) const; 37 | Matrix4& operator+=(const Matrix4 &m); 38 | Matrix4& operator-=(const Matrix4 &m); 39 | Matrix4& operator*=(const Matrix4 &m); 40 | void print(std::ostream &os) const; 41 | }; 42 | 43 | Matrix4 operator*(float s, const Matrix4 &m); 44 | std::ostream& operator<<(std::ostream &os, const Matrix4 &m); 45 | 46 | #endif 47 | 48 | -------------------------------------------------------------------------------- /include/linalg/quaternion.h: -------------------------------------------------------------------------------- 1 | #ifndef QUATERNION_H 2 | #define QUATERNION_H 3 | 4 | #include 5 | #include "vector.h" 6 | #include "transform.h" 7 | 8 | struct Quaternion; 9 | inline Quaternion operator+(const Quaternion &a, const Quaternion &b); 10 | inline Quaternion operator-(const Quaternion &a, const Quaternion &b); 11 | inline Quaternion operator*(const Quaternion &a, const Quaternion &b); 12 | inline Quaternion operator*(const Quaternion &q, float f); 13 | inline Quaternion operator*(float f, const Quaternion &q); 14 | inline Quaternion operator/(const Quaternion &q, float f); 15 | 16 | /* 17 | * A Quaternion that can be operated on and converted into a transform 18 | */ 19 | struct Quaternion { 20 | Vector v; 21 | float w; 22 | 23 | Quaternion(const Vector &v = Vector{0, 0, 0}, float w = 0) : v(v), w(w) 24 | {} 25 | Quaternion(const Transform &t); 26 | /* 27 | * Spherically interpolate between this quaternion and another 28 | */ 29 | Quaternion slerp(const Quaternion &q, float t) const; 30 | /* 31 | * Compute the dot product of two quaternions 32 | */ 33 | inline float dot(const Quaternion &b) const { 34 | return v.dot(b.v) + w * b.w; 35 | } 36 | inline Quaternion normalized() const { 37 | return *this / std::sqrt(dot(*this)); 38 | } 39 | Transform to_transform() const; 40 | inline Quaternion& operator+=(const Quaternion &q){ 41 | v += q.v; 42 | w += q.w; 43 | return *this; 44 | } 45 | inline Quaternion& operator-=(const Quaternion &q){ 46 | v -= q.v; 47 | w -= q.w; 48 | return *this; 49 | } 50 | inline Quaternion& operator*=(const Quaternion &q){ 51 | v = v.cross(q.v) + w * q.v + q.w * v; 52 | w = w * q.w - v.dot(q.v); 53 | return *this; 54 | } 55 | inline Quaternion& operator*=(float f){ 56 | v *= f; 57 | w *= f; 58 | return *this; 59 | } 60 | inline Quaternion& operator/=(float f){ 61 | v /= f; 62 | w /= f; 63 | return *this; 64 | } 65 | }; 66 | 67 | inline Quaternion operator+(const Quaternion &a, const Quaternion &b){ 68 | return Quaternion{a.v + b.v, a.w + b.w}; 69 | } 70 | inline Quaternion operator-(const Quaternion &a, const Quaternion &b){ 71 | return Quaternion{a.v - b.v, a.w - b.w}; 72 | } 73 | inline Quaternion operator*(const Quaternion &a, const Quaternion &b){ 74 | return Quaternion{a.v.cross(b.v) + a.w * b.v + b.w * a.v, 75 | a.w * b.w - a.v.dot(b.v)}; 76 | } 77 | inline Quaternion operator*(const Quaternion &q, float f){ 78 | return Quaternion{q.v * f, q.w * f}; 79 | } 80 | inline Quaternion operator*(float f, const Quaternion &q){ 81 | return Quaternion{q.v * f, q.w * f}; 82 | } 83 | inline Quaternion operator/(const Quaternion &q, float f){ 84 | return Quaternion{q.v / f, q.w / f}; 85 | } 86 | 87 | #endif 88 | 89 | -------------------------------------------------------------------------------- /include/linalg/ray.h: -------------------------------------------------------------------------------- 1 | #ifndef RAY_H 2 | #define RAY_H 3 | 4 | #include 5 | #include 6 | #include "vector.h" 7 | #include "point.h" 8 | 9 | class DifferentialGeometry; 10 | 11 | /* 12 | * A ray in 3D space starting at o and in direction d 13 | */ 14 | struct Ray { 15 | Point o; 16 | Vector d; 17 | //min/max t range of the ray 18 | float min_t, max_t; 19 | //recursion depth of this ray, needed for some algorithms 20 | int depth; 21 | float time; 22 | 23 | inline Ray(const Point &o = Point{}, const Vector &d = Vector{}, float min_t = 0, 24 | float max_t = std::numeric_limits::infinity(), int depth = 0, float time = 0) 25 | : o(o), d(d), min_t(min_t), max_t(max_t), depth(depth), time(time) 26 | {} 27 | /* 28 | * Use to indicate that some ray has spawned this one, 29 | * increasing the recursion depth 30 | */ 31 | inline Ray(const Point &o, const Vector &d, const Ray &parent, float min_t = 0, 32 | float max_t = std::numeric_limits::infinity()) 33 | : o(o), d(d), min_t(min_t), max_t(max_t), depth(parent.depth + 1), time(parent.time) 34 | {} 35 | //Get a point at some t along the ray 36 | inline Point operator()(float t) const { 37 | return o + t * d; 38 | } 39 | }; 40 | inline std::ostream& operator<<(std::ostream &os, const Ray &r){ 41 | os << "r ( o = " << r.o << ", d = " << r.d 42 | << ", t = { " << r.min_t << ", " << r.max_t 43 | << "}, depth = " << r.depth << ")"; 44 | return os; 45 | } 46 | 47 | /* 48 | * A ray + 2 offset rays (x, y) for use in computing texture anti-aliasing 49 | */ 50 | struct RayDifferential : Ray { 51 | //Rays one sample over in x/y 52 | Ray rx, ry; 53 | 54 | inline RayDifferential(const Point &o = Point{}, const Vector &d = Vector{}, float min_t = 0, 55 | float max_t = std::numeric_limits::infinity(), int depth = 0, float time = 0) 56 | : Ray(o, d, min_t, max_t, depth, time) 57 | {} 58 | /* 59 | * Use to indicate that some ray has spawned this one, 60 | * increasing the recursion depth 61 | */ 62 | inline RayDifferential(const Point &o, const Vector &d, const Ray &parent, float min_t = 0, 63 | float max_t = std::numeric_limits::infinity()) 64 | : Ray(o, d, parent, min_t, max_t) 65 | {} 66 | inline explicit RayDifferential(const Ray &r) : Ray{r} {} 67 | inline void scale_differentials(float s){ 68 | rx.o = o + (rx.o - o) * s; 69 | ry.o = o + (ry.o - o) * s; 70 | rx.d = d + (rx.d - d) * s; 71 | ry.d = d + (ry.d - d) * s; 72 | } 73 | inline bool has_differentials() const { 74 | return rx.d.length_sqr() > 0 && ry.d.length_sqr() > 0; 75 | } 76 | }; 77 | inline std::ostream& operator<<(std::ostream &os, const RayDifferential &r){ 78 | os << "rdiff ( " << static_cast(r) << "\n\trx = " 79 | << r.rx << "\n\try = " << r.ry << " )"; 80 | return os; 81 | } 82 | 83 | #endif 84 | 85 | -------------------------------------------------------------------------------- /include/loaders/async_loader.h: -------------------------------------------------------------------------------- 1 | #ifndef ASYNC_LOADER_H 2 | #define ASYNC_LOADER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | /* 9 | * An asynchronous resource loader: takes a task name, 10 | * the loading function (which should return bool indicating task status) 11 | * and the args for that function 12 | * You can wait for the tasks to finish via the wait function 13 | */ 14 | class AsyncLoader { 15 | std::unordered_map> tasks; 16 | 17 | public: 18 | /* 19 | * Add a task to be executed asynchronously and monitored 20 | * by the async loader 21 | */ 22 | template 23 | void run_task(const std::string &name, F &&f, Args&&... args){ 24 | tasks[name] = std::async(std::launch::async, std::forward(f), std::forward(args)...); 25 | } 26 | /* 27 | * Wait for all tasks to be completed and report their status 28 | */ 29 | inline void wait(){ 30 | for (auto &task : tasks){ 31 | if (task.second.get()){ 32 | std::cout << "Task " << task.first << " completed successfully" << std::endl; 33 | } 34 | else { 35 | std::cout << "Task " << task.first << " failed to complete" << std::endl; 36 | } 37 | } 38 | } 39 | }; 40 | 41 | #endif 42 | 43 | -------------------------------------------------------------------------------- /include/loaders/load_filter.h: -------------------------------------------------------------------------------- 1 | #ifndef LOAD_FILTER_H 2 | #define LOAD_FILTER_H 3 | 4 | #include 5 | #include 6 | #include "filters/filter.h" 7 | 8 | /* 9 | * Load the filter for the scene from the tag 10 | * XMLElement passed should be the element for the tag 11 | */ 12 | std::unique_ptr load_filter(tinyxml2::XMLElement *elem); 13 | 14 | #endif 15 | 16 | -------------------------------------------------------------------------------- /include/loaders/load_light.h: -------------------------------------------------------------------------------- 1 | #ifndef LOAD_LIGHT_H 2 | #define LOAD_LIGHT_H 3 | 4 | #include 5 | #include "lights/light.h" 6 | 7 | /* 8 | * Load all the light information into the light cache. 9 | * elem should be the first light XML element in the file 10 | */ 11 | void load_lights(tinyxml2::XMLElement *elem, LightCache &cache); 12 | 13 | #endif 14 | 15 | -------------------------------------------------------------------------------- /include/loaders/load_material.h: -------------------------------------------------------------------------------- 1 | #ifndef LOAD_MATERIAL_H 2 | #define LOAD_MATERIAL_H 3 | 4 | #include 5 | #include "material/material.h" 6 | #include "textures/texture.h" 7 | 8 | /* 9 | * Load all the material information into the material cache. elem should 10 | * be the first material XML element in the file 11 | * We do this in a pre-pass to avoid having to store the material 12 | * names on the nodes and do a post-load pass to set up the node 13 | * materials like is done in Cem's loading code, due to how the XML 14 | * file is layed out with materials coming after objects 15 | * Loaded textures for the materials will be placed in the texture cache 16 | */ 17 | void load_materials(tinyxml2::XMLElement *elem, MaterialCache &cache, TextureCache &tcache, 18 | const std::string &file); 19 | 20 | #endif 21 | 22 | -------------------------------------------------------------------------------- /include/loaders/load_renderer.h: -------------------------------------------------------------------------------- 1 | #ifndef LOAD_RENDERER_H 2 | #define LOAD_RENDERER_H 3 | 4 | #include 5 | #include 6 | #include "renderer/renderer.h" 7 | #include "integrator/surface_integrator.h" 8 | #include "integrator/volume_integrator.h" 9 | 10 | /* 11 | * Load the surface integrator from the renderer child of the config element passed 12 | */ 13 | std::unique_ptr load_surface_integrator(tinyxml2::XMLElement *elem); 14 | /* 15 | * Load the volume integrator from the vol_integrator child of the config element passed 16 | */ 17 | std::unique_ptr load_volume_integrator(tinyxml2::XMLElement *elem); 18 | 19 | #endif 20 | 21 | -------------------------------------------------------------------------------- /include/loaders/load_sampler.h: -------------------------------------------------------------------------------- 1 | #ifndef LOAD_SAMPLER_H 2 | #define LOAD_SAMPLER_H 3 | 4 | #include 5 | #include 6 | #include "samplers/sampler.h" 7 | 8 | /* 9 | * Load sampler configuration from the config tag passed. 10 | * Sampler information is loaded from the tag 11 | * also takes the image width and height to set the sampler to sample 12 | */ 13 | std::unique_ptr load_sampler(tinyxml2::XMLElement *elem, size_t w, size_t h); 14 | 15 | #endif 16 | 17 | -------------------------------------------------------------------------------- /include/loaders/load_scene.h: -------------------------------------------------------------------------------- 1 | #ifndef LOAD_SCENE_H 2 | #define LOAD_SCENE_H 3 | 4 | #include 5 | #include 6 | #include "linalg/vector.h" 7 | #include "linalg/transform.h" 8 | #include "geometry/geometry.h" 9 | #include "scene.h" 10 | 11 | #ifdef _WIN32 12 | const char PATH_SEP = '\\'; 13 | #else 14 | const char PATH_SEP = '/'; 15 | #endif 16 | 17 | /* 18 | * Load a scene as described by the XML document and return it and 19 | * set its max ray recursion depth to the desired value 20 | * Based off of Cem's load scene utility but migrated to TinyXML-2 21 | */ 22 | Scene load_scene(const std::string &file); 23 | /* 24 | * Read the x,y,z attributes of the XMLElement and return it 25 | */ 26 | void read_vector(tinyxml2::XMLElement *elem, Vector &v); 27 | /* 28 | * Read the r,g,b attributes of the XMLElement and return it 29 | */ 30 | void read_color(tinyxml2::XMLElement *elem, Colorf &c); 31 | /* 32 | * Read the x,y,z attributes of the XMLElement and return it 33 | */ 34 | void read_point(tinyxml2::XMLElement *elem, Point &p); 35 | /* 36 | * Read a transform from the scale, rotate and translate elements 37 | * which should be children of the element passed 38 | */ 39 | void read_transform(tinyxml2::XMLElement *elem, Transform &t); 40 | /* 41 | * Read the float attribute of the XMLElement and return it 42 | * optionally passing the attribute name to read from. Default is value 43 | */ 44 | void read_float(tinyxml2::XMLElement *elem, float &f, const std::string &attrib = "value"); 45 | /* 46 | * Read the int attribute of the XMLElement and return it 47 | * optionally passing the attribute name to read from. Default is value 48 | */ 49 | void read_int(tinyxml2::XMLElement *elem, int &i, const std::string &attrib = "value"); 50 | 51 | #endif 52 | 53 | -------------------------------------------------------------------------------- /include/loaders/load_texture.h: -------------------------------------------------------------------------------- 1 | #ifndef LOAD_TEXTURE_H 2 | #define LOAD_TEXTURE_H 3 | 4 | #include 5 | #include 6 | #include "textures/texture.h" 7 | 8 | /* 9 | * Load the texture for the XML element into the texture cache 10 | * and return a non-owning pointer to it. If the item is 11 | * already in the cache the existing entry will be returned 12 | * name specifies the material name, to be used for prefixing 13 | * generated texture names 14 | * Also takes the XML file name so we can construct paths to the textures 15 | */ 16 | Texture* load_texture(tinyxml2::XMLElement *elem, const std::string &mat_name, 17 | TextureCache &cache, const std::string &file); 18 | /* 19 | * Load the texture color as a constant texture by computing the RGB color 20 | * from the sampled spectrum data in the spd file. spd files are from PBRT, 21 | * the ones supported (metals only) can be found at https://github.com/mmp/pbrt-v2/tree/master/scenes/spds/metals 22 | */ 23 | Texture* load_spd(tinyxml2::XMLElement *elem, const std::string &mat_name, 24 | TextureCache &cache, const std::string &file); 25 | 26 | #endif 27 | 28 | -------------------------------------------------------------------------------- /include/loaders/load_volume.h: -------------------------------------------------------------------------------- 1 | #ifndef LOAD_VOLUMES_H 2 | #define LOAD_VOLUMES_H 3 | 4 | #include 5 | #include 6 | #include "scene.h" 7 | #include "volume/volume.h" 8 | #include "volume/volume_node.h" 9 | 10 | /* 11 | * Load the volume specified by the element passed, returns nullptr if loading failed 12 | */ 13 | Volume* load_volume(tinyxml2::XMLElement *elem, VolumeCache &cache, const std::string &scene_file); 14 | /* 15 | * Load the volume node at the element 16 | * will also push the volume's transform onto the stack 17 | */ 18 | void load_volume_node(tinyxml2::XMLElement *elem, Scene &scene, std::stack &transform_stack, 19 | const std::string &file); 20 | 21 | #endif 22 | 23 | -------------------------------------------------------------------------------- /include/material/anisotropic_distribution.h: -------------------------------------------------------------------------------- 1 | #ifndef ANISOTROPIC_DISTRIBUTION_H 2 | #define ANISOTROPIC_DISTRIBUTION_H 3 | 4 | #include "microfacet_distribution.h" 5 | 6 | /* 7 | * Implementation of the anisotropic microfacet distribution described by 8 | * Ashikhmin and Shirley 9 | */ 10 | class AnisotropicDistribution : public MicrofacetDistribution { 11 | //The exponents for microfacets exactly along the x/y axes 12 | float exp_x, exp_y; 13 | 14 | public: 15 | /* 16 | * Create the anisotropic distribution specifying the exponents for microfacets 17 | * oriented exactly along the x and y axes 18 | */ 19 | AnisotropicDistribution(float exp_x, float exp_y); 20 | /* 21 | * Compute the probability density for microfacets to be 22 | * oriented with normal = w_h in this distribution 23 | */ 24 | float operator()(const Vector &w_h) const override; 25 | /* 26 | * Sample the distribution for some outgoing direction, returning the incident 27 | * direction and the PDF for this pair of vectors 28 | */ 29 | void sample(const Vector &w_o, Vector &w_i, const std::array &u, float &pdf_val) const override; 30 | /* 31 | * Sample the PDF of the distribution for some pair of directions 32 | */ 33 | float pdf(const Vector &w_o, const Vector &w_i) const override; 34 | 35 | private: 36 | /* 37 | * Sample the first quadrant of the unit hemisphere for the distribution 38 | */ 39 | void sample_first_quadrant(float u_0, float u_1, float &phi, float &cos_theta) const; 40 | }; 41 | 42 | #endif 43 | 44 | -------------------------------------------------------------------------------- /include/material/blinn_distribution.h: -------------------------------------------------------------------------------- 1 | #ifndef BLINN_DISTRIBUTION_H 2 | #define BLINN_DISTRIBUTION_H 3 | 4 | #include "microfacet_distribution.h" 5 | 6 | /* 7 | * Implementation of the microfacet distribution described by Blinn 8 | */ 9 | class BlinnDistribution : public MicrofacetDistribution { 10 | float exponent; 11 | 12 | public: 13 | /* 14 | * Create the microfacet distribution with some exponent, the higher 15 | * the exponent the more shiny the surface appears 16 | */ 17 | BlinnDistribution(float exponent); 18 | /* 19 | * Compute the probability density for microfacets to be 20 | * oriented with normal = w_h in this distribution 21 | */ 22 | float operator()(const Vector &w_h) const override; 23 | /* 24 | * Sample the distribution for some outgoing direction, returning the incident 25 | * direction and the PDF for this pair of vectors 26 | */ 27 | void sample(const Vector &w_o, Vector &w_i, const std::array &u, float &pdf_val) const override; 28 | /* 29 | * Sample the PDF of the distribution for some pair of directions 30 | */ 31 | float pdf(const Vector &w_o, const Vector &w_i) const override; 32 | }; 33 | 34 | #endif 35 | 36 | -------------------------------------------------------------------------------- /include/material/btdf_adapter.h: -------------------------------------------------------------------------------- 1 | #ifndef BTDF_ADAPTER_H 2 | #define BTDF_ADAPTER_H 3 | 4 | #include "bxdf.h" 5 | 6 | /* 7 | * Take an existing BRDF model and use it as a transmissive model, 8 | * or the other way around. This adapter flips the reflection and transmission 9 | * type flags and flips incident vectors to the other hemisphere 10 | */ 11 | class BTDFAdapter : public BxDF { 12 | BxDF *bxdf; 13 | 14 | public: 15 | /* 16 | * Create the adapter to adapt the existing BxDF model from reflection to transmission 17 | * or vice-versa 18 | */ 19 | BTDFAdapter(BxDF *b); 20 | /* 21 | * Compute the value of the BxDF for some incident and outgoing directions 22 | */ 23 | Colorf operator()(const Vector &w_o, const Vector &w_i) const override; 24 | /* 25 | * Sample the BxDFs value for some outgoing direction using the random values 26 | * passed and returning the incident light direction 27 | */ 28 | Colorf sample(const Vector &w_o, Vector &w_i, const std::array &samples, float &pdf_val) const override; 29 | /* 30 | * Compute the hemispherical-directional reflectance function using the samples passed 31 | */ 32 | Colorf rho_hd(const Vector &w_o, const std::array *samples, int n_samples) const override; 33 | /* 34 | * Compute the hemispherical-hemispherical reflectance function using the samples passed 35 | * samples_a and samples_b should contain the same number of samples 36 | */ 37 | Colorf rho_hh(const std::array *samples_a, const std::array *samples_b, 38 | int n_samples) const override; 39 | /* 40 | * Compute the probability density function for sampling the directions passed 41 | */ 42 | float pdf(const Vector &w_o, const Vector &w_i) const override; 43 | /* 44 | * Flip the vector to the other hemisphere in shading space 45 | */ 46 | static Vector flip_hemisphere(const Vector &v); 47 | }; 48 | 49 | #endif 50 | 51 | -------------------------------------------------------------------------------- /include/material/fresnel.h: -------------------------------------------------------------------------------- 1 | #ifndef FRESNEL_H 2 | #define FRESNEL_H 3 | 4 | #include "film/color.h" 5 | 6 | /* 7 | * Implementation of BxDF motivated by PBR 8 | */ 9 | /* 10 | * Compute the Fresnel reflectance term for dielectric materials 11 | */ 12 | Colorf fresnel_dielectric(float cos_i, float cos_t, float eta_i, float eta_t); 13 | /* 14 | * Compute the Fresnel reflectance term for conductive materials 15 | */ 16 | Colorf fresnel_conductor(float cos_i, const Colorf &eta, const Colorf &k); 17 | 18 | /* 19 | * Interface for computing Fresnel terms generically 20 | */ 21 | class Fresnel { 22 | public: 23 | /* 24 | * Evaluate the Fresnel reflectance term for light incident along some angle 25 | */ 26 | virtual Colorf operator()(float cos_i) const = 0; 27 | }; 28 | 29 | /* 30 | * Computes the Fresnel reflectance term for dielectrics using fresnel_dielectric 31 | */ 32 | class FresnelDielectric : public Fresnel { 33 | public: 34 | const float eta_i, eta_t; 35 | 36 | /* 37 | * Construct the Fresnel dielectric evaluator for the interface 38 | * between two materials 39 | */ 40 | FresnelDielectric(float eta_i, float eta_t); 41 | /* 42 | * Evaluate the Fresnel reflectance term for light incident along some angle 43 | */ 44 | Colorf operator()(float cos_i) const override; 45 | }; 46 | 47 | /* 48 | * Computes the Fresnel reflectance term for conductors using fresnel_conductor 49 | */ 50 | class FresnelConductor : public Fresnel { 51 | public: 52 | const Colorf eta, k; 53 | 54 | /* 55 | * Construct the Fresnel conductor evaluator for the conductor being hit 56 | */ 57 | FresnelConductor(const Colorf &eta, const Colorf &k); 58 | /* 59 | * Evaluate the Fresnel reflectance term for light incident along some angle 60 | */ 61 | Colorf operator()(float cos_i) const override; 62 | }; 63 | 64 | /* 65 | * No-op Fresnel, simply returns 1 66 | */ 67 | class FresnelNoOp : public Fresnel { 68 | public: 69 | /* 70 | * Evaluate the Fresnel reflectance term for light incident along some angle 71 | */ 72 | Colorf operator()(float cos_i) const override; 73 | }; 74 | 75 | /* 76 | * FresnelFlip takes the Fresnel value computed by the one passed and returns 77 | * 1 - fresnel_value, eg. given a reflective Fresnel value this will return the 78 | * transmission Fresnel value 79 | */ 80 | class FresnelFlip : public Fresnel { 81 | const Fresnel *fresnel; 82 | 83 | public: 84 | FresnelFlip(const Fresnel *fresnel); 85 | /* 86 | * Evaluate the Fresnel reflectance term for light incident along some angle 87 | */ 88 | Colorf operator()(float cos_i) const override; 89 | }; 90 | 91 | #endif 92 | 93 | -------------------------------------------------------------------------------- /include/material/glass_material.h: -------------------------------------------------------------------------------- 1 | #ifndef GLASS_MATERIAL_H 2 | #define GLASS_MATERIAL_H 3 | 4 | #include "textures/texture.h" 5 | #include "material.h" 6 | 7 | /* 8 | * A material describing perfectly smooth glass with specular reflection 9 | * and transmission 10 | */ 11 | class GlassMaterial : public Material { 12 | const Texture *reflect, *transmit; 13 | const float refr_index; 14 | 15 | public: 16 | /* 17 | * Create the glass material, specifying the colors and refractive index 18 | */ 19 | GlassMaterial(const Texture *reflect, const Texture *transmit, float refr_index); 20 | /* 21 | * Get the BSDF to compute the shading for the material at this 22 | * piece of geometry 23 | */ 24 | BSDF* get_bsdf(const DifferentialGeometry &dg, MemoryPool &pool) const override; 25 | }; 26 | 27 | #endif 28 | 29 | -------------------------------------------------------------------------------- /include/material/lambertian.h: -------------------------------------------------------------------------------- 1 | #ifndef LAMBERTIAN_H 2 | #define LAMBERTIAN_H 3 | 4 | #include "bxdf.h" 5 | 6 | /* 7 | * BRDF describing the Lambertian diffuse reflectance model 8 | */ 9 | class Lambertian : public BxDF { 10 | Colorf reflectance; 11 | 12 | public: 13 | /* 14 | * Create a Lambertian BRDF with some color 15 | */ 16 | Lambertian(const Colorf &reflectance); 17 | /* 18 | * Compute the value of the BxDF for some incident and outgoing directions 19 | */ 20 | Colorf operator()(const Vector &w_o, const Vector &w_i) const override; 21 | /* 22 | * Compute the hemispherical-directional reflectance function using the samples passed 23 | */ 24 | Colorf rho_hd(const Vector &w_o, const std::array *samples, int n_samples) const override; 25 | /* 26 | * Compute the hemispherical-hemispherical reflectance function using the samples passed 27 | */ 28 | Colorf rho_hh(const std::array *samples_a, const std::array *samples_b, 29 | int n_samples) const override; 30 | }; 31 | 32 | #endif 33 | 34 | -------------------------------------------------------------------------------- /include/material/material.h: -------------------------------------------------------------------------------- 1 | #ifndef MATERIAL_H 2 | #define MATERIAL_H 3 | 4 | #include 5 | #include "geometry/differential_geometry.h" 6 | #include "cache.h" 7 | #include "memory_pool.h" 8 | #include "bsdf.h" 9 | 10 | /* 11 | * Base interface for materials to implement, should return the BSDF that 12 | * describes the material properties at the point 13 | */ 14 | class Material { 15 | public: 16 | /* 17 | * Get the BSDF to compute the shading for the material at this 18 | * piece of geometry. Allocation of the BxDFs and BSDF will be done in the 19 | * memory pool passed 20 | */ 21 | virtual BSDF* get_bsdf(const DifferentialGeometry &dg, MemoryPool &pool) const = 0; 22 | }; 23 | 24 | typedef Cache MaterialCache; 25 | 26 | #endif 27 | 28 | -------------------------------------------------------------------------------- /include/material/matte_material.h: -------------------------------------------------------------------------------- 1 | #ifndef MATTE_MATERIAL_H 2 | #define MATTE_MATERIAL_H 3 | 4 | #include "textures/texture.h" 5 | #include "material.h" 6 | 7 | /* 8 | * A material describing a purely diffuse surface, parameterized by 9 | * its diffuse color and a roughness value 10 | */ 11 | class MatteMaterial : public Material { 12 | const Texture *diffuse; 13 | const float roughness; 14 | 15 | public: 16 | /* 17 | * Create the matte material specifying the texture to use for diffuse color 18 | * and a scalar roughness value (TODO roughness can be a texture) 19 | */ 20 | MatteMaterial(const Texture *diffuse, float roughness); 21 | /* 22 | * Get the BSDF to compute the shading for the material at this 23 | * piece of geometry 24 | */ 25 | BSDF* get_bsdf(const DifferentialGeometry &dg, MemoryPool &pool) const override; 26 | }; 27 | 28 | #endif 29 | 30 | -------------------------------------------------------------------------------- /include/material/merl_brdf.h: -------------------------------------------------------------------------------- 1 | #ifndef MERL_BRDF_H 2 | #define MERL_BRDF_H 3 | 4 | #include 5 | #include "linalg/util.h" 6 | #include "bxdf.h" 7 | 8 | /* 9 | * BRDF that uses measured data from "A Data-Driven Reflectance Model", 10 | * by Wojciech Matusik, Hanspeter Pfister, Matt Brand and Leonard McMillan, 11 | * in ACM Transactions on Graphics 22, 3(2003), 759-769. 12 | * 13 | * to model the surface reflection properties. Implemnentation based on 14 | * the method introduced in PBR 15 | */ 16 | class MerlBRDF : public BxDF { 17 | //The measured brdf data in regular halfangle format 18 | const std::vector &brdf; 19 | const int n_theta_h, n_theta_d, n_phi_d; 20 | 21 | public: 22 | /* 23 | * Create the Merl BRDF using some loaded data for modeling the BRDF 24 | */ 25 | MerlBRDF(const std::vector &brdf, int n_theta_h, int n_theta_d, int n_phi_d); 26 | /* 27 | * Compute the value of the BxDF for some incident and outgoing directions 28 | */ 29 | Colorf operator()(const Vector &w_o, const Vector &w_i) const override; 30 | 31 | private: 32 | /* 33 | * Remap values from the angular value to the index in the MERL BRDF data table 34 | */ 35 | static constexpr inline int map_index(float val, float max, int n_vals){ 36 | return clamp(static_cast(val / max * n_vals), 0, n_vals - 1); 37 | } 38 | }; 39 | 40 | #endif 41 | 42 | -------------------------------------------------------------------------------- /include/material/merl_material.h: -------------------------------------------------------------------------------- 1 | #ifndef MERL_MATERIAL_H 2 | #define MERL_MATERIAL_H 3 | 4 | #include 5 | #include 6 | #include "material.h" 7 | 8 | /* 9 | * Material that uses measured data from "A Data-Driven Reflectance Model", 10 | * by Wojciech Matusik, Hanspeter Pfister, Matt Brand and Leonard McMillan, 11 | * in ACM Transactions on Graphics 22, 3(2003), 759-769. 12 | * 13 | * to model the surface reflection properties. Implemnentation based on 14 | * the method introduced in PBR 15 | */ 16 | class MerlMaterial : public Material { 17 | int n_theta_h, n_theta_d, n_phi_d; 18 | //The measured brdf data in regular halfangle format 19 | std::vector brdf; 20 | 21 | public: 22 | /* 23 | * Create the measured material, loading the BRDF data from some MERL 24 | * binary BRDF file 25 | */ 26 | MerlMaterial(const std::string &file); 27 | /* 28 | * Get the BSDF to compute the shading for the material at this 29 | * piece of geometry. Allocation of the BxDFs and BSDF will be done in the 30 | * memory pool passed 31 | */ 32 | BSDF* get_bsdf(const DifferentialGeometry &dg, MemoryPool &pool) const override; 33 | 34 | private: 35 | /* 36 | * Load the MERL BRDF data from a file, returns true if successful 37 | * Implemented as described in PBR 38 | */ 39 | bool load_brdf(const std::string &file); 40 | }; 41 | 42 | #endif 43 | 44 | -------------------------------------------------------------------------------- /include/material/metal_material.h: -------------------------------------------------------------------------------- 1 | #ifndef METAL_MATERIAL_H 2 | #define METAL_MATERIAL_H 3 | 4 | #include "textures/texture.h" 5 | #include "material.h" 6 | 7 | /* 8 | * A material that models a metal surface, described by its 9 | * refractive index and absorption coefficient 10 | */ 11 | class MetalMaterial : public Material { 12 | const Texture *refr_index, *absoption_coef; 13 | const float rough_x, rough_y; 14 | 15 | public: 16 | /* 17 | * Create the metal, specifying the textures to be used for its attributes 18 | * To create an anisotropic material specify both rough_x and rough_y, for a 19 | * non-aniostropic material only specify rough_x 20 | */ 21 | MetalMaterial(const Texture *refr_index, const Texture *absoption_coef, float rough_x, float rough_y = -1); 22 | /* 23 | * Get the BSDF to compute the shading for the material at this 24 | * piece of geometry 25 | */ 26 | BSDF* get_bsdf(const DifferentialGeometry &dg, MemoryPool &pool) const override; 27 | }; 28 | 29 | #endif 30 | 31 | -------------------------------------------------------------------------------- /include/material/microfacet_distribution.h: -------------------------------------------------------------------------------- 1 | #ifndef MICROFACET_DISTRIBUTION_H 2 | #define MICROFACET_DISTRIBUTION_H 3 | 4 | #include "linalg/vector.h" 5 | 6 | /* 7 | * Describes the distribution of microfacets on a surface for microfacet 8 | * based reflectance models 9 | */ 10 | class MicrofacetDistribution { 11 | public: 12 | /* 13 | * Compute the probability density for microfacets to be 14 | * oriented with normal = w_h in this distribution 15 | */ 16 | virtual float operator()(const Vector &w_h) const = 0; 17 | /* 18 | * Sample the distribution for some outgoing direction, returning the incident 19 | * direction and the PDF for this pair of vectors 20 | */ 21 | virtual void sample(const Vector &w_o, Vector &w_i, const std::array &u, float &pdf_val) const = 0; 22 | /* 23 | * Sample the PDF of the distribution for some pair of directions 24 | */ 25 | virtual float pdf(const Vector &w_o, const Vector &w_i) const = 0; 26 | /* 27 | * Compute the geometric attenuation term for the distribution 28 | * for the outgoing and incident directions for the microfacets 29 | * with normal = w_h 30 | */ 31 | static float geom_atten(const Vector &w_o, const Vector &w_i, const Vector &w_h); 32 | }; 33 | 34 | #endif 35 | 36 | -------------------------------------------------------------------------------- /include/material/mix_material.h: -------------------------------------------------------------------------------- 1 | #ifndef MIX_MATERIAL_H 2 | #define MIX_MATERIAL_H 3 | 4 | #include "textures/texture.h" 5 | #include "material/material.h" 6 | 7 | /* 8 | * Material that mixes the output of two other materials by some color 9 | * scaling factor 10 | */ 11 | class MixMaterial : public Material { 12 | const Material *mat_a, *mat_b; 13 | const Texture *scale; 14 | 15 | public: 16 | /* 17 | * Specify the two materials to be mixed and the scale level for material a, 18 | * material b is scaled by (1 - scale) 19 | */ 20 | MixMaterial(const Material *mat_a, const Material *mat_b, const Texture *scale); 21 | /* 22 | * Get the BSDF to compute the shading for the material at this 23 | * piece of geometry. Allocation of the BxDFs and BSDF will be done in the 24 | * memory pool passed 25 | */ 26 | BSDF* get_bsdf(const DifferentialGeometry &dg, MemoryPool &pool) const override; 27 | }; 28 | 29 | #endif 30 | 31 | -------------------------------------------------------------------------------- /include/material/oren_nayer.h: -------------------------------------------------------------------------------- 1 | #ifndef OREN_NAYER_H 2 | #define OREN_NAYER_H 3 | 4 | #include "bxdf.h" 5 | 6 | /* 7 | * BRDF describing the OrenNayer microfacet diffuse reflection model 8 | */ 9 | class OrenNayer : public BxDF { 10 | Colorf reflectance; 11 | float a, b; 12 | 13 | public: 14 | /* 15 | * Create an OrenNayer BRDF with some color and specify the sigma 16 | * for the Gaussian microfacet distribution, sigma should be in degrees 17 | */ 18 | OrenNayer(const Colorf &reflectance, float sigma); 19 | /* 20 | * Compute the value of the BxDF for some incident and outgoing directions 21 | */ 22 | Colorf operator()(const Vector &w_o, const Vector &w_i) const override; 23 | }; 24 | 25 | #endif 26 | 27 | -------------------------------------------------------------------------------- /include/material/plastic_material.h: -------------------------------------------------------------------------------- 1 | #ifndef PLASTIC_MATERIAL_H 2 | #define PLASTIC_MATERIAL_H 3 | 4 | #include "textures/texture.h" 5 | #include "material.h" 6 | 7 | /* 8 | * A material that models plastic, parameterized by diffuse and specular 9 | * colors along with a roughness value 10 | */ 11 | class PlasticMaterial : public Material { 12 | const Texture *diffuse, *specular; 13 | const float rough_x, rough_y; 14 | 15 | public: 16 | /* 17 | * Create the plastic material specifying the textures to be used for 18 | * the diffuse and specular colors 19 | */ 20 | PlasticMaterial(const Texture *diffuse, const Texture *specular, float rough_x, float rough_y); 21 | /* 22 | * Get the BSDF to compute the shading for the material at this 23 | * piece of geometry 24 | */ 25 | BSDF* get_bsdf(const DifferentialGeometry &dg, MemoryPool &pool) const override; 26 | }; 27 | 28 | #endif 29 | 30 | -------------------------------------------------------------------------------- /include/material/scaled_bxdf.h: -------------------------------------------------------------------------------- 1 | #ifndef SCALED_BXDF_H 2 | #define SCALED_BXDF_H 3 | 4 | #include "bxdf.h" 5 | 6 | /* 7 | * A BxDF that scales the output of the given BxDF by some color 8 | */ 9 | class ScaledBxDF : public BxDF { 10 | BxDF *bxdf; 11 | Colorf scale; 12 | 13 | public: 14 | /* 15 | * Create the scaled BxDF to apply the desired scaling to the output 16 | * of the BxDF passed 17 | */ 18 | ScaledBxDF(BxDF *bxdf, const Colorf &scale); 19 | /* 20 | * Compute the value of the BxDF for some incident and outgoing directions 21 | */ 22 | Colorf operator()(const Vector &w_o, const Vector &w_i) const override; 23 | /* 24 | * Sample the BxDFs value for some outgoing direction using the random values 25 | * passed and returning the incident light direction 26 | */ 27 | Colorf sample(const Vector &w_o, Vector &w_i, const std::array &samples, float &pdf_val) const override; 28 | /* 29 | * Compute the hemispherical-directional reflectance function using the samples passed 30 | */ 31 | Colorf rho_hd(const Vector &w_o, const std::array *samples, int n_samples) const override; 32 | /* 33 | * Compute the hemispherical-hemispherical reflectance function using the samples passed 34 | * samples_a and samples_b should contain the same number of samples 35 | */ 36 | Colorf rho_hh(const std::array *samples_a, const std::array *samples_b, 37 | int n_samples) const override; 38 | /* 39 | * Compute the probability density function for sampling the directions passed 40 | */ 41 | float pdf(const Vector &w_o, const Vector &w_i) const override; 42 | }; 43 | 44 | #endif 45 | 46 | -------------------------------------------------------------------------------- /include/material/specular_metal_material.h: -------------------------------------------------------------------------------- 1 | #ifndef SPECULAR_METAL_MATERIAL 2 | #define SPECULAR_METAL_MATERIAL 3 | 4 | #include "textures/texture.h" 5 | #include "material.h" 6 | 7 | /* 8 | * A material that models a perfectly specular metal surface, described by its 9 | * refractive index and absorption coefficient 10 | */ 11 | class SpecularMetalMaterial : public Material { 12 | const Texture *refr_index, *absoption_coef; 13 | 14 | public: 15 | /* 16 | * Create the metal, specifying the textures to be used for its attributes 17 | * To create an anisotropic material specify both rough_x and rough_y, for a 18 | * non-aniostropic material only specify rough_x 19 | */ 20 | SpecularMetalMaterial(const Texture *refr_index, const Texture *absoption_coef); 21 | /* 22 | * Get the BSDF to compute the shading for the material at this 23 | * piece of geometry 24 | */ 25 | BSDF* get_bsdf(const DifferentialGeometry &dg, MemoryPool &pool) const override; 26 | }; 27 | 28 | #endif 29 | 30 | -------------------------------------------------------------------------------- /include/material/specular_reflection.h: -------------------------------------------------------------------------------- 1 | #ifndef SPECULAR_REFLECTION_H 2 | #define SPECULAR_REFLECTION_H 3 | 4 | #include "fresnel.h" 5 | #include "bxdf.h" 6 | 7 | /* 8 | * BRDF describing perfectly specular reflection, eg. a delta distribution 9 | */ 10 | class SpecularReflection : public BxDF { 11 | Colorf reflection; 12 | Fresnel *fresnel; 13 | 14 | public: 15 | /* 16 | * Create a specularly reflective BRDF with some reflective color 17 | * and desired Fresnel component 18 | */ 19 | SpecularReflection(const Colorf &reflection, Fresnel *fresnel); 20 | /* 21 | * Compute the value of the BxDF for some incident and outgoing directions 22 | * Note that because this is a delta distribution this will always return 0, 23 | * sample must be used for delta distributions instead 24 | */ 25 | Colorf operator()(const Vector &w_o, const Vector &w_i) const override; 26 | /* 27 | * Sample the BRDFs value for some outgoing direction using the random values 28 | * passed and returning the incident light direction 29 | */ 30 | Colorf sample(const Vector &w_o, Vector &w_i, const std::array &samples, float &pdf_val) const override; 31 | /* 32 | * Compute the probability density function for sampling the directions passed 33 | * Note again that this returns 0 because this is a delta distribution 34 | */ 35 | float pdf(const Vector &w_o, const Vector &w_i) const override; 36 | }; 37 | 38 | #endif 39 | 40 | -------------------------------------------------------------------------------- /include/material/specular_transmission.h: -------------------------------------------------------------------------------- 1 | #ifndef SPECULAR_TRANSMISSION_H 2 | #define SPECULAR_TRANSMISSION_H 3 | 4 | #include "fresnel.h" 5 | #include "bxdf.h" 6 | 7 | /* 8 | * BTDF describing perfectly specular transmission eg. a delta distribution 9 | */ 10 | class SpecularTransmission : public BxDF { 11 | Colorf transmission; 12 | FresnelDielectric *fresnel; 13 | 14 | public: 15 | /* 16 | * Create a specularly transmissive BTDF with some transmissive color 17 | * and desired Fresnel component 18 | */ 19 | SpecularTransmission(const Colorf &transmission, FresnelDielectric *fresnel); 20 | /* 21 | * Compute the value of the BxDF for some incident and outgoing directions 22 | * Note that because this is a delta distribution this will always return 0, 23 | * sample must be used for delta distributions instead 24 | */ 25 | Colorf operator()(const Vector &w_o, const Vector &w_i) const override; 26 | /* 27 | * Sample the BTDFs value for some outgoing direction using the random values 28 | * passed and returning the incident light direction 29 | */ 30 | Colorf sample(const Vector &w_o, Vector &w_i, const std::array &samples, float &pdf_val) const override; 31 | /* 32 | * Compute the probability density function for sampling the directions passed 33 | * Note again that this returns 0 because this is a delta distribution 34 | */ 35 | float pdf(const Vector &w_o, const Vector &w_i) const override; 36 | }; 37 | 38 | #endif 39 | 40 | -------------------------------------------------------------------------------- /include/material/torrance_sparrow.h: -------------------------------------------------------------------------------- 1 | #ifndef TORRANCE_SPARROW_H 2 | #define TORRANCE_SPARROW_H 3 | 4 | #include 5 | #include "bxdf.h" 6 | #include "microfacet_distribution.h" 7 | #include "fresnel.h" 8 | 9 | /* 10 | * Implementation of the Torrance-Sparrow microfacet BRDF model 11 | */ 12 | class TorranceSparrow : public BxDF { 13 | Colorf reflectance; 14 | Fresnel *fresnel; 15 | MicrofacetDistribution *distribution; 16 | 17 | public: 18 | /* 19 | * Create the microface BRDF with some color, fresnel term 20 | * and desired microfacet distribution 21 | */ 22 | TorranceSparrow(const Colorf &reflectance, Fresnel *fresnel, MicrofacetDistribution *distribution); 23 | /* 24 | * Compute the value of the BxDF for some incident and outgoing directions 25 | */ 26 | Colorf operator()(const Vector &w_o, const Vector &w_i) const override; 27 | /* 28 | * Sample the BxDFs value for some outgoing direction using the random values 29 | * passed and returning the incident light direction 30 | */ 31 | Colorf sample(const Vector &w_o, Vector &w_i, const std::array &samples, float &pdf_val) const override; 32 | /* 33 | * Compute the probability density function for sampling the directions passed 34 | */ 35 | float pdf(const Vector &w_o, const Vector &w_i) const override; 36 | }; 37 | 38 | #endif 39 | 40 | -------------------------------------------------------------------------------- /include/material/translucent_material.h: -------------------------------------------------------------------------------- 1 | #ifndef TRANSLUCENT_MATERIAL_H 2 | #define TRANSLUCENT_MATERIAL_H 3 | 4 | #include "textures/texture.h" 5 | #include "material.h" 6 | 7 | /* 8 | * A material modeling a translucent dielectric, eg. transparent 9 | * plastics or similar 10 | */ 11 | class TranslucentMaterial : public Material { 12 | const Texture *diffuse, *specular, *reflect, *transmit; 13 | const float roughness, refr_index; 14 | 15 | public: 16 | /* 17 | * Create the translucent material specifying the textures to be used 18 | * for the colors of each property and the roughness 19 | */ 20 | TranslucentMaterial(const Texture *diffuse, const Texture *specular, const Texture *reflect, 21 | const Texture *transmit, float roughness, float refr_index); 22 | /* 23 | * Get the BSDF to compute the shading for the material at this 24 | * piece of geometry 25 | */ 26 | BSDF* get_bsdf(const DifferentialGeometry &dg, MemoryPool &pool) const override; 27 | }; 28 | 29 | #endif 30 | 31 | -------------------------------------------------------------------------------- /include/memory_pool.h: -------------------------------------------------------------------------------- 1 | #ifndef MEMORY_POOL_H 2 | #define MEMORY_POOL_H 3 | 4 | #include 5 | 6 | /* 7 | * A memory pool that allocates memory in chunks of the specified block size. 8 | * Only supports freeing of the entire pool, not individual allocations and can not 9 | * be moved or copied (it's possible to implement but I don't think I need it in the project) 10 | */ 11 | class MemoryPool { 12 | struct Block { 13 | uint64_t size; 14 | char *block; 15 | 16 | Block(uint64_t size, char *block); 17 | }; 18 | 19 | uint64_t cur_block_pos, block_size; 20 | Block cur_block; 21 | std::vector used, available; 22 | 23 | public: 24 | /* 25 | * Create the memory pool specifying the block size to do allocations in (default is 32k) 26 | */ 27 | MemoryPool(uint32_t block_size = 32768); 28 | MemoryPool(const MemoryPool&) = delete; 29 | MemoryPool& operator=(const MemoryPool&) = delete; 30 | MemoryPool(const MemoryPool&&) = delete; 31 | MemoryPool& operator=(const MemoryPool&&) = delete; 32 | ~MemoryPool(); 33 | /* 34 | * Construct a type in the memory pool 35 | */ 36 | template 37 | T* alloc(Args&&... args){ 38 | return new(alloc(sizeof(T))) T{std::forward(args)...}; 39 | } 40 | /* 41 | * Allocate an array of types in the memory pool, elements in the array 42 | * will be uninitialized 43 | */ 44 | template 45 | T* alloc_array(int size){ 46 | return static_cast(alloc(sizeof(T) * size)); 47 | } 48 | /* 49 | * Clear all used blocks, making them available again 50 | */ 51 | void free_blocks(); 52 | 53 | private: 54 | /* 55 | * Allocate some number of bytes to be used for constructing some object 56 | */ 57 | void* alloc(uint64_t size); 58 | }; 59 | 60 | #endif 61 | 62 | -------------------------------------------------------------------------------- /include/mesh_preprocess.h: -------------------------------------------------------------------------------- 1 | #ifndef MESH_PREPROCESS_H 2 | #define MESH_PREPROCESS_H 3 | 4 | #include 5 | 6 | /* 7 | * Batch process all the mesh names passed in 8 | */ 9 | void batch_process(char **argv, int argc); 10 | 11 | /* 12 | * Preprocess a mesh by loading the obj file into a model and 13 | * serializing in a binary format that can be read in much faster later 14 | * The binary model data will be written to the same file name passed in 15 | * but with the extension bobj. 16 | * 17 | * The binary model format produced will contain: 18 | * uint32: number of vertices 19 | * uint32: number of triangles 20 | * [float]: 3 * num verts positions 21 | * [float]: 3 * num verts texcoords 22 | * [float]: 3 * num verts normals 23 | * [int]: 3 * num tris indices 24 | */ 25 | bool process_wobj(const std::string &file); 26 | 27 | #endif 28 | 29 | -------------------------------------------------------------------------------- /include/monte_carlo/distribution1d.h: -------------------------------------------------------------------------------- 1 | #ifndef DISTRIBUTION1D_H 2 | #define DISTRIBUTION1D_H 3 | 4 | #include 5 | 6 | /* 7 | * Represents a piecewise-constant 1D function's PDF and CDF, as described in PBR 8 | */ 9 | class Distribution1D { 10 | std::vector function, cdf; 11 | float integral; 12 | 13 | public: 14 | /* 15 | * Create the sampling distribution for the passed function values 16 | */ 17 | Distribution1D(const std::vector &function); 18 | Distribution1D(); 19 | /* 20 | * Sample one of the buckets of the function and return the bucket 21 | * number sampled, u should be in the range [0, 1) 22 | * optionally returns the pdf of sampling the bucket that was sampled 23 | */ 24 | int sample_discrete(float u, float *pdf_val = nullptr) const; 25 | }; 26 | 27 | #endif 28 | 29 | -------------------------------------------------------------------------------- /include/renderer/renderer.h: -------------------------------------------------------------------------------- 1 | #ifndef RENDERER_H 2 | #define RENDERER_H 3 | 4 | #include 5 | #include "samplers/sampler.h" 6 | #include "linalg/ray.h" 7 | #include "memory_pool.h" 8 | 9 | class SurfaceIntegrator; 10 | class VolumeIntegrator; 11 | class Scene; 12 | 13 | /* 14 | * Interface for renderers, given a ray to trace and a scene 15 | * to trace it in returns the illumination along the ray 16 | */ 17 | class Renderer { 18 | std::unique_ptr surface_integrator; 19 | std::unique_ptr volume_integrator; 20 | 21 | public: 22 | Renderer(std::unique_ptr surface_integrator, std::unique_ptr volume_integrator); 23 | /* 24 | * Have the renderer and its integrators perform any needed pre-processing of the scene 25 | */ 26 | void preprocess(const Scene &scene); 27 | /* 28 | * Compute the incident radiance along the ray in the scene 29 | * The default implementation simply calls the surface integrator on 30 | * the hit geometry to compute the illumination 31 | */ 32 | virtual Colorf illumination(RayDifferential &ray, const Scene &scene, Sampler &sampler, MemoryPool &pool) const; 33 | /* 34 | * Compute the beam transmittance for line segment along the ray from min_t to max_t using the 35 | * volume integrator, if any. If no volume integrator is being used, simply returns 1 (eg. air) 36 | */ 37 | virtual Colorf transmittance(const Scene &scene, const RayDifferential &ray, Sampler &sampler, MemoryPool &pool) const; 38 | 39 | }; 40 | 41 | #endif 42 | 43 | -------------------------------------------------------------------------------- /include/samplers/stratified_sampler.h: -------------------------------------------------------------------------------- 1 | #ifndef STRATIFIED_SAMPLER_H 2 | #define STRATIFIED_SAMPLER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "sampler.h" 9 | 10 | /* 11 | * A stratified sampler, generates multipled jittered 12 | * samples per pixel in its sample region 13 | */ 14 | class StratifiedSampler : public Sampler { 15 | const int spp; 16 | 17 | public: 18 | StratifiedSampler(int x_start, int x_end, int y_start, int y_end, int spp, int seed); 19 | StratifiedSampler(int x_start, int x_end, int y_start, int y_end, int spp); 20 | /* 21 | * Get some {x, y} positions to sample in the space being sampled 22 | * If the sampler has finished sampling samples will be empty 23 | */ 24 | void get_samples(std::vector &samples) override; 25 | /* 26 | * Get a set of 2D samples in range [0, 1) 27 | * Note that the stratified sampler ignores the offset value 28 | */ 29 | void get_samples(std::array *samples, int n_samples, int offset = 0) override; 30 | /* 31 | * Get a set of 1D samples in range [0, 1) 32 | * Note that the stratified sampler ignores the offset value 33 | */ 34 | void get_samples(float *samples, int n_samples, int offset = 0) override; 35 | /* 36 | * Get the max number of samples this sampler will take per pixel 37 | */ 38 | int get_max_spp() const override; 39 | /* 40 | * Get subsamplers that divide the space to be sampled 41 | * into count disjoint subsections where each samples a w x h 42 | * section of the original sampler 43 | */ 44 | std::vector> get_subsamplers(int w, int h) const override; 45 | /* 46 | * Generate a 1d pattern of stratified samples and return them 47 | * samples will be normalized between [0, 1) 48 | */ 49 | static void sample1d(float *samples, int n_samples, std::minstd_rand &rng); 50 | /* 51 | * Generate a 2d pattern of stratified samples and return them 52 | * sample positions will be normalized between [0, 1) 53 | */ 54 | static void sample2d(std::array *samples, int n_samples, std::minstd_rand &rng); 55 | }; 56 | 57 | #endif 58 | 59 | -------------------------------------------------------------------------------- /include/scene.h: -------------------------------------------------------------------------------- 1 | #ifndef SCENE_H 2 | #define SCENE_H 3 | 4 | #include 5 | #include 6 | #include "geometry/geometry.h" 7 | #include "volume/volume_node.h" 8 | #include "material/material.h" 9 | #include "textures/texture.h" 10 | #include "lights/light.h" 11 | #include "film/render_target.h" 12 | #include "film/camera.h" 13 | #include "samplers/sampler.h" 14 | #include "textures/texture.h" 15 | #include "renderer/renderer.h" 16 | #include "integrator/surface_integrator.h" 17 | 18 | /* 19 | * Describes a scene that we're rendering 20 | */ 21 | class Scene { 22 | GeometryCache geom_cache; 23 | MaterialCache mat_cache; 24 | TextureCache tex_cache; 25 | LightCache light_cache; 26 | VolumeCache volume_cache; 27 | Camera camera; 28 | RenderTarget render_target; 29 | std::unique_ptr sampler; 30 | std::unique_ptr renderer; 31 | Node root; 32 | std::unique_ptr volume_root; 33 | Texture *background, *environment; 34 | 35 | public: 36 | /* 37 | * Setup the camera and render target for the scene 38 | * Geometry can be added by adding nodes to the root 39 | * and selecting from or adding to the geometry cache 40 | */ 41 | Scene(Camera camera, RenderTarget target, std::unique_ptr sampler, 42 | std::unique_ptr); 43 | GeometryCache& get_geom_cache(); 44 | MaterialCache& get_mat_cache(); 45 | TextureCache& get_tex_cache(); 46 | LightCache& get_light_cache(); 47 | const LightCache& get_light_cache() const; 48 | VolumeCache& get_volume_cache(); 49 | Camera& get_camera(); 50 | RenderTarget& get_render_target(); 51 | const RenderTarget& get_render_target() const; 52 | const Sampler& get_sampler() const; 53 | const Renderer& get_renderer() const; 54 | Renderer& get_renderer(); 55 | /* 56 | * Get the root node of the geometry scene graph 57 | */ 58 | Node& get_root(); 59 | const Node& get_root() const; 60 | /* 61 | * Get the root node of the volume scene graph 62 | * Note: this may be null as volumes are optional while 63 | * a root geometry node isn't 64 | */ 65 | VolumeNode* get_volume_root(); 66 | const VolumeNode* get_volume_root() const; 67 | void set_volume_root(std::unique_ptr vol); 68 | void set_background(Texture *t); 69 | void set_environment(Texture *t); 70 | const Texture* get_background() const; 71 | const Texture* get_environment() const; 72 | }; 73 | 74 | #endif 75 | 76 | -------------------------------------------------------------------------------- /include/textures/checkerboard_texture.h: -------------------------------------------------------------------------------- 1 | #ifndef CHECKERBOARD_TEXTURE_H 2 | #define CHECKERBOARD_TEXTURE_H 3 | 4 | #include 5 | #include "texture.h" 6 | #include "texture_mapping.h" 7 | 8 | /* 9 | * Displays a checkerboard of the two color values 10 | */ 11 | class CheckerboardTexture : public Texture { 12 | std::unique_ptr mapping; 13 | Colorf a, b; 14 | 15 | public: 16 | CheckerboardTexture(const Colorf &a, const Colorf &b, 17 | std::unique_ptr mapping); 18 | /* 19 | * Sample the texture color for the piece of geometry being textured 20 | */ 21 | Colorf sample(const DifferentialGeometry &dg) const override; 22 | /* 23 | * Sometimes we want to re-use a texture but through a different mapping 24 | * This method samples the texture directly using the texture sample passed in 25 | */ 26 | Colorf sample(const TextureSample &sample) const override; 27 | 28 | private: 29 | /* 30 | * Compute the integral of the checkerboard step function so 31 | * we can interpolate the textures when blending between them 32 | */ 33 | float step_integral(float x) const; 34 | }; 35 | 36 | #endif 37 | 38 | -------------------------------------------------------------------------------- /include/textures/constant_texture.h: -------------------------------------------------------------------------------- 1 | #ifndef CONSTANT_TEXTURE_H 2 | #define CONSTANT_TEXTURE_H 3 | 4 | #include "texture.h" 5 | 6 | /* 7 | * A texture with the same value everywhere 8 | */ 9 | class ConstantTexture : public Texture { 10 | Colorf color; 11 | 12 | public: 13 | /* 14 | * Create the constant valued texture and assign the value to show 15 | */ 16 | ConstantTexture(const Colorf &color); 17 | /* 18 | * Sample the texture color for the piece of geometry being textured 19 | */ 20 | Colorf sample(const DifferentialGeometry &dg) const override; 21 | /* 22 | * Sometimes we want to re-use a texture but through a different mapping 23 | * This method samples the texture directly using the texture sample passed in 24 | */ 25 | Colorf sample(const TextureSample &sample) const override; 26 | }; 27 | 28 | #endif 29 | 30 | -------------------------------------------------------------------------------- /include/textures/image_texture.h: -------------------------------------------------------------------------------- 1 | #ifndef IMAGE_TEXTURE_H 2 | #define IMAGE_TEXTURE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "texture.h" 8 | #include "texture_mapping.h" 9 | #include "mipmap.h" 10 | 11 | /* 12 | * A 2D image to be used as a texture 13 | * If the texture has one color component the color returned when sampling 14 | * will be RRR. 15 | * If two components RG0 is returned, for three RGB is returned 16 | */ 17 | class ImageTexture : public Texture { 18 | std::unique_ptr mapping; 19 | //Image width, height and number of components per pixel 20 | int width, height, ncomp; 21 | MipMap mipmap; 22 | 23 | public: 24 | /* 25 | * Load the image texture from an image file, mapping controls 26 | * how u,v coordinates are mapped to texture s,t coordinates 27 | */ 28 | ImageTexture(const std::string &file, std::unique_ptr mapping, 29 | WRAP_MODE wrap_mode = WRAP_MODE::REPEAT); 30 | /* 31 | * Sample the texture color for the piece of geometry being textured 32 | */ 33 | Colorf sample(const DifferentialGeometry &dg) const override; 34 | /* 35 | * Sometimes we want to re-use a texture but through a different mapping 36 | * This method samples the texture directly using the texture sample passed in 37 | */ 38 | Colorf sample(const TextureSample &sample) const override; 39 | 40 | private: 41 | /* 42 | * Load a texture from an image file and return the status of the load 43 | */ 44 | bool load_image(const std::string &file, std::vector &texels); 45 | /* 46 | * Load a texture from a ppm file and return the status of the load 47 | */ 48 | bool load_ppm(const std::string &file, std::vector &texels); 49 | /* 50 | * Load a texture using stb_image and return the status of the load 51 | */ 52 | bool load_stb(const std::string &file, std::vector &texels); 53 | }; 54 | 55 | #endif 56 | 57 | -------------------------------------------------------------------------------- /include/textures/remapped_texture.h: -------------------------------------------------------------------------------- 1 | #ifndef REMAPPED_TEXTURE_H 2 | #define REMAPPED_TEXTURE_H 3 | 4 | #include "texture.h" 5 | #include "texture_mapping.h" 6 | 7 | /* 8 | * A bit of a hack, allows for re-using an existing texture but 9 | * with a different texture mapping 10 | */ 11 | class RemappedTexture : public Texture { 12 | const Texture &texture; 13 | std::unique_ptr mapping; 14 | 15 | public: 16 | /* 17 | * Create the remapped texture to apply sample an existing texture 18 | * with a new mapping scheme 19 | */ 20 | RemappedTexture(const Texture &texture, std::unique_ptr mapping); 21 | /* 22 | * Sample the texture color for the piece of geometry being textured 23 | */ 24 | Colorf sample(const DifferentialGeometry &dg) const override; 25 | /* 26 | * Sometimes we want to re-use a texture but through a different mapping 27 | * This method samples the texture directly using the texture sample passed in 28 | */ 29 | Colorf sample(const TextureSample &sample) const override; 30 | }; 31 | 32 | #endif 33 | 34 | -------------------------------------------------------------------------------- /include/textures/scale_texture.h: -------------------------------------------------------------------------------- 1 | #ifndef SCALE_TEXTURE_H 2 | #define SCALE_TEXTURE_H 3 | 4 | #include "texture.h" 5 | 6 | /* 7 | * Texture that multiplies two textures together to form the final color 8 | */ 9 | class ScaleTexture : public Texture { 10 | const Texture &a, &b; 11 | 12 | public: 13 | /* 14 | * Create the scale texture and set the two textures to be multiplied together 15 | * These textures aren't owned by the scale texture and should live in the cache 16 | */ 17 | ScaleTexture(const Texture &a, const Texture &b); 18 | /* 19 | * Sample the texture color for the piece of geometry being textured 20 | */ 21 | Colorf sample(const DifferentialGeometry &dg) const override; 22 | /* 23 | * Sometimes we want to re-use a texture but through a different mapping 24 | * This method samples the texture directly using the texture sample passed in 25 | */ 26 | Colorf sample(const TextureSample &sample) const override; 27 | }; 28 | 29 | #endif 30 | 31 | -------------------------------------------------------------------------------- /include/textures/spherical_mapping.h: -------------------------------------------------------------------------------- 1 | #ifndef SPHERICAL_MAPPING_H 2 | #define SPHERICAL_MAPPING_H 3 | 4 | #include "linalg/point.h" 5 | #include "linalg/transform.h" 6 | #include "texture_mapping.h" 7 | 8 | /* 9 | * Mapping that projects the hit point to a point on a sphere around the object 10 | * and uses the sphere parameterization as the texture coordinates 11 | */ 12 | class SphericalMapping : public TextureMapping { 13 | Transform transform; 14 | 15 | public: 16 | SphericalMapping(const Transform &transform); 17 | /* 18 | * Compute the texture sample position and derivatives for the 19 | * differential geometry being rendered 20 | */ 21 | TextureSample map(const DifferentialGeometry &dg) const override; 22 | 23 | private: 24 | /* 25 | * Project a point onto the sphere placed around the object and return the s,t 26 | * coordinates of the sphere at that point 27 | */ 28 | void sphere_project(const Point &p, float &s, float &t) const; 29 | }; 30 | 31 | #endif 32 | 33 | -------------------------------------------------------------------------------- /include/textures/texture.h: -------------------------------------------------------------------------------- 1 | #ifndef TEXTURE_H 2 | #define TEXTURE_H 3 | 4 | #include "cache.h" 5 | #include "geometry/differential_geometry.h" 6 | #include "film/color.h" 7 | #include "texture_mapping.h" 8 | 9 | /* 10 | * Interface for texture, provides method to sample the color of the 11 | * for some differential geometry being rendered 12 | */ 13 | class Texture { 14 | public: 15 | /* 16 | * Sample the texture color for the piece of geometry being textured 17 | */ 18 | virtual Colorf sample(const DifferentialGeometry &dg) const = 0; 19 | /* 20 | * Sometimes we want to re-use a texture but through a different mapping 21 | * This method samples the texture directly using the texture sample passed in 22 | */ 23 | virtual Colorf sample(const TextureSample &sample) const = 0; 24 | }; 25 | 26 | typedef Cache TextureCache; 27 | 28 | #endif 29 | 30 | -------------------------------------------------------------------------------- /include/textures/texture_mapping.h: -------------------------------------------------------------------------------- 1 | #ifndef TEXTURE_MAPPING_H 2 | #define TEXTURE_MAPPING_H 3 | 4 | #include "geometry/differential_geometry.h" 5 | 6 | /* 7 | * Stores information about the s,t coords and derivatives 8 | * to evaluat a texture's value 9 | */ 10 | struct TextureSample { 11 | float s, t, ds_dx, ds_dy, dt_dx, dt_dy; 12 | 13 | TextureSample(float s = 0, float t = 0, float ds_dx = 0, 14 | float ds_dy = 0, float dt_dx = 0, float dt_dy = 0); 15 | }; 16 | 17 | /* 18 | * Interface for mapping u,v coords to s,t texture coords 19 | */ 20 | class TextureMapping { 21 | public: 22 | /* 23 | * Compute the texture sample position and derivatives for the 24 | * differential geometry being rendered 25 | */ 26 | virtual TextureSample map(const DifferentialGeometry &dg) const = 0; 27 | }; 28 | 29 | #endif 30 | 31 | -------------------------------------------------------------------------------- /include/textures/uv_mapping.h: -------------------------------------------------------------------------------- 1 | #ifndef UV_MAPPING_H 2 | #define UV_MAPPING_H 3 | 4 | #include "linalg/transform.h" 5 | #include "texture_mapping.h" 6 | 7 | /* 8 | * A mapping that maps the object's uv coordinates to s,t in texture space 9 | */ 10 | class UVMapping : public TextureMapping { 11 | Transform transform; 12 | 13 | public: 14 | /* 15 | * Create a uv mapping that will scale and translate the texture 16 | * coordinates by some amount 17 | */ 18 | UVMapping(const Transform &transform); 19 | /* 20 | * Compute the texture sample position and derivatives for the 21 | * differential geometry being rendered 22 | */ 23 | TextureSample map(const DifferentialGeometry &dg) const override; 24 | }; 25 | 26 | #endif 27 | 28 | -------------------------------------------------------------------------------- /include/textures/uv_texture.h: -------------------------------------------------------------------------------- 1 | #ifndef UV_TEXTURE_H 2 | #define UV_TEXTURE_H 3 | 4 | #include 5 | #include "texture.h" 6 | #include "texture_mapping.h" 7 | 8 | /* 9 | * Procedural texture that displays the st texture coordinates 10 | * as RG color values 11 | */ 12 | class UVTexture : public Texture { 13 | std::unique_ptr mapping; 14 | 15 | public: 16 | UVTexture(std::unique_ptr mapping); 17 | /* 18 | * Sample the texture color for the piece of geometry being textured 19 | */ 20 | Colorf sample(const DifferentialGeometry &dg) const override; 21 | /* 22 | * Sometimes we want to re-use a texture but through a different mapping 23 | * This method samples the texture directly using the texture sample passed in 24 | */ 25 | Colorf sample(const TextureSample &sample) const override; 26 | }; 27 | 28 | #endif 29 | 30 | -------------------------------------------------------------------------------- /include/volume/exponential_volume.h: -------------------------------------------------------------------------------- 1 | #ifndef EXPONENTIAL_VOLUME_H 2 | #define EXPONENTIAL_VOLUME_H 3 | 4 | #include "varying_density_volume.h" 5 | 6 | /* 7 | * Defines a volume with density that falls of as an exponential, a*e^{-b*h} 8 | * where h is the 'height' along the up vector specified 9 | */ 10 | class ExponentialVolume : public VaryingDensityVolume { 11 | //Parameters for the exponential falloff 12 | float a, b; 13 | Vector up; 14 | BBox region; 15 | 16 | public: 17 | /* 18 | * Create the exponential falloff volume specifying both the volume properties 19 | * and parameters for the exponential falloff density function 20 | */ 21 | ExponentialVolume(const Colorf &sig_a, const Colorf &sig_s, const Colorf &emit, float phase_asymmetry, 22 | const BBox ®ion, float a, float b, const Vector &up); 23 | /* 24 | * Get the object-space bounds for the volume region 25 | */ 26 | BBox bound() const override; 27 | /* 28 | * Test if the ray intersects the volume region, returning the min/max t 29 | * values that the ray overlapped the region in 30 | */ 31 | bool intersect(const Ray &ray, std::array &t) const override; 32 | /* 33 | * Return the density of the volume at some point 34 | */ 35 | float density(const Point &p) const override; 36 | }; 37 | 38 | #endif 39 | 40 | -------------------------------------------------------------------------------- /include/volume/grid_volume.h: -------------------------------------------------------------------------------- 1 | #ifndef GRID_VOLUME_H 2 | #define GRID_VOLUME_H 3 | 4 | #include 5 | #include 6 | #include "varying_density_volume.h" 7 | 8 | /* 9 | * Defines a volume where the density values are stored in some 10 | * 3D grid. Currently only gridvolume format encoding 1 from Mitsuba is supported 11 | * See the grid-based volume data source section of the Mitsuba doc for the file format: 12 | * http://www.mitsuba-renderer.org/releases/current/documentation.pdf 13 | * 14 | * Wenzel Jakob's fluid simulator is quick to get running and outputs 15 | * files in the supported format: 16 | * http://www.mitsuba-renderer.org/misc.html 17 | */ 18 | class GridVolume : public VaryingDensityVolume { 19 | float density_scale; 20 | //Number of cells along x/y/z 21 | uint32_t n_x, n_y, n_z; 22 | BBox region; 23 | std::vector grid; 24 | 25 | public: 26 | /* 27 | * Create the grid volume specifying the properties of the volume and 28 | * the Mitsuba vol file to load the density grid from and an optional 29 | * extra scaling to apply to the density values 30 | */ 31 | GridVolume(const Colorf &sig_a, const Colorf &sig_s, const Colorf &emit, 32 | float phase_asymmetry, const std::string &vol_file, float density_scale = 1); 33 | /* 34 | * Get the object-space bounds for the volume region 35 | */ 36 | BBox bound() const override; 37 | /* 38 | * Test if the ray intersects the volume region, returning the min/max t 39 | * values that the ray overlapped the region in 40 | */ 41 | bool intersect(const Ray &ray, std::array &t) const override; 42 | /* 43 | * Return the density of the volume at some point 44 | */ 45 | float density(const Point &p) const override; 46 | 47 | private: 48 | /* 49 | * Utility to easily lookup the density at some location in the grid 50 | */ 51 | float grid_density(uint32_t x, uint32_t y, uint32_t z) const; 52 | /* 53 | * Load the volume data from the vol file 54 | * returns true if successful 55 | */ 56 | bool load_vol_file(const std::string &vol_file); 57 | }; 58 | 59 | #endif 60 | 61 | -------------------------------------------------------------------------------- /include/volume/homogeneous_volume.h: -------------------------------------------------------------------------------- 1 | #ifndef HOMOGENEOUS_VOLUME_H 2 | #define HOMOGENEOUS_VOLUME_H 3 | 4 | #include "volume.h" 5 | 6 | /* 7 | * A very simple volume, describes a box in space with 8 | * homogeneous scattering properties throughout 9 | */ 10 | class HomogeneousVolume : public Volume { 11 | //Scattering, absoprtion, emission coefficients describing the volume 12 | Colorf sig_a, sig_s, emit; 13 | float phase_asymmetry; 14 | //The region that the volume occupies 15 | BBox region; 16 | 17 | public: 18 | /* 19 | * Construct the homgeneous volume specifying its properties and bounds 20 | */ 21 | HomogeneousVolume(const Colorf &sig_a, const Colorf &sig_s, const Colorf &emit, 22 | float phase_asymmetry, const BBox ®ion); 23 | /* 24 | * Get the object-space bounds for the volume region 25 | */ 26 | BBox bound() const override; 27 | /* 28 | * Test if the ray intersects the volume region, returning the min/max t 29 | * values that the ray overlapped the region in 30 | */ 31 | bool intersect(const Ray &ray, std::array &t) const override; 32 | /* 33 | * Get the absorption coefficient of the volume at some point 34 | * along the direction passed 35 | */ 36 | Colorf absorption(const Point &p, const Vector &v) const override; 37 | /* 38 | * Get the scattering coefficient of the volume at some point 39 | * along the direction passed 40 | */ 41 | Colorf scattering(const Point &p, const Vector &v) const override; 42 | /* 43 | * Get the attenuation coefficient of the volume at some poiint 44 | * along the direciton passed 45 | */ 46 | Colorf attenuation(const Point &p, const Vector &v) const; 47 | /* 48 | * Get the emission property of the volume region at some point 49 | * along the direction passed 50 | */ 51 | Colorf emission(const Point &p, const Vector &v) const override; 52 | /* 53 | * Get the optical thickness of the region along the ray on the line 54 | * segment from min_t to max_t 55 | * For volumes that use Monte Carlo integration to compute this value step 56 | * and offset are used to control how to step along the segment to estimate 57 | * the integral 58 | */ 59 | Colorf optical_thickness(const Ray &ray, float step = 1, float offset = 0.5) const override; 60 | /* 61 | * Get the phase function value for the volume at the some point 62 | * for a ray incident along w_i and exiting along w_o 63 | */ 64 | float phase(const Point &p, const Vector &w_i, const Vector &w_o) const override; 65 | }; 66 | 67 | #endif 68 | 69 | -------------------------------------------------------------------------------- /include/volume/varying_density_volume.h: -------------------------------------------------------------------------------- 1 | #ifndef VARYING_DENSITY_VOLUME_H 2 | #define VARYING_DENSITY_VOLUME_H 3 | 4 | #include "volume.h" 5 | 6 | /* 7 | * Base for volumes that have a varying density but have constant scattering, 8 | * absorption, etc. Provides overloads of absoprtion, scattering, etc. that 9 | * multiply the value with the volume density at the point 10 | */ 11 | class VaryingDensityVolume : public Volume { 12 | //Scattering, absoprtion, emission coefficients describing the volume 13 | Colorf sig_a, sig_s, emit; 14 | float phase_asymmetry; 15 | 16 | public: 17 | /* 18 | * Construct the volume specifying its properties 19 | */ 20 | VaryingDensityVolume(const Colorf &sig_a, const Colorf &sig_s, const Colorf &emit, float phase_asymmetry); 21 | /* 22 | * Return the density of the volume at some point 23 | */ 24 | virtual float density(const Point &p) const = 0; 25 | /* 26 | * Get the absorption coefficient of the volume at some point 27 | * along the direction passed 28 | */ 29 | Colorf absorption(const Point &p, const Vector &v) const override; 30 | /* 31 | * Get the scattering coefficient of the volume at some point 32 | * along the direction passed 33 | */ 34 | Colorf scattering(const Point &p, const Vector &v) const override; 35 | /* 36 | * Get the attenuation coefficient of the volume at some poiint 37 | * along the direciton passed 38 | */ 39 | Colorf attenuation(const Point &p, const Vector &v) const; 40 | /* 41 | * Get the emission property of the volume region at some point 42 | * along the direction passed 43 | */ 44 | Colorf emission(const Point &p, const Vector &v) const override; 45 | /* 46 | * Get the optical thickness of the region along the ray on the line 47 | * segment from min_t to max_t 48 | * For volumes that use Monte Carlo integration to compute this value step 49 | * and offset are used to control how to step along the segment to estimate 50 | * the integral 51 | */ 52 | Colorf optical_thickness(const Ray &ray, float step = 1, float offset = 0.5) const override; 53 | /* 54 | * Get the phase function value for the volume at the some point 55 | * for a ray incident along w_i and exiting along w_o 56 | */ 57 | float phase(const Point &p, const Vector &w_i, const Vector &w_o) const override; 58 | }; 59 | 60 | #endif 61 | 62 | -------------------------------------------------------------------------------- /include/volume/volume.h: -------------------------------------------------------------------------------- 1 | #ifndef VOLUME_H 2 | #define VOLUME_H 3 | 4 | #include "cache.h" 5 | #include "geometry/bbox.h" 6 | #include "film/color.h" 7 | #include "linalg/util.h" 8 | #include "linalg/ray.h" 9 | 10 | /* 11 | * Various common phase functions provided for the volumes to use 12 | * phase functions take the incident direction as w_i and the 13 | * outgoing direction as w_o 14 | * See: PBR 15 | */ 16 | constexpr float phase_isotropic(){ 17 | return 1.f / (4 * PI); 18 | } 19 | float phase_rayleigh(const Vector &w_i, const Vector &w_o); 20 | float phase_mie_hazy(const Vector &w_i, const Vector &w_o); 21 | float phase_mie_murky(const Vector &w_i, const Vector &w_o); 22 | float phase_henyey_greenstein(const Vector &w_i, const Vector &w_o, float g); 23 | float phase_schlick(const Vector &w_i, const Vector &w_o, float g); 24 | 25 | /* 26 | * Interface that describes a volume occupying some region in the scene 27 | * based on PBRT's VolumeRegion 28 | */ 29 | class Volume { 30 | public: 31 | /* 32 | * Get the object-space bounds for the volume region 33 | */ 34 | virtual BBox bound() const = 0; 35 | /* 36 | * Test if the ray intersects the volume region, returning the min/max t 37 | * values that the ray overlapped the region in 38 | */ 39 | virtual bool intersect(const Ray &ray, std::array &t) const = 0; 40 | /* 41 | * Get the absorption coefficient of the volume at some point 42 | * along the direction passed 43 | */ 44 | virtual Colorf absorption(const Point &p, const Vector &v) const = 0; 45 | /* 46 | * Get the scattering coefficient of the volume at some point 47 | * along the direction passed 48 | */ 49 | virtual Colorf scattering(const Point &p, const Vector &v) const = 0; 50 | /* 51 | * Get the attenuation coefficient of the volume at some poiint 52 | * along the direciton passed 53 | */ 54 | virtual Colorf attenuation(const Point &p, const Vector &v) const; 55 | /* 56 | * Get the emission property of the volume region at some point 57 | * along the direction passed 58 | */ 59 | virtual Colorf emission(const Point &p, const Vector &v) const = 0; 60 | /* 61 | * Get the optical thickness of the region along the ray on the line 62 | * segment from min_t to max_t 63 | * For volumes that use Monte Carlo integration to compute this value step 64 | * and offset are used to control how to step along the segment to estimate 65 | * the integral 66 | */ 67 | virtual Colorf optical_thickness(const Ray &ray, float step = 1, float offset = 0.5) const = 0; 68 | /* 69 | * Get the phase function value for the volume at the some point 70 | * for a ray incident along w_i and exiting along w_o 71 | */ 72 | virtual float phase(const Point &p, const Vector &w_i, const Vector &w_o) const = 0; 73 | }; 74 | 75 | typedef Cache VolumeCache; 76 | 77 | #endif 78 | 79 | -------------------------------------------------------------------------------- /previewer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(previewer previewer.cpp util.cpp gl_core_3_3.c) 2 | 3 | -------------------------------------------------------------------------------- /previewer/previewer.h: -------------------------------------------------------------------------------- 1 | #ifndef PREVIEWER_H 2 | #define PREVIEWER_H 3 | 4 | #include "driver.h" 5 | 6 | /* 7 | * Run the rendering and periodically update a live preview 8 | * of the rendered image 9 | * returns false if any errors were encountered or rendering was 10 | * aborted for some reason 11 | */ 12 | bool render_with_preview(Driver &driver); 13 | 14 | #endif 15 | 16 | -------------------------------------------------------------------------------- /previewer/util.h: -------------------------------------------------------------------------------- 1 | #ifndef UTIL_H 2 | #define UTIL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "gl_core_3_3.h" 8 | 9 | namespace util { 10 | /* 11 | * Load a GLSL shader from some shader program text 12 | */ 13 | GLint load_shader(GLenum type, const std::string &text); 14 | /* 15 | * Build a shader program from the vertex and fragment shaders passed 16 | */ 17 | GLint load_program(const std::string &vertex_src, const std::string &fragment_src); 18 | /* 19 | * Check for an OpenGL error and log it along with the message passed 20 | * if an error occured. Will return true if an error occured & was logged 21 | */ 22 | bool log_glerror(const std::string &msg); 23 | /* 24 | * A debug callback for the GL_ARB_debug_out extension 25 | */ 26 | #ifdef _WIN32 27 | void APIENTRY gldebug_callback(GLenum src, GLenum type, GLuint id, GLenum severity, 28 | GLsizei len, const GLchar *msg, const GLvoid *user); 29 | #else 30 | void gldebug_callback(GLenum src, GLenum type, GLuint id, GLenum severity, 31 | GLsizei len, const GLchar *msg, const GLvoid *user); 32 | #endif 33 | } 34 | 35 | #endif 36 | 37 | -------------------------------------------------------------------------------- /scenes/cornell.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(linalg) 2 | add_subdirectory(film) 3 | add_subdirectory(geometry) 4 | add_subdirectory(samplers) 5 | add_subdirectory(material) 6 | add_subdirectory(loaders) 7 | add_subdirectory(lights) 8 | add_subdirectory(accelerators) 9 | add_subdirectory(filters) 10 | add_subdirectory(textures) 11 | add_subdirectory(integrator) 12 | add_subdirectory(renderer) 13 | add_subdirectory(monte_carlo) 14 | add_subdirectory(volume) 15 | set(TRAY_LIBS loaders lights renderer integrator linalg film geometry volume 16 | samplers material accelerators filters textures monte_carlo) 17 | 18 | add_executable(tray main.cpp mesh_preprocess.cpp driver.cpp block_queue.cpp args.cpp scene.cpp 19 | memory_pool.cpp) 20 | 21 | # Need to link libm on Unix 22 | if (NOT WIN32) 23 | find_library(M_LIB m) 24 | set(LIBS ${TRAY_LIBS} ${M_LIB}) 25 | else () 26 | set(LIBS ${TRAY_LIBS}) 27 | endif() 28 | 29 | # If we're buidling the previewer we need SDL2 and OpenGL 30 | if (BUILD_PREVIEWER) 31 | set(LIBS ${LIBS} previewer ${SDL2_LIBRARY} ${OPENGL_LIBRARIES}) 32 | endif() 33 | 34 | target_link_libraries(tray ${LIBS} ${CMAKE_THREAD_LIBS_INIT}) 35 | install(TARGETS tray DESTINATION ${TRAY_INSTALL_DIR}) 36 | 37 | -------------------------------------------------------------------------------- /src/accelerators/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(accelerators bvh.cpp) 2 | 3 | -------------------------------------------------------------------------------- /src/args.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "args.h" 4 | 5 | bool flag(char **begin, char **end, const std::string &f){ 6 | return std::find(begin, end, f) != end; 7 | } 8 | template<> 9 | std::string get_param(char **beg, char **end, const std::string &f){ 10 | char **it = std::find(beg, end, f); 11 | if (it != end && ++it != end){ 12 | return std::string{*it}; 13 | } 14 | return std::string{""}; 15 | } 16 | template<> 17 | int get_param(char **beg, char **end, const std::string &f){ 18 | char **it = std::find(beg, end, f); 19 | if (it != end && ++it != end){ 20 | return std::stoi(*it); 21 | } 22 | return 0; 23 | } 24 | template<> 25 | float get_param(char **beg, char **end, const std::string &f){ 26 | char **it = std::find(beg, end, f); 27 | if (it != end && ++it != end){ 28 | return std::stof(*it); 29 | } 30 | return 0; 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/block_queue.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "samplers/sampler.h" 7 | #include "block_queue.h" 8 | 9 | //Fabian Giesen's Morton code generation 10 | //See: http://fgiesen.wordpress.com/2009/12/13/decoding-morton-codes/ 11 | static uint32_t part1_by1(uint32_t x){ 12 | // x = ---- ---- ---- ---- fedc ba98 7654 3210 13 | x &= 0x0000ffff; 14 | // x = ---- ---- fedc ba98 ---- ---- 7654 3210 15 | x = (x ^ (x << 8)) & 0x00ff00ff; 16 | // x = ---- fedc ---- ba98 ---- 7654 ---- 3210 17 | x = (x ^ (x << 4)) & 0x0f0f0f0f; 18 | // x = --fe --dc --ba --98 --76 --54 --32 --10 19 | x = (x ^ (x << 2)) & 0x33333333; 20 | // x = -f-e -d-c -b-a -9-8 -7-6 -5-4 -3-2 -1-0 21 | x = (x ^ (x << 1)) & 0x55555555; 22 | return x; 23 | } 24 | static uint32_t morton2(uint32_t x, uint32_t y){ 25 | return (part1_by1(y) << 1) + part1_by1(x); 26 | } 27 | 28 | BlockQueue::BlockQueue(const Sampler &sampler, int bwidth, int bheight) 29 | : samplers(sampler.get_subsamplers(bwidth, bheight)), sampler_idx(0), total_time(0) 30 | { 31 | //Sort the samplers in Morton order 32 | std::sort(samplers.begin(), samplers.end(), 33 | [](const std::unique_ptr &a, const std::unique_ptr &b){ 34 | return morton2(a->x_start, a->y_start) < morton2(b->x_start, b->y_start); 35 | }); 36 | } 37 | Sampler* BlockQueue::get_block(){ 38 | //I doubt I'll ever run this on an image big enough to make overflowing back to the 1st sampler 39 | //a concern here, especially since threads exit after getting a null sampler 40 | unsigned int s = sampler_idx.fetch_add(1, std::memory_order_acq_rel); 41 | if (s >= samplers.size()){ 42 | return nullptr; 43 | } 44 | //Only one thread will get the sampler with this number unless we're running 45 | //really fast, so should be ok here 46 | if (s % (samplers.size() / 10) == 0){ 47 | std::cout << "Starting work on block " << s << " of " << samplers.size() 48 | << " : ~" << 100.f * static_cast(s) / samplers.size() << "% of pixels completed" 49 | << std::endl; 50 | if (s == 0){ 51 | prev = std::chrono::high_resolution_clock::now(); 52 | } 53 | else { 54 | auto now = std::chrono::high_resolution_clock::now(); 55 | auto elapsed = std::chrono::duration_cast(now - prev); 56 | auto avg_block = (total_time.count() + elapsed.count()) / s; 57 | std::cout << "Average block time: " << avg_block << "ms" 58 | << "\nRender time so far: " << total_time.count() << "ms" 59 | << "\nEstimated remaining time: " << avg_block * (samplers.size() - s) << "ms\n"; 60 | total_time += elapsed; 61 | prev = now; 62 | } 63 | } 64 | return samplers[s].get(); 65 | } 66 | 67 | -------------------------------------------------------------------------------- /src/film/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(film render_target.cpp camera.cpp color.cpp cie_vals.cpp) 2 | 3 | -------------------------------------------------------------------------------- /src/filters/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(filters box_filter.cpp triangle_filter.cpp gaussian_filter.cpp mitchell_filter.cpp 2 | lanczos_sinc_filter.cpp) 3 | 4 | -------------------------------------------------------------------------------- /src/filters/box_filter.cpp: -------------------------------------------------------------------------------- 1 | #include "filters/box_filter.h" 2 | 3 | BoxFilter::BoxFilter(float w, float h) : Filter(w, h){} 4 | float BoxFilter::weight(float, float) const { 5 | return 1; 6 | } 7 | 8 | -------------------------------------------------------------------------------- /src/filters/gaussian_filter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "filters/gaussian_filter.h" 4 | 5 | GaussianFilter::GaussianFilter(float w, float h, float alpha) 6 | : Filter(w, h), alpha(alpha), exp_x(std::exp(-alpha * w * w)), 7 | exp_y(std::exp(-alpha * h * h)) 8 | {} 9 | float GaussianFilter::weight(float x, float y) const { 10 | return gaussian_1d(x, exp_x) * gaussian_1d(y, exp_y); 11 | } 12 | float GaussianFilter::gaussian_1d(float x, float expv) const { 13 | return std::max(0.f, std::exp(-alpha * x * x) - expv); 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/filters/lanczos_sinc_filter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "linalg/util.h" 3 | #include "filters/lanczos_sinc_filter.h" 4 | 5 | LanczosSincFilter::LanczosSincFilter(float w, float h, float a) 6 | : Filter(w, h), a(a) 7 | {} 8 | float LanczosSincFilter::weight(float x, float y) const { 9 | return lanczos_sinc1d(x * inv_w) * lanczos_sinc1d(y * inv_h); 10 | } 11 | float LanczosSincFilter::lanczos_sinc1d(float x) const { 12 | float abs_x = std::abs(x); 13 | if (abs_x <= 1e-5){ 14 | return 1; 15 | } 16 | if (abs_x >= a){ 17 | return 0; 18 | } 19 | abs_x *= PI; 20 | return a * std::sin(abs_x) * std::sin(abs_x / a) / std::pow(abs_x, 2.f); 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/filters/mitchell_filter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "filters/mitchell_filter.h" 3 | 4 | MitchellFilter::MitchellFilter(float w, float h, float b, float c) 5 | : Filter(w, h), b(b), c(c) 6 | {} 7 | float MitchellFilter::weight(float x, float y) const { 8 | return mitchell_1d(x * inv_w) * mitchell_1d(y * inv_h); 9 | } 10 | float MitchellFilter::mitchell_1d(float x) const { 11 | float abs_x = std::abs(2 * x); 12 | //Filter for |x| >= 2 -> 0 13 | if (abs_x >= 2){ 14 | return 0; 15 | } 16 | float filter = 0; 17 | //Filter for 1 <= |x| < 2 18 | if (abs_x >= 1){ 19 | filter = (-b - 6.f * c) * std::pow(abs_x, 3.f) 20 | + (6.f * b + 30.f * c) * std::pow(abs_x, 2.f) 21 | + (-12.f * b - 48.f * c) * abs_x + 8.f * b + 24.f * c; 22 | } 23 | //Filter for |x| < 1 24 | else { 25 | filter = (12.f - 9.f * b - 6.f * c) * std::pow(abs_x, 3.f) 26 | + (-18.f + 12.f * b + 6.f * c) * std::pow(abs_x, 2.f) 27 | + 6.f - 2.f * b; 28 | } 29 | return filter / 6.f; 30 | } 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/filters/triangle_filter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "filters/triangle_filter.h" 3 | 4 | TriangleFilter::TriangleFilter(float w, float h) : Filter(w, h){} 5 | float TriangleFilter::weight(float x, float y) const { 6 | return std::max(0.f, w - std::abs(x)) * std::max(0.f, h - std::abs(y)); 7 | } 8 | 9 | -------------------------------------------------------------------------------- /src/geometry/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(geometry geometry.cpp sphere.cpp plane.cpp tri_mesh.cpp differential_geometry.cpp 2 | cylinder.cpp disk.cpp cone.cpp) 3 | 4 | -------------------------------------------------------------------------------- /src/geometry/differential_geometry.cpp: -------------------------------------------------------------------------------- 1 | #include "linalg/point.h" 2 | #include "linalg/vector.h" 3 | #include "linalg/ray.h" 4 | #include "linalg/util.h" 5 | #include "geometry/geometry.h" 6 | #include "geometry/differential_geometry.h" 7 | 8 | DifferentialGeometry::DifferentialGeometry() : node(nullptr), hit_side(NONE), u(0), v(0), du_dx(0), dv_dx(0), 9 | du_dy(0), dv_dy(0) 10 | {} 11 | DifferentialGeometry::DifferentialGeometry(const Point &point, const Vector &dpdu, const Vector &dpdv, 12 | const Normal &dn_du, const Normal &dn_dv, const Normal &geom_normal, float u, float v, const Node *node, HITSIDE hit_side) 13 | : point(point), normal(dpdu.cross(dpdv).normalized()), geom_normal(geom_normal), node(node), hit_side(hit_side), 14 | dp_du(dpdu), dp_dv(dpdv), dn_du(dn_du), dn_dv(dn_dv), u(u), v(v), du_dx(0), dv_dx(0), du_dy(0), dv_dy(0) 15 | {} 16 | void DifferentialGeometry::compute_differentials(const RayDifferential &r){ 17 | if (r.has_differentials()){ 18 | //Compute the intersection points of the ray differentials with the plane of the 19 | //hit geometry (eg the plane at point with normal) 20 | float d = -normal.dot(Vector{point}); 21 | float t = -(normal.dot(Vector{r.rx.o}) + d) / normal.dot(r.rx.d); 22 | Point px = r.rx(t); 23 | t = -(normal.dot(Vector{r.ry.o}) + d) / normal.dot(r.ry.d); 24 | Point py = r.ry(t); 25 | dp_dx = px - point; 26 | dp_dy = py - point; 27 | 28 | //Solve linear system with px and py to compute the various parameterization differentials 29 | //be careful to choose the system to not be degenerate 30 | std::array axes; 31 | if (std::abs(normal.x) > std::abs(normal.y) && std::abs(normal.x) > std::abs(normal.z)){ 32 | axes[0] = AXIS::Y; 33 | axes[1] = AXIS::Z; 34 | } 35 | else if (std::abs(normal.y) > std::abs(normal.z)){ 36 | axes[0] = AXIS::X; 37 | axes[1] = AXIS::Z; 38 | } 39 | else { 40 | axes[0] = AXIS::X; 41 | axes[1] = AXIS::Y; 42 | } 43 | std::array mat{ 44 | dp_du[axes[0]], dp_dv[axes[0]], 45 | dp_du[axes[1]], dp_dv[axes[1]] 46 | }; 47 | std::array bx{ 48 | px[axes[0]] - point[axes[0]], 49 | px[axes[1]] - point[axes[1]] 50 | }; 51 | std::array by{ 52 | py[axes[0]] - point[axes[0]], 53 | py[axes[1]] - point[axes[1]] 54 | }; 55 | if (!solve_linear2x2(mat, bx, du_dx, dv_dx)){ 56 | du_dx = 0; 57 | dv_dx = 0; 58 | } 59 | if (!solve_linear2x2(mat, by, du_dy, dv_dy)){ 60 | du_dy = 0; 61 | dv_dy = 0; 62 | } 63 | } 64 | else { 65 | du_dx = 0; 66 | dv_dx = 0; 67 | du_dy = 0; 68 | dv_dy = 0; 69 | dp_dx = Vector{0}; 70 | dp_dy = Vector{0}; 71 | } 72 | } 73 | 74 | -------------------------------------------------------------------------------- /src/geometry/disk.cpp: -------------------------------------------------------------------------------- 1 | #include "monte_carlo/util.h" 2 | #include "linalg/util.h" 3 | #include "linalg/ray.h" 4 | #include "linalg/vector.h" 5 | #include "geometry/disk.h" 6 | 7 | Disk::Disk(float radius, float inner_radius) : radius(radius), inner_radius(inner_radius){} 8 | bool Disk::intersect(Ray &ray, DifferentialGeometry &dg) const { 9 | //We just intersect with the plane the disk lies in and then see if that point is on the disk 10 | //If the ray is perpindicular to the normal there's no 11 | //way for it to hit the plane 12 | if (std::abs(ray.d.z) < 1e-7){ 13 | return false; 14 | } 15 | //We're still treating the plane as infinite here so if it's not 16 | //perpindicular it definitely hits somewhere 17 | float t = -ray.o.z / ray.d.z; 18 | if (t < ray.min_t || t > ray.max_t){ 19 | return false; 20 | } 21 | //It's in the range for the ray so now check if it's in range 22 | //for the finite plane 23 | Point hit = ray(t); 24 | float dist_sqr = hit.x * hit.x + hit.y * hit.y; 25 | if (dist_sqr > radius * radius || dist_sqr < inner_radius * inner_radius){ 26 | return false; 27 | } 28 | float phi = std::atan2(hit.y, hit.x); 29 | if (phi < 0){ 30 | phi += TAU; 31 | } 32 | dg.point = hit; 33 | ray.max_t = t; 34 | 35 | dg.u = phi / TAU; 36 | dg.v = 1 - (std::sqrt(dist_sqr) - inner_radius) / (radius - inner_radius); 37 | float inv_z = 1 - dg.v > 0 ? 1.f / (1 - dg.v) : 0; 38 | dg.dp_du = Vector{-TAU * hit.y, TAU * hit.x, 0}; 39 | dg.dp_dv = Vector{-hit.x * inv_z, -hit.y * inv_z, 0}; 40 | dg.dp_dv *= (radius - inner_radius) / radius; 41 | //Normal doesn't change over the plane so these are trivial 42 | dg.normal = Normal{dg.dp_du.cross(dg.dp_dv).normalized()}; 43 | dg.dn_du = Normal{0, 0, 0}; 44 | dg.dn_dv = Normal{0, 0, 0}; 45 | dg.geom = this; 46 | if (ray.d.dot(dg.normal) < 0){ 47 | dg.hit_side = HITSIDE::FRONT; 48 | } 49 | else { 50 | dg.hit_side = HITSIDE::BACK; 51 | } 52 | return true; 53 | } 54 | BBox Disk::bound() const { 55 | return BBox{Point{-radius, -radius, 0}, Point{radius, radius, 0}}; 56 | } 57 | void Disk::refine(std::vector &prims){ 58 | prims.push_back(this); 59 | } 60 | float Disk::surface_area() const { 61 | return PI * (radius * radius - inner_radius * inner_radius); 62 | } 63 | Point Disk::sample(const GeomSample &gs, Normal &normal) const { 64 | Point p; 65 | auto disk_pos = concentric_sample_disk(gs.u); 66 | p.x = disk_pos[0] * radius; 67 | p.y = disk_pos[1] * radius; 68 | clamp(p.x, inner_radius, radius); 69 | clamp(p.y, inner_radius, radius); 70 | normal = Normal{0, 0, 1}; 71 | return p; 72 | } 73 | bool Disk::attach_light(const Transform&){ 74 | return true; 75 | } 76 | 77 | -------------------------------------------------------------------------------- /src/geometry/plane.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "linalg/vector.h" 3 | #include "linalg/ray.h" 4 | #include "geometry/plane.h" 5 | 6 | bool Plane::intersect(Ray &ray, DifferentialGeometry &diff_geom) const { 7 | //If the ray is perpindicular to the normal there's no 8 | //way for it to hit the plane 9 | if (std::abs(ray.d.z) < 1e-8){ 10 | return false; 11 | } 12 | //We're still treating the plane as infinite here so if it's not 13 | //perpindicular it definitely hits somewhere 14 | float t = -ray.o.z / ray.d.z; 15 | if (t < ray.min_t || t > ray.max_t){ 16 | return false; 17 | } 18 | //It's in the range for the ray so now check if it's in range 19 | //for the finite plane 20 | Point hit = ray(t); 21 | if (hit.x >= -1 && hit.x <= 1 && hit.y >= -1 && hit.y <= 1){ 22 | ray.max_t = t; 23 | diff_geom.point = hit; 24 | diff_geom.normal = Normal{0, 0, 1}; 25 | diff_geom.geom_normal = diff_geom.normal; 26 | if (ray.d.dot(diff_geom.normal) < 0){ 27 | diff_geom.hit_side = HITSIDE::FRONT; 28 | } 29 | else { 30 | diff_geom.hit_side = HITSIDE::BACK; 31 | } 32 | //Compute parameterization of surface and various derivatives for texturing 33 | //Plane is parameterized by x and y coords 34 | diff_geom.u = (hit.x + 1) / 2; 35 | //We flip the y parameterization to put image top-left at the top-left of the plane 36 | diff_geom.v = -(hit.y + 1) / 2 + 1; 37 | //The change in x/y vs. u/v. Is this correct? 38 | diff_geom.dp_du = Vector{2, 0, 0}; 39 | diff_geom.dp_dv = Vector{0, 2, 0}; 40 | //Normal doesn't change over the plane so these are trivial 41 | diff_geom.dn_du = Normal{0, 0, 0}; 42 | diff_geom.dn_dv = Normal{0, 0, 0}; 43 | diff_geom.geom = this; 44 | return true; 45 | } 46 | return false; 47 | } 48 | BBox Plane::bound() const { 49 | return BBox{Point{-1, -1, 0}, Point{1, 1, 0}}; 50 | } 51 | void Plane::refine(std::vector &prims){ 52 | prims.push_back(this); 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/integrator/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(integrator surface_integrator.cpp path_integrator.cpp bidir_path_integrator.cpp 2 | whitted_integrator.cpp photon_map_integrator.cpp emission_integrator.cpp 3 | single_scattering_integrator.cpp) 4 | 5 | -------------------------------------------------------------------------------- /src/integrator/emission_integrator.cpp: -------------------------------------------------------------------------------- 1 | #include "scene.h" 2 | #include "integrator/emission_integrator.h" 3 | 4 | EmissionIntegrator::EmissionIntegrator(float step_size) : step_size(step_size){} 5 | Colorf EmissionIntegrator::radiance(const Scene &scene, const Renderer&, const RayDifferential &ray, 6 | Sampler &sampler, MemoryPool&, Colorf &transmit) const 7 | { 8 | const VolumeNode *vol = scene.get_volume_root(); 9 | std::array t_range; 10 | if (vol == nullptr || !vol->intersect(ray, t_range) || t_range[0] == t_range[1]){ 11 | transmit = Colorf{1}; 12 | return Colorf{0}; 13 | } 14 | Colorf rad; 15 | //We integrate through the volume via ray marching with steps of size step_size 16 | int n_samples = std::ceil((t_range[1] - t_range[0]) / step_size); 17 | float step = (t_range[1] - t_range[0]) / n_samples; 18 | transmit = Colorf{1}; 19 | Point p = ray(t_range[0]), p_prev; 20 | Vector w_o = -ray.d; 21 | //Our first point inside the volume is offset by some random value 22 | float t = t_range[0] + sampler.random_float() * step; 23 | //Step through the volume 24 | for (int i = 0; i < n_samples; ++i, t += step, p_prev = p){ 25 | p = ray(t); 26 | //Step forward and update the transmittance to include contrubition from this segment 27 | Ray step_ray{p_prev, p - p_prev, ray, 0, 1}; 28 | Colorf step_tau = -vol->optical_thickness(step_ray, 0.5 * step_size, sampler.random_float()); 29 | transmit *= step_tau.exp(); 30 | 31 | //We consider terminating the ray if the transmittance is very low 32 | if (transmit.luminance() < 1e-3){ 33 | const float continue_prob = 0.5; 34 | if (sampler.random_float() > continue_prob){ 35 | transmit = 0; 36 | break; 37 | } 38 | transmit /= continue_prob; 39 | } 40 | //Add in emission contribution from emissive volumes 41 | rad += transmit * vol->emission(p, w_o); 42 | } 43 | return rad * step; 44 | } 45 | Colorf EmissionIntegrator::transmittance(const Scene &scene, const Renderer&, const RayDifferential &ray, 46 | Sampler &sampler, MemoryPool&) const 47 | { 48 | const VolumeNode *vol = scene.get_volume_root(); 49 | if (vol == nullptr){ 50 | return Colorf{1}; 51 | } 52 | Colorf tau = -vol->optical_thickness(ray, step_size, sampler.random_float()); 53 | return tau.exp(); 54 | } 55 | 56 | -------------------------------------------------------------------------------- /src/integrator/whitted_integrator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "scene.h" 3 | #include "material/material.h" 4 | #include "lights/light.h" 5 | #include "lights/area_light.h" 6 | #include "lights/occlusion_tester.h" 7 | #include "material/bsdf.h" 8 | #include "renderer/renderer.h" 9 | #include "integrator/whitted_integrator.h" 10 | 11 | WhittedIntegrator::WhittedIntegrator(int max_depth) : max_depth(max_depth){} 12 | Colorf WhittedIntegrator::illumination(const Scene &scene, const Renderer &renderer, const RayDifferential &ray, 13 | DifferentialGeometry &dg, Sampler &sampler, MemoryPool &pool) const 14 | { 15 | Vector w_o = -ray.d; 16 | Colorf illum; 17 | const Material *mat = dg.node->get_material(); 18 | const AreaLight *area_light = dg.node->get_area_light(); 19 | if (area_light){ 20 | illum += area_light->radiance(dg.point, dg.normal, w_o); 21 | } 22 | if (!mat){ 23 | return illum; 24 | } 25 | dg.compute_differentials(ray); 26 | BSDF *bsdf = mat->get_bsdf(dg, pool); 27 | 28 | LightSample lsample; 29 | //Compute the incident light from all lights in the scene 30 | for (const auto &l : scene.get_light_cache()){ 31 | sampler.get_samples(&lsample.u, 1); 32 | sampler.get_samples(&lsample.light, 1); 33 | Vector w_i; 34 | float pdf_val = 0; 35 | OcclusionTester occlusion; 36 | Colorf li = l.second->sample(bsdf->dg.point, lsample, w_i, pdf_val, occlusion); 37 | //If there's no light or no probability for this sample there's no illumination 38 | if (li.luminance() == 0 || pdf_val == 0){ 39 | continue; 40 | } 41 | Colorf c = (*bsdf)(w_o, w_i); 42 | if (!c.is_black() && !occlusion.occluded(scene)){ 43 | illum += c * li * std::abs(w_i.dot(bsdf->dg.normal)) / pdf_val; 44 | } 45 | } 46 | if (ray.depth < max_depth){ 47 | illum += spec_reflect(ray, *bsdf, renderer, scene, sampler, pool); 48 | illum += spec_transmit(ray, *bsdf, renderer, scene, sampler, pool); 49 | } 50 | return illum; 51 | } 52 | 53 | -------------------------------------------------------------------------------- /src/lights/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(lights light.cpp point_light.cpp occlusion_tester.cpp area_light.cpp) 2 | 3 | -------------------------------------------------------------------------------- /src/lights/area_light.cpp: -------------------------------------------------------------------------------- 1 | #include "scene.h" 2 | #include "geometry/geometry.h" 3 | #include "monte_carlo/util.h" 4 | #include "lights/area_light.h" 5 | 6 | AreaLight::AreaLight(const Transform &to_world, const Colorf &emit, Geometry *geometry, int n_samples) 7 | : Light(to_world, n_samples), emit(emit), geometry(geometry), surface_area(geometry->surface_area()) 8 | {} 9 | Colorf AreaLight::radiance(const Point&, const Normal &n, const Vector &w) const { 10 | return w.dot(n) > 0 ? emit : Colorf{0}; 11 | } 12 | Colorf AreaLight::sample(const Point &p, const LightSample &lsample, Vector &w_i, float &pdf_val, 13 | OcclusionTester &occlusion) const 14 | { 15 | Normal n_l; 16 | Point p_l = to_light(p); 17 | Point ps_l = geometry->sample(p_l, GeomSample{lsample.u, lsample.light}, n_l); 18 | Point ps_w = to_world(ps_l); 19 | Vector w_il = (ps_l - p_l).normalized(); 20 | to_world(w_il, w_i); 21 | pdf_val = geometry->pdf(p_l, w_il); 22 | occlusion.set_points(p, ps_w); 23 | return radiance(ps_w, n_l, -w_il); 24 | } 25 | Colorf AreaLight::sample(const Scene&, const LightSample &lsample, const std::array &b, 26 | Ray &ray, Normal &normal, float &pdf_val) const 27 | { 28 | Point o = geometry->sample(GeomSample{lsample.u, lsample.light}, normal); 29 | Vector d = uniform_sample_sphere(b); 30 | //Make sure the ray is heading out of the surface 31 | if (d.dot(normal) < 0){ 32 | d *= -1; 33 | } 34 | Point o_w = to_world(o); 35 | Vector d_w = to_world(d); 36 | ray = Ray{o_w, d_w, 0.001}; 37 | pdf_val = geometry->pdf(o) * INV_TAU; 38 | to_world(normal, normal); 39 | return radiance(o_w, normal, d_w); 40 | } 41 | Colorf AreaLight::power(const Scene&) const { 42 | return emit * surface_area * PI; 43 | } 44 | bool AreaLight::delta_light() const { 45 | return false; 46 | } 47 | float AreaLight::pdf(const Point &p, const Vector &w_i) const { 48 | return geometry->pdf(to_light(p), to_light(w_i)); 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/lights/light.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "lights/light.h" 3 | 4 | Light::Light(const Transform &to_world, int n_samples) 5 | : to_world(to_world), to_light(to_world.inverse()), n_samples(n_samples) 6 | { 7 | if (to_light.has_scale()){ 8 | std::cout << "WARNING: The renderer assumes that light transformations" 9 | << " don't have any scaling factors in them, this could be a problem!\n"; 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/lights/occlusion_tester.cpp: -------------------------------------------------------------------------------- 1 | #include "scene.h" 2 | #include "geometry/differential_geometry.h" 3 | #include "lights/occlusion_tester.h" 4 | 5 | void OcclusionTester::set_points(const Point &a, const Point &b){ 6 | ray = Ray{a, b - a, 0.001, 0.999}; 7 | } 8 | void OcclusionTester::set_ray(const Point &p, const Vector &d){ 9 | ray = Ray{p, d.normalized(), 0.001}; 10 | } 11 | bool OcclusionTester::occluded(const Scene &scene){ 12 | DifferentialGeometry dg; 13 | return scene.get_root().intersect(ray, dg); 14 | } 15 | Colorf OcclusionTester::transmittance(const Scene &scene, const Renderer &renderer, Sampler &sampler, 16 | MemoryPool &pool) 17 | { 18 | return renderer.transmittance(scene, RayDifferential{ray}, sampler, pool); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/lights/point_light.cpp: -------------------------------------------------------------------------------- 1 | #include "linalg/util.h" 2 | #include "monte_carlo/util.h" 3 | #include "lights/point_light.h" 4 | 5 | PointLight::PointLight(const Transform &to_world, const Colorf &intensity) 6 | : Light(to_world), position(to_world(Point{0, 0, 0})), intensity(intensity) 7 | {} 8 | Colorf PointLight::sample(const Point &p, const LightSample&, Vector &w_i, float &pdf_val, OcclusionTester &occlusion) const { 9 | w_i = (position - p).normalized(); 10 | pdf_val = 1; 11 | occlusion.set_points(p, position); 12 | return intensity / position.distance_sqr(p); 13 | } 14 | Colorf PointLight::sample(const Scene&, const LightSample &lsample, const std::array&, 15 | Ray &ray, Normal &normal, float &pdf_val) const 16 | { 17 | ray = Ray{position, uniform_sample_sphere(lsample.u)}; 18 | normal = Normal{ray.d}; 19 | pdf_val = uniform_sphere_pdf(); 20 | return intensity; 21 | } 22 | Colorf PointLight::power(const Scene&) const { 23 | return 4 * PI * intensity; 24 | } 25 | bool PointLight::delta_light() const { 26 | return true; 27 | } 28 | float PointLight::pdf(const Point&, const Vector&) const { 29 | return 0; 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/linalg/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(linalg matrix4.cpp transform.cpp quaternion.cpp animated_transform.cpp) 2 | 3 | -------------------------------------------------------------------------------- /src/linalg/quaternion.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "linalg/quaternion.h" 4 | 5 | Quaternion::Quaternion(const Transform &t){ 6 | //See Ken Shoemake - Quaternions (1991) 7 | float trace = t.mat[0][0] + t.mat[1][1] + t.mat[2][2]; 8 | if (trace > 0){ 9 | float s = std::sqrt(trace + 1.f); 10 | w = s / 2; 11 | s = 0.5 / s; 12 | v.x = (t.mat[2][1] - t.mat[1][2]) * s; 13 | v.y = (t.mat[0][2] - t.mat[2][0]) * s; 14 | v.z = (t.mat[1][0] - t.mat[0][1]) * s; 15 | } 16 | else { 17 | const std::array next{1, 2, 0}; 18 | std::array q; 19 | int i = 0; 20 | if (t.mat[1][1] > t.mat[0][0]){ 21 | i = 1; 22 | } 23 | if (t.mat[2][2] > t.mat[i][i]){ 24 | i = 2; 25 | } 26 | int j = next[i]; 27 | int k = next[j]; 28 | float s = std::sqrt((t.mat[i][i] - (t.mat[j][j] + t.mat[k][k])) + 1); 29 | q[i] = s * 0.5; 30 | if (s != 0){ 31 | s = 0.5 / s; 32 | } 33 | w = (t.mat[k][j] - t.mat[j][k]) * s; 34 | q[j] = (t.mat[j][i] + t.mat[i][j]) * s; 35 | q[k] = (t.mat[k][i] + t.mat[i][k]) * s; 36 | v = Vector{q[0], q[1], q[2]}; 37 | } 38 | } 39 | Quaternion Quaternion::slerp(const Quaternion &q, float t) const { 40 | float cos_theta = dot(q); 41 | if (cos_theta > 0.9995){ 42 | return ((1 - t) * *this + t * q).normalized(); 43 | } 44 | float theta = std::acos(clamp(cos_theta, -1.f, 1.f)); 45 | Quaternion q_perp = (q - *this * cos_theta).normalized(); 46 | return *this * std::cos(theta * t) + q_perp * std::sin(theta * t); 47 | } 48 | Transform Quaternion::to_transform() const { 49 | float xx = v.x * v.x, yy = v.y * v.y, zz = v.z * v.z, 50 | xy = v.x * v.y, xz = v.x * v.z, yz = v.y * v.z, 51 | wx = v.x * w, wy = v.y * w, wz = v.z * w; 52 | Matrix4 m{{ 53 | 1 - 2 * (yy + zz), 2 * (xy + wz), 2 * (xz - wy), 0, 54 | 2 * (xy - wz), 1 - 2 * (xx + zz), 2 * (yz + wx), 0, 55 | 2 * (xz + wy), 2 * (yz - wx), 1 - 2 * (xx + yy), 0, 56 | 0, 0, 0, 1 57 | }}; 58 | //Transpose since we're left-handed. TODO are we? 59 | return Transform{m.transpose(), m}; 60 | } 61 | 62 | -------------------------------------------------------------------------------- /src/loaders/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(loaders load_scene.cpp load_material.cpp load_light.cpp load_filter.cpp load_sampler.cpp 2 | load_texture.cpp load_renderer.cpp load_volume.cpp ${tinyxml2_DIR}/tinyxml2.cpp) 3 | 4 | -------------------------------------------------------------------------------- /src/loaders/load_filter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "filters/filter.h" 5 | #include "filters/box_filter.h" 6 | #include "filters/triangle_filter.h" 7 | #include "filters/gaussian_filter.h" 8 | #include "filters/mitchell_filter.h" 9 | #include "filters/lanczos_sinc_filter.h" 10 | 11 | std::unique_ptr load_filter(tinyxml2::XMLElement *elem){ 12 | tinyxml2::XMLElement *f = elem->FirstChildElement("filter"); 13 | if (!f){ 14 | return std::make_unique(0.5, 0.5); 15 | } 16 | std::string type = f->Attribute("type"); 17 | float w = f->FloatAttribute("w"); 18 | float h = f->FloatAttribute("h"); 19 | if (type == "box"){ 20 | std::cout << "Using BoxFilter\n"; 21 | return std::make_unique(w, h); 22 | } 23 | if (type == "triangle"){ 24 | std::cout << "Using TriangleFilter\n"; 25 | return std::make_unique(w, h); 26 | } 27 | if (type == "gaussian"){ 28 | std::cout << "Using GaussianFilter\n"; 29 | float alpha = f->FloatAttribute("alpha"); 30 | return std::make_unique(w, h, alpha); 31 | } 32 | if (type == "mitchell"){ 33 | std::cout << "Using MitchellFilter\n"; 34 | float b = f->FloatAttribute("b"); 35 | float c = f->FloatAttribute("c"); 36 | return std::make_unique(w, h, b, c); 37 | } 38 | if (type == "lanczos"){ 39 | std::cout << "Using LanczosSincFilter\n"; 40 | float a = f->FloatAttribute("a"); 41 | return std::make_unique(w, h, a); 42 | } 43 | std::cout << "Error: unrecognized filter type, defaulting to BoxFilter{0.5, 0.5}\n"; 44 | return std::make_unique(0.5, 0.5); 45 | } 46 | 47 | -------------------------------------------------------------------------------- /src/loaders/load_light.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "linalg/transform.h" 5 | #include "lights/point_light.h" 6 | #include "lights/area_light.h" 7 | #include "loaders/load_scene.h" 8 | #include "loaders/load_light.h" 9 | 10 | /* 11 | * Load the DirectLight properties and return the light 12 | * elem should be the root of the direct light being loaded 13 | */ 14 | //static std::unique_ptr load_directl(tinyxml2::XMLElement *elem); 15 | /* 16 | * Load the PointLight properties and return the light 17 | * elem should be the root of the direct light being loaded 18 | */ 19 | static std::unique_ptr load_point_light(tinyxml2::XMLElement *elem); 20 | 21 | void load_lights(tinyxml2::XMLElement *elem, LightCache &cache){ 22 | using namespace tinyxml2; 23 | using namespace std::literals; 24 | for (XMLNode *n = elem; n; n = n->NextSibling()){ 25 | if (n->Value() == "light"s){ 26 | XMLElement *l = n->ToElement(); 27 | std::string name = l->Attribute("name"); 28 | std::cout << "Loading light: " << name << std::endl; 29 | std::unique_ptr light = nullptr; 30 | std::string type = l->Attribute("type"); 31 | if (type == "point"){ 32 | light = load_point_light(l); 33 | } 34 | if (light != nullptr){ 35 | cache.add(name, std::move(light)); 36 | } 37 | } 38 | else { 39 | //The lights are all passed in a block, so once 40 | //we hit something not a light we're done loading 41 | return; 42 | } 43 | } 44 | } 45 | std::unique_ptr load_point_light(tinyxml2::XMLElement *elem){ 46 | Colorf color{1, 1, 1}; 47 | Vector pos{0, 0, 0}; 48 | read_color(elem->FirstChildElement("intensity"), color); 49 | read_vector(elem->FirstChildElement("position"), pos); 50 | return std::make_unique(Transform::translate(pos), color); 51 | } 52 | 53 | -------------------------------------------------------------------------------- /src/loaders/load_renderer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "renderer/renderer.h" 4 | #include "integrator/path_integrator.h" 5 | #include "integrator/whitted_integrator.h" 6 | #include "integrator/bidir_path_integrator.h" 7 | #include "integrator/photon_map_integrator.h" 8 | #include "integrator/emission_integrator.h" 9 | #include "integrator/single_scattering_integrator.h" 10 | #include "loaders/load_renderer.h" 11 | 12 | std::unique_ptr load_surface_integrator(tinyxml2::XMLElement *elem){ 13 | tinyxml2::XMLElement *r = elem->FirstChildElement("renderer"); 14 | if (!r){ 15 | return std::make_unique(3, 8); 16 | } 17 | std::string type = r->Attribute("type"); 18 | int max_depth = r->IntAttribute("max_depth"); 19 | if (type == "whitted"){ 20 | return std::make_unique(max_depth); 21 | } 22 | if (type == "photon"){ 23 | int num_caustic = r->IntAttribute("num_caustic"); 24 | int num_indirect = r->IntAttribute("num_indirect"); 25 | int max_phot_depth = 6, query_size = 50, final_gather_samples = 32; 26 | float max_dist_sqr = 0.1, gather_angle = 10, max_radiance_dist = -1; 27 | r->QueryIntAttribute("max_phot_depth", &max_phot_depth); 28 | r->QueryIntAttribute("query_size", &query_size); 29 | r->QueryIntAttribute("final_gather_samples", &final_gather_samples); 30 | r->QueryFloatAttribute("max_dist_sqr", &max_dist_sqr); 31 | r->QueryFloatAttribute("gather_angle", &gather_angle); 32 | r->QueryFloatAttribute("max_radiance_dist", &max_radiance_dist); 33 | return std::make_unique(num_caustic, num_indirect, max_depth, 34 | max_phot_depth, query_size, final_gather_samples, max_dist_sqr, gather_angle, 35 | max_radiance_dist); 36 | } 37 | 38 | int min_depth = r->IntAttribute("min_depth"); 39 | if (type == "path"){ 40 | return std::make_unique(min_depth, max_depth); 41 | } 42 | if (type == "bidir"){ 43 | return std::make_unique(min_depth, max_depth); 44 | } 45 | std::cout << "Load renderer error: Unrecognized surface integrator " << type << std::endl; 46 | return std::make_unique(3, 8); 47 | } 48 | std::unique_ptr load_volume_integrator(tinyxml2::XMLElement *elem){ 49 | tinyxml2::XMLElement *integrator = elem->FirstChildElement("vol_integrator"); 50 | if (!integrator){ 51 | return nullptr; 52 | } 53 | std::string type = integrator->Attribute("type"); 54 | float step = integrator->FloatAttribute("step"); 55 | 56 | if (type == "emission"){ 57 | return std::make_unique(step); 58 | } 59 | if (type == "single_scatter"){ 60 | return std::make_unique(step); 61 | } 62 | std::cout << "Scene error: Unrecognized volume integrator " << type << std::endl; 63 | return nullptr; 64 | } 65 | 66 | -------------------------------------------------------------------------------- /src/loaders/load_sampler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "loaders/load_sampler.h" 6 | #include "samplers/sampler.h" 7 | #include "samplers/stratified_sampler.h" 8 | #include "samplers/ld_sampler.h" 9 | #include "samplers/adaptive_sampler.h" 10 | 11 | std::unique_ptr load_sampler(tinyxml2::XMLElement *elem, size_t w, size_t h){ 12 | tinyxml2::XMLElement *s = elem->FirstChildElement("sampler"); 13 | if (!s){ 14 | return std::make_unique(0, w, 0, h, 1); 15 | } 16 | std::string type = s->Attribute("type"); 17 | if (type == "stratified"){ 18 | int spp = s->IntAttribute("spp"); 19 | std::cout << "Using StratifiedSampler with " << spp * spp << " samples per pixel\n"; 20 | return std::make_unique(0, w, 0, h, spp); 21 | } 22 | if (type == "lowdiscrepancy"){ 23 | int spp = s->IntAttribute("spp"); 24 | std::cout << "Using LDSampler with " << spp << " samples per pixel\n"; 25 | return std::make_unique(0, w, 0, h, spp); 26 | } 27 | if (type == "adaptive"){ 28 | int min_spp = s->IntAttribute("min"); 29 | int max_spp = s->IntAttribute("max"); 30 | std::cout << "Using AdaptiveSampler with min: " << min_spp 31 | << " and max: " << max_spp << " samples per pixel\n"; 32 | return std::make_unique(0, w, 0, h, min_spp, max_spp); 33 | } 34 | std::cout << "Error: unrecognized sampler type, defaulting to StratifiedSampler" 35 | << " with 1 sampler per pixel\n"; 36 | return std::make_unique(0, w, 0, h, 1); 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/material/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(material bxdf.cpp fresnel.cpp specular_reflection.cpp specular_transmission.cpp 2 | lambertian.cpp bsdf.cpp matte_material.cpp oren_nayer.cpp scaled_bxdf.cpp plastic_material.cpp 3 | torrance_sparrow.cpp microfacet_distribution.cpp blinn_distribution.cpp anisotropic_distribution.cpp 4 | btdf_adapter.cpp translucent_material.cpp metal_material.cpp merl_brdf.cpp merl_material.cpp glass_material.cpp 5 | mix_material.cpp specular_metal_material.cpp) 6 | 7 | -------------------------------------------------------------------------------- /src/material/blinn_distribution.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "material/bxdf.h" 3 | #include "material/blinn_distribution.h" 4 | 5 | BlinnDistribution::BlinnDistribution(float exponent) : exponent(exponent){} 6 | float BlinnDistribution::operator()(const Vector &w_h) const { 7 | return (exponent + 2) * INV_TAU * std::pow(std::abs(BxDF::cos_theta(w_h)), exponent); 8 | } 9 | void BlinnDistribution::sample(const Vector &w_o, Vector &w_i, const std::array &u, float &pdf_val) const { 10 | //Sample a direction on the hemisphere for the half-vector 11 | float cos_theta = std::pow(u[0], 1 / (exponent + 1)); 12 | float sin_theta = std::sqrt(std::max(0.f, 1 - cos_theta * cos_theta)); 13 | float phi = TAU * u[1]; 14 | Vector w_h = spherical_dir(sin_theta, cos_theta, phi); 15 | if (!BxDF::same_hemisphere(w_o, w_h)){ 16 | w_h = -w_h; 17 | } 18 | //The sampled incident direction is the outgoing direction reflected about the half-vector 19 | w_i = -w_o + 2 * w_o.dot(w_h) * w_h; 20 | pdf_val = ((exponent + 1) * std::pow(cos_theta, exponent)) / (TAU * 4 * w_o.dot(w_h)); 21 | if (w_o.dot(w_h) <= 0){ 22 | pdf_val = 0; 23 | } 24 | } 25 | float BlinnDistribution::pdf(const Vector &w_o, const Vector &w_i) const { 26 | Vector w_h = (w_o + w_i).normalized(); 27 | float cos_theta = std::abs(BxDF::cos_theta(w_h)); 28 | float pdf_val = ((exponent + 1) * std::pow(cos_theta, exponent)) / (TAU * 4 * w_o.dot(w_h)); 29 | if (w_o.dot(w_h) <= 0){ 30 | pdf_val = 0; 31 | } 32 | return pdf_val; 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/material/btdf_adapter.cpp: -------------------------------------------------------------------------------- 1 | #include "material/btdf_adapter.h" 2 | 3 | BTDFAdapter::BTDFAdapter(BxDF *b) : BxDF(BxDFTYPE(b->type ^ (BxDFTYPE::REFLECTION | BxDFTYPE::TRANSMISSION))), bxdf(b){} 4 | Colorf BTDFAdapter::operator()(const Vector &w_o, const Vector &w_i) const { 5 | return (*bxdf)(w_o, flip_hemisphere(w_i)); 6 | } 7 | Colorf BTDFAdapter::sample(const Vector &w_o, Vector &w_i, const std::array &samples, float &pdf_val) const { 8 | Colorf f = bxdf->sample(w_o, w_i, samples, pdf_val); 9 | w_i = flip_hemisphere(w_i); 10 | return f; 11 | } 12 | Colorf BTDFAdapter::rho_hd(const Vector &w_o, const std::array *samples, int n_samples) const { 13 | return bxdf->rho_hd(flip_hemisphere(w_o), samples, n_samples); 14 | } 15 | Colorf BTDFAdapter::rho_hh(const std::array *samples_a, const std::array *samples_b, int n_samples) const { 16 | return bxdf->rho_hh(samples_a, samples_b, n_samples); 17 | } 18 | float BTDFAdapter::pdf(const Vector &w_o, const Vector &w_i) const { 19 | return bxdf->pdf(w_o, flip_hemisphere(w_i)); 20 | } 21 | Vector BTDFAdapter::flip_hemisphere(const Vector &v){ 22 | return Vector{v.x, v.y, -v.z}; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/material/bxdf.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "film/color.h" 4 | #include "monte_carlo/util.h" 5 | #include "material/bxdf.h" 6 | 7 | BxDF::BxDF(BxDFTYPE t) : type(t){} 8 | bool BxDF::matches(BxDFTYPE flags) const { 9 | return (type & flags) == type; 10 | } 11 | Colorf BxDF::sample(const Vector &wo, Vector &wi, const std::array &samples, float &pdf_val) const { 12 | wi = cos_sample_hemisphere(samples); 13 | //We may need to flip the sample to be in the same hemisphere as wo 14 | if (wo.z < 0){ 15 | wi.z *= -1; 16 | } 17 | pdf_val = pdf(wo, wi); 18 | return (*this)(wo, wi); 19 | } 20 | Colorf BxDF::rho_hd(const Vector &wo, const std::array *samples, int n_samples) const { 21 | Colorf c; 22 | for (int i = 0; i < n_samples; ++i){ 23 | Vector wi; 24 | float pdf_val = 0; 25 | Colorf f = sample(wo, wi, samples[i], pdf_val); 26 | if (pdf_val > 0){ 27 | c += f * std::abs(cos_theta(wi)) / pdf_val; 28 | } 29 | } 30 | return c / n_samples; 31 | } 32 | Colorf BxDF::rho_hh(const std::array *samples_a, const std::array *samples_b, 33 | int n_samples) const 34 | { 35 | Colorf c; 36 | for (int i = 0; i < n_samples; ++i){ 37 | Vector wo = uniform_sample_hemisphere(samples_a[i]); 38 | float pdf_o = uniform_hemisphere_pdf(); 39 | Vector wi; 40 | float pdf_i = 0; 41 | Colorf f = sample(wo, wi, samples_b[i], pdf_i); 42 | if (pdf_i > 0){ 43 | c += f * std::abs(cos_theta(wo)) * std::abs(cos_theta(wi)) / (pdf_o * pdf_i); 44 | } 45 | } 46 | return c / (PI * n_samples); 47 | } 48 | float BxDF::pdf(const Vector &wo, const Vector &wi) const { 49 | //If wo and wi are not in the same hemisphere there's no chance of sampling 50 | return same_hemisphere(wo, wi) ? std::abs(cos_theta(wi)) * INV_PI : 0; 51 | } 52 | 53 | -------------------------------------------------------------------------------- /src/material/fresnel.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "linalg/util.h" 3 | #include "material/fresnel.h" 4 | 5 | Colorf fresnel_dielectric(float cos_i, float cos_t, float eta_i, float eta_t){ 6 | Colorf r_par = (eta_t * cos_i - eta_i * cos_t) / (eta_t * cos_i + eta_i * cos_t); 7 | Colorf r_perp = (eta_i * cos_i - eta_t * cos_t) / (eta_i * cos_i + eta_t * cos_t); 8 | return 0.5 * (r_par * r_par + r_perp * r_perp); 9 | } 10 | Colorf fresnel_conductor(float cos_i, const Colorf &eta, const Colorf &k){ 11 | Colorf a = (eta * eta + k * k) * cos_i * cos_i; 12 | Colorf r_par = (a - 2 * eta * cos_i + 1) / (a + 2 * eta * cos_i + 1); 13 | a = eta * eta + k * k; 14 | Colorf r_perp = (a - 2 * eta * cos_i + cos_i * cos_i) / (a + 2 * eta * cos_i + cos_i * cos_i); 15 | //These are actually r_par^2 and r_perp^2, so don't square here 16 | return 0.5 * (r_par + r_perp); 17 | } 18 | 19 | FresnelDielectric::FresnelDielectric(float eta_i, float eta_t) 20 | : eta_i(eta_i), eta_t(eta_t) 21 | {} 22 | Colorf FresnelDielectric::operator()(float cos_i) const { 23 | //We need to find out which side of the material we're incident on so 24 | //we can pass the correct indices of refraction 25 | cos_i = clamp(cos_i, -1.f, 1.f); 26 | float ei = cos_i > 0 ? eta_i : eta_t; 27 | float et = cos_i > 0 ? eta_t : eta_i; 28 | float sin_t = ei / et * std::sqrt(std::max(0.f, 1.f - cos_i * cos_i)); 29 | //Handle total internal reflection 30 | if (sin_t >= 1){ 31 | return Colorf{1}; 32 | } 33 | float cos_t = std::sqrt(std::max(0.f, 1.f - sin_t * sin_t)); 34 | return fresnel_dielectric(std::abs(cos_i), cos_t, ei, et); 35 | } 36 | FresnelConductor::FresnelConductor(const Colorf &eta, const Colorf &k) : eta(eta), k(k){} 37 | Colorf FresnelConductor::operator()(float cos_i) const { 38 | return fresnel_conductor(std::abs(cos_i), eta, k); 39 | } 40 | 41 | Colorf FresnelNoOp::operator()(float) const { 42 | return Colorf{1}; 43 | } 44 | 45 | FresnelFlip::FresnelFlip(const Fresnel *fresnel) : fresnel(fresnel){} 46 | Colorf FresnelFlip::operator()(float cos_i) const { 47 | return 1 - (*fresnel)(cos_i); 48 | } 49 | 50 | -------------------------------------------------------------------------------- /src/material/glass_material.cpp: -------------------------------------------------------------------------------- 1 | #include "film/color.h" 2 | #include "textures/texture.h" 3 | #include "material/specular_reflection.h" 4 | #include "material/specular_transmission.h" 5 | #include "material/fresnel.h" 6 | #include "material/glass_material.h" 7 | 8 | GlassMaterial::GlassMaterial(const Texture *reflect, const Texture *transmit, float refr_index) 9 | : reflect(reflect), transmit(transmit), refr_index(refr_index) 10 | {} 11 | BSDF* GlassMaterial::get_bsdf(const DifferentialGeometry &dg, MemoryPool &pool) const { 12 | BSDF *bsdf = pool.alloc(dg, refr_index); 13 | Colorf refl = reflect->sample(dg); 14 | if (!refl.is_black()){ 15 | bsdf->add(pool.alloc(refl, 16 | pool.alloc(1.f, refr_index))); 17 | } 18 | Colorf trans = transmit->sample(dg); 19 | if (!trans.is_black()){ 20 | bsdf->add(pool.alloc(trans, 21 | pool.alloc(1.f, refr_index))); 22 | } 23 | return bsdf; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /src/material/lambertian.cpp: -------------------------------------------------------------------------------- 1 | #include "linalg/util.h" 2 | #include "material/lambertian.h" 3 | 4 | Lambertian::Lambertian(const Colorf &reflectance) 5 | : BxDF(BxDFTYPE(BxDFTYPE::REFLECTION | BxDFTYPE::DIFFUSE)), reflectance(reflectance) 6 | {} 7 | Colorf Lambertian::operator()(const Vector&, const Vector&) const { 8 | return reflectance * INV_PI; 9 | } 10 | Colorf Lambertian::rho_hd(const Vector&, const std::array*, int) const { 11 | return reflectance; 12 | } 13 | Colorf Lambertian::rho_hh(const std::array*, const std::array*, int) const { 14 | return reflectance; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/material/matte_material.cpp: -------------------------------------------------------------------------------- 1 | #include "film/color.h" 2 | #include "textures/texture.h" 3 | #include "material/lambertian.h" 4 | #include "material/oren_nayer.h" 5 | #include "material/matte_material.h" 6 | #include "material/fresnel.h" 7 | 8 | MatteMaterial::MatteMaterial(const Texture *diffuse, float roughness) 9 | : diffuse(diffuse), roughness(roughness) 10 | {} 11 | BSDF* MatteMaterial::get_bsdf(const DifferentialGeometry &dg, MemoryPool &pool) const { 12 | BSDF *bsdf = pool.alloc(dg); 13 | Colorf kd = diffuse->sample(dg).normalized(); 14 | if (!kd.is_black()){ 15 | if (roughness == 0){ 16 | bsdf->add(pool.alloc(kd)); 17 | } 18 | else { 19 | bsdf->add(pool.alloc(kd, roughness)); 20 | } 21 | } 22 | return bsdf; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/material/merl_brdf.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "material/merl_brdf.h" 4 | 5 | MerlBRDF::MerlBRDF(const std::vector &brdf, int n_theta_h, int n_theta_d, int n_phi_d) 6 | : BxDF(BxDFTYPE(BxDFTYPE::REFLECTION | BxDFTYPE::GLOSSY)), brdf(brdf), n_theta_h(n_theta_h), 7 | n_theta_d(n_theta_d), n_phi_d(n_phi_d) 8 | {} 9 | Colorf MerlBRDF::operator()(const Vector &w_oi, const Vector &w_ii) const { 10 | //Find the half-vector and transform into the half angle coordinate system 11 | Vector w_o = w_oi; 12 | Vector w_i = w_ii; 13 | Vector w_h = w_o + w_i; 14 | if (w_h.z < 0){ 15 | w_o = -w_o; 16 | w_i = -w_i; 17 | w_h = -w_h; 18 | } 19 | if (w_h.length_sqr() == 0){ 20 | return Colorf{0}; 21 | } 22 | w_h = w_h.normalized(); 23 | //Directly compute the rows of the matrix performing the rotation of w_h to (0, 0, 1) 24 | float theta_h = spherical_theta(w_h); 25 | float cos_phi_h = cos_phi(w_h); 26 | float sin_phi_h = sin_phi(w_h); 27 | float cos_theta_h = cos_theta(w_h); 28 | float sin_theta_h = sin_theta(w_h); 29 | Vector w_hx{cos_phi_h * cos_theta_h, sin_phi_h * cos_theta_h, -sin_theta_h}; 30 | Vector w_hy{-sin_phi_h, cos_phi_h, 0}; 31 | Vector w_d{w_i.dot(w_hx), w_i.dot(w_hy), w_i.dot(w_h)}; 32 | float theta_d = spherical_theta(w_d); 33 | float phi_d = spherical_phi(w_d); 34 | //Wrap phi_d if needed to keep it in range 35 | if (phi_d > PI){ 36 | phi_d -= PI; 37 | } 38 | int theta_h_idx = map_index(std::sqrt(std::max(0.f, 2 * theta_h / PI)), 1, n_theta_h); 39 | int theta_d_idx = map_index(theta_d, PI / 2, n_theta_d); 40 | int phi_d_idx = map_index(phi_d, PI, n_phi_d); 41 | int i = phi_d_idx + n_phi_d * (theta_d_idx + theta_h_idx * n_theta_d); 42 | assert(i < brdf.size()); 43 | return Colorf{brdf[3 * i], brdf[3 * i + 1], brdf[3 * i + 2]}; 44 | } 45 | 46 | -------------------------------------------------------------------------------- /src/material/merl_material.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "material/bsdf.h" 6 | #include "material/merl_brdf.h" 7 | #include "material/merl_material.h" 8 | 9 | MerlMaterial::MerlMaterial(const std::string &file) : n_theta_h(90), n_theta_d(90), n_phi_d(180){ 10 | if (!load_brdf(file)){ 11 | std::cout << "MerlMaterial error: could not load " << file << std::endl; 12 | std::exit(1); 13 | } 14 | } 15 | BSDF* MerlMaterial::get_bsdf(const DifferentialGeometry &dg, MemoryPool &pool) const { 16 | BSDF *bsdf = pool.alloc(dg); 17 | bsdf->add(pool.alloc(brdf, n_theta_h, n_theta_d, n_phi_d)); 18 | return bsdf; 19 | } 20 | bool MerlMaterial::load_brdf(const std::string &file){ 21 | FILE *f = fopen(file.c_str(), "rb"); 22 | if (!f){ 23 | std::cout << "MerlMaterial error: could not open " << file << std::endl; 24 | return false; 25 | } 26 | std::array dims; 27 | if (fread(dims.data(), sizeof(int), 3, f) != 3){ 28 | std::cout << "MerlMaterial error: file ended unexpectedly" << std::endl; 29 | fclose(f); 30 | return false; 31 | } 32 | int n_vals = dims[0] * dims[1] * dims[2]; 33 | if (n_vals != n_theta_h * n_theta_d * n_phi_d){ 34 | std::cout << "MerlMaterial error: dimensions in file are incorrect" << std::endl; 35 | fclose(f); 36 | return false; 37 | } 38 | brdf.resize(3 * n_vals); 39 | //The values in the file are binary doubles so we need to convert them down 40 | //and scale them as described in the paper 41 | uint32_t chunk_size = 2 * n_phi_d; 42 | std::vector tmp(chunk_size); 43 | int n_chunks = n_vals / chunk_size; 44 | assert(n_vals % n_chunks == 0); 45 | const std::array scaling{1.f / 1500.f, 1.f / 1500.f, 1.66f / 1500.f}; 46 | for (int c = 0; c < 3; ++c){ 47 | int offset = 0; 48 | for (int i = 0; i < n_chunks; ++i){ 49 | if (fread(tmp.data(), sizeof(double), chunk_size, f) != chunk_size){ 50 | std::cout << "MerlMaterial error: file ended unexpectedly" << std::endl; 51 | fclose(f); 52 | return false; 53 | } 54 | for (uint32_t j = 0; j < chunk_size; ++j){ 55 | brdf[3 * offset++ + c] = std::max(0.0, tmp[j] * scaling[c]); 56 | } 57 | } 58 | } 59 | fclose(f); 60 | return true; 61 | } 62 | 63 | -------------------------------------------------------------------------------- /src/material/metal_material.cpp: -------------------------------------------------------------------------------- 1 | #include "material/bsdf.h" 2 | #include "material/blinn_distribution.h" 3 | #include "material/anisotropic_distribution.h" 4 | #include "material/torrance_sparrow.h" 5 | #include "material/lambertian.h" 6 | #include "material/fresnel.h" 7 | #include "material/metal_material.h" 8 | 9 | MetalMaterial::MetalMaterial(const Texture *refr_index, const Texture *absoption_coef, float rough_x, float rough_y) 10 | : refr_index(refr_index), absoption_coef(absoption_coef), rough_x(rough_x), rough_y(rough_y) 11 | {} 12 | BSDF* MetalMaterial::get_bsdf(const DifferentialGeometry &dg, MemoryPool &pool) const { 13 | BSDF *bsdf = pool.alloc(dg); 14 | MicrofacetDistribution *distrib = nullptr; 15 | if (rough_y < 0){ 16 | distrib = pool.alloc(1.f / rough_x); 17 | } 18 | else { 19 | distrib = pool.alloc(1.f / rough_x, 1.f / rough_y); 20 | } 21 | bsdf->add(pool.alloc(1.f, 22 | pool.alloc(refr_index->sample(dg), absoption_coef->sample(dg)), distrib)); 23 | return bsdf; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /src/material/microfacet_distribution.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "material/bxdf.h" 3 | #include "material/microfacet_distribution.h" 4 | 5 | 6 | float MicrofacetDistribution::geom_atten(const Vector &w_o, const Vector &w_i, const Vector &w_h){ 7 | float n_dot_h = std::abs(BxDF::cos_theta(w_h)); 8 | float n_dot_o = std::abs(BxDF::cos_theta(w_o)); 9 | float n_dot_i = std::abs(BxDF::cos_theta(w_i)); 10 | float o_dot_h = std::abs(w_o.dot(w_h)); 11 | return std::min(1.f, std::min(2 * n_dot_h * n_dot_o / o_dot_h, 2 * n_dot_h * n_dot_i / o_dot_h)); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/material/mix_material.cpp: -------------------------------------------------------------------------------- 1 | #include "material/bsdf.h" 2 | #include "material/scaled_bxdf.h" 3 | #include "material/mix_material.h" 4 | 5 | MixMaterial::MixMaterial(const Material *mat_a, const Material *mat_b, const Texture *scale) 6 | : mat_a(mat_a), mat_b(mat_b), scale(scale) 7 | {} 8 | BSDF* MixMaterial::get_bsdf(const DifferentialGeometry &dg, MemoryPool &pool) const { 9 | //Get the BSDFs from the materials we're mixing and scale their component BxDFs to 10 | //create a new BSDF that combines the two 11 | BSDF *bsdf = pool.alloc(dg); 12 | Colorf s = scale->sample(dg).normalized(); 13 | BSDF *a = mat_a->get_bsdf(dg, pool); 14 | for (int i = 0; i < a->num_bxdfs(); ++i){ 15 | bsdf->add(pool.alloc((*a)[i], s)); 16 | } 17 | BSDF *b = mat_b->get_bsdf(dg, pool); 18 | for (int i = 0; i < b->num_bxdfs(); ++i){ 19 | bsdf->add(pool.alloc((*b)[i], 1 - s)); 20 | } 21 | return bsdf; 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/material/oren_nayer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "linalg/util.h" 3 | #include "material/oren_nayer.h" 4 | 5 | OrenNayer::OrenNayer(const Colorf &reflectance, float sigma) : BxDF(BxDFTYPE(BxDFTYPE::REFLECTION | BxDFTYPE::DIFFUSE)), 6 | reflectance(reflectance) 7 | { 8 | //We just need sigma^2 in radians 9 | sigma = radians(sigma); 10 | sigma *= sigma; 11 | a = 1.f - sigma / (2.f * (sigma + 0.33f)); 12 | b = 0.45f * sigma / (sigma + 0.09f); 13 | } 14 | Colorf OrenNayer::operator()(const Vector &w_o, const Vector &w_i) const { 15 | float alpha = std::max(cos_theta(w_i), cos_theta(w_o)); 16 | float beta = std::min(cos_theta(w_i), cos_theta(w_o)); 17 | float max_cos = 0; 18 | if (sin_theta(w_i) > 1e-4 && sin_theta(w_o) > 1e-4){ 19 | max_cos = std::max(0.f, cos_phi(w_i) * cos_phi(w_o) + sin_phi(w_i) * sin_phi(w_o)); 20 | } 21 | return reflectance * INV_PI * (a + b * max_cos * std::sin(alpha) * std::tan(beta)); 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/material/plastic_material.cpp: -------------------------------------------------------------------------------- 1 | #include "material/bsdf.h" 2 | #include "material/blinn_distribution.h" 3 | #include "material/anisotropic_distribution.h" 4 | #include "material/torrance_sparrow.h" 5 | #include "material/lambertian.h" 6 | #include "material/fresnel.h" 7 | #include "material/plastic_material.h" 8 | 9 | PlasticMaterial::PlasticMaterial(const Texture *diffuse, const Texture *specular, float rough_x, float rough_y) 10 | : diffuse(diffuse), specular(specular), rough_x(rough_x), rough_y(rough_y) 11 | {} 12 | BSDF* PlasticMaterial::get_bsdf(const DifferentialGeometry &dg, MemoryPool &pool) const { 13 | BSDF *bsdf = pool.alloc(dg); 14 | Colorf kd = diffuse->sample(dg).normalized(); 15 | if (!kd.is_black()){ 16 | bsdf->add(pool.alloc(kd)); 17 | } 18 | Colorf ks = specular->sample(dg).normalized(); 19 | if (!ks.is_black()){ 20 | MicrofacetDistribution *distrib = nullptr; 21 | if (rough_y < 0){ 22 | distrib = pool.alloc(1.f / rough_x); 23 | } 24 | else { 25 | distrib = pool.alloc(1.f / rough_x, 1.f / rough_y); 26 | } 27 | bsdf->add(pool.alloc(ks, 28 | pool.alloc(1.5f, 1.f), distrib)); 29 | } 30 | return bsdf; 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/material/scaled_bxdf.cpp: -------------------------------------------------------------------------------- 1 | #include "material/scaled_bxdf.h" 2 | 3 | ScaledBxDF::ScaledBxDF(BxDF *bxdf, const Colorf &scale) : BxDF(bxdf->type), bxdf(bxdf), scale(scale){} 4 | Colorf ScaledBxDF::operator()(const Vector &w_o, const Vector &w_i) const { 5 | return scale * (*bxdf)(w_o, w_i); 6 | } 7 | Colorf ScaledBxDF::sample(const Vector &w_o, Vector &w_i, const std::array &samples, float &pdf_val) const { 8 | return scale * bxdf->sample(w_o, w_i, samples, pdf_val); 9 | } 10 | Colorf ScaledBxDF::rho_hd(const Vector &w_o, const std::array *samples, int n_samples) const { 11 | return scale * bxdf->rho_hd(w_o, samples, n_samples); 12 | } 13 | Colorf ScaledBxDF::rho_hh(const std::array *samples_a, const std::array *samples_b, int n_samples) const { 14 | return scale * bxdf->rho_hh(samples_a, samples_b, n_samples); 15 | } 16 | float ScaledBxDF::pdf(const Vector &w_o, const Vector &w_i) const { 17 | return bxdf->pdf(w_o, w_i); 18 | } 19 | 20 | -------------------------------------------------------------------------------- /src/material/specular_metal_material.cpp: -------------------------------------------------------------------------------- 1 | #include "material/bsdf.h" 2 | #include "material/fresnel.h" 3 | #include "material/specular_reflection.h" 4 | #include "material/specular_metal_material.h" 5 | 6 | SpecularMetalMaterial::SpecularMetalMaterial(const Texture *refr_index, const Texture *absoption_coef) 7 | : refr_index(refr_index), absoption_coef(absoption_coef) 8 | {} 9 | BSDF* SpecularMetalMaterial::get_bsdf(const DifferentialGeometry &dg, MemoryPool &pool) const { 10 | BSDF *bsdf = pool.alloc(dg); 11 | bsdf->add(pool.alloc(1.f, 12 | pool.alloc(refr_index->sample(dg), absoption_coef->sample(dg)))); 13 | return bsdf; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/material/specular_reflection.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "material/specular_reflection.h" 3 | 4 | SpecularReflection::SpecularReflection(const Colorf &reflection, Fresnel *fresnel) 5 | : BxDF(BxDFTYPE(BxDFTYPE::REFLECTION | BxDFTYPE::SPECULAR)), reflection(reflection), fresnel(fresnel) 6 | {} 7 | Colorf SpecularReflection::operator()(const Vector&, const Vector&) const { 8 | return Colorf{0}; 9 | } 10 | Colorf SpecularReflection::sample(const Vector &w_o, Vector &w_i, const std::array&, float &pdf_val) const { 11 | //Compute the perfect specular reflection direction since we're explicitly sampling the delta distribution 12 | w_i = Vector{-w_o.x, -w_o.y, w_o.z}; 13 | pdf_val = 1; 14 | return (*fresnel)(cos_theta(w_o)) * reflection / std::abs(cos_theta(w_i)); 15 | } 16 | float SpecularReflection::pdf(const Vector&, const Vector&) const { 17 | return 0; 18 | } 19 | 20 | -------------------------------------------------------------------------------- /src/material/specular_transmission.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "material/fresnel.h" 3 | #include "material/specular_transmission.h" 4 | 5 | SpecularTransmission::SpecularTransmission(const Colorf &transmission, FresnelDielectric *fresnel) 6 | : BxDF(BxDFTYPE(BxDFTYPE::TRANSMISSION | BxDFTYPE::SPECULAR)), transmission(transmission), fresnel(fresnel) 7 | {} 8 | Colorf SpecularTransmission::operator()(const Vector&, const Vector&) const { 9 | return Colorf{0}; 10 | } 11 | Colorf SpecularTransmission::sample(const Vector &w_o, Vector &w_i, const std::array&, float &pdf_val) const { 12 | //Determine the side of the material we're incident on and pick indices of refraction accordingly 13 | bool entering = cos_theta(w_o) > 0; 14 | float ei = entering ? fresnel->eta_i : fresnel->eta_t; 15 | float et = entering ? fresnel->eta_t : fresnel->eta_i; 16 | float sin_i2 = sin_theta2(w_o); 17 | float eta = ei / et; 18 | float sin_t2 = eta * eta * sin_i2; 19 | //If total internal reflection, nothing is transmitted 20 | if (sin_t2 >= 1){ 21 | return Colorf{0}; 22 | } 23 | //Compute the transmitted ray direction 24 | float cos_t = std::sqrt(std::max(0.f, 1.f - sin_t2)); 25 | cos_t = entering ? -cos_t : cos_t; 26 | w_i = Vector{eta * -w_o.x, eta * -w_o.y, cos_t}; 27 | pdf_val = 1; 28 | return (Colorf{1} - (*fresnel)(cos_theta(w_o))) * transmission / std::abs(cos_theta(w_i)); 29 | } 30 | float SpecularTransmission::pdf(const Vector&, const Vector&) const { 31 | return 0; 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/material/torrance_sparrow.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "material/torrance_sparrow.h" 3 | 4 | 5 | TorranceSparrow::TorranceSparrow(const Colorf &reflectance, Fresnel *fresnel, MicrofacetDistribution *distribution) 6 | : BxDF(BxDFTYPE(BxDFTYPE::REFLECTION | BxDFTYPE::GLOSSY)), reflectance(reflectance), 7 | fresnel(fresnel), distribution(distribution) 8 | {} 9 | 10 | Colorf TorranceSparrow::operator()(const Vector &w_o, const Vector &w_i) const { 11 | float cos_to = std::abs(cos_theta(w_o)); 12 | float cos_ti = std::abs(cos_theta(w_i)); 13 | if (cos_to == 0 || cos_ti == 0){ 14 | return Colorf{0}; 15 | } 16 | Vector w_h = w_i + w_o; 17 | if (w_h.length_sqr() == 0){ 18 | return Colorf{0}; 19 | } 20 | w_h = w_h.normalized(); 21 | float cos_th = w_i.dot(w_h); 22 | return reflectance * (*distribution)(w_h) * distribution->geom_atten(w_o, w_i, w_h) * (*fresnel)(cos_th) 23 | / (4 * cos_ti * cos_to); 24 | } 25 | Colorf TorranceSparrow::sample(const Vector &w_o, Vector &w_i, const std::array &samples, float &pdf_val) const { 26 | distribution->sample(w_o, w_i, samples, pdf_val); 27 | if (!same_hemisphere(w_o, w_i)){ 28 | return Colorf{0}; 29 | } 30 | return (*this)(w_o, w_i); 31 | } 32 | float TorranceSparrow::pdf(const Vector &w_o, const Vector &w_i) const { 33 | if (!same_hemisphere(w_o, w_i)){ 34 | return 0; 35 | } 36 | return distribution->pdf(w_o, w_i); 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/material/translucent_material.cpp: -------------------------------------------------------------------------------- 1 | #include "material/bsdf.h" 2 | #include "material/blinn_distribution.h" 3 | #include "material/torrance_sparrow.h" 4 | #include "material/lambertian.h" 5 | #include "material/fresnel.h" 6 | #include "material/btdf_adapter.h" 7 | #include "material/translucent_material.h" 8 | 9 | TranslucentMaterial::TranslucentMaterial(const Texture *diffuse, const Texture *specular, const Texture *reflect, 10 | const Texture *transmit, float roughness, float refr_index) 11 | : diffuse(diffuse), specular(specular), reflect(reflect), transmit(transmit), 12 | roughness(roughness), refr_index(refr_index) 13 | {} 14 | BSDF* TranslucentMaterial::get_bsdf(const DifferentialGeometry &dg, MemoryPool &pool) const { 15 | BSDF *bsdf = pool.alloc(dg, refr_index); 16 | Colorf kr = reflect->sample(dg).normalized(); 17 | Colorf kt = transmit->sample(dg).normalized(); 18 | if (kr.is_black() && kt.is_black()){ 19 | return bsdf; 20 | } 21 | Colorf kd = diffuse->sample(dg).normalized(); 22 | if (!kd.is_black()){ 23 | if (!kr.is_black()){ 24 | bsdf->add(pool.alloc(kr * kd)); 25 | } 26 | if (!kt.is_black()){ 27 | bsdf->add(pool.alloc(pool.alloc(kt * kd))); 28 | } 29 | } 30 | Colorf ks = specular->sample(dg).normalized(); 31 | if (!ks.is_black()){ 32 | if (!kr.is_black()){ 33 | bsdf->add(pool.alloc(kr * ks, 34 | pool.alloc(refr_index, 1.f), 35 | pool.alloc(1.f / roughness))); 36 | } 37 | if (!kt.is_black()){ 38 | bsdf->add(pool.alloc(pool.alloc(kt * ks, 39 | pool.alloc(pool.alloc(refr_index, 1.f)), 40 | pool.alloc(1.f / roughness)))); 41 | } 42 | } 43 | return bsdf; 44 | } 45 | 46 | -------------------------------------------------------------------------------- /src/memory_pool.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "memory_pool.h" 4 | 5 | MemoryPool::Block::Block(uint64_t size, char *block) : size(size), block(block){} 6 | 7 | MemoryPool::MemoryPool(uint32_t block_size) : cur_block_pos(0), block_size(block_size), 8 | cur_block(block_size, new char[block_size]) 9 | {} 10 | MemoryPool::~MemoryPool(){ 11 | delete[] cur_block.block; 12 | for (auto &b : used){ 13 | delete[] b.block; 14 | } 15 | for (auto &b : available){ 16 | delete[] b.block; 17 | } 18 | } 19 | void MemoryPool::free_blocks(){ 20 | cur_block_pos = 0; 21 | while (!used.empty()){ 22 | #ifdef DEBUG 23 | std::memset(used.back().block, 255, used.back().size); 24 | #endif 25 | available.push_back(used.back()); 26 | used.pop_back(); 27 | } 28 | } 29 | void* MemoryPool::alloc(uint64_t size){ 30 | //Round size to minimum machine alignment 31 | size = (size + 15) & ~15; 32 | //If we need a new block to store this allocation 33 | if (cur_block_pos + size > cur_block.size){ 34 | used.push_back(cur_block); 35 | //If we've got an available block that can fit use that, otherwise allocate a new one 36 | auto block = std::find_if(available.begin(), available.end(), 37 | [size](const auto &b){ 38 | return size <= b.size; 39 | }); 40 | if (block != available.end()){ 41 | cur_block = *block; 42 | available.erase(block); 43 | } 44 | else { 45 | uint64_t sz = std::max(size, block_size); 46 | cur_block = Block{sz, new char[sz]}; 47 | } 48 | cur_block_pos = 0; 49 | } 50 | void *mem = cur_block.block + cur_block_pos; 51 | cur_block_pos += size; 52 | return mem; 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/mesh_preprocess.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "geometry/tri_mesh.h" 7 | #include "loaders/async_loader.h" 8 | #include "mesh_preprocess.h" 9 | 10 | void batch_process(char **argv, int argc){ 11 | auto files = std::find(argv, argv + argc, std::string{"-pmesh"}) + 1; 12 | AsyncLoader loader; 13 | for (char **f = files; f < argv + argc; ++f){ 14 | std::string mesh{*f}; 15 | loader.run_task("process mesh: " + mesh, process_wobj, mesh); 16 | } 17 | //Wait for all the tasks to finish 18 | loader.wait(); 19 | } 20 | bool process_wobj(const std::string &file){ 21 | std::cout << "Processing mesh " << file << std::endl; 22 | TriMesh mesh{file, true}; 23 | if (mesh.vert_indices.empty()){ 24 | std::cout << "Error: process_wobj failed to load model " << file << std::endl; 25 | return false; 26 | } 27 | std::string file_out = file.substr(0, file.rfind("obj")) + "bobj"; 28 | std::cout << "Writing binary mesh to " << file_out << std::endl; 29 | 30 | std::FILE *fout = std::fopen(file_out.c_str(), "wb"); 31 | uint32_t nverts = mesh.vertices.size(); 32 | uint32_t ntris = mesh.tris.size(); 33 | std::fwrite(&nverts, sizeof(uint32_t), 1, fout); 34 | std::fwrite(&ntris, sizeof(uint32_t), 1, fout); 35 | std::fwrite(mesh.vertices.data(), sizeof(Point), nverts, fout); 36 | std::fwrite(mesh.texcoords.data(), sizeof(Point), nverts, fout); 37 | std::fwrite(mesh.normals.data(), sizeof(Normal), nverts, fout); 38 | std::fwrite(mesh.vert_indices.data(), sizeof(int), 3 * ntris, fout); 39 | std::fclose(fout); 40 | return true; 41 | } 42 | 43 | -------------------------------------------------------------------------------- /src/monte_carlo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(monte_carlo util.cpp distribution1d.cpp) 2 | 3 | -------------------------------------------------------------------------------- /src/monte_carlo/distribution1d.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "monte_carlo/distribution1d.h" 4 | 5 | Distribution1D::Distribution1D(const std::vector &fcn) 6 | : function(fcn), cdf(function.size() + 1), integral(0) 7 | { 8 | //Compute the integral of the function 9 | for (size_t i = 1; i < cdf.size(); ++i){ 10 | cdf[i] = cdf[i - 1] + function[i - 1] / function.size(); 11 | } 12 | integral = cdf.back(); 13 | //Normalize the CDF by the integral 14 | for (auto &c : cdf){ 15 | c /= integral; 16 | } 17 | } 18 | Distribution1D::Distribution1D(){} 19 | int Distribution1D::sample_discrete(float u, float *pdf_val) const { 20 | auto a = std::lower_bound(cdf.begin(), cdf.end(), u); 21 | int b = std::min(static_cast(std::distance(cdf.begin(), a)), static_cast(cdf.size() - 2)); 22 | if (pdf_val){ 23 | *pdf_val = function[b] / (integral * function.size()); 24 | } 25 | return b; 26 | } 27 | 28 | -------------------------------------------------------------------------------- /src/monte_carlo/util.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "monte_carlo/util.h" 3 | 4 | 5 | Vector uniform_sample_hemisphere(const std::array &u){ 6 | float r = std::sqrt(std::max(0.f, 1.f - u[0] * u[0])); 7 | float phi = TAU * u[1]; 8 | return Vector{std::cos(phi) * r, std::sin(phi) * r, u[0]}; 9 | } 10 | Vector cos_sample_hemisphere(const std::array &u){ 11 | //We use Malley's method here, generate samples on a disk then project 12 | //them up to the hemisphere 13 | std::array d = concentric_sample_disk(u); 14 | return Vector{d[0], d[1], std::sqrt(std::max(0.f, 1.f - d[0] * d[0] - d[1] * d[1]))}; 15 | } 16 | Vector uniform_sample_sphere(const std::array &u){ 17 | float z = 1 - 2 * u[0]; 18 | float r = std::sqrt(std::max(0.f, 1.f - z * z)); 19 | float phi = TAU * u[1]; 20 | return Vector{std::cos(phi) * r, std::sin(phi) * r, z}; 21 | } 22 | Vector uniform_sample_tri(const std::array &u){ 23 | float su = std::sqrt(u[0]); 24 | Vector b{1 - su, u[1] * su, 0}; 25 | b.z = 1 - b.x - b.y; 26 | return b; 27 | } 28 | std::array concentric_sample_disk(const std::array &u){ 29 | std::array s{2 * u[0] - 1, 2 * u[1] - 1}; 30 | float radius, theta; 31 | if (s[0] == 0 && s[1] == 0){ 32 | return {0.f, 0.f}; 33 | } 34 | if (s[0] >= -s[1]){ 35 | if (s[0] > s[1]){ 36 | radius = s[0]; 37 | theta = s[1] > 0 ? s[1] / s[0] : 8 + s[1] / s[0]; 38 | } 39 | else { 40 | radius = s[1]; 41 | //Is this test correct here? Paper doesn't use it 42 | theta = 2 - s[0] / s[1]; 43 | } 44 | } 45 | else { 46 | if (s[0] <= s[1]){ 47 | radius = -s[0]; 48 | theta = 4 + s[1] / s[0]; 49 | } 50 | else { 51 | radius = -s[1]; 52 | theta = 6 - s[0] / s[1]; 53 | } 54 | } 55 | theta *= PI / 4.f; 56 | return {radius * std::cos(theta), radius * std::sin(theta)}; 57 | } 58 | Vector uniform_sample_cone(const std::array &u, float cos_theta){ 59 | cos_theta = lerp(u[0], cos_theta, 1.f); 60 | float sin_theta = std::sqrt(1 - cos_theta * cos_theta); 61 | float phi = u[1] * TAU; 62 | return Vector{std::cos(phi) * sin_theta, std::sin(phi) * sin_theta, cos_theta}; 63 | } 64 | Vector uniform_sample_cone(const std::array &u, float cos_theta, const Vector &x, 65 | const Vector &y, const Vector &z) 66 | { 67 | cos_theta = lerp(u[0], cos_theta, 1.f); 68 | float sin_theta = std::sqrt(1 - cos_theta * cos_theta); 69 | float phi = u[1] * TAU; 70 | return std::cos(phi) * sin_theta * x + std::sin(phi) * sin_theta * y + cos_theta * z; 71 | } 72 | 73 | -------------------------------------------------------------------------------- /src/renderer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(renderer renderer.cpp) 2 | 3 | -------------------------------------------------------------------------------- /src/renderer/renderer.cpp: -------------------------------------------------------------------------------- 1 | #include "linalg/ray.h" 2 | #include "geometry/differential_geometry.h" 3 | #include "geometry/geometry.h" 4 | #include "scene.h" 5 | #include "integrator/surface_integrator.h" 6 | #include "integrator/volume_integrator.h" 7 | #include "integrator/emission_integrator.h" 8 | #include "integrator/single_scattering_integrator.h" 9 | #include "renderer/renderer.h" 10 | 11 | Renderer::Renderer(std::unique_ptr surface_integrator, std::unique_ptr volume_integrator) 12 | : surface_integrator(std::move(surface_integrator)), volume_integrator(std::move(volume_integrator)) 13 | {} 14 | void Renderer::preprocess(const Scene &scene){ 15 | surface_integrator->preprocess(scene); 16 | } 17 | Colorf Renderer::illumination(RayDifferential &ray, const Scene &scene, Sampler &sampler, MemoryPool &pool) const { 18 | DifferentialGeometry dg; 19 | Colorf illum; 20 | if (scene.get_root().intersect(ray, dg)){ 21 | illum = surface_integrator->illumination(scene, *this, ray, dg, sampler, pool); 22 | } 23 | else if (scene.get_environment()){ 24 | //TODO: Compute light along the ray coming from lights, eg for things like lightmaps that are wrapped around the scene 25 | DifferentialGeometry dg; 26 | dg.point = Point{ray.d.x, ray.d.y, ray.d.z}; 27 | illum = scene.get_environment()->sample(dg); 28 | } 29 | Colorf vol_radiance, transmit{1}; 30 | if (volume_integrator != nullptr){ 31 | vol_radiance = volume_integrator->radiance(scene, *this, ray, sampler, pool, transmit); 32 | } 33 | return transmit * illum + vol_radiance; 34 | } 35 | Colorf Renderer::transmittance(const Scene &scene, const RayDifferential &ray, Sampler &sampler, MemoryPool &pool) const { 36 | return volume_integrator != nullptr ? volume_integrator->transmittance(scene, *this, ray, sampler, pool) : Colorf{1}; 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/samplers/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(samplers sampler.cpp stratified_sampler.cpp ld_sampler.cpp adaptive_sampler.cpp) 2 | 3 | -------------------------------------------------------------------------------- /src/samplers/sampler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "samplers/sampler.h" 6 | 7 | Sampler::Sampler(int x_start, int x_end, int y_start, int y_end, int seed) 8 | : x(x_start), y(y_start), rng(seed), x_start(x_start), x_end(x_end), y_start(y_start), y_end(y_end) 9 | {} 10 | Sampler::Sampler(int x_start, int x_end, int y_start, int y_end) 11 | : Sampler(x_start, x_end, y_start, y_end, std::chrono::duration_cast( 12 | std::chrono::high_resolution_clock::now().time_since_epoch()).count()) 13 | {} 14 | float Sampler::random_float(){ 15 | return float_distrib(rng); 16 | } 17 | bool Sampler::report_results(const std::vector&, 18 | const std::vector&, const std::vector&) 19 | { 20 | return true; 21 | } 22 | bool Sampler::has_samples(){ 23 | return y != y_end; 24 | } 25 | int Sampler::width() const { 26 | return x_end - x_start; 27 | } 28 | int Sampler::height() const { 29 | return y_end - y_start; 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/scene.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "integrator/volume_integrator.h" 3 | #include "geometry/geometry.h" 4 | #include "volume/volume_node.h" 5 | #include "material/material.h" 6 | #include "lights/light.h" 7 | #include "samplers/sampler.h" 8 | #include "textures/texture.h" 9 | #include "scene.h" 10 | 11 | Scene::Scene(Camera camera, RenderTarget target, std::unique_ptr sampler, std::unique_ptr renderer) 12 | : camera(std::move(camera)), render_target(std::move(target)), sampler(std::move(sampler)), 13 | renderer(std::move(renderer)), root(nullptr, nullptr, Transform{}, "root"), background(nullptr), environment(nullptr) 14 | {} 15 | GeometryCache& Scene::get_geom_cache(){ 16 | return geom_cache; 17 | } 18 | MaterialCache& Scene::get_mat_cache(){ 19 | return mat_cache; 20 | } 21 | TextureCache& Scene::get_tex_cache(){ 22 | return tex_cache; 23 | } 24 | LightCache& Scene::get_light_cache(){ 25 | return light_cache; 26 | } 27 | const LightCache& Scene::get_light_cache() const { 28 | return light_cache; 29 | } 30 | VolumeCache& Scene::get_volume_cache(){ 31 | return volume_cache; 32 | } 33 | Camera& Scene::get_camera(){ 34 | return camera; 35 | } 36 | RenderTarget& Scene::get_render_target(){ 37 | return render_target; 38 | } 39 | const RenderTarget& Scene::get_render_target() const { 40 | return render_target; 41 | } 42 | const Sampler& Scene::get_sampler() const { 43 | return *sampler; 44 | } 45 | const Renderer& Scene::get_renderer() const { 46 | return *renderer; 47 | } 48 | Renderer& Scene::get_renderer(){ 49 | return *renderer; 50 | } 51 | Node& Scene::get_root(){ 52 | return root; 53 | } 54 | const Node& Scene::get_root() const { 55 | return root; 56 | } 57 | VolumeNode* Scene::get_volume_root() { 58 | return volume_root != nullptr ? volume_root.get() : nullptr; 59 | } 60 | const VolumeNode* Scene::get_volume_root() const { 61 | return volume_root != nullptr ? volume_root.get() : nullptr; 62 | } 63 | void Scene::set_volume_root(std::unique_ptr vol){ 64 | volume_root = std::move(vol); 65 | } 66 | void Scene::set_background(Texture *t){ 67 | background = t; 68 | } 69 | void Scene::set_environment(Texture *t){ 70 | environment = t; 71 | } 72 | const Texture* Scene::get_background() const { 73 | return background; 74 | } 75 | const Texture* Scene::get_environment() const { 76 | return environment; 77 | } 78 | 79 | -------------------------------------------------------------------------------- /src/textures/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(textures texture_mapping.cpp uv_mapping.cpp spherical_mapping.cpp constant_texture.cpp 2 | image_texture.cpp uv_texture.cpp checkerboard_texture.cpp scale_texture.cpp 3 | remapped_texture.cpp mipmap.cpp) 4 | 5 | -------------------------------------------------------------------------------- /src/textures/checkerboard_texture.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "textures/texture.h" 5 | #include "textures/texture_mapping.h" 6 | #include "textures/checkerboard_texture.h" 7 | 8 | CheckerboardTexture::CheckerboardTexture(const Colorf &a, const Colorf &b, 9 | std::unique_ptr mapping) 10 | : mapping(std::move(mapping)), a(a), b(b) 11 | {} 12 | Colorf CheckerboardTexture::sample(const DifferentialGeometry &dg) const { 13 | return sample(mapping->map(dg)); 14 | } 15 | Colorf CheckerboardTexture::sample(const TextureSample &sp) const { 16 | TextureSample samp = sp; 17 | //Cem considers each check to be 0.5 wide instead of 1 unit wide so scale to match 18 | samp.s *= 2; 19 | samp.t *= 2; 20 | samp.ds_dx *= 2; 21 | samp.ds_dy *= 2; 22 | samp.dt_dx *= 2; 23 | samp.dt_dy *= 2; 24 | //Filter the checkerboard texture by computing an AABB about (s, t) 25 | //hs and ht are the half vectors of this box in s and t respectively 26 | float hs = std::max(std::abs(samp.ds_dx), std::abs(samp.ds_dy)); 27 | float ht = std::max(std::abs(samp.dt_dx), std::abs(samp.dt_dy)); 28 | std::array s = {samp.s - hs, samp.s + hs}; 29 | std::array t = {samp.t - ht, samp.t + ht}; 30 | 31 | //Check if the AABB is entirely in one check 32 | if (static_cast(s[0]) == static_cast(s[1]) 33 | && static_cast(t[0]) == static_cast(t[1])) 34 | { 35 | if ((static_cast(samp.s) + static_cast(samp.t)) % 2 == 0){ 36 | return a; 37 | } 38 | return b; 39 | } 40 | //Compute the integrals of the step function in s and t to find out 41 | //how much area of each check we overlap 42 | float s_int = (step_integral(s[1]) - step_integral(s[0])) / (2 * hs); 43 | float t_int = (step_integral(t[1]) - step_integral(t[0])) / (2 * ht); 44 | float area_b = s_int + t_int - 2 * s_int * t_int; 45 | if (hs > 1 || ht > 1){ 46 | area_b = 0.5; 47 | } 48 | //Interpolate the checks based on how much area each occupies in our sample 49 | return (1 - area_b) * a + area_b * b; 50 | 51 | } 52 | float CheckerboardTexture::step_integral(float x) const { 53 | float fx = std::floor(x / 2); 54 | return fx + 2.f * std::max(x / 2.f - fx - 0.5f, 0.f); 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/textures/constant_texture.cpp: -------------------------------------------------------------------------------- 1 | #include "textures/constant_texture.h" 2 | 3 | ConstantTexture::ConstantTexture(const Colorf &color) : color(color){} 4 | Colorf ConstantTexture::sample(const DifferentialGeometry&) const { 5 | return color; 6 | } 7 | Colorf ConstantTexture::sample(const TextureSample&) const { 8 | return color; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/textures/image_texture.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #define STB_IMAGE_IMPLEMENTATION 7 | #include 8 | #include "linalg/util.h" 9 | #include "textures/texture.h" 10 | #include "textures/texture_mapping.h" 11 | #include "textures/mipmap.h" 12 | #include "textures/image_texture.h" 13 | 14 | ImageTexture::ImageTexture(const std::string &file, std::unique_ptr mapping, WRAP_MODE wrap_mode) 15 | : mapping(std::move(mapping)), width(0), height(0), ncomp(0) 16 | { 17 | std::vector texels; 18 | if (!load_image(file, texels)){ 19 | std::cout << "ImageTexture Error: could not load " << file << std::endl; 20 | } 21 | else { 22 | mipmap = MipMap(texels, width, height, ncomp, wrap_mode); 23 | } 24 | } 25 | Colorf ImageTexture::sample(const DifferentialGeometry &dg) const { 26 | return sample(mapping->map(dg)); 27 | } 28 | Colorf ImageTexture::sample(const TextureSample &sample) const { 29 | return mipmap.sample(sample); 30 | } 31 | bool ImageTexture::load_image(const std::string &file, std::vector &texels){ 32 | if (file.substr(file.rfind(".")) == ".ppm"){ 33 | return load_ppm(file, texels); 34 | } 35 | return load_stb(file, texels); 36 | } 37 | bool ImageTexture::load_ppm(const std::string &file, std::vector &texels){ 38 | std::ifstream f{file, std::ios::in | std::ios::binary}; 39 | if (!f){ 40 | return false; 41 | } 42 | std::string line; 43 | if (!std::getline(f, line) || line != "P6"){ 44 | return false; 45 | } 46 | //Skip comments 47 | while (std::getline(f, line) && line[0] == '#'); 48 | 49 | size_t sep = line.find(' '); 50 | width = std::stoi(line.substr(0, sep)); 51 | height = std::stoi(line.substr(sep + 1)); 52 | //Read the max val, but we know it's 255 so ignore it 53 | std::getline(f, line); 54 | ncomp = 3; 55 | texels.resize(width * height * ncomp); 56 | if (!f.read(reinterpret_cast(texels.data()), width * height * ncomp)){ 57 | return false; 58 | } 59 | return true; 60 | } 61 | bool ImageTexture::load_stb(const std::string &file, std::vector &texels){ 62 | uint8_t *img = stbi_load(file.c_str(), &width, &height, &ncomp, 0); 63 | if (!img){ 64 | return false; 65 | } 66 | texels.reserve(width * height * ncomp); 67 | texels.insert(texels.begin(), img, img + width * height * ncomp); 68 | stbi_image_free(img); 69 | return true; 70 | } 71 | 72 | -------------------------------------------------------------------------------- /src/textures/remapped_texture.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "textures/texture.h" 3 | #include "textures/texture_mapping.h" 4 | #include "textures/remapped_texture.h" 5 | 6 | RemappedTexture::RemappedTexture(const Texture &texture, std::unique_ptr mapping) 7 | : texture(texture), mapping(std::move(mapping)) 8 | {} 9 | Colorf RemappedTexture::sample(const DifferentialGeometry &dg) const { 10 | return sample(mapping->map(dg)); 11 | } 12 | Colorf RemappedTexture::sample(const TextureSample &sample) const { 13 | return texture.sample(sample); 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/textures/scale_texture.cpp: -------------------------------------------------------------------------------- 1 | #include "textures/texture.h" 2 | #include "textures/scale_texture.h" 3 | 4 | ScaleTexture::ScaleTexture(const Texture &a, const Texture &b) : a(a), b(b){} 5 | Colorf ScaleTexture::sample(const DifferentialGeometry &dg) const { 6 | return a.sample(dg) * b.sample(dg); 7 | } 8 | Colorf ScaleTexture::sample(const TextureSample &sample) const { 9 | return a.sample(sample) * b.sample(sample); 10 | } 11 | 12 | -------------------------------------------------------------------------------- /src/textures/spherical_mapping.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "linalg/util.h" 3 | #include "linalg/point.h" 4 | #include "linalg/transform.h" 5 | #include "textures/texture_mapping.h" 6 | #include "textures/spherical_mapping.h" 7 | 8 | SphericalMapping::SphericalMapping(const Transform &transform) : transform(transform){} 9 | TextureSample SphericalMapping::map(const DifferentialGeometry &dg) const { 10 | TextureSample sample; 11 | sphere_project(dg.point, sample.s, sample.t); 12 | //Compute differentials using forward differencing 13 | float sx, tx; 14 | const static float delta = .1; 15 | sphere_project(dg.point + delta * dg.dp_dx, sx, tx); 16 | sample.ds_dx = (sx - sample.s) / delta; 17 | sample.dt_dx = (tx - sample.t) / delta; 18 | //Handle wrapping about t 19 | if (sample.dt_dx > 0.5){ 20 | sample.dt_dx = 1 - sample.dt_dx; 21 | } 22 | else if (sample.dt_dx < -0.5){ 23 | sample.dt_dx = -(sample.dt_dx + 1); 24 | } 25 | float sy, ty; 26 | sphere_project(dg.point + delta * dg.dp_dy, sy, ty); 27 | sample.ds_dy = (sy - sample.s) / delta; 28 | sample.dt_dy = (sy - sample.t) / delta; 29 | //Handle wrapping about t 30 | if (sample.dt_dy > 0.5){ 31 | sample.dt_dy = 1 - sample.dt_dy; 32 | } 33 | else if (sample.dt_dy < -0.5){ 34 | sample.dt_dy = -(sample.dt_dy + 1); 35 | } 36 | return sample; 37 | } 38 | void SphericalMapping::sphere_project(const Point &p, float &s, float &t) const { 39 | Vector d = Vector{transform(p)}.normalized(); 40 | float theta = spherical_theta(d); 41 | float phi = spherical_phi(d); 42 | s = theta * INV_PI; 43 | t = phi * INV_TAU; 44 | } 45 | 46 | -------------------------------------------------------------------------------- /src/textures/texture_mapping.cpp: -------------------------------------------------------------------------------- 1 | #include "geometry/differential_geometry.h" 2 | #include "linalg/vector.h" 3 | #include "textures/texture_mapping.h" 4 | 5 | TextureSample::TextureSample(float s, float t, float ds_dx, float ds_dy, float dt_dx, float dt_dy) 6 | : s(s), t(t), ds_dx(ds_dx), ds_dy(ds_dy), dt_dx(dt_dx), dt_dy(dt_dy) 7 | {} 8 | 9 | -------------------------------------------------------------------------------- /src/textures/uv_mapping.cpp: -------------------------------------------------------------------------------- 1 | #include "linalg/transform.h" 2 | #include "linalg/vector.h" 3 | #include "textures/uv_mapping.h" 4 | 5 | UVMapping::UVMapping(const Transform &transform) : transform(transform){} 6 | TextureSample UVMapping::map(const DifferentialGeometry &dg) const { 7 | TextureSample sample; 8 | Point p{dg.u, dg.v, 0}; 9 | transform(p, p); 10 | sample.s = p.x; 11 | sample.t = p.y; 12 | Vector d{dg.du_dx, dg.dv_dx, 0}; 13 | transform(d, d); 14 | sample.ds_dx = d.x; 15 | sample.dt_dx = d.y; 16 | d = Vector{dg.du_dy, dg.dv_dy, 0}; 17 | transform(d, d); 18 | sample.ds_dy = d.x; 19 | sample.dt_dy = d.y; 20 | return sample; 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/textures/uv_texture.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "textures/texture_mapping.h" 3 | #include "textures/uv_texture.h" 4 | 5 | UVTexture::UVTexture(std::unique_ptr mapping) : mapping(std::move(mapping)){} 6 | Colorf UVTexture::sample(const DifferentialGeometry &dg) const { 7 | return sample(mapping->map(dg)); 8 | } 9 | Colorf UVTexture::sample(const TextureSample &sample) const { 10 | return Colorf{sample.s, sample.t, 0}; 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/volume/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(volume volume.cpp volume_node.cpp homogeneous_volume.cpp geometry_volume.cpp 2 | varying_density_volume.cpp exponential_volume.cpp grid_volume.cpp) 3 | 4 | -------------------------------------------------------------------------------- /src/volume/exponential_volume.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "volume/exponential_volume.h" 3 | 4 | ExponentialVolume::ExponentialVolume(const Colorf &sig_a, const Colorf &sig_s, const Colorf &emit, float phase_asymmetry, 5 | const BBox ®ion, float a, float b, const Vector &up) 6 | : VaryingDensityVolume(sig_a, sig_s, emit, phase_asymmetry), a(a), b(b), up(up), region(region) 7 | {} 8 | BBox ExponentialVolume::bound() const { 9 | return region; 10 | } 11 | bool ExponentialVolume::intersect(const Ray &ray, std::array &t) const { 12 | return region.intersect(ray, &t[0], &t[1]); 13 | } 14 | float ExponentialVolume::density(const Point &p) const { 15 | if (!region.inside(p)){ 16 | return 0; 17 | } 18 | float h = up.dot(p - region.min); 19 | return a * std::exp(-b * h); 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/volume/geometry_volume.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "geometry/differential_geometry.h" 4 | #include "volume/geometry_volume.h" 5 | 6 | GeometryVolume::GeometryVolume(const Colorf &sig_a, const Colorf &sig_s, const Colorf &emit, 7 | float phase_asymmetry, const Geometry *geometry) 8 | : sig_a(sig_a), sig_s(sig_s), emit(emit), phase_asymmetry(phase_asymmetry), geometry(geometry) 9 | {} 10 | BBox GeometryVolume::bound() const { 11 | return geometry->bound(); 12 | } 13 | bool GeometryVolume::intersect(const Ray &r, std::array &t) const { 14 | //We need to shoot the ray to hit the object to find t[0], then re-shoot it out 15 | //the other side to find t[1] 16 | Ray ray = r; 17 | DifferentialGeometry dg; 18 | if (!geometry->intersect(ray, dg)){ 19 | return false; 20 | } 21 | t[0] = ray.max_t; 22 | //Setting ray.min_t seems to give too thin of volumes? 23 | ray.min_t = ray.max_t + 0.0001; 24 | ray.max_t = std::numeric_limits::infinity(); 25 | if (!geometry->intersect(ray, dg)){ 26 | return false; 27 | } 28 | t[1] = ray.max_t; 29 | return true; 30 | } 31 | Colorf GeometryVolume::absorption(const Point &p, const Vector&) const { 32 | return inside(p) ? sig_a : 0; 33 | } 34 | Colorf GeometryVolume::scattering(const Point &p, const Vector&) const { 35 | return inside(p) ? sig_s : 0; 36 | } 37 | Colorf GeometryVolume::attenuation(const Point &p, const Vector&) const { 38 | return inside(p) ? sig_a + sig_s : 0; 39 | } 40 | Colorf GeometryVolume::emission(const Point &p, const Vector&) const { 41 | return inside(p) ? emit : 0; 42 | } 43 | Colorf GeometryVolume::optical_thickness(const Ray &ray, float, float) const { 44 | std::array t; 45 | if (!intersect(ray, t)){ 46 | return 0; 47 | } 48 | return ray(t[0]).distance(ray(t[1])) * (sig_a + sig_s); 49 | } 50 | float GeometryVolume::phase(const Point &p, const Vector &w_i, const Vector &w_o) const { 51 | return inside(p) ? phase_henyey_greenstein(w_i, w_o, phase_asymmetry) : 0; 52 | } 53 | bool GeometryVolume::inside(const Point &p) const { 54 | std::array dg; 55 | std::array ray{RayDifferential{p, Vector{1, 0, 0}}, 56 | RayDifferential{p, Vector{-1, 0, 0}} 57 | }; 58 | return geometry->intersect(ray[0], dg[0]) && geometry->intersect(ray[1], dg[1]); 59 | } 60 | 61 | -------------------------------------------------------------------------------- /src/volume/homogeneous_volume.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "volume/homogeneous_volume.h" 3 | 4 | HomogeneousVolume::HomogeneousVolume(const Colorf &sig_a, const Colorf &sig_s, const Colorf &emit, 5 | float phase_asymmetry, const BBox ®ion) 6 | : sig_a(sig_a), sig_s(sig_s), emit(emit), phase_asymmetry(phase_asymmetry), region(region) 7 | {} 8 | BBox HomogeneousVolume::bound() const { 9 | return region; 10 | } 11 | bool HomogeneousVolume::intersect(const Ray &ray, std::array &t) const { 12 | return region.intersect(ray, &t[0], &t[1]); 13 | } 14 | Colorf HomogeneousVolume::absorption(const Point &p, const Vector&) const { 15 | return region.inside(p) ? sig_a : 0; 16 | } 17 | Colorf HomogeneousVolume::scattering(const Point &p, const Vector&) const { 18 | return region.inside(p) ? sig_s : 0; 19 | } 20 | Colorf HomogeneousVolume::attenuation(const Point &p, const Vector&) const { 21 | return region.inside(p) ? sig_a + sig_s : 0; 22 | } 23 | Colorf HomogeneousVolume::emission(const Point &p, const Vector&) const { 24 | return region.inside(p) ? emit : 0; 25 | } 26 | Colorf HomogeneousVolume::optical_thickness(const Ray &ray, float, float) const { 27 | std::array t; 28 | if (!region.intersect(ray, &t[0], &t[1])){ 29 | return 0; 30 | } 31 | return ray(t[0]).distance(ray(t[1])) * (sig_a + sig_s); 32 | } 33 | float HomogeneousVolume::phase(const Point &p, const Vector &w_i, const Vector &w_o) const { 34 | return region.inside(p) ? phase_henyey_greenstein(w_i, w_o, phase_asymmetry) : 0; 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/volume/varying_density_volume.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "volume/varying_density_volume.h" 3 | 4 | VaryingDensityVolume::VaryingDensityVolume(const Colorf &sig_a, const Colorf &sig_s, const Colorf &emit, float phase_asymmetry) 5 | : sig_a(sig_a), sig_s(sig_s), emit(emit), phase_asymmetry(phase_asymmetry) 6 | {} 7 | Colorf VaryingDensityVolume::absorption(const Point &p, const Vector&) const { 8 | return density(p) * sig_a; 9 | } 10 | Colorf VaryingDensityVolume::scattering(const Point &p, const Vector&) const { 11 | return density(p) * sig_s; 12 | } 13 | Colorf VaryingDensityVolume::attenuation(const Point &p, const Vector&) const { 14 | return density(p) * (sig_a + sig_s); 15 | } 16 | Colorf VaryingDensityVolume::emission(const Point &p, const Vector&) const { 17 | return density(p) * emit; 18 | } 19 | Colorf VaryingDensityVolume::optical_thickness(const Ray &ray, float step, float offset) const { 20 | float length = ray.d.length(); 21 | if (length == 0){ 22 | return 0; 23 | } 24 | std::array t; 25 | Ray r{ray.o, ray.d / length, r.min_t * length, r.max_t * length}; 26 | if (!intersect(r, t)){ 27 | return 0; 28 | } 29 | Colorf tau; 30 | for (float i = t[0] + offset * step; i < t[1]; i += step){ 31 | tau += attenuation(r(i), -r.d); 32 | } 33 | return tau * step; 34 | } 35 | float VaryingDensityVolume::phase(const Point &p, const Vector &w_i, const Vector &w_o) const { 36 | if (density(p) == 0){ 37 | return 0; 38 | } 39 | return phase_henyey_greenstein(w_i, w_o, phase_asymmetry); 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/volume/volume.cpp: -------------------------------------------------------------------------------- 1 | #include "volume/volume.h" 2 | 3 | float phase_rayleigh(const Vector &w_i, const Vector &w_o){ 4 | float cos_t = w_i.dot(w_o); 5 | return 3.f / (16 * PI) * (1 + cos_t * cos_t); 6 | } 7 | float phase_mie_hazy(const Vector &w_i, const Vector &w_o){ 8 | float cos_t = w_i.dot(w_o); 9 | return (0.5 + 4.5 * std::pow(0.5 * (1.f + cos_t), 8.f)) / (4 * PI); 10 | } 11 | float phase_mie_murky(const Vector &w_i, const Vector &w_o){ 12 | float cos_t = w_i.dot(w_o); 13 | return (0.5 + 16.5 * std::pow(0.5 * (1.f + cos_t), 32.f)) / (4 * PI); 14 | } 15 | float phase_henyey_greenstein(const Vector &w_i, const Vector &w_o, float g){ 16 | float cos_t = w_i.dot(w_o); 17 | return 1.f / (4 * PI) * (1 - g * g) / std::pow(1 + g * g - 2 * g * cos_t, 1.5f); 18 | } 19 | float phase_schlick(const Vector &w_i, const Vector &w_o, float g){ 20 | float alpha = 1.5; 21 | float k = alpha * g + (1 - alpha) * std::pow(g, 3.f); 22 | float k_cos_t = k * w_i.dot(w_o); 23 | return 1.f / (4 * PI) * (1 - k * k) / ((1 - k_cos_t) * (1 - k_cos_t)); 24 | } 25 | 26 | Colorf Volume::attenuation(const Point &p, const Vector &v) const { 27 | return absorption(p, v) + scattering(p, v); 28 | } 29 | 30 | --------------------------------------------------------------------------------