Initial commit

This commit is contained in:
Ziyi 2024-02-15 14:10:30 +01:00 committed by GitHub
commit 43b180a44f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
174 changed files with 168433 additions and 0 deletions

42
.github/workflows/cmake.yml vendored Normal file
View File

@ -0,0 +1,42 @@
name: Build
on: [push]
env:
BUILD_TYPE: Release
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
# Setup caching of build artifacts to reduce total build time
- name: ccache
uses: hendrikmuhs/ccache-action@v1
- name: Create Build Environment
run: cmake -E make_directory ${{github.workspace}}/build
- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -yq libglu1-mesa-dev libxxf86vm-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libx11-dev
- name: Configure CMake
# Use a bash shell so we can use the same syntax for environment variable
# access regardless of the host operating system
shell: bash
working-directory: ${{github.workspace}}/build
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
- name: Build
working-directory: ${{github.workspace}}/build
shell: bash
run: cmake --build . --config $BUILD_TYPE -- -j
- name: Test
working-directory: ${{github.workspace}}
shell: bash
run: python3 run_tests.py

27
.gitignore vendored Normal file
View File

@ -0,0 +1,27 @@
/build
.DS_Store
/nori
/warptest
/ext_build
CMakeCache.txt
CMakeSettings.json
CMakeFiles
Makefile
*.cmake
.ninja_deps
.ninja_log
build.ninja
rules.ninja
.vs
.vscode
/out
*.sublime-workspace
*.sublime-project
*.vcxproj
*.filters
*.sdf
*.sln
*.dir
*.opendb
Debug
x64

30
.gitmodules vendored Normal file
View File

@ -0,0 +1,30 @@
[submodule "ext/filesystem"]
path = ext/filesystem
url = https://github.com/wjakob/filesystem
[submodule "ext/tbb"]
path = ext/tbb
url = https://github.com/wjakob/tbb
[submodule "ext/pugixml"]
path = ext/pugixml
url = http://github.com/zeux/pugixml
[submodule "ext/tinyformat"]
path = ext/tinyformat
url = https://github.com/wjakob/tinyformat
[submodule "ext/hypothesis"]
path = ext/hypothesis
url = https://github.com/wjakob/hypothesis
[submodule "ext/pcg32"]
path = ext/pcg32
url = https://github.com/wjakob/pcg32
[submodule "ext/nanogui"]
path = ext/nanogui
url = https://github.com/mitsuba-renderer/nanogui
[submodule "ext/zlib"]
path = ext/zlib
url = https://github.com/mitsuba-renderer/zlib
[submodule "ext/openexr"]
path = ext/openexr
url = https://github.com/mitsuba-renderer/openexr/
[submodule "ext/eigen"]
path = ext/eigen
url = https://gitlab.com/libeigen/eigen.git

128
CMakeLists.txt Normal file
View File

@ -0,0 +1,128 @@
cmake_minimum_required (VERSION 3.8)
project(nori)
add_subdirectory(ext ext_build)
include_directories(
# Nori include files
${CMAKE_CURRENT_SOURCE_DIR}/include
# tinyformat string formatting library
${TFM_INCLUDE_DIR}
# Eigen linear algebra library
SYSTEM ${EIGEN_INCLUDE_DIR}
# OpenEXR high dynamic range bitmap library
SYSTEM ${OPENEXR_INCLUDE_DIRS}
# Intel Thread Building Blocks
SYSTEM ${TBB_INCLUDE_DIR}
# Pseudorandom number generator
${PCG32_INCLUDE_DIR}
# PugiXML parser
${PUGIXML_INCLUDE_DIR}
# Helper functions for statistical hypothesis tests
${HYPOTHESIS_INCLUDE_DIR}
# GLFW library for OpenGL context creation
SYSTEM ${GLFW_INCLUDE_DIR}
# GLEW library for accessing OpenGL functions
SYSTEM ${GLEW_INCLUDE_DIR}
# NanoVG drawing library
SYSTEM ${NANOVG_INCLUDE_DIR}
# NanoGUI user interface library
SYSTEM ${NANOGUI_INCLUDE_DIR}
SYSTEM ${NANOGUI_EXTRA_INCS}
# Portable filesystem API
SYSTEM ${FILESYSTEM_INCLUDE_DIR}
# STB Image Write
SYSTEM ${STB_IMAGE_WRITE_INCLUDE_DIR}
)
# The following lines build the main executable. If you add a source
# code file to Nori, be sure to include it in this list.
add_executable(nori
# Header files
include/nori/bbox.h
include/nori/bitmap.h
include/nori/block.h
include/nori/bsdf.h
include/nori/accel.h
include/nori/camera.h
include/nori/color.h
include/nori/common.h
include/nori/dpdf.h
include/nori/frame.h
include/nori/integrator.h
include/nori/emitter.h
include/nori/mesh.h
include/nori/object.h
include/nori/parser.h
include/nori/proplist.h
include/nori/ray.h
include/nori/rfilter.h
include/nori/sampler.h
include/nori/scene.h
include/nori/timer.h
include/nori/transform.h
include/nori/vector.h
include/nori/warp.h
# Source code files
src/bitmap.cpp
src/block.cpp
src/accel.cpp
src/chi2test.cpp
src/common.cpp
src/diffuse.cpp
src/gui.cpp
src/independent.cpp
src/main.cpp
src/mesh.cpp
src/obj.cpp
src/object.cpp
src/parser.cpp
src/perspective.cpp
src/proplist.cpp
src/rfilter.cpp
src/scene.cpp
src/ttest.cpp
src/warp.cpp
src/microfacet.cpp
src/mirror.cpp
src/dielectric.cpp
)
add_definitions(${NANOGUI_EXTRA_DEFS})
# The following lines build the warping test application
add_executable(warptest
include/nori/warp.h
src/warp.cpp
src/warptest.cpp
src/microfacet.cpp
src/object.cpp
src/proplist.cpp
src/common.cpp
)
if (WIN32)
target_link_libraries(nori tbb_static pugixml IlmImf nanogui ${NANOGUI_EXTRA_LIBS} zlibstatic)
else()
target_link_libraries(nori tbb_static pugixml IlmImf nanogui ${NANOGUI_EXTRA_LIBS})
endif()
target_link_libraries(warptest tbb_static nanogui ${NANOGUI_EXTRA_LIBS})
# Force colored output for the ninja generator
if (CMAKE_GENERATOR STREQUAL "Ninja")
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fcolor-diagnostics")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics")
elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdiagnostics-color=always")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always")
endif()
endif()
target_compile_features(warptest PRIVATE cxx_std_17)
target_compile_features(nori PRIVATE cxx_std_17)
# vim: set et ts=2 sw=2 ft=cmake nospell:

26
README.md Normal file
View File

@ -0,0 +1,26 @@
Advanced Computer Graphics — Homeworks
======================================
Student name:
Sciper number:
## Build status
**Insert your build badge URL here**
## Homework results
| Homework | Links
| ---------: | ---------------------------------------------
| 1 | [report.html](results/homework-1/report.html)
| 2 | [report.html](results/homework-2/report.html)
| 3 | [report.html](results/homework-3/report.html)
| 4 | [report.html](results/homework-4/report.html)
| 5 | [report.html](results/homework-5/report.html)
## Featured result
Feel free to show off your best render here!

215
ext/CMakeLists.txt Normal file
View File

