mirror of
https://github.com/mmp/pbrt-v4
synced 2026-09-26 16:20:07 +03:00
Initial commit for public release.
This commit is contained in:
commit
9772673e43
230 changed files with 401008 additions and 0 deletions
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
*~
|
||||||
|
.#*
|
||||||
|
#*#
|
||||||
|
src/build
|
||||||
|
.DS_Store
|
||||||
|
.ipynb_checkpoints/
|
||||||
|
build/
|
||||||
19
.gitmodules
vendored
Normal file
19
.gitmodules
vendored
Normal 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
831
CMakeLists.txt
Normal 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
202
LICENSE.txt
Normal 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
189
README.md
Normal 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
27
THIRD_PARTY.md
Normal 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
59
cmake/FindASan.cmake
Normal 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
57
cmake/FindMSan.cmake
Normal 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 ()
|
||||||
62
cmake/FindSanitizers.cmake
Normal file
62
cmake/FindSanitizers.cmake
Normal 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
64
cmake/FindTSan.cmake
Normal 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
46
cmake/FindUBSan.cmake
Normal 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
55
cmake/asan-wrapper
Executable 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
24
cmake/checkcuda.cu
Normal 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 */
|
||||||
|
}
|
||||||
|
}
|
||||||
173
cmake/sanitize-helpers.cmake
Normal file
173
cmake/sanitize-helpers.cmake
Normal 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 ()
|
||||||
1557
exporters/cinema4d/PBRT Export/pbrt.pyp
Normal file
1557
exporters/cinema4d/PBRT Export/pbrt.pyp
Normal file
File diff suppressed because it is too large
Load diff
23
exporters/cinema4d/PBRT Export/res/c4d_symbols.h
Normal file
23
exporters/cinema4d/PBRT Export/res/c4d_symbols.h
Normal 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,
|
||||||
|
};
|
||||||
62
exporters/cinema4d/PBRT Export/res/dialogs/dlg_pbrt.res
Normal file
62
exporters/cinema4d/PBRT Export/res/dialogs/dlg_pbrt.res
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
exporters/cinema4d/PBRT Export/res/pbrt-env-bake.c4d
Normal file
BIN
exporters/cinema4d/PBRT Export/res/pbrt-env-bake.c4d
Normal file
Binary file not shown.
|
|
@ -0,0 +1,8 @@
|
||||||
|
// C4D-StringResource
|
||||||
|
// Identifier Text
|
||||||
|
|
||||||
|
STRINGTABLE
|
||||||
|
{
|
||||||
|
IDS_PBRT_START "Start";
|
||||||
|
IDS_PBRT_ABORT "Abort";
|
||||||
|
}
|
||||||
|
|
@ -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";
|
||||||
|
}
|
||||||
27
exporters/cinema4d/readme.md
Normal file
27
exporters/cinema4d/readme.md
Normal 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
98
src/ext/CMakeLists.txt
Normal 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")
|
||||||
|
|
||||||
1
src/ext/double-conversion
Submodule
1
src/ext/double-conversion
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit cc1f75a114aca8d2af69f73a5a959aecbab0e87a
|
||||||
1
src/ext/filesystem
Submodule
1
src/ext/filesystem
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit f45da753728cde9b1c380b343e41c8b1ca6498d7
|
||||||
9592
src/ext/gtest/gtest-all.cc
Normal file
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
20065
src/ext/gtest/gtest.h
Normal file
File diff suppressed because it is too large
Load diff
41
src/ext/gtest/gtest_main.cc
Normal file
41
src/ext/gtest/gtest_main.cc
Normal 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
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
1759
src/ext/lodepng/lodepng.h
Normal file
File diff suppressed because it is too large
Load diff
1
src/ext/openexr
Submodule
1
src/ext/openexr
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 5cfb5dab6dfada731586b0281bdb15ee75e26782
|
||||||
1
src/ext/ptex
Submodule
1
src/ext/ptex
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 77b387406028d0dd6fea76d59d51e17aafe53358
|
||||||
1612
src/ext/rply/rply.cpp
Normal file
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
389
src/ext/rply/rply.h
Normal 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
|
||||||
825
src/ext/skymodel/ArHosekSkyModel.c
Normal file
825
src/ext/skymodel/ArHosekSkyModel.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
451
src/ext/skymodel/ArHosekSkyModel.h
Normal file
451
src/ext/skymodel/ArHosekSkyModel.h
Normal 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_
|
||||||
3863
src/ext/skymodel/ArHosekSkyModelData_CIEXYZ.h
Normal file
3863
src/ext/skymodel/ArHosekSkyModelData_CIEXYZ.h
Normal file
File diff suppressed because it is too large
Load diff
3861
src/ext/skymodel/ArHosekSkyModelData_RGB.h
Normal file
3861
src/ext/skymodel/ArHosekSkyModelData_RGB.h
Normal file
File diff suppressed because it is too large
Load diff
33770
src/ext/skymodel/ArHosekSkyModelData_Spectral.h
Normal file
33770
src/ext/skymodel/ArHosekSkyModelData_Spectral.h
Normal file
File diff suppressed because it is too large
Load diff
1
src/ext/stb
Submodule
1
src/ext/stb
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit b42009b3b9d4ca35bc703f5310eedc74f584be58
|
||||||
1
src/ext/zlib
Submodule
1
src/ext/zlib
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 54d591eabf9fe0e84c725638f8d5d8d202a093fa
|
||||||
15
src/pbrt/.clang-format
Normal file
15
src/pbrt/.clang-format
Normal 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
83
src/pbrt/base/#shape.h#
Normal 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 ¶meters,
|
||||||
|
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
40
src/pbrt/base/bssrdf.h
Normal 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
198
src/pbrt/base/bxdf.h
Normal 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
79
src/pbrt/base/camera.h
Normal 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 ¶meters, 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
65
src/pbrt/base/film.h
Normal 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 ¶meters, 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
48
src/pbrt/base/filter.h
Normal 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 ¶meters, 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
102
src/pbrt/base/light.h
Normal 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 ¶meters,
|
||||||
|
const Transform &renderFromLight,
|
||||||
|
const CameraTransform &cameraTransform,
|
||||||
|
MediumHandle outsideMedium, const FileLoc *loc,
|
||||||
|
Allocator alloc);
|
||||||
|
static LightHandle CreateArea(const std::string &name,
|
||||||
|
const ParameterDictionary ¶meters,
|
||||||
|
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
|
||||||
51
src/pbrt/base/lightsampler.h
Normal file
51
src/pbrt/base/lightsampler.h
Normal 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
72
src/pbrt/base/material.h
Normal 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 ¶meters,
|
||||||
|
/*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
90
src/pbrt/base/medium.h
Normal 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 ¶meters,
|
||||||
|
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
66
src/pbrt/base/sampler.h
Normal 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 ¶meters,
|
||||||
|
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
82
src/pbrt/base/shape.h
Normal 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 ¶meters,
|
||||||
|
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
88
src/pbrt/base/texture.h
Normal 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 ¶meters,
|
||||||
|
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 ¶meters,
|
||||||
|
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
22
src/pbrt/bsdf.cpp
Normal 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
201
src/pbrt/bsdf.h
Normal 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
795
src/pbrt/bsdfs_test.cpp
Normal 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
144
src/pbrt/bssrdf.cpp
Normal 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
357
src/pbrt/bssrdf.h
Normal 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
997
src/pbrt/bxdfs.cpp
Normal 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
1457
src/pbrt/bxdfs.h
Normal file
File diff suppressed because it is too large
Load diff
1543
src/pbrt/cameras.cpp
Normal file
1543
src/pbrt/cameras.cpp
Normal file
File diff suppressed because it is too large
Load diff
547
src/pbrt/cameras.h
Normal file
547
src/pbrt/cameras.h
Normal 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 ¶meters,
|
||||||
|
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 ¶meters,
|
||||||
|
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 ¶meters,
|
||||||
|
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 ¶meters,
|
||||||
|
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
|
||||||
480
src/pbrt/cmd/cyhair2pbrt.cpp
Normal file
480
src/pbrt/cmd/cyhair2pbrt.cpp
Normal 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
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
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
241
src/pbrt/cmd/pbrt.cpp
Normal 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;
|
||||||
|
}
|
||||||
81
src/pbrt/cmd/pbrt_test.cpp
Normal file
81
src/pbrt/cmd/pbrt_test.cpp
Normal 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;
|
||||||
|
}
|
||||||
902
src/pbrt/cmd/rgb2spec_opt.cpp
Normal file
902
src/pbrt/cmd/rgb2spec_opt.cpp
Normal 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
455
src/pbrt/cmd/soac.cpp
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
1185
src/pbrt/cpu/accelerators.cpp
Normal file
1185
src/pbrt/cpu/accelerators.cpp
Normal file
File diff suppressed because it is too large
Load diff
108
src/pbrt/cpu/accelerators.h
Normal file
108
src/pbrt/cpu/accelerators.h
Normal 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 ¶meters);
|
||||||
|
|
||||||
|
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 ¶meters);
|
||||||
|
|
||||||
|
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 ¶meters);
|
||||||
|
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
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
470
src/pbrt/cpu/integrators.h
Normal 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 ¶meters,
|
||||||
|
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 ¶meters, 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 ¶meters, 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 ¶meters, 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 ¶meters, 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 ¶meters, 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 ¶meters, 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 ¶meters, 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 ¶meters, 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 ¶meters,
|
||||||
|
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 ¶meters,
|
||||||
|
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
|
||||||
421
src/pbrt/cpu/integrators_test.cpp
Normal file
421
src/pbrt/cpu/integrators_test.cpp
Normal 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
172
src/pbrt/cpu/primitive.cpp
Normal 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
122
src/pbrt/cpu/primitive.h
Normal 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
340
src/pbrt/cpu/render.cpp
Normal 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 ¶meters,
|
||||||
|
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
18
src/pbrt/cpu/render.h
Normal 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
586
src/pbrt/film.cpp
Normal 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 ¶meters, 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 ¶meters,
|
||||||
|
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 ¶meters, 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
323
src/pbrt/film.h
Normal 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 ¶meters, 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 ¶meters, 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
163
src/pbrt/filters.cpp
Normal 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 ¶meters, 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 ¶meters,
|
||||||
|
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 ¶meters,
|
||||||
|
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 ¶meters,
|
||||||
|
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 ¶meters,
|
||||||
|
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 ¶meters,
|
||||||
|
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
258
src/pbrt/filters.h
Normal 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 ¶meters, 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 ¶meters,
|
||||||
|
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 ¶meters,
|
||||||
|
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 ¶meters,
|
||||||
|
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 ¶meters,
|
||||||
|
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
100
src/pbrt/filters_test.cpp
Normal 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
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
120
src/pbrt/gpu/accel.h
Normal 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
96
src/pbrt/gpu/camera.cpp
Normal 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
44
src/pbrt/gpu/film.cpp
Normal 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
72
src/pbrt/gpu/init.cpp
Normal 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
14
src/pbrt/gpu/init.h
Normal 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
77
src/pbrt/gpu/launch.cpp
Normal 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
101
src/pbrt/gpu/launch.h
Normal 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
334
src/pbrt/gpu/media.cpp
Normal 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
700
src/pbrt/gpu/optix.cu
Normal 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
71
src/pbrt/gpu/optix.h
Normal 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
|
||||||
580
src/pbrt/gpu/pathintegrator.cpp
Normal file
580
src/pbrt/gpu/pathintegrator.cpp
Normal 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
|
||||||
121
src/pbrt/gpu/pathintegrator.h
Normal file
121
src/pbrt/gpu/pathintegrator.h
Normal 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
73
src/pbrt/gpu/samples.cpp
Normal 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
211
src/pbrt/gpu/subsurface.cpp
Normal 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
|
||||||
264
src/pbrt/gpu/surfscatter.cpp
Normal file
264
src/pbrt/gpu/surfscatter.cpp
Normal 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
Loading…
Add table
Add a link
Reference in a new issue