Initial commit for public release.

This commit is contained in:
Matt Pharr 2020-08-17 16:17:05 -07:00
commit 9772673e43
230 changed files with 401008 additions and 0 deletions

7
.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
*~
.#*
#*#
src/build
.DS_Store
.ipynb_checkpoints/
build/

19
.gitmodules vendored Normal file
View file

@ -0,0 +1,19 @@
[submodule "src/ext/zlib"]
path = src/ext/zlib
url = https://github.com/mitsuba-renderer/zlib
[submodule "src/ext/ptex"]
path = src/ext/ptex
url = https://github.com/wdas/ptex.git
[submodule "src/ext/double-conversion"]
path = src/ext/double-conversion
url = https://github.com/mmp/double-conversion
[submodule "src/ext/stb"]
path = src/ext/stb
url = https://github.com/nothings/stb.git
[submodule "src/ext/openexr"]
path = src/ext/openexr
url = https://github.com/mmp/openexr.git
branch = zlibstatic-export-workaround
[submodule "src/ext/filesystem"]
path = src/ext/filesystem
url = https://github.com/wjakob/filesystem.git

831
CMakeLists.txt Normal file
View file

@ -0,0 +1,831 @@
# pbrt-v4 top-level CMakeLists.txt
cmake_minimum_required (VERSION 3.12)
project (PBRT-V4 LANGUAGES CXX C)
set (CMAKE_CXX_STANDARD 17)
set (CMAKE_CXX_STANDARD_REQUIRED ON)
# For sanitizers
set (CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
# Configuration options
option (PBRT_FLOAT_AS_DOUBLE "Use 64-bit floats" OFF)
option (PBRT_BUILD_NATIVE_EXECUTABLE "Build executable optimized for CPU architecture of system pbrt was built on" ON)
option (PBRT_NVTX "Insert NVTX annotations for NVIDIA Profiling and Debugging Tools" OFF)
set (PBRT_OPTIX7_PATH "" CACHE STRING "Path to OptiX 7 SDK")
if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
message (STATUS "Setting build type to 'Release' as none was specified.")
set (CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
set_property (CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release"
"MinSizeRel" "RelWithDebInfo")
endif ()
function (CHECK_EXT NAME DIR HASH)
if (NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/src/ext/${DIR}")
message (FATAL_ERROR "The ${NAME} submodule directory is missing! "
"You probably did not clone the project with --recursive. It is possible to recover by running:\n"
" \"git submodule update --init --recursive\"")
endif ()
find_package(Git)
if (GIT_FOUND)
execute_process(
COMMAND ${GIT_EXECUTABLE} branch --contains ${HASH} HEAD
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/src/ext/${DIR}"
RESULT_VARIABLE "git_return"
ERROR_QUIET
OUTPUT_QUIET)
if (NOT ${git_return} EQUAL 0)
message (FATAL_ERROR "The ${CMAKE_CURRENT_SOURCE_DIR}/src/ext/${DIR} "
"submodule isn't up to date. Please run:\n"
" \"git submodule update --recursive\"")
else()
#message(STATUS "${NAME}: includes git commit: ${HASH}")
endif()
else(GIT_FOUND)
message(STATUS "git not found: unable to verify revisions in submodules")
endif(GIT_FOUND)
endfunction()
check_ext ("OpenEXR" "openexr/OpenEXR" 023e879e52e7486c4)
check_ext ("Ptex" "ptex/src" 82bd326)
check_ext ("double-conversion" "double-conversion/cmake" 9a8e518)
check_ext ("filesystem" "filesystem/filesystem" 4efd2628)
check_ext ("stb" "stb/tools" 1034f5)
check_ext ("zlib" "zlib/doc" 54d591e)
if (CMAKE_BUILD_TYPE MATCHES Release)
add_definitions (-D NDEBUG)
endif ()
# To build a release build with CHECKs enabled, comment-out the above
# 3 lines and un-comment out this one:
# SET(CMAKE_CXX_FLAGS_RELEASE "-O3")
enable_testing ()
find_package ( Sanitizers )
find_package ( Threads )
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
if (MSVC)
add_definitions (/D _CRT_SECURE_NO_WARNINGS)
set(PBRT_DEFINITIONS ${PBRT_DEFINITIONS} PBRT_IS_MSVC)
list (APPEND PBRT_CXX_FLAGS /wd4305) # double constant assigned to float
list (APPEND PBRT_CXX_FLAGS /wd4244) # int -> float conversion
list (APPEND PBRT_CXX_FLAGS /wd4843) # double -> float conversion
list (APPEND PBRT_CXX_FLAGS /wd4267) # size_t -> int conversion
list (APPEND PBRT_CXX_FLAGS /wd4838) # double -> int conversion
list (APPEND PBRT_CXX_FLAGS /wd26495) # uninitialized member variable
list (APPEND PBRT_CXX_FLAGS /wd26451) # arithmetic on 4-byte value, then cast to 8-byte
endif ()
#######################################
## ext
set (BUILD_SHARED_LIBS OFF)
add_subdirectory (${CMAKE_CURRENT_SOURCE_DIR}/src/ext)
#########################################
## CUDA / OptiX
include (CheckLanguage)
check_language(CUDA)
if (CMAKE_CUDA_COMPILER)
find_package (CUDA REQUIRED)
# This seems to be necessary starting with 3.17.1, but gives an error
# about 17 being an unsupported version earlier...
if (${CMAKE_VERSION} VERSION_GREATER "3.17.0")
set (CMAKE_CUDA_STANDARD 17)
endif ()
message (STATUS "Found CUDA: ${CMAKE_CUDA_COMPILER_VERSION}")
if ("${PBRT_OPTIX7_PATH}" STREQUAL "")
message (WARNING "Found CUDA but PBRT_OPTIX7_PATH is not set. Disabling GPU compilation.")
else ()
enable_language (CUDA)
set (PBRT_DEFINITIONS ${PBRT_DEFINITIONS} PBRT_BUILD_GPU_RENDERER)
set (PBRT_DEFINITIONS ${PBRT_DEFINITIONS} NVTX)
set (PBRT_CUDA_ENABLED ON)
# FIXME
include_directories (${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES}) # for regular c++ compiles
# http://www.ssl.berkeley.edu/~jimm/grizzly_docs/SSL/opt/intel/cc/9.0/lib/locale/en_US/mcpcom.msg
set (PBRT_CUDA_DIAG_FLAGS "")
#set (PBRT_CUDA_DIAG_FLAGS "${PBRT_CUDA_DIAG_FLAGS} -Xptxas --warn-on-double-precision-use")
set (PBRT_CUDA_DIAG_FLAGS "${PBRT_CUDA_DIAG_FLAGS} -Xcudafe --diag_suppress=partial_override")
set (PBRT_CUDA_DIAG_FLAGS "${PBRT_CUDA_DIAG_FLAGS} -Xcudafe --diag_suppress=virtual_function_decl_hidden")
set (PBRT_CUDA_DIAG_FLAGS "${PBRT_CUDA_DIAG_FLAGS} -Xcudafe --diag_suppress=integer_sign_change")
set (PBRT_CUDA_DIAG_FLAGS "${PBRT_CUDA_DIAG_FLAGS} -Xcudafe --diag_suppress=declared_but_not_referenced")
# WAR invalid warnings about this with "if constexpr"
set (PBRT_CUDA_DIAG_FLAGS "${PBRT_CUDA_DIAG_FLAGS} -Xcudafe --diag_suppress=implicit_return_from_non_void_function")
set (PBRT_CUDA_DIAG_FLAGS "${PBRT_CUDA_DIAG_FLAGS} --expt-relaxed-constexpr")
set (PBRT_CUDA_DIAG_FLAGS "${PBRT_CUDA_DIAG_FLAGS} --extended-lambda")
set (CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} ${PBRT_CUDA_DIAG_FLAGS}")
# Willie hears yeh..
set (CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Xnvlink -suppress-stack-size-warning")
# https://wagonhelm.github.io/articles/2018-03/detecting-cuda-capability-with-cmake
# Get CUDA compute capability
set (OUTPUTFILE ${CMAKE_BINARY_DIR}/checkcuda)
execute_process (COMMAND nvcc -lcuda ${CMAKE_SOURCE_DIR}/cmake/checkcuda.cu -o ${OUTPUTFILE})
execute_process (COMMAND ${OUTPUTFILE}
RESULT_VARIABLE CUDA_RETURN_CODE
OUTPUT_VARIABLE ARCH)
set (CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --std=c++17")
if (CMAKE_BUILD_TYPE MATCHES Release)
set (CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --use_fast_math -lineinfo --maxrregcount 128")
else()
set (CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --use_fast_math -G -g")
endif ()
if (NOT ${CUDA_RETURN_CODE} EQUAL 0)
message (SEND_ERROR "Unable to determine GPU's compute capability")
else ()
message (STATUS "CUDA Architecture: ${ARCH}")
set (CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --gpu-architecture=${ARCH}")
endif ()
set (PBRT_CUDA_LIB cuda)
# optix
# FIXME
include_directories (${PBRT_OPTIX7_PATH}/include)
# FIXME. Sigh. I'm not sure how else to pass this through to cuda_compile_ptx...
include_directories (src)
include_directories (${CMAKE_BINARY_DIR})
# from Ingo's configure_optix.cmake (Apache licensed)
find_program (BIN2C bin2c DOC "Path to the CUDA SDK bin2c executable.")
# this macro defines cmake rules that execute the following four steps:
# 1) compile the given cuda file ${cuda_file} to an intermediary PTX file
# 2) use the 'bin2c' tool (that comes with CUDA) to
# create a second intermediary (.c-)file which defines a const string variable
# (named '${c_var_name}') whose (constant) value is the PTX output
# from the previous step.
# 3) compile the given .c file to an intermediary object file (why thus has
# that PTX string 'embedded' as a global constant.
# 4) assign the name of the intermediary .o file to the cmake variable
# 'output_var', which can then be added to cmake targets.
macro (cuda_compile_and_embed output_var cuda_file)
set (c_var_name ${output_var})
cuda_compile_ptx (ptx_files ${cuda_file}
OPTIONS --std=c++17 -O3 ${PBRT_CUDA_DIAG_FLAGS} -DNDEBUG --use_fast_math
# disable "extern declaration... is treated as a static definition" warning
-Xcudafe=--display_error_number -Xcudafe=--diag_suppress=3089
--gpu-architecture=${ARCH} -D PBRT_BUILD_GPU_RENDERER)
list (GET ptx_files 0 ptx_file)
set (embedded_file ${ptx_file}_embedded.c)
add_custom_command (
OUTPUT ${embedded_file}
COMMAND ${BIN2C} -c --padd 0 --type char --name ${c_var_name} ${ptx_file} > ${embedded_file}
DEPENDS ${ptx_file}
COMMENT "compiling (and embedding ptx from) ${cuda_file}"
)
set (${output_var} ${embedded_file})
endmacro ()
endif ()
else ()
message (STATUS "CUDA not found")
endif ()
if (PBRT_FLOAT_AS_DOUBLE)
set (PBRT_DEFINITIONS ${PBRT_DEFINITIONS} PBRT_FLOAT_AS_DOUBLE)
endif ()
###########################################################################
# Annoying compiler-specific details
INCLUDE(CheckCXXCompilerFlag)
# TODO: how to specify this on windows?
check_cxx_compiler_flag ("-march=native" COMPILER_SUPPORTS_MARCH_NATIVE)
if (COMPILER_SUPPORTS_MARCH_NATIVE AND PBRT_BUILD_NATIVE_EXECUTABLE AND NOT PBRT_CUDA_ENABLED)
list (APPEND PBRT_CXX_FLAGS -march=native)
endif ()
if (CMAKE_CXX_COMPILER_ID STREQUAL "Intel")
list(APPEND PBRT_CXX_FLAGS -std=c++17)
FIND_PROGRAM(XIAR xiar)
IF(XIAR)
SET(CMAKE_AR "${XIAR}")
ENDIF(XIAR)
MARK_AS_ADVANCED(XIAR)
FIND_PROGRAM(XILD xild)
IF(XILD)
SET(CMAKE_LINKER "${XILD}")
ENDIF(XILD)
MARK_AS_ADVANCED(XILD)
# ICC will default to -fp-model fast=1, which performs value-unsafe optimizations which will
# cause pbrt_test to fail. For safety, -fp-model precise is explicitly set here by default.
set(FP_MODEL "precise" CACHE STRING "The floating point model to compile with.")
set_property(CACHE FP_MODEL PROPERTY STRINGS "precise" "fast=1" "fast=2")
list (APPEND PBRT_CXX_FLAGS "-fp-model ${FP_MODEL}")
endif ()
###########################################################################
# Check for various C++ features and set preprocessor variables or
# define workarounds.
include (CheckCXXSourceCompiles)
check_cxx_source_compiles ("
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
int main() {
int fd = open(\"foo\", O_RDONLY);
struct stat s;
fstat(fd, &s);
size_t len = s.st_size;
void *ptr = mmap(0, len, PROT_READ, MAP_FILE | MAP_SHARED, fd, 0);
munmap(ptr, len);
}
" HAVE_MMAP)
if (HAVE_MMAP)
set(PBRT_DEFINITIONS ${PBRT_DEFINITIONS} PBRT_HAVE_MMAP)
ENDIF ()
include (CheckIncludeFiles)
check_cxx_source_compiles ("
#include <intrin.h>
int main() {
unsigned long lz = 0, v = 1234;
if (_BitScanReverse(&lz, v)) return lz;
return 0;
} " HAS_INTRIN_H)
if (HAS_INTRIN_H)
set (PBRT_DEFINITIONS ${PBRT_DEFINITIONS} PBRT_HAS_INTRIN_H)
endif ()
########################################
# os/compiler-specific stuff
if (CMAKE_SYSTEM_NAME STREQUAL Windows)
set (PBRT_DEFINITIONS ${PBRT_DEFINITIONS} PBRT_IS_WINDOWS NOMINMAX)
elseif (CMAKE_SYSTEM_NAME STREQUAL Darwin)
set (PBRT_DEFINITIONS ${PBRT_DEFINITIONS} PBRT_IS_OSX)
elseif (CMAKE_SYSTEM_NAME STREQUAL Linux)
set (PBRT_DEFINITIONS ${PBRT_DEFINITIONS} PBRT_IS_LINUX)
# -rdynamic so we can get backtrace symbols...
# --no-as-needed so libprofiler sticks around
set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -rdynamic -Wl,--no-as-needed")
else ()
message (SEND_ERROR "Unknown system name: " + CMAKE_SYSTEM_NAME)
endif()
# libgoogle-perftools-dev
find_library (PROFILE_LIB profiler)
if (NOT PROFILE_LIB)
message (STATUS "Unable to find -lprofiler")
else ()
message (STATUS "Found -lprofiler: ${PROFILE_LIB}")
endif ()
########################################
# noinline
check_cxx_source_compiles (
"__declspec(noinline) void foo() { }
int main() { }"
HAVE_DECLSPEC_NOINLINE)
check_cxx_source_compiles (
"__attribute__((noinline)) void foo() { }
int main() { }"
HAVE_ATTRIBUTE_NOINLINE)
if (HAVE_ATTRIBUTE_NOINLINE)
set (PBRT_DEFINITIONS ${PBRT_DEFINITIONS} "PBRT_NOINLINE=__attribute__((noinline))")
elseif (HAVE_DECLSPEC_NOINLINE)
set (PBRT_DEFINITIONS ${PBRT_DEFINITIONS} "PBRT_NOINLINE=__declspec(noinline)")
else ()
set (PBRT_DEFINITIONS ${PBRT_DEFINITIONS} PBRT_NOINLINE)
endif ()
########################################
# Aligned memory allocation
check_cxx_source_compiles ( "
#include <malloc.h>
int main() { void * ptr = _aligned_malloc(1024, 32); }
" HAVE__ALIGNED_MALLOC )
check_cxx_source_compiles ( "
#include <stdlib.h>
int main() {
void *ptr;
posix_memalign(&ptr, 32, 1024);
} " HAVE_POSIX_MEMALIGN )
if (HAVE__ALIGNED_MALLOC)
set (PBRT_DEFINITIONS ${PBRT_DEFINITIONS} PBRT_HAVE__ALIGNED_MALLOC)
elseif (HAVE_POSIX_MEMALIGN)
set (PBRT_DEFINITIONS ${PBRT_DEFINITIONS} PBRT_HAVE_POSIX_MEMALIGN)
else ()
message (SEND_ERROR "Unable to find a way to allocate aligned memory")
endif ()
########################################
# are long and int64_t the same
check_cxx_source_compiles ("
#include <cstdint>
#include <type_traits>
static_assert(!std::is_same<long, int64_t>::value && !std::is_same<long long, int64_t>::value);
int main() { }
" INT64_IS_OWN_TYPE)
if (INT64_IS_OWN_TYPE)
set (PBRT_DEFINITIONS ${PBRT_DEFINITIONS} PBRT_INT64_IS_OWN_TYPE)
endif ()
if (PBRT_NVTX)
add_definitions( -D NVTX )
endif()
###########################################################################
# On to pbrt...
set (PBRT_SOURCE
src/pbrt/bsdf.cpp
src/pbrt/bssrdf.cpp
src/pbrt/bxdfs.cpp
src/pbrt/cameras.cpp
src/pbrt/film.cpp
src/pbrt/filters.cpp
src/pbrt/interaction.cpp
src/pbrt/lights.cpp
src/pbrt/lightsamplers.cpp
src/pbrt/materials.cpp
src/pbrt/media.cpp
src/pbrt/options.cpp
src/pbrt/paramdict.cpp
src/pbrt/parsedscene.cpp
src/pbrt/parser.cpp
src/pbrt/pbrt.cpp
src/pbrt/ray.cpp
src/pbrt/samplers.cpp
src/pbrt/shapes.cpp
src/pbrt/textures.cpp
src/pbrt/cpu/accelerators.cpp
src/pbrt/cpu/integrators.cpp
src/pbrt/cpu/primitive.cpp
src/pbrt/cpu/render.cpp
)
set (PBRT_SOURCE_HEADERS
src/pbrt/bsdf.h
src/pbrt/bssrdf.h
src/pbrt/bxdfs.h
src/pbrt/cameras.h
src/pbrt/film.h
src/pbrt/filters.h
src/pbrt/interaction.h
src/pbrt/lightsamplers.h
src/pbrt/lights.h
src/pbrt/materials.h
src/pbrt/media.h
src/pbrt/options.h
src/pbrt/paramdict.h
src/pbrt/parsedscene.h
src/pbrt/parser.h
src/pbrt/pbrt.h
src/pbrt/pbrt.soa
src/pbrt/ray.h
src/pbrt/samplers.h
src/pbrt/shapes.h
src/pbrt/textures.h
)
SET (PBRT_UTIL_SOURCE
src/pbrt/util/bluenoise.cpp
src/pbrt/util/check.cpp
src/pbrt/util/color.cpp
src/pbrt/util/colorspace.cpp
src/pbrt/util/display.cpp
src/pbrt/util/error.cpp
src/pbrt/util/file.cpp
src/pbrt/util/float.cpp
src/pbrt/util/image.cpp
src/pbrt/util/log.cpp
src/pbrt/util/loopsubdiv.cpp
src/pbrt/util/lowdiscrepancy.cpp
src/pbrt/util/math.cpp
src/pbrt/util/memory.cpp
src/pbrt/util/mesh.cpp
src/pbrt/util/mipmap.cpp
src/pbrt/util/parallel.cpp
src/pbrt/util/pmj02tables.cpp
src/pbrt/util/primes.cpp
src/pbrt/util/print.cpp
src/pbrt/util/progressreporter.cpp
src/pbrt/util/pstd.cpp
src/pbrt/util/rng.cpp
src/pbrt/util/sampling.cpp
src/pbrt/util/scattering.cpp
src/pbrt/util/sobolmatrices.cpp
src/pbrt/util/spectrum.cpp
src/pbrt/util/stats.cpp
src/pbrt/util/stbimage.cpp
src/pbrt/util/string.cpp
src/pbrt/util/transform.cpp
src/pbrt/util/vecmath.cpp
)
SET (PBRT_UTIL_SOURCE_HEADERS
src/pbrt/util/args.h
src/pbrt/util/bits.h
src/pbrt/util/bluenoise.h
src/pbrt/util/buffercache.h
src/pbrt/util/check.h
src/pbrt/util/color.h
src/pbrt/util/colorspace.h
src/pbrt/util/containers.h
src/pbrt/util/display.h
src/pbrt/util/error.h
src/pbrt/util/file.h
src/pbrt/util/float.h
src/pbrt/util/hash.h
src/pbrt/util/image.h
src/pbrt/util/log.h
src/pbrt/util/loopsubdiv.h
src/pbrt/util/lowdiscrepancy.h
src/pbrt/util/math.h
src/pbrt/util/memory.h
src/pbrt/util/mesh.h
src/pbrt/util/mipmap.h
src/pbrt/util/parallel.h
src/pbrt/util/pmj02tables.h
src/pbrt/util/primes.h
src/pbrt/util/print.h
src/pbrt/util/progressreporter.h
src/pbrt/util/pstd.h
src/pbrt/util/rng.h
src/pbrt/util/sampling.h
src/pbrt/util/scattering.h
src/pbrt/util/shuffle.h
src/pbrt/util/soa.h
src/pbrt/util/sobolmatrices.h
src/pbrt/util/spectrum.h
src/pbrt/util/splines.h
src/pbrt/util/stats.h
src/pbrt/util/string.h
src/pbrt/util/taggedptr.h
src/pbrt/util/transform.h
src/pbrt/util/vecmath.h
)
if (PBRT_CUDA_ENABLED)
set (PBRT_GPU_SOURCE
src/pbrt/gpu/accel.cpp
src/pbrt/gpu/camera.cpp
src/pbrt/gpu/film.cpp
src/pbrt/gpu/init.cpp
src/pbrt/gpu/launch.cpp
src/pbrt/gpu/media.cpp
src/pbrt/gpu/pathintegrator.cpp
src/pbrt/gpu/samples.cpp
src/pbrt/gpu/subsurface.cpp
src/pbrt/gpu/surfscatter.cpp
)
set (PBRT_GPU_SOURCE_HEADERS
src/pbrt/gpu/accel.h
src/pbrt/gpu/init.h
src/pbrt/gpu/launch.h
src/pbrt/gpu/optix.h
src/pbrt/gpu/pathintegrator.h
src/pbrt/gpu/workitems.h
src/pbrt/gpu/workitems.soa
src/pbrt/gpu/workqueue.h
)
set_source_files_properties (
src/pbrt/bsdf.cpp
src/pbrt/bssrdf.cpp
src/pbrt/bxdfs.cpp
src/pbrt/cameras.cpp
src/pbrt/film.cpp
src/pbrt/filters.cpp
# src/pbrt/genscene.cpp
src/pbrt/interaction.cpp
src/pbrt/lights.cpp
src/pbrt/lightsamplers.cpp
src/pbrt/materials.cpp
# src/pbrt/media.cpp
src/pbrt/options.cpp
# src/pbrt/paramdict.cpp
# src/pbrt/parser.cpp
src/pbrt/pbrt.cpp
src/pbrt/samplers.cpp
src/pbrt/shapes.cpp
src/pbrt/textures.cpp
src/pbrt/util/bluenoise.cpp
src/pbrt/util/check.cpp
src/pbrt/util/color.cpp
src/pbrt/util/colorspace.cpp
src/pbrt/util/error.cpp
# src/pbrt/util/file.cpp
# src/pbrt/util/float.cpp
# src/pbrt/util/image.cpp
src/pbrt/util/log.cpp
# src/pbrt/util/loopsubdiv.cpp
src/pbrt/util/lowdiscrepancy.cpp
src/pbrt/util/math.cpp
# src/pbrt/util/memory.cpp
src/pbrt/util/mesh.cpp
# src/pbrt/util/mipmap.cpp
# src/pbrt/util/parallel.cpp
src/pbrt/util/pmj02tables.cpp
src/pbrt/util/primes.cpp
# src/pbrt/util/print.cpp
# src/pbrt/util/progressreporter.cpp
src/pbrt/util/pstd.cpp
src/pbrt/util/rng.cpp
src/pbrt/util/sampling.cpp
src/pbrt/util/scattering.cpp
src/pbrt/util/sobolmatrices.cpp
src/pbrt/util/spectrum.cpp
src/pbrt/util/stats.cpp
# src/pbrt/util/stbimage.cpp
# src/pbrt/util/string.cpp
src/pbrt/util/transform.cpp
src/pbrt/util/vecmath.cpp
${PBRT_GPU_SOURCE}
PROPERTIES LANGUAGE CUDA
)
cuda_compile_and_embed (PBRT_EMBEDDED_PTX src/pbrt/gpu/optix.cu)
endif ()
source_group("Source Files" FILES ${PBRT_SOURCE})
source_group("Header Files" FILES ${PBRT_SOURCE_HEADERS})
source_group("Source Files/util" FILES ${PBRT_UTIL_SOURCE})
source_group("Header Files/util" FILES ${PBRT_UTIL_SOURCE_HEADERS})
if (PBRT_CUDA_ENABLED)
source_group("Source Files/gpu" FILES ${PBRT_GPU_SOURCE})
source_group("Header Files/gpu" FILES ${PBRT_GPU_SOURCE_HEADERS})
endif ()
###########################################################################
# pbrt libraries and executables
set(PBRT_DEFINITIONS ${PBRT_DEFINITIONS} PTEX_STATIC)
######################
# soac
add_executable (soac src/pbrt/cmd/soac.cpp)
add_executable (pbrt::soac ALIAS soac)
target_compile_definitions (soac PRIVATE ${PBRT_DEFINITIONS})
target_compile_options (soac PUBLIC ${PBRT_CXX_FLAGS})
set_target_properties (soac PROPERTIES OUTPUT_NAME soac)
add_custom_command (OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/pbrt_soa.h
COMMAND soac ${CMAKE_SOURCE_DIR}/src/pbrt/pbrt.soa > ${CMAKE_CURRENT_BINARY_DIR}/pbrt_soa.h
DEPENDS soac ${CMAKE_SOURCE_DIR}/src/pbrt/pbrt.soa)
set (PBRT_SOA_GENERATED ${CMAKE_CURRENT_BINARY_DIR}/pbrt_soa.h)
if (PBRT_CUDA_ENABLED)
add_custom_command (OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/gpu_workitems_soa.h
COMMAND soac ${CMAKE_SOURCE_DIR}/src/pbrt/gpu/workitems.soa > ${CMAKE_CURRENT_BINARY_DIR}/gpu_workitems_soa.h
DEPENDS soac ${CMAKE_SOURCE_DIR}/src/pbrt/gpu/workitems.soa)
set (PBRT_SOA_GENERATED ${PBRT_SOA_GENERATED} ${CMAKE_CURRENT_BINARY_DIR}/gpu_workitems_soa.h)
endif ()
######################
# pbrt_lib
add_library (pbrt_lib STATIC
${PBRT_SOA_GENERATED}
${CMAKE_CURRENT_BINARY_DIR}/rgbspectrum_srgb.cpp
${CMAKE_CURRENT_BINARY_DIR}/rgbspectrum_dci_p3.cpp
${CMAKE_CURRENT_BINARY_DIR}/rgbspectrum_rec2020.cpp
${CMAKE_CURRENT_BINARY_DIR}/rgbspectrum_aces.cpp
${PBRT_SOURCE}
${PBRT_UTIL_SOURCE}
${PBRT_GPU_SOURCE}
src/ext/gtest/gtest-all.cc
src/ext/lodepng/lodepng.cpp
src/ext/rply/rply.cpp
)
add_library (pbrt::pbrt_lib ALIAS pbrt_lib)
target_compile_definitions (pbrt_lib PRIVATE ${PBRT_DEFINITIONS})
target_include_directories (pbrt_lib PUBLIC
src
src/ext
${STB_INCLUDE}
${OPENEXR_INCLUDE}
${ZLIB_INCLUDE_DIRS}
${FILESYSTEM_INCLUDE}
${PTEX_INCLUDE}
${DOUBLE_CONVERSION_INCLUDE}
${CMAKE_CURRENT_BINARY_DIR}
)
target_compile_options (pbrt_lib PUBLIC ${PBRT_CXX_FLAGS})
add_sanitizers (pbrt_lib)
if (WIN32)
# Avoid a name clash when building on Visual Studio
set_target_properties (pbrt_lib PROPERTIES OUTPUT_NAME libpbrt)
endif()
set (ALL_PBRT_LIBS
pbrt_lib
${CMAKE_THREAD_LIBS_INIT}
${OPENEXR_LIBS}
Ptex_static
${ZLIB_LIBRARIES}
double-conversion
${PBRT_CUDA_LIB}
)
if (PBRT_CUDA_ENABLED)
set_property (TARGET pbrt_lib PROPERTY CUDA_SEPARABLE_COMPILATION ON)
add_library (pbrt_embedded_ptx_lib STATIC
${PBRT_EMBEDDED_PTX}
)
set (ALL_PBRT_LIBS ${ALL_PBRT_LIBS} pbrt_embedded_ptx_lib)
endif()
if (WIN32)
set (ALL_PBRT_LIBS ${ALL_PBRT_LIBS} dbghelp wsock32 ws2_32)
endif ()
if (PROFILE_LIB)
set(ALL_PBRT_LIBS ${ALL_PBRT_LIBS} ${PROFILE_LIB})
endif ()
######################
## rgb2spec_opt
add_executable (rgb2spec_opt src/pbrt/cmd/rgb2spec_opt.cpp)
add_executable (pbrt::rgb2spec_opt ALIAS rgb2spec_opt)
target_compile_definitions (rgb2spec_opt PRIVATE ${PBRT_DEFINITIONS})
target_compile_options (rgb2spec_opt PUBLIC ${PBRT_CXX_FLAGS})
target_link_libraries (rgb2spec_opt ${CMAKE_THREAD_LIBS_INIT})
add_custom_command (OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/rgbspectrum_aces.cpp
COMMAND rgb2spec_opt 64 ${CMAKE_CURRENT_BINARY_DIR}/rgbspectrum_aces.cpp ACES2065_1
DEPENDS rgb2spec_opt)
add_custom_command (OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/rgbspectrum_dci_p3.cpp
COMMAND rgb2spec_opt 64 ${CMAKE_CURRENT_BINARY_DIR}/rgbspectrum_dci_p3.cpp DCI_P3
DEPENDS rgb2spec_opt)
add_custom_command (OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/rgbspectrum_rec2020.cpp
COMMAND rgb2spec_opt 64 ${CMAKE_CURRENT_BINARY_DIR}/rgbspectrum_rec2020.cpp REC2020
DEPENDS rgb2spec_opt)
add_custom_command (OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/rgbspectrum_srgb.cpp
COMMAND rgb2spec_opt 64 ${CMAKE_CURRENT_BINARY_DIR}/rgbspectrum_srgb.cpp sRGB
DEPENDS rgb2spec_opt)
######################
# Main renderer
add_executable (pbrt_exe src/pbrt/cmd/pbrt.cpp)
add_executable (pbrt::pbrt_exe ALIAS pbrt_exe)
target_compile_definitions (pbrt_exe PRIVATE ${PBRT_DEFINITIONS})
target_compile_options (pbrt_exe PRIVATE ${PBRT_CXX_FLAGS})
target_include_directories (pbrt_exe PRIVATE src src/ext)
target_link_libraries (pbrt_exe PRIVATE ${ALL_PBRT_LIBS})
set_target_properties (pbrt_exe PROPERTIES OUTPUT_NAME pbrt)
add_sanitizers (pbrt_exe)
######################
# imgtool
add_executable (imgtool src/pbrt/cmd/imgtool.cpp)
add_executable (pbrt::imgtool ALIAS imgtool)
add_library (sky_lib STATIC src/ext/skymodel/ArHosekSkyModel.c)
set_property (TARGET sky_lib PROPERTY FOLDER "ext")
target_compile_definitions (imgtool PRIVATE ${PBRT_DEFINITIONS})
target_compile_options (imgtool PRIVATE ${PBRT_CXX_FLAGS})
target_include_directories (imgtool PRIVATE src src/ext)
target_link_libraries (imgtool PRIVATE ${ALL_PBRT_LIBS} sky_lib)
add_sanitizers (imgtool)
######################
# obj2pbrt
add_executable (obj2pbrt src/pbrt/cmd/obj2pbrt.cpp)
target_compile_definitions (obj2pbrt PRIVATE ${PBRT_DEFINITIONS})
target_compile_options (obj2pbrt PRIVATE ${PBRT_CXX_FLAGS})
add_sanitizers (obj2pbrt)
######################
# cyhair2pbrt
add_executable (cyhair2pbrt src/pbrt/cmd/cyhair2pbrt.cpp)
target_compile_definitions (cyhair2pbrt PRIVATE ${PBRT_DEFINITIONS})
target_compile_options (cyhair2pbrt PRIVATE ${PBRT_CXX_FLAGS})
add_sanitizers (cyhair2pbrt)
##################
# Unit tests
set (PBRT_TEST_SOURCE
src/pbrt/bsdfs_test.cpp
src/pbrt/filters_test.cpp
src/pbrt/lights_test.cpp
src/pbrt/lightsamplers_test.cpp
src/pbrt/media_test.cpp
src/pbrt/parser_test.cpp
src/pbrt/samplers_test.cpp
src/pbrt/shapes_test.cpp
src/pbrt/cpu/integrators_test.cpp
src/pbrt/util/args_test.cpp
src/pbrt/util/bits_test.cpp
src/pbrt/util/color_test.cpp
src/pbrt/util/containers_test.cpp
src/pbrt/util/file_test.cpp
src/pbrt/util/float_test.cpp
src/pbrt/util/hash_test.cpp
src/pbrt/util/image_test.cpp
src/pbrt/util/math_test.cpp
src/pbrt/util/parallel_test.cpp
src/pbrt/util/print_test.cpp
src/pbrt/util/pstd_test.cpp
src/pbrt/util/rng_test.cpp
src/pbrt/util/sampling_test.cpp
src/pbrt/util/spectrum_test.cpp
src/pbrt/util/splines_test.cpp
src/pbrt/util/taggedptr_test.cpp
src/pbrt/util/transform_test.cpp
src/pbrt/util/vecmath_test.cpp
)
add_executable (pbrt_test src/pbrt/cmd/pbrt_test.cpp ${PBRT_TEST_SOURCE})
target_link_libraries (pbrt_test PRIVATE ${ALL_PBRT_LIBS})
target_compile_definitions (pbrt_test PRIVATE ${PBRT_DEFINITIONS})
target_include_directories (pbrt_test PRIVATE src src/ext ${DOUBLE_CONVERSION_INCLUDE})
target_compile_options(pbrt_test PUBLIC ${PBRT_CXX_FLAGS})
add_sanitizers (pbrt_test)
add_test (pbrt_unit_test pbrt_test)
###############################
# Installation
install (TARGETS
pbrt_exe
imgtool
obj2pbrt
cyhair2pbrt
DESTINATION
bin
)
install (TARGETS
pbrt_lib
DESTINATION
lib
)

202
LICENSE.txt Normal file
View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

189
README.md Normal file
View file

@ -0,0 +1,189 @@
pbrt, Version 4 (Early Release)
===============================
This is an early release of pbrt-v4, the rendering system that will be
described in the (eventually) forthcoming fourth edition of *Physically
Based Rendering: From Theory to Implementation*. (We hope to have an
online version of the book posted late in 2020 and printed books available
in Spring 2021.)
We are making this code available for hardy adventurers; it's not yet
extensively documented, but if you're familiar with previous versions of
pbrt, you should be able to make your away around it. Our hope is that the
system will be useful to some people in its current form and that any bugs
in the current implementation might be found now, allowing us to correct
them before the book is final.
A number of scenes for pbrt-v4 are [available in a git repository](TODO).
Features
--------
pbrt-v4 represents a substantial update to the previous version of pbrt-v3.
Major changes include:
* Spectral rendering
* Rendering computations are always performed using
point-sampled spectra; the use of RGB color is limited to the scene
description (e.g., image texture maps), and final image output.
* Modernized volumetric scattering
* An all-new `VolPathIntegrator` based on the null-scattering path
integral formulation of [Miller et
al. 2019](https://cs.dartmouth.edu/~wjarosz/publications/miller19null.html)
has been added.
* Tighter majorants are used for null-scattering with the `GridDensityMedium`
via a separate low-resolution grid of majorants.
* Emissive volumes are now supported.
* Support for rendering on GPUs is available on systems that have CUDA and OptiX.
* The GPU path provides all of the functionality of the CPU-based
`VolPathIntegrator`, including volumetric scattering, subsurface
scattering, all of pbrt's cameras, samplers, shapes, lights, materials
and BxDFs, etc.
* Performance is substantially faster than rendering on the CPU.
* New BxDFs and Materials
* The provided BxDFs and Materials have been redesigned to be more
closely tied to physical scattering processes, along the lines of
Mitsuba's materials. (Among other things, the kitchen-sink UberMaterial
is now gone.)
* Measured BRDFs are now represented using [Dupuy and Jakob's
approach](https://rgl.epfl.ch/publications/Dupuy2018Adaptive).
* Scattering from layered materials is accurately simulated using Monte
Carlo random walks (after [Guo et al. 2018](https://shuangz.com/projects/layered-sa18/).)
* A variety of light sampling improvements have been implemented.
* "Many-light" sampling is available via light BVHs ([Conty and Kulla 2018](http://aconty.com/pdf/many-lights-hpg2018.pdf)).
* Solid angle sampling is used for triangle
([Arvo1995](https://dl.acm.org/doi/10.1145/218380.218500)) and
quadrilateral ([Ureña et al. 2013](https://www.arnoldrenderer.com/research/egsr2013_spherical_rectangle.pdf))
light sources.
* A single ray is now traced for both indirect lighting and BSDF-sampled direct-lighting.
* Warp product sampling is used for approximate cosine-weighted solid angle
sampling ([Hart et al. 2019](https://onlinelibrary.wiley.com/doi/abs/10.1111/cgf.14060)).
* An implementation of Bitterli et al's environment light [portal sampling](https://benedikt-bitterli.me/pmems.html)
technique is included.
* And also...
* Various improvements have been made to the `Sampler` classes, including
better randomization and a new sampler that implements pmj02bn sampling ([Christensen et
al. 2018](https://graphics.pixar.com/library/ProgressiveMultiJitteredSampling/)).
* A new `GBufferFilm` that provides position, normal, albedo, etc., at
each pixel is now available. (This is particularly useful for denoising and ML training.)
* Path regularization (optionally).
* A bilinear patch primitive has been added ([Reshetov 2019](https://link.springer.com/chapter/10.1007/978-1-4842-4427-2_8)).
* Accurate modeling of film response in cameras and photometric lighting controls thanks to a contribution from Anders Langlands and Luca Fascione.
* Various improvements to ray--shape intersection precision.
* Most of the low-level sampling code has been factored out into
stand-alone functions for easier reuse. Also, functions that invert
many sampling techniques are provided.
* Unit tests have been substantially increased.
We have also made a refactoring pass throughout the entire system, cleaning
up various APIs and data types to improve both readability and usability.
Finally, pbrt-v4 can work together with the
[tev](https://github.com/Tom94/tev) image viewer to display the image as
it's being rendered. As of recent versions, *tev* can display images
provided to it via a network socket; by default, it listens to port 14158,
though this can be changed via its ``--hostname`` command-line option. If
you have an instance of *tev* running, you can run pbrt like:
```bash
$ pbrt --display-server localhost:14158 scene.pbrt
```
In that case, the image will be progressively displayed as it renders.
Building the code
-----------------
As before, pbrt uses git submodules for a number of third-party libraries
that it depends on. Therefore, be sure to use the `--recursive` flag when
cloning the repository:
```bash
$ git clone --recursive https://github.com/mmp/pbrt-v4.git
```
If you accidentally clone pbrt without using ``--recursive`` (or to update
the pbrt source tree after a new submodule has been added, run the
following command to also fetch the dependencies:
```bash
$ git submodule update --init --recursive
```
pbrt uses [cmake](http://www.cmake.org/) for its build system. Note that a
release build is the default; provide `-DCMAKE_BUILD_TYPE=Debug` to cmake
for a debug build.
pbrt should build on any system that has C++ compiler with support for
C++17. We welcome PRs that make it build on more systems.
Bug Reports and PRs
-------------------
Please use the [pbrt-v4 github issue
tracker](https://github.com/mmp/pbrt-v4/issues) to report bugs in pbrt-v4.
(We have pre-populated it with a number of issues corresponding to known
bugs in the initial release.)
We are always happy to receive pull requests that fix bugs, including any
bugs you find yourself or open issues in the issue tracker.
Note, however, that in the interests of finishing the book in a finite
amount of time, the functionality of pbrt-v4 is basically fixed at this
point. We therefore will not be merging PRs that make major changes to the
system's operation or structure (but feel free to keep them in your own
forks!). Also, don't bother sending PRs for anything marked "TODO" or
"FIXME" in the source code; we'll take care of those as we finish polishing
things up.
Updating pbrt-v3 scenes
-----------------------
There are a variety of changes to the input file format and, as noted
above, the new format is not yet documented. However, pbrt-v4 partially
makes up for that by providing an automatic upgrade mechanism:
```bash
$ pbrt --upgrade old.pbrt > new.pbrt
```
Most scene files can be automatically updated. In some cases manual
intervention is required; an error message will be printed in this case.
The environment map parameterization has also changed (from equi-rect to an
equi-area mapping); you can upgrade environment maps using
```bash
$ imgtool makeenv old.exr --outfile new.exr
```
Using pbrt on the GPU
---------------------
To run on the GPU, pbrt requires:
* C++17 support on the GPU, including kernel launch with C++ lambdas.
* Unified memory so that the CPU can allocate and initialize data
structures for code that runs on the GPU.
* An API for ray-object intersections on the GPU.
These requirements are effectively what makes it possible to bring pbrt to
the GPU with limited changes to the core system. As a practical matter,
these capabilities are only available via CUDA and OptiX on NVIDIA GPUs
today, though we'd be happy to see pbrt running on any other GPUs that
provide those capabilities.
pbrt's GPU path specifically requires CUDA 11.0 and OptiX 7.1. The build
scripts will automatically attempt to find a CUDA compiler, looking in the
usual places; the cmake output will indicate whether it was successful. It
is necessary to set the cmake `PBRT_OPTIX7_PATH` configuration option to
point at an OptiX 7.1 install.
Even when compiled with GPU support, pbrt uses the CPU by default unless
the `--gpu` command-line option is given. Note that when rendering with
the GPU, the `--spp` command-line flag can be helpful to easily crank up
the number of samples per pixel. Also, it's extra fun to use *tev* to watch
rendering progress.
To denoise images using the OptiX denoiser, set the scene's "Film" type to
be "gbuffer" when rendering and use EXR for the image format; a "deep"
image will be generated with auxilary channels like albedo and normal that
are useful for the denoiser. The resulting EXR can be denoised using
```bash
$ imgtool denoise-optix noisy.exr --outfile denoised.exr
```

27
THIRD_PARTY.md Normal file
View file

@ -0,0 +1,27 @@
pbrt-v4 makes use of the following third-party libraries and data. Thanks
to all of the developers who have made these available!
* [double-conversion](https://github.com/google/double-conversion)
* [filesystem](https://github.com/wjakob/filesystem)
* [googletest](https://github.com/google/googletest)
* [lodepng](https://lodev.org/lodepng/)
* [OpenEXR](http:://www.openexr.com)
* [Ptex](http://ptex.us/)
* [rply](http://w3.impa.br/~diego/software/rply/)
* [skymodel](https://cgg.mff.cuni.cz/projects/SkylightModelling/)
* [stb](https://github.com/nothings/stb)
* [tinyobjloader](https://github.com/tinyobjloader/tinyobjloader)
* [zlib](https://zlib.net/)
Thanks also to Anders Langlands, who provided the Sensor implementation
used in the film model and Syoyo Fujita for the cyhair converter.
pbrt-v4 also includes spectral data from the following sources:
* Glass refractive index tables from https://refractiveindex.info, public
domain CC0.
* Camera sensor measurement data from https://github.com/ampas/rawtoaces,
Copyright © 2017 Academy of Motion Picture Arts and Sciences.

59
cmake/FindASan.cmake Normal file
View file

@ -0,0 +1,59 @@
# The MIT License (MIT)
#
# Copyright (c)
# 2013 Matthew Arsenault
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
option(SANITIZE_ADDRESS "Enable AddressSanitizer for sanitized targets." Off)
set(FLAG_CANDIDATES
# Clang 3.2+ use this version. The no-omit-frame-pointer option is optional.
"-g -fsanitize=address -fno-omit-frame-pointer"
"-g -fsanitize=address"
# Older deprecated flag for ASan
"-g -faddress-sanitizer"
)
if (SANITIZE_ADDRESS AND (SANITIZE_THREAD OR SANITIZE_MEMORY))
message(FATAL_ERROR "AddressSanitizer is not compatible with "
"ThreadSanitizer or MemorySanitizer.")
endif ()
include(sanitize-helpers)
if (SANITIZE_ADDRESS)
sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "AddressSanitizer"
"ASan")
find_program(ASan_WRAPPER "asan-wrapper" PATHS ${CMAKE_MODULE_PATH})
mark_as_advanced(ASan_WRAPPER)
endif ()
function (add_sanitize_address TARGET)
if (NOT SANITIZE_ADDRESS)
return()
endif ()
saitizer_add_flags(${TARGET} "AddressSanitizer" "ASan")
endfunction ()

57
cmake/FindMSan.cmake Normal file
View file

@ -0,0 +1,57 @@
# The MIT License (MIT)
#
# Copyright (c)
# 2013 Matthew Arsenault
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
option(SANITIZE_MEMORY "Enable MemorySanitizer for sanitized targets." Off)
set(FLAG_CANDIDATES
"-g -fsanitize=memory"
)
include(sanitize-helpers)
if (SANITIZE_MEMORY)
if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
message(WARNING "MemorySanitizer disabled for target ${TARGET} because "
"MemorySanitizer is supported for Linux systems only.")
set(SANITIZE_MEMORY Off CACHE BOOL
"Enable MemorySanitizer for sanitized targets." FORCE)
elseif (NOT ${CMAKE_SIZEOF_VOID_P} EQUAL 8)
message(WARNING "MemorySanitizer disabled for target ${TARGET} because "
"MemorySanitizer is supported for 64bit systems only.")
set(SANITIZE_MEMORY Off CACHE BOOL
"Enable MemorySanitizer for sanitized targets." FORCE)
else ()
sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "MemorySanitizer"
"MSan")
endif ()
endif ()
function (add_sanitize_memory TARGET)
if (NOT SANITIZE_MEMORY)
return()
endif ()
saitizer_add_flags(${TARGET} "MemorySanitizer" "MSan")
endfunction ()

View file

@ -0,0 +1,62 @@
# The MIT License (MIT)
#
# Copyright (c)
# 2013 Matthew Arsenault
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# If any of the used compiler is a GNU compiler, add a second option to static
# link against the sanitizers.
option(SANITIZE_LINK_STATIC "Try to link static against sanitizers." Off)
set(FIND_QUIETLY_FLAG "")
if (DEFINED Sanitizers_FIND_QUIETLY)
set(FIND_QUIETLY_FLAG "QUIET")
endif ()
find_package(ASan ${FIND_QUIETLY_FLAG})
find_package(TSan ${FIND_QUIETLY_FLAG})
find_package(MSan ${FIND_QUIETLY_FLAG})
find_package(UBSan ${FIND_QUIETLY_FLAG})
function(sanitizer_add_blacklist_file FILE)
if(NOT IS_ABSOLUTE ${FILE})
set(FILE "${CMAKE_CURRENT_SOURCE_DIR}/${FILE}")
endif()
get_filename_component(FILE "${FILE}" REALPATH)
sanitizer_check_compiler_flags("-fsanitize-blacklist=${FILE}"
"SanitizerBlacklist" "SanBlist")
endfunction()
function(add_sanitizers ...)
foreach (TARGET ${ARGV})
add_sanitize_address(${TARGET})
add_sanitize_thread(${TARGET})
add_sanitize_memory(${TARGET})
add_sanitize_undefined(${TARGET})
endforeach ()
endfunction(add_sanitizers)

64
cmake/FindTSan.cmake Normal file
View file

@ -0,0 +1,64 @@
# The MIT License (MIT)
#
# Copyright (c)
# 2013 Matthew Arsenault
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
option(SANITIZE_THREAD "Enable ThreadSanitizer for sanitized targets." Off)
set(FLAG_CANDIDATES
"-g -fsanitize=thread"
)
# ThreadSanitizer is not compatible with MemorySanitizer.
if (SANITIZE_THREAD AND SANITIZE_MEMORY)
message(FATAL_ERROR "ThreadSanitizer is not compatible with "
"MemorySanitizer.")
endif ()
include(sanitize-helpers)
if (SANITIZE_THREAD)
if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
message(WARNING "ThreadSanitizer disabled for target ${TARGET} because "
"ThreadSanitizer is supported for Linux systems only.")
set(SANITIZE_THREAD Off CACHE BOOL
"Enable ThreadSanitizer for sanitized targets." FORCE)
elseif (NOT ${CMAKE_SIZEOF_VOID_P} EQUAL 8)
message(WARNING "ThreadSanitizer disabled for target ${TARGET} because "
"ThreadSanitizer is supported for 64bit systems only.")
set(SANITIZE_THREAD Off CACHE BOOL
"Enable ThreadSanitizer for sanitized targets." FORCE)
else ()
sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "ThreadSanitizer"
"TSan")
endif ()
endif ()
function (add_sanitize_thread TARGET)
if (NOT SANITIZE_THREAD)
return()
endif ()
saitizer_add_flags(${TARGET} "ThreadSanitizer" "TSan")
endfunction ()

46
cmake/FindUBSan.cmake Normal file
View file

@ -0,0 +1,46 @@
# The MIT License (MIT)
#
# Copyright (c)
# 2013 Matthew Arsenault
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
option(SANITIZE_UNDEFINED
"Enable UndefinedBehaviorSanitizer for sanitized targets." Off)
set(FLAG_CANDIDATES
"-g -fsanitize=undefined"
)
include(sanitize-helpers)
if (SANITIZE_UNDEFINED)
sanitizer_check_compiler_flags("${FLAG_CANDIDATES}"
"UndefinedBehaviorSanitizer" "UBSan")
endif ()
function (add_sanitize_undefined TARGET)
if (NOT SANITIZE_UNDEFINED)
return()
endif ()
saitizer_add_flags(${TARGET} "UndefinedBehaviorSanitizer" "UBSan")
endfunction ()

55
cmake/asan-wrapper Executable file
View file

@ -0,0 +1,55 @@
#!/bin/sh
# The MIT License (MIT)
#
# Copyright (c)
# 2013 Matthew Arsenault
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# This script is a wrapper for AddressSanitizer. In some special cases you need
# to preload AddressSanitizer to avoid error messages - e.g. if you're
# preloading another library to your application. At the moment this script will
# only do something, if we're running on a Linux platform. OSX might not be
# affected.
# Exit immediately, if platform is not Linux.
if [ "$(uname)" != "Linux" ]
then
exec $@
fi
# Get the used libasan of the application ($1). If a libasan was found, it will
# be prepended to LD_PRELOAD.
libasan=$(ldd $1 | grep libasan | sed "s/^[[:space:]]//" | cut -d' ' -f1)
if [ -n "$libasan" ]
then
if [ -n "$LD_PRELOAD" ]
then
export LD_PRELOAD="$libasan:$LD_PRELOAD"
else
export LD_PRELOAD="$libasan"
fi
fi
# Execute the application.
exec $@

24
cmake/checkcuda.cu Normal file
View file

@ -0,0 +1,24 @@
// https://wagonhelm.github.io/articles/2018-03/detecting-cuda-capability-with-cmake
// Justin Francis
#include <stdio.h>
int main(int argc, char **argv){
cudaDeviceProp dP;
float min_cc = 5.0; // TODO: figure out what this should be.
int rc = cudaGetDeviceProperties(&dP, 0);
if(rc != cudaSuccess) {
cudaError_t error = cudaGetLastError();
printf("CUDA error: %s", cudaGetErrorString(error));
return rc; /* Failure */
}
if((dP.major+(dP.minor/10)) < min_cc) {
printf("Min Compute Capability of %2.1f required: %d.%d found\n Not Building CUDA Code",
min_cc, dP.major, dP.minor);
return 1; /* Failure */
} else {
printf("sm_%d%d", dP.major, dP.minor);
return 0; /* Success */
}
}

View file

@ -0,0 +1,173 @@
# The MIT License (MIT)
#
# Copyright (c)
# 2013 Matthew Arsenault
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# Helper function to get the language of a source file.
function (sanitizer_lang_of_source FILE RETURN_VAR)
get_filename_component(FILE_EXT "${FILE}" EXT)
string(TOLOWER "${FILE_EXT}" FILE_EXT)
string(SUBSTRING "${FILE_EXT}" 1 -1 FILE_EXT)
get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
foreach (LANG ${ENABLED_LANGUAGES})
list(FIND CMAKE_${LANG}_SOURCE_FILE_EXTENSIONS "${FILE_EXT}" TEMP)
if (NOT ${TEMP} EQUAL -1)
set(${RETURN_VAR} "${LANG}" PARENT_SCOPE)
return()
endif ()
endforeach()
set(${RETURN_VAR} "" PARENT_SCOPE)
endfunction ()
# Helper function to get compilers used by a target.
function (sanitizer_target_compilers TARGET RETURN_VAR)
# Check if all sources for target use the same compiler. If a target uses
# e.g. C and Fortran mixed and uses different compilers (e.g. clang and
# gfortran) this can trigger huge problems, because different compilers may
# use different implementations for sanitizers.
set(BUFFER "")
get_target_property(TSOURCES ${TARGET} SOURCES)
foreach (FILE ${TSOURCES})
# If expression was found, FILE is a generator-expression for an object
# library. Object libraries will be ignored.
string(REGEX MATCH "TARGET_OBJECTS:([^ >]+)" _file ${FILE})
if ("${_file}" STREQUAL "")
sanitizer_lang_of_source(${FILE} LANG)
if (LANG)
list(APPEND BUFFER ${CMAKE_${LANG}_COMPILER_ID})
endif ()
endif ()
endforeach ()
list(REMOVE_DUPLICATES BUFFER)
set(${RETURN_VAR} "${BUFFER}" PARENT_SCOPE)
endfunction ()
# Helper function to check compiler flags for language compiler.
function (sanitizer_check_compiler_flag FLAG LANG VARIABLE)
if (${LANG} STREQUAL "C")
include(CheckCCompilerFlag)
check_c_compiler_flag("${FLAG}" ${VARIABLE})
elseif (${LANG} STREQUAL "CXX")
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag("${FLAG}" ${VARIABLE})
elseif (${LANG} STREQUAL "Fortran")
# CheckFortranCompilerFlag was introduced in CMake 3.x. To be compatible
# with older Cmake versions, we will check if this module is present
# before we use it. Otherwise we will define Fortran coverage support as
# not available.
include(CheckFortranCompilerFlag OPTIONAL RESULT_VARIABLE INCLUDED)
if (INCLUDED)
check_fortran_compiler_flag("${FLAG}" ${VARIABLE})
elseif (NOT CMAKE_REQUIRED_QUIET)
message(STATUS "Performing Test ${VARIABLE}")
message(STATUS "Performing Test ${VARIABLE}"
" - Failed (Check not supported)")
endif ()
endif()
endfunction ()
# Helper function to test compiler flags.
function (sanitizer_check_compiler_flags FLAG_CANDIDATES NAME PREFIX)
set(CMAKE_REQUIRED_QUIET ${${PREFIX}_FIND_QUIETLY})
get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
foreach (LANG ${ENABLED_LANGUAGES})
# Sanitizer flags are not dependend on language, but the used compiler.
# So instead of searching flags foreach language, search flags foreach
# compiler used.
set(COMPILER ${CMAKE_${LANG}_COMPILER_ID})
if (NOT DEFINED ${PREFIX}_${COMPILER}_FLAGS)
foreach (FLAG ${FLAG_CANDIDATES})
if(NOT CMAKE_REQUIRED_QUIET)
message(STATUS "Try ${COMPILER} ${NAME} flag = [${FLAG}]")
endif()
set(CMAKE_REQUIRED_FLAGS "${FLAG}")
unset(${PREFIX}_FLAG_DETECTED CACHE)
sanitizer_check_compiler_flag("${FLAG}" ${LANG}
${PREFIX}_FLAG_DETECTED)
if (${PREFIX}_FLAG_DETECTED)
# If compiler is a GNU compiler, search for static flag, if
# SANITIZE_LINK_STATIC is enabled.
if (SANITIZE_LINK_STATIC AND (${COMPILER} STREQUAL "GNU"))
string(TOLOWER ${PREFIX} PREFIX_lower)
sanitizer_check_compiler_flag(
"-static-lib${PREFIX_lower}" ${LANG}
${PREFIX}_STATIC_FLAG_DETECTED)
if (${PREFIX}_STATIC_FLAG_DETECTED)
set(FLAG "-static-lib${PREFIX_lower} ${FLAG}")
endif ()
endif ()
set(${PREFIX}_${COMPILER}_FLAGS "${FLAG}" CACHE STRING
"${NAME} flags for ${COMPILER} compiler.")
mark_as_advanced(${PREFIX}_${COMPILER}_FLAGS)
break()
endif ()
endforeach ()
if (NOT ${PREFIX}_FLAG_DETECTED)
set(${PREFIX}_${COMPILER}_FLAGS "" CACHE STRING
"${NAME} flags for ${COMPILER} compiler.")
mark_as_advanced(${PREFIX}_${COMPILER}_FLAGS)
endif ()
endif ()
endforeach ()
endfunction ()
# Helper to assign sanitizer flags for TARGET.
function (saitizer_add_flags TARGET NAME PREFIX)
# Get list of compilers used by target and check, if target can be checked
# by sanitizer.
sanitizer_target_compilers(${TARGET} TARGET_COMPILER)
list(LENGTH TARGET_COMPILER NUM_COMPILERS)
if (NUM_COMPILERS GREATER 1)
message(WARNING "${NAME} disabled for target ${TARGET} because it will "
"be compiled by different compilers.")
return()
elseif ((NUM_COMPILERS EQUAL 0) OR
("${${PREFIX}_${TARGET_COMPILER}_FLAGS}" STREQUAL ""))
message(WARNING "${NAME} disabled for target ${TARGET} because there is"
" no sanitizer available for target sources.")
return()
endif()
# Set compile- and link-flags for target.
set_property(TARGET ${TARGET} APPEND_STRING
PROPERTY COMPILE_FLAGS " ${${PREFIX}_${TARGET_COMPILER}_FLAGS}")
set_property(TARGET ${TARGET} APPEND_STRING
PROPERTY COMPILE_FLAGS " ${SanBlist_${TARGET_COMPILER}_FLAGS}")
set_property(TARGET ${TARGET} APPEND_STRING
PROPERTY LINK_FLAGS " ${${PREFIX}_${TARGET_COMPILER}_FLAGS}")
endfunction ()

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,23 @@
enum
{
DLG_PBRT = 60000,
IDS_PBRT,
IDC_PBRT_START,
IDC_PBRT_LOG,
IDC_PBRT_EXE,
IDC_PBRT_MODE,
IDC_PBRT_MODE_EXPORT,
IDC_PBRT_MODE_EXPORT_AND_RENDER,
IDC_PBRT_MODE_RENDER,
IDC_PBRT_SAMPLES,
IDC_PBRT_ABORT,
IDC_BUTTON_GROUP,
IDC_PBRT_LOGLEVEL,
IDC_PBRT_LOGLEVEL_DEBUG,
IDC_PBRT_LOGLEVEL_INFO,
IDC_PBRT_LOGLEVEL_WARNING,
IDC_PBRT_LOGLEVEL_ERROR,
IDC_PBRT_INTENSITY,
IDS_PBRT_START = 60100,
IDS_PBRT_ABORT,
};

View file

@ -0,0 +1,62 @@
// C4D-DialogResource
DIALOG DLG_PBRT
{
NAME IDS_PBRT;
SCALE_V; SCALE_H;
GROUP
{
SCALE_V; SCALE_H;
COLUMNS 1;
GROUP
{
SCALE_H;
COLUMNS 2;
BORDERSIZE 4, 4, 4, 4;
STATICTEXT 0 { NAME IDS_PBRT_MODE; ALIGN_LEFT; }
COMBOBOX IDC_PBRT_MODE
{
//SIZE 100;
SCALE_H;
CHILDS
{
IDC_PBRT_MODE_EXPORT_AND_RENDER, IDS_PBRT_MODE_EXPORT_AND_RENDER;
IDC_PBRT_MODE_EXPORT, IDS_PBRT_MODE_EXPORT;
IDC_PBRT_MODE_RENDER, IDS_PBRT_MODE_RENDER;
}
}
STATICTEXT 0 { NAME IDS_PBRT_EXE; ALIGN_LEFT; }
FILENAME IDC_PBRT_EXE { SCALE_H; }
STATICTEXT 0 { NAME IDS_PBRT_SAMPLES; ALIGN_LEFT; }
EDITNUMBERARROWS IDC_PBRT_SAMPLES { SCALE_H; }
STATICTEXT 0 { NAME IDS_PBRT_INTENSITY; ALIGN_LEFT; }
EDITNUMBERARROWS IDC_PBRT_INTENSITY { SCALE_H; }
STATICTEXT 0 { NAME IDS_PBRT_LOGLEVEL; ALIGN_LEFT; }
COMBOBOX IDC_PBRT_LOGLEVEL
{
SCALE_H;
CHILDS
{
IDC_PBRT_LOGLEVEL_DEBUG, IDS_PBRT_LOGLEVEL_DEBUG;
IDC_PBRT_LOGLEVEL_INFO, IDS_PBRT_LOGLEVEL_INFO;
IDC_PBRT_LOGLEVEL_WARNING, IDS_PBRT_LOGLEVEL_WARNING;
IDC_PBRT_LOGLEVEL_ERROR, IDS_PBRT_LOGLEVEL_ERROR;
}
}
STATICTEXT 0 { }
GROUP IDC_BUTTON_GROUP
{
SCALE_H;
COLUMNS 1;
}
}
TREEVIEW IDC_PBRT_LOG { HAS_HEADER; ALTERNATE_BG; FIXED_LAYOUT; RESIZE_HEADER; SCALE_V; SCALE_H; BORDER; }
}
}

Binary file not shown.

View file

@ -0,0 +1,8 @@
// C4D-StringResource
// Identifier Text
STRINGTABLE
{
IDS_PBRT_START "Start";
IDS_PBRT_ABORT "Abort";
}

View file

@ -0,0 +1,18 @@
// C4D-DialogResource
DIALOGSTRINGS DLG_PBRT
{
IDS_PBRT "Export to PBRT";
IDS_PBRT_EXE "Renderer";
IDS_PBRT_MODE "Mode";
IDS_PBRT_MODE_EXPORT "Export";
IDS_PBRT_MODE_EXPORT_AND_RENDER "Export and Render";
IDS_PBRT_MODE_RENDER "Render";
IDS_PBRT_SAMPLES "Samples";
IDS_PBRT_INTENSITY "Light Intensity";
IDS_PBRT_LOGLEVEL "Logging Level";
IDS_PBRT_LOGLEVEL_DEBUG "Debug";
IDS_PBRT_LOGLEVEL_INFO "Info";
IDS_PBRT_LOGLEVEL_WARNING "Warning";
IDS_PBRT_LOGLEVEL_ERROR "Error";
}

View file

@ -0,0 +1,27 @@
# PBRT Exporter for Cinema 4D
## Compatibility
This version of the PBRT Exporter for Cinema 4D has been tested with PBRT v3 and Cinema 4D R16. It will probably also work with earlier and later Cinema 4D versions. It will definitely not work with different versions than PBRT v3.
## Installation
To install the exporter, just copy the 'PBRT Export' folder into the plugins folder of your Cinema 4D Installation. It will then show up in the Plugins menu the next time you start Cinema 4D.
## Operation
Choosing 'Export to PBRT...' from the Plugins menu will open the export dialog. The export mode controls where PBRT files are written and whether the renderer is started. The 'Render' mode will export the scene to a temporary location, start pbrt and open the resulting image in the Picture Viewer once the rendering is done. 'Export' will ask you for a location the pbrt scene should be written to. 'Export and Render' will ask you where the scene should be written and will start a rendering. For 'Render' and 'Export and Render' is important to let the plugin know where your pbrt executable is located. This can be specified using the 'Renderer' input field. 'Samples' allows you to specify the number of Samples per Pixel to be used. 'Light Intensity' lets you globally scale the intensity of all exported light sources in the scene. During export, a detailed log is created. The 'Logging Level' lets you choose how much detail you want to see in the log window at the bottom of the export dialog.
By default, the 'directlighting' integrator is used. When a Global Illumination effect is added to the regular Cinema 4D render settings, the 'path' integrator is used instead.
## Supported Features
- Omni and Distant Light sources are exported
- The Physical Sky object will have appropriate light sources added and the background is baked into an environment texture and added as infinite light.
- All geometric objects that create polygons are exported.
- The plugin attempts to move basic material attributes (base color, specularity, bump). Furthermore it detects translucency setups using the Backlight shader and attempts to translate those.
## Copyright
This plugin has been created by Burak Kahraman and Timm Dapper of Laubwerk GmbH (www.laubwerk.com). It is distributed under the same license as the rest of the PBRT repository.

98
src/ext/CMakeLists.txt Normal file
View file

@ -0,0 +1,98 @@
cmake_minimum_required (VERSION 3.12)
###########################################################################
# stb
set (STB_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/stb PARENT_SCOPE)
###########################################################################
# filesystem
set (FILESYSTEM_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/filesystem PARENT_SCOPE)
###########################################################################
# zlib
find_package (ZLIB)
if (NOT ZLIB_FOUND)
# Build zlib
set (ZLIB_BUILD_STATIC_LIBS ON CACHE BOOL " " FORCE)
set (ZLIB_BUILD_SHARED_LIBS OFF CACHE BOOL " " FORCE)
add_subdirectory (zlib)
set (ZLIB_LIBRARIES zlibstatic)
set (ZLIB_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/zlib ${CMAKE_CURRENT_BINARY_DIR}/zlib)
# try to make openexr happy about this...
set (ZLIB_LIBRARY zlibstatic)
set (ZLIB_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/zlib ${CMAKE_CURRENT_BINARY_DIR}/zlib)
set (ZLIB_FOUND TRUE)
set_property (TARGET zlibstatic PROPERTY FOLDER "ext")
add_library (ZLIB::ZLIB ALIAS zlibstatic)
include_directories(${ZLIB_INCLUDE_DIRS}) # yuck, but so openexr/ptex can find zlib.h...
endif ()
set (ZLIB_INCLUDE_DIRS ${ZLIB_INCLUDE_DIRS} PARENT_SCOPE)
set (ZLIB_LIBRARIES ${ZLIB_LIBARIES} PARENT_SCOPE)
###########################################################################
# OpenEXR
set (ILMBASE_NAMESPACE_VERSIONING OFF CACHE BOOL " " FORCE)
set (OPENEXR_NAMESPACE_VERSIONING OFF CACHE BOOL " " FORCE)
set (OPENEXR_BUILD_SHARED_LIBS OFF CACHE BOOL " " FORCE)
set (ILMBASE_BUILD_SHARED_LIBS OFF CACHE BOOL " " FORCE)
set (PYILMBASE_ENABLE OFF CACHE BOOL " " FORCE)
set (OPENEXR_BUILD_UTILS OFF CACHE BOOL " " FORCE)
add_subdirectory (openexr)
set_property (TARGET IexMath IlmThread Half
Iex Imath IlmImf HalfTest IexTest
IlmImfExamples IlmImfTest IlmImfUtil IlmImfUtilTest ImathTest
PROPERTY FOLDER "ext/OpenEXR")
set (OPENEXR_INCLUDE
${CMAKE_CURRENT_SOURCE_DIR}/openexr/IlmBase/Imath
${CMAKE_CURRENT_SOURCE_DIR}/openexr/IlmBase/Half
${CMAKE_CURRENT_SOURCE_DIR}/openexr/IlmBase/Iex
${CMAKE_CURRENT_SOURCE_DIR}/openexr/OpenEXR/IlmImf
${CMAKE_CURRENT_BINARY_DIR}/openexr/IlmBase/config
${CMAKE_CURRENT_BINARY_DIR}/openexr/OpenEXR/config
PARENT_SCOPE
)
if (WIN32)
set (OPENEXR_LIBS OpenEXR::IlmImf IlmBase::Imath IlmBase::Half ${ZLIB_LIBRARY} PARENT_SCOPE)
else ()
set (OPENEXR_LIBS OpenEXR::IlmImf IlmBase::Imath IlmBase::Half PARENT_SCOPE)
endif ()
###########################################################################
# ptex
set (PTEX_BUILD_SHARED_LIBS OFF CACHE BOOL " " FORCE)
set (CMAKE_MACOSX_RPATH 1)
if (WIN32)
add_definitions (/D PTEX_STATIC)
endif ()
add_subdirectory (ptex)
set_property (TARGET Ptex_static ptxinfo halftest ftest rtest wtest PROPERTY FOLDER "ext/ptex")
set (PTEX_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/ptex/src/ptex PARENT_SCOPE)
###########################################################################
# double-conversion
add_subdirectory (double-conversion)
set (DOUBLE_CONVERSION_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/double-conversion PARENT_SCOPE)
set_property (TARGET double-conversion cctest PROPERTY FOLDER "ext")

@ -0,0 +1 @@
Subproject commit cc1f75a114aca8d2af69f73a5a959aecbab0e87a

1
src/ext/filesystem Submodule

@ -0,0 +1 @@
Subproject commit f45da753728cde9b1c380b343e41c8b1ca6498d7

9592
src/ext/gtest/gtest-all.cc Normal file

File diff suppressed because it is too large Load diff

20065
src/ext/gtest/gtest.h Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,41 @@
// Copyright 2006, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <stdio.h>
#include <gtest/gtest.h>
#include <glog/logging.h>
GTEST_API_ int main(int argc, char **argv) {
google::InitGoogleLogging(argv[0]);
FLAGS_stderrthreshold = 1; // Warning and above.
printf("Running main() from gtest_main.cc\n");
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

6224
src/ext/lodepng/lodepng.cpp Normal file

File diff suppressed because it is too large Load diff

1759
src/ext/lodepng/lodepng.h Normal file

File diff suppressed because it is too large Load diff

1
src/ext/openexr Submodule

@ -0,0 +1 @@
Subproject commit 5cfb5dab6dfada731586b0281bdb15ee75e26782

1
src/ext/ptex Submodule

@ -0,0 +1 @@
Subproject commit 77b387406028d0dd6fea76d59d51e17aafe53358

1612
src/ext/rply/rply.cpp Normal file

File diff suppressed because it is too large Load diff

389
src/ext/rply/rply.h Normal file
View file

@ -0,0 +1,389 @@
#if defined(_MSC_VER)
#ifndef NOMINMAX
#define NOMINMAX
#endif
#endif
#ifndef PBRT_EXT_RPLY_H
#define PBRT_EXT_RPLY_H
// ext/rply.h*
/* ----------------------------------------------------------------------
* RPly library, read/write PLY files
* Diego Nehab, IMPA
* http://www.impa.br/~diego/software/rply
*
* This library is distributed under the MIT License. See notice
* at the end of this file.
* ---------------------------------------------------------------------- */
#define RPLY_VERSION "RPly 1.1.3"
#define RPLY_COPYRIGHT "Copyright (C) 2003-2013 Diego Nehab"
#define RPLY_AUTHORS "Diego Nehab"
/* ----------------------------------------------------------------------
* Types
* ---------------------------------------------------------------------- */
/* structures are opaque */
typedef struct t_ply_ *p_ply;
typedef struct t_ply_element_ *p_ply_element;
typedef struct t_ply_property_ *p_ply_property;
typedef struct t_ply_argument_ *p_ply_argument;
/* ply format mode type */
typedef enum e_ply_storage_mode_ {
PLY_BIG_ENDIAN,
PLY_LITTLE_ENDIAN,
PLY_ASCII,
PLY_DEFAULT /* has to be the last in enum */
} e_ply_storage_mode; /* order matches ply_storage_mode_list */
/* ply data type */
typedef enum e_ply_type {
PLY_INT8,
PLY_UINT8,
PLY_INT16,
PLY_UINT16,
PLY_INT32,
PLY_UIN32,
PLY_FLOAT32,
PLY_FLOAT64,
PLY_CHAR,
PLY_UCHAR,
PLY_SHORT,
PLY_USHORT,
PLY_INT,
PLY_UINT,
PLY_FLOAT,
PLY_DOUBLE,
PLY_LIST /* has to be the last in enum */
} e_ply_type; /* order matches ply_type_list */
/* ----------------------------------------------------------------------
* Error callback prototype
*
* message: error message
* ply: handle returned by ply_open or ply_create
* ---------------------------------------------------------------------- */
typedef void (*p_ply_error_cb)(p_ply ply, const char *message);
/* ----------------------------------------------------------------------
* Gets user data from within an error callback
*
* ply: handle returned by ply_open or ply_create
* idata,pdata: contextual information set in ply_open or ply_create
* ---------------------------------------------------------------------- */
int ply_get_ply_user_data(p_ply ply, void **pdata, long *idata);
/* ----------------------------------------------------------------------
* Opens a PLY file for reading (fails if file is not a PLY file)
*
* name: file name
* error_cb: error callback function
* idata,pdata: contextual information available to users
*
* Returns 1 if successful, 0 otherwise
* ---------------------------------------------------------------------- */
p_ply ply_open(const char *name, p_ply_error_cb error_cb, long idata,
void *pdata);
/* ----------------------------------------------------------------------
* Reads and parses the header of a PLY file returned by ply_open
*
* ply: handle returned by ply_open
*
* Returns 1 if successfull, 0 otherwise
* ---------------------------------------------------------------------- */
int ply_read_header(p_ply ply);
/* ----------------------------------------------------------------------
* Property reading callback prototype
*
* argument: parameters for property being processed when callback is called
*
* Returns 1 if should continue processing file, 0 if should abort.
* ---------------------------------------------------------------------- */
typedef int (*p_ply_read_cb)(p_ply_argument argument);
/* ----------------------------------------------------------------------
* Sets up callbacks for property reading after header was parsed
*
* ply: handle returned by ply_open
* element_name: element where property is
* property_name: property to associate element with
* read_cb: function to be called for each property value
* pdata/idata: user data that will be passed to callback
*
* Returns 0 if no element or no property in element, returns the
* number of element instances otherwise.
* ---------------------------------------------------------------------- */
long ply_set_read_cb(p_ply ply, const char *element_name,
const char *property_name, p_ply_read_cb read_cb,
void *pdata, long idata);
/* ----------------------------------------------------------------------
* Returns information about the element originating a callback
*
* argument: handle to argument
* element: receives a the element handle (if non-null)
* instance_index: receives the index of the current element instance
* (if non-null)
*
* Returns 1 if successfull, 0 otherwise
* ---------------------------------------------------------------------- */
int ply_get_argument_element(p_ply_argument argument, p_ply_element *element,
long *instance_index);
/* ----------------------------------------------------------------------
* Returns information about the property originating a callback
*
* argument: handle to argument
* property: receives the property handle (if non-null)
* length: receives the number of values in this property (if non-null)
* value_index: receives the index of current property value (if non-null)
*
* Returns 1 if successfull, 0 otherwise
* ---------------------------------------------------------------------- */
int ply_get_argument_property(p_ply_argument argument, p_ply_property *property,
long *length, long *value_index);
/* ----------------------------------------------------------------------
* Returns user data associated with callback
*
* pdata: receives a copy of user custom data pointer (if non-null)
* idata: receives a copy of user custom data integer (if non-null)
*
* Returns 1 if successfull, 0 otherwise
* ---------------------------------------------------------------------- */
int ply_get_argument_user_data(p_ply_argument argument, void **pdata,
long *idata);
/* ----------------------------------------------------------------------
* Returns the value associated with a callback
*
* argument: handle to argument
*
* Returns the current data item
* ---------------------------------------------------------------------- */
double ply_get_argument_value(p_ply_argument argument);
/* ----------------------------------------------------------------------
* Reads all elements and properties calling the callbacks defined with
* calls to ply_set_read_cb
*
* ply: handle returned by ply_open
*
* Returns 1 if successfull, 0 otherwise
* ---------------------------------------------------------------------- */
int ply_read(p_ply ply);
/* ----------------------------------------------------------------------
* Iterates over all elements by returning the next element.
* Call with NULL to return handle to first element.
*
* ply: handle returned by ply_open
* last: handle of last element returned (NULL for first element)
*
* Returns element if successfull or NULL if no more elements
* ---------------------------------------------------------------------- */
p_ply_element ply_get_next_element(p_ply ply, p_ply_element last);
/* ----------------------------------------------------------------------
* Iterates over all comments by returning the next comment.
* Call with NULL to return pointer to first comment.
*
* ply: handle returned by ply_open
* last: pointer to last comment returned (NULL for first comment)
*
* Returns comment if successfull or NULL if no more comments
* ---------------------------------------------------------------------- */
const char *ply_get_next_comment(p_ply ply, const char *last);
/* ----------------------------------------------------------------------
* Iterates over all obj_infos by returning the next obj_info.
* Call with NULL to return pointer to first obj_info.
*
* ply: handle returned by ply_open
* last: pointer to last obj_info returned (NULL for first obj_info)
*
* Returns obj_info if successfull or NULL if no more obj_infos
* ---------------------------------------------------------------------- */
const char *ply_get_next_obj_info(p_ply ply, const char *last);
/* ----------------------------------------------------------------------
* Returns information about an element
*
* element: element of interest
* name: receives a pointer to internal copy of element name (if non-null)
* ninstances: receives the number of instances of this element (if non-null)
*
* Returns 1 if successfull or 0 otherwise
* ---------------------------------------------------------------------- */
int ply_get_element_info(p_ply_element element, const char **name,
long *ninstances);
/* ----------------------------------------------------------------------
* Iterates over all properties by returning the next property.
* Call with NULL to return handle to first property.
*
* element: handle of element with the properties of interest
* last: handle of last property returned (NULL for first property)
*
* Returns element if successfull or NULL if no more properties
* ---------------------------------------------------------------------- */
p_ply_property ply_get_next_property(p_ply_element element,
p_ply_property last);
/* ----------------------------------------------------------------------
* Returns information about a property
*
* property: handle to property of interest
* name: receives a pointer to internal copy of property name (if non-null)
* type: receives the property type (if non-null)
* length_type: for list properties, receives the scalar type of
* the length field (if non-null)
* value_type: for list properties, receives the scalar type of the value
* fields (if non-null)
*
* Returns 1 if successfull or 0 otherwise
* ---------------------------------------------------------------------- */
int ply_get_property_info(p_ply_property property, const char **name,
e_ply_type *type, e_ply_type *length_type,
e_ply_type *value_type);
/* ----------------------------------------------------------------------
* Creates new PLY file
*
* name: file name
* storage_mode: file format mode
*
* Returns handle to PLY file if successfull, NULL otherwise
* ---------------------------------------------------------------------- */
p_ply ply_create(const char *name, e_ply_storage_mode storage_mode,
p_ply_error_cb error_cb, long idata, void *pdata);
/* ----------------------------------------------------------------------
* Adds a new element to the PLY file created by ply_create
*
* ply: handle returned by ply_create
* name: name of new element
* ninstances: number of element of this time in file
*
* Returns 1 if successfull, 0 otherwise
* ---------------------------------------------------------------------- */
int ply_add_element(p_ply ply, const char *name, long ninstances);
/* ----------------------------------------------------------------------
* Adds a new property to the last element added by ply_add_element
*
* ply: handle returned by ply_create
* name: name of new property
* type: property type
* length_type: scalar type of length field of a list property
* value_type: scalar type of value fields of a list property
*
* Returns 1 if successfull, 0 otherwise
* ---------------------------------------------------------------------- */
int ply_add_property(p_ply ply, const char *name, e_ply_type type,
e_ply_type length_type, e_ply_type value_type);
/* ----------------------------------------------------------------------
* Adds a new list property to the last element added by ply_add_element
*
* ply: handle returned by ply_create
* name: name of new property
* length_type: scalar type of length field of a list property
* value_type: scalar type of value fields of a list property
*
* Returns 1 if successfull, 0 otherwise
* ---------------------------------------------------------------------- */
int ply_add_list_property(p_ply ply, const char *name, e_ply_type length_type,
e_ply_type value_type);
/* ----------------------------------------------------------------------
* Adds a new property to the last element added by ply_add_element
*
* ply: handle returned by ply_create
* name: name of new property
* type: property type
*
* Returns 1 if successfull, 0 otherwise
* ---------------------------------------------------------------------- */
int ply_add_scalar_property(p_ply ply, const char *name, e_ply_type type);
/* ----------------------------------------------------------------------
* Adds a new comment item
*
* ply: handle returned by ply_create
* comment: pointer to string with comment text
*
* Returns 1 if successfull, 0 otherwise
* ---------------------------------------------------------------------- */
int ply_add_comment(p_ply ply, const char *comment);
/* ----------------------------------------------------------------------
* Adds a new obj_info item
*
* ply: handle returned by ply_create
* comment: pointer to string with obj_info data
*
* Returns 1 if successfull, 0 otherwise
* ---------------------------------------------------------------------- */
int ply_add_obj_info(p_ply ply, const char *obj_info);
/* ----------------------------------------------------------------------
* Writes the PLY file header after all element and properties have been
* defined by calls to ply_add_element and ply_add_property
*
* ply: handle returned by ply_create
*
* Returns 1 if successfull, 0 otherwise
* ---------------------------------------------------------------------- */
int ply_write_header(p_ply ply);
/* ----------------------------------------------------------------------
* Writes one property value, in the order they should be written to the
* file. For each element type, write all elements of that type in order.
* For each element, write all its properties in order. For scalar
* properties, just write the value. For list properties, write the length
* and then each of the values.
*
* ply: handle returned by ply_create
*
* Returns 1 if successfull, 0 otherwise
* ---------------------------------------------------------------------- */
int ply_write(p_ply ply, double value);
/* ----------------------------------------------------------------------
* Closes a PLY file handle. Releases all memory used by handle
*
* ply: handle to be closed.
*
* Returns 1 if successfull, 0 otherwise
* ---------------------------------------------------------------------- */
int ply_close(p_ply ply);
/* ----------------------------------------------------------------------
* Copyright (C) 2003-2011 Diego Nehab. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* ---------------------------------------------------------------------- */
#endif // PBRT_EXT_RPLY_H

View file

@ -0,0 +1,825 @@
/*
This source is published under the following 3-clause BSD license.
Copyright (c) 2012 - 2013, Lukas Hosek and Alexander Wilkie
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* None of the names of the contributors may be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* ============================================================================
This file is part of a sample implementation of the analytical skylight and
solar radiance models presented in the SIGGRAPH 2012 paper
"An Analytic Model for Full Spectral Sky-Dome Radiance"
and the 2013 IEEE CG&A paper
"Adding a Solar Radiance Function to the Hosek Skylight Model"
both by
Lukas Hosek and Alexander Wilkie
Charles University in Prague, Czech Republic
Version: 1.4a, February 22nd, 2013
Version history:
1.4a February 22nd, 2013
Removed unnecessary and counter-intuitive solar radius parameters
from the interface of the colourspace sky dome initialisation functions.
1.4 February 11th, 2013
Fixed a bug which caused the relative brightness of the solar disc
and the sky dome to be off by a factor of about 6. The sun was too
bright: this affected both normal and alien sun scenarios. The
coefficients of the solar radiance function were changed to fix this.
1.3 January 21st, 2013 (not released to the public)
Added support for solar discs that are not exactly the same size as
the terrestrial sun. Also added support for suns with a different
emission spectrum ("Alien World" functionality).
1.2a December 18th, 2012
Fixed a mistake and some inaccuracies in the solar radiance function
explanations found in ArHosekSkyModel.h. The actual source code is
unchanged compared to version 1.2.
1.2 December 17th, 2012
Native RGB data and a solar radiance function that matches the turbidity
conditions were added.
1.1 September 2012
The coefficients of the spectral model are now scaled so that the output
is given in physical units: W / (m^-2 * sr * nm). Also, the output of the
XYZ model is now no longer scaled to the range [0...1]. Instead, it is
the result of a simple conversion from spectral data via the CIE 2 degree
standard observer matching functions. Therefore, after multiplication
with 683 lm / W, the Y channel now corresponds to luminance in lm.
1.0 May 11th, 2012
Initial release.
Please visit http://cgg.mff.cuni.cz/projects/SkylightModelling/ to check if
an updated version of this code has been published!
============================================================================ */
/*
All instructions on how to use this code are in the accompanying header file.
*/
#include "ArHosekSkyModel.h"
#include "ArHosekSkyModelData_Spectral.h"
#include "ArHosekSkyModelData_CIEXYZ.h"
#include "ArHosekSkyModelData_RGB.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
// Some macro definitions that occur elsewhere in ART, and that have to be
// replicated to make this a stand-alone module.
#ifndef NIL
#define NIL 0
#endif
#ifndef MATH_PI
#define MATH_PI 3.141592653589793
#endif
#ifndef MATH_DEG_TO_RAD
#define MATH_DEG_TO_RAD ( MATH_PI / 180.0 )
#endif
#ifndef MATH_RAD_TO_DEG
#define MATH_RAD_TO_DEG ( 180.0 / MATH_PI )
#endif
#ifndef DEGREES
#define DEGREES * MATH_DEG_TO_RAD
#endif
#ifndef TERRESTRIAL_SOLAR_RADIUS
#define TERRESTRIAL_SOLAR_RADIUS ( ( 0.51 DEGREES ) / 2.0 )
#endif
#ifndef ALLOC
#define ALLOC(_struct) ((_struct *)malloc(sizeof(_struct)))
#endif
// internal definitions
typedef double *ArHosekSkyModel_Dataset;
typedef double *ArHosekSkyModel_Radiance_Dataset;
// internal functions
void ArHosekSkyModel_CookConfiguration(
ArHosekSkyModel_Dataset dataset,
ArHosekSkyModelConfiguration config,
double turbidity,
double albedo,
double solar_elevation
)
{
double * elev_matrix;
int int_turbidity = (int)turbidity;
double turbidity_rem = turbidity - (double)int_turbidity;
solar_elevation = pow(solar_elevation / (MATH_PI / 2.0), (1.0 / 3.0));
// alb 0 low turb
elev_matrix = dataset + ( 9 * 6 * (int_turbidity-1) );
unsigned int i;
for( i = 0; i < 9; ++i )
{
//(1-t).^3* A1 + 3*(1-t).^2.*t * A2 + 3*(1-t) .* t .^ 2 * A3 + t.^3 * A4;
config[i] =
(1.0-albedo) * (1.0 - turbidity_rem)
* ( pow(1.0-solar_elevation, 5.0) * elev_matrix[i] +
5.0 * pow(1.0-solar_elevation, 4.0) * solar_elevation * elev_matrix[i+9] +
10.0*pow(1.0-solar_elevation, 3.0)*pow(solar_elevation, 2.0) * elev_matrix[i+18] +
10.0*pow(1.0-solar_elevation, 2.0)*pow(solar_elevation, 3.0) * elev_matrix[i+27] +
5.0*(1.0-solar_elevation)*pow(solar_elevation, 4.0) * elev_matrix[i+36] +
pow(solar_elevation, 5.0) * elev_matrix[i+45]);
}
// alb 1 low turb
elev_matrix = dataset + (9*6*10 + 9*6*(int_turbidity-1));
for( i = 0; i < 9; ++i)
{
//(1-t).^3* A1 + 3*(1-t).^2.*t * A2 + 3*(1-t) .* t .^ 2 * A3 + t.^3 * A4;
config[i] +=
(albedo) * (1.0 - turbidity_rem)
* ( pow(1.0-solar_elevation, 5.0) * elev_matrix[i] +
5.0 * pow(1.0-solar_elevation, 4.0) * solar_elevation * elev_matrix[i+9] +
10.0*pow(1.0-solar_elevation, 3.0)*pow(solar_elevation, 2.0) * elev_matrix[i+18] +
10.0*pow(1.0-solar_elevation, 2.0)*pow(solar_elevation, 3.0) * elev_matrix[i+27] +
5.0*(1.0-solar_elevation)*pow(solar_elevation, 4.0) * elev_matrix[i+36] +
pow(solar_elevation, 5.0) * elev_matrix[i+45]);
}
if(int_turbidity == 10)
return;
// alb 0 high turb
elev_matrix = dataset + (9*6*(int_turbidity));
for( i = 0; i < 9; ++i)
{
//(1-t).^3* A1 + 3*(1-t).^2.*t * A2 + 3*(1-t) .* t .^ 2 * A3 + t.^3 * A4;
config[i] +=
(1.0-albedo) * (turbidity_rem)
* ( pow(1.0-solar_elevation, 5.0) * elev_matrix[i] +
5.0 * pow(1.0-solar_elevation, 4.0) * solar_elevation * elev_matrix[i+9] +
10.0*pow(1.0-solar_elevation, 3.0)*pow(solar_elevation, 2.0) * elev_matrix[i+18] +
10.0*pow(1.0-solar_elevation, 2.0)*pow(solar_elevation, 3.0) * elev_matrix[i+27] +
5.0*(1.0-solar_elevation)*pow(solar_elevation, 4.0) * elev_matrix[i+36] +
pow(solar_elevation, 5.0) * elev_matrix[i+45]);
}
// alb 1 high turb
elev_matrix = dataset + (9*6*10 + 9*6*(int_turbidity));
for( i = 0; i < 9; ++i)
{
//(1-t).^3* A1 + 3*(1-t).^2.*t * A2 + 3*(1-t) .* t .^ 2 * A3 + t.^3 * A4;
config[i] +=
(albedo) * (turbidity_rem)
* ( pow(1.0-solar_elevation, 5.0) * elev_matrix[i] +
5.0 * pow(1.0-solar_elevation, 4.0) * solar_elevation * elev_matrix[i+9] +
10.0*pow(1.0-solar_elevation, 3.0)*pow(solar_elevation, 2.0) * elev_matrix[i+18] +
10.0*pow(1.0-solar_elevation, 2.0)*pow(solar_elevation, 3.0) * elev_matrix[i+27] +
5.0*(1.0-solar_elevation)*pow(solar_elevation, 4.0) * elev_matrix[i+36] +
pow(solar_elevation, 5.0) * elev_matrix[i+45]);
}
}
double ArHosekSkyModel_CookRadianceConfiguration(
ArHosekSkyModel_Radiance_Dataset dataset,
double turbidity,
double albedo,
double solar_elevation
)
{
double* elev_matrix;
int int_turbidity = (int)turbidity;
double turbidity_rem = turbidity - (double)int_turbidity;
double res;
solar_elevation = pow(solar_elevation / (MATH_PI / 2.0), (1.0 / 3.0));
// alb 0 low turb
elev_matrix = dataset + (6*(int_turbidity-1));
//(1-t).^3* A1 + 3*(1-t).^2.*t * A2 + 3*(1-t) .* t .^ 2 * A3 + t.^3 * A4;
res = (1.0-albedo) * (1.0 - turbidity_rem) *
( pow(1.0-solar_elevation, 5.0) * elev_matrix[0] +
5.0*pow(1.0-solar_elevation, 4.0)*solar_elevation * elev_matrix[1] +
10.0*pow(1.0-solar_elevation, 3.0)*pow(solar_elevation, 2.0) * elev_matrix[2] +
10.0*pow(1.0-solar_elevation, 2.0)*pow(solar_elevation, 3.0) * elev_matrix[3] +
5.0*(1.0-solar_elevation)*pow(solar_elevation, 4.0) * elev_matrix[4] +
pow(solar_elevation, 5.0) * elev_matrix[5]);
// alb 1 low turb
elev_matrix = dataset + (6*10 + 6*(int_turbidity-1));
//(1-t).^3* A1 + 3*(1-t).^2.*t * A2 + 3*(1-t) .* t .^ 2 * A3 + t.^3 * A4;
res += (albedo) * (1.0 - turbidity_rem) *
( pow(1.0-solar_elevation, 5.0) * elev_matrix[0] +
5.0*pow(1.0-solar_elevation, 4.0)*solar_elevation * elev_matrix[1] +
10.0*pow(1.0-solar_elevation, 3.0)*pow(solar_elevation, 2.0) * elev_matrix[2] +
10.0*pow(1.0-solar_elevation, 2.0)*pow(solar_elevation, 3.0) * elev_matrix[3] +
5.0*(1.0-solar_elevation)*pow(solar_elevation, 4.0) * elev_matrix[4] +
pow(solar_elevation, 5.0) * elev_matrix[5]);
if(int_turbidity == 10)
return res;
// alb 0 high turb
elev_matrix = dataset + (6*(int_turbidity));
//(1-t).^3* A1 + 3*(1-t).^2.*t * A2 + 3*(1-t) .* t .^ 2 * A3 + t.^3 * A4;
res += (1.0-albedo) * (turbidity_rem) *
( pow(1.0-solar_elevation, 5.0) * elev_matrix[0] +
5.0*pow(1.0-solar_elevation, 4.0)*solar_elevation * elev_matrix[1] +
10.0*pow(1.0-solar_elevation, 3.0)*pow(solar_elevation, 2.0) * elev_matrix[2] +
10.0*pow(1.0-solar_elevation, 2.0)*pow(solar_elevation, 3.0) * elev_matrix[3] +
5.0*(1.0-solar_elevation)*pow(solar_elevation, 4.0) * elev_matrix[4] +
pow(solar_elevation, 5.0) * elev_matrix[5]);
// alb 1 high turb
elev_matrix = dataset + (6*10 + 6*(int_turbidity));
//(1-t).^3* A1 + 3*(1-t).^2.*t * A2 + 3*(1-t) .* t .^ 2 * A3 + t.^3 * A4;
res += (albedo) * (turbidity_rem) *
( pow(1.0-solar_elevation, 5.0) * elev_matrix[0] +
5.0*pow(1.0-solar_elevation, 4.0)*solar_elevation * elev_matrix[1] +
10.0*pow(1.0-solar_elevation, 3.0)*pow(solar_elevation, 2.0) * elev_matrix[2] +
10.0*pow(1.0-solar_elevation, 2.0)*pow(solar_elevation, 3.0) * elev_matrix[3] +
5.0*(1.0-solar_elevation)*pow(solar_elevation, 4.0) * elev_matrix[4] +
pow(solar_elevation, 5.0) * elev_matrix[5]);
return res;
}
double ArHosekSkyModel_GetRadianceInternal(
ArHosekSkyModelConfiguration configuration,
double theta,
double gamma
)
{
const double expM = exp(configuration[4] * gamma);
const double rayM = cos(gamma)*cos(gamma);
const double mieM = (1.0 + cos(gamma)*cos(gamma)) / pow((1.0 + configuration[8]*configuration[8] - 2.0*configuration[8]*cos(gamma)), 1.5);
const double zenith = sqrt(cos(theta));
return (1.0 + configuration[0] * exp(configuration[1] / (cos(theta) + 0.01))) *
(configuration[2] + configuration[3] * expM + configuration[5] * rayM + configuration[6] * mieM + configuration[7] * zenith);
}
// spectral version
ArHosekSkyModelState * arhosekskymodelstate_alloc_init(
const double solar_elevation,
const double atmospheric_turbidity,
const double ground_albedo
)
{
ArHosekSkyModelState * state = ALLOC(ArHosekSkyModelState);
state->solar_radius = ( 0.51 DEGREES ) / 2.0;
state->turbidity = atmospheric_turbidity;
state->albedo = ground_albedo;
state->elevation = solar_elevation;
unsigned int wl;
for( wl = 0; wl < 11; ++wl )
{
ArHosekSkyModel_CookConfiguration(
datasets[wl],
state->configs[wl],
atmospheric_turbidity,
ground_albedo,
solar_elevation
);
state->radiances[wl] =
ArHosekSkyModel_CookRadianceConfiguration(
datasetsRad[wl],
atmospheric_turbidity,
ground_albedo,
solar_elevation
);
state->emission_correction_factor_sun[wl] = 1.0;
state->emission_correction_factor_sky[wl] = 1.0;
}
return state;
}
// 'blackbody_scaling_factor'
//
// Fudge factor, computed in Mathematica, to scale the results of the
// following function to match the solar radiance spectrum used in the
// original simulation. The scaling is done so their integrals over the
// range from 380.0 to 720.0 nanometers match for a blackbody temperature
// of 5800 K.
// Which leaves the original spectrum being less bright overall than the 5.8k
// blackbody radiation curve if the ultra-violet part of the spectrum is
// also considered. But the visible brightness should be very similar.
const double blackbody_scaling_factor = 3.19992 * 10E-11;
// 'art_blackbody_dd_value()' function
//
// Blackbody radiance, Planck's formula
double art_blackbody_dd_value(
const double temperature,
const double lambda
)
{
double c1 = 3.74177 * 10E-17;
double c2 = 0.0143878;
double value;
value = ( c1 / ( pow( lambda, 5.0 ) ) )
* ( 1.0 / ( exp( c2 / ( lambda * temperature ) ) - 1.0 ) );
return value;
}
// 'originalSolarRadianceTable[]'
//
// The solar spectrum incident at the top of the atmosphere, as it was used
// in the brute force path tracer that generated the reference results the
// model was fitted to. We need this as the yardstick to compare any altered
// Blackbody emission spectra for alien world stars to.
// This is just the data from the Preetham paper, extended into the UV range.
const double originalSolarRadianceTable[] =
{
7500.0,
12500.0,
21127.5,
26760.5,
30663.7,
27825.0,
25503.8,
25134.2,
23212.1,
21526.7,
19870.8
};
ArHosekSkyModelState * arhosekskymodelstate_alienworld_alloc_init(
const double solar_elevation,
const double solar_intensity,
const double solar_surface_temperature_kelvin,
const double atmospheric_turbidity,
const double ground_albedo
)
{
ArHosekSkyModelState * state = ALLOC(ArHosekSkyModelState);
state->turbidity = atmospheric_turbidity;
state->albedo = ground_albedo;
state->elevation = solar_elevation;
unsigned int wl;
for( wl = 0; wl < 11; ++wl )
{
// Basic init as for the normal scenario
ArHosekSkyModel_CookConfiguration(
datasets[wl],
state->configs[wl],
atmospheric_turbidity,
ground_albedo,
solar_elevation
);
state->radiances[wl] =
ArHosekSkyModel_CookRadianceConfiguration(
datasetsRad[wl],
atmospheric_turbidity,
ground_albedo,
solar_elevation
);
// The wavelength of this band in nanometers
double owl = ( 320.0 + 40.0 * wl ) * 10E-10;
// The original intensity we just computed
double osr = originalSolarRadianceTable[wl];
// The intensity of a blackbody with the desired temperature
// The fudge factor described above is used to make sure the BB
// function matches the used radiance data reasonably well
// in magnitude.
double nsr =
art_blackbody_dd_value(solar_surface_temperature_kelvin, owl)
* blackbody_scaling_factor;
// Correction factor for this waveband is simply the ratio of
// the two.
state->emission_correction_factor_sun[wl] = nsr / osr;
}
// We then compute the average correction factor of all wavebands.
// Theoretically, some weighting to favour wavelengths human vision is
// more sensitive to could be introduced here - think V(lambda). But
// given that the whole effort is not *that* accurate to begin with (we
// are talking about the appearance of alien worlds, after all), simple
// averaging over the visible wavelenghts (! - this is why we start at
// WL #2, and only use 2-11) seems like a sane first approximation.
double correctionFactor = 0.0;
unsigned int i;
for ( i = 2; i < 11; i++ )
{
correctionFactor +=
state->emission_correction_factor_sun[i];
}
// This is the average ratio in emitted energy between our sun, and an
// equally large sun with the blackbody spectrum we requested.
// Division by 9 because we only used 9 of the 11 wavelengths for this
// (see above).
double ratio = correctionFactor / 9.0;
// This ratio is then used to determine the radius of the alien sun
// on the sky dome. The additional factor 'solar_intensity' can be used
// to make the alien sun brighter or dimmer compared to our sun.
state->solar_radius =
( sqrt( solar_intensity ) * TERRESTRIAL_SOLAR_RADIUS )
/ sqrt( ratio );
// Finally, we have to reduce the scaling factor of the sky by the
// ratio used to scale the solar disc size. The rationale behind this is
// that the scaling factors apply to the new blackbody spectrum, which
// can be more or less bright than the one our sun emits. However, we
// just scaled the size of the alien solar disc so it is roughly as
// bright (in terms of energy emitted) as the terrestrial sun. So the sky
// dome has to be reduced in brightness appropriately - but not in an
// uniform fashion across wavebands. If we did that, the sky colour would
// be wrong.
for ( i = 0; i < 11; i++ )
{
state->emission_correction_factor_sky[i] =
solar_intensity
* state->emission_correction_factor_sun[i] / ratio;
}
return state;
}
void arhosekskymodelstate_free(
ArHosekSkyModelState * state
)
{
free(state);
}
double arhosekskymodel_radiance(
ArHosekSkyModelState * state,
double theta,
double gamma,
double wavelength
)
{
int low_wl = (wavelength - 320.0 ) / 40.0;
if ( low_wl < 0 || low_wl >= 11 )
return 0.0f;
double interp = fmod((wavelength - 320.0 ) / 40.0, 1.0);
double val_low =
ArHosekSkyModel_GetRadianceInternal(
state->configs[low_wl],
theta,
gamma
)
* state->radiances[low_wl]
* state->emission_correction_factor_sky[low_wl];
if ( interp < 1e-6 )
return val_low;
double result = ( 1.0 - interp ) * val_low;
if ( low_wl+1 < 11 )
{
result +=
interp
* ArHosekSkyModel_GetRadianceInternal(
state->configs[low_wl+1],
theta,
gamma
)
* state->radiances[low_wl+1]
* state->emission_correction_factor_sky[low_wl+1];
}
return result;
}
// xyz and rgb versions
ArHosekSkyModelState * arhosek_xyz_skymodelstate_alloc_init(
const double turbidity,
const double albedo,
const double elevation
)
{
ArHosekSkyModelState * state = ALLOC(ArHosekSkyModelState);
state->solar_radius = TERRESTRIAL_SOLAR_RADIUS;
state->turbidity = turbidity;
state->albedo = albedo;
state->elevation = elevation;
unsigned int channel;
for( channel = 0; channel < 3; ++channel )
{
ArHosekSkyModel_CookConfiguration(
datasetsXYZ[channel],
state->configs[channel],
turbidity,
albedo,
elevation
);
state->radiances[channel] =
ArHosekSkyModel_CookRadianceConfiguration(
datasetsXYZRad[channel],
turbidity,
albedo,
elevation
);
}
return state;
}
ArHosekSkyModelState * arhosek_rgb_skymodelstate_alloc_init(
const double turbidity,
const double albedo,
const double elevation
)
{
ArHosekSkyModelState* state = ALLOC(ArHosekSkyModelState);
state->solar_radius = TERRESTRIAL_SOLAR_RADIUS;
state->turbidity = turbidity;
state->albedo = albedo;
state->elevation = elevation;
unsigned int channel;
for( channel = 0; channel < 3; ++channel )
{
ArHosekSkyModel_CookConfiguration(
datasetsRGB[channel],
state->configs[channel],
turbidity,
albedo,
elevation
);
state->radiances[channel] =
ArHosekSkyModel_CookRadianceConfiguration(
datasetsRGBRad[channel],
turbidity,
albedo,
elevation
);
}
return state;
}
double arhosek_tristim_skymodel_radiance(
ArHosekSkyModelState * state,
double theta,
double gamma,
int channel
)
{
return
ArHosekSkyModel_GetRadianceInternal(
state->configs[channel],
theta,
gamma
)
* state->radiances[channel];
}
const int pieces = 45;
const int order = 4;
double arhosekskymodel_sr_internal(
ArHosekSkyModelState * state,
int turbidity,
int wl,
double elevation
)
{
int pos =
(int) (pow(2.0*elevation / MATH_PI, 1.0/3.0) * pieces); // floor
if ( pos > 44 ) pos = 44;
const double break_x =
pow(((double) pos / (double) pieces), 3.0) * (MATH_PI * 0.5);
const double * coefs =
solarDatasets[wl] + (order * pieces * turbidity + order * (pos+1) - 1);
double res = 0.0;
const double x = elevation - break_x;
double x_exp = 1.0;
int i;
for (i = 0; i < order; ++i)
{
res += x_exp * *coefs--;
x_exp *= x;
}
return res * state->emission_correction_factor_sun[wl];
}
double arhosekskymodel_solar_radiance_internal2(
ArHosekSkyModelState * state,
double wavelength,
double elevation,
double gamma
)
{
assert(
wavelength >= 320.0
&& wavelength <= 720.0
&& state->turbidity >= 1.0
&& state->turbidity <= 10.0
);
// sun distance to diameter ratio, squared
const double sol_rad_sin = sin(state->solar_radius);
const double ar2 = 1 / ( sol_rad_sin * sol_rad_sin );
const double singamma = sin(gamma);
double sc2 = 1.0 - ar2 * singamma * singamma;
if (sc2 < 0.0 ) sc2 = 0.0;
double sampleCosine = sqrt (sc2);
if (sampleCosine == 0.) return 0.;
int turb_low = (int) state->turbidity - 1;
double turb_frac = state->turbidity - (double) (turb_low + 1);
if ( turb_low == 9 )
{
turb_low = 8;
turb_frac = 1.0;
}
int wl_low = (int) ((wavelength - 320.0) / 40.0);
double wl_frac = fmod(wavelength, 40.0) / 40.0;
if ( wl_low == 10 )
{
wl_low = 9;
wl_frac = 1.0;
}
double direct_radiance =
( 1.0 - turb_frac )
* ( (1.0 - wl_frac)
* arhosekskymodel_sr_internal(
state,
turb_low,
wl_low,
elevation
)
+ wl_frac
* arhosekskymodel_sr_internal(
state,
turb_low,
wl_low+1,
elevation
)
)
+ turb_frac
* ( ( 1.0 - wl_frac )
* arhosekskymodel_sr_internal(
state,
turb_low+1,
wl_low,
elevation
)
+ wl_frac
* arhosekskymodel_sr_internal(
state,
turb_low+1,
wl_low+1,
elevation
)
);
double ldCoefficient[6];
int i;
for ( i = 0; i < 6; i++ )
ldCoefficient[i] =
(1.0 - wl_frac) * limbDarkeningDatasets[wl_low ][i]
+ wl_frac * limbDarkeningDatasets[wl_low+1][i];
// The following will be improved in future versions of the model:
// here, we directly use fitted 5th order polynomials provided by the
// astronomical community for the limb darkening effect. Astronomers need
// such accurate fittings for their predictions. However, this sort of
// accuracy is not really needed for CG purposes, so an approximated
// dataset based on quadratic polynomials will be provided in a future
// release.
double darkeningFactor =
ldCoefficient[0]
+ ldCoefficient[1] * sampleCosine
+ ldCoefficient[2] * pow( sampleCosine, 2.0 )
+ ldCoefficient[3] * pow( sampleCosine, 3.0 )
+ ldCoefficient[4] * pow( sampleCosine, 4.0 )
+ ldCoefficient[5] * pow( sampleCosine, 5.0 );
direct_radiance *= darkeningFactor;
return direct_radiance;
}
double arhosekskymodel_solar_radiance(
ArHosekSkyModelState * state,
double theta,
double gamma,
double wavelength
)
{
double direct_radiance =
arhosekskymodel_solar_radiance_internal2(
state,
wavelength,
((MATH_PI/2.0)-theta),
gamma
);
double inscattered_radiance =
arhosekskymodel_radiance(
state,
theta,
gamma,
wavelength
);
return direct_radiance + inscattered_radiance;
}

View file

@ -0,0 +1,451 @@
/*
This source is published under the following 3-clause BSD license.
Copyright (c) 2012 - 2013, Lukas Hosek and Alexander Wilkie
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* None of the names of the contributors may be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* ============================================================================
This file is part of a sample implementation of the analytical skylight and
solar radiance models presented in the SIGGRAPH 2012 paper
"An Analytic Model for Full Spectral Sky-Dome Radiance"
and the 2013 IEEE CG&A paper
"Adding a Solar Radiance Function to the Hosek Skylight Model"
both by
Lukas Hosek and Alexander Wilkie
Charles University in Prague, Czech Republic
Version: 1.4a, February 22nd, 2013
Version history:
1.4a February 22nd, 2013
Removed unnecessary and counter-intuitive solar radius parameters
from the interface of the colourspace sky dome initialisation functions.
1.4 February 11th, 2013
Fixed a bug which caused the relative brightness of the solar disc
and the sky dome to be off by a factor of about 6. The sun was too
bright: this affected both normal and alien sun scenarios. The
coefficients of the solar radiance function were changed to fix this.
1.3 January 21st, 2013 (not released to the public)
Added support for solar discs that are not exactly the same size as
the terrestrial sun. Also added support for suns with a different
emission spectrum ("Alien World" functionality).
1.2a December 18th, 2012
Fixed a mistake and some inaccuracies in the solar radiance function
explanations found in ArHosekSkyModel.h. The actual source code is
unchanged compared to version 1.2.
1.2 December 17th, 2012
Native RGB data and a solar radiance function that matches the turbidity
conditions were added.
1.1 September 2012
The coefficients of the spectral model are now scaled so that the output
is given in physical units: W / (m^-2 * sr * nm). Also, the output of the
XYZ model is now no longer scaled to the range [0...1]. Instead, it is
the result of a simple conversion from spectral data via the CIE 2 degree
standard observer matching functions. Therefore, after multiplication
with 683 lm / W, the Y channel now corresponds to luminance in lm.
1.0 May 11th, 2012
Initial release.
Please visit http://cgg.mff.cuni.cz/projects/SkylightModelling/ to check if
an updated version of this code has been published!
============================================================================ */
/*
This code is taken from ART, a rendering research system written in a
mix of C99 / Objective C. Since ART is not a small system and is intended to
be inter-operable with other libraries, and since C does not have namespaces,
the structures and functions in ART all have to have somewhat wordy
canonical names that begin with Ar.../ar..., like those seen in this example.
Usage information:
==================
Model initialisation
--------------------
A separate ArHosekSkyModelState has to be maintained for each spectral
band you want to use the model for. So in a renderer with 'num_channels'
bands, you would need something like
ArHosekSkyModelState * skymodel_state[num_channels];
You then have to allocate and initialise these states. In the following code
snippet, we assume that 'albedo' is defined as
double albedo[num_channels];
with a ground albedo value between [0,1] for each channel. The solar elevation
is given in radians.
for ( unsigned int i = 0; i < num_channels; i++ )
skymodel_state[i] =
arhosekskymodelstate_alloc_init(
turbidity,
albedo[i],
solarElevation
);
Note that starting with version 1.3, there is also a second initialisation
function which generates skydome states for different solar emission spectra
and solar radii: 'arhosekskymodelstate_alienworld_alloc_init()'.
See the notes about the "Alien World" functionality provided further down for a
discussion of the usefulness and limits of that second initalisation function.
Sky model states that have been initialised with either function behave in a
completely identical fashion during use and cleanup.
Using the model to generate skydome samples
-------------------------------------------
Generating a skydome radiance spectrum "skydome_result" for a given location
on the skydome determined via the angles theta and gamma works as follows:
double skydome_result[num_channels];
for ( unsigned int i = 0; i < num_channels; i++ )
skydome_result[i] =
arhosekskymodel_radiance(
skymodel_state[i],
theta,
gamma,
channel_center[i]
);
The variable "channel_center" is assumed to hold the channel center wavelengths
for each of the num_channels samples of the spectrum we are building.
Cleanup after use
-----------------
After rendering is complete, the content of the sky model states should be
disposed of via
for ( unsigned int i = 0; i < num_channels; i++ )
arhosekskymodelstate_free( skymodel_state[i] );
CIE XYZ Version of the Model
----------------------------
Usage of the CIE XYZ version of the model is exactly the same, except that
num_channels is of course always 3, and that ArHosekTristimSkyModelState and
arhosek_tristim_skymodel_radiance() have to be used instead of their spectral
counterparts.
RGB Version of the Model
------------------------
The RGB version uses sRGB primaries with a linear gamma ramp. The same set of
functions as with the XYZ data is used, except the model is initialized
by calling arhosek_rgb_skymodelstate_alloc_init.
Solar Radiance Function
-----------------------
For each position on the solar disc, this function returns the entire radiance
one sees - direct emission, as well as in-scattered light in the area of the
solar disc. The latter is important for low solar elevations - nice images of
the setting sun would not be possible without this. This is also the reason why
this function, just like the regular sky dome model evaluation function, needs
access to the sky dome data structures, as these provide information on
in-scattered radiance.
CAVEAT #1: in this release, this function is only provided in spectral form!
RGB/XYZ versions to follow at a later date.
CAVEAT #2: (fixed from release 1.3 onwards)
CAVEAT #3: limb darkening renders the brightness of the solar disc
inhomogeneous even for high solar elevations - only taking a single
sample at the centre of the sun will yield an incorrect power
estimate for the solar disc! Always take multiple random samples
across the entire solar disc to estimate its power!
CAVEAT #4: in this version, the limb darkening calculations still use a fairly
computationally expensive 5th order polynomial that was directly
taken from astronomical literature. For the purposes of Computer
Graphics, this is needlessly accurate, though, and will be replaced
by a cheaper approximation in a future release.
"Alien World" functionality
---------------------------
The Hosek sky model can be used to roughly (!) predict the appearance of
outdoor scenes on earth-like planets, i.e. planets of a similar size and
atmospheric make-up. Since the spectral version of our model predicts sky dome
luminance patterns and solar radiance independently for each waveband, and
since the intensity of each waveband is solely dependent on the input radiance
from the star that the world in question is orbiting, it is trivial to re-scale
the wavebands to match a different star radiance.
At least in theory, the spectral version of the model has always been capable
of this sort of thing, and the actual sky dome and solar radiance models were
actually not altered at all in this release. All we did was to add some support
functionality for doing this more easily with the existing data and functions,
and to add some explanations.
Just use 'arhosekskymodelstate_alienworld_alloc_init()' to initialise the sky
model states (you will have to provide values for star temperature and solar
intensity compared to the terrestrial sun), and do everything else as you
did before.
CAVEAT #1: we assume the emission of the star that illuminates the alien world
to be a perfect blackbody emission spectrum. This is never entirely
realistic - real star emission spectra are considerably more complex
than this, mainly due to absorption effects in the outer layers of
stars. However, blackbody spectra are a reasonable first assumption
in a usage scenario like this, where 100% accuracy is simply not
necessary: for rendering purposes, there are likely no visible
differences between a highly accurate solution based on a more
involved simulation, and this approximation.
CAVEAT #2: we always use limb darkening data from our own sun to provide this
"appearance feature", even for suns of strongly different
temperature. Which is presumably not very realistic, but (as with
the unaltered blackbody spectrum from caveat #1) probably not a bad
first guess, either. If you need more accuracy than we provide here,
please make inquiries with a friendly astro-physicst of your choice.
CAVEAT #3: you have to provide a value for the solar intensity of the star
which illuminates the alien world. For this, please bear in mind
that there is very likely a comparatively tight range of absolute
solar irradiance values for which an earth-like planet with an
atmosphere like the one we assume in our model can exist in the
first place!
Too much irradiance, and the atmosphere probably boils off into
space, too little, it freezes. Which means that stars of
considerably different emission colour than our sun will have to be
fairly different in size from it, to still provide a reasonable and
inhabitable amount of irradiance. Red stars will need to be much
larger than our sun, while white or blue stars will have to be
comparatively tiny. The initialisation function handles this and
computes a plausible solar radius for a given emission spectrum. In
terms of absolute radiometric values, you should probably not stray
all too far from a solar intensity value of 1.0.
CAVEAT #4: although we now support different solar radii for the actual solar
disc, the sky dome luminance patterns are *not* parameterised by
this value - i.e. the patterns stay exactly the same for different
solar radii! Which is of course not correct. But in our experience,
solar discs up to several degrees in diameter (! - our own sun is
half a degree across) do not cause the luminance patterns on the sky
to change perceptibly. The reason we know this is that we initially
used unrealistically large suns in our brute force path tracer, in
order to improve convergence speeds (which in the beginning were
abysmal). Later, we managed to do the reference renderings much
faster even with realistically small suns, and found that there was
no real difference in skydome appearance anyway.
Conclusion: changing the solar radius should not be over-done, so
close orbits around red supergiants are a no-no. But for the
purposes of getting a fairly credible first impression of what an
alien world with a reasonably sized sun would look like, what we are
doing here is probably still o.k.
HINT #1: if you want to model the sky of an earth-like planet that orbits
a binary star, just super-impose two of these models with solar
intensity of ~0.5 each, and closely spaced solar positions. Light is
additive, after all. Tattooine, here we come... :-)
P.S. according to Star Wars canon, Tattooine orbits a binary
that is made up of a G and K class star, respectively.
So ~5500K and ~4200K should be good first guesses for their
temperature. Just in case you were wondering, after reading the
previous paragraph.
*/
#ifndef _ARHOSEK_SKYMODEL_H_
#define _ARHOSEK_SKYMODEL_H_
typedef double ArHosekSkyModelConfiguration[9];
// Spectral version of the model
/* ----------------------------------------------------------------------------
ArHosekSkyModelState struct
---------------------------
This struct holds the pre-computation data for one particular albedo value.
Most fields are self-explanatory, but users should never directly
manipulate any of them anyway. The only consistent way to manipulate such
structs is via the functions 'arhosekskymodelstate_alloc_init' and
'arhosekskymodelstate_free'.
'emission_correction_factor_sky'
'emission_correction_factor_sun'
The original model coefficients were fitted against the emission of
our local sun. If a different solar emission is desired (i.e. if the
model is being used to predict skydome appearance for an earth-like
planet that orbits a different star), these correction factors, which
are determined during the alloc_init step, are applied to each waveband
separately (they default to 1.0 in normal usage). This is the simplest
way to retrofit this sort of capability to the existing model. The
different factors for sky and sun are needed since the solar disc may
be of a different size compared to the terrestrial sun.
---------------------------------------------------------------------------- */
typedef struct ArHosekSkyModelState
{
ArHosekSkyModelConfiguration configs[11];
double radiances[11];
double turbidity;
double solar_radius;
double emission_correction_factor_sky[11];
double emission_correction_factor_sun[11];
double albedo;
double elevation;
}
ArHosekSkyModelState;
/* ----------------------------------------------------------------------------
arhosekskymodelstate_alloc_init() function
------------------------------------------
Initialises an ArHosekSkyModelState struct for a terrestrial setting.
---------------------------------------------------------------------------- */
ArHosekSkyModelState * arhosekskymodelstate_alloc_init(
const double solar_elevation,
const double atmospheric_turbidity,
const double ground_albedo
);
/* ----------------------------------------------------------------------------
arhosekskymodelstate_alienworld_alloc_init() function
-----------------------------------------------------
Initialises an ArHosekSkyModelState struct for an "alien world" setting
with a sun of a surface temperature given in 'kelvin'. The parameter
'solar_intensity' controls the overall brightness of the sky, relative
to the solar irradiance on Earth. A value of 1.0 yields a sky dome that
is, on average over the wavelenghts covered in the model (!), as bright
as the terrestrial sky in radiometric terms.
Which means that the solar radius has to be adjusted, since the
emissivity of a solar surface with a given temperature is more or less
fixed. So hotter suns have to be smaller to be equally bright as the
terrestrial sun, while cooler suns have to be larger. Note that there are
limits to the validity of the luminance patterns of the underlying model:
see the discussion above for more on this. In particular, an alien sun with
a surface temperature of only 2000 Kelvin has to be very large if it is
to be as bright as the terrestrial sun - so large that the luminance
patterns are no longer a really good fit in that case.
If you need information about the solar radius that the model computes
for a given temperature (say, for light source sampling purposes), you
have to query the 'solar_radius' variable of the sky model state returned
*after* running this function.
---------------------------------------------------------------------------- */
ArHosekSkyModelState * arhosekskymodelstate_alienworld_alloc_init(
const double solar_elevation,
const double solar_intensity,
const double solar_surface_temperature_kelvin,
const double atmospheric_turbidity,
const double ground_albedo
);
void arhosekskymodelstate_free(
ArHosekSkyModelState * state
);
double arhosekskymodel_radiance(
ArHosekSkyModelState * state,
double theta,
double gamma,
double wavelength
);
// CIE XYZ and RGB versions
ArHosekSkyModelState * arhosek_xyz_skymodelstate_alloc_init(
const double turbidity,
const double albedo,
const double elevation
);
ArHosekSkyModelState * arhosek_rgb_skymodelstate_alloc_init(
const double turbidity,
const double albedo,
const double elevation
);
double arhosek_tristim_skymodel_radiance(
ArHosekSkyModelState * state,
double theta,
double gamma,
int channel
);
// Delivers the complete function: sky + sun, including limb darkening.
// Please read the above description before using this - there are several
// caveats!
double arhosekskymodel_solar_radiance(
ArHosekSkyModelState * state,
double theta,
double gamma,
double wavelength
);
#endif // _ARHOSEK_SKYMODEL_H_

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1
src/ext/stb Submodule

@ -0,0 +1 @@
Subproject commit b42009b3b9d4ca35bc703f5310eedc74f584be58

1
src/ext/zlib Submodule

@ -0,0 +1 @@
Subproject commit 54d591eabf9fe0e84c725638f8d5d8d202a093fa

15
src/pbrt/.clang-format Normal file
View file

@ -0,0 +1,15 @@
BasedOnStyle: Google
AccessModifierOffset: -2
IndentCaseLabels: false
PointerBindsToType: false
Standard: Cpp11
IndentWidth: 4
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AllowShortLambdasOnASingleLine: All
AlwaysBreakBeforeMultilineStrings: false
IncludeBlocks: Preserve
AllowAllConstructorInitializersOnNextLine: true
MaxEmptyLinesToKeep: 1
ColumnLimit: 90

83
src/pbrt/base/#shape.h# Normal file
View file

@ -0,0 +1,83 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_BASE_SHAPE_H
#define PBRT_BASE_SHAPE_H
#include <pbrt/pbrt.h>
#include <pbrt/util/buffercache.h>
#include <pbrt/util/float.h>
#include <pbrt/util/taggedptr.h>
#include <pbrt/util/vecmath.h>
#include <string>
namespace pbrt {
// Shape Declarations
class Triangle;
class BilinearPatch;
class Curve;
class Sphere;
class Cylinder;
class Disk;
struct ShapeSample;
struct ShapeIntersection;
class ShapeSampleContext;
// ShapeHandle Definition
class ShapeHandle
: public TaggedPointer<Triangle, BilinearPatch, Curve, Sphere, Cylinder, Disk> {
public:
// Shape Interface
using TaggedPointer::TaggedPointer;
static pstd::vector<ShapeHandle> Create(const std::string &name,
const Transform *renderFromObject,
const Transform *objectFromRender,
bool reverseOrientation,
const ParameterDictionary &parameters,
const FileLoc *loc, Allocator alloc);
std::string ToString() const;
PBRT_CPU_GPU inline Bounds3f Bounds() const;
PBRT_CPU_GPU inline DirectionCone NormalBounds() const;
PBRT_CPU_GPU inline pstd::optional<ShapeIntersection> Intersect(
const Ray &ray, Float tMax = Infinity) const;
PBRT_CPU_GPU inline bool IntersectP(const Ray &ray, Float tMax = Infinity) const;
PBRT_CPU_GPU inline Float Area() const;
PBRT_CPU_GPU inline pstd::optional<ShapeSample> Sample(const Point2f &u) const;
PBRT_CPU_GPU inline Float PDF(const Interaction &) const;
PBRT_CPU_GPU inline pstd::optional<ShapeSample> Sample(const ShapeSampleContext &ctx,
const Point2f &u) const;
PBRT_CPU_GPU inline Float PDF(const ShapeSampleContext &ctx,
const Vector3f &wi) const;
private:
// ShapeHandle Private Members
friend class TriangleMesh;
friend class BilinearPatchMesh;
static BufferCache<int> *indexBufferCache;
static BufferCache<Point3f> *pBufferCache;
static BufferCache<Normal3f> *nBufferCache;
static BufferCache<Point2f> *uvBufferCache;
static BufferCache<Vector3f> *sBufferCache;
static BufferCache<int> *faceIndexBufferCache;
};
} // namespace pbrt
#endif // PBRT_BASE_SHAPE_H

40
src/pbrt/base/bssrdf.h Normal file
View file

@ -0,0 +1,40 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_BASE_BSSRDF_H
#define PBRT_BASE_BSSRDF_H
#include <pbrt/pbrt.h>
#include <pbrt/util/pstd.h>
#include <pbrt/util/taggedptr.h>
#include <string>
namespace pbrt {
struct BSSRDFSample;
struct BSSRDFProbeSegment;
struct SubsurfaceInteraction;
struct BSSRDFTable;
// BSSRDFHandle Definition
class TabulatedBSSRDF;
class BSSRDFHandle : public TaggedPointer<TabulatedBSSRDF> {
public:
// BSSRDFHandle Public Interface
using TaggedPointer::TaggedPointer;
PBRT_CPU_GPU inline SampledSpectrum S(const Point3f &p, const Vector3f &wi);
PBRT_CPU_GPU inline BSSRDFProbeSegment Sample(Float u1, const Point2f &u2) const;
PBRT_CPU_GPU inline BSSRDFSample ProbeIntersectionToSample(
const SubsurfaceInteraction &si, ScratchBuffer &scratchBuffer) const;
};
} // namespace pbrt
#endif // PBRT_BASE_BSSRDF_H

198
src/pbrt/base/bxdf.h Normal file
View file

@ -0,0 +1,198 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_BASE_BXDF_H
#define PBRT_BASE_BXDF_H
#include <pbrt/pbrt.h>
#include <pbrt/util/pstd.h>
#include <pbrt/util/spectrum.h>
#include <pbrt/util/taggedptr.h>
#include <pbrt/util/vecmath.h>
#include <string>
namespace pbrt {
class MeasuredBRDF;
// BxDFReflTransFlags Definition
enum class BxDFReflTransFlags {
Unset = 0,
Reflection = 1 << 0,
Transmission = 1 << 1,
All = Reflection | Transmission
};
PBRT_CPU_GPU
inline BxDFReflTransFlags operator|(BxDFReflTransFlags a, BxDFReflTransFlags b) {
return BxDFReflTransFlags((int)a | (int)b);
}
PBRT_CPU_GPU
inline int operator&(BxDFReflTransFlags a, BxDFReflTransFlags b) {
return ((int)a & (int)b);
}
PBRT_CPU_GPU
inline BxDFReflTransFlags &operator|=(BxDFReflTransFlags &a, BxDFReflTransFlags b) {
(int &)a |= int(b);
return a;
}
std::string ToString(BxDFReflTransFlags flags);
// BxDFFlags Definition
enum class BxDFFlags {
Unset = 0,
Reflection = 1 << 0,
Transmission = 1 << 1,
Diffuse = 1 << 2,
Glossy = 1 << 3,
Specular = 1 << 4,
// Composite _BxDFFlags_ definitions
DiffuseReflection = Diffuse | Reflection,
DiffuseTransmission = Diffuse | Transmission,
GlossyReflection = Glossy | Reflection,
GlossyTransmission = Glossy | Transmission,
SpecularReflection = Specular | Reflection,
SpecularTransmission = Specular | Transmission,
All = Diffuse | Glossy | Specular | Reflection | Transmission
};
PBRT_CPU_GPU
inline BxDFFlags operator|(BxDFFlags a, BxDFFlags b) {
return BxDFFlags((int)a | (int)b);
}
PBRT_CPU_GPU
inline int operator&(BxDFFlags a, BxDFFlags b) {
return ((int)a & (int)b);
}
PBRT_CPU_GPU
inline int operator&(BxDFFlags a, BxDFReflTransFlags b) {
return ((int)a & (int)b);
}
PBRT_CPU_GPU
inline BxDFFlags &operator|=(BxDFFlags &a, BxDFFlags b) {
(int &)a |= int(b);
return a;
}
PBRT_CPU_GPU
inline bool IsReflective(BxDFFlags flags) {
return (flags & BxDFFlags::Reflection) != 0;
}
PBRT_CPU_GPU
inline bool IsTransmissive(BxDFFlags flags) {
return (flags & BxDFFlags::Transmission) != 0;
}
PBRT_CPU_GPU
inline bool IsDiffuse(BxDFFlags flags) {
return (flags & BxDFFlags::Diffuse) != 0;
}
PBRT_CPU_GPU
inline bool IsGlossy(BxDFFlags flags) {
return (flags & BxDFFlags::Glossy) != 0;
}
PBRT_CPU_GPU
inline bool IsSpecular(BxDFFlags flags) {
return (flags & BxDFFlags::Specular) != 0;
}
std::string ToString(BxDFFlags flags);
// TransportMode Definition
enum class TransportMode { Radiance, Importance };
PBRT_CPU_GPU
inline TransportMode operator~(TransportMode mode) {
return (mode == TransportMode::Radiance) ? TransportMode::Importance
: TransportMode::Radiance;
}
std::string ToString(TransportMode mode);
// BSDFSample Definition
struct BSDFSample {
// BSDFSample Public Methods
BSDFSample() = default;
PBRT_CPU_GPU
BSDFSample(const SampledSpectrum &f, const Vector3f &wi, Float pdf, BxDFFlags flags)
: f(f), wi(wi), pdf(pdf), flags(flags) {}
PBRT_CPU_GPU
operator bool() const { return pdf > 0; }
PBRT_CPU_GPU
bool IsReflection() const { return pbrt::IsReflective(flags); }
PBRT_CPU_GPU
bool IsTransmission() const { return pbrt::IsTransmissive(flags); }
PBRT_CPU_GPU
bool IsDiffuse() const { return pbrt::IsDiffuse(flags); }
PBRT_CPU_GPU
bool IsGlossy() const { return pbrt::IsGlossy(flags); }
PBRT_CPU_GPU
bool IsSpecular() const { return pbrt::IsSpecular(flags); }
std::string ToString() const;
SampledSpectrum f;
Vector3f wi;
Float pdf = 0;
BxDFFlags flags;
};
class IdealDiffuseBxDF;
class DiffuseBxDF;
class DielectricInterfaceBxDF;
class ThinDielectricBxDF;
class HairBxDF;
class MeasuredBxDF;
class ConductorBxDF;
class BSSRDFAdapter;
class CoatedDiffuseBxDF;
class CoatedConductorBxDF;
// BxDFHandle Definition
class BxDFHandle : public TaggedPointer<IdealDiffuseBxDF, DiffuseBxDF, CoatedDiffuseBxDF,
CoatedConductorBxDF, DielectricInterfaceBxDF,
ThinDielectricBxDF, HairBxDF, MeasuredBxDF,
ConductorBxDF, BSSRDFAdapter> {
public:
// BxDF Interface
using TaggedPointer::TaggedPointer;
std::string ToString() const;
PBRT_CPU_GPU inline BxDFFlags Flags() const;
PBRT_CPU_GPU inline SampledSpectrum f(Vector3f wo, Vector3f wi,
TransportMode mode) const;
PBRT_CPU_GPU inline BSDFSample Sample_f(
Vector3f wo, Float uc, const Point2f &u, TransportMode mode,
BxDFReflTransFlags sampleFlags = BxDFReflTransFlags::All) const;
PBRT_CPU_GPU inline bool SampledPDFIsProportional() const;
PBRT_CPU_GPU inline Float PDF(
Vector3f wo, Vector3f wi, TransportMode mode,
BxDFReflTransFlags sampleFlags = BxDFReflTransFlags::All) const;
PBRT_CPU_GPU
SampledSpectrum rho(Vector3f wo, pstd::span<const Float> uc,
pstd::span<const Point2f> u2) const;
SampledSpectrum rho(pstd::span<const Float> uc1, pstd::span<const Point2f> u1,
pstd::span<const Float> uc2, pstd::span<const Point2f> u2) const;
PBRT_CPU_GPU inline void Regularize();
};
} // namespace pbrt
#endif // PBRT_BASE_BXDF_H

79
src/pbrt/base/camera.h Normal file
View file

@ -0,0 +1,79 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_BASE_CAMERA_H
#define PBRT_BASE_CAMERA_H
#include <pbrt/pbrt.h>
#include <pbrt/base/film.h>
#include <pbrt/base/filter.h>
#include <pbrt/util/taggedptr.h>
#include <pbrt/util/transform.h>
#include <pbrt/util/vecmath.h>
#include <string>
namespace pbrt {
// Camera Declarations
struct CameraRay;
struct CameraRayDifferential;
struct CameraWiSample;
struct CameraSample;
class CameraTransform;
class PerspectiveCamera;
class OrthographicCamera;
class SphericalCamera;
class RealisticCamera;
// CameraHandle Definition
class CameraHandle : public TaggedPointer<PerspectiveCamera, OrthographicCamera,
SphericalCamera, RealisticCamera> {
public:
// Camera Interface
using TaggedPointer::TaggedPointer;
static CameraHandle Create(const std::string &name,
const ParameterDictionary &parameters, MediumHandle medium,
const CameraTransform &cameraTransform, FilmHandle film,
const FileLoc *loc, Allocator alloc);
PBRT_CPU_GPU inline FilmHandle GetFilm() const;
PBRT_CPU_GPU inline Float SampleTime(Float u) const;
PBRT_CPU_GPU inline const CameraTransform &GetCameraTransform() const;
void InitMetadata(ImageMetadata *metadata) const;
std::string ToString() const;
PBRT_CPU_GPU inline CameraRay GenerateRay(CameraSample sample,
SampledWavelengths &lambda) const;
PBRT_CPU_GPU
pstd::optional<CameraRayDifferential> GenerateRayDifferential(
const CameraSample &sample, SampledWavelengths &lambda) const;
PBRT_CPU_GPU
void ApproximatedPdxy(const SurfaceInteraction &si) const;
PBRT_CPU_GPU
SampledSpectrum We(const Ray &ray, SampledWavelengths &lambda,
Point2f *pRaster2 = nullptr) const;
PBRT_CPU_GPU
void PDF_We(const Ray &ray, Float *pdfPos, Float *pdfDir) const;
PBRT_CPU_GPU
pstd::optional<CameraWiSample> SampleWi(const Interaction &ref, const Point2f &u,
SampledWavelengths &lambda) const;
};
} // namespace pbrt
#endif // PBRT_BASE_CAMERA_H

65
src/pbrt/base/film.h Normal file
View file

@ -0,0 +1,65 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_BASE_FILM_H
#define PBRT_BASE_FILM_H
#include <pbrt/pbrt.h>
#include <pbrt/base/filter.h>
#include <pbrt/util/pstd.h>
#include <pbrt/util/taggedptr.h>
#include <string>
namespace pbrt {
class VisibleSurface;
class RGBFilm;
class GBufferFilm;
// FilmHandle Definition
class FilmHandle : public TaggedPointer<RGBFilm, GBufferFilm> {
public:
// Film Interface
PBRT_CPU_GPU inline SampledWavelengths SampleWavelengths(Float u) const;
PBRT_CPU_GPU inline void AddSample(const Point2i &pFilm, SampledSpectrum L,
const SampledWavelengths &lambda,
const VisibleSurface *visibleSurface,
Float weight);
PBRT_CPU_GPU
bool UsesVisibleSurface() const;
PBRT_CPU_GPU
void AddSplat(const Point2f &p, SampledSpectrum v, const SampledWavelengths &lambda);
PBRT_CPU_GPU inline Point2i FullResolution() const;
PBRT_CPU_GPU inline Float Diagonal() const;
PBRT_CPU_GPU inline Bounds2i PixelBounds() const;
PBRT_CPU_GPU
RGB GetPixelRGB(const Point2i &p, Float splatScale = 1) const;
void WriteImage(ImageMetadata metadata, Float splatScale = 1);
Image GetImage(ImageMetadata *metadata, Float splatScale = 1);
using TaggedPointer::TaggedPointer;
static FilmHandle Create(const std::string &name,
const ParameterDictionary &parameters, const FileLoc *loc,
FilterHandle filter, Allocator alloc);
PBRT_CPU_GPU inline FilterHandle GetFilter() const;
std::string GetFilename() const;
std::string ToString() const;
PBRT_CPU_GPU inline Bounds2f SampleBounds() const;
};
} // namespace pbrt
#endif // PBRT_BASE_FILM_H

48
src/pbrt/base/filter.h Normal file
View file

@ -0,0 +1,48 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_BASE_FILTER_H
#define PBRT_BASE_FILTER_H
#include <pbrt/pbrt.h>
#include <pbrt/util/taggedptr.h>
#include <string>
namespace pbrt {
// Filter Declarations
struct FilterSample;
class BoxFilter;
class GaussianFilter;
class MitchellFilter;
class LanczosSincFilter;
class TriangleFilter;
// FilterHandle Definition
class FilterHandle : public TaggedPointer<BoxFilter, GaussianFilter, MitchellFilter,
LanczosSincFilter, TriangleFilter> {
public:
// Filter Interface
using TaggedPointer::TaggedPointer;
static FilterHandle Create(const std::string &name,
const ParameterDictionary &parameters, const FileLoc *loc,
Allocator alloc);
PBRT_CPU_GPU inline Vector2f Radius() const;
PBRT_CPU_GPU inline Float Evaluate(const Point2f &p) const;
PBRT_CPU_GPU inline FilterSample Sample(const Point2f &u) const;
PBRT_CPU_GPU inline Float Integral() const;
std::string ToString() const;
};
} // namespace pbrt
#endif // PBRT_BASE_FILTER_H

102
src/pbrt/base/light.h Normal file
View file

@ -0,0 +1,102 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_BASE_LIGHT_H
#define PBRT_BASE_LIGHT_H
#include <pbrt/pbrt.h>
#include <pbrt/base/medium.h>
#include <pbrt/base/shape.h>
#include <pbrt/util/pstd.h>
#include <pbrt/util/taggedptr.h>
#include <string>
namespace pbrt {
// LightType Definition
enum class LightType : int { DeltaPosition, DeltaDirection, Area, Infinite };
// LightSamplingMode Definition
enum class LightSamplingMode { WithMIS, WithoutMIS };
class PointLight;
class DistantLight;
class ProjectionLight;
class GoniometricLight;
class DiffuseAreaLight;
class UniformInfiniteLight;
class ImageInfiniteLight;
class PortalImageInfiniteLight;
class SpotLight;
class LightSampleContext;
struct LightBounds;
struct LightLiSample;
struct LightLeSample;
// LightHandle Definition
class LightHandle
: public TaggedPointer<PointLight, DistantLight, ProjectionLight, GoniometricLight,
SpotLight, DiffuseAreaLight, UniformInfiniteLight,
ImageInfiniteLight, PortalImageInfiniteLight> {
public:
// Light Interface
using TaggedPointer::TaggedPointer;
static LightHandle Create(const std::string &name,
const ParameterDictionary &parameters,
const Transform &renderFromLight,
const CameraTransform &cameraTransform,
MediumHandle outsideMedium, const FileLoc *loc,
Allocator alloc);
static LightHandle CreateArea(const std::string &name,
const ParameterDictionary &parameters,
const Transform &renderFromLight,
const MediumInterface &mediumInterface,
const ShapeHandle shape, const FileLoc *loc,
Allocator alloc);
void Preprocess(const Bounds3f &sceneBounds);
PBRT_CPU_GPU inline LightType Type() const;
SampledSpectrum Phi(const SampledWavelengths &lambda) const;
PBRT_CPU_GPU inline LightLiSample SampleLi(
LightSampleContext ctx, Point2f u, SampledWavelengths lambda,
LightSamplingMode mode = LightSamplingMode::WithoutMIS) const;
PBRT_CPU_GPU inline Float PDF_Li(
LightSampleContext ctx, Vector3f wi,
LightSamplingMode mode = LightSamplingMode::WithoutMIS) const;
std::string ToString() const;
// AreaLights only
PBRT_CPU_GPU inline SampledSpectrum L(const Point3f &p, const Normal3f &n,
const Point2f &uv, const Vector3f &w,
const SampledWavelengths &lambda) const;
PBRT_CPU_GPU
void PDF_Le(const Interaction &intr, Vector3f &w, Float *pdfPos, Float *pdfDir) const;
// InfiniteAreaLights only
PBRT_CPU_GPU inline SampledSpectrum Le(const Ray &ray,
const SampledWavelengths &lambda) const;
LightBounds Bounds() const;
PBRT_CPU_GPU
LightLeSample SampleLe(const Point2f &u1, const Point2f &u2,
SampledWavelengths &lambda, Float time) const;
// Note shouldn't be called for area lights..
PBRT_CPU_GPU
void PDF_Le(const Ray &ray, Float *pdfPos, Float *pdfDir) const;
};
} // namespace pbrt
#endif // PBRT_BASE_LIGHT_H

View file

@ -0,0 +1,51 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_BASE_LIGHTSAMPLER_H
#define PBRT_BASE_LIGHTSAMPLER_H
#include <pbrt/pbrt.h>
#include <pbrt/util/taggedptr.h>
#include <string>
namespace pbrt {
// SampledLight Definition
struct SampledLight {
LightHandle light;
Float pdf = 0;
std::string ToString() const;
};
class UniformLightSampler;
class PowerLightSampler;
class BVHLightSampler;
class ExhaustiveLightSampler;
// LightSamplerHandle Definition
class LightSamplerHandle : public TaggedPointer<UniformLightSampler, PowerLightSampler,
BVHLightSampler, ExhaustiveLightSampler> {
public:
// LightSampler Interface
using TaggedPointer::TaggedPointer;
static LightSamplerHandle Create(const std::string &name,
pstd::span<const LightHandle> lights,
Allocator alloc);
std::string ToString() const;
PBRT_CPU_GPU inline pstd::optional<SampledLight> Sample(const LightSampleContext &ctx,
Float u) const;
PBRT_CPU_GPU inline Float PDF(const LightSampleContext &ctx, LightHandle light) const;
PBRT_CPU_GPU inline pstd::optional<SampledLight> Sample(Float u) const;
PBRT_CPU_GPU inline Float PDF(LightHandle light) const;
};
} // namespace pbrt
#endif // PBRT_BASE_LIGHTSAMPLER_H

72
src/pbrt/base/material.h Normal file
View file

@ -0,0 +1,72 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_BASE_MATERIAL_H
#define PBRT_BASE_MATERIAL_H
#include <pbrt/pbrt.h>
#include <pbrt/base/bssrdf.h>
#include <pbrt/base/texture.h>
#include <pbrt/util/taggedptr.h>
#include <map>
#include <string>
namespace pbrt {
struct MaterialEvalContext;
// Material Declarations
class CoatedDiffuseMaterial;
class CoatedConductorMaterial;
class ConductorMaterial;
class DielectricMaterial;
class DiffuseMaterial;
class DiffuseTransmissionMaterial;
class HairMaterial;
class MeasuredMaterial;
class SubsurfaceMaterial;
class ThinDielectricMaterial;
// MaterialHandle Definition
class MaterialHandle
: public TaggedPointer<CoatedDiffuseMaterial, CoatedConductorMaterial,
ConductorMaterial, DielectricMaterial, DiffuseMaterial,
DiffuseTransmissionMaterial, HairMaterial, MeasuredMaterial,
SubsurfaceMaterial, ThinDielectricMaterial> {
public:
// Material Interface
using TaggedPointer::TaggedPointer;
static MaterialHandle Create(
const std::string &name, const TextureParameterDictionary &parameters,
/*const */ std::map<std::string, MaterialHandle> &namedMaterials,
const FileLoc *loc, Allocator alloc);
std::string ToString() const;
template <typename TextureEvaluator>
PBRT_CPU_GPU inline bool CanEvaluateTextures(TextureEvaluator texEval) const;
template <typename TextureEvaluator>
PBRT_CPU_GPU inline BSDF GetBSDF(TextureEvaluator texEval, MaterialEvalContext ctx,
SampledWavelengths &lambda,
ScratchBuffer &scratchBuffer) const;
template <typename TextureEvaluator>
PBRT_CPU_GPU inline BSSRDFHandle GetBSSRDF(TextureEvaluator texEval,
MaterialEvalContext ctx,
SampledWavelengths &lambda,
ScratchBuffer &scratchBuffer) const;
PBRT_CPU_GPU inline FloatTextureHandle GetDisplacement() const;
PBRT_CPU_GPU inline bool IsTransparent() const;
PBRT_CPU_GPU inline bool HasSubsurfaceScattering() const;
};
} // namespace pbrt
#endif // PBRT_BASE_MATERIAL_H

90
src/pbrt/base/medium.h Normal file
View file

@ -0,0 +1,90 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_BASE_MEDIUM_H
#define PBRT_BASE_MEDIUM_H
#include <pbrt/pbrt.h>
#include <pbrt/util/pstd.h>
#include <pbrt/util/rng.h>
#include <pbrt/util/spectrum.h>
#include <pbrt/util/taggedptr.h>
#include <string>
#include <vector>
namespace pbrt {
// PhaseFunctionSample Definition
struct PhaseFunctionSample {
PBRT_CPU_GPU operator bool() const { return pdf > 0; }
Float p;
Vector3f wi;
Float pdf = 0;
};
// PhaseFunctionHandle Definition
class HGPhaseFunction;
class PhaseFunctionHandle : public TaggedPointer<HGPhaseFunction> {
public:
// PhaseFunctionHandle Interface
using TaggedPointer::TaggedPointer;
std::string ToString() const;
PBRT_CPU_GPU inline Float p(const Vector3f &wo, const Vector3f &wi) const;
PBRT_CPU_GPU inline PhaseFunctionSample Sample_p(const Vector3f &wo,
const Point2f &u) const;
PBRT_CPU_GPU inline Float PDF(const Vector3f &wo, const Vector3f &wi) const;
};
class HomogeneousMedium;
class GridDensityMedium;
struct MediumSample;
// MediumHandle Definition
class MediumHandle : public TaggedPointer<HomogeneousMedium, GridDensityMedium> {
public:
// MediumHandle Interface
using TaggedPointer::TaggedPointer;
static MediumHandle Create(const std::string &name,
const ParameterDictionary &parameters,
const Transform &renderFromMedium, const FileLoc *loc,
Allocator alloc);
std::string ToString() const;
template <typename F>
PBRT_CPU_GPU void SampleTmaj(const Ray &ray, Float tMax, RNG &rng,
const SampledWavelengths &lambda, F callback) const;
bool IsEmissive() const;
};
// MediumInterface Definition
struct MediumInterface {
// MediumInterface Public Methods
std::string ToString() const;
MediumInterface() = default;
PBRT_CPU_GPU
MediumInterface(MediumHandle medium) : inside(medium), outside(medium) {}
PBRT_CPU_GPU
MediumInterface(MediumHandle inside, MediumHandle outside)
: inside(inside), outside(outside) {}
PBRT_CPU_GPU
bool IsMediumTransition() const { return inside != outside; }
// MediumInterface Public Members
MediumHandle inside, outside;
};
} // namespace pbrt
#endif // PBRT_BASE_MEDIUM_H

66
src/pbrt/base/sampler.h Normal file
View file

@ -0,0 +1,66 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_BASE_SAMPLER_H
#define PBRT_BASE_SAMPLER_H
#include <pbrt/pbrt.h>
#include <pbrt/util/taggedptr.h>
#include <pbrt/util/vecmath.h>
#include <string>
#include <vector>
namespace pbrt {
// CameraSample Definition
struct CameraSample {
Point2f pFilm;
Point2f pLens;
Float time = 0;
Float weight = 1;
std::string ToString() const;
};
// Sampler Declarations
class HaltonSampler;
class PaddedSobolSampler;
class PMJ02BNSampler;
class RandomSampler;
class SobolSampler;
class StratifiedSampler;
class MLTSampler;
class DebugMLTSampler;
// SamplerHandle Definition
class SamplerHandle
: public TaggedPointer<HaltonSampler, PaddedSobolSampler, PMJ02BNSampler,
RandomSampler, SobolSampler, StratifiedSampler, MLTSampler,
DebugMLTSampler> {
public:
// Sampler Interface
using TaggedPointer::TaggedPointer;
static SamplerHandle Create(const std::string &name,
const ParameterDictionary &parameters,
const Point2i &fullResolution, const FileLoc *loc,
Allocator alloc);
PBRT_CPU_GPU inline int SamplesPerPixel() const;
PBRT_CPU_GPU inline void StartPixelSample(const Point2i &p, int sampleIndex,
int dimension = 0);
PBRT_CPU_GPU inline Float Get1D();
PBRT_CPU_GPU inline Point2f Get2D();
std::vector<SamplerHandle> Clone(int n, Allocator alloc);
std::string ToString() const;
};
} // namespace pbrt
#endif // PBRT_BASE_SAMPLER_H

82
src/pbrt/base/shape.h Normal file
View file

@ -0,0 +1,82 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_BASE_SHAPE_H
#define PBRT_BASE_SHAPE_H
#include <pbrt/pbrt.h>
#include <pbrt/util/buffercache.h>
#include <pbrt/util/float.h>
#include <pbrt/util/taggedptr.h>
#include <pbrt/util/vecmath.h>
#include <string>
namespace pbrt {
// Shape Declarations
class Triangle;
class BilinearPatch;
class Curve;
class Sphere;
class Cylinder;
class Disk;
struct ShapeSample;
struct ShapeIntersection;
class ShapeSampleContext;
// ShapeHandle Definition
class ShapeHandle
: public TaggedPointer<Triangle, BilinearPatch, Curve, Sphere, Cylinder, Disk> {
public:
// Shape Interface
using TaggedPointer::TaggedPointer;
static pstd::vector<ShapeHandle> Create(const std::string &name,
const Transform *renderFromObject,
const Transform *objectFromRender,
bool reverseOrientation,
const ParameterDictionary &parameters,
const FileLoc *loc, Allocator alloc);
std::string ToString() const;
PBRT_CPU_GPU inline Bounds3f Bounds() const;
PBRT_CPU_GPU inline DirectionCone NormalBounds() const;
PBRT_CPU_GPU inline pstd::optional<ShapeIntersection> Intersect(
const Ray &ray, Float tMax = Infinity) const;
PBRT_CPU_GPU inline bool IntersectP(const Ray &ray, Float tMax = Infinity) const;
PBRT_CPU_GPU inline Float Area() const;
PBRT_CPU_GPU inline pstd::optional<ShapeSample> Sample(const Point2f &u) const;
PBRT_CPU_GPU inline Float PDF(const Interaction &) const;
PBRT_CPU_GPU inline pstd::optional<ShapeSample> Sample(const ShapeSampleContext &ctx,
const Point2f &u) const;
PBRT_CPU_GPU inline Float PDF(const ShapeSampleContext &ctx,
const Vector3f &wi) const;
private:
// ShapeHandle Private Members
friend class TriangleMesh;
friend class BilinearPatchMesh;
static BufferCache<int> *indexBufferCache;
static BufferCache<Point3f> *pBufferCache;
static BufferCache<Normal3f> *nBufferCache;
static BufferCache<Point2f> *uvBufferCache;
static BufferCache<Vector3f> *sBufferCache;
static BufferCache<int> *faceIndexBufferCache;
};
} // namespace pbrt
#endif // PBRT_BASE_SHAPE_H

88
src/pbrt/base/texture.h Normal file
View file

@ -0,0 +1,88 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_BASE_TEXTURE_H
#define PBRT_BASE_TEXTURE_H
#include <pbrt/pbrt.h>
#include <pbrt/util/taggedptr.h>
#include <string>
namespace pbrt {
struct TextureEvalContext;
class FloatConstantTexture;
class FloatBilerpTexture;
class FloatCheckerboardTexture;
class FloatDotsTexture;
class FBmTexture;
class GPUFloatImageTexture;
class FloatImageTexture;
class FloatMixTexture;
class FloatPtexTexture;
class FloatScaledTexture;
class WindyTexture;
class WrinkledTexture;
// FloatTextureHandle Definition
class FloatTextureHandle
: public TaggedPointer<FloatImageTexture, GPUFloatImageTexture, FloatMixTexture,
FloatScaledTexture, FloatConstantTexture, FloatBilerpTexture,
FloatCheckerboardTexture, FloatDotsTexture, FBmTexture,
FloatPtexTexture, WindyTexture, WrinkledTexture> {
public:
// FloatTexture Interface
using TaggedPointer::TaggedPointer;
static FloatTextureHandle Create(const std::string &name,
const Transform &renderFromTexture,
const TextureParameterDictionary &parameters,
const FileLoc *loc, Allocator alloc, bool gpu);
std::string ToString() const;
PBRT_CPU_GPU inline Float Evaluate(TextureEvalContext ctx) const;
};
class RGBConstantTexture;
class RGBReflectanceConstantTexture;
class SpectrumConstantTexture;
class SpectrumBilerpTexture;
class SpectrumCheckerboardTexture;
class SpectrumImageTexture;
class GPUSpectrumImageTexture;
class MarbleTexture;
class SpectrumMixTexture;
class SpectrumDotsTexture;
class SpectrumPtexTexture;
class SpectrumScaledTexture;
// SpectrumTextureHandle Definition
class SpectrumTextureHandle
: public TaggedPointer<
RGBConstantTexture, RGBReflectanceConstantTexture, SpectrumImageTexture,
GPUSpectrumImageTexture, SpectrumMixTexture, SpectrumScaledTexture,
SpectrumConstantTexture, SpectrumBilerpTexture, SpectrumCheckerboardTexture,
MarbleTexture, SpectrumDotsTexture, SpectrumPtexTexture> {
public:
// SpectrumTexture Interface
using TaggedPointer::TaggedPointer;
static SpectrumTextureHandle Create(const std::string &name,
const Transform &renderFromTexture,
const TextureParameterDictionary &parameters,
const FileLoc *loc, Allocator alloc, bool gpu);
std::string ToString() const;
PBRT_CPU_GPU inline SampledSpectrum Evaluate(TextureEvalContext ctx,
SampledWavelengths lambda) const;
};
} // namespace pbrt
#endif // PBRT_BASE_TEXTURE_H

22
src/pbrt/bsdf.cpp Normal file
View file

@ -0,0 +1,22 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#include <pbrt/bsdf.h>
#include <pbrt/util/spectrum.h>
namespace pbrt {
std::string BSDFSample::ToString() const {
return StringPrintf("[ BSDFSample f: %s wi: %s pdf: %s flags: %s ]", f, wi, pdf,
flags);
}
// BSDF Method Definitions
std::string BSDF::ToString() const {
return StringPrintf("[ BSDF eta: %f bxdf: %s shadingFrame: %s ng: %s ]", eta, bxdf,
shadingFrame, ng);
}
} // namespace pbrt

201
src/pbrt/bsdf.h Normal file
View file

@ -0,0 +1,201 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_BSDF_H
#define PBRT_BSDF_H
#include <pbrt/pbrt.h>
#include <pbrt/bxdfs.h>
#include <pbrt/interaction.h>
#include <pbrt/util/memory.h>
#include <pbrt/util/pstd.h>
#include <pbrt/util/vecmath.h>
namespace pbrt {
// BSDF Definition
class BSDF {
public:
// BSDF Public Methods
BSDF() = default;
PBRT_CPU_GPU
BSDF(const Vector3f &wo, const Normal3f &n, const Normal3f &ns, const Vector3f &dpdus,
BxDFHandle bxdf, Float eta = 1)
: eta(Dot(wo, n) < 0 ? 1 / eta : eta),
bxdf(bxdf),
ng(n),
shadingFrame(Frame::FromXZ(Normalize(dpdus), Vector3f(ns))) {}
PBRT_CPU_GPU
operator bool() const { return (bool)bxdf; }
PBRT_CPU_GPU
Vector3f RenderToLocal(const Vector3f &v) const { return shadingFrame.ToLocal(v); }
PBRT_CPU_GPU
Vector3f LocalToRender(const Vector3f &v) const { return shadingFrame.FromLocal(v); }
PBRT_CPU_GPU
BxDFHandle GetBxDF() const { return bxdf; }
PBRT_CPU_GPU
void SetBxDF(BxDFHandle b) { bxdf = b; }
PBRT_CPU_GPU
bool IsNonSpecular() const {
return (bxdf.Flags() & (BxDFFlags::Diffuse | BxDFFlags::Glossy));
}
PBRT_CPU_GPU
bool IsDiffuse() const { return (bxdf.Flags() & BxDFFlags::Diffuse); }
PBRT_CPU_GPU
bool IsGlossy() const { return (bxdf.Flags() & BxDFFlags::Glossy); }
PBRT_CPU_GPU
bool IsSpecular() const { return (bxdf.Flags() & BxDFFlags::Specular); }
PBRT_CPU_GPU
bool HasReflection() const { return (bxdf.Flags() & BxDFFlags::Reflection); }
PBRT_CPU_GPU
bool HasTransmission() const { return (bxdf.Flags() & BxDFFlags::Transmission); }
PBRT_CPU_GPU
SampledSpectrum f(Vector3f woRender, Vector3f wiRender,
TransportMode mode = TransportMode::Radiance) const {
Vector3f wi = RenderToLocal(wiRender), wo = RenderToLocal(woRender);
if (wo.z == 0)
return {};
return bxdf.f(wo, wi, mode) * GBump(woRender, wiRender, mode);
}
template <typename BxDF>
PBRT_CPU_GPU SampledSpectrum f(Vector3f woW, Vector3f wiW,
TransportMode mode = TransportMode::Radiance) const {
Vector3f wi = RenderToLocal(wiW), wo = RenderToLocal(woW);
if (wo.z == 0)
return {};
const BxDF *specificBxDF = bxdf.Cast<BxDF>();
return specificBxDF->f(wo, wi, mode) * GBump(woW, wiW, mode);
}
PBRT_CPU_GPU
SampledSpectrum rho(pstd::span<const Float> uc1, pstd::span<const Point2f> u1,
pstd::span<const Float> uc2, pstd::span<const Point2f> u2) const {
return bxdf.rho(uc1, u1, uc2, u2);
}
PBRT_CPU_GPU
SampledSpectrum rho(const Vector3f &woRender, pstd::span<const Float> uc,
pstd::span<const Point2f> u) const {
Vector3f wo = RenderToLocal(woRender);
return bxdf.rho(wo, uc, u);
}
PBRT_CPU_GPU
BSDFSample Sample_f(Vector3f woRender, Float u, const Point2f &u2,
TransportMode mode = TransportMode::Radiance,
BxDFReflTransFlags sampleFlags = BxDFReflTransFlags::All) const {
Vector3f wo = RenderToLocal(woRender);
if (wo.z == 0 || !(bxdf.Flags() & sampleFlags))
return {};
BSDFSample bs = bxdf.Sample_f(wo, u, u2, mode, sampleFlags);
if (!bs || !bs.f)
return {};
DCHECK_GT(bs.pdf, 0);
VLOG(2, "For wo = %s, sampled f = %s, pdf = %f, ratio = %s, wi = %s", wo, bs.f,
bs.pdf, (bs.pdf > 0) ? (bs.f / bs.pdf) : SampledSpectrum(0.), bs.wi);
bs.wi = LocalToRender(bs.wi);
bs.f *= GBump(woRender, bs.wi, mode);
return bs;
}
PBRT_CPU_GPU
Float PDF(Vector3f woRender, Vector3f wiRender,
TransportMode mode = TransportMode::Radiance,
BxDFReflTransFlags sampleFlags = BxDFReflTransFlags::All) const {
Vector3f wo = RenderToLocal(woRender), wi = RenderToLocal(wiRender);
if (wo.z == 0)
return 0;
return bxdf.PDF(wo, wi, mode, sampleFlags);
}
PBRT_CPU_GPU
bool SampledPDFIsProportional() const { return bxdf.SampledPDFIsProportional(); }
template <typename BxDF>
PBRT_CPU_GPU BSDFSample
Sample_f(Vector3f woRender, Float u, const Point2f &u2,
TransportMode mode = TransportMode::Radiance,
BxDFReflTransFlags sampleFlags = BxDFReflTransFlags::All) const {
Vector3f wo = RenderToLocal(woRender);
if (wo.z == 0)
return {};
const BxDF *specificBxDF = bxdf.Cast<BxDF>();
if (!(specificBxDF->Flags() & sampleFlags))
return {};
BSDFSample bs = specificBxDF->Sample_f(wo, u, u2, mode, sampleFlags);
if (!bs || !bs.f)
return {};
CHECK_GT(bs.pdf, 0);
VLOG(2, "For wo = %s, sampled f = %s, pdf = %f, ratio = %s, wi = %s", wo, bs.f,
bs.pdf, (bs.pdf > 0) ? (bs.f / bs.pdf) : SampledSpectrum(0.), bs.wi);
bs.wi = LocalToRender(bs.wi);
bs.f *= GBump(woRender, bs.wi, mode);
return bs;
}
template <typename BxDF>
PBRT_CPU_GPU Float
PDF(Vector3f woRender, Vector3f wiRender,
TransportMode mode = TransportMode::Radiance,
BxDFReflTransFlags sampleFlags = BxDFReflTransFlags::All) const {
Vector3f wo = RenderToLocal(woRender), wi = RenderToLocal(wiRender);
if (wo.z == 0)
return 0.;
const BxDF *specificBxDF = bxdf.Cast<BxDF>();
return specificBxDF->PDF(wo, wi, mode, sampleFlags);
}
std::string ToString() const;
PBRT_CPU_GPU
void Regularize() { bxdf.Regularize(); }
// BSDF Public Members
Float eta;
private:
friend class SOA<BSDF>;
// BSDF Private Methods
PBRT_CPU_GPU
Float GBump(Vector3f wo, Vector3f wi, TransportMode mode) const {
return 1; // disable for now...
Vector3f w = (mode == TransportMode::Radiance) ? wi : wo;
Normal3f ngf = FaceForward(ng, w);
Normal3f nsf = FaceForward(Normal3f(shadingFrame.z), ngf);
Float cosThetaIs = std::max<Float>(0, Dot(nsf, w)), cosThetaIg = Dot(ngf, w);
Float cosThetaN = Dot(ngf, nsf);
CHECK_GE(cosThetaIs, 0);
CHECK_GE(cosThetaIg, 0);
CHECK_GE(cosThetaN, 0);
if (cosThetaIs == 0 || cosThetaIg == 0 || cosThetaN == 0)
return 0;
Float G = cosThetaIg / (cosThetaIs * cosThetaN);
if (G >= 1)
return 1;
return -G * G * G + G * G + G;
}
// BSDF Private Members
BxDFHandle bxdf;
Frame shadingFrame;
Normal3f ng;
};
} // namespace pbrt
#endif // PBRT_BSDF_H

795
src/pbrt/bsdfs_test.cpp Normal file
View file

@ -0,0 +1,795 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#include <gtest/gtest.h>
#include <pbrt/pbrt.h>
#include <pbrt/bsdf.h>
#include <pbrt/interaction.h>
#include <pbrt/options.h>
#include <pbrt/paramdict.h>
#include <pbrt/shapes.h>
#include <pbrt/util/image.h>
#include <pbrt/util/log.h>
#include <pbrt/util/memory.h>
#include <pbrt/util/parallel.h>
#include <pbrt/util/print.h>
#include <pbrt/util/rng.h>
#include <pbrt/util/sampling.h>
#include <pbrt/util/spectrum.h>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <functional>
using namespace pbrt;
/* The null hypothesis will be rejected when the associated
p-value is below the significance level specified here. */
#define CHI2_SLEVEL 0.01
/* Resolution of the frequency table discretization. The azimuthal
resolution is twice this value. */
#define CHI2_THETA_RES 10
#define CHI2_PHI_RES (2 * CHI2_THETA_RES)
/* Number of MC samples to compute the observed frequency table */
#define CHI2_SAMPLECOUNT 1000000
/* Minimum expected bin frequency. The chi^2 test does not
work reliably when the expected frequency in a cell is
low (e.g. less than 5), because normality assumptions
break down in this case. Therefore, the implementation
will merge such low-frequency cells when they fall below
the threshold specified here. */
#define CHI2_MINFREQ 5
/* Each provided BSDF will be tested for a few different
incident directions. The value specified here determines
how many tests will be executed per BSDF */
#define CHI2_RUNS 5
/// Regularized lower incomplete gamma function (based on code from Cephes)
double RLGamma(double a, double x) {
const double epsilon = 0.000000000000001;
const double big = 4503599627370496.0;
const double bigInv = 2.22044604925031308085e-16;
if (a < 0 || x < 0)
throw std::runtime_error("LLGamma: invalid arguments range!");
if (x == 0)
return 0.0f;
double ax = (a * std::log(x)) - x - std::lgamma(a);
if (ax < -709.78271289338399)
return a < x ? 1.0 : 0.0;
if (x <= 1 || x <= a) {
double r2 = a;
double c2 = 1;
double ans2 = 1;
do {
r2 = r2 + 1;
c2 = c2 * x / r2;
ans2 += c2;
} while ((c2 / ans2) > epsilon);
return std::exp(ax) * ans2 / a;
}
int c = 0;
double y = 1 - a;
double z = x + y + 1;
double p3 = 1;
double q3 = x;
double p2 = x + 1;
double q2 = z * x;
double ans = p2 / q2;
double error;
do {
c++;
y += 1;
z += 2;
double yc = y * c;
double p = (p2 * z) - (p3 * yc);
double q = (q2 * z) - (q3 * yc);
if (q != 0) {
double nextans = p / q;
error = std::abs((ans - nextans) / nextans);
ans = nextans;
} else {
// zero div, skip
error = 1;
}
// shift
p3 = p2;
p2 = p;
q3 = q2;
q2 = q;
// normalize fraction when the numerator becomes large
if (std::abs(p) > big) {
p3 *= bigInv;
p2 *= bigInv;
q3 *= bigInv;
q2 *= bigInv;
}
} while (error > epsilon);
return 1.0 - (std::exp(ax) * ans);
}
/// Chi^2 distribution cumulative distribution function
double Chi2CDF(double x, int dof) {
if (dof < 1 || x < 0) {
return 0.0;
} else if (dof == 2) {
return 1.0 - std::exp(-0.5 * x);
} else {
return (Float)RLGamma(0.5 * dof, 0.5 * x);
}
}
/// Adaptive Simpson integration over an 1D interval
Float AdaptiveSimpson(const std::function<Float(Float)>& f, Float x0, Float x1,
Float eps = 1e-6f, int depth = 6) {
int count = 0;
/* Define an recursive lambda function for integration over subintervals */
std::function<Float(Float, Float, Float, Float, Float, Float, Float, Float, int)>
integrate = [&](Float a, Float b, Float c, Float fa, Float fb, Float fc, Float I,
Float eps, int depth) {
/* Evaluate the function at two intermediate points */
Float d = 0.5f * (a + b), e = 0.5f * (b + c), fd = f(d), fe = f(e);
/* Simpson integration over each subinterval */
Float h = c - a, I0 = (Float)(1.0 / 12.0) * h * (fa + 4 * fd + fb),
I1 = (Float)(1.0 / 12.0) * h * (fb + 4 * fe + fc), Ip = I0 + I1;
++count;
/* Stopping criterion from J.N. Lyness (1969)
"Notes on the adaptive Simpson quadrature routine" */
if (depth <= 0 || std::abs(Ip - I) < 15 * eps) {
// Richardson extrapolation
return Ip + (Float)(1.0 / 15.0) * (Ip - I);
}
return integrate(a, d, b, fa, fd, fb, I0, .5f * eps, depth - 1) +
integrate(b, e, c, fb, fe, fc, I1, .5f * eps, depth - 1);
};
Float a = x0, b = 0.5f * (x0 + x1), c = x1;
Float fa = f(a), fb = f(b), fc = f(c);
Float I = (c - a) * (Float)(1.0 / 6.0) * (fa + 4 * fb + fc);
return integrate(a, b, c, fa, fb, fc, I, eps, depth);
}
/// Nested adaptive Simpson integration over a 2D rectangle
Float AdaptiveSimpson2D(const std::function<Float(Float, Float)>& f, Float x0, Float y0,
Float x1, Float y1, Float eps = 1e-6f, int depth = 6) {
/* Lambda function that integrates over the X axis */
auto integrate = [&](Float y) {
return AdaptiveSimpson(std::bind(f, std::placeholders::_1, y), x0, x1, eps,
depth);
};
Float value = AdaptiveSimpson(integrate, y0, y1, eps, depth);
return value;
}
/// Generate a histogram of the BSDF density function via MC sampling
void FrequencyTable(const BSDF* bsdf, const Vector3f& wo, RNG& rng, int sampleCount,
int thetaRes, int phiRes, Float* target) {
memset(target, 0, thetaRes * phiRes * sizeof(Float));
Float factorTheta = thetaRes / Pi, factorPhi = phiRes / (2 * Pi);
Vector3f wi;
for (int i = 0; i < sampleCount; ++i) {
Float u = rng.Uniform<Float>();
Point2f sample{rng.Uniform<Float>(), rng.Uniform<Float>()};
BSDFSample bs = bsdf->Sample_f(wo, u, sample);
if (!bs || bs.IsSpecular())
continue;
Vector3f wiL = bsdf->RenderToLocal(bs.wi);
Point2f coords(SafeACos(wiL.z) * factorTheta,
std::atan2(wiL.y, wiL.x) * factorPhi);
if (coords.y < 0)
coords.y += 2 * Pi * factorPhi;
int thetaBin = std::min(std::max(0, (int)std::floor(coords.x)), thetaRes - 1);
int phiBin = std::min(std::max(0, (int)std::floor(coords.y)), phiRes - 1);
target[thetaBin * phiRes + phiBin] += 1;
}
}
// Numerically integrate the probability density function over rectangles in
// spherical coordinates.
void IntegrateFrequencyTable(const BSDF* bsdf, const Vector3f& wo, int sampleCount,
int thetaRes, int phiRes, Float* target) {
memset(target, 0, thetaRes * phiRes * sizeof(Float));
Float factorTheta = Pi / thetaRes, factorPhi = (2 * Pi) / phiRes;
for (int i = 0; i < thetaRes; ++i) {
for (int j = 0; j < phiRes; ++j) {
*target++ =
sampleCount *
AdaptiveSimpson2D(
[&](Float theta, Float phi) -> Float {
Float cosTheta = std::cos(theta), sinTheta = std::sin(theta);
Float cosPhi = std::cos(phi), sinPhi = std::sin(phi);
Vector3f wiL(sinTheta * cosPhi, sinTheta * sinPhi, cosTheta);
return bsdf->PDF(wo, bsdf->LocalToRender(wiL)) * sinTheta;
},
i* factorTheta, j* factorPhi, (i + 1) * factorTheta,
(j + 1) * factorPhi);
}
}
}
/// Write the frequency tables to disk in a format that is nicely plottable by
/// Octave and MATLAB
void DumpTables(const Float* frequencies, const Float* expFrequencies, int thetaRes,
int phiRes, const char* filename) {
std::ofstream f(filename);
f << "frequencies = [ ";
for (int i = 0; i < thetaRes; ++i) {
for (int j = 0; j < phiRes; ++j) {
f << frequencies[i * phiRes + j];
if (j + 1 < phiRes)
f << ", ";
}
if (i + 1 < thetaRes)
f << "; ";
}
f << " ];" << std::endl << "expFrequencies = [ ";
for (int i = 0; i < thetaRes; ++i) {
for (int j = 0; j < phiRes; ++j) {
f << expFrequencies[i * phiRes + j];
if (j + 1 < phiRes)
f << ", ";
}
if (i + 1 < thetaRes)
f << "; ";
}
f << " ];" << std::endl
<< "colormap(jet);" << std::endl
<< "clf; subplot(2,1,1);" << std::endl
<< "imagesc(frequencies);" << std::endl
<< "title('Observed frequencies');" << std::endl
<< "axis equal;" << std::endl
<< "subplot(2,1,2);" << std::endl
<< "imagesc(expFrequencies);" << std::endl
<< "axis equal;" << std::endl
<< "title('Expected frequencies');" << std::endl;
f.close();
}
/// Run A Chi^2 test based on the given frequency tables
std::pair<bool, std::string> Chi2Test(const Float* frequencies,
const Float* expFrequencies, int thetaRes,
int phiRes, int sampleCount, Float minExpFrequency,
Float significanceLevel, int numTests) {
struct Cell {
Float expFrequency;
size_t index;
};
/* Sort all cells by their expected frequencies */
std::vector<Cell> cells(thetaRes * phiRes);
for (size_t i = 0; i < cells.size(); ++i) {
cells[i].expFrequency = expFrequencies[i];
cells[i].index = i;
}
std::sort(cells.begin(), cells.end(), [](const Cell& a, const Cell& b) {
return a.expFrequency < b.expFrequency;
});
/* Compute the Chi^2 statistic and pool cells as necessary */
Float pooledFrequencies = 0, pooledExpFrequencies = 0, chsq = 0;
int pooledCells = 0, dof = 0;
for (const Cell& c : cells) {
if (expFrequencies[c.index] == 0) {
if (frequencies[c.index] > sampleCount * 1e-5f) {
/* Uh oh: samples in a c that should be completely empty
according to the probability density function. Ordinarily,
even a single sample requires immediate rejection of the null
hypothesis. But due to finite-precision computations and
rounding
errors, this can occasionally happen without there being an
actual bug. Therefore, the criterion here is a bit more
lenient. */
std::string result =
StringPrintf("Encountered %f samples in a c with expected "
"frequency 0. Rejecting the null hypothesis!",
frequencies[c.index]);
return std::make_pair(false, result);
}
} else if (expFrequencies[c.index] < minExpFrequency) {
/* Pool cells with low expected frequencies */
pooledFrequencies += frequencies[c.index];
pooledExpFrequencies += expFrequencies[c.index];
pooledCells++;
} else if (pooledExpFrequencies > 0 && pooledExpFrequencies < minExpFrequency) {
/* Keep on pooling cells until a sufficiently high
expected frequency is achieved. */
pooledFrequencies += frequencies[c.index];
pooledExpFrequencies += expFrequencies[c.index];
pooledCells++;
} else {
Float diff = frequencies[c.index] - expFrequencies[c.index];
chsq += (diff * diff) / expFrequencies[c.index];
++dof;
}
}
if (pooledExpFrequencies > 0 || pooledFrequencies > 0) {
Float diff = pooledFrequencies - pooledExpFrequencies;
chsq += (diff * diff) / pooledExpFrequencies;
++dof;
}
/* All parameters are assumed to be known, so there is no
additional DF reduction due to model parameters */
dof -= 1;
if (dof <= 0) {
std::string result =
StringPrintf("The number of degrees of freedom %d is too low!", dof);
return std::make_pair(false, result);
}
/* Probability of obtaining a test statistic at least
as extreme as the one observed under the assumption
that the distributions match */
Float pval = 1 - (Float)Chi2CDF(chsq, dof);
/* Apply the Sidak correction term, since we'll be conducting multiple
independent
hypothesis tests. This accounts for the fact that the probability of a
failure
increases quickly when several hypothesis tests are run in sequence. */
Float alpha = 1.0f - std::pow(1.0f - significanceLevel, 1.0f / numTests);
if (pval < alpha || !std::isfinite(pval)) {
std::string result = StringPrintf("Rejected the null hypothesis (p-value = %f, "
"significance level = %f",
pval, alpha);
return std::make_pair(false, result);
} else {
return std::make_pair(true, std::string(""));
}
}
void TestBSDF(std::function<BSDF*(const SurfaceInteraction&, Allocator)> createBSDF,
const char* description) {
const int thetaRes = CHI2_THETA_RES;
const int phiRes = CHI2_PHI_RES;
const int sampleCount = CHI2_SAMPLECOUNT;
Float* frequencies = new Float[thetaRes * phiRes];
Float* expFrequencies = new Float[thetaRes * phiRes];
RNG rng;
int index = 0;
std::cout.precision(3);
// Create BSDF, which requires creating a Shape, casting a Ray that
// hits the shape to get a SurfaceInteraction object.
BSDF* bsdf = nullptr;
auto t = std::make_shared<const Transform>(RotateX(-90));
auto tInv = std::make_shared<const Transform>(Inverse(*t));
{
bool reverseOrientation = false;
std::shared_ptr<Disk> disk = std::make_shared<Disk>(
t.get(), tInv.get(), reverseOrientation, 0., 1., 0, 360.);
Point3f origin(0.1, 1,
0); // offset slightly so we don't hit center of disk
Vector3f direction(0, -1, 0);
Ray r(origin, direction);
auto si = disk->Intersect(r);
ASSERT_TRUE(si.has_value());
bsdf = createBSDF(si->intr, Allocator());
}
for (int k = 0; k < CHI2_RUNS; ++k) {
/* Randomly pick an outgoing direction on the hemisphere */
Point2f sample{rng.Uniform<Float>(), rng.Uniform<Float>()};
Vector3f woL = SampleCosineHemisphere(sample);
Vector3f wo = bsdf->LocalToRender(woL);
FrequencyTable(bsdf, wo, rng, sampleCount, thetaRes, phiRes, frequencies);
IntegrateFrequencyTable(bsdf, wo, sampleCount, thetaRes, phiRes, expFrequencies);
std::string filename =
StringPrintf("/tmp/chi2test_%s_%03i.m", description, ++index);
DumpTables(frequencies, expFrequencies, thetaRes, phiRes, filename.c_str());
auto result = Chi2Test(frequencies, expFrequencies, thetaRes, phiRes, sampleCount,
CHI2_MINFREQ, CHI2_SLEVEL, CHI2_RUNS);
EXPECT_TRUE(result.first) << result.second << ", iteration " << k;
}
delete[] frequencies;
delete[] expFrequencies;
}
BSDF* createLambertian(const SurfaceInteraction& si, Allocator alloc) {
SampledSpectrum Kd(1.);
return alloc.new_object<BSDF>(
si.wo, si.n, si.shading.n, si.shading.dpdu,
alloc.new_object<DiffuseBxDF>(Kd, SampledSpectrum(0.), 0));
}
TEST(BSDFSampling, Lambertian) {
TestBSDF(createLambertian, "Lambertian");
}
#if 0
BSDF* createMicrofacet(const SurfaceInteraction& si, Allocator alloc, float roughx,
float roughy) {
Float alphax = TrowbridgeReitzDistribution::RoughnessToAlpha(roughx);
Float alphay = TrowbridgeReitzDistribution::RoughnessToAlpha(roughy);
TrowbridgeReitzDistribution distrib(alphax, alphay);
FresnelHandle fresnel = alloc.new_object<FresnelDielectric>(1.5, true);
return alloc.new_object<BSDF>(si.wo, si.n, si.shading.n, si.shading.dpdu,
alloc.new_object<MicrofacetReflectionBxDF>(distrib, fresnel));
// CO return alloc.new_object<BSDF>(si,
// alloc.new_object<DielectricInterface>(1.5, distrib,
// TransportMode::Radiance));
}
TEST(BSDFSampling, TR_VA_0p5) {
TestBSDF(
[](const SurfaceInteraction& si, Allocator alloc) -> BSDF* {
return createMicrofacet(si, alloc, 0.5, 0.5);
},
"Trowbridge-Reitz, visible area sample, alpha = 0.5");
}
TEST(BSDFSampling, TR_VA_0p3_0p15) {
TestBSDF(
[](const SurfaceInteraction& si, Allocator alloc) -> BSDF* {
return createMicrofacet(si, alloc, 0.3, 0.15);
},
"Trowbridge-Reitz, visible area sample, alpha = 0.3/0.15");
}
#endif
///////////////////////////////////////////////////////////////////////////
// Energy Conservation Tests
static void TestEnergyConservation(
std::function<BSDF*(const SurfaceInteraction&, Allocator)> createBSDF,
const char* description) {
RNG rng;
// Create BSDF, which requires creating a Shape, casting a Ray that
// hits the shape to get a SurfaceInteraction object.
auto t = std::make_shared<const Transform>(RotateX(-90));
auto tInv = std::make_shared<const Transform>(Inverse(*t));
bool reverseOrientation = false;
std::shared_ptr<Disk> disk =
std::make_shared<Disk>(t.get(), tInv.get(), reverseOrientation, 0., 1., 0, 360.);
Point3f origin(0.1, 1,
0); // offset slightly so we don't hit center of disk
Vector3f direction(0, -1, 0);
Ray r(origin, direction);
auto si = disk->Intersect(r);
ASSERT_TRUE(si.has_value());
BSDF* bsdf = createBSDF(si->intr, Allocator());
for (int i = 0; i < 10; ++i) {
Point2f uo{rng.Uniform<Float>(), rng.Uniform<Float>()};
Vector3f woL = SampleUniformHemisphere(uo);
Vector3f wo = bsdf->LocalToRender(woL);
const int nSamples = 16384;
SampledSpectrum Lo(0.f);
for (int j = 0; j < nSamples; ++j) {
Float u = rng.Uniform<Float>();
Point2f ui{rng.Uniform<Float>(), rng.Uniform<Float>()};
BSDFSample bs = bsdf->Sample_f(wo, u, ui);
if (bs)
Lo += bs.f * AbsDot(bs.wi, si->intr.n) / bs.pdf;
}
Lo /= nSamples;
EXPECT_LT(Lo.MaxComponentValue(), 1.01)
<< description << ": Lo = " << Lo << ", wo = " << wo;
}
}
TEST(BSDFEnergyConservation, LambertianReflection) {
TestEnergyConservation(
[](const SurfaceInteraction& si, Allocator alloc) -> BSDF* {
return alloc.new_object<BSDF>(
si.wo, si.n, si.shading.n, si.shading.dpdu,
alloc.new_object<DiffuseBxDF>(SampledSpectrum(1.f), SampledSpectrum(0.),
0));
},
"LambertianReflection");
}
TEST(BSDFEnergyConservation, OrenNayar) {
TestEnergyConservation(
[](const SurfaceInteraction& si, Allocator alloc) -> BSDF* {
return alloc.new_object<BSDF>(
si.wo, si.n, si.shading.n, si.shading.dpdu,
alloc.new_object<DiffuseBxDF>(SampledSpectrum(1.f), SampledSpectrum(0.),
20));
},
"Oren-Nayar sigma 20");
}
#if 0
TEST(BSDFEnergyConservation,
MicrofacetReflectionBxDFTrowbridgeReitz_alpha0_1_dielectric1_5) {
TestEnergyConservation(
[](const SurfaceInteraction& si, Allocator alloc) -> BSDF* {
FresnelHandle fresnel = alloc.new_object<FresnelDielectric>(1.f, 1.5f);
TrowbridgeReitzDistribution distrib(0.1, 0.1);
return alloc.new_object<BSDF>(si.wo, si.n, si.shading.n, si.shading.dpdu,
alloc.new_object<MicrofacetReflectionBxDF>(distrib, fresnel));
},
"MicrofacetReflectionBxDF, Fresnel dielectric, TrowbridgeReitz alpha "
"0.1");
}
TEST(BSDFEnergyConservation,
MicrofacetReflectionBxDFTrowbridgeReitz_alpha1_5_dielectric1_5) {
TestEnergyConservation(
[](const SurfaceInteraction& si, Allocator alloc) -> BSDF* {
FresnelHandle fresnel = alloc.new_object<FresnelDielectric>(1.f, 1.5f);
TrowbridgeReitzDistribution distrib(1.5, 1.5);
return alloc.new_object<BSDF>(si.wo, si.n, si.shading.n, si.shading.dpdu,
alloc.new_object<MicrofacetReflectionBxDF>(distrib, fresnel));
},
"MicrofacetReflectionBxDF, Fresnel dielectric, TrowbridgeReitz alpha "
"1.5");
}
TEST(BSDFEnergyConservation,
MicrofacetReflectionBxDFTrowbridgeReitz_alpha0_01_dielectric1_5) {
TestEnergyConservation(
[](const SurfaceInteraction& si, Allocator alloc) -> BSDF* {
FresnelHandle fresnel = alloc.new_object<FresnelDielectric>(1.f, 1.5f);
TrowbridgeReitzDistribution distrib(0.01, 0.01);
return alloc.new_object<BSDF>(si.wo, si.n, si.shading.n, si.shading.dpdu,
alloc.new_object<MicrofacetReflectionBxDF>(distrib, fresnel));
},
"MicrofacetReflectionBxDF, Fresnel dielectric, TrowbridgeReitz alpha "
"0.01");
}
TEST(BSDFEnergyConservation, MicrofacetReflectionBxDFTrowbridgeReitz_alpha0_1_conductor) {
TestEnergyConservation(
[](const SurfaceInteraction& si, Allocator alloc) -> BSDF* {
SampledWavelengths lambda = SampledWavelengths::SampleUniform(0.5);
SampledSpectrum etaT = GetNamedSpectrum("metal-Al-eta").Sample(lambda);
SampledSpectrum K = GetNamedSpectrum("metal-Al-k").Sample(lambda);
FresnelHandle fresnel = alloc.new_object<FresnelConductor>(etaT, K);
TrowbridgeReitzDistribution distrib(0.1, 0.1);
return alloc.new_object<BSDF>(si.wo, si.n, si.shading.n, si.shading.dpdu,
alloc.new_object<MicrofacetReflectionBxDF>(distrib, fresnel));
},
"MicrofacetReflectionBxDF, Fresnel conductor, TrowbridgeReitz alpha "
"0.1");
}
TEST(BSDFEnergyConservation, MicrofacetReflectionBxDFTrowbridgeReitz_alpha1_5_conductor) {
TestEnergyConservation(
[](const SurfaceInteraction& si, Allocator alloc) -> BSDF* {
SampledWavelengths lambda = SampledWavelengths::SampleUniform(0.5);
SampledSpectrum etaT = GetNamedSpectrum("metal-Al-eta").Sample(lambda);
SampledSpectrum K = GetNamedSpectrum("metal-Al-k").Sample(lambda);
FresnelHandle fresnel = alloc.new_object<FresnelConductor>(etaT, K);
TrowbridgeReitzDistribution distrib(1.5, 1.5);
return alloc.new_object<BSDF>(si.wo, si.n, si.shading.n, si.shading.dpdu,
alloc.new_object<MicrofacetReflectionBxDF>(distrib, fresnel));
},
"MicrofacetReflectionBxDF, Fresnel conductor, TrowbridgeReitz alpha "
"1.5");
}
TEST(BSDFEnergyConservation,
MicrofacetReflectionBxDFTrowbridgeReitz_alpha0_01_conductor) {
TestEnergyConservation(
[](const SurfaceInteraction& si, Allocator alloc) -> BSDF* {
SampledWavelengths lambda = SampledWavelengths::SampleUniform(0.5);
SampledSpectrum etaT = GetNamedSpectrum("metal-Al-eta").Sample(lambda);
SampledSpectrum K = GetNamedSpectrum("metal-Al-k").Sample(lambda);
FresnelHandle fresnel = alloc.new_object<FresnelConductor>(etaT, K);
TrowbridgeReitzDistribution distrib(0.01, 0.01);
return alloc.new_object<BSDF>(si.wo, si.n, si.shading.n, si.shading.dpdu,
alloc.new_object<MicrofacetReflectionBxDF>(distrib, fresnel));
},
"MicrofacetReflectionBxDF, Fresnel conductor, TrowbridgeReitz alpha "
"0.01");
}
#endif
// Hair Tests
#if 0
TEST(Hair, Reciprocity) {
RNG rng;
for (int i = 0; i < 10; ++i) {
Hair h(-1 + 2 * rng.Uniform<Float>(), 1.55,
HairBSDF::SigmaAFromConcentration(.3 + 7.7 * rng.Uniform<Float>()),
.1 + .9 * rng.Uniform<Float>(),
.1 + .9 * rng.Uniform<Float>());
Vector3f wi = SampleUniformSphere({rng.Uniform<Float>(), rng.Uniform<Float>()});
Vector3f wo = SampleUniformSphere({rng.Uniform<Float>(), rng.Uniform<Float>()});
Spectrum a = h.f(wi, wo) * AbsCosTheta(wo);
Spectrum b = h.f(wo, wi) * AbsCosTheta(wi);
EXPECT_EQ(a.y(), b.y()) << h << ", a = " << a << ", b = " << b << ", wi = " << wi
<< ", wo = " << wo;
}
}
#endif
TEST(Hair, WhiteFurnace) {
RNG rng;
Vector3f wo = SampleUniformSphere({rng.Uniform<Float>(), rng.Uniform<Float>()});
for (Float beta_m = .1; beta_m < 1; beta_m += .2) {
for (Float beta_n = .1; beta_n < 1; beta_n += .2) {
// Estimate reflected uniform incident radiance from hair
Float ySum = 0;
// More samples for the smooth case, since we're sampling blindly.
int count = (beta_m < .5 || beta_n < .5) ? 100000 : 20000;
for (int i = 0; i < count; ++i) {
SampledWavelengths lambda =
SampledWavelengths::SampleXYZ(RadicalInverse(0, i));
Float h = Clamp(-1 + 2. * RadicalInverse(1, i), -.999999, .999999);
SampledSpectrum sigma_a(0.f);
HairBxDF hair(h, 1.55, sigma_a, beta_m, beta_n, 0.f);
Vector3f wi =
SampleUniformSphere({RadicalInverse(2, i), RadicalInverse(3, i)});
SampledSpectrum f =
hair.f(wo, wi, TransportMode::Radiance) * AbsCosTheta(wi);
ySum += f.y(lambda);
}
Float avg = ySum / (count * UniformSpherePDF());
EXPECT_TRUE(avg >= .95 && avg <= 1.05) << avg;
}
}
}
TEST(Hair, HOnTheEdge) {
Vector3f wo(0.54986966, 0.03359017, 0.83457476),
wi(-0.37383357, -0.91920084, 0.12376696);
Float h = -1, beta_m = .1, beta_n = .1;
SampledSpectrum sigma_a(0.f);
HairBxDF hair(h, 1.55, sigma_a, beta_m, beta_n, 0.f);
SampledSpectrum f = hair.f(wo, wi, TransportMode::Radiance);
}
TEST(Hair, WhiteFurnaceSampled) {
RNG rng;
SampledWavelengths lambda = SampledWavelengths::SampleXYZ(0.5);
Vector3f wo = SampleUniformSphere({rng.Uniform<Float>(), rng.Uniform<Float>()});
for (Float beta_m = .1; beta_m < 1; beta_m += .2) {
for (Float beta_n = .1; beta_n < 1; beta_n += .2) {
Float ySum = 0;
int count = 10000;
for (int i = 0; i < count; ++i) {
SampledWavelengths lambda =
SampledWavelengths::SampleXYZ(RadicalInverse(0, i));
Float h = Clamp(-1 + 2. * RadicalInverse(1, i), -.999999, .999999);
SampledSpectrum sigma_a(0.f);
HairBxDF hair(h, 1.55, sigma_a, beta_m, beta_n, 0.f);
Float uc = RadicalInverse(2, i);
Point2f u(RadicalInverse(3, i), RadicalInverse(4, i));
BSDFSample bs = hair.Sample_f(wo, uc, u, TransportMode::Radiance,
BxDFReflTransFlags::All);
if (bs) {
SampledSpectrum f = bs.f * AbsCosTheta(bs.wi) / bs.pdf;
ySum += f.y(lambda);
}
}
Float avg = ySum / count;
EXPECT_TRUE(avg >= .99 && avg <= 1.01) << avg;
}
}
}
TEST(Hair, SamplingWeights) {
RNG rng;
SampledWavelengths lambda = SampledWavelengths::SampleXYZ(0.5);
for (Float beta_m = .1; beta_m < 1; beta_m += .2)
for (Float beta_n = .4; beta_n < 1; beta_n += .2) {
int count = 10000;
for (int i = 0; i < count; ++i) {
Float h = Clamp(-1 + 2. * RadicalInverse(0, i), -.999999, .999999);
// Check _HairBxDF::Sample\_f()_ sample weight
SampledSpectrum sigma_a(0.);
HairBxDF hair(h, 1.55, sigma_a, beta_m, beta_n, 0.f);
Vector3f wo =
SampleUniformSphere({RadicalInverse(1, i), RadicalInverse(2, i)});
Float uc = RadicalInverse(3, i);
Point2f u = {RadicalInverse(4, i), RadicalInverse(5, i)};
BSDFSample bs = hair.Sample_f(wo, uc, u, TransportMode::Radiance,
BxDFReflTransFlags::All);
if (bs) {
Float sum = 0;
int ny = 20;
for (Float u : Stratified1D(ny)) {
SampledWavelengths lambda = SampledWavelengths::SampleXYZ(u);
sum += bs.f.y(lambda) * AbsCosTheta(bs.wi) / bs.pdf;
}
// Verify that hair BSDF sample weight is close to 1 for
// _wi_
Float avg = sum / ny;
EXPECT_GT(avg, 0.99);
EXPECT_LT(avg, 1.01);
}
}
}
}
TEST(Hair, SamplingConsistency) {
RNG rng;
SampledWavelengths lambda = SampledWavelengths::SampleXYZ(0.5);
for (Float beta_m = .2; beta_m < 1; beta_m += .2)
for (Float beta_n = .4; beta_n < 1; beta_n += .2) {
// Declare variables for hair sampling test
const int count = 64 * 1024;
SampledSpectrum sigma_a(.25);
Vector3f wo =
SampleUniformSphere({rng.Uniform<Float>(), rng.Uniform<Float>()});
auto Li = [](const Vector3f& w) { return SampledSpectrum(w.z * w.z); };
SampledSpectrum fImportance(0.), fUniform(0.);
for (int i = 0; i < count; ++i) {
// Compute estimates of scattered radiance for hair sampling
// test
Float h = -1 + 2 * rng.Uniform<Float>();
HairBxDF hair(h, 1.55, sigma_a, beta_m, beta_n, 0.f);
Vector3f wi;
Float uc = rng.Uniform<Float>();
Point2f u = {rng.Uniform<Float>(), rng.Uniform<Float>()};
BSDFSample bs = hair.Sample_f(wo, uc, u, TransportMode::Radiance,
BxDFReflTransFlags::All);
if (bs)
fImportance +=
bs.f * Li(bs.wi) * AbsCosTheta(bs.wi) / (count * bs.pdf);
wi = SampleUniformSphere(u);
fUniform += hair.f(wo, wi, TransportMode::Radiance) * Li(wi) *
AbsCosTheta(wi) / (count * UniformSpherePDF());
}
// Verify consistency of estimated hair reflected radiance values
Float err =
std::abs(fImportance.y(lambda) - fUniform.y(lambda)) / fUniform.y(lambda);
EXPECT_LT(err, 0.05);
}
}

144
src/pbrt/bssrdf.cpp Normal file
View file

@ -0,0 +1,144 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#include <pbrt/bssrdf.h>
#include <pbrt/media.h>
#include <pbrt/shapes.h>
#include <pbrt/util/math.h>
#include <pbrt/util/memory.h>
#include <pbrt/util/parallel.h>
#include <pbrt/util/print.h>
#include <pbrt/util/sampling.h>
#include <cmath>
namespace pbrt {
std::string TabulatedBSSRDF::ToString() const {
return StringPrintf("[ TabulatedBSSRDF po: %s eta: %f ns: %s ss: %s ts: %s "
"sigma_t: %s rho: %s table: %s ]",
po, eta, ns, ss, ts, sigma_t, rho, *table);
}
// BSSRDF Function Definitions
Float BeamDiffusionMS(Float sigma_s, Float sigma_a, Float g, Float eta, Float r) {
const int nSamples = 100;
Float Ed = 0;
// Precompute information for dipole integrand
// Compute reduced scattering coefficients $\sigmaps, \sigmapt$ and albedo $\rhop$
Float sigmap_s = sigma_s * (1 - g);
Float sigmap_t = sigma_a + sigmap_s;
Float rhop = sigmap_s / sigmap_t;
// Compute non-classical diffusion coefficient $D_\roman{G}$ using Equation
// $(\ref{eq:diffusion-coefficient-grosjean})$
Float D_g = (2 * sigma_a + sigmap_s) / (3 * sigmap_t * sigmap_t);
// Compute effective transport coefficient $\sigmatr$ based on $D_\roman{G}$
Float sigma_tr = SafeSqrt(sigma_a / D_g);
// Determine linear extrapolation distance $\depthextrapolation$ using Equation
// $(\ref{eq:dipole-boundary-condition})$
Float fm1 = FresnelMoment1(eta), fm2 = FresnelMoment2(eta);
Float ze = -2 * D_g * (1 + 3 * fm2) / (1 - 2 * fm1);
// Determine exitance scale factors using Equations $(\ref{eq:kp-exitance-phi})$ and
// $(\ref{eq:kp-exitance-e})$
Float cPhi = .25f * (1 - 2 * fm1), cE = .5f * (1 - 3 * fm2);
for (int i = 0; i < nSamples; ++i) {
// Sample real point source depth $\depthreal$
Float zr = -std::log(1 - (i + .5f) / nSamples) / sigmap_t;
// Evaluate dipole integrand $E_{\roman{d}}$ at $\depthreal$ and add to _Ed_
Float zv = -zr + 2 * ze;
Float dr = std::sqrt(r * r + zr * zr), dv = std::sqrt(r * r + zv * zv);
// Compute dipole fluence rate $\dipole(r)$ using Equation
// $(\ref{eq:diffusion-dipole})$
Float phiD = Inv4Pi / D_g *
(std::exp(-sigma_tr * dr) / dr - std::exp(-sigma_tr * dv) / dv);
// Compute dipole vector irradiance $-\N{}\cdot\dipoleE(r)$ using Equation
// $(\ref{eq:diffusion-dipole-vector-irradiance-normal})$
Float EDn =
Inv4Pi *
(zr * (1 + sigma_tr * dr) * std::exp(-sigma_tr * dr) / (dr * dr * dr) -
zv * (1 + sigma_tr * dv) * std::exp(-sigma_tr * dv) / (dv * dv * dv));
// Add contribution from dipole for depth $\depthreal$ to _Ed_
Float E = phiD * cPhi + EDn * cE;
Float kappa = 1 - std::exp(-2 * sigmap_t * (dr + zr));
Ed += kappa * rhop * rhop * E;
}
return Ed / nSamples;
}
Float BeamDiffusionSS(Float sigma_s, Float sigma_a, Float g, Float eta, Float r) {
// Compute material parameters and minimum $t$ below the critical angle
Float sigma_t = sigma_a + sigma_s, rho = sigma_s / sigma_t;
Float tCrit = r * SafeSqrt(eta * eta - 1);
Float Ess = 0;
const int nSamples = 100;
for (int i = 0; i < nSamples; ++i) {
// Evaluate single-scattering integrand and add to _Ess_
Float ti = tCrit - std::log(1 - (i + .5f) / nSamples) / sigma_t;
// Determine length $d$ of connecting segment and $\cos\theta_\roman{o}$
Float d = std::sqrt(r * r + ti * ti);
Float cosTheta_o = ti / d;
// Add contribution of single scattering at depth $t$
Ess += rho * std::exp(-sigma_t * (d + tCrit)) / (d * d) *
HenyeyGreenstein(cosTheta_o, g) * (1 - FrDielectric(-cosTheta_o, eta)) *
std::abs(cosTheta_o);
}
return Ess / nSamples;
}
void ComputeBeamDiffusionBSSRDF(Float g, Float eta, BSSRDFTable *t) {
// Choose radius values of the diffusion profile discretization
t->radiusSamples[0] = 0;
t->radiusSamples[1] = 2.5e-3f;
for (int i = 2; i < t->radiusSamples.size(); ++i)
t->radiusSamples[i] = t->radiusSamples[i - 1] * 1.2f;
// Choose albedo values of the diffusion profile discretization
for (int i = 0; i < t->rhoSamples.size(); ++i)
t->rhoSamples[i] = (1 - std::exp(-8 * i / (Float)(t->rhoSamples.size() - 1))) /
(1 - std::exp(-8));
ParallelFor(0, t->rhoSamples.size(), [&](int i) {
// Compute the diffusion profile for the _i_th albedo sample
// Compute scattering profile for chosen albedo $\rho$
size_t nSamples = t->radiusSamples.size();
for (int j = 0; j < nSamples; ++j) {
Float rho = t->rhoSamples[i], r = t->radiusSamples[j];
t->profile[i * nSamples + j] = 2 * Pi * r *
(BeamDiffusionSS(rho, 1 - rho, g, eta, r) +
BeamDiffusionMS(rho, 1 - rho, g, eta, r));
}
// Compute effective albedo $\rho_{\roman{eff}}$ and CDF for importance sampling
t->rhoEff[i] =
IntegrateCatmullRom(t->radiusSamples, {&t->profile[i * nSamples], nSamples},
{&t->profileCDF[i * nSamples], nSamples});
});
}
// BSSRDFTable Method Definitions
BSSRDFTable::BSSRDFTable(int nRhoSamples, int nRadiusSamples, Allocator alloc)
: rhoSamples(nRhoSamples, alloc),
radiusSamples(nRadiusSamples, alloc),
profile(nRadiusSamples * nRhoSamples, alloc),
rhoEff(nRhoSamples, alloc),
profileCDF(nRadiusSamples * nRhoSamples, alloc) {}
std::string BSSRDFTable::ToString() const {
return StringPrintf("[ BSSRDFTable rhoSamples: %s radiusSamples: %s profile: %s "
"rhoEff: %s profileCDF: %s ]",
rhoSamples, radiusSamples, profile, rhoEff, profileCDF);
}
} // namespace pbrt

357
src/pbrt/bssrdf.h Normal file
View file

@ -0,0 +1,357 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_BSSRDF_H
#define PBRT_BSSRDF_H
#include <pbrt/pbrt.h>
#include <pbrt/base/bssrdf.h>
#include <pbrt/bsdf.h>
#include <pbrt/interaction.h>
#include <pbrt/util/check.h>
#include <pbrt/util/pstd.h>
#include <pbrt/util/scattering.h>
#include <pbrt/util/spectrum.h>
#include <pbrt/util/taggedptr.h>
#include <pbrt/util/vecmath.h>
#include <string>
namespace pbrt {
// BSSRDFSample Definition
struct BSSRDFSample {
SampledSpectrum S;
Float pdf;
BSDF bsdf;
Vector3f wo;
};
// SubsurfaceInteraction Definition
struct SubsurfaceInteraction {
SubsurfaceInteraction() = default;
PBRT_CPU_GPU
SubsurfaceInteraction(const SurfaceInteraction &si)
: pi(si.pi),
n(si.n),
dpdu(si.dpdu),
dpdv(si.dpdv),
ns(si.shading.n),
dpdus(si.shading.dpdu),
dpdvs(si.shading.dpdv) {}
PBRT_CPU_GPU
operator SurfaceInteraction() const {
SurfaceInteraction si;
si.pi = pi;
si.n = n;
si.dpdu = dpdu;
si.dpdv = dpdv;
si.shading.n = ns;
si.shading.dpdu = dpdus;
si.shading.dpdv = dpdvs;
return si;
}
PBRT_CPU_GPU
Point3f p() const { return Point3f(pi); }
Point3fi pi;
Normal3f n;
Vector3f dpdu, dpdv;
Normal3f ns;
Vector3f dpdus, dpdvs;
};
// BSSRDF Function Declarations
Float BeamDiffusionSS(Float sigma_s, Float sigma_a, Float g, Float eta, Float r);
Float BeamDiffusionMS(Float sigma_s, Float sigma_a, Float g, Float eta, Float r);
void ComputeBeamDiffusionBSSRDF(Float g, Float eta, BSSRDFTable *t);
// BSSRDFTable Definition
struct BSSRDFTable {
// BSSRDFTable Public Members
pstd::vector<Float> rhoSamples, radiusSamples;
pstd::vector<Float> profile;
pstd::vector<Float> rhoEff;
pstd::vector<Float> profileCDF;
// BSSRDFTable Public Methods
BSSRDFTable(int nRhoSamples, int nRadiusSamples, Allocator alloc);
std::string ToString() const;
PBRT_CPU_GPU
Float EvalProfile(int rhoIndex, int radiusIndex) const {
CHECK(rhoIndex >= 0 && rhoIndex < rhoSamples.size());
CHECK(radiusIndex >= 0 && radiusIndex < radiusSamples.size());
return profile[rhoIndex * radiusSamples.size() + radiusIndex];
}
};
// BSSRDFProbeSegment Definition
struct BSSRDFProbeSegment {
BSSRDFProbeSegment() = default;
PBRT_CPU_GPU
BSSRDFProbeSegment(const Point3f &p0, const Point3f &p1, Float time)
: p0(p0), p1(p1), time(time), valid(true) {}
PBRT_CPU_GPU operator bool() const { return valid; }
Point3f p0, p1;
Float time;
bool valid = false;
};
// TabulatedBSSRDF Definition
class TabulatedBSSRDF {
public:
using BxDF = BSSRDFAdapter;
// TabulatedBSSRDF Public Methods
TabulatedBSSRDF() = default;
PBRT_CPU_GPU
TabulatedBSSRDF(const Point3f &po, const Vector3f &dpdu, const Normal3f &ns,
const Vector3f &wo, Float time, Float eta,
const SampledSpectrum &sigma_a, const SampledSpectrum &sigma_s,
const BSSRDFTable *table)
: po(po),
wo(wo),
eta(eta),
ns(ns),
ss(Normalize(dpdu)),
ts(Cross(ns, ss)),
table(table) {
sigma_t = sigma_a + sigma_s;
rho = SafeDiv(sigma_s, sigma_t);
}
PBRT_CPU_GPU
SampledSpectrum S(const Point3f &p, const Vector3f &wi) {
Float Ft = FrDielectric(CosTheta(wo), eta);
return (1 - Ft) * Sp(p) * Sw(wi);
}
PBRT_CPU_GPU
SampledSpectrum Sp(const Point3f &pi) const { return Sr(Distance(po, pi)); }
PBRT_CPU_GPU
SampledSpectrum Sr(Float r) const {
SampledSpectrum Sr(0.f);
for (int ch = 0; ch < NSpectrumSamples; ++ch) {
// Convert $r$ into unitless optical radius $r_{\roman{optical}}$
Float rOptical = r * sigma_t[ch];
// Compute spline weights to interpolate BSSRDF on channel _ch_
int rhoOffset, radiusOffset;
Float rhoWeights[4], radiusWeights[4];
if (!CatmullRomWeights(table->rhoSamples, rho[ch], &rhoOffset, rhoWeights) ||
!CatmullRomWeights(table->radiusSamples, rOptical, &radiusOffset,
radiusWeights))
continue;
// Set BSSRDF value _Sr[ch]_ using tensor spline interpolation
Float sr = 0;
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 4; ++j) {
// Accumulate contribution of $(i,j)$ table sample
Float weight = rhoWeights[i] * radiusWeights[j];
if (weight != 0)
sr +=
weight * table->EvalProfile(rhoOffset + i, radiusOffset + j);
}
}
// Cancel marginal PDF factor from tabulated BSSRDF profile
if (rOptical != 0)
sr /= 2 * Pi * rOptical;
Sr[ch] = sr;
}
// Transform BSSRDF value into world space units
Sr *= sigma_t * sigma_t;
return ClampZero(Sr);
}
PBRT_CPU_GPU
SampledSpectrum Sw(const Vector3f &w) const {
Float c = 1 - 2 * FresnelMoment1(1 / eta);
return SampledSpectrum((1 - FrDielectric(CosTheta(w), eta)) / (c * Pi));
}
std::string ToString() const;
PBRT_CPU_GPU
BSSRDFProbeSegment Sample(Float u1, const Point2f &u2) const {
// Choose projection axis for BSSRDF sampling
Vector3f vx, vy, vz;
switch (SampleDiscrete({0.5, .25, .25}, u1, nullptr, &u1)) {
case 0:
vx = ss;
vy = ts;
vz = Vector3f(ns);
break;
case 1:
// Prepare for sampling rays with respect to _ss_
vx = ts;
vy = Vector3f(ns);
vz = ss;
break;
case 2:
// Prepare for sampling rays with respect to _ts_
vx = Vector3f(ns);
vy = ss;
vz = ts;
break;
default:
LOG_FATAL("Unexpected value returned from SampleDiscrete");
}
// Choose spectral channel for BSSRDF sampling
int ch = std::min<int>(u1 * NSpectrumSamples, NSpectrumSamples - 1);
u1 = std::min(u1 * NSpectrumSamples - ch, OneMinusEpsilon);
// Sample BSSRDF profile in polar coordinates
Float r = Sample_Sr(ch, u2[0]);
if (r < 0)
return {};
Float phi = 2 * Pi * u2[1];
// Compute BSSRDF profile bounds and intersection height
Float rMax = Sample_Sr(ch, 0.999f);
if (r >= rMax)
return {};
Float l = 2 * std::sqrt(rMax * rMax - r * r);
// Return BSSRDF sampling ray segment
Point3f pStart =
po + r * (vx * std::cos(phi) + vy * std::sin(phi)) - l * vz * 0.5f;
Point3f pTarget = pStart + l * vz;
return BSSRDFProbeSegment{pStart, pTarget, time};
}
PBRT_CPU_GPU
Float Sample_Sr(int ch, Float u) const {
if (sigma_t[ch] == 0)
return -1;
return SampleCatmullRom2D(table->rhoSamples, table->radiusSamples, table->profile,
table->profileCDF, rho[ch], u) /
sigma_t[ch];
}
PBRT_CPU_GPU
Float PDF_Sr(int ch, Float r) const {
// Convert $r$ into unitless optical radius $r_{\roman{optical}}$
Float rOptical = r * sigma_t[ch];
// Compute spline weights to interpolate BSSRDF density on channel _ch_
int rhoOffset, radiusOffset;
Float rhoWeights[4], radiusWeights[4];
if (!CatmullRomWeights(table->rhoSamples, rho[ch], &rhoOffset, rhoWeights) ||
!CatmullRomWeights(table->radiusSamples, rOptical, &radiusOffset,
radiusWeights))
return 0.f;
// Return BSSRDF profile density for channel _ch_
Float sr = 0, rhoEff = 0;
for (int i = 0; i < 4; ++i) {
if (rhoWeights[i] == 0)
continue;
rhoEff += table->rhoEff[rhoOffset + i] * rhoWeights[i];
for (int j = 0; j < 4; ++j) {
if (radiusWeights[j] == 0)
continue;
sr += table->EvalProfile(rhoOffset + i, radiusOffset + j) *
rhoWeights[i] * radiusWeights[j];
}
}
// Cancel marginal PDF factor from tabulated BSSRDF profile
if (rOptical != 0)
sr /= 2 * Pi * rOptical;
return std::max<Float>(0, sr * sigma_t[ch] * sigma_t[ch] / rhoEff);
}
PBRT_CPU_GPU
Float PDF_Sp(const Point3f &pi, const Normal3f &ni) const {
// Express $\pti-\pto$ and $\bold{n}_i$ with respect to local coordinates at
// $\pto$
Vector3f d = pi - po;
Vector3f dLocal(Dot(ss, d), Dot(ts, d), Dot(ns, d));
Normal3f nLocal(Dot(ss, ni), Dot(ts, ni), Dot(ns, ni));
// Compute BSSRDF profile radius under projection along each axis
Float rProj[3] = {std::sqrt(dLocal.y * dLocal.y + dLocal.z * dLocal.z),
std::sqrt(dLocal.z * dLocal.z + dLocal.x * dLocal.x),
std::sqrt(dLocal.x * dLocal.x + dLocal.y * dLocal.y)};
// Return combined probability from all BSSRDF sampling strategies
Float pdf = 0, axisProb[3] = {.25f, .25f, .5f};
Float chProb = 1 / (Float)NSpectrumSamples;
for (int axis = 0; axis < 3; ++axis)
for (int ch = 0; ch < NSpectrumSamples; ++ch)
pdf += PDF_Sr(ch, rProj[axis]) * std::abs(nLocal[axis]) * chProb *
axisProb[axis];
return pdf;
}
PBRT_CPU_GPU
BSSRDFSample ProbeIntersectionToSample(const SubsurfaceInteraction &si,
BSSRDFAdapter *bxdf) const {
*bxdf = BSSRDFAdapter(eta);
Vector3f wo = Vector3f(si.ns);
BSDF bsdf(wo, si.n, si.ns, si.dpdus, bxdf, eta);
return BSSRDFSample{Sp(si.p()), PDF_Sp(si.p(), si.n), bsdf, wo};
}
private:
friend class SOA<TabulatedBSSRDF>;
// TabulatedBSSRDF Private Data
Point3f po;
Vector3f wo;
Float time;
Normal3f ns;
Vector3f ss, ts;
Float eta;
const BSSRDFTable *table;
SampledSpectrum sigma_t, rho;
};
// BSSRDF Inline Functions
PBRT_CPU_GPU
inline void SubsurfaceFromDiffuse(const BSSRDFTable &t, const SampledSpectrum &rhoEff,
const SampledSpectrum &mfp, SampledSpectrum *sigma_a,
SampledSpectrum *sigma_s) {
for (int c = 0; c < NSpectrumSamples; ++c) {
Float rho = InvertCatmullRom(t.rhoSamples, t.rhoEff, rhoEff[c]);
(*sigma_s)[c] = rho / mfp[c];
(*sigma_a)[c] = (1 - rho) / mfp[c];
}
}
inline SampledSpectrum BSSRDFHandle::S(const Point3f &p, const Vector3f &wi) {
auto s = [&](auto ptr) { return ptr->S(p, wi); };
return Dispatch(s);
}
inline BSSRDFProbeSegment BSSRDFHandle::Sample(Float u1, const Point2f &u2) const {
auto sample = [&](auto ptr) { return ptr->Sample(u1, u2); };
return Dispatch(sample);
}
inline BSSRDFSample BSSRDFHandle::ProbeIntersectionToSample(
const SubsurfaceInteraction &si, ScratchBuffer &scratchBuffer) const {
auto pits = [&](auto ptr) {
using BxDF = typename std::remove_reference<decltype(*ptr)>::type::BxDF;
BxDF *bxdf = (BxDF *)scratchBuffer.Alloc(sizeof(BxDF), alignof(BxDF));
return ptr->ProbeIntersectionToSample(si, bxdf);
};
return Dispatch(pits);
}
} // namespace pbrt
#endif // PBRT_BSSRDF_H

997
src/pbrt/bxdfs.cpp Normal file
View file

@ -0,0 +1,997 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#include <pbrt/bxdfs.h>
#include <pbrt/bssrdf.h>
#include <pbrt/interaction.h>
#include <pbrt/media.h>
#include <pbrt/options.h>
#include <pbrt/util/check.h>
#include <pbrt/util/color.h>
#include <pbrt/util/colorspace.h>
#include <pbrt/util/error.h>
#include <pbrt/util/float.h>
#include <pbrt/util/hash.h>
#include <pbrt/util/log.h>
#include <pbrt/util/math.h>
#include <pbrt/util/memory.h>
#include <pbrt/util/print.h>
#include <pbrt/util/sampling.h>
#include <pbrt/util/stats.h>
#include <unordered_map>
namespace pbrt {
std::string ToString(BxDFReflTransFlags flags) {
if (flags == BxDFReflTransFlags::Unset)
return "Unset";
std::string s;
if (flags & BxDFReflTransFlags::Reflection)
s += "Reflection,";
if (flags & BxDFReflTransFlags::Transmission)
s += "Transmission,";
return s;
}
std::string ToString(BxDFFlags flags) {
if (flags == BxDFFlags::Unset)
return "Unset";
std::string s;
if (flags & BxDFFlags::Reflection)
s += "Reflection,";
if (flags & BxDFFlags::Transmission)
s += "Transmission,";
if (flags & BxDFFlags::Diffuse)
s += "Diffuse,";
if (flags & BxDFFlags::Glossy)
s += "Glossy,";
if (flags & BxDFFlags::Specular)
s += "Specular,";
return s;
}
std::string ToString(TransportMode mode) {
return mode == TransportMode::Radiance ? "Radiance" : "Importance";
}
// BxDF Method Definitions
std::string IdealDiffuseBxDF::ToString() const {
return StringPrintf("[ IdealDiffuseBxDF R: %s ]", R);
}
std::string DiffuseBxDF::ToString() const {
return StringPrintf("[ DiffuseBxDF R: %s T: %s A: %f B: %f ]", R, T, A, B);
}
template <typename TopBxDF, typename BottomBxDF, bool SupportAttenuation>
std::string LayeredBxDF<TopBxDF, BottomBxDF, SupportAttenuation>::ToString() const {
return StringPrintf(
"[ LayeredBxDF top: %s bottom: %s thickness: %f albedo: %s g: %f ]", top, bottom,
thickness, albedo, g);
}
std::string DielectricInterfaceBxDF::ToString() const {
return StringPrintf("[ DielectricInterfaceBxDF eta: %f mfDistrib: %s ]", eta,
mfDistrib.ToString());
}
std::string ThinDielectricBxDF::ToString() const {
return StringPrintf("[ ThinDielectricBxDF eta: %f ]", eta);
}
std::string ConductorBxDF::ToString() const {
return StringPrintf("[ ConductorBxDF mfDistrib: %s eta: %s k: %s ]", mfDistrib, eta,
k);
}
// HairBxDF Method Definitions
HairBxDF::HairBxDF(Float h, Float eta, const SampledSpectrum &sigma_a, Float beta_m,
Float beta_n, Float alpha)
: h(h),
gamma_o(SafeASin(h)),
eta(eta),
sigma_a(sigma_a),
beta_m(beta_m),
beta_n(beta_n) {
CHECK(h >= -1 && h <= 1);
CHECK(beta_m >= 0 && beta_m <= 1);
CHECK(beta_n >= 0 && beta_n <= 1);
// Compute longitudinal variance from $\beta_m$
static_assert(pMax >= 3,
"Longitudinal variance code must be updated to handle low pMax");
v[0] = Sqr(0.726f * beta_m + 0.812f * Sqr(beta_m) + 3.7f * Pow<20>(beta_m));
v[1] = .25 * v[0];
v[2] = 4 * v[0];
for (int p = 3; p <= pMax; ++p)
// TODO: is there anything better here?
v[p] = v[2];
// Compute azimuthal logistic scale factor from $\beta_n$
static const Float SqrtPiOver8 = 0.626657069f;
s = SqrtPiOver8 * (0.265f * beta_n + 1.194f * Sqr(beta_n) + 5.372f * Pow<22>(beta_n));
CHECK(!std::isnan(s));
// Compute $\alpha$ terms for hair scales
sin2kAlpha[0] = std::sin(Radians(alpha));
cos2kAlpha[0] = SafeSqrt(1 - Sqr(sin2kAlpha[0]));
for (int i = 1; i < 3; ++i) {
sin2kAlpha[i] = 2 * cos2kAlpha[i - 1] * sin2kAlpha[i - 1];
cos2kAlpha[i] = Sqr(cos2kAlpha[i - 1]) - Sqr(sin2kAlpha[i - 1]);
}
}
SampledSpectrum HairBxDF::f(Vector3f wo, Vector3f wi, TransportMode mode) const {
// Compute hair coordinate system terms related to _wo_
Float sinTheta_o = wo.x;
Float cosTheta_o = SafeSqrt(1 - Sqr(sinTheta_o));
Float phi_o = std::atan2(wo.z, wo.y);
// Compute hair coordinate system terms related to _wi_
Float sinTheta_i = wi.x;
Float cosTheta_i = SafeSqrt(1 - Sqr(sinTheta_i));
Float phi_i = std::atan2(wi.z, wi.y);
// Compute $\cos \thetat$ for refracted ray
Float sinTheta_t = sinTheta_o / eta;
Float cosTheta_t = SafeSqrt(1 - Sqr(sinTheta_t));
// Compute $\gammat$ for refracted ray
Float etap = SafeSqrt(eta * eta - Sqr(sinTheta_o)) / cosTheta_o;
Float sinGamma_t = h / etap;
Float cosGamma_t = SafeSqrt(1 - Sqr(sinGamma_t));
Float gamma_t = SafeASin(sinGamma_t);
// Compute the transmittance _T_ of a single path through the cylinder
SampledSpectrum T = Exp(-sigma_a * (2 * cosGamma_t / cosTheta_t));
// Evaluate hair BSDF
Float phi = phi_i - phi_o;
pstd::array<SampledSpectrum, pMax + 1> ap = Ap(cosTheta_o, eta, h, T);
SampledSpectrum fsum(0.);
for (int p = 0; p < pMax; ++p) {
// Compute $\sin \thetai$ and $\cos \thetai$ terms accounting for scales
Float sinThetap_o, cosThetap_o;
if (p == 0) {
sinThetap_o = sinTheta_o * cos2kAlpha[1] - cosTheta_o * sin2kAlpha[1];
cosThetap_o = cosTheta_o * cos2kAlpha[1] + sinTheta_o * sin2kAlpha[1];
}
// Handle remainder of $p$ values for hair scale tilt
else if (p == 1) {
sinThetap_o = sinTheta_o * cos2kAlpha[0] + cosTheta_o * sin2kAlpha[0];
cosThetap_o = cosTheta_o * cos2kAlpha[0] - sinTheta_o * sin2kAlpha[0];
} else if (p == 2) {
sinThetap_o = sinTheta_o * cos2kAlpha[2] + cosTheta_o * sin2kAlpha[2];
cosThetap_o = cosTheta_o * cos2kAlpha[2] - sinTheta_o * sin2kAlpha[2];
} else {
sinThetap_o = sinTheta_o;
cosThetap_o = cosTheta_o;
}
// Handle out-of-range $\cos \thetao$ from scale adjustment
cosThetap_o = std::abs(cosThetap_o);
fsum += Mp(cosTheta_i, cosThetap_o, sinTheta_i, sinThetap_o, v[p]) * ap[p] *
Np(phi, p, s, gamma_o, gamma_t);
}
// Compute contribution of remaining terms after _pMax_
fsum += Mp(cosTheta_i, cosTheta_o, sinTheta_i, sinTheta_o, v[pMax]) * ap[pMax] /
(2.f * Pi);
if (AbsCosTheta(wi) > 0)
fsum /= AbsCosTheta(wi);
CHECK(!std::isinf(fsum.Average()) && !std::isnan(fsum.Average()));
return fsum;
}
pstd::array<Float, HairBxDF::pMax + 1> HairBxDF::ComputeApPDF(Float cosTheta_o) const {
// Compute array of $A_p$ values for _cosThetaO_
Float sinTheta_o = SafeSqrt(1 - cosTheta_o * cosTheta_o);
// Compute $\cos \thetat$ for refracted ray
Float sinTheta_t = sinTheta_o / eta;
Float cosTheta_t = SafeSqrt(1 - Sqr(sinTheta_t));
// Compute $\gammat$ for refracted ray
Float etap = SafeSqrt(eta * eta - Sqr(sinTheta_o)) / cosTheta_o;
Float sinGamma_t = h / etap;
Float cosGamma_t = SafeSqrt(1 - Sqr(sinGamma_t));
Float gamma_t = SafeASin(sinGamma_t);
// Compute the transmittance _T_ of a single path through the cylinder
SampledSpectrum T = Exp(-sigma_a * (2 * cosGamma_t / cosTheta_t));
pstd::array<SampledSpectrum, pMax + 1> ap = Ap(cosTheta_o, eta, h, T);
// Compute $A_p$ PDF from individual $A_p$ terms
pstd::array<Float, pMax + 1> apPDF;
Float sumY = 0;
for (const SampledSpectrum &as : ap)
sumY += as.Average();
for (int i = 0; i <= pMax; ++i)
apPDF[i] = ap[i].Average() / sumY;
return apPDF;
}
BSDFSample HairBxDF::Sample_f(Vector3f wo, Float uc, const Point2f &u, TransportMode mode,
BxDFReflTransFlags sampleFlags) const {
// Compute hair coordinate system terms related to _wo_
Float sinTheta_o = wo.x;
Float cosTheta_o = SafeSqrt(1 - Sqr(sinTheta_o));
Float phi_o = std::atan2(wo.z, wo.y);
// Determine which term $p$ to sample for hair scattering
pstd::array<Float, pMax + 1> apPDF = ComputeApPDF(cosTheta_o);
int p = SampleDiscrete(apPDF, uc, nullptr, &uc);
// Rotate $\sin \thetao$ and $\cos \thetao$ to account for hair scale tilt
Float sinThetap_o, cosThetap_o;
if (p == 0) {
sinThetap_o = sinTheta_o * cos2kAlpha[1] - cosTheta_o * sin2kAlpha[1];
cosThetap_o = cosTheta_o * cos2kAlpha[1] + sinTheta_o * sin2kAlpha[1];
} else if (p == 1) {
sinThetap_o = sinTheta_o * cos2kAlpha[0] + cosTheta_o * sin2kAlpha[0];
cosThetap_o = cosTheta_o * cos2kAlpha[0] - sinTheta_o * sin2kAlpha[0];
} else if (p == 2) {
sinThetap_o = sinTheta_o * cos2kAlpha[2] + cosTheta_o * sin2kAlpha[2];
cosThetap_o = cosTheta_o * cos2kAlpha[2] - sinTheta_o * sin2kAlpha[2];
} else {
sinThetap_o = sinTheta_o;
cosThetap_o = cosTheta_o;
}
// Sample $M_p$ to compute $\thetai$
Float cosTheta = 1 + v[p] * std::log(std::max<Float>(u[0], 1e-5) +
(1 - u[0]) * std::exp(-2 / v[p]));
Float sinTheta = SafeSqrt(1 - Sqr(cosTheta));
Float cosPhi = std::cos(2 * Pi * u[1]);
Float sinTheta_i = -cosTheta * sinThetap_o + sinTheta * cosPhi * cosThetap_o;
Float cosTheta_i = SafeSqrt(1 - Sqr(sinTheta_i));
// Sample $N_p$ to compute $\Delta\phi$
// Compute $\gammat$ for refracted ray
Float etap = SafeSqrt(eta * eta - Sqr(sinTheta_o)) / cosTheta_o;
Float sinGamma_t = h / etap;
Float cosGamma_t = SafeSqrt(1 - Sqr(sinGamma_t));
Float gamma_t = SafeASin(sinGamma_t);
Float dphi;
if (p < pMax)
dphi = Phi(p, gamma_o, gamma_t) + SampleTrimmedLogistic(uc, s, -Pi, Pi);
else
dphi = 2 * Pi * uc;
// Compute _wi_ from sampled hair scattering angles
Float phi_i = phi_o + dphi;
Vector3f wi(sinTheta_i, cosTheta_i * std::cos(phi_i), cosTheta_i * std::sin(phi_i));
// Compute PDF for sampled hair scattering direction _wi_
Float pdf = 0;
for (int p = 0; p < pMax; ++p) {
// Rotate $\sin \thetao$ and $\cos \thetao$ to account for hair scale tilt
Float sinThetap_o, cosThetap_o;
if (p == 0) {
sinThetap_o = sinTheta_o * cos2kAlpha[1] - cosTheta_o * sin2kAlpha[1];
cosThetap_o = cosTheta_o * cos2kAlpha[1] + sinTheta_o * sin2kAlpha[1];
} else if (p == 1) {
sinThetap_o = sinTheta_o * cos2kAlpha[0] + cosTheta_o * sin2kAlpha[0];
cosThetap_o = cosTheta_o * cos2kAlpha[0] - sinTheta_o * sin2kAlpha[0];
} else if (p == 2) {
sinThetap_o = sinTheta_o * cos2kAlpha[2] + cosTheta_o * sin2kAlpha[2];
cosThetap_o = cosTheta_o * cos2kAlpha[2] - sinTheta_o * sin2kAlpha[2];
} else {
sinThetap_o = sinTheta_o;
cosThetap_o = cosTheta_o;
}
// Handle out-of-range $\cos \thetao$ from scale adjustment
cosThetap_o = std::abs(cosThetap_o);
pdf += Mp(cosTheta_i, cosThetap_o, sinTheta_i, sinThetap_o, v[p]) * apPDF[p] *
Np(dphi, p, s, gamma_o, gamma_t);
}
pdf += Mp(cosTheta_i, cosTheta_o, sinTheta_i, sinTheta_o, v[pMax]) * apPDF[pMax] *
(1 / (2 * Pi));
// if (std::abs(wi->x) < .9999) CHECK_NEAR(*pdf, PDF(wo, *wi), .01);
return BSDFSample(f(wo, wi, mode), wi, pdf, Flags());
}
Float HairBxDF::PDF(Vector3f wo, Vector3f wi, TransportMode mode,
BxDFReflTransFlags sampleFlags) const {
// TODO? flags...
// Compute hair coordinate system terms related to _wo_
Float sinTheta_o = wo.x;
Float cosTheta_o = SafeSqrt(1 - Sqr(sinTheta_o));
Float phi_o = std::atan2(wo.z, wo.y);
// Compute hair coordinate system terms related to _wi_
Float sinTheta_i = wi.x;
Float cosTheta_i = SafeSqrt(1 - Sqr(sinTheta_i));
Float phi_i = std::atan2(wi.z, wi.y);
// Compute $\gammat$ for refracted ray
Float etap = SafeSqrt(eta * eta - Sqr(sinTheta_o)) / cosTheta_o;
Float sinGamma_t = h / etap;
Float gamma_t = SafeASin(sinGamma_t);
// Compute PDF for $A_p$ terms
pstd::array<Float, pMax + 1> apPDF = ComputeApPDF(cosTheta_o);
// Compute PDF sum for hair scattering events
Float phi = phi_i - phi_o;
Float pdf = 0;
for (int p = 0; p < pMax; ++p) {
// Compute $\sin \thetao$ and $\cos \thetao$ terms accounting for scales
Float sinThetap_o, cosThetap_o;
if (p == 0) {
sinThetap_o = sinTheta_o * cos2kAlpha[1] - cosTheta_o * sin2kAlpha[1];
cosThetap_o = cosTheta_o * cos2kAlpha[1] + sinTheta_o * sin2kAlpha[1];
}
// Handle remainder of $p$ values for hair scale tilt
else if (p == 1) {
sinThetap_o = sinTheta_o * cos2kAlpha[0] + cosTheta_o * sin2kAlpha[0];
cosThetap_o = cosTheta_o * cos2kAlpha[0] - sinTheta_o * sin2kAlpha[0];
} else if (p == 2) {
sinThetap_o = sinTheta_o * cos2kAlpha[2] + cosTheta_o * sin2kAlpha[2];
cosThetap_o = cosTheta_o * cos2kAlpha[2] - sinTheta_o * sin2kAlpha[2];
} else {
sinThetap_o = sinTheta_o;
cosThetap_o = cosTheta_o;
}
// Handle out-of-range $\cos \thetao$ from scale adjustment
cosThetap_o = std::abs(cosThetap_o);
pdf += Mp(cosTheta_i, cosThetap_o, sinTheta_i, sinThetap_o, v[p]) * apPDF[p] *
Np(phi, p, s, gamma_o, gamma_t);
}
pdf += Mp(cosTheta_i, cosTheta_o, sinTheta_i, sinTheta_o, v[pMax]) * apPDF[pMax] *
(1 / (2 * Pi));
return pdf;
}
RGBSpectrum HairBxDF::SigmaAFromConcentration(Float ce, Float cp) {
RGB eumelaninSigmaA(0.419f, 0.697f, 1.37f);
RGB pheomelaninSigmaA(0.187f, 0.4f, 1.05f);
RGB sigma_a = ce * eumelaninSigmaA + cp * pheomelaninSigmaA;
#ifdef PBRT_IS_GPU_CODE
return RGBSpectrum(*RGBColorSpace_sRGB, sigma_a);
#else
return RGBSpectrum(*RGBColorSpace::sRGB, sigma_a);
#endif
}
SampledSpectrum HairBxDF::SigmaAFromReflectance(const SampledSpectrum &c, Float beta_n,
const SampledWavelengths &lambda) {
SampledSpectrum sigma_a;
for (int i = 0; i < NSpectrumSamples; ++i)
sigma_a[i] =
Sqr(std::log(c[i]) / (5.969f - 0.215f * beta_n + 2.532f * Sqr(beta_n) -
10.73f * Pow<3>(beta_n) + 5.574f * Pow<4>(beta_n) +
0.245f * Pow<5>(beta_n)));
return sigma_a;
}
std::string HairBxDF::ToString() const {
return StringPrintf("[ HairBxDF h: %f gamma_o: %f eta: %f beta_m: %f beta_n: %f "
"v[0]: %f s: %f sigma_a: %s ]",
h, gamma_o, eta, beta_m, beta_n, v[0], s, sigma_a);
}
// *****************************************************************************
// Tensor file I/O
// *****************************************************************************
class Tensor {
public:
// Data type of the tensor's fields
enum Type {
/* Invalid/unspecified */
Invalid = 0,
/* Signed and unsigned integer values */
UInt8,
Int8,
UInt16,
Int16,
UInt32,
Int32,
UInt64,
Int64,
/* Floating point values */
Float16,
Float32,
Float64,
};
struct Field {
// Data type of the tensor's fields
Type dtype;
// Offset in the file
size_t offset;
/// Specifies both rank and size along each dimension
std::vector<size_t> shape;
/// Pointer to the start of the tensor
std::unique_ptr<uint8_t[]> data;
};
/// Load a tensor file into memory
Tensor(const std::string &filename);
/// Does the file contain a field of the specified name?
bool has_field(const std::string &name) const;
/// Return a data structure with information about the specified field
const Field &field(const std::string &name) const;
/// Return a human-readable summary
std::string ToString() const;
/// Return the total size of the tensor's data
size_t size() const { return m_size; }
std::string filename() const { return m_filename; }
private:
std::unordered_map<std::string, Field> m_fields;
std::string m_filename;
size_t m_size;
};
static std::ostream &operator<<(std::ostream &os, Tensor::Type value) {
switch (value) {
case Tensor::Invalid:
os << "invalid";
break;
case Tensor::UInt8:
os << "uint8_t";
break;
case Tensor::Int8:
os << "int8_t";
break;
case Tensor::UInt16:
os << "uint16_t";
break;
case Tensor::Int16:
os << "int16_t";
break;
case Tensor::UInt32:
os << "uint32_t";
break;
case Tensor::Int32:
os << "int8_t";
break;
case Tensor::UInt64:
os << "uint64_t";
break;
case Tensor::Int64:
os << "int64_t";
break;
case Tensor::Float16:
os << "float16_t";
break;
case Tensor::Float32:
os << "float32_t";
break;
case Tensor::Float64:
os << "float64_t";
break;
default:
os << "unkown";
break;
}
return os;
}
static size_t type_size(Tensor::Type value) {
switch (value) {
case Tensor::Invalid:
return 0;
break;
case Tensor::UInt8:
return 1;
break;
case Tensor::Int8:
return 1;
break;
case Tensor::UInt16:
return 2;
break;
case Tensor::Int16:
return 2;
break;
case Tensor::UInt32:
return 4;
break;
case Tensor::Int32:
return 4;
break;
case Tensor::UInt64:
return 8;
break;
case Tensor::Int64:
return 8;
break;
case Tensor::Float16:
return 2;
break;
case Tensor::Float32:
return 4;
break;
case Tensor::Float64:
return 8;
break;
default:
return 0;
break;
}
}
Tensor::Tensor(const std::string &filename) : m_filename(filename) {
// Helpful macros to limit error-handling code duplication
#ifdef ASSERT
#undef ASSERT
#endif // ASSERT
#define ASSERT(cond, msg) \
do { \
if (!(cond)) { \
fclose(file); \
ErrorExit("%s: Tensor: " msg, filename); \
} \
} while (0)
#define SAFE_READ(vars, size, count) \
ASSERT(fread(vars, size, count, file) == (count), "Unable to read " #vars ".")
FILE *file = fopen(filename.c_str(), "rb");
if (file == NULL)
ErrorExit("%s: unable to open file", filename);
ASSERT(!fseek(file, 0, SEEK_END), "Unable to seek to end of file.");
long size = ftell(file);
ASSERT(size != -1, "Unable to tell file cursor position.");
m_size = static_cast<size_t>(size);
rewind(file);
ASSERT(m_size >= 12 + 2 + 4, "Invalid tensor file: too small, truncated?");
uint8_t header[12], version[2];
uint32_t n_fields;
SAFE_READ(header, sizeof(*header), 12);
SAFE_READ(version, sizeof(*version), 2);
SAFE_READ(&n_fields, sizeof(n_fields), 1);
ASSERT(memcmp(header, "tensor_file", 12) == 0,
"Invalid tensor file: invalid header.");
ASSERT(version[0] == 1 && version[1] == 0,
"Invalid tensor file: unknown file version.");
for (uint32_t i = 0; i < n_fields; ++i) {
uint8_t dtype;
uint16_t name_length, ndim;
uint64_t offset;
SAFE_READ(&name_length, sizeof(name_length), 1);
std::string name(name_length, '\0');
SAFE_READ((char *)name.data(), 1, name_length);
SAFE_READ(&ndim, sizeof(ndim), 1);
SAFE_READ(&dtype, sizeof(dtype), 1);
SAFE_READ(&offset, sizeof(offset), 1);
ASSERT(dtype != Invalid && dtype <= Float64,
"Invalid tensor file: unknown type.");
std::vector<size_t> shape(ndim);
size_t total_size = type_size((Type)dtype); // no need to check here, line 43
// already removes invalid types
for (size_t j = 0; j < (size_t)ndim; ++j) {
uint64_t size_value;
SAFE_READ(&size_value, sizeof(size_value), 1);
shape[j] = (size_t)size_value;
total_size *= shape[j];
}
auto data = std::unique_ptr<uint8_t[]>(new uint8_t[total_size]);
long cur_pos = ftell(file);
ASSERT(cur_pos != -1, "Unable to tell current cursor position.");
ASSERT(fseek(file, offset, SEEK_SET) != -1, "Unable to seek to tensor offset.");
SAFE_READ(data.get(), 1, total_size);
ASSERT(fseek(file, cur_pos, SEEK_SET) != -1,
"Unable to seek back to current position");
m_fields[name] =
Field{(Type)dtype, static_cast<size_t>(offset), shape, std::move(data)};
}
fclose(file);
#undef SAFE_READ
#undef ASSERT
}
/// Does the file contain a field of the specified name?
bool Tensor::has_field(const std::string &name) const {
return m_fields.find(name) != m_fields.end();
}
/// Return a data structure with information about the specified field
const Tensor::Field &Tensor::field(const std::string &name) const {
auto it = m_fields.find(name);
CHECK(it != m_fields.end());
return it->second;
}
/// Return a human-readable summary
std::string Tensor::ToString() const {
std::ostringstream oss;
oss << "Tensor[" << std::endl
<< " filename = \"" << m_filename << "\"," << std::endl
<< " size = " << size() << "," << std::endl
<< " fields = {" << std::endl;
size_t ctr = 0;
for (const auto &it : m_fields) {
oss << " \"" << it.first << "\""
<< " => [" << std::endl
<< " dtype = " << it.second.dtype << "," << std::endl
<< " offset = " << it.second.offset << "," << std::endl
<< " shape = [";
const auto &shape = it.second.shape;
for (size_t j = 0; j < shape.size(); ++j) {
oss << shape[j];
if (j + 1 < shape.size())
oss << ", ";
}
oss << "]" << std::endl;
oss << " ]";
if (++ctr < m_fields.size())
oss << ",";
oss << std::endl;
}
oss << " }" << std::endl << "]";
return oss.str();
}
// MeasuredBRDF Definition
class MeasuredBRDF {
public:
MeasuredBRDF(Allocator alloc)
: ndf(alloc),
sigma(alloc),
vndf(alloc),
luminance(alloc),
spectra(alloc),
wavelengths(alloc) {}
static MeasuredBRDF *Create(const std::string &filename, Allocator alloc);
std::string ToString() const {
return StringPrintf("[ MeasuredBRDF filename: %s ]", filename);
}
using Warp2D0 = PiecewiseLinear2D<0>;
using Warp2D2 = PiecewiseLinear2D<2>;
using Warp2D3 = PiecewiseLinear2D<3>;
Warp2D0 ndf;
Warp2D0 sigma;
Warp2D2 vndf;
Warp2D2 luminance;
Warp2D3 spectra;
pstd::vector<float> wavelengths;
bool isotropic;
bool jacobian;
std::string filename;
};
STAT_MEMORY_COUNTER("Memory/Measured BRDF data", measuredBRDFBytes);
MeasuredBRDF *MeasuredBRDF::Create(const std::string &filename, Allocator alloc) {
Tensor tf = Tensor(filename);
auto &theta_i = tf.field("theta_i");
auto &phi_i = tf.field("phi_i");
auto &ndf = tf.field("ndf");
auto &sigma = tf.field("sigma");
auto &vndf = tf.field("vndf");
auto &spectra = tf.field("spectra");
auto &luminance = tf.field("luminance");
auto &wavelengths = tf.field("wavelengths");
auto &description = tf.field("description");
auto &jacobian = tf.field("jacobian");
if (!(description.shape.size() == 1 && description.dtype == Tensor::UInt8 &&
theta_i.shape.size() == 1 && theta_i.dtype == Tensor::Float32 &&
phi_i.shape.size() == 1 && phi_i.dtype == Tensor::Float32 &&
wavelengths.shape.size() == 1 && wavelengths.dtype == Tensor::Float32 &&
ndf.shape.size() == 2 && ndf.dtype == Tensor::Float32 &&
sigma.shape.size() == 2 && sigma.dtype == Tensor::Float32 &&
vndf.shape.size() == 4 && vndf.dtype == Tensor::Float32 &&
vndf.shape[0] == phi_i.shape[0] && vndf.shape[1] == theta_i.shape[0] &&
luminance.shape.size() == 4 && luminance.dtype == Tensor::Float32 &&
luminance.shape[0] == phi_i.shape[0] &&
luminance.shape[1] == theta_i.shape[0] &&
luminance.shape[2] == luminance.shape[3] &&
spectra.dtype == Tensor::Float32 && spectra.shape.size() == 5 &&
spectra.shape[0] == phi_i.shape[0] && spectra.shape[1] == theta_i.shape[0] &&
spectra.shape[2] == wavelengths.shape[0] &&
spectra.shape[3] == spectra.shape[4] &&
luminance.shape[2] == spectra.shape[3] &&
luminance.shape[3] == spectra.shape[4] &&
jacobian.shape.size() == 1 && jacobian.shape[0] == 1 &&
jacobian.dtype == Tensor::UInt8)) {
Error("%s: invalid BRDF file structure: %s", filename, tf);
return nullptr;
}
MeasuredBRDF *brdf = alloc.new_object<MeasuredBRDF>(alloc);
brdf->filename = filename;
brdf->isotropic = phi_i.shape[0] <= 2;
brdf->jacobian = ((uint8_t *)jacobian.data.get())[0];
if (!brdf->isotropic) {
float *phi_i_data = (float *)phi_i.data.get();
int reduction =
(int)std::rint((2 * Pi) / (phi_i_data[phi_i.shape[0] - 1] - phi_i_data[0]));
if (reduction != 1)
ErrorExit("%s: reduction %d (!= 1) not supported", filename, reduction);
}
/* Construct NDF interpolant data structure */
brdf->ndf = Warp2D0(alloc, (float *)ndf.data.get(), ndf.shape[1], ndf.shape[0], {},
{}, false, false);
/* Construct projected surface area interpolant data structure */
brdf->sigma = Warp2D0(alloc, (float *)sigma.data.get(), sigma.shape[1],
sigma.shape[0], {}, {}, false, false);
/* Construct VNDF warp data structure */
brdf->vndf =
Warp2D2(alloc, (float *)vndf.data.get(), vndf.shape[3], vndf.shape[2],
{{(int)phi_i.shape[0], (int)theta_i.shape[0]}},
{{(const float *)phi_i.data.get(), (const float *)theta_i.data.get()}});
/* Construct Luminance warp data structure */
brdf->luminance =
Warp2D2(alloc, (float *)luminance.data.get(), luminance.shape[3],
luminance.shape[2], {{(int)phi_i.shape[0], (int)theta_i.shape[0]}},
{{(const float *)phi_i.data.get(), (const float *)theta_i.data.get()}});
/* Copy wavelength information */
size_t size = wavelengths.shape[0];
brdf->wavelengths.resize(size);
for (size_t i = 0; i < size; ++i)
brdf->wavelengths[i] = ((const float *)wavelengths.data.get())[i];
/* Construct spectral interpolant */
brdf->spectra =
Warp2D3(alloc, (float *)spectra.data.get(), spectra.shape[4], spectra.shape[3],
{{(int)phi_i.shape[0], (int)theta_i.shape[0], (int)wavelengths.shape[0]}},
{{(const float *)phi_i.data.get(), (const float *)theta_i.data.get(),
(const float *)wavelengths.data.get()}},
false, false);
measuredBRDFBytes += sizeof(MeasuredBRDF) + 4 * brdf->wavelengths.size() +
brdf->ndf.BytesUsed() + brdf->sigma.BytesUsed() +
brdf->vndf.BytesUsed() + brdf->luminance.BytesUsed() +
brdf->spectra.BytesUsed();
return brdf;
}
MeasuredBRDF *MeasuredBxDF::BRDFDataFromFile(const std::string &filename,
Allocator alloc) {
static std::map<std::string, MeasuredBRDF *> loadedData;
if (loadedData.find(filename) == loadedData.end())
loadedData[filename] = MeasuredBRDF::Create(filename, alloc);
return loadedData[filename];
}
// MeasuredBxDF Method Definitions
SampledSpectrum MeasuredBxDF::f(Vector3f wo, Vector3f wi, TransportMode mode) const {
if (!SameHemisphere(wo, wi))
return SampledSpectrum(0.);
if (wo.z < 0) {
wo = -wo;
wi = -wi;
}
Vector3f wm = wi + wo;
if (LengthSquared(wm) == 0)
return SampledSpectrum(0);
wm = Normalize(wm);
/* Cartesian -> spherical coordinates */
Float theta_i = SphericalTheta(wi), phi_i = std::atan2(wi.y, wi.x);
Float theta_m = SphericalTheta(wm), phi_m = std::atan2(wm.y, wm.x);
/* Spherical coordinates -> unit coordinate system */
Vector2f u_wi(theta2u(theta_i), phi2u(phi_i));
Vector2f u_wm(theta2u(theta_m), phi2u(brdf->isotropic ? (phi_m - phi_i) : phi_m));
u_wm.y = u_wm.y - std::floor(u_wm.y);
Float params[2] = {phi_i, theta_i};
auto ui = brdf->vndf.Invert(u_wm, params);
Vector2f sample = ui.p;
Float vndfPDF = ui.pdf;
SampledSpectrum fr(0);
for (int i = 0; i < pbrt::NSpectrumSamples; ++i) {
Float params_fr[3] = {phi_i, theta_i, lambda[i]};
fr[i] = brdf->spectra.Evaluate(sample, params_fr);
CHECK_RARE(1e-6, fr[i] < 0);
fr[i] = std::max<Float>(0, fr[i]);
}
return fr * brdf->ndf.Evaluate(u_wm, params) /
(4 * brdf->sigma.Evaluate(u_wi, params) * AbsCosTheta(wi));
}
BSDFSample MeasuredBxDF::Sample_f(Vector3f wo, Float uc, const Point2f &u,
TransportMode mode,
BxDFReflTransFlags sampleFlags) const {
if (!(sampleFlags & BxDFReflTransFlags::Reflection))
return {};
bool flipWi = false;
if (wo.z <= 0) {
wo = -wo;
flipWi = true;
}
Float theta_i = SphericalTheta(wo), phi_i = std::atan2(wo.y, wo.x);
Vector2f sample = Vector2f(u.y, u.x);
Float params[2] = {phi_i, theta_i};
auto s = brdf->luminance.Sample(sample, params);
sample = s.p;
Float lumPDF = s.pdf;
s = brdf->vndf.Sample(sample, params);
Vector2f u_wm = s.p;
Float ndfPDF = s.pdf;
Float phi_m = u2phi(u_wm.y), theta_m = u2theta(u_wm.x);
if (brdf->isotropic)
phi_m += phi_i;
/* Spherical -> Cartesian coordinates */
Float sinTheta_m = std::sin(theta_m), cosTheta_m = std::cos(theta_m);
Vector3f wm = SphericalDirection(sinTheta_m, cosTheta_m, phi_m);
Vector3f wi = Reflect(wo, wm);
if (wi.z <= 0)
return {};
SampledSpectrum fr(0);
for (int i = 0; i < pbrt::NSpectrumSamples; ++i) {
Float params_fr[3] = {phi_i, theta_i, lambda[i]};
fr[i] = brdf->spectra.Evaluate(sample, params_fr);
CHECK_RARE(1e-6, fr[i] < 0);
fr[i] = std::max<Float>(0, fr[i]);
}
Vector2f u_wo = Vector2f(theta2u(theta_i), phi2u(phi_i));
fr *= brdf->ndf.Evaluate(u_wm, params) /
(4 * brdf->sigma.Evaluate(u_wo, params) * AbsCosTheta(wi));
Float jacobian =
4 * Dot(wo, wm) * std::max<Float>(2 * Sqr(Pi) * u_wm.x * sinTheta_m, 1e-6f);
Float pdf = ndfPDF * lumPDF / jacobian;
if (flipWi)
wi = -wi;
return BSDFSample(fr, wi, pdf, BxDFFlags::GlossyReflection);
}
Float MeasuredBxDF::PDF(Vector3f wo, Vector3f wi, TransportMode mode,
BxDFReflTransFlags sampleFlags) const {
if (!(sampleFlags & BxDFReflTransFlags::Reflection))
return 0;
if (!SameHemisphere(wo, wi))
return 0;
if (wo.z < 0) {
wo = -wo;
wi = -wi;
}
Vector3f wm = wi + wo;
if (LengthSquared(wm) == 0)
return 0;
wm = Normalize(wm);
/* Cartesian -> spherical coordinates */
Float theta_i = SphericalTheta(wi), phi_i = std::atan2(wi.y, wi.x);
Float theta_m = SphericalTheta(wm), phi_m = std::atan2(wm.y, wm.x);
/* Spherical coordinates -> unit coordinate system */
Vector2f u_wm(theta2u(theta_m), phi2u(brdf->isotropic ? (phi_m - phi_i) : phi_m));
u_wm.y = u_wm.y - std::floor(u_wm.y);
Float params[2] = {phi_i, theta_i};
auto ui = brdf->vndf.Invert(u_wm, params);
Vector2f sample = ui.p;
Float vndfPDF = ui.pdf;
Float pdf = brdf->luminance.Evaluate(sample, params);
Float sinTheta_m = std::sqrt(Sqr(wm.x) + Sqr(wm.y));
Float jacobian =
4.f * Dot(wi, wm) * std::max<Float>(2 * Sqr(Pi) * u_wm.x * sinTheta_m, 1e-6f);
return vndfPDF * pdf / jacobian;
}
std::string MeasuredBxDF::ToString() const {
return StringPrintf("[ MeasuredBxDF brdf: %s ]", *brdf);
}
std::string BSSRDFAdapter::ToString() const {
return StringPrintf("[ BSSRDFAdapter eta: %f ]", eta);
}
// BxDFHandle Method Definitions
SampledSpectrum BxDFHandle::rho(Vector3f wo, pstd::span<const Float> uc,
pstd::span<const Point2f> u2) const {
if (wo.z == 0)
return SampledSpectrum(0.f);
SampledSpectrum r(0.);
DCHECK_EQ(uc.size(), u2.size());
for (size_t i = 0; i < uc.size(); ++i) {
// Estimate one term of $\rho_\roman{hd}$
auto bs = Sample_f(wo, uc[i], u2[i], TransportMode::Radiance);
if (bs)
r += bs.f * AbsCosTheta(bs.wi) / bs.pdf;
}
return r / uc.size();
}
SampledSpectrum BxDFHandle::rho(pstd::span<const Float> uc1, pstd::span<const Point2f> u1,
pstd::span<const Float> uc2,
pstd::span<const Point2f> u2) const {
DCHECK_EQ(uc1.size(), u1.size());
DCHECK_EQ(uc2.size(), u2.size());
DCHECK_EQ(u1.size(), u2.size());
SampledSpectrum r(0.f);
for (size_t i = 0; i < uc1.size(); ++i) {
// Estimate one term of $\rho_\roman{hh}$
Vector3f wo = SampleUniformHemisphere(u1[i]);
if (wo.z == 0)
continue;
Float pdfo = UniformHemispherePDF();
auto bs = Sample_f(wo, uc2[i], u2[i], TransportMode::Radiance);
if (bs)
r += bs.f * AbsCosTheta(bs.wi) * AbsCosTheta(wo) / (pdfo * bs.pdf);
}
return r / (Pi * u1.size());
}
std::string BxDFHandle::ToString() const {
auto toStr = [](auto ptr) { return ptr->ToString(); };
return DispatchCPU(toStr);
}
template class LayeredBxDF<DielectricInterfaceBxDF, IdealDiffuseBxDF, false>;
template class LayeredBxDF<DielectricInterfaceBxDF, ConductorBxDF, false>;
} // namespace pbrt

1457
src/pbrt/bxdfs.h Normal file

File diff suppressed because it is too large Load diff

1543
src/pbrt/cameras.cpp Normal file

File diff suppressed because it is too large Load diff

547
src/pbrt/cameras.h Normal file
View file

@ -0,0 +1,547 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_CAMERAS_H
#define PBRT_CAMERAS_H
#include <pbrt/pbrt.h>
#include <pbrt/base/camera.h>
#include <pbrt/base/film.h>
#include <pbrt/film.h>
#include <pbrt/interaction.h>
#include <pbrt/ray.h>
#include <pbrt/samplers.h>
#include <pbrt/util/image.h>
#include <pbrt/util/scattering.h>
#include <memory>
#include <string>
#include <vector>
namespace pbrt {
// CameraTransform Definition
class CameraTransform {
public:
// CameraTransform Public Methods
CameraTransform() = default;
explicit CameraTransform(const AnimatedTransform &worldFromCamera);
PBRT_CPU_GPU
Point3f RenderFromCamera(const Point3f &p, Float time) const {
return renderFromCamera(p, time);
}
PBRT_CPU_GPU
Point3f CameraFromRender(const Point3f &p, Float time) const {
return renderFromCamera.ApplyInverse(p, time);
}
PBRT_CPU_GPU
Point3f RenderFromWorld(const Point3f &p) const {
return worldFromRender.ApplyInverse(p);
}
PBRT_CPU_GPU
Transform RenderFromWorld() const { return Inverse(worldFromRender); }
PBRT_CPU_GPU
Transform CameraFromRender(Float time) const {
return Inverse(renderFromCamera.Interpolate(time));
}
PBRT_CPU_GPU
Transform CameraFromWorld(Float time) const {
return Inverse(worldFromRender * renderFromCamera.Interpolate(time));
}
PBRT_CPU_GPU
bool CameraFromRenderHasScale() const { return renderFromCamera.HasScale(); }
PBRT_CPU_GPU
Vector3f RenderFromCamera(const Vector3f &v, Float time) const {
return renderFromCamera(v, time);
}
PBRT_CPU_GPU
Ray RenderFromCamera(const Ray &r) const { return renderFromCamera(r); }
PBRT_CPU_GPU
RayDifferential RenderFromCamera(const RayDifferential &r) const {
return renderFromCamera(r);
}
PBRT_CPU_GPU
Vector3f CameraFromRender(const Vector3f &v, Float time) const {
return renderFromCamera.ApplyInverse(v, time);
}
std::string ToString() const;
private:
// CameraTransform Private Members
AnimatedTransform renderFromCamera;
Transform worldFromRender;
};
// CameraWiSample Definition
struct CameraWiSample {
public:
CameraWiSample() = default;
PBRT_CPU_GPU
CameraWiSample(const SampledSpectrum &Wi, const Vector3f &wi, Float pdf,
Point2f pRaster, const Interaction &pRef, const Interaction &pLens)
: Wi(Wi), wi(wi), pdf(pdf), pRaster(pRaster), pRef(pRef), pLens(pLens) {}
SampledSpectrum Wi;
Vector3f wi;
Float pdf;
Point2f pRaster;
Interaction pRef, pLens;
};
// CameraRay Definition
struct CameraRay {
Ray ray;
SampledSpectrum weight = SampledSpectrum(1);
};
// CameraRayDifferential Definition
struct CameraRayDifferential {
RayDifferential ray;
SampledSpectrum weight = SampledSpectrum(1);
};
// CameraBase Definition
class CameraBase {
public:
// CameraBase Public Methods
PBRT_CPU_GPU
FilmHandle GetFilm() const { return film; }
PBRT_CPU_GPU
const CameraTransform &GetCameraTransform() const { return cameraTransform; }
PBRT_CPU_GPU
Float SampleTime(Float u) const { return Lerp(u, shutterOpen, shutterClose); }
PBRT_CPU_GPU
void ApproximatedPdxy(const SurfaceInteraction &si) const;
void InitMetadata(ImageMetadata *metadata) const;
std::string ToString() const;
protected:
// CameraBase Protected Members
CameraTransform cameraTransform;
Float shutterOpen, shutterClose;
FilmHandle film;
MediumHandle medium;
Vector3f minPosDifferentialX, minPosDifferentialY;
Vector3f minDirDifferentialX, minDirDifferentialY;
// CameraBase Protected Methods
CameraBase() = default;
CameraBase(const CameraTransform &cameraTransform, Float shutterOpen,
Float shutterClose, FilmHandle film, MediumHandle medium);
PBRT_CPU_GPU
static pstd::optional<CameraRayDifferential> GenerateRayDifferential(
CameraHandle camera, const CameraSample &sample, SampledWavelengths &lambda);
PBRT_CPU_GPU
Ray RenderFromCamera(const Ray &r) const {
return cameraTransform.RenderFromCamera(r);
}
PBRT_CPU_GPU
RayDifferential RenderFromCamera(const RayDifferential &r) const {
return cameraTransform.RenderFromCamera(r);
}
PBRT_CPU_GPU
Vector3f RenderFromCamera(const Vector3f &v, Float time) const {
return cameraTransform.RenderFromCamera(v, time);
}
PBRT_CPU_GPU
Point3f RenderFromCamera(const Point3f &p, Float time) const {
return cameraTransform.RenderFromCamera(p, time);
}
PBRT_CPU_GPU
Vector3f CameraFromRender(const Vector3f &v, Float time) const {
return cameraTransform.CameraFromRender(v, time);
}
PBRT_CPU_GPU
Point3f CameraFromRender(const Point3f &p, Float time) const {
return cameraTransform.CameraFromRender(p, time);
}
void FindMinimumDifferentials(CameraHandle camera);
};
// ProjectiveCamera Definition
class ProjectiveCamera : public CameraBase {
public:
// ProjectiveCamera Public Methods
ProjectiveCamera() = default;
void InitMetadata(ImageMetadata *metadata) const;
std::string BaseToString() const;
ProjectiveCamera(const CameraTransform &cameraTransform,
const Transform &screenFromCamera, const Bounds2f &screenWindow,
Float shutterOpen, Float shutterClose, Float lensRadius,
Float focalDistance, FilmHandle film, MediumHandle medium)
: CameraBase(cameraTransform, shutterOpen, shutterClose, film, medium),
screenFromCamera(screenFromCamera),
lensRadius(lensRadius),
focalDistance(focalDistance) {
// Compute projective camera transformations
// Compute projective camera screen transformations
rasterFromScreen =
Scale(film.FullResolution().x, film.FullResolution().y, 1) *
Scale(1 / (screenWindow.pMax.x - screenWindow.pMin.x),
1 / (screenWindow.pMin.y - screenWindow.pMax.y), 1) *
Translate(Vector3f(-screenWindow.pMin.x, -screenWindow.pMax.y, 0));
screenFromRaster = Inverse(rasterFromScreen);
cameraFromRaster = Inverse(screenFromCamera) * screenFromRaster;
}
// ProjectiveCamera Protected Members
Transform screenFromCamera, cameraFromRaster;
Transform rasterFromScreen, screenFromRaster;
Float lensRadius, focalDistance;
};
// OrthographicCamera Definition
class OrthographicCamera : public ProjectiveCamera {
public:
// OrthographicCamera Public Methods
OrthographicCamera(const CameraTransform &cameraTransform,
const Bounds2f &screenWindow, Float shutterOpen,
Float shutterClose, Float lensRadius, Float focalDistance,
FilmHandle film, MediumHandle medium)
: ProjectiveCamera(cameraTransform, Orthographic(0, 1), screenWindow, shutterOpen,
shutterClose, lensRadius, focalDistance, film, medium) {
// Compute differential changes in origin for orthographic camera rays
dxCamera = cameraFromRaster(Vector3f(1, 0, 0));
dyCamera = cameraFromRaster(Vector3f(0, 1, 0));
minDirDifferentialX = minDirDifferentialY = Vector3f(0, 0, 0);
minPosDifferentialX = dxCamera;
minPosDifferentialY = dyCamera;
}
PBRT_CPU_GPU
CameraRay GenerateRay(CameraSample sample, SampledWavelengths &lambda) const;
PBRT_CPU_GPU
pstd::optional<CameraRayDifferential> GenerateRayDifferential(
const CameraSample &sample, SampledWavelengths &lambda) const;
static OrthographicCamera *Create(const ParameterDictionary &parameters,
const CameraTransform &cameraTransform,
FilmHandle film, MediumHandle medium,
const FileLoc *loc, Allocator alloc = {});
PBRT_CPU_GPU
SampledSpectrum We(const Ray &ray, SampledWavelengths &lambda,
Point2f *pRaster2 = nullptr) const {
LOG_FATAL("We() unimplemented for OrthographicCamera");
return {};
}
PBRT_CPU_GPU
void PDF_We(const Ray &ray, Float *pdfPos, Float *pdfDir) const {
LOG_FATAL("PDF_We() unimplemented for OrthographicCamera");
}
PBRT_CPU_GPU
pstd::optional<CameraWiSample> SampleWi(const Interaction &ref, const Point2f &sample,
SampledWavelengths &lambda) const {
LOG_FATAL("SampleWi() unimplemented for OrthographicCamera");
return {};
}
std::string ToString() const;
private:
// OrthographicCamera Private Members
Vector3f dxCamera, dyCamera;
};
// PerspectiveCamera Definition
class PerspectiveCamera : public ProjectiveCamera {
public:
// PerspectiveCamera Public Methods
PerspectiveCamera(const CameraTransform &cameraTransform,
const Bounds2f &screenWindow, Float shutterOpen, Float shutterClose,
Float lensRadius, Float focalDistance, Float fov, FilmHandle film,
MediumHandle medium)
: ProjectiveCamera(cameraTransform, Perspective(fov, 1e-2f, 1000.f), screenWindow,
shutterOpen, shutterClose, lensRadius, focalDistance, film,
medium) {
// Compute differential changes in origin for perspective camera rays
dxCamera =
(cameraFromRaster(Point3f(1, 0, 0)) - cameraFromRaster(Point3f(0, 0, 0)));
dyCamera =
(cameraFromRaster(Point3f(0, 1, 0)) - cameraFromRaster(Point3f(0, 0, 0)));
// Compute _cosTotalWidth_ for perspective camera
Point2f radius = Point2f(film.GetFilter().Radius());
Point3f pCornerRaster(-radius.x, -radius.y, 0.f);
Vector3f wCornerCamera = Normalize(Vector3f(cameraFromRaster(pCornerRaster)));
cosTotalWidth = wCornerCamera.z;
DCHECK_LT(.9999 * cosTotalWidth, std::cos(Radians(fov / 2)));
// Compute image plane bounds at $z=1$ for _PerspectiveCamera_
Point2i res = film.FullResolution();
Point3f pMin = cameraFromRaster(Point3f(0, 0, 0));
Point3f pMax = cameraFromRaster(Point3f(res.x, res.y, 0));
pMin /= pMin.z;
pMax /= pMax.z;
A = std::abs((pMax.x - pMin.x) * (pMax.y - pMin.y));
FindMinimumDifferentials(this);
}
PerspectiveCamera() = default;
static PerspectiveCamera *Create(const ParameterDictionary &parameters,
const CameraTransform &cameraTransform,
FilmHandle film, MediumHandle medium,
const FileLoc *loc, Allocator alloc = {});
PBRT_CPU_GPU
CameraRay GenerateRay(CameraSample sample, SampledWavelengths &lambda) const;
PBRT_CPU_GPU
pstd::optional<CameraRayDifferential> GenerateRayDifferential(
const CameraSample &sample, SampledWavelengths &lambda) const;
PBRT_CPU_GPU
SampledSpectrum We(const Ray &ray, SampledWavelengths &lambda,
Point2f *pRaster2 = nullptr) const;
PBRT_CPU_GPU
void PDF_We(const Ray &ray, Float *pdfPos, Float *pdfDir) const;
PBRT_CPU_GPU
pstd::optional<CameraWiSample> SampleWi(const Interaction &ref, const Point2f &sample,
SampledWavelengths &lambda) const;
std::string ToString() const;
private:
// PerspectiveCamera Private Members
Float cosTotalWidth;
Vector3f dxCamera, dyCamera;
Float A;
};
// SphericalCamera Definition
class SphericalCamera : public CameraBase {
public:
// SphericalCamera::Mapping Definition
enum Mapping { EquiRect, EquiArea };
// SphericalCamera Public Methods
SphericalCamera(const CameraTransform &cameraTransform, Float shutterOpen,
Float shutterClose, FilmHandle film, MediumHandle medium,
Mapping mapping)
: CameraBase(cameraTransform, shutterOpen, shutterClose, film, medium),
mapping(mapping) {
FindMinimumDifferentials(this);
}
static SphericalCamera *Create(const ParameterDictionary &parameters,
const CameraTransform &cameraTransform,
FilmHandle film, MediumHandle medium,
const FileLoc *loc, Allocator alloc = {});
PBRT_CPU_GPU
CameraRay GenerateRay(CameraSample sample, SampledWavelengths &lambda) const;
PBRT_CPU_GPU
pstd::optional<CameraRayDifferential> GenerateRayDifferential(
const CameraSample &sample, SampledWavelengths &lambda) const {
return CameraBase::GenerateRayDifferential(this, sample, lambda);
}
PBRT_CPU_GPU
SampledSpectrum We(const Ray &ray, SampledWavelengths &lambda,
Point2f *pRaster2 = nullptr) const {
LOG_FATAL("We() unimplemented for SphericalCamera");
return {};
}
PBRT_CPU_GPU
void PDF_We(const Ray &ray, Float *pdfPos, Float *pdfDir) const {
LOG_FATAL("PDF_We() unimplemented for SphericalCamera");
}
PBRT_CPU_GPU
pstd::optional<CameraWiSample> SampleWi(const Interaction &ref, const Point2f &sample,
SampledWavelengths &lambda) const {
LOG_FATAL("SampleWi() unimplemented for SphericalCamera");
return {};
}
std::string ToString() const;
private:
// SphericalCamera Private Members
Mapping mapping;
};
// RealisticCamera Definition
class RealisticCamera : public CameraBase {
public:
// RealisticCamera Public Methods
RealisticCamera(const CameraTransform &cameraTransform, Float shutterOpen,
Float shutterClose, Float apertureDiameter, Float focusDistance,
Float dispersionFactor, std::vector<Float> &lensData, Float scale,
FilmHandle film, MediumHandle medium, Image apertureImage,
Allocator alloc);
static RealisticCamera *Create(const ParameterDictionary &parameters,
const CameraTransform &cameraTransform,
FilmHandle film, MediumHandle medium,
const FileLoc *loc, Allocator alloc = {});
PBRT_CPU_GPU
CameraRay GenerateRay(CameraSample sample, SampledWavelengths &lambda) const;
PBRT_CPU_GPU
pstd::optional<CameraRayDifferential> GenerateRayDifferential(
const CameraSample &sample, SampledWavelengths &lambda) const {
return CameraBase::GenerateRayDifferential(this, sample, lambda);
}
PBRT_CPU_GPU
SampledSpectrum We(const Ray &ray, SampledWavelengths &lambda,
Point2f *pRaster2 = nullptr) const {
LOG_FATAL("We() unimplemented for RealisticCamera");
return {};
}
PBRT_CPU_GPU
void PDF_We(const Ray &ray, Float *pdfPos, Float *pdfDir) const {
LOG_FATAL("PDF_We() unimplemented for RealisticCamera");
}
PBRT_CPU_GPU
pstd::optional<CameraWiSample> SampleWi(const Interaction &ref, const Point2f &sample,
SampledWavelengths &lambda) const {
LOG_FATAL("SampleWi() unimplemented for RealisticCamera");
return {};
}
std::string ToString() const;
private:
// RealisticCamera Private Declarations
struct LensElementInterface {
Float curvatureRadius;
Float thickness;
Float eta;
Float apertureRadius;
std::string ToString() const;
};
// RealisticCamera Private Methods
PBRT_CPU_GPU
Float LensRearZ() const { return elementInterfaces.back().thickness; }
PBRT_CPU_GPU
Float LensFrontZ() const {
Float zSum = 0;
for (const LensElementInterface &element : elementInterfaces)
zSum += element.thickness;
return zSum;
}
PBRT_CPU_GPU
Float RearElementRadius() const { return elementInterfaces.back().apertureRadius; }
PBRT_CPU_GPU
Float TraceLensesFromFilm(const Ray &rCamera, Ray *rOut, Float lambda = 550) const;
PBRT_CPU_GPU
static bool IntersectSphericalElement(Float radius, Float zCenter, const Ray &ray,
Float *t, Normal3f *n) {
// Compute _t0_ and _t1_ for ray--element intersection
Point3f o = ray.o - Vector3f(0, 0, zCenter);
Float A = ray.d.x * ray.d.x + ray.d.y * ray.d.y + ray.d.z * ray.d.z;
Float B = 2 * (ray.d.x * o.x + ray.d.y * o.y + ray.d.z * o.z);
Float C = o.x * o.x + o.y * o.y + o.z * o.z - radius * radius;
Float t0, t1;
if (!Quadratic(A, B, C, &t0, &t1))
return false;
// Select intersection $t$ based on ray direction and element curvature
bool useCloserT = (ray.d.z > 0) ^ (radius < 0);
*t = useCloserT ? std::min(t0, t1) : std::max(t0, t1);
if (*t < 0)
return false;
// Compute surface normal of element at ray intersection point
*n = Normal3f(Vector3f(o + *t * ray.d));
*n = FaceForward(Normalize(*n), -ray.d);
return true;
}
PBRT_CPU_GPU
bool TraceLensesFromScene(const Ray &rCamera, Ray *rOut) const;
PBRT_CPU_GPU
Float FilmDiagonal() const { return film.Diagonal() * scale; }
void DrawLensSystem() const;
void DrawRayPathFromFilm(const Ray &r, bool arrow, bool toOpticalIntercept) const;
void DrawRayPathFromScene(const Ray &r, bool arrow, bool toOpticalIntercept) const;
static void ComputeCardinalPoints(const Ray &rIn, const Ray &rOut, Float *p,
Float *f);
void ComputeThickLensApproximation(Float pz[2], Float f[2]) const;
Float FocusThickLens(Float focusDistance);
Float FocusBinarySearch(Float focusDistance);
Float FocusDistance(Float filmDist);
Bounds2f BoundExitPupil(Float filmX0, Float filmX1) const;
void RenderExitPupil(Float sx, Float sy, const char *filename) const;
PBRT_CPU_GPU
Point3f SampleExitPupil(const Point2f &pFilm, const Point2f &lensSample,
Float *sampleBoundsArea) const;
void TestExitPupilBounds() const;
// RealisticCamera Private Members
Float scale;
Float dispersionFactor;
Image apertureImage;
pstd::vector<LensElementInterface> elementInterfaces;
pstd::vector<Bounds2f> exitPupilBounds;
};
inline CameraRay CameraHandle::GenerateRay(CameraSample sample,
SampledWavelengths &lambda) const {
auto generate = [&](auto ptr) { return ptr->GenerateRay(sample, lambda); };
return Dispatch(generate);
}
inline FilmHandle CameraHandle::GetFilm() const {
auto getfilm = [&](auto ptr) { return ptr->GetFilm(); };
return Dispatch(getfilm);
}
inline Float CameraHandle::SampleTime(Float u) const {
auto sample = [&](auto ptr) { return ptr->SampleTime(u); };
return Dispatch(sample);
}
inline const CameraTransform &CameraHandle::GetCameraTransform() const {
auto gtc = [&](auto ptr) -> auto && { return ptr->GetCameraTransform(); };
return DispatchCRef(gtc);
}
} // namespace pbrt
#endif // PBRT_CAMERAS_H

View file

@ -0,0 +1,480 @@
//
// cyhair2pbrt.cpp
//
// Convert CyHair files to PBRT.
// Hair vertices are interpreted as Catmull-Rom spline points.
// The tool simply converts Catmull-Rom spline points to cubic Bezier points.
//
// MIT license
//
///////////// start of cyhair_loader
// clang-format off
/*
The MIT License (MIT)
Copyright (c) 2016 Light Transport Entertainment, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// Simple Cyhair loader.
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <iostream>
//#include <pbrt/core/cyhair_loader.h>
namespace cyhair {
class real3 {
public:
real3() : x(0.0f), y(0.0f), z(0.0f) {}
real3(float v) : x(v), y(v), z(v) {}
real3(float xx, float yy, float zz) : x(xx), y(yy), z(zz) {}
//~real3() {}
real3 operator+(const real3 &f2) const {
return {x + f2.x, y + f2.y, z + f2.z};
}
real3 operator*(const real3 &f2) const {
return {x * f2.x, y * f2.y, z * f2.z};
}
real3 operator/(const real3 &f2) const {
return {x / f2.x, y / f2.y, z / f2.z};
}
real3 operator/(const float f) const { return {x / f, y / f, z / f}; }
float x, y, z;
};
inline real3 operator*(float f, const real3 &v) {
return {v.x * f, v.y * f, v.z * f};
}
static const float toC2B[4][4] = {
{0.0f, 6.0f / 6.0f, 0.0f, 0.0f},
{-1.0f / 6.0f, 6.0f / 6.0f, 1.0f / 6.0f, 0.0f},
{0.0f, 1.0f / 6.0f, 6.0f / 6.0f, -1.0f / 6.0f},
{0.0f, 0.0, 6.0f / 6.0f, 0.0f}};
static const float toC2B0[4][4] = {
{0.0f, 6.0f / 6.0f, 0.0f, 0.0f},
{0.0f, 3.0f / 6.0f, 4.0f / 6.0f, -1.0f / 6.0f},
{0.0f, 1.0f / 6.0f, 6.0f / 6.0f, -1.0f / 6.0f},
{0.0f, 0.0f, 6.0f / 6.0f, 0.0f}};
static const float toC2B1[4][4] = {
{0.0f, 6.0f / 6.0f, 0.0f, 0.0f},
{-1.0f / 6.0f, 6.0f / 6.0f, 1.0f / 6.0f, 0.0f},
{-1.0f / 6.0f, 4.0f / 6.0f, 3.0f / 6.0f, 0.0f},
{0.0f, 0.0f, 6.0f / 6.0f, 0.0f}};
static void mul_matrix(real3 out[4], const float mat[4][4], const real3 pt[4]) {
for (int i = 0; i < 4; i++) {
out[i] = mat[i][0] * pt[0] + mat[i][1] * pt[1] + mat[i][2] * pt[2] +
mat[i][3] * pt[3];
}
}
static void CamullRomToCubicBezier(real3 Q[4], const real3 *cps, int cps_size,
int seg_idx) {
size_t sz = static_cast<size_t>(cps_size);
if (sz == 2) {
Q[0] = cps[seg_idx];
Q[1] = cps[seg_idx] * 2.0f / 3.0f + cps[seg_idx + 1] * 1.0f / 3.0f;
Q[2] = cps[seg_idx] * 1.0f / 3.0f + cps[seg_idx + 1] * 2.0f / 3.0f;
Q[3] = cps[seg_idx + 1];
} else {
real3 P[4];
if (seg_idx == 0) {
P[0] = real3(0.0f);
P[1] = cps[seg_idx + 0];
P[2] = cps[seg_idx + 1];
P[3] = cps[seg_idx + 2];
mul_matrix(Q, toC2B0, P);
} else if (seg_idx == static_cast<int>(sz - 2)) {
P[0] = cps[seg_idx - 1];
P[1] = cps[seg_idx + 0];
P[2] = cps[seg_idx + 1];
P[3] = real3(0.0f);
mul_matrix(Q, toC2B1, P);
} else {
P[0] = cps[seg_idx - 1];
P[1] = cps[seg_idx + 0];
P[2] = cps[seg_idx + 1];
P[3] = cps[seg_idx + 2];
mul_matrix(Q, toC2B, P);
}
}
}
struct CyHairHeader {
char magic[4];
unsigned int num_strands;
unsigned int total_points;
unsigned int flags;
unsigned int default_segments;
float default_thickness;
float default_transparency;
float default_color[3];
char infomation[88];
};
class CyHair {
public:
CyHair()
: flags_(0),
num_strands_(0),
total_points_(0),
default_segments_(-1),
default_thickness_(0.01f),
default_transparency_(1.0f) {
default_color_[0] = 0.5f;
default_color_[1] = 0.5f;
default_color_[2] = 0.5f;
}
~CyHair() {}
/// Load CyHair data from a file.
bool Load(const char *filename);
/// Convert to cubic bezier curves.
/// 4(cubic) * 3(xyz) * num_curves = vertices.size()
/// 4(cubic) * num_curves = radiuss.size()
/// `max_strands` limits the number of strands to convert. -1 = convert all
/// strands.
/// `thickness` overwrites strand thickness if it have positive value.
/// Apply `vertex_translate` after `vertex_scale`.
/// TODO(syoyo) return strand/segment information
bool ToCubicBezierCurves(std::vector<float> *vertices,
std::vector<float> *radiuss,
const float vertex_scale[3],
const float vertex_translate[3],
const int max_strands = -1,
const float thickness = -1.0f);
CyHairHeader header_;
// Raw CyHair values
std::vector<unsigned short> segments_;
std::vector<float> points_; // xyz
std::vector<float> thicknesses_;
std::vector<float> transparencies_;
std::vector<float> colors_; // rgb
unsigned int flags_;
unsigned int num_strands_;
unsigned int total_points_;
int default_segments_;
float default_thickness_;
float default_transparency_;
float default_color_[3];
int pad0;
// Processed CyHair values
std::vector<unsigned int> strand_offsets_;
};
bool CyHair::Load(const char *filename) {
FILE *fp = fopen(filename, "rb");
if (!fp) {
return false;
}
assert(sizeof(CyHairHeader) == 128);
CyHairHeader header;
if (1 != fread(&header, 128, 1, fp)) {
fclose(fp);
return false;
}
if (memcmp(header.magic, "HAIR", 4) != 0) {
fclose(fp);
return false;
}
flags_ = header.flags;
default_thickness_ = header.default_thickness;
default_transparency_ = header.default_transparency;
default_segments_ = static_cast<int>(header.default_segments);
default_color_[0] = header.default_color[0];
default_color_[1] = header.default_color[1];
default_color_[2] = header.default_color[2];
const bool has_segments = flags_ & 0x1;
const bool has_points = flags_ & 0x2;
const bool has_thickness = flags_ & 0x4;
const bool has_transparency = flags_ & 0x8;
const bool has_color = flags_ & 0x10;
num_strands_ = header.num_strands;
total_points_ = header.total_points;
if (!has_points) {
std::cout << "No point data in CyHair." << std::endl;
return false;
}
if ((default_segments_ < 1) && (!has_segments)) {
std::cout << "No valid segment information in CyHair." << std::endl;
return false;
}
// First read all strand data from a file.
if (has_segments) {
segments_.resize(num_strands_);
if (1 !=
fread(&segments_[0], sizeof(unsigned short) * num_strands_, 1, fp)) {
std::cout << "Failed to read CyHair segments data." << std::endl;
fclose(fp);
return false;
}
}
if (has_points) {
std::cout << "[CyHair] Has points." << std::endl;
points_.resize(3 * total_points_);
size_t n = fread(&points_[0], total_points_ * sizeof(float) * 3, 1, fp);
if (1 != n) {
std::cout << "Failed to read CyHair points data." << std::endl;
fclose(fp);
return false;
}
}
if (has_thickness) {
std::cout << "[CyHair] Has thickness." << std::endl;
thicknesses_.resize(total_points_);
if (1 != fread(&thicknesses_[0], total_points_ * sizeof(float), 1, fp)) {
std::cout << "Failed to read CyHair thickness data." << std::endl;
fclose(fp);
return false;
}
}
if (has_transparency) {
std::cout << "[CyHair] Has transparency." << std::endl;
transparencies_.resize(total_points_);
if (1 != fread(&transparencies_[0], total_points_ * sizeof(float), 1, fp)) {
std::cout << "Failed to read CyHair transparencies data." << std::endl;
fclose(fp);
return false;
}
}
if (has_color) {
std::cout << "[CyHair] Has color." << std::endl;
colors_.resize(3 * total_points_);
if (1 != fread(&colors_[0], total_points_ * sizeof(float) * 3, 1, fp)) {
std::cout << "Failed to read CyHair colors data." << std::endl;
fclose(fp);
return false;
}
}
// Build strand offset table.
strand_offsets_.resize(num_strands_);
strand_offsets_[0] = 0;
for (size_t i = 1; i < num_strands_; i++) {
int num_segments = segments_.empty() ? default_segments_ : segments_[i - 1];
strand_offsets_[i] =
strand_offsets_[i - 1] + static_cast<unsigned int>(num_segments + 1);
}
return true;
}
bool CyHair::ToCubicBezierCurves(std::vector<float> *vertices,
std::vector<float> *radiuss,
const float vertex_scale[3],
const float vertex_translate[3],
const int max_strands, const float user_thickness) {
if (points_.empty() || strand_offsets_.empty()) {
return false;
}
vertices->clear();
radiuss->clear();
int num_strands = static_cast<int>(num_strands_);
if ((max_strands > 0) && (max_strands < num_strands)) {
num_strands = max_strands;
}
std::cout << "[Hair] Convert first " << num_strands << " strands from "
<< max_strands << " strands in the original hair data."
<< std::endl;
// Assume input points are CatmullRom spline.
for (size_t i = 0; i < static_cast<size_t>(num_strands); i++) {
if ((i % 1000) == 0) {
std::cout << i << " / " << num_strands_ << std::endl;
}
int num_segments = segments_.empty() ? default_segments_ : segments_[i];
if (num_segments < 2) {
continue;
}
std::vector<real3> segment_points;
for (size_t k = 0; k < static_cast<size_t>(num_segments); k++) {
// Zup -> Yup
real3 p(points_[3 * (strand_offsets_[i] + k) + 0],
points_[3 * (strand_offsets_[i] + k) + 2],
points_[3 * (strand_offsets_[i] + k) + 1]);
segment_points.push_back(p);
}
// Skip both endpoints
for (int s = 1; s < num_segments - 1; s++) {
int seg_idx = s - 1;
real3 q[4];
CamullRomToCubicBezier(q, segment_points.data(), num_segments, seg_idx);
vertices->push_back(vertex_scale[0] * q[0].x + vertex_translate[0]);
vertices->push_back(vertex_scale[1] * q[0].y + vertex_translate[1]);
vertices->push_back(vertex_scale[2] * q[0].z + vertex_translate[2]);
vertices->push_back(vertex_scale[0] * q[1].x + vertex_translate[0]);
vertices->push_back(vertex_scale[1] * q[1].y + vertex_translate[1]);
vertices->push_back(vertex_scale[2] * q[1].z + vertex_translate[2]);
vertices->push_back(vertex_scale[0] * q[2].x + vertex_translate[0]);
vertices->push_back(vertex_scale[1] * q[2].y + vertex_translate[1]);
vertices->push_back(vertex_scale[2] * q[2].z + vertex_translate[2]);
vertices->push_back(vertex_scale[0] * q[3].x + vertex_translate[0]);
vertices->push_back(vertex_scale[1] * q[3].y + vertex_translate[1]);
vertices->push_back(vertex_scale[2] * q[3].z + vertex_translate[2]);
if (user_thickness > 0) {
// Use user supplied thickness.
radiuss->push_back(user_thickness);
radiuss->push_back(user_thickness);
radiuss->push_back(user_thickness);
radiuss->push_back(user_thickness);
} else {
// TODO(syoyo) Support per point/segment thickness
radiuss->push_back(default_thickness_);
radiuss->push_back(default_thickness_);
radiuss->push_back(default_thickness_);
radiuss->push_back(default_thickness_);
}
}
}
return true;
}
} // namespace cyhair
// clang-format on
///////////////////////////////////////////////////////////////////////////
// The above is cyhair_loader.{h,cc} basically directly; pbrt specific
// code follows...
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <vector>
int main(int argc, char *argv[]) {
if (argc <= 2 || strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0) {
fprintf(stderr, "usage: cyhair2pbrt [CyHair filename] [pbrt output filename] "
"(max strands) (thickness)\n");
return EXIT_FAILURE;
}
FILE *f = (strcmp(argv[2], "-") == 0) ? stdout : fopen(argv[2], "w");
if (!f) {
perror(argv[2]);
return EXIT_FAILURE;
}
int max_strands = -1; // -1 = Convert all strands
float user_thickness = 1.0f; // -1 = Use thickness in CyHair file.
if (argc > 3) {
max_strands = atoi(argv[3]);
}
if (argc > 4) {
user_thickness = atof(argv[4]);
}
cyhair::CyHair hair;
bool ret = hair.Load(argv[1]);
if (!ret) {
fprintf(stderr, "Failed to load CyHair file [ %s ]\n", argv[1]);
return EXIT_FAILURE;
}
std::vector<float> points;
std::vector<float> radiuss;
const float vertex_scale[3] = {1.0f, 1.0f, 1.0f};
const float vertex_translate[3] = {0.0f, 0.0f, 0.0f};
ret = hair.ToCubicBezierCurves(&points, &radiuss, vertex_scale, vertex_translate,
max_strands, user_thickness);
if (!ret) {
fprintf(stderr, "Failed to convert CyHair data\n");
return EXIT_FAILURE;
}
double bounds[2][3] = {{1e30, 1e30, 1e30}, {-1e30, -1e30, -1e30}};
for (size_t i = 0; i < points.size() / 3; ++i) {
const double thickness = static_cast<double>(radiuss[i]);
for (size_t c = 0; c < 3; ++c) {
bounds[0][c] = std::min(bounds[0][c],
static_cast<double>(points[3 * i + c]) - thickness);
bounds[1][c] = std::max(bounds[1][c],
static_cast<double>(points[3 * i + c]) + thickness);
}
}
fprintf(f, "# Converted from \"%s\" by cyhair2pbrt\n", argv[1]);
fprintf(f, "# The number of strands = %d. user_thickness = %f\n",
static_cast<int>(radiuss.size() / 4), static_cast<double>(user_thickness));
fprintf(f, "# Scene bounds: (%f, %f, %f) - (%f, %f, %f)\n\n\n", bounds[0][0],
bounds[0][1], bounds[0][2], bounds[1][0], bounds[1][1], bounds[1][2]);
const size_t num_curves = radiuss.size() / 4;
for (size_t i = 0; i < num_curves; i++) {
fprintf(f, R"(Shape "curve" "string type" [ "cylinder" ] "point3 P" [ )");
for (size_t j = 0; j < 12; j++) {
fprintf(f, "%f ", static_cast<double>(points[12 * i + j]));
}
fprintf(f, " ] \"float width0\" [ %f ] \"float width1\" [ %f ]\n",
static_cast<double>(radiuss[4 * i + 0]),
static_cast<double>(radiuss[4 * i + 3]));
}
if (f != stdout)
fclose(f);
fprintf(stderr, "Converted %d strands.\n", static_cast<int>(radiuss.size() / 4));
return EXIT_SUCCESS;
}

2374
src/pbrt/cmd/imgtool.cpp Normal file

File diff suppressed because it is too large Load diff

1618
src/pbrt/cmd/obj2pbrt.cpp Normal file

File diff suppressed because it is too large Load diff

241
src/pbrt/cmd/pbrt.cpp Normal file
View file

@ -0,0 +1,241 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#include <pbrt/pbrt.h>
#include <pbrt/cpu/render.h>
#include <pbrt/options.h>
#include <pbrt/parsedscene.h>
#include <pbrt/parser.h>
#include <pbrt/util/args.h>
#include <pbrt/util/check.h>
#include <pbrt/util/error.h>
#include <pbrt/util/log.h>
#include <pbrt/util/memory.h>
#include <pbrt/util/parallel.h>
#include <pbrt/util/print.h>
#include <pbrt/util/spectrum.h>
#include <pbrt/util/string.h>
#ifdef NVTX
#include <sys/syscall.h>
#include "nvtx3/nvToolsExt.h"
#endif
using namespace pbrt;
#ifdef PBRT_BUILD_GPU_RENDERER
namespace pbrt {
extern void GPURender(ParsedScene &);
}
#else
namespace pbrt {
void GPURender(ParsedScene &) {
ErrorExit("GPU rendering is not supported on this system.");
}
} // namespace pbrt
#endif
static void usage(const std::string &msg = {}) {
if (!msg.empty())
fprintf(stderr, "pbrt: %s\n\n", msg.c_str());
fprintf(stderr,
R"(usage: pbrt [<options>] <filename.pbrt...>
Rendering options:
--cropwindow <x0,x1,y0,y1> Specify an image crop window w.r.t. [0,1]^2
--debugstart <values> Inform the Integrator where to start rendering for
faster debugging. (<values> are Integrator-specific
and come from error message text.)
--disable-pixel-jitter Always sample pixels at their centers.
--disable-wavelength-jitter Always sample the same %d wavelengths of light.
--display-server <addr:port> Connect to display server at given address and port
to display the image as it's being rendered.
--force-diffuse Convert all materials to be diffuse.)"
#ifdef PBRT_BUILD_GPU_RENDERER
R"(
--gpu Use the GPU for rendering. (Default: disabled)
--gpu-device <index> Use specified GPU for rendering.)"
#endif
R"(
--help Print this help text.
--mse-reference-image Filename for reference image to use for MSE computation.
--mse-reference-out File to write MSE error vs spp results.
--nthreads <num> Use specified number of threads for rendering.
--outfile <filename> Write the final image to the given filename.
--pixel <x,y> Render just the specified pixel.
--pixelbounds <x0,x1,y0,y1> Specify an image crop window w.r.t. pixel coordinates.
--pixelstats Record per-pixel statistics and write additional images
with their values.
--quick Automatically reduce a number of quality settings
to render more quickly.
--quiet Suppress all text output other than error messages.
--render-coord-sys <name> Coordinate system to use for the scene when rendering,
where name is "camera", "cameraworld", or "world".
--seed <n> Set random number generator seed. Default: 0.
--spp <n> Override number of pixel samples specified in scene
description file.
Logging options:
--log-level <level> Log messages at or above this level, where <level>
is "verbose", "error", or "fatal". Default: "error".
--vlog-level <n> Set VLOG verbosity. (Default: 0, disabled.)
Reformatting options:
--format Print a reformatted version of the input file(s) to
standard output. Does not render an image.
--toply Print a reformatted version of the input file(s) to
standard output and convert all triangle meshes to
PLY files. Does not render an image.
--upgrade Upgrade a pbrt-v3 file to pbrt-v4's format.
)",
NSpectrumSamples);
exit(msg.empty() ? 0 : 1);
}
// main program
int main(int argc, char *argv[]) {
#ifdef NVTX
nvtxNameOsThread(syscall(SYS_gettid), "MAIN_THREAD");
#endif
// Declare variables for parsed command line
PBRTOptions options;
std::vector<std::string> filenames;
std::string logLevel = "error";
std::string renderCoordSys = "cameraworld";
bool format = false, toPly = false;
// Process command-line arguments
++argv;
while (*argv != nullptr) {
if ((*argv)[0] != '-') {
filenames.push_back(*argv);
++argv;
continue;
}
auto onError = [](const std::string &err) {
usage(err);
exit(1);
};
std::string cropWindow, pixelBounds, pixel;
if (ParseArg(&argv, "cropwindow", &cropWindow, onError)) {
pstd::optional<std::vector<Float>> c = SplitStringToFloats(cropWindow, ',');
if (!c || c->size() != 4) {
usage("Didn't find four values after --cropwindow");
return 1;
}
options.cropWindow =
Bounds2f(Point2f((*c)[0], (*c)[2]), Point2f((*c)[1], (*c)[3]));
} else if (ParseArg(&argv, "pixel", &pixel, onError)) {
pstd::optional<std::vector<int>> p = SplitStringToInts(pixel, ',');
if (!p || p->size() != 2) {
usage("Didn't find two values after --pixel");
return 1;
}
options.pixelBounds =
Bounds2i(Point2i((*p)[0], (*p)[1]), Point2i((*p)[0] + 1, (*p)[1] + 1));
} else if (ParseArg(&argv, "pixelbounds", &pixelBounds, onError)) {
pstd::optional<std::vector<int>> p = SplitStringToInts(pixelBounds, ',');
if (!p || p->size() != 4) {
usage("Didn't find four integer values after --pixelbounds");
return 1;
}
options.pixelBounds =
Bounds2i(Point2i((*p)[0], (*p)[2]), Point2i((*p)[1], (*p)[3]));
} else if (
#ifdef PBRT_BUILD_GPU_RENDERER
ParseArg(&argv, "gpu", &options.useGPU, onError) ||
ParseArg(&argv, "gpu-device", &options.gpuDevice, onError) ||
#endif
ParseArg(&argv, "debugstart", &options.debugStart, onError) ||
ParseArg(&argv, "disable-pixel-jitter", &options.disablePixelJitter,
onError) ||
ParseArg(&argv, "disable-wavelength-jitter", &options.disableWavelengthJitter,
onError) ||
ParseArg(&argv, "display-server", &options.displayServer, onError) ||
ParseArg(&argv, "force-diffuse", &options.forceDiffuse, onError) ||
ParseArg(&argv, "format", &format, onError) ||
ParseArg(&argv, "log-level", &logLevel, onError) ||
ParseArg(&argv, "mse-reference-image", &options.mseReferenceImage, onError) ||
ParseArg(&argv, "mse-reference-out", &options.mseReferenceOutput, onError) ||
ParseArg(&argv, "nthreads", &options.nThreads, onError) ||
ParseArg(&argv, "outfile", &options.imageFile, onError) ||
ParseArg(&argv, "pixelstats", &options.recordPixelStatistics, onError) ||
ParseArg(&argv, "quick", &options.quickRender, onError) ||
ParseArg(&argv, "quiet", &options.quiet, onError) ||
ParseArg(&argv, "render-coord-sys", &renderCoordSys, onError) ||
ParseArg(&argv, "seed", &options.seed, onError) ||
ParseArg(&argv, "spp", &options.pixelSamples, onError) ||
ParseArg(&argv, "toply", &toPly, onError) ||
ParseArg(&argv, "upgrade", &options.upgrade, onError) ||
ParseArg(&argv, "vlog-level", &options.logConfig.vlogLevel, onError)) {
// success
} else if ((strcmp(*argv, "--help") == 0) || (strcmp(*argv, "-help") == 0) ||
(strcmp(*argv, "-h") == 0)) {
usage();
return 0;
} else {
usage(StringPrintf("argument \"%s\" unknown", *argv));
return 1;
}
}
// Print welcome banner
if (!options.quiet && !format && !toPly && !options.upgrade) {
printf("pbrt version 4 (built %s at %s)\n", __DATE__, __TIME__);
#ifndef NDEBUG
LOG_VERBOSE("Running debug build");
printf("*** DEBUG BUILD ***\n");
#endif // !NDEBUG
printf("Copyright (c)1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.\n");
printf("The source code to pbrt (but *not* the book contents) is covered "
"by the BSD License.\n");
printf("See the file LICENSE.txt for the conditions of the license.\n");
fflush(stdout);
}
if (renderCoordSys == "camera")
options.renderingSpace = RenderingCoordinateSystem::Camera;
else if (renderCoordSys == "cameraworld")
options.renderingSpace = RenderingCoordinateSystem::CameraWorld;
else if (renderCoordSys == "world")
options.renderingSpace = RenderingCoordinateSystem::World;
else
ErrorExit("%s: unknown rendering coordinate system.", renderCoordSys);
if (!options.mseReferenceImage.empty() && options.mseReferenceOutput.empty())
ErrorExit("Must provide MSE reference output filename via "
"--mse-reference-out");
if (!options.mseReferenceOutput.empty() && options.mseReferenceImage.empty())
ErrorExit("Must provide MSE reference image via --mse-reference-image");
options.logConfig.level = LogLevelFromString(logLevel);
InitPBRT(options);
if (format || toPly || options.upgrade) {
FormattingScene formattingScene(toPly, options.upgrade);
ParseFiles(&formattingScene, filenames);
} else {
// Parse provided scene description files
ParsedScene scene;
ParseFiles(&scene, filenames);
// Render scene
if (options.useGPU)
GPURender(scene);
else
CPURender(scene);
LOG_VERBOSE("Memory used after post-render cleanup: %s", GetCurrentRSS());
// Clean up after rendering scene
CleanupPBRT();
}
return 0;
}

View file

@ -0,0 +1,81 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#include <pbrt/pbrt.h>
#include <pbrt/options.h>
#include <pbrt/util/args.h>
#include <pbrt/util/error.h>
#include <pbrt/util/print.h>
#include <gtest/gtest.h>
#include <string>
using namespace pbrt;
void usage(const std::string &msg = "") {
if (!msg.empty())
fprintf(stderr, "pbrt_test: %s\n\n", msg.c_str());
fprintf(stderr, R"(pbrt_test arguments:
--log-level <level> Log messages at or above this level, where <level>
is "verbose", "error", or "fatal". Default: "error".
--nthreads <num> Use specified number of threads for rendering.
--test_filter <regexp> Regular expression of test names to run.
--vlog-level <n> Set VLOG verbosity. (Default: 0, disabled.)
)");
exit(msg.empty() ? 0 : 1);
}
int main(int argc, char **argv) {
PBRTOptions opt;
opt.quiet = true;
std::string logLevel = "error";
std::string testFilter;
char **origArgv = argv;
// Process command-line arguments
++argv;
while (*argv != nullptr) {
auto onError = [](const std::string &err) {
usage(err);
exit(1);
};
if (ParseArg(&argv, "log-level", &logLevel, onError) ||
ParseArg(&argv, "nthreads", &opt.nThreads, onError) ||
ParseArg(&argv, "test-filter", &testFilter, onError) ||
ParseArg(&argv, "vlog-level", &opt.logConfig.vlogLevel, onError)) {
// success
} else if ((strcmp(*argv, "--help") == 0) || (strcmp(*argv, "-h") == 0)) {
usage();
return 0;
} else {
usage(StringPrintf("argument \"%s\" unknown", *argv));
return 1;
}
}
opt.logConfig.level = LogLevelFromString(logLevel);
InitPBRT(opt);
int googleArgc = 1;
const char *googleArgv[4] = {};
googleArgv[0] = argv[0];
std::string filter;
if (!testFilter.empty()) {
filter = StringPrintf("--gtest_filter=%s", testFilter);
googleArgc += 1;
googleArgv[1] = filter.c_str();
}
testing::InitGoogleTest(&googleArgc, (char **)googleArgv);
int ret = RUN_ALL_TESTS();
CleanupPBRT();
return ret;
}

View file

@ -0,0 +1,902 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#if defined(_MSC_VER)
#ifndef NOMINMAX
#define NOMINMAX
#endif
#define strcasecmp _stricmp
#endif
#include <assert.h>
#include <algorithm>
#include <cmath>
#include <condition_variable>
#include <cstring>
#include <functional>
#include <iostream>
#include <mutex>
#include <stdexcept>
#include <thread>
#include <vector>
/**
* This file contains:
*
* 1. CIE 1931 curves at sampled at 5nm intervals
*
* 2. CIE D65 and D50 spectra sampled at 5nm intervals.
* Both are normalized to have unit luminance.
*
* 3. XYZ <-> sRGB conversion matrices
* XYZ <-> ProPhoto RGB conversion matrices
*
* 4. A convenience function "cie_interp" to access the discretized
* data at arbitrary wavelengths (with linear interpolation)
}
*/
#define CIE_LAMBDA_MIN 360.0
#define CIE_LAMBDA_MAX 830.0
#define CIE_SAMPLES 95
const double cie_x[CIE_SAMPLES] = {
0.000129900000, 0.000232100000, 0.000414900000, 0.000741600000, 0.001368000000,
0.002236000000, 0.004243000000, 0.007650000000, 0.014310000000, 0.023190000000,
0.043510000000, 0.077630000000, 0.134380000000, 0.214770000000, 0.283900000000,
0.328500000000, 0.348280000000, 0.348060000000, 0.336200000000, 0.318700000000,
0.290800000000, 0.251100000000, 0.195360000000, 0.142100000000, 0.095640000000,
0.057950010000, 0.032010000000, 0.014700000000, 0.004900000000, 0.002400000000,
0.009300000000, 0.029100000000, 0.063270000000, 0.109600000000, 0.165500000000,
0.225749900000, 0.290400000000, 0.359700000000, 0.433449900000, 0.512050100000,
0.594500000000, 0.678400000000, 0.762100000000, 0.842500000000, 0.916300000000,
0.978600000000, 1.026300000000, 1.056700000000, 1.062200000000, 1.045600000000,
1.002600000000, 0.938400000000, 0.854449900000, 0.751400000000, 0.642400000000,
0.541900000000, 0.447900000000, 0.360800000000, 0.283500000000, 0.218700000000,
0.164900000000, 0.121200000000, 0.087400000000, 0.063600000000, 0.046770000000,
0.032900000000, 0.022700000000, 0.015840000000, 0.011359160000, 0.008110916000,
0.005790346000, 0.004109457000, 0.002899327000, 0.002049190000, 0.001439971000,
0.000999949300, 0.000690078600, 0.000476021300, 0.000332301100, 0.000234826100,
0.000166150500, 0.000117413000, 0.000083075270, 0.000058706520, 0.000041509940,
0.000029353260, 0.000020673830, 0.000014559770, 0.000010253980, 0.000007221456,
0.000005085868, 0.000003581652, 0.000002522525, 0.000001776509, 0.000001251141};
const double cie_y[CIE_SAMPLES] = {
0.000003917000, 0.000006965000, 0.000012390000, 0.000022020000, 0.000039000000,
0.000064000000, 0.000120000000, 0.000217000000, 0.000396000000, 0.000640000000,
0.001210000000, 0.002180000000, 0.004000000000, 0.007300000000, 0.011600000000,
0.016840000000, 0.023000000000, 0.029800000000, 0.038000000000, 0.048000000000,
0.060000000000, 0.073900000000, 0.090980000000, 0.112600000000, 0.139020000000,
0.169300000000, 0.208020000000, 0.258600000000, 0.323000000000, 0.407300000000,
0.503000000000, 0.608200000000, 0.710000000000, 0.793200000000, 0.862000000000,
0.914850100000, 0.954000000000, 0.980300000000, 0.994950100000, 1.000000000000,
0.995000000000, 0.978600000000, 0.952000000000, 0.915400000000, 0.870000000000,
0.816300000000, 0.757000000000, 0.694900000000, 0.631000000000, 0.566800000000,
0.503000000000, 0.441200000000, 0.381000000000, 0.321000000000, 0.265000000000,
0.217000000000, 0.175000000000, 0.138200000000, 0.107000000000, 0.081600000000,
0.061000000000, 0.044580000000, 0.032000000000, 0.023200000000, 0.017000000000,
0.011920000000, 0.008210000000, 0.005723000000, 0.004102000000, 0.002929000000,
0.002091000000, 0.001484000000, 0.001047000000, 0.000740000000, 0.000520000000,
0.000361100000, 0.000249200000, 0.000171900000, 0.000120000000, 0.000084800000,
0.000060000000, 0.000042400000, 0.000030000000, 0.000021200000, 0.000014990000,
0.000010600000, 0.000007465700, 0.000005257800, 0.000003702900, 0.000002607800,
0.000001836600, 0.000001293400, 0.000000910930, 0.000000641530, 0.000000451810};
const double cie_z[CIE_SAMPLES] = {
0.000606100000, 0.001086000000, 0.001946000000, 0.003486000000, 0.006450001000,
0.010549990000, 0.020050010000, 0.036210000000, 0.067850010000, 0.110200000000,
0.207400000000, 0.371300000000, 0.645600000000, 1.039050100000, 1.385600000000,
1.622960000000, 1.747060000000, 1.782600000000, 1.772110000000, 1.744100000000,
1.669200000000, 1.528100000000, 1.287640000000, 1.041900000000, 0.812950100000,
0.616200000000, 0.465180000000, 0.353300000000, 0.272000000000, 0.212300000000,
0.158200000000, 0.111700000000, 0.078249990000, 0.057250010000, 0.042160000000,
0.029840000000, 0.020300000000, 0.013400000000, 0.008749999000, 0.005749999000,
0.003900000000, 0.002749999000, 0.002100000000, 0.001800000000, 0.001650001000,
0.001400000000, 0.001100000000, 0.001000000000, 0.000800000000, 0.000600000000,
0.000340000000, 0.000240000000, 0.000190000000, 0.000100000000, 0.000049999990,
0.000030000000, 0.000020000000, 0.000010000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000};
#define N(x) (x / 10566.864005283874576)
const double cie_d65[CIE_SAMPLES] = {
N(46.6383), N(49.3637), N(52.0891), N(51.0323), N(49.9755), N(52.3118), N(54.6482),
N(68.7015), N(82.7549), N(87.1204), N(91.486), N(92.4589), N(93.4318), N(90.057),
N(86.6823), N(95.7736), N(104.865), N(110.936), N(117.008), N(117.41), N(117.812),
N(116.336), N(114.861), N(115.392), N(115.923), N(112.367), N(108.811), N(109.082),
N(109.354), N(108.578), N(107.802), N(106.296), N(104.79), N(106.239), N(107.689),
N(106.047), N(104.405), N(104.225), N(104.046), N(102.023), N(100.0), N(98.1671),
N(96.3342), N(96.0611), N(95.788), N(92.2368), N(88.6856), N(89.3459), N(90.0062),
N(89.8026), N(89.5991), N(88.6489), N(87.6987), N(85.4936), N(83.2886), N(83.4939),
N(83.6992), N(81.863), N(80.0268), N(80.1207), N(80.2146), N(81.2462), N(82.2778),
N(80.281), N(78.2842), N(74.0027), N(69.7213), N(70.6652), N(71.6091), N(72.979),
N(74.349), N(67.9765), N(61.604), N(65.7448), N(69.8856), N(72.4863), N(75.087),
N(69.3398), N(63.5927), N(55.0054), N(46.4182), N(56.6118), N(66.8054), N(65.0941),
N(63.3828), N(63.8434), N(64.304), N(61.8779), N(59.4519), N(55.7054), N(51.959),
N(54.6998), N(57.4406), N(58.8765), N(60.3125)};
#undef N
#define N(x) (x / 106.8)
const double cie_e[CIE_SAMPLES] = {
N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0),
N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0),
N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0),
N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0),
N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0),
N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0),
N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0),
N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0),
N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0),
N(1.0), N(1.0), N(1.0), N(1.0), N(1.0)};
#undef N
#define N(x) (x / 10503.2)
const double cie_d50[CIE_SAMPLES] = {
N(23.942000), N(25.451000), N(26.961000), N(25.724000), N(24.488000),
N(27.179000), N(29.871000), N(39.589000), N(49.308000), N(52.910000),
N(56.513000), N(58.273000), N(60.034000), N(58.926000), N(57.818000),
N(66.321000), N(74.825000), N(81.036000), N(87.247000), N(88.930000),
N(90.612000), N(90.990000), N(91.368000), N(93.238000), N(95.109000),
N(93.536000), N(91.963000), N(93.843000), N(95.724000), N(96.169000),
N(96.613000), N(96.871000), N(97.129000), N(99.614000), N(102.099000),
N(101.427000), N(100.755000), N(101.536000), N(102.317000), N(101.159000),
N(100.000000), N(98.868000), N(97.735000), N(98.327000), N(98.918000),
N(96.208000), N(93.499000), N(95.593000), N(97.688000), N(98.478000),
N(99.269000), N(99.155000), N(99.042000), N(97.382000), N(95.722000),
N(97.290000), N(98.857000), N(97.262000), N(95.667000), N(96.929000),
N(98.190000), N(100.597000), N(103.003000), N(101.068000), N(99.133000),
N(93.257000), N(87.381000), N(89.492000), N(91.604000), N(92.246000),
N(92.889000), N(84.872000), N(76.854000), N(81.683000), N(86.511000),
N(89.546000), N(92.580000), N(85.405000), N(78.230000), N(67.961000),
N(57.692000), N(70.307000), N(82.923000), N(80.599000), N(78.274000),
N(0), N(0), N(0), N(0), N(0),
N(0), N(0), N(0), N(0)};
#undef N
#define N(x) (x / 10536.3)
const double cie_d60[CIE_SAMPLES] = {
N(38.683115), N(41.014457), N(42.717548), N(42.264182), N(41.454941),
N(41.763698), N(46.605319), N(59.226938), N(72.278594), N(78.231500),
N(80.440600), N(82.739580), N(82.915027), N(79.009168), N(77.676264),
N(85.163609), N(95.681274), N(103.267764), N(107.954821), N(109.777964),
N(109.559187), N(108.418402), N(107.758141), N(109.071548), N(109.671404),
N(106.734741), N(103.707873), N(103.981942), N(105.232199), N(105.235867),
N(104.427667), N(103.052881), N(102.522934), N(104.371416), N(106.052671),
N(104.948900), N(103.315154), N(103.416286), N(103.538599), N(102.099304),
N(100.000000), N(97.992725), N(96.751421), N(97.102402), N(96.712823),
N(93.174457), N(89.921479), N(90.351933), N(91.999793), N(92.384009),
N(92.098710), N(91.722859), N(90.646003), N(88.327552), N(86.526483),
N(87.034239), N(87.579186), N(85.884584), N(83.976140), N(83.743140),
N(84.724074), N(86.450818), N(87.493491), N(86.546330), N(83.483070),
N(78.268785), N(74.172451), N(74.275184), N(76.620385), N(79.423856),
N(79.051849), N(71.763360), N(65.471371), N(67.984085), N(74.106079),
N(78.556612), N(79.527120), N(75.584935), N(67.307163), N(55.275106),
N(49.273538), N(59.008629), N(70.892412), N(70.950115), N(67.163996),
N(67.445480), N(68.171371), N(66.466636), N(62.989809), N(58.067786),
N(54.990892), N(56.915942), N(60.825601), N(62.987850)};
#undef N
const double xyz_to_srgb[3][3] = {{3.240479, -1.537150, -0.498535},
{-0.969256, 1.875991, 0.041556},
{0.055648, -0.204043, 1.057311}};
const double srgb_to_xyz[3][3] = {{0.412453, 0.357580, 0.180423},
{0.212671, 0.715160, 0.072169},
{0.019334, 0.119193, 0.950227}};
const double xyz_to_xyz[3][3] = {
{1.0, 0.0, 0.0},
{0.0, 1.0, 0.0},
{0.0, 0.0, 1.0},
};
const double xyz_to_ergb[3][3] = {
{2.689989, -1.276020, -0.413844},
{-1.022095, 1.978261, 0.043821},
{0.061203, -0.224411, 1.162859},
};
const double ergb_to_xyz[3][3] = {
{0.496859, 0.339094, 0.164047},
{0.256193, 0.678188, 0.065619},
{0.023290, 0.113031, 0.863978},
};
const double xyz_to_prophoto_rgb[3][3] = {{1.3459433, -0.2556075, -0.0511118},
{-0.5445989, 1.5081673, 0.0205351},
{0.0000000, 0.0000000, 1.2118128}};
const double prophoto_rgb_to_xyz[3][3] = {{0.7976749, 0.1351917, 0.0313534},
{0.2880402, 0.7118741, 0.0000857},
{0.0000000, 0.0000000, 0.8252100}};
const double xyz_to_aces2065_1[3][3] = {{1.0498110175, 0.0000000000, -0.0000974845},
{-0.4959030231, 1.3733130458, 0.0982400361},
{0.0000000000, 0.0000000000, 0.9912520182}};
const double aces2065_1_to_xyz[3][3] = {{0.9525523959, 0.0000000000, 0.0000936786},
{0.3439664498, 0.7281660966, -0.0721325464},
{0.0000000000, 0.0000000000, 1.0088251844}};
const double xyz_to_rec2020[3][3] = {{1.7166511880, -0.3556707838, -0.2533662814},
{-0.6666843518, 1.6164812366, 0.0157685458},
{0.0176398574, -0.0427706133, 0.9421031212}};
const double rec2020_to_xyz[3][3] = {{0.6369580483, 0.1446169036, 0.1688809752},
{0.2627002120, 0.6779980715, 0.0593017165},
{0.0000000000, 0.0280726930, 1.0609850577}};
const double xyz_to_dcip3[3][3] = {{2.4931748, -0.93126315, -0.40265882},
{-0.82950425, 1.7626965, 0.023625137},
{0.035853732, -0.07618918, 0.9570952}};
const double dcip3_to_xyz[3][3] = {{0.48663378, 0.26566276, 0.19817366},
{0.22900413, 0.69172573, 0.079269454},
{0., 0.04511256, 1.0437145}};
double cie_interp(const double *data, double x) {
x -= CIE_LAMBDA_MIN;
x *= (CIE_SAMPLES - 1) / (CIE_LAMBDA_MAX - CIE_LAMBDA_MIN);
int offset = (int)x;
if (offset < 0)
offset = 0;
if (offset > CIE_SAMPLES - 2)
offset = CIE_SAMPLES - 2;
double weight = x - offset;
return (1.0 - weight) * data[offset] + weight * data[offset + 1];
}
// LU decomposition & triangular solving code lifted from Wikipedia
/* INPUT: A - array of pointers to rows of a square matrix having dimension N
* Tol - small tolerance number to detect failure when the matrix is near
* degenerate OUTPUT: Matrix A is changed, it contains both matrices L-E and U
* as A=(L-E)+U such that P*A=L*U. The permutation matrix is not stored as a
* matrix, but in an integer vector P of size N+1 containing column indexes
* where the permutation matrix has "1". The last element P[N]=S+N, where S is
* the number of row exchanges needed for determinant computation, det(P)=(-1)^S
*/
int LUPDecompose(double **A, int N, double Tol, int *P) {
int i, j, k, imax;
double maxA, *ptr, absA;
for (i = 0; i <= N; i++)
P[i] = i; // Unit permutation matrix, P[N] initialized with N
for (i = 0; i < N; i++) {
maxA = 0.0;
imax = i;
for (k = i; k < N; k++)
if ((absA = fabs(A[k][i])) > maxA) {
maxA = absA;
imax = k;
}
if (maxA < Tol)
return 0; // failure, matrix is degenerate
if (imax != i) {
// pivoting P
j = P[i];
P[i] = P[imax];
P[imax] = j;
// pivoting rows of A
ptr = A[i];
A[i] = A[imax];
A[imax] = ptr;
// counting pivots starting from N (for determinant)
P[N]++;
}
for (j = i + 1; j < N; j++) {
A[j][i] /= A[i][i];
for (k = i + 1; k < N; k++)
A[j][k] -= A[j][i] * A[i][k];
}
}
return 1; // decomposition done
}
/* INPUT: A,P filled in LUPDecompose; b - rhs vector; N - dimension
* OUTPUT: x - solution vector of A*x=b
*/
void LUPSolve(double **A, int *P, double *b, int N, double *x) {
for (int i = 0; i < N; i++) {
x[i] = b[P[i]];
for (int k = 0; k < i; k++)
x[i] -= A[i][k] * x[k];
}
for (int i = N - 1; i >= 0; i--) {
for (int k = i + 1; k < N; k++)
x[i] -= A[i][k] * x[k];
x[i] = x[i] / A[i][i];
}
}
#if defined(_OPENMP)
#define RGB2SPEC_USE_OPENMP 1
#elif defined(__APPLE__)
#define RGB2SPEC_USE_GCD 1
#include <dispatch/dispatch.h>
#endif
/// Discretization of quadrature scheme
#define CIE_FINE_SAMPLES ((CIE_SAMPLES - 1) * 3 + 1)
#define RGB2SPEC_EPSILON 1e-4
/// Precomputed tables for fast spectral -> RGB conversion
double lambda_tbl[CIE_FINE_SAMPLES], rgb_tbl[3][CIE_FINE_SAMPLES], rgb_to_xyz[3][3],
xyz_to_rgb[3][3], xyz_whitepoint[3];
/// Currently supported gamuts
enum Gamut {
SRGB,
ProPhotoRGB,
ACES2065_1,
REC2020,
ERGB,
XYZ,
DCI_P3,
NO_GAMUT,
};
double sigmoid(double x) {
return 0.5 * x / std::sqrt(1.0 + x * x) + 0.5;
}
double smoothstep(double x) {
return x * x * (3.0 - 2.0 * x);
}
double sqr(double x) {
return x * x;
}
void cie_lab(double *p) {
double X = 0.0, Y = 0.0, Z = 0.0, Xw = xyz_whitepoint[0], Yw = xyz_whitepoint[1],
Zw = xyz_whitepoint[2];
for (int j = 0; j < 3; ++j) {
X += p[j] * rgb_to_xyz[0][j];
Y += p[j] * rgb_to_xyz[1][j];
Z += p[j] * rgb_to_xyz[2][j];
}
auto f = [](double t) -> double {
double delta = 6.0 / 29.0;
if (t > delta * delta * delta)
return cbrt(t);
else
return t / (delta * delta * 3.0) + (4.0 / 29.0);
};
p[0] = 116.0 * f(Y / Yw) - 16.0;
p[1] = 500.0 * (f(X / Xw) - f(Y / Yw));
p[2] = 200.0 * (f(Y / Yw) - f(Z / Zw));
}
/**
* This function precomputes tables used to convert arbitrary spectra
* to RGB (either sRGB or ProPhoto RGB)
*
* A composite quadrature rule integrates the CIE curves, reflectance, and
* illuminant spectrum over each 5nm segment in the 360..830nm range using
* Simpson's 3/8 rule (4th-order accurate), which evaluates the integrand at
* four positions per segment. While the CIE curves and illuminant spectrum are
* linear over the segment, the reflectance could have arbitrary behavior,
* hence the extra precations.
*/
void init_tables(Gamut gamut) {
memset(rgb_tbl, 0, sizeof(rgb_tbl));
memset(xyz_whitepoint, 0, sizeof(xyz_whitepoint));
double h = (CIE_LAMBDA_MAX - CIE_LAMBDA_MIN) / (CIE_FINE_SAMPLES - 1);
const double *illuminant = nullptr;
switch (gamut) {
case SRGB:
illuminant = cie_d65;
memcpy(xyz_to_rgb, xyz_to_srgb, sizeof(double) * 9);
memcpy(rgb_to_xyz, srgb_to_xyz, sizeof(double) * 9);
break;
case ERGB:
illuminant = cie_e;
memcpy(xyz_to_rgb, xyz_to_ergb, sizeof(double) * 9);
memcpy(rgb_to_xyz, ergb_to_xyz, sizeof(double) * 9);
break;
case XYZ:
illuminant = cie_e;
memcpy(xyz_to_rgb, xyz_to_xyz, sizeof(double) * 9);
memcpy(rgb_to_xyz, xyz_to_xyz, sizeof(double) * 9);
break;
case ProPhotoRGB:
illuminant = cie_d50;
memcpy(xyz_to_rgb, xyz_to_prophoto_rgb, sizeof(double) * 9);
memcpy(rgb_to_xyz, prophoto_rgb_to_xyz, sizeof(double) * 9);
break;
case ACES2065_1:
illuminant = cie_d60;
memcpy(xyz_to_rgb, xyz_to_aces2065_1, sizeof(double) * 9);
memcpy(rgb_to_xyz, aces2065_1_to_xyz, sizeof(double) * 9);
break;
case REC2020:
illuminant = cie_d65;
memcpy(xyz_to_rgb, xyz_to_rec2020, sizeof(double) * 9);
memcpy(rgb_to_xyz, rec2020_to_xyz, sizeof(double) * 9);
break;
case DCI_P3:
illuminant = cie_d65;
memcpy(xyz_to_rgb, xyz_to_dcip3, sizeof(double) * 9);
memcpy(rgb_to_xyz, dcip3_to_xyz, sizeof(double) * 9);
break;
default:
throw std::runtime_error("init_gamut(): invalid/unsupported gamut.");
}
for (int i = 0; i < CIE_FINE_SAMPLES; ++i) {
double lambda = CIE_LAMBDA_MIN + i * h;
double xyz[3] = {cie_interp(cie_x, lambda), cie_interp(cie_y, lambda),
cie_interp(cie_z, lambda)},
I = cie_interp(illuminant, lambda);
double weight = 3.0 / 8.0 * h;
if (i == 0 || i == CIE_FINE_SAMPLES - 1)
;
else if ((i - 1) % 3 == 2)
weight *= 2.f;
else
weight *= 3.f;
lambda_tbl[i] = lambda;
for (int k = 0; k < 3; ++k)
for (int j = 0; j < 3; ++j)
rgb_tbl[k][i] += xyz_to_rgb[k][j] * xyz[j] * I * weight;
for (int i = 0; i < 3; ++i)
xyz_whitepoint[i] += xyz[i] * I * weight;
}
}
void eval_residual(const double *coeffs, const double *rgb, double *residual) {
double out[3] = {0.0, 0.0, 0.0};
for (int i = 0; i < CIE_FINE_SAMPLES; ++i) {
/* Scale lambda to 0..1 range */
double lambda =
(lambda_tbl[i] - CIE_LAMBDA_MIN) / (CIE_LAMBDA_MAX - CIE_LAMBDA_MIN);
/* Polynomial */
double x = 0.0;
for (int i = 0; i < 3; ++i)
x = x * lambda + coeffs[i];
/* Sigmoid */
double s = sigmoid(x);
/* Integrate against precomputed curves */
for (int j = 0; j < 3; ++j)
out[j] += rgb_tbl[j][i] * s;
}
cie_lab(out);
memcpy(residual, rgb, sizeof(double) * 3);
cie_lab(residual);
for (int j = 0; j < 3; ++j)
residual[j] -= out[j];
}
void eval_jacobian(const double *coeffs, const double *rgb, double **jac) {
double r0[3], r1[3], tmp[3];
for (int i = 0; i < 3; ++i) {
memcpy(tmp, coeffs, sizeof(double) * 3);
tmp[i] -= RGB2SPEC_EPSILON;
eval_residual(tmp, rgb, r0);
memcpy(tmp, coeffs, sizeof(double) * 3);
tmp[i] += RGB2SPEC_EPSILON;
eval_residual(tmp, rgb, r1);
for (int j = 0; j < 3; ++j)
jac[j][i] = (r1[j] - r0[j]) * 1.0 / (2 * RGB2SPEC_EPSILON);
}
}
double gauss_newton(const double rgb[3], double coeffs[3], int it = 15) {
double r = 0;
for (int i = 0; i < it; ++i) {
double J0[3], J1[3], J2[3], *J[3] = {J0, J1, J2};
double residual[3];
eval_residual(coeffs, rgb, residual);
eval_jacobian(coeffs, rgb, J);
int P[4];
int rv = LUPDecompose(J, 3, 1e-15, P);
if (rv != 1) {
std::cout << "RGB " << rgb[0] << " " << rgb[1] << " " << rgb[2] << std::endl;
std::cout << "-> " << coeffs[0] << " " << coeffs[1] << " " << coeffs[2]
<< std::endl;
throw std::runtime_error("LU decomposition failed!");
}
double x[3];
LUPSolve(J, P, residual, 3, x);
r = 0.0;
for (int j = 0; j < 3; ++j) {
coeffs[j] -= x[j];
r += residual[j] * residual[j];
}
double max = std::max(std::max(coeffs[0], coeffs[1]), coeffs[2]);
if (max > 200) {
for (int j = 0; j < 3; ++j)
coeffs[j] *= 200 / max;
}
if (r < 1e-6)
break;
}
return std::sqrt(r);
}
static Gamut parse_gamut(const char *str) {
if (!strcasecmp(str, "sRGB"))
return SRGB;
if (!strcasecmp(str, "eRGB"))
return ERGB;
if (!strcasecmp(str, "XYZ"))
return XYZ;
if (!strcasecmp(str, "ProPhotoRGB"))
return ProPhotoRGB;
if (!strcasecmp(str, "ACES2065_1"))
return ACES2065_1;
if (!strcasecmp(str, "REC2020"))
return REC2020;
if (!strcasecmp(str, "DCI_P3"))
return DCI_P3;
return NO_GAMUT;
}
/* hack: below is a copy of enough of util/parallel.* to be able to run
ParallelFor to generate the tables. Note that we don't want to #include
<util/parallel.h>, since we'd end up spending lots of time regenerating
these tables whenever that header file changed.
*/
void ParallelFor(int64_t start, int64_t end, std::function<void(int64_t, int64_t)> func,
const char *progressName = nullptr);
inline void ParallelFor(int64_t start, int64_t end, std::function<void(int64_t)> func,
const char *progressName = nullptr) {
ParallelFor(
start, end,
[&func](int64_t start, int64_t end) {
for (int64_t i = start; i < end; ++i)
func(i);
},
progressName);
}
class ParallelJob {
public:
virtual ~ParallelJob() { assert(removed); }
// *lock should be locked going in and and unlocked coming out.
virtual void RunStep(std::unique_lock<std::mutex> *lock) = 0;
virtual bool HaveWork() const = 0;
bool Finished() const { return !HaveWork() && activeWorkers == 0; }
private:
friend class ThreadPool;
ParallelJob *prev = nullptr, *next = nullptr;
int activeWorkers = 0;
bool removed = false;
};
class ThreadPool {
public:
explicit ThreadPool(int nThreads);
~ThreadPool();
size_t size() const { return threads.size(); }
std::unique_lock<std::mutex> AddToJobList(ParallelJob *job);
void RemoveFromJobList(ParallelJob *job);
void WorkOrWait(std::unique_lock<std::mutex> *lock);
private:
void workerFunc(int tIndex);
ParallelJob *jobList = nullptr;
// Protects jobList
mutable std::mutex jobListMutex;
// Signaled both when a new job is added to the list and when a job has
// finished.
std::condition_variable jobListCondition;
std::vector<std::thread> threads;
bool shutdownThreads = false;
};
static std::unique_ptr<ThreadPool> threadPool;
int AvailableCores() {
return std::max<int>(1, std::thread::hardware_concurrency());
}
int RunningThreads() {
return threadPool ? (1 + threadPool->size()) : 1;
}
ThreadPool::ThreadPool(int nThreads) {
// Launch one fewer worker thread than the total number we want doing
// work, since the main thread helps out, too.
for (int i = 0; i < nThreads - 1; ++i)
threads.push_back(std::thread(&ThreadPool::workerFunc, this, i + 1));
}
ThreadPool::~ThreadPool() {
if (threads.empty())
return;
{
std::lock_guard<std::mutex> lock(jobListMutex);
shutdownThreads = true;
jobListCondition.notify_all();
}
for (std::thread &thread : threads)
thread.join();
}
std::unique_lock<std::mutex> ThreadPool::AddToJobList(ParallelJob *job) {
std::unique_lock<std::mutex> lock(jobListMutex);
if (jobList != nullptr)
jobList->prev = job;
job->next = jobList;
jobList = job;
jobListCondition.notify_all();
return lock;
}
void ThreadPool::RemoveFromJobList(ParallelJob *job) {
assert(!job->removed);
if (job->prev != nullptr) {
job->prev->next = job->next;
} else {
assert(jobList == job);
jobList = job->next;
}
if (job->next != nullptr)
job->next->prev = job->prev;
job->removed = true;
}
void ThreadPool::workerFunc(int tIndex) {
std::unique_lock<std::mutex> lock(jobListMutex);
while (!shutdownThreads)
WorkOrWait(&lock);
}
void ThreadPool::WorkOrWait(std::unique_lock<std::mutex> *lock) {
assert(lock->owns_lock());
ParallelJob *job = jobList;
while ((job != nullptr) && !job->HaveWork())
job = job->next;
if (job != nullptr) {
// Run a chunk of loop iterations for _loop_
job->activeWorkers++;
job->RunStep(lock);
assert(!lock->owns_lock());
lock->lock();
// Update _loop_ to reflect completion of iterations
job->activeWorkers--;
if (job->Finished())
jobListCondition.notify_all();
} else
// Wait for something to change (new work, or this loop being
// finished).
jobListCondition.wait(*lock);
}
class ParallelForLoop1D : public ParallelJob {
public:
ParallelForLoop1D(int64_t start, int64_t end, int chunkSize,
std::function<void(int64_t, int64_t)> func)
: func(std::move(func)), nextIndex(start), maxIndex(end), chunkSize(chunkSize) {}
bool HaveWork() const { return nextIndex < maxIndex; }
void RunStep(std::unique_lock<std::mutex> *lock);
private:
std::function<void(int64_t, int64_t)> func;
int64_t nextIndex;
int64_t maxIndex;
int chunkSize;
};
void ParallelForLoop1D::RunStep(std::unique_lock<std::mutex> *lock) {
// Find the set of loop iterations to run next
int64_t indexStart = nextIndex;
int64_t indexEnd = std::min(indexStart + chunkSize, maxIndex);
// Update _loop_ to reflect iterations this thread will run
nextIndex = indexEnd;
if (!HaveWork())
threadPool->RemoveFromJobList(this);
lock->unlock();
// Run loop indices in _[indexStart, indexEnd)_
func(indexStart, indexEnd);
}
void ParallelFor(int64_t start, int64_t end, std::function<void(int64_t, int64_t)> func,
const char *progressName) {
assert(threadPool);
int64_t chunkSize = std::max<int64_t>(1, (end - start) / (8 * RunningThreads()));
// Create and enqueue _ParallelJob_ for this loop
ParallelForLoop1D loop(start, end, chunkSize, std::move(func));
std::unique_lock<std::mutex> lock = threadPool->AddToJobList(&loop);
// Help out with parallel loop iterations in the current thread
while (!loop.Finished())
threadPool->WorkOrWait(&lock);
}
int main(int argc, char **argv) {
if (argc < 3) {
printf("Syntax: rgb2spec_opt <resolution> <output> [<gamut>]\n"
"where <gamut> is one of "
"sRGB,eRGB,XYZ,ProPhotoRGB,ACES2065_1,REC2020\n");
exit(-1);
}
Gamut gamut = SRGB;
if (argc > 3)
gamut = parse_gamut(argv[3]);
if (gamut == NO_GAMUT) {
fprintf(stderr, "Could not parse gamut `%s'!\n", argv[3]);
exit(-1);
}
init_tables(gamut);
const int res = atoi(argv[1]);
if (res == 0) {
printf("Invalid resolution!\n");
exit(-1);
}
int nThreads = AvailableCores();
threadPool = std::make_unique<ThreadPool>(nThreads);
printf("Optimizing %s spectra...\n", argv[3]);
fflush(stdout);
float *scale = new float[res];
for (int k = 0; k < res; ++k)
scale[k] = (float)smoothstep(smoothstep(k / double(res - 1)));
size_t bufsize = 3 * 3 * res * res * res;
float *out = new float[bufsize];
for (int l = 0; l < 3; ++l) {
ParallelFor(0, res, [&](size_t j) {
const double y = j / double(res - 1);
fflush(stdout);
for (int i = 0; i < res; ++i) {
const double x = i / double(res - 1);
double coeffs[3], rgb[3];
memset(coeffs, 0, sizeof(double) * 3);
int start = res / 5;
for (int k = start; k < res; ++k) {
double b = (double)scale[k];
rgb[l] = b;
rgb[(l + 1) % 3] = x * b;
rgb[(l + 2) % 3] = y * b;
double resid = gauss_newton(rgb, coeffs);
(void)resid;
double c0 = 360.0, c1 = 1.0 / (830.0 - 360.0);
double A = coeffs[0], B = coeffs[1], C = coeffs[2];
int idx = ((l * res + k) * res + j) * res + i;
out[3 * idx + 0] = float(A * (sqr(c1)));
out[3 * idx + 1] = float(B * c1 - 2 * A * c0 * (sqr(c1)));
out[3 * idx + 2] = float(C - B * c0 * c1 + A * (sqr(c0 * c1)));
// out[3*idx + 2] = resid;
}
memset(coeffs, 0, sizeof(double) * 3);
for (int k = start; k >= 0; --k) {
double b = (double)scale[k];
rgb[l] = b;
rgb[(l + 1) % 3] = x * b;
rgb[(l + 2) % 3] = y * b;
double resid = gauss_newton(rgb, coeffs);
(void)resid;
double c0 = 360.0, c1 = 1.0 / (830.0 - 360.0);
double A = coeffs[0], B = coeffs[1], C = coeffs[2];
int idx = ((l * res + k) * res + j) * res + i;
out[3 * idx + 0] = float(A * (sqr(c1)));
out[3 * idx + 1] = float(B * c1 - 2 * A * c0 * (sqr(c1)));
out[3 * idx + 2] = float(C - B * c0 * c1 + A * (sqr(c0 * c1)));
// out[3*idx + 2] = resid;
}
}
});
}
FILE *f = fopen(argv[2], "w");
if (f == nullptr)
throw std::runtime_error("Could not create file!");
fprintf(f, "#include <pbrt/pbrt.h>\n");
fprintf(f, "namespace pbrt {\n");
fprintf(f, "extern PBRT_CONST int %sToSpectrumTable_Res = %d;\n", argv[3], res);
fprintf(f, "extern PBRT_CONST float %sToSpectrumTable_Scale[%d] = {\n", argv[3], res);
for (int i = 0; i < res; ++i)
fprintf(f, "%.9g, ", scale[i]);
fprintf(f, "};\n");
fprintf(f, "extern PBRT_CONST float %sToSpectrumTable_Data[%d] = {\n", argv[3],
(int)bufsize);
for (int i = 0; i < bufsize; ++i)
fprintf(f, "%.9g,%c", out[i], ((i + 1) % 9) == 8 ? '\n' : ' ');
fprintf(f, "};\n");
fprintf(f, "} // namespace pbrt\n");
fclose(f);
threadPool.reset();
}

455
src/pbrt/cmd/soac.cpp Normal file
View file

@ -0,0 +1,455 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
/*
TODO:
- how to do float4, fancy packing tricks?
flat int:32;
float float:32
struct Foo { int a, b; float c, d; };
would be nice to load as a big float4...
- mechanism to not store fields that are easily recomputed...
maybe the answer is to just do that--recompute only when needed--in the
original struct!
*/
#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <fstream>
#include <functional>
#include <map>
#include <set>
#include <string>
#include <utility>
#include <vector>
int line = 1;
#ifdef __GNUG__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-security"
#endif // __GNUG__
const char *filename;
template <typename... Args>
static void error(const char *fmt, Args... args) {
fprintf(stderr, "%s:%d: ", filename, line);
fprintf(stderr, fmt, std::forward<Args>(args)...);
exit(1);
}
#ifdef __GNUG__
#pragma GCC diagnostic pop
#endif // __GNUG__
struct OptionalString {
OptionalString() = default;
OptionalString(std::string s) : s(s), set(true) {}
operator bool() const { return set; }
operator std::string() const {
assert(set);
return s;
}
bool operator==(const char *str) const {
assert(set);
return s == str;
}
bool operator!=(const char *str) const {
assert(set);
return s != str;
}
std::string s;
bool set = false;
};
struct Member {
std::string type;
bool isConst = false;
int numPointers = 0;
std::string GetType() const {
std::string s;
if (isConst)
s = "const ";
s += type;
for (int i = 0; i < numPointers; ++i)
s += "*";
return s;
}
std::vector<std::string> names;
std::vector<std::string> arraySizes;
};
struct SOA {
std::string type;
std::string templateType;
std::vector<Member> members;
};
int main(int argc, char *argv[]) {
if (argc != 2)
error("usage: soac <soac filename>\n");
// Read the file
filename = argv[1];
std::ifstream ifs(filename, std::ios::binary);
if (!ifs) {
error("%s: %s", filename, strerror(errno));
return {};
}
std::string fileContents((std::istreambuf_iterator<char>(ifs)),
(std::istreambuf_iterator<char>()));
int pos = 0;
auto eof = [&]() { return pos == fileContents.size(); };
auto getc = [&]() {
assert(!eof());
if (fileContents[pos] == '\n')
++line;
return fileContents[pos++];
};
auto ungetc = [&]() {
assert(pos > 0);
--pos;
if (fileContents[pos] == '\n')
--line;
};
std::function<OptionalString(bool)> getToken;
getToken = [&](bool eofOk) -> OptionalString {
if (eof()) {
if (eofOk)
return OptionalString();
else
error("Premature end of file.\n");
}
// Skip whitespace
while (true) {
if (eof()) {
if (eofOk)
return OptionalString();
else
error("Premature end of file.\n");
}
char c = getc();
if (!isspace(c)) {
ungetc();
break;
}
}
assert(!eof());
std::string s;
s += getc();
if (s[0] == '/' && !eof()) {
if (getc() == '/') {
// skip to EOL
while (true) {
if (eof()) {
if (eofOk)
return OptionalString();
else
error("Premature end of file.\n");
}
if (getc() == '\n')
return getToken(eofOk);
}
} else
ungetc();
}
if (!isalpha(s[0]) && s[0] != '_')
return OptionalString(s);
while (!eof()) {
char c = getc();
if (!isalnum(c) && c != '_') {
// end of token
ungetc();
break;
}
s += c;
}
return OptionalString(s);
};
std::set<std::string> flatTypes, externSOA;
auto isFlatType = [&](std::string type) {
return flatTypes.find(type) != flatTypes.end();
};
// keep as a vector so that we can emit them in the order they were
// defined.
std::vector<SOA> soaTypes;
auto soaTypeExists = [&](std::string type) {
for (const auto &s : soaTypes)
if (s.type == type)
return true;
return externSOA.find(type) != externSOA.end();
};
auto expect = [&](const char *str) {
OptionalString tok = getToken(true);
if (!tok)
error("Premature end of file; expected \"%s\".\n", str);
if (tok != str)
error("Syntax error: expected \"%s\".\n", str);
};
while (true) {
OptionalString os = getToken(true);
if (!os)
break;
std::string tok = os.s;
if (tok == "flat") {
OptionalString typeTok = getToken(false);
std::string type = typeTok;
if (flatTypes.find(type) != flatTypes.end())
error("%s flat type redeclared.\n", type.c_str());
flatTypes.insert(type);
expect(";");
} else if (tok == "soa") {
SOA soa;
OptionalString typeTok = getToken(false);
soa.type = (std::string)typeTok;
if (!isalpha(soa.type[0]))
error("%s: invalid type identifier.\n", soa.type.c_str());
if (soaTypeExists(soa.type))
error("%s: type redefined.\n", soa.type.c_str());
OptionalString tok = getToken(false);
if (tok == "<") {
tok = getToken(false);
soa.templateType = (std::string)tok;
if (!isalpha(soa.templateType[0]))
error("%s: invalid type identifier.\n", soa.templateType.c_str());
expect(">");
expect("{");
} else if (tok == ";") {
externSOA.insert(soa.type);
continue;
} else if (tok != "{")
error("Syntax error: expected \"{\".\n");
while (true) {
OptionalString tok = getToken(false);
if (tok == "}")
break;
Member member;
member.type = (std::string)tok;
// Hacks to parse things like const Foo *
if (member.type == "const") {
member.isConst = true;
tok = getToken(false);
member.type = (std::string)tok;
}
while (true) {
tok = getToken(false);
if (tok == "*")
++member.numPointers;
else
break;
}
// Don't check the type if it's a pointer; we already know
// how to SOA pointers..
if (member.numPointers == 0 && member.type != soa.templateType &&
flatTypes.find(member.type) == flatTypes.end() &&
!soaTypeExists(member.type))
error("%s: undefined type\n", member.type.c_str());
while (true) {
std::string memberName = tok;
member.names.push_back(memberName);
member.arraySizes.push_back(""); // assume no array for starters
tok = getToken(false);
if (tok == "[") {
tok = getToken(false);
// just pass it through without interpretation
member.arraySizes[member.arraySizes.size() - 1] =
(std::string)tok;
expect("]");
tok = getToken(false);
}
if (tok == ";")
break;
else if (tok == ",")
tok = getToken(false); // and go around again...
}
if (member.names.empty())
error("No members specified after type declaration.\n");
soa.members.push_back(member);
}
expect(";");
soaTypes.push_back(soa);
} else
error("%s: invalid token", tok.c_str());
}
// And now emit them...
printf("// SOA definitions automatically generated by soac\n");
printf("// DO NOT EDIT THIS FILE MANUALLY\n\n");
printf("template <typename T> struct SOA;\n\n");
for (const auto &soa : soaTypes) {
if (!soa.templateType.empty())
printf("template <typename %s> struct SOA<%s<%s>> {\n",
soa.templateType.c_str(), soa.type.c_str(), soa.templateType.c_str());
else
printf("template <> struct SOA<%s> {\n", soa.type.c_str());
// Constructor
printf(" SOA() = default;\n");
printf(" SOA(int n, Allocator alloc) : nAlloc(n) {\n");
for (const auto &member : soa.members) {
for (int i = 0; i < member.names.size(); ++i) {
std::string name = member.names[i];
if (!member.arraySizes[i].empty()) {
printf(" for (int i = 0; i < %s; ++i)\n",
member.arraySizes[i].c_str());
if (isFlatType(member.type) || member.numPointers > 0)
printf(
" this->%s[i] = alloc.allocate_object<%s>(n);\n",
name.c_str(), member.GetType().c_str());
else {
assert(member.isConst == false && member.numPointers == 0);
printf(" this->%s[i] = SOA<%s>(n, alloc);\n", name.c_str(),
member.type.c_str());
}
} else {
if (isFlatType(member.type) || member.numPointers > 0)
printf(" this->%s = alloc.allocate_object<%s>(n);\n",
name.c_str(), member.GetType().c_str());
else
printf(" this->%s = SOA<%s>(n, alloc);\n", name.c_str(),
member.type.c_str());
}
}
}
printf(" }\n\n");
// operator[] madness...
printf(" struct GetSetIndirector {\n");
if (!soa.templateType.empty()) {
printf(" PBRT_CPU_GPU\n");
printf(" operator %s<%s>() const {\n", soa.type.c_str(),
soa.templateType.c_str());
printf(" %s<%s> r;\n", soa.type.c_str(), soa.templateType.c_str());
} else {
printf(" PBRT_CPU_GPU\n");
printf(" operator %s() const {\n", soa.type.c_str());
printf(" %s r;\n", soa.type.c_str());
}
for (const auto &member : soa.members)
for (int i = 0; i < member.names.size(); ++i) {
std::string name = member.names[i];
if (!member.arraySizes[i].empty()) {
printf(" for (int c = 0; c < %s; ++c)\n",
member.arraySizes[i].c_str());
printf(" r.%s[c] = soa->%s[c][i];\n", name.c_str(),
name.c_str());
} else
printf(" r.%s = soa->%s[i];\n", name.c_str(),
name.c_str());
}
printf(" return r;\n");
printf(" }\n");
printf(" PBRT_CPU_GPU\n");
if (!soa.templateType.empty())
printf(" void operator=(const %s<%s> &a) {\n", soa.type.c_str(),
soa.templateType.c_str());
else
printf(" void operator=(const %s &a) {\n", soa.type.c_str());
for (const auto &member : soa.members)
for (int i = 0; i < member.names.size(); ++i) {
std::string name = member.names[i];
if (!member.arraySizes[i].empty()) {
printf(" for (int c = 0; c < %s; ++c)\n",
member.arraySizes[i].c_str());
printf(" soa->%s[c][i] = a.%s[c];\n", name.c_str(),
name.c_str());
} else
printf(" soa->%s[i] = a.%s;\n", name.c_str(),
name.c_str());
}
printf(" }\n\n");
printf(" SOA *soa;\n");
printf(" int i;\n");
printf(" };\n\n");
printf(" PBRT_CPU_GPU\n");
printf(" GetSetIndirector operator[](int i) {\n");
printf(" DCHECK_LT(i, nAlloc);\n");
printf(" return GetSetIndirector{this, i};\n");
printf(" }\n");
printf(" PBRT_CPU_GPU\n");
if (!soa.templateType.empty()) {
printf(" %s<%s> operator[](int i) const {\n", soa.type.c_str(),
soa.templateType.c_str());
printf(" DCHECK_LT(i, nAlloc);\n");
printf(" %s<%s> r;\n", soa.type.c_str(), soa.templateType.c_str());
} else {
printf(" %s operator[](int i) const {\n", soa.type.c_str());
printf(" DCHECK_LT(i, nAlloc);\n");
printf(" %s r;\n", soa.type.c_str());
}
for (const auto &member : soa.members)
for (int i = 0; i < member.names.size(); ++i) {
std::string name = member.names[i];
if (!member.arraySizes[i].empty()) {
printf(" for (int c = 0; c < %s; ++c)\n",
member.arraySizes[i].c_str());
printf(" r.%s[c] = this->%s[c][i];\n", name.c_str(),
name.c_str());
} else
printf(" r.%s = this->%s[i];\n", name.c_str(), name.c_str());
}
printf(" return r;\n");
printf(" }\n");
printf("\n");
// Member definitions
printf(" int nAlloc;\n");
for (const auto &member : soa.members) {
for (int i = 0; i < member.names.size(); ++i) {
std::string name = member.names[i];
if (!member.arraySizes[i].empty()) {
if (isFlatType(member.type) || member.numPointers > 0)
printf(" %s * /*__restrict__*/ %s[%s];\n",
member.GetType().c_str(), name.c_str(),
member.arraySizes[i].c_str());
else
printf(" SOA<%s> %s[%s];\n", member.type.c_str(), name.c_str(),
member.arraySizes[i].c_str());
} else {
if (isFlatType(member.type) || member.numPointers > 0)
printf(" %s * __restrict__ %s;\n", member.GetType().c_str(),
name.c_str());
else
printf(" SOA<%s> %s;\n", member.type.c_str(), name.c_str());
}
}
}
printf("};\n\n");
}
}

File diff suppressed because it is too large Load diff

108
src/pbrt/cpu/accelerators.h Normal file
View file

@ -0,0 +1,108 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_CPU_ACCELERATORS_H
#define PBRT_CPU_ACCELERATORS_H
#include <pbrt/pbrt.h>
#include <pbrt/cpu/primitive.h>
#include <atomic>
#include <memory>
#include <vector>
namespace pbrt {
PrimitiveHandle CreateAccelerator(const std::string &name,
std::vector<PrimitiveHandle> prims,
const ParameterDictionary &parameters);
struct BVHBuildNode;
struct BVHPrimitiveInfo;
struct LinearBVHNode;
struct MortonPrimitive;
// BVHAccel Definition
class BVHAccel {
public:
// BVHAccel Public Types
enum class SplitMethod { SAH, HLBVH, Middle, EqualCounts };
// BVHAccel Public Methods
BVHAccel(std::vector<PrimitiveHandle> p, int maxPrimsInNode = 1,
SplitMethod splitMethod = SplitMethod::SAH);
static BVHAccel *Create(std::vector<PrimitiveHandle> prims,
const ParameterDictionary &parameters);
Bounds3f Bounds() const;
pstd::optional<ShapeIntersection> Intersect(const Ray &ray, Float tMax) const;
bool IntersectP(const Ray &ray, Float tMax) const;
private:
// BVHAccel Private Methods
BVHBuildNode *recursiveBuild(std::vector<Allocator> &threadAllocators,
std::vector<BVHPrimitiveInfo> &primitiveInfo, int start,
int end, std::atomic<int> *totalNodes,
std::vector<PrimitiveHandle> &orderedPrims,
std::atomic<int> *orderedPrimsOffset);
BVHBuildNode *HLBVHBuild(Allocator alloc,
const std::vector<BVHPrimitiveInfo> &primitiveInfo,
std::atomic<int> *totalNodes,
std::vector<PrimitiveHandle> &orderedPrims);
BVHBuildNode *emitLBVH(BVHBuildNode *&buildNodes,
const std::vector<BVHPrimitiveInfo> &primitiveInfo,
MortonPrimitive *mortonPrims, int nPrimitives, int *totalNodes,
std::vector<PrimitiveHandle> &orderedPrims,
std::atomic<int> *orderedPrimsOffset, int bitIndex);
BVHBuildNode *buildUpperSAH(Allocator alloc,
std::vector<BVHBuildNode *> &treeletRoots, int start,
int end, std::atomic<int> *totalNodes) const;
int flattenBVHTree(BVHBuildNode *node, int *offset);
// BVHAccel Private Members
int maxPrimsInNode;
SplitMethod splitMethod;
std::vector<PrimitiveHandle> primitives;
LinearBVHNode *nodes = nullptr;
};
struct KdAccelNode;
struct BoundEdge;
// KdTreeAccel Definition
class KdTreeAccel {
public:
// KdTreeAccel Public Methods
KdTreeAccel(std::vector<PrimitiveHandle> p, int isectCost = 80, int traversalCost = 1,
Float emptyBonus = 0.5, int maxPrims = 1, int maxDepth = -1);
static KdTreeAccel *Create(std::vector<PrimitiveHandle> prims,
const ParameterDictionary &parameters);
pstd::optional<ShapeIntersection> Intersect(const Ray &ray, Float tMax) const;
Bounds3f Bounds() const { return bounds; }
bool IntersectP(const Ray &ray, Float tMax) const;
private:
// KdTreeAccel Private Methods
void buildTree(int nodeNum, const Bounds3f &bounds,
const std::vector<Bounds3f> &primBounds, int *primNums, int nprims,
int depth, const std::unique_ptr<BoundEdge[]> edges[3], int *prims0,
int *prims1, int badRefines = 0);
// KdTreeAccel Private Members
int isectCost, traversalCost, maxPrims;
Float emptyBonus;
std::vector<PrimitiveHandle> primitives;
std::vector<int> primitiveIndices;
KdAccelNode *nodes;
int nAllocedNodes, nextFreeNode;
Bounds3f bounds;
};
} // namespace pbrt
#endif // PBRT_CPU_ACCELERATORS_H

3281
src/pbrt/cpu/integrators.cpp Normal file

File diff suppressed because it is too large Load diff

470
src/pbrt/cpu/integrators.h Normal file
View file

@ -0,0 +1,470 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_CPU_INTEGRATORS_H
#define PBRT_CPU_INTEGRATORS_H
#include <pbrt/pbrt.h>
#include <pbrt/base/camera.h>
#include <pbrt/base/sampler.h>
#include <pbrt/bsdf.h>
#include <pbrt/cameras.h>
#include <pbrt/cpu/primitive.h>
#include <pbrt/film.h>
#include <pbrt/interaction.h>
#include <pbrt/lights.h>
#include <pbrt/lightsamplers.h>
#include <pbrt/util/lowdiscrepancy.h>
#include <pbrt/util/print.h>
#include <pbrt/util/pstd.h>
#include <pbrt/util/rng.h>
#include <pbrt/util/sampling.h>
#include <memory>
#include <ostream>
#include <string>
#include <vector>
namespace pbrt {
// Integrator Definition
class Integrator {
public:
// Integrator Public Methods
virtual ~Integrator();
static std::unique_ptr<Integrator> Create(const std::string &name,
const ParameterDictionary &parameters,
CameraHandle camera, SamplerHandle sampler,
PrimitiveHandle aggregate,
std::vector<LightHandle> lights,
const RGBColorSpace *colorSpace,
const FileLoc *loc);
virtual std::string ToString() const = 0;
const Bounds3f &SceneBounds() const { return sceneBounds; }
pstd::optional<ShapeIntersection> Intersect(const Ray &ray,
Float tMax = Infinity) const;
bool IntersectP(const Ray &ray, Float tMax = Infinity) const;
virtual void Render() = 0;
bool Unoccluded(const Interaction &p0, const Interaction &p1) const {
return !IntersectP(p0.SpawnRayTo(p1), 1 - ShadowEpsilon);
}
SampledSpectrum Tr(const Interaction &p0, const Interaction &p1,
const SampledWavelengths &lambda, RNG &rng) const;
// Integrator Public Members
std::vector<LightHandle> lights;
std::vector<LightHandle> infiniteLights;
protected:
// Integrator Private Methods
Integrator(PrimitiveHandle aggregate, std::vector<LightHandle> l)
: lights(std::move(l)), aggregate(aggregate) {
// Integrator Constructor Implementation
if (aggregate)
sceneBounds = aggregate.Bounds();
for (auto &light : lights) {
light.Preprocess(sceneBounds);
if (light.Type() == LightType::Infinite)
infiniteLights.push_back(light);
}
}
// Integrator Private Members
PrimitiveHandle aggregate;
Bounds3f sceneBounds;
};
// ImageTileIntegrator Definition
class ImageTileIntegrator : public Integrator {
public:
// ImageTileIntegrator Public Methods
ImageTileIntegrator(CameraHandle camera, SamplerHandle sampler,
PrimitiveHandle aggregate, std::vector<LightHandle> lights)
: Integrator(aggregate, lights), camera(camera), samplerPrototype(sampler) {}
void Render();
virtual void EvaluatePixelSample(const Point2i &pPixel, int sampleIndex,
SamplerHandle sampler,
ScratchBuffer &scratchBuffer) = 0;
protected:
// ImageTileIntegrator Protected Members
CameraHandle camera;
SamplerHandle samplerPrototype;
};
// RayIntegrator Definition
class RayIntegrator : public ImageTileIntegrator {
public:
// RayIntegrator Public Methods
RayIntegrator(CameraHandle camera, SamplerHandle sampler, PrimitiveHandle aggregate,
std::vector<LightHandle> lights)
: ImageTileIntegrator(camera, sampler, aggregate, lights) {}
void EvaluatePixelSample(const Point2i &pPixel, int sampleIndex,
SamplerHandle sampler, ScratchBuffer &scratchBuffer) final;
virtual SampledSpectrum Li(RayDifferential ray, SampledWavelengths &lambda,
SamplerHandle sampler, ScratchBuffer &scratchBuffer,
VisibleSurface *visibleSurface = nullptr) const = 0;
};
// RandomWalkIntegrator Definition
class RandomWalkIntegrator : public RayIntegrator {
public:
// RandomWalkIntegrator Public Methods
RandomWalkIntegrator(int maxDepth, CameraHandle camera, SamplerHandle sampler,
PrimitiveHandle aggregate, std::vector<LightHandle> lights)
: RayIntegrator(camera, sampler, aggregate, lights), maxDepth(maxDepth) {}
SampledSpectrum Li(RayDifferential ray, SampledWavelengths &lambda,
SamplerHandle sampler, ScratchBuffer &scratchBuffer,
VisibleSurface *visibleSurface = nullptr) const;
static std::unique_ptr<RandomWalkIntegrator> Create(
const ParameterDictionary &parameters, CameraHandle camera, SamplerHandle sampler,
PrimitiveHandle aggregate, std::vector<LightHandle> lights, const FileLoc *loc);
std::string ToString() const;
private:
// RandomWalkIntegrator Private Methods
SampledSpectrum RandomWalk(RayDifferential ray, SampledWavelengths &lambda,
SamplerHandle sampler, ScratchBuffer &scratchBuffer,
int depth) const;
// RandomWalkIntegrator Private Members
int maxDepth;
};
// SimplePathIntegrator Definition
class SimplePathIntegrator : public RayIntegrator {
public:
// SimplePathIntegrator Public Methods
SimplePathIntegrator(int maxDepth, bool sampleLights, bool sampleBSDF,
CameraHandle camera, SamplerHandle sampler,
PrimitiveHandle aggregate, std::vector<LightHandle> lights);
SampledSpectrum Li(RayDifferential ray, SampledWavelengths &lambda,
SamplerHandle sampler, ScratchBuffer &scratchBuffer,
VisibleSurface *visibleSurface) const;
static std::unique_ptr<SimplePathIntegrator> Create(
const ParameterDictionary &parameters, CameraHandle camera, SamplerHandle sampler,
PrimitiveHandle aggregate, std::vector<LightHandle> lights, const FileLoc *loc);
std::string ToString() const;
private:
// SimplePathIntegrator Private Members
int maxDepth;
bool sampleLights, sampleBSDF;
UniformLightSampler lightSampler;
};
// PathIntegrator Definition
class PathIntegrator : public RayIntegrator {
public:
// PathIntegrator Public Methods
PathIntegrator(int maxDepth, CameraHandle camera, SamplerHandle sampler,
PrimitiveHandle aggregate, std::vector<LightHandle> lights,
Float rrThreshold = 1, const std::string &lightSampleStrategy = "bvh",
bool regularize = false);
SampledSpectrum Li(RayDifferential ray, SampledWavelengths &lambda,
SamplerHandle sampler, ScratchBuffer &scratchBuffer,
VisibleSurface *visibleSurface) const;
static std::unique_ptr<PathIntegrator> Create(
const ParameterDictionary &parameters, CameraHandle camera, SamplerHandle sampler,
PrimitiveHandle aggregate, std::vector<LightHandle> lights, const FileLoc *loc);
std::string ToString() const;
private:
// PathIntegrator Private Methods
SampledSpectrum SampleLd(const SurfaceInteraction &intr, const BSDF &bsdf,
SampledWavelengths &lambda, SamplerHandle sampler) const;
// PathIntegrator Private Members
int maxDepth;
Float rrThreshold;
LightSamplerHandle lightSampler;
bool regularize;
};
// SimpleVolPathIntegrator Definition
class SimpleVolPathIntegrator : public RayIntegrator {
public:
// SimpleVolPathIntegrator Public Methods
SimpleVolPathIntegrator(int maxDepth, CameraHandle camera, SamplerHandle sampler,
PrimitiveHandle aggregate, std::vector<LightHandle> lights);
SampledSpectrum Li(RayDifferential ray, SampledWavelengths &lambda,
SamplerHandle sampler, ScratchBuffer &scratchBuffer,
VisibleSurface *visibleSurface) const;
static std::unique_ptr<SimpleVolPathIntegrator> Create(
const ParameterDictionary &parameters, CameraHandle camera, SamplerHandle sampler,
PrimitiveHandle aggregate, std::vector<LightHandle> lights, const FileLoc *loc);
std::string ToString() const;
private:
int maxDepth;
bool sampleLights, samplePhase;
};
// VolPathIntegrator Definition
class VolPathIntegrator : public RayIntegrator {
public:
// VolPathIntegrator Public Methods
VolPathIntegrator(int maxDepth, CameraHandle camera, SamplerHandle sampler,
PrimitiveHandle aggregate, std::vector<LightHandle> lights,
Float rrThreshold = 1,
const std::string &lightSampleStrategy = "bvh",
bool regularize = false)
: RayIntegrator(camera, sampler, aggregate, lights),
maxDepth(maxDepth),
rrThreshold(rrThreshold),
lightSampler(
LightSamplerHandle::Create(lightSampleStrategy, lights, Allocator())),
regularize(regularize) {}
SampledSpectrum Li(RayDifferential ray, SampledWavelengths &lambda,
SamplerHandle sampler, ScratchBuffer &scratchBuffer,
VisibleSurface *visibleSurface) const;
static std::unique_ptr<VolPathIntegrator> Create(
const ParameterDictionary &parameters, CameraHandle camera, SamplerHandle sampler,
PrimitiveHandle aggregate, std::vector<LightHandle> lights, const FileLoc *loc);
std::string ToString() const;
private:
// VolPathIntegrator Private Methods
SampledSpectrum SampleLd(const Interaction &intr, const BSDF *bsdf,
SampledWavelengths &lambda, SamplerHandle sampler,
const SampledSpectrum &beta,
const SampledSpectrum &pathPDF) const;
static void rescale(SampledSpectrum &beta, SampledSpectrum &pdfLight,
SampledSpectrum &pdfUni) {
if (beta.MaxComponentValue() > 0x1p24f ||
pdfLight.MaxComponentValue() > 0x1p24f ||
pdfUni.MaxComponentValue() > 0x1p24f) {
beta /= 0x1p24f;
pdfLight /= 0x1p24f;
pdfUni /= 0x1p24f;
}
}
// VolPathIntegrator Private Members
const int maxDepth;
const Float rrThreshold;
LightSamplerHandle lightSampler;
bool regularize;
};
// AOIntegrator Definition
class AOIntegrator : public RayIntegrator {
public:
// AOIntegrator Public Methods
AOIntegrator(bool cosSample, Float maxDist, CameraHandle camera,
SamplerHandle sampler, PrimitiveHandle aggregate,
std::vector<LightHandle> lights, SpectrumHandle illuminant);
SampledSpectrum Li(RayDifferential ray, SampledWavelengths &lambda,
SamplerHandle sampler, ScratchBuffer &scratchBuffer,
VisibleSurface *visibleSurface) const;
static std::unique_ptr<AOIntegrator> Create(
const ParameterDictionary &parameters, SpectrumHandle illuminant,
CameraHandle camera, SamplerHandle sampler, PrimitiveHandle aggregate,
std::vector<LightHandle> lights, const FileLoc *loc);
std::string ToString() const;
private:
bool cosSample;
Float maxDist;
SpectrumHandle illuminant;
};
// LightPathIntegrator Definition
class LightPathIntegrator : public ImageTileIntegrator {
public:
// LightPathIntegrator Public Methods
LightPathIntegrator(int maxDepth, CameraHandle camera, SamplerHandle sampler,
PrimitiveHandle aggregate, std::vector<LightHandle> lights);
void EvaluatePixelSample(const Point2i &pPixel, int sampleIndex,
SamplerHandle sampler, ScratchBuffer &scratchBuffer);
static std::unique_ptr<LightPathIntegrator> Create(
const ParameterDictionary &parameters, CameraHandle camera, SamplerHandle sampler,
PrimitiveHandle aggregate, std::vector<LightHandle> lights, const FileLoc *loc);
std::string ToString() const;
private:
// LightPathIntegrator Private Data
int maxDepth;
std::unique_ptr<PowerLightSampler> lightSampler;
};
// BDPTIntegrator Definition
struct Vertex;
class BDPTIntegrator : public RayIntegrator {
public:
// BDPTIntegrator Public Methods
BDPTIntegrator(CameraHandle camera, SamplerHandle sampler, PrimitiveHandle aggregate,
std::vector<LightHandle> lights, int maxDepth,
bool visualizeStrategies, bool visualizeWeights,
const std::string &lightSampleStrategy = "power",
bool regularize = false)
: RayIntegrator(camera, sampler, aggregate, lights),
maxDepth(maxDepth),
visualizeStrategies(visualizeStrategies),
visualizeWeights(visualizeWeights),
lightSampleStrategy(lightSampleStrategy),
lightSampler(new PowerLightSampler(lights, Allocator())),
regularize(regularize) {}
SampledSpectrum Li(RayDifferential ray, SampledWavelengths &lambda,
SamplerHandle sampler, ScratchBuffer &scratchBuffer,
VisibleSurface *visibleSurface) const;
static std::unique_ptr<BDPTIntegrator> Create(
const ParameterDictionary &parameters, CameraHandle camera, SamplerHandle sampler,
PrimitiveHandle aggregate, std::vector<LightHandle> lights, const FileLoc *loc);
std::string ToString() const;
void Render();
private:
// BDPTIntegrator Private Members
int maxDepth;
bool visualizeStrategies;
bool visualizeWeights;
std::string lightSampleStrategy;
bool regularize;
LightSamplerHandle lightSampler;
mutable std::vector<FilmHandle> weightFilms;
};
// MLTIntegrator Definition
class MLTSampler;
class MLTIntegrator : public Integrator {
public:
// MLTIntegrator Public Methods
MLTIntegrator(CameraHandle camera, PrimitiveHandle aggregate,
std::vector<LightHandle> lights, int maxDepth, int nBootstrap,
int nChains, int mutationsPerPixel, Float sigma,
Float largeStepProbability, bool regularize)
: Integrator(aggregate, lights),
lightSampler(new PowerLightSampler(lights, Allocator())),
camera(camera),
maxDepth(maxDepth),
nBootstrap(nBootstrap),
nChains(nChains),
mutationsPerPixel(mutationsPerPixel),
sigma(sigma),
largeStepProbability(largeStepProbability),
regularize(regularize) {}
void Render();
static std::unique_ptr<MLTIntegrator> Create(const ParameterDictionary &parameters,
CameraHandle camera,
PrimitiveHandle aggregate,
std::vector<LightHandle> lights,
const FileLoc *loc);
std::string ToString() const;
private:
// MLTIntegrator Constants
static constexpr int cameraStreamIndex = 0;
static constexpr int lightStreamIndex = 1;
static constexpr int connectionStreamIndex = 2;
static constexpr int nSampleStreams = 3;
// MLTIntegrator Private Methods
SampledSpectrum L(ScratchBuffer &scratchBuffer, MLTSampler &sampler, int k,
Point2f *pRaster, SampledWavelengths *lambda);
// MLTIntegrator Private Members
LightSamplerHandle lightSampler;
bool regularize;
CameraHandle camera;
int maxDepth;
int nBootstrap;
int mutationsPerPixel;
Float sigma, largeStepProbability;
int nChains;
};
// SPPMIntegrator Definition
class SPPMIntegrator : public Integrator {
public:
// SPPMIntegrator Public Methods
SPPMIntegrator(CameraHandle camera, PrimitiveHandle aggregate,
std::vector<LightHandle> lights, int nIterations,
int photonsPerIteration, int maxDepth, Float initialSearchRadius,
bool regularize, int seed, const RGBColorSpace *colorSpace)
: Integrator(aggregate, lights),
camera(camera),
initialSearchRadius(initialSearchRadius),
nIterations(nIterations),
maxDepth(maxDepth),
photonsPerIteration(photonsPerIteration > 0
? photonsPerIteration
: camera.GetFilm().PixelBounds().Area()),
regularize(regularize),
colorSpace(colorSpace),
digitPermutationsSeed(seed) {}
static std::unique_ptr<SPPMIntegrator> Create(const ParameterDictionary &parameters,
const RGBColorSpace *colorSpace,
CameraHandle camera,
PrimitiveHandle aggregate,
std::vector<LightHandle> lights,
const FileLoc *loc);
std::string ToString() const;
void Render();
private:
// SPPMIntegrator Private Methods
SampledSpectrum SampleLd(const SurfaceInteraction &intr, const BSDF &bsdf,
SampledWavelengths &lambda, SamplerHandle sampler,
LightSamplerHandle lightSampler) const;
// SPPMIntegrator Private Members
CameraHandle camera;
Float initialSearchRadius;
int digitPermutationsSeed;
int nIterations;
bool regularize;
int maxDepth;
int photonsPerIteration;
const RGBColorSpace *colorSpace;
};
} // namespace pbrt
#endif // PBRT_CPU_INTEGRATORS_H

View file

@ -0,0 +1,421 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#include <gtest/gtest.h>
#include <pbrt/cameras.h>
#include <pbrt/cpu/accelerators.h>
#include <pbrt/cpu/integrators.h>
#include <pbrt/filters.h>
#include <pbrt/lights.h>
#include <pbrt/materials.h>
#include <pbrt/options.h>
#include <pbrt/pbrt.h>
#include <pbrt/samplers.h>
#include <pbrt/shapes.h>
#include <pbrt/textures.h>
#include <pbrt/util/color.h>
#include <pbrt/util/colorspace.h>
#include <pbrt/util/image.h>
#include <pbrt/util/spectrum.h>
#include <pbrt/util/vecmath.h>
#include <memory>
using namespace pbrt;
static std::string inTestDir(const std::string &path) {
return path;
}
struct TestScene {
PrimitiveHandle aggregate;
std::vector<LightHandle> lights;
std::string description;
float expected;
};
struct TestIntegrator {
Integrator *integrator;
const FilmHandle film;
std::string description;
TestScene scene;
};
void PrintTo(const TestIntegrator &tr, ::std::ostream *os) {
*os << tr.description;
}
void CheckSceneAverage(const std::string &filename, float expected) {
pstd::optional<ImageAndMetadata> im = Image::Read(filename);
ASSERT_TRUE((bool)im);
ASSERT_EQ(im->image.NChannels(), 3);
float delta = .025;
float sum = 0;
Image &image = im->image;
for (int t = 0; t < image.Resolution()[1]; ++t)
for (int s = 0; s < image.Resolution()[0]; ++s)
for (int c = 0; c < 3; ++c)
sum += image.GetChannel(Point2i(s, t), c);
int nPixels = image.Resolution().x * image.Resolution().y * 3;
EXPECT_NEAR(expected, sum / nPixels, delta);
}
std::vector<TestScene> GetScenes() {
std::vector<TestScene> scenes;
Allocator alloc;
static Transform identity;
{
// Unit sphere, Kd = 0.5, point light I = 3.1415 at center
// -> With GI, should have radiance of 1.
ShapeHandle sphere = new Sphere(&identity, &identity,
true /* reverse orientation */, 1, -1, 1, 360);
static ConstantSpectrum cs(0.5);
SpectrumTextureHandle Kd = alloc.new_object<SpectrumConstantTexture>(&cs);
FloatTextureHandle sigma = alloc.new_object<FloatConstantTexture>(0.);
// FIXME: here and below, Materials leak...
MaterialHandle material = new DiffuseMaterial(Kd, sigma, nullptr);
MediumInterface mediumInterface;
std::vector<PrimitiveHandle> prims;
prims.push_back(PrimitiveHandle(
new GeometricPrimitive(sphere, material, nullptr, mediumInterface)));
PrimitiveHandle bvh(new BVHAccel(std::move(prims)));
static ConstantSpectrum I(Pi);
std::vector<LightHandle> lights;
lights.push_back(new PointLight(identity, MediumInterface(), &I, Allocator()));
scenes.push_back({bvh, lights, "Sphere, 1 light, Kd = 0.5", 1.0});
}
{
// Unit sphere, Kd = 0.5, 4 point lights I = 3.1415/4 at center
// -> With GI, should have radiance of 1.
ShapeHandle sphere = new Sphere(&identity, &identity,
true /* reverse orientation */, 1, -1, 1, 360);
static ConstantSpectrum cs(0.5);
SpectrumTextureHandle Kd = alloc.new_object<SpectrumConstantTexture>(&cs);
FloatTextureHandle sigma = alloc.new_object<FloatConstantTexture>(0.);
const MaterialHandle material = new DiffuseMaterial(Kd, sigma, nullptr);
MediumInterface mediumInterface;
std::vector<PrimitiveHandle> prims;
prims.push_back(PrimitiveHandle(
new GeometricPrimitive(sphere, material, nullptr, mediumInterface)));
PrimitiveHandle bvh(new BVHAccel(std::move(prims)));
static ConstantSpectrum I(Pi / 4);
std::vector<LightHandle> lights;
lights.push_back(new PointLight(identity, MediumInterface(), &I, Allocator()));
lights.push_back(new PointLight(identity, MediumInterface(), &I, Allocator()));
lights.push_back(new PointLight(identity, MediumInterface(), &I, Allocator()));
lights.push_back(new PointLight(identity, MediumInterface(), &I, Allocator()));
scenes.push_back({bvh, lights, "Sphere, 1 light, Kd = 0.5", 1.0});
}
{
// Unit sphere, Kd = 0.5, Le = 0.5
// -> With GI, should have radiance of 1.
ShapeHandle sphere = new Sphere(&identity, &identity,
true /* reverse orientation */, 1, -1, 1, 360);
static ConstantSpectrum cs(0.5);
SpectrumTextureHandle Kd = alloc.new_object<SpectrumConstantTexture>(&cs);
FloatTextureHandle sigma = alloc.new_object<FloatConstantTexture>(0.);
const MaterialHandle material = new DiffuseMaterial(Kd, sigma, nullptr);
static ConstantSpectrum Le(0.5);
LightHandle areaLight =
new DiffuseAreaLight(identity, MediumInterface(), &Le, 1.f, sphere, Image(),
nullptr, false, Allocator());
std::vector<LightHandle> lights;
lights.push_back(areaLight);
MediumInterface mediumInterface;
std::vector<PrimitiveHandle> prims;
prims.push_back(PrimitiveHandle(
new GeometricPrimitive(sphere, material, lights.back(), mediumInterface)));
PrimitiveHandle bvh(new BVHAccel(std::move(prims)));
scenes.push_back({bvh, lights, "Sphere, Kd = 0.5, Le = 0.5", 1.0});
}
#if 0
{
// Unit sphere, Kd = 0.25, Kr = .5, point light I = 7.4 at center
// -> With GI, should have radiance of ~1.
ShapeHandle sphere = new Sphere(
&identity, &identity, true /* reverse orientation */, 1, -1, 1, 360);
static ConstantSpectrum cs5(0.5), cs25(0.25);
SpectrumTextureHandle Kd =
alloc.new_object<SpectrumConstantTexture>(&cs25);
SpectrumTextureHandle Kr =
alloc.new_object<SpectrumConstantTexture>(&cs5);
SpectrumTextureHandle black =
alloc.new_object<SpectrumConstantTexture>(Spectra::Zero());
SpectrumTextureHandle white =
alloc.new_object<SpectrumConstantTexture>(Spectra::One());
FloatTextureHandle zero =
alloc.new_object<FloatConstantTexture>(0.);
FloatTextureHandle one =
alloc.new_object<FloatConstantTexture>(1.);
const MaterialHandle material = new UberMaterial(
Kd, black, Kr, black, zero, zero, one, nullptr, false, nullptr);
MediumInterface mediumInterface;
std::vector<PrimitiveHandle> prims;
prims.push_back(PrimitiveHandle(new GeometricPrimitive(
sphere, material, nullptr, mediumInterface)));
PrimitiveHandle bvh(new BVHAccel(std::move(prims)));
static ConstantSpectrum I(3. * Pi);
std::vector<LightHandle> lights;
lights.push_back(std::make_unique<PointLight>(identity,
nullptr, &I, Allocator()));
scenes.push_back({bvh, lights, "Sphere, 1 light, Kd = 0.25 Kr = 0.5", 1.0});
}
#endif
#if 0
{
// Unit sphere, Kd = 0.25, Kr = .5, Le .587
// -> With GI, should have radiance of ~1.
ShapeHandle sphere = new Sphere(
&identity, &identity, true /* reverse orientation */, 1, -1, 1, 360);
static ConstantSpectrum cs5(0.5), cs25(0.25);
SpectrumTextureHandle Kd =
alloc.new_object<SpectrumConstantTexture>(&cs25);
SpectrumTextureHandle Kr =
alloc.new_object<SpectrumConstantTexture>(&cs5);
SpectrumTextureHandle black =
alloc.new_object<SpectrumConstantTexture>(Spectra::Zero());
SpectrumTextureHandle white =
alloc.new_object<SpectrumConstantTexture>(Spectra::One());
FloatTextureHandle zero =
alloc.new_object<FloatConstantTexture>(0.);
FloatTextureHandle one =
alloc.new_object<FloatConstantTexture>(1.);
std::shared_ptr<Material> material = std::make_shared<UberMaterial>(
Kd, black, Kr, black, zero, zero, zero, white, one, nullptr, false, nullptr);
static ConstantSpectrum Le(0.587);
std::shared_ptr<AreaLight> areaLight = std::make_shared<DiffuseAreaLight>(
identity, nullptr, &Le, 8, sphere, true, false,
std::make_shared<ParameterDictionary>(std::initializer_list<const NamedValues *>{}, nullptr));
MediumInterface mediumInterface;
std::vector<std::shared_ptr<Primitive>> prims;
prims.push_back(PrimitiveHandle(new GeometricPrimitive(
sphere, material, areaLight, mediumInterface)));
PrimitiveHandle bvh(new BVHAccel(std::move(prims)));
std::vector<std::shared_ptr<Light>> lights;
lights.push_back(std::move(areaLight));
scenes.push_back({bvh, lights, "Sphere, Kd = 0.25 Kr = 0.5, Le = 0.587", 1.0});
}
#endif
return scenes;
}
std::vector<std::pair<SamplerHandle, std::string>> GetSamplers(
const Point2i &resolution) {
std::vector<std::pair<SamplerHandle, std::string>> samplers;
samplers.push_back(std::make_pair(new HaltonSampler(256, resolution), "Halton 256"));
samplers.push_back(std::make_pair(new PaddedSobolSampler(256, RandomizeStrategy::Xor),
"Padded Sobol 256"));
samplers.push_back(
std::make_pair(new SobolSampler(256, resolution, RandomizeStrategy::None),
"Sobol 256 Not Randomized"));
samplers.push_back(std::make_pair(
new SobolSampler(256, resolution, RandomizeStrategy::CranleyPatterson),
"Sobol 256 Cranley Patterson Randomization"));
samplers.push_back(
std::make_pair(new SobolSampler(256, resolution, RandomizeStrategy::Xor),
"Sobol 256 XOR Scramble"));
samplers.push_back(
std::make_pair(new SobolSampler(256, resolution, RandomizeStrategy::Owen),
"Sobol 256 Owen Scramble"));
samplers.push_back(std::make_pair(new RandomSampler(256), "Random 256"));
samplers.push_back(
std::make_pair(new StratifiedSampler(16, 16, true), "Stratified 16x16"));
samplers.push_back(std::make_pair(new PMJ02BNSampler(256), "PMJ02bn 256"));
return samplers;
}
std::vector<TestIntegrator> GetIntegrators() {
std::vector<TestIntegrator> integrators;
Point2i resolution(10, 10);
static Transform id;
AnimatedTransform identity(id, 0, id, 1);
for (const auto &scene : GetScenes()) {
// Path tracing integrators
for (auto &sampler : GetSamplers(resolution)) {
FilterHandle filter = new BoxFilter(Vector2f(0.5, 0.5));
RGBFilm *film = new RGBFilm(resolution,
Bounds2i(Point2i(0, 0), resolution), filter, 1.,
inTestDir("test.exr"), 1., RGBColorSpace::sRGB);
PerspectiveCamera *camera = new PerspectiveCamera(
CameraTransform(identity), Bounds2f(Point2f(-1, -1), Point2f(1, 1)), 0.,
1., 0., 10., 45, film, nullptr);
const FilmHandle filmp = camera->GetFilm();
Integrator *integrator = new PathIntegrator(8, camera, sampler.first,
scene.aggregate, scene.lights);
integrators.push_back({integrator, filmp,
"Path, depth 8, Perspective, " + sampler.second +
", " + scene.description,
scene});
}
for (auto &sampler : GetSamplers(resolution)) {
FilterHandle filter = new BoxFilter(Vector2f(0.5, 0.5));
RGBFilm *film = new RGBFilm(resolution,
Bounds2i(Point2i(0, 0), resolution), filter, 1.,
inTestDir("test.exr"), 1., RGBColorSpace::sRGB);
OrthographicCamera *camera = new OrthographicCamera(
CameraTransform(identity), Bounds2f(Point2f(-.1, -.1), Point2f(.1, .1)),
0., 1., 0., 10., film, nullptr);
const FilmHandle filmp = camera->GetFilm();
Integrator *integrator = new PathIntegrator(8, camera, sampler.first,
scene.aggregate, scene.lights);
integrators.push_back(
{integrator, filmp,
"Path, depth 8, Ortho, " + sampler.second + ", " + scene.description,
scene});
}
// Volume path tracing integrators
for (auto &sampler : GetSamplers(resolution)) {
FilterHandle filter = new BoxFilter(Vector2f(0.5, 0.5));
RGBFilm *film = new RGBFilm(resolution,
Bounds2i(Point2i(0, 0), resolution), filter, 1.,
inTestDir("test.exr"), 1., RGBColorSpace::sRGB);
PerspectiveCamera *camera = new PerspectiveCamera(
CameraTransform(identity), Bounds2f(Point2f(-1, -1), Point2f(1, 1)), 0.,
1., 0., 10., 45, film, nullptr);
const FilmHandle filmp = camera->GetFilm();
Integrator *integrator = new VolPathIntegrator(8, camera, sampler.first,
scene.aggregate, scene.lights);
integrators.push_back({integrator, filmp,
"VolPath, depth 8, Perspective, " + sampler.second +
", " + scene.description,
scene});
}
for (auto &sampler : GetSamplers(resolution)) {
FilterHandle filter = new BoxFilter(Vector2f(0.5, 0.5));
RGBFilm *film = new RGBFilm(resolution,
Bounds2i(Point2i(0, 0), resolution), filter, 1.,
inTestDir("test.exr"), 1., RGBColorSpace::sRGB);
OrthographicCamera *camera = new OrthographicCamera(
CameraTransform(identity), Bounds2f(Point2f(-.1, -.1), Point2f(.1, .1)),
0., 1., 0., 10., film, nullptr);
const FilmHandle filmp = camera->GetFilm();
Integrator *integrator = new VolPathIntegrator(8, camera, sampler.first,
scene.aggregate, scene.lights);
integrators.push_back(
{integrator, filmp,
"VolPath, depth 8, Ortho, " + sampler.second + ", " + scene.description,
scene});
}
// Simple path (perspective only, still sample light and BSDFs). Yolo
for (auto &sampler : GetSamplers(resolution)) {
FilterHandle filter = new BoxFilter(Vector2f(0.5, 0.5));
RGBFilm *film = new RGBFilm(resolution,
Bounds2i(Point2i(0, 0), resolution), filter, 1.,
inTestDir("test.exr"), 1., RGBColorSpace::sRGB);
PerspectiveCamera *camera = new PerspectiveCamera(
CameraTransform(identity), Bounds2f(Point2f(-1, -1), Point2f(1, 1)), 0.,
1., 0., 10., 45, film, nullptr);
const FilmHandle filmp = camera->GetFilm();
Integrator *integrator = new SimplePathIntegrator(
8, true, true, camera, sampler.first, scene.aggregate, scene.lights);
integrators.push_back({integrator, filmp,
"SimplePath, depth 8, Perspective, " + sampler.second +
", " + scene.description,
scene});
}
// BDPT
for (auto &sampler : GetSamplers(resolution)) {
FilterHandle filter = new BoxFilter(Vector2f(0.5, 0.5));
RGBFilm *film = new RGBFilm(resolution,
Bounds2i(Point2i(0, 0), resolution), filter, 1.,
inTestDir("test.exr"), 1., RGBColorSpace::sRGB);
PerspectiveCamera *camera = new PerspectiveCamera(
CameraTransform(identity), Bounds2f(Point2f(-1, -1), Point2f(1, 1)), 0.,
1., 0., 10., 45, film, nullptr);
const FilmHandle filmp = camera->GetFilm();
Integrator *integrator =
new BDPTIntegrator(camera, sampler.first, scene.aggregate, scene.lights,
6, false, false, "power", false);
integrators.push_back({integrator, filmp,
"BDPT, depth 8, Perspective, " + sampler.second +
", " + scene.description,
scene});
}
// MLT
{
FilterHandle filter = new BoxFilter(Vector2f(0.5, 0.5));
RGBFilm *film = new RGBFilm(resolution,
Bounds2i(Point2i(0, 0), resolution), filter, 1.,
inTestDir("test.exr"), 1., RGBColorSpace::sRGB);
PerspectiveCamera *camera = new PerspectiveCamera(
CameraTransform(identity), Bounds2f(Point2f(-1, -1), Point2f(1, 1)), 0.,
1., 0., 10., 45, film, nullptr);
const FilmHandle filmp = camera->GetFilm();
Integrator *integrator =
new MLTIntegrator(camera, scene.aggregate, scene.lights, 8 /* depth */,
100000 /* n bootstrap */, 1000 /* nchains */,
1024 /* mutations per pixel */, 0.01 /* sigma */,
0.3 /* large step prob */, false /* regularize */);
integrators.push_back({integrator, filmp,
"MLT, depth 8, Perspective, " + scene.description,
scene});
}
}
return integrators;
}
struct RenderTest : public testing::TestWithParam<TestIntegrator> {};
TEST_P(RenderTest, RadianceMatches) {
const TestIntegrator &tr = GetParam();
tr.integrator->Render();
CheckSceneAverage(inTestDir("test.exr"), tr.scene.expected);
// The SpatialLightSampler class keeps a per-thread cache that
// must be cleared out between test runs. In turn, this means that we
// must delete the Integrator here in order to make sure that its
// destructor runs. (This is ugly and should be fixed in a better way.)
delete tr.integrator;
EXPECT_EQ(0, remove(inTestDir("test.exr").c_str()));
}
INSTANTIATE_TEST_CASE_P(AnalyticTestScenes, RenderTest,
testing::ValuesIn(GetIntegrators()));

172
src/pbrt/cpu/primitive.cpp Normal file
View file

@ -0,0 +1,172 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#include <pbrt/cpu/primitive.h>
#include <pbrt/cpu/accelerators.h>
#include <pbrt/interaction.h>
#include <pbrt/materials.h>
#include <pbrt/shapes.h>
#include <pbrt/textures.h>
#include <pbrt/util/check.h>
#include <pbrt/util/log.h>
#include <pbrt/util/taggedptr.h>
#include <pbrt/util/vecmath.h>
namespace pbrt {
Bounds3f PrimitiveHandle::Bounds() const {
auto bounds = [&](auto ptr) { return ptr->Bounds(); };
return DispatchCPU(bounds);
}
pstd::optional<ShapeIntersection> PrimitiveHandle::Intersect(const Ray &r,
Float tMax) const {
auto isect = [&](auto ptr) { return ptr->Intersect(r, tMax); };
return DispatchCPU(isect);
}
bool PrimitiveHandle::IntersectP(const Ray &r, Float tMax) const {
auto isectp = [&](auto ptr) { return ptr->IntersectP(r, tMax); };
return DispatchCPU(isectp);
}
// GeometricPrimitive Method Definitions
GeometricPrimitive::GeometricPrimitive(ShapeHandle shape, MaterialHandle material,
LightHandle areaLight,
const MediumInterface &mediumInterface,
FloatTextureHandle alpha)
: shape(shape),
material(material),
areaLight(areaLight),
mediumInterface(mediumInterface),
alpha(alpha) {
primitiveMemory += sizeof(*this);
}
pstd::optional<ShapeIntersection> GeometricPrimitive::Intersect(const Ray &r,
Float tMax) const {
pstd::optional<ShapeIntersection> si = shape.Intersect(r, tMax);
if (!si)
return {};
CHECK_LT(si->tHit, 1.001 * tMax);
// Test intersection against alpha texture, if present
if (alpha && alpha.Evaluate(si->intr) == 0) {
// Ignore this hit and trace a new ray.
Ray rNext = si->intr.SpawnRay(r.d);
pstd::optional<ShapeIntersection> siNext = Intersect(rNext, tMax - si->tHit);
if (siNext)
// The returned t value has to account for both ray segments.
siNext->tHit += si->tHit;
return siNext;
}
// Initialize _SurfaceInteraction_ after _Shape_ intersection
si->intr.areaLight = areaLight;
si->intr.material = material;
CHECK_GE(Dot(si->intr.n, si->intr.shading.n), 0.);
if (mediumInterface.IsMediumTransition())
si->intr.mediumInterface = &mediumInterface;
else
si->intr.medium = r.medium;
return si;
}
bool GeometricPrimitive::IntersectP(const Ray &r, Float tMax) const {
// Skip shadow intersection test for transparent materials
if (material && material.IsTransparent())
return false;
if (alpha)
return Intersect(r, tMax).has_value();
else
return shape.IntersectP(r, tMax);
}
Bounds3f GeometricPrimitive::Bounds() const {
return shape.Bounds();
}
// SimplePrimitive Method Definitions
SimplePrimitive::SimplePrimitive(ShapeHandle shape, MaterialHandle material)
: shape(shape), material(material) {
primitiveMemory += sizeof(*this);
}
Bounds3f SimplePrimitive::Bounds() const {
return shape.Bounds();
}
bool SimplePrimitive::IntersectP(const Ray &r, Float tMax) const {
if (material && material.IsTransparent())
return false;
return shape.IntersectP(r, tMax);
}
pstd::optional<ShapeIntersection> SimplePrimitive::Intersect(const Ray &r,
Float tMax) const {
pstd::optional<ShapeIntersection> si = shape.Intersect(r, tMax);
if (!si)
return {};
CHECK_LT(si->tHit, 1.001 * tMax);
si->intr.areaLight = nullptr;
si->intr.material = material;
CHECK_GE(Dot(si->intr.n, si->intr.shading.n), 0.);
si->intr.medium = r.medium;
return si;
}
// TransformedPrimitive Method Definitions
pstd::optional<ShapeIntersection> TransformedPrimitive::Intersect(const Ray &r,
Float tMax) const {
// Transform ray to primitive-space and intersect with primitive
Ray ray = renderFromPrimitive->ApplyInverse(r, &tMax);
pstd::optional<ShapeIntersection> si = primitive.Intersect(ray, tMax);
if (!si)
return {};
CHECK_LT(si->tHit, 1.001 * tMax);
// Return transformed instance's intersection information
si->intr = (*renderFromPrimitive)(si->intr);
CHECK_GE(Dot(si->intr.n, si->intr.shading.n), 0);
return si;
}
bool TransformedPrimitive::IntersectP(const Ray &r, Float tMax) const {
Ray ray = renderFromPrimitive->ApplyInverse(r, &tMax);
return primitive.IntersectP(ray, tMax);
}
// AnimatedPrimitive Method Definitions
AnimatedPrimitive::AnimatedPrimitive(PrimitiveHandle p,
const AnimatedTransform &renderFromPrimitive)
: primitive(p), renderFromPrimitive(renderFromPrimitive) {
primitiveMemory += sizeof(*this);
CHECK(renderFromPrimitive.IsAnimated());
}
pstd::optional<ShapeIntersection> AnimatedPrimitive::Intersect(const Ray &r,
Float tMax) const {
// Compute _ray_ after transformation by _renderFromPrimitive_
Transform interpRenderFromPrimitive = renderFromPrimitive.Interpolate(r.time);
Ray ray = interpRenderFromPrimitive.ApplyInverse(r, &tMax);
pstd::optional<ShapeIntersection> si = primitive.Intersect(ray, tMax);
if (!si)
return {};
// Transform instance's intersection data to render space
si->intr = interpRenderFromPrimitive(si->intr);
CHECK_GE(Dot(si->intr.n, si->intr.shading.n), 0);
return si;
}
bool AnimatedPrimitive::IntersectP(const Ray &r, Float tMax) const {
Ray ray = renderFromPrimitive.ApplyInverse(r, &tMax);
return primitive.IntersectP(ray, tMax);
}
} // namespace pbrt

122
src/pbrt/cpu/primitive.h Normal file
View file

@ -0,0 +1,122 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_CPU_PRIMITIVE_H
#define PBRT_CPU_PRIMITIVE_H
#include <pbrt/pbrt.h>
#include <pbrt/base/light.h>
#include <pbrt/base/material.h>
#include <pbrt/base/medium.h>
#include <pbrt/base/shape.h>
#include <pbrt/base/texture.h>
#include <pbrt/util/stats.h>
#include <pbrt/util/taggedptr.h>
#include <pbrt/util/transform.h>
#include <memory>
namespace pbrt {
STAT_MEMORY_COUNTER("Memory/Primitives", primitiveMemory);
class SimplePrimitive;
class GeometricPrimitive;
class TransformedPrimitive;
class AnimatedPrimitive;
class BVHAccel;
class KdTreeAccel;
// PrimitiveHandle Definition
class PrimitiveHandle
: public TaggedPointer<SimplePrimitive, GeometricPrimitive, TransformedPrimitive,
AnimatedPrimitive, BVHAccel, KdTreeAccel> {
public:
// Primitive Interface
using TaggedPointer::TaggedPointer;
Bounds3f Bounds() const;
pstd::optional<ShapeIntersection> Intersect(const Ray &r,
Float tMax = Infinity) const;
bool IntersectP(const Ray &r, Float tMax = Infinity) const;
};
// GeometricPrimitive Definition
class GeometricPrimitive {
public:
// GeometricPrimitive Public Methods
GeometricPrimitive(ShapeHandle shape, MaterialHandle material, LightHandle areaLight,
const MediumInterface &mediumInterface,
FloatTextureHandle alpha = nullptr);
Bounds3f Bounds() const;
pstd::optional<ShapeIntersection> Intersect(const Ray &r, Float tMax) const;
bool IntersectP(const Ray &r, Float tMax) const;
private:
// GeometricPrimitive Private Members
ShapeHandle shape;
MaterialHandle material;
LightHandle areaLight;
MediumInterface mediumInterface;
FloatTextureHandle alpha;
};
// SimplePrimitive Definition
class SimplePrimitive {
public:
// SimplePrimitive Public Methods
Bounds3f Bounds() const;
pstd::optional<ShapeIntersection> Intersect(const Ray &r, Float tMax) const;
bool IntersectP(const Ray &r, Float tMax) const;
SimplePrimitive(ShapeHandle shape, MaterialHandle material);
private:
ShapeHandle shape;
MaterialHandle material;
};
// TransformedPrimitive Definition
class TransformedPrimitive {
public:
// TransformedPrimitive Public Methods
TransformedPrimitive(PrimitiveHandle primitive, const Transform *renderFromPrimitive)
: primitive(primitive), renderFromPrimitive(renderFromPrimitive) {
primitiveMemory += sizeof(*this);
}
pstd::optional<ShapeIntersection> Intersect(const Ray &r, Float tMax) const;
bool IntersectP(const Ray &r, Float tMax) const;
Bounds3f Bounds() const { return (*renderFromPrimitive)(primitive.Bounds()); }
private:
// TransformedPrimitive Private Members
PrimitiveHandle primitive;
const Transform *renderFromPrimitive;
};
// AnimatedPrimitive Definition
class AnimatedPrimitive {
public:
// AnimatedPrimitive Public Methods
AnimatedPrimitive(PrimitiveHandle primitive,
const AnimatedTransform &renderFromPrimitive);
pstd::optional<ShapeIntersection> Intersect(const Ray &r, Float tMax) const;
bool IntersectP(const Ray &r, Float tMax) const;
Bounds3f Bounds() const {
return renderFromPrimitive.MotionBounds(primitive.Bounds());
}
private:
// AnimatedPrimitive Private Members
PrimitiveHandle primitive;
AnimatedTransform renderFromPrimitive;
};
} // namespace pbrt
#endif // PBRT_CPU_PRIMITIVE_H

340
src/pbrt/cpu/render.cpp Normal file
View file

@ -0,0 +1,340 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#include <pbrt/cpu/render.h>
#include <pbrt/cameras.h>
#include <pbrt/cpu/accelerators.h>
#include <pbrt/cpu/integrators.h>
#include <pbrt/film.h>
#include <pbrt/filters.h>
#include <pbrt/lights.h>
#include <pbrt/materials.h>
#include <pbrt/media.h>
#include <pbrt/parsedscene.h>
#include <pbrt/samplers.h>
#include <pbrt/shapes.h>
#include <pbrt/textures.h>
#include <pbrt/util/colorspace.h>
namespace pbrt {
void CPURender(ParsedScene &parsedScene) {
Allocator alloc;
// Create media first (so have them for the camera...)
std::map<std::string, MediumHandle> media = parsedScene.CreateMedia(alloc);
bool haveScatteringMedia = false;
auto findMedium = [&media, &haveScatteringMedia](const std::string &s,
const FileLoc *loc) -> MediumHandle {
if (s.empty())
return nullptr;
auto iter = media.find(s);
if (iter == media.end())
ErrorExit(loc, "%s: medium not defined", s);
haveScatteringMedia = true;
return iter->second;
};
// Filter
FilterHandle filter =
FilterHandle::Create(parsedScene.filter.name, parsedScene.filter.parameters,
&parsedScene.filter.loc, alloc);
// Film
FilmHandle film =
FilmHandle::Create(parsedScene.film.name, parsedScene.film.parameters,
&parsedScene.film.loc, filter, alloc);
// Camera
MediumHandle cameraMedium =
findMedium(parsedScene.camera.medium, &parsedScene.camera.loc);
CameraHandle camera = CameraHandle::Create(
parsedScene.camera.name, parsedScene.camera.parameters, cameraMedium,
parsedScene.camera.cameraTransform, film, &parsedScene.camera.loc, alloc);
// Create _Sampler_ for rendering
SamplerHandle sampler = SamplerHandle::Create(
parsedScene.sampler.name, parsedScene.sampler.parameters,
camera.GetFilm().FullResolution(), &parsedScene.sampler.loc, alloc);
// Textures
std::map<std::string, FloatTextureHandle> floatTextures;
std::map<std::string, SpectrumTextureHandle> spectrumTextures;
parsedScene.CreateTextures(&floatTextures, &spectrumTextures, alloc, false);
// Materials
std::map<std::string, MaterialHandle> namedMaterials;
std::vector<MaterialHandle> materials;
parsedScene.CreateMaterials(floatTextures, spectrumTextures, alloc, &namedMaterials,
&materials);
bool haveSubsurface = false;
for (const auto &mtl : parsedScene.materials)
if (mtl.name == "subsurface")
haveSubsurface = true;
for (const auto &namedMtl : parsedScene.namedMaterials)
if (namedMtl.second.name == "subsurface")
haveSubsurface = true;
// Lights (area lights will be done later, with shapes...)
std::vector<LightHandle> lights;
lights.reserve(parsedScene.lights.size() + parsedScene.areaLights.size());
for (const auto &light : parsedScene.lights) {
MediumHandle outsideMedium = findMedium(light.medium, &light.loc);
if (light.renderFromObject.IsAnimated())
Warning(&light.loc,
"Animated lights aren't supported. Using the start transform.");
LightHandle l = LightHandle::Create(
light.name, light.parameters, light.renderFromObject.startTransform,
parsedScene.camera.cameraTransform, outsideMedium, &light.loc, alloc);
lights.push_back(l);
}
// Primitives
auto getAlphaTexture = [&](const ParameterDictionary &parameters,
const FileLoc *loc) -> FloatTextureHandle {
std::string alphaTexName = parameters.GetTexture("alpha");
if (!alphaTexName.empty()) {
if (floatTextures.find(alphaTexName) != floatTextures.end())
return floatTextures[alphaTexName];
else
ErrorExit(loc, "%s: couldn't find float texture for \"alpha\" parameter.",
alphaTexName);
} else if (parameters.GetOneFloat("alpha", 1.f) == 0.f)
return alloc.new_object<FloatConstantTexture>(0.f);
else
return nullptr;
};
// Non-animated shapes
auto CreatePrimitivesForShapes =
[&](const std::vector<ShapeSceneEntity> &shapes) -> std::vector<PrimitiveHandle> {
std::vector<PrimitiveHandle> primitives;
for (const auto &sh : shapes) {
pstd::vector<ShapeHandle> shapes =
ShapeHandle::Create(sh.name, sh.renderFromObject, sh.objectFromRender,
sh.reverseOrientation, sh.parameters, &sh.loc, alloc);
if (shapes.empty())
continue;
FloatTextureHandle alphaTex = getAlphaTexture(sh.parameters, &sh.loc);
sh.parameters.ReportUnused(); // do now so can grab alpha...
MaterialHandle mtl = nullptr;
if (!sh.materialName.empty()) {
auto iter = namedMaterials.find(sh.materialName);
if (iter == namedMaterials.end())
ErrorExit(&sh.loc, "%s: no named material defined.", sh.materialName);
mtl = iter->second;
} else {
CHECK_LT(sh.materialIndex, materials.size());
mtl = materials[sh.materialIndex];
}
MediumInterface mi(findMedium(sh.insideMedium, &sh.loc),
findMedium(sh.outsideMedium, &sh.loc));
for (auto &s : shapes) {
// Possibly create area light for shape
LightHandle areaHandle = nullptr;
if (sh.lightIndex != -1) {
CHECK_LT(sh.lightIndex, parsedScene.areaLights.size());
const auto &areaLightEntity = parsedScene.areaLights[sh.lightIndex];
LightHandle area = LightHandle::CreateArea(
areaLightEntity.name, areaLightEntity.parameters,
*sh.renderFromObject, mi, s, &areaLightEntity.loc, Allocator{});
areaHandle = area;
if (area)
lights.push_back(area);
}
if (areaHandle == nullptr && !mi.IsMediumTransition() && !alphaTex)
primitives.push_back(new SimplePrimitive(s, mtl));
else
primitives.push_back(
new GeometricPrimitive(s, mtl, areaHandle, mi, alphaTex));
}
}
return primitives;
};
std::vector<PrimitiveHandle> primitives =
CreatePrimitivesForShapes(parsedScene.shapes);
// Animated shapes
auto CreatePrimitivesForAnimatedShapes =
[&](const std::vector<AnimatedShapeSceneEntity> &shapes)
-> std::vector<PrimitiveHandle> {
std::vector<PrimitiveHandle> primitives;
primitives.reserve(shapes.size());
for (const auto &sh : shapes) {
pstd::vector<ShapeHandle> shapes =
ShapeHandle::Create(sh.name, sh.identity, sh.identity,
sh.reverseOrientation, sh.parameters, &sh.loc, alloc);
if (shapes.empty())
continue;
FloatTextureHandle alphaTex = getAlphaTexture(sh.parameters, &sh.loc);
sh.parameters.ReportUnused(); // do now so can grab alpha...
// Create initial shape or shapes for animated shape
MaterialHandle mtl = nullptr;
if (!sh.materialName.empty()) {
auto iter = namedMaterials.find(sh.materialName);
if (iter == namedMaterials.end())
ErrorExit(&sh.loc, "%s: no named material defined.", sh.materialName);
mtl = iter->second;
} else {
CHECK_LT(sh.materialIndex, materials.size());
mtl = materials[sh.materialIndex];
}
MediumInterface mi(findMedium(sh.insideMedium, &sh.loc),
findMedium(sh.outsideMedium, &sh.loc));
std::vector<PrimitiveHandle> prims;
for (auto &s : shapes) {
// Possibly create area light for shape
LightHandle areaHandle = nullptr;
if (sh.lightIndex != -1) {
CHECK_LT(sh.lightIndex, parsedScene.areaLights.size());
const auto &areaLightEntity = parsedScene.areaLights[sh.lightIndex];
if (sh.renderFromObject.IsAnimated())
Warning(&sh.loc, "Animated area lights aren't supported. Using "
"the start transform.");
LightHandle area = LightHandle::CreateArea(
areaLightEntity.name, areaLightEntity.parameters,
sh.renderFromObject.startTransform, mi, s, &sh.loc, Allocator{});
areaHandle = area;
if (area)
lights.push_back(area);
}
if (areaHandle == nullptr && !mi.IsMediumTransition() && !alphaTex)
prims.push_back(new SimplePrimitive(s, mtl));
else
prims.push_back(
new GeometricPrimitive(s, mtl, areaHandle, mi, alphaTex));
}
// TODO: could try to be greedy or even segment them according
// to same sh.renderFromObject...
// Create single _Primitive_ for _prims_
if (prims.size() > 1) {
PrimitiveHandle bvh = new BVHAccel(std::move(prims));
prims.clear();
prims.push_back(bvh);
}
primitives.push_back(new AnimatedPrimitive(prims[0], sh.renderFromObject));
}
return primitives;
};
std::vector<PrimitiveHandle> animatedPrimitives =
CreatePrimitivesForAnimatedShapes(parsedScene.animatedShapes);
primitives.insert(primitives.end(), animatedPrimitives.begin(),
animatedPrimitives.end());
// Instance definitions
std::map<std::string, PrimitiveHandle> instanceDefinitions;
for (const auto &inst : parsedScene.instanceDefinitions) {
if (instanceDefinitions.find(inst.first) != instanceDefinitions.end())
ErrorExit("%s: object instance redefined", inst.first);
std::vector<PrimitiveHandle> instancePrimitives =
CreatePrimitivesForShapes(inst.second.shapes);
std::vector<PrimitiveHandle> movingInstancePrimitives =
CreatePrimitivesForAnimatedShapes(inst.second.animatedShapes);
instancePrimitives.insert(instancePrimitives.end(),
movingInstancePrimitives.begin(),
movingInstancePrimitives.end());
if (instancePrimitives.empty()) {
instanceDefinitions[inst.first] = nullptr;
} else {
if (instancePrimitives.size() > 1) {
PrimitiveHandle bvh = new BVHAccel(std::move(instancePrimitives));
instancePrimitives.clear();
instancePrimitives.push_back(bvh);
}
instanceDefinitions[inst.first] = instancePrimitives[0];
}
}
// Instances
for (const auto &inst : parsedScene.instances) {
auto iter = instanceDefinitions.find(inst.name);
if (iter == instanceDefinitions.end())
ErrorExit(&inst.loc, "%s: object instance not defined", inst.name);
if (iter->second == nullptr)
// empty instance
continue;
if (inst.renderFromInstance)
primitives.push_back(
new TransformedPrimitive(iter->second, inst.renderFromInstance));
else
primitives.push_back(
new AnimatedPrimitive(iter->second, inst.renderFromInstanceAnim));
}
// Accelerator
PrimitiveHandle accel = nullptr;
if (!primitives.empty())
accel = CreateAccelerator(parsedScene.accelerator.name, std::move(primitives),
parsedScene.accelerator.parameters);
// Integrator
const RGBColorSpace *integratorColorSpace = parsedScene.film.parameters.ColorSpace();
std::unique_ptr<Integrator> integrator(Integrator::Create(
parsedScene.integrator.name, parsedScene.integrator.parameters, camera, sampler,
accel, lights, integratorColorSpace, &parsedScene.integrator.loc));
// Helpful warnings
if (haveScatteringMedia && parsedScene.integrator.name != "volpath" &&
parsedScene.integrator.name != "simplevolpath" &&
parsedScene.integrator.name != "bdpt" && parsedScene.integrator.name != "mlt")
Warning("Scene has scattering media but \"%s\" integrator doesn't support "
"volume scattering. Consider using \"volpath\", \"simplevolpath\", "
"\"bdpt\", or \"mlt\".",
parsedScene.integrator.name);
bool haveLights = !lights.empty();
for (const auto &m : media)
haveLights |= m.second.IsEmissive();
if (!haveLights && parsedScene.integrator.name != "ambientocclusion" &&
parsedScene.integrator.name != "aov")
Warning("No light sources defined in scene; rendering a black image.");
if (parsedScene.film.name == "gbuffer" && parsedScene.integrator.name != "path")
Warning(&parsedScene.film.loc,
"GBufferFilm is not supported by %s. The channels "
"other than R, G, B will be zero.",
parsedScene.integrator.name);
if (haveSubsurface && parsedScene.integrator.name != "volpath")
Warning("Some objects in the scene have subsurface scattering, which is "
"not supported by the %s integrator. Use the \"volpath\" integrator "
"to render them correctly.",
parsedScene.integrator.name);
LOG_VERBOSE("Memory used after scene creation: %d", GetCurrentRSS());
// Render!
integrator->Render();
LOG_VERBOSE("Memory used after rendering: %s", GetCurrentRSS());
PtexTextureBase::ReportStats();
ImageTextureBase::ClearCache();
FreeBufferCaches();
}
} // namespace pbrt

18
src/pbrt/cpu/render.h Normal file
View file

@ -0,0 +1,18 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_CPU_RENDER_H
#define PBRT_CPU_RENDER_H
#include <pbrt/pbrt.h>
namespace pbrt {
class ParsedScene;
void CPURender(ParsedScene &scene);
} // namespace pbrt
#endif // PBRT_CPU_RENDER_H

586
src/pbrt/film.cpp Normal file
View file

@ -0,0 +1,586 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#include <pbrt/film.h>
#include <pbrt/bsdf.h>
#include <pbrt/cameras.h>
#include <pbrt/filters.h>
#include <pbrt/options.h>
#include <pbrt/paramdict.h>
#include <pbrt/util/bluenoise.h>
#include <pbrt/util/check.h>
#include <pbrt/util/color.h>
#include <pbrt/util/colorspace.h>
#include <pbrt/util/error.h>
#include <pbrt/util/image.h>
#include <pbrt/util/lowdiscrepancy.h>
#include <pbrt/util/memory.h>
#include <pbrt/util/parallel.h>
#include <pbrt/util/print.h>
#include <pbrt/util/spectrum.h>
#include <pbrt/util/stats.h>
#include <pbrt/util/transform.h>
namespace pbrt {
void FilmHandle::AddSplat(const Point2f &p, SampledSpectrum v,
const SampledWavelengths &lambda) {
auto splat = [&](auto ptr) { return ptr->AddSplat(p, v, lambda); };
return Dispatch(splat);
}
void FilmHandle::WriteImage(ImageMetadata metadata, Float splatScale) {
auto write = [&](auto ptr) { return ptr->WriteImage(metadata, splatScale); };
return DispatchCPU(write);
}
Image FilmHandle::GetImage(ImageMetadata *metadata, Float splatScale) {
auto get = [&](auto ptr) { return ptr->GetImage(metadata, splatScale); };
return DispatchCPU(get);
}
std::string FilmHandle::ToString() const {
if (ptr() == nullptr)
return "(nullptr)";
auto ts = [&](auto ptr) { return ptr->ToString(); };
return DispatchCPU(ts);
}
std::string FilmHandle::GetFilename() const {
auto get = [&](auto ptr) { return ptr->GetFilename(); };
return DispatchCPU(get);
}
// FilmBase Method Definitions
std::string FilmBase::BaseToString() const {
return StringPrintf("fullResolution: %s diagonal: %f filter: %s filename: %s "
"pixelBounds: %s",
fullResolution, diagonal, filter, filename, pixelBounds);
}
Bounds2f FilmBase::SampleBounds() const {
return Bounds2f(Point2f(pixelBounds.pMin) - filter.Radius() + Vector2f(0.5f, 0.5f),
Point2f(pixelBounds.pMax) + filter.Radius() - Vector2f(0.5f, 0.5f));
}
// VisibleSurface Method Definitions
VisibleSurface::VisibleSurface(const SurfaceInteraction &si,
const CameraTransform &cameraTransform,
const SampledSpectrum &albedo,
const SampledWavelengths &lambda)
: albedo(albedo) {
set = true;
// Initialize geometric _VisibleSurface_ members
Transform cameraFromRender = cameraTransform.CameraFromRender(si.time);
p = cameraFromRender(si.p());
Vector3f wo = cameraFromRender(si.wo);
n = FaceForward(cameraFromRender(si.n), wo);
ns = FaceForward(cameraFromRender(si.shading.n), wo);
time = si.time;
dzdx = cameraFromRender(si.dpdx).z;
dzdy = cameraFromRender(si.dpdy).z;
}
std::string VisibleSurface::ToString() const {
return StringPrintf("[ VisibleSurface set: %s p: %s n: %s ns: %s dzdx: %f dzdy: %f "
"time: %f albedo: %s ]",
set, p, n, ns, dzdx, dzdy, time, albedo);
}
STAT_MEMORY_COUNTER("Memory/Film pixels", filmPixelMemory);
// RGBFilm Method Definitions
RGBFilm::RGBFilm(const Point2i &resolution, const Bounds2i &pixelBounds,
FilterHandle filter, Float diagonal, const std::string &filename,
Float scale, const RGBColorSpace *colorSpace, Float maxComponentValue,
bool writeFP16, Allocator allocator)
: FilmBase(resolution, pixelBounds, filter, diagonal, filename),
pixels(pixelBounds, allocator),
scale(scale),
colorSpace(colorSpace),
maxComponentValue(maxComponentValue),
writeFP16(writeFP16) {
filterIntegral = filter.Integral();
CHECK(!pixelBounds.IsEmpty());
CHECK(colorSpace != nullptr);
filmPixelMemory += pixelBounds.Area() * sizeof(Pixel);
}
SampledWavelengths RGBFilm::SampleWavelengths(Float u) const {
return SampledWavelengths::SampleXYZ(u);
}
void RGBFilm::AddSplat(const Point2f &p, SampledSpectrum v,
const SampledWavelengths &lambda) {
CHECK(!v.HasNaNs());
RGB rgb = v.ToRGB(lambda, *colorSpace);
// Optionally clamp splat sensor RGB value
Float m = std::max({rgb.r, rgb.g, rgb.b});
if (m > maxComponentValue)
rgb *= maxComponentValue / m;
// Compute bounds of affected pixels for splat, _splatBounds_
Point2f pDiscrete = p + Vector2f(0.5, 0.5);
Bounds2i splatBounds(Point2i(Floor(pDiscrete - filter.Radius())),
Point2i(Floor(pDiscrete + filter.Radius())) + Vector2i(1, 1));
splatBounds = Intersect(splatBounds, pixelBounds);
for (Point2i pi : splatBounds) {
// Evaluate filter at _pi_ and add splat contribution
Float wt = filter.Evaluate(Point2f(p - pi - Vector2f(0.5, 0.5)));
if (wt != 0) {
Pixel &pixel = pixels[pi];
for (int i = 0; i < 3; ++i)
pixel.splatRGB[i].Add(wt * rgb[i]);
}
}
}
void RGBFilm::WriteImage(ImageMetadata metadata, Float splatScale) {
Image image = GetImage(&metadata, splatScale);
LOG_VERBOSE("Writing image %s with bounds %s", filename, pixelBounds);
image.Write(filename, metadata);
}
Image RGBFilm::GetImage(ImageMetadata *metadata, Float splatScale) {
// Convert image to RGB and compute final pixel values
LOG_VERBOSE("Converting image to RGB and computing final weighted pixel values");
PixelFormat format = writeFP16 ? PixelFormat::Half : PixelFormat::Float;
Image image(format, Point2i(pixelBounds.Diagonal()), {"R", "G", "B"});
ParallelFor2D(pixelBounds, [&](Point2i p) {
RGB rgb = GetPixelRGB(p, splatScale);
Point2i pOffset(p.x - pixelBounds.pMin.x, p.y - pixelBounds.pMin.y);
image.SetChannels(pOffset, {rgb[0], rgb[1], rgb[2]});
});
metadata->pixelBounds = pixelBounds;
metadata->fullResolution = fullResolution;
metadata->colorSpace = colorSpace;
Float varianceSum = 0;
for (Point2i p : pixelBounds) {
const Pixel &pixel = pixels[p];
varianceSum += Float(pixel.varianceEstimator.Variance());
}
metadata->estimatedVariance = varianceSum / pixelBounds.Area();
return image;
}
std::string RGBFilm::ToString() const {
return StringPrintf("[ RGBFilm %s scale: %f colorSpace: %s maxComponentValue: %f "
"writeFP16: %s ]",
BaseToString(), scale, *colorSpace, maxComponentValue, writeFP16);
}
RGBFilm *RGBFilm::Create(const ParameterDictionary &parameters, FilterHandle filter,
const RGBColorSpace *colorSpace, const FileLoc *loc,
Allocator alloc) {
std::string filename = parameters.GetOneString("filename", "");
if (!Options->imageFile.empty()) {
if (!filename.empty())
Warning(loc,
"Output filename supplied on command line, \"%s\" will "
"override "
"filename provided in scene description file, \"%s\".",
Options->imageFile, filename);
filename = Options->imageFile;
} else if (filename.empty())
filename = "pbrt.exr";
Point2i fullResolution(parameters.GetOneInt("xresolution", 1280),
parameters.GetOneInt("yresolution", 720));
if (Options->quickRender) {
fullResolution.x = std::max(1, fullResolution.x / 4);
fullResolution.y = std::max(1, fullResolution.y / 4);
}
Bounds2i pixelBounds(Point2i(0, 0), fullResolution);
std::vector<int> pb = parameters.GetIntArray("pixelbounds");
if (Options->pixelBounds) {
Bounds2i newBounds = *Options->pixelBounds;
if (Intersect(newBounds, pixelBounds) != newBounds)
Warning(loc, "Supplied pixel bounds extend beyond image "
"resolution. Clamping.");
pixelBounds = Intersect(newBounds, pixelBounds);
if (!pb.empty())
Warning(loc, "Both pixel bounds and crop window were specified. Using the "
"crop window.");
} else if (!pb.empty()) {
if (pb.size() != 4)
Error(loc, "%d values supplied for \"pixelbounds\". Expected 4.",
int(pb.size()));
else {
Bounds2i newBounds = Bounds2i({pb[0], pb[2]}, {pb[1], pb[3]});
if (Intersect(newBounds, pixelBounds) != newBounds)
Warning(loc, "Supplied pixel bounds extend beyond image "
"resolution. Clamping.");
pixelBounds = Intersect(newBounds, pixelBounds);
}
}
std::vector<Float> cr = parameters.GetFloatArray("cropwindow");
if (Options->cropWindow) {
Bounds2f crop = *Options->cropWindow;
// Compute film image bounds
pixelBounds = Bounds2i(Point2i(std::ceil(fullResolution.x * crop.pMin.x),
std::ceil(fullResolution.y * crop.pMin.y)),
Point2i(std::ceil(fullResolution.x * crop.pMax.x),
std::ceil(fullResolution.y * crop.pMax.y)));
if (!cr.empty())
Warning(loc, "Crop window supplied on command line will override "
"crop window specified with Film.");
if (Options->pixelBounds || !pb.empty())
Warning(loc, "Both pixel bounds and crop window were specified. Using the "
"crop window.");
} else if (!cr.empty()) {
if (Options->pixelBounds)
Warning(loc, "Ignoring \"cropwindow\" since pixel bounds were specified "
"on the command line.");
else if (cr.size() == 4) {
if (!pb.empty())
Warning(loc, "Both pixel bounds and crop window were "
"specified. Using the "
"crop window.");
Bounds2f crop;
crop.pMin.x = Clamp(std::min(cr[0], cr[1]), 0.f, 1.f);
crop.pMax.x = Clamp(std::max(cr[0], cr[1]), 0.f, 1.f);
crop.pMin.y = Clamp(std::min(cr[2], cr[3]), 0.f, 1.f);
crop.pMax.y = Clamp(std::max(cr[2], cr[3]), 0.f, 1.f);
// Compute film image bounds
pixelBounds = Bounds2i(Point2i(std::ceil(fullResolution.x * crop.pMin.x),
std::ceil(fullResolution.y * crop.pMin.y)),
Point2i(std::ceil(fullResolution.x * crop.pMax.x),
std::ceil(fullResolution.y * crop.pMax.y)));
} else
Error(loc, "%d values supplied for \"cropwindow\". Expected 4.",
(int)cr.size());
}
if (pixelBounds.IsEmpty())
ErrorExit(loc, "Degenerate pixel bounds provided to film: %s.", pixelBounds);
Float scale = parameters.GetOneFloat("scale", 1.);
Float diagonal = parameters.GetOneFloat("diagonal", 35.);
Float maxComponentValue = parameters.GetOneFloat("maxcomponentvalue", Infinity);
bool writeFP16 = parameters.GetOneBool("savefp16", true);
return alloc.new_object<RGBFilm>(fullResolution, pixelBounds, filter, diagonal,
filename, scale, colorSpace, maxComponentValue,
writeFP16, alloc);
}
// GBufferFilm Method Definitions
void GBufferFilm::AddSample(const Point2i &pFilm, SampledSpectrum L,
const SampledWavelengths &lambda,
const VisibleSurface *visibleSurface, Float weight) {
RGB rgb = L.ToRGB(lambda, *colorSpace);
Float m = std::max({rgb.r, rgb.g, rgb.b});
if (m > maxComponentValue) {
L *= maxComponentValue / m;
rgb *= maxComponentValue / m;
}
Pixel &p = pixels[pFilm];
if (visibleSurface && *visibleSurface) {
// Update variance estimates.
// TODO: store channels independently?
p.rgbVarianceEstimator.Add(L.y(lambda));
p.pSum += weight * visibleSurface->p;
p.nSum += weight * visibleSurface->n;
p.nsSum += weight * visibleSurface->ns;
p.dzdxSum += weight * visibleSurface->dzdx;
p.dzdySum += weight * visibleSurface->dzdy;
SampledSpectrum albedo =
visibleSurface->albedo * colorSpace->illuminant.Sample(lambda);
RGB albedoRGB = albedo.ToRGB(lambda, *colorSpace);
for (int c = 0; c < 3; ++c)
p.albedoSum[c] += weight * albedoRGB[c];
}
for (int c = 0; c < 3; ++c)
p.rgbSum[c] += rgb[c] * weight;
p.weightSum += weight;
}
GBufferFilm::GBufferFilm(const Point2i &resolution, const Bounds2i &pixelBounds,
FilterHandle filter, Float diagonal, const std::string &filename,
Float scale, const RGBColorSpace *colorSpace,
Float maxComponentValue, bool writeFP16, Allocator alloc)
: FilmBase(resolution, pixelBounds, filter, diagonal, filename),
pixels(pixelBounds, alloc),
scale(scale),
colorSpace(colorSpace),
maxComponentValue(maxComponentValue),
writeFP16(writeFP16),
filterIntegral(filter.Integral()) {
CHECK(!pixelBounds.IsEmpty());
filmPixelMemory += pixelBounds.Area() * sizeof(Pixel);
}
SampledWavelengths GBufferFilm::SampleWavelengths(Float u) const {
return SampledWavelengths::SampleXYZ(u);
}
void GBufferFilm::AddSplat(const Point2f &p, SampledSpectrum v,
const SampledWavelengths &lambda) {
// NOTE: same code as RGBFilm::AddSplat()...
CHECK(!v.HasNaNs());
RGB rgb = v.ToRGB(lambda, *colorSpace);
Float m = std::max({rgb.r, rgb.g, rgb.b});
if (m > maxComponentValue)
rgb *= maxComponentValue / m;
Point2f pDiscrete = p + Vector2f(0.5, 0.5);
Bounds2i splatBounds(Point2i(Floor(pDiscrete - filter.Radius())),
Point2i(Floor(pDiscrete + filter.Radius())) + Vector2i(1, 1));
splatBounds = Intersect(splatBounds, pixelBounds);
for (Point2i pi : splatBounds) {
Float wt = filter.Evaluate(Point2f(p - pi - Vector2f(0.5, 0.5)));
if (wt != 0) {
Pixel &pixel = pixels[pi];
for (int i = 0; i < 3; ++i)
pixel.splatRGB[i].Add(wt * rgb[i]);
}
}
}
void GBufferFilm::WriteImage(ImageMetadata metadata, Float splatScale) {
Image image = GetImage(&metadata, splatScale);
LOG_VERBOSE("Writing image %s with bounds %s", filename, pixelBounds);
image.Write(filename, metadata);
}
Image GBufferFilm::GetImage(ImageMetadata *metadata, Float splatScale) {
// Convert image to RGB and compute final pixel values
LOG_VERBOSE("Converting image to RGB and computing final weighted pixel values");
PixelFormat format = writeFP16 ? PixelFormat::Half : PixelFormat::Float;
Image image(format, Point2i(pixelBounds.Diagonal()),
{"R",
"G",
"B",
"Albedo.R",
"Albedo.G",
"Albedo.B",
"Px",
"Py",
"Pz",
"dzdx",
"dzdy",
"Nx",
"Ny",
"Nz",
"Nsx",
"Nsy",
"Nsz",
"materialId.R",
"materialId.G",
"materialId.B",
"rgbVariance",
"rgbRelativeVariance"});
ImageChannelDesc rgbDesc = image.GetChannelDesc({"R", "G", "B"});
ImageChannelDesc pDesc = image.GetChannelDesc({"Px", "Py", "Pz"});
ImageChannelDesc dzDesc = image.GetChannelDesc({"dzdx", "dzdy"});
ImageChannelDesc nDesc = image.GetChannelDesc({"Nx", "Ny", "Nz"});
ImageChannelDesc nsDesc = image.GetChannelDesc({"Nsx", "Nsy", "Nsz"});
ImageChannelDesc albedoRgbDesc =
image.GetChannelDesc({"Albedo.R", "Albedo.G", "Albedo.B"});
ImageChannelDesc varianceDesc =
image.GetChannelDesc({"rgbVariance", "rgbRelativeVariance"});
ParallelFor2D(pixelBounds, [&](Point2i p) {
Pixel &pixel = pixels[p];
RGB rgb(pixel.rgbSum[0], pixel.rgbSum[1], pixel.rgbSum[2]);
RGB albedoRgb(pixel.albedoSum[0], pixel.albedoSum[1], pixel.albedoSum[2]);
// Normalize pixel with weight sum
Float weightSum = pixel.weightSum;
Point3f pt = pixel.pSum;
Float dzdx = pixel.dzdxSum, dzdy = pixel.dzdySum;
if (weightSum != 0) {
rgb /= weightSum;
albedoRgb /= weightSum;
pt /= weightSum;
dzdx /= weightSum;
dzdy /= weightSum;
}
// Add splat value at pixel
for (int c = 0; c < 3; ++c)
rgb[c] += splatScale * pixel.splatRGB[c] / filterIntegral;
rgb *= scale;
Point2i pOffset(p.x - pixelBounds.pMin.x, p.y - pixelBounds.pMin.y);
image.SetChannels(pOffset, rgbDesc, {rgb[0], rgb[1], rgb[2]});
image.SetChannels(pOffset, albedoRgbDesc,
{albedoRgb[0], albedoRgb[1], albedoRgb[2]});
Normal3f n =
LengthSquared(pixel.nSum) > 0 ? Normalize(pixel.nSum) : Normal3f(0, 0, 0);
Normal3f ns =
LengthSquared(pixel.nsSum) > 0 ? Normalize(pixel.nsSum) : Normal3f(0, 0, 0);
image.SetChannels(pOffset, pDesc, {pt.x, pt.y, pt.z});
image.SetChannels(pOffset, dzDesc, {std::abs(dzdx), std::abs(dzdy)});
image.SetChannels(pOffset, nDesc, {n.x, n.y, n.z});
image.SetChannels(pOffset, nsDesc, {ns.x, ns.y, ns.z});
image.SetChannels(pOffset, varianceDesc,
{pixel.rgbVarianceEstimator.Variance(),
pixel.rgbVarianceEstimator.RelativeVariance()});
});
metadata->pixelBounds = pixelBounds;
metadata->fullResolution = fullResolution;
metadata->colorSpace = colorSpace;
Float varianceSum = 0;
for (Point2i p : pixelBounds) {
const Pixel &pixel = pixels[p];
varianceSum += pixel.rgbVarianceEstimator.Variance();
}
metadata->estimatedVariance = varianceSum / pixelBounds.Area();
return image;
}
std::string GBufferFilm::ToString() const {
return StringPrintf("[ GBufferFilm %s colorSpace: %s maxComponentValue: %f "
"writeFP16: %s ]",
BaseToString(), *colorSpace, maxComponentValue, writeFP16);
}
GBufferFilm *GBufferFilm::Create(const ParameterDictionary &parameters,
FilterHandle filter, const RGBColorSpace *colorSpace,
const FileLoc *loc, Allocator alloc) {
std::string filename = parameters.GetOneString("filename", "");
if (!Options->imageFile.empty()) {
if (!filename.empty())
Warning(loc,
"Output filename supplied on command line, \"%s\" will "
"override "
"filename provided in scene description file, \"%s\".",
Options->imageFile, filename);
filename = Options->imageFile;
} else if (filename.empty())
filename = "pbrt.exr";
Point2i fullResolution(parameters.GetOneInt("xresolution", 1280),
parameters.GetOneInt("yresolution", 720));
if (Options->quickRender) {
fullResolution.x = std::max(1, fullResolution.x / 4);
fullResolution.y = std::max(1, fullResolution.y / 4);
}
Bounds2i pixelBounds(Point2i(0, 0), fullResolution);
std::vector<int> pb = parameters.GetIntArray("pixelbounds");
if (Options->pixelBounds) {
Bounds2i newBounds = *Options->pixelBounds;
if (Intersect(newBounds, pixelBounds) != newBounds)
Warning(loc, "Supplied pixel bounds extend beyond image "
"resolution. Clamping.");
pixelBounds = Intersect(newBounds, pixelBounds);
if (!pb.empty())
Warning(loc, "Both pixel bounds and crop window were specified. Using the "
"crop window.");
} else if (!pb.empty()) {
if (pb.size() != 4)
Error(loc, "%d values supplied for \"pixelbounds\". Expected 4.",
int(pb.size()));
else {
Bounds2i newBounds = Bounds2i({pb[0], pb[2]}, {pb[1], pb[3]});
if (Intersect(newBounds, pixelBounds) != newBounds)
Warning(loc, "Supplied pixel bounds extend beyond image "
"resolution. Clamping.");
pixelBounds = Intersect(newBounds, pixelBounds);
}
}
std::vector<Float> cr = parameters.GetFloatArray("cropwindow");
if (Options->cropWindow) {
Bounds2f crop = *Options->cropWindow;
// Compute film image bounds
pixelBounds = Bounds2i(Point2i(std::ceil(fullResolution.x * crop.pMin.x),
std::ceil(fullResolution.y * crop.pMin.y)),
Point2i(std::ceil(fullResolution.x * crop.pMax.x),
std::ceil(fullResolution.y * crop.pMax.y)));
if (!cr.empty())
Warning(loc, "Crop window supplied on command line will override "
"crop window specified with Film.");
if (Options->pixelBounds || !pb.empty())
Warning(loc, "Both pixel bounds and crop window were specified. Using the "
"crop window.");
} else if (!cr.empty()) {
if (Options->pixelBounds)
Warning(loc, "Ignoring \"cropwindow\" since pixel bounds were specified "
"on the command line.");
else if (cr.size() == 4) {
if (!pb.empty())
Warning(loc, "Both pixel bounds and crop window were "
"specified. Using the "
"crop window.");
Bounds2f crop;
crop.pMin.x = Clamp(std::min(cr[0], cr[1]), 0.f, 1.f);
crop.pMax.x = Clamp(std::max(cr[0], cr[1]), 0.f, 1.f);
crop.pMin.y = Clamp(std::min(cr[2], cr[3]), 0.f, 1.f);
crop.pMax.y = Clamp(std::max(cr[2], cr[3]), 0.f, 1.f);
// Compute film image bounds
pixelBounds = Bounds2i(Point2i(std::ceil(fullResolution.x * crop.pMin.x),
std::ceil(fullResolution.y * crop.pMin.y)),
Point2i(std::ceil(fullResolution.x * crop.pMax.x),
std::ceil(fullResolution.y * crop.pMax.y)));
} else
Error(loc, "%d values supplied for \"cropwindow\". Expected 4.",
(int)cr.size());
}
if (pixelBounds.IsEmpty())
ErrorExit(loc, "Degenerate pixel bounds provided to film: %s.", pixelBounds);
Float diagonal = parameters.GetOneFloat("diagonal", 35.);
Float maxComponentValue = parameters.GetOneFloat("maxcomponentvalue", Infinity);
Float scale = parameters.GetOneFloat("scale", 1.);
bool writeFP16 = parameters.GetOneBool("savefp16", true);
return alloc.new_object<GBufferFilm>(fullResolution, pixelBounds, filter, diagonal,
filename, scale, colorSpace, maxComponentValue,
writeFP16, alloc);
}
FilmHandle FilmHandle::Create(const std::string &name,
const ParameterDictionary &parameters, const FileLoc *loc,
FilterHandle filter, Allocator alloc) {
FilmHandle film;
if (name == "rgb")
film = RGBFilm::Create(parameters, filter, parameters.ColorSpace(), loc, alloc);
else if (name == "gbuffer")
film =
GBufferFilm::Create(parameters, filter, parameters.ColorSpace(), loc, alloc);
else
ErrorExit(loc, "%s: film type unknown.", name);
if (!film)
ErrorExit(loc, "%s: unable to create film.", name);
parameters.ReportUnused();
return film;
}
} // namespace pbrt

323
src/pbrt/film.h Normal file
View file

@ -0,0 +1,323 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_FILM_H
#define PBRT_FILM_H
#include <pbrt/pbrt.h>
#include <pbrt/base/bxdf.h>
#include <pbrt/base/camera.h>
#include <pbrt/base/film.h>
#include <pbrt/bsdf.h>
#include <pbrt/util/color.h>
#include <pbrt/util/colorspace.h>
#include <pbrt/util/parallel.h>
#include <pbrt/util/pstd.h>
#include <pbrt/util/sampling.h>
#include <pbrt/util/spectrum.h>
#include <pbrt/util/transform.h>
#include <pbrt/util/vecmath.h>
#include <atomic>
#include <map>
#include <string>
#include <thread>
#include <vector>
namespace pbrt {
// VisibleSurface Definition
class VisibleSurface {
public:
// VisibleSurface Public Methods
VisibleSurface() = default;
PBRT_CPU_GPU
VisibleSurface(const SurfaceInteraction &si, const CameraTransform &cameraTransform,
const SampledSpectrum &albedo, const SampledWavelengths &lambda);
std::string ToString() const;
PBRT_CPU_GPU
operator bool() const { return set; }
// VisibleSurface Public Members
bool set = false;
Point3f p;
Normal3f n, ns;
Float time = 0;
Float dzdx = 0, dzdy = 0; // x/y: raster space, z: camera space
SampledSpectrum albedo;
};
// FilmBase Definition
class FilmBase {
public:
// FilmBase Public Methods
FilmBase(const Point2i &resolution, const Bounds2i &pixelBounds, FilterHandle filter,
Float diagonal, const std::string &filename)
: fullResolution(resolution),
diagonal(diagonal * .001),
filter(filter),
filename(filename),
pixelBounds(pixelBounds) {
CHECK(!pixelBounds.IsEmpty());
CHECK_GE(pixelBounds.pMin.x, 0);
CHECK_LE(pixelBounds.pMax.x, resolution.x);
CHECK_GE(pixelBounds.pMin.y, 0);
CHECK_LE(pixelBounds.pMax.y, resolution.y);
LOG_VERBOSE("Created film with full resolution %s, pixelBounds %s", resolution,
pixelBounds);
}
PBRT_CPU_GPU
FilterHandle GetFilter() const { return filter; }
PBRT_CPU_GPU
Point2i FullResolution() const { return fullResolution; }
PBRT_CPU_GPU
Float Diagonal() const { return diagonal; }
PBRT_CPU_GPU
Bounds2i PixelBounds() const { return pixelBounds; }
std::string GetFilename() const { return filename; }
std::string BaseToString() const;
PBRT_CPU_GPU
Bounds2f SampleBounds() const;
protected:
// FilmBase Protected Members
Point2i fullResolution;
Float diagonal;
FilterHandle filter;
std::string filename;
Bounds2i pixelBounds;
};
// RGBFilm Definition
class RGBFilm : public FilmBase {
public:
// RGBFilm Public Methods
PBRT_CPU_GPU
void AddSample(const Point2i &pFilm, SampledSpectrum L,
const SampledWavelengths &lambda, const VisibleSurface *visibleSurface,
Float weight) {
RGB rgb = L.ToRGB(lambda, *colorSpace);
// Optionally clamp sensor RGB value
Float m = std::max({rgb.r, rgb.g, rgb.b});
if (m > maxComponentValue) {
L *= maxComponentValue / m;
rgb *= maxComponentValue / m;
}
DCHECK(InsideExclusive(pFilm, pixelBounds));
// Update pixel variance estimate
pixels[pFilm].varianceEstimator.Add(L.Average());
// Update pixel values with filtered sample contribution
Pixel &pixel = pixels[pFilm];
for (int c = 0; c < 3; ++c)
pixel.rgbSum[c] += weight * rgb[c];
pixel.weightSum += weight;
}
PBRT_CPU_GPU
bool UsesVisibleSurface() const { return false; }
PBRT_CPU_GPU
RGB GetPixelRGB(const Point2i &p, Float splatScale = 1) const {
const Pixel &pixel = pixels[p];
RGB rgb(pixel.rgbSum[0], pixel.rgbSum[1], pixel.rgbSum[2]);
// Normalize _rgb_ with weight sum
Float weightSum = pixel.weightSum;
if (weightSum != 0)
rgb /= weightSum;
// Add splat value at pixel
for (int c = 0; c < 3; ++c)
rgb[c] += splatScale * pixel.splatRGB[c] / filterIntegral;
// Scale pixel value by _scale_
rgb *= scale;
return rgb;
}
RGBFilm() = default;
RGBFilm(const Point2i &resolution, const Bounds2i &pixelBounds, FilterHandle filter,
Float diagonal, const std::string &filename, Float scale,
const RGBColorSpace *colorSpace, Float maxComponentValue = Infinity,
bool writeFP16 = true, Allocator allocator = {});
static RGBFilm *Create(const ParameterDictionary &parameters, FilterHandle filter,
const RGBColorSpace *colorSpace, const FileLoc *loc,
Allocator alloc);
PBRT_CPU_GPU
SampledWavelengths SampleWavelengths(Float u) const;
PBRT_CPU_GPU
void AddSplat(const Point2f &p, SampledSpectrum v, const SampledWavelengths &lambda);
void WriteImage(ImageMetadata metadata, Float splatScale = 1);
Image GetImage(ImageMetadata *metadata, Float splatScale = 1);
std::string ToString() const;
private:
// RGBFilm::Pixel Definition
struct Pixel {
Pixel() = default;
double rgbSum[3] = {0., 0., 0.};
double weightSum = 0.;
AtomicDouble splatRGB[3];
VarianceEstimator<Float> varianceEstimator;
};
// RGBFilm Private Members
Array2D<Pixel> pixels;
Float scale;
const RGBColorSpace *colorSpace;
Float maxComponentValue;
bool writeFP16;
Float filterIntegral;
};
// GBufferFilm Definition
class GBufferFilm : public FilmBase {
public:
// GBufferFilm Public Methods
GBufferFilm(const Point2i &resolution, const Bounds2i &pixelBounds,
FilterHandle filter, Float diagonal, const std::string &filename,
Float scale, const RGBColorSpace *colorSpace,
Float maxComponentValue = Infinity, bool writeFP16 = true,
Allocator alloc = {});
static GBufferFilm *Create(const ParameterDictionary &parameters, FilterHandle filter,
const RGBColorSpace *colorSpace, const FileLoc *loc,
Allocator alloc);
PBRT_CPU_GPU
SampledWavelengths SampleWavelengths(Float u) const;
PBRT_CPU_GPU
void AddSample(const Point2i &pFilm, SampledSpectrum L,
const SampledWavelengths &lambda, const VisibleSurface *visibleSurface,
Float weight);
PBRT_CPU_GPU
void AddSplat(const Point2f &p, SampledSpectrum v, const SampledWavelengths &lambda);
PBRT_CPU_GPU
bool UsesVisibleSurface() const { return true; }
PBRT_CPU_GPU
RGB GetPixelRGB(const Point2i &p, Float splatScale = 1) const {
const Pixel &pixel = pixels[p];
RGB rgb(pixel.rgbSum[0], pixel.rgbSum[1], pixel.rgbSum[2]);
// Normalize pixel with weight sum
Float weightSum = pixel.weightSum;
if (weightSum != 0)
rgb /= weightSum;
// Add splat value at pixel
for (int c = 0; c < 3; ++c)
rgb[c] += splatScale * pixel.splatRGB[c] / filterIntegral;
// Scale pixel value by _scale_
rgb *= scale;
return rgb;
}
void WriteImage(ImageMetadata metadata, Float splatScale = 1);
Image GetImage(ImageMetadata *metadata, Float splatScale = 1);
std::string ToString() const;
private:
// GBufferFilm::Pixel Definition
struct Pixel {
Pixel() = default;
double rgbSum[3] = {0., 0., 0.};
double weightSum = 0.;
AtomicDouble splatRGB[3];
Point3f pSum;
Float dzdxSum = 0, dzdySum = 0;
Normal3f nSum, nsSum;
double albedoSum[3] = {0., 0., 0.};
VarianceEstimator<Float> rgbVarianceEstimator;
};
// GBufferFilm Private Members
Array2D<Pixel> pixels;
Float scale;
const RGBColorSpace *colorSpace;
Float maxComponentValue;
bool writeFP16;
Float filterIntegral;
};
PBRT_CPU_GPU
inline SampledWavelengths FilmHandle::SampleWavelengths(Float u) const {
auto sample = [&](auto ptr) { return ptr->SampleWavelengths(u); };
return Dispatch(sample);
}
PBRT_CPU_GPU
inline Bounds2f FilmHandle::SampleBounds() const {
auto sb = [&](auto ptr) { return ptr->SampleBounds(); };
return Dispatch(sb);
}
PBRT_CPU_GPU
inline Bounds2i FilmHandle::PixelBounds() const {
auto pb = [&](auto ptr) { return ptr->PixelBounds(); };
return Dispatch(pb);
}
PBRT_CPU_GPU
inline Point2i FilmHandle::FullResolution() const {
auto fr = [&](auto ptr) { return ptr->FullResolution(); };
return Dispatch(fr);
}
PBRT_CPU_GPU
inline Float FilmHandle::Diagonal() const {
auto diag = [&](auto ptr) { return ptr->Diagonal(); };
return Dispatch(diag);
}
PBRT_CPU_GPU
inline FilterHandle FilmHandle::GetFilter() const {
auto filter = [&](auto ptr) { return ptr->GetFilter(); };
return Dispatch(filter);
}
PBRT_CPU_GPU
inline bool FilmHandle::UsesVisibleSurface() const {
auto uses = [&](auto ptr) { return ptr->UsesVisibleSurface(); };
return Dispatch(uses);
}
PBRT_CPU_GPU
inline RGB FilmHandle::GetPixelRGB(const Point2i &p, Float splatScale) const {
auto get = [&](auto ptr) { return ptr->GetPixelRGB(p, splatScale); };
return Dispatch(get);
}
PBRT_CPU_GPU
inline void FilmHandle::AddSample(const Point2i &pFilm, SampledSpectrum L,
const SampledWavelengths &lambda,
const VisibleSurface *visibleSurface, Float weight) {
auto add = [&](auto ptr) {
return ptr->AddSample(pFilm, L, lambda, visibleSurface, weight);
};
return Dispatch(add);
}
} // namespace pbrt
#endif // PBRT_FILM_H

163
src/pbrt/filters.cpp Normal file
View file

@ -0,0 +1,163 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#include <pbrt/filters.h>
#include <pbrt/paramdict.h>
#include <pbrt/util/print.h>
#include <pbrt/util/rng.h>
namespace pbrt {
std::string FilterHandle::ToString() const {
if (ptr() == nullptr)
return "(nullptr)";
auto ts = [&](auto ptr) { return ptr->ToString(); };
return DispatchCPU(ts);
}
// Box Filter Method Definitions
std::string BoxFilter::ToString() const {
return StringPrintf("[ BoxFilter radius: %s ]", radius);
}
BoxFilter *BoxFilter::Create(const ParameterDictionary &parameters, const FileLoc *loc,
Allocator alloc) {
Float xw = parameters.GetOneFloat("xradius", 0.5f);
Float yw = parameters.GetOneFloat("yradius", 0.5f);
return alloc.new_object<BoxFilter>(Vector2f(xw, yw));
}
// Gaussian Filter Method Definitions
std::string GaussianFilter::ToString() const {
return StringPrintf(
"[ GaussianFilter radius: %s sigma: %f expX: %f expY: %f sampler: %s ]", radius,
sigma, expX, expY, sampler);
}
GaussianFilter *GaussianFilter::Create(const ParameterDictionary &parameters,
const FileLoc *loc, Allocator alloc) {
// Find common filter parameters
Float xw = parameters.GetOneFloat("xradius", 1.5f);
Float yw = parameters.GetOneFloat("yradius", 1.5f);
Float sigma = parameters.GetOneFloat("sigma", 0.5f); // equivalent to old alpha = 2
return alloc.new_object<GaussianFilter>(Vector2f(xw, yw), sigma, alloc);
}
// Mitchell Filter Method Definitions
std::string MitchellFilter::ToString() const {
return StringPrintf("[ MitchellFilter radius: %s B: %f C: %f sampler: %s ]", radius,
B, C, sampler);
}
MitchellFilter *MitchellFilter::Create(const ParameterDictionary &parameters,
const FileLoc *loc, Allocator alloc) {
// Find common filter parameters
Float xw = parameters.GetOneFloat("xradius", 2.f);
Float yw = parameters.GetOneFloat("yradius", 2.f);
Float B = parameters.GetOneFloat("B", 1.f / 3.f);
Float C = parameters.GetOneFloat("C", 1.f / 3.f);
return alloc.new_object<MitchellFilter>(Vector2f(xw, yw), B, C, alloc);
}
// Sinc Filter Method Definitions
Float LanczosSincFilter::Integral() const {
Float sum = 0;
int sqrtSamples = 64;
int nSamples = sqrtSamples * sqrtSamples;
Float area = 2 * radius.x * 2 * radius.y;
RNG rng;
for (int y = 0; y < sqrtSamples; ++y) {
for (int x = 0; x < sqrtSamples; ++x) {
Point2f u((x + rng.Uniform<Float>()) / sqrtSamples,
(y + rng.Uniform<Float>()) / sqrtSamples);
Point2f p(Lerp(u.x, -radius.x, radius.x), Lerp(u.y, -radius.y, radius.y));
sum += Evaluate(p);
}
}
return sum / nSamples * area;
}
std::string LanczosSincFilter::ToString() const {
return StringPrintf("[ LanczosSincFilter radius: %s tau: %f sampler: %s ]", radius,
tau, sampler);
}
LanczosSincFilter *LanczosSincFilter::Create(const ParameterDictionary &parameters,
const FileLoc *loc, Allocator alloc) {
Float xw = parameters.GetOneFloat("xradius", 4.);
Float yw = parameters.GetOneFloat("yradius", 4.);
Float tau = parameters.GetOneFloat("tau", 3.f);
return alloc.new_object<LanczosSincFilter>(Vector2f(xw, yw), tau, alloc);
}
// Triangle Filter Method Definitions
std::string TriangleFilter::ToString() const {
return StringPrintf("[ TriangleFilter radius: %s ]", radius);
}
TriangleFilter *TriangleFilter::Create(const ParameterDictionary &parameters,
const FileLoc *loc, Allocator alloc) {
// Find common filter parameters
Float xw = parameters.GetOneFloat("xradius", 2.f);
Float yw = parameters.GetOneFloat("yradius", 2.f);
return alloc.new_object<TriangleFilter>(Vector2f(xw, yw));
}
FilterHandle FilterHandle::Create(const std::string &name,
const ParameterDictionary &parameters,
const FileLoc *loc, Allocator alloc) {
FilterHandle filter = nullptr;
if (name == "box")
filter = BoxFilter::Create(parameters, loc, alloc);
else if (name == "gaussian")
filter = GaussianFilter::Create(parameters, loc, alloc);
else if (name == "mitchell")
filter = MitchellFilter::Create(parameters, loc, alloc);
else if (name == "sinc")
filter = LanczosSincFilter::Create(parameters, loc, alloc);
else if (name == "triangle")
filter = TriangleFilter::Create(parameters, loc, alloc);
else
ErrorExit(loc, "%s: filter type unknown.", name);
if (!filter)
ErrorExit(loc, "%s: unable to create filter.", name);
parameters.ReportUnused();
return filter;
}
// FilterSampler Method Definitions
FilterSampler::FilterSampler(FilterHandle filter, int freq, Allocator alloc)
: domain(Point2f(-filter.Radius()), Point2f(filter.Radius())),
values(int(16 * 2 * filter.Radius().x), int(16 * 2 * filter.Radius().y), alloc),
distrib(alloc) {
for (int y = 0; y < values.ySize(); ++y) {
for (int x = 0; x < values.xSize(); ++x) {
Point2f p = domain.Lerp(
Point2f((x + 0.5f) / values.xSize(), (y + 0.5f) / values.ySize()));
values(x, y) = std::abs(filter.Evaluate(p));
}
}
distrib = std::move(PiecewiseConstant2D(values, domain, alloc));
// And again without the abs() for use in Sample...
for (int y = 0; y < values.ySize(); ++y) {
for (int x = 0; x < values.xSize(); ++x) {
Point2f p = domain.Lerp(
Point2f((x + 0.5f) / values.xSize(), (y + 0.5f) / values.ySize()));
values(x, y) = filter.Evaluate(p);
}
}
}
std::string FilterSampler::ToString() const {
return StringPrintf("[ FilterSampler domain: %s values: %s distrib: %s ]", domain,
values, distrib);
}
} // namespace pbrt

258
src/pbrt/filters.h Normal file
View file

@ -0,0 +1,258 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_FILTERS_H
#define PBRT_FILTERS_H
#include <pbrt/pbrt.h>
#include <pbrt/base/filter.h>
#include <pbrt/util/math.h>
#include <pbrt/util/sampling.h>
#include <cmath>
#include <memory>
#include <string>
namespace pbrt {
// FilterSample Definition
struct FilterSample {
Point2f p;
Float weight;
};
class FilterSampler {
public:
// FilterSampler Public Methods
FilterSampler(FilterHandle filter, int freq = 64, Allocator alloc = {});
std::string ToString() const;
PBRT_CPU_GPU
FilterSample Sample(const Point2f &u) const {
Point2f p = distrib.Sample(u);
Point2f p01 = Point2f(domain.Offset(p));
Point2i pi(Clamp(p01.x * values.xSize() + 0.5f, 0, values.xSize() - 1),
Clamp(p01.y * values.ySize() + 0.5f, 0, values.ySize() - 1));
return {p, values[pi] < 0 ? -1.f : 1.f};
}
private:
// FilterSampler Private Members
Bounds2f domain;
Array2D<Float> values;
PiecewiseConstant2D distrib;
};
// BoxFilter Definition
class BoxFilter {
public:
// BoxFilter Public Methods
BoxFilter(const Vector2f &radius = Vector2f(0.5, 0.5)) : radius(radius) {}
static BoxFilter *Create(const ParameterDictionary &parameters, const FileLoc *loc,
Allocator alloc);
PBRT_CPU_GPU
Vector2f Radius() const { return radius; }
std::string ToString() const;
PBRT_CPU_GPU
Float Evaluate(const Point2f &p) const {
return (std::abs(p.x) <= radius.x && std::abs(p.y) <= radius.y) ? 1 : 0;
}
PBRT_CPU_GPU
FilterSample Sample(const Point2f &u) const {
Point2f p(Lerp(u[0], -radius.x, radius.x), Lerp(u[1], -radius.y, radius.y));
return {p, 1.f};
}
PBRT_CPU_GPU
Float Integral() const { return 2 * radius.x * 2 * radius.y; }
private:
Vector2f radius;
};
// GaussianFilter Definition
class GaussianFilter {
public:
// GaussianFilter Public Methods
GaussianFilter(const Vector2f &radius, Float sigma = 0.5f, Allocator alloc = {})
: radius(radius),
sigma(sigma),
expX(Gaussian(radius.x, 0, sigma)),
expY(Gaussian(radius.y, 0, sigma)),
sampler(this, 64, alloc) {}
static GaussianFilter *Create(const ParameterDictionary &parameters,
const FileLoc *loc, Allocator alloc);
PBRT_CPU_GPU
Vector2f Radius() const { return radius; }
std::string ToString() const;
PBRT_CPU_GPU
Float Evaluate(const Point2f &p) const {
return (std::max<Float>(0, Gaussian(p.x, 0, sigma) - expX) *
std::max<Float>(0, Gaussian(p.y, 0, sigma) - expY));
}
PBRT_CPU_GPU
FilterSample Sample(const Point2f &u) const { return sampler.Sample(u); }
PBRT_CPU_GPU
Float Integral() const {
return ((GaussianIntegral(-radius.x, radius.x, 0, sigma) - 2 * radius.x * expX) *
(GaussianIntegral(-radius.y, radius.y, 0, sigma) - 2 * radius.y * expY));
}
private:
// GaussianFilter Private Members
Vector2f radius;
Float sigma;
Float expX, expY;
FilterSampler sampler;
};
// MitchellFilter Definition
class MitchellFilter {
public:
// MitchellFilter Public Methods
MitchellFilter(const Vector2f &radius, Float B = 1.f / 3.f, Float C = 1.f / 3.f,
Allocator alloc = {})
: radius(radius), B(B), C(C), sampler(this, 64, alloc) {}
static MitchellFilter *Create(const ParameterDictionary &parameters,
const FileLoc *loc, Allocator alloc);
PBRT_CPU_GPU
Vector2f Radius() const { return radius; }
std::string ToString() const;
PBRT_CPU_GPU
Float Evaluate(const Point2f &p) const {
return Mitchell1D(p.x / radius.x) * Mitchell1D(p.y / radius.y);
}
PBRT_CPU_GPU
FilterSample Sample(const Point2f &u) const { return sampler.Sample(u); }
PBRT_CPU_GPU
Float Integral() const { return radius.x * radius.y / 4; }
private:
// MitchellFilter Private Methods
PBRT_CPU_GPU
Float Mitchell1D(Float x) const {
x = std::abs(2 * x);
if (x <= 1)
return ((12 - 9 * B - 6 * C) * x * x * x + (-18 + 12 * B + 6 * C) * x * x +
(6 - 2 * B)) *
(1.f / 6.f);
else if (x <= 2)
return ((-B - 6 * C) * x * x * x + (6 * B + 30 * C) * x * x +
(-12 * B - 48 * C) * x + (8 * B + 24 * C)) *
(1.f / 6.f);
else
return 0;
}
// MitchellFilter Private Members
Vector2f radius;
Float B, C;
FilterSampler sampler;
};
// LanczosSincFilter Definition
class LanczosSincFilter {
public:
// LanczosSincFilter Public Methods
LanczosSincFilter(const Vector2f &radius, Float tau = 3.f, Allocator alloc = {})
: radius(radius), tau(tau), sampler(this, 64, alloc) {}
static LanczosSincFilter *Create(const ParameterDictionary &parameters,
const FileLoc *loc, Allocator alloc);
PBRT_CPU_GPU
Vector2f Radius() const { return radius; }
std::string ToString() const;
PBRT_CPU_GPU
Float Evaluate(const Point2f &p) const {
return WindowedSinc(p.x, radius.x, tau) * WindowedSinc(p.y, radius.y, tau);
}
PBRT_CPU_GPU
FilterSample Sample(const Point2f &u) const { return sampler.Sample(u); }
PBRT_CPU_GPU
Float Integral() const;
private:
Vector2f radius;
Float tau;
FilterSampler sampler;
};
// TriangleFilter Definition
class TriangleFilter {
public:
// TriangleFilter Public Methods
TriangleFilter(const Vector2f &radius) : radius(radius) {}
static TriangleFilter *Create(const ParameterDictionary &parameters,
const FileLoc *loc, Allocator alloc);
PBRT_CPU_GPU
Vector2f Radius() const { return radius; }
std::string ToString() const;
PBRT_CPU_GPU
Float Evaluate(const Point2f &p) const {
return std::max<Float>(0, radius.x - std::abs(p.x)) *
std::max<Float>(0, radius.y - std::abs(p.y));
}
PBRT_CPU_GPU
FilterSample Sample(const Point2f &u) const {
return {Point2f(SampleTent(u[0], radius.x), SampleTent(u[1], radius.y)), 1.f};
}
PBRT_CPU_GPU
Float Integral() const { return radius.x * radius.x * radius.y * radius.y; }
private:
Vector2f radius;
};
inline Float FilterHandle::Evaluate(const Point2f &p) const {
auto eval = [&](auto ptr) { return ptr->Evaluate(p); };
return Dispatch(eval);
}
inline FilterSample FilterHandle::Sample(const Point2f &u) const {
auto sample = [&](auto ptr) { return ptr->Sample(u); };
return Dispatch(sample);
}
inline Vector2f FilterHandle::Radius() const {
auto radius = [&](auto ptr) { return ptr->Radius(); };
return Dispatch(radius);
}
inline Float FilterHandle::Integral() const {
auto integral = [&](auto ptr) { return ptr->Integral(); };
return Dispatch(integral);
}
} // namespace pbrt
#endif // PBRT_FILTERS_H

100
src/pbrt/filters_test.cpp Normal file
View file

@ -0,0 +1,100 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#include <gtest/gtest.h>
#include <pbrt/filters.h>
#include <pbrt/pbrt.h>
#include <pbrt/util/math.h>
#include <pbrt/util/sampling.h>
#include <algorithm>
#include <vector>
using namespace pbrt;
TEST(Sinc, ZeroHandling) {
Float x = 0;
Float prev = 1;
for (int i = 0; i < 10000; ++i) {
Float cur = Sinc(x);
EXPECT_LE(cur, prev);
x = NextFloatUp(x);
prev = cur;
}
x = -0;
prev = 1;
for (int i = 0; i < 10000; ++i) {
Float cur = Sinc(x);
EXPECT_LE(cur, prev);
x = NextFloatDown(x);
prev = cur;
}
}
TEST(Filter, ZeroPastRadius) {
auto makeFilters = [](const Vector2f &radius) -> std::vector<FilterHandle> {
return {new BoxFilter(radius), new GaussianFilter(radius),
new MitchellFilter(radius), new LanczosSincFilter(radius),
new TriangleFilter(radius)};
};
for (Vector2f r : {Vector2f(1, 1), Vector2f(1.5, .25), Vector2f(.33, 5.2),
Vector2f(.1, .1), Vector2f(3, 3)}) {
for (FilterHandle f : makeFilters(r)) {
EXPECT_EQ(0, f.Evaluate(Point2f(0, r.y + 1e-3)));
EXPECT_EQ(0, f.Evaluate(Point2f(r.x, r.y + 1e-3)));
EXPECT_EQ(0, f.Evaluate(Point2f(-r.x, r.y + 1e-3)));
EXPECT_EQ(0, f.Evaluate(Point2f(0, -r.y - 1e-3)));
EXPECT_EQ(0, f.Evaluate(Point2f(r.x, -r.y - 1e-3)));
EXPECT_EQ(0, f.Evaluate(Point2f(-r.x, -r.y - 1e-3)));
EXPECT_EQ(0, f.Evaluate(Point2f(r.x + 1e-3, 0)));
EXPECT_EQ(0, f.Evaluate(Point2f(r.x + 1e-3, r.y)));
EXPECT_EQ(0, f.Evaluate(Point2f(r.x + 1e-3, -r.y)));
EXPECT_EQ(0, f.Evaluate(Point2f(-r.x - 1e-3, 0)));
EXPECT_EQ(0, f.Evaluate(Point2f(-r.x - 1e-3, r.y)));
EXPECT_EQ(0, f.Evaluate(Point2f(-r.x - 1e-3, -r.y)));
}
}
}
static Float integrateFilter(FilterHandle f) {
Float sum = 0;
int sqrtSamples = 256;
int nSamples = sqrtSamples * sqrtSamples;
Float area = 2 * f.Radius().x * 2 * f.Radius().y;
for (Point2f u : Stratified2D(sqrtSamples, sqrtSamples)) {
Point2f p(Lerp(u.x, -f.Radius().x, f.Radius().x),
Lerp(u.y, -f.Radius().y, f.Radius().y));
sum += f.Evaluate(p);
}
return sum / nSamples * area;
}
TEST(Filter, Integral) {
auto approxEqual = [](Float a, Float b) {
if (std::max(std::abs(a), std::abs(b)) < 1e-3)
return std::abs(a - b) < 1e-5;
else
return 2 * std::abs(a - b) / std::abs(a + b) < 1e-2;
};
auto makeFilters = [](const Vector2f &radius) -> std::vector<FilterHandle> {
return {new BoxFilter(radius), new GaussianFilter(radius),
new MitchellFilter(radius), new LanczosSincFilter(radius),
new TriangleFilter(radius)};
};
for (FilterHandle f : makeFilters(Vector2f(1, 1)))
EXPECT_TRUE(approxEqual(f.Integral(), integrateFilter(f))) << f;
for (FilterHandle f : makeFilters(Vector2f(2.5, 1)))
EXPECT_TRUE(approxEqual(f.Integral(), integrateFilter(f))) << f;
for (FilterHandle f : makeFilters(Vector2f(1, 2.5)))
EXPECT_TRUE(approxEqual(f.Integral(), integrateFilter(f))) << f;
for (FilterHandle f : makeFilters(Vector2f(3.4, 2.5)))
EXPECT_TRUE(approxEqual(f.Integral(), integrateFilter(f))) << f;
}

1207
src/pbrt/gpu/accel.cpp Normal file

File diff suppressed because it is too large Load diff

120
src/pbrt/gpu/accel.h Normal file
View file

@ -0,0 +1,120 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_GPU_ACCEL_H
#define PBRT_GPU_ACCEL_H
#include <pbrt/pbrt.h>
#include <pbrt/gpu/optix.h>
#include <pbrt/gpu/workitems.h>
#include <pbrt/materials.h>
#include <pbrt/parsedscene.h>
#include <pbrt/util/containers.h>
#include <pbrt/util/pstd.h>
#include <pbrt/util/soa.h>
#include <map>
#include <string>
#include <utility>
#include <vector>
#include <cuda.h>
#include <cuda_runtime.h>
#include <optix.h>
#include <cuda/std/atomic>
namespace pbrt {
class GPUAccel {
public:
GPUAccel(const ParsedScene &scene, Allocator alloc, CUstream cudaStream,
const std::map<int, pstd::vector<LightHandle> *> &shapeIndexToAreaLights,
const std::map<std::string, MediumHandle> &media,
pstd::array<bool, MaterialHandle::NumTags()> *haveBasicEvalMaterial,
pstd::array<bool, MaterialHandle::NumTags()> *haveUniversalEvalMaterial,
bool *haveSubsurface);
Bounds3f Bounds() const { return bounds; }
std::pair<cudaEvent_t, cudaEvent_t> IntersectClosest(
int maxRays, EscapedRayQueue *escapedRayQueue,
HitAreaLightQueue *hitAreaLightQueue, MaterialEvalQueue *basicEvalMaterialQueue,
MaterialEvalQueue *universalEvalMaterialQueue,
MediumTransitionQueue *mediumTransitionQueue,
MediumSampleQueue *mediumSampleQueue, RayQueue *rayQueue) const;
std::pair<cudaEvent_t, cudaEvent_t> IntersectShadow(
int maxRays, ShadowRayQueue *shadowRayQueue) const;
std::pair<cudaEvent_t, cudaEvent_t> IntersectShadowTr(int maxRays,
ShadowRayQueue *shadowRayQueue) const;
std::pair<cudaEvent_t, cudaEvent_t> IntersectOneRandom(
int maxRays, SubsurfaceScatterQueue *subsurfaceScatterQueue) const;
private:
struct HitgroupRecord;
OptixTraversableHandle createGASForTriangles(
const std::vector<ShapeSceneEntity> &shapes, const OptixProgramGroup &intersectPG,
const OptixProgramGroup &shadowPG, const OptixProgramGroup &randomHitPG,
const std::map<std::string, FloatTextureHandle> &floatTextures,
const std::map<std::string, MaterialHandle> &namedMaterials,
const std::vector<MaterialHandle> &materials,
const std::map<std::string, MediumHandle> &media,
const std::map<int, pstd::vector<LightHandle> *> &shapeIndexToAreaLights,
Bounds3f *gasBounds);
OptixTraversableHandle createGASForBLPs(
const std::vector<ShapeSceneEntity> &shapes, const OptixProgramGroup &intersectPG,
const OptixProgramGroup &shadowPG, const OptixProgramGroup &randomHitPG,
const std::map<std::string, FloatTextureHandle> &floatTextures,
const std::map<std::string, MaterialHandle> &namedMaterials,
const std::vector<MaterialHandle> &materials,
const std::map<std::string, MediumHandle> &media,
const std::map<int, pstd::vector<LightHandle> *> &shapeIndexToAreaLights,
Bounds3f *gasBounds);
OptixTraversableHandle createGASForQuadrics(
const std::vector<ShapeSceneEntity> &shapes, const OptixProgramGroup &intersectPG,
const OptixProgramGroup &shadowPG, const OptixProgramGroup &randomHitPG,
const std::map<std::string, FloatTextureHandle> &floatTextures,
const std::map<std::string, MaterialHandle> &namedMaterials,
const std::vector<MaterialHandle> &materials,
const std::map<std::string, MediumHandle> &media,
const std::map<int, pstd::vector<LightHandle> *> &shapeIndexToAreaLights,
Bounds3f *gasBounds);
OptixTraversableHandle buildBVH(const std::vector<OptixBuildInput> &buildInputs);
Allocator alloc;
Bounds3f bounds;
CUstream cudaStream;
OptixDeviceContext optixContext;
OptixModule optixModule;
OptixPipeline optixPipeline;
struct ParamBufferState {
bool used = false;
cudaEvent_t finishedEvent;
CUdeviceptr ptr = 0;
void *hostPtr = nullptr;
};
mutable std::vector<ParamBufferState> paramsPool;
mutable size_t nextParamOffset = 0;
ParamBufferState &getParamBuffer(const RayIntersectParameters &) const;
pstd::vector<HitgroupRecord> intersectHGRecords;
pstd::vector<HitgroupRecord> shadowHGRecords;
pstd::vector<HitgroupRecord> randomHitHGRecords;
OptixShaderBindingTable intersectSBT = {}, shadowSBT = {}, shadowTrSBT = {};
OptixShaderBindingTable randomHitSBT = {};
OptixTraversableHandle rootTraversable = {};
};
} // namespace pbrt
#endif // PBRT_GPU_ACCEL_H

96
src/pbrt/gpu/camera.cpp Normal file
View file

@ -0,0 +1,96 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#include <pbrt/pbrt.h>
#include <pbrt/cameras.h>
#include <pbrt/gpu/launch.h>
#include <pbrt/gpu/pathintegrator.h>
#include <pbrt/options.h>
#include <pbrt/samplers.h>
#include <pbrt/util/bluenoise.h>
#include <pbrt/util/spectrum.h>
#include <pbrt/util/vecmath.h>
#ifdef PBRT_GPU_DBG
#ifndef TO_STRING
#define TO_STRING(x) TO_STRING2(x)
#define TO_STRING2(x) #x
#endif // !TO_STRING
#define DBG(...) printf(__FILE__ ":" TO_STRING(__LINE__) ": " __VA_ARGS__)
#else
#define DBG(...)
#endif // PBRT_GPU_DBG
namespace pbrt {
template <typename Sampler>
void GPUPathIntegrator::GenerateCameraRays(int y0, int sampleIndex) {
Vector2i resolution = film.PixelBounds().Diagonal();
Bounds2i pixelBounds = film.PixelBounds();
GPUParallelFor("Generate Camera rays", maxQueueSize, [=] PBRT_GPU(int pixelIndex) {
Point2i pPixel(pixelBounds.pMin.x + int(pixelIndex) % resolution.x,
pixelBounds.pMin.y + y0 + int(pixelIndex) / resolution.x);
pixelSampleState.pPixel[pixelIndex] = pPixel;
// If we've split the image into multiple spans of scanlines,
// then in the final pass, we may have a few more threads
// launched than there are remaining pixels. Bail out without
// enqueuing a ray if so.
if (!InsideExclusive(pPixel, pixelBounds))
return;
// Initialize the Sampler for the current pixel and sample.
Sampler sampler = *this->sampler.Cast<Sampler>();
sampler.StartPixelSample(pPixel, sampleIndex, 0);
// Sample wavelengths for the ray path for the pixel sample.
// Use a blue noise pattern rather than the Sampler.
Float lu = RadicalInverse(1, sampleIndex) + BlueNoise(47, pPixel.x, pPixel.y);
if (lu >= 1)
lu -= 1;
if (GetOptions().disableWavelengthJitter)
lu = 0.5f;
SampledWavelengths lambda = film.SampleWavelengths(lu);
// Generate samples for the camera ray and the ray itself.
CameraSample cameraSample = GetCameraSample(sampler, pPixel, filter);
CameraRay cameraRay = camera.GenerateRay(cameraSample, lambda);
// Initialize the rest of the pixel sample's state.
pixelSampleState.L[pixelIndex] = SampledSpectrum(0.f);
pixelSampleState.lambda[pixelIndex] = lambda;
pixelSampleState.cameraRayWeight[pixelIndex] = cameraRay.weight;
pixelSampleState.filterWeight[pixelIndex] = cameraSample.weight;
if (initializeVisibleSurface)
pixelSampleState.visibleSurface[pixelIndex] = VisibleSurface();
if (cameraRay.weight)
// Enqueue the camera ray if the camera gave us one with
// non-zero weight. (RealisticCamera doesn't always return
// a ray, e.g. in the case of vignetting...)
rayQueues[0]->PushCameraRay(cameraRay.ray, lambda, pixelIndex);
});
}
void GPUPathIntegrator::GenerateCameraRays(int y0, int sampleIndex) {
auto generateRays = [=](auto sampler) {
using Sampler = std::remove_reference_t<decltype(*sampler)>;
if constexpr (!std::is_same_v<Sampler, MLTSampler> &&
!std::is_same_v<Sampler, DebugMLTSampler>)
GenerateCameraRays<Sampler>(y0, sampleIndex);
};
// Somewhat surprisingly, GenerateCameraRays() is specialized on the
// type of the Sampler being used and not on, say, the Camera. By
// specializing on the sampler type, the particular Sampler used can be
// stack allocated (rather than living in global memory), which in turn
// allows its state to be stored in registers in the
// GenerateCameraRays() kernel. There's little benefit from
// specializing on the Camera since its state is read-only and shared
// among all of the threads, so caches well in practice.
sampler.DispatchCPU(generateRays);
}
} // namespace pbrt

44
src/pbrt/gpu/film.cpp Normal file
View file

@ -0,0 +1,44 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#include <pbrt/pbrt.h>
#include <pbrt/film.h>
#include <pbrt/gpu/launch.h>
#include <pbrt/gpu/pathintegrator.h>
#ifdef PBRT_GPU_DBG
#ifndef TO_STRING
#define TO_STRING(x) TO_STRING2(x)
#define TO_STRING2(x) #x
#endif // !TO_STRING
#define DBG(...) printf(__FILE__ ":" TO_STRING(__LINE__) ": " __VA_ARGS__)
#else
#define DBG(...)
#endif
namespace pbrt {
void GPUPathIntegrator::UpdateFilm() {
GPUParallelFor("Update Film", maxQueueSize, [=] PBRT_GPU(int pixelIndex) {
Point2i pPixel = pixelSampleState.pPixel[pixelIndex];
if (!InsideExclusive(pPixel, film.PixelBounds()))
return;
// Compute final weighted radiance value
SampledSpectrum Lw = SampledSpectrum(pixelSampleState.L[pixelIndex]) *
pixelSampleState.cameraRayWeight[pixelIndex];
SampledWavelengths lambda = pixelSampleState.lambda[pixelIndex];
Float filterWeight = pixelSampleState.filterWeight[pixelIndex];
if (initializeVisibleSurface) {
VisibleSurface visibleSurface = pixelSampleState.visibleSurface[pixelIndex];
film.AddSample(pPixel, Lw, lambda, &visibleSurface, filterWeight);
} else
film.AddSample(pPixel, Lw, lambda, nullptr, filterWeight);
});
}
} // namespace pbrt

72
src/pbrt/gpu/init.cpp Normal file
View file

@ -0,0 +1,72 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#include <pbrt/gpu/init.h>
#include <pbrt/options.h>
#include <pbrt/util/check.h>
#include <pbrt/util/log.h>
#include <pbrt/util/print.h>
#include <cuda.h>
#ifdef NVTX
#include <nvtx3/nvToolsExtCuda.h>
#endif
namespace pbrt {
void GPUInit() {
cudaFree(nullptr);
int driverVersion;
CUDA_CHECK(cudaDriverGetVersion(&driverVersion));
int runtimeVersion;
CUDA_CHECK(cudaRuntimeGetVersion(&runtimeVersion));
auto versionToString = [](int version) {
int major = version / 1000;
int minor = (version - major * 1000) / 10;
return StringPrintf("%d.%d", major, minor);
};
LOG_VERBOSE("GPU CUDA driver %s, CUDA runtime %s", versionToString(driverVersion),
versionToString(runtimeVersion));
int nDevices;
CUDA_CHECK(cudaGetDeviceCount(&nDevices));
for (int i = 0; i < nDevices; ++i) {
cudaDeviceProp deviceProperties;
CUDA_CHECK(cudaGetDeviceProperties(&deviceProperties, i));
CHECK(deviceProperties.canMapHostMemory);
size_t stackSize;
CUDA_CHECK(cudaDeviceGetLimit(&stackSize, cudaLimitStackSize));
size_t printfFIFOSize;
CUDA_CHECK(cudaDeviceGetLimit(&printfFIFOSize, cudaLimitPrintfFifoSize));
LOG_VERBOSE(
"CUDA device %d (%s) with %f MiB, %d SMs running at %f MHz "
"with shader model %d.%d, max stack %d printf FIFO %d",
i, deviceProperties.name, deviceProperties.totalGlobalMem / (1024. * 1024.),
deviceProperties.multiProcessorCount, deviceProperties.clockRate / 1000.,
deviceProperties.major, deviceProperties.minor, stackSize, printfFIFOSize);
}
int device = Options->gpuDevice ? *Options->gpuDevice : 0;
LOG_VERBOSE("Selecting GPU device %d", device);
#ifdef NVTX
nvtxNameCuDevice(device, "PBRT_GPU");
#endif
CUDA_CHECK(cudaSetDevice(device));
CUDA_CHECK(cudaDeviceSetLimit(cudaLimitStackSize, 8192));
size_t stackSize;
CUDA_CHECK(cudaDeviceGetLimit(&stackSize, cudaLimitStackSize));
LOG_VERBOSE("Reset stack size to %d", stackSize);
CUDA_CHECK(cudaDeviceSetLimit(cudaLimitPrintfFifoSize, 32 * 1024 * 1024));
CUDA_CHECK(cudaDeviceSetCacheConfig(cudaFuncCachePreferL1));
}
} // namespace pbrt

14
src/pbrt/gpu/init.h Normal file
View file

@ -0,0 +1,14 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_GPU_INIT_H
#define PBRT_GPU_INIT_H
namespace pbrt {
void GPUInit();
} // namespace pbrt
#endif // PBRT_GPU_INIT_H

77
src/pbrt/gpu/launch.cpp Normal file
View file

@ -0,0 +1,77 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#include <pbrt/gpu/launch.h>
#include <pbrt/util/print.h>
#include <map>
#include <vector>
namespace pbrt {
static std::vector<std::type_index> gpuKernelLaunchOrder;
static std::map<std::type_index, GPUKernelStats> gpuKernels;
GPUKernelStats &GetGPUKernelStats(std::type_index typeIndex, const char *description) {
auto iter = gpuKernels.find(typeIndex);
if (iter != gpuKernels.end()) {
CHECK_EQ(iter->second.description, std::string(description));
return iter->second;
}
gpuKernelLaunchOrder.push_back(typeIndex);
gpuKernels[typeIndex] = GPUKernelStats(description);
return gpuKernels.find(typeIndex)->second;
}
void ReportKernelStats() {
CUDA_CHECK(cudaDeviceSynchronize());
// Compute total milliseconds over all kernels and launches
float totalms = 0.f;
for (const auto kernelTypeId : gpuKernelLaunchOrder) {
const GPUKernelStats &stats = gpuKernels[kernelTypeId];
for (const auto &launch : stats.launchEvents) {
cudaEventSynchronize(launch.second);
float ms = 0;
cudaEventElapsedTime(&ms, launch.first, launch.second);
totalms += ms;
}
}
printf("GPU Kernel Profile:\n");
int otherLaunches = 0;
float otherms = 0;
const float otherCutoff = 0.001f * totalms;
for (const auto kernelTypeId : gpuKernelLaunchOrder) {
float summs = 0.f, minms = 1e30, maxms = 0;
const GPUKernelStats &stats = gpuKernels[kernelTypeId];
for (const auto &launch : stats.launchEvents) {
float ms = 0;
cudaEventElapsedTime(&ms, launch.first, launch.second);
summs += ms;
minms = std::min(minms, ms);
maxms = std::max(maxms, ms);
}
if (summs > otherCutoff)
Printf(" %-49s %5d launches %9.2f ms / %5.1f%s (avg %6.3f, min "
"%6.3f, max %7.3f)\n",
stats.description, stats.launchEvents.size(), summs,
100.f * summs / totalms, "%", summs / stats.launchEvents.size(), minms,
maxms);
else {
otherms += summs;
otherLaunches += stats.launchEvents.size();
}
}
Printf(" %-49s %5d launches %9.2f ms / %5.1f%s (avg %6.3f)\n", "Other",
otherLaunches, otherms, 100.f * otherms / totalms, "%",
otherms / otherLaunches);
Printf("\nTotal GPU time: %9.2f ms\n", totalms);
Printf("\n");
}
} // namespace pbrt

101
src/pbrt/gpu/launch.h Normal file
View file

@ -0,0 +1,101 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_GPU_LAUNCH_H
#define PBRT_GPU_LAUNCH_H
#include <pbrt/pbrt.h>
#include <pbrt/util/check.h>
#include <pbrt/util/log.h>
#include <typeindex>
#include <typeinfo>
#include <vector>
#include <cuda.h>
#include <cuda_runtime_api.h>
#ifdef NVTX
#include <nvtx3/nvToolsExt.h>
#endif
namespace pbrt {
struct GPUKernelStats {
GPUKernelStats() = default;
GPUKernelStats(const char *description) : description(description) {
launchEvents.reserve(256);
}
std::string description;
int blockSize = 0;
std::vector<std::pair<cudaEvent_t, cudaEvent_t>> launchEvents;
};
GPUKernelStats &GetGPUKernelStats(std::type_index typeIndex, const char *description);
template <typename T>
inline GPUKernelStats &GetGPUKernelStats(const char *description) {
return GetGPUKernelStats(std::type_index(typeid(T)), description);
}
template <typename F>
__global__ void Kernel(F func, int nItems) {
int tid = blockIdx.x * blockDim.x + threadIdx.x;
if (tid >= nItems)
return;
func(tid);
}
template <typename F>
void GPUParallelFor(const char *description, int nItems, F func) {
#ifdef NVTX
nvtxRangePush(description);
#endif
auto kernel = &Kernel<F>;
GPUKernelStats &kernelStats = GetGPUKernelStats<F>(description);
if (kernelStats.blockSize == 0) {
int minGridSize;
CUDA_CHECK(cudaOccupancyMaxPotentialBlockSize(
&minGridSize, &kernelStats.blockSize, kernel, 0, 0));
LOG_VERBOSE("[%s]: block size %d", description, kernelStats.blockSize);
}
cudaEvent_t start, stop;
cudaEventCreate(&start);
cudaEventCreate(&stop);
#ifndef NDEBUG
LOG_VERBOSE("Launching %s", description);
#endif
cudaEventRecord(start);
int gridSize = (nItems + kernelStats.blockSize - 1) / kernelStats.blockSize;
kernel<<<gridSize, kernelStats.blockSize>>>(func, nItems);
cudaEventRecord(stop);
kernelStats.launchEvents.push_back(std::make_pair(start, stop));
#ifndef NDEBUG
CUDA_CHECK(cudaDeviceSynchronize());
LOG_VERBOSE("Post-sync %s", description);
#endif
#ifdef NVTX
nvtxRangePop();
#endif
}
template <typename F>
void GPUDo(const char *description, F func) {
GPUParallelFor(description, 1, [=] PBRT_GPU(int) { func(); });
}
void ReportKernelStats();
} // namespace pbrt
#endif // PBRT_GPU_LAUNCH_H

334
src/pbrt/gpu/media.cpp Normal file
View file

@ -0,0 +1,334 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#include <pbrt/gpu/pathintegrator.h>
#include <pbrt/gpu/accel.h>
#include <pbrt/gpu/launch.h>
#include <pbrt/media.h>
#ifdef PBRT_GPU_DBG
#ifndef TO_STRING
#define TO_STRING(x) TO_STRING2(x)
#define TO_STRING2(x) #x
#endif // !TO_STRING
#define DBG(...) printf(__FILE__ ":" TO_STRING(__LINE__) ": " __VA_ARGS__)
#else
#define DBG(...)
#endif // PBRT_GPU_DBG
namespace pbrt {
void GPUPathIntegrator::SampleMediumInteraction(int depth) {
ForAllQueued(
"Sample medium interaction", mediumSampleQueue, maxQueueSize,
[=] PBRT_GPU(MediumSampleWorkItem ms, int index) {
Ray ray = ms.ray;
Float tMax = ms.tMax;
DBG("Sampling medium interaction ray index %d depth %d ray %f %f %f d %f %f "
"%f tMax %f\n",
ms.rayIndex, depth, ray.o.x, ray.o.y, ray.o.z, ray.d.x, ray.d.y, ray.d.z,
tMax);
SampledWavelengths lambda = ms.lambda;
SampledSpectrum beta = ms.beta;
SampledSpectrum pdfUni = ms.pdfUni;
SampledSpectrum pdfNEE = ms.pdfNEE;
SampledSpectrum L(0.f);
RNG rng(Hash(tMax), Hash(ray.d));
DBG("Lambdas %f %f %f %f\n", lambda[0], lambda[1], lambda[2], lambda[3]);
DBG("Medium sample beta %f %f %f %f pdfUni %f %f %f %f pdfNEE %f %f %f %f\n",
beta[0], beta[1], beta[2], beta[3], pdfUni[0], pdfUni[1], pdfUni[2],
pdfUni[3], pdfNEE[0], pdfNEE[1], pdfNEE[2], pdfNEE[3]);
// Sample the medium according to T_maj, the homogeneous
// transmission function based on the majorant.
bool scattered = false;
ray.medium.SampleTmaj(
ray, tMax, rng, lambda, [&](const MediumSample &mediumSample) {
if (!mediumSample.intr) {
// No interaction was sampled, but update the path
// throughput and unidirectional PDF to the end of
// the ray segment.
beta *= mediumSample.Tmaj;
pdfUni *= mediumSample.Tmaj;
DBG("No intr: beta %f %f %f %f pdfUni %f %f %f %f\n", beta[0],
beta[1], beta[2], beta[3], pdfUni[0], pdfUni[1], pdfUni[2],
pdfUni[3]);
return false;
}
const MediumInteraction &intr = *mediumSample.intr;
const SampledSpectrum &sigma_a = intr.sigma_a;
const SampledSpectrum &sigma_s = intr.sigma_s;
const SampledSpectrum &Tmaj = mediumSample.Tmaj;
DBG("Medium event Tmaj %f %f %f %f sigma_a %f %f %f %f sigma_s %f %f "
"%f %f\n",
Tmaj[0], Tmaj[1], Tmaj[2], Tmaj[3], sigma_a[0], sigma_a[1],
sigma_a[2], sigma_a[3], sigma_s[0], sigma_s[1], sigma_s[2],
sigma_s[3]);
// Add emission, if present. Always do this and scale
// by sigma_a/sigma_maj rather than only doing it
// (without scaling) at absorption events.
if (depth < maxDepth && intr.Le)
L += beta * intr.Le * sigma_a /
(intr.sigma_maj[0] * pdfUni.Average());
// Compute probabilities for each type of scattering.
Float pAbsorb = sigma_a[0] / intr.sigma_maj[0];
Float pScatter = sigma_s[0] / intr.sigma_maj[0];
Float pNull = std::max<Float>(0, 1 - pAbsorb - pScatter);
DBG("Medium scattering probabilities: %f %f %f\n", pAbsorb, pScatter,
pNull);
// And randomly choose one.
Float um = rng.Uniform<Float>();
int mode = SampleDiscrete({pAbsorb, pScatter, pNull}, um);
if (mode == 0) {
// Absorption--done.
DBG("absorbed\n");
beta = SampledSpectrum(0.f);
// Tell the medium to stop traveral.
return false;
} else if (mode == 1) {
// Scattering.
DBG("scattered\n");
beta *= Tmaj * sigma_s;
pdfUni *= Tmaj * sigma_s;
// TODO: don't hard code a phase function.
const HGPhaseFunction *phase =
intr.phase.CastOrNullptr<HGPhaseFunction>();
// Enqueue medium scattering work.
mediumScatterQueue->Push(MediumScatterWorkItem{
intr.p(), lambda, beta, pdfUni, ms.rayIndex, *phase, -ray.d,
ms.etaScale, ray.medium, ms.pixelIndex});
scattered = true;
return false;
} else {
// Null scattering.
DBG("null-scattered\n");
SampledSpectrum sigma_n = intr.sigma_n();
beta *= Tmaj * sigma_n;
pdfUni *= Tmaj * sigma_n;
pdfNEE *= Tmaj * intr.sigma_maj;
// It's not unususal for these values to have large
// magnitudes after multiple null scattering
// events, even though in the end ratios like
// beta/pdfUni are generally around 1. To avoid
// overflow, we rescale all three of them by the
// same factor when they become large.
if (beta.MaxComponentValue() > 0x1p24f ||
pdfUni.MaxComponentValue() > 0x1p24f ||
pdfNEE.MaxComponentValue() > 0x1p24f) {
// Note that no precision is lost in the
// rescaling since we're dividing by a power of
// 2.
beta *= 1.f / 0x1p24f;
pdfUni *= 1.f / 0x1p24f;
pdfNEE *= 1.f / 0x1p24f;
}
return true;
}
});
DBG("Post ray medium sample L %f %f %f %f beta %f %f %f %f\n", L[0], L[1],
L[2], L[3], beta[0], beta[1], beta[2], beta[3]);
DBG("Post ray medium sample pdfUni %f %f %f %f pdfNEE %f %f %f %f\n",
pdfUni[0], pdfUni[1], pdfUni[2], pdfUni[3], pdfNEE[0], pdfNEE[1],
pdfNEE[2], pdfNEE[3]);
// Add any emission found to its pixel sample's L value.
if (L) {
SampledSpectrum Lp = pixelSampleState.L[ms.pixelIndex];
pixelSampleState.L[ms.pixelIndex] = Lp + L;
DBG("Added emitted radiance %f %f %f %f at pixel index %d ray index %d\n",
L[0], L[1], L[2], L[3], ms.pixelIndex, ms.rayIndex);
}
// There's more work to do if there was a scattering event in
// the medium.
if (scattered)
return;
// Otherwise, enqueue bump and medium stuff...
// FIXME: this is all basically duplicate code w/optix.cu
if (ms.tMax == Infinity) {
// no intersection
if (escapedRayQueue) {
DBG("Adding ray to escapedRayQueue pixel index %d depth %d\n",
ms.pixelIndex, depth);
escapedRayQueue->Push(EscapedRayWorkItem{
beta, pdfUni, pdfNEE, lambda, ray.o, ray.d, ms.piPrev, ms.nPrev,
ms.nsPrev, (int)ms.isSpecularBounce, ms.pixelIndex});
}
}
MaterialHandle material = ms.material;
if (!material) {
Interaction intr(ms.pi, ms.n);
intr.mediumInterface = &ms.mediumInterface;
Ray newRay = intr.SpawnRay(ray.d);
mediumTransitionQueue->Push(MediumTransitionWorkItem{
newRay, lambda, beta, pdfUni, pdfNEE, ms.piPrev, ms.nPrev, ms.nsPrev,
ms.isSpecularBounce, ms.anyNonSpecularBounces, ms.etaScale,
ms.pixelIndex});
#if 0
// WHY NOT THIS?
rayQueues[(depth + 1) & 1]->PushIndirect(newRay, ms.piPrev, ms.nPrev, ms.nsPrev,
beta, pdfUni, pdfNEE, lambda, ms.etaScale,
ms.isSpecularBounce, ms.anyNonSpecularBounces,
ms.pixelIndex);
#endif
return;
}
if (ms.areaLight) {
DBG("Ray hit an area light: adding to hitAreaLightQueue pixel index %d "
"depth %d\n",
ms.pixelIndex, depth);
// TODO: intr.wo == -ray.d?
hitAreaLightQueue->Push(HitAreaLightWorkItem{
ms.areaLight, lambda, beta, pdfUni, pdfNEE, Point3f(ms.pi), ms.n,
ms.uv, -ray.d, ms.piPrev, ray.d, ray.time, ms.nPrev, ms.nsPrev,
ms.isSpecularBounce, ms.pixelIndex});
}
FloatTextureHandle displacement = material.GetDisplacement();
MaterialEvalQueue *q =
(material.CanEvaluateTextures(BasicTextureEvaluator()) &&
(!displacement ||
BasicTextureEvaluator().CanEvaluate({displacement}, {})))
? basicEvalMaterialQueue
: universalEvalMaterialQueue;
DBG("Enqueuing for material eval, mtl tag %d", material.Tag());
auto enqueue = [=](auto ptr) {
using Material = typename std::remove_reference_t<decltype(*ptr)>;
q->Push<Material>(MaterialEvalWorkItem<Material>{
ptr, lambda, beta, pdfUni, ms.pi, ms.n, ms.ns, ms.dpdus, ms.dpdvs,
ms.dndus, ms.dndvs, -ray.d, ms.uv, ray.time, ms.anyNonSpecularBounces,
ms.etaScale, ms.mediumInterface, ms.rayIndex, ms.pixelIndex});
};
material.Dispatch(enqueue);
});
using PhaseFunction = HGPhaseFunction;
std::string desc = std::string("Sample direct/indirect - Henyey Greenstein");
ForAllQueued(
desc.c_str(), mediumScatterQueue, maxQueueSize,
[=] PBRT_GPU(MediumScatterWorkItem ms, int index) {
RaySamples raySamples = rayQueues[depth & 1]->raySamples[ms.rayIndex];
Float time = 0; // TODO: FIXME
Vector3f wo = ms.wo;
// Sample direct lighting at medium scattering event. First,
// choose a light source.
LightSampleContext ctx(Point3fi(ms.p), Normal3f(0, 0, 0), Normal3f(0, 0, 0));
pstd::optional<SampledLight> sampledLight =
lightSampler.Sample(ctx, raySamples.direct.uc);
LightHandle light = sampledLight->light;
if (light) {
// And now sample a point on the light.
LightLiSample ls = light.SampleLi(ctx, raySamples.direct.u, ms.lambda,
LightSamplingMode::WithMIS);
if (ls && ls.L) {
Vector3f wi = ls.wi;
SampledSpectrum beta = ms.beta * ms.phase.p(wo, wi);
DBG("Phase phase beta %f %f %f %f\n", beta[0], beta[1], beta[2],
beta[3]);
// Compute PDFs for direct lighting MIS calculation.
Float lightPDF = ls.pdf * sampledLight->pdf;
Float phasePDF =
IsDeltaLight(light.Type()) ? 0.f : ms.phase.PDF(wo, wi);
SampledSpectrum pdfUni = ms.pdfUni * phasePDF;
SampledSpectrum pdfNEE = ms.pdfUni * lightPDF;
SampledSpectrum Ld = beta * ls.L;
Ray ray(ms.p, ls.pLight.p() - ms.p, time, ms.medium);
// Enqueue shadow ray
shadowRayQueue->Push(ShadowRayWorkItem{ray, 1 - ShadowEpsilon,
ms.lambda, Ld, pdfUni, pdfNEE,
ms.pixelIndex});
DBG("Enqueued medium shadow ray depth %d "
"Ld %f %f %f %f pdfUni %f %f %f %f "
"pdfNEE %f %f %f %f parent ray index %d parent pixel index %d\n",
depth, Ld[0], Ld[1], Ld[2], Ld[3], pdfUni[0], pdfUni[1],
pdfUni[2], pdfUni[3], pdfNEE[0], pdfNEE[1], pdfNEE[2], pdfNEE[3],
ms.rayIndex, ms.pixelIndex);
}
}
// Sample indirect lighting.
PhaseFunctionSample phaseSample =
ms.phase.Sample_p(wo, raySamples.indirect.u);
if (!phaseSample)
return;
SampledSpectrum beta = ms.beta * phaseSample.p;
SampledSpectrum pdfUni = ms.pdfUni * phaseSample.pdf;
SampledSpectrum pdfNEE = ms.pdfUni;
// Russian roulette
SampledSpectrum rrBeta = beta * ms.etaScale / pdfUni.Average();
if (rrBeta.MaxComponentValue() < 1 && depth > 1) {
Float q = std::max<Float>(0, 1 - rrBeta.MaxComponentValue());
if (raySamples.indirect.rr < q) {
DBG("RR terminated medium indirect with q %f ray index %d\n", q,
ms.rayIndex);
return;
}
pdfUni *= 1 - q;
pdfNEE *= 1 - q;
}
Ray ray(ms.p, phaseSample.wi, time, ms.medium);
bool isSpecularBounce = false;
bool anyNonSpecularBounces = true;
// Spawn indirect ray.
rayQueues[(depth + 1) & 1]->PushIndirect(
ray, Point3fi(ms.p), Normal3f(0, 0, 0), Normal3f(0, 0, 0), beta, pdfUni,
pdfNEE, ms.lambda, ms.etaScale, isSpecularBounce, anyNonSpecularBounces,
ms.pixelIndex);
DBG("Enqueuing indirect medium ray at depth %d ray index %d pixel index %d\n",
depth + 1, ms.rayIndex, ms.pixelIndex);
});
}
void GPUPathIntegrator::HandleMediumTransitions(int depth) {
ForAllQueued(
"Handle medium transitions", mediumTransitionQueue, maxQueueSize,
[=] PBRT_GPU(MediumTransitionWorkItem mt, int index) {
// Have to do this here, later, since we can't be writing into
// the other ray queue in optix closest hit. (Wait--really?
// Why not? Basically boils down to current indirect enqueue (and other
// places?))
// TODO: figure this out...
rayQueues[(depth + 1) & 1]->PushIndirect(
mt.ray, mt.piPrev, mt.nPrev, mt.nsPrev, mt.beta, mt.pdfUni, mt.pdfNEE,
mt.lambda, mt.etaScale, mt.isSpecularBounce, mt.anyNonSpecularBounces,
mt.pixelIndex);
DBG("Enqueuied ray after medium transition at depth %d pixel index %d",
depth + 1, mt.pixelIndex);
});
}
} // namespace pbrt

700
src/pbrt/gpu/optix.cu Normal file
View file

@ -0,0 +1,700 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#include <pbrt/pbrt.h>
#include <pbrt/gpu/accel.h>
#include <pbrt/gpu/optix.h>
#include <pbrt/interaction.h>
#include <pbrt/materials.h>
#include <pbrt/media.h>
#include <pbrt/shapes.h>
#include <pbrt/textures.h>
#include <pbrt/util/float.h>
#include <pbrt/util/rng.h>
#include <pbrt/util/transform.h>
#include <pbrt/util/vecmath.h>
#include <pbrt/util/color.cpp> // :-(
#include <pbrt/util/colorspace.cpp> // :-(
#include <pbrt/util/spectrum.cpp> // :-(
#include <pbrt/util/transform.cpp> // :-(
#include <optix_device.h>
#include <utility>
#ifdef PBRT_GPU_DBG
#ifndef TO_STRING
#define TO_STRING(x) TO_STRING2(x)
#define TO_STRING2(x) #x
#endif // !TO_STRING
#define DBG(...) printf(__FILE__ ":" TO_STRING(__LINE__) ": " __VA_ARGS__)
#else
#define DBG(...)
#endif // PBRT_GPU_DBG
using namespace pbrt;
extern "C" {
extern __constant__ pbrt::RayIntersectParameters params;
}
///////////////////////////////////////////////////////////////////////////
// Utility functions
// Payload management
__device__ inline uint32_t packPointer0(void *ptr) {
uint64_t uptr = reinterpret_cast<uint64_t>(ptr);
return uptr >> 32;
}
__device__ inline uint32_t packPointer1(void *ptr) {
uint64_t uptr = reinterpret_cast<uint64_t>(ptr);
return uint32_t(uptr);
}
template <typename T>
static __forceinline__ __device__ T *getPayload() {
uint32_t p0 = optixGetPayload_0(), p1 = optixGetPayload_1();
const uint64_t uptr = (uint64_t(p0) << 32) | p1;
return reinterpret_cast<T *>(uptr);
}
template <typename... Args>
__device__ inline void Trace(OptixTraversableHandle traversable, Ray ray, Float tMin,
Float tMax, OptixRayFlags flags, Args &&... payload) {
optixTrace(traversable, make_float3(ray.o.x, ray.o.y, ray.o.z),
make_float3(ray.d.x, ray.d.y, ray.d.z), tMin, tMax, ray.time,
OptixVisibilityMask(255), flags, 0, /* ray type */
1, /* number of ray types */
0, /* missSBTIndex */
std::forward<Args>(payload)...);
}
///////////////////////////////////////////////////////////////////////////
// Closest hit
struct ClosestHitContext {
PBRT_GPU
ClosestHitContext(MediumHandle rayMedium, bool shadowRay)
: rayMedium(rayMedium), shadowRay(shadowRay) {}
MediumHandle rayMedium;
bool shadowRay;
// out
Point3fi piHit;
Normal3f nHit;
MaterialHandle material;
MediumInterface mediumInterface;
PBRT_GPU
Ray SpawnRayTo(const Point3f &p) const {
Interaction intr(piHit, nHit);
intr.mediumInterface = &mediumInterface;
return intr.SpawnRayTo(p);
}
};
extern "C" __global__ void __raygen__findClosest() {
int rayIndex(optixGetLaunchIndex().x);
if (rayIndex >= params.rayQueue->Size())
return;
RayWorkItem r = (*params.rayQueue)[rayIndex];
Ray ray = r.ray;
Float tMax = 1e30f;
ClosestHitContext ctx(ray.medium, false);
uint32_t p0 = packPointer0(&ctx), p1 = packPointer1(&ctx);
DBG("ray o %f %f %f dir %f %f %f tmax %f\n", ray.o.x, ray.o.y, ray.o.z, ray.d.x,
ray.d.y, ray.d.z, tMax);
uint32_t missed = 0;
Trace(params.traversable, ray, 0.f /* tMin */, tMax, OPTIX_RAY_FLAG_NONE, p0, p1,
missed);
if (missed) {
if (ray.medium) {
DBG("Adding miss ray to mediumSampleQueue. "
"ray %f %f %f d %f %f %f beta %f %f %f %f\n",
r.ray.o.x, r.ray.o.y, r.ray.o.z, r.ray.d.x, r.ray.d.y, r.ray.d.z,
r.beta[0], r.beta[1], r.beta[2], r.beta[3]);
params.mediumSampleQueue->Push(r.ray, Infinity, r.lambda, r.beta, r.pdfUni,
r.pdfNEE, rayIndex, r.pixelIndex, r.piPrev,
r.nPrev, r.nsPrev, r.isSpecularBounce,
r.anyNonSpecularBounces, r.etaScale);
} else if (params.escapedRayQueue) {
DBG("Adding ray to escapedRayQueue ray index %d pixel index %d\n", rayIndex,
r.pixelIndex);
params.escapedRayQueue->Push(EscapedRayWorkItem{
r.beta, r.pdfUni, r.pdfNEE, r.lambda, ray.o, ray.d, r.piPrev, r.nPrev,
r.nsPrev, (int)r.isSpecularBounce, r.pixelIndex});
}
}
}
extern "C" __global__ void __miss__noop() {
optixSetPayload_2(1);
}
static __forceinline__ __device__ void ProcessClosestIntersection(
SurfaceInteraction intr) {
int rayIndex = optixGetLaunchIndex().x;
MediumHandle rayMedium = getPayload<ClosestHitContext>()->rayMedium;
if (intr.mediumInterface)
getPayload<ClosestHitContext>()->mediumInterface = *intr.mediumInterface;
else
getPayload<ClosestHitContext>()->mediumInterface = MediumInterface(rayMedium);
getPayload<ClosestHitContext>()->piHit = intr.pi;
getPayload<ClosestHitContext>()->nHit = intr.n;
getPayload<ClosestHitContext>()->material = intr.material;
if (getPayload<ClosestHitContext>()->shadowRay)
return;
// We only have the ray queue (and it only makes sense to access) for
// regular closest hit rays.
RayWorkItem r = (*params.rayQueue)[rayIndex];
if (rayMedium) {
assert(params.mediumSampleQueue);
DBG("Enqueuing into medium sample queue\n");
params.mediumSampleQueue->Push(
MediumSampleWorkItem{r.ray,
optixGetRayTmax(),
r.lambda,
r.beta,
r.pdfUni,
r.pdfNEE,
rayIndex,
r.pixelIndex,
r.piPrev,
r.nPrev,
r.nsPrev,
r.isSpecularBounce,
r.anyNonSpecularBounces,
r.etaScale,
intr.areaLight,
intr.pi,
intr.n,
-r.ray.d,
intr.uv,
intr.material,
intr.shading.n,
intr.shading.dpdu,
intr.shading.dpdv,
intr.shading.dndu,
intr.shading.dndv,
getPayload<ClosestHitContext>()->mediumInterface});
return;
}
// FIXME: this is all basically duplicate code w/medium.cpp
MaterialHandle material = intr.material;
if (!material) {
DBG("Enqueuing into medium transition queue: ray index %d pixel index %d \n",
rayIndex, r.pixelIndex);
Ray newRay = intr.SpawnRay(r.ray.d);
params.mediumTransitionQueue->Push(MediumTransitionWorkItem{
newRay, r.lambda, r.beta, r.pdfUni, r.pdfNEE, r.piPrev, r.nPrev, r.nsPrev,
r.isSpecularBounce, r.anyNonSpecularBounces, r.etaScale, r.pixelIndex});
return;
}
if (intr.areaLight) {
DBG("Ray hit an area light: adding to hitAreaLightQueue ray index %d pixel index "
"%d\n",
rayIndex, r.pixelIndex);
Ray ray = r.ray;
// TODO: intr.wo == -ray.d?
params.hitAreaLightQueue->Push(HitAreaLightWorkItem{
intr.areaLight, r.lambda, r.beta, r.pdfUni, r.pdfNEE, intr.p(), intr.n,
intr.uv, intr.wo, r.piPrev, ray.d, ray.time, r.nPrev, r.nsPrev,
(int)r.isSpecularBounce, r.pixelIndex});
}
FloatTextureHandle displacement = material.GetDisplacement();
MaterialEvalQueue *q =
(material.CanEvaluateTextures(BasicTextureEvaluator()) &&
(!displacement || BasicTextureEvaluator().CanEvaluate({displacement}, {})))
? params.basicEvalMaterialQueue
: params.universalEvalMaterialQueue;
DBG("Enqueuing for material eval, mtl tag %d\n", material.Tag());
auto enqueue = [=](auto ptr) {
using Material = typename std::remove_reference_t<decltype(*ptr)>;
q->Push<Material>(MaterialEvalWorkItem<Material>{
ptr, r.lambda, r.beta, r.pdfUni, intr.pi, intr.n, intr.shading.n,
intr.shading.dpdu, intr.shading.dpdv, intr.shading.dndu, intr.shading.dndv,
intr.wo, intr.uv, intr.time, r.anyNonSpecularBounces, r.etaScale,
getPayload<ClosestHitContext>()->mediumInterface, rayIndex, r.pixelIndex});
};
material.Dispatch(enqueue);
DBG("Closest hit found intersection at t %f\n", optixGetRayTmax());
}
///////////////////////////////////////////////////////////////////////////
// Triangles
static __forceinline__ __device__ pstd::optional<SurfaceInteraction>
getTriangleIntersection() {
const TriangleMeshRecord &rec = *(const TriangleMeshRecord *)optixGetSbtDataPointer();
float b1 = optixGetTriangleBarycentrics().x;
float b2 = optixGetTriangleBarycentrics().y;
float b0 = 1 - b1 - b2;
float3 rd = optixGetWorldRayDirection();
Vector3f wo = -Vector3f(rd.x, rd.y, rd.z);
assert(optixGetTransformListSize() == 1);
float worldFromObj[12], objFromWorld[12];
optixGetObjectToWorldTransformMatrix(worldFromObj);
optixGetWorldToObjectTransformMatrix(objFromWorld);
SquareMatrix<4> worldFromObjM(worldFromObj[0], worldFromObj[1], worldFromObj[2],
worldFromObj[3], worldFromObj[4], worldFromObj[5],
worldFromObj[6], worldFromObj[7], worldFromObj[8],
worldFromObj[9], worldFromObj[10], worldFromObj[11],
0.f, 0.f, 0.f, 1.f);
SquareMatrix<4> objFromWorldM(objFromWorld[0], objFromWorld[1], objFromWorld[2],
objFromWorld[3], objFromWorld[4], objFromWorld[5],
objFromWorld[6], objFromWorld[7], objFromWorld[8],
objFromWorld[9], objFromWorld[10], objFromWorld[11],
0.f, 0.f, 0.f, 1.f);
Transform worldFromInstance(worldFromObjM, objFromWorldM);
return Triangle::InteractionFromIntersection(rec.mesh, optixGetPrimitiveIndex(),
{b0, b1, b2}, optixGetRayTime(), wo,
worldFromInstance);
}
static __forceinline__ __device__ bool alphaKilled(const TriangleMeshRecord &rec) {
if (!rec.alphaTexture)
return false;
pstd::optional<SurfaceInteraction> intr = getTriangleIntersection();
if (!intr)
return true;
BasicTextureEvaluator eval;
Float alpha = eval(rec.alphaTexture, *intr);
return alpha == 0;
}
extern "C" __global__ void __closesthit__triangle() {
const TriangleMeshRecord &rec = *(const TriangleMeshRecord *)optixGetSbtDataPointer();
// It's slightly dicey to assume intr is valid. But invalid would
// presumably mean that OptiX returned a hit with a degenerate
// triangle...
SurfaceInteraction intr = *getTriangleIntersection();
if (rec.mediumInterface && rec.mediumInterface->IsMediumTransition())
intr.mediumInterface = rec.mediumInterface;
intr.material = rec.material;
if (!rec.areaLights.empty())
intr.areaLight = rec.areaLights[optixGetPrimitiveIndex()];
ProcessClosestIntersection(intr);
}
extern "C" __global__ void __anyhit__triangle() {
const TriangleMeshRecord &rec = *(const TriangleMeshRecord *)optixGetSbtDataPointer();
if (alphaKilled(rec))
optixIgnoreIntersection();
}
extern "C" __global__ void __anyhit__shadowTriangle() {
const TriangleMeshRecord &rec = *(const TriangleMeshRecord *)optixGetSbtDataPointer();
if (rec.material && rec.material.IsTransparent())
optixIgnoreIntersection();
if (alphaKilled(rec))
optixIgnoreIntersection();
}
///////////////////////////////////////////////////////////////////////////
// Shadow rays
extern "C" __global__ void __raygen__shadow() {
int index = optixGetLaunchIndex().x;
if (index >= params.shadowRayQueue->Size())
return;
ShadowRayWorkItem sr = (*params.shadowRayQueue)[index];
uint32_t missed = 0;
Trace(params.traversable, sr.ray, 1e-5f /* tMin */, sr.tMax, OPTIX_RAY_FLAG_NONE,
missed);
SampledSpectrum Ld;
if (missed)
Ld = sr.Ld / (sr.pdfUni + sr.pdfNEE).Average();
else
Ld = SampledSpectrum(0.);
params.shadowRayQueue->Ld[index] = Ld;
}
extern "C" __global__ void __miss__shadow() {
optixSetPayload_0(1);
}
extern "C" __global__ void __raygen__shadow_Tr() {
DBG("raygen sahadow tr %d\n", optixGetLaunchIndex().x);
int index = optixGetLaunchIndex().x;
if (index >= params.shadowRayQueue->Size())
return;
ShadowRayWorkItem sr = (*params.shadowRayQueue)[index];
SampledWavelengths lambda = sr.lambda;
SampledSpectrum Ld = sr.Ld;
DBG("Initial Ld %f %f %f %f shadow ray index %d pixel index %d\n", Ld[0], Ld[1],
Ld[2], Ld[3], index, sr.pixelIndex);
SampledSpectrum pdfUni = sr.pdfUni, pdfNEE = sr.pdfNEE;
Ray ray = sr.ray;
Float tMax = sr.tMax;
Point3f pLight = ray(tMax);
RNG rng(Hash(ray.o), Hash(ray.d));
while (true) {
ClosestHitContext ctx(ray.medium, true);
uint32_t p0 = packPointer0(&ctx), p1 = packPointer1(&ctx);
DBG("Tracing shadow tr shadow ray index %d pixel index %d "
"ray %f %f %f d %f %f %f tMax %f\n",
index, sr.pixelIndex, ray.o.x, ray.o.y, ray.o.z, ray.d.x, ray.d.y, ray.d.z,
tMax);
uint32_t missed = 0;
Trace(params.traversable, ray, 1e-5f /* tMin */, tMax, OPTIX_RAY_FLAG_NONE, p0,
p1, missed);
if (!missed && ctx.material) {
DBG("Hit opaque. Bye\n");
// Hit opaque surface
Ld = SampledSpectrum(0.f);
break;
}
if (ray.medium) {
DBG("Ray medium %p. Will sample tmaj...\n", ray.medium.ptr());
Float tEnd =
missed ? tMax : (Distance(ray.o, Point3f(ctx.piHit)) / Length(ray.d));
ray.medium.SampleTmaj(ray, tEnd, rng, lambda,
[&](const MediumSample &mediumSample) {
if (!mediumSample.intr)
// FIXME: include last Tmaj?
return false;
const SampledSpectrum &Tmaj = mediumSample.Tmaj;
const MediumInteraction &intr = *mediumSample.intr;
SampledSpectrum sigma_n = intr.sigma_n();
// ratio-tracking: only evaluate null scattering
Ld *= Tmaj * sigma_n;
pdfNEE *= Tmaj * intr.sigma_maj;
pdfUni *= Tmaj * sigma_n;
if (!Ld)
return false;
if (Ld.MaxComponentValue() > 0x1p24f ||
pdfNEE.MaxComponentValue() > 0x1p24f ||
pdfUni.MaxComponentValue() > 0x1p24f) {
Ld *= 1.f / 0x1p24f;
pdfNEE *= 1.f / 0x1p24f;
pdfUni *= 1.f / 0x1p24f;
}
return true;
});
}
if (missed || !Ld)
// done
break;
ray = ctx.SpawnRayTo(pLight);
if (ray.d == Vector3f(0, 0, 0))
break;
}
Ld /= (pdfUni + pdfNEE).Average();
DBG("Setting final Ld for shadow ray index %d pixel index %d = as %f %f %f %f\n",
index, sr.pixelIndex, Ld[0], Ld[1], Ld[2], Ld[3]);
params.shadowRayQueue->Ld[index] = Ld;
}
extern "C" __global__ void __miss__shadow_Tr() {
optixSetPayload_2(1);
}
/////////////////////////////////////////////////////////////////////////////////////
// Quadrics
static __device__ inline SurfaceInteraction getQuadricIntersection(
const QuadricIntersection &si) {
QuadricRecord &rec = *((QuadricRecord *)optixGetSbtDataPointer());
float3 rd = optixGetWorldRayDirection();
Vector3f wo = -Vector3f(rd.x, rd.y, rd.z);
Float time = optixGetRayTime();
SurfaceInteraction intr;
if (const Sphere *sphere = rec.shape.CastOrNullptr<Sphere>())
intr = sphere->InteractionFromIntersection(si, wo, time);
else if (const Cylinder *cylinder = rec.shape.CastOrNullptr<Cylinder>())
intr = cylinder->InteractionFromIntersection(si, wo, time);
else if (const Disk *disk = rec.shape.CastOrNullptr<Disk>())
intr = disk->InteractionFromIntersection(si, wo, time);
else
assert(!"unexpected quadric");
return intr;
}
extern "C" __global__ void __closesthit__quadric() {
QuadricRecord &rec = *((QuadricRecord *)optixGetSbtDataPointer());
QuadricIntersection qi;
qi.pObj =
Point3f(BitsToFloat(optixGetAttribute_0()), BitsToFloat(optixGetAttribute_1()),
BitsToFloat(optixGetAttribute_2()));
qi.phi = BitsToFloat(optixGetAttribute_3());
SurfaceInteraction intr = getQuadricIntersection(qi);
if (rec.mediumInterface && rec.mediumInterface->IsMediumTransition())
intr.mediumInterface = rec.mediumInterface;
intr.material = rec.material;
if (rec.areaLight)
intr.areaLight = rec.areaLight;
ProcessClosestIntersection(intr);
}
extern "C" __global__ void __anyhit__shadowQuadric() {
QuadricRecord &rec = *((QuadricRecord *)optixGetSbtDataPointer());
if (rec.material && rec.material.IsTransparent())
optixIgnoreIntersection();
}
extern "C" __global__ void __intersection__quadric() {
QuadricRecord &rec = *((QuadricRecord *)optixGetSbtDataPointer());
float3 org = optixGetObjectRayOrigin();
float3 dir = optixGetObjectRayDirection();
Float tMax = optixGetRayTmax();
Ray ray(Point3f(org.x, org.y, org.z), Vector3f(dir.x, dir.y, dir.z));
pstd::optional<QuadricIntersection> isect;
if (const Sphere *sphere = rec.shape.CastOrNullptr<Sphere>())
isect = sphere->BasicIntersect(ray, tMax);
else if (const Cylinder *cylinder = rec.shape.CastOrNullptr<Cylinder>())
isect = cylinder->BasicIntersect(ray, tMax);
else if (const Disk *disk = rec.shape.CastOrNullptr<Disk>())
isect = disk->BasicIntersect(ray, tMax);
if (!isect)
return;
if (rec.alphaTexture) {
SurfaceInteraction intr = getQuadricIntersection(*isect);
BasicTextureEvaluator eval;
Float alpha = eval(rec.alphaTexture, intr);
if (alpha == 0)
// No hit
return;
}
optixReportIntersection(isect->tHit, 0 /* hit kind */, FloatToBits(isect->pObj.x),
FloatToBits(isect->pObj.y), FloatToBits(isect->pObj.z),
FloatToBits(isect->phi));
}
///////////////////////////////////////////////////////////////////////////
// Bilinear patches
static __forceinline__ __device__ SurfaceInteraction
getBilinearPatchIntersection(Point2f uv) {
BilinearMeshRecord &rec = *((BilinearMeshRecord *)optixGetSbtDataPointer());
float3 rd = optixGetWorldRayDirection();
Vector3f wo = -Vector3f(rd.x, rd.y, rd.z);
return BilinearPatch::InteractionFromIntersection(rec.mesh, optixGetPrimitiveIndex(),
uv, optixGetRayTime(), wo);
}
extern "C" __global__ void __closesthit__bilinearPatch() {
BilinearMeshRecord &rec = *((BilinearMeshRecord *)optixGetSbtDataPointer());
Point2f uv(BitsToFloat(optixGetAttribute_0()), BitsToFloat(optixGetAttribute_1()));
SurfaceInteraction intr = getBilinearPatchIntersection(uv);
if (rec.mediumInterface && rec.mediumInterface->IsMediumTransition())
intr.mediumInterface = rec.mediumInterface;
intr.material = rec.material;
if (!rec.areaLights.empty())
intr.areaLight = rec.areaLights[optixGetPrimitiveIndex()];
ProcessClosestIntersection(intr);
}
extern "C" __global__ void __anyhit__shadowBilinearPatch() {
BilinearMeshRecord &rec = *((BilinearMeshRecord *)optixGetSbtDataPointer());
if (rec.material && rec.material.IsTransparent())
optixIgnoreIntersection();
}
extern "C" __global__ void __intersection__bilinearPatch() {
BilinearMeshRecord &rec = *((BilinearMeshRecord *)optixGetSbtDataPointer());
float3 org = optixGetObjectRayOrigin();
float3 dir = optixGetObjectRayDirection();
Float tMax = optixGetRayTmax();
Ray ray(Point3f(org.x, org.y, org.z), Vector3f(dir.x, dir.y, dir.z));
int vertexIndex = 4 * optixGetPrimitiveIndex();
Point3f p00 = rec.mesh->p[rec.mesh->vertexIndices[vertexIndex]];
Point3f p10 = rec.mesh->p[rec.mesh->vertexIndices[vertexIndex + 1]];
Point3f p01 = rec.mesh->p[rec.mesh->vertexIndices[vertexIndex + 2]];
Point3f p11 = rec.mesh->p[rec.mesh->vertexIndices[vertexIndex + 3]];
pstd::optional<BilinearIntersection> isect =
BilinearPatch::Intersect(ray, tMax, p00, p10, p01, p11);
if (!isect)
return;
if (rec.alphaTexture) {
SurfaceInteraction intr = getBilinearPatchIntersection(isect->uv);
BasicTextureEvaluator eval;
Float alpha = eval(rec.alphaTexture, intr);
if (alpha == 0)
// No intersection
return;
}
optixReportIntersection(isect->t, 0 /* hit kind */, FloatToBits(isect->uv[0]),
FloatToBits(isect->uv[1]));
}
///////////////////////////////////////////////////////////////////////////
// Random hit (for subsurface scattering)
struct RandomHitPayload {
WeightedReservoirSampler<SubsurfaceInteraction> wrs;
MaterialHandle material;
};
extern "C" __global__ void __raygen__randomHit() {
// Keep as uint32_t so can pass directly to optixTrace.
uint32_t index = optixGetLaunchIndex().x;
if (index >= params.subsurfaceScatterQueue->Size())
return;
SubsurfaceScatterWorkItem s = (*params.subsurfaceScatterQueue)[index];
Ray ray(s.p0, s.p1 - s.p0);
Float tMax = 1.f;
RandomHitPayload payload;
payload.wrs.Seed(Hash(s.p0, s.p1));
payload.material = s.material;
uint32_t ptr0 = packPointer0(&payload), ptr1 = packPointer1(&payload);
DBG("Randomhit raygen ray.o %f %f %f ray.d %f %f %f tMax %f\n", ray.o.x, ray.o.y,
ray.o.z, ray.d.x, ray.d.y, ray.d.z, tMax);
Trace(params.traversable, ray, 0.f /* tMin */, tMax, OPTIX_RAY_FLAG_NONE, ptr0, ptr1);
if (payload.wrs.HasSample() &&
payload.wrs.WeightSum() > 0) { // TODO: latter check shouldn't be needed...
const SubsurfaceInteraction &si = payload.wrs.GetSample();
DBG("optix si p %f %f %f n %f %f %f\n", si.p().x, si.p().y, si.p().z, si.n.x,
si.n.y, si.n.z);
params.subsurfaceScatterQueue->weight[index] = payload.wrs.WeightSum();
params.subsurfaceScatterQueue->ssi[index] = payload.wrs.GetSample();
} else
params.subsurfaceScatterQueue->weight[index] = 0;
}
extern "C" __global__ void __anyhit__randomHitTriangle() {
const TriangleMeshRecord &rec = *(const TriangleMeshRecord *)optixGetSbtDataPointer();
RandomHitPayload *p = getPayload<RandomHitPayload>();
DBG("Anyhit triangle for random hit: rec.material %p params.materials %p\n",
rec.material.ptr(), p->material.ptr());
if (rec.material == p->material)
p->wrs.Add([&] PBRT_CPU_GPU() { return *getTriangleIntersection(); }, 1.f);
optixIgnoreIntersection();
}
extern "C" __global__ void __anyhit__randomHitBilinearPatch() {
BilinearMeshRecord &rec = *(BilinearMeshRecord *)optixGetSbtDataPointer();
RandomHitPayload *p = getPayload<RandomHitPayload>();
DBG("Anyhit blp for random hit: rec.material %p params.materials %p\n",
rec.material.ptr(), p->material.ptr());
if (rec.material == p->material)
p->wrs.Add(
[&] PBRT_CPU_GPU() {
Point2f uv(BitsToFloat(optixGetAttribute_0()),
BitsToFloat(optixGetAttribute_1()));
return getBilinearPatchIntersection(uv);
},
1.f);
optixIgnoreIntersection();
}
extern "C" __global__ void __anyhit__randomHitQuadric() {
QuadricRecord &rec = *((QuadricRecord *)optixGetSbtDataPointer());
RandomHitPayload *p = getPayload<RandomHitPayload>();
DBG("Anyhit quadric for random hit: rec.material %p params.materials %p\n",
rec.material.ptr(), p->material.ptr());
if (rec.material == p->material) {
p->wrs.Add(
[&] PBRT_CPU_GPU() {
QuadricIntersection qi;
qi.pObj = Point3f(BitsToFloat(optixGetAttribute_0()),
BitsToFloat(optixGetAttribute_1()),
BitsToFloat(optixGetAttribute_2()));
qi.phi = BitsToFloat(optixGetAttribute_3());
return getQuadricIntersection(qi);
},
1.f);
}
optixIgnoreIntersection();
}

71
src/pbrt/gpu/optix.h Normal file
View file

@ -0,0 +1,71 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_GPU_OPTIX_H
#define PBRT_GPU_OPTIX_H
#include <pbrt/pbrt.h>
#include <pbrt/base/light.h>
#include <pbrt/base/material.h>
#include <pbrt/base/medium.h>
#include <pbrt/base/shape.h>
#include <pbrt/base/texture.h>
#include <pbrt/gpu/workitems.h>
#include <pbrt/gpu/workqueue.h>
#include <pbrt/util/pstd.h>
#include <optix.h>
namespace pbrt {
class TriangleMesh;
class BilinearPatchMesh;
struct TriangleMeshRecord {
const TriangleMesh *mesh;
MaterialHandle material;
FloatTextureHandle alphaTexture;
pstd::span<LightHandle> areaLights;
MediumInterface *mediumInterface;
};
struct BilinearMeshRecord {
const BilinearPatchMesh *mesh;
MaterialHandle material;
FloatTextureHandle alphaTexture;
pstd::span<LightHandle> areaLights;
MediumInterface *mediumInterface;
};
struct QuadricRecord {
ShapeHandle shape;
MaterialHandle material;
FloatTextureHandle alphaTexture;
LightHandle areaLight;
MediumInterface *mediumInterface;
};
struct RayIntersectParameters {
OptixTraversableHandle traversable;
RayQueue *rayQueue;
// closest hit
EscapedRayQueue *escapedRayQueue;
HitAreaLightQueue *hitAreaLightQueue;
MaterialEvalQueue *basicEvalMaterialQueue, *universalEvalMaterialQueue;
MediumTransitionQueue *mediumTransitionQueue;
MediumSampleQueue *mediumSampleQueue;
// shadow rays
ShadowRayQueue *shadowRayQueue;
// Subsurface scattering...
SubsurfaceScatterQueue *subsurfaceScatterQueue;
};
} // namespace pbrt
#endif // PBRT_GPU_OPTIX_H

View file

@ -0,0 +1,580 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#include <pbrt/gpu/pathintegrator.h>
#include <pbrt/base/medium.h>
#include <pbrt/cameras.h>
#include <pbrt/film.h>
#include <pbrt/filters.h>
#include <pbrt/gpu/accel.h>
#include <pbrt/gpu/launch.h>
#include <pbrt/gpu/optix.h>
#include <pbrt/lights.h>
#include <pbrt/lightsamplers.h>
#include <pbrt/util/color.h>
#include <pbrt/util/colorspace.h>
#include <pbrt/util/display.h>
#include <pbrt/util/file.h>
#include <pbrt/util/image.h>
#include <pbrt/util/log.h>
#include <pbrt/util/print.h>
#include <pbrt/util/progressreporter.h>
#include <pbrt/util/pstd.h>
#include <pbrt/util/stats.h>
#include <pbrt/util/taggedptr.h>
#include <cstring>
#include <iostream>
#include <map>
#include <cuda.h>
#include <cuda_profiler_api.h>
#include <cuda_runtime.h>
#include <cuda/std/atomic>
#ifdef NVTX
#include "nvtx3/nvToolsExt.h"
#include "nvtx3/nvToolsExtCuda.h"
#endif
#ifdef PBRT_GPU_DBG
#ifndef TO_STRING
#define TO_STRING(x) TO_STRING2(x)
#define TO_STRING2(x) #x
#endif // !TO_STRING
#define DBG(...) printf(__FILE__ ":" TO_STRING(__LINE__) ": " __VA_ARGS__)
#else
#define DBG(...)
#endif
namespace pbrt {
STAT_MEMORY_COUNTER("Memory/GPU path integrator pixel state", pathIntegratorBytes);
GPUPathIntegrator::GPUPathIntegrator(Allocator alloc, const ParsedScene &scene) {
// Allocate all of the data structures that represent the scene...
std::map<std::string, MediumHandle> media = scene.CreateMedia(alloc);
haveMedia = false;
// Check the shapes...
for (const auto &shape : scene.shapes)
if (!shape.insideMedium.empty() || !shape.outsideMedium.empty())
haveMedia = true;
for (const auto &shape : scene.animatedShapes)
if (!shape.insideMedium.empty() || !shape.outsideMedium.empty())
haveMedia = true;
auto findMedium = [&](const std::string &s, const FileLoc *loc) -> MediumHandle {
if (s.empty())
return nullptr;
auto iter = media.find(s);
if (iter == media.end())
ErrorExit(loc, "%s: medium not defined", s);
haveMedia = true;
return iter->second;
};
filter = FilterHandle::Create(scene.filter.name, scene.filter.parameters,
&scene.filter.loc, alloc);
film = FilmHandle::Create(scene.film.name, scene.film.parameters, &scene.film.loc,
filter, alloc);
initializeVisibleSurface = film.UsesVisibleSurface();
sampler = SamplerHandle::Create(scene.sampler.name, scene.sampler.parameters,
film.FullResolution(), &scene.sampler.loc, alloc);
MediumHandle cameraMedium = findMedium(scene.camera.medium, &scene.camera.loc);
camera = CameraHandle::Create(scene.camera.name, scene.camera.parameters,
cameraMedium, scene.camera.cameraTransform, film,
&scene.camera.loc, alloc);
pstd::vector<LightHandle> allLights;
for (const auto &light : scene.lights) {
MediumHandle outsideMedium = findMedium(light.medium, &light.loc);
if (light.renderFromObject.IsAnimated())
Warning(&light.loc,
"Animated lights aren't supported. Using the start transform.");
LightHandle l = LightHandle::Create(
light.name, light.parameters, light.renderFromObject.startTransform,
scene.camera.cameraTransform, outsideMedium, &light.loc, alloc);
if (l.Is<UniformInfiniteLight>() || l.Is<ImageInfiniteLight>() ||
l.Is<PortalImageInfiniteLight>()) {
if (envLight)
Warning(&light.loc,
"Multiple infinite lights specified. Using this one.");
envLight = l;
}
allLights.push_back(l);
}
// Area lights...
std::map<int, pstd::vector<LightHandle> *> shapeIndexToAreaLights;
for (size_t i = 0; i < scene.shapes.size(); ++i) {
const auto &shape = scene.shapes[i];
if (shape.lightIndex == -1)
continue;
CHECK_LT(shape.lightIndex, scene.areaLights.size());
const auto &areaLightEntity = scene.areaLights[shape.lightIndex];
AnimatedTransform renderFromLight(*shape.renderFromObject);
pstd::vector<ShapeHandle> shapeHandles = ShapeHandle::Create(
shape.name, shape.renderFromObject, shape.objectFromRender,
shape.reverseOrientation, shape.parameters, &shape.loc, alloc);
if (shapeHandles.empty())
continue;
MediumHandle outsideMedium = findMedium(shape.outsideMedium, &shape.loc);
pstd::vector<LightHandle> *lightsForShape =
alloc.new_object<pstd::vector<LightHandle>>(alloc);
for (ShapeHandle sh : shapeHandles) {
if (renderFromLight.IsAnimated())
Warning(&shape.loc,
"Animated lights aren't supported. Using the start transform.");
DiffuseAreaLight *area = DiffuseAreaLight::Create(
renderFromLight.startTransform, outsideMedium, areaLightEntity.parameters,
areaLightEntity.parameters.ColorSpace(), &areaLightEntity.loc, alloc, sh);
allLights.push_back(area);
lightsForShape->push_back(area);
}
shapeIndexToAreaLights[i] = lightsForShape;
}
haveBasicEvalMaterial.fill(false);
haveUniversalEvalMaterial.fill(false);
haveSubsurface = false;
accel = new GPUAccel(scene, alloc, nullptr /* cuda stream */, shapeIndexToAreaLights,
media, &haveBasicEvalMaterial, &haveUniversalEvalMaterial,
&haveSubsurface);
// Preprocess the light sources
for (LightHandle light : allLights)
light.Preprocess(accel->Bounds());
bool haveLights = !allLights.empty();
for (const auto &m : media)
haveLights |= m.second.IsEmissive();
if (!haveLights)
ErrorExit("No light sources specified");
std::string lightSamplerName =
scene.integrator.parameters.GetOneString("lightsampler", "bvh");
if (allLights.size() == 1)
lightSamplerName = "uniform";
lightSampler = LightSamplerHandle::Create(lightSamplerName, allLights, alloc);
// Integrator parameters
regularize = scene.integrator.parameters.GetOneBool("regularize", false);
maxDepth = scene.integrator.parameters.GetOneInt("maxdepth", 5);
///////////////////////////////////////////////////////////////////////////
// Allocate storage for all of the queues/buffers...
CUDATrackedMemoryResource *mr =
dynamic_cast<CUDATrackedMemoryResource *>(gpuMemoryAllocator.resource());
CHECK(mr != nullptr);
size_t startSize = mr->BytesAllocated();
// Compute number of scanlines to render per pass.
Vector2i resolution = film.PixelBounds().Diagonal();
// TODO: make this configurable. Base it on the amount of GPU memory?
int maxSamples = 1024 * 1024;
scanlinesPerPass = std::max(1, maxSamples / resolution.x);
int nPasses = (resolution.y + scanlinesPerPass - 1) / scanlinesPerPass;
scanlinesPerPass = (resolution.y + nPasses - 1) / nPasses;
maxQueueSize = resolution.x * scanlinesPerPass;
LOG_VERBOSE("Will render in %d passes %d scanlines per pass\n", nPasses,
scanlinesPerPass);
pixelSampleState = SOA<PixelSampleState>(maxQueueSize, alloc);
rayQueues[0] = alloc.new_object<RayQueue>(maxQueueSize, alloc);
rayQueues[1] = alloc.new_object<RayQueue>(maxQueueSize, alloc);
shadowRayQueue = alloc.new_object<ShadowRayQueue>(maxQueueSize, alloc);
if (haveSubsurface) {
bssrdfEvalQueue =
alloc.new_object<GetBSSRDFAndProbeRayQueue>(maxQueueSize, alloc);
subsurfaceScatterQueue =
alloc.new_object<SubsurfaceScatterQueue>(maxQueueSize, alloc);
}
if (envLight)
escapedRayQueue = alloc.new_object<EscapedRayQueue>(maxQueueSize, alloc);
hitAreaLightQueue = alloc.new_object<HitAreaLightQueue>(maxQueueSize, alloc);
basicEvalMaterialQueue = alloc.new_object<MaterialEvalQueue>(
maxQueueSize, alloc,
pstd::MakeConstSpan(&haveBasicEvalMaterial[1], haveBasicEvalMaterial.size() - 1));
universalEvalMaterialQueue = alloc.new_object<MaterialEvalQueue>(
maxQueueSize, alloc,
pstd::MakeConstSpan(&haveUniversalEvalMaterial[1],
haveUniversalEvalMaterial.size() - 1));
// Always allocate this, even if no media
mediumTransitionQueue = alloc.new_object<MediumTransitionQueue>(maxQueueSize, alloc);
if (haveMedia) {
mediumSampleQueue = alloc.new_object<MediumSampleQueue>(maxQueueSize, alloc);
mediumScatterQueue = alloc.new_object<MediumScatterQueue>(maxQueueSize, alloc);
}
stats = alloc.new_object<Stats>(maxDepth, alloc);
size_t endSize = mr->BytesAllocated();
pathIntegratorBytes += endSize - startSize;
}
void GPUPathIntegrator::TraceShadowRays(int depth) {
std::pair<cudaEvent_t, cudaEvent_t> events;
if (haveMedia)
events =
accel->IntersectShadowTr(maxQueueSize, shadowRayQueue);
else
events = accel->IntersectShadow(maxQueueSize, shadowRayQueue);
struct IsectShadowHack {};
GetGPUKernelStats<IsectShadowHack>("Tracing shadow rays")
.launchEvents.push_back(events);
// Add contribution if light was visible
ForAllQueued("Incorporate shadow ray contribution", shadowRayQueue, maxQueueSize,
[=] PBRT_GPU(const ShadowRayWorkItem sr, int index) {
if (!sr.Ld)
return;
SampledSpectrum Lpixel = pixelSampleState.L[sr.pixelIndex];
DBG("Adding shadow ray Ld %f %f %f %f at pixel index %d \n",
sr.Ld[0], sr.Ld[1], sr.Ld[2], sr.Ld[3], sr.pixelIndex);
pixelSampleState.L[sr.pixelIndex] = Lpixel + sr.Ld;
});
GPUDo("Reset shadowRayQueue", [=] PBRT_GPU() {
stats->shadowRays[depth] += shadowRayQueue->Size();
shadowRayQueue->Reset();
});
}
void GPUPathIntegrator::Render(ImageMetadata *metadata) {
Vector2i resolution = film.PixelBounds().Diagonal();
int spp = sampler.SamplesPerPixel();
RGB *displayRGB = nullptr, *displayRGBHost = nullptr;
std::atomic<bool> exitCopyThread{false};
std::thread copyThread;
if (!Options->displayServer.empty()) {
// Allocate staging memory on the GPU to store the current WIP
// image.
CUDA_CHECK(cudaMalloc(&displayRGB, resolution.x * resolution.y * sizeof(RGB)));
CUDA_CHECK(cudaMemset(displayRGB, 0, resolution.x * resolution.y * sizeof(RGB)));
// Host-side memory for the WIP Image. We'll just let this leak so
// that the lambda passed to DisplayDynamic below doesn't access
// freed memory after Render() returns...
displayRGBHost = new RGB[resolution.x * resolution.y];
copyThread = std::thread([&]() {
#ifdef NVTX
nvtxNameOsThread(syscall(SYS_gettid), "DISPLAY_SERVER_COPY_THREAD");
#endif
// Copy back to the CPU using a separate stream so that we can
// periodically but asynchronously pick up the latest results
// from the GPU.
cudaStream_t memcpyStream;
CUDA_CHECK(cudaStreamCreate(&memcpyStream));
#ifdef NVTX
nvtxNameCuStream(memcpyStream, "DISPLAY_SERVER_COPY_STREAM");
#endif
// Copy back to the host from the GPU buffer, without any
// synthronization.
while (!exitCopyThread) {
CUDA_CHECK(cudaMemcpyAsync(displayRGBHost, displayRGB,
resolution.x * resolution.y * sizeof(RGB),
cudaMemcpyDeviceToHost, memcpyStream));
std::this_thread::sleep_for(std::chrono::milliseconds(50));
CUDA_CHECK(cudaStreamSynchronize(memcpyStream));
}
// Copy one more time to get the final image before exiting.
CUDA_CHECK(cudaMemcpy(displayRGBHost, displayRGB,
resolution.x * resolution.y * sizeof(RGB),
cudaMemcpyDeviceToHost));
CUDA_CHECK(cudaDeviceSynchronize());
});
// Now on the CPU side, give the display system a lambda that
// copies values from |displayRGBHost| into its buffers used for
// sending messages to the display program (i.e., tev).
DisplayDynamic(film.GetFilename(), {resolution.x, resolution.y}, {"R", "G", "B"},
[resolution, displayRGBHost](
Bounds2i b, pstd::span<pstd::span<Float>> displayValue) {
int index = 0;
for (Point2i p : b) {
RGB rgb = displayRGBHost[p.x + p.y * resolution.x];
displayValue[0][index] = rgb.r;
displayValue[1][index] = rgb.g;
displayValue[2][index] = rgb.b;
++index;
}
});
}
ProgressReporter progress(spp, "Rendering", Options->quiet, true /* GPU */);
for (int sampleIndex = 0; sampleIndex < spp; ++sampleIndex) {
for (int y0 = 0; y0 < resolution.y; y0 += scanlinesPerPass) {
GPUDo("Reset ray queue", [=] PBRT_GPU() {
DBG("Starting scanlines at y0 = %d, sample %d / %d\n", y0, sampleIndex,
spp);
rayQueues[0]->Reset();
});
GenerateCameraRays(y0, sampleIndex);
GPUDo("Update camera ray stats",
[=] PBRT_GPU() { stats->cameraRays += rayQueues[0]->Size(); });
for (int depth = 0; true; ++depth) {
GenerateRaySamples(depth, sampleIndex);
GPUDo("Reset queues before tracing rays", [=] PBRT_GPU() {
hitAreaLightQueue->Reset();
if (escapedRayQueue)
escapedRayQueue->Reset();
basicEvalMaterialQueue->Reset();
universalEvalMaterialQueue->Reset();
if (bssrdfEvalQueue)
bssrdfEvalQueue->Reset();
if (subsurfaceScatterQueue)
subsurfaceScatterQueue->Reset();
mediumTransitionQueue->Reset();
if (mediumSampleQueue)
mediumSampleQueue->Reset();
if (mediumScatterQueue)
mediumScatterQueue->Reset();
rayQueues[(depth + 1) & 1]->Reset();
});
auto events = accel->IntersectClosest(
maxQueueSize, escapedRayQueue, hitAreaLightQueue,
basicEvalMaterialQueue, universalEvalMaterialQueue,
mediumTransitionQueue, mediumSampleQueue, rayQueues[depth & 1]);
struct IsectHack {};
GetGPUKernelStats<IsectHack>("Tracing closest hit rays")
.launchEvents.push_back(events);
if (depth > 0)
GPUDo("Update indirect ray stats", [=] PBRT_GPU() {
stats->indirectRays[depth] += rayQueues[depth & 1]->Size();
});
if (haveMedia)
SampleMediumInteraction(depth);
if (escapedRayQueue)
HandleEscapedRays(depth);
HandleRayFoundEmission(depth);
if (depth == maxDepth)
break;
EvaluateMaterialsAndBSDFs(depth);
// Do immediately so that we have space for shadow rays for
// subsurface..
TraceShadowRays(depth);
HandleMediumTransitions(depth);
if (haveSubsurface)
SampleSubsurface(depth);
}
UpdateFilm();
if (!Options->displayServer.empty())
GPUParallelFor("Update Display RGB Buffer", maxQueueSize,
[=] PBRT_GPU(int pixelIndex) {
Point2i pPixel = pixelSampleState.pPixel[pixelIndex];
if (!InsideExclusive(pPixel, film.PixelBounds()))
return;
Point2i p(pPixel - film.PixelBounds().pMin);
displayRGB[p.x + p.y * resolution.x] =
film.GetPixelRGB(pPixel);
});
}
progress.Update();
}
progress.Done();
CUDA_CHECK(cudaDeviceSynchronize());
// Wait until rendering is all done before we start to shut down the
// display stuff..
if (!Options->displayServer.empty()) {
exitCopyThread = true;
copyThread.join();
}
metadata->samplesPerPixel = sampler.SamplesPerPixel();
camera.InitMetadata(metadata);
}
void GPUPathIntegrator::HandleEscapedRays(int depth) {
ForAllQueued("Handle escaped rays", escapedRayQueue, maxQueueSize,
[=] PBRT_GPU(const EscapedRayWorkItem er, int index) {
Ray ray(er.rayo, er.rayd);
SampledSpectrum Le = envLight.Le(ray, er.lambda);
if (!Le)
return;
SampledSpectrum L = pixelSampleState.L[er.pixelIndex];
if (depth == 0 || er.specularBounce) {
L += er.beta * Le / er.pdfUni.Average();
} else {
Float time = 0; // FIXME
LightSampleContext ctx(er.piPrev, er.nPrev, er.nsPrev);
Float lightChoicePDF = lightSampler.PDF(ctx, envLight);
Float lightPDF =
lightChoicePDF *
envLight.PDF_Li(ctx, ray.d, LightSamplingMode::WithMIS);
SampledSpectrum pdfUni = er.pdfUni;
SampledSpectrum pdfNEE = er.pdfNEE * lightPDF;
L += er.beta * Le / (pdfUni + pdfNEE).Average();
}
DBG("Added L %f %f %f %f for escaped ray pixel index %d\n", L[0],
L[1], L[2], L[3], er.pixelIndex);
pixelSampleState.L[er.pixelIndex] = L;
});
}
void GPUPathIntegrator::HandleRayFoundEmission(int depth) {
ForAllQueued(
"Handle emitters hit by indirect rays", hitAreaLightQueue, maxQueueSize,
[=] PBRT_GPU(const HitAreaLightWorkItem he, int index) {
LightHandle areaLight = he.areaLight;
SampledSpectrum Le = areaLight.L(he.p, he.n, he.uv, he.wo, he.lambda);
if (!Le)
return;
DBG("Got Le %f %f %f %f from hit area light at depth %d\n", Le[0], Le[1],
Le[2], Le[3], depth);
SampledSpectrum L = pixelSampleState.L[he.pixelIndex];
if (depth == 0 || he.isSpecularBounce) {
L += he.beta * Le / he.pdfUni.Average();
} else {
Vector3f wi = he.rayd;
LightSampleContext ctx(he.piPrev, he.nPrev, he.nsPrev);
Float lightChoicePDF = lightSampler.PDF(ctx, areaLight);
Float lightPDF = lightChoicePDF *
areaLight.PDF_Li(ctx, wi, LightSamplingMode::WithMIS);
SampledSpectrum pdfUni = he.pdfUni;
SampledSpectrum pdfNEE = he.pdfNEE * lightPDF;
L += he.beta * Le / (pdfUni + pdfNEE).Average();
}
DBG("Added L %f %f %f %f for pixel index %d\n", L[0], L[1], L[2], L[3],
he.pixelIndex);
pixelSampleState.L[he.pixelIndex] = L;
});
}
void GPURender(ParsedScene &scene) {
GPUPathIntegrator *integrator =
gpuMemoryAllocator.new_object<GPUPathIntegrator>(gpuMemoryAllocator, scene);
// Set things up so that we can still have read from the
// GPUPathIntegrator struct on the CPU without hurting
// performance. (This makes it possible to use the values of things
// like GPUPathIntegrator::haveSubsurface to conditionally launch
// kernels according to what's in the scene...)
int deviceIndex;
CUDA_CHECK(cudaGetDevice(&deviceIndex));
CUDA_CHECK(
cudaMemAdvise(integrator, sizeof(*integrator), cudaMemAdviseSetReadMostly, 0));
CUDA_CHECK(cudaMemAdvise(integrator, sizeof(*integrator),
cudaMemAdviseSetPreferredLocation, deviceIndex));
// Copy all of the scene data structures over to GPU memory. This
// ensures that there isn't a big performance hitch for the first batch
// of rays as that stuff is copied over on demand.
CUDATrackedMemoryResource *mr =
dynamic_cast<CUDATrackedMemoryResource *>(gpuMemoryAllocator.resource());
CHECK(mr != nullptr);
mr->PrefetchToGPU();
///////////////////////////////////////////////////////////////////////////
// Render!
Timer timer;
ImageMetadata metadata;
integrator->Render(&metadata);
LOG_VERBOSE("Total rendering time: %.3f s", timer.ElapsedSeconds());
CUDA_CHECK(cudaProfilerStop());
if (!Options->quiet) {
ReportKernelStats();
Printf("GPU Statistics:\n");
Printf("%s\n", integrator->stats->Print());
}
metadata.renderTimeSeconds = timer.ElapsedSeconds();
metadata.samplesPerPixel = integrator->sampler.SamplesPerPixel();
std::vector<GPULogItem> logs = ReadGPULogs();
for (const auto &item : logs)
Log(item.level, item.file, item.line, item.message);
integrator->film.WriteImage(metadata);
}
GPUPathIntegrator::Stats::Stats(int maxDepth, Allocator alloc)
: indirectRays(maxDepth + 1, alloc), shadowRays(maxDepth, alloc) {}
std::string GPUPathIntegrator::Stats::Print() const {
std::string s;
s += StringPrintf(" %-42s %12" PRIu64 "\n", "Camera rays",
cameraRays);
for (int i = 1; i < indirectRays.size(); ++i)
s += StringPrintf(" %-42s %12" PRIu64 "\n",
StringPrintf("Indirect rays, depth %-3d", i), indirectRays[i]);
for (int i = 0; i < shadowRays.size(); ++i)
s += StringPrintf(" %-42s %12" PRIu64 "\n",
StringPrintf("Shadow rays, depth %-3d", i), shadowRays[i]);
return s;
}
} // namespace pbrt

View file

@ -0,0 +1,121 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#ifndef PBRT_GPU_PATHINTEGRATOR_H
#define PBRT_GPU_PATHINTEGRATOR_H
#include <pbrt/pbrt.h>
#include <pbrt/base/bxdf.h>
#include <pbrt/base/camera.h>
#include <pbrt/base/film.h>
#include <pbrt/base/filter.h>
#include <pbrt/base/light.h>
#include <pbrt/base/lightsampler.h>
#include <pbrt/base/sampler.h>
#include <pbrt/gpu/workitems.h>
#include <pbrt/gpu/workqueue.h>
#include <pbrt/util/pstd.h>
namespace pbrt {
class ParsedScene;
class GPUAccel;
void GPUInit();
void GPURender(ParsedScene &scene);
class GPUPathIntegrator {
public:
GPUPathIntegrator(Allocator alloc, const ParsedScene &scene);
void Render(ImageMetadata *metadata);
void GenerateCameraRays(int y0, int sampleIndex);
template <typename Sampler>
void GenerateCameraRays(int y0, int sampleIndex);
void GenerateRaySamples(int depth, int sampleIndex);
template <typename Sampler>
void GenerateRaySamples(int depth, int sampleIndex);
void TraceShadowRays(int depth);
void SampleMediumInteraction(int depth);
void HandleMediumTransitions(int depth);
void SampleSubsurface(int depth);
void HandleEscapedRays(int depth);
void HandleRayFoundEmission(int depth);
void EvaluateMaterialsAndBSDFs(int depth);
template <typename Material>
void EvaluateMaterialAndBSDF(int depth);
template <typename Material, typename TextureEvaluator>
void EvaluateMaterialAndBSDF(TextureEvaluator texEval, MaterialEvalQueue *evalQueue,
int depth);
void SampleDirect(int depth);
template <typename BxDF>
void SampleDirect(int depth);
void SampleIndirect(int depth);
template <typename BxDF>
void SampleIndirect(int depth);
void UpdateFilm();
FilterHandle filter;
FilmHandle film;
SamplerHandle sampler;
CameraHandle camera;
LightHandle envLight;
LightSamplerHandle lightSampler;
int maxDepth;
bool regularize;
int maxQueueSize, scanlinesPerPass;
// Various properties of the scene
bool initializeVisibleSurface;
bool haveSubsurface;
bool haveMedia;
pstd::array<bool, MaterialHandle::NumTags()> haveBasicEvalMaterial;
pstd::array<bool, MaterialHandle::NumTags()> haveUniversalEvalMaterial;
GPUAccel *accel = nullptr;
SOA<PixelSampleState> pixelSampleState;
RayQueue *rayQueues[2] = {nullptr, nullptr};
ShadowRayQueue *shadowRayQueue = nullptr;
EscapedRayQueue *escapedRayQueue = nullptr;
HitAreaLightQueue *hitAreaLightQueue = nullptr;
MaterialEvalQueue *basicEvalMaterialQueue = nullptr;
MaterialEvalQueue *universalEvalMaterialQueue = nullptr;
GetBSSRDFAndProbeRayQueue *bssrdfEvalQueue = nullptr;
SubsurfaceScatterQueue *subsurfaceScatterQueue = nullptr;
MediumTransitionQueue *mediumTransitionQueue = nullptr;
MediumSampleQueue *mediumSampleQueue = nullptr;
MediumScatterQueue *mediumScatterQueue = nullptr;
struct Stats {
Stats(int maxDepth, Allocator alloc);
std::string Print() const;
// Note: not atomics: tid 0 always updates them for everyone...
uint64_t cameraRays = 0;
pstd::vector<uint64_t> indirectRays, shadowRays;
};
Stats *stats;
};
} // namespace pbrt
#endif // PBRT_GPU_PATHINTEGRATOR_H

73
src/pbrt/gpu/samples.cpp Normal file
View file

@ -0,0 +1,73 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#include <pbrt/pbrt.h>
#include <pbrt/gpu/pathintegrator.h>
#include <pbrt/samplers.h>
#include <type_traits>
#ifdef PBRT_GPU_DBG
#ifndef TO_STRING
#define TO_STRING(x) TO_STRING2(x)
#define TO_STRING2(x) #x
#endif // !TO_STRING
#define DBG(...) printf(__FILE__ ":" TO_STRING(__LINE__) ": " __VA_ARGS__)
#else
#define DBG(...)
#endif // PBRT_GPU_DBG
namespace pbrt {
template <typename Sampler>
void GPUPathIntegrator::GenerateRaySamples(int depth, int sampleIndex) {
std::string desc = std::string("Generate ray samples - ") + Sampler::Name();
ForAllQueued(desc.c_str(), rayQueues[depth & 1], maxQueueSize,
[=] PBRT_GPU(const RayWorkItem w, int index) {
// Figure out how many dimensions have been consumed so far: 5
// are used for the initial camera sample and then either 7 or
// 10 per ray, depending on whether there's subsurface
// scattering.
int dimension = 5 + 7 * depth;
if (haveSubsurface)
dimension += 3 * depth;
// Initialize a Sampler
Sampler pixelSampler = *sampler.Cast<Sampler>();
Point2i pPixel = pixelSampleState.pPixel[w.pixelIndex];
pixelSampler.StartPixelSample(pPixel, sampleIndex, dimension);
// Generate the samples for the ray and store them with it in
// the ray queue.
RaySamples rs;
rs.direct.u = pixelSampler.Get2D();
rs.direct.uc = pixelSampler.Get1D();
rs.indirect.u = pixelSampler.Get2D();
rs.indirect.uc = pixelSampler.Get1D();
rs.indirect.rr = pixelSampler.Get1D();
rs.haveSubsurface = haveSubsurface;
if (haveSubsurface) {
rs.subsurface.uc = pixelSampler.Get1D();
rs.subsurface.u = pixelSampler.Get2D();
}
rayQueues[depth & 1]->raySamples[index] = rs;
});
}
void GPUPathIntegrator::GenerateRaySamples(int depth, int sampleIndex) {
auto generateSamples = [=](auto sampler) {
using Sampler = std::remove_reference_t<decltype(*sampler)>;
if constexpr (!std::is_same_v<Sampler, MLTSampler> &&
!std::is_same_v<Sampler, DebugMLTSampler>)
GenerateRaySamples<Sampler>(depth, sampleIndex);
};
// Call the appropriate GenerateRaySamples specialization based on the
// Sampler's actual type.
sampler.DispatchCPU(generateSamples);
}
} // namespace pbrt

211
src/pbrt/gpu/subsurface.cpp Normal file
View file

@ -0,0 +1,211 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#include <pbrt/pbrt.h>
#include <pbrt/bssrdf.h>
#include <pbrt/gpu/accel.h>
#include <pbrt/gpu/launch.h>
#include <pbrt/gpu/pathintegrator.h>
#include <pbrt/interaction.h>
#include <pbrt/lightsamplers.h>
#include <pbrt/samplers.h>
#include <pbrt/util/sampling.h>
#include <pbrt/util/spectrum.h>
#ifdef PBRT_GPU_DBG
#ifndef TO_STRING
#define TO_STRING(x) TO_STRING2(x)
#define TO_STRING2(x) #x
#endif // !TO_STRING
#define DBG(...) printf(__FILE__ ":" TO_STRING(__LINE__) ": " __VA_ARGS__)
#else
#define DBG(...)
#endif
namespace pbrt {
void GPUPathIntegrator::SampleSubsurface(int depth) {
ForAllQueued(
"Get BSSRDF and enqueue probe ray", bssrdfEvalQueue, maxQueueSize,
[=] PBRT_GPU(const GetBSSRDFAndProbeRayWorkItem be, int index) {
using BSSRDF = typename SubsurfaceMaterial::BSSRDF;
BSSRDF bssrdf;
const SubsurfaceMaterial *material = be.material.Cast<SubsurfaceMaterial>();
MaterialEvalContext ctx = be.GetMaterialEvalContext();
SampledWavelengths lambda = be.lambda;
material->GetBSSRDF(BasicTextureEvaluator(), ctx, lambda, &bssrdf);
RaySamples raySamples = rayQueues[depth & 1]->raySamples[be.rayIndex];
Float uc = raySamples.subsurface.uc;
Point2f u = raySamples.subsurface.u;
BSSRDFProbeSegment probeSeg = bssrdf.Sample(uc, u);
if (probeSeg)
subsurfaceScatterQueue->Push(probeSeg.p0, probeSeg.p1, material, bssrdf,
be.beta, be.pdfUni, be.mediumInterface,
be.rayIndex);
});
auto events = accel->IntersectOneRandom(maxQueueSize, subsurfaceScatterQueue);
struct IsectRandomHack {};
GetGPUKernelStats<IsectRandomHack>("Tracing subsurface scattering probe rays")
.launchEvents.push_back(events);
ForAllQueued(
"Handle out-scattering after SSS", subsurfaceScatterQueue, maxQueueSize,
[=] PBRT_GPU(SubsurfaceScatterWorkItem s, int index) {
if (s.weight == 0)
return;
using BSSRDF = TabulatedBSSRDF;
BSSRDF bssrdf = s.bssrdf;
using BxDF = typename BSSRDF::BxDF;
BxDF bxdf;
SubsurfaceInteraction &intr = s.ssi;
BSSRDFSample bssrdfSample = bssrdf.ProbeIntersectionToSample(intr, &bxdf);
if (!bssrdfSample.S || bssrdfSample.pdf == 0)
return;
SampledSpectrum betap = s.beta * bssrdfSample.S * s.weight / bssrdfSample.pdf;
SampledWavelengths lambda = rayQueues[depth & 1]->lambda[s.rayIndex];
Float etaScale = rayQueues[depth & 1]->etaScale[s.rayIndex];
RaySamples raySamples = rayQueues[depth & 1]->raySamples[s.rayIndex];
Vector3f wo = bssrdfSample.wo;
BSDF &bsdf = bssrdfSample.bsdf;
Float time = 0; // TODO: pipe through
// NOTE: the remainder is copied from the Material/BSDF eval method.
// Will unify into shared fragments in the book...
// Indirect...
{
Point2f u = raySamples.indirect.u;
Float uc = raySamples.indirect.uc;
BSDFSample bsdfSample = bsdf.Sample_f<BxDF>(wo, uc, u);
if (bsdfSample && bsdfSample.f) {
Vector3f wi = bsdfSample.wi;
SampledSpectrum beta = betap * bsdfSample.f * AbsDot(wi, intr.ns);
SampledSpectrum pdfUni = s.pdfUni, pdfNEE = pdfUni;
DBG("%s f*cos[0] %f bsdfSample.pdf %f f*cos/pdf %f\n", BxDF::Name(),
bsdfSample.f[0] * AbsDot(wi, intr.ns), bsdfSample.pdf,
bsdfSample.f[0] * AbsDot(wi, intr.ns) / bsdfSample.pdf);
if (bsdf.SampledPDFIsProportional()) {
Float pdf = bsdf.PDF(wo, wi);
beta *= pdf / bsdfSample.pdf;
pdfUni *= pdf;
DBG("Sampled PDF is proportional: pdf %f\n", pdf);
} else
pdfUni *= bsdfSample.pdf;
if (bsdfSample.IsTransmission())
etaScale *= Sqr(bsdf.eta);
// Russian roulette
SampledSpectrum rrBeta = beta * etaScale / pdfUni.Average();
if (rrBeta.MaxComponentValue() < 1 && depth > 1) {
Float q = std::max<Float>(0, 1 - rrBeta.MaxComponentValue());
if (raySamples.indirect.rr < q) {
beta = SampledSpectrum(0.f);
DBG("Path terminated with RR\n");
}
pdfUni *= 1 - q;
pdfNEE *= 1 - q;
}
if (beta) {
Ray ray = SpawnRay(intr.pi, intr.n, time, wi);
if (haveMedia)
// TODO: should always just take outside in this case?
ray.medium = Dot(ray.d, intr.n) > 0
? s.mediumInterface.outside
: s.mediumInterface.inside;
// || rather than | is intentional, to avoid the read if
// possible...
bool anyNonSpecularBounces = true;
int pixelIndex = rayQueues[depth & 1]->pixelIndex[s.rayIndex];
rayQueues[(depth + 1) & 1]->PushIndirect(
ray, intr.pi, intr.n, intr.ns, beta, pdfUni, pdfNEE, lambda,
etaScale, bsdfSample.IsSpecular(), anyNonSpecularBounces,
pixelIndex);
DBG("Spawned indirect ray at depth %d from prev index %d. "
"Specular %d Beta %f %f %f %f pdfUni %f %f %f %f pdfNEE %f "
"%f %f %f "
"beta/pdfUni %f %f %f %f\n",
depth + 1, int(s.rayIndex), int(bsdfSample.IsSpecular()),
beta[0], beta[1], beta[2], beta[3], pdfUni[0], pdfUni[1],
pdfUni[2], pdfUni[3], pdfNEE[0], pdfNEE[1], pdfNEE[2],
pdfNEE[3], SafeDiv(beta, pdfUni)[0], SafeDiv(beta, pdfUni)[1],
SafeDiv(beta, pdfUni)[2], SafeDiv(beta, pdfUni)[3]);
}
}
}
// Direct lighting...
if (!bsdf.IsSpecular()) {
LightSampleContext ctx(intr.pi, intr.n, intr.ns);
pstd::optional<SampledLight> sampledLight =
lightSampler.Sample(ctx, raySamples.direct.uc);
LightHandle light = sampledLight->light;
if (!light)
return;
LightLiSample ls = light.SampleLi(ctx, raySamples.direct.u, lambda,
LightSamplingMode::WithMIS);
if (!ls || !ls.L)
return;
Vector3f wi = ls.wi;
SampledSpectrum f = bsdf.f<BxDF>(wo, wi);
if (!f)
return;
SampledSpectrum beta = betap * f * AbsDot(wi, intr.ns);
DBG("depth %d beta %f %f %f %f f %f %f %f %f ls.L %f %f %f %f ls.pdf "
"%f\n",
depth, beta[0], beta[1], beta[2], beta[3], f[0], f[1], f[2], f[3],
ls.L[0], ls.L[1], ls.L[2], ls.L[3], ls.pdf);
Float lightPDF = ls.pdf * sampledLight->pdf;
// This causes pdfUni to be zero for the shadow ray, so that
// part of MIS just becomes a no-op.
Float bsdfPDF = IsDeltaLight(light.Type()) ? 0.f : bsdf.PDF<BxDF>(wo, wi);
SampledSpectrum pdfUni = s.pdfUni * bsdfPDF;
SampledSpectrum pdfNEE = s.pdfUni * lightPDF;
SampledSpectrum Ld = beta * ls.L;
DBG("depth %d Ld %f %f %f %f "
"new beta %f %f %f %f beta/uni %f %f %f %f Ld/uni %f %f %f %f\n",
depth, Ld[0], Ld[1], Ld[2], Ld[3], beta[0], beta[1], beta[2], beta[3],
SafeDiv(beta, pdfUni)[0], SafeDiv(beta, pdfUni)[1],
SafeDiv(beta, pdfUni)[2], SafeDiv(beta, pdfUni)[3],
SafeDiv(Ld, pdfUni)[0], SafeDiv(Ld, pdfUni)[1],
SafeDiv(Ld, pdfUni)[2], SafeDiv(Ld, pdfUni)[3]);
Ray ray = SpawnRayTo(intr.pi, intr.n, time, ls.pLight.pi, ls.pLight.n);
if (haveMedia)
// TODO: as above, always take outside here?
ray.medium = Dot(ray.d, intr.n) > 0 ? s.mediumInterface.outside
: s.mediumInterface.inside;
int pixelIndex = rayQueues[depth & 1]->pixelIndex[s.rayIndex];
shadowRayQueue->Push(ShadowRayWorkItem{ray, 1 - ShadowEpsilon, lambda, Ld,
pdfUni, pdfNEE, pixelIndex});
}
});
TraceShadowRays(depth);
}
} // namespace pbrt

View file

@ -0,0 +1,264 @@
// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#include <pbrt/pbrt.h>
#include <pbrt/base/bxdf.h>
#include <pbrt/bxdfs.h>
#include <pbrt/cameras.h>
#include <pbrt/gpu/launch.h>
#include <pbrt/gpu/pathintegrator.h>
#include <pbrt/interaction.h>
#include <pbrt/materials.h>
#include <pbrt/options.h>
#include <pbrt/textures.h>
#include <pbrt/util/check.h>
#include <pbrt/util/containers.h>
#include <pbrt/util/spectrum.h>
#include <pbrt/util/vecmath.h>
#include <type_traits>
#ifdef PBRT_GPU_DBG
#ifndef TO_STRING
#define TO_STRING(x) TO_STRING2(x)
#define TO_STRING2(x) #x
#endif // !TO_STRING
#define DBG(...) printf(__FILE__ ":" TO_STRING(__LINE__) ": " __VA_ARGS__)
#else
#define DBG(...)
#endif // PBRT_GPU_DBG
namespace pbrt {
template <typename Material, typename TextureEvaluator>
void GPUPathIntegrator::EvaluateMaterialAndBSDF(TextureEvaluator texEval,
MaterialEvalQueue *evalQueue, int depth) {
std::string name = StringPrintf(
"%s + BxDF Eval (%s tex)", Material::Name(),
std::is_same_v<TextureEvaluator, BasicTextureEvaluator> ? "Basic" : "Universal");
ForAllQueued(
name.c_str(), evalQueue->Get<Material>(), maxQueueSize,
[=] PBRT_GPU(const MaterialEvalWorkItem<Material> me, int index) {
const Material *material = me.material;
Normal3f ns = me.ns;
Vector3f dpdus = me.dpdus;
FloatTextureHandle displacement = material->GetDisplacement();
if (displacement) {
// Compute shading normal (and shading dpdu) via bump mapping.
DCHECK(texEval.CanEvaluate({displacement}, {}));
BumpEvalContext bctx = me.GetBumpEvalContext();
Vector3f dpdvs;
Bump(texEval, displacement, bctx, &dpdus, &dpdvs);
ns = Normal3f(Normalize(Cross(dpdus, dpdvs)));
ns = FaceForward(ns, me.n);
}
// Evaluate the material (and thence, its textures), to get the BSDF.
SampledWavelengths lambda = me.lambda;
MaterialEvalContext ctx = me.GetMaterialEvalContext(ns, dpdus);
using BxDF = typename Material::BxDF;
BxDF bxdf;
BSDF bsdf = material->GetBSDF(texEval, ctx, lambda, &bxdf);
// BSDF regularization, if appropriate.
if (regularize && me.anyNonSpecularBounces)
bsdf.Regularize();
if (depth == 0 && initializeVisibleSurface) {
SurfaceInteraction intr;
intr.pi = me.pi;
intr.n = me.n;
intr.shading.n = ns;
intr.wo = me.wo;
// TODO: intr.time
// Estimate BSDF's albedo
constexpr int nRhoSamples = 16;
SampledSpectrum rho(0.f);
for (int i = 0; i < nRhoSamples; ++i) {
// Generate sample for hemispherical-directional reflectance
Float uc = RadicalInverse(0, i + 1);
Point2f u(RadicalInverse(1, i + 1), RadicalInverse(2, i + 1));
// Estimate one term of $\rho_\roman{hd}$
BSDFSample bs = bsdf.Sample_f(me.wo, uc, u);
if (bs && bs.pdf > 0)
rho += bs.f * AbsDot(bs.wi, ns) / bs.pdf;
}
SampledSpectrum albedo = rho / nRhoSamples;
pixelSampleState.visibleSurface[me.pixelIndex] =
VisibleSurface(intr, camera.GetCameraTransform(), albedo, lambda);
}
Vector3f wo = me.wo;
RaySamples raySamples = rayQueues[depth & 1]->raySamples[me.rayIndex];
// Sample indirect lighting
BSDFSample bsdfSample =
bsdf.Sample_f<BxDF>(wo, raySamples.indirect.uc, raySamples.indirect.u);
if (bsdfSample && bsdfSample.f) {
Vector3f wi = bsdfSample.wi;
SampledSpectrum beta = me.beta * bsdfSample.f * AbsDot(wi, ns);
SampledSpectrum pdfUni = me.pdfUni, pdfNEE = pdfUni;
DBG("%s f*cos[0] %f bsdfSample.pdf %f f*cos/pdf %f\n", BxDF::Name(),
bsdfSample.f[0] * AbsDot(wi, ns), bsdfSample.pdf,
bsdfSample.f[0] * AbsDot(wi, ns) / bsdfSample.pdf);
if (bsdf.SampledPDFIsProportional()) {
// The PDFs need to be handled slightly differently for
// stochastically-sampled layered materials..
Float pdf = bsdf.PDF(wo, wi);
beta *= pdf / bsdfSample.pdf;
pdfUni *= pdf;
} else
pdfUni *= bsdfSample.pdf;
Float etaScale = me.etaScale;
if (bsdfSample.IsTransmission())
etaScale *= Sqr(bsdf.eta);
// Russian roulette
SampledSpectrum rrBeta = beta * etaScale / pdfUni.Average();
if (rrBeta.MaxComponentValue() < 1 && depth > 1) {
Float q = std::max<Float>(0, 1 - rrBeta.MaxComponentValue());
if (raySamples.indirect.rr < q) {
beta = SampledSpectrum(0.f);
DBG("Path terminated with RR ray index %d\n", me.rayIndex);
}
pdfUni *= 1 - q;
pdfNEE *= 1 - q;
}
if (beta) {
if (bsdfSample.IsTransmission() &&
material->HasSubsurfaceScattering()) {
// There's a BSSRDF and sampled ray scattered into
// the surface; enqueue a work item for subsurface
// scattering rather than tracing the ray.
bssrdfEvalQueue->Push(GetBSSRDFAndProbeRayWorkItem{
material, lambda, beta, pdfUni, Point3f(me.pi), wo, me.n, ns,
dpdus, me.uv, me.mediumInterface, me.rayIndex});
} else {
Ray ray = SpawnRay(me.pi, me.n, me.time, wi);
if (haveMedia)
ray.medium = Dot(ray.d, me.n) > 0 ? me.mediumInterface.outside
: me.mediumInterface.inside;
// || rather than | is intentional, to avoid the read if
// possible...
bool anyNonSpecularBounces =
!bsdfSample.IsSpecular() || me.anyNonSpecularBounces;
// Spawn indriect ray.
rayQueues[(depth + 1) & 1]->PushIndirect(
ray, me.pi, me.n, ns, beta, pdfUni, pdfNEE, lambda, etaScale,
bsdfSample.IsSpecular(), anyNonSpecularBounces,
me.pixelIndex);
DBG("Spawned indirect ray at depth %d from prev ray index %d. "
"Specular %d Beta %f %f %f %f pdfUni %f %f %f %f pdfNEE %f "
"%f %f %f "
"beta/pdfUni %f %f %f %f\n",
depth + 1, int(me.rayIndex), int(bsdfSample.IsSpecular()),
beta[0], beta[1], beta[2], beta[3], pdfUni[0], pdfUni[1],
pdfUni[2], pdfUni[3], pdfNEE[0], pdfNEE[1], pdfNEE[2],
pdfNEE[3], SafeDiv(beta, pdfUni)[0], SafeDiv(beta, pdfUni)[1],
SafeDiv(beta, pdfUni)[2], SafeDiv(beta, pdfUni)[3]);
}
}
}
// Sample direct lighting.
if (!bsdf.IsSpecular()) {
// Choose a light source using the LightSampler.
LightSampleContext ctx(me.pi, me.n, ns);
pstd::optional<SampledLight> sampledLight =
lightSampler.Sample(ctx, raySamples.direct.uc);
LightHandle light = sampledLight->light;
if (!light)
return;
// Remarkably, this substantially improves L1 cache hits with
// CoatedDiffuseBxDF and gives about a 60% perf. benefit.
__syncthreads();
// And now sample the light source itself.
LightLiSample ls = light.SampleLi(ctx, raySamples.direct.u, lambda,
LightSamplingMode::WithMIS);
if (!ls || !ls.L)
return;
Vector3f wi = ls.wi;
SampledSpectrum f = bsdf.f<BxDF>(wo, wi);
if (!f)
return;
SampledSpectrum beta = me.beta * f * AbsDot(wi, ns);
DBG("ray index %d depth %d beta %f %f %f %f f %f %f %f %f ls.L %f %f %f "
"%f ls.pdf %f\n",
me.rayIndex, depth, beta[0], beta[1], beta[2], beta[3], f[0], f[1],
f[2], f[3], ls.L[0], ls.L[1], ls.L[2], ls.L[3], ls.pdf);
// Compute light and BSDF PDFs for MIS.
Float lightPDF = ls.pdf * sampledLight->pdf;
// This causes pdfUni to be zero for the shadow ray, so that
// part of MIS just becomes a no-op.
Float bsdfPDF = IsDeltaLight(light.Type()) ? 0.f : bsdf.PDF<BxDF>(wo, wi);
SampledSpectrum pdfUni = me.pdfUni * bsdfPDF;
SampledSpectrum pdfNEE = me.pdfUni * lightPDF;
SampledSpectrum Ld = beta * ls.L;
Ray ray = SpawnRayTo(me.pi, me.n, me.time, ls.pLight.pi, ls.pLight.n);
if (haveMedia)
ray.medium = Dot(ray.d, me.n) > 0 ? me.mediumInterface.outside
: me.mediumInterface.inside;
shadowRayQueue->Push(ShadowRayWorkItem{ray, 1 - ShadowEpsilon, lambda, Ld,
pdfUni, pdfNEE, me.pixelIndex});
DBG("ray index %d spawned shadow ray depth %d Ld %f %f %f %f "
"new beta %f %f %f %f beta/uni %f %f %f %f Ld/uni %f %f %f %f\n",
me.rayIndex, depth, Ld[0], Ld[1], Ld[2], Ld[3], beta[0], beta[1],
beta[2], beta[3], SafeDiv(beta, pdfUni)[0], SafeDiv(beta, pdfUni)[1],
SafeDiv(beta, pdfUni)[2], SafeDiv(beta, pdfUni)[3],
SafeDiv(Ld, pdfUni)[0], SafeDiv(Ld, pdfUni)[1],
SafeDiv(Ld, pdfUni)[2], SafeDiv(Ld, pdfUni)[3]);
}
});
}
template <typename Material>
void GPUPathIntegrator::EvaluateMaterialAndBSDF(int depth) {
if (haveBasicEvalMaterial[MaterialHandle::TypeIndex<Material>()])
EvaluateMaterialAndBSDF<Material>(BasicTextureEvaluator(), basicEvalMaterialQueue,
depth);
if (haveUniversalEvalMaterial[MaterialHandle::TypeIndex<Material>()])
EvaluateMaterialAndBSDF<Material>(UniversalTextureEvaluator(),
universalEvalMaterialQueue, depth);
}
struct EvaluateMaterialCallback {
int depth;
GPUPathIntegrator *integrator;
template <typename Material>
void operator()() {
integrator->EvaluateMaterialAndBSDF<Material>(depth);
}
};
void GPUPathIntegrator::EvaluateMaterialsAndBSDFs(int depth) {
MaterialHandle::ForEachType(EvaluateMaterialCallback{depth, this});
}
} // namespace pbrt

Some files were not shown because too many files have changed in this diff Show more