@ -0,0 +1,215 @@
# =======================================================================
# WARNING WARNING WARNING WARNING WARNING WARNING
# =======================================================================
# Remember to put on SAFETY GOGGLES before looking at this file. You
# are most certainly not expected to read or understand any of it.
# =======================================================================
#
# This CMake file is responsible for compiling dependency libraries and
# setting up suitable compiler flags for various platforms. You do not
# need to read or change anything in this file; see CMakeLists.txt in
# the main Nori folder instead.
include(CheckCXXCompilerFlag)
if(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/openexr/OpenEXR")
message(FATAL_ERROR "The Nori dependencies are missing! "
"You probably did not clone the project with --recursive. It is possible to recover "
"by calling \"git submodule update --init --recursive\"")
endif()
# Set a default build configuration (Release)
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()
string(TOUPPER "${CMAKE_BUILD_TYPE}" U_CMAKE_BUILD_TYPE)
# Enable folders for projects in Visual Studio
if (CMAKE_GENERATOR MATCHES "Visual Studio")
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
endif()
if (APPLE)
set(CMAKE_MACOSX_RPATH ON)
endif()
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
# Prefer libc++ in conjunction with Clang
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
CHECK_CXX_COMPILER_FLAG("-stdlib=libc++" HAS_LIBCPP)
if (HAS_LIBCPP)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -D_LIBCPP_VERSION")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -stdlib=libc++")
message(STATUS "Nori: using libc++.")
else()
message(WARNING "libc++ is recommended in conjunction with clang. Please insteall the libc++ development headers, provided e.g. by the packages 'libc++-dev' and 'libc++abi-dev' on Debian/Ubuntu.")
endif()
endif()
# Enable link time optimization and set the default symbol
# visibility to hidden (very important to obtain small binaries)
if (NOT ${U_CMAKE_BUILD_TYPE} MATCHES DEBUG)
# Default symbol visibility
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden")
endif()
# Disable specific GCC 7 warnings
if (CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 7.0)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations -Wno-misleading-indentation -Wformat-truncation=0 -Wno-int-in-bool-context -Wimplicit-fallthrough=0")
endif()
endif()
# Sanitize build environment for static build with C++11
if (MSVC)
# Disable annoying secure CRT warnings
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D_CRT_SECURE_NO_WARNINGS")
# We'll select the TBB library ourselves
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D__TBB_NO_IMPLICIT_LINKAGE")
# Parallel build on MSVC (all targets)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
if (NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:SSE2")
# Disable Eigen vectorization for Windows 32 bit builds (issues with unaligned access segfaults)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /DEIGEN_DONT_ALIGN")
endif()
# Static build
set(CompilerFlags
CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO
CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE
CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO)
foreach(CompilerFlag ${CompilerFlags})
string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}")
endforeach()
endif()
if (WIN32)
# Build zlib (only on Windows)
set(ZLIB_BUILD_STATIC_LIBS ON CACHE BOOL " " FORCE)
set(ZLIB_BUILD_SHARED_LIBS OFF CACHE BOOL " " FORCE)
add_subdirectory(zlib)
set(ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/zlib" CACHE PATH " " FORCE)
if (CMAKE_GENERATOR STREQUAL "Ninja")
set(ZLIB_LIBRARY "${CMAKE_CURRENT_BINARY_DIR}/zlib/zlibstatic.lib" CACHE FILEPATH " " FORCE)
else()
set(ZLIB_LIBRARY "${CMAKE_CURRENT_BINARY_DIR}/zlib/$<CONFIGURATION>/zlibstatic.lib" CACHE FILEPATH " " FORCE)
endif()
set_property(TARGET zlibstatic PROPERTY FOLDER "dependencies")
include_directories(${ZLIB_INCLUDE_DIR} "${CMAKE_CURRENT_BINARY_DIR}/zlib")
endif()
# Build OpenER
set(ILMBASE_BUILD_SHARED_LIBS OFF CACHE BOOL " " FORCE)
set(OPENEXR_BUILD_SHARED_LIBS OFF CACHE BOOL " " FORCE)
set(ILMBASE_NAMESPACE_VERSIONING OFF CACHE BOOL " " FORCE)
set(OPENEXR_NAMESPACE_VERSIONING OFF CACHE BOOL " " FORCE)
add_subdirectory(openexr)
set_property(TARGET IexMath eLut toFloat b44ExpLogTable dwaLookups IlmThread Half Iex Imath IlmImf PROPERTY FOLDER "dependencies")
# Build Thread Building Blocks (main shared libraries only)
set(TBB_BUILD_SHARED OFF CACHE BOOL " " FORCE)
set(TBB_BUILD_STATIC ON CACHE BOOL " " FORCE)
set(TBB_BUILD_TBBMALLOC OFF CACHE BOOL " " FORCE)
set(TBB_BUILD_TBBMALLOC_PROXY OFF CACHE BOOL " " FORCE)
set(TBB_BUILD_TESTS OFF CACHE BOOL " " FORCE)
add_subdirectory(tbb)
set_property(TARGET tbb_static tbb_def_files PROPERTY FOLDER "dependencies")
if (APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DGL_SILENCE_DEPRECATION=1")
endif()
# Build NanoGUI
# Use OpenGL backend for UI. In the future, this can be removed to use Metal on MacOS
set(NANOGUI_BACKEND "OpenGL" CACHE BOOL " " FORCE)
set(NANOGUI_BUILD_EXAMPLES OFF CACHE BOOL " " FORCE)
set(NANOGUI_BUILD_SHARED OFF CACHE BOOL " " FORCE)
set(NANOGUI_BUILD_PYTHON OFF CACHE BOOL " " FORCE)
add_subdirectory(nanogui)
set_property(TARGET glfw glfw_objects nanogui PROPERTY FOLDER "dependencies")
# Build the pugixml parser
add_library(pugixml STATIC pugixml/src/pugixml.cpp)
set_property(TARGET pugixml PROPERTY
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/pugixml")
set_property(TARGET pugixml PROPERTY FOLDER "dependencies")
set(OPENEXR_INCLUDE_DIRS
${CMAKE_CURRENT_SOURCE_DIR}/openexr/IlmBase/Imath
${CMAKE_CURRENT_SOURCE_DIR}/openexr/IlmBase/Iex
${CMAKE_CURRENT_SOURCE_DIR}/openexr/IlmBase/Half
${CMAKE_CURRENT_SOURCE_DIR}/openexr/OpenEXR/IlmImf
${CMAKE_CURRENT_BINARY_DIR}/openexr/OpenEXR/config
${CMAKE_CURRENT_BINARY_DIR}/openexr/IlmBase/config)
set(PCG32_INCLUDE_DIR
${CMAKE_CURRENT_SOURCE_DIR}/pcg32)
set(TFM_INCLUDE_DIR
${CMAKE_CURRENT_SOURCE_DIR}/tinyformat)
set(HYPOTHESIS_INCLUDE_DIR
${CMAKE_CURRENT_SOURCE_DIR}/hypothesis)
set(GLFW_INCLUDE_DIR
${CMAKE_CURRENT_SOURCE_DIR}/nanogui/ext/glfw/include)
set(GLEW_INCLUDE_DIR
${CMAKE_CURRENT_SOURCE_DIR}/nanogui/ext/glew/include)
set(NANOVG_INCLUDE_DIR
${CMAKE_CURRENT_SOURCE_DIR}/nanogui/ext/nanovg/src)
set(STB_IMAGE_WRITE_INCLUDE_DIR
${CMAKE_CURRENT_SOURCE_DIR}/nanogui/ext/nanovg/example)
set(NANOGUI_INCLUDE_DIR
${CMAKE_CURRENT_SOURCE_DIR}/nanogui/include)
set(EIGEN_INCLUDE_DIR
${CMAKE_CURRENT_SOURCE_DIR}/eigen)
set(TBB_INCLUDE_DIR
${CMAKE_CURRENT_SOURCE_DIR}/tbb/include)
set(FILESYSTEM_INCLUDE_DIR
${CMAKE_CURRENT_SOURCE_DIR}/filesystem)
set(PUGIXML_INCLUDE_DIR
${CMAKE_CURRENT_SOURCE_DIR}/pugixml/src)
# Compile remainder of the codebase with compiler warnings turned on
if(MSVC)
if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]")
string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
endif()
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
# Re-enable disabled warnings
# if(CMAKE_CXX_FLAGS MATCHES "-Wno-[^ ]+")
# string(REGEX REPLACE "-Wno-[^ ]+" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
# endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter")
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-gnu-anonymous-struct -Wno-c99-extensions -Wno-nested-anon-types -Wno-deprecated-register")
endif()
endif()
set(CompilerFlags
CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO CMAKE_C_FLAGS
CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE CMAKE_C_FLAGS_MINSIZEREL
CMAKE_C_FLAGS_RELWITHDEBINFO COMPILE_DEFINITIONS U_CMAKE_BUILD_TYPE
CMAKE_MACOSX_RPATH
OPENEXR_INCLUDE_DIRS PCG32_INCLUDE_DIR TFM_INCLUDE_DIR
HYPOTHESIS_INCLUDE_DIR GLFW_INCLUDE_DIR GLEW_INCLUDE_DIR
NANOVG_INCLUDE_DIR NANOGUI_EXTRA_INCS NANOGUI_EXTRA_DEFS
NANOGUI_EXTRA_LIBS NANOGUI_INCLUDE_DIR EIGEN_INCLUDE_DIR
STB_IMAGE_WRITE_INCLUDE_DIR TBB_INCLUDE_DIR
FILESYSTEM_INCLUDE_DIR PUGIXML_INCLUDE_DIR
)
foreach(CompilerFlag ${CompilerFlags})
set(${CompilerFlag} "${${CompilerFlag}}" PARENT_SCOPE)
endforeach()

15
ext/README.md Normal file
View File

@ -0,0 +1,15 @@
### Overview of dependency libraries used by Nori
Nori requires several utility libraries to function correctly; a full list with
explanations is given below. You should feel free to use any of their
functionality in your own submissions—however, you are not required to do so.
* `filesystem`: tiny self-contained library for manipulating file paths
* `hypothesis`: utility functions for statistical hypothesis tests
* `nanogui`: minimalistic GUI library for OpenGL
* `openexr`: High dynamic range image format library
* `pcg32`: tiny self-contained pseudorandom number generator
* `pugixml`: light-weight XML processing library
* `tbb`: Intel's Boost Thread Building Blocks for multithreading
* `tinyformat`: type-safe C++11 version of `sprintf` and friends
* `zlib`: data compression library, used by `openexr`

1
ext/eigen Submodule

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

1
ext/filesystem Submodule

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

1
ext/hypothesis Submodule

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

1
ext/nanogui Submodule

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

1
ext/openexr Submodule

@ -0,0 +1 @@
Subproject commit 080d443cbd8d9672330c1fa2a84dcc9a5c220e25

1
ext/pcg32 Submodule

@ -0,0 +1 @@
Subproject commit 70099eadb86d3999c38cf69d2c55f8adc1f7fe34

183
ext/plugin/io_nori.py Normal file
View File

@ -0,0 +1,183 @@
import math
import os
import shutil
from xml.dom.minidom import Document
import bpy
import bpy_extras
from bpy.props import BoolProperty, IntProperty, StringProperty
from bpy_extras.io_utils import ExportHelper
from mathutils import Matrix
bl_info = {
"name": "Export Nori scenes format",
"author": "Adrien Gruson, Delio Vicini, Tizian Zeltner",
"version": (0, 1),
"blender": (2, 80, 0),
"location": "File > Export > Nori exporter (.xml)",
"description": "Export Nori scene format (.xml)",
"warning": "",
"wiki_url": "",
"tracker_url": "",
"category": "Import-Export"}
class NoriWriter:
def __init__(self, context, filepath):
self.context = context
self.filepath = filepath
self.working_dir = os.path.dirname(self.filepath)
def create_xml_element(self, name, attr):
el = self.doc.createElement(name)
for k, v in attr.items():
el.setAttribute(k, v)
return el
def create_xml_entry(self, t, name, value):
return self.create_xml_element(t, {"name": name, "value": value})
def create_xml_transform(self, mat, el=None):
transform = self.create_xml_element("transform", {"name": "toWorld"})
if(el):
transform.appendChild(el)
value = ""
for j in range(4):
for i in range(4):
value += str(mat[j][i]) + ","
transform.appendChild(self.create_xml_element("matrix", {"value": value[:-1]}))
return transform
def create_xml_mesh_entry(self, filename):
meshElement = self.create_xml_element("mesh", {"type": "obj"})
meshElement.appendChild(self.create_xml_element("string", {"name": "filename", "value": "meshes/"+filename}))
return meshElement
def write(self):
"""Main method to write the blender scene into Nori format"""
n_samples = 32
# create xml document
self.doc = Document()
self.scene = self.doc.createElement("scene")
self.doc.appendChild(self.scene)
# 1) write integrator configuration
self.scene.appendChild(self.create_xml_element("integrator", {"type": "normals"}))
# 2) write sampler
sampler = self.create_xml_element("sampler", {"type": "independent"})
sampler.appendChild(self.create_xml_element("integer", {"name": "sampleCount", "value": str(n_samples)}))
self.scene.appendChild(sampler)
# 3) export one camera
cameras = [cam for cam in self.context.scene.objects
if cam.type in {'CAMERA'}]
if(len(cameras) == 0):
print("WARN: No camera to export")
else:
if(len(cameras) > 1):
print("WARN: Does not handle multiple camera, only export the active one")
self.scene.appendChild(self.write_camera(self.context.scene.camera)) # export the active one
# 4) export all meshes
if not os.path.exists(self.working_dir + "/meshes"):
os.makedirs(self.working_dir + "/meshes")
meshes = [obj for obj in self.context.scene.objects
if obj.type in {'MESH', 'FONT', 'SURFACE', 'META'}]
print(meshes)
for mesh in meshes:
self.write_mesh(mesh)
# 6) write the xml file
self.doc.writexml(open(self.filepath, "w"), "", "\t", "\n")
def write_camera(self, cam):
"""convert the selected camera (cam) into xml format"""
camera = self.create_xml_element("camera", {"type": "perspective"})
camera.appendChild(self.create_xml_entry("float", "fov", str(cam.data.angle * 180 / math.pi)))
camera.appendChild(self.create_xml_entry("float", "nearClip", str(cam.data.clip_start)))
camera.appendChild(self.create_xml_entry("float", "farClip", str(cam.data.clip_end)))
percent = self.context.scene.render.resolution_percentage / 100.0
camera.appendChild(self.create_xml_entry("integer", "width", str(
int(self.context.scene.render.resolution_x * percent))))
camera.appendChild(self.create_xml_entry("integer", "height", str(
int(self.context.scene.render.resolution_y * percent))))
mat = cam.matrix_world
# Conversion to Y-up coordinate system
coord_transf = bpy_extras.io_utils.axis_conversion(
from_forward='Y', from_up='Z', to_forward='-Z', to_up='Y').to_4x4()
mat = coord_transf @ mat
pos = mat.translation
# Nori's camera needs this these coordinates to be flipped
m = Matrix([[-1, 0, 0, 0], [0, 1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 0]])
t = mat.to_3x3() @ m.to_3x3()
mat = Matrix([[t[0][0], t[0][1], t[0][2], pos[0]],
[t[1][0], t[1][1], t[1][2], pos[1]],
[t[2][0], t[2][1], t[2][2], pos[2]],
[0, 0, 0, 1]])
trans = self.create_xml_transform(mat)
camera.appendChild(trans)
return camera
def write_mesh(self, mesh):
viewport_selection = self.context.selected_objects
bpy.ops.object.select_all(action='DESELECT')
obj_name = mesh.name + ".obj"
obj_path = os.path.join(self.working_dir, 'meshes', obj_name)
mesh.select_set(True)
bpy.ops.export_scene.obj(filepath=obj_path, check_existing=False,
use_selection=True, use_edges=False, use_smooth_groups=False,
use_materials=False, use_triangles=True, use_mesh_modifiers=True)
mesh.select_set(False)
# Add the corresponding entry to the xml
mesh_element = self.create_xml_mesh_entry(obj_name)
# We currently just export a default material, a more complex material conversion
# could be implemented following: http://tinyurl.com/nnhxwuh
bsdf_element = self.create_xml_element("bsdf", {"type": "diffuse"})
bsdf_element.appendChild(self.create_xml_entry("color", "albedo", "0.75,0.75,0.75"))
mesh_element.appendChild(bsdf_element)
self.scene.appendChild(mesh_element)
for ob in viewport_selection:
ob.select_set(True)
class NoriExporter(bpy.types.Operator, ExportHelper):
"""Export a blender scene into Nori scene format"""
# add to menu
bl_idname = "export_scene.nori"
bl_label = "Export Nori scene"
filename_ext = ".xml"
filter_glob: StringProperty(default="*.xml", options={'HIDDEN'})
def execute(self, context):
nori = NoriWriter(context, self.filepath)
nori.write()
return {'FINISHED'}
def menu_func_export(self, context):
self.layout.operator(NoriExporter.bl_idname, text="Export Nori scene...")
def register():
bpy.utils.register_class(NoriExporter)
bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
def unregister():
bpy.utils.unregister_class(NoriExporter)
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
if __name__ == "__main__":
register()

22
ext/plugin/readme.md Normal file
View File

@ -0,0 +1,22 @@
# Nori Exporter for Blender
by Delio Vicini and Tizian Zeltner, based on Adrien Gruson's original exporter.
## Installation
First, you must download a fairly recent version of Blender (the plugin is tested for versions >= 2.8). You can download blender from https://www.blender.org or using your system's package manager.
To install the plugin, open Blender and go to "Edit -> Preferences... -> Add-ons" and click on "Install...".
This should open a file browser in which you can navigate to the `io_nori.py` file and select it.
This will copy the exporter script to Blender's plugin directory.
After the plugin is installed, it has to be activated by clicking the checkbox next to it in the Add-ons menu.
## Usage
Once the plugin is installed, scenes can be expored by clicking "File -> Export -> Export Nori Scene..."
The plugin exports all objects in the scene as separate OBJ FIles. It then generates a Nori XML file with the scene's camera, a basic integrator and XML entries to reference all the exported meshes.
## Limitations
The plugin does not support exporting BSDFs and emitters. It will just assign a default BSDF to all shapes. It further exports each mesh as is.
This means that if you have a mesh with multiple materials in Blender, you will have to split it manually into separate submeshes before exporting (one for each material). This can be done by selecting the mesh with multiple materials, going to edit mode (tab) then selecting all vertices (A) and clicking "separate" (P) and then "by material". This will separate the mesh into meshes with one material each. After exporting, each of these meshes will have a separate entry in the scene's XML file and can therefore be assigned a different BSDF.

1
ext/pugixml Submodule

@ -0,0 +1 @@
Subproject commit 35a63cb1e63d3487cf68f73e963b6f1e58cf45da

1
ext/tbb Submodule

@ -0,0 +1 @@
Subproject commit 141b0e310e1fb552bdca887542c9c1a8544d6503

1
ext/tinyformat Submodule

@ -0,0 +1 @@
Subproject commit 43816c694297a27909da0982f91315fb33d60676

1
ext/zlib Submodule

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

73
include/nori/accel.h Normal file
View File

@ -0,0 +1,73 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <nori/mesh.h>
NORI_NAMESPACE_BEGIN
/**
* \brief Acceleration data structure for ray intersection queries
*
* The current implementation falls back to a brute force loop
* through the geometry.
*/
class Accel {
public:
/**
* \brief Register a triangle mesh for inclusion in the acceleration
* data structure
*
* This function can only be used before \ref build() is called
*/
void addMesh(Mesh *mesh);
/// Build the acceleration data structure (currently a no-op)
void build();
/// Return an axis-aligned box that bounds the scene
const BoundingBox3f &getBoundingBox() const { return m_bbox; }
/**
* \brief Intersect a ray against all triangles stored in the scene and
* return detailed intersection information
*
* \param ray
* A 3-dimensional ray data structure with minimum/maximum extent
* information
*
* \param its
* A detailed intersection record, which will be filled by the
* intersection query
*
* \param shadowRay
* \c true if this is a shadow ray query, i.e. a query that only aims to
* find out whether the ray is blocked or not without returning detailed
* intersection information.
*
* \return \c true if an intersection was found
*/
bool rayIntersect(const Ray3f &ray, Intersection &its, bool shadowRay) const;
private:
Mesh *m_mesh = nullptr; ///< Mesh (only a single one for now)
BoundingBox3f m_bbox; ///< Bounding box of the entire scene
};
NORI_NAMESPACE_END

399
include/nori/bbox.h Normal file
View File

@ -0,0 +1,399 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <nori/ray.h>
NORI_NAMESPACE_BEGIN
/**
* \brief Generic n-dimensional bounding box data structure
*
* Maintains a minimum and maximum position along each dimension and provides
* various convenience functions for querying and modifying them.
*
* This class is parameterized by the underlying point data structure,
* which permits the use of different scalar types and dimensionalities, e.g.
* \code
* TBoundingBox<Vector3i> integerBBox(Point3i(0, 1, 3), Point3i(4, 5, 6));
* TBoundingBox<Vector2d> doubleBBox(Point2d(0.0, 1.0), Point2d(4.0, 5.0));
* \endcode
*
* \tparam T The underlying point data type (e.g. \c Point2d)
* \ingroup libcore
*/
template <typename _PointType> struct TBoundingBox {
enum {
Dimension = _PointType::Dimension
};
typedef _PointType PointType;
typedef typename PointType::Scalar Scalar;
typedef typename PointType::VectorType VectorType;
/**
* \brief Create a new invalid bounding box
*
* Initializes the components of the minimum
* and maximum position to \f$\infty\f$ and \f$-\infty\f$,
* respectively.
*/
TBoundingBox() {
reset();
}
/// Create a collapsed bounding box from a single point
TBoundingBox(const PointType &p)
: min(p), max(p) { }
/// Create a bounding box from two positions
TBoundingBox(const PointType &min, const PointType &max)
: min(min), max(max) {
}
/// Test for equality against another bounding box
bool operator==(const TBoundingBox &bbox) const {
return min == bbox.min && max == bbox.max;
}
/// Test for inequality against another bounding box
bool operator!=(const TBoundingBox &bbox) const {
return min != bbox.min || max != bbox.max;
}
/// Calculate the n-dimensional volume of the bounding box
Scalar getVolume() const {
return (max - min).prod();
}
/// Calculate the n-1 dimensional volume of the boundary
float getSurfaceArea() const {
VectorType d = max - min;
float result = 0.0f;
for (int i=0; i<Dimension; ++i) {
float term = 1.0f;
for (int j=0; j<Dimension; ++j) {
if (i == j)
continue;
term *= d[j];
}
result += term;
}
return 2.0f * result;
}
/// Return the center point
PointType getCenter() const {
return (max + min) * (Scalar) 0.5f;
}
/**
* \brief Check whether a point lies \a on or \a inside the bounding box
*
* \param p The point to be tested
*
* \param strict Set this parameter to \c true if the bounding
* box boundary should be excluded in the test
*/
bool contains(const PointType &p, bool strict = false) const {
if (strict) {
return (p.array() > min.array()).all()
&& (p.array() < max.array()).all();
} else {
return (p.array() >= min.array()).all()
&& (p.array() <= max.array()).all();
}
}
/**
* \brief Check whether a specified bounding box lies \a on or \a within
* the current bounding box
*
* Note that by definition, an 'invalid' bounding box (where min=\f$\infty\f$
* and max=\f$-\infty\f$) does not cover any space. Hence, this method will always
* return \a true when given such an argument.
*
* \param strict Set this parameter to \c true if the bounding
* box boundary should be excluded in the test
*/
bool contains(const TBoundingBox &bbox, bool strict = false) const {
if (strict) {
return (bbox.min.array() > min.array()).all()
&& (bbox.max.array() < max.array()).all();
} else {
return (bbox.min.array() >= min.array()).all()
&& (bbox.max.array() <= max.array()).all();
}
}
/**
* \brief Check two axis-aligned bounding boxes for possible overlap.
*
* \param strict Set this parameter to \c true if the bounding
* box boundary should be excluded in the test
*
* \return \c true If overlap was detected.
*/
bool overlaps(const TBoundingBox &bbox, bool strict = false) const {
if (strict) {
return (bbox.min.array() < max.array()).all()
&& (bbox.max.array() > min.array()).all();
} else {
return (bbox.min.array() <= max.array()).all()
&& (bbox.max.array() >= min.array()).all();
}
}
/**
* \brief Calculate the smallest squared distance between
* the axis-aligned bounding box and the point \c p.
*/
Scalar squaredDistanceTo(const PointType &p) const {
Scalar result = 0;
for (int i=0; i<Dimension; ++i) {
Scalar value = 0;
if (p[i] < min[i])
value = min[i] - p[i];
else if (p[i] > max[i])
value = p[i] - max[i];
result += value*value;
}
return result;
}
/**
* \brief Calculate the smallest distance between
* the axis-aligned bounding box and the point \c p.
*/
Scalar distanceTo(const PointType &p) const {
return std::sqrt(squaredDistanceTo(p));
}
/**
* \brief Calculate the smallest square distance between
* the axis-aligned bounding box and \c bbox.
*/
Scalar squaredDistanceTo(const TBoundingBox &bbox) const {
Scalar result = 0;
for (int i=0; i<Dimension; ++i) {
Scalar value = 0;
if (bbox.max[i] < min[i])
value = min[i] - bbox.max[i];
else if (bbox.min[i] > max[i])
value = bbox.min[i] - max[i];
result += value*value;
}
return result;
}
/**
* \brief Calculate the smallest distance between
* the axis-aligned bounding box and \c bbox.
*/
Scalar distanceTo(const TBoundingBox &bbox) const {
return std::sqrt(squaredDistanceTo(bbox));
}
/**
* \brief Check whether this is a valid bounding box
*
* A bounding box \c bbox is valid when
* \code
* bbox.min[dim] <= bbox.max[dim]
* \endcode
* holds along each dimension \c dim.
*/
bool isValid() const {
return (max.array() >= min.array()).all();
}
/// Check whether this bounding box has collapsed to a single point
bool isPoint() const {
return (max.array() == min.array()).all();
}
/// Check whether this bounding box has any associated volume
bool hasVolume() const {
return (max.array() > min.array()).all();
}
/// Return the dimension index with the largest associated side length
int getMajorAxis() const {
VectorType d = max - min;
int largest = 0;
for (int i=1; i<Dimension; ++i)
if (d[i] > d[largest])
largest = i;
return largest;
}
/// Return the dimension index with the shortest associated side length
int getMinorAxis() const {
VectorType d = max - min;
int shortest = 0;
for (int i=1; i<Dimension; ++i)
if (d[i] < d[shortest])
shortest = i;
return shortest;
}
/**
* \brief Calculate the bounding box extents
* \return max-min
*/
VectorType getExtents() const {
return max - min;
}
/// Clip to another bounding box
void clip(const TBoundingBox &bbox) {
min = min.cwiseMax(bbox.min);
max = max.cwiseMin(bbox.max);
}
/**
* \brief Mark the bounding box as invalid.
*
* This operation sets the components of the minimum
* and maximum position to \f$\infty\f$ and \f$-\infty\f$,
* respectively.
*/
void reset() {
min.setConstant( std::numeric_limits<Scalar>::infinity());
max.setConstant(-std::numeric_limits<Scalar>::infinity());
}
/// Expand the bounding box to contain another point
void expandBy(const PointType &p) {
min = min.cwiseMin(p);
max = max.cwiseMax(p);
}
/// Expand the bounding box to contain another bounding box
void expandBy(const TBoundingBox &bbox) {
min = min.cwiseMin(bbox.min);
max = max.cwiseMax(bbox.max);
}
/// Merge two bounding boxes
static TBoundingBox merge(const TBoundingBox &bbox1, const TBoundingBox &bbox2) {
return TBoundingBox(
bbox1.min.cwiseMin(bbox2.min),
bbox1.max.cwiseMax(bbox2.max)
);
}
/// Return the index of the largest axis
int getLargestAxis() const {
VectorType extents = max-min;
if (extents[0] >= extents[1] && extents[0] >= extents[2])
return 0;
else if (extents[1] >= extents[0] && extents[1] >= extents[2])
return 1;
else
return 2;
}
/// Return the position of a bounding box corner
PointType getCorner(int index) const {
PointType result;
for (int i=0; i<Dimension; ++i)
result[i] = (index & (1 << i)) ? max[i] : min[i];
return result;
}
/// Return a string representation of the bounding box
std::string toString() const {
if (!isValid())
return "BoundingBox[invalid]";
else
return tfm::format("BoundingBox[min=%s, max=%s]", min.toString(), max.toString());
}
/// Check if a ray intersects a bounding box
bool rayIntersect(const Ray3f &ray) const {
float nearT = -std::numeric_limits<float>::infinity();
float farT = std::numeric_limits<float>::infinity();
for (int i=0; i<3; i++) {
float origin = ray.o[i];
float minVal = min[i], maxVal = max[i];
if (ray.d[i] == 0) {
if (origin < minVal || origin > maxVal)
return false;
} else {
float t1 = (minVal - origin) * ray.dRcp[i];
float t2 = (maxVal - origin) * ray.dRcp[i];
if (t1 > t2)
std::swap(t1, t2);
nearT = std::max(t1, nearT);
farT = std::min(t2, farT);
if (!(nearT <= farT))
return false;
}
}
return ray.mint <= farT && nearT <= ray.maxt;
}
/// Return the overlapping region of the bounding box and an unbounded ray
bool rayIntersect(const Ray3f &ray, float &nearT, float &farT) const {
nearT = -std::numeric_limits<float>::infinity();
farT = std::numeric_limits<float>::infinity();
for (int i=0; i<3; i++) {
float origin = ray.o[i];
float minVal = min[i], maxVal = max[i];
if (ray.d[i] == 0) {
if (origin < minVal || origin > maxVal)
return false;
} else {
float t1 = (minVal - origin) * ray.dRcp[i];
float t2 = (maxVal - origin) * ray.dRcp[i];
if (t1 > t2)
std::swap(t1, t2);
nearT = std::max(t1, nearT);
farT = std::min(t2, farT);
if (!(nearT <= farT))
return false;
}
}
return true;
}
PointType min; ///< Component-wise minimum
PointType max; ///< Component-wise maximum
};
NORI_NAMESPACE_END

54
include/nori/bitmap.h Normal file
View File

@ -0,0 +1,54 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <nori/color.h>
#include <nori/vector.h>
NORI_NAMESPACE_BEGIN
/**
* \brief Stores a RGB high dynamic-range bitmap
*
* The bitmap class provides I/O support using the OpenEXR file format
*/
class Bitmap : public Eigen::Array<Color3f, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> {
public:
typedef Eigen::Array<Color3f, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> Base;
/**
* \brief Allocate a new bitmap of the specified size
*
* The contents will initially be undefined, so make sure
* to call \ref clear() if necessary
*/
Bitmap(const Vector2i &size = Vector2i(0, 0))
: Base(size.y(), size.x()) { }
/// Load an OpenEXR file with the specified filename
Bitmap(const std::string &filename);
/// Save the bitmap as an EXR file with the specified filename
void saveEXR(const std::string &filename);
/// Save the bitmap as a PNG file (with sRGB tonemapping) with the specified filename
void savePNG(const std::string &filename);
};
NORI_NAMESPACE_END

166
include/nori/block.h Normal file
View File

@ -0,0 +1,166 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* =======================================================================
This file contains classes for parallel rendering of "image blocks".
* ======================================================================= */
#pragma once
#include <nori/color.h>
#include <nori/vector.h>
#include <tbb/mutex.h>
#define NORI_BLOCK_SIZE 32 /* Block size used for parallelization */
NORI_NAMESPACE_BEGIN
/**
* \brief Weighted pixel storage for a rectangular subregion of an image
*
* This class implements storage for a rectangular subregion of a
* larger image that is being rendered. For each pixel, it records color
* values along with a weight that specifies the accumulated influence of
* nearby samples on the pixel (according to the used reconstruction filter).
*
* When rendering with filters, the samples in a rectangular
* region will generally also contribute to pixels just outside of
* this region. For that reason, this class also stores information about
* a small border region around the rectangle, whose size depends on the
* properties of the reconstruction filter.
*/
class ImageBlock : public Eigen::Array<Color4f, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> {
public:
/**
* Create a new image block of the specified maximum size
* \param size
* Desired maximum size of the block
* \param filter
* Samples will be convolved with the image reconstruction
* filter provided here.
*/
ImageBlock(const Vector2i &size, const ReconstructionFilter *filter);
/// Release all memory
~ImageBlock();
/// Configure the offset of the block within the main image
void setOffset(const Point2i &offset) { m_offset = offset; }
/// Return the offset of the block within the main image
inline const Point2i &getOffset() const { return m_offset; }
/// Configure the size of the block within the main image
void setSize(const Point2i &size) { m_size = size; }
/// Return the size of the block within the main image
inline const Vector2i &getSize() const { return m_size; }
/// Return the border size in pixels
inline int getBorderSize() const { return m_borderSize; }
/**
* \brief Turn the block into a proper bitmap
*
* This entails normalizing all pixels and discarding
* the border region.
*/
Bitmap *toBitmap() const;
/// Convert a bitmap into an image block
void fromBitmap(const Bitmap &bitmap);
/// Clear all contents
void clear() { setConstant(Color4f()); }
/// Record a sample with the given position and radiance value
void put(const Point2f &pos, const Color3f &value);
/**
* \brief Merge another image block into this one
*
* During the merge operation, this function locks
* the destination block using a mutex.
*/
void put(ImageBlock &b);
/// Lock the image block (using an internal mutex)
inline void lock() const { m_mutex.lock(); }
/// Unlock the image block
inline void unlock() const { m_mutex.unlock(); }
/// Return a human-readable string summary
std::string toString() const;
protected:
Point2i m_offset;
Vector2i m_size;
int m_borderSize = 0;
float *m_filter = nullptr;
float m_filterRadius = 0;
float *m_weightsX = nullptr;
float *m_weightsY = nullptr;
float m_lookupFactor = 0;
mutable tbb::mutex m_mutex;
};
/**
* \brief Spiraling block generator
*
* This class can be used to chop up an image into many small
* rectangular blocks suitable for parallel rendering. The blocks
* are ordered in spiraling pattern so that the center is
* rendered first.
*/
class BlockGenerator {
public:
/**
* \brief Create a block generator with
* \param size
* Size of the image that should be split into blocks
* \param blockSize
* Maximum size of the individual blocks
*/
BlockGenerator(const Vector2i &size, int blockSize);
/**
* \brief Return the next block to be rendered
*
* This function is thread-safe
*
* \return \c false if there were no more blocks
*/
bool next(ImageBlock &block);
/// Return the total number of blocks
int getBlockCount() const { return m_blocksLeft; }
protected:
enum EDirection { ERight = 0, EDown, ELeft, EUp };
Point2i m_block;
Vector2i m_numBlocks;
Vector2i m_size;
int m_blockSize;
int m_numSteps;
int m_blocksLeft;
int m_stepsLeft;
int m_direction;
tbb::mutex m_mutex;
};
NORI_NAMESPACE_END

115
include/nori/bsdf.h Normal file
View File

@ -0,0 +1,115 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <nori/object.h>
NORI_NAMESPACE_BEGIN
/**
* \brief Convenience data structure used to pass multiple
* parameters to the evaluation and sampling routines in \ref BSDF
*/
struct BSDFQueryRecord {
/// Incident direction (in the local frame)
Vector3f wi;
/// Outgoing direction (in the local frame)
Vector3f wo;
/// Relative refractive index in the sampled direction
float eta;
/// Measure associated with the sample
EMeasure measure;
/// Create a new record for sampling the BSDF
BSDFQueryRecord(const Vector3f &wi)
: wi(wi), eta(1.f), measure(EUnknownMeasure) { }
/// Create a new record for querying the BSDF
BSDFQueryRecord(const Vector3f &wi,
const Vector3f &wo, EMeasure measure)
: wi(wi), wo(wo), eta(1.f), measure(measure) { }
};
/**
* \brief Superclass of all bidirectional scattering distribution functions
*/
class BSDF : public NoriObject {
public:
/**
* \brief Sample the BSDF and return the importance weight (i.e. the
* value of the BSDF * cos(theta_o) divided by the probability density
* of the sample with respect to solid angles).
*
* \param bRec A BSDF query record
* \param sample A uniformly distributed sample on \f$[0,1]^2\f$
*
* \return The BSDF value divided by the probability density of the sample
* sample. The returned value also includes the cosine
* foreshortening factor associated with the outgoing direction,
* when this is appropriate. A zero value means that sampling
* failed.
*/
virtual Color3f sample(BSDFQueryRecord &bRec, const Point2f &sample) const = 0;
/**
* \brief Evaluate the BSDF for a pair of directions and measure
* specified in \code bRec
*
* \param bRec
* A record with detailed information on the BSDF query
* \return
* The BSDF value, evaluated for each color channel
*/
virtual Color3f eval(const BSDFQueryRecord &bRec) const = 0;
/**
* \brief Compute the probability of sampling \c bRec.wo
* (conditioned on \c bRec.wi).
*
* This method provides access to the probability density that
* is realized by the \ref sample() method.
*
* \param bRec
* A record with detailed information on the BSDF query
*
* \return
* A probability/density value expressed with respect
* to the specified measure
*/
virtual float pdf(const BSDFQueryRecord &bRec) const = 0;
/**
* \brief Return the type of object (i.e. Mesh/BSDF/etc.)
* provided by this instance
* */
EClassType getClassType() const { return EBSDF; }
/**
* \brief Return whether or not this BRDF is diffuse. This
* is primarily used by photon mapping to decide whether
* or not to store photons on a surface
*/
virtual bool isDiffuse() const { return false; }
};
NORI_NAMESPACE_END

76
include/nori/camera.h Normal file
View File

@ -0,0 +1,76 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <nori/object.h>
NORI_NAMESPACE_BEGIN
/**
* \brief Generic camera interface
*
* This class provides an abstract interface to cameras in Nori and
* exposes the ability to sample their response function. By default, only
* a perspective camera implementation exists, but you may choose to
* implement other types (e.g. an environment camera, or a physically-based
* camera model that simulates the behavior actual lenses)
*/
class Camera : public NoriObject {
public:
/**
* \brief Importance sample a ray according to the camera's response function
*
* \param ray
* A ray data structure to be filled with a position
* and direction value
*
* \param samplePosition
* Denotes the desired sample position on the film
* expressed in fractional pixel coordinates
*
* \param apertureSample
* A uniformly distributed 2D vector that is used to sample
* a position on the aperture of the sensor if necessary.
*
* \return
* An importance weight associated with the sampled ray.
* This accounts for the difference in the camera response
* function and the sampling density.
*/
virtual Color3f sampleRay(Ray3f &ray,
const Point2f &samplePosition,
const Point2f &apertureSample) const = 0;
/// Return the size of the output image in pixels
const Vector2i &getOutputSize() const { return m_outputSize; }
/// Return the camera's reconstruction filter in image space
const ReconstructionFilter *getReconstructionFilter() const { return m_rfilter; }
/**
* \brief Return the type of object (i.e. Mesh/Camera/etc.)
* provided by this instance
* */
EClassType getClassType() const { return ECamera; }
protected:
Vector2i m_outputSize;
ReconstructionFilter *m_rfilter;
};
NORI_NAMESPACE_END

125
include/nori/color.h Normal file
View File

@ -0,0 +1,125 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <nori/common.h>
NORI_NAMESPACE_BEGIN
/**
* \brief Represents a linear RGB color value
*/
struct Color3f : public Eigen::Array3f {
public:
typedef Eigen::Array3f Base;
/// Initialize the color vector with a uniform value
Color3f(float value = 0.f) : Base(value, value, value) { }
/// Initialize the color vector with specific per-channel values
Color3f(float r, float g, float b) : Base(r, g, b) { }
/// Construct a color vector from ArrayBase (needed to play nice with Eigen)
template <typename Derived> Color3f(const Eigen::ArrayBase<Derived>& p)
: Base(p) { }
/// Assign a color vector from ArrayBase (needed to play nice with Eigen)
template <typename Derived> Color3f &operator=(const Eigen::ArrayBase<Derived>& p) {
this->Base::operator=(p);
return *this;
}
/// Return a reference to the red channel
float &r() { return x(); }
/// Return a reference to the red channel (const version)
const float &r() const { return x(); }
/// Return a reference to the green channel
float &g() { return y(); }
/// Return a reference to the green channel (const version)
const float &g() const { return y(); }
/// Return a reference to the blue channel
float &b() { return z(); }
/// Return a reference to the blue channel (const version)
const float &b() const { return z(); }
/// Clamp to the positive range
Color3f clamp() const { return Color3f(std::max(r(), 0.0f),
std::max(g(), 0.0f), std::max(b(), 0.0f)); }
/// Check if the color vector contains a NaN/Inf/negative value
bool isValid() const;
/// Convert from sRGB to linear RGB
Color3f toLinearRGB() const;
/// Convert from linear RGB to sRGB
Color3f toSRGB() const;
/// Return the associated luminance
float getLuminance() const;
/// Return a human-readable string summary
std::string toString() const {
return tfm::format("[%f, %f, %f]", coeff(0), coeff(1), coeff(2));
}
};
/**
* \brief Represents a linear RGB color and a weight
*
* This is used by Nori's image reconstruction filter code
*/
struct Color4f : public Eigen::Array4f {
public:
typedef Eigen::Array4f Base;
/// Create an zero value
Color4f() : Base(0.0f, 0.0f, 0.0f, 0.0f) { }
/// Create from a 3-channel color
Color4f(const Color3f &c) : Base(c.r(), c.g(), c.b(), 1.0f) { }
/// Initialize the color vector with specific per-channel values
Color4f(float r, float g, float b, float w) : Base(r, g, b, w) { }
/// Construct a color vector from ArrayBase (needed to play nice with Eigen)
template <typename Derived> Color4f(const Eigen::ArrayBase<Derived>& p)
: Base(p) { }
/// Assign a color vector from ArrayBase (needed to play nice with Eigen)
template <typename Derived> Color4f &operator=(const Eigen::ArrayBase<Derived>& p) {
this->Base::operator=(p);
return *this;
}
/// Divide by the filter weight and convert into a \ref Color3f value
Color3f divideByFilterWeight() const {
if (w() != 0)
return head<3>() / w();
else
return Color3f(0.0f);
}
/// Return a human-readable string summary
std::string toString() const {
return tfm::format("[%f, %f, %f, %f]", coeff(0), coeff(1), coeff(2), coeff(3));
}
};
NORI_NAMESPACE_END

268
include/nori/common.h Normal file
View File

@ -0,0 +1,268 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#if defined(_MSC_VER)
/* Disable some warnings on MSVC++ */
#pragma warning(disable : 4127 4702 4100 4515 4800 4146 4512)
#define WIN32_LEAN_AND_MEAN /* Don't ever include MFC on Windows */
#define NOMINMAX /* Don't override min/max */
#endif
/* Include the basics needed by any Nori file */
#include <iostream>
#include <algorithm>
#include <vector>
#include <Eigen/Core>
#include <stdint.h>
#include <ImathPlatform.h>
#include <tinyformat.h>
/* Convenience definitions */
#define NORI_NAMESPACE_BEGIN namespace nori {
#define NORI_NAMESPACE_END }
#if defined(__NORI_APPLE__NORI_)
#define PLATFORM_MACOS
#elif defined(__NORI_linux__NORI_)
#define PLATFORM_LINUX
#elif defined(WIN32)
#define PLATFORM_WINDOWS
#endif
/* "Ray epsilon": relative error threshold for ray intersection computations */
#define Epsilon 1e-4f
/* A few useful constants */
#undef M_PI
#define M_PI 3.14159265358979323846f
#define INV_PI 0.31830988618379067154f
#define INV_TWOPI 0.15915494309189533577f
#define INV_FOURPI 0.07957747154594766788f
#define SQRT_TWO 1.41421356237309504880f
#define INV_SQRT_TWO 0.70710678118654752440f
/* Forward declarations */
namespace filesystem {
class path;
class resolver;
};
NORI_NAMESPACE_BEGIN
/* Forward declarations */
template <typename Scalar, int Dimension> struct TVector;
template <typename Scalar, int Dimension> struct TPoint;
template <typename Point, typename Vector> struct TRay;
template <typename Point> struct TBoundingBox;
/* Basic Nori data structures (vectors, points, rays, bounding boxes,
kd-trees) are oblivious to the underlying data type and dimension.
The following list of typedefs establishes some convenient aliases
for specific types. */
typedef TVector<float, 1> Vector1f;
typedef TVector<float, 2> Vector2f;
typedef TVector<float, 3> Vector3f;
typedef TVector<float, 4> Vector4f;
typedef TVector<double, 1> Vector1d;
typedef TVector<double, 2> Vector2d;
typedef TVector<double, 3> Vector3d;
typedef TVector<double, 4> Vector4d;
typedef TVector<int, 1> Vector1i;
typedef TVector<int, 2> Vector2i;
typedef TVector<int, 3> Vector3i;
typedef TVector<int, 4> Vector4i;
typedef TPoint<float, 1> Point1f;
typedef TPoint<float, 2> Point2f;
typedef TPoint<float, 3> Point3f;
typedef TPoint<float, 4> Point4f;
typedef TPoint<double, 1> Point1d;
typedef TPoint<double, 2> Point2d;
typedef TPoint<double, 3> Point3d;
typedef TPoint<double, 4> Point4d;
typedef TPoint<int, 1> Point1i;
typedef TPoint<int, 2> Point2i;
typedef TPoint<int, 3> Point3i;
typedef TPoint<int, 4> Point4i;
typedef TBoundingBox<Point1f> BoundingBox1f;
typedef TBoundingBox<Point2f> BoundingBox2f;
typedef TBoundingBox<Point3f> BoundingBox3f;
typedef TBoundingBox<Point4f> BoundingBox4f;
typedef TBoundingBox<Point1d> BoundingBox1d;
typedef TBoundingBox<Point2d> BoundingBox2d;
typedef TBoundingBox<Point3d> BoundingBox3d;
typedef TBoundingBox<Point4d> BoundingBox4d;
typedef TBoundingBox<Point1i> BoundingBox1i;
typedef TBoundingBox<Point2i> BoundingBox2i;
typedef TBoundingBox<Point3i> BoundingBox3i;
typedef TBoundingBox<Point4i> BoundingBox4i;
typedef TRay<Point2f, Vector2f> Ray2f;
typedef TRay<Point3f, Vector3f> Ray3f;
/// Some more forward declarations
class BSDF;
class Bitmap;
class BlockGenerator;
class Camera;
class ImageBlock;
class Integrator;
class KDTree;
class Emitter;
struct EmitterQueryRecord;
class Mesh;
class NoriObject;
class NoriObjectFactory;
class NoriScreen;
class PhaseFunction;
class ReconstructionFilter;
class Sampler;
class Scene;
/// Import cout, cerr, endl for debugging purposes
using std::cout;
using std::cerr;
using std::endl;
typedef Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic> MatrixXf;
typedef Eigen::Matrix<uint32_t, Eigen::Dynamic, Eigen::Dynamic> MatrixXu;
/// Simple exception class, which stores a human-readable error description
class NoriException : public std::runtime_error {
public:
/// Variadic template constructor to support printf-style arguments
template <typename... Args> NoriException(const char *fmt, const Args &... args)
: std::runtime_error(tfm::format(fmt, args...)) { }
};
/// Return the number of cores (real and virtual)
extern int getCoreCount();
/// Indent a string by the specified number of spaces
extern std::string indent(const std::string &string, int amount = 2);
/// Convert a string to lower case
extern std::string toLower(const std::string &value);
/// Convert a string into an boolean value
extern bool toBool(const std::string &str);
/// Convert a string into a signed integer value
extern int toInt(const std::string &str);
/// Convert a string into an unsigned integer value
extern unsigned int toUInt(const std::string &str);
/// Convert a string into a floating point value
extern float toFloat(const std::string &str);
/// Convert a string into a 3D vector
extern Eigen::Vector3f toVector3f(const std::string &str);
/// Tokenize a string into a list by splitting at 'delim'
extern std::vector<std::string> tokenize(const std::string &s, const std::string &delim = ", ", bool includeEmpty = false);
/// Check if a string ends with another string
extern bool endsWith(const std::string &value, const std::string &ending);
/// Convert a time value in milliseconds into a human-readable string
extern std::string timeString(double time, bool precise = false);
/// Convert a memory amount in bytes into a human-readable string
extern std::string memString(size_t size, bool precise = false);
/// Measures associated with probability distributions
enum EMeasure {
EUnknownMeasure = 0,
ESolidAngle,
EDiscrete
};
//// Convert radians to degrees
inline float radToDeg(float value) { return value * (180.0f / M_PI); }
/// Convert degrees to radians
inline float degToRad(float value) { return value * (M_PI / 180.0f); }
#if !defined(_GNU_SOURCE)
/// Emulate sincosf using sinf() and cosf()
inline void sincosf(float theta, float *_sin, float *_cos) {
*_sin = sinf(theta);
*_cos = cosf(theta);
}
#endif
/// Simple floating point clamping function
inline float clamp(float value, float min, float max) {
if (value < min)
return min;
else if (value > max)
return max;
else return value;
}
/// Simple integer clamping function
inline int clamp(int value, int min, int max) {
if (value < min)
return min;
else if (value > max)
return max;
else return value;
}
/// Linearly interpolate between two values
inline float lerp(float t, float v1, float v2) {
return ((float) 1 - t) * v1 + t * v2;
}
/// Always-positive modulo operation
inline int mod(int a, int b) {
int r = a % b;
return (r < 0) ? r+b : r;
}
/// Compute a direction for the given coordinates in spherical coordinates
extern Vector3f sphericalDirection(float theta, float phi);
/// Compute a direction for the given coordinates in spherical coordinates
extern Point2f sphericalCoordinates(const Vector3f &dir);
/**
* \brief Calculates the unpolarized fresnel reflection coefficient for a
* dielectric material. Handles incidence from either side (i.e.
* \code cosThetaI<0 is allowed).
*
* \param cosThetaI
* Cosine of the angle between the normal and the incident ray
* \param extIOR
* Refractive index of the side that contains the surface normal
* \param intIOR
* Refractive index of the interior
*/
extern float fresnel(float cosThetaI, float extIOR, float intIOR);
/**
* \brief Return the global file resolver instance
*
* This class is used to locate resource files (e.g. mesh or
* texture files) referenced by a scene being loaded
*/
extern filesystem::resolver *getFileResolver();
NORI_NAMESPACE_END

198
include/nori/dpdf.h Normal file
View File

@ -0,0 +1,198 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <nori/common.h>
NORI_NAMESPACE_BEGIN
/**
* \brief Discrete probability distribution
*
* This data structure can be used to transform uniformly distributed
* samples to a stored discrete probability distribution.
*
* \ingroup libcore
*/
struct DiscretePDF {
public:
/// Allocate memory for a distribution with the given number of entries
explicit DiscretePDF(size_t nEntries = 0) {
reserve(nEntries);
clear();
}
/// Clear all entries
void clear() {
m_cdf.clear();
m_cdf.push_back(0.0f);
m_normalized = false;
}
/// Reserve memory for a certain number of entries
void reserve(size_t nEntries) {
m_cdf.reserve(nEntries+1);
}
/// Append an entry with the specified discrete probability
void append(float pdfValue) {
m_cdf.push_back(m_cdf[m_cdf.size()-1] + pdfValue);
}
/// Return the number of entries so far
size_t size() const {
return m_cdf.size()-1;
}
/// Access an entry by its index
float operator[](size_t entry) const {
return m_cdf[entry+1] - m_cdf[entry];
}
/// Have the probability densities been normalized?
bool isNormalized() const {
return m_normalized;
}
/**
* \brief Return the original (unnormalized) sum of all PDF entries
*
* This assumes that \ref normalize() has previously been called
*/
float getSum() const {
return m_sum;
}
/**
* \brief Return the normalization factor (i.e. the inverse of \ref getSum())
*
* This assumes that \ref normalize() has previously been called
*/
float getNormalization() const {
return m_normalization;
}
/**
* \brief Normalize the distribution
*
* \return Sum of the (previously unnormalized) entries
*/
float normalize() {
m_sum = m_cdf[m_cdf.size()-1];
if (m_sum > 0) {
m_normalization = 1.0f / m_sum;
for (size_t i=1; i<m_cdf.size(); ++i)
m_cdf[i] *= m_normalization;
m_cdf[m_cdf.size()-1] = 1.0f;
m_normalized = true;
} else {
m_normalization = 0.0f;
}
return m_sum;
}
/**
* \brief %Transform a uniformly distributed sample to the stored distribution
*
* \param[in] sampleValue
* An uniformly distributed sample on [0,1]
* \return
* The discrete index associated with the sample
*/
size_t sample(float sampleValue) const {
std::vector<float>::const_iterator entry =
std::lower_bound(m_cdf.begin(), m_cdf.end(), sampleValue);
size_t index = (size_t) std::max((ptrdiff_t) 0, entry - m_cdf.begin() - 1);
return std::min(index, m_cdf.size()-2);
}
/**
* \brief %Transform a uniformly distributed sample to the stored distribution
*
* \param[in] sampleValue
* An uniformly distributed sample on [0,1]
* \param[out] pdf
* Probability value of the sample
* \return
* The discrete index associated with the sample
*/
size_t sample(float sampleValue, float &pdf) const {
size_t index = sample(sampleValue);
pdf = operator[](index);
return index;
}
/**
* \brief %Transform a uniformly distributed sample to the stored distribution
*
* The original sample is value adjusted so that it can be "reused".
*
* \param[in, out] sampleValue
* An uniformly distributed sample on [0,1]
* \return
* The discrete index associated with the sample
*/
size_t sampleReuse(float &sampleValue) const {
size_t index = sample(sampleValue);
sampleValue = (sampleValue - m_cdf[index])
/ (m_cdf[index + 1] - m_cdf[index]);
return index;
}
/**
* \brief %Transform a uniformly distributed sample.
*
* The original sample is value adjusted so that it can be "reused".
*
* \param[in,out]
* An uniformly distributed sample on [0,1]
* \param[out] pdf
* Probability value of the sample
* \return
* The discrete index associated with the sample
*/
size_t sampleReuse(float &sampleValue, float &pdf) const {
size_t index = sample(sampleValue, pdf);
sampleValue = (sampleValue - m_cdf[index])
/ (m_cdf[index + 1] - m_cdf[index]);
return index;
}
/**
* \brief Turn the underlying distribution into a
* human-readable string format
*/
std::string toString() const {
std::string result = tfm::format("DiscretePDF[sum=%f, "
"normalized=%f, pdf = {", m_sum, m_normalized);
for (size_t i=0; i<m_cdf.size(); ++i) {
result += std::to_string(operator[](i));
if (i != m_cdf.size()-1)
result += ", ";
}
return result + "}]";
}
private:
std::vector<float> m_cdf;
float m_sum, m_normalization;
bool m_normalized;
};
NORI_NAMESPACE_END

38
include/nori/emitter.h Normal file
View File

@ -0,0 +1,38 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <nori/object.h>
NORI_NAMESPACE_BEGIN
/**
* \brief Superclass of all emitters
*/
class Emitter : public NoriObject {
public:
/**
* \brief Return the type of object (i.e. Mesh/Emitter/etc.)
* provided by this instance
* */
EClassType getClassType() const { return EEmitter; }
};
NORI_NAMESPACE_END

147
include/nori/frame.h Normal file
View File

@ -0,0 +1,147 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <nori/vector.h>
NORI_NAMESPACE_BEGIN
/**
* \brief Stores a three-dimensional orthonormal coordinate frame
*
* This class is mostly used to quickly convert between different
* cartesian coordinate systems and to efficiently compute certain
* quantities (e.g. \ref cosTheta(), \ref tanTheta, ..).
*/
struct Frame {
Vector3f s, t;
Normal3f n;
/// Default constructor -- performs no initialization!
Frame() { }
/// Given a normal and tangent vectors, construct a new coordinate frame
Frame(const Vector3f &s, const Vector3f &t, const Normal3f &n)
: s(s), t(t), n(n) { }
/// Construct a frame from the given orthonormal vectors
Frame(const Vector3f &x, const Vector3f &y, const Vector3f &z)
: s(x), t(y), n(z) { }
/// Construct a new coordinate frame from a single vector
Frame(const Vector3f &n) : n(n) {
coordinateSystem(n, s, t);
}
/// Convert from world coordinates to local coordinates
Vector3f toLocal(const Vector3f &v) const {
return Vector3f(
v.dot(s), v.dot(t), v.dot(n)
);
}
/// Convert from local coordinates to world coordinates
Vector3f toWorld(const Vector3f &v) const {
return s * v.x() + t * v.y() + n * v.z();
}
/** \brief Assuming that the given direction is in the local coordinate
* system, return the cosine of the angle between the normal and v */
static float cosTheta(const Vector3f &v) {
return v.z();
}
/** \brief Assuming that the given direction is in the local coordinate
* system, return the sine of the angle between the normal and v */
static float sinTheta(const Vector3f &v) {
float temp = sinTheta2(v);
if (temp <= 0.0f)
return 0.0f;
return std::sqrt(temp);
}
/** \brief Assuming that the given direction is in the local coordinate
* system, return the tangent of the angle between the normal and v */
static float tanTheta(const Vector3f &v) {
float temp = 1 - v.z()*v.z();
if (temp <= 0.0f)
return 0.0f;
return std::sqrt(temp) / v.z();
}
/** \brief Assuming that the given direction is in the local coordinate
* system, return the squared sine of the angle between the normal and v */
static float sinTheta2(const Vector3f &v) {
return 1.0f - v.z() * v.z();
}
/** \brief Assuming that the given direction is in the local coordinate
* system, return the sine of the phi parameter in spherical coordinates */
static float sinPhi(const Vector3f &v) {
float sinTheta = Frame::sinTheta(v);
if (sinTheta == 0.0f)
return 1.0f;
return clamp(v.y() / sinTheta, -1.0f, 1.0f);
}
/** \brief Assuming that the given direction is in the local coordinate
* system, return the cosine of the phi parameter in spherical coordinates */
static float cosPhi(const Vector3f &v) {
float sinTheta = Frame::sinTheta(v);
if (sinTheta == 0.0f)
return 1.0f;
return clamp(v.x() / sinTheta, -1.0f, 1.0f);
}
/** \brief Assuming that the given direction is in the local coordinate
* system, return the squared sine of the phi parameter in spherical
* coordinates */
static float sinPhi2(const Vector3f &v) {
return clamp(v.y() * v.y() / sinTheta2(v), 0.0f, 1.0f);
}
/** \brief Assuming that the given direction is in the local coordinate
* system, return the squared cosine of the phi parameter in spherical
* coordinates */
static float cosPhi2(const Vector3f &v) {
return clamp(v.x() * v.x() / sinTheta2(v), 0.0f, 1.0f);
}
/// Equality test
bool operator==(const Frame &frame) const {
return frame.s == s && frame.t == t && frame.n == n;
}
/// Inequality test
bool operator!=(const Frame &frame) const {
return !operator==(frame);
}
/// Return a human-readable string summary of this frame
std::string toString() const {
return tfm::format(
"Frame[\n"
" s = %s,\n"
" t = %s,\n"
" n = %s\n"
"]", s.toString(), t.toString(), n.toString());
}
};
NORI_NAMESPACE_END

38
include/nori/gui.h Normal file
View File

@ -0,0 +1,38 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <nori/common.h>
#include <nanogui/screen.h>
NORI_NAMESPACE_BEGIN
class NoriScreen : public nanogui::Screen {
public:
NoriScreen(const ImageBlock &block);
void draw_contents() override;
private:
const ImageBlock &m_block;
nanogui::ref<nanogui::Shader> m_shader;
nanogui::ref<nanogui::Texture> m_texture;
nanogui::ref<nanogui::RenderPass> m_renderPass;
float m_scale = 1.f;
};
NORI_NAMESPACE_END

63
include/nori/integrator.h Normal file
View File

@ -0,0 +1,63 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <nori/object.h>
NORI_NAMESPACE_BEGIN
/**
* \brief Abstract integrator (i.e. a rendering technique)
*
* In Nori, the different rendering techniques are collectively referred to as
* integrators, since they perform integration over a high-dimensional
* space. Each integrator represents a specific approach for solving
* the light transport equation---usually favored in certain scenarios, but
* at the same time affected by its own set of intrinsic limitations.
*/
class Integrator : public NoriObject {
public:
/// Release all memory
virtual ~Integrator() { }
/// Perform an (optional) preprocess step
virtual void preprocess(const Scene *scene) { }
/**
* \brief Sample the incident radiance along a ray
*
* \param scene
* A pointer to the underlying scene
* \param sampler
* A pointer to a sample generator
* \param ray
* The ray in question
* \return
* A (usually) unbiased estimate of the radiance in this direction
*/
virtual Color3f Li(const Scene *scene, Sampler *sampler, const Ray3f &ray) const = 0;
/**
* \brief Return the type of object (i.e. Mesh/BSDF/etc.)
* provided by this instance
* */
EClassType getClassType() const { return EIntegrator; }
};
NORI_NAMESPACE_END

181
include/nori/mesh.h Normal file
View File

@ -0,0 +1,181 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <nori/object.h>
#include <nori/frame.h>
#include <nori/bbox.h>
NORI_NAMESPACE_BEGIN
/**
* \brief Intersection data structure
*
* This data structure records local information about a ray-triangle intersection.
* This includes the position, traveled ray distance, uv coordinates, as well
* as well as two local coordinate frames (one that corresponds to the true
* geometry, and one that is used for shading computations).
*/
struct Intersection {
/// Position of the surface intersection
Point3f p;
/// Unoccluded distance along the ray
float t;
/// UV coordinates, if any
Point2f uv;
/// Shading frame (based on the shading normal)
Frame shFrame;
/// Geometric frame (based on the true geometry)
Frame geoFrame;
/// Pointer to the associated mesh
const Mesh *mesh;
/// Create an uninitialized intersection record
Intersection() : mesh(nullptr) { }
/// Transform a direction vector into the local shading frame
Vector3f toLocal(const Vector3f &d) const {
return shFrame.toLocal(d);
}
/// Transform a direction vector from local to world coordinates
Vector3f toWorld(const Vector3f &d) const {
return shFrame.toWorld(d);
}
/// Return a human-readable summary of the intersection record
std::string toString() const;
};
/**
* \brief Triangle mesh
*
* This class stores a triangle mesh object and provides numerous functions
* for querying the individual triangles. Subclasses of \c Mesh implement
* the specifics of how to create its contents (e.g. by loading from an
* external file)
*/
class Mesh : public NoriObject {
public:
/// Release all memory
virtual ~Mesh();
/// Initialize internal data structures (called once by the XML parser)
virtual void activate();
/// Return the total number of triangles in this shape
uint32_t getTriangleCount() const { return (uint32_t) m_F.cols(); }
/// Return the total number of vertices in this shape
uint32_t getVertexCount() const { return (uint32_t) m_V.cols(); }
/// Return the surface area of the given triangle
float surfaceArea(uint32_t index) const;
//// Return an axis-aligned bounding box of the entire mesh
const BoundingBox3f &getBoundingBox() const { return m_bbox; }
//// Return an axis-aligned bounding box containing the given triangle
BoundingBox3f getBoundingBox(uint32_t index) const;
//// Return the centroid of the given triangle
Point3f getCentroid(uint32_t index) const;
/** \brief Ray-triangle intersection test
*
* Uses the algorithm by Moeller and Trumbore discussed at
* <tt>http://www.acm.org/jgt/papers/MollerTrumbore97/code.html</tt>.
*
* Note that the test only applies to a single triangle in the mesh.
* An acceleration data structure like \ref BVH is needed to search
* for intersections against many triangles.
*
* \param index
* Index of the triangle that should be intersected
* \param ray
* The ray segment to be used for the intersection query
* \param t
* Upon success, \a t contains the distance from the ray origin to the
* intersection point,
* \param u
* Upon success, \c u will contain the 'U' component of the intersection
* in barycentric coordinates
* \param v
* Upon success, \c v will contain the 'V' component of the intersection
* in barycentric coordinates
* \return
* \c true if an intersection has been detected
*/
bool rayIntersect(uint32_t index, const Ray3f &ray, float &u, float &v, float &t) const;
/// Return a pointer to the vertex positions
const MatrixXf &getVertexPositions() const { return m_V; }
/// Return a pointer to the vertex normals (or \c nullptr if there are none)
const MatrixXf &getVertexNormals() const { return m_N; }
/// Return a pointer to the texture coordinates (or \c nullptr if there are none)
const MatrixXf &getVertexTexCoords() const { return m_UV; }
/// Return a pointer to the triangle vertex index list
const MatrixXu &getIndices() const { return m_F; }
/// Is this mesh an area emitter?
bool isEmitter() const { return m_emitter != nullptr; }
/// Return a pointer to an attached area emitter instance
Emitter *getEmitter() { return m_emitter; }
/// Return a pointer to an attached area emitter instance (const version)
const Emitter *getEmitter() const { return m_emitter; }
/// Return a pointer to the BSDF associated with this mesh
const BSDF *getBSDF() const { return m_bsdf; }
/// Register a child object (e.g. a BSDF) with the mesh
virtual void addChild(NoriObject *child);
/// Return the name of this mesh
const std::string &getName() const { return m_name; }
/// Return a human-readable summary of this instance
std::string toString() const;
/**
* \brief Return the type of object (i.e. Mesh/BSDF/etc.)
* provided by this instance
* */
EClassType getClassType() const { return EMesh; }
protected:
/// Create an empty mesh
Mesh();
protected:
std::string m_name; ///< Identifying name
MatrixXf m_V; ///< Vertex positions
MatrixXf m_N; ///< Vertex normals
MatrixXf m_UV; ///< Vertex texture coordinates
MatrixXu m_F; ///< Faces
BSDF *m_bsdf = nullptr; ///< BSDF of the surface
Emitter *m_emitter = nullptr; ///< Associated emitter, if any
BoundingBox3f m_bbox; ///< Bounding box of the mesh
};
NORI_NAMESPACE_END

163
include/nori/object.h Normal file
View File

@ -0,0 +1,163 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <nori/proplist.h>
NORI_NAMESPACE_BEGIN
/**
* \brief Base class of all objects
*
* A Nori object represents an instance that is part of
* a scene description, e.g. a scattering model or emitter.
*/
class NoriObject {
public:
enum EClassType {
EScene = 0,
EMesh,
EBSDF,
EPhaseFunction,
EEmitter,
EMedium,
ECamera,
EIntegrator,
ESampler,
ETest,
EReconstructionFilter,
EClassTypeCount
};
/// Virtual destructor
virtual ~NoriObject() { }
/**
* \brief Return the type of object (i.e. Mesh/BSDF/etc.)
* provided by this instance
* */
virtual EClassType getClassType() const = 0;
/**
* \brief Add a child object to the current instance
*
* The default implementation does not support children and
* simply throws an exception
*/
virtual void addChild(NoriObject *child);
/**
* \brief Set the parent object
*
* Subclasses may choose to override this method to be
* notified when they are added to a parent object. The
* default implementation does nothing.
*/
virtual void setParent(NoriObject *parent);
/**
* \brief Perform some action associated with the object
*
* The default implementation throws an exception. Certain objects
* may choose to override it, e.g. to implement initialization,
* testing, or rendering functionality.
*
* This function is called by the XML parser once it has
* constructed an object and added all of its children
* using \ref addChild().
*/
virtual void activate();
/// Return a brief string summary of the instance (for debugging purposes)
virtual std::string toString() const = 0;
/// Turn a class type into a human-readable string
static std::string classTypeName(EClassType type) {
switch (type) {
case EScene: return "scene";
case EMesh: return "mesh";
case EBSDF: return "bsdf";
case EEmitter: return "emitter";
case ECamera: return "camera";
case EIntegrator: return "integrator";
case ESampler: return "sampler";
case ETest: return "test";
default: return "<unknown>";
}
}
};
/**
* \brief Factory for Nori objects
*
* This utility class is part of a mini-RTTI framework and can
* instantiate arbitrary Nori objects by their name.
*/
class NoriObjectFactory {
public:
typedef std::function<NoriObject *(const PropertyList &)> Constructor;
/**
* \brief Register an object constructor with the object factory
*
* This function is called by the macro \ref NORI_REGISTER_CLASS
*
* \param name
* An internal name that is associated with this class. This is the
* 'type' field found in the scene description XML files
*
* \param constr
* A function pointer to an anonymous function that is
* able to call the constructor of the class.
*/
static void registerClass(const std::string &name, const Constructor &constr);
/**
* \brief Construct an instance from the class of the given name
*
* \param name
* An internal name that is associated with this class. This is the
* 'type' field found in the scene description XML files
*
* \param propList
* A list of properties that will be passed to the constructor
* of the class.
*/
static NoriObject *createInstance(const std::string &name,
const PropertyList &propList) {
if (!m_constructors || m_constructors->find(name) == m_constructors->end())
throw NoriException("A constructor for class \"%s\" could not be found!", name);
return (*m_constructors)[name](propList);
}
private:
static std::map<std::string, Constructor> *m_constructors;
};
/// Macro for registering an object constructor with the \ref NoriObjectFactory
#define NORI_REGISTER_CLASS(cls, name) \
cls *cls ##_create(const PropertyList &list) { \
return new cls(list); \
} \
static struct cls ##_{ \
cls ##_() { \
NoriObjectFactory::registerClass(name, cls ##_create); \
} \
} cls ##__NORI_;
NORI_NAMESPACE_END

