├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake └── FindFFTW.cmake ├── include └── layer │ ├── color.h │ ├── common.h │ ├── fourier.h │ ├── frame.h │ ├── fresnel.h │ ├── hg.h │ ├── layer.h │ ├── log.h │ ├── math.h │ ├── microfacet.h │ ├── mmap.h │ ├── quad.h │ ├── simd.h │ ├── spline.h │ ├── storage.h │ └── vector.h ├── recipes ├── coated-diffuse.py ├── coated-gold-with-scatmedium.py ├── coated-gold.py ├── extract.py └── utils │ ├── __init__.py │ ├── cie.py │ └── materials.py ├── src ├── fourier.cpp ├── fresnel.cpp ├── hg.cpp ├── layer.cpp ├── log.cpp ├── math.cpp ├── microfacet.cpp ├── mmap.cpp ├── py_doc.h ├── py_filesystem.cpp ├── py_fourier.cpp ├── py_fresnel.cpp ├── py_layer.cpp ├── py_math.cpp ├── py_quad.cpp ├── py_spline.cpp ├── py_vector.cpp ├── python.cpp ├── python.h ├── simd.cpp └── storage.cpp └── tests ├── __init__.py └── test_spline.py /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeCache.txt 2 | CMakeFiles 3 | Makefile 4 | layerlab.*so 5 | layerlab.*pyd 6 | *.cmake 7 | *.pyc 8 | *.pyo 9 | __pycache__ 10 | \.DS_Store 11 | tbb 12 | /.ninja_* 13 | /*.ninja 14 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ext/pybind11"] 2 | path = ext/pybind11 3 | url = https://github.com/pybind/pybind11 4 | [submodule "ext/eigen"] 5 | path = ext/eigen 6 | url = https://github.com/libigl/eigen 7 | [submodule "ext/tbb"] 8 | path = ext/tbb 9 | url = https://github.com/wjakob/tbb 10 | [submodule "ext/tinyformat"] 11 | path = ext/tinyformat 12 | url = https://github.com/wjakob/tinyformat 13 | [submodule "ext/filesystem"] 14 | path = ext/filesystem 15 | url = https://github.com/wjakob/filesystem 16 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMakeLists.txt -- Build system for the Layer Lab 2 | # 3 | # Copyright (c) 2015 Wenzel Jakob 4 | # 5 | # All rights reserved. Use of this source code is governed by a 6 | # BSD-style license that can be found in the LICENSE file. 7 | 8 | cmake_minimum_required(VERSION 2.8.12) 9 | 10 | project(layerlab) 11 | 12 | option(USE_AVX "Enable AVX optimizations" TRUE) 13 | set(LAYERLAB_PYTHON_VERSION "" CACHE STRING "Python version to use for compiling layer lab") 14 | 15 | include(CheckCXXCompilerFlag) 16 | 17 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 18 | message(STATUS "Setting build type to 'Release' as none was specified.") 19 | set(CMAKE_BUILD_TYPE MinSizeRel CACHE STRING "Choose the type of build." FORCE) 20 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" 21 | "MinSizeRel" "RelWithDebInfo") 22 | endif() 23 | string(TOUPPER "${CMAKE_BUILD_TYPE}" U_CMAKE_BUILD_TYPE) 24 | 25 | # Build TBB 26 | if (UNIX) 27 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") 28 | set(CMAKE_MACOSX_RPATH ON) 29 | endif() 30 | 31 | option(TBB_BUILD_SHARED "" OFF) 32 | option(TBB_BUILD_STATIC "" ON) 33 | option(TBB_BUILD_TESTS "" OFF) 34 | option(TBB_BUILD_TBBMALLOC "" OFF) 35 | option(TBB_BUILD_TBBMALLOC_PROXY "" OFF) 36 | 37 | add_subdirectory(ext/tbb) 38 | add_subdirectory(ext/pybind11) 39 | 40 | set(CMAKE_MACOSX_RPATH ON) 41 | 42 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") 43 | CHECK_CXX_COMPILER_FLAG("-std=c++14" HAS_CPP14_FLAG) 44 | CHECK_CXX_COMPILER_FLAG("-std=c++11" HAS_CPP11_FLAG) 45 | 46 | if (HAS_CPP14_FLAG) 47 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") 48 | elseif (HAS_CPP11_FLAG) 49 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 50 | else() 51 | message(FATAL_ERROR "Unsupported compiler -- at least C++11 support is needed!") 52 | endif() 53 | 54 | # Enable link time optimization and set the default symbol 55 | # visibility to hidden (very important to obtain small binaries) 56 | if (NOT ${U_CMAKE_BUILD_TYPE} MATCHES DEBUG) 57 | # Default symbol visibility 58 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") 59 | 60 | # Check for Link Time Optimization support 61 | CHECK_CXX_COMPILER_FLAG("-flto" HAS_LTO_FLAG) 62 | if (HAS_LTO_FLAG) 63 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto") 64 | endif() 65 | endif() 66 | endif() 67 | 68 | # Search for FFTW3 69 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") 70 | find_package(FFTW) 71 | 72 | if (PKG_FFTW_FOUND) 73 | add_definitions("-DHAVE_FFTW") 74 | include_directories(${FFTW_INCLUDES}) 75 | endif() 76 | 77 | # Compile with AVX 78 | if (USE_AVX) 79 | if (MSVC) 80 | # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:AVX") 81 | else() 82 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx") 83 | endif() 84 | endif() 85 | 86 | # Compile with compiler warnings turned on 87 | if(MSVC) 88 | if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]") 89 | string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") 90 | else() 91 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") 92 | endif() 93 | else() 94 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") 95 | endif() 96 | 97 | # Set platform-specific flags 98 | if (WIN32) 99 | add_definitions(-D__WINDOWS__ -D_UNICODE) 100 | elseif(UNIX) 101 | if(APPLE) 102 | add_definitions(-D__OSX__) 103 | else() 104 | add_definitions(-D__LINUX__) 105 | endif() 106 | endif() 107 | 108 | include_directories( 109 | ${PYTHON_INCLUDE_DIR} 110 | ext/eigen 111 | ext/filesystem 112 | ext/pybind11/include 113 | ext/tbb/include 114 | ext/tinyformat 115 | include 116 | ) 117 | 118 | pybind11_add_module(layerlab SHARED 119 | # Layer lab files 120 | include/layer/common.h 121 | include/layer/color.h 122 | include/layer/frame.h 123 | include/layer/spline.h 124 | include/layer/quad.h 125 | include/layer/simd.h 126 | include/layer/vector.h 127 | include/layer/math.h src/math.cpp 128 | include/layer/hg.h src/hg.cpp 129 | include/layer/fourier.h src/fourier.cpp 130 | include/layer/microfacet.h src/microfacet.cpp 131 | include/layer/layer.h src/layer.cpp 132 | include/layer/fresnel.h src/fresnel.cpp 133 | include/layer/mmap.h src/mmap.cpp 134 | include/layer/log.h src/log.cpp 135 | include/layer/storage.h src/storage.cpp 136 | include/layer/simd.h src/simd.cpp 137 | 138 | # Python API 139 | src/python.cpp src/py_math.cpp src/py_fourier.cpp 140 | src/py_spline.cpp src/py_layer.cpp src/py_quad.cpp src/py_fresnel.cpp 141 | src/py_filesystem.cpp 142 | ) 143 | 144 | add_custom_target(mkdoc COMMAND 145 | python3 ${CMAKE_CURRENT_SOURCE_DIR}/ext/pybind11/tools/mkdoc.py 146 | -I${PYTHON_INCLUDE_DIR} -Iext/eigen -Iext/pybind11/include -Iext/filesystem 147 | -Iext/tinyformat -Iinclude 148 | ${CMAKE_CURRENT_SOURCE_DIR}/include/layer/*.h > ${CMAKE_CURRENT_SOURCE_DIR}/src/py_doc.h) 149 | 150 | set_target_properties(layerlab PROPERTIES PREFIX "") 151 | link_directories(${CMAKE_CURRENT_BUILD_DIR}/tbb) 152 | target_link_libraries(layerlab PRIVATE tbb_static) 153 | 154 | if (PKG_FFTW_FOUND) 155 | target_link_libraries(layerlab PRIVATE ${FFTW_THREADS_LIB}) 156 | endif() 157 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Wenzel Jakob 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ``layerlab``: A computational toolbox for layered materials 2 | 3 | ``layerlab`` is a Python-based toolbox for computations 4 | involving layered materials that implements a model described in the paper 5 | [A Comprehensive Framework for Rendering Layered Materials](http://www.cs.cornell.edu/projects/layered-sg14/) 6 | by Wenzel Jakob, Eugene D'Eon, Otto Jakob and Steve Marschner. 7 | 8 | A layered BSDF model describes the directional reflectance properties of a 9 | material whose internal structure consists of a stack of scattering and/or 10 | absorbing layers separated by smooth or rough interfaces. The bottom of the 11 | stack could be an opaque interface (such as a metal) or a transparent one. Such 12 | structural decompositions into layers and interfaces dramatically enlarge the 13 | size of the “language” that is available to describe materials, and for this 14 | reason they have been the focus of considerable interest in computer graphics 15 | in the last years. 16 | 17 | See [http://www.mitsuba-renderer.org/~wenzel/papers/layerlab-sg15.pdf](http://www.mitsuba-renderer.org/~wenzel/papers/layerlab-sg15.pdf) 18 | for a tutorial on using ``layerlab``. 19 | 20 | A few recipe-style examples are available in the ``recipes`` directory. 21 | -------------------------------------------------------------------------------- /cmake/FindFFTW.cmake: -------------------------------------------------------------------------------- 1 | # - Find the FFTW library 2 | # 3 | # Usage: 4 | # find_package(FFTW [REQUIRED] [QUIET] ) 5 | # 6 | # It sets the following variables: 7 | # FFTW_FOUND ... true if fftw is found on the system 8 | # FFTW_LIBRARIES ... full path to fftw library 9 | # FFTW_INCLUDES ... fftw include directory 10 | # 11 | # The following variables will be checked by the function 12 | # FFTW_USE_STATIC_LIBS ... if true, only static libraries are found 13 | # FFTW_ROOT ... if set, the libraries are exclusively searched 14 | # under this path 15 | # FFTW_LIBRARY ... fftw library to use 16 | # FFTW_INCLUDE_DIR ... fftw include directory 17 | # 18 | 19 | #If environment variable FFTWDIR is specified, it has same effect as FFTW_ROOT 20 | if( NOT FFTW_ROOT AND ENV{FFTWDIR} ) 21 | set( FFTW_ROOT $ENV{FFTWDIR} ) 22 | endif() 23 | 24 | # Check if we can use PkgConfig 25 | find_package(PkgConfig) 26 | 27 | #Determine from PKG 28 | if( PKG_CONFIG_FOUND AND NOT FFTW_ROOT ) 29 | pkg_check_modules( PKG_FFTW QUIET "fftw3" ) 30 | endif() 31 | 32 | #Check whether to search static or dynamic libs 33 | set( CMAKE_FIND_LIBRARY_SUFFIXES_SAV ${CMAKE_FIND_LIBRARY_SUFFIXES} ) 34 | 35 | if( ${FFTW_USE_STATIC_LIBS} ) 36 | set( CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX} ) 37 | else() 38 | set( CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_SHARED_LIBRARY_SUFFIX} ) 39 | endif() 40 | 41 | if( FFTW_ROOT ) 42 | 43 | #find libs 44 | find_library( 45 | FFTW_LIB 46 | NAMES "fftw3" 47 | PATHS ${FFTW_ROOT} 48 | PATH_SUFFIXES "lib" "lib64" 49 | NO_DEFAULT_PATH 50 | ) 51 | 52 | find_library( 53 | FFTW_THREADS_LIB 54 | NAMES "fftw3_threads" 55 | PATHS ${FFTW_ROOT} 56 | PATH_SUFFIXES "lib" "lib64" 57 | NO_DEFAULT_PATH 58 | ) 59 | 60 | find_library( 61 | FFTWF_LIB 62 | NAMES "fftw3f" 63 | PATHS ${FFTW_ROOT} 64 | PATH_SUFFIXES "lib" "lib64" 65 | NO_DEFAULT_PATH 66 | ) 67 | 68 | find_library( 69 | FFTWF_THREADS_LIB 70 | NAMES "fftw3f_threads" 71 | PATHS ${FFTW_ROOT} 72 | PATH_SUFFIXES "lib" "lib64" 73 | NO_DEFAULT_PATH 74 | ) 75 | 76 | find_library( 77 | FFTWL_LIB 78 | NAMES "fftw3l" 79 | PATHS ${FFTW_ROOT} 80 | PATH_SUFFIXES "lib" "lib64" 81 | NO_DEFAULT_PATH 82 | ) 83 | 84 | find_library( 85 | FFTWL_THREADS_LIB 86 | NAMES "fftw3l_threads" 87 | PATHS ${FFTW_ROOT} 88 | PATH_SUFFIXES "lib" "lib64" 89 | NO_DEFAULT_PATH 90 | ) 91 | 92 | #find includes 93 | find_path( 94 | FFTW_INCLUDES 95 | NAMES "fftw3.h" 96 | PATHS ${FFTW_ROOT} 97 | PATH_SUFFIXES "include" 98 | NO_DEFAULT_PATH 99 | ) 100 | 101 | else() 102 | 103 | find_library( 104 | FFTW_LIB 105 | NAMES "fftw3" 106 | PATHS ${PKG_FFTW_LIBRARY_DIRS} ${LIB_INSTALL_DIR} 107 | ) 108 | 109 | find_library( 110 | FFTW_THREADS_LIB 111 | NAMES "fftw3_threads" 112 | PATHS ${PKG_FFTW_LIBRARY_DIRS} ${LIB_INSTALL_DIR} 113 | ) 114 | 115 | find_library( 116 | FFTWF_LIB 117 | NAMES "fftw3f" 118 | PATHS ${PKG_FFTW_LIBRARY_DIRS} ${LIB_INSTALL_DIR} 119 | ) 120 | 121 | find_library( 122 | FFTWF_THREADS_LIB 123 | NAMES "fftw3f_threads" 124 | PATHS ${PKG_FFTW_LIBRARY_DIRS} ${LIB_INSTALL_DIR} 125 | ) 126 | 127 | find_library( 128 | FFTWL_LIB 129 | NAMES "fftw3l" 130 | PATHS ${PKG_FFTW_LIBRARY_DIRS} ${LIB_INSTALL_DIR} 131 | ) 132 | 133 | find_library( 134 | FFTWL_THREADS_LIB 135 | NAMES "fftw3l_threads" 136 | PATHS ${PKG_FFTW_LIBRARY_DIRS} ${LIB_INSTALL_DIR} 137 | ) 138 | 139 | find_path( 140 | FFTW_INCLUDES 141 | NAMES "fftw3.h" 142 | PATHS ${PKG_FFTW_INCLUDE_DIRS} ${INCLUDE_INSTALL_DIR} 143 | ) 144 | 145 | endif( FFTW_ROOT ) 146 | 147 | set(FFTW_LIBRARIES ${FFTW_LIB} ${FFTWF_LIB}) 148 | 149 | if(FFTWL_LIB) 150 | set(FFTW_LIBRARIES ${FFTW_LIBRARIES} ${FFTWL_LIB}) 151 | endif() 152 | 153 | set( CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES_SAV} ) 154 | 155 | include(FindPackageHandleStandardArgs) 156 | find_package_handle_standard_args(FFTW DEFAULT_MSG 157 | FFTW_INCLUDES FFTW_LIBRARIES) 158 | 159 | mark_as_advanced(FFTW_INCLUDES FFTW_LIBRARIES FFTW_LIB FFTWF_LIB FFTWL_LIB FFTW_THREADS_LIB FFTWF_THREADS_LIB FFTWL_THREADS_LIB) 160 | 161 | -------------------------------------------------------------------------------- /include/layer/color.h: -------------------------------------------------------------------------------- 1 | /* 2 | color.h -- Data types for representing RGB colors 3 | 4 | Copyright (c) 2015 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | 15 | NAMESPACE_BEGIN(layer) 16 | 17 | /// Data type for representing linearized RGB color values 18 | template struct TColor3 : public Eigen::Array { 19 | public: 20 | enum { 21 | Dimension = 3 22 | }; 23 | 24 | typedef Eigen::Array Base; 25 | 26 | /// Initialize the color vector with a uniform value 27 | TColor3(Scalar value = (Scalar) 0) : Base(value, value, value) { } 28 | 29 | /// Initialize the color vector with specific per-channel values 30 | TColor3(Scalar r, Scalar g, Scalar b) : Base(r, g, b) { } 31 | 32 | /// Assign a color from a dense Eigen expression template 33 | template TColor3(const Eigen::DenseBase& p) 34 | : Base(p) { } 35 | 36 | /// Assign a color from a dense Eigen expression template 37 | template TColor3 &operator=(const Eigen::DenseBase& p) { 38 | this->Base::operator=(p); 39 | return *this; 40 | } 41 | 42 | /// Return a reference to the red channel 43 | Scalar &r() { return Base::x(); } 44 | /// Return a reference to the red channel (const version) 45 | const Scalar &r() const { return Base::x(); } 46 | 47 | /// Return a reference to the green channel 48 | Scalar &g() { return Base::y(); } 49 | /// Return a reference to the green channel (const version) 50 | const Scalar &g() const { return Base::y(); } 51 | 52 | /// Return a reference to the blue channel 53 | Scalar &b() { return Base::z(); } 54 | /// Return a reference to the blue channel (const version) 55 | const Scalar &b() const { return Base::z(); } 56 | 57 | /// Clamp to the positive range 58 | TColor3 clamp() const { 59 | return TColor3(std::max(r(), (Scalar) 0), 60 | std::max(g(), (Scalar) 0), 61 | std::max(b(), (Scalar) 0)); 62 | } 63 | 64 | /// Check if the color vector contains a NaN/Inf/negative value 65 | bool isValid() const { 66 | for (int i = 0; i < 3; ++i) { 67 | Scalar value = Base::coeff(i); 68 | if (value < 0 || !std::isfinite(value)) 69 | return false; 70 | } 71 | return true; 72 | } 73 | 74 | /// Convert from sRGB to linear RGB 75 | TColor3 toLinearRGB() const { 76 | TColor3 result; 77 | 78 | for (int i = 0; i < 3; ++i) { 79 | Scalar value = Base::coeff(i); 80 | 81 | if (value <= (Scalar) 0.04045) 82 | result[i] = value * ((Scalar) 1.0 / (Scalar) 12.92); 83 | else 84 | result[i] = std::pow((value + (Scalar) 0.055) * 85 | (1 / (Scalar) 1.055), (Scalar) 2.4); 86 | } 87 | 88 | return result; 89 | } 90 | 91 | /// Convert from linear RGB to sRGB 92 | TColor3 toSRGB() const { 93 | TColor3 result; 94 | for (int i = 0; i < 3; ++i) { 95 | Scalar value = Base::coeff(i); 96 | if (value <= (Scalar) 0.0031308) 97 | result[i] = (Scalar) 12.92 * value; 98 | else 99 | result[i] = (Scalar) 1.055 * std::pow(value, 100 | (1 / (Scalar) 2.4)) - (Scalar) 0.055; 101 | } 102 | return result; 103 | } 104 | 105 | /// Return the associated luminance 106 | Scalar getLuminance() const { 107 | return Base::coeff(0) * (Scalar) 0.212671 + 108 | Base::coeff(1) * (Scalar) 0.715160 + 109 | Base::coeff(2) * (Scalar) 0.072169; 110 | } 111 | 112 | /// Stream operator 113 | friend std::ostream& operator<<(std::ostream& os, const TColor3 &c) { 114 | os << c.transpose(); return os; 115 | } 116 | }; 117 | 118 | typedef TColor3 Color3f; 119 | typedef TColor3 Color3d; 120 | typedef TColor3 Color3; 121 | 122 | NAMESPACE_END(layer) 123 | -------------------------------------------------------------------------------- /include/layer/common.h: -------------------------------------------------------------------------------- 1 | /* 2 | common.h -- Basic macros and type definitions 3 | 4 | Copyright (c) 2015 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #pragma once 11 | 12 | #if !defined(NAMESPACE_BEGIN) 13 | #define NAMESPACE_BEGIN(name) namespace name { 14 | #endif 15 | 16 | #if !defined(NAMESPACE_END) 17 | #define NAMESPACE_END(name) } 18 | #endif 19 | 20 | #if defined(_MSC_VER) 21 | #pragma warning(disable: 4127) // warning C4127: conditional expression is constant 22 | #pragma warning(disable: 4244) // warning C4244: 'initializing': conversion from 'double' to 'int', possible loss of data 23 | #pragma warning(disable: 4305) // warning C4305: 'initializing': truncation from 'double' to 'float' 24 | #pragma warning(disable: 4800) // warning C4800 : 'uint32_t' : forcing value to bool 'true' or 'false' (performance warning) 25 | #pragma warning(disable: 4838) // warning C4838: conversion from 'double' to 'float' requires a narrowing conversion 26 | #pragma warning(disable: 4714) // warning C4714: function marked as __forceinline not inlined 27 | #pragma warning(disable: 4456) // warning C4456 : declaration of 'x' hides previous local declaration 28 | #define NOMINMAX 29 | #include 30 | typedef SSIZE_T ssize_t; 31 | #endif 32 | 33 | #define __TBB_NO_IMPLICIT_LINKAGE 1 34 | #define EIGEN_DONT_PARALLELIZE 35 | #define EIGEN_DEFAULT_IO_FORMAT \ 36 | Eigen::IOFormat(7, 0, ", ", ";\n", "", "", "[", "]") 37 | 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | NAMESPACE_BEGIN(layer) 44 | 45 | namespace fs = ::filesystem; 46 | 47 | typedef double Float; 48 | 49 | /* Forward declarations */ 50 | template struct TVector; 51 | template struct TPoint; 52 | template struct TNormal3; 53 | template struct TFrame3; 54 | 55 | /* Lots of aliases for various dimensions and data types */ 56 | typedef TVector Vector1f; 57 | typedef TVector Vector2f; 58 | typedef TVector Vector3f; 59 | typedef TVector Vector4f; 60 | typedef TVector Vector1d; 61 | typedef TVector Vector2d; 62 | typedef TVector Vector3d; 63 | typedef TVector Vector4d; 64 | typedef TVector Vector1; 65 | typedef TVector Vector2; 66 | typedef TVector Vector3; 67 | typedef TVector Vector4; 68 | typedef TVector Vector1i; 69 | typedef TVector Vector2i; 70 | typedef TVector Vector3i; 71 | typedef TVector Vector4i; 72 | typedef TVector Vector1u; 73 | typedef TVector Vector2u; 74 | typedef TVector Vector3u; 75 | typedef TVector Vector4u; 76 | typedef TVector Vector1s; 77 | typedef TVector Vector2s; 78 | typedef TVector Vector3s; 79 | typedef TVector Vector4s; 80 | typedef TPoint Point1f; 81 | typedef TPoint Point2f; 82 | typedef TPoint Point3f; 83 | typedef TPoint Point4f; 84 | typedef TPoint Point1d; 85 | typedef TPoint Point2d; 86 | typedef TPoint Point3d; 87 | typedef TPoint Point4d; 88 | typedef TPoint Point1; 89 | typedef TPoint Point2; 90 | typedef TPoint Point3; 91 | typedef TPoint Point4; 92 | typedef TPoint Point1i; 93 | typedef TPoint Point2i; 94 | typedef TPoint Point3i; 95 | typedef TPoint Point4i; 96 | typedef TNormal3 Normal3; 97 | typedef TNormal3 Normal3f; 98 | typedef TNormal3 Normal3d; 99 | typedef TFrame3 Frame3f; 100 | typedef TFrame3 Frame3d; 101 | typedef TFrame3 Frame3; 102 | typedef Normal3 Normal; 103 | typedef Vector3 Vector; 104 | typedef Point3 Point; 105 | typedef Frame3 Frame; 106 | 107 | NAMESPACE_END(layer) 108 | -------------------------------------------------------------------------------- /include/layer/fourier.h: -------------------------------------------------------------------------------- 1 | /* 2 | fourier.h -- Functions for sampling and evaluating Fourier series 3 | 4 | Copyright (c) 2015 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | 15 | #if defined(__AVX__) 16 | #define FOURIER_SCALAR 0 17 | #define FOURIER_VECTORIZED 1 18 | #define LANE_WIDTH 8 19 | #else 20 | #define FOURIER_SCALAR 1 21 | #define FOURIER_VECTORIZED 0 22 | #define LANE_WIDTH 1 23 | #endif 24 | 25 | #define LANE_SIZE_BYTES (LANE_WIDTH * 4) 26 | 27 | NAMESPACE_BEGIN(layer) 28 | 29 | /** 30 | * \brief Evaluate an even Fourier series (i.e. containing 31 | * only cosine terms). 32 | * 33 | * \param coeffs 34 | * Coefficient storage 35 | * \param nCoeffs 36 | * Denotes the size of \c coeffs 37 | * \param phi 38 | * Angle for which the series should be evaluated 39 | * \return 40 | * The value of the series for this angle 41 | * \remark 42 | * In the Python API, the \c nCoeffs parameter is automatically 43 | * computed from the length of the input arrays and thus not needed. 44 | */ 45 | Float evalFourier(const float *coeffs, size_t nCoeffs, Float phi); 46 | 47 | /** 48 | * \brief Simultaneously evaluate three even Fourier series 49 | * corresponding to the color channels (Y, R, B) and return 50 | * a spectral power distribution 51 | * 52 | * \param coeffs 53 | * Coefficient storage 54 | * \param nCoeffs 55 | * Denotes the size of \c coeffs 56 | * \param phi 57 | * Angle for which the series should be evaluated 58 | * \return 59 | * The value of the series for this angle 60 | * \remark 61 | * In the Python API, the \c nCoeffs parameter is automatically 62 | * computed from the length of the input arrays and thus not needed. 63 | */ 64 | Color3 evalFourier3(float *const coeffs[3], size_t nCoeffs, Float phi); 65 | 66 | /** 67 | * \brief Sample a angle from an even Fourier series 68 | * (i.e. containing only cosine terms). 69 | * 70 | * This is done by importance sampling a uniform piecewise constant 71 | * approximation with 2^nRecursions elements, which is constructed 72 | * on the fly in a recursive manner. 73 | * 74 | * \param coeffs 75 | * Coefficient storage 76 | * \param nCoeffs 77 | * Denotes the size of \c coeffs 78 | * \param recip 79 | * Precomputed array of integer reciprocals, i.e. inf, 1, 1/2, 1/3, 80 | * 1/4, .. of size nCoeffs-1. This is used to reduce 81 | * integer-to-FP pipeline stalls and division latencies at runtime. 82 | * \param sample 83 | * A uniformly distributed random sample in the interval [0,1] 84 | * \param[out] pdf 85 | * This parameter is used to return the probability density of 86 | * the sampling scheme evaluated at the generated point on the 87 | * underlying piecewise constant approximation (on [0, \pi]) 88 | * \param[out] phi 89 | * Used to return the sampled angle (on [0, \pi]) 90 | * \return 91 | * The importance weight (i.e. the value of the Fourier series 92 | * divided by \c pdf) 93 | */ 94 | Float sampleFourier(const float *coeffs, const Float *recip, size_t nCoeffs, 95 | Float sample, Float &pdf, Float &phi); 96 | 97 | /** 98 | * \brief Sample a angle from three Fourier series 99 | * corresponding to the color channels (Y, R, B) 100 | * 101 | * This is done by importance sampling a uniform piecewise constant 102 | * approximation with respect to the luminance, which is constructed 103 | * on the fly in a recursive manner. 104 | * 105 | * \param coeffs 106 | * Coefficient storage 107 | * \param nCoeffs 108 | * Denotes the size of \c coeffs 109 | * \param recip 110 | * Precomputed array of integer reciprocals, i.e. inf, 1, 1/2, 1/3, 111 | * 1/4, .. of size nCoeffs-1. This is used to reduce 112 | * integer-to-FP pipeline stalls and division latencies at runtime. 113 | * \param sample 114 | * A uniformly distributed random sample in the interval [0,1] 115 | * \param[out] pdf 116 | * This parameter is used to return the probability density of 117 | * the sampling scheme evaluated at the generated point on the 118 | * underlying piecewise constant approximation (on [0, \pi]) 119 | * \param[out] phi 120 | * Used to return the sampled angle (on [0, \pi]) 121 | * \return 122 | * The importance weight (i.e. the value of the Fourier series 123 | * divided by \c pdf) 124 | */ 125 | Color3 sampleFourier3(float *const coeffs[3], const double *recip, size_t nCoeffs, 126 | Float sample, Float &pdf, Float &phi); 127 | 128 | /** 129 | * \brief Evaluate the probability density of the sampling scheme 130 | * implemented by \ref sampleFourier() at the position \c phi. 131 | * 132 | * \param coeffs 133 | * Coefficient storage 134 | * \param nCoeffs 135 | * Denotes the size of \c coeffs 136 | * \return 137 | * The continuous probability density on [0, \pi] 138 | */ 139 | inline Float pdfFourier(const float *coeffs, size_t nCoeffs, Float phi) { 140 | return evalFourier(coeffs, nCoeffs, phi) * math::InvTwoPi / (Float)coeffs[0]; 141 | } 142 | 143 | /** 144 | * \brief Computes the Fourier series of a product of even Fourier series 145 | * using discrete convolution. 146 | * 147 | * The input series are assumed to have \c ka and \c kb coefficients, and the 148 | * output must have room for ka+kb-1 coefficients. 149 | * 150 | * \remark a 151 | * First input array of Fourier coefficients 152 | * \remark ka 153 | * Size of the first input array 154 | * \remark b 155 | * Second input array of Fourier coefficients 156 | * \remark kb 157 | * Size of the second input array 158 | * \remark c 159 | * Pointer into the output array 160 | * \remark 161 | * In the Python API, the \c ka and \c kb parameters are automatically 162 | * computed from the lengths of the input arrays and thus not needed. 163 | * The \c parameter is also removed (the result array is directly returned). 164 | */ 165 | void convolveFourier(const Float *a, size_t ka, const Float *b, size_t kb, Float *c); 166 | 167 | /** 168 | * \brief Compute a Fourier series of the given even function by integrating it 169 | * against the basis functions using Filon quadrature 170 | * 171 | * Filon quadrature works by constructing a piecewise quadratic interpolant of 172 | * the original function. The Fourier basis functions are then integrated 173 | * against this representation, which has an analytic solution. This avoids all 174 | * of the problems of traditional quadrature schemes involving highly 175 | * oscillatory integrals. It is thus possible to obtain accurate coefficients 176 | * even for high orders. 177 | * 178 | * \param f 179 | * Function to be integrated 180 | * \param[out] coeffs 181 | * Output buffer used to store the computed coefficients. The function adds 182 | * the computed coefficients to the buffer rather than overwriting the 183 | * existing contents. 184 | * \param nCoeffs 185 | * Desired number of coefficients 186 | * \param nEvals 187 | * Desired resolution of the piecewise quadratic interpolant 188 | * \param a 189 | * Start of the integration, can optionally be set to values other than 190 | * zero. Note that the Fourier basis functions are not orthogonal anymore in 191 | * this case. 192 | * \param b 193 | * End of the integration, can be set to values other than pi. Note that the 194 | * Fourier basis functions are not orthogonal anymore in this case. 195 | * \remark 196 | * In the Python API, the \c coeffs array is directly returned. 197 | */ 198 | void filonIntegrate(const std::function &f, Float *coeffs, 199 | size_t nCoeffs, int nEvals, Float a = 0, Float b = math::Pi); 200 | 201 | #if FOURIER_SCALAR == 1 202 | 203 | #define fourier_aligned_alloca(size) \ 204 | __align_helper(alloca(size), size) 205 | 206 | static inline float *__align_helper(void *ptr, size_t size) { 207 | memset(ptr, 0, size); 208 | return (float *) ptr; 209 | } 210 | 211 | #else 212 | 213 | /** 214 | * \brief Helper functions for allocating temporary stack memory for 215 | * Fourier coefficients. 216 | * 217 | * This macro works like alloca(), except that it also ensures that 218 | * the resulting buffer is properly aligned so that it can be used 219 | * with the SSE/AVX-vectorized evaluation and sampling routines. 220 | * 221 | * Given an allocation, we require memory for 222 | * 223 | * 1 float at alignment -4 224 | * N quadruplets at alignment 0 225 | * 226 | * SSE: 12 bytes in addition to make sure that 227 | * this alignment can be established 228 | * AVX: 28 bytes in addition to make sure that 229 | * this alignment can be established 230 | * 231 | * i.e. 232 | * 233 | * SSE: size = 4 + 16*((size-4 + 12) / 16) + 12; 234 | * SSE: size = 4 + 32*((size-4 + 28) / 32) + 28; 235 | * 236 | * and to align: 237 | * 238 | * SSE: buffer += 12 - buffer mod 16 239 | * AVX: buffer += 28 - buffer mod 32 240 | */ 241 | 242 | #define fourier_aligned_alloca(size) \ 243 | __align_helper(alloca(LANE_SIZE_BYTES*(((LANE_SIZE_BYTES-2*sizeof(float))+size) / LANE_SIZE_BYTES) + LANE_SIZE_BYTES), \ 244 | LANE_SIZE_BYTES*(((LANE_SIZE_BYTES-2*sizeof(float))+size) / LANE_SIZE_BYTES) + LANE_SIZE_BYTES) 245 | 246 | namespace { 247 | static inline float *__align_helper(void *ptr, size_t size) { 248 | memset(ptr, 0, size); 249 | return (float *) ((uint8_t *) ptr + (LANE_SIZE_BYTES - sizeof(float) - ((uintptr_t) ptr) % LANE_SIZE_BYTES)); 250 | } 251 | }; 252 | 253 | #endif 254 | 255 | NAMESPACE_END(layer) 256 | -------------------------------------------------------------------------------- /include/layer/frame.h: -------------------------------------------------------------------------------- 1 | /* 2 | vector.h -- This file contains templates and specializations for 3 | 2/3D points, vectors, and normals over different underlying data types. 4 | These all transform differently under homogeneous coordinate 5 | transformations and are thus implemented as separate types. 6 | 7 | Copyright (c) 2015 Wenzel Jakob 8 | 9 | All rights reserved. Use of this source code is governed by a 10 | BSD-style license that can be found in the LICENSE file. 11 | */ 12 | 13 | #pragma once 14 | 15 | #include 16 | #include 17 | 18 | NAMESPACE_BEGIN(layer) 19 | 20 | /** 21 | * \brief Stores a three-dimensional orthonormal coordinate frame 22 | * 23 | * This class converts between different cartesian coordinate systems and 24 | * efficiently computes certain derived quantities based on spherical 25 | * coordinates (e.g. \ref cosTheta(), \ref tanTheta(), ..). 26 | */ 27 | template struct TFrame3 { 28 | typedef TVector VectorType; 29 | typedef TNormal3 NormalType; 30 | 31 | /// First tangent 32 | VectorType s; 33 | /// Second tangent 34 | VectorType t; 35 | /// Normal direction 36 | NormalType n; 37 | 38 | /// Default constructor -- performs no initialization! 39 | TFrame3() { } 40 | 41 | /// Construct a new coordinate frame from a single vector 42 | TFrame3(const VectorType &n) : n(n) { 43 | coordinateSystem(n, s, t); 44 | } 45 | 46 | /// Construct a frame from the given orthonormal vectors 47 | TFrame3(const VectorType &s, const VectorType &t, const VectorType &n) 48 | : s(s), t(t), n(n) { } 49 | 50 | /// Convert from world coordinates to local coordinates 51 | VectorType toLocal(const VectorType &v) const { 52 | return VectorType( 53 | v.dot(s), 54 | v.dot(t), 55 | v.dot(n) 56 | ); 57 | } 58 | 59 | /// Convert from local coordinates to world coordinates 60 | VectorType toWorld(const VectorType &v) const { 61 | return s * v.x() + t * v.y() + n * v.z(); 62 | } 63 | 64 | /** \brief Assuming that the given direction is in the local coordinate 65 | * system, return the squared sine of the angle between the normal and v */ 66 | static Scalar sinTheta2(const VectorType &v) { 67 | return (Scalar) 1 - v.z() * v.z(); 68 | } 69 | 70 | /** \brief Assuming that the given direction is in the local coordinate 71 | * system, return the squared cosine of the angle between the normal and v */ 72 | static Scalar cosTheta2(const VectorType &v) { 73 | return v.z() * v.z(); 74 | } 75 | 76 | /** \brief Assuming that the given direction is in the local coordinate 77 | * system, return the squared tangent of the angle between the normal and v */ 78 | static Scalar tanTheta2(const VectorType &v) { 79 | return sinTheta2(v) / cosTheta2(v); 80 | } 81 | 82 | /** \brief Assuming that the given direction is in the local coordinate 83 | * system, return the sine of the angle between the normal and v */ 84 | static Scalar sinTheta(const VectorType &v) { 85 | return math::safe_sqrt(sinTheta2(v)); 86 | } 87 | 88 | /** \brief Assuming that the given direction is in the local coordinate 89 | * system, return the cosine of the angle between the normal and v */ 90 | static Scalar cosTheta(const VectorType &v) { 91 | return v.z(); 92 | } 93 | 94 | /** \brief Assuming that the given direction is in the local coordinate 95 | * system, return the tangent of the angle between the normal and v */ 96 | static Scalar tanTheta(const VectorType &v) { 97 | return sinTheta(v) / cosTheta(v); 98 | } 99 | 100 | /** \brief Assuming that the given direction is in the local coordinate 101 | * system, return the squared sine of the phi parameter in spherical 102 | * coordinates */ 103 | static Scalar sinPhi2(const VectorType &v) { 104 | Scalar sinTheta2 = TFrame3::sinTheta2(v); 105 | if (sinTheta2 == 0) 106 | return 0; 107 | return math::clamp(v.y() * v.y() / sinTheta2, (Scalar) 0, (Scalar) 1); 108 | } 109 | 110 | /** \brief Assuming that the given direction is in the local coordinate 111 | * system, return the squared cosine of the phi parameter in spherical 112 | * coordinates */ 113 | static Scalar cosPhi2(const VectorType &v) { 114 | Scalar sinTheta2 = TFrame3::sinTheta2(v); 115 | if (sinTheta2 == 0) 116 | return 0; 117 | return math::clamp(v.x() * v.x() / sinTheta2, (Scalar) 0, (Scalar) 1); 118 | } 119 | 120 | /** \brief Assuming that the given direction is in the local coordinate 121 | * system, return the sine of the phi parameter in spherical coordinates */ 122 | static Scalar sinPhi(const VectorType &v) { 123 | Scalar sinTheta = TFrame3::sinTheta(v); 124 | if (sinTheta == 0) 125 | return 0; 126 | return math::clamp(v.y() / sinTheta, (Scalar) -1, (Scalar) 1); 127 | } 128 | 129 | /** \brief Assuming that the given direction is in the local coordinate 130 | * system, return the cosine of the phi parameter in spherical coordinates */ 131 | static Scalar cosPhi(const VectorType &v) { 132 | Scalar sinTheta = TFrame3::sinTheta(v); 133 | if (sinTheta == 0) 134 | return 0; 135 | return math::clamp(v.x() / sinTheta, (Scalar) -1, (Scalar) 1); 136 | } 137 | 138 | /// Equality test 139 | bool operator==(const TFrame3 &frame) const { 140 | return frame.s == s && frame.t == t && frame.n == n; 141 | } 142 | 143 | /// Inequality test 144 | bool operator!=(const TFrame3 &frame) const { 145 | return !operator==(frame); 146 | } 147 | 148 | /// Stream operator 149 | friend std::ostream& operator<<(std::ostream& os, const TFrame3& f) { 150 | os << "Frame[" << std::endl 151 | << " s = " << f.s << "," << std::endl 152 | << " t = " << f.t << "," << std::endl 153 | << " n = " << f.n << std::endl 154 | << "]"; 155 | return os; 156 | } 157 | }; 158 | 159 | NAMESPACE_END(layer) 160 | -------------------------------------------------------------------------------- /include/layer/fresnel.h: -------------------------------------------------------------------------------- 1 | /* 2 | fresnel.h -- Fresnel coefficients for dielectrics and conductors 3 | 4 | Copyright (c) 2015 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | 15 | NAMESPACE_BEGIN(layer) 16 | 17 | /** 18 | * \brief Calculates the unpolarized Fresnel reflection coefficient 19 | * at a planar interface between two dielectrics 20 | * 21 | * This function also computes the transmitted direction and returns it using 22 | * the \c cosThetaT argument. When encountering total internal reflection, it 23 | * sets cosThetaT=0 and returns the value 1. 24 | * 25 | * When cosThetaI < 0, the function computes the Fresnel reflectance 26 | * from the \a internal boundary, which is equivalent to calling the function 27 | * with arguments fresnelDielectric(abs(cosThetaI), cosThetaT, 1/eta). 28 | * 29 | * \remark When accessed from Python, this function has the signature 30 | * "F, cosThetaT = fresnelDielectric(cosThetaI, eta)". 31 | * 32 | * \param cosThetaI 33 | * Cosine of the angle between the normal and the incident ray 34 | * (may be negative) 35 | * \param cosThetaT 36 | * Argument used to return the cosine of the angle between the normal 37 | * and the transmitted ray, will have the opposite sign of \c cosThetaI 38 | * \param eta 39 | * Relative refractive index 40 | */ 41 | extern Float fresnelDielectric(Float cosThetaI, Float &cosThetaT, Float eta); 42 | 43 | /** 44 | * \brief Calculates the unpolarized Fresnel reflection coefficient 45 | * at a planar interface between two dielectrics 46 | * 47 | * This is just a convenience wrapper function around the other \c 48 | * fresnelDielectric function, which does not return the transmitted direction 49 | * cosine in case it is not needed by the application. 50 | * 51 | * \param cosThetaI 52 | * Cosine of the angle between the normal and the incident ray 53 | * \param eta 54 | * Relative refractive index 55 | */ 56 | inline Float fresnelDielectric(Float cosThetaI, Float eta) { 57 | Float unused; 58 | return fresnelDielectric(cosThetaI, unused, eta); 59 | } 60 | 61 | /** 62 | * \brief Calculates the unpolarized Fresnel reflection coefficient 63 | * at a planar interface having a complex-valued relative index of 64 | * refraction 65 | * 66 | * \remark The name of this function is a slight misnomer, since it supports 67 | * the general case of a complex-valued relative index of refraction (rather 68 | * than being restricted to conductors) 69 | * 70 | * \param cosThetaI 71 | * Cosine of the angle between the normal and the incident ray 72 | * \param eta 73 | * Relative index of refraction (complex) 74 | */ 75 | extern Float fresnelConductor(Float cosThetaI, std::complex eta); 76 | 77 | /** 78 | * \brief Calculates the diffuse unpolarized Fresnel reflectance of 79 | * a dielectric material (sometimes referred to as "Fdr"). 80 | * 81 | * This value quantifies what fraction of diffuse incident illumination 82 | * will, on average, be reflected at a dielectric material boundary 83 | * 84 | * \param eta 85 | * Relative refraction coefficient 86 | */ 87 | extern Float fresnelDielectricIntegral(Float eta); 88 | 89 | /** 90 | * \brief Calculates the diffuse unpolarized Fresnel reflectance of 91 | * a conductor 92 | * 93 | * This value quantifies what fraction of diffuse incident illumination 94 | * will, on average, be reflected at a conductive material boundary 95 | * 96 | * \param eta 97 | * Relative refractive index (real component) 98 | * \param k 99 | * Relative refractive index (imaginary component) 100 | */ 101 | extern Float fresnelConductorIntegral(std::complex eta); 102 | 103 | NAMESPACE_END(layer) 104 | -------------------------------------------------------------------------------- /include/layer/hg.h: -------------------------------------------------------------------------------- 1 | /* 2 | hg.h -- Henyey-Greenstein model evaluation routines 3 | 4 | Copyright (c) 2015 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | 15 | NAMESPACE_BEGIN(layer) 16 | 17 | /** 18 | * \brief Evaluate the HG model using the mu_i, mu_o, phi_d parameterization 19 | * 20 | * \param mu_i 21 | * Incident zenith angle cosine 22 | * \param mu_o 23 | * Exitant zenith angle cosine 24 | * \param phi_d 25 | * Azimuthal difference angle 26 | * \param g 27 | * Anisotropy parameter 28 | */ 29 | Float hg(Float mu_o, Float mu_i, Float g, Float phi_d); 30 | 31 | /** 32 | * \brief Compute a Fourier series of the HG model 33 | * 34 | * This function first finds the 0-th and 1st-order Fourier coefficients using 35 | * elliptic integrals. 36 | * 37 | * The other coefficients can then be determined much more efficiently; the 38 | * approach here is based on the idea that the ratio of adjacent coefficients 39 | * eventually converges to a constant value. Using a 2nd-order Taylor 40 | * expansion, we can obtain a fairly accurate estimate of this ratio somewhere 41 | * "in the middle" (i.e. for large $n$, but well before the aforementioned 42 | * convergence). 43 | * 44 | * Using a backwards recurrence scheme, we can then determine all previous 45 | * ratios and, thereby (using the explicitly computed first values), all 46 | * Fourier coefficients. 47 | * 48 | * This approach is based on the article 49 | * 50 | * "A Recurrence Formula For Computing Fourier Components of the 51 | * Henyey-Greenstein Phase Function" by E.G. Yanovitskij 52 | * 53 | * Journal of Quantitative Spectroscopy & Radiative Transfer, 57, no 1. 1977 54 | * 55 | * \param mu_i 56 | * Incident zenith angle cosine 57 | * \param mu_o 58 | * Exitant zenith angle cosine 59 | * \param g 60 | * Anisotropy parameter 61 | * \param kmax 62 | * Indicates a desired maximum number of Fourier coefficients. The 63 | * implementation will blur out higher Frequency content to try to 64 | * achieve this number. 65 | * \param result 66 | * Storage for the generated Fourier coefficients 67 | */ 68 | void hgFourierSeries(Float mu_o, Float mu_i, Float g, size_t kmax, 69 | Float relerr, std::vector &result); 70 | 71 | NAMESPACE_END(layer) 72 | -------------------------------------------------------------------------------- /include/layer/layer.h: -------------------------------------------------------------------------------- 1 | /* 2 | layer.h -- Microfacet BSDF evaluation routines 3 | 4 | Copyright (c) 2015 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | 15 | NAMESPACE_BEGIN(layer) 16 | 17 | #define ERROR_GOAL 1e-3f 18 | #define DROP_THRESHOLD 1e-9f 19 | 20 | typedef Eigen::SparseMatrix MatrixS; 21 | 22 | /** 23 | * \brief Helper class, which stores one Fourier order of a 24 | * layer scattering function 25 | */ 26 | struct LayerMode { 27 | public: 28 | /** 29 | * \brief Create a new layer mode data structure 30 | * 31 | * \param size 32 | * Resolution of the underlying matrix, must be a multiple of 2 33 | */ 34 | LayerMode(size_t size = 0) { 35 | if (size % 2 == 1) 36 | throw std::runtime_error("LayerMode(): 'size' must be a multiple of 2"); 37 | MatrixS::Index hSize = (MatrixS::Index) size / 2; 38 | reflectionTop.resize(hSize, hSize); 39 | reflectionBottom.resize(hSize, hSize); 40 | transmissionTopBottom.resize(hSize, hSize); 41 | transmissionBottomTop.resize(hSize, hSize); 42 | } 43 | 44 | /// Return the underlying matrix resolution (i.e. the number of discretizations in \mu) 45 | size_t resolution() const { 46 | return reflectionTop.rows() * 2; 47 | } 48 | 49 | /// Reset scattering matrix to that of a clear non-scattering layer 50 | void clear() { 51 | reflectionTop.setZero(); 52 | reflectionBottom.setZero(); 53 | transmissionTopBottom.setIdentity(); 54 | transmissionBottomTop.setIdentity(); 55 | } 56 | 57 | /// Reverse the layer 58 | void reverse() { 59 | reflectionTop.swap(reflectionBottom); 60 | transmissionTopBottom.swap(transmissionBottomTop); 61 | } 62 | 63 | /// Is this layer represented by a diagonal matrix? 64 | bool isDiagonal() const { 65 | int n = reflectionTop.rows(); 66 | return 67 | reflectionTop.nonZeros() == 0 && 68 | reflectionBottom.nonZeros() == 0 && 69 | transmissionTopBottom.nonZeros() == n && 70 | transmissionBottomTop.nonZeros() == n; 71 | } 72 | 73 | /// Access a matrix entry 74 | Float coeff(MatrixS::Index i, MatrixS::Index j) const { 75 | int n = reflectionTop.rows(); 76 | 77 | if (i < n && j < n) 78 | return transmissionBottomTop.coeff(i, j); 79 | else if (i >= n && j >= n) 80 | return transmissionTopBottom.coeff(i-n, j-n); 81 | else if (i < n && j >= n) 82 | return reflectionTop.coeff(i, j-n); 83 | else if (i >= n && j < n) 84 | return reflectionBottom.coeff(i-n, j); 85 | else 86 | throw std::runtime_error("LayerMode::coeff(): out of bounds!"); 87 | } 88 | 89 | /// Return the number of nonzero coefficients 90 | size_t nonZeros() const { 91 | return 92 | reflectionTop.nonZeros() + 93 | reflectionBottom.nonZeros() + 94 | transmissionTopBottom.nonZeros() + 95 | transmissionBottomTop.nonZeros(); 96 | } 97 | 98 | void addScaled(const LayerMode &layer, Float scale) { 99 | reflectionTop += layer.reflectionTop * scale; 100 | reflectionBottom += layer.reflectionBottom * scale; 101 | transmissionTopBottom += layer.transmissionTopBottom * scale; 102 | transmissionBottomTop += layer.transmissionBottomTop * scale; 103 | } 104 | 105 | /// Return a human-readable summary 106 | std::string toString() const; 107 | public: 108 | /// Reflection matrix on the top interface 109 | MatrixS reflectionTop; 110 | /// Reflection matrix on the bottom interface 111 | MatrixS reflectionBottom; 112 | /// Transmission matrix going from the top to the bottom interface 113 | MatrixS transmissionTopBottom; 114 | /// Transmission matrix going from the bottom to the top interface 115 | MatrixS transmissionBottomTop; 116 | }; 117 | 118 | 119 | /** 120 | * \brief Discretized layer reflection model 121 | * 122 | * Describes the linear response to illumination that is incident along a 123 | * chosen set of zenith angles. The azimuthal dependence is modeled as 124 | * an even real Fourier transform. Each Fourier order is stored in a special 125 | * \c LayerMode data structure. 126 | */ 127 | class Layer { 128 | public: 129 | /** 130 | * \brief Create a new layer with the given discretization in zenith angles 131 | * \param nodes 132 | * A vector with the zenith angle cosines of the chosen discretization 133 | * \param weights 134 | * Associated weights for each angle. Usually, the 'nodes' and 'weights' 135 | * are generated using some kind of quadrature rule (e.g. Gauss-Legendre, 136 | * Gauss-Lobatto, etc.) 137 | * \param nFourierOrders 138 | * Specifies the number of coefficients to use for the azimuthal Fourier 139 | * expansion (default: 1) 140 | */ 141 | Layer(const VectorX &nodes, const VectorX &weights, size_t nFourierOrders = 1); 142 | 143 | /// Return the number of Fourier orders 144 | size_t fourierOrders() const { return m_modes.size(); } 145 | 146 | /// Return the number of nodes (i.e. the number of discretizations in \mu) 147 | size_t resolution() const { return m_nodes.size(); } 148 | 149 | /** 150 | * \brief Initialize the layer with a diffuse base layer 151 | * 152 | * \param albedo 153 | * The layer's diffuse reflectance (in [0, 1]) 154 | */ 155 | void setDiffuse(Float albedo); 156 | 157 | /** 158 | * \brief Initialize the layer with an isotropic phase function 159 | * 160 | * \param albedo 161 | * The layer's single scattering albedo reflectance (in [0, 1]) 162 | */ 163 | void setIsotropic(Float albedo); 164 | 165 | /** 166 | * \brief Initialize the layer with a Henyey-Greenstein phase function 167 | * 168 | * \param albedo 169 | * The layer's single scattering albedo reflectance (in [0, 1]) 170 | * \param g 171 | * The layer's HG anisotropy parameter(in [-1, 1]) 172 | */ 173 | void setHenyeyGreenstein(Float albedo, Float g); 174 | 175 | /** 176 | * \brief Initialize the layer with a von Mises-Fisher phase function 177 | * 178 | * \param albedo 179 | * The layer's single scattering albedo reflectance (in [0, 1]) 180 | * \param kappa 181 | * The layer's kappa anisotropy parameter 182 | */ 183 | void setVonMisesFisher(Float albedo, Float kappa); 184 | 185 | /** 186 | * \brief Initialize the layer with a microfacet model (dielectric or conductor) 187 | * 188 | * \param eta 189 | * Relative index of refraction (complex) 190 | * \param alpha 191 | * Beckmann roughness coefficient 192 | * \param conserveEnergy 193 | * Correct for energy loss due to multiple scattering? 194 | * Default: \c true 195 | * \param fourierOrders 196 | * Number of fourier orders that should be used internally 197 | * in the computation. Defaults to the value returned by 198 | * \ref fourierOrders() 199 | */ 200 | void setMicrofacet(std::complex eta, Float alpha, 201 | bool conserveEnergy = true, 202 | size_t fourierOrders = 0); 203 | 204 | /** 205 | * \brief Combine two layers using the adding equations 206 | * 207 | * \param l1 208 | * Input layer 1 209 | * \param l2 210 | * Input layer 2 211 | * \param output 212 | * Used to return the resulting layer 213 | * \param homogeneous 214 | * When both layers are homogenous, (i.e. if their two sides are 215 | * indistinguishable, this flag should be set to \c true to get a 216 | * speed-up). Default: \c false 217 | * \remark 218 | * In the Python API, the \c output parameter is directly returned 219 | */ 220 | static void add(const Layer &l1, const Layer &l2, Layer &output, 221 | bool homogeneous = false); 222 | 223 | /** 224 | * \brief Initialize the layer with a Matusik-style BRDF data file 225 | * 226 | * \param path 227 | * Filename of the BRDF data file 228 | * \param channel 229 | * Color channel to extract in [0..2] 230 | * \param fourierOrders 231 | * Number of fourier orders that should be used internally 232 | * in the computation. Defaults to the value returned by 233 | * \ref fourierOrders() 234 | */ 235 | void setMatusik(const fs::path &path, int channel, int fourierOrders = -1); 236 | 237 | /** 238 | * \brief Append a layer above the current one 239 | * 240 | * This is just a convenience wrapper around \ref Layer::add() 241 | * 242 | * \param l 243 | * The layer to be appended 244 | * \param homogeneous 245 | * When the layers are homogenous, (i.e. if their two sides are 246 | * indistinguishable, this flag should be set to \c true to get a 247 | * speed-up). Default: \c false 248 | */ 249 | void addToTop(const Layer &l, bool homogeneous = false) { 250 | Layer::add(l, *this, *this, homogeneous); 251 | } 252 | 253 | /** 254 | * \brief Append a layer below the current one 255 | * 256 | * This is just a convenience wrapper around \ref Layer::add() 257 | * 258 | * \param l 259 | * The layer to be appended 260 | * \param homogeneous 261 | * When the layers are homogenous, (i.e. if their two sides are 262 | * indistinguishable, this flag should be set to \c true to get a 263 | * speed-up). Default: \c false 264 | */ 265 | void addToBottom(const Layer &l, bool homogeneous = false) { 266 | Layer::add(*this, l, *this, homogeneous); 267 | } 268 | 269 | /// Solve for the transport matrix of a layer with the given optical thickness (using Adding-Doubling) 270 | void expand(Float tau); 271 | 272 | /// Return the used integration weights 273 | const VectorX &weights() const { return m_weights; } 274 | 275 | /// Return the used integration nodes 276 | const VectorX &nodes() const { return m_nodes; } 277 | 278 | /// Reset scattering matrix to that of a clear non-scattering layer 279 | void clear(); 280 | 281 | /// Reverse the layer 282 | void reverse(); 283 | 284 | /// Look up a mode of the azimuthal Fourier expansion 285 | LayerMode &operator[](size_t i) { return m_modes[i]; } 286 | 287 | /// Look up a mode of the azimuthal Fourier expansion (const version) 288 | const LayerMode &operator[](size_t i) const { return m_modes[i]; } 289 | 290 | /** 291 | * \brief Return a dense representation of a mode's scattering matrix 292 | * 293 | * All integration weights and cosine foreshortening factors are 294 | * removed so that the coefficients can be interpreted as sampled 295 | * BSDF values 296 | */ 297 | MatrixX matrix(size_t mode = 0) const; 298 | 299 | /// Evaluate the BSDF for a given pair of zenith angle cosines 300 | Float eval(Float mu_o, Float mu_i, Float phi_d = 0) const; 301 | 302 | /// Write the layer coefficients to a sparse file 303 | void write(const std::string &filename); 304 | 305 | /// Return a human-readable summary 306 | std::string toString() const; 307 | 308 | protected: 309 | /// Helper struct for sparse matrix construction 310 | struct Quartet { 311 | uint32_t l, o, i; 312 | Float value; 313 | 314 | Quartet(uint32_t l, uint32_t o, uint32_t i, Float value) 315 | : l(l), o(o), i(i), value(value) { } 316 | Quartet(size_t l, size_t o, size_t i, Float value) 317 | : l((uint32_t) l), o((uint32_t) o), i((uint32_t) i), value(value) { } 318 | }; 319 | 320 | /// Initialize from a list of quartets 321 | void setQuartets(const std::vector &quartets); 322 | protected: 323 | /// Storage for all of the Fourier modes 324 | std::vector m_modes; 325 | 326 | /// Integration nodes 327 | VectorX m_nodes; 328 | 329 | /// Integration weights 330 | VectorX m_weights; 331 | }; 332 | 333 | /// Heuristic to guess a suitable number of parameters (Microfacet model) 334 | extern std::pair parameterHeuristicMicrofacet(Float alpha, std::complex &eta); 335 | 336 | /// Heuristic to guess a suitable number of parameters (HG model) 337 | extern std::pair parameterHeuristicHG(Float g); 338 | 339 | NAMESPACE_END(layer) 340 | -------------------------------------------------------------------------------- /include/layer/log.h: -------------------------------------------------------------------------------- 1 | /* 2 | common.h -- Basic macros and type definitions 3 | 4 | Copyright (c) 2015 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | 14 | #if defined(_MSC_VER) 15 | #pragma warning(push) 16 | #pragma warning(disable: 4702) // warning C4702: unreachable code 17 | #endif 18 | #include 19 | #if defined(_MSC_VER) 20 | #pragma warning(pop) 21 | #endif 22 | 23 | 24 | #define Error(...) \ 25 | throw std::runtime_error(tfm::format(__VA_ARGS__)) 26 | 27 | #define Trace(format, ...) \ 28 | tfm::printf("Trace: " format "\n", ##__VA_ARGS__) 29 | 30 | #define Warn(format, ...) \ 31 | tfm::printf("Warning: " format "\n", ##__VA_ARGS__) 32 | 33 | #define Log(format, ...) \ 34 | tfm::printf("Log: " format "\n", ##__VA_ARGS__) 35 | 36 | NAMESPACE_BEGIN(layer) 37 | 38 | /** 39 | * \brief Convert a time difference (in seconds) into a human-readable string 40 | * representation 41 | * \param time 42 | * Time difference in (fractional) sections 43 | * \param precise 44 | * When set to true, a higher-precision string representation is 45 | * generated. 46 | */ 47 | extern std::string timeString(Float time, bool precise = false); 48 | 49 | /** 50 | * \brief Convert a memory amount (in bytes) into a human-readable string 51 | * representation 52 | * \param size 53 | * An unsigned integer size value 54 | * \param precise 55 | * When set to true, a higher-precision string representation is 56 | * generated. 57 | */ 58 | extern std::string memString(size_t size, bool precise = false); 59 | 60 | #if defined(__WINDOWS__) 61 | /// Return a string version of GetLastError() 62 | extern std::string lastErrorText(); 63 | #endif 64 | 65 | NAMESPACE_END(layer) 66 | -------------------------------------------------------------------------------- /include/layer/math.h: -------------------------------------------------------------------------------- 1 | /* 2 | math.h -- Mathematical routines, special functions, etc. 3 | 4 | Copyright (c) 2015 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | NAMESPACE_BEGIN(layer) 17 | NAMESPACE_BEGIN(math) 18 | 19 | // ----------------------------------------------------------------------- 20 | //! @{ \name Useful constants in various precisions 21 | // ----------------------------------------------------------------------- 22 | 23 | static const double E_d = 2.71828182845904523536; 24 | static const double Pi_d = 3.14159265358979323846; 25 | static const double InvPi_d = 0.31830988618379067154; 26 | static const double InvTwoPi_d = 0.15915494309189533577; 27 | static const double InvFourPi_d = 0.07957747154594766788; 28 | static const double SqrtPi_d = 1.77245385090551602793; 29 | static const double InvSqrtPi_d = 0.56418958354775628695; 30 | static const double SqrtTwo_d = 1.41421356237309504880; 31 | static const double InvSqrtTwo_d = 0.70710678118654752440; 32 | static const double SqrtTwoPi_d = 2.50662827463100050242; 33 | static const double InvSqrtTwoPi_d = 0.39894228040143267794; 34 | static const double Epsilon_d = 1e-7; 35 | #if !defined(_MSC_VER) 36 | static const double OneMinusEpsilon_d = 0x1.fffffffffffff7p-1; 37 | static const double RecipOverflow_d = 0x1p-1024; 38 | #else 39 | static const double OneMinusEpsilon_d = 0.999999999999999888; 40 | static const double RecipOverflow_d = 5.56268464626800345e-309; 41 | #endif 42 | static const double Infinity_d = std::numeric_limits::infinity(); 43 | static const double MaxFloat_d = std::numeric_limits::max(); 44 | static const double MachineEpsilon_d = std::numeric_limits::epsilon() / 2; 45 | 46 | static const float E_f = (float) E_d; 47 | static const float Pi_f = (float) Pi_d; 48 | static const float InvPi_f = (float) InvPi_d; 49 | static const float InvTwoPi_f = (float) InvTwoPi_d; 50 | static const float InvFourPi_f = (float) InvFourPi_d; 51 | static const float SqrtPi_f = (float) SqrtPi_d; 52 | static const float InvSqrtPi_f = (float) InvSqrtPi_d; 53 | static const float SqrtTwo_f = (float) SqrtTwo_d; 54 | static const float InvSqrtTwo_f = (float) InvSqrtTwo_d; 55 | static const float SqrtTwoPi_f = (float) SqrtTwoPi_d; 56 | static const float InvSqrtTwoPi_f = (float) InvSqrtTwoPi_d; 57 | static const float Epsilon_f = 1e-4f; 58 | #if !defined(_MSC_VER) 59 | static const float OneMinusEpsilon_f = 0x1.fffffep-1f; 60 | static const float RecipOverflow_f = 0x1p-128f; 61 | #else 62 | static const float OneMinusEpsilon_f = 0.999999940395355225f; 63 | static const float RecipOverflow_f = 2.93873587705571876e-39f; 64 | #endif 65 | static const float MaxFloat_f = std::numeric_limits::max(); 66 | static const float Infinity_f = std::numeric_limits::infinity(); 67 | static const float MachineEpsilon_f = std::numeric_limits::epsilon() / 2; 68 | 69 | static const Float E = (Float) E_d; 70 | static const Float Pi = (Float) Pi_d; 71 | static const Float InvPi = (Float) InvPi_d; 72 | static const Float InvTwoPi = (Float) InvTwoPi_d; 73 | static const Float InvFourPi = (Float) InvFourPi_d; 74 | static const Float SqrtPi = (Float) SqrtPi_d; 75 | static const Float InvSqrtPi = (Float) InvSqrtPi_d; 76 | static const Float SqrtTwo = (Float) SqrtTwo_d; 77 | static const Float InvSqrtTwo = (Float) InvSqrtTwo_d; 78 | static const Float SqrtTwoPi = (Float) SqrtTwoPi_d; 79 | static const Float InvSqrtTwoPi = (Float) InvSqrtTwoPi_d; 80 | static const Float OneMinusEpsilon = sizeof(Float) == sizeof(double) ? 81 | OneMinusEpsilon_d : OneMinusEpsilon_f; 82 | static const Float RecipOverflow = sizeof(Float) == sizeof(double) ? 83 | RecipOverflow_d : RecipOverflow_f; 84 | static const Float Epsilon = sizeof(Float) == sizeof(double) ? 85 | Epsilon_d : Epsilon_f; 86 | static const Float MaxFloat = std::numeric_limits::max(); 87 | static const Float Infinity = std::numeric_limits::infinity(); 88 | static const Float MachineEpsilon = std::numeric_limits::epsilon() / 2; 89 | 90 | //! @} 91 | // ----------------------------------------------------------------------- 92 | 93 | // ----------------------------------------------------------------------- 94 | //! @{ \name "Safe" mathematical functions that avoid domain errors 95 | // ----------------------------------------------------------------------- 96 | 97 | /// Arcsine variant that gracefully handles arguments > 1 due to roundoff errors 98 | template Scalar safe_asin(Scalar value) { 99 | return std::asin(std::min((Scalar) 1, std::max((Scalar) -1, value))); 100 | } 101 | 102 | /// Arccosine variant that gracefully handles arguments > 1 due to roundoff errors 103 | template Scalar safe_acos(Scalar value) { 104 | return std::acos(std::min((Scalar) 1, std::max((Scalar) -1, value))); 105 | } 106 | 107 | /// Square root variant that gracefully handles arguments < 0 due to roundoff errors 108 | template Scalar safe_sqrt(Scalar value) { 109 | return std::sqrt(std::max((Scalar) 0, value)); 110 | } 111 | 112 | //! @} 113 | // ----------------------------------------------------------------------- 114 | 115 | // ----------------------------------------------------------------------- 116 | //! @{ \name Complete and incomplete elliptic integrals 117 | //! Caution: the 'k' factor is squared in the elliptic integral, which 118 | //! differs from the convention of Mathematica's EllipticK etc. 119 | // ----------------------------------------------------------------------- 120 | 121 | /// Complete elliptic integral of the first kind (double precision) 122 | extern double comp_ellint_1(double k); 123 | /// Complete elliptic integral of the second kind (double precision) 124 | extern double comp_ellint_2(double k); 125 | /// Complete elliptic integral of the third kind (double precision) 126 | extern double comp_ellint_3(double k, double nu); 127 | /// Incomplete elliptic integral of the first kind (double precision) 128 | extern double ellint_1(double k, double phi); 129 | /// Incomplete elliptic integral of the second kind (double precision) 130 | extern double ellint_2(double k, double phi); 131 | /// Incomplete elliptic integral of the third kind (double precision) 132 | extern double ellint_3(double k, double nu, double phi); 133 | 134 | /// Complete elliptic integral of the first kind (single precision) 135 | extern float comp_ellint_1(float k); 136 | /// Complete elliptic integral of the second kind (single precision) 137 | extern float comp_ellint_2(float k); 138 | /// Complete elliptic integral of the third kind (single precision) 139 | extern float comp_ellint_3(float k, float nu); 140 | /// Incomplete elliptic integral of the first kind (single precision) 141 | extern float ellint_1(float k, float phi); 142 | /// Incomplete elliptic integral of the second kind (single precision) 143 | extern float ellint_2(float k, float phi); 144 | /// Incomplete elliptic integral of the first kind (single precision) 145 | extern float ellint_3(float k, float nu, float phi); 146 | 147 | //! @} 148 | // ----------------------------------------------------------------------- 149 | 150 | // ----------------------------------------------------------------------- 151 | //! @{ \name Bessel functions 152 | // ----------------------------------------------------------------------- 153 | 154 | /// Exponentially scaled modified Bessel function of the first kind (order 0), double precision 155 | extern double i0e(double x); 156 | /// Exponentially scaled modified Bessel function of the first kind (order 0), single precision 157 | extern float i0e(float x); 158 | 159 | //! @} 160 | // ----------------------------------------------------------------------- 161 | 162 | // ----------------------------------------------------------------------- 163 | //! @{ \name Legendre functions 164 | // ----------------------------------------------------------------------- 165 | 166 | /// Evaluate the l-th Legendre polynomial using recurrence, single precision 167 | extern float legendre_p(int l, float x); 168 | 169 | /// Evaluate the l-th Legendre polynomial using recurrence, double precision 170 | extern double legendre_p(int l, double x); 171 | 172 | /// Evaluate the an associated Legendre polynomial using recurrence, single precision 173 | extern float legendre_p(int l, int m, float x); 174 | 175 | /// Evaluate the an associated Legendre polynomial using recurrence, double precision 176 | extern double legendre_p(int l, int m, double x); 177 | 178 | /// Evaluate the l-th Legendre polynomial and its derivative using recurrence, single precision 179 | extern std::pair legendre_pd(int l, float x); 180 | 181 | /// Evaluate the l-th Legendre polynomial and its derivative using recurrence, double precision 182 | extern std::pair legendre_pd(int l, double x); 183 | 184 | /// Evaluate the function legendre_pd(l+1, x) - legendre_pd(l-1, x), single precision 185 | extern std::pair legendre_pd_diff(int l, float x); 186 | 187 | /// Evaluate the function legendre_pd(l+1, x) - legendre_pd(l-1, x), double precision 188 | extern std::pair legendre_pd_diff(int l, double x); 189 | 190 | //! @} 191 | // ----------------------------------------------------------------------- 192 | 193 | // ----------------------------------------------------------------------- 194 | //! @{ \name Miscellaneous mathematical helper functions 195 | // ----------------------------------------------------------------------- 196 | 197 | /// Simple signum function -- note that it returns the FP sign of the input (and never zero) 198 | template Scalar signum(Scalar value) { 199 | return std::copysign((Scalar) 1, value); 200 | } 201 | 202 | /// Generic range clamping function 203 | template Scalar clamp(Scalar value, Scalar min, Scalar max) { 204 | return (value < min) ? min : 205 | (value > max ? max : value); 206 | } 207 | 208 | /** 209 | * \brief Find an interval in an ordered set 210 | * 211 | * This function is very similar to \c std::upper_bound, but it uses a functor 212 | * rather than an actual array to permit working with procedurally defined 213 | * data. It returns the index \c i such that pred(i) is \c true and pred(i+1) 214 | * is \c false. 215 | * 216 | * This function is primarily used to locate an interval (i, i+1) for linear 217 | * interpolation, hence its name. To avoid issues out of bounds accesses, and 218 | * to deal with predicates that evaluate to \c true or \c false on the entire 219 | * domain, the returned left interval index is clamped to the range [0, 220 | * size-2]. 221 | */ 222 | template 223 | size_t findInterval(size_t size, const Predicate &pred) { 224 | size_t first = 0, len = size; 225 | while (len > 0) { 226 | size_t half = len >> 1, middle = first + half; 227 | if (pred(middle)) { 228 | first = middle + 1; 229 | len -= half + 1; 230 | } else { 231 | len = half; 232 | } 233 | } 234 | return (size_t) clamp((ssize_t) first - 1, 0, size - 2); 235 | } 236 | 237 | /// Quantile function of the standard normal distribution (double precision) 238 | extern double normal_quantile(double p); 239 | 240 | /// Quantile function of the standard normal distribution (single precision) 241 | extern float normal_quantile(float p); 242 | 243 | /// Cumulative distribution function of the standard normal distribution (double precision) 244 | extern double normal_cdf(double p); 245 | 246 | /// Cumulative distribution function of the standard normal distribution (single precision) 247 | extern float normal_cdf(float p); 248 | 249 | /// Error function (double precision) 250 | extern double erf(double p); 251 | 252 | /// Error function (single precision) 253 | extern float erf(float p); 254 | 255 | /// Inverse error function (double precision) 256 | extern double erfinv(double p); 257 | 258 | /// Inverse error function (single precision) 259 | extern float erfinv(float p); 260 | 261 | //! @} 262 | // ----------------------------------------------------------------------- 263 | 264 | NAMESPACE_END(math) 265 | NAMESPACE_END(layer) 266 | -------------------------------------------------------------------------------- /include/layer/microfacet.h: -------------------------------------------------------------------------------- 1 | /* 2 | microfacet.h -- Microfacet BSDF evaluation routines 3 | 4 | Copyright (c) 2015 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | NAMESPACE_BEGIN(layer) 17 | 18 | /** 19 | * \brief Smith's 1D shadowing masking term for the Beckmann microfacet 20 | * distribution 21 | * 22 | * \param v 23 | * Incident direction 24 | * \param m 25 | * Microsurface normal 26 | * \param alpha 27 | * Beckmann roughness parameter 28 | */ 29 | extern Float smithG1(const Vector3 &v, const Vector3 &m, Float alpha); 30 | 31 | /** 32 | * \brief Evaluate the Beckmann distribution-based microfacet BSDF by 33 | * Walter et al. using the mu_i, mu_o, phi_d parameterization 34 | * 35 | * \param mu_i 36 | * Incident zenith angle cosine 37 | * \param mu_o 38 | * Exitant zenith angle cosine 39 | * \param eta 40 | * Relative index of refraction (complex) 41 | * \param alpha 42 | * Beckmann roughness parameter 43 | * \param phi_d 44 | * Azimuthal difference angle 45 | */ 46 | extern Float microfacet(Float mu_o, Float mu_i, std::complex eta, 47 | Float alpha, Float phi_d); 48 | 49 | /** 50 | * \brief Evaluate the Beckmann distribution-based microfacet BSDF by 51 | * Walter et al. using the mu_i, mu_o, phi_d parameterization. This 52 | * version leaves out the exponential term 53 | * 54 | * \param mu_i 55 | * Incident zenith angle cosine 56 | * \param mu_o 57 | * Exitant zenith angle cosine 58 | * \param eta 59 | * Relative index of refraction (complex) 60 | * \param k 61 | * Absorption coefficient 62 | * \param alpha 63 | * Beckmann roughness parameter 64 | * \param phi_d 65 | * Azimuthal difference angle 66 | */ 67 | extern Float microfacetNoExp(Float mu_o, Float mu_i, std::complex eta, 68 | Float alpha, Float phi_d); 69 | 70 | /** 71 | * \brief Return Fourier series coefficients for an exponential 72 | * of a cosine, specifically the expression "exp(A+B*cos(phi))" 73 | * 74 | * \param 75 | * A The 'A' coefficient in above expression 76 | * \param A 77 | * The 'B' coefficient in above expression 78 | * \param relerr 79 | * Relative error goal 80 | */ 81 | extern void expCosFourierSeries(Float A, Float B, Float relerr, 82 | std::vector &coeffs); 83 | 84 | /** 85 | * \brief Compute a Fourier series of the Beckmann-distribution based 86 | * microfacet BSDF by Walter et al. (covers both the dielectric and 87 | * conducting case). This version leaves out the exponential term 88 | * 89 | * \param mu_i 90 | * Incident zenith angle cosine 91 | * \param mu_o 92 | * Exitant zenith angle cosine 93 | * \param eta 94 | * Relative index of refraction (complex) 95 | * \param alpha 96 | * Beckmann roughness parameter 97 | * \param n 98 | * Indicates a desired number of Fourier coefficients (usually 8-12 are 99 | * plenty -- in cases where the function contains high frequencies, it will 100 | * automatically increase n). 101 | * \param phiMax 102 | * The implementation minimizes the fitting error on the interval [0, phiMax]. 103 | * If in doubt, set phiMax = math::Pi 104 | * \param result 105 | * Storage for the generated Fourier coefficients 106 | */ 107 | extern void microfacetNoExpFourierSeries(Float mu_o, Float mu_i, 108 | std::complex eta, Float alpha, 109 | size_t n, Float phiMax, 110 | std::vector &result); 111 | 112 | /** 113 | * \brief Compute a Fourier series of the Beckmann-distribution based 114 | * microfacet BSDF by Walter et al. (covers both the dielectric and 115 | * conducting case) 116 | * 117 | * \param mu_i 118 | * Incident zenith angle cosine 119 | * \param mu_o 120 | * Exitant zenith angle cosine 121 | * \param eta 122 | * Relative index of refraction (complex) 123 | * \param alpha 124 | * Beckmann roughness parameter 125 | * \param n 126 | * Indicates a desired maximum number of Fourier coefficients. The 127 | * implementation will blur out higher Frequency content to try to 128 | * achieve this number. 129 | * \param relerr 130 | * A relative error threshold after which series terms can safely 131 | * be truncated 132 | * \param result 133 | * Storage for the generated Fourier coefficients 134 | */ 135 | extern void microfacetFourierSeries(Float mu_o, Float mu_i, 136 | std::complex eta, Float alpha, size_t n, 137 | Float relerr, std::vector &result); 138 | 139 | NAMESPACE_END(layer) 140 | -------------------------------------------------------------------------------- /include/layer/mmap.h: -------------------------------------------------------------------------------- 1 | /* 2 | mmap.h -- Portable memory mapped file implementation 3 | 4 | Copyright (c) 2015 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | 15 | NAMESPACE_BEGIN(layer) 16 | 17 | /// Basic cross-platform abstraction for memory mapped files 18 | class MemoryMappedFile { 19 | public: 20 | /// Create a new memory-mapped file of the specified size 21 | MemoryMappedFile(const fs::path &filename, size_t size); 22 | 23 | /// Map the specified file into memory 24 | MemoryMappedFile(const fs::path &filename, bool readOnly = true); 25 | 26 | /// Return a pointer to the memory-mapped file contents 27 | void *data(); 28 | 29 | /// Return a pointer to the memory-mapped file contents (const version) 30 | const void *data() const; 31 | 32 | /// Return the size of the mapped region 33 | size_t size() const; 34 | 35 | /** 36 | * \brief Resize the memory-mapped file 37 | * 38 | * This involves remapping the file, which will 39 | * generally change the pointer obtained via data() 40 | */ 41 | void resize(size_t size); 42 | 43 | /// Return the associated filename 44 | const fs::path &filename() const; 45 | 46 | /// Return whether the mapped memory region is read-only 47 | bool readOnly() const; 48 | 49 | /// Return a string representation 50 | std::string toString() const; 51 | 52 | /** 53 | * \brief Create a temporary memory-mapped file 54 | * 55 | * \remark When closing the mapping, the file is 56 | * automatically deleted. 57 | */ 58 | static MemoryMappedFile* createTemporary(size_t size); 59 | 60 | /// Release all resources 61 | virtual ~MemoryMappedFile(); 62 | 63 | protected: 64 | /// Internal constructor 65 | MemoryMappedFile(); 66 | 67 | private: 68 | struct MemoryMappedFilePrivate; 69 | std::unique_ptr d; 70 | }; 71 | 72 | NAMESPACE_END(layer) 73 | -------------------------------------------------------------------------------- /include/layer/quad.h: -------------------------------------------------------------------------------- 1 | /* 2 | quad.h -- Functions for numerical quadrature 3 | 4 | Copyright (c) 2015 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | NAMESPACE_BEGIN(layer) 18 | NAMESPACE_BEGIN(quad) 19 | 20 | /** 21 | * \brief Computes the nodes and weights of a Gauss-Legendre quadrature 22 | * (aka "Gaussian quadrature") rule with the given number of evaluations. 23 | * 24 | * Integration is over the interval \f$[-1, 1]\f$. Gauss-Legendre quadrature 25 | * maximizes the order of exactly integrable polynomials achieves this up to 26 | * degree \f$2n-1\f$ (where \f$n\f$ is the number of function evaluations). 27 | * 28 | * This method is numerically well-behaved until about \f$n=200\f$ 29 | * and then becomes progressively less accurate. It is generally not a 30 | * good idea to go much higher---in any case, a composite or 31 | * adaptive integration scheme will be superior for large \f$n\f$. 32 | * 33 | * \param n 34 | * Desired number of evalution points 35 | * \param[out] nodes 36 | * Length-\c n array used to store the nodes of the quadrature rule 37 | * \param[out] nodes 38 | * Length-\c n array used to store the weights of the quadrature rule 39 | * \remark 40 | * In the Python API, the \c nodes and \c weights field are returned 41 | * as a tuple 42 | */ 43 | template 44 | void gaussLegendre(int n, Scalar *nodes, Scalar *weights) { 45 | if (n-- < 1) 46 | throw std::runtime_error("gaussLegendre(): n must be >= 1"); 47 | 48 | if (n == 0) { 49 | nodes[0] = 0; 50 | weights[0] = 2; 51 | } else if (n == 1) { 52 | nodes[0] = (Scalar) -std::sqrt(1.0 / 3.0); 53 | nodes[1] = -nodes[0]; 54 | weights[0] = weights[1] = 1; 55 | } 56 | 57 | int m = (n+1)/2; 58 | for (int i=0; i 20) 65 | throw std::runtime_error( 66 | "gaussLobatto(" + std::to_string(n) + 67 | "): did not converge after 20 iterations!"); 68 | 69 | /* Search for the interior roots of P_{n+1}(x) using Newton's method. */ 70 | std::pair L = math::legendre_pd(n+1, x); 71 | double step = L.first / L.second; 72 | x -= step; 73 | 74 | if (std::abs(step) <= 4 * std::abs(x) * std::numeric_limits::epsilon()) 75 | break; 76 | } 77 | 78 | std::pair L = math::legendre_pd(n+1, x); 79 | weights[i] = weights[n - i] = 80 | (Scalar)(2 / ((1 - x * x) * (L.second * L.second))); 81 | nodes[i] = (Scalar) x; 82 | nodes[n - i] = (Scalar) -x; 83 | assert(i == 0 || x > nodes[i-1]); 84 | } 85 | 86 | if ((n % 2) == 0) { 87 | std::pair L = math::legendre_pd(n+1, 0.0); 88 | weights[n/2] = (double) (2 / (L.second*L.second)); 89 | nodes[n/2] = 0; 90 | } 91 | } 92 | 93 | /** 94 | * \brief Computes the nodes and weights of a Gauss-Lobatto quadrature 95 | * rule with the given number of evaluations. 96 | * 97 | * Integration is over the interval \f$[-1, 1]\f$. Gauss-Lobatto quadrature 98 | * is preferable to Gauss-Legendre quadrature whenever the endpoints of the 99 | * integration domain should explicitly be included. It maximizes the order 100 | * of exactly integrable polynomials subject to this constraint and achieves 101 | * this up to degree \f$2n-3\f$ (where \f$n\f$ is the number of function 102 | * evaluations). 103 | * 104 | * This method is numerically well-behaved until about \f$n=200\f$ 105 | * and then becomes progressively less accurate. It is generally not a 106 | * good idea to go much higher---in any case, a composite or 107 | * adaptive integration scheme will be superior for large \f$n\f$. 108 | * 109 | * \param n 110 | * Desired number of evalution points 111 | * \param[out] nodes 112 | * Length-\c n array used to store the nodes of the quadrature rule 113 | * \param[out] nodes 114 | * Length-\c n array used to store the weights of the quadrature rule 115 | * \remark 116 | * In the Python API, the \c nodes and \c weights field are returned 117 | * as a tuple 118 | */ 119 | template 120 | void gaussLobatto(int n, Scalar *nodes, Scalar *weights) { 121 | if (n-- < 2) 122 | throw std::runtime_error("gaussLobatto(): n must be >= 1"); 123 | 124 | nodes[0] = -1; 125 | nodes[n] = 1; 126 | weights[0] = weights[n] = 2 / (Scalar) (n * (n+1)); 127 | 128 | int m = (n+1)/2; 129 | for (int i=1; i 20) 138 | throw std::runtime_error( 139 | "gaussLobatto(" + std::to_string(n) + 140 | "): did not converge after 20 iterations!"); 141 | 142 | /* Search for the interior roots of P_n'(x) using Newton's method. The same 143 | roots are also shared by P_{n+1}-P_{n-1}, which is nicer to evaluate. */ 144 | 145 | std::pair Q = math::legendre_pd_diff(n, x); 146 | double step = Q.first / Q.second; 147 | x -= step; 148 | 149 | if (std::abs(step) <= 4 * std::abs(x) * std::numeric_limits::epsilon()) 150 | break; 151 | } 152 | 153 | double Ln = math::legendre_p(n, x); 154 | weights[i] = weights[n - i] = (Scalar) (2 / ((n * (n + 1)) * Ln * Ln)); 155 | nodes[i] = (Scalar) x; 156 | nodes[n - i] = (Scalar) -x; 157 | assert(x > nodes[i-1]); 158 | } 159 | 160 | if ((n % 2) == 0) { 161 | double Ln = math::legendre_p(n, 0.0); 162 | weights[n / 2] = (Scalar) (2 / ((n * (n + 1)) * Ln * Ln)); 163 | nodes[n/2] = 0; 164 | } 165 | } 166 | 167 | /** 168 | * \brief Computes the nodes and weights of a composite Simpson quadrature 169 | * rule with the given number of evaluations. 170 | * 171 | * Integration is over the interval \f$[-1, 1]\f$, which will be split into 172 | * \f$(n-1) / 2\f$ sub-intervals with overlapping endpoints. A 3-point 173 | * Simpson rule is applied per interval, which is exact for polynomials of 174 | * degree three or less. 175 | * 176 | * \param n 177 | * Desired number of evalution points. Must be an odd number bigger than 3. 178 | * \param[out] nodes 179 | * Length-\c n array used to store the nodes of the quadrature rule 180 | * \param[out] nodes 181 | * Length-\c n array used to store the weights of the quadrature rule 182 | * \remark 183 | * In the Python API, the \c nodes and \c weights field are returned 184 | * as a tuple 185 | */ 186 | template 187 | void compositeSimpson(int n, Scalar *nodes, Scalar *weights) { 188 | if (n % 2 != 1 || n < 3) 189 | throw std::runtime_error("compositeSimpson(): n must be >= 3 and odd"); 190 | 191 | n = (n - 1) / 2; 192 | 193 | Scalar h = (Scalar) 2 / (Scalar) (2 * n), 194 | weight = h * (Scalar) (1.0 / 3.0); 195 | 196 | for (int i = 0; i < n; ++i) { 197 | Float x = -1 + h * (2*i); 198 | nodes[2*i] = x; 199 | nodes[2*i+1] = x+h; 200 | weights[2*i] = (i == 0 ? 1 : 2) * weight; 201 | weights[2*i+1] = 4 * weight; 202 | } 203 | 204 | nodes[2*n] = 1; 205 | weights[2*n] = weight; 206 | 207 | } 208 | 209 | /** 210 | * \brief Computes the nodes and weights of a composite Simpson 3/8 quadrature 211 | * rule with the given number of evaluations. 212 | * 213 | * Integration is over the interval \f$[-1, 1]\f$, which will be split into 214 | * \f$(n-1) / 3\f$ sub-intervals with overlapping endpoints. A 4-point 215 | * Simpson rule is applied per interval, which is exact for polynomials of 216 | * degree four or less. 217 | * 218 | * \param n 219 | * Desired number of evalution points. Must be an odd number bigger than 3. 220 | * \param[out] nodes 221 | * Length-\c n array used to store the nodes of the quadrature rule 222 | * \param[out] nodes 223 | * Length-\c n array used to store the weights of the quadrature rule 224 | * \remark 225 | * In the Python API, the \c nodes and \c weights field are returned 226 | * as a tuple 227 | */ 228 | template 229 | void compositeSimpson38(int n, Scalar *nodes, Scalar *weights) { 230 | if ((n-1) % 3 != 0 || n < 4) 231 | throw std::runtime_error("compositeSimpson38(): n-1 must be divisible by 3"); 232 | 233 | n = (n - 1) / 3; 234 | 235 | Scalar h = (Scalar) 2 / (Scalar) (3 * n), 236 | weight = h * (Scalar) (3.0 / 8.0); 237 | 238 | for (int i = 0; i < n; ++i) { 239 | Float x = -1 + h * (3*i); 240 | nodes[3*i] = x; 241 | nodes[3*i+1] = x+h; 242 | nodes[3*i+2] = x+2*h; 243 | weights[3*i] = (i == 0 ? 1 : 2) * weight; 244 | weights[3*i+1] = 3 * weight; 245 | weights[3*i+2] = 3 * weight; 246 | } 247 | 248 | nodes[3*n] = 1; 249 | weights[3*n] = weight; 250 | } 251 | 252 | //! @} 253 | // ----------------------------------------------------------------------- 254 | 255 | NAMESPACE_END(quad) 256 | NAMESPACE_END(layer) 257 | -------------------------------------------------------------------------------- /include/layer/simd.h: -------------------------------------------------------------------------------- 1 | /* 2 | simd.h -- Useful declarations for SIMD optimized code 3 | 4 | Copyright (c) 2015 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | 14 | #if defined(_MSC_VER) 15 | # include 16 | #else 17 | # include 18 | #endif 19 | 20 | #define _mm256_set_ss(value) _mm256_insertf128_ps(_mm256_setzero_ps(), _mm_set_ss(value), 0) 21 | #define _mm256_set_sd(value) _mm256_insertf128_pd(_mm256_setzero_pd(), _mm_set_sd(value), 0) 22 | #define _mm256_splat0_pd(value) _mm256_permute2f128_pd(_mm256_shuffle_pd(value, value, 0x0), value, 0x00) 23 | #define _mm256_splat1_pd(value) _mm256_permute2f128_pd(_mm256_shuffle_pd(value, value, 0x3), value, 0x00) 24 | #define _mm256_splat2_pd(value) _mm256_permute2f128_pd(_mm256_shuffle_pd(value, value, 0x0), value, 0x11) 25 | #define _mm256_splat3_pd(value) _mm256_permute2f128_pd(_mm256_shuffle_pd(value, value, 0xC), value, 0x11) 26 | 27 | #if !defined(MM_ALIGN16) 28 | # if defined(__GNUC__) 29 | # define MM_ALIGN16 __attribute__ ((aligned (16))) 30 | # else 31 | # define MM_ALIGN16 __declspec(align(16)) 32 | # endif 33 | #endif 34 | 35 | #if !defined(MM_ALIGN32) 36 | # if defined(__GNUC__) 37 | # define MM_ALIGN32 __attribute__ ((aligned (32))) 38 | # else 39 | # define MM_ALIGN32 __declspec(align(32)) 40 | # endif 41 | #endif 42 | 43 | NAMESPACE_BEGIN(layer) 44 | NAMESPACE_BEGIN(simd) 45 | 46 | /// Allocate an aligned region of memory 47 | void * malloc(size_t size); 48 | 49 | /// Free an aligned region of memory 50 | void free(void *ptr); 51 | 52 | #if defined(__AVX__) 53 | /// Perform four simultaneous double precision horizontal additions using AVX 54 | inline void hadd(__m256d a, __m256d b, __m256d c, __m256d d, double *target) { 55 | /* See http://stackoverflow.com/questions/10833234/4-horizontal-double-precision-sums-in-one-go-with-avx */ 56 | __m256d sumab = _mm256_hadd_pd(a, b); 57 | __m256d sumcd = _mm256_hadd_pd(c, d); 58 | __m256d blend = _mm256_blend_pd(sumab, sumcd, 0x0C); 59 | __m256d perm = _mm256_permute2f128_pd(sumab, sumcd, 0x21); 60 | __m256d result = _mm256_add_pd(perm, blend); 61 | _mm256_store_pd(target, result); 62 | } 63 | 64 | /// Perform two simultaneous double precision horizontal additions using AVX 65 | inline void hadd(__m256d a, __m256d b, double *target) { 66 | /* See http://stackoverflow.com/questions/9775538/fastest-way-to-do-horizontal-vector-sum-with-avx-instructions */ 67 | __m256d sum = _mm256_hadd_pd(a, b); 68 | __m128d sum_high = _mm256_extractf128_pd(sum, 1); 69 | __m128d result = _mm_add_pd(sum_high, _mm256_castpd256_pd128(sum)); 70 | _mm_store_pd(target, result); 71 | } 72 | 73 | /// Perform a double precision horizontal addition using AVX 74 | inline double hadd(__m256d x) { 75 | double MM_ALIGN16 result[2]; 76 | hadd(x, x, result); 77 | return result[0]; 78 | } 79 | #endif 80 | 81 | NAMESPACE_END(simd) 82 | NAMESPACE_END(layer) 83 | -------------------------------------------------------------------------------- /include/layer/storage.h: -------------------------------------------------------------------------------- 1 | /* 2 | layer.h -- Sparse data structure for storing layers 3 | 4 | Copyright (c) 2015 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #if defined(_MSC_VER) 18 | #pragma warning(disable: 4200) // warning C4200 : nonstandard extension used : zero - sized array in struct / union 19 | #endif 20 | 21 | NAMESPACE_BEGIN(layer) 22 | 23 | /* 24 | * \brief Storage class for isotropic BSDFs 25 | * 26 | * This class implements sparse storage support for isotropic BSDFs which are 27 | * point-sampled as a function of the incident and exitant zenith angles and 28 | * expanded into Fourier coefficients as a function of the azimuthal difference 29 | * angle. 30 | */ 31 | class BSDFStorage { 32 | public: 33 | typedef uint32_t OffsetType; 34 | 35 | /// Map an existing BSDF storage file into memory 36 | BSDFStorage(const fs::path &filename, bool readOnly = true); 37 | 38 | /// Return the number of Fourier coefficients 39 | size_t maxOrder() const { return (size_t) m_header->nMaxOrder; } 40 | 41 | /// Return the number of color channels 42 | size_t channelCount() const { return (size_t) m_header->nChannels; } 43 | 44 | /// Return the resolution of the discretization in \mu_i and \mu_o 45 | size_t nodeCount() const { return (size_t) m_header->nNodes; } 46 | 47 | /// Return the number of basis functions stored in this file (usually just 1) 48 | size_t basisCount() const { return (size_t) m_header->nBases; } 49 | 50 | /// Return the number of model parameters 51 | size_t parameterCount() const { return (size_t) m_header->nParameters; } 52 | 53 | /// Return the number of samples associated with parameter \c i 54 | size_t parameterSampleCount(size_t i) const { return (size_t) m_paramSampleCounts[i]; } 55 | 56 | /// Return the sample positions associated with parameter \c i 57 | const float *parameterSamplePositions(size_t i) const { return m_paramSamplePositionsNested[i]; } 58 | 59 | /// Does this file store coefficients for the harmonic extrapolation-based model? 60 | bool extrapolated() const; 61 | 62 | /// Return the size of the underlying representation in bytes 63 | size_t size() const; 64 | 65 | /// Return metadata attached to the BSDF file (if any) 66 | const std::string &metadata() const { return m_metadata; } 67 | 68 | /// Return the relative index of refraction 69 | Float eta() const { return (Float) m_header->eta; } 70 | 71 | /// Set the relative index of refraction 72 | void setEta(Float eta) { m_header->eta = (float) eta; } 73 | 74 | /// Return the Beckmann-equivalent roughness (0: bottom, 1: top surface) 75 | Float alpha(int index) const { assert(index >= 0 && index <= 1); return (Float) m_header->alpha[index]; } 76 | 77 | /// Return the Beckmann-equivalent roughness (0: bottom, 1: top surface) 78 | void setAlpha(int index, Float alpha) { assert(index >= 0 && index <= 1); m_header->alpha[index] = (float) alpha; } 79 | 80 | /// Return the nodes of the underlying discretization in \mu_i and \mu_o 81 | const float *getNodes() const { return m_nodes; } 82 | 83 | /// Return a pointer to the coefficients of the CDF associated with the incident angle \c i 84 | float *cdf(size_t i) { return m_cdfMu + i*nodeCount()*basisCount(); } 85 | 86 | /// Return a pointer to the coefficients of the CDF associated with the incident angle \c i 87 | const float *cdf(int i) const { return m_cdfMu + i*nodeCount()*basisCount(); } 88 | 89 | /// Evaluate the model for the given values of \mu_i, \mu_o, and \phi_d 90 | Color3 eval(Float mu_i, Float mu_o, Float phi_d, const float *basisCoeffs = NULL) const; 91 | 92 | /// Evaluate the model for the given values of \mu_i, \mu_o, and \phi_d 93 | Float pdf(Float mu_i, Float mu_o, Float phi_d, const float *basisCoeffs = NULL) const; 94 | 95 | /// Importance sample the model 96 | Color3 sample(Float mu_i, Float &mu_o, Float &phi_d, Float &pdf, 97 | const Point2 &sample, 98 | const float *basisCoeffs = NULL) const; 99 | 100 | /// For debugging: return a Fourier series for the given parameters 101 | void interpolateSeries(Float mu_i, Float mu_o, int basis, int channel, float *coeffs) const; 102 | 103 | /// Forcefully release all resources 104 | void close() { delete m_mmap; m_mmap = NULL; m_header = NULL; m_coeffs = NULL; m_cdfMu = NULL; m_nodes = NULL; } 105 | 106 | /// Return a string representation 107 | std::string toString() const; 108 | 109 | /// Create a BSDF storage file from a Layer data structure (monochromatic) 110 | static BSDFStorage *fromLayer(const fs::path &filename, const Layer *layer, 111 | bool extrapolate = false, const std::string &metadata = "") { 112 | const Layer *layers[1] = { layer }; 113 | return BSDFStorage::fromLayerGeneral(filename, layers, 1, 1, 0, NULL, NULL, extrapolate, metadata); 114 | } 115 | 116 | /// Create a BSDF storage file from three Layer data structures (RGB) 117 | static BSDFStorage *fromLayerRGB(const fs::path &filename, const Layer *layerR, 118 | const Layer *layerG, const Layer *layerB, bool extrapolate = false, const std::string &metadata = "") { 119 | const Layer *layers[3] = { layerR, layerG, layerB }; 120 | return BSDFStorage::fromLayerGeneral(filename, layers, 3, 1, 0, NULL, NULL, extrapolate, metadata); 121 | } 122 | 123 | /// Create a BSDF storage file from three Layer data structures (most general interface) 124 | static BSDFStorage *fromLayerGeneral(const fs::path &filename, 125 | const Layer **layers, size_t nChannels, size_t nBases = 1, size_t nParameters = 0, 126 | const size_t *paramSampleCounts = NULL, const float **paramSamplePositions = NULL, 127 | bool extrapolate = false, const std::string &metadata = ""); 128 | 129 | std::string stats() const; 130 | 131 | /// Virtual destructor 132 | virtual ~BSDFStorage(); 133 | protected: 134 | struct Header { 135 | uint8_t identifier[7]; // Set to 'SCATFUN' 136 | uint8_t version; // Currently version is 1 137 | uint32_t flags; // 0x01: file contains a BSDF, 0x02: uses harmonic extrapolation 138 | uint32_t nNodes; // Number of samples in the elevational discretization 139 | 140 | uint32_t nCoeffs; // Total number of Fourier series coefficients stored in the file 141 | uint32_t nMaxOrder; // Coeff. count for the longest series occuring in the file 142 | uint32_t nChannels; // Number of color channels (usually 1 or 3) 143 | uint32_t nBases; // Number of BSDF basis functions (relevant for texturing) 144 | 145 | uint32_t nMetadataBytes; // Size of descriptive metadata that follows the BSDF data 146 | uint32_t nParameters; // Number of textured material parameters 147 | uint32_t nParameterValues; // Total number of BSDF samples for all textured parameters 148 | float eta; // Relative IOR through the material (eta(bottom) / eta(top)) 149 | 150 | float alpha[2]; // Beckmann-equiv. roughness on the top (0) and bottom (1) side 151 | float unused[2]; // Unused fields to pad the header to 64 bytes 152 | 153 | float data[0]; // BSDF data starts here 154 | }; 155 | 156 | /// Create a new BSDF storage file for the given amount of coefficients etc 157 | BSDFStorage(const fs::path &filename, size_t nNodes, size_t nChannels, 158 | size_t nMaxOrder, size_t nCoeffs, size_t nBases = 1, 159 | size_t nParameters = 0, const size_t *paramSampleCounts = NULL, 160 | const float **paramSamplePositions = NULL, bool extrapolate = false, 161 | const std::string &metadata = ""); 162 | 163 | /// Return a posize_ter to the underlying sparse offset table 164 | OffsetType *offsetTable(size_t o = 0, size_t i = 0) 165 | { return m_offsetTable + 2*(o + i * nodeCount()); } 166 | 167 | /// Return a posize_ter to the underlying sparse offset table (const version) 168 | const OffsetType *offsetTable(size_t o = 0, size_t i = 0) const 169 | { return m_offsetTable + 2*(o + i * nodeCount()); } 170 | 171 | /// Return the sparse data offset of the given incident and exitant angle pair 172 | const float *coeff(size_t o, size_t i) const { return m_coeffs + offsetTable(o, i)[0]; } 173 | 174 | /// Return the sparse data offset of the given incident and exitant angle pair 175 | float *coeff(size_t o, size_t i) { return m_coeffs + offsetTable(o, i)[0]; } 176 | 177 | /// Return the sparse data offset and size of the given incident and exitant angle pair 178 | const float *coeff(size_t o, size_t i, size_t basis, size_t channel) const { 179 | const OffsetType *offsetPtr = offsetTable(o, i); 180 | OffsetType offset = offsetPtr[0], size = offsetPtr[1]; 181 | return m_coeffs + offset + basis * size + basisCount()*size*channel; 182 | } 183 | 184 | /// Return the sparse data offset and size of the given incident and exitant angle pair 185 | float *coeff(size_t o, size_t i, size_t basis, size_t channel) { 186 | const OffsetType *offsetPtr = offsetTable(o, i); 187 | OffsetType offset = offsetPtr[0], size = offsetPtr[1]; 188 | return m_coeffs + offset + basis * size + basisCount()*size*channel; 189 | } 190 | 191 | /// Return the sparse data size of the given incident and exitant angle pair 192 | OffsetType coeffCount(size_t o, size_t i) const { 193 | return offsetTable(o, i)[1]; 194 | } 195 | 196 | /// Return the sparse data offset and size of the given incident and exitant angle pair 197 | std::pair coeffAndCount(size_t o, size_t i) const { 198 | const OffsetType *offset = offsetTable(o, i); 199 | return std::make_pair(m_coeffs + offset[0], offset[1]); 200 | } 201 | 202 | /// Return the sparse data offset and size of the given incident and exitant angle pair 203 | std::pair coeffAndCount(size_t o, size_t i) { 204 | const OffsetType *offset = offsetTable(o, i); 205 | return std::make_pair(m_coeffs + offset[0], offset[1]); 206 | } 207 | 208 | /// Return the sparse data offset and size of the given incident and exitant angle pair 209 | std::pair coeffAndCount(size_t o, size_t i, size_t basis) const { 210 | const OffsetType *offsetPtr = offsetTable(o, i); 211 | OffsetType offset = offsetPtr[0], size = offsetPtr[1]; 212 | return std::make_pair(m_coeffs + offset + basis * size, size); 213 | } 214 | 215 | /// Return the sparse data offset and size of the given incident and exitant angle pair 216 | std::pair coeffAndCount(size_t o, size_t i, size_t basis) { 217 | const OffsetType *offsetPtr = offsetTable(o, i); 218 | OffsetType offset = offsetPtr[0], size = offsetPtr[1]; 219 | return std::make_pair(m_coeffs + offset + basis * size, size); 220 | } 221 | 222 | /// Return the sparse data offset and size of the given incident and exitant angle pair 223 | std::pair coeffAndCount(size_t o, size_t i, size_t basis, size_t channel) const { 224 | const OffsetType *offsetPtr = offsetTable(o, i); 225 | OffsetType offset = offsetPtr[0], size = offsetPtr[1]; 226 | return std::make_pair(m_coeffs + offset + basis * size + basisCount()*size*channel, size); 227 | } 228 | 229 | /// Return the sparse data offset and size of the given incident and exitant angle pair 230 | std::pair coeffAndCount(size_t o, size_t i, size_t basis, size_t channel) { 231 | const OffsetType *offsetPtr = offsetTable(o, i); 232 | OffsetType offset = offsetPtr[0], size = offsetPtr[1]; 233 | return std::make_pair(m_coeffs + offset + basis * size + basisCount()*size*channel, size); 234 | } 235 | 236 | /// Evaluate the discrete CDF that is used to sample a zenith angle spline segment 237 | float evalLatitudinalCDF(size_t knotOffset, float *knotWeights, size_t index, const float *basisCoeffs) const { 238 | const size_t n = nodeCount(), m = basisCount(); 239 | const float *cdf = m_cdfMu + (knotOffset*n + index) * m; 240 | float result = 0; 241 | 242 | for (size_t i=0; i<4; ++i) { 243 | float weight = knotWeights[i]; 244 | if (weight != 0) 245 | for (size_t basis=0; basis coeffAndCount = 260 | this->coeffAndCount(index, knotOffset + i); 261 | const OffsetType count = coeffAndCount.second; 262 | if (!count) 263 | continue; 264 | 265 | const float *coeff = coeffAndCount.first; 266 | for (size_t basis=0; basis 8 | 9 | All rights reserved. Use of this source code is governed by a 10 | BSD-style license that can be found in the LICENSE file. 11 | */ 12 | 13 | #pragma once 14 | 15 | #include 16 | #include 17 | #include // for cross products 18 | 19 | NAMESPACE_BEGIN(layer) 20 | 21 | /* Dynamic vectors */ 22 | typedef Eigen::Matrix VectorX; 23 | typedef Eigen::Matrix VectorXf; 24 | typedef Eigen::Matrix VectorXd; 25 | 26 | /* Dynamic matrices */ 27 | typedef Eigen::Matrix MatrixX; 28 | typedef Eigen::Matrix MatrixXf; 29 | typedef Eigen::Matrix MatrixXd; 30 | 31 | /// Generic N-dimensional vector data structure based on Eigen::Matrix 32 | template struct TVector : public Eigen::Matrix { 33 | public: 34 | enum { 35 | Dimension = _Dimension 36 | }; 37 | 38 | typedef TVector VectorType; 39 | typedef TPoint PointType; 40 | typedef Eigen::Matrix Base; 41 | 42 | /// Create a new vector with constant component values 43 | TVector(Scalar value = (Scalar) 0) { Base::setConstant(value); } 44 | 45 | /// Create a new 2D vector (type error if \c Dimension != 2) 46 | TVector(Scalar x, Scalar y) : Base(x, y) { } 47 | 48 | /// Create a new 3D vector (type error if \c Dimension != 3) 49 | TVector(Scalar x, Scalar y, Scalar z) : Base(x, y, z) { } 50 | 51 | /// Create a new 4D vector (type error if \c Dimension != 4) 52 | TVector(Scalar x, Scalar y, Scalar z, Scalar w) : Base(x, y, z, w) { } 53 | 54 | /// Dummy constructor 55 | TVector(ssize_t rows, ssize_t cols) { assert(rows * cols == Dimension); (void) rows; (void) cols; } 56 | 57 | /// Construct a vector from a dense Eigen expression template 58 | template TVector(const Eigen::DenseBase& p) 59 | : Base(p) { } 60 | 61 | /// Assign a vector from a dense Eigen expression template 62 | template TVector &operator=(const Eigen::DenseBase& p) { 63 | this->Base::operator=(p); return *this; 64 | } 65 | 66 | /// Stream operator 67 | friend std::ostream& operator<<(std::ostream& os, const TVector& v) { 68 | os << v.transpose(); return os; 69 | } 70 | }; 71 | 72 | /// Generic N-dimensional point data structure based on Eigen::Matrix 73 | template struct TPoint : public Eigen::Matrix { 74 | public: 75 | enum { 76 | Dimension = _Dimension 77 | }; 78 | 79 | typedef TVector VectorType; 80 | typedef TPoint PointType; 81 | typedef Eigen::Matrix Base; 82 | 83 | /// Create a new point with constant component values 84 | TPoint(Scalar value = (Scalar) 0) { Base::setConstant(value); } 85 | 86 | /// Create a new 2D point (type error if \c Dimension != 2) 87 | TPoint(Scalar x, Scalar y) : Base(x, y) { } 88 | 89 | /// Create a new 3D point (type error if \c Dimension != 3) 90 | TPoint(Scalar x, Scalar y, Scalar z) : Base(x, y, z) { } 91 | 92 | /// Create a new 4D point (type error if \c Dimension != 4) 93 | TPoint(Scalar x, Scalar y, Scalar z, Scalar w) : Base(x, y, z, w) { } 94 | 95 | /// Construct a point from a dense Eigen expression template 96 | template TPoint(const Eigen::DenseBase& p) 97 | : Base(p) { } 98 | 99 | /// Assign a point from a dense Eigen expression template 100 | template TPoint &operator=(const Eigen::DenseBase& p) { 101 | this->Base::operator=(p); return *this; 102 | } 103 | 104 | /// Stream operator 105 | friend std::ostream& operator<<(std::ostream& os, const TPoint& v) { 106 | os << v.transpose(); return os; 107 | } 108 | }; 109 | 110 | /// 3-dimensional surface normal representation 111 | template struct TNormal3 : public TVector { 112 | public: 113 | enum { 114 | Dimension = 3 115 | }; 116 | 117 | typedef TVector VectorType; 118 | typedef TPoint PointType; 119 | typedef VectorType Base; 120 | 121 | /// Create a new normal with constant component values 122 | TNormal3(Scalar value = (Scalar) 0) { Base::setConstant(value); } 123 | 124 | /// Create a new 3D normal 125 | TNormal3(Scalar x, Scalar y, Scalar z) : Base(x, y, z) { } 126 | 127 | /// Construct a normal from a dense Eigen expression template 128 | template TNormal3(const Eigen::DenseBase& p) 129 | : Base(p) { } 130 | 131 | /// Assign a normal from a dense Eigen expression template 132 | template TNormal3 &operator=(const Eigen::DenseBase& p) { 133 | this->Base::operator=(p); 134 | return *this; 135 | } 136 | 137 | /// Stream operator 138 | friend std::ostream& operator<<(std::ostream& os, const TNormal3& v) { 139 | os << v.transpose(); return os; 140 | } 141 | }; 142 | 143 | /** 144 | * \brief Given the unit vector n, find an orthonormal basis {s, t, n} 145 | * 146 | * Based on 147 | * "Building an Orthonormal Basis from a 3D Unit Vector Without Normalization" 148 | * by Jeppe Revall Frisvad 149 | * in "Journal of Graphics Tools" 16(3), pp. 151-159, August 2012. 150 | */ 151 | template 152 | void coordinateSystem(const TVector &n, TVector &s, 153 | TVector &t) { 154 | if (n.z() < (Scalar) -0.9999999) { // Handle the singularity 155 | s = TVector(0, -1, 0); 156 | t = TVector(-1, 0, 0); 157 | return; 158 | } 159 | const Scalar a = (Scalar) 1 / ((Scalar) 1 + n.z()); 160 | const Scalar b = -n.x() * n.y() * a; 161 | s = TVector((Scalar) 1 - n.x() * n.x() * a, b, -n.x()); 162 | t = TVector(b, (Scalar) 1 - n.y() * n.y() * a, -n.y()); 163 | } 164 | 165 | NAMESPACE_END(layer) 166 | 167 | 168 | NAMESPACE_BEGIN(Eigen) 169 | NAMESPACE_BEGIN(internal) 170 | /* Inform Eigen expression template system how to deal with these new types */ 171 | 172 | template 173 | struct traits> 174 | : public Eigen::internal::traits> {}; 175 | 176 | template 177 | struct evaluator> 178 | : public Eigen::internal::evaluator> {}; 179 | 180 | template 181 | struct traits> 182 | : public Eigen::internal::traits> {}; 183 | 184 | template 185 | struct evaluator> 186 | : public Eigen::internal::evaluator> {}; 187 | 188 | NAMESPACE_END(internal) 189 | NAMESPACE_END(Eigen) 190 | -------------------------------------------------------------------------------- /recipes/coated-diffuse.py: -------------------------------------------------------------------------------- 1 | # Creates a diffuse layer with a rough dielectric coating 2 | 3 | import sys 4 | sys.path.append('.') 5 | 6 | import layerlab as ll 7 | 8 | albedo = [0.5, 0.5, 0.6] 9 | eta = 1.5 10 | alpha = 0.02 # Beckmann roughness 11 | 12 | # Construct quadrature scheme suitable for the material 13 | n, m = ll.parameterHeuristicMicrofacet(eta=eta, alpha=alpha) 14 | mu, w = ll.quad.gaussLobatto(n) 15 | print("# of nodes = %i, fourier orders = %i" % (n, m)) 16 | 17 | # Construct coating layer 18 | print("Creating coating layer") 19 | coating = ll.Layer(mu, w, m) 20 | coating.setMicrofacet(eta=eta, alpha=alpha) 21 | 22 | output = [] 23 | for channel in range(3): 24 | # Construct diffuse bottom layer for each channel 25 | print("Creating diffuse layer") 26 | l = ll.Layer(mu, w, m) 27 | l.setDiffuse(albedo[channel]) 28 | 29 | # Apply coating 30 | print("Applying coating..") 31 | l.addToTop(coating) 32 | output.append(l) 33 | 34 | # .. and write to disk 35 | print("Writing to disk..") 36 | storage = ll.BSDFStorage.fromLayerRGB("output.bsdf", *output) 37 | storage.close() 38 | -------------------------------------------------------------------------------- /recipes/coated-gold-with-scatmedium.py: -------------------------------------------------------------------------------- 1 | # Creates a rough gold layer with a rough dielectric coating containing an 2 | # anisotropic scattering medium 3 | 4 | import sys 5 | sys.path.append('.') 6 | 7 | from utils.materials import gold 8 | from utils.cie import get_rgb 9 | 10 | import layerlab as ll 11 | 12 | eta_top = 1.5 13 | 14 | # This step integrates the spectral IOR against the CIE XYZ curves to obtain 15 | # equivalent sRGB values. This may seem fairly approximate but turns out to 16 | # yield excellent agreement with spectral reference renders 17 | print('Computing gold IOR parameters') 18 | eta_bot = get_rgb(gold) 19 | 20 | alpha_top = 0.1 # Beckmann roughness of top layer (coating) 21 | alpha_bot = 0.1 # Beckmann roughness of bottom layer (gold) 22 | 23 | # Medium parameters 24 | g = 0.5 # Scattering anisotropy 25 | albedo = [0.25, 0.0, 0.95] # Single scattering albedo 26 | tau = 0.5 # Optical depth 27 | 28 | # Construct quadrature scheme suitable for the material 29 | n_top, m_top = ll.parameterHeuristicMicrofacet(eta=eta_top, alpha=alpha_top) 30 | n_bot, m_bot = ll.parameterHeuristicMicrofacet(eta=eta_bot[0], alpha=alpha_bot) 31 | n_med, m_med = ll.parameterHeuristicHG(g=g) 32 | 33 | n = max(n_top, n_bot) # Max of zenith angle discretization 34 | m = m_top # Number of Fourier orders determined by top layer 35 | mu, w = ll.quad.gaussLobatto(n) 36 | print("# of nodes = %i, fourier orders = %i" % (n, m)) 37 | 38 | # Construct coating layer 39 | print("Creating coating layer") 40 | coating = ll.Layer(mu, w, m) 41 | coating.setMicrofacet(eta=eta_top, alpha=alpha_top) 42 | 43 | output = [] 44 | for channel in range(3): 45 | # Construct diffuse bottom layer for each channel 46 | print("Creating metal layer") 47 | l = ll.Layer(mu, w, m) 48 | l.setMicrofacet(eta=eta_bot[channel], alpha=alpha_bot) 49 | 50 | # Construct medium layer 51 | print("Creating medium layer") 52 | l2 = ll.Layer(mu, w, m) 53 | l2.setHenyeyGreenstein(g=g, albedo=albedo[channel]) 54 | l2.expand(tau) 55 | 56 | # Apply medium layer 57 | print("Applying medium ..") 58 | l.addToTop(l2) 59 | 60 | # Apply coating 61 | print("Applying coating..") 62 | l.addToTop(coating) 63 | output.append(l) 64 | 65 | # .. and write to disk 66 | print("Writing to disk..") 67 | storage = ll.BSDFStorage.fromLayerRGB("output.bsdf", *output) 68 | storage.close() 69 | -------------------------------------------------------------------------------- /recipes/coated-gold.py: -------------------------------------------------------------------------------- 1 | # Creates a rough gold layer with a rough dielectric coating 2 | 3 | import sys 4 | sys.path.append('.') 5 | 6 | from utils.materials import gold 7 | from utils.cie import get_rgb 8 | 9 | import layerlab as ll 10 | 11 | eta_top = 1.5 12 | 13 | # This step integrates the spectral IOR against the CIE XYZ curves to obtain 14 | # equivalent sRGB values. This may seem fairly approximate but turns out to 15 | # yield excellent agreement with spectral reference renders 16 | print('Computing gold IOR parameters') 17 | eta_bot = get_rgb(gold) 18 | 19 | alpha_top = 0.1 # Beckmann roughness of top layer (coating) 20 | alpha_bot = 0.1 # Beckmann roughness of bottom layer (gold) 21 | 22 | # Construct quadrature scheme suitable for the material 23 | n_top, m_top = ll.parameterHeuristicMicrofacet(eta=eta_top, alpha=alpha_top) 24 | n_bot, m_bot = ll.parameterHeuristicMicrofacet(eta=eta_bot[0], alpha=alpha_bot) 25 | n = max(n_top, n_bot) # Max of zenith angle discretization 26 | m = m_top # Number of Fourier orders determined by top layer 27 | mu, w = ll.quad.gaussLobatto(n) 28 | print("# of nodes = %i, fourier orders = %i" % (n, m)) 29 | 30 | # Construct coating layer 31 | print("Creating coating layer") 32 | coating = ll.Layer(mu, w, m) 33 | coating.setMicrofacet(eta=eta_top, alpha=alpha_top) 34 | 35 | output = [] 36 | for channel in range(3): 37 | # Construct diffuse bottom layer for each channel 38 | print("Creating metal layer") 39 | l = ll.Layer(mu, w, m) 40 | l.setMicrofacet(eta=eta_bot[channel], alpha=alpha_bot) 41 | 42 | # Apply coating 43 | print("Applying coating..") 44 | l.addToTop(coating) 45 | output.append(l) 46 | 47 | # .. and write to disk 48 | print("Writing to disk..") 49 | storage = ll.BSDFStorage.fromLayerRGB("output.bsdf", *output) 50 | storage.close() 51 | -------------------------------------------------------------------------------- /recipes/extract.py: -------------------------------------------------------------------------------- 1 | import jsonpickle 2 | name = "Cr" 3 | l1 = open("%s.eta.spd" % name).readlines() 4 | l2 = open("%s.k.spd" % name).readlines() 5 | 6 | values = [] 7 | wavelengths = [] 8 | 9 | for i in range(len(l1)): 10 | if l1[i].startswith('#'): 11 | continue 12 | lambda1, x1 = l1[i].strip().split() 13 | lambda2, x2 = l2[i].strip().split() 14 | if lambda1 != lambda2: 15 | raise Exception("internal error") 16 | values.append(complex(x1) + complex(x2)*1j) 17 | wavelengths.append(float(lambda1)) 18 | print(wavelengths) 19 | print(values) 20 | -------------------------------------------------------------------------------- /recipes/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjakob/layerlab/3e5257e3076a7287d1da9bbd4ee3f05fe37d3ee3/recipes/utils/__init__.py -------------------------------------------------------------------------------- /recipes/utils/cie.py: -------------------------------------------------------------------------------- 1 | # Helper functions for converting spectral power distributions to linearized sRGB values 2 | 3 | from scipy.integrate import quad 4 | from scipy import interpolate 5 | 6 | CIE_wavelengths = range(360, 830+1) 7 | 8 | CIE_X = interpolate.interp1d(CIE_wavelengths, [ 9 | 0.0001299000, 0.0001458470, 0.0001638021, 0.0001840037, 0.0002066902, 10 | 0.0002321000, 0.0002607280, 0.0002930750, 0.0003293880, 0.0003699140, 11 | 0.0004149000, 0.0004641587, 0.0005189860, 0.0005818540, 0.0006552347, 12 | 0.0007416000, 0.0008450296, 0.0009645268, 0.001094949, 0.001231154, 13 | 0.001368000, 0.001502050, 0.001642328, 0.001802382, 0.001995757, 0.002236000, 14 | 0.002535385, 0.002892603, 0.003300829, 0.003753236, 0.004243000, 0.004762389, 15 | 0.005330048, 0.005978712, 0.006741117, 0.007650000, 0.008751373, 0.01002888, 16 | 0.01142170, 0.01286901, 0.01431000, 0.01570443, 0.01714744, 0.01878122, 17 | 0.02074801, 0.02319000, 0.02620736, 0.02978248, 0.03388092, 0.03846824, 18 | 0.04351000, 0.04899560, 0.05502260, 0.06171880, 0.06921200, 0.07763000, 19 | 0.08695811, 0.09717672, 0.1084063, 0.1207672, 0.1343800, 0.1493582, 0.1653957, 20 | 0.1819831, 0.1986110, 0.2147700, 0.2301868, 0.2448797, 0.2587773, 0.2718079, 21 | 0.2839000, 0.2949438, 0.3048965, 0.3137873, 0.3216454, 0.3285000, 0.3343513, 22 | 0.3392101, 0.3431213, 0.3461296, 0.3482800, 0.3495999, 0.3501474, 0.3500130, 23 | 0.3492870, 0.3480600, 0.3463733, 0.3442624, 0.3418088, 0.3390941, 0.3362000, 24 | 0.3331977, 0.3300411, 0.3266357, 0.3228868, 0.3187000, 0.3140251, 0.3088840, 25 | 0.3032904, 0.2972579, 0.2908000, 0.2839701, 0.2767214, 0.2689178, 0.2604227, 26 | 0.2511000, 0.2408475, 0.2298512, 0.2184072, 0.2068115, 0.1953600, 0.1842136, 27 | 0.1733273, 0.1626881, 0.1522833, 0.1421000, 0.1321786, 0.1225696, 0.1132752, 28 | 0.1042979, 0.09564000, 0.08729955, 0.07930804, 0.07171776, 0.06458099, 29 | 0.05795001, 0.05186211, 0.04628152, 0.04115088, 0.03641283, 0.03201000, 30 | 0.02791720, 0.02414440, 0.02068700, 0.01754040, 0.01470000, 0.01216179, 31 | 0.009919960, 0.007967240, 0.006296346, 0.004900000, 0.003777173, 0.002945320, 32 | 0.002424880, 0.002236293, 0.002400000, 0.002925520, 0.003836560, 0.005174840, 33 | 0.006982080, 0.009300000, 0.01214949, 0.01553588, 0.01947752, 0.02399277, 34 | 0.02910000, 0.03481485, 0.04112016, 0.04798504, 0.05537861, 0.06327000, 35 | 0.07163501, 0.08046224, 0.08973996, 0.09945645, 0.1096000, 0.1201674, 36 | 0.1311145, 0.1423679, 0.1538542, 0.1655000, 0.1772571, 0.1891400, 0.2011694, 37 | 0.2133658, 0.2257499, 0.2383209, 0.2510668, 0.2639922, 0.2771017, 0.2904000, 38 | 0.3038912, 0.3175726, 0.3314384, 0.3454828, 0.3597000, 0.3740839, 0.3886396, 39 | 0.4033784, 0.4183115, 0.4334499, 0.4487953, 0.4643360, 0.4800640, 0.4959713, 40 | 0.5120501, 0.5282959, 0.5446916, 0.5612094, 0.5778215, 0.5945000, 0.6112209, 41 | 0.6279758, 0.6447602, 0.6615697, 0.6784000, 0.6952392, 0.7120586, 0.7288284, 42 | 0.7455188, 0.7621000, 0.7785432, 0.7948256, 0.8109264, 0.8268248, 0.8425000, 43 | 0.8579325, 0.8730816, 0.8878944, 0.9023181, 0.9163000, 0.9297995, 0.9427984, 44 | 0.9552776, 0.9672179, 0.9786000, 0.9893856, 0.9995488, 1.0090892, 1.0180064, 45 | 1.0263000, 1.0339827, 1.0409860, 1.0471880, 1.0524667, 1.0567000, 1.0597944, 46 | 1.0617992, 1.0628068, 1.0629096, 1.0622000, 1.0607352, 1.0584436, 1.0552244, 47 | 1.0509768, 1.0456000, 1.0390369, 1.0313608, 1.0226662, 1.0130477, 1.0026000, 48 | 0.9913675, 0.9793314, 0.9664916, 0.9528479, 0.9384000, 0.9231940, 0.9072440, 49 | 0.8905020, 0.8729200, 0.8544499, 0.8350840, 0.8149460, 0.7941860, 0.7729540, 50 | 0.7514000, 0.7295836, 0.7075888, 0.6856022, 0.6638104, 0.6424000, 0.6215149, 51 | 0.6011138, 0.5811052, 0.5613977, 0.5419000, 0.5225995, 0.5035464, 0.4847436, 52 | 0.4661939, 0.4479000, 0.4298613, 0.4120980, 0.3946440, 0.3775333, 0.3608000, 53 | 0.3444563, 0.3285168, 0.3130192, 0.2980011, 0.2835000, 0.2695448, 0.2561184, 54 | 0.2431896, 0.2307272, 0.2187000, 0.2070971, 0.1959232, 0.1851708, 0.1748323, 55 | 0.1649000, 0.1553667, 0.1462300, 0.1374900, 0.1291467, 0.1212000, 0.1136397, 56 | 0.1064650, 0.09969044, 0.09333061, 0.08740000, 0.08190096, 0.07680428, 57 | 0.07207712, 0.06768664, 0.06360000, 0.05980685, 0.05628216, 0.05297104, 58 | 0.04981861, 0.04677000, 0.04378405, 0.04087536, 0.03807264, 0.03540461, 59 | 0.03290000, 0.03056419, 0.02838056, 0.02634484, 0.02445275, 0.02270000, 60 | 0.02108429, 0.01959988, 0.01823732, 0.01698717, 0.01584000, 0.01479064, 61 | 0.01383132, 0.01294868, 0.01212920, 0.01135916, 0.01062935, 0.009938846, 62 | 0.009288422, 0.008678854, 0.008110916, 0.007582388, 0.007088746, 0.006627313, 63 | 0.006195408, 0.005790346, 0.005409826, 0.005052583, 0.004717512, 0.004403507, 64 | 0.004109457, 0.003833913, 0.003575748, 0.003334342, 0.003109075, 0.002899327, 65 | 0.002704348, 0.002523020, 0.002354168, 0.002196616, 0.002049190, 0.001910960, 66 | 0.001781438, 0.001660110, 0.001546459, 0.001439971, 0.001340042, 0.001246275, 67 | 0.001158471, 0.001076430, 0.0009999493, 0.0009287358, 0.0008624332, 68 | 0.0008007503, 0.0007433960, 0.0006900786, 0.0006405156, 0.0005945021, 69 | 0.0005518646, 0.0005124290, 0.0004760213, 0.0004424536, 0.0004115117, 70 | 0.0003829814, 0.0003566491, 0.0003323011, 0.0003097586, 0.0002888871, 71 | 0.0002695394, 0.0002515682, 0.0002348261, 0.0002191710, 0.0002045258, 72 | 0.0001908405, 0.0001780654, 0.0001661505, 0.0001550236, 0.0001446219, 73 | 0.0001349098, 0.0001258520, 0.0001174130, 0.0001095515, 0.0001022245, 74 | 0.00009539445, 0.00008902390, 0.00008307527, 0.00007751269, 0.00007231304, 75 | 0.00006745778, 0.00006292844, 0.00005870652, 0.00005477028, 0.00005109918, 76 | 0.00004767654, 0.00004448567, 0.00004150994, 0.00003873324, 0.00003614203, 77 | 0.00003372352, 0.00003146487, 0.00002935326, 0.00002737573, 0.00002552433, 78 | 0.00002379376, 0.00002217870, 0.00002067383, 0.00001927226, 0.00001796640, 79 | 0.00001674991, 0.00001561648, 0.00001455977, 0.00001357387, 0.00001265436, 80 | 0.00001179723, 0.00001099844, 0.00001025398, 0.000009559646, 0.000008912044, 81 | 0.000008308358, 0.000007745769, 0.000007221456, 0.000006732475, 0.000006276423, 82 | 0.000005851304, 0.000005455118, 0.000005085868, 0.000004741466, 0.000004420236, 83 | 0.000004120783, 0.000003841716, 0.000003581652, 0.000003339127, 0.000003112949, 84 | 0.000002902121, 0.000002705645, 0.000002522525, 0.000002351726, 0.000002192415, 85 | 0.000002043902, 0.000001905497, 0.000001776509, 0.000001656215, 0.000001544022, 86 | 0.000001439440, 0.000001341977, 0.000001251141 87 | ], kind='cubic') 88 | 89 | 90 | CIE_Y = interpolate.interp1d(CIE_wavelengths, [ 91 | 0.000003917000, 0.000004393581, 0.000004929604, 0.000005532136, 92 | 0.000006208245, 0.000006965000, 0.000007813219, 0.000008767336, 93 | 0.000009839844, 0.00001104323, 0.00001239000, 0.00001388641, 0.00001555728, 94 | 0.00001744296, 0.00001958375, 0.00002202000, 0.00002483965, 0.00002804126, 95 | 0.00003153104, 0.00003521521, 0.00003900000, 0.00004282640, 0.00004691460, 96 | 0.00005158960, 0.00005717640, 0.00006400000, 0.00007234421, 0.00008221224, 97 | 0.00009350816, 0.0001061361, 0.0001200000, 0.0001349840, 0.0001514920, 98 | 0.0001702080, 0.0001918160, 0.0002170000, 0.0002469067, 0.0002812400, 99 | 0.0003185200, 0.0003572667, 0.0003960000, 0.0004337147, 0.0004730240, 100 | 0.0005178760, 0.0005722187, 0.0006400000, 0.0007245600, 0.0008255000, 101 | 0.0009411600, 0.001069880, 0.001210000, 0.001362091, 0.001530752, 102 | 0.001720368, 0.001935323, 0.002180000, 0.002454800, 0.002764000, 103 | 0.003117800, 0.003526400, 0.004000000, 0.004546240, 0.005159320, 104 | 0.005829280, 0.006546160, 0.007300000, 0.008086507, 0.008908720, 105 | 0.009767680, 0.01066443, 0.01160000, 0.01257317, 0.01358272, 0.01462968, 106 | 0.01571509, 0.01684000, 0.01800736, 0.01921448, 0.02045392, 0.02171824, 107 | 0.02300000, 0.02429461, 0.02561024, 0.02695857, 0.02835125, 0.02980000, 108 | 0.03131083, 0.03288368, 0.03452112, 0.03622571, 0.03800000, 0.03984667, 109 | 0.04176800, 0.04376600, 0.04584267, 0.04800000, 0.05024368, 0.05257304, 110 | 0.05498056, 0.05745872, 0.06000000, 0.06260197, 0.06527752, 0.06804208, 111 | 0.07091109, 0.07390000, 0.07701600, 0.08026640, 0.08366680, 0.08723280, 112 | 0.09098000, 0.09491755, 0.09904584, 0.1033674, 0.1078846, 0.1126000, 113 | 0.1175320, 0.1226744, 0.1279928, 0.1334528, 0.1390200, 0.1446764, 114 | 0.1504693, 0.1564619, 0.1627177, 0.1693000, 0.1762431, 0.1835581, 115 | 0.1912735, 0.1994180, 0.2080200, 0.2171199, 0.2267345, 0.2368571, 116 | 0.2474812, 0.2586000, 0.2701849, 0.2822939, 0.2950505, 0.3085780, 117 | 0.3230000, 0.3384021, 0.3546858, 0.3716986, 0.3892875, 0.4073000, 118 | 0.4256299, 0.4443096, 0.4633944, 0.4829395, 0.5030000, 0.5235693, 119 | 0.5445120, 0.5656900, 0.5869653, 0.6082000, 0.6293456, 0.6503068, 120 | 0.6708752, 0.6908424, 0.7100000, 0.7281852, 0.7454636, 0.7619694, 121 | 0.7778368, 0.7932000, 0.8081104, 0.8224962, 0.8363068, 0.8494916, 122 | 0.8620000, 0.8738108, 0.8849624, 0.8954936, 0.9054432, 0.9148501, 123 | 0.9237348, 0.9320924, 0.9399226, 0.9472252, 0.9540000, 0.9602561, 124 | 0.9660074, 0.9712606, 0.9760225, 0.9803000, 0.9840924, 0.9874812, 125 | 0.9903128, 0.9928116, 0.9949501, 0.9967108, 0.9980983, 0.9991120, 126 | 0.9997482, 1.0000000, 0.9998567, 0.9993046, 0.9983255, 0.9968987, 127 | 0.9950000, 0.9926005, 0.9897426, 0.9864444, 0.9827241, 0.9786000, 128 | 0.9740837, 0.9691712, 0.9638568, 0.9581349, 0.9520000, 0.9454504, 129 | 0.9384992, 0.9311628, 0.9234576, 0.9154000, 0.9070064, 0.8982772, 130 | 0.8892048, 0.8797816, 0.8700000, 0.8598613, 0.8493920, 0.8386220, 131 | 0.8275813, 0.8163000, 0.8047947, 0.7930820, 0.7811920, 0.7691547, 132 | 0.7570000, 0.7447541, 0.7324224, 0.7200036, 0.7074965, 0.6949000, 133 | 0.6822192, 0.6694716, 0.6566744, 0.6438448, 0.6310000, 0.6181555, 134 | 0.6053144, 0.5924756, 0.5796379, 0.5668000, 0.5539611, 0.5411372, 135 | 0.5283528, 0.5156323, 0.5030000, 0.4904688, 0.4780304, 0.4656776, 136 | 0.4534032, 0.4412000, 0.4290800, 0.4170360, 0.4050320, 0.3930320, 137 | 0.3810000, 0.3689184, 0.3568272, 0.3447768, 0.3328176, 0.3210000, 138 | 0.3093381, 0.2978504, 0.2865936, 0.2756245, 0.2650000, 0.2547632, 139 | 0.2448896, 0.2353344, 0.2260528, 0.2170000, 0.2081616, 0.1995488, 140 | 0.1911552, 0.1829744, 0.1750000, 0.1672235, 0.1596464, 0.1522776, 141 | 0.1451259, 0.1382000, 0.1315003, 0.1250248, 0.1187792, 0.1127691, 0.1070000, 142 | 0.1014762, 0.09618864, 0.09112296, 0.08626485, 0.08160000, 0.07712064, 143 | 0.07282552, 0.06871008, 0.06476976, 0.06100000, 0.05739621, 0.05395504, 144 | 0.05067376, 0.04754965, 0.04458000, 0.04175872, 0.03908496, 0.03656384, 145 | 0.03420048, 0.03200000, 0.02996261, 0.02807664, 0.02632936, 0.02470805, 146 | 0.02320000, 0.02180077, 0.02050112, 0.01928108, 0.01812069, 0.01700000, 147 | 0.01590379, 0.01483718, 0.01381068, 0.01283478, 0.01192000, 0.01106831, 148 | 0.01027339, 0.009533311, 0.008846157, 0.008210000, 0.007623781, 149 | 0.007085424, 0.006591476, 0.006138485, 0.005723000, 0.005343059, 150 | 0.004995796, 0.004676404, 0.004380075, 0.004102000, 0.003838453, 151 | 0.003589099, 0.003354219, 0.003134093, 0.002929000, 0.002738139, 152 | 0.002559876, 0.002393244, 0.002237275, 0.002091000, 0.001953587, 153 | 0.001824580, 0.001703580, 0.001590187, 0.001484000, 0.001384496, 154 | 0.001291268, 0.001204092, 0.001122744, 0.001047000, 0.0009765896, 155 | 0.0009111088, 0.0008501332, 0.0007932384, 0.0007400000, 0.0006900827, 156 | 0.0006433100, 0.0005994960, 0.0005584547, 0.0005200000, 0.0004839136, 157 | 0.0004500528, 0.0004183452, 0.0003887184, 0.0003611000, 0.0003353835, 158 | 0.0003114404, 0.0002891656, 0.0002684539, 0.0002492000, 0.0002313019, 159 | 0.0002146856, 0.0001992884, 0.0001850475, 0.0001719000, 0.0001597781, 160 | 0.0001486044, 0.0001383016, 0.0001287925, 0.0001200000, 0.0001118595, 161 | 0.0001043224, 0.00009733560, 0.00009084587, 0.00008480000, 0.00007914667, 162 | 0.00007385800, 0.00006891600, 0.00006430267, 0.00006000000, 0.00005598187, 163 | 0.00005222560, 0.00004871840, 0.00004544747, 0.00004240000, 0.00003956104, 164 | 0.00003691512, 0.00003444868, 0.00003214816, 0.00003000000, 0.00002799125, 165 | 0.00002611356, 0.00002436024, 0.00002272461, 0.00002120000, 0.00001977855, 166 | 0.00001845285, 0.00001721687, 0.00001606459, 0.00001499000, 0.00001398728, 167 | 0.00001305155, 0.00001217818, 0.00001136254, 0.00001060000, 0.000009885877, 168 | 0.000009217304, 0.000008592362, 0.000008009133, 0.000007465700, 169 | 0.000006959567, 0.000006487995, 0.000006048699, 0.000005639396, 170 | 0.000005257800, 0.000004901771, 0.000004569720, 0.000004260194, 171 | 0.000003971739, 0.000003702900, 0.000003452163, 0.000003218302, 172 | 0.000003000300, 0.000002797139, 0.000002607800, 0.000002431220, 173 | 0.000002266531, 0.000002113013, 0.000001969943, 0.000001836600, 174 | 0.000001712230, 0.000001596228, 0.000001488090, 0.000001387314, 175 | 0.000001293400, 0.000001205820, 0.000001124143, 0.000001048009, 176 | 0.0000009770578, 0.0000009109300, 0.0000008492513, 0.0000007917212, 177 | 0.0000007380904, 0.0000006881098, 0.0000006415300, 0.0000005980895, 178 | 0.0000005575746, 0.0000005198080, 0.0000004846123, 0.0000004518100 179 | ], kind='cubic') 180 | 181 | CIE_Z = interpolate.interp1d(CIE_wavelengths, [ 182 | 0.0006061000, 0.0006808792, 0.0007651456, 0.0008600124, 0.0009665928, 183 | 0.001086000, 0.001220586, 0.001372729, 0.001543579, 0.001734286, 184 | 0.001946000, 0.002177777, 0.002435809, 0.002731953, 0.003078064, 185 | 0.003486000, 0.003975227, 0.004540880, 0.005158320, 0.005802907, 186 | 0.006450001, 0.007083216, 0.007745488, 0.008501152, 0.009414544, 187 | 0.01054999, 0.01196580, 0.01365587, 0.01558805, 0.01773015, 0.02005001, 188 | 0.02251136, 0.02520288, 0.02827972, 0.03189704, 0.03621000, 0.04143771, 189 | 0.04750372, 0.05411988, 0.06099803, 0.06785001, 0.07448632, 0.08136156, 190 | 0.08915364, 0.09854048, 0.1102000, 0.1246133, 0.1417017, 0.1613035, 191 | 0.1832568, 0.2074000, 0.2336921, 0.2626114, 0.2947746, 0.3307985, 192 | 0.3713000, 0.4162091, 0.4654642, 0.5196948, 0.5795303, 0.6456000, 193 | 0.7184838, 0.7967133, 0.8778459, 0.9594390, 1.0390501, 1.1153673, 194 | 1.1884971, 1.2581233, 1.3239296, 1.3856000, 1.4426352, 1.4948035, 195 | 1.5421903, 1.5848807, 1.6229600, 1.6564048, 1.6852959, 1.7098745, 196 | 1.7303821, 1.7470600, 1.7600446, 1.7696233, 1.7762637, 1.7804334, 197 | 1.7826000, 1.7829682, 1.7816998, 1.7791982, 1.7758671, 1.7721100, 198 | 1.7682589, 1.7640390, 1.7589438, 1.7524663, 1.7441000, 1.7335595, 199 | 1.7208581, 1.7059369, 1.6887372, 1.6692000, 1.6475287, 1.6234127, 200 | 1.5960223, 1.5645280, 1.5281000, 1.4861114, 1.4395215, 1.3898799, 201 | 1.3387362, 1.2876400, 1.2374223, 1.1878243, 1.1387611, 1.0901480, 202 | 1.0419000, 0.9941976, 0.9473473, 0.9014531, 0.8566193, 0.8129501, 203 | 0.7705173, 0.7294448, 0.6899136, 0.6521049, 0.6162000, 0.5823286, 204 | 0.5504162, 0.5203376, 0.4919673, 0.4651800, 0.4399246, 0.4161836, 205 | 0.3938822, 0.3729459, 0.3533000, 0.3348578, 0.3175521, 0.3013375, 206 | 0.2861686, 0.2720000, 0.2588171, 0.2464838, 0.2347718, 0.2234533, 207 | 0.2123000, 0.2011692, 0.1901196, 0.1792254, 0.1685608, 0.1582000, 208 | 0.1481383, 0.1383758, 0.1289942, 0.1200751, 0.1117000, 0.1039048, 209 | 0.09666748, 0.08998272, 0.08384531, 0.07824999, 0.07320899, 0.06867816, 210 | 0.06456784, 0.06078835, 0.05725001, 0.05390435, 0.05074664, 0.04775276, 211 | 0.04489859, 0.04216000, 0.03950728, 0.03693564, 0.03445836, 0.03208872, 212 | 0.02984000, 0.02771181, 0.02569444, 0.02378716, 0.02198925, 0.02030000, 213 | 0.01871805, 0.01724036, 0.01586364, 0.01458461, 0.01340000, 0.01230723, 214 | 0.01130188, 0.01037792, 0.009529306, 0.008749999, 0.008035200, 0.007381600, 215 | 0.006785400, 0.006242800, 0.005749999, 0.005303600, 0.004899800, 216 | 0.004534200, 0.004202400, 0.003900000, 0.003623200, 0.003370600, 217 | 0.003141400, 0.002934800, 0.002749999, 0.002585200, 0.002438600, 218 | 0.002309400, 0.002196800, 0.002100000, 0.002017733, 0.001948200, 219 | 0.001889800, 0.001840933, 0.001800000, 0.001766267, 0.001737800, 220 | 0.001711200, 0.001683067, 0.001650001, 0.001610133, 0.001564400, 221 | 0.001513600, 0.001458533, 0.001400000, 0.001336667, 0.001270000, 222 | 0.001205000, 0.001146667, 0.001100000, 0.001068800, 0.001049400, 223 | 0.001035600, 0.001021200, 0.001000000, 0.0009686400, 0.0009299200, 224 | 0.0008868800, 0.0008425600, 0.0008000000, 0.0007609600, 0.0007236800, 225 | 0.0006859200, 0.0006454400, 0.0006000000, 0.0005478667, 0.0004916000, 226 | 0.0004354000, 0.0003834667, 0.0003400000, 0.0003072533, 0.0002831600, 227 | 0.0002654400, 0.0002518133, 0.0002400000, 0.0002295467, 0.0002206400, 228 | 0.0002119600, 0.0002021867, 0.0001900000, 0.0001742133, 0.0001556400, 229 | 0.0001359600, 0.0001168533, 0.0001000000, 0.00008613333, 0.00007460000, 230 | 0.00006500000, 0.00005693333, 0.00004999999, 0.00004416000, 0.00003948000, 231 | 0.00003572000, 0.00003264000, 0.00003000000, 0.00002765333, 0.00002556000, 232 | 0.00002364000, 0.00002181333, 0.00002000000, 0.00001813333, 0.00001620000, 233 | 0.00001420000, 0.00001213333, 0.00001000000, 0.000007733333, 234 | 0.000005400000, 0.000003200000, 0.000001333333, 0.000000000000, 0.0, 0.0, 235 | 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 236 | 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 237 | 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 238 | 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 239 | 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 240 | 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 241 | 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 242 | 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 243 | 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 244 | 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 245 | 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 246 | 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 247 | ], kind='cubic') 248 | 249 | def get_xyz(spd): 250 | X = quad(lambda l : CIE_X(l) * spd(l).real, 360, 830, limit=100)[0] + \ 251 | 1j* quad(lambda l : CIE_X(l) * spd(l).imag, 360, 830, limit=100)[0] 252 | Y = quad(lambda l : CIE_Y(l) * spd(l).real, 360, 830, limit=100)[0] + \ 253 | 1j* quad(lambda l : CIE_Y(l) * spd(l).imag, 360, 830, limit=100)[0] 254 | Z = quad(lambda l : CIE_Z(l) * spd(l).real, 360, 830, limit=100)[0] + \ 255 | 1j* quad(lambda l : CIE_Z(l) * spd(l).imag, 360, 830, limit=100)[0] 256 | Y_sum = quad(lambda l : CIE_Y(l), 360, 830, limit=100)[0] 257 | return X / Y_sum, Y / Y_sum, Z / Y_sum 258 | 259 | def get_rgb(spd): 260 | X, Y, Z = get_xyz(spd) 261 | R = 3.240479 * X + -1.537150 * Y + -0.498535 * Z 262 | G = -0.969256 * X + 1.875991 * Y + 0.041556 * Z 263 | B = 0.055648 * X + -0.204043 * Y + 1.057311 * Z 264 | return R, G, B 265 | 266 | -------------------------------------------------------------------------------- /recipes/utils/materials.py: -------------------------------------------------------------------------------- 1 | # Complex-valued IOR curves for a few metals 2 | 3 | from scipy import interpolate 4 | 5 | lambda_gold = [298.75705, 302.400421, 306.133759, 309.960449, 313.884003, 317.908142, 6 | 322.036835, 326.274139, 330.624481, 335.092377, 339.682678, 344.400482, 7 | 349.251221, 354.240509, 359.37442, 364.659332, 370.10202, 375.709625, 8 | 381.489777, 387.450562, 393.600555, 399.948975, 406.505493, 413.280579, 9 | 420.285339, 427.531647, 435.032196, 442.800629, 450.851562, 459.200653, 10 | 467.864838, 476.862213, 486.212463, 495.936707, 506.057861, 516.600769, 11 | 527.592224, 539.061646, 551.040771, 563.564453, 576.670593, 590.400818, 12 | 604.800842, 619.920898, 635.816284, 652.548279, 670.184753, 688.800964, 13 | 708.481018, 729.318665, 751.41925, 774.901123, 799.897949, 826.561157, 14 | 855.063293, 885.601257] 15 | 16 | eta_gold = [1.795+1.920375j, 1.812+1.92j, 1.822625+1.918875j, 1.83+1.916j, 17 | 1.837125+1.911375j, 1.84+1.904j, 1.83425+1.891375j, 18 | 1.824+1.878j, 1.812+1.86825j, 1.798+1.86j, 1.782+1.85175j, 19 | 1.766+1.846j, 1.7525+1.84525j, 1.74+1.848j, 1.727625+1.852375j, 20 | 1.716+1.862j, 1.705875+1.883j, 1.696+1.906j, 1.68475+1.9225j, 21 | 1.674+1.936j, 1.666+1.94775j, 1.658+1.956j, 1.64725+1.959375j, 22 | 1.636+1.958j, 1.628+1.951375j, 1.616+1.94j, 1.59625+1.9245j, 23 | 1.562+1.904j, 1.502125+1.875875j, 1.426+1.846j, 24 | 1.345875+1.814625j, 1.242+1.796j, 1.08675+1.797375j, 25 | 0.916+1.84j, 0.7545+1.9565j, 0.608+2.12j, 0.49175+2.32625j, 26 | 0.402+2.54j, 0.3455+2.730625j, 0.306+2.88j, 0.267625+2.940625j, 27 | 0.236+2.97j, 0.212375+3.015j, 0.194+3.06j, 0.17775+3.07j, 28 | 0.166+3.15j, 0.161+3.445812j, 0.16+3.8j, 0.160875+4.087687j, 29 | 0.164+4.357j, 0.1695+4.610188j, 0.176+4.86j, 30 | 0.181375+5.125813j, 0.188+5.39j, 0.198125+5.63125j, 0.21+5.88j] 31 | 32 | lambda_aluminium = [298.75705, 302.400421, 306.133759, 309.960449, 313.884003, 33 | 317.908142, 322.036835, 326.274139, 330.624481, 335.092377, 339.682678, 34 | 344.400482, 349.251221, 354.240509, 359.37442, 364.659332, 370.10202, 35 | 375.709625, 381.489777, 387.450562, 393.600555, 399.948975, 406.505493, 36 | 413.280579, 420.285339, 427.531647, 435.032196, 442.800629, 450.851562, 37 | 459.200653, 467.864838, 476.862213, 486.212463, 495.936707, 506.057861, 38 | 516.600769, 527.592224, 539.061646, 551.040771, 563.564453, 576.670593, 39 | 590.400818, 604.800842, 619.920898, 635.816284, 652.548279, 670.184753, 40 | 688.800964, 708.481018, 729.318665, 751.41925, 774.901123, 799.897949, 41 | 826.561157, 855.063293, 885.601257] 42 | 43 | eta_aluminium = [(0.273375+3.59375j), (0.28+3.64j), (0.286813+3.689375j), 44 | (0.294+3.74j), (0.301875+3.789375j), (0.31+3.84j), 45 | (0.317875+3.894375j), (0.326+3.95j), (0.33475+4.005j), (0.344+4.06j), 46 | (0.353813+4.11375j), (0.364+4.17j), (0.374375+4.23375j), (0.385+4.3j), 47 | (0.39575+4.365j), (0.407+4.43j), (0.419125+4.49375j), (0.432+4.56j), 48 | (0.445688+4.63375j), (0.46+4.71j), (0.474688+4.784375j), (0.49+4.86j), 49 | (0.506188+4.938125j), (0.523+5.02j), (0.540063+5.10875j), (0.558+5.2j), 50 | (0.577313+5.29j), (0.598+5.38j), (0.620313+5.48j), (0.644+5.58j), 51 | (0.668625+5.69j), (0.695+5.8j), (0.72375+5.915j), (0.755+6.03j), 52 | (0.789+6.15j), (0.826+6.28j), (0.867+6.42j), (0.912+6.55j), 53 | (0.963+6.7j), (1.02+6.85j), (1.08+7j), (1.15+7.15j), (1.22+7.31j), 54 | (1.3+7.48j), (1.39+7.65j), (1.49+7.82j), (1.6+8.01j), (1.74+8.21j), 55 | (1.91+8.39j), (2.14+8.57j), (2.41+8.62j), (2.63+8.6j), (2.8+8.45j), 56 | (2.74+8.31j), (2.58+8.21j), (2.24+8.21j)] 57 | 58 | lambda_copper = [302.400421, 306.133759, 309.960449, 313.884003, 317.908142, 59 | 322.036835, 326.274139, 330.624481, 335.092377, 339.682678, 344.400482, 60 | 349.251221, 354.240509, 359.37442, 364.659332, 370.10202, 375.709625, 61 | 381.489777, 387.450562, 393.600555, 399.948975, 406.505493, 413.280579, 62 | 420.285339, 427.531647, 435.032196, 442.800629, 450.851562, 459.200653, 63 | 467.864838, 476.862213, 486.212463, 495.936707, 506.057861, 516.600769, 64 | 527.592224, 539.061646, 551.040771, 563.564453, 576.670593, 590.400818, 65 | 604.800842, 619.920898, 635.816284, 652.548279, 670.184753, 688.800964, 66 | 708.481018, 729.318665, 751.41925, 774.901123, 799.897949, 826.561157, 67 | 855.063293, 885.601257] 68 | 69 | eta_copper = [(1.38+1.687j), (1.358438+1.703313j), (1.34+1.72j), 70 | (1.329063+1.744563j), (1.325+1.77j), (1.3325+1.791625j), (1.34+1.81j), 71 | (1.334375+1.822125j), (1.325+1.834j), (1.317812+1.85175j), 72 | (1.31+1.872j), (1.300313+1.89425j), (1.29+1.916j), 73 | (1.281563+1.931688j), (1.27+1.95j), (1.249062+1.972438j), 74 | (1.225+2.015j), (1.2+2.121562j), (1.18+2.21j), (1.174375+2.177188j), 75 | (1.175+2.13j), (1.1775+2.160063j), (1.18+2.21j), (1.178125+2.249938j), 76 | (1.175+2.289j), (1.172812+2.326j), (1.17+2.362j), (1.165312+2.397625j), 77 | (1.16+2.433j), (1.155312+2.469187j), (1.15+2.504j), 78 | (1.142812+2.535875j), (1.135+2.564j), (1.131562+2.589625j), 79 | (1.12+2.605j), (1.092437+2.595562j), (1.04+2.583j), (0.950375+2.5765j), 80 | (0.826+2.599j), (0.645875+2.678062j), (0.468+2.809j), 81 | (0.35125+3.01075j), (0.272+3.24j), (0.230813+3.458187j), (0.214+3.67j), 82 | (0.20925+3.863125j), (0.213+4.05j), (0.21625+4.239563j), (0.223+4.43j), 83 | (0.2365+4.619563j), (0.25+4.817j), (0.254188+5.034125j), (0.26+5.26j), 84 | (0.28+5.485625j), (0.3+5.717j)] 85 | 86 | lambda_chrome = [300.194, 307.643005, 316.276001, 323.708008, 333.279999, 87 | 341.542999, 351.217987, 362.514984, 372.312012, 385.031006, 396.10202, 88 | 409.175018, 424.58902, 438.09201, 455.80899, 471.406982, 490.040009, 89 | 512.314026, 532.102966, 558.468018, 582.06604, 610.739014, 700.452026, 90 | 815.65802, 826.53302, 849.17804, 860.971985, 885.570984] 91 | 92 | eta_chrome = [(0.98+2.67j), (1.02+2.76j), (1.06+2.85j), (1.12+2.95j), 93 | (1.18+3.04j), (1.26+3.12j), (1.33+3.18j), (1.39+3.24j), (1.43+3.31j), 94 | (1.44+3.4j), (1.48+3.54j), (1.54+3.71j), (1.65+3.89j), (1.8+4.06j), 95 | (1.99+4.22j), (2.22+4.36j), (2.49+4.44j), (2.75+4.46j), (2.98+4.45j), 96 | (3.18+4.41j), (3.34+4.38j), (3.48+4.36j), (3.84+4.37j), (4.23+4.34j), 97 | (4.27+4.33j), (4.31+4.32j), (4.33+4.32j), (4.38+4.31j)] 98 | 99 | gold = interpolate.interp1d(lambda_gold, eta_gold, kind='cubic') 100 | copper = interpolate.interp1d(lambda_copper, eta_copper, kind='cubic') 101 | aluminium = interpolate.interp1d(lambda_aluminium, eta_aluminium, kind='cubic') 102 | chrome = interpolate.interp1d(lambda_chrome, eta_chrome, kind='cubic') 103 | -------------------------------------------------------------------------------- /src/fresnel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | fresnel.cpp -- Fresnel coefficients for dielectrics and conductors 3 | 4 | Copyright (c) 2015 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | NAMESPACE_BEGIN(layer) 15 | 16 | Float fresnelDielectric(Float cosThetaI_, Float &cosThetaT_, Float eta) { 17 | if (eta == 1) { 18 | cosThetaT_ = -cosThetaI_; 19 | return 0.0f; 20 | } 21 | 22 | /* Using Snell's law, calculate the squared sine of the 23 | angle between the normal and the transmitted ray */ 24 | Float scale = (cosThetaI_ > 0) ? 1/eta : eta, 25 | cosThetaTSqr = 1 - (1-cosThetaI_*cosThetaI_) * (scale*scale); 26 | 27 | /* Check for total internal reflection */ 28 | if (cosThetaTSqr <= 0.0f) { 29 | cosThetaT_ = 0.0f; 30 | return 1.0f; 31 | } 32 | 33 | /* Find the absolute cosines of the incident/transmitted rays */ 34 | Float cosThetaI = std::abs(cosThetaI_); 35 | Float cosThetaT = std::sqrt(cosThetaTSqr); 36 | 37 | Float Rs = (cosThetaI - eta * cosThetaT) 38 | / (cosThetaI + eta * cosThetaT); 39 | Float Rp = (eta * cosThetaI - cosThetaT) 40 | / (eta * cosThetaI + cosThetaT); 41 | 42 | cosThetaT_ = (cosThetaI_ > 0) ? -cosThetaT : cosThetaT; 43 | 44 | /* No polarization -- return the unpolarized reflectance */ 45 | return 0.5f * (Rs * Rs + Rp * Rp); 46 | } 47 | 48 | Float fresnelConductor(Float cosThetaI, std::complex eta_) { 49 | /* Modified from "Optics" by K.D. Moeller, University Science Books, 1988 */ 50 | Float eta = eta_.real(), k = eta_.imag(); 51 | 52 | Float cosThetaI2 = cosThetaI*cosThetaI, 53 | sinThetaI2 = 1-cosThetaI2, 54 | sinThetaI4 = sinThetaI2*sinThetaI2; 55 | 56 | Float temp1 = eta*eta - k*k - sinThetaI2, 57 | a2pb2 = math::safe_sqrt(temp1*temp1 + 4*k*k*eta*eta), 58 | a = math::safe_sqrt(0.5f * (a2pb2 + temp1)); 59 | 60 | Float term1 = a2pb2 + cosThetaI2, 61 | term2 = 2*a*cosThetaI; 62 | 63 | Float Rs2 = (term1 - term2) / (term1 + term2); 64 | 65 | Float term3 = a2pb2*cosThetaI2 + sinThetaI4, 66 | term4 = term2*sinThetaI2; 67 | 68 | Float Rp2 = Rs2 * (term3 - term4) / (term3 + term4); 69 | 70 | return 0.5f * (Rp2 + Rs2); 71 | } 72 | 73 | Float fresnelDielectricIntegral(Float eta) { 74 | /* Fast mode: the following code approximates the 75 | * diffuse Frensel reflectance for the eta<1 and 76 | * eta>1 cases. An evalution of the accuracy led 77 | * to the following scheme, which cherry-picks 78 | * fits from two papers where they are best. 79 | */ 80 | if (eta < 1) { 81 | /* Fit by Egan and Hilgeman (1973). Works 82 | reasonably well for "normal" IOR values (<2). 83 | 84 | Max rel. error in 1.0 - 1.5 : 0.1% 85 | Max rel. error in 1.5 - 2 : 0.6% 86 | Max rel. error in 2.0 - 5 : 9.5% 87 | */ 88 | return -1.4399f * (eta * eta) 89 | + 0.7099f * eta 90 | + 0.6681f 91 | + 0.0636f / eta; 92 | } else { 93 | /* Fit by d'Eon and Irving (2011) 94 | * 95 | * Maintains a good accuracy even for 96 | * unrealistic IOR values. 97 | * 98 | * Max rel. error in 1.0 - 2.0 : 0.1% 99 | * Max rel. error in 2.0 - 10.0 : 0.2% 100 | */ 101 | Float invEta = 1.0f / eta, 102 | invEta2 = invEta*invEta, 103 | invEta3 = invEta2*invEta, 104 | invEta4 = invEta3*invEta, 105 | invEta5 = invEta4*invEta; 106 | 107 | return 0.919317f - 3.4793f * invEta 108 | + 6.75335f * invEta2 109 | - 7.80989f * invEta3 110 | + 4.98554f * invEta4 111 | - 1.36881f * invEta5; 112 | } 113 | } 114 | 115 | Float fresnelConductorIntegral(std::complex eta) { 116 | /* 10 point Gauss-Lobatto rule */ 117 | 118 | Float nodes[] = { 0. , 0.04023304591677057118, 119 | 0.13061306744724748841, 0.26103752509477773369, 120 | 0.41736052116680649737, 0.58263947883319344712, 121 | 0.73896247490522226631, 0.8693869325527525671 , 122 | 0.95976695408322942882, 1. }; 123 | Float weights[] = { 0.01111111111111111154, 0.0666529954255350443, 124 | 0.11244467103156326193, 0.14602134183984186167, 125 | 0.16376988059194869107, 0.16376988059194869107, 126 | 0.14602134183984186167, 0.11244467103156326193, 127 | 0.0666529954255350443, 0.01111111111111111154 }; 128 | 129 | Float value = 0; 130 | for (int i=0; i<10; ++i) 131 | value += fresnelConductor(std::sqrt(nodes[i]), eta) * weights[i]; 132 | 133 | return value; 134 | } 135 | 136 | NAMESPACE_END(layer) 137 | -------------------------------------------------------------------------------- /src/hg.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | hg.cpp -- Henyey-Greenstein model evaluation routines 3 | 4 | Copyright (c) 2015 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | NAMESPACE_BEGIN(layer) 15 | 16 | Float hg(Float mu_o, Float mu_i, Float g, Float phi_d) { 17 | Float cosTheta = mu_i * mu_o + std::cos(phi_d) * 18 | std::sqrt((1-mu_i*mu_i) * (1-mu_o*mu_o)); 19 | 20 | Float temp = 1.0f + g*g - 2.0f * g * cosTheta; 21 | 22 | return math::InvFourPi * (1 - g*g) / (temp * std::sqrt(temp)); 23 | } 24 | 25 | void hgFourierSeries(Float mu_o, Float mu_i, Float g, size_t kmax, Float relerr, 26 | std::vector &result) { 27 | result.clear(); 28 | if (g == 0) { 29 | result.push_back(math::InvFourPi); 30 | return; 31 | } 32 | 33 | /* Compute A and B coefficients */ 34 | Float a = 1 + g*g - 2*g*mu_i*mu_o; 35 | Float b = -2 * g * math::safe_sqrt((1-mu_i*mu_i) * (1-mu_o*mu_o)); 36 | 37 | /* Find the first two Fourier coefficients using elliptic integrals */ 38 | Float absB = std::abs(b), arg = std::sqrt(2*absB / (a+absB)); 39 | Float K = math::comp_ellint_1(arg), E = math::comp_ellint_2(arg); 40 | Float sqrtAB = std::sqrt(a+absB), temp = (1-g*g) * (0.5f * math::InvPi * math::InvPi); 41 | 42 | Float coeff0 = (E * temp * sqrtAB) / (a*a - b*b); 43 | Float coeff1 = b == 0 ? 0 : 44 | (math::signum(b) * temp / (absB * sqrtAB) * (K - a / (a-absB) * E)); 45 | 46 | size_t m = std::max(kmax * 2, (size_t) 500); 47 | Float *s = (Float *) alloca(sizeof(Float) * (m + 1)); 48 | 49 | /* Compute the ratio between the $m$-th and $m+1$-th term 50 | using a second-order Taylor expansion */ 51 | Float z = a / math::safe_sqrt(a*a - b*b), 52 | delta = z / math::safe_sqrt(z*z-1); 53 | s[m] = (1 + 1 / (Float) (2*m) - (1+3*z) / (Float) (8*m*m)) * 54 | std::sqrt((z-1) / (z+1)); 55 | 56 | do { 57 | /* Work backwards using a recurrence scheme */ 58 | --m; 59 | s[m] = (2*m+3) / (4*(m+1) * delta - (2*m+1) * s[m+1]); 60 | } while (m != 0); 61 | 62 | /* Simple to get a bit of extra accuracy here: apply a correction 63 | in case s[0] does not quite match the known reference value */ 64 | Float C = 0.0f; 65 | if (s[0] != 0) 66 | C = coeff1 / (coeff0 * s[0]); 67 | 68 | /* Now, multiply all ratios together to get the desired value */ 69 | result.push_back(coeff0); 70 | 71 | Float prod = coeff0 * C * 2; 72 | for (size_t j=0; j 2 | #include 3 | #if defined(__WINDOWS__) 4 | #include 5 | #endif 6 | 7 | NAMESPACE_BEGIN(layer) 8 | 9 | std::string timeString(Float time, bool precise) { 10 | if (std::isnan(time)) 11 | return "nan"; 12 | else if (std::isinf(time)) 13 | return "inf"; 14 | 15 | char suffix = 's'; 16 | if (time > 60) { 17 | time /= 60; suffix = 'm'; 18 | if (time > 60) { 19 | time /= 60; suffix = 'h'; 20 | if (time > 12) { 21 | time /= 12; suffix = 'd'; 22 | } 23 | } 24 | } 25 | 26 | std::ostringstream os; 27 | os.precision(precise ? 4 : 1); 28 | os << std::fixed << time << suffix; 29 | 30 | return os.str(); 31 | } 32 | 33 | std::string memString(size_t size, bool precise) { 34 | Float value = (Float) size; 35 | const char *suffixes[] = { 36 | "B", "KiB", "MiB", "GiB", "TiB", "PiB" 37 | }; 38 | int suffix = 0; 39 | while (suffix < 5 && value > 1024.0f) { 40 | value /= 1024.0f; ++suffix; 41 | } 42 | 43 | std::ostringstream os; 44 | os.precision(suffix == 0 ? 0 : (precise ? 4 : 1)); 45 | os << std::fixed << value << " " << suffixes[suffix]; 46 | 47 | return os.str(); 48 | } 49 | 50 | #if defined(__WINDOWS__) 51 | std::string lastErrorText() { 52 | DWORD errCode = GetLastError(); 53 | char *errorText = NULL; 54 | if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER 55 | | FORMAT_MESSAGE_FROM_SYSTEM 56 | | FORMAT_MESSAGE_IGNORE_INSERTS, 57 | NULL, 58 | errCode, 59 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 60 | (LPTSTR)&errorText, 61 | 0, 62 | NULL)) { 63 | return "Internal error while looking up an error code"; 64 | } 65 | std::string result(errorText); 66 | LocalFree(errorText); 67 | return result; 68 | } 69 | #endif 70 | 71 | NAMESPACE_END(layer) 72 | -------------------------------------------------------------------------------- /src/microfacet.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | hg.cpp -- Henyey-Greenstein model evaluation routines 3 | 4 | Copyright (c) 2015 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | NAMESPACE_BEGIN(layer) 19 | 20 | static int expcosCoefficientCount(Float B, Float relerr) { 21 | Float prod = 1, invB = 1.0f / B; 22 | if (B == 0) 23 | return 1; 24 | 25 | for (int i=0; ; ++i) { 26 | prod /= 1 + i * invB; 27 | 28 | if (prod < relerr) 29 | return i+1; 30 | } 31 | } 32 | 33 | static Float modBesselRatio(Float B, Float k) { 34 | const Float eps = std::numeric_limits::epsilon(), 35 | invTwoB = 2.0f / B; 36 | 37 | Float i = (Float) k, 38 | D = 1 / (invTwoB * i++), 39 | Cd = D, C = Cd; 40 | 41 | while (std::abs(Cd) > eps * std::abs(C)) { 42 | Float coeff = invTwoB * i++; 43 | D = 1 / (D + coeff); 44 | Cd *= coeff*D - 1; 45 | C += Cd; 46 | } 47 | 48 | return C; 49 | } 50 | 51 | void expCosFourierSeries(Float A, Float B, Float relerr, std::vector &coeffs) { 52 | /* Determine the required number of coefficients and allocate memory */ 53 | int n = expcosCoefficientCount(B, relerr); 54 | coeffs.resize(n); 55 | 56 | /* Determine the last ratio and work downwards */ 57 | coeffs[n-1] = modBesselRatio(B, n - 1); 58 | for (int i=n-2; i>0; --i) 59 | coeffs[i] = B / (2*i + B*coeffs[i+1]); 60 | 61 | /* Evaluate the exponentially scaled I0 and correct scaling */ 62 | coeffs[0] = math::i0e(B) * std::exp(A+B); 63 | 64 | /* Apply the ratios & factor of two upwards */ 65 | Float prod = 2*coeffs[0]; 66 | for (int i=1; i eta_, 96 | Float alpha, Float phi_d) { 97 | Float sinThetaI = math::safe_sqrt(1-mu_i*mu_i), 98 | sinThetaO = math::safe_sqrt(1-mu_o*mu_o), 99 | cosPhi = std::cos(phi_d), 100 | sinPhi = std::sin(phi_d); 101 | 102 | Vector wi(-sinThetaI, 0, -mu_i); 103 | Vector wo(sinThetaO*cosPhi, sinThetaO*sinPhi, mu_o); 104 | bool reflect = -mu_i*mu_o > 0; 105 | 106 | if (mu_o == 0 || mu_i == 0) 107 | return 0.f; 108 | 109 | bool conductor = eta_.imag() != 0.0f; 110 | if (conductor && !reflect) 111 | return 0.0f; 112 | std::complex eta = 113 | (-mu_i > 0 || conductor) ? eta_ : std::complex(1) / eta_; 114 | 115 | Vector H = (wi + wo * (reflect ? 1.0f : eta.real())).normalized(); 116 | H *= math::signum(Frame::cosTheta(H)); 117 | 118 | Float cosThetaH2 = Frame::cosTheta2(H), 119 | exponent = -Frame::tanTheta2(H) / (alpha*alpha), 120 | D = std::exp(exponent) / (math::Pi * alpha*alpha * cosThetaH2*cosThetaH2), 121 | F = !conductor ? fresnelDielectric(wi.dot(H), eta_.real()) 122 | : fresnelConductor(std::abs(wi.dot(H)), eta), 123 | G = smithG1(wi, H, alpha) * smithG1(wo, H, alpha); 124 | 125 | if (reflect) { 126 | return F * D * G / (4.0f * std::abs(mu_i*mu_o)); 127 | } else { 128 | Float sqrtDenom = wi.dot(H) + eta.real() * wo.dot(H); 129 | 130 | return std::abs(((1 - F) * D * G * eta.real() * eta.real() * wi.dot(H) 131 | * wo.dot(H)) / (mu_i*mu_o * sqrtDenom * sqrtDenom)); 132 | } 133 | } 134 | 135 | Float microfacetNoExp(Float mu_o, Float mu_i, std::complex eta_, 136 | Float alpha, Float phi_d) { 137 | Float sinThetaI = math::safe_sqrt(1-mu_i*mu_i), 138 | sinThetaO = math::safe_sqrt(1-mu_o*mu_o), 139 | cosPhi = std::cos(phi_d), 140 | sinPhi = std::sin(phi_d); 141 | 142 | Vector wi(-sinThetaI, 0, -mu_i); 143 | Vector wo(sinThetaO*cosPhi, sinThetaO*sinPhi, mu_o); 144 | bool reflect = -mu_i*mu_o > 0; 145 | 146 | if (mu_o == 0 || mu_i == 0) 147 | return 0.f; 148 | 149 | bool conductor = eta_.imag() != 0.0f; 150 | if (conductor && !reflect) 151 | return 0.0f; 152 | std::complex eta = 153 | (-mu_i > 0 || conductor) ? eta_ : std::complex(1) / eta_; 154 | 155 | Vector H = (wi + wo * (reflect ? 1.0f : eta.real())).normalized(); 156 | H *= math::signum(Frame::cosTheta(H)); 157 | 158 | Float cosThetaH2 = Frame::cosTheta2(H), 159 | D = (Float) 1 / (math::Pi * alpha*alpha * cosThetaH2*cosThetaH2), 160 | F = !conductor ? fresnelDielectric(wi.dot(H), eta_.real()) 161 | : fresnelConductor(std::abs(wi.dot(H)), eta), 162 | G = smithG1(wi, H, alpha) * smithG1(wo, H, alpha); 163 | 164 | if (reflect) { 165 | return F * D * G / (4.0f * std::abs(mu_i*mu_o)); 166 | } else { 167 | Float sqrtDenom = wi.dot(H) + eta.real() * wo.dot(H); 168 | 169 | return std::abs(((1 - F) * D * G * eta.real() * eta.real() * wi.dot(H) 170 | * wo.dot(H)) / (mu_i*mu_o * sqrtDenom * sqrtDenom)); 171 | } 172 | } 173 | 174 | static Float Bmax(size_t n, Float relerr) { 175 | if (relerr >= 1e-1f) 176 | return 0.1662f*std::pow((Float) n, (Float) 2.05039); 177 | else if (relerr >= 1e-2f) 178 | return 0.0818f*std::pow((Float) n, (Float) 2.04982); 179 | else if (relerr >= 1e-3f) 180 | return 0.0538f*std::pow((Float) n, (Float) 2.05001); 181 | else if (relerr >= 1e-4f) 182 | return 0.0406f*std::pow((Float) n, (Float) 2.04686); 183 | else if (relerr >= 1e-5f) 184 | return 0.0337f*std::pow((Float) n, (Float) 2.03865); 185 | else if (relerr >= 1e-6f) 186 | return 0.0299f*std::pow((Float) n, (Float) 2.02628); 187 | else 188 | throw std::runtime_error("Bmax(): unknown relative error bound!"); 189 | } 190 | 191 | void microfacetNoExpFourierSeries(Float mu_o, Float mu_i, std::complex eta_, 192 | Float alpha, size_t n, Float phiMax, 193 | std::vector &result) { 194 | 195 | bool reflect = -mu_i * mu_o > 0; 196 | 197 | Float sinMu2 = math::safe_sqrt((1 - mu_i * mu_i) * (1 - mu_o * mu_o)), 198 | phiCritical = 0.0f; 199 | 200 | bool conductor = (eta_.imag() != 0.0f); 201 | std::complex eta = 202 | (-mu_i > 0 || conductor) ? eta_ : std::complex(1) / eta_; 203 | 204 | if (reflect) { 205 | if (!conductor) 206 | phiCritical = math::safe_acos((2*eta.real()*eta.real()-mu_i*mu_o-1)/sinMu2); 207 | } else if (!reflect) { 208 | if (conductor) 209 | throw std::runtime_error("lowfreqFourierSeries(): encountered refraction case for a conductor"); 210 | Float etaDenser = (eta.real() > 1 ? eta.real() : 1 / eta.real()); 211 | phiCritical = math::safe_acos((1 - etaDenser * mu_i * mu_o) / 212 | (etaDenser * sinMu2)); 213 | } 214 | 215 | if (!conductor && phiCritical > math::Epsilon && 216 | phiCritical < math::Pi - math::Epsilon && 217 | phiCritical < phiMax - math::Epsilon) { 218 | /* Uh oh, some high frequency content leaked in the generally low frequency part. 219 | Increase the number of coefficients so that we can capture it. Fortunately, this 220 | happens very rarely. */ 221 | n = std::max(n, (size_t) 100); 222 | } 223 | 224 | VectorX coeffs(n); 225 | coeffs.setZero(); 226 | std::function integrand = std::bind( 227 | µfacetNoExp, mu_o, mu_i, eta_, alpha, std::placeholders::_1); 228 | 229 | const int nEvals = 200; 230 | if (reflect) { 231 | if (phiCritical > math::Epsilon && phiCritical < phiMax-math::Epsilon) { 232 | filonIntegrate(integrand, coeffs.data(), n, nEvals, 0, phiCritical); 233 | filonIntegrate(integrand, coeffs.data(), n, nEvals, phiCritical, phiMax); 234 | } else { 235 | filonIntegrate(integrand, coeffs.data(), n, nEvals, 0, phiMax); 236 | } 237 | } else { 238 | filonIntegrate(integrand, coeffs.data(), n, nEvals, 0, 239 | std::min(phiCritical, phiMax)); 240 | } 241 | 242 | if (phiMax < math::Pi - math::Epsilon) { 243 | /* Precompute some sines and cosines */ 244 | VectorX cosPhi(n), sinPhi(n); 245 | for (size_t i=0; i eta_, 296 | Float alpha, size_t n, Float relerr, 297 | std::vector &result) { 298 | bool reflect = -mu_i * mu_o > 0; 299 | 300 | /* Compute the 'A' and 'B' constants, as well as the critical azimuth */ 301 | Float A, B; 302 | Float sinMu2 = math::safe_sqrt((1 - mu_i * mu_i) * (1 - mu_o * mu_o)); 303 | 304 | bool conductor = (eta_.imag() != 0.0f); 305 | std::complex eta = 306 | (-mu_i > 0 || conductor) ? eta_ : std::complex(1) / eta_; 307 | 308 | if (reflect) { 309 | Float temp = 1.0f / (alpha * (mu_i - mu_o)); 310 | A = (mu_i * mu_i + mu_o * mu_o - 2) * temp * temp; 311 | B = 2 * sinMu2 * temp * temp; 312 | } else { 313 | if (conductor) { 314 | /* No refraction in conductors */ 315 | result.clear(); 316 | result.push_back(0.0f); 317 | return; 318 | } else { 319 | Float temp = 1.0f / (alpha * (mu_i - eta.real() * mu_o)); 320 | A = (mu_i * mu_i - 1 + eta.real() * eta.real() * (mu_o * mu_o - 1)) * temp * temp; 321 | B = 2 * eta.real() * sinMu2 * temp * temp; 322 | } 323 | } 324 | 325 | /* Minor optimization: don't even bother computing the Fourier series 326 | if the contribution to the scattering model is miniscule */ 327 | if (math::i0e(B) * std::exp(A+B) < 1e-10) { 328 | result.clear(); 329 | result.push_back(0.0f); 330 | return; 331 | } 332 | 333 | Float B_max = Bmax(n, relerr); 334 | if (B > B_max) { 335 | A = A + B - B_max + std::log(math::i0e(B) / math::i0e(B_max)); 336 | B = B_max; 337 | } 338 | 339 | std::vector lowfreq_coeffs, expcos_coeffs; 340 | 341 | /* Compute Fourier coefficients of the exponential term */ 342 | expCosFourierSeries(A, B, relerr, expcos_coeffs); 343 | 344 | /* Compute Fourier coefficients of the low-frequency term 345 | Only fit in the region where the result is actually 346 | going to make some sort of difference given the convolution 347 | with expcos_coeffs */ 348 | Float phiMax = math::safe_acos(1 + std::log(relerr) / B); 349 | 350 | microfacetNoExpFourierSeries(mu_o, mu_i, eta_, alpha, 351 | 12, phiMax, lowfreq_coeffs); 352 | 353 | /* Perform discrete circular convolution of the two series */ 354 | result.resize(lowfreq_coeffs.size() + expcos_coeffs.size() - 1); 355 | 356 | convolveFourier(lowfreq_coeffs.data(), lowfreq_coeffs.size(), 357 | expcos_coeffs.data(), expcos_coeffs.size(), result.data()); 358 | 359 | /* Truncate the series if error bounds are satisfied */ 360 | for (size_t i=0; i 2 | #include 3 | #include 4 | 5 | #if defined(__LINUX__) || defined(__OSX__) 6 | # include 7 | # include 8 | #elif defined(__WINDOWS__) 9 | # include 10 | #endif 11 | 12 | NAMESPACE_BEGIN(layer) 13 | 14 | struct MemoryMappedFile::MemoryMappedFilePrivate { 15 | fs::path filename; 16 | #if defined(__WINDOWS__) 17 | HANDLE file; 18 | HANDLE fileMapping; 19 | #endif 20 | size_t size; 21 | void *data; 22 | bool readOnly; 23 | bool temp; 24 | 25 | MemoryMappedFilePrivate(const fs::path &f = "", size_t s = 0) 26 | : filename(f), size(s), data(NULL), readOnly(false), temp(false) {} 27 | 28 | void create() { 29 | #if defined(__LINUX__) || defined(__OSX__) 30 | int fd = open(filename.str().c_str(), O_RDWR | O_CREAT | O_TRUNC, 0664); 31 | if (fd == -1) 32 | Error("Could not open \"%s\"!", filename.str()); 33 | int result = lseek(fd, size-1, SEEK_SET); 34 | if (result == -1) 35 | Error("Could not set file size of \"%s\"!", filename.str()); 36 | result = write(fd, "", 1); 37 | if (result != 1) 38 | Error("Could not write to \"%s\"!", filename.str()); 39 | data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 40 | if (data == NULL) 41 | Error("Could not map \"%s\" to memory!", filename.str()); 42 | if (close(fd) != 0) 43 | Error("close(): unable to close file!"); 44 | #elif defined(__WINDOWS__) 45 | file = CreateFileW(filename.wstr().c_str(), GENERIC_WRITE | GENERIC_READ, 46 | FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 47 | if (file == INVALID_HANDLE_VALUE) 48 | Error("Could not open \"%s\": %s", filename.str(), 49 | lastErrorText()); 50 | fileMapping = CreateFileMapping(file, NULL, PAGE_READWRITE, 0, 51 | static_cast(size), NULL); 52 | if (fileMapping == NULL) 53 | Error("CreateFileMapping: Could not map \"%s\" to memory: %s", 54 | filename.str(), lastErrorText()); 55 | data = (void *) MapViewOfFile(fileMapping, FILE_MAP_WRITE, 0, 0, 0); 56 | if (data == NULL) 57 | Error("MapViewOfFile: Could not map \"%s\" to memory: %s", 58 | filename.str(), lastErrorText()); 59 | #endif 60 | readOnly = false; 61 | } 62 | 63 | void createTemp() { 64 | readOnly = false; 65 | temp = true; 66 | 67 | #if defined(__LINUX__) || defined(__OSX__) 68 | char *path = strdup("/tmp/mmap_XXXXXX"); 69 | int fd = mkstemp(path); 70 | if (fd == -1) 71 | Error("Unable to create temporary file (1): %s", strerror(errno)); 72 | filename = path; 73 | free(path); 74 | 75 | int result = lseek(fd, size-1, SEEK_SET); 76 | if (result == -1) 77 | Error("Could not set file size of \"%s\"!", filename.str()); 78 | result = write(fd, "", 1); 79 | if (result != 1) 80 | Error("Could not write to \"%s\"!", filename.str()); 81 | 82 | data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 83 | if (data == NULL) 84 | Error("Could not map \"%s\" to memory!", filename.str()); 85 | 86 | if (close(fd) != 0) 87 | Error("close(): unable to close file!"); 88 | #elif defined(__WINDOWS__) 89 | WCHAR tempPath[MAX_PATH]; 90 | WCHAR tempFilename[MAX_PATH]; 91 | 92 | unsigned int ret = GetTempPathW(MAX_PATH, tempPath); 93 | if (ret == 0 || ret > MAX_PATH) 94 | Error("GetTempFileName failed(): %s", lastErrorText()); 95 | 96 | ret = GetTempFileNameW(tempPath, L"mitsuba", 0, tempFilename); 97 | if (ret == 0) 98 | Error("GetTempFileName failed(): %s", lastErrorText()); 99 | 100 | file = CreateFileW(tempFilename, GENERIC_READ | GENERIC_WRITE, 101 | 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 102 | 103 | if (file == INVALID_HANDLE_VALUE) 104 | Error("Error while trying to create temporary file: %s", 105 | lastErrorText()); 106 | 107 | filename = fs::path(tempFilename); 108 | 109 | fileMapping = CreateFileMapping(file, NULL, PAGE_READWRITE, 0, 110 | static_cast(size), NULL); 111 | if (fileMapping == NULL) 112 | Error("CreateFileMapping: Could not map \"%s\" to memory: %s", 113 | filename.str(), lastErrorText()); 114 | data = (void *) MapViewOfFile(fileMapping, FILE_MAP_WRITE, 0, 0, 0); 115 | if (data == NULL) 116 | Error("MapViewOfFile: Could not map \"%s\" to memory: %s", 117 | filename.str(), lastErrorText()); 118 | #endif 119 | } 120 | 121 | void map() { 122 | if (!filename.exists()) 123 | Error("The file \"%s\" does not exist!", filename.str()); 124 | size = filename.file_size(); 125 | 126 | #if defined(__LINUX__) || defined(__OSX__) 127 | int fd = open(filename.str().c_str(), readOnly ? O_RDONLY : O_RDWR); 128 | if (fd == -1) 129 | Error("Could not open \"%s\"!", filename.str()); 130 | data = mmap(NULL, size, PROT_READ | (readOnly ? 0 : PROT_WRITE), MAP_SHARED, fd, 0); 131 | if (data == NULL) 132 | Error("Could not map \"%s\" to memory!", filename.str()); 133 | if (close(fd) != 0) 134 | Error("close(): unable to close file!"); 135 | #elif defined(__WINDOWS__) 136 | file = CreateFileW(filename.wstr().c_str(), GENERIC_READ | (readOnly ? 0 : GENERIC_WRITE), 137 | FILE_SHARE_WRITE|FILE_SHARE_READ, NULL, OPEN_EXISTING, 138 | FILE_ATTRIBUTE_NORMAL, NULL); 139 | if (file == INVALID_HANDLE_VALUE) 140 | Error("Could not open \"%s\": %s", filename.str(), 141 | lastErrorText()); 142 | fileMapping = CreateFileMapping(file, NULL, readOnly ? PAGE_READONLY : PAGE_READWRITE, 0, 0, NULL); 143 | if (fileMapping == NULL) 144 | Error("CreateFileMapping: Could not map \"%s\" to memory: %s", 145 | filename.str(), lastErrorText()); 146 | data = (void *) MapViewOfFile(fileMapping, readOnly ? FILE_MAP_READ : FILE_MAP_WRITE, 0, 0, 0); 147 | if (data == NULL) 148 | Error("MapViewOfFile: Could not map \"%s\" to memory: %s", 149 | filename.str(), lastErrorText()); 150 | #endif 151 | } 152 | 153 | void unmap() { 154 | Trace("Unmapping \"%s\" from memory", filename.str()); 155 | 156 | #if defined(__LINUX__) || defined(__OSX__) 157 | if (temp) { 158 | /* Temporary file that will be deleted in any case: 159 | invalidate dirty pages to avoid a costly flush to disk */ 160 | int retval = msync(data, size, MS_INVALIDATE); 161 | if (retval != 0) 162 | Error("munmap(): unable to unmap memory: %s", strerror(errno)); 163 | } 164 | 165 | int retval = munmap(data, size); 166 | if (retval != 0) 167 | Error("munmap(): unable to unmap memory: %s", strerror(errno)); 168 | #elif defined(__WINDOWS__) 169 | if (!UnmapViewOfFile(data)) 170 | Error("UnmapViewOfFile(): unable to unmap memory: %s", lastErrorText()); 171 | if (!CloseHandle(fileMapping)) 172 | Error("CloseHandle(): unable to close file mapping: %s", lastErrorText()); 173 | if (!CloseHandle(file)) 174 | Error("CloseHandle(): unable to close file: %s", lastErrorText()); 175 | #endif 176 | 177 | if (temp) { 178 | try { 179 | filename.remove_file(); 180 | } catch (...) { 181 | Warn("unmap(): Unable to delete file \"%s\"", filename.str()); 182 | } 183 | } 184 | 185 | data = NULL; 186 | size = 0; 187 | } 188 | }; 189 | 190 | MemoryMappedFile::MemoryMappedFile() 191 | : d(new MemoryMappedFilePrivate()) { } 192 | 193 | MemoryMappedFile::MemoryMappedFile(const fs::path &filename, size_t size) 194 | : d(new MemoryMappedFilePrivate(filename, size)) { 195 | Trace("Creating memory-mapped file \"%s\" (%s)..", 196 | filename.str(), memString(d->size)); 197 | d->create(); 198 | } 199 | 200 | MemoryMappedFile::MemoryMappedFile(const fs::path &filename, bool readOnly) 201 | : d(new MemoryMappedFilePrivate(filename)) { 202 | d->readOnly = readOnly; 203 | d->map(); 204 | Trace("Mapped \"%s\" into memory (%s)..", 205 | filename.str(), memString(d->size)); 206 | } 207 | 208 | MemoryMappedFile::~MemoryMappedFile() { 209 | if (d->data) { 210 | try { 211 | d->unmap(); 212 | } catch (std::exception &e) { 213 | /* Don't throw exceptions from a destructor */ 214 | Warn("%s", e.what()); 215 | } 216 | } 217 | } 218 | 219 | void MemoryMappedFile::resize(size_t size) { 220 | if (!d->data) 221 | Error("Internal error in MemoryMappedFile::resize()!"); 222 | bool temp = d->temp; 223 | d->temp = false; 224 | d->unmap(); 225 | d->filename.resize_file(size); 226 | d->size = size; 227 | d->map(); 228 | d->temp = temp; 229 | } 230 | 231 | void *MemoryMappedFile::data() { 232 | return d->data; 233 | } 234 | 235 | /// Return a pointer to the file contents in memory (const version) 236 | const void *MemoryMappedFile::data() const { 237 | return d->data; 238 | } 239 | 240 | size_t MemoryMappedFile::size() const { 241 | return d->size; 242 | } 243 | 244 | bool MemoryMappedFile::readOnly() const { 245 | return d->readOnly; 246 | } 247 | 248 | const fs::path &MemoryMappedFile::filename() const { 249 | return d->filename; 250 | } 251 | 252 | MemoryMappedFile *MemoryMappedFile::createTemporary(size_t size) { 253 | MemoryMappedFile *result = new MemoryMappedFile(); 254 | result->d->size = size; 255 | result->d->createTemp(); 256 | return result; 257 | } 258 | 259 | std::string MemoryMappedFile::toString() const { 260 | std::ostringstream oss; 261 | oss << "MemoryMappedFile[filename=\"" 262 | << d->filename.str() << "\", size=" 263 | << memString(d->size) << "]"; 264 | return oss.str(); 265 | } 266 | 267 | NAMESPACE_END(layer) 268 | -------------------------------------------------------------------------------- /src/py_filesystem.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "python.h" 7 | 8 | void python_export_filesystem(py::module &m) { 9 | using namespace fs; 10 | 11 | py::module fs_module = 12 | m.def_submodule("filesystem", "Cross-platform filesystem support"); 13 | 14 | py::class_ path_class(fs_module, "path"); 15 | 16 | path_class 17 | .def(py::init<>()) 18 | .def(py::init()) 19 | .def(py::init()) 20 | .def("__len__", &path::length) 21 | .def("file_size", &path::file_size) 22 | .def("empty", &path::empty) 23 | .def("is_absolute", &path::is_absolute) 24 | .def("make_absolute", &path::make_absolute) 25 | .def("exists", &path::exists) 26 | .def("is_directory", &path::is_directory) 27 | .def("is_file", &path::is_file) 28 | .def("extension", &path::extension) 29 | .def("parent_path", &path::parent_path) 30 | .def("remove_file", &path::remove_file) 31 | .def("resize_file", &path::resize_file) 32 | .def("str", [](const path &p) { return p.str(); }) 33 | .def("str", [](const path &p, path::path_type t) { return p.str(t); }) 34 | .def("set", [](path &p, const std::string &v, path::path_type t) { p.set(v, t); }) 35 | .def("set", [](path &p, const std::string &v) { p.set(v); }) 36 | .def(py::self / py::self) 37 | .def("__repr__", [](const path &p) { return p.str(); }) 38 | .def_static("getcwd", &path::getcwd); 39 | 40 | py::enum_(path_class, "path_type") 41 | .value("windows_path", path::windows_path) 42 | .value("posix_path", path::posix_path) 43 | .value("native_path", path::native_path) 44 | .export_values(); 45 | 46 | py::class_(fs_module, "resolver") 47 | .def(py::init<>()) 48 | .def("__len__", &resolver::size) 49 | .def("append", &resolver::append) 50 | .def("prepend", &resolver::prepend) 51 | .def("resolve", &resolver::resolve) 52 | .def("__getitem__", [](const resolver &r, size_t i) { 53 | if (i >= r.size()) 54 | throw py::index_error(); 55 | return r[i]; 56 | }) 57 | .def("__setitem__", [](resolver &r, size_t i, path &v) { 58 | if (i >= r.size()) 59 | throw py::index_error(); 60 | r[i] = v; 61 | }) 62 | .def("__delitem__", [](resolver &r, size_t i) { 63 | if (i >= r.size()) 64 | throw py::index_error(); 65 | r.erase(r.begin() + i); 66 | }) 67 | .def("__repr__", [](const resolver &r) { 68 | std::ostringstream oss; 69 | oss << r; 70 | return oss.str(); 71 | }); 72 | 73 | py::class_(fs_module, "MemoryMappedFile", py::buffer_protocol()) 74 | .def(py::init()) 75 | .def(py::init()) 76 | .def(py::init()) 77 | .def("resize", &MemoryMappedFile::resize) 78 | .def("__repr__", &MemoryMappedFile::toString) 79 | .def("size", &MemoryMappedFile::size) 80 | .def("filename", &MemoryMappedFile::filename) 81 | .def("readOnly", &MemoryMappedFile::readOnly) 82 | .def_static("createTemporary", &MemoryMappedFile::createTemporary) 83 | .def_buffer([](MemoryMappedFile &m) -> py::buffer_info { 84 | return py::buffer_info( 85 | m.data(), 86 | sizeof(uint8_t), 87 | py::format_descriptor::format(), 88 | 1, 89 | { (size_t) m.size() }, 90 | { sizeof(uint8_t) } 91 | ); 92 | }); 93 | 94 | py::implicitly_convertible(); 95 | } 96 | -------------------------------------------------------------------------------- /src/py_fourier.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "python.h" 5 | 6 | using namespace layer; 7 | 8 | void python_export_fourier(py::module &m_) { 9 | py::module m= m_.def_submodule("fourier", "Functions for sampling and evaluating Fourier series"); 10 | 11 | /* fourier.h bindings */ 12 | m.def("filonIntegrate", [](const std::function &f, int nCoeffs, int nEvals, Float a, Float b) { 13 | std::vector coeffs(nCoeffs, 0); 14 | filonIntegrate(f, &coeffs[0], nCoeffs, nEvals, a, b); 15 | return coeffs; 16 | }, D(filonIntegrate), py::arg("f"), py::arg("nCoeffs"), py::arg("nEvals"), py::arg("a") = 0, py::arg("b") = math::Pi); 17 | 18 | m.def("convolveFourier", [](const std::vector &a, const std::vector &b) { 19 | std::vector c(a.size() + b.size() - 1); 20 | convolveFourier(&a[0], (int) a.size(), &b[0], (int) b.size(), &c[0]); 21 | return c; 22 | }, D(convolveFourier), py::arg("a"), py::arg("b")); 23 | 24 | m.def("evalFourier", [](const std::vector &coeffs, py::array_t phi) { 25 | float *temp = fourier_aligned_alloca(coeffs.size() * sizeof(float)); 26 | memcpy(temp, &coeffs[0], sizeof(float) * coeffs.size()); 27 | return py::vectorize([&](Float phi) { return evalFourier(temp, coeffs.size(), phi); })(phi); 28 | }, D(evalFourier), py::arg("coeffs"), py::arg("phi")); 29 | 30 | m.def("evalFourier3", [](const std::vector> &coeffs, Float phi) { 31 | if (coeffs.size() != 3) 32 | throw std::runtime_error("Incompatible input"); 33 | size_t size = 34 | std::max({ coeffs[0].size(), coeffs[1].size(), coeffs[0].size() }); 35 | float *temp[3]; 36 | for (int i=0; i<3; ++i) { 37 | temp[i] = fourier_aligned_alloca(size * sizeof(float)); 38 | memcpy(temp[i], &coeffs[i][0], sizeof(float) * coeffs[i].size()); 39 | } 40 | return evalFourier3(temp, coeffs[0].size(), phi); 41 | }, D(evalFourier3), py::arg("coeffs"), py::arg("phi")); 42 | } 43 | -------------------------------------------------------------------------------- /src/py_fresnel.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "python.h" 3 | 4 | void python_export_fresnel(py::module &m) { 5 | /* fresnel.h bindings */ 6 | m.def("fresnelConductor", &fresnelConductor, D(fresnelConductor), 7 | py::arg("cosThetaI"), py::arg("eta")); 8 | m.def("fresnelDielectric", [](Float cosThetaI, Float eta) { 9 | Float cosThetaT; 10 | Float F = fresnelDielectric(cosThetaI, cosThetaT, eta); 11 | return std::make_pair(F, cosThetaT); 12 | }, D(fresnelDielectric), py::arg("cosThetaI"), py::arg("eta")); 13 | m.def("fresnelConductorIntegral", &fresnelConductorIntegral, D(fresnelConductorIntegral)); 14 | m.def("fresnelDielectricIntegral", &fresnelDielectricIntegral, D(fresnelDielectricIntegral)); 15 | } 16 | -------------------------------------------------------------------------------- /src/py_layer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "python.h" 8 | 9 | void python_export_layer(py::module &m) { 10 | /* hg.h bindings */ 11 | m.def("hg", py::vectorize(&hg), D(hg), py::arg("mu_o"), py::arg("mu_i"), py::arg("g"), 12 | py::arg("phi_d")); 13 | 14 | m.def("hgFourierSeries", [](Float mu_o, Float mu_i, Float g, int kmax, Float relerr) { 15 | std::vector result; 16 | hgFourierSeries(mu_o, mu_i, g, kmax, relerr, result); 17 | return result; 18 | }, D(hgFourierSeries), py::arg("mu_o"), py::arg("mu_i"), py::arg("g"), py::arg("kmax"), py::arg("relerr")); 19 | 20 | /* microfacet.h bindings */ 21 | m.def("smithG1", &smithG1, D(smithG1), py::arg("v"), py::arg("m"), py::arg("alpha")); 22 | m.def("microfacetNoExp", py::vectorize(µfacetNoExp), D(microfacetNoExp), py::arg("mu_o"), 23 | py::arg("mu_i"), py::arg("eta"), py::arg("alpha"), py::arg("phi_d")); 24 | 25 | m.def("microfacet", py::vectorize(µfacet), D(microfacet), py::arg("mu_o"), 26 | py::arg("mu_i"), py::arg("eta"), py::arg("alpha"), py::arg("phi_d")); 27 | 28 | m.def("microfacetNoExpFourierSeries", 29 | [](Float mu_o, Float mu_i, std::complex eta, Float alpha, 30 | int n, Float phiMax) { 31 | std::vector result; 32 | microfacetNoExpFourierSeries(mu_o, mu_i, eta, alpha, n, phiMax, result); 33 | return result; 34 | }, D(microfacetNoExpFourierSeries), py::arg("mu_o"), py::arg("mu_i"), 35 | py::arg("eta"), py::arg("alpha"), py::arg("n"), py::arg("phiMax")); 36 | 37 | m.def("microfacetFourierSeries", 38 | [](Float mu_o, Float mu_i, std::complex eta, Float alpha, 39 | int n, Float relerr) { 40 | std::vector result; 41 | microfacetFourierSeries(mu_o, mu_i, eta, alpha, n, relerr, result); 42 | return result; 43 | }, D(microfacetFourierSeries), py::arg("mu_o"), py::arg("mu_i"), 44 | py::arg("eta"), py::arg("alpha"), py::arg("n"), py::arg("relerr")); 45 | 46 | m.def("expCosFourierSeries", [](Float A, Float B, Float relerr) { 47 | std::vector result; 48 | expCosFourierSeries(A, B, relerr, result); 49 | return result; 50 | }, D(expCosFourierSeries), py::arg("A"), py::arg("B"), py::arg("relerr")); 51 | 52 | py::class_(m, "LayerMode") 53 | .def(py::init(), D(LayerMode, LayerMode)) 54 | .def(py::init<>()) 55 | .def("reverse", &LayerMode::reverse, D(LayerMode, reverse)) 56 | .def("clear", &LayerMode::clear, D(LayerMode, clear)) 57 | .def("nonZeros", &LayerMode::nonZeros, D(LayerMode, nonZeros)) 58 | .def("__repr__", &LayerMode::toString, D(LayerMode, toString)) 59 | .def_property_readonly("reflectionTop", [](const LayerMode &m) -> MatrixX { return m.reflectionTop; }, D(LayerMode, reflectionTop)) 60 | .def_property_readonly("reflectionBottom", [](const LayerMode &m) -> MatrixX { return m.reflectionBottom; }, D(LayerMode, reflectionBottom)) 61 | .def_property_readonly("transmissionTopBottom", [](const LayerMode &m) -> MatrixX { return m.transmissionTopBottom; }, D(LayerMode, transmissionTopBottom)) 62 | .def_property_readonly("transmissionBottomTop", [](const LayerMode &m) -> MatrixX { return m.transmissionBottomTop; }, D(LayerMode, transmissionBottomTop)); 63 | 64 | py::class_(m, "Layer", D(Layer)) 65 | .def(py::init(), py::arg("nodes"), py::arg("weights"), py::arg("nFourierOrders") = 1) 66 | .def(py::init()) 67 | .def("reverse", &Layer::reverse, D(Layer, reverse)) 68 | .def("clear", &Layer::clear, D(Layer, clear)) 69 | .def("setDiffuse", &Layer::setDiffuse, D(Layer, setDiffuse), py::arg("albedo")) 70 | .def("setHenyeyGreenstein", &Layer::setHenyeyGreenstein, D(Layer, setHenyeyGreenstein), py::arg("albedo"), py::arg("g")) 71 | .def("setIsotropic", &Layer::setIsotropic, D(Layer, setIsotropic), py::arg("albedo")) 72 | .def("setVonMisesFisher", &Layer::setVonMisesFisher, D(Layer, setVonMisesFisher), py::arg("albedo"), py::arg("kappa")) 73 | .def("setMatusik", &Layer::setMatusik, D(Layer, setMatusik), py::arg("filename"), py::arg("channel"), py::arg("fourierOrders") = 0) 74 | .def("setMicrofacet", &Layer::setMicrofacet, D(Layer, setMicrofacet), 75 | py::arg("eta"), py::arg("alpha"), py::arg("conserveEnergy") = false, 76 | py::arg("fourierOrders") = 0) 77 | .def("__repr__", &Layer::toString, D(Layer, toString)) 78 | .def_static("add", [](const Layer &l1, const Layer &l2, bool homogeneous) { Layer l3(l1.nodes(), l1.weights()); Layer::add(l1, l2, l3, homogeneous); return l3; }, D(Layer, add)) 79 | .def_static("add", [](const Layer &l1, const Layer &l2) { Layer l3(l1.nodes(), l1.weights()); Layer::add(l1, l2, l3); return l3; }) 80 | .def("addToTop", [](Layer &l1, const Layer &l2, bool homogeneous) { l1.addToTop(l2, homogeneous); }, D(Layer, addToTop)) 81 | .def("addToTop", [](Layer &l1, const Layer &l2) { l1.addToTop(l2); }) 82 | .def("addToBottom", [](Layer &l1, const Layer &l2, bool homogeneous) { l1.addToBottom(l2, homogeneous); }, D(Layer, addToBottom)) 83 | .def("addToBottom", [](Layer &l1, const Layer &l2) { l1.addToBottom(l2); }) 84 | .def("expand", &Layer::expand, D(Layer, expand)) 85 | .def("eval", [](const Layer &l, py::array_t mu_o, py::array_t mu_i, py::array_t phi_d) { 86 | return py::vectorize([&l](Float mu_o, Float mu_i, Float phi_d) { return l.eval(mu_o, mu_i, phi_d); })(mu_o, mu_i, phi_d); 87 | }, py::arg("mu_o"), py::arg("mu_i"), py::arg("phi_d") = 0) 88 | .def("matrix", &Layer::matrix, py::arg("matrix") = 0) 89 | .def("__getitem__", [](Layer &m, size_t i) -> LayerMode& { 90 | if (i >= m.fourierOrders()) 91 | throw py::index_error(); 92 | return m[i]; 93 | }, D(Layer, operator_array), py::return_value_policy::reference_internal) 94 | .def_property_readonly("resolution", &Layer::resolution, D(Layer, resolution)) 95 | .def_property_readonly("fourierOrders", &Layer::fourierOrders, D(Layer, fourierOrders)) 96 | .def_property_readonly("weights", &Layer::weights, D(Layer, weights)) 97 | .def_property_readonly("nodes", &Layer::nodes, D(Layer, nodes)); 98 | 99 | py::class_(m, "BSDFStorage", D(BSDFStorage)) 100 | .def(py::init()) 101 | .def(py::init()) 102 | .def("close", &BSDFStorage::close, D(BSDFStorage, close)) 103 | .def_property_readonly("maxOrder", &BSDFStorage::maxOrder, D(BSDFStorage, maxOrder)) 104 | .def_property_readonly("channelCount", &BSDFStorage::channelCount, D(BSDFStorage, channelCount)) 105 | .def_property_readonly("nodeCount", &BSDFStorage::nodeCount, D(BSDFStorage, nodeCount)) 106 | .def_property_readonly("basisCount", &BSDFStorage::basisCount, D(BSDFStorage, basisCount)) 107 | .def_property_readonly("parameterCount", &BSDFStorage::parameterCount, D(BSDFStorage, parameterCount)) 108 | .def_property_readonly("size", &BSDFStorage::size, D(BSDFStorage, size)) 109 | .def_property_readonly("metadata", &BSDFStorage::metadata, D(BSDFStorage, metadata)) 110 | .def_property_readonly("eta", &BSDFStorage::eta, D(BSDFStorage, eta)) 111 | .def_property_readonly("extrapolated", &BSDFStorage::extrapolated, D(BSDFStorage, extrapolated)) 112 | .def("alpha", [](const BSDFStorage &m, size_t i) { 113 | if (i >= 2) 114 | throw py::index_error(); 115 | return m.alpha((int) i); 116 | }, D(BSDFStorage, alpha)) 117 | .def("setAlpha", [](BSDFStorage &m, size_t i, float v) { 118 | if (i >= 2) 119 | throw py::index_error(); 120 | m.setAlpha((int) i, v); 121 | }, D(BSDFStorage, setAlpha)) 122 | .def("parameterSampleCount", [](const BSDFStorage &m, size_t i) { 123 | if (i >= m.parameterCount()) 124 | throw py::index_error(); 125 | return m.parameterSampleCount(i); 126 | }, D(BSDFStorage, parameterSampleCount)) 127 | .def("parameterSamplePositions", [](const BSDFStorage &m, size_t i) { 128 | if (i >= m.parameterCount()) 129 | throw py::index_error(); 130 | py::list list; 131 | for (size_t j=0; j 2 | #include "python.h" 3 | 4 | void python_export_math(py::module &m) { 5 | /* math.h bindings */ 6 | py::module math = m.def_submodule("math", "Mathematical routines, special functions, etc."); 7 | 8 | math.def("signum", (double(*)(double)) math::signum, D(math, signum)); 9 | math.def("safe_acos", (double(*)(double)) math::safe_acos, D(math, safe_acos)); 10 | math.def("safe_asin", (double(*)(double)) math::safe_asin, D(math, safe_asin)); 11 | math.def("safe_sqrt", (double(*)(double)) math::safe_sqrt, D(math, safe_sqrt)); 12 | math.def("erf", (double(*)(double)) math::erf, D(math, erf)); 13 | math.def("erfinv", (double(*)(double)) math::erfinv, D(math, erfinv)); 14 | math.def("normal_quantile", (double(*)(double)) math::normal_quantile, D(math, normal_quantile)); 15 | math.def("normal_cdf", (double(*)(double)) math::normal_cdf, D(math, normal_cdf)); 16 | math.def("comp_ellint_1", (double(*)(double)) math::comp_ellint_1, D(math, comp_ellint_1)); 17 | math.def("comp_ellint_2", (double(*)(double)) math::comp_ellint_2, D(math, comp_ellint_2)); 18 | math.def("comp_ellint_3", (double(*)(double, double)) math::comp_ellint_3, D(math, comp_ellint_3)); 19 | math.def("ellint_1", (double(*)(double, double)) math::ellint_1, D(math, ellint_1)); 20 | math.def("ellint_2", (double(*)(double, double)) math::ellint_2, D(math, ellint_2)); 21 | math.def("ellint_3", (double(*)(double, double, double)) math::ellint_3, D(math, ellint_3)); 22 | math.def("i0e", (double(*)(double)) math::i0e, D(math, i0e)); 23 | math.def("legendre_p", (double(*)(int l, double)) math::legendre_p, D(math, legendre_p)); 24 | math.def("legendre_p", (double(*)(int, int, double)) math::legendre_p, D(math, legendre_p, 2)); 25 | math.def("legendre_pd", (std::pair(*)(int l, double)) math::legendre_pd, D(math, legendre_pd)); 26 | 27 | math.attr("E") = py::cast(math::E_d); 28 | math.attr("Pi") = py::cast(math::Pi_d); 29 | math.attr("InvPi") = py::cast(math::InvPi_d); 30 | math.attr("InvTwoPi") = py::cast(math::InvTwoPi_d); 31 | math.attr("InvFourPi") = py::cast(math::InvFourPi_d); 32 | math.attr("SqrtPi") = py::cast(math::SqrtPi_d); 33 | math.attr("InvSqrtPi") = py::cast(math::InvSqrtPi_d); 34 | math.attr("SqrtTwo") = py::cast(math::SqrtTwo_d); 35 | math.attr("InvSqrtTwo") = py::cast(math::InvSqrtTwo_d); 36 | math.attr("SqrtTwoPi") = py::cast(math::SqrtTwoPi_d); 37 | math.attr("InvSqrtTwoPi") = py::cast(math::InvSqrtTwoPi_d); 38 | math.attr("OneMinusEpsilon") = py::cast(math::OneMinusEpsilon); 39 | math.attr("RecipOverflow") = py::cast(math::RecipOverflow); 40 | math.attr("Infinity") = py::cast(math::Infinity); 41 | math.attr("MaxFloat") = py::cast(math::MaxFloat); 42 | math.attr("MachineEpsilon") = py::cast(math::MachineEpsilon); 43 | 44 | math.def("findInterval", [](size_t size, py::function pred) { 45 | return math::findInterval(size, [&](size_t i) { return pred(i).cast(); }); 46 | }, D(math, findInterval)); 47 | } 48 | -------------------------------------------------------------------------------- /src/py_quad.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "python.h" 4 | 5 | void python_export_quad(py::module &m_) { 6 | /* quad.h bindings */ 7 | py::module m = m_.def_submodule("quad", "Functions for numerical quadrature"); 8 | 9 | m.def("gaussLegendre", [](int n) { 10 | std::vector nodes(n), weights(n); 11 | quad::gaussLegendre(n, nodes.data(), weights.data()); 12 | return std::make_pair(py::array(n, nodes.data()), py::array(n, weights.data())); 13 | }, D(quad, gaussLegendre)); 14 | 15 | m.def("gaussLobatto", [](int n) { 16 | std::vector nodes(n), weights(n); 17 | quad::gaussLobatto(n, nodes.data(), weights.data()); 18 | return std::make_pair(py::array(n, nodes.data()), py::array(n, weights.data())); 19 | }, D(quad, gaussLobatto)); 20 | 21 | m.def("compositeSimpson", [](int n) { 22 | std::vector nodes(n), weights(n); 23 | quad::compositeSimpson(n, nodes.data(), weights.data()); 24 | return std::make_pair(py::array(n, nodes.data()), py::array(n, weights.data())); 25 | }, D(quad, compositeSimpson)); 26 | 27 | m.def("compositeSimpson38", [](int n) { 28 | std::vector nodes(n), weights(n); 29 | quad::compositeSimpson38(n, nodes.data(), weights.data()); 30 | return std::make_pair(py::array(n, nodes.data()), py::array(n, weights.data())); 31 | }, D(quad, compositeSimpson38)); 32 | } 33 | -------------------------------------------------------------------------------- /src/py_spline.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "python.h" 4 | 5 | #define D(...) DOC(layer, __VA_ARGS__ ) 6 | 7 | using namespace layer::spline; 8 | 9 | void python_export_spline(py::module &m) { 10 | /* spline.h bindings */ 11 | py::module spline = m.def_submodule( 12 | "spline", "Functions for evaluating and sampling Catmull-Rom splines"); 13 | 14 | spline.def("evalSpline", evalSpline, D(spline, evalSpline)); 15 | spline.def("evalSplineD", evalSplineD, D(spline, evalSplineD)); 16 | spline.def("evalSplineI", evalSplineI, D(spline, evalSplineI)); 17 | 18 | spline.def("eval1D", [](Float min, Float max, const VectorX &values, 19 | Float x, bool extrapolate) { 20 | return eval1D(min, max, values.data(), values.size(), x, extrapolate); 21 | }, D(spline, eval1D)); 22 | 23 | spline.def("eval1D", [](Float min, Float max, const VectorX &values, 24 | const MatrixX &x, bool extrapolate) -> MatrixX { 25 | MatrixX result(x.rows(), x.cols()); 26 | for (int i=0; i MatrixX { 41 | if (nodes.size() != values.size()) 42 | throw std::runtime_error("'nodes' and 'values' must have a matching size!"); 43 | MatrixX result(x.rows(), x.cols()); 44 | for (int i=0; i result(values.size()); 72 | integrate1D(nodes.data(), values.data(), values.size(), result.data()); 73 | return result; 74 | }, D(spline, integrate1D, 2)); 75 | 76 | spline.def("sample1D", [](Float min, Float max, const VectorX &values, 77 | const VectorX &cdf, Float sample) { 78 | if (values.size() != cdf.size()) 79 | throw std::runtime_error("'values' and 'cdf' must have a matching size!"); 80 | Float pos, fval, pdf; 81 | pos = sample1D(min, max, values.data(), cdf.data(), values.size(), 82 | sample, &fval, &pdf); 83 | return std::make_tuple(pos, fval, pdf); 84 | }, D(spline, sample1D)); 85 | 86 | spline.def("sample1D", [](const VectorX &nodes, const VectorX &values, 87 | const VectorX &cdf, Float sample) { 88 | if (nodes.size() != values.size() || nodes.size() != cdf.size()) 89 | throw std::runtime_error("'nodes', 'values', and 'cdf' must have a matching size!"); 90 | Float pos, fval, pdf; 91 | pos = sample1D(nodes.data(), values.data(), cdf.data(), nodes.size(), sample, &fval, &pdf); 92 | return std::make_tuple(pos, fval, pdf); 93 | }, D(spline, sample1D, 2)); 94 | 95 | spline.def("evalSplineWeights", [](Float min, Float max, size_t size, Float x, bool extrapolate) { 96 | Float weights[4] = { 0, 0, 0, 0 }; 97 | ssize_t offset = 0; 98 | bool success = evalSplineWeights(min, max, size, x, offset, weights, extrapolate); 99 | return std::make_tuple( 100 | success, offset, std::make_tuple(weights[0], weights[1], weights[2], weights[3]) 101 | ); 102 | }, D(spline, evalSplineWeights)); 103 | 104 | spline.def("evalSplineWeights", [](const VectorX &nodes, Float x, bool extrapolate) { 105 | Float weights[4] = { 0, 0, 0, 0}; 106 | ssize_t offset = 0; 107 | bool success = evalSplineWeights(nodes.data(), nodes.size(), x, offset, weights, extrapolate); 108 | return std::make_tuple( 109 | success, offset, std::make_tuple(weights[0], weights[1], weights[2], weights[3]) 110 | ); 111 | }, D(spline, evalSplineWeights, 2)); 112 | 113 | spline.def("eval2D", [](const VectorX &nodes1, const VectorX &nodes2, 114 | const MatrixX &values, Float x, Float y, 115 | bool extrapolate) { 116 | if (values.rows() != nodes1.size() || values.cols() != nodes2.size()) 117 | throw std::runtime_error("'nodes' and 'values' must have a matching size!"); 118 | 119 | return eval2D(nodes1.data(), nodes1.size(), nodes2.data(), nodes2.size(), values.data(), y, x, extrapolate); 120 | }, D(spline, eval2D)); 121 | 122 | spline.def("eval2D", [](const VectorX &nodes1, const VectorX &nodes2, 123 | const MatrixX &values, const MatrixX &x, const MatrixX &y, 124 | bool extrapolate) { 125 | if (values.rows() != nodes1.size() || values.cols() != nodes2.size()) 126 | throw std::runtime_error("'nodes' and 'values' must have a matching size!"); 127 | if (x.rows() != nodes1.size() || x.cols() != y.size()) 128 | throw std::runtime_error("'x' and 'y' must have a matching size!"); 129 | 130 | MatrixX result(x.rows(), x.cols()); 131 | for (int i=0; i 2 | #include 3 | #include 4 | #include 5 | 6 | #include "python.h" 7 | 8 | void python_export_vector(py::module &m) { 9 | } 10 | -------------------------------------------------------------------------------- /src/python.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | src/python.cpp -- Layer lab Python bindings 3 | 4 | Copyright (c) 2015 Wenzel Jakob 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE file. 8 | */ 9 | 10 | #include 11 | #include "python.h" 12 | 13 | PYTHON_DECLARE(math); 14 | PYTHON_DECLARE(spline); 15 | PYTHON_DECLARE(fourier); 16 | PYTHON_DECLARE(filesystem); 17 | PYTHON_DECLARE(layer); 18 | PYTHON_DECLARE(fresnel); 19 | PYTHON_DECLARE(quad); 20 | 21 | PYBIND11_MODULE(layerlab, m) { 22 | m.doc() = "Layer lab Python plugin"; 23 | 24 | PYTHON_IMPORT(math); 25 | PYTHON_IMPORT(spline); 26 | PYTHON_IMPORT(fourier); 27 | PYTHON_IMPORT(filesystem); 28 | PYTHON_IMPORT(layer); 29 | PYTHON_IMPORT(fresnel); 30 | PYTHON_IMPORT(quad); 31 | } 32 | -------------------------------------------------------------------------------- /src/python.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "py_doc.h" 11 | 12 | #define D(...) DOC(layer, __VA_ARGS__ ) 13 | 14 | #define PYTHON_DECLARE(name) \ 15 | extern void python_export_##name(py::module &) 16 | #define PYTHON_IMPORT(name) \ 17 | python_export_##name(m) 18 | 19 | using namespace layer; 20 | namespace py = pybind11; 21 | -------------------------------------------------------------------------------- /src/simd.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #if defined(__LINUX__) 3 | #include 4 | #endif 5 | 6 | NAMESPACE_BEGIN(layer) 7 | NAMESPACE_BEGIN(simd) 8 | 9 | /* Assumed L1 cache line size for alignment purposes */ 10 | #if !defined(L1_CACHE_LINE_SIZE) 11 | #define L1_CACHE_LINE_SIZE 64 12 | #endif 13 | 14 | void * malloc(size_t size) { 15 | #if defined(__WINDOWS__) 16 | return _aligned_malloc(size, L1_CACHE_LINE_SIZE); 17 | #elif defined(__OSX__) 18 | /* OSX malloc already returns 16-byte aligned data suitable 19 | for AltiVec and SSE computations */ 20 | return ::malloc(size); 21 | #else 22 | return memalign(L1_CACHE_LINE_SIZE, size); 23 | #endif 24 | } 25 | 26 | void free(void *ptr) { 27 | #if defined(__WINDOWS__) 28 | _aligned_free(ptr); 29 | #else 30 | ::free(ptr); 31 | #endif 32 | } 33 | 34 | NAMESPACE_END(simd) 35 | NAMESPACE_END(layer) 36 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | import sys, os 2 | 3 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 4 | -------------------------------------------------------------------------------- /tests/test_spline.py: -------------------------------------------------------------------------------- 1 | import sys, os 2 | 3 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 4 | 5 | import pytest 6 | 7 | from layerlab import * 8 | import numpy as np 9 | from numpy.linalg import norm 10 | 11 | values = np.array([1.0, 5.0, 3.0, 6.0]) 12 | nodes = np.array([5.0, 20/3, 25/3, 10.0]) 13 | eps = 1e-5 14 | 15 | @pytest.mark.parametrize("input, expected1, expected2", 16 | [ (4, 0, 0.328), (5, 1, 1), (6, 3.832, 3.832), (10, 6, 6), (11, 0, 6.36) ]) 17 | def test_eval1D_1(input, expected1, expected2): 18 | assert abs(spline.eval1D(nodes, values, input, False) - expected1) < eps 19 | assert abs(spline.eval1D(nodes, values, input, True ) - expected2) < eps 20 | assert abs(spline.eval1D(5, 10, values, input, False) - expected1) < eps 21 | assert abs(spline.eval1D(5, 10, values, input, True ) - expected2) < eps 22 | 23 | def test_eval1D_2(): 24 | input = np.array([4.0, 5.0, 6.0, 10.0, 11.0]) 25 | expected1 = np.array([0, 1, 3.832, 6, 0]) 26 | expected2 = np.array([0.328, 1, 3.832, 6, 6.36]) 27 | assert norm(spline.eval1D(5, 10, values, input, False) - expected1) < eps 28 | assert norm(spline.eval1D(5, 10, values, input, True ) - expected2) < eps 29 | assert norm(spline.eval1D(nodes, values, input, False) - expected1) < eps 30 | assert norm(spline.eval1D(nodes, values, input, True ) - expected2) < eps 31 | 32 | @pytest.mark.parametrize("input, expected1, expected2", 33 | [ (4, 0, 0.328), (5, 1, 1), (6, 3.832, 3.832), (10, 6, 6), (11, 0, 6.36) ]) 34 | def test_eval1D_3(input, expected1, expected2): 35 | results = [ 36 | spline.evalSplineWeights(nodes, input, False), 37 | spline.evalSplineWeights(5, 10, len(values), input, False), 38 | spline.evalSplineWeights(nodes, input, True), 39 | spline.evalSplineWeights(5, 10, len(values), input, True) 40 | ] 41 | for index, result in enumerate(results): 42 | value = 0 43 | for j in range(4): 44 | if result[2][j] != 0: 45 | value += values[result[1] + j] * result[2][j] 46 | if index < 2: 47 | assert abs(value - expected1) < eps 48 | else: 49 | assert abs(value - expected2) < eps 50 | 51 | @pytest.mark.parametrize("input, expected", 52 | [ (2, 4), (4.0, 4.644584273224155), (5.5, 5.3522) ]) 53 | def test_invert1D(input, expected): 54 | x = np.array([3.0, 4, 5, 6]) 55 | f = np.array([1.0, 2, 5, 6]) 56 | assert abs(spline.invert1D(x, f, input) - expected) < eps 57 | assert abs(spline.invert1D(3, 6, f, input) - expected) < eps 58 | 59 | def test_integrate1D(): 60 | expected = np.array([0, 5.416666666666669, 12.152777777777782, 19.305555555555557]) 61 | assert norm(spline.integrate1D(5, 10, values) - expected) < eps 62 | assert norm(spline.integrate1D(nodes, values) - expected) < eps 63 | 64 | @pytest.mark.parametrize("input, expected", 65 | [ (0, 5), (0.124213, 6), (0.5, 7.57735), (1, 10) ]) 66 | def test_sample1D(input, expected): 67 | cdf = spline.integrate1D(nodes, values) 68 | assert abs(spline.sample1D(5, 10, values, cdf, input)[0] - expected) < eps 69 | assert abs(spline.sample1D(nodes, values, cdf, input)[0] - expected) < eps 70 | --------------------------------------------------------------------------------