31
include/nori/parser.h Normal file
View File

@ -0,0 +1,31 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <nori/object.h>
NORI_NAMESPACE_BEGIN
/**
* \brief Load a scene from the specified filename and
* return its root object
*/
extern NoriObject *loadFromXML(const std::string &filename);
NORI_NAMESPACE_END

137
include/nori/proplist.h Normal file
View File

@ -0,0 +1,137 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <nori/color.h>
#include <nori/transform.h>
#include <map>
NORI_NAMESPACE_BEGIN
/**
* \brief This is an associative container used to supply the constructors
* of \ref NoriObject subclasses with parameter information.
*/
class PropertyList {
public:
PropertyList() { }
/// Set a boolean property
void setBoolean(const std::string &name, const bool &value);
/// Get a boolean property, and throw an exception if it does not exist
bool getBoolean(const std::string &name) const;
/// Get a boolean property, and use a default value if it does not exist
bool getBoolean(const std::string &name, const bool &defaultValue) const;
/// Set an integer property
void setInteger(const std::string &name, const int &value);
/// Get an integer property, and throw an exception if it does not exist
int getInteger(const std::string &name) const;
/// Get am integer property, and use a default value if it does not exist
int getInteger(const std::string &name, const int &defaultValue) const;
/// Set a float property
void setFloat(const std::string &name, const float &value);
/// Get a float property, and throw an exception if it does not exist
float getFloat(const std::string &name) const;
/// Get a float property, and use a default value if it does not exist
float getFloat(const std::string &name, const float &defaultValue) const;
/// Set a string property
void setString(const std::string &name, const std::string &value);
/// Get a string property, and throw an exception if it does not exist
std::string getString(const std::string &name) const;
/// Get a string property, and use a default value if it does not exist
std::string getString(const std::string &name, const std::string &defaultValue) const;
/// Set a color property
void setColor(const std::string &name, const Color3f &value);
/// Get a color property, and throw an exception if it does not exist
Color3f getColor(const std::string &name) const;
/// Get a color property, and use a default value if it does not exist
Color3f getColor(const std::string &name, const Color3f &defaultValue) const;
/// Set a point property
void setPoint(const std::string &name, const Point3f &value);
/// Get a point property, and throw an exception if it does not exist
Point3f getPoint(const std::string &name) const;
/// Get a point property, and use a default value if it does not exist
Point3f getPoint(const std::string &name, const Point3f &defaultValue) const;
/// Set a vector property
void setVector(const std::string &name, const Vector3f &value);
/// Get a vector property, and throw an exception if it does not exist
Vector3f getVector(const std::string &name) const;
/// Get a vector property, and use a default value if it does not exist
Vector3f getVector(const std::string &name, const Vector3f &defaultValue) const;
/// Set a transform property
void setTransform(const std::string &name, const Transform &value);
/// Get a transform property, and throw an exception if it does not exist
Transform getTransform(const std::string &name) const;
/// Get a transform property, and use a default value if it does not exist
Transform getTransform(const std::string &name, const Transform &defaultValue) const;
private:
/* Custom variant data type (stores one of boolean/integer/float/...) */
struct Property {
enum {
boolean_type, integer_type, float_type,
string_type, color_type, point_type,
vector_type, transform_type
} type;
/* Visual studio lacks support for unrestricted unions (as of ver. 2013) */
struct Value
{
Value() : boolean_value(false) { }
~Value() { }
bool boolean_value;
int integer_value;
float float_value;
std::string string_value;
Color3f color_value;
Point3f point_value;
Vector3f vector_value;
Transform transform_value;
} value;
Property() : type(boolean_type) { }
};
std::map<std::string, Property> m_properties;
};
NORI_NAMESPACE_END

101
include/nori/ray.h Normal file
View File

@ -0,0 +1,101 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <nori/vector.h>
NORI_NAMESPACE_BEGIN
/**
* \brief Simple n-dimensional ray segment data structure
*
* Along with the ray origin and direction, this data structure additionally
* stores a ray segment [mint, maxt] (whose entries may include positive/negative
* infinity), as well as the componentwise reciprocals of the ray direction.
* That is just done for convenience, as these values are frequently required.
*
* \remark Important: be careful when changing the ray direction. You must
* call \ref update() to compute the componentwise reciprocals as well, or Nori's
* ray-triangle intersection code will go haywire.
*/
template <typename _PointType, typename _VectorType> struct TRay {
typedef _PointType PointType;
typedef _VectorType VectorType;
typedef typename PointType::Scalar Scalar;
PointType o; ///< Ray origin
VectorType d; ///< Ray direction
VectorType dRcp; ///< Componentwise reciprocals of the ray direction
Scalar mint; ///< Minimum position on the ray segment
Scalar maxt; ///< Maximum position on the ray segment
/// Construct a new ray
TRay() : mint(Epsilon),
maxt(std::numeric_limits<Scalar>::infinity()) { }
/// Construct a new ray
TRay(const PointType &o, const VectorType &d) : o(o), d(d),
mint(Epsilon), maxt(std::numeric_limits<Scalar>::infinity()) {
update();
}
/// Construct a new ray
TRay(const PointType &o, const VectorType &d,
Scalar mint, Scalar maxt) : o(o), d(d), mint(mint), maxt(maxt) {
update();
}
/// Copy constructor
TRay(const TRay &ray)
: o(ray.o), d(ray.d), dRcp(ray.dRcp),
mint(ray.mint), maxt(ray.maxt) { }
/// Copy a ray, but change the covered segment of the copy
TRay(const TRay &ray, Scalar mint, Scalar maxt)
: o(ray.o), d(ray.d), dRcp(ray.dRcp), mint(mint), maxt(maxt) { }
/// Update the reciprocal ray directions after changing 'd'
void update() {
dRcp = d.cwiseInverse();
}
/// Return the position of a point along the ray
PointType operator() (Scalar t) const { return o + t * d; }
/// Return a ray that points into the opposite direction
TRay reverse() const {
TRay result;
result.o = o; result.d = -d; result.dRcp = -dRcp;
result.mint = mint; result.maxt = maxt;
return result;
}
/// Return a human-readable string summary of this ray
std::string toString() const {
return tfm::format(
"Ray[\n"
" o = %s,\n"
" d = %s,\n"
" mint = %f,\n"
" maxt = %f\n"
"]", o.toString(), d.toString(), mint, maxt);
}
};
NORI_NAMESPACE_END

57
include/nori/rfilter.h Normal file
View File

@ -0,0 +1,57 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <nori/object.h>
/// Reconstruction filters will be tabulated at this resolution
#define NORI_FILTER_RESOLUTION 32
NORI_NAMESPACE_BEGIN
/**
* \brief Generic radially symmetric image reconstruction filter
*
* When adding radiance-valued samples to the rendered image, Nori
* first convolves them with a so-called image reconstruction filter.
*
* To learn more about reconstruction filters and sampling theory
* in general, take a look at the excellenent chapter 7 of PBRT,
* which is freely available at:
*
* http://graphics.stanford.edu/~mmp/chapters/pbrt_chapter7.pdf
*/
class ReconstructionFilter : public NoriObject {
public:
/// Return the filter radius in fractional pixels
float getRadius() const { return m_radius; }
/// Evaluate the filter function
virtual float eval(float x) const = 0;
/**
* \brief Return the type of object (i.e. Mesh/Camera/etc.)
* provided by this instance
* */
EClassType getClassType() const { return EReconstructionFilter; }
protected:
float m_radius;
};
NORI_NAMESPACE_END

109
include/nori/sampler.h Normal file
View File

@ -0,0 +1,109 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <nori/object.h>
#include <memory>
NORI_NAMESPACE_BEGIN
class ImageBlock;
/**
* \brief Abstract sample generator
*
* A sample generator is responsible for generating the random number stream
* that will be passed an \ref Integrator implementation as it computes the
* radiance incident along a specified ray.
*
* The most simple conceivable sample generator is just a wrapper around the
* Mersenne-Twister random number generator and is implemented in
* <tt>independent.cpp</tt> (it is named this way because it generates
* statistically independent random numbers).
*
* Fancier samplers might use stratification or low-discrepancy sequences
* (e.g. Halton, Hammersley, or Sobol point sets) for improved convergence.
* Another use of this class is in producing intentionally correlated
* random numbers, e.g. as part of a Metropolis-Hastings integration scheme.
*
* The general interface between a sampler and a rendering algorithm is as
* follows: Before beginning to render a pixel, the rendering algorithm calls
* \ref generate(). The first pixel sample can now be computed, after which
* \ref advance() needs to be invoked. This repeats until all pixel samples have
* been exhausted. While computing a pixel sample, the rendering
* algorithm requests (pseudo-) random numbers using the \ref next1D() and
* \ref next2D() functions.
*
* Conceptually, the right way of thinking of this goes as follows:
* For each sample in a pixel, a sample generator produces a (hypothetical)
* point in an infinite dimensional random number hypercube. A rendering
* algorithm can then request subsequent 1D or 2D components of this point
* using the \ref next1D() and \ref next2D() functions. Fancy implementations
* of this class make certain guarantees about the stratification of the
* first n components with respect to the other points that are sampled
* within a pixel.
*/
class Sampler : public NoriObject {
public:
/// Release all memory
virtual ~Sampler() { }
/// Create an exact clone of the current instance
virtual std::unique_ptr<Sampler> clone() const = 0;
/**
* \brief Prepare to render a new image block
*
* This function is called when the sampler begins rendering
* a new image block. This can be used to deterministically
* initialize the sampler so that repeated program runs
* always create the same image.
*/
virtual void prepare(const ImageBlock &block) = 0;
/**
* \brief Prepare to generate new samples
*
* This function is called initially and every time the
* integrator starts rendering a new pixel.
*/
virtual void generate() = 0;
/// Advance to the next sample
virtual void advance() = 0;
/// Retrieve the next component value from the current sample
virtual float next1D() = 0;
/// Retrieve the next two component values from the current sample
virtual Point2f next2D() = 0;
/// Return the number of configured pixel samples
virtual size_t getSampleCount() const { return m_sampleCount; }
/**
* \brief Return the type of object (i.e. Mesh/Sampler/etc.)
* provided by this instance
* */
EClassType getClassType() const { return ESampler; }
protected:
size_t m_sampleCount;
};
NORI_NAMESPACE_END

127
include/nori/scene.h Normal file
View File

@ -0,0 +1,127 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <nori/accel.h>
NORI_NAMESPACE_BEGIN
/**
* \brief Main scene data structure
*
* This class holds information on scene objects and is responsible for
* coordinating rendering jobs. It also provides useful query routines that
* are mostly used by the \ref Integrator implementations.
*/
class Scene : public NoriObject {
public:
/// Construct a new scene object
Scene(const PropertyList &);
/// Release all memory
virtual ~Scene();
/// Return a pointer to the scene's kd-tree
const Accel *getAccel() const { return m_accel; }
/// Return a pointer to the scene's integrator
const Integrator *getIntegrator() const { return m_integrator; }
/// Return a pointer to the scene's integrator
Integrator *getIntegrator() { return m_integrator; }
/// Return a pointer to the scene's camera
const Camera *getCamera() const { return m_camera; }
/// Return a pointer to the scene's sample generator (const version)
const Sampler *getSampler() const { return m_sampler; }
/// Return a pointer to the scene's sample generator
Sampler *getSampler() { return m_sampler; }
/// Return a reference to an array containing all meshes
const std::vector<Mesh *> &getMeshes() const { return m_meshes; }
/**
* \brief Intersect a ray against all triangles stored in the scene
* and return detailed intersection information
*
* \param ray
* A 3-dimensional ray data structure with minimum/maximum
* extent information
*
* \param its
* A detailed intersection record, which will be filled by the
* intersection query
*
* \return \c true if an intersection was found
*/
bool rayIntersect(const Ray3f &ray, Intersection &its) const {
return m_accel->rayIntersect(ray, its, false);
}
/**
* \brief Intersect a ray against all triangles stored in the scene
* and \a only determine whether or not there is an intersection.
*
* This method much faster than the other ray tracing function,
* but the performance comes at the cost of not providing any
* additional information about the detected intersection
* (not even its position).
*
* \param ray
* A 3-dimensional ray data structure with minimum/maximum
* extent information
*
* \return \c true if an intersection was found
*/
bool rayIntersect(const Ray3f &ray) const {
Intersection its; /* Unused */
return m_accel->rayIntersect(ray, its, true);
}
/// \brief Return an axis-aligned box that bounds the scene
const BoundingBox3f &getBoundingBox() const {
return m_accel->getBoundingBox();
}
/**
* \brief Inherited from \ref NoriObject::activate()
*
* Initializes the internal data structures (kd-tree,
* emitter sampling data structures, etc.)
*/
void activate();
/// Add a child object to the scene (meshes, integrators etc.)
void addChild(NoriObject *obj);
/// Return a string summary of the scene (for debugging purposes)
std::string toString() const;
EClassType getClassType() const { return EScene; }
private:
std::vector<Mesh *> m_meshes;
Integrator *m_integrator = nullptr;
Sampler *m_sampler = nullptr;
Camera *m_camera = nullptr;
Accel *m_accel = nullptr;
};
NORI_NAMESPACE_END

67
include/nori/timer.h Normal file
View File

@ -0,0 +1,67 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <nori/common.h>
#include <chrono>
NORI_NAMESPACE_BEGIN
/**
* \brief Simple timer with millisecond precision
*
* This class is convenient for collecting performance data
*/
class Timer {
public:
/// Create a new timer and reset it
Timer() { reset(); }
/// Reset the timer to the current time
void reset() { start = std::chrono::system_clock::now(); }
/// Return the number of milliseconds elapsed since the timer was last reset
double elapsed() const {
auto now = std::chrono::system_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
return (double) duration.count();
}
/// Like \ref elapsed(), but return a human-readable string
std::string elapsedString(bool precise = false) const {
return timeString(elapsed(), precise);
}
/// Return the number of milliseconds elapsed since the timer was last reset and then reset it
double lap() {
auto now = std::chrono::system_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
start = now;
return (double) duration.count();
}
/// Like \ref lap(), but return a human-readable string
std::string lapString(bool precise = false) {
return timeString(lap(), precise);
}
private:
std::chrono::system_clock::time_point start;
};
NORI_NAMESPACE_END

98
include/nori/transform.h Normal file
View File

@ -0,0 +1,98 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <nori/common.h>
#include <nori/ray.h>
NORI_NAMESPACE_BEGIN
/**
* \brief Homogeneous coordinate transformation
*
* This class stores a general homogeneous coordinate tranformation, such as
* rotation, translation, uniform or non-uniform scaling, and perspective
* transformations. The inverse of this transformation is also recorded
* here, since it is required when transforming normal vectors.
*/
struct Transform {
public:
/// Create the identity transform
Transform() :
m_transform(Eigen::Matrix4f::Identity()),
m_inverse(Eigen::Matrix4f::Identity()) { }
/// Create a new transform instance for the given matrix
Transform(const Eigen::Matrix4f &trafo);
/// Create a new transform instance for the given matrix and its inverse
Transform(const Eigen::Matrix4f &trafo, const Eigen::Matrix4f &inv)
: m_transform(trafo), m_inverse(inv) { }
/// Return the underlying matrix
const Eigen::Matrix4f &getMatrix() const {
return m_transform;
}
/// Return the inverse of the underlying matrix
const Eigen::Matrix4f &getInverseMatrix() const {
return m_inverse;
}
/// Return the inverse transformation
Transform inverse() const {
return Transform(m_inverse, m_transform);
}
/// Concatenate with another transform
Transform operator*(const Transform &t) const;
/// Apply the homogeneous transformation to a 3D vector
Vector3f operator*(const Vector3f &v) const {
return m_transform.topLeftCorner<3,3>() * v;
}
/// Apply the homogeneous transformation to a 3D normal
Normal3f operator*(const Normal3f &n) const {
return m_inverse.topLeftCorner<3, 3>().transpose() * n;
}
/// Transform a point by an arbitrary matrix in homogeneous coordinates
Point3f operator*(const Point3f &p) const {
Vector4f result = m_transform * Vector4f(p[0], p[1], p[2], 1.0f);
return result.head<3>() / result.w();
}
/// Apply the homogeneous transformation to a ray
Ray3f operator*(const Ray3f &r) const {
return Ray3f(
operator*(r.o),
operator*(r.d),
r.mint, r.maxt
);
}
/// Return a string representation
std::string toString() const;
private:
Eigen::Matrix4f m_transform;
Eigen::Matrix4f m_inverse;
};
NORI_NAMESPACE_END

169
include/nori/vector.h Normal file
View File

@ -0,0 +1,169 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <nori/common.h>
NORI_NAMESPACE_BEGIN
/* ===================================================================
This file contains a few templates and specializations, which
provide 2/3D points, vectors, and normals over different
underlying data types. Points, vectors, and normals are distinct
in Nori, because they transform differently under homogeneous
coordinate transformations.
* =================================================================== */
/**
* \brief Generic N-dimensional vector data structure based on Eigen::Matrix
*/
template <typename _Scalar, int _Dimension> struct TVector : public Eigen::Matrix<_Scalar, _Dimension, 1> {
public:
enum {
Dimension = _Dimension
};
typedef _Scalar Scalar;
typedef Eigen::Matrix<Scalar, Dimension, 1> Base;
typedef TVector<Scalar, Dimension> VectorType;
typedef TPoint<Scalar, Dimension> PointType;
/// Create a new vector with constant component vlaues
TVector(Scalar value = (Scalar) 0) { Base::setConstant(value); }
/// Create a new 2D vector (type error if \c Dimension != 2)
TVector(Scalar x, Scalar y) : Base(x, y) { }
/// Create a new 3D vector (type error if \c Dimension != 3)
TVector(Scalar x, Scalar y, Scalar z) : Base(x, y, z) { }
/// Create a new 4D vector (type error if \c Dimension != 4)
TVector(Scalar x, Scalar y, Scalar z, Scalar w) : Base(x, y, z, w) { }
/// Construct a vector from MatrixBase (needed to play nice with Eigen)
template <typename Derived> TVector(const Eigen::MatrixBase<Derived>& p)
: Base(p) { }
/// Assign a vector from MatrixBase (needed to play nice with Eigen)
template <typename Derived> TVector &operator=(const Eigen::MatrixBase<Derived>& p) {
this->Base::operator=(p);
return *this;
}
/// Return a human-readable string summary
std::string toString() const {
std::string result;
for (size_t i=0; i<Dimension; ++i) {
result += std::to_string(this->coeff(i));
if (i+1 < Dimension)
result += ", ";
}
return "[" + result + "]";
}
};
/**
* \brief Generic N-dimensional point data structure based on Eigen::Matrix
*/
template <typename _Scalar, int _Dimension> struct TPoint : public Eigen::Matrix<_Scalar, _Dimension, 1> {
public:
enum {
Dimension = _Dimension
};
typedef _Scalar Scalar;
typedef Eigen::Matrix<Scalar, Dimension, 1> Base;
typedef TVector<Scalar, Dimension> VectorType;
typedef TPoint<Scalar, Dimension> PointType;
/// Create a new point with constant component vlaues
TPoint(Scalar value = (Scalar) 0) { Base::setConstant(value); }
/// Create a new 2D point (type error if \c Dimension != 2)
TPoint(Scalar x, Scalar y) : Base(x, y) { }
/// Create a new 3D point (type error if \c Dimension != 3)
TPoint(Scalar x, Scalar y, Scalar z) : Base(x, y, z) { }
/// Create a new 4D point (type error if \c Dimension != 4)
TPoint(Scalar x, Scalar y, Scalar z, Scalar w) : Base(x, y, z, w) { }
/// Construct a point from MatrixBase (needed to play nice with Eigen)
template <typename Derived> TPoint(const Eigen::MatrixBase<Derived>& p)
: Base(p) { }
/// Assign a point from MatrixBase (needed to play nice with Eigen)
template <typename Derived> TPoint &operator=(const Eigen::MatrixBase<Derived>& p) {
this->Base::operator=(p);
return *this;
}
/// Return a human-readable string summary
std::string toString() const {
std::string result;
for (size_t i=0; i<Dimension; ++i) {
result += std::to_string(this->coeff(i));
if (i+1 < Dimension)
result += ", ";
}
return "[" + result + "]";
}
};
/**
* \brief 3-dimensional surface normal representation
*/
struct Normal3f : public Eigen::Matrix<float, 3, 1> {
public:
enum {
Dimension = 3
};
typedef float Scalar;
typedef Eigen::Matrix<Scalar, Dimension, 1> Base;
typedef TVector<Scalar, Dimension> VectorType;
typedef TPoint<Scalar, Dimension> PointType;
/// Create a new normal with constant component vlaues
Normal3f(Scalar value = 0.0f) { Base::setConstant(value); }
/// Create a new 3D normal
Normal3f(Scalar x, Scalar y, Scalar z) : Base(x, y, z) { }
/// Construct a normal from MatrixBase (needed to play nice with Eigen)
template <typename Derived> Normal3f(const Eigen::MatrixBase<Derived>& p)
: Base(p) { }
/// Assign a normal from MatrixBase (needed to play nice with Eigen)
template <typename Derived> Normal3f &operator=(const Eigen::MatrixBase<Derived>& p) {
this->Base::operator=(p);
return *this;
}
/// Return a human-readable string summary
std::string toString() const {
return tfm::format("[%f, %f, %f]", coeff(0), coeff(1), coeff(2));
}
};
/// Complete the set {a} to an orthonormal base
extern void coordinateSystem(const Vector3f &a, Vector3f &b, Vector3f &c);
NORI_NAMESPACE_END

72
include/nori/warp.h Normal file
View File

@ -0,0 +1,72 @@
/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <nori/common.h>
#include <nori/sampler.h>
NORI_NAMESPACE_BEGIN
/// A collection of useful warping functions for importance sampling
class Warp {
public:
/// Dummy warping function: takes uniformly distributed points in a square and just returns them
static Point2f squareToUniformSquare(const Point2f &sample);
/// Probability density of \ref squareToUniformSquare()
static float squareToUniformSquarePdf(const Point2f &p);
/// Sample a 2D tent distribution
static Point2f squareToTent(const Point2f &sample);
/// Probability density of \ref squareToTent()
static float squareToTentPdf(const Point2f &p);
/// Uniformly sample a vector on a 2D disk with radius 1, centered around the origin
static Point2f squareToUniformDisk(const Point2f &sample);
/// Probability density of \ref squareToUniformDisk()
static float squareToUniformDiskPdf(const Point2f &p);
/// Uniformly sample a vector on the unit sphere with respect to solid angles
static Vector3f squareToUniformSphere(const Point2f &sample);
/// Probability density of \ref squareToUniformSphere()
static float squareToUniformSpherePdf(const Vector3f &v);
/// Uniformly sample a vector on the unit hemisphere around the pole (0,0,1) with respect to solid angles
static Vector3f squareToUniformHemisphere(const Point2f &sample);
/// Probability density of \ref squareToUniformHemisphere()
static float squareToUniformHemispherePdf(const Vector3f &v);
/// Uniformly sample a vector on the unit hemisphere around the pole (0,0,1) with respect to projected solid angles
static Vector3f squareToCosineHemisphere(const Point2f &sample);
/// Probability density of \ref squareToCosineHemisphere()
static float squareToCosineHemispherePdf(const Vector3f &v);
/// Warp a uniformly distributed square sample to a Beckmann distribution * cosine for the given 'alpha' parameter
static Vector3f squareToBeckmann(const Point2f &sample, float alpha);
/// Probability density of \ref squareToBeckmann()
static float squareToBeckmannPdf(const Vector3f &m, float alpha);
};
NORI_NAMESPACE_END

View File

BIN
results/homework-1/mine.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

View File

@ -0,0 +1,60 @@
**Homework 1**
Student name:
Sciper number:
Feedback
========
Use this section to provide feedback about the assignment.
About this template
===================
* Structure your report using numbers and titles following those of the assignment.
* For every task include all images you generated in a format that is viewable within a web browser. You can use HDRI Tools, or photoshop to tonemap you images and save them as jpgs or pngs. We recommend tonemapping your images by just changing exposure and using sRGB (if available) or a gamma of 2.2.
* Include descriptions of encountered problems, a list of external libraries that you used (if applicable) and the time you spent on each task.
For an overview of Markdeep and its syntax, see the [official demo document](https://casual-effects.com/markdeep/features.md.html) and
the associated [source code](https://casual-effects.com/markdeep/features.md.html?noformat).
Exercise writeup
================
Proofs and justifications
-------------------------
LaTeX is also supported:
$$
L_o(\mathbf{x}, \omega_o) = \int_{\Omega} L_i(\mathbf{x},\omega_i)\, f(\mathbf{x}, \omega_i, \omega_o)\, |\cos\theta_i|\, \mathrm{d}\omega_i
$$
Reference comparisons
---------------------
Comparison 1
<div class="twentytwenty-container">
<img src="reference.jpg" alt="Reference">
<img src="mine.jpg" alt="Mine">
</div>
Comparison 2
<div class="twentytwenty-container">
<img src="mine.jpg" alt="Reference">
<img src="reference.jpg" alt="Mine">
</div>
<!-- Slider -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="../resources/jquery.event.move.js"></script>
<script src="../resources/jquery.twentytwenty.js"></script>
<link href="../resources/offcanvas.css" rel="stylesheet">
<link href="../resources/twentytwenty.css" rel="stylesheet" type="text/css" />
<script>var markdeepOptions = {onLoad: function() {$(".twentytwenty-container").twentytwenty({default_offset_pct: 0.5, move_slider_on_hover: true});} };</script>
<!-- Markdeep: -->
<script src="https://morgan3d.github.io/markdeep/latest/markdeep.min.js?" charset="utf-8"></script>
<script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

View File

@ -0,0 +1,43 @@
**Homework 2**
Student name:
Sciper number:
Octree construction (50 pts)
============================
Ray traversal (25 pts)
======================
Improved ray traversal (25 pts)
===============================
Surface normal visualization of the Ajax bust:
<div class="twentytwenty-container">
<img src="ajax-normals-ref.png" alt="Reference">
<img src="ajax-normals.png" alt="Mine">
</div>
Note: Nori automatically generates both an `.exr` as well as an sRGB tonemapped `.png` image of your rendering that is directly used for the comparison above. Please still commit both versions in your `results/homework-X` folder.
Feedback
========
We would appreciate any comments or criticism to improve the projects in future years--naturally, this part will not be graded. Examples of information that is useful to us includes:
* How much time did you spend on the assignment? How was it divided between designing, coding, and testing?
* What advice should we have given you before you started?
* What was hard or surprising about the assignment?
* What did you like or dislike? What else would you change?
<!-- Slider -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="../resources/jquery.event.move.js"></script>
<script src="../resources/jquery.twentytwenty.js"></script>
<link href="../resources/offcanvas.css" rel="stylesheet">
<link href="../resources/twentytwenty.css" rel="stylesheet" type="text/css" />
<script>var markdeepOptions = {onLoad: function() {$(".twentytwenty-container").twentytwenty({default_offset_pct: 0.5, move_slider_on_hover: true});} };</script>
<!-- Markdeep: -->
<script src="https://morgan3d.github.io/markdeep/latest/markdeep.min.js?" charset="utf-8"></script>
<script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

View File

@ -0,0 +1,74 @@
**Homework 3**
Student name:
Sciper number:
Monte Carlo Sampling (60 pts)
=============================
For each of these sections, don't forget to include necessary derivations as well as screenshots of passing $\chi^2$ tests from the <tt>warptest</tt> executable.
Tent
----
Uniform disk
------------
Uniform sphere
--------------
Uniform hemisphere
------------------
Cosine hemisphere
-----------------
Beckmann distribution
---------------------
Two simple rendering algorithms (40 pts)
========================================
Point lights
------------
Ajax bust illuminated by a point light source:
<div class="twentytwenty-container">
<img src="ajax-simple-ref.png" alt="Reference">
<img src="ajax-simple.png" alt="Mine">
</div>
Note: Nori automatically generates both a `.exr` as well as an sRGB tonemapped `.png` image of your rendering that should directly used for the comparison above. Please still commit both versions in your `results/homework-X` folder.
Ambient occlusion
-----------------
Ajax bust rendered using ambient occlusion:
<div class="twentytwenty-container">
<img src="ajax-ao-ref.png" alt="Reference">
<img src="ajax-ao.png" alt="Mine">
</div>
Note: Nori automatically generates both a `.exr` as well as an sRGB tonemapped `.png` image of your rendering that should directly used for the comparison above. Please still commit both versions in your `results/homework-X` folder.
Feedback
========
We would appreciate any comments or criticism to improve the projects in future years--naturally, this part will not be graded. Examples of information that is useful to us includes:
* How much time did you spend on the assignment? How was it divided between designing, coding, and testing?
* What advice should we have given you before you started?
* What was hard or surprising about the assignment?
* What did you like or dislike? What else would you change?
<!-- Slider -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="../resources/jquery.event.move.js"></script>
<script src="../resources/jquery.twentytwenty.js"></script>
<link href="../resources/offcanvas.css" rel="stylesheet">
<link href="../resources/twentytwenty.css" rel="stylesheet" type="text/css" />
<script>var markdeepOptions = {onLoad: function() {$(".twentytwenty-container").twentytwenty({default_offset_pct: 0.5, move_slider_on_hover: true});} };</script>
<!-- Markdeep: -->
<script src="https://morgan3d.github.io/markdeep/latest/markdeep.min.js?" charset="utf-8"></script>
<script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

View File

@ -0,0 +1,66 @@
**Homework 4**
Student name:
Sciper number:
Area lights (25 pts)
====================
Distribution Ray Tracing (40 pts)
=================================
Diffuse logo:
<div class="twentytwenty-container">
<img src="logo-diffuse-ref.png" alt="Reference">
<img src="logo-diffuse.png" alt="Mine">
</div>
Cornell box (distributed):
<div class="twentytwenty-container">
<img src="cbox-distributed-ref.png" alt="Reference">
<img src="cbox-distributed.png" alt="Mine">
</div>
Note: please commit both EXR and PNG versions of your renders in the `results/homework-X` folder.
Dielectrics (25 pts)
====================
Whitted-style ray tracing (10 pts)
==================================
Dielectric logo:
<div class="twentytwenty-container">
<img src="logo-dielectric-ref.png" alt="Reference">
<img src="logo-dielectric.png" alt="Mine">
</div>
Cornell box (Whitted):
<div class="twentytwenty-container">
<img src="cbox-whitted-ref.png" alt="Reference">
<img src="cbox-whitted.png" alt="Mine">
</div>
Note: please commit both EXR and PNG versions of your renders in the `results/homework-X` folder.
Feedback
========
We would appreciate any comments or criticism to improve the projects in future years--naturally, this part will not be graded. Examples of information that is useful to us includes:
* How much time did you spend on the assignment? How was it divided between designing, coding, and testing?
* What advice should we have given you before you started?
* What was hard or surprising about the assignment?
* What did you like or dislike? What else would you change?
<!-- Slider -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="../resources/jquery.event.move.js"></script>
<script src="../resources/jquery.twentytwenty.js"></script>
<link href="../resources/offcanvas.css" rel="stylesheet">
<link href="../resources/twentytwenty.css" rel="stylesheet" type="text/css" />
<script>var markdeepOptions = {onLoad: function() {$(".twentytwenty-container").twentytwenty({default_offset_pct: 0.5, move_slider_on_hover: true});} };</script>
<!-- Markdeep: -->
<script src="https://morgan3d.github.io/markdeep/latest/markdeep.min.js?" charset="utf-8"></script>
<script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1017 KiB

View File

@ -0,0 +1,123 @@
**Homework 5**
Student name:
Sciper number:
Microfacet BRDF (30 points)
===========================
Evaluating the Microfacet BRDF
------------------------------
Sampling the Microfacet BRDF
------------------------------
Validation
----------
Ajax (smooth):
<div class="twentytwenty-container">
<img src="ajax-smooth-ref.png" alt="Reference">
<img src="ajax-smooth.png" alt="Mine">
</div>
Ajax (rough):
<div class="twentytwenty-container">
<img src="ajax-rough-ref.png" alt="Reference">
<img src="ajax-rough.png" alt="Mine">
</div>
Brute force path tracer (15 points)
===================================
Validation
----------
Cornell box:
<div class="twentytwenty-container">
<img src="cbox_mats-ref.png" alt="Reference">
<img src="cbox_mats.png" alt="Mine">
</div>
Veach material test scene:
<div class="twentytwenty-container">
<img src="veach_mats-ref.png" alt="Reference">
<img src="veach_mats.png" alt="Mine">
</div>
Table test scene:
<div class="twentytwenty-container">
<img src="table_mats-ref.png" alt="Reference">
<img src="table_mats.png" alt="Mine">
</div>
Path tracer with next event estimation (25 points)
==================================================
Validation
----------
Cornell box:
<div class="twentytwenty-container">
<img src="cbox_ems-ref.png" alt="Reference">
<img src="cbox_ems.png" alt="Mine">
</div>
Veach material test scene:
<div class="twentytwenty-container">
<img src="veach_ems-ref.png" alt="Reference">
<img src="veach_ems.png" alt="Mine">
</div>
Table test scene:
<div class="twentytwenty-container">
<img src="table_ems-ref.png" alt="Reference">
<img src="table_ems.png" alt="Mine">
</div>
Path tracer with Multiple Importance Sampling (30 points)
=========================================================
Validation
----------
Cornell box:
<div class="twentytwenty-container">
<img src="cbox_mis-ref.png" alt="Reference">
<img src="cbox_mis.png" alt="Mine">
</div>
Veach material test scene:
<div class="twentytwenty-container">
<img src="veach_mis-ref.png" alt="Reference">
<img src="veach_mis.png" alt="Mine">
</div>
Table test scene:
<div class="twentytwenty-container">
<img src="table_mis-ref.png" alt="Reference">
<img src="table_mis.png" alt="Mine">
</div>
Feedback
========
We would appreciate any comments or criticism to improve the projects in future years--naturally, this part will not be graded. Examples of information that is useful to us includes:
* How much time did you spend on the assignment? How was it divided between designing, coding, and testing?
* What advice should we have given you before you started?
* What was hard or surprising about the assignment?
* What did you like or dislike? What else would you change?
<!-- Slider -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="../resources/jquery.event.move.js"></script>
<script src="../resources/jquery.twentytwenty.js"></script>
<link href="../resources/offcanvas.css" rel="stylesheet">
<link href="../resources/twentytwenty.css" rel="stylesheet" type="text/css" />
<script>var markdeepOptions = {onLoad: function() {$(".twentytwenty-container").twentytwenty({default_offset_pct: 0.5, move_slider_on_hover: true});} };</script>
<!-- Markdeep: -->
<script src="https://morgan3d.github.io/markdeep/latest/markdeep.min.js?" charset="utf-8"></script>
<script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 884 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 841 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 763 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 KiB

View File

@ -0,0 +1,63 @@
**Final Project Proposal**
Student name:
Sciper number:
Scene description and motivation
================================
Describe the scene you plan to render. Use at least one motivational image.
Feature list
============
Using the table below, list the features you'll use to achieve your render, and how they are used.
* Include the point counts, they should add up to *80 points*.
* If your list of features adds up to more than 80 points, you must reduce the point count for the features of your choice until the total reaches 80 points exactly.
<table>
<tr>
<th>Feature</th>
<th>Identifier</th>
<th>Standard point count</th>
<th>Adjusted point count</th>
</tr>
<tr>
<td>Feature 1</td>
<td>#S-abc</td>
<td>20</td>
<td>20</td>
</tr>
<tr>
<td>Feature 2</td>
<td>#M-BADCAFE</td>
<td>30</td>
<td>15</td>
</tr>
<tr>
<td>Feature ...</td>
<td>#E-...</td>
<td>...</td>
<td>...</td>
</tr>
<tr>
<td colspan="2"><strong>Total</strong></td>
<td>95</td>
<td>80</td>
</tr>
</table>
<!-- Slider -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="../resources/jquery.event.move.js"></script>
<script src="../resources/jquery.twentytwenty.js"></script>
<link href="../resources/offcanvas.css" rel="stylesheet">
<link href="../resources/twentytwenty.css" rel="stylesheet" type="text/css" />
<script>var markdeepOptions = {onLoad: function() {$(".twentytwenty-container").twentytwenty({default_offset_pct: 0.5, move_slider_on_hover: true});} };</script>
<!-- Markdeep: -->
<script src="https://morgan3d.github.io/markdeep/latest/markdeep.min.js?" charset="utf-8"></script>
<script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>

View File

@ -0,0 +1,84 @@
**Final Project Report**
Student name:
Sciper number:
Final render
============
Include your final render here.
Motivation
==========
Summarize your motivation for rendering this scene and describe how it connects to the theme (this
can be adapted from your project proposal).
Feature list
============
Please paste below the table of features from your project proposal (including adjustments
made after submission, if any).
<table>
<tr>
<th>Feature</th>
<th>Standard point count</th>
<th>Adjusted point count</th>
</tr>
<tr>
<td>Feature 1</td>
<td>20</td>
<td>20</td>
</tr>
<tr>
<td>Feature 2</td>
<td>30</td>
<td>15</td>
</tr>
<tr>
<td>Feature ...</td>
<td>...</td>
<td>...</td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td>95</td>
<td>80</td>
</tr>
</table>
Feature 1
=========
Description, design choices and **validation** for feature 1.
Feature 2
=========
Description, design choices and **validation** for feature 2.
Feedback
========
Please provide feedback about the final project or the course in general.
<!-- Slider -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="../resources/jquery.event.move.js"></script>
<script src="../resources/jquery.twentytwenty.js"></script>
<link href="../resources/offcanvas.css" rel="stylesheet">
<link href="../resources/twentytwenty.css" rel="stylesheet" type="text/css" />
<script>var markdeepOptions = {onLoad: function() {$(".twentytwenty-container").twentytwenty({default_offset_pct: 0.5, move_slider_on_hover: true});} };</script>
<!-- Markdeep: -->
<script src="https://morgan3d.github.io/markdeep/latest/markdeep.min.js?" charset="utf-8"></script>
<script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>

View File

@ -0,0 +1,599 @@
// DOM.event.move
//
// 2.0.0
//
// Stephen Band
//
// Triggers 'movestart', 'move' and 'moveend' events after
// mousemoves following a mousedown cross a distance threshold,
// similar to the native 'dragstart', 'drag' and 'dragend' events.
// Move events are throttled to animation frames. Move event objects
// have the properties:
//
// pageX:
// pageY: Page coordinates of pointer.
// startX:
// startY: Page coordinates of pointer at movestart.
// distX:
// distY: Distance the pointer has moved since movestart.
// deltaX:
// deltaY: Distance the finger has moved since last event.
// velocityX:
// velocityY: Average velocity over last few events.
(function(fn) {
if (typeof define === 'function' && define.amd) {
define([], fn);
} else if ((typeof module !== "undefined" && module !== null) && module.exports) {
module.exports = fn;
} else {
fn();
}
})(function(){
var assign = Object.assign || window.jQuery && jQuery.extend;
// Number of pixels a pressed pointer travels before movestart
// event is fired.
var threshold = 8;
// Shim for requestAnimationFrame, falling back to timer. See:
// see http://paulirish.com/2011/requestanimationframe-for-smart-animating/
var requestFrame = (function(){
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(fn, element){
return window.setTimeout(function(){
fn();
}, 25);
}
);
})();
// Shim for customEvent
// see https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
(function () {
if ( typeof window.CustomEvent === "function" ) return false;
function CustomEvent ( event, params ) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
var evt = document.createEvent( 'CustomEvent' );
evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
return evt;
}
CustomEvent.prototype = window.Event.prototype;
window.CustomEvent = CustomEvent;
})();
var ignoreTags = {
textarea: true,
input: true,
select: true,
button: true
};
var mouseevents = {
move: 'mousemove',
cancel: 'mouseup dragstart',
end: 'mouseup'
};
var touchevents = {
move: 'touchmove',
cancel: 'touchend',
end: 'touchend'
};
var rspaces = /\s+/;
// DOM Events
var eventOptions = { bubbles: true, cancelable: true };
var eventsSymbol = typeof Symbol === "function" ? Symbol('events') : {};
function createEvent(type) {
return new CustomEvent(type, eventOptions);
}
function getEvents(node) {
return node[eventsSymbol] || (node[eventsSymbol] = {});
}
function on(node, types, fn, data, selector) {
types = types.split(rspaces);
var events = getEvents(node);
var i = types.length;
var handlers, type;
function handler(e) { fn(e, data); }
while (i--) {
type = types[i];
handlers = events[type] || (events[type] = []);
handlers.push([fn, handler]);
node.addEventListener(type, handler);
}
}
function off(node, types, fn, selector) {
types = types.split(rspaces);
var events = getEvents(node);
var i = types.length;
var type, handlers, k;
if (!events) { return; }
while (i--) {
type = types[i];
handlers = events[type];
if (!handlers) { continue; }
k = handlers.length;
while (k--) {
if (handlers[k][0] === fn) {
node.removeEventListener(type, handlers[k][1]);
handlers.splice(k, 1);
}
}
}
}
function trigger(node, type, properties) {
// Don't cache events. It prevents you from triggering an event of a
// given type from inside the handler of another event of that type.
var event = createEvent(type);
if (properties) { assign(event, properties); }
node.dispatchEvent(event);
}
// Constructors
function Timer(fn){
var callback = fn,
active = false,
running = false;
function trigger(time) {
if (active){
callback();
requestFrame(trigger);
running = true;
active = false;
}
else {
running = false;
}
}
this.kick = function(fn) {
active = true;
if (!running) { trigger(); }
};
this.end = function(fn) {
var cb = callback;
if (!fn) { return; }
// If the timer is not running, simply call the end callback.
if (!running) {
fn();
}
// If the timer is running, and has been kicked lately, then
// queue up the current callback and the end callback, otherwise
// just the end callback.
else {
callback = active ?
function(){ cb(); fn(); } :
fn ;
active = true;
}
};
}
// Functions
function noop() {}
function preventDefault(e) {
e.preventDefault();
}
function isIgnoreTag(e) {
return !!ignoreTags[e.target.tagName.toLowerCase()];
}
function isPrimaryButton(e) {
// Ignore mousedowns on any button other than the left (or primary)
// mouse button, or when a modifier key is pressed.
return (e.which === 1 && !e.ctrlKey && !e.altKey);
}
function identifiedTouch(touchList, id) {
var i, l;
if (touchList.identifiedTouch) {
return touchList.identifiedTouch(id);
}
// touchList.identifiedTouch() does not exist in
// webkit yet… we must do the search ourselves...
i = -1;
l = touchList.length;
while (++i < l) {
if (touchList[i].identifier === id) {
return touchList[i];
}
}
}
function changedTouch(e, data) {
var touch = identifiedTouch(e.changedTouches, data.identifier);
// This isn't the touch you're looking for.
if (!touch) { return; }
// Chrome Android (at least) includes touches that have not
// changed in e.changedTouches. That's a bit annoying. Check
// that this touch has changed.
if (touch.pageX === data.pageX && touch.pageY === data.pageY) { return; }
return touch;
}
// Handlers that decide when the first movestart is triggered
function mousedown(e){
// Ignore non-primary buttons
if (!isPrimaryButton(e)) { return; }
// Ignore form and interactive elements
if (isIgnoreTag(e)) { return; }
on(document, mouseevents.move, mousemove, e);
on(document, mouseevents.cancel, mouseend, e);
}
function mousemove(e, data){
checkThreshold(e, data, e, removeMouse);
}
function mouseend(e, data) {
removeMouse();
}
function removeMouse() {
off(document, mouseevents.move, mousemove);
off(document, mouseevents.cancel, mouseend);
}
function touchstart(e) {
// Don't get in the way of interaction with form elements
if (ignoreTags[e.target.tagName.toLowerCase()]) { return; }
var touch = e.changedTouches[0];
// iOS live updates the touch objects whereas Android gives us copies.
// That means we can't trust the touchstart object to stay the same,
// so we must copy the data. This object acts as a template for
// movestart, move and moveend event objects.
var data = {
target: touch.target,
pageX: touch.pageX,
pageY: touch.pageY,
identifier: touch.identifier,
// The only way to make handlers individually unbindable is by
// making them unique.
touchmove: function(e, data) { touchmove(e, data); },
touchend: function(e, data) { touchend(e, data); }
};
on(document, touchevents.move, data.touchmove, data);
on(document, touchevents.cancel, data.touchend, data);
}
function touchmove(e, data) {
var touch = changedTouch(e, data);
if (!touch) { return; }
checkThreshold(e, data, touch, removeTouch);
}
function touchend(e, data) {
var touch = identifiedTouch(e.changedTouches, data.identifier);
if (!touch) { return; }
removeTouch(data);
}
function removeTouch(data) {
off(document, touchevents.move, data.touchmove);
off(document, touchevents.cancel, data.touchend);
}
function checkThreshold(e, data, touch, fn) {
var distX = touch.pageX - data.pageX;
var distY = touch.pageY - data.pageY;
// Do nothing if the threshold has not been crossed.
if ((distX * distX) + (distY * distY) < (threshold * threshold)) { return; }
triggerStart(e, data, touch, distX, distY, fn);
}
function triggerStart(e, data, touch, distX, distY, fn) {
var touches = e.targetTouches;
var time = e.timeStamp - data.timeStamp;
// Create a movestart object with some special properties that
// are passed only to the movestart handlers.
var template = {
altKey: e.altKey,
ctrlKey: e.ctrlKey,
shiftKey: e.shiftKey,
startX: data.pageX,
startY: data.pageY,
distX: distX,
distY: distY,
deltaX: distX,
deltaY: distY,
pageX: touch.pageX,
pageY: touch.pageY,
velocityX: distX / time,
velocityY: distY / time,
identifier: data.identifier,
targetTouches: touches,
finger: touches ? touches.length : 1,
enableMove: function() {
this.moveEnabled = true;
this.enableMove = noop;
e.preventDefault();
}
};
// Trigger the movestart event.
trigger(data.target, 'movestart', template);
// Unbind handlers that tracked the touch or mouse up till now.
fn(data);
}
// Handlers that control what happens following a movestart
function activeMousemove(e, data) {
var timer = data.timer;
data.touch = e;
data.timeStamp = e.timeStamp;
timer.kick();
}
function activeMouseend(e, data) {
var target = data.target;
var event = data.event;
var timer = data.timer;
removeActiveMouse();
endEvent(target, event, timer, function() {
// Unbind the click suppressor, waiting until after mouseup
// has been handled.
setTimeout(function(){
off(target, 'click', preventDefault);
}, 0);
});
}
function removeActiveMouse() {
off(document, mouseevents.move, activeMousemove);
off(document, mouseevents.end, activeMouseend);
}
function activeTouchmove(e, data) {
var event = data.event;
var timer = data.timer;
var touch = changedTouch(e, event);
if (!touch) { return; }
// Stop the interface from gesturing
e.preventDefault();
event.targetTouches = e.targetTouches;
data.touch = touch;
data.timeStamp = e.timeStamp;
timer.kick();
}
function activeTouchend(e, data) {
var target = data.target;
var event = data.event;
var timer = data.timer;
var touch = identifiedTouch(e.changedTouches, event.identifier);
// This isn't the touch you're looking for.
if (!touch) { return; }
removeActiveTouch(data);
endEvent(target, event, timer);
}
function removeActiveTouch(data) {
off(document, touchevents.move, data.activeTouchmove);
off(document, touchevents.end, data.activeTouchend);
}
// Logic for triggering move and moveend events
function updateEvent(event, touch, timeStamp) {
var time = timeStamp - event.timeStamp;
event.distX = touch.pageX - event.startX;
event.distY = touch.pageY - event.startY;
event.deltaX = touch.pageX - event.pageX;
event.deltaY = touch.pageY - event.pageY;
// Average the velocity of the last few events using a decay
// curve to even out spurious jumps in values.
event.velocityX = 0.3 * event.velocityX + 0.7 * event.deltaX / time;
event.velocityY = 0.3 * event.velocityY + 0.7 * event.deltaY / time;
event.pageX = touch.pageX;
event.pageY = touch.pageY;
}
function endEvent(target, event, timer, fn) {
timer.end(function(){
trigger(target, 'moveend', event);
return fn && fn();
});
}
// Set up the DOM
function movestart(e) {
if (e.defaultPrevented) { return; }
if (!e.moveEnabled) { return; }
var event = {
startX: e.startX,
startY: e.startY,
pageX: e.pageX,
pageY: e.pageY,
distX: e.distX,
distY: e.distY,
deltaX: e.deltaX,
deltaY: e.deltaY,
velocityX: e.velocityX,
velocityY: e.velocityY,
identifier: e.identifier,
targetTouches: e.targetTouches,
finger: e.finger
};
var data = {
target: e.target,
event: event,
timer: new Timer(update),
touch: undefined,
timeStamp: e.timeStamp
};
function update(time) {
updateEvent(event, data.touch, data.timeStamp);
trigger(data.target, 'move', event);
}
if (e.identifier === undefined) {
// We're dealing with a mouse event.
// Stop clicks from propagating during a move
on(e.target, 'click', preventDefault);
on(document, mouseevents.move, activeMousemove, data);
on(document, mouseevents.end, activeMouseend, data);
}
else {
// In order to unbind correct handlers they have to be unique
data.activeTouchmove = function(e, data) { activeTouchmove(e, data); };
data.activeTouchend = function(e, data) { activeTouchend(e, data); };
// We're dealing with a touch.
on(document, touchevents.move, data.activeTouchmove, data);
on(document, touchevents.end, data.activeTouchend, data);
}
}
on(document, 'mousedown', mousedown);
on(document, 'touchstart', touchstart);
on(document, 'movestart', movestart);
// jQuery special events
//
// jQuery event objects are copies of DOM event objects. They need
// a little help copying the move properties across.
if (!window.jQuery) { return; }
var properties = ("startX startY pageX pageY distX distY deltaX deltaY velocityX velocityY").split(' ');
function enableMove1(e) { e.enableMove(); }
function enableMove2(e) { e.enableMove(); }
function enableMove3(e) { e.enableMove(); }
function add(handleObj) {
var handler = handleObj.handler;
handleObj.handler = function(e) {
// Copy move properties across from originalEvent
var i = properties.length;
var property;
while(i--) {
property = properties[i];
e[property] = e.originalEvent[property];
}
handler.apply(this, arguments);
};
}
jQuery.event.special.movestart = {
setup: function() {
// Movestart must be enabled to allow other move events
on(this, 'movestart', enableMove1);
// Do listen to DOM events
return false;
},
teardown: function() {
off(this, 'movestart', enableMove1);
return false;
},
add: add
};
jQuery.event.special.move = {
setup: function() {
on(this, 'movestart', enableMove2);
return false;
},
teardown: function() {
off(this, 'movestart', enableMove2);
return false;
},
add: add
};
jQuery.event.special.moveend = {
setup: function() {
on(this, 'movestart', enableMove3);
return false;
},
teardown: function() {
off(this, 'movestart', enableMove3);
return false;
},
add: add
};
});

View File

@ -0,0 +1,151 @@
(function($){
$.fn.twentytwenty = function(options) {
var options = $.extend({
default_offset_pct: 0.5,
orientation: 'horizontal',
before_label: 'Before',
after_label: 'After',
no_overlay: false,
move_slider_on_hover: false,
move_with_handle_only: true,
click_to_move: false
}, options);
return this.each(function() {
var sliderPct = options.default_offset_pct;
var container = $(this);
var sliderOrientation = options.orientation;
var beforeDirection = (sliderOrientation === 'vertical') ? 'down' : 'left';
var afterDirection = (sliderOrientation === 'vertical') ? 'up' : 'right';
container.wrap("<div class='twentytwenty-wrapper twentytwenty-" + sliderOrientation + "'></div>");
if(!options.no_overlay) {
container.append("<div class='twentytwenty-overlay'></div>");
var overlay = container.find(".twentytwenty-overlay");
overlay.append("<div class='twentytwenty-before-label' data-content='"+options.before_label+"'></div>");
overlay.append("<div class='twentytwenty-after-label' data-content='"+options.after_label+"'></div>");
}
var beforeImg = container.find("img:first");
var afterImg = container.find("img:last");
container.append("<div class='twentytwenty-handle'></div>");
var slider = container.find(".twentytwenty-handle");
slider.append("<span class='twentytwenty-" + beforeDirection + "-arrow'></span>");
slider.append("<span class='twentytwenty-" + afterDirection + "-arrow'></span>");
container.addClass("twentytwenty-container");
beforeImg.addClass("twentytwenty-before");
afterImg.addClass("twentytwenty-after");
var calcOffset = function(dimensionPct) {
var w = beforeImg.width();
var h = beforeImg.height();
return {
w: w+"px",
h: h+"px",
cw: (dimensionPct*w)+"px",
ch: (dimensionPct*h)+"px"
};
};
var adjustContainer = function(offset) {
if (sliderOrientation === 'vertical') {
beforeImg.css("clip", "rect(0,"+offset.w+","+offset.ch+",0)");
afterImg.css("clip", "rect("+offset.ch+","+offset.w+","+offset.h+",0)");
}
else {
beforeImg.css("clip", "rect(0,"+offset.cw+","+offset.h+",0)");
afterImg.css("clip", "rect(0,"+offset.w+","+offset.h+","+offset.cw+")");
}
container.css("height", offset.h);
};
var adjustSlider = function(pct) {
var offset = calcOffset(pct);
slider.css((sliderOrientation==="vertical") ? "top" : "left", (sliderOrientation==="vertical") ? offset.ch : offset.cw);
adjustContainer(offset);
};
// Return the number specified or the min/max number if it outside the range given.
var minMaxNumber = function(num, min, max) {
return Math.max(min, Math.min(max, num));
};
// Calculate the slider percentage based on the position.
var getSliderPercentage = function(positionX, positionY) {
var sliderPercentage = (sliderOrientation === 'vertical') ?
(positionY-offsetY)/imgHeight :
(positionX-offsetX)/imgWidth;
return minMaxNumber(sliderPercentage, 0, 1);
};
$(window).on("resize.twentytwenty", function(e) {
adjustSlider(sliderPct);
});
var offsetX = 0;
var offsetY = 0;
var imgWidth = 0;
var imgHeight = 0;
var onMoveStart = function(e) {
if (((e.distX > e.distY && e.distX < -e.distY) || (e.distX < e.distY && e.distX > -e.distY)) && sliderOrientation !== 'vertical') {
e.preventDefault();
}
else if (((e.distX < e.distY && e.distX < -e.distY) || (e.distX > e.distY && e.distX > -e.distY)) && sliderOrientation === 'vertical') {
e.preventDefault();
}
container.addClass("active");
offsetX = container.offset().left;
offsetY = container.offset().top;
imgWidth = beforeImg.width();
imgHeight = beforeImg.height();
};
var onMove = function(e) {
if (container.hasClass("active")) {
sliderPct = getSliderPercentage(e.pageX, e.pageY);
adjustSlider(sliderPct);
}
};
var onMoveEnd = function() {
container.removeClass("active");
};
var moveTarget = options.move_with_handle_only ? slider : container;
moveTarget.on("movestart",onMoveStart);
moveTarget.on("move",onMove);
moveTarget.on("moveend",onMoveEnd);
if (options.move_slider_on_hover) {
container.on("mouseenter", onMoveStart);
container.on("mousemove", onMove);
container.on("mouseleave", onMoveEnd);
}
slider.on("touchmove", function(e) {
e.preventDefault();
});
container.find("img").on("mousedown", function(event) {
event.preventDefault();
});
if (options.click_to_move) {
container.on('click', function(e) {
offsetX = container.offset().left;
offsetY = container.offset().top;
imgWidth = beforeImg.width();
imgHeight = beforeImg.height();
sliderPct = getSliderPercentage(e.pageX, e.pageY);
adjustSlider(sliderPct);
});
}
$(window).trigger("resize.twentytwenty");
});
};
})(jQuery);

View File

@ -0,0 +1,79 @@
/*
* Style tweaks
* --------------------------------------------------
*/
html,
body {
overflow-x: hidden; /* Prevent scroll on narrow devices */
}
body {
padding-top: 70px;
}
footer {
padding: 30px 0;
}
table {
width: 100%;
border: 1px solid gray;
text-align: center;
}
table tr {
border-bottom: 1px solid gray;
}
table th {
padding: 0.5em;
border-bottom: 1px solid gray;
}
table td:first-child {
text-align: left;
}
table td {
padding: 1em;
border-right: 1px solid gray;
}
/*
* Off Canvas
* --------------------------------------------------
*/
@media screen and (max-width: 767px) {
.row-offcanvas {
position: relative;
-webkit-transition: all .25s ease-out;
-moz-transition: all .25s ease-out;
transition: all .25s ease-out;
}
.row-offcanvas-right {
right: 0;
}
.row-offcanvas-left {
left: 0;
}
.row-offcanvas-right
.sidebar-offcanvas {
right: -80%; /* 6 columns */
}
.row-offcanvas-left
.sidebar-offcanvas {
left: -80%; /* 6 columns */
}
.row-offcanvas-right.active {
right: 80%; /* 6 columns */
}
.row-offcanvas-left.active {
left: 80%; /* 6 columns */
}
.sidebar-offcanvas {
position: absolute;
top: 0;
width: 80%; /* 6 columns */
}
}

View File

@ -0,0 +1,206 @@
.twentytwenty-horizontal .twentytwenty-handle:before, .twentytwenty-horizontal .twentytwenty-handle:after, .twentytwenty-vertical .twentytwenty-handle:before, .twentytwenty-vertical .twentytwenty-handle:after {
content: " ";
display: block;
background: white;
position: absolute;
z-index: 30;
-webkit-box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5);
-moz-box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5);
box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5); }
.twentytwenty-horizontal .twentytwenty-handle:before, .twentytwenty-horizontal .twentytwenty-handle:after {
width: 3px;
height: 9999px;
left: 50%;
margin-left: -1.5px; }
.twentytwenty-vertical .twentytwenty-handle:before, .twentytwenty-vertical .twentytwenty-handle:after {
width: 9999px;
height: 3px;
top: 50%;
margin-top: -1.5px; }
.twentytwenty-before-label, .twentytwenty-after-label, .twentytwenty-overlay {
position: absolute;
top: 0;
width: 100%;
height: 100%; }
.twentytwenty-before-label, .twentytwenty-after-label, .twentytwenty-overlay {
-webkit-transition-duration: 0.5s;
-moz-transition-duration: 0.5s;
transition-duration: 0.5s; }
.twentytwenty-before-label, .twentytwenty-after-label {
-webkit-transition-property: opacity;
-moz-transition-property: opacity;
transition-property: opacity; }
.twentytwenty-before-label:before, .twentytwenty-after-label:before {
color: white;
font-size: 13px;
letter-spacing: 0.1em; }
.twentytwenty-before-label:before, .twentytwenty-after-label:before {
position: absolute;
background: rgba(255, 255, 255, 0.2);
line-height: 38px;
padding: 0 20px;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px; }
.twentytwenty-horizontal .twentytwenty-before-label:before, .twentytwenty-horizontal .twentytwenty-after-label:before {
top: 50%;
margin-top: -19px; }
.twentytwenty-vertical .twentytwenty-before-label:before, .twentytwenty-vertical .twentytwenty-after-label:before {
left: 50%;
margin-left: -45px;
text-align: center;
width: 90px; }
.twentytwenty-left-arrow, .twentytwenty-right-arrow, .twentytwenty-up-arrow, .twentytwenty-down-arrow {
width: 0;
height: 0;
border: 6px inset transparent;
position: absolute; }
.twentytwenty-left-arrow, .twentytwenty-right-arrow {
top: 50%;
margin-top: -6px; }
.twentytwenty-up-arrow, .twentytwenty-down-arrow {
left: 50%;
margin-left: -6px; }
.twentytwenty-container {
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
z-index: 0;
overflow: hidden;
position: relative;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none; }
.twentytwenty-container img {
max-width: 100%;
position: absolute;
top: 0;
display: block; }
.twentytwenty-container.active .twentytwenty-overlay, .twentytwenty-container.active :hover.twentytwenty-overlay {
background: rgba(0, 0, 0, 0); }
.twentytwenty-container.active .twentytwenty-overlay .twentytwenty-before-label,
.twentytwenty-container.active .twentytwenty-overlay .twentytwenty-after-label, .twentytwenty-container.active :hover.twentytwenty-overlay .twentytwenty-before-label,
.twentytwenty-container.active :hover.twentytwenty-overlay .twentytwenty-after-label {
opacity: 0; }
.twentytwenty-container * {
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box; }
.twentytwenty-before-label {
opacity: 0; }
.twentytwenty-before-label:before {
content: attr(data-content); }
.twentytwenty-after-label {
opacity: 0; }
.twentytwenty-after-label:before {
content: attr(data-content); }
.twentytwenty-horizontal .twentytwenty-before-label:before {
left: 10px; }
.twentytwenty-horizontal .twentytwenty-after-label:before {
right: 10px; }
.twentytwenty-vertical .twentytwenty-before-label:before {
top: 10px; }
.twentytwenty-vertical .twentytwenty-after-label:before {
bottom: 10px; }
.twentytwenty-overlay {
-webkit-transition-property: background;
-moz-transition-property: background;
transition-property: background;
background: rgba(0, 0, 0, 0);
z-index: 25; }
.twentytwenty-overlay:hover {
background: rgba(0, 0, 0, 0.5); }
.twentytwenty-overlay:hover .twentytwenty-after-label {
opacity: 1; }
.twentytwenty-overlay:hover .twentytwenty-before-label {
opacity: 1; }
.twentytwenty-before {
z-index: 20; }
.twentytwenty-after {
z-index: 10; }
.twentytwenty-handle {
height: 38px;
width: 38px;
position: absolute;
left: 50%;
top: 50%;
margin-left: -22px;
margin-top: -22px;
border: 3px solid white;
-webkit-border-radius: 1000px;
-moz-border-radius: 1000px;
border-radius: 1000px;
-webkit-box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5);
-moz-box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5);
box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5);
z-index: 40;
cursor: pointer; }
.twentytwenty-horizontal .twentytwenty-handle:before {
bottom: 50%;
margin-bottom: 22px;
-webkit-box-shadow: 0 3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
-moz-box-shadow: 0 3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
box-shadow: 0 3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); }
.twentytwenty-horizontal .twentytwenty-handle:after {
top: 50%;
margin-top: 22px;
-webkit-box-shadow: 0 -3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
-moz-box-shadow: 0 -3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
box-shadow: 0 -3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); }
.twentytwenty-vertical .twentytwenty-handle:before {
left: 50%;
margin-left: 22px;
-webkit-box-shadow: 3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
-moz-box-shadow: 3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
box-shadow: 3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); }
.twentytwenty-vertical .twentytwenty-handle:after {
right: 50%;
margin-right: 22px;
-webkit-box-shadow: -3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
-moz-box-shadow: -3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
box-shadow: -3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); }
.twentytwenty-left-arrow {
border-right: 6px solid white;
left: 50%;
margin-left: -17px; }
.twentytwenty-right-arrow {
border-left: 6px solid white;
right: 50%;
margin-right: -17px; }
.twentytwenty-up-arrow {
border-bottom: 6px solid white;
top: 50%;
margin-top: -17px; }
.twentytwenty-down-arrow {
border-top: 6px solid white;
bottom: 50%;
margin-bottom: -17px; }

87
run_tests.py Normal file
View File

@ -0,0 +1,87 @@
from __future__ import print_function
import os
import subprocess
import sys
TEST_SCENES = [
"pa4/tests/test-mesh.xml",
"pa4/tests/test-mesh-furnace.xml",
"pa5/tests/chi2test-microfacet.xml",
"pa5/tests/ttest-microfacet.xml",
"pa5/tests/test-direct.xml",
"pa5/tests/test-furnace.xml",
]
TEST_WARPS = [
("square", None),
("tent", None),
("disk", None),
("uniform_sphere", None),
("uniform_hemisphere", None),
("cosine_hemisphere", None),
("beckmann", 0.05),
("beckmann", 0.10),
("beckmann", 0.30),
("microfacet_brdf", 0.05),
("microfacet_brdf", 0.10),
("microfacet_brdf", 0.30),
("microfacet_brdf", (0.05, 0.5)),
("microfacet_brdf", (0.10, 0.5)),
("microfacet_brdf", (0.30, 0.5)),
]
def find_build_directory():
root = os.path.join(".")
if os.path.isfile(os.path.join(root, "nori")):
return root
root = os.path.dirname(root)
if os.path.isfile(os.path.join(root, "nori")):
return root
return os.path.join(root, "build")
def test_warps_and_scenes(scenes, warps):
total = len(scenes) + len(warps)
passed = 0
failed = []
build_dir = find_build_directory()
for t in scenes:
path = os.path.join("scenes", t)
ret = subprocess.call([os.path.join(build_dir, "nori"), path])
if ret == 0:
passed += 1
else:
failed.append(t)
for (warp_type, param) in warps:
args = [os.path.join(build_dir, "warptest"), warp_type]
if param is not None:
if not isinstance(param, (list, tuple)):
param = [param]
for p in param:
args.append(str(p))
ret = subprocess.call(args)
if ret == 0:
passed += 1
else:
failed.append(' '.join(args))
print("")
if passed < total:
print("\033[91m" + "Passed " + str(passed) + " / " + str(total) + " tests." + "\033[0m")
print("Failed tests:")
for t in failed:
print("\t" + str(t))
return False
else:
print("\033[92m" + "Passed " + str(passed) + " / " + str(total) + " tests." + "\033[0m")
print("\tGood job!")
return True
if __name__ == '__main__':
if not test_warps_and_scenes(TEST_SCENES, TEST_WARPS):
sys.exit(1)

4048
scenes/pa1/bunny.obj Normal file

File diff suppressed because it is too large Load Diff

32
scenes/pa1/bunny.xml Normal file
View File

@ -0,0 +1,32 @@
<scene>
<!-- Independent sample generator, one sample per pixel -->
<sampler type="independent">
<integer name="sampleCount" value="1"/>
</sampler>
<!-- Render the visible surface normals -->
<integrator type="normals"/>
<!-- Load the Stanford bunny (https://graphics.stanford.edu/data/3Dscanrep/) -->
<mesh type="obj">
<string name="filename" value="bunny.obj"/>
<bsdf type="diffuse"/>
</mesh>
<!-- Render the scene viewed by a perspective camera -->
<camera type="perspective">
<!-- 3D origin, target point, and 'up' vector -->
<transform name="toWorld">
<lookat target="-0.0123771, 0.0540913, -0.239922"
origin="-0.0315182, 0.284011, 0.7331"
up="0.00717446, 0.973206, -0.229822"/>
</transform>
<!-- Field of view: 30 degrees -->
<float name="fov" value="16"/>
<!-- 768 x 768 pixels -->
<integer name="width" value="768"/>
<integer name="height" value="768"/>
</camera>
</scene>

View File

@ -0,0 +1,33 @@
<scene>
<!-- Independent sample generator, 32 samples per pixel -->
<sampler type="independent">
<integer name="sampleCount" value="32"/>
</sampler>
<!-- Render the visible surface normals -->
<integrator type="normals"/>
<!-- Load the Ajax bust (a freely available scan from the Jotero forum) -->
<mesh type="obj">
<string name="filename" value="ajax.obj"/>
<bsdf type="diffuse"/>
</mesh>
<!-- Render the scene viewed by a perspective camera -->
<camera type="perspective">
<!-- 3D origin, target point, and 'up' vector -->
<transform name="toWorld">
<lookat target="-64.8161, 47.2211, 23.8576"
origin="-65.6055, 47.5762, 24.3583"
up="0.299858, 0.934836, -0.190177"/>
</transform>
<!-- Field of view: 30 degrees -->
<float name="fov" value="30"/>
<!-- 768 x 768 pixels -->
<integer name="width" value="768"/>
<integer name="height" value="768"/>
</camera>
</scene>

33
scenes/pa3/ajax-ao.xml Normal file
View File

@ -0,0 +1,33 @@
<scene>
<!-- Independent sample generator, 32 samples per pixel -->
<sampler type="independent">
<integer name="sampleCount" value="512"/>
</sampler>
<!-- Use the ambient occlusion integrator -->
<integrator type="ao"/>
<!-- Load the Ajax bust (a freely available scan from the Jotero forum) -->
<mesh type="obj">
<string name="filename" value="ajax.obj"/>
<bsdf type="diffuse"/>
</mesh>
<!-- Render the scene viewed by a perspective camera -->
<camera type="perspective">
<!-- 3D origin, target point, and 'up' vector -->
<transform name="toWorld">
<lookat target="-64.8161, 47.2211, 23.8576"
origin="-65.6055, 47.5762, 24.3583"
up="0.299858, 0.934836, -0.190177"/>
</transform>
<!-- Field of view: 30 degrees -->
<float name="fov" value="30"/>
<!-- 768 x 768 pixels -->
<integer name="width" value="768"/>
<integer name="height" value="768"/>
</camera>
</scene>

View File

@ -0,0 +1,36 @@
<scene>
<!-- Independent sample generator, 32 samples per pixel -->
<sampler type="independent">
<integer name="sampleCount" value="32"/>
</sampler>
<!-- Use the simple point light integrator -->
<integrator type="simple">
<point name="position" value="-20, 40, 20"/>
<color name="energy" value="3.76e4, 3.76e4, 3.76e4"/>
</integrator>
<!-- Load the Ajax bust (a freely available scan from the Jotero forum) -->
<mesh type="obj">
<string name="filename" value="ajax.obj"/>
<bsdf type="diffuse"/>
</mesh>
<!-- Render the scene viewed by a perspective camera -->
<camera type="perspective">
<!-- 3D origin, target point, and 'up' vector -->
<transform name="toWorld">
<lookat target="-64.8161, 47.2211, 23.8576"
origin="-65.6055, 47.5762, 24.3583"
up="0.299858, 0.934836, -0.190177"/>
</transform>
<!-- Field of view: 30 degrees -->
<float name="fov" value="30"/>
<!-- 768 x 768 pixels -->
<integer name="width" value="768"/>
<integer name="height" value="768"/>
</camera>
</scene>

View File

@ -0,0 +1,64 @@
<?xml version='1.0' encoding='utf-8'?>
<scene>
<integrator type="whitted"/>
<camera type="perspective">
<float name="fov" value="27.7856"/>
<transform name="toWorld">
<scale value="-1,1,1"/>
<lookat target="0, 0.893051, 4.41198" origin="0, 0.919769, 5.41159" up="0, 1, 0"/>
</transform>
<integer name="height" value="600"/>
<integer name="width" value="800"/>
</camera>
<sampler type="independent">
<integer name="sampleCount" value="512"/>
</sampler>
<mesh type="obj">
<string name="filename" value="meshes/walls.obj"/>
<bsdf type="diffuse">
<color name="albedo" value="0.725 0.71 0.68"/>
</bsdf>
</mesh>
<mesh type="obj">
<string name="filename" value="meshes/rightwall.obj"/>
<bsdf type="diffuse">
<color name="albedo" value="0.161 0.133 0.427"/>
</bsdf>
</mesh>
<mesh type="obj">
<string name="filename" value="meshes/leftwall.obj"/>
<bsdf type="diffuse">
<color name="albedo" value="0.630 0.065 0.05"/>
</bsdf>
</mesh>
<mesh type="obj">
<string name="filename" value="meshes/sphere1.obj"/>
<bsdf type="diffuse"/>
</mesh>
<mesh type="obj">
<string name="filename" value="meshes/sphere2.obj"/>
<bsdf type="diffuse"/>
</mesh>
<mesh type="obj">
<string name="filename" value="meshes/light.obj"/>
<emitter type="area">
<color name="radiance" value="40 40 40"/>
</emitter>
</mesh>
</scene>

View File

@ -0,0 +1,64 @@
<?xml version='1.0' encoding='utf-8'?>
<scene>
<integrator type="whitted"/>
<camera type="perspective">
<float name="fov" value="27.7856"/>
<transform name="toWorld">
<scale value="-1,1,1"/>
<lookat target="0, 0.893051, 4.41198" origin="0, 0.919769, 5.41159" up="0, 1, 0"/>
</transform>
<integer name="height" value="600"/>
<integer name="width" value="800"/>
</camera>
<sampler type="independent">
<integer name="sampleCount" value="512"/>
</sampler>
<mesh type="obj">
<string name="filename" value="meshes/walls.obj"/>
<bsdf type="diffuse">
<color name="albedo" value="0.725 0.71 0.68"/>
</bsdf>
</mesh>
<mesh type="obj">
<string name="filename" value="meshes/rightwall.obj"/>
<bsdf type="diffuse">
<color name="albedo" value="0.161 0.133 0.427"/>
</bsdf>
</mesh>
<mesh type="obj">
<string name="filename" value="meshes/leftwall.obj"/>
<bsdf type="diffuse">
<color name="albedo" value="0.630 0.065 0.05"/>
</bsdf>
</mesh>
<mesh type="obj">
<string name="filename" value="meshes/sphere1.obj"/>
<bsdf type="mirror"/>
</mesh>
<mesh type="obj">
<string name="filename" value="meshes/sphere2.obj"/>
<bsdf type="dielectric"/>
</mesh>
<mesh type="obj">
<string name="filename" value="meshes/light.obj"/>
<emitter type="area">
<color name="radiance" value="40 40 40"/>
</emitter>
</mesh>
</scene>

View File

@ -0,0 +1,13 @@
# Blender v2.72 (sub 0) OBJ File: ''
# www.blender.org
mtllib leftwall.mtl
o leftWall
v -1.020000 1.590000 -1.040000
v -1.020000 1.590000 0.990000
v -1.010000 -0.000000 0.990000
v -0.990000 0.000000 -1.040000
vt 0.000000 1.000000
usemtl leftWall
s 1
f 1/1 2/1 3/1
f 4/1 1/1 3/1

View File

@ -0,0 +1,13 @@
# Blender v2.72 (sub 0) OBJ File: ''
# www.blender.org
mtllib light.mtl
o light
v 0.230000 1.580000 -0.220000
v 0.230000 1.580000 0.160000
v -0.240000 1.580000 0.160000
v -0.240000 1.580000 -0.220000
vt 0.000000 1.000000
usemtl light
s 1
f 1/1 2/1 3/1
f 4/1 1/1 3/1

View File

@ -0,0 +1,13 @@
# Blender v2.72 (sub 0) OBJ File: ''
# www.blender.org
mtllib rightwall.mtl
o rightWall
v 1.000000 1.590000 0.990000
v 1.000000 1.590000 -1.040000
v 1.000000 0.000000 -1.040000
v 1.000000 -0.000000 0.990000
vt 0.000000 1.000000
usemtl rightWall
s 1
f 1/1 2/1 3/1
f 4/1 1/1 3/1

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,33 @@
# Blender v2.72 (sub 0) OBJ File: ''
# www.blender.org
mtllib walls.mtl
o ceiling
v 1.000000 1.590000 -1.040000
v 1.000000 1.590000 0.990000
v -1.020000 1.590000 0.990000
v -1.020000 1.590000 -1.040000
vt 0.000000 1.000000
usemtl ceiling
s 1
f 1/1 2/1 3/1
f 4/1 1/1 3/1
o floor
v 1.000000 0.000000 -1.040000
v -0.990000 0.000000 -1.040000
v -1.010000 -0.000000 0.990000
v 1.000000 -0.000000 0.990000
vt 0.000000 1.000000
usemtl floor
s 1
f 5/2 6/2 7/2
f 8/2 5/2 7/2
o backWall
v 1.000000 1.590000 -1.040000
v -1.020000 1.590000 -1.040000
v -0.990000 0.000000 -1.040000
v 1.000000 0.000000 -1.040000
vt 0.000000 1.000000
usemtl backWall
s 1
f 9/3 10/3 11/3
f 12/3 9/3 11/3

View File

@ -0,0 +1,56 @@
<scene>
<integrator type="whitted"/>
<sampler type="independent">
<!-- You might want to reduce this number during development -->
<integer name="sampleCount" value="4096"/>
<!-- <integer name="sampleCount" value="16"/> -->
</sampler>
<camera type="perspective">
<!-- Camera-to-world coordinate system transformation -->
<transform name="toWorld">
<lookat origin="2.668, 1.078, 6.668" target="-0.841,0.85,-0.051" up="0,1,0"/>
</transform>
<float name="fov" value="60"/>
<integer name="width" value="768"/>
<integer name="height" value="384"/>
</camera>
<mesh type="obj">
<!--
Pineapple SVG:
CC BY-NC 4.0 Licence
"Pineapple Black And White" (http://clipartmag.com/pineapple-black-and-white)
http://clipartmag.com/pineapple-black-and-white#pineapple-black-and-white-2.jpg
-->
<string name="filename" value="meshes/logo.obj"/>
<bsdf type="dielectric"/>
</mesh>
<mesh type="obj">
<string name="filename" value="meshes/floor.obj"/>
<bsdf type="diffuse">
<color name="albedo" value="0.5, 0.5, 0.5"/>
</bsdf>
<transform name="toWorld">
<scale value="6, 6, 6"/>
</transform>
</mesh>
<mesh type="obj">
<string name="filename" value="meshes/light1.obj"/>
<emitter type="area">
<color name="radiance" value="60, 200, 120"/>
</emitter>
</mesh>
<mesh type="obj">
<string name="filename" value="meshes/light2.obj"/>
<emitter type="area">
<color name="radiance" value="80, 80, 800"/>
</emitter>
</mesh>
</scene>

View File

@ -0,0 +1,58 @@
<scene>
<integrator type="whitted"/>
<sampler type="independent">
<!-- You might want to reduce this number during development -->
<integer name="sampleCount" value="4096"/>
<!-- <integer name="sampleCount" value="16"/> -->
</sampler>
<camera type="perspective">
<!-- Camera-to-world coordinate system transformation -->
<transform name="toWorld">
<lookat origin="2.668, 1.078, 6.668" target="-0.841,0.85,-0.051" up="0,1,0"/>
</transform>
<float name="fov" value="60"/>
<integer name="width" value="768"/>
<integer name="height" value="384"/>
</camera>
<mesh type="obj">
<!--
Pineapple SVG:
CC BY-NC 4.0 Licence
"Pineapple Black And White" (http://clipartmag.com/pineapple-black-and-white)
http://clipartmag.com/pineapple-black-and-white#pineapple-black-and-white-2.jpg
-->
<string name="filename" value="meshes/logo.obj"/>
<bsdf type="diffuse">
<color name="albedo" value="0.5, 0.5, 0.5"/>
</bsdf>
</mesh>
<mesh type="obj">
<string name="filename" value="meshes/floor.obj"/>
<bsdf type="diffuse">
<color name="albedo" value="0.5, 0.5, 0.5"/>
</bsdf>
<transform name="toWorld">
<scale value="6, 6, 6"/>
</transform>
</mesh>
<mesh type="obj">
<string name="filename" value="meshes/light1.obj"/>
<emitter type="area">
<color name="radiance" value="60, 200, 120"/>
</emitter>
</mesh>
<mesh type="obj">
<string name="filename" value="meshes/light2.obj"/>
<emitter type="area">
<color name="radiance" value="80, 80, 800"/>
</emitter>
</mesh>
</scene>

View File

@ -0,0 +1,147 @@
v -9.2 -4.5 3.2
v -9.2 -4.48421 2.68512
v -9.2 -4.43692 2.17227
v -9.2 -4.3583 1.66347
v -9.2 -4.24867 1.16074
v -9.2 -4.10845 0.666061
v -9.2 -3.93821 0.181379
v -9.2 -3.73862 -0.29139
v -9.2 -3.51045 -0.75038
v -9.2 -3.25462 -1.19378
v -9.2 -2.97214 -1.61984
v -9.2 -2.66411 -2.02688
v -9.2 -2.33175 -2.41329
v -9.2 -1.97638 -2.77754
v -9.2 -1.59939 -3.11821
v -9.2 -1.20228 -3.43394
v -9.2 -0.786614 -3.72349
v -9.2 -0.354029 -3.98571
v -9.2 0.0937657 -4.21958
v -9.2 0.555004 -4.42417
v -9.2 1.02786 -4.59866
v -9.2 1.51048 -4.74238
v -9.2 2.00095 -4.85476
v -9.2 2.49733 -4.93534
v -9.2 2.99768 -4.98382
v -9.2 3.5 -5
v -9.2 4.00232 -4.98382
v -9.2 4.50267 -4.93534
v -9.2 4.99905 -4.85476
v -9.2 5.48952 -4.74238
v -9.2 5.97214 -4.59866
v -9.2 6.445 -4.42417
v -9.2 6.90623 -4.21958
v -9.2 7.35403 -3.98571
v -9.2 7.78661 -3.72349
v -9.2 -4.48421 3.71488
v 10.8 -4.5 3.2
v 10.8 -4.48421 2.68512
v 10.8 -4.43692 2.17227
v 10.8 -4.3583 1.66347
v 10.8 -4.24867 1.16074
v 10.8 -4.10845 0.666061
v 10.8 -3.93821 0.181379
v 10.8 -3.73862 -0.29139
v 10.8 -3.51045 -0.75038
v 10.8 -3.25462 -1.19378
v 10.8 -2.97214 -1.61984
v 10.8 -2.66411 -2.02688
v 10.8 -2.33175 -2.41329
v 10.8 -1.97638 -2.77754
v 10.8 -1.59939 -3.11821
v 10.8 -1.20228 -3.43394
v 10.8 -0.786614 -3.72349
v 10.8 -0.354029 -3.98571
v 10.8 0.0937657 -4.21958
v 10.8 0.555004 -4.42417
v 10.8 1.02786 -4.59866
v 10.8 1.51048 -4.74238
v 10.8 2.00095 -4.85476
v 10.8 2.49733 -4.93534
v 10.8 2.99768 -4.98382
v 10.8 3.5 -5
v 10.8 4.00232 -4.98382
v 10.8 4.50267 -4.93534
v 10.8 4.99905 -4.85476
v 10.8 5.48952 -4.74238
v 10.8 5.97214 -4.59866
v 10.8 6.445 -4.42417
v 10.8 6.90623 -4.21958
v 10.8 7.35403 -3.98571
v 10.8 7.78661 -3.72349
v 10.8 -4.48421 3.71488
vn 1 0 0
vn 0 1 0
vn 0 0.998121 0.0612671
vn 0 0.99249 0.122328
vn 0 0.983118 0.182975
vn 0 0.970027 0.242998
vn 0 0.953248 0.302189
vn 0 0.932821 0.360339
vn 0 0.908798 0.417237
vn 0 0.881239 0.472672
vn 0 0.850218 0.52643
vn 0 0.815823 0.578301
vn 0 0.778154 0.628074
vn 0 0.737325 0.675539
vn 0 0.693466 0.72049
vn 0 0.646723 0.762725
vn 0 0.59726 0.802047
vn 0 0.545255 0.83827
vn 0 0.490904 0.871214
vn 0 0.434418 0.900712
vn 0 0.376024 0.92661
vn 0 0.315965 0.948771
vn 0 0.254497 0.967074
vn 0 0.191886 0.981417
vn 0 0.128409 0.991721
vn 0 0.0643503 0.997927
vn 0 -7.45445e-09 1
vn 0 -0.0643503 0.997927
vn 0 -0.128409 0.991721
vn 0 -0.191886 0.981417
vn 0 -0.254497 0.967074
vn 0 -0.315965 0.948771
vn 0 -0.376024 0.92661
vn 0 -0.434418 0.900712
vn 0 -0.490903 0.871214
vn 0 -0.518378 0.855152
vn 0 0.99953 -0.0306454
vn -1 0 0
f 1//1 2//1 3//1 4//1 5//1 6//1 7//1 8//1 9//1 10//1 11//1 12//1 13//1 14//1 15//1 16//1 17//1 18//1 19//1 20//1 21//1 22//1 23//1 24//1 25//1 26//1 27//1 28//1 29//1 30//1 31//1 32//1 33//1 34//1 35//1 36//1
f 37//2 38//3 2//3 1//2
f 38//3 39//4 3//4 2//3
f 39//4 40//5 4//5 3//4
f 40//5 41//6 5//6 4//5
f 41//6 42//7 6//7 5//6
f 42//7 43//8 7//8 6//7
f 43//8 44//9 8//9 7//8
f 44//9 45//10 9//10 8//9
f 45//10 46//11 10//11 9//10
f 46//11 47//12 11//12 10//11
f 47//12 48//13 12//13 11//12
f 48//13 49//14 13//14 12//13
f 49//14 50//15 14//15 13//14
f 50//15 51//16 15//16 14//15
f 51//16 52//17 16//17 15//16
f 52//17 53//18 17//18 16//17
f 53//18 54//19 18//19 17//18
f 54//19 55//20 19//20 18//19
f 55//20 56//21 20//21 19//20
f 56//21 57//22 21//22 20//21
f 57//22 58//23 22//23 21//22
f 58//23 59//24 23//24 22//23
f 59//24 60//25 24//25 23//24
f 60//25 61//26 25//26 24//25
f 61//26 62//27 26//27 25//26
f 62//27 63//28 27//28 26//27
f 63//28 64//29 28//29 27//28
f 64//29 65//30 29//30 28//29
f 65//30 66//31 30//31 29//30
f 66//31 67//32 31//32 30//31
f 67//32 68//33 32//33 31//32
f 68//33 69//34 33//34 32//33
f 69//34 70//35 34//35 33//34
f 70//35 71//36 35//36 34//35
f 72//37 37//2 1//2 36//37
f 37//38 72//38 71//38 70//38 69//38 68//38 67//38 66//38 65//38 64//38 63//38 62//38 61//38 60//38 59//38 58//38 57//38 56//38 55//38 54//38 53//38 52//38 51//38 50//38 49//38 48//38 47//38 46//38 45//38 44//38 43//38 42//38 41//38 40//38 39//38 38//38

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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