├── .dockerignore ├── .gitattributes ├── .gitignore ├── .gitmodules ├── .lmenv_docker ├── .pylintrc ├── .travis.yml ├── CMakeLists.txt ├── Dockerfile ├── Dockerfile.conda ├── LICENSE ├── MANIFEST.in ├── README.md ├── cmake ├── LmAddPlugin.cmake ├── LmCopyDll.cmake ├── PrecompiledHeader.cmake └── lightmetricaConfig.cmake.in ├── conda ├── bld.bat ├── build.sh ├── conda_build_config.yaml └── meta.yaml ├── doc ├── Doxyfile ├── Makefile ├── _static │ └── example │ │ ├── blank.jpg │ │ ├── custom_material.jpg │ │ ├── custom_renderer.jpg │ │ ├── pt_cornell_box.jpg │ │ ├── pt_fireplace_room.jpg │ │ ├── quad.jpg │ │ ├── raycast_cornell_box.jpg │ │ └── raycast_fireplace_room.jpg ├── api_ref.rst ├── basic_rendering.rst ├── build.rst ├── build_system.rst ├── changelog.rst ├── component.rst ├── component_ref.rst ├── conf.py ├── example.rst ├── extending_framework.rst ├── functest.rst ├── index.rst ├── intro.rst ├── make.bat ├── managing_experiment.rst ├── path_sampling.rst ├── perftest.rst ├── python_binding.rst └── test.rst ├── environment.yml ├── example ├── CMakeLists.txt ├── custom_renderer.cpp └── pt.cpp ├── functest ├── CMakeLists.txt ├── __init__.py ├── example_blank.py ├── example_cpp.py ├── example_custom_renderer.py ├── example_pt.py ├── example_quad.py ├── example_raycast.py ├── example_serialization.py ├── func_accel_consistency.py ├── func_error_handling.py ├── func_lights.py ├── func_materials.py ├── func_obj_loader_consistency.py ├── func_render_all.py ├── func_render_instancing.py ├── func_renderers.py ├── func_scheduler.py ├── func_serial_consistency.py ├── func_update_asset.py ├── lmenv.py ├── lmscene.py ├── perf_accel.py ├── perf_obj_loader.py ├── perf_serial.py ├── renderer_ao.cpp ├── run_all.py └── util.py ├── include └── lm │ ├── accel.h │ ├── assetgroup.h │ ├── bidir.h │ ├── camera.h │ ├── common.h │ ├── component.h │ ├── core.h │ ├── debug.h │ ├── exception.h │ ├── film.h │ ├── json.h │ ├── jsontype.h │ ├── light.h │ ├── lm.h │ ├── logger.h │ ├── loggercontext.h │ ├── material.h │ ├── math.h │ ├── medium.h │ ├── mesh.h │ ├── model.h │ ├── objloader.h │ ├── parallel.h │ ├── parallelcontext.h │ ├── path.h │ ├── phase.h │ ├── progress.h │ ├── progresscontext.h │ ├── pylm.h │ ├── renderer.h │ ├── scene.h │ ├── scenenode.h │ ├── scheduler.h │ ├── serial.h │ ├── serialtype.h │ ├── surface.h │ ├── texture.h │ ├── timer.h │ ├── user.h │ ├── version.h │ └── volume.h ├── lightmetrica └── __init__.py ├── lightmetrica_jupyter └── __init__.py ├── pch ├── pch.cpp ├── pch.h ├── pch_pylm.cpp └── pch_pylm.h ├── plugin ├── CMakeLists.txt ├── accel_embree │ ├── CMakeLists.txt │ ├── accel_embree.cpp │ ├── accel_embree_instanced.cpp │ └── embree_params.h ├── accel_nanort │ ├── CMakeLists.txt │ └── accel_nanort.cpp ├── model_pbrt │ ├── CMakeLists.txt │ └── model_pbrt.cpp ├── objloader_tinyobjloader │ ├── CMakeLists.txt │ └── objloader_tinyobjloader.cpp └── volume_openvdb │ ├── CMakeLists.txt │ ├── renderer_volraycast.cpp │ └── volume_openvdb.cpp ├── pytest ├── CMakeLists.txt ├── conftest.py ├── pylm_test.cpp ├── pylm_test_component.cpp ├── pylm_test_json.cpp ├── pylm_test_math.cpp ├── pylm_test_simple.cpp ├── test_component.py ├── test_json.py ├── test_math.py └── test_simple.py ├── run_tests.py ├── setup.py ├── src ├── CMakeLists.txt ├── accel │ └── accel_sahbvh.cpp ├── assetgroup.cpp ├── camera │ └── camera_pinhole.cpp ├── component.cpp ├── debug.cpp ├── exception.cpp ├── ext │ ├── README.md │ └── rang.hpp ├── film │ └── film_bitmap.cpp ├── light │ ├── light_area.cpp │ ├── light_directional.cpp │ ├── light_env.cpp │ ├── light_envconst.cpp │ └── light_point.cpp ├── logger.cpp ├── material │ ├── material_diffuse.cpp │ ├── material_glass.cpp │ ├── material_glossy.cpp │ ├── material_mask.cpp │ ├── material_mirror.cpp │ ├── material_mixture.cpp │ └── material_proxy.cpp ├── medium │ ├── medium_heterogeneous.cpp │ └── medium_homogeneous.cpp ├── mesh │ ├── mesh_raw.cpp │ └── mesh_wavefrontobj.cpp ├── model │ └── model_wavefrontobj.cpp ├── objloader │ ├── objloader.cpp │ └── objloader_simple.cpp ├── parallel │ ├── parallel.cpp │ └── parallel_openmp.cpp ├── phase │ ├── phase_hg.cpp │ └── phase_isotropic.cpp ├── progress.cpp ├── pylm.cpp ├── renderer │ ├── renderer_bdpt.cpp │ ├── renderer_bdptopt.cpp │ ├── renderer_blank.cpp │ ├── renderer_lt.cpp │ ├── renderer_pt.cpp │ ├── renderer_raycast.cpp │ └── renderer_volpt.cpp ├── scene.cpp ├── scheduler.cpp ├── serial.cpp ├── texture │ ├── texture_bitmap.cpp │ └── texture_constant.cpp ├── user.cpp ├── version.cpp ├── versiondef.h.in └── volume │ ├── volume_checker.cpp │ ├── volume_constant.cpp │ ├── volume_gaussian.cpp │ ├── volume_multi.cpp │ └── volume_sphere.cpp └── test ├── CMakeLists.txt ├── main.cpp ├── test_assets.cpp ├── test_common.cpp ├── test_common.h ├── test_component.cpp ├── test_exception.cpp ├── test_interface.h ├── test_interface_impl.cpp ├── test_json.cpp ├── test_logger.cpp ├── test_python.h.in └── test_serial.cpp /.dockerignore: -------------------------------------------------------------------------------- 1 | build* 2 | external* 3 | **/__pycache__ 4 | **/*.pyc 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.jpg filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # vscode 3 | .vscode 4 | 5 | # Images 6 | *.pfm 7 | 8 | # OS junk files 9 | [Tt]humbs.db 10 | *.DS_Store 11 | 12 | # Temporary files 13 | *.swp 14 | *.pyc 15 | *.tmp 16 | 17 | # VS 18 | *.ilk 19 | *.pdb 20 | 21 | # Project files 22 | .token 23 | [Bb]uild*/ 24 | _build/ 25 | [Rr]esults/ 26 | misc/ 27 | resources/ 28 | scenes/ 29 | experimental/ 30 | external/ 31 | external 32 | dist*/ 33 | .idea/ 34 | executed_functest*/ 35 | doc/_build 36 | jupyter 37 | *.serialized 38 | .lmenv 39 | .lmenv_debug 40 | .lmenv_linux 41 | *.ipynb 42 | functest/output 43 | functest/_lm_renderer_ao.py 44 | functest/_run_worker_process.py 45 | functest/blank.png 46 | functest/temp 47 | 48 | # https://github.com/github/gitignore/blob/master/Python.gitignore 49 | # Byte-compiled / optimized / DLL files 50 | __pycache__/ 51 | *.py[cod] 52 | *$py.class 53 | 54 | # C extensions 55 | *.so 56 | 57 | # Distribution / packaging 58 | .Python 59 | build/ 60 | develop-eggs/ 61 | dist/ 62 | downloads/ 63 | eggs/ 64 | .eggs/ 65 | lib/ 66 | lib64/ 67 | parts/ 68 | sdist/ 69 | var/ 70 | wheels/ 71 | *.egg-info/ 72 | .installed.cfg 73 | *.egg 74 | MANIFEST 75 | 76 | # PyInstaller 77 | # Usually these files are written by a python script from a template 78 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 79 | *.manifest 80 | *.spec 81 | 82 | # Installer logs 83 | pip-log.txt 84 | pip-delete-this-directory.txt 85 | 86 | # Unit test / coverage reports 87 | htmlcov/ 88 | .tox/ 89 | .nox/ 90 | .coverage 91 | .coverage.* 92 | .cache 93 | nosetests.xml 94 | coverage.xml 95 | *.cover 96 | .hypothesis/ 97 | .pytest_cache/ 98 | 99 | # Translations 100 | *.mo 101 | *.pot 102 | 103 | # Django stuff: 104 | *.log 105 | local_settings.py 106 | db.sqlite3 107 | 108 | # Flask stuff: 109 | instance/ 110 | .webassets-cache 111 | 112 | # Scrapy stuff: 113 | .scrapy 114 | 115 | # Sphinx documentation 116 | docs/_build/ 117 | 118 | # PyBuilder 119 | target/ 120 | 121 | # Jupyter Notebook 122 | .ipynb_checkpoints 123 | 124 | # IPython 125 | profile_default/ 126 | ipython_config.py 127 | 128 | # pyenv 129 | .python-version 130 | 131 | # celery beat schedule file 132 | celerybeat-schedule 133 | 134 | # SageMath parsed files 135 | *.sage.py 136 | 137 | # Environments 138 | .env 139 | .venv 140 | env/ 141 | venv/ 142 | ENV/ 143 | env.bak/ 144 | venv.bak/ 145 | 146 | # Spyder project settings 147 | .spyderproject 148 | .spyproject 149 | 150 | # Rope project settings 151 | .ropeproject 152 | 153 | # mkdocs documentation 154 | /site 155 | 156 | # mypy 157 | .mypy_cache/ 158 | .dmypy.json 159 | dmypy.json -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "plugin/objloader_tinyobjloader/tinyobjloader"] 2 | path = plugin/objloader_tinyobjloader/tinyobjloader 3 | url = https://github.com/syoyo/tinyobjloader.git 4 | [submodule "plugin/accel_nanort/nanort"] 5 | path = plugin/accel_nanort/nanort 6 | url = https://github.com/lighttransport/nanort.git 7 | -------------------------------------------------------------------------------- /.lmenv_docker: -------------------------------------------------------------------------------- 1 | { 2 | "path": "/lightmetrica-v3", 3 | "bin_path": "/lightmetrica-v3/_build/bin", 4 | "scene_path": "/lm3/scenes" 5 | } 6 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [TYPECHECK] 2 | 3 | ignored-modules=pylm_test_module -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | MAINTAINER Hisanari Otsu 3 | 4 | # Change default shell to bash 5 | SHELL ["/bin/bash", "-c"] 6 | 7 | # System packages 8 | ENV DEBIAN_FRONTEND=noninteractive 9 | RUN apt update && apt install -y \ 10 | tmux \ 11 | vim \ 12 | curl \ 13 | git \ 14 | git-lfs \ 15 | software-properties-common \ 16 | build-essential \ 17 | doxygen \ 18 | graphviz 19 | 20 | # Install miniconda 21 | WORKDIR / 22 | RUN curl -OJLs https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh 23 | RUN bash Miniconda3-latest-Linux-x86_64.sh -p /miniconda -b 24 | ENV PATH /miniconda/bin:$PATH 25 | 26 | # Setup lm3_dev environment 27 | COPY environment.yml environment.yml 28 | RUN conda env create -f environment.yml 29 | RUN echo "source activate lm3_dev" > ~/.bashrc 30 | ENV PATH /opt/conda/envs/lm3_dev/bin:$PATH 31 | 32 | # Install binary dependencies of imageio 33 | RUN source ~/.bashrc && imageio_download_bin freeimage 34 | 35 | # Build and install Lighmetrica 36 | COPY . /lightmetrica-v3 37 | WORKDIR /lightmetrica-v3 38 | RUN source ~/.bashrc && \ 39 | cmake -H. -B_build -DCMAKE_BUILD_TYPE=Release -DLM_BUILD_GUI_EXAMPLES=OFF && \ 40 | cmake --build _build --target install -- -j4 41 | 42 | # Run tests 43 | WORKDIR /lightmetrica-v3 44 | RUN source ~/.bashrc && \ 45 | python run_tests.py --lmenv .lmenv_docker 46 | 47 | # Default command 48 | CMD ["/bin/bash"] -------------------------------------------------------------------------------- /Dockerfile.conda: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | MAINTAINER Hisanari Otsu 3 | 4 | # Change default shell to bash 5 | SHELL ["/bin/bash", "-c"] 6 | 7 | # System packages 8 | ENV DEBIAN_FRONTEND=noninteractive 9 | RUN apt update && apt install -y \ 10 | tmux \ 11 | vim \ 12 | curl \ 13 | git \ 14 | git-lfs \ 15 | software-properties-common \ 16 | build-essential \ 17 | doxygen \ 18 | graphviz 19 | 20 | # Install miniconda 21 | WORKDIR / 22 | RUN curl -OJLs https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh 23 | RUN bash Miniconda3-latest-Linux-x86_64.sh -p /miniconda -b 24 | ENV PATH /miniconda/bin:$PATH 25 | 26 | # Setup lm3_dev environment 27 | COPY environment.yml environment.yml 28 | RUN conda env create -f environment.yml 29 | RUN echo "source activate lm3_dev" > ~/.bashrc 30 | ENV PATH /opt/conda/envs/lm3_dev/bin:$PATH 31 | 32 | # Install binary dependencies of imageio 33 | RUN source ~/.bashrc && imageio_download_bin freeimage 34 | 35 | # Expose port for Jupyter notebook 36 | EXPOSE 8888 37 | 38 | # Default command 39 | CMD ["/bin/bash"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Lightmetrica 2 | 3 | Copyright (c) 2019 Hisanari Otsu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-exclude __pycache__ *.pyc *pyo 2 | recursive-include lightmetrica *.pyd 3 | recursive-include lightmetrica *.so -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Lightmetrica -- A research-oriented renderer 2 | ==================== 3 | 4 | [![Build Status](https://travis-ci.com/lightmetrica/lightmetrica-v3.svg?branch=master)](https://travis-ci.com/lightmetrica/lightmetrica-v3) 5 | [![Documentation](https://img.shields.io/badge/docs-Sphinx-blue.svg)](https://lightmetrica.github.io/lightmetrica-v3-doc/) 6 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/lightmetrica/lightmetrica-v3/blob/master/LICENSE) 7 | 8 | 9 | *This project is under development for the initial release of Version 3. Please be careful using the versions in development because they might incur abrupt API changes.* 10 | 11 | ![teaser](doc/_static/example/pt_fireplace_room.jpg) 12 | 13 | **Lightmetrica** is a research-oriented renderer. The development of the framework is motivated by the goal to provide a practical environment for rendering research and development, where the researchers and developers need to tackle various challenging requirements through the development process. 14 | 15 | ## Quick start 16 | 17 | Install [miniconda](https://docs.conda.io/en/latest/miniconda.html) and type the following commands. 18 | 19 | ```bash 20 | $ conda create -n lm3 python=3.7 21 | $ conda activate lm3 22 | $ conda install lightmetrica -c hi2p-perim -c conda-forge 23 | $ python 24 | >>> import lightmetrica as lm 25 | >>> lm.init() 26 | >>> lm.info() 27 | ``` 28 | 29 | ## Documentation 30 | 31 | You can find the documentation from [here](https://lightmetrica.github.io/lightmetrica-v3-doc/). 32 | 33 | ## Features 34 | 35 | - Extension support 36 | - Component object model that allows to extend any interfaces as plugins 37 | - Serialization of component objects 38 | - Position-agnostic plugins 39 | - Verification support 40 | - Visual debugger 41 | - Performance measurements 42 | - Statistical verification 43 | - Orchestration support 44 | - Complete set of Python API 45 | - Jupyter notebook integration 46 | 47 | ## Author 48 | 49 | Hisanari Otsu ([Personal site](http://lightmetrica.org/h-otsu/), [Twitter](https://twitter.com/hisanari_otsu)) 50 | 51 | ## License 52 | 53 | This software is distributed under MIT License. For details, see LICENCE file. 54 | -------------------------------------------------------------------------------- /cmake/LmAddPlugin.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Lightmetrica - Copyright (c) 2018 Hisanari Otsu 3 | # Distributed under MIT license. See LICENSE file for details. 4 | # 5 | 6 | # Add a plugin 7 | function(lm_add_plugin) 8 | # Parse arguments 9 | cmake_parse_arguments(_ARG "" "NAME;INCLUDE_DIR" "HEADERS;SOURCES;LIBRARIES" ${ARGN}) 10 | 11 | # INTERFACE library for headers 12 | if (DEFINED _ARG_INCLUDE_DIR) 13 | set(_INTERFACE_DEFINED 1) 14 | add_library(${_ARG_NAME}_interface INTERFACE) 15 | add_library(${_ARG_NAME}::interface ALIAS ${_ARG_NAME}_interface) 16 | target_include_directories(${_ARG_NAME}_interface 17 | INTERFACE "$") 18 | else() 19 | set(_INTERFACE_DEFINED 0) 20 | endif() 21 | 22 | # MODULE library for the dynamic loaded library 23 | add_library(${_ARG_NAME} MODULE ${_ARG_HEADERS} ${_ARG_SOURCES}) 24 | target_link_libraries(${_ARG_NAME} 25 | PRIVATE 26 | liblm 27 | $<${_INTERFACE_DEFINED}:${_ARG_NAME}_interface> 28 | ${_ARG_LIBRARIES}) 29 | set_target_properties(${_ARG_NAME} PROPERTIES PREFIX "") 30 | set_target_properties(${_ARG_NAME} PROPERTIES DEBUG_POSTFIX "-debug") 31 | set_target_properties(${_ARG_NAME} PROPERTIES FOLDER "lm/plugin") 32 | set_target_properties(${_ARG_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") 33 | source_group("Header Files" FILES ${_ARG_HEADERS}) 34 | source_group("Source Files" FILES ${_ARG_SOURCES}) 35 | if (LM_INSTALL) 36 | install( 37 | TARGETS ${_ARG_NAME} 38 | EXPORT ${PROJECT_NAME}Targets 39 | LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR} 40 | ) 41 | endif() 42 | endfunction() 43 | -------------------------------------------------------------------------------- /cmake/LmCopyDll.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | # Distributed under MIT license. See LICENSE file for details. 4 | # 5 | 6 | # 7 | # CopyDLL 8 | # 9 | # This module provides automatic copy functions of dynamic libraries 10 | # for Visual Studio build environment in Windows. 11 | # 12 | 13 | if(WIN32) 14 | include(CMakeParseArguments) 15 | 16 | # 17 | # add_custom_command_copy_dll 18 | # 19 | # A custom command to copy a dynamic library 20 | # to the same directory as a library or an executable. 21 | # 22 | # Params 23 | # - NAME 24 | # + Library or executable name. 25 | # DLL files would be copied output directory of the specified library or executable. 26 | # - DLL 27 | # + Target DLL file 28 | # 29 | function(add_custom_command_copy_dll) 30 | cmake_parse_arguments(_ARG "" "TARGET;NAME;DLL" "" ${ARGN}) 31 | add_custom_command( 32 | TARGET ${_ARG_TARGET} 33 | PRE_BUILD 34 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 35 | ${_ARG_DLL} 36 | $) 37 | endfunction() 38 | 39 | # 40 | # add_custom_command_copy_dll_release_debug 41 | # 42 | # A custom command to copy a dynamic library 43 | # to the same directory as a library or an executable. 44 | # If you want to separate release and debug dlls, use this function 45 | # 46 | # Params 47 | # - NAME 48 | # + Library or executable name. 49 | # DLL files would be copied output directory of the specified library or executable. 50 | # - DLL_RELEASE 51 | # + Target DLL file (release) 52 | # - DLL_DEBUG 53 | # + Target DLL file (debug) 54 | # 55 | function(add_custom_command_copy_dll_release_debug) 56 | cmake_parse_arguments(_ARG "" "TARGET;NAME;DLL_RELEASE;DLL_DEBUG" "" ${ARGN}) 57 | add_custom_command( 58 | TARGET ${_ARG_TARGET} 59 | PRE_BUILD 60 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 61 | "$<$:${_ARG_DLL_RELEASE}>$<$:${_ARG_DLL_RELEASE}>$<$:${_ARG_DLL_DEBUG}>" 62 | "$") 63 | endfunction() 64 | endif() -------------------------------------------------------------------------------- /cmake/lightmetricaConfig.cmake.in: -------------------------------------------------------------------------------- 1 | # 2 | # Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | # Distributed under MIT license. See LICENSE file for details. 4 | # 5 | 6 | @PACKAGE_INIT@ 7 | 8 | # Avoid to include the target if it is already loaded. 9 | # This can happen if Lightmetrica is already loaded as a transitive 10 | # dependency of an other project. 11 | if (NOT TARGET lightmetrica::liblm) 12 | # Configuration files of glm is installed in lib64 directory 13 | set_property(GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS TRUE) 14 | 15 | # Find transitive dependencies 16 | include(CMakeFindDependencyMacro) 17 | find_dependency(cereal REQUIRED) 18 | find_dependency(glm REQUIRED) 19 | find_dependency(nlohmann_json REQUIRED) 20 | find_dependency(fmt REQUIRED) 21 | 22 | # Include targets 23 | include(${CMAKE_CURRENT_LIST_DIR}/lightmetricaTargets.cmake) 24 | endif() 25 | -------------------------------------------------------------------------------- /conda/bld.bat: -------------------------------------------------------------------------------- 1 | cmake -H. -B_build ^ 2 | -G "Ninja" ^ 3 | -D CMAKE_BUILD_TYPE=Release ^ 4 | -D CMAKE_INSTALL_PREFIX=%LIBRARY_PREFIX% ^ 5 | -D LM_BUILD_TESTS=OFF ^ 6 | -D LM_BUILD_EXAMPLES=OFF ^ 7 | -D CONDA_BUILD=1 8 | if errorlevel 1 exit 1 9 | 10 | cmake --build _build --target install 11 | if errorlevel 1 exit 1 12 | 13 | %PYTHON% -m pip install --no-deps --ignore-installed . 14 | if errorlevel 1 exit 1 15 | -------------------------------------------------------------------------------- /conda/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cmake -H. -B_build \ 4 | -D CMAKE_BUILD_TYPE=Release \ 5 | -D CMAKE_INSTALL_PREFIX=$PREFIX \ 6 | -D LM_BUILD_TESTS=OFF \ 7 | -D LM_BUILD_EXAMPLES=OFF \ 8 | -D CONDA_BUILD=1 9 | 10 | cmake --build _build --target install 11 | 12 | $PYTHON -m pip install --no-deps --ignore-installed . 13 | -------------------------------------------------------------------------------- /conda/conda_build_config.yaml: -------------------------------------------------------------------------------- 1 | c_compiler: 2 | - vs2017 # [win] 3 | - gcc # [linux] 4 | cxx_compiler: 5 | - vs2017 # [win] 6 | - gxx # [linux] 7 | target_platform: # [win] 8 | - win-64 # [win] -------------------------------------------------------------------------------- /conda/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: lightmetrica 3 | version: 3.0.0 4 | 5 | source: 6 | path: ../ 7 | 8 | build: 9 | number: 0 10 | 11 | requirements: 12 | build: 13 | - {{ compiler('cxx') }} 14 | - cmake 15 | host: 16 | - pip 17 | - python 18 | - doctest 19 | - embree3 20 | - glm 21 | - nlohmann_json=3.10.2 22 | - pybind11=2.7.1 23 | - cereal 24 | - fmt 25 | - stb 26 | - vdbloader 27 | - tbb-devel 28 | - pbrt-parser 29 | run: 30 | - zeromq 31 | - embree3 32 | - glm 33 | - nlohmann_json=3.10.2 34 | - cereal 35 | - fmt 36 | - numpy 37 | 38 | about: 39 | home: https://github.com/hi2p-perim/lightmetrica-v3 40 | summary: A research-oriented renderer 41 | license: MIT 42 | license_family: MIT 43 | license_file: LICENSE -------------------------------------------------------------------------------- /doc/Doxyfile: -------------------------------------------------------------------------------- 1 | PROJECT_NAME = lightmetrica-v3 2 | INPUT = ../include/lm/ ../src/ 3 | RECURSIVE = YES 4 | EXAMPLE_PATH = ../ 5 | EXCLUDE_PATTERNS = */src/ext/* */include/lm/ext/* 6 | 7 | GENERATE_HTML = YES 8 | GENERATE_LATEX = NO 9 | GENERATE_XML = YES 10 | HTML_OUTPUT = _build/doxygenhtml 11 | XML_OUTPUT = _build/doxygenxml 12 | XML_PROGRAMLISTING = YES 13 | 14 | EXTRACT_ALL = YES 15 | ENABLE_PREPROCESSING = YES 16 | MACRO_EXPANSION = YES 17 | #EXPAND_ONLY_PREDEF = YES 18 | PREDEFINED = "LM_PUBLIC_API=" \ 19 | "LM_INLINE=" 20 | #EXPAND_AS_DEFINED = LM_NAMESPACE_BEGIN 21 | 22 | ALIASES = "rst=\verbatim embed:rst" 23 | ALIASES += "endrst=\endverbatim" 24 | 25 | QUIET = YES 26 | WARNINGS = YES 27 | WARN_IF_UNDOCUMENTED = NO -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = LightmetricaV3Prototype 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /doc/_static/example/blank.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:26d7d66245d5499ba392403365d8a87109f6b32c7fc6993a2f21c5df58e65adf 3 | size 33267 4 | -------------------------------------------------------------------------------- /doc/_static/example/custom_material.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:d666f88380c90d4f3e2d0734852a0f9c530b06700e76f230f934210ea93054ed 3 | size 252924 4 | -------------------------------------------------------------------------------- /doc/_static/example/custom_renderer.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:03b91c1ccf9b23bb6dcbe27915f9c909800c6983c468c7c7821f9a4cf670431b 3 | size 161527 4 | -------------------------------------------------------------------------------- /doc/_static/example/pt_cornell_box.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:352c7fb6873ec1e3e18104b043e324bb88927c35b622e98c7a2a1d6501c7b7c1 3 | size 163871 4 | -------------------------------------------------------------------------------- /doc/_static/example/pt_fireplace_room.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:a20f08778f8347c18b610e4a81012944974c4928b86e40a6e86daf78497df9db 3 | size 276928 4 | -------------------------------------------------------------------------------- /doc/_static/example/quad.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:29ab5f24bbdfdaafc40b097da9d597691c0f17a60bca4d922b9525ed593f4b03 3 | size 35793 4 | -------------------------------------------------------------------------------- /doc/_static/example/raycast_cornell_box.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:7fa9c72168765feec990f8958d5267322b88aceb51f3c40e8a80def4590a6787 3 | size 52983 4 | -------------------------------------------------------------------------------- /doc/_static/example/raycast_fireplace_room.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:6970a4503448161b4f4a405ae97642623eacb138e6537d278a26499704a6752f 3 | size 223849 4 | -------------------------------------------------------------------------------- /doc/api_ref.rst: -------------------------------------------------------------------------------- 1 | .. _api_ref: 2 | 3 | API reference 4 | ############# 5 | 6 | User 7 | ====================== 8 | 9 | .. doxygengroup:: user 10 | :content-only: 11 | :members: 12 | 13 | Log 14 | ====================== 15 | 16 | .. doxygengroup:: log 17 | :content-only: 18 | :members: 19 | 20 | Exception 21 | ====================== 22 | 23 | .. doxygengroup:: exception 24 | :content-only: 25 | :members: 26 | 27 | Parallel 28 | ====================== 29 | 30 | .. doxygengroup:: parallel 31 | :content-only: 32 | :members: 33 | 34 | Progress 35 | ====================== 36 | 37 | .. doxygengroup:: progress 38 | :content-only: 39 | :members: 40 | 41 | Debug 42 | ====================== 43 | 44 | .. doxygengroup:: debug 45 | :content-only: 46 | :members: 47 | 48 | Json 49 | ====================== 50 | 51 | .. doxygengroup:: json 52 | :content-only: 53 | :members: 54 | 55 | Math 56 | ====================== 57 | 58 | .. doxygengroup:: math 59 | :content-only: 60 | :members: 61 | 62 | OBJ loader 63 | ====================== 64 | 65 | .. doxygengroup:: objloader 66 | :content-only: 67 | :members: 68 | 69 | Component 70 | ====================== 71 | 72 | .. doxygengroup:: comp 73 | :content-only: 74 | :members: 75 | 76 | Acceleration structure 77 | ====================== 78 | 79 | .. doxygengroup:: accel 80 | :content-only: 81 | :members: 82 | 83 | Scene 84 | ====================== 85 | 86 | .. doxygengroup:: scene 87 | :content-only: 88 | :members: 89 | 90 | Renderer 91 | ====================== 92 | 93 | .. doxygengroup:: renderer 94 | :content-only: 95 | :members: 96 | 97 | Camera 98 | ====================== 99 | 100 | .. doxygengroup:: camera 101 | :content-only: 102 | :members: 103 | 104 | Film 105 | ====================== 106 | 107 | .. doxygengroup:: film 108 | :content-only: 109 | :members: 110 | 111 | Light 112 | ====================== 113 | 114 | .. doxygengroup:: light 115 | :content-only: 116 | :members: 117 | 118 | Participating medium 119 | ====================== 120 | 121 | .. doxygengroup:: medium 122 | :content-only: 123 | :members: 124 | 125 | Volume data 126 | ====================== 127 | 128 | .. doxygengroup:: volume 129 | :content-only: 130 | :members: 131 | 132 | Material 133 | ====================== 134 | 135 | .. doxygengroup:: material 136 | :content-only: 137 | :members: 138 | 139 | Phase function 140 | ====================== 141 | 142 | .. doxygengroup:: phase 143 | :content-only: 144 | :members: 145 | 146 | Texture 147 | ====================== 148 | 149 | .. doxygengroup:: texture 150 | :content-only: 151 | :members: 152 | 153 | Mesh 154 | ====================== 155 | 156 | .. doxygengroup:: mesh 157 | :content-only: 158 | :members: 159 | 160 | Model 161 | ====================== 162 | 163 | .. doxygengroup:: model 164 | :content-only: 165 | :members: 166 | 167 | Path sampling 168 | ====================== 169 | 170 | .. doxygengroup:: path 171 | :content-only: 172 | :members: -------------------------------------------------------------------------------- /doc/changelog.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | =============== 3 | 4 | v3.0.0 (not yet to be released) 5 | ------------------------------------------ 6 | 7 | - Initial release 8 | -------------------------------------------------------------------------------- /doc/example.rst: -------------------------------------------------------------------------------- 1 | .. _example: 2 | 3 | Examples 4 | ######################## 5 | 6 | .. toctree:: 7 | :maxdepth: 1 8 | 9 | executed_functest/example_blank 10 | executed_functest/example_quad 11 | executed_functest/example_raycast 12 | executed_functest/example_pt 13 | executed_functest/example_custom_material 14 | executed_functest/example_custom_renderer 15 | executed_functest/example_serialization 16 | executed_functest/example_cpp -------------------------------------------------------------------------------- /doc/functest.rst: -------------------------------------------------------------------------------- 1 | .. _functest: 2 | 3 | Functional tests 4 | ######################## 5 | 6 | .. toctree:: 7 | :maxdepth: 1 8 | 9 | executed_functest/func_render_all 10 | executed_functest/func_accel_consistency 11 | executed_functest/func_error_handling 12 | executed_functest/func_obj_loader_consistency 13 | executed_functest/func_render_instancing 14 | executed_functest/func_serial_consistency 15 | executed_functest/func_update_asset 16 | executed_functest/func_scheduler 17 | executed_functest/func_materials 18 | executed_functest/func_lights 19 | executed_functest/func_renderers -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | .. Lightmetrica Version 3 documentation master file, created by 2 | sphinx-quickstart on Fri Feb 23 15:44:26 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Lightmetrica -- Research-oriented renderer 7 | ============================================ 8 | 9 | .. warning:: 10 | 11 | `This project is under development for the initial release of Version 3. Please be careful using the versions in development because they might incur abrupt API changes.` 12 | 13 | Welcome to Lightmetrica Version 3 documentation! 14 | This documentation includes **guide**, **examples and tests**, and **references**. 15 | 16 | **Guide** explains basic to advanced usage of the framework, for instance how to build the framework from the source or how to render the images, or how to extend the framework. 17 | 18 | **Example and tests** illustrates actual usage of the framework using Python/C++ API and the results of functional/performance/statistics tests. The examples and tests are written in a Jupyter notebook and the results are always executed in a CI build. It guarantees that the results are always generated from the working version of the framework. In other words, the broken documentation indicates a new commit ruined the runtime behavior of the framework. 19 | 20 | **References** describe the explanation of the detailed API and the components of the framework. API reference describes function-wise or interface-wise features of the framework. It is especially useful when you want develop your own extensions. Build-in component references describes the built-in components of the interfaces such as materials or renderers. It also explains a brief background of the theory behind the component. This reference is useful when you want to use the renderer as a user. 21 | 22 | ---- 23 | 24 | .. toctree:: 25 | :maxdepth: 1 26 | 27 | intro 28 | changelog 29 | 30 | .. toctree:: 31 | :maxdepth: 2 32 | :caption: Guide 33 | 34 | build 35 | managing_experiment 36 | basic_rendering 37 | extending_framework 38 | 39 | .. toctree:: 40 | :maxdepth: 2 41 | :caption: Advanced Topics 42 | 43 | build_system 44 | component 45 | python_binding 46 | path_sampling 47 | 48 | .. toctree:: 49 | :maxdepth: 2 50 | :caption: Examples and tests 51 | 52 | test 53 | example 54 | functest 55 | perftest 56 | 57 | .. toctree:: 58 | :maxdepth: 2 59 | :caption: References 60 | 61 | component_ref 62 | api_ref 63 | 64 | .. todolist:: 65 | 66 | Indices and tables 67 | ------------------- 68 | 69 | * :ref:`genindex` 70 | * :ref:`modindex` 71 | * :ref:`search` 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=LightmetricaV3Prototype 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /doc/perftest.rst: -------------------------------------------------------------------------------- 1 | .. _perftest: 2 | 3 | Performance tests 4 | ######################## 5 | 6 | .. toctree:: 7 | :maxdepth: 1 8 | 9 | executed_functest/perf_accel 10 | executed_functest/perf_obj_loader 11 | executed_functest/perf_serial -------------------------------------------------------------------------------- /doc/python_binding.rst: -------------------------------------------------------------------------------- 1 | Python binding 2 | ######################## 3 | 4 | Python API 5 | ==================================== 6 | 7 | The features of the framework can be directly accessible via Python API so that we can directly manipulate the renderer from the experimental codes. This Python binding is achieved with `pybind11 `_ library. 8 | 9 | We can manipulate the scene or organize the renderer jobs only with the Python API. Although we also expose C++ API as well as Python API, our recommendation is to use Python API as much as possible. We believe our Python API is flexible enough to cover the most of experimental workflow. Technically, we can even extend the renderer only with Python API, e.g., extending rendering techniques or materials, although we do not recommend it due to the massive performance loss. For detail, find corresponding examples in :ref:`functest`. 10 | 11 | Also, our Python API supports automatic type conversion of the types between the corresponding Python and C++ interfaces. Some of the internal types appeared in the C++ API are automatically converted to the corresponding types in Python. For instance, math types (``lm::Vec3``, ``lm::Mat4``) are converted to numpy array and Json type is converted to a ``dict``. 12 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: lm3_dev 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | - python=3.7 6 | - cmake 7 | - jupyter 8 | - matplotlib 9 | - scipy 10 | - pandas 11 | - sphinx 12 | - sphinx_rtd_theme 13 | - jupytext 14 | - tqdm 15 | - colorama 16 | - imageio 17 | - pytest 18 | - embree3 19 | - glm 20 | # Temporarily downgraded 21 | - pybind11=2.7.1 22 | - nlohmann_json=3.10.2 23 | - glew 24 | - tbb-devel 25 | - doctest 26 | - fmt 27 | - hi2p-perim::stb 28 | - hi2p-perim::cereal 29 | - hi2p-perim::vdbloader 30 | - hi2p-perim::pbrt-parser 31 | - pip: 32 | - sphinx-autobuild 33 | - breathe 34 | - sphinx_tabs 35 | - nbsphinx 36 | - nbmerge -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | # Distributed under MIT license. See LICENSE file for details. 4 | # 5 | 6 | # Helper function to add an example 7 | function(lm_add_example) 8 | # Parse arguments 9 | cmake_parse_arguments(_ARG "" "NAME" "SOURCES;LIBRARIES" ${ARGN}) 10 | 11 | # Executable 12 | add_executable(${_ARG_NAME} ${_ARG_SOURCES}) 13 | target_link_libraries(${_ARG_NAME} PRIVATE liblm ${_ARG_LIBRARIES}) 14 | set_target_properties(${_ARG_NAME} PROPERTIES FOLDER "lm/example") 15 | set_target_properties(${_ARG_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") 16 | source_group("Source Files" FILES ${_ARG_SOURCES}) 17 | endfunction() 18 | 19 | # ------------------------------------------------------------------------------------------------- 20 | 21 | # Non-GUI examples 22 | lm_add_example(NAME pt SOURCES "pt.cpp") 23 | lm_add_example(NAME custom_renderer SOURCES "custom_renderer.cpp") 24 | -------------------------------------------------------------------------------- /example/pt.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | 8 | /* 9 | Example of rendering an image with path tracing explaining basic usage of user APIs. 10 | 11 | Example: 12 | $ ./004_pt ./scenes/fireplace_room/fireplace_room.obj result.pfm \ 13 | 10 20 1920 1080 \ 14 | 5.101118 1.083746 -2.756308 \ 15 | 4.167568 1.078925 -2.397892 \ 16 | 43.001194 17 | */ 18 | int main(int argc, char** argv) { 19 | try { 20 | // Initialize the framework 21 | lm::init(); 22 | lm::parallel::init(lm::parallel::DefaultType, { 23 | #if LM_DEBUG_MODE 24 | {"num_threads", 1} 25 | #else 26 | {"num_threads", -1} 27 | #endif 28 | }); 29 | lm::info(); 30 | 31 | // Parse command line arguments 32 | const auto opt = lm::json::parse_positional_args<13>(argc, argv, R"({{ 33 | "obj": "{}", 34 | "out": "{}", 35 | "spp": {}, 36 | "max_verts": {}, 37 | "w": {}, 38 | "h": {}, 39 | "eye": [{},{},{}], 40 | "lookat": [{},{},{}], 41 | "vfov": {} 42 | }})"); 43 | 44 | // ---------------------------------------------------------------------------------------- 45 | 46 | // Define assets 47 | 48 | // Film for the rendered image 49 | const auto* film = lm::load("film1", "film::bitmap", { 50 | {"w", opt["w"]}, 51 | {"h", opt["h"]} 52 | }); 53 | 54 | // Pinhole camera 55 | const lm::Float w = opt["w"]; 56 | const lm::Float h = opt["h"]; 57 | const auto* camera = lm::load("camera1", "camera::pinhole", { 58 | {"film", film->loc()}, 59 | {"position", opt["eye"]}, 60 | {"center", opt["lookat"]}, 61 | {"up", {0,1,0}}, 62 | {"vfov", opt["vfov"]}, 63 | {"aspect", w/h} 64 | }); 65 | 66 | // OBJ model 67 | const auto* model = lm::load("obj1", "model::wavefrontobj", { 68 | {"path", opt["obj"]} 69 | }); 70 | 71 | // Define scene primitives 72 | const auto* accel = lm::load("accel", "accel::sahbvh", {}); 73 | auto* scene = lm::load("scene", "scene::default", { 74 | {"accel", accel->loc()} 75 | }); 76 | 77 | // Camera 78 | scene->add_primitive({ 79 | {"camera", camera->loc()} 80 | }); 81 | 82 | // Create primitives from model asset 83 | scene->add_primitive({ 84 | {"model", model->loc()} 85 | }); 86 | 87 | // Build acceleration structure 88 | scene->build(); 89 | 90 | // ---------------------------------------------------------------------------------------- 91 | 92 | // Render an image 93 | const auto* renderer = lm::load("renderer", "renderer::pt", { 94 | {"output", film->loc()}, 95 | {"scene", scene->loc()}, 96 | {"scheduler", "sample"}, 97 | {"spp", opt["spp"]}, 98 | {"max_verts", opt["max_verts"]} 99 | }); 100 | renderer->render(); 101 | 102 | // Save rendered image 103 | film->save(opt["out"]); 104 | 105 | // Shutdown the framework 106 | lm::shutdown(); 107 | } 108 | catch (const std::exception& e) { 109 | LM_ERROR("Runtime error: {}", e.what()); 110 | } 111 | 112 | return 0; 113 | } -------------------------------------------------------------------------------- /functest/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | # Distributed under MIT license. See LICENSE file for details. 4 | # 5 | 6 | include(LmAddPlugin) 7 | 8 | lm_add_plugin( 9 | NAME functest_renderer_ao 10 | SOURCES 11 | "renderer_ao.cpp") -------------------------------------------------------------------------------- /functest/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightmetrica/lightmetrica-v3/70601dbef13a513df032911d47f790791671a8e0/functest/__init__.py -------------------------------------------------------------------------------- /functest/example_cpp.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # cell_metadata_json: true 5 | # formats: ipynb,py:light 6 | # text_representation: 7 | # extension: .py 8 | # format_name: light 9 | # format_version: '1.5' 10 | # jupytext_version: 1.3.3 11 | # kernelspec: 12 | # display_name: Python 3 13 | # language: python 14 | # name: python3 15 | # --- 16 | 17 | # ## C++ examples 18 | # 19 | # This example executes the examples written with C++ API. 20 | 21 | import lmenv 22 | env = lmenv.load('.lmenv') 23 | 24 | import os 25 | import sys 26 | import subprocess as sp 27 | import numpy as np 28 | import imageio 29 | # %matplotlib inline 30 | import matplotlib.pyplot as plt 31 | 32 | outdir = './output' 33 | width = 1920 34 | height = 1080 35 | 36 | 37 | # + 38 | def run_example(ex, params): 39 | print('Executing example [name=%s]' % ex) 40 | sys.stdout.flush() 41 | # Execute the example executable 42 | # We convert backslashes as path separator in Windows environment 43 | # to slashes because subprocess might pass unescaped backslash. 44 | sp.call([ 45 | os.path.join(env.bin_path, ex) 46 | ] + [str(v).replace('\\', '/') for v in params]) 47 | 48 | def visualize(out): 49 | img = imageio.imread(out) 50 | f = plt.figure(figsize=(15,15)) 51 | ax = f.add_subplot(111) 52 | ax.imshow(np.clip(np.power(img,1/2.2),0,1), origin='lower') 53 | plt.show() 54 | 55 | 56 | # - 57 | 58 | # ### pt.cpp 59 | 60 | # + {"raw_mimetype": "text/restructuredtext", "active": ""} 61 | # Corresponding Python example: :ref:`example_pt` 62 | # - 63 | 64 | out = os.path.join(outdir, 'pt.pfm') 65 | run_example('pt', [ 66 | os.path.join(env.scene_path, 'fireplace_room/fireplace_room.obj'), 67 | out, 68 | 5, 20, 69 | width, height, 70 | 5.101118, 1.083746, -2.756308, 71 | 4.167568, 1.078925, -2.397892, 72 | 43.001194 73 | ]) 74 | visualize(out) 75 | 76 | out = os.path.join(outdir, 'pt2.pfm') 77 | run_example('pt', [ 78 | os.path.join(env.scene_path, 'cornell_box/CornellBox-Sphere.obj'), 79 | out, 80 | 5, 20, 81 | width, height, 82 | 0, 1, 5, 83 | 0, 1, 0, 84 | 30 85 | ]) 86 | visualize(out) 87 | 88 | # ### custom_renderer.cpp 89 | 90 | # + {"raw_mimetype": "text/restructuredtext", "active": ""} 91 | # Corresponding Python example: :ref:`example_custom_renderer` 92 | # - 93 | 94 | out = os.path.join(outdir, 'custom_renderer.pfm') 95 | run_example('custom_renderer', [ 96 | os.path.join(env.scene_path, 'fireplace_room/fireplace_room.obj'), 97 | out, 98 | 5, 99 | width, height, 100 | 5.101118, 1.083746, -2.756308, 101 | 4.167568, 1.078925, -2.397892, 102 | 43.001194 103 | ]) 104 | visualize(out) 105 | -------------------------------------------------------------------------------- /functest/example_pt.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # cell_metadata_json: true 5 | # formats: ipynb,py:light 6 | # text_representation: 7 | # extension: .py 8 | # format_name: light 9 | # format_version: '1.5' 10 | # jupytext_version: 1.3.3 11 | # kernelspec: 12 | # display_name: Python 3 13 | # language: python 14 | # name: python3 15 | # --- 16 | 17 | # + {"raw_mimetype": "text/restructuredtext", "active": ""} 18 | # .. _example_pt: 19 | # 20 | # Rendering with path tracing 21 | # =========================== 22 | # 23 | # This example describes how to render the scene with path tracing. Path tracing is a rendering technique based on Monte Carlo method and notably one of the most basic (yet practical) rendering algorithms taking global illumination into account. Our framework implements path tracing as ``renderer::pt`` renderer. 24 | # 25 | # The use of the renderer is straightforward; we just need to create ``renderer::pt`` renderer and call :cpp:func:`lm::Renderer::render` function with some renderer-specific parameters. Thanks to the modular design of the framework, the most of the code can be the same as :ref:`example_raycast`. 26 | # - 27 | 28 | import lmenv 29 | env = lmenv.load('.lmenv') 30 | 31 | import os 32 | import numpy as np 33 | import imageio 34 | # %matplotlib inline 35 | import matplotlib.pyplot as plt 36 | import lightmetrica as lm 37 | # %load_ext lightmetrica_jupyter 38 | 39 | lm.init() 40 | lm.log.init('jupyter') 41 | lm.progress.init('jupyter') 42 | lm.info() 43 | 44 | # + 45 | # Film for the rendered image 46 | film = lm.load_film('film', 'bitmap', w=1920, h=1080) 47 | 48 | # Pinhole camera 49 | camera = lm.load_camera('camera1', 'pinhole', 50 | position=[5.101118, 1.083746, -2.756308], 51 | center=[4.167568, 1.078925, -2.397892], 52 | up=[0,1,0], 53 | vfov=43.001194, 54 | aspect=16/9) 55 | 56 | # OBJ model 57 | model = lm.load_model('model', 'wavefrontobj', 58 | path=os.path.join(env.scene_path, 'fireplace_room/fireplace_room.obj')) 59 | # - 60 | 61 | accel = lm.load_accel('accel', 'sahbvh') 62 | scene = lm.load_scene('scene', 'default', accel=accel) 63 | scene.add_primitive(camera=camera) 64 | scene.add_primitive(model=model) 65 | scene.build() 66 | 67 | renderer = lm.load_renderer('renderer', 'pt', 68 | scene=scene, 69 | output=film, 70 | scheduler='sample', 71 | spp=5, 72 | max_verts=20) 73 | renderer.render() 74 | 75 | img = np.copy(film.buffer()) 76 | f = plt.figure(figsize=(15,15)) 77 | ax = f.add_subplot(111) 78 | ax.imshow(np.clip(np.power(img,1/2.2),0,1), origin='lower') 79 | plt.show() 80 | -------------------------------------------------------------------------------- /functest/example_raycast.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # cell_metadata_json: true 5 | # formats: ipynb,py:light 6 | # text_representation: 7 | # extension: .py 8 | # format_name: light 9 | # format_version: '1.5' 10 | # jupytext_version: 1.3.3 11 | # kernelspec: 12 | # display_name: Python 3 13 | # language: python 14 | # name: python3 15 | # --- 16 | 17 | # + {"raw_mimetype": "text/restructuredtext", "active": ""} 18 | # .. _example_raycast: 19 | # 20 | # Raycasting a scene with OBJ models 21 | # ======================================= 22 | # 23 | # This example demonstrates how to render a scene with OBJ models using raycasting. 24 | # - 25 | 26 | import lmenv 27 | env = lmenv.load('.lmenv') 28 | 29 | import os 30 | import numpy as np 31 | import imageio 32 | # %matplotlib inline 33 | import matplotlib.pyplot as plt 34 | import lightmetrica as lm 35 | # %load_ext lightmetrica_jupyter 36 | 37 | # + {"nbsphinx": "hidden"} 38 | if not lm.Release: 39 | lm.debug.attach_to_debugger() 40 | # - 41 | 42 | lm.init() 43 | lm.log.init('jupyter') 44 | lm.progress.init('jupyter') 45 | lm.info() 46 | 47 | # + {"raw_mimetype": "text/restructuredtext", "active": ""} 48 | # Following is the definition of assets. To load an OBJ model, we can use ``model::wavefrontobj`` asset. This asset internally creates meshes and materials by reading the associated MTL file. 49 | # 50 | # .. note:: 51 | # A model asset can be considered as a special asset containing (a part of) the scene graph and assets reference by the structure. 52 | 53 | # + 54 | # Film for the rendered image 55 | film = lm.load_film('film', 'bitmap', w=1920, h=1080) 56 | 57 | # Pinhole camera 58 | camera = lm.load_camera('camera', 'pinhole', 59 | position=[5.101118, 1.083746, -2.756308], 60 | center=[4.167568, 1.078925, -2.397892], 61 | up=[0,1,0], 62 | vfov=43.001194, 63 | aspect=16/9) 64 | 65 | # OBJ model 66 | model = lm.load_model('model', 'wavefrontobj', 67 | path=os.path.join(env.scene_path, 'fireplace_room/fireplace_room.obj')) 68 | 69 | # + {"raw_mimetype": "text/restructuredtext", "active": ""} 70 | # We can create primitives from the loaded mode using ``model` parameter for the :cpp:func:`lm::Scene::add_primitive` function. 71 | # - 72 | 73 | accel = lm.load_accel('accel', 'sahbvh') 74 | scene = lm.load_scene('scene', 'default', accel=accel) 75 | scene.add_primitive(camera=camera) 76 | scene.add_primitive(model=model) 77 | scene.build() 78 | 79 | # + {"raw_mimetype": "text/restructuredtext", "active": ""} 80 | # Executing the renderer will produce the following image. 81 | # - 82 | 83 | renderer = lm.load_renderer('renderer', 'raycast', 84 | scene=scene, 85 | output=film, 86 | bg_color=[0,0,0]) 87 | renderer.render() 88 | 89 | img = np.copy(film.buffer()) 90 | f = plt.figure(figsize=(15,15)) 91 | ax = f.add_subplot(111) 92 | ax.imshow(np.clip(np.power(img,1/2.2),0,1), origin='lower') 93 | plt.show() 94 | -------------------------------------------------------------------------------- /functest/func_accel_consistency.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # cell_metadata_json: true 5 | # formats: ipynb,py:light 6 | # text_representation: 7 | # extension: .py 8 | # format_name: light 9 | # format_version: '1.5' 10 | # jupytext_version: 1.3.3 11 | # kernelspec: 12 | # display_name: Python 3 13 | # language: python 14 | # name: python3 15 | # --- 16 | 17 | # ## Checking consistency of acceleration structures 18 | # 19 | # This test checks consistencies between different acceleration structures. We render the images with `renderer::raycast` with different accelleration structures for various scenes, and compute differences among them. If the implementations are correct, all difference images should be blank because ray-triangle intersection is a deterministic process. 20 | 21 | import lmenv 22 | env = lmenv.load('.lmenv') 23 | 24 | import os 25 | import imageio 26 | import pandas as pd 27 | import numpy as np 28 | # %matplotlib inline 29 | import matplotlib.pyplot as plt 30 | from mpl_toolkits.axes_grid1 import make_axes_locatable 31 | import lmscene 32 | import lightmetrica as lm 33 | 34 | # %load_ext lightmetrica_jupyter 35 | 36 | # + {"code_folding": [0]} 37 | # Initialize Lightmetrica 38 | lm.init() 39 | lm.log.init('jupyter') 40 | lm.progress.init('jupyter') 41 | lm.info() 42 | lm.comp.load_plugin(os.path.join(env.bin_path, 'accel_nanort')) 43 | lm.comp.load_plugin(os.path.join(env.bin_path, 'accel_embree')) 44 | 45 | 46 | # - 47 | 48 | # ### Difference images (pixelwised RMSE) 49 | # 50 | # Correct if the difference images are blank. 51 | 52 | # + {"code_folding": [0]} 53 | # Function to build and render the image 54 | def build_and_render(scene, accel_name): 55 | accel = lm.load_accel('accel', accel_name) 56 | scene.set_accel(accel.loc()) 57 | scene.build() 58 | film = lm.load_film('film_output', 'bitmap', w=1920, h=1080) 59 | renderer = lm.load_renderer('renderer', 'raycast', scene=scene, output=film) 60 | renderer.render() 61 | return np.copy(film.buffer()) 62 | 63 | 64 | # + {"code_folding": []} 65 | # Accels and scenes 66 | accel_names = ['nanort', 'embree', 'embreeinstanced'] 67 | scene_names = lmscene.scenes_small() 68 | 69 | 70 | # + 71 | def rmse(img1, img2): 72 | return np.sqrt(np.mean((img1 - img2) ** 2)) 73 | 74 | def rmse_pixelwised(img1, img2): 75 | return np.sqrt(np.sum((img1 - img2) ** 2, axis=2) / 3) 76 | 77 | 78 | # - 79 | 80 | # Execute rendering for each scene and accel 81 | rmse_df = pd.DataFrame(columns=accel_names, index=scene_names) 82 | for scene_name in scene_names: 83 | print("Rendering [scene='{}']".format(scene_name)) 84 | 85 | # Load scene 86 | scene = lm.load_scene('scene', 'default') 87 | lmscene.load(scene, env.scene_path, scene_name) 88 | 89 | # Use the image for 'accel::sahbvh' as reference 90 | ref = build_and_render(scene, 'sahbvh') 91 | 92 | # Check consistency for other accels 93 | for accel_name in accel_names: 94 | # Render and compute a different image 95 | img = build_and_render(scene, accel_name) 96 | diff = rmse_pixelwised(ref, img) 97 | 98 | # Record rmse 99 | e = rmse(ref, img) 100 | rmse_df[accel_name][scene_name] = e 101 | 102 | # Visualize the difference image 103 | f = plt.figure(figsize=(10,10)) 104 | ax = f.add_subplot(111) 105 | im = ax.imshow(diff, origin='lower') 106 | divider = make_axes_locatable(ax) 107 | cax = divider.append_axes("right", size="5%", pad=0.05) 108 | plt.colorbar(im, cax=cax) 109 | ax.set_title('{}, sahbvh vs. {}'.format(scene_name, accel_name)) 110 | plt.show() 111 | 112 | # ### RMSE 113 | # 114 | # Correct if the values are near zero. 115 | 116 | rmse_df 117 | -------------------------------------------------------------------------------- /functest/func_lights.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # formats: ipynb,py:light 5 | # text_representation: 6 | # extension: .py 7 | # format_name: light 8 | # format_version: '1.5' 9 | # jupytext_version: 1.3.3 10 | # kernelspec: 11 | # display_name: Python 3 12 | # language: python 13 | # name: python3 14 | # --- 15 | 16 | # # Lights 17 | # 18 | # This test showcases various light sources supported by Lightmetrica. We render images using `renderer::pt`. 19 | 20 | # %load_ext autoreload 21 | # %autoreload 2 22 | 23 | import lmenv 24 | env = lmenv.load('.lmenv') 25 | 26 | import os 27 | import pickle 28 | import json 29 | import numpy as np 30 | import matplotlib.pyplot as plt 31 | import lightmetrica as lm 32 | # %load_ext lightmetrica_jupyter 33 | import lmscene 34 | 35 | lm.init() 36 | lm.log.init('jupyter') 37 | lm.progress.init('jupyter') 38 | lm.info() 39 | lm.comp.load_plugin(os.path.join(env.bin_path, 'accel_embree')) 40 | if not lm.Release: 41 | lm.parallel.init('openmp', num_threads=1) 42 | lm.debug.attach_to_debugger() 43 | 44 | 45 | # + 46 | def render(scene, name, **kwargs): 47 | w = 854 48 | h = 480 49 | film = lm.load_film('film', 'bitmap', w=w, h=h) 50 | renderer = lm.load_renderer('renderer', name, 51 | scene=scene, 52 | output=film, 53 | max_verts=20, 54 | scheduler='time', 55 | render_time=10, 56 | **kwargs) 57 | renderer.render() 58 | return np.copy(film.buffer()) 59 | 60 | def display_image(img, fig_size=15, scale=1): 61 | f = plt.figure(figsize=(fig_size,fig_size)) 62 | ax = f.add_subplot(111) 63 | ax.imshow(np.clip(np.power(img*scale,1/2.2),0,1), origin='lower') 64 | ax.axis('off') 65 | plt.show() 66 | 67 | 68 | # - 69 | 70 | # ## Scene setup 71 | 72 | accel = lm.load_accel('accel', 'embree') 73 | scene = lm.load_scene('scene', 'default', accel=accel) 74 | mat = lm.load_material('mat_ut', 'glossy', Ks=[.8,.2,.2], ax=0.1, ay=0.1) 75 | 76 | # ## Rendering 77 | 78 | # ### Area light 79 | # 80 | # ``light::area`` 81 | 82 | scene.reset() 83 | lmscene.bunny_with_area_light(scene, env.scene_path, mat_knob=mat.loc()) 84 | scene.build() 85 | img = render(scene, 'pt') 86 | display_image(img) 87 | 88 | # ### Environment light 89 | # 90 | # `light::env` `light::envconst` 91 | 92 | scene.reset() 93 | lmscene.bunny_with_env_light( 94 | scene, env.scene_path, 95 | mat_knob=mat.loc(), 96 | path=os.path.join(env.scene_path, 'flower_road_1k.hdr'), 97 | rot=90, 98 | flip=False, 99 | scale=0.1) 100 | scene.build() 101 | img = render(scene, 'pt') 102 | display_image(img) 103 | 104 | # ### Point light 105 | # 106 | # `light::point` 107 | 108 | scene.reset() 109 | lmscene.bunny_with_point_light(scene, env.scene_path, mat_knob=mat.loc()) 110 | scene.build() 111 | img = render(scene, 'pt') 112 | display_image(img) 113 | 114 | # ### Directional light 115 | # 116 | # `light::directional` 117 | 118 | scene.reset() 119 | lmscene.bunny_with_directional_light(scene, env.scene_path, mat_knob=mat.loc()) 120 | scene.build() 121 | img = render(scene, 'pt') 122 | display_image(img) 123 | -------------------------------------------------------------------------------- /functest/func_obj_loader_consistency.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # formats: ipynb,py:light 5 | # text_representation: 6 | # extension: .py 7 | # format_name: light 8 | # format_version: '1.5' 9 | # jupytext_version: 1.3.3 10 | # kernelspec: 11 | # display_name: Python 3 12 | # language: python 13 | # name: python3 14 | # --- 15 | 16 | # ## Checking consistency of OBJ loader 17 | 18 | # %load_ext autoreload 19 | # %autoreload 2 20 | 21 | import lmenv 22 | env = lmenv.load('.lmenv') 23 | 24 | import os 25 | import imageio 26 | import pandas as pd 27 | import numpy as np 28 | # %matplotlib inline 29 | import matplotlib.pyplot as plt 30 | from mpl_toolkits.axes_grid1 import make_axes_locatable 31 | import lmscene 32 | import lightmetrica as lm 33 | 34 | # %load_ext lightmetrica_jupyter 35 | 36 | lm.init() 37 | lm.log.init('jupyter') 38 | lm.progress.init('jupyter') 39 | lm.info() 40 | 41 | lm.comp.load_plugin(os.path.join(env.bin_path, 'accel_embree')) 42 | lm.comp.load_plugin(os.path.join(env.bin_path, 'objloader_tinyobjloader')) 43 | 44 | 45 | def build_and_render(scene_name): 46 | lm.reset() 47 | accel = lm.load_accel('accel', 'embree') 48 | scene = lm.load_scene('scene', 'default', accel=accel) 49 | lmscene.load(scene, env.scene_path, scene_name) 50 | scene.build() 51 | film = lm.load_film('film_output', 'bitmap', w=1920, h=1080) 52 | renderer = lm.load_renderer('renderer', 'raycast', scene=scene, output=film) 53 | renderer.render() 54 | return np.copy(film.buffer()) 55 | 56 | 57 | objloaders = ['tinyobjloader'] 58 | scene_names = lmscene.scenes_small() 59 | 60 | 61 | def rmse_pixelwised(img1, img2): 62 | return np.sqrt(np.sum((img1 - img2) ** 2, axis=2) / 3) 63 | 64 | 65 | for scene_name in scene_names: 66 | # Reference 67 | lm.objloader.init('simple') 68 | ref = build_and_render(scene_name) 69 | 70 | # Visualize reference 71 | f = plt.figure(figsize=(15,15)) 72 | ax = f.add_subplot(111) 73 | ax.imshow(np.clip(np.power(ref,1/2.2),0,1), origin='lower') 74 | ax.set_title('{}, simple'.format(scene_name)) 75 | plt.show() 76 | 77 | # Check consistency with other loaders 78 | for objloader in objloaders: 79 | # Render 80 | lm.objloader.init(objloader, {}) 81 | img = build_and_render(scene_name) 82 | diff = rmse_pixelwised(ref, img) 83 | 84 | # Visualize 85 | f = plt.figure(figsize=(15,15)) 86 | ax = f.add_subplot(111) 87 | ax.imshow(np.clip(np.power(img,1/2.2),0,1), origin='lower') 88 | ax.set_title('{}, {}'.format(scene_name, objloader)) 89 | plt.show() 90 | 91 | # Visualize the difference image 92 | f = plt.figure(figsize=(15,15)) 93 | ax = f.add_subplot(111) 94 | im = ax.imshow(diff, origin='lower') 95 | divider = make_axes_locatable(ax) 96 | cax = divider.append_axes("right", size="5%", pad=0.05) 97 | plt.colorbar(im, cax=cax) 98 | ax.set_title('{}, simple vs. {}'.format(scene_name, objloader)) 99 | plt.show() 100 | -------------------------------------------------------------------------------- /functest/func_render_all.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # formats: ipynb,py:light 5 | # text_representation: 6 | # extension: .py 7 | # format_name: light 8 | # format_version: '1.5' 9 | # jupytext_version: 1.3.3 10 | # kernelspec: 11 | # display_name: Python 3 12 | # language: python 13 | # name: python3 14 | # --- 15 | 16 | # ## Rendering all scenes 17 | # 18 | # This test checks scene loading and basic rendering for all test scenes. 19 | 20 | # %load_ext autoreload 21 | # %autoreload 2 22 | 23 | import lmenv 24 | env = lmenv.load('.lmenv') 25 | 26 | import os 27 | import imageio 28 | import pandas as pd 29 | import numpy as np 30 | # %matplotlib inline 31 | import matplotlib.pyplot as plt 32 | from mpl_toolkits.axes_grid1 import make_axes_locatable 33 | import lmscene 34 | import lightmetrica as lm 35 | 36 | os.getpid() 37 | 38 | # %load_ext lightmetrica_jupyter 39 | 40 | lm.init() 41 | lm.log.init('jupyter') 42 | lm.progress.init('jupyter') 43 | lm.info() 44 | 45 | if not lm.Release: 46 | lm.debug.attach_to_debugger() 47 | 48 | lm.comp.load_plugin(os.path.join(env.bin_path, 'accel_embree')) 49 | lm.comp.load_plugin(os.path.join(env.bin_path, 'objloader_tinyobjloader')) 50 | 51 | lm.objloader.init('tinyobjloader') 52 | 53 | scene_names = lmscene.scenes_small() 54 | 55 | for scene_name in scene_names: 56 | lm.reset() 57 | 58 | # Load scene 59 | accel = lm.load_accel('accel', 'embree') 60 | scene = lm.load_scene('scene', 'default', accel=accel) 61 | lmscene.load(scene, env.scene_path, scene_name) 62 | scene.build() 63 | 64 | # Render 65 | film = lm.load_film('film_output', 'bitmap', w=1920, h=1080) 66 | renderer = lm.load_renderer('renderer', 'raycast', scene=scene, output=film) 67 | renderer.render() 68 | 69 | # Visualize 70 | img = np.copy(film.buffer()) 71 | f = plt.figure(figsize=(15,15)) 72 | ax = f.add_subplot(111) 73 | ax.imshow(np.clip(np.power(img,1/2.2),0,1), origin='lower') 74 | ax.set_title(scene) 75 | plt.show() 76 | -------------------------------------------------------------------------------- /functest/func_renderers.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # formats: ipynb,py:light 5 | # text_representation: 6 | # extension: .py 7 | # format_name: light 8 | # format_version: '1.5' 9 | # jupytext_version: 1.3.3 10 | # kernelspec: 11 | # display_name: Python 3 12 | # language: python 13 | # name: python3 14 | # --- 15 | 16 | # # Renderers 17 | # 18 | # This test showcases rendering with various renderer approaches. 19 | 20 | # %load_ext autoreload 21 | # %autoreload 2 22 | 23 | import lmenv 24 | env = lmenv.load('.lmenv') 25 | 26 | import os 27 | import pickle 28 | import json 29 | import numpy as np 30 | import matplotlib.pyplot as plt 31 | import lightmetrica as lm 32 | # %load_ext lightmetrica_jupyter 33 | import lmscene 34 | 35 | lm.init() 36 | lm.log.init('jupyter') 37 | lm.progress.init('jupyter') 38 | lm.info() 39 | lm.comp.load_plugin(os.path.join(env.bin_path, 'accel_embree')) 40 | if not lm.Release: 41 | lm.parallel.init('openmp', num_threads=1) 42 | lm.debug.attach_to_debugger() 43 | 44 | 45 | # + 46 | def render(scene, name, **kwargs): 47 | w = 854 48 | h = 480 49 | film = lm.load_film('film', 'bitmap', w=w, h=h) 50 | renderer = lm.load_renderer('renderer', name, 51 | scene=scene, 52 | output=film, 53 | max_verts=10, 54 | scheduler='time', 55 | render_time=30, 56 | **kwargs) 57 | renderer.render() 58 | return np.copy(film.buffer()) 59 | 60 | def display_image(img, fig_size=15, scale=1): 61 | f = plt.figure(figsize=(fig_size,fig_size)) 62 | ax = f.add_subplot(111) 63 | ax.imshow(np.clip(np.power(img*scale,1/2.2),0,1), origin='lower') 64 | ax.axis('off') 65 | plt.show() 66 | 67 | 68 | # - 69 | 70 | # ## Scene setup 71 | 72 | # Create scene 73 | accel = lm.load_accel('accel', 'embree') 74 | scene = lm.load_scene('scene', 'default', accel=accel) 75 | lmscene.cornell_box_sphere(scene, env.scene_path) 76 | scene.build() 77 | 78 | # ## Rendering 79 | 80 | # ### Path tracing (naive) 81 | # 82 | # `renderer::pt` with `sampling_mode = naive`. For comparison, the primary rays are sampling from the entire image (`primary_ray_sampling_mode = image`). 83 | 84 | img = render(scene, 'pt', 85 | sampling_mode='naive', 86 | primary_ray_sampling_mode='image') 87 | display_image(img) 88 | 89 | # ### Path tracing (next event estimation) 90 | # 91 | # `renderer::pt` with `sampling_mode = nee` 92 | 93 | img = render(scene, 'pt', 94 | sampling_mode='nee', 95 | primary_ray_sampling_mode='image') 96 | display_image(img) 97 | 98 | # ### Path tracing (multiple importance sampling) 99 | # 100 | # `renderer::pt` with `sampling_mode = mis` 101 | 102 | img = render(scene, 'pt', 103 | sampling_mode='mis', 104 | primary_ray_sampling_mode='image') 105 | display_image(img) 106 | 107 | # ### Light tracing 108 | # 109 | # `renderer::lt` 110 | 111 | img = render(scene, 'lt') 112 | display_image(img) 113 | 114 | # ### Bidirectional path tracing 115 | # 116 | # `renderer::bdpt` (unoptimized, bad absolute performance) 117 | 118 | img = render(scene, 'bdpt') 119 | display_image(img) 120 | 121 | # `renderer::bdptopt` (optimized) 122 | 123 | img = render(scene, 'bdptopt') 124 | display_image(img) 125 | -------------------------------------------------------------------------------- /functest/func_scheduler.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # formats: ipynb,py:light 5 | # text_representation: 6 | # extension: .py 7 | # format_name: light 8 | # format_version: '1.5' 9 | # jupytext_version: 1.3.3 10 | # kernelspec: 11 | # display_name: Python 3 12 | # language: python 13 | # name: python3 14 | # --- 15 | 16 | # ## Scheduler for rendering 17 | 18 | import lmenv 19 | env = lmenv.load('.lmenv') 20 | 21 | import os 22 | import numpy as np 23 | import imageio 24 | # %matplotlib inline 25 | import matplotlib.pyplot as plt 26 | from mpl_toolkits.axes_grid1 import make_axes_locatable 27 | import lightmetrica as lm 28 | # %load_ext lightmetrica_jupyter 29 | import lmscene 30 | 31 | if not lm.Release: 32 | lm.attach_to_debugger() 33 | 34 | lm.init() 35 | if not lm.Release: 36 | lm.parallel.init('openmp', num_threads=1) 37 | lm.log.init('jupyter') 38 | lm.progress.init('jupyter') 39 | lm.info() 40 | 41 | lm.comp.load_plugin(os.path.join(env.bin_path, 'accel_embree')) 42 | 43 | accel = lm.load_accel('accel', 'embree') 44 | scene = lm.load_scene('scene', 'default', accel=accel) 45 | lmscene.load(scene, env.scene_path, 'fireplace_room') 46 | scene.build() 47 | film = lm.load_film('film_output', 'bitmap', w=1920, h=1080) 48 | 49 | shared_renderer_params = { 50 | 'scene': scene.loc(), 51 | 'output': film.loc(), 52 | 'image_sample_mode': 'image', 53 | 'max_verts': 20, 54 | } 55 | 56 | # ### w/ sample-based scheduler 57 | 58 | renderer = lm.load_renderer('renderer', 'pt', 59 | **shared_renderer_params, 60 | scheduler='sample', 61 | spp=1, 62 | num_samples=10000000) 63 | renderer.render() 64 | 65 | img1 = np.copy(film.buffer()) 66 | f = plt.figure(figsize=(15,15)) 67 | ax = f.add_subplot(111) 68 | ax.imshow(np.clip(np.power(img1,1/2.2),0,1), origin='lower') 69 | plt.show() 70 | 71 | # ### w/ time-based scheduler 72 | 73 | renderer = lm.load_renderer('renderer', 'pt', 74 | **shared_renderer_params, 75 | scheduler='time', 76 | render_time=5) 77 | renderer.render() 78 | 79 | img2 = np.copy(film.buffer()) 80 | f = plt.figure(figsize=(15,15)) 81 | ax = f.add_subplot(111) 82 | ax.imshow(np.clip(np.power(img2,1/2.2),0,1), origin='lower') 83 | plt.show() 84 | 85 | # ### Diff 86 | 87 | from scipy.ndimage import gaussian_filter 88 | diff_gauss = np.abs(gaussian_filter(img1 - img2, sigma=3)) 89 | f = plt.figure(figsize=(15,15)) 90 | ax = f.add_subplot(111) 91 | ax.imshow(np.clip(np.power(diff_gauss,1/2.2),0,1), origin='lower') 92 | plt.show() 93 | -------------------------------------------------------------------------------- /functest/func_serial_consistency.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # formats: ipynb,py:light 5 | # text_representation: 6 | # extension: .py 7 | # format_name: light 8 | # format_version: '1.5' 9 | # jupytext_version: 1.3.3 10 | # kernelspec: 11 | # display_name: Python 3 12 | # language: python 13 | # name: python3 14 | # --- 15 | 16 | # ## Checking consistency of serialization 17 | # 18 | # This test checks the consistency of the internal states between before/after serialization. We render two images. One with normal configuration and the other with deserialized states. 19 | 20 | import lmenv 21 | env = lmenv.load('.lmenv') 22 | 23 | import os 24 | import imageio 25 | import pandas as pd 26 | import numpy as np 27 | # %matplotlib inline 28 | import matplotlib.pyplot as plt 29 | from mpl_toolkits.axes_grid1 import make_axes_locatable 30 | import lmscene 31 | import lightmetrica as lm 32 | 33 | os.getpid() 34 | 35 | # %load_ext lightmetrica_jupyter 36 | 37 | lm.init() 38 | lm.log.init('jupyter') 39 | lm.progress.init('jupyter') 40 | lm.info() 41 | 42 | if not lm.Release: 43 | lm.debug.attach_to_debugger() 44 | 45 | scene_names = lmscene.scenes_small() 46 | 47 | 48 | def rmse(img1, img2): 49 | return np.sqrt(np.mean((img1 - img2) ** 2)) 50 | 51 | 52 | rmse_series = pd.Series(index=scene_names) 53 | for scene_name in scene_names: 54 | print("Testing [scene='{}']".format(scene_name)) 55 | 56 | # Load scene and render 57 | print('w/o serialization') 58 | lm.reset() 59 | lm.load_film('film_output', 'bitmap', w=1920, h=1080) 60 | lm.load_accel('accel', 'sahbvh') 61 | scene = lm.load_scene('scene', 'default', accel='$.assets.accel') 62 | lmscene.load(scene, env.scene_path, scene_name) 63 | scene.build() 64 | lm.load_renderer('renderer', 'raycast', 65 | scene='$.assets.scene', 66 | output='$.assets.film_output') 67 | 68 | renderer = lm.get_renderer('$.assets.renderer') 69 | renderer.render() 70 | film = lm.get_film('$.assets.film_output') 71 | img_orig = np.copy(film.buffer()) 72 | 73 | # Visualize 74 | f = plt.figure(figsize=(15,15)) 75 | ax = f.add_subplot(111) 76 | ax.imshow(np.clip(np.power(img_orig,1/2.2),0,1), origin='lower') 77 | plt.show() 78 | 79 | # Serialize, reset, deserialize, and render 80 | print('w/ serialization') 81 | lm.save_state_to_file('lm.serialized') 82 | lm.reset() 83 | lm.load_state_from_file('lm.serialized') 84 | 85 | renderer = lm.get_renderer('$.assets.renderer') 86 | renderer.render() 87 | film = lm.get_film('$.assets.film_output') 88 | img_serial = np.copy(film.buffer()) 89 | 90 | # Visualize 91 | f = plt.figure(figsize=(15,15)) 92 | ax = f.add_subplot(111) 93 | ax.imshow(np.clip(np.power(img_serial,1/2.2),0,1), origin='lower') 94 | plt.show() 95 | 96 | # Compare two images 97 | e = rmse(img_orig, img_serial) 98 | rmse_series[scene_name] = e 99 | 100 | rmse_series 101 | -------------------------------------------------------------------------------- /functest/func_update_asset.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # formats: ipynb,py:light 5 | # text_representation: 6 | # extension: .py 7 | # format_name: light 8 | # format_version: '1.5' 9 | # jupytext_version: 1.3.3 10 | # kernelspec: 11 | # display_name: Python 3 12 | # language: python 13 | # name: python3 14 | # --- 15 | 16 | # ## Updating already-loaded material 17 | # 18 | # This test shows an example of updating already-loaded material. 19 | 20 | import lmenv 21 | env = lmenv.load('.lmenv') 22 | 23 | import os 24 | import imageio 25 | import pandas as pd 26 | import numpy as np 27 | import timeit 28 | # %matplotlib inline 29 | import matplotlib.pyplot as plt 30 | from mpl_toolkits.axes_grid1 import make_axes_locatable 31 | import lmscene 32 | import lightmetrica as lm 33 | 34 | # %load_ext lightmetrica_jupyter 35 | 36 | lm.init() 37 | lm.log.init('jupyter') 38 | lm.progress.init('jupyter') 39 | lm.info() 40 | 41 | camera = lm.load_camera('camera_main', 'pinhole', 42 | position=[5.101118, 1.083746, -2.756308], 43 | center=[4.167568, 1.078925, -2.397892], 44 | up=[0,1,0], 45 | vfov=43.001194, 46 | aspect=16/9) 47 | material = lm.load_material('obj_base_mat', 'diffuse', 48 | Kd=[.8,.2,.2]) 49 | model = lm.load_model('model_obj', 'wavefrontobj', 50 | path=os.path.join(env.scene_path, 'fireplace_room/fireplace_room.obj'), 51 | base_material=material) 52 | accel = lm.load_accel('accel', 'sahbvh') 53 | scene = lm.load_scene('scene', 'default', accel=accel) 54 | scene.add_primitive(camera=camera) 55 | scene.add_primitive(model=model) 56 | scene.build() 57 | 58 | film = lm.load_film('film_output', 'bitmap', w=1920, h=1080) 59 | renderer = lm.load_renderer('renderer', 'raycast', scene=scene, output=film) 60 | renderer.render() 61 | 62 | img = np.copy(film.buffer()) 63 | f = plt.figure(figsize=(10,10)) 64 | ax = f.add_subplot(111) 65 | ax.imshow(np.clip(np.power(img,1/2.2),0,1), origin='lower') 66 | ax.set_title('orig') 67 | 68 | # Replace `obj_base_mat` with different color 69 | # Note that this is not trivial, because `model::wavefrontobj` 70 | # already holds a reference to the original material. 71 | lm.load_material('obj_base_mat', 'diffuse', Kd=[.2,.8,.2]) 72 | 73 | renderer.render() 74 | 75 | img = np.copy(film.buffer()) 76 | f = plt.figure(figsize=(10,10)) 77 | ax = f.add_subplot(111) 78 | ax.imshow(np.clip(np.power(img,1/2.2),0,1), origin='lower') 79 | ax.set_title('modified') 80 | -------------------------------------------------------------------------------- /functest/lmenv.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | import numpy as np 4 | import imageio 5 | from argparse import Namespace 6 | 7 | def load(config_path): 8 | """Load configuration file of Lightmetrica environment""" 9 | 10 | # Open config file 11 | with open(config_path) as f: 12 | config = json.load(f) 13 | 14 | # Add root directory and binary directory to sys.path 15 | if config['path'] not in sys.path: 16 | sys.path.insert(0, config['path']) 17 | if config['bin_path'] not in sys.path: 18 | sys.path.insert(0, config['bin_path']) 19 | 20 | return Namespace(**config) -------------------------------------------------------------------------------- /functest/perf_accel.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # formats: ipynb,py:light 5 | # text_representation: 6 | # extension: .py 7 | # format_name: light 8 | # format_version: '1.5' 9 | # jupytext_version: 1.3.3 10 | # kernelspec: 11 | # display_name: Python 3 12 | # language: python 13 | # name: python3 14 | # --- 15 | 16 | # ## Performance testing of acceleration structures 17 | # 18 | # This test checks performance of acceleration structure implemented in the framework for various scenes. 19 | 20 | import lmenv 21 | env = lmenv.load('.lmenv') 22 | 23 | import os 24 | import imageio 25 | import pandas as pd 26 | import numpy as np 27 | import timeit 28 | # %matplotlib inline 29 | import matplotlib.pyplot as plt 30 | from mpl_toolkits.axes_grid1 import make_axes_locatable 31 | import lmscene 32 | import lightmetrica as lm 33 | 34 | # %load_ext lightmetrica_jupyter 35 | 36 | lm.init() 37 | lm.log.init('jupyter') 38 | lm.progress.init('jupyter') 39 | lm.info() 40 | 41 | lm.comp.load_plugin(os.path.join(env.bin_path, 'accel_nanort')) 42 | lm.comp.load_plugin(os.path.join(env.bin_path, 'accel_embree')) 43 | 44 | accel_names = [ 45 | 'sahbvh', 46 | 'nanort', 47 | 'embree', 48 | 'embreeinstanced' 49 | ] 50 | scene_names = lmscene.scenes_small() 51 | 52 | # + 53 | build_time_df = pd.DataFrame(columns=accel_names, index=scene_names) 54 | render_time_df = pd.DataFrame(columns=accel_names, index=scene_names) 55 | 56 | film = lm.load_film('film_output', 'bitmap', { 57 | 'w': 1920, 58 | 'h': 1080 59 | }) 60 | 61 | for scene_name in scene_names: 62 | # Create scene w/o accel 63 | scene = lm.load_scene('scene', 'default', {}) 64 | lmscene.load(scene, env.scene_path, scene_name) 65 | renderer = lm.load_renderer('renderer', 'raycast', { 66 | 'scene': scene.loc(), 67 | 'output': film.loc() 68 | }) 69 | 70 | for accel_name in accel_names: 71 | accel = lm.load_accel('accel', accel_name, {}) 72 | scene.set_accel(accel.loc()) 73 | def build(): 74 | scene.build() 75 | build_time = timeit.timeit(stmt=build, number=1) 76 | build_time_df[accel_name][scene_name] = build_time 77 | 78 | def render(): 79 | renderer.render() 80 | render_time = timeit.timeit(stmt=render, number=1) 81 | render_time_df[accel_name][scene_name] = render_time 82 | # - 83 | 84 | build_time_df 85 | 86 | render_time_df 87 | -------------------------------------------------------------------------------- /functest/perf_obj_loader.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # formats: ipynb,py:light 5 | # text_representation: 6 | # extension: .py 7 | # format_name: light 8 | # format_version: '1.4' 9 | # jupytext_version: 1.2.4 10 | # kernelspec: 11 | # display_name: Python 3 12 | # language: python 13 | # name: python3 14 | # --- 15 | 16 | # ## Performance testing of OBJ loader 17 | 18 | # %load_ext autoreload 19 | # %autoreload 2 20 | 21 | import lmenv 22 | env = lmenv.load('.lmenv') 23 | 24 | import os 25 | import imageio 26 | import pandas as pd 27 | import numpy as np 28 | import timeit 29 | import lmscene 30 | import lightmetrica as lm 31 | 32 | # %load_ext lightmetrica_jupyter 33 | 34 | lm.init() 35 | lm.log.init('jupyter') 36 | lm.progress.init('jupyter') 37 | lm.info() 38 | 39 | lm.comp.load_plugin(os.path.join(env.bin_path, 'objloader_tinyobjloader')) 40 | 41 | objloader_names = ['simple', 'tinyobjloader'] 42 | scene_names = lmscene.scenes_small() 43 | 44 | loading_time_df = pd.DataFrame(columns=objloader_names, index=scene_names) 45 | for scene_name in scene_names: 46 | # Check consistency with other loaders 47 | for objloader_name in objloader_names: 48 | # Load the scene with selected obj loader 49 | lm.objloader.init(objloader_name) 50 | lm.reset() 51 | 52 | def load_model(): 53 | lm.load_model('model_obj', 'wavefrontobj', { 54 | 'path': os.path.join(env.scene_path, 'fireplace_room/fireplace_room.obj') 55 | }) 56 | 57 | loading_time = timeit.timeit(stmt=load_model, number=1) 58 | loading_time_df[objloader_name][scene_name] = loading_time 59 | 60 | loading_time_df 61 | -------------------------------------------------------------------------------- /functest/perf_serial.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # formats: ipynb,py:light 5 | # text_representation: 6 | # extension: .py 7 | # format_name: light 8 | # format_version: '1.4' 9 | # jupytext_version: 1.2.4 10 | # kernelspec: 11 | # display_name: Python 3 12 | # language: python 13 | # name: python3 14 | # --- 15 | 16 | # ## Performance testing of serialization 17 | # 18 | # This test checks the performance improvement of scene setup with serialization feature. 19 | 20 | import lmenv 21 | env = lmenv.load('.lmenv') 22 | 23 | import os 24 | import imageio 25 | import pandas as pd 26 | import numpy as np 27 | import timeit 28 | # %matplotlib inline 29 | import matplotlib.pyplot as plt 30 | from mpl_toolkits.axes_grid1 import make_axes_locatable 31 | import lmscene 32 | import lightmetrica as lm 33 | 34 | # %load_ext lightmetrica_jupyter 35 | 36 | lm.init() 37 | lm.log.init('jupyter') 38 | lm.progress.init('jupyter') 39 | lm.info() 40 | 41 | scene_names = lmscene.scenes_small() 42 | 43 | scene_setup_time_df = pd.DataFrame( 44 | columns=['scene loading', 'serialization', 'deserialization'], 45 | index=scene_names) 46 | for scene_name in scene_names: 47 | lm.reset() 48 | 49 | lm.load_film('film_output', 'bitmap', { 50 | 'w': 1920, 51 | 'h': 1080 52 | }) 53 | 54 | # Load the scene without serialization 55 | def load_scene(): 56 | accel = lm.load_accel('accel', 'sahbvh', {}) 57 | scene = lm.load_scene('scene', 'default', { 58 | 'accel': accel.loc() 59 | }) 60 | lmscene.load(scene, env.scene_path, scene_name) 61 | loading_time_without_serialization = timeit.timeit(stmt=load_scene, number=1) 62 | scene_setup_time_df['scene loading'][scene_name] = loading_time_without_serialization 63 | 64 | # Export the internal state to a file 65 | def serialize_scene(): 66 | lm.save_state_to_file('lm.serialized') 67 | serialization_time = timeit.timeit(stmt=serialize_scene, number=1) 68 | scene_setup_time_df['serialization'][scene_name] = serialization_time 69 | 70 | # Import the internal state from the serialized file 71 | lm.reset() 72 | def deserialize_scene(): 73 | lm.load_state_from_file('lm.serialized') 74 | deserialization_time = timeit.timeit(stmt=deserialize_scene, number=1) 75 | scene_setup_time_df['deserialization'][scene_name] = deserialization_time 76 | 77 | scene_setup_time_df 78 | -------------------------------------------------------------------------------- /functest/renderer_ao.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | 8 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 9 | 10 | class Renderer_AO final : public Renderer { 11 | private: 12 | Scene* scene_; 13 | Film* film_; 14 | long long spp_; 15 | int rng_seed_ = 42; 16 | 17 | public: 18 | virtual void construct(const Json& prop) override { 19 | scene_ = json::comp_ref(prop, "scene"); 20 | film_ = json::comp_ref(prop, "output"); 21 | spp_ = json::value(prop, "spp"); 22 | } 23 | 24 | virtual Json render() const override { 25 | const auto size = film_->size(); 26 | parallel::foreach(size.w*size.h, [&](long long index, int threadId) -> void { 27 | thread_local Rng rng(rng_seed_ + threadId); 28 | const int x = int(index % size.w); 29 | const int y = int(index / size.w); 30 | const auto ray = path::primary_ray(scene_, {(x+.5_f)/size.w, (y+.5_f)/size.h}); 31 | const auto hit = scene_->intersect(ray); 32 | if (!hit) { 33 | return; 34 | } 35 | auto V = 0_f; 36 | for (long long i = 0; i < spp_; i++) { 37 | const auto [n, u, v] = hit->geom.orthonormal_basis_twosided(-ray.d); 38 | const auto d = math::sample_cosine_weighted(rng.next()); 39 | V += scene_->intersect({hit->geom.p, u*d.x+v*d.y+n*d.z}, Eps, .2_f) ? 0_f : 1_f; 40 | } 41 | V /= spp_; 42 | film_->set_pixel(x, y, Vec3(V)); 43 | }); 44 | return {}; 45 | } 46 | }; 47 | 48 | LM_COMP_REG_IMPL(Renderer_AO, "renderer::ao"); 49 | 50 | LM_NAMESPACE_END(LM_NAMESPACE) 51 | -------------------------------------------------------------------------------- /functest/run_all.py: -------------------------------------------------------------------------------- 1 | """Run all functional tests""" 2 | import os 3 | import sys 4 | from colorama import Fore, Back, Style 5 | import nbformat 6 | from nbconvert.preprocessors import ExecutePreprocessor 7 | import argparse 8 | import jupytext 9 | import nbmerge 10 | from shutil import copyfile 11 | 12 | def run_functests(output_dir, lmenv_path): 13 | # Output directory of executed notebooks 14 | if not os.path.exists(output_dir): 15 | os.mkdir(output_dir) 16 | 17 | # Base directory of the functional tests. 18 | # That is, the directory where this script is located. 19 | base_path = os.path.dirname(os.path.realpath(__file__)) 20 | 21 | # Copy .lmenv file to the functest directory 22 | copyfile(lmenv_path, os.path.join(base_path, '.lmenv')) 23 | 24 | # Tests 25 | tests = [ 26 | 'example_blank', 27 | 'example_quad', 28 | 'example_raycast', 29 | 'example_pt', 30 | 'example_cpp', 31 | 'example_custom_renderer', 32 | 'example_serialization', 33 | 'func_render_all', 34 | 'func_render_instancing', 35 | 'func_accel_consistency', 36 | 'func_error_handling', 37 | 'func_obj_loader_consistency', 38 | 'func_serial_consistency', 39 | 'func_update_asset', 40 | 'func_scheduler', 41 | 'func_materials', 42 | 'func_lights', 43 | 'func_renderers', 44 | 'perf_accel', 45 | 'perf_obj_loader', 46 | 'perf_serial' 47 | ] 48 | 49 | # Execute tests 50 | for test in tests: 51 | print(Fore.GREEN + "Running test [name='{}']".format(test) + Style.RESET_ALL, flush=True) 52 | 53 | # Read the requested notebook 54 | nb = jupytext.read(os.path.join(base_path, test + '.py')) 55 | 56 | # Execute the notebook 57 | ep = ExecutePreprocessor(timeout=600) 58 | ep.preprocess(nb, {'metadata': {'path': base_path}}) 59 | 60 | # Write result 61 | with open(os.path.join(output_dir, test + '.ipynb'), mode='w', encoding='utf-8') as f: 62 | nbformat.write(nb, f) 63 | 64 | # Merge executed notebooks 65 | print(Fore.GREEN + "Merging notebooks" + Style.RESET_ALL) 66 | notebook_paths = [os.path.join(output_dir, test + '.ipynb') for test in tests] 67 | nb = nbmerge.merge_notebooks(os.getcwd(), notebook_paths) 68 | with open(os.path.join(output_dir, 'merged.ipynb'), mode='w', encoding='utf-8') as f: 69 | nbformat.write(nb, f) 70 | 71 | # Notify success 72 | print(Fore.GREEN + "All tests have been executed successfully" + Style.RESET_ALL) 73 | 74 | if __name__ == '__main__': 75 | parser = argparse.ArgumentParser(description='Execute all functional tests') 76 | parser.add_argument('--lmenv', type=str, help='Path to .lmenv file') 77 | parser.add_argument('--output-dir', nargs='?', type=str, default='executed_functest', help='Output directory of executed notebooks') 78 | args = parser.parse_args() 79 | run_functests(output_dir, lmenv) -------------------------------------------------------------------------------- /include/lm/accel.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "core.h" 9 | 10 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 11 | 12 | /*! 13 | \addtogroup accel 14 | @{ 15 | */ 16 | 17 | /*! 18 | \brief Ray-triangles acceleration structure. 19 | 20 | \rst 21 | Interfaces acceleration structure for ray-triangles intersection. 22 | We provided several tests to check validity or performance of the implementations. 23 | See ``functest`` directory for detail. 24 | \endrst 25 | */ 26 | class Accel : public Component { 27 | public: 28 | /*! 29 | \brief Build acceleration structure. 30 | \param scene Input scene. 31 | 32 | \rst 33 | Builds the acceleration structure from the primitives inside the given scene. 34 | When a primitive inside the scene is updated by addition or modification, 35 | you need to call the function again to update the structure. 36 | \endrst 37 | */ 38 | virtual void build(const Scene& scene) = 0; 39 | 40 | /*! 41 | \brief Hit result. 42 | 43 | \rst 44 | Shows information associated to the hit point of the ray. 45 | More additional information like surface positions can be obtained 46 | from querying appropriate data types from these information. 47 | \endrst 48 | */ 49 | struct Hit { 50 | Float t; //!< Distance to the hit point. 51 | Vec2 uv; //!< Barycentric coordinates. 52 | Transform global_transform; //!< Global transformation. 53 | int primitive; //!< Primitive node index. 54 | int face; //!< Face index. 55 | }; 56 | 57 | /*! 58 | \brief Compute closest intersection point. 59 | \param ray Ray. 60 | \param tmin Lower valid range of the ray. 61 | \param tmax Higher valid range of the ray. 62 | 63 | \rst 64 | Finds the closest intersecion point in the direction specified by ``ray``. 65 | The validity of the ray segment is speicified by the range ``[tmin, tmax]`` 66 | measured from the origin of the ray. 67 | If no intersection is found, the function returns nullopt. 68 | \endrst 69 | */ 70 | virtual std::optional intersect(Ray ray, Float tmin, Float tmax) const = 0; 71 | }; 72 | 73 | /*! 74 | @} 75 | */ 76 | 77 | LM_NAMESPACE_END(LM_NAMESPACE) 78 | -------------------------------------------------------------------------------- /include/lm/assetgroup.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "component.h" 9 | 10 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 11 | 12 | /*! 13 | \addtogroup assets 14 | @{ 15 | */ 16 | 17 | /*! 18 | \brief Asset group. 19 | 20 | \rst 21 | This class represents a collection of the assets (e.g., materials, meshes, etc.). 22 | The assets in the framework are stored in this class. 23 | \endrst 24 | */ 25 | class AssetGroup : public Component { 26 | public: 27 | /*! 28 | \brief Loads an asset. 29 | \param name Name of the asset. 30 | \param impl_key Key of component implementation in `interface::implementation` format. 31 | \param prop Properties. 32 | \return Pointer to the created asset. nullptr if failed. 33 | 34 | \rst 35 | Loads an asset from the given information and registers to the class. 36 | ``impl_key`` is used to create an instance and ``prop`` is used to construct it. 37 | ``prop`` is passed to :cpp:func:`lm::Component::construct` function of 38 | the implementation of the asset. 39 | This function returns a pointer to the created instance. 40 | If failed, it returns nullptr. 41 | 42 | If the asset with same name is already loaded, the function tries 43 | to deregister the previously-loaded asset and reload an asset again. 44 | If the global component hierarchy contains a reference to the original asset, 45 | the function automatically resolves the reference to the new asset. 46 | For usage, see ``functest/func_update_asset.py``. 47 | \endrst 48 | */ 49 | virtual Component* load_asset(const std::string& name, const std::string& impl_key, const Json& prop) = 0; 50 | 51 | /*! 52 | \brief Load a serialized asset. 53 | \param name Name of the asset. 54 | \param path Path to the serialized asset. 55 | */ 56 | virtual Component* load_serialized(const std::string& name, const std::string& path) = 0; 57 | }; 58 | 59 | /*! 60 | @} 61 | */ 62 | 63 | LM_NAMESPACE_END(LM_NAMESPACE) 64 | -------------------------------------------------------------------------------- /include/lm/core.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | // Includes core headers of Lightmetrica 9 | #include "component.h" 10 | #include "exception.h" 11 | #include "json.h" 12 | #include "logger.h" 13 | #include "serial.h" -------------------------------------------------------------------------------- /include/lm/debug.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "component.h" 9 | #include "math.h" 10 | 11 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 12 | LM_NAMESPACE_BEGIN(debug) 13 | 14 | /*! 15 | \addtogroup debug 16 | @{ 17 | */ 18 | 19 | /*! 20 | \brief Poll JSON value. 21 | \param val Value. 22 | */ 23 | LM_PUBLIC_API void poll(const Json& val); 24 | 25 | /*! 26 | \brief Function being called on polling JSON value. 27 | \param val Value. 28 | */ 29 | using OnPollFunc = std::function; 30 | 31 | /*! 32 | \brief Register polling function for floating-point values. 33 | \param func Callback function. 34 | */ 35 | LM_PUBLIC_API void reg_on_poll(const OnPollFunc& func); 36 | 37 | /*! 38 | \brief Attach to debugger. 39 | 40 | \rst 41 | Attach the process to Visual Studio Debugger. 42 | This function is only available in Windows environment. 43 | \endrst 44 | */ 45 | LM_PUBLIC_API void attach_to_debugger(); 46 | 47 | /*! 48 | \brief Prints assets managed by the framework. 49 | \param visualize_weak_refs Visualize weak references if enabled. 50 | 51 | \rst 52 | This function visualizes a tree of assets managed by the framework. 53 | If ``visualize_weak_refs`` flag enabled, weak references are also visualized. 54 | \endrst 55 | */ 56 | LM_PUBLIC_API void print_asset_tree(bool visualize_weak_refs); 57 | 58 | /*! 59 | @} 60 | */ 61 | 62 | LM_NAMESPACE_END(debug) 63 | LM_NAMESPACE_END(LM_NAMESPACE) 64 | -------------------------------------------------------------------------------- /include/lm/jsontype.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "common.h" 9 | 10 | #pragma warning(push) 11 | #pragma warning(disable:4127) // conditional expression is constant 12 | #include 13 | #pragma warning(pop) 14 | 15 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 16 | 17 | /*! 18 | \addtogroup json 19 | @{ 20 | */ 21 | 22 | /*! 23 | \brief JSON type. 24 | 25 | \rst 26 | We use nlohmann_json library to represent various properties. 27 | This type is mainly used to communicate data between user and framework through API. 28 | \endrst 29 | */ 30 | using Json = nlohmann::basic_json< 31 | std::map, // Object type 32 | std::vector, // Arrray type 33 | std::string, // String type 34 | bool, // Boolean type 35 | std::int64_t, // Signed integer type 36 | std::uint64_t, // Unsigned integer type 37 | Float, // Floating point type 38 | std::allocator, 39 | nlohmann::adl_serializer>; 40 | 41 | /*! 42 | @} 43 | */ 44 | 45 | LM_NAMESPACE_END(LM_NAMESPACE) -------------------------------------------------------------------------------- /include/lm/lm.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | // Includes all headers of Lightmetrica 9 | #include "component.h" 10 | #include "version.h" 11 | #include "json.h" 12 | #include "serial.h" 13 | #include "user.h" 14 | #include "logger.h" 15 | #include "loggercontext.h" 16 | #include "progress.h" 17 | #include "progresscontext.h" 18 | #include "exception.h" 19 | #include "scheduler.h" 20 | #include "debug.h" 21 | #include "parallel.h" 22 | #include "parallelcontext.h" 23 | #include "math.h" 24 | #include "mesh.h" 25 | #include "camera.h" 26 | #include "texture.h" 27 | #include "material.h" 28 | #include "phase.h" 29 | #include "volume.h" 30 | #include "medium.h" 31 | #include "light.h" 32 | #include "scene.h" 33 | #include "path.h" 34 | #include "accel.h" 35 | #include "film.h" 36 | #include "model.h" 37 | #include "objloader.h" 38 | #include "renderer.h" 39 | #include "assetgroup.h" -------------------------------------------------------------------------------- /include/lm/loggercontext.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "logger.h" 9 | #include "component.h" 10 | 11 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 12 | LM_NAMESPACE_BEGIN(log) 13 | 14 | /*! 15 | \addtogroup log 16 | @{ 17 | */ 18 | 19 | /*! 20 | \brief Logger context. 21 | 22 | \rst 23 | You may implement this interface to implement user-specific log subsystem. 24 | Each virtual function corresponds to API call with functions inside ``log`` namespace. 25 | \endrst 26 | */ 27 | class LoggerContext : public Component { 28 | public: 29 | virtual void log(LogLevel level, int severity, const char* filename, int line, const char* message) = 0; 30 | virtual void update_indentation(int n) = 0; 31 | virtual void set_severity(int severity) = 0; 32 | }; 33 | 34 | /*! 35 | @} 36 | */ 37 | 38 | LM_NAMESPACE_END(log) 39 | LM_NAMESPACE_END(LM_NAMESPACE) 40 | -------------------------------------------------------------------------------- /include/lm/medium.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "component.h" 9 | #include "math.h" 10 | 11 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 12 | 13 | /*! 14 | \addtogroup medium 15 | @{ 16 | */ 17 | 18 | /*! 19 | \brief Participating medium. 20 | */ 21 | class Medium : public Component { 22 | public: 23 | //! Result of distance sampling. 24 | struct DistanceSample { 25 | Vec3 p; //!< Sampled point. 26 | Vec3 weight; //!< Contribution divided by probability. 27 | bool medium; //!< True if the point is is medium. 28 | }; 29 | 30 | /*! 31 | \brief Sample a distance in a ray direction. 32 | \param rng Random number generator. 33 | \param ray Ray. 34 | \param tmin Lower bound of the valid range of the ray. 35 | \param tmax Upper bound of the valid range of the ray. 36 | 37 | \rst 38 | This function samples a scattering event in the valid range of the ray segmnet. 39 | Note that this function assumes there is no scene surfaces 40 | in the given range of the ray segment. 41 | If it does not sample a scattering event, this function returns ``nullopt``. 42 | \endrst 43 | */ 44 | virtual std::optional sample_distance(Rng& rng, Ray ray, Float tmin, Float tmax) const = 0; 45 | 46 | /*! 47 | \brief Evaluate transmittance. 48 | 49 | \rst 50 | This function estimates transmittance of the given ray segment. 51 | Note that this function assumes there is no scene surfaces 52 | in the given range of the ray segment. 53 | This function might need a random number generator 54 | because heterogeneous media needs stochastic estimation. 55 | \endrst 56 | */ 57 | virtual Vec3 eval_transmittance(Rng& rng, Ray ray, Float tmin, Float tmax) const = 0; 58 | 59 | /*! 60 | \brief Check if the medium has emissive component. 61 | \return True if the participating media contains emitter. 62 | */ 63 | virtual bool is_emitter() const = 0; 64 | 65 | /*! 66 | \brief Get underlying phase function. 67 | \return PHase function. 68 | */ 69 | virtual const Phase* phase() const = 0; 70 | }; 71 | 72 | /*! 73 | @} 74 | */ 75 | 76 | LM_NAMESPACE_END(LM_NAMESPACE) 77 | -------------------------------------------------------------------------------- /include/lm/mesh.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "component.h" 9 | #include "math.h" 10 | 11 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 12 | 13 | /*! 14 | \addtogroup mesh 15 | @{ 16 | */ 17 | 18 | /*! 19 | \brief Triangle mesh. 20 | 21 | \rst 22 | This component interface represents a triangle mesh, 23 | respondible for handling or manipulating triangle mesh. 24 | \endrst 25 | */ 26 | class Mesh : public Component { 27 | public: 28 | /*! 29 | \brief Vertex of a triangle. 30 | 31 | \rst 32 | Represents geometry information associated with a vertice of a triangle. 33 | \endrst 34 | */ 35 | struct Point { 36 | Vec3 p; //!< Position. 37 | Vec3 n; //!< Normal. 38 | Vec2 t; //!< Texture coordinates. 39 | }; 40 | 41 | /*! 42 | \brief Triangle. 43 | 44 | \rst 45 | Represents a triangle composed of three vertices. 46 | \endrst 47 | */ 48 | struct Tri { 49 | Point p1; //!< First vertex. 50 | Point p2; //!< Second vertex 51 | Point p3; //!< Third vertex. 52 | }; 53 | 54 | /*! 55 | \brief Callback function for processing a triangle. 56 | \param face Face index. 57 | \param tri Triangle. 58 | 59 | \rst 60 | The function of this type is used as a callback function to process a single triangle, 61 | used as an argument of :cpp:func:`lm::Mesh::foreach_triangle` function. 62 | \endrst 63 | */ 64 | using ProcessTriangleFunc = std::function; 65 | 66 | /*! 67 | \brief Iterate triangles in the mesh. 68 | \param process_triangle Callback function to process a triangle. 69 | 70 | \rst 71 | This function enumerates all triangles inside the mesh. 72 | A specified callback function is called for each triangle. 73 | \endrst 74 | */ 75 | virtual void foreach_triangle(const ProcessTriangleFunc& process_triangle) const = 0; 76 | 77 | /*! 78 | \brief Get triangle by face index. 79 | */ 80 | virtual Tri triangle_at(int face) const = 0; 81 | 82 | /*! 83 | \brief Interpolated vertex of a triangle. 84 | */ 85 | struct InterpolatedPoint { 86 | Vec3 p; //!< Position. 87 | Vec3 n; //!< Shading normal. 88 | Vec3 gn; //!< Geometry normal. 89 | Vec2 t; //!< Texture coordinates. 90 | }; 91 | 92 | /*! 93 | \brief Compute surface geometry information at a point. 94 | */ 95 | virtual InterpolatedPoint surface_point(int face, Vec2 uv) const = 0; 96 | 97 | /*! 98 | \brief Get number of triangles. 99 | */ 100 | virtual int num_triangles() const = 0; 101 | }; 102 | 103 | /*! 104 | @} 105 | */ 106 | 107 | LM_NAMESPACE_END(LM_NAMESPACE) 108 | -------------------------------------------------------------------------------- /include/lm/model.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "component.h" 9 | #include "scenenode.h" 10 | 11 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 12 | 13 | /*! 14 | \addtogroup model 15 | @{ 16 | */ 17 | 18 | /*! 19 | \brief 3D model format. 20 | 21 | \rst 22 | The componet interface represents a 3D model 23 | that aggregates multiple meshes and materials. 24 | As well as meshes and materials, a model contains 25 | a set associations between meshes and materials, 26 | used to generate a set of scene primitives. 27 | See :ref:`making_scene` for detail. 28 | \endrst 29 | */ 30 | class Model : public Component { 31 | public: 32 | /*! 33 | \brief Callback function to process a primitive. 34 | \param mesh Underlying mesh. 35 | \param material Underlying material. 36 | \param light Underlying light. 37 | 38 | \rst 39 | The function of this type is used as an argument of 40 | :cpp:func:`lm::Model::create_primitives` function. 41 | \endrst 42 | */ 43 | using CreatePrimitiveFunc = std::function; 44 | 45 | /*! 46 | \brief Create primitives from underlying components. 47 | \param create_primitive Callback function to be called for each primitive. 48 | 49 | \rst 50 | This function enumerates a set of primitives generated from the model. 51 | A specified callback function is called for each primitive. 52 | The function is internally used by the framework, 53 | so the users do not want to used it directly. 54 | \endrst 55 | */ 56 | virtual void create_primitives(const CreatePrimitiveFunc& create_primitive) const = 0; 57 | 58 | /*! 59 | \brief Callback function to process a scene node in the model. 60 | \param node Scene node. 61 | 62 | \rst 63 | This function is called for each visit of the scene node in the model, 64 | used as a callback function for :cpp:func:`lm::Model::foreach_node`. 65 | \endrst 66 | */ 67 | using VisitNodeFuncType = std::function; 68 | 69 | /*! 70 | \brief Traverses scene nodes in the model. 71 | \param visit Callback function to be called for each visit of the scene node. 72 | 73 | \rst 74 | A model can have its own scene graph to represent a scene. 75 | This function can be used to traverse scene nodes in that scene graph. 76 | For instance, this function is used by :cpp:class:`lm::Scene` to copy 77 | the underlying scene graph to the main scene graph. 78 | \endrst 79 | */ 80 | virtual void foreach_node(const VisitNodeFuncType& visit) const = 0; 81 | }; 82 | 83 | /*! 84 | @} 85 | */ 86 | 87 | LM_NAMESPACE_END(LM_NAMESPACE) 88 | -------------------------------------------------------------------------------- /include/lm/objloader.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "component.h" 9 | #include "math.h" 10 | 11 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 12 | LM_NAMESPACE_BEGIN(objloader) 13 | 14 | /*! 15 | \addtogroup objloader 16 | @{ 17 | */ 18 | 19 | /*! 20 | \brief Surface geometry shared among meshes. 21 | */ 22 | struct OBJSurfaceGeometry { 23 | std::vector ps; //!< Positions. 24 | std::vector ns; //!< Normals. 25 | std::vector ts; //!< Texture coordinates. 26 | 27 | template 28 | void serialize(Archive& ar) { 29 | ar(ps, ns, ts); 30 | } 31 | }; 32 | 33 | /*! 34 | \brief Face indices. 35 | */ 36 | struct OBJMeshFaceIndex { 37 | int p = -1; //!< Index of position. 38 | int t = -1; //!< Index of texture coordinates. 39 | int n = -1; //!< Index of normal. 40 | 41 | template 42 | void serialize(Archive& ar) { 43 | ar(p, t, n); 44 | } 45 | }; 46 | 47 | /*! 48 | \brief Face. 49 | */ 50 | using OBJMeshFace = std::vector; 51 | 52 | /*! 53 | \brief MLT material parameters. 54 | */ 55 | struct MTLMatParams { 56 | std::string name; //!< Name. 57 | int illum; //!< Type. 58 | Vec3 Kd; //!< Diffuse reflectance. 59 | Vec3 Ks; //!< Specular reflectance. 60 | Vec3 Ke; //!< Luminance. 61 | std::string mapKd; //!< Path to the texture. 62 | Float Ni; //!< Index of refraction. 63 | Float Ns; //!< Specular exponent for phong shading. 64 | Float an; //!< Anisotropy. 65 | 66 | template 67 | void serialize(Archive& ar) { 68 | ar(name, illum, Kd, Ks, Ke, mapKd, Ni, Ns, an); 69 | } 70 | }; 71 | 72 | /*! 73 | \brief Callback function to process a mesh. 74 | */ 75 | using ProcessMeshFunc = std::function; 76 | 77 | /*! 78 | \brief Callback function to process a material. 79 | */ 80 | using ProcessMaterialFunc = std::function; 81 | 82 | // ------------------------------------------------------------------------------------------------ 83 | 84 | //! Default parallel context type 85 | constexpr const char* DefaultType = "simple"; 86 | 87 | /*! 88 | \brief Initialize objloader context. 89 | */ 90 | LM_PUBLIC_API void init(const std::string& type = DefaultType, const Json& prop = {}); 91 | 92 | /*! 93 | \brief Shutdown parallel context. 94 | */ 95 | LM_PUBLIC_API void shutdown(); 96 | 97 | /*! 98 | \brief Load an OBJ file. 99 | */ 100 | LM_PUBLIC_API bool load( 101 | const std::string& path, 102 | OBJSurfaceGeometry& geo, 103 | const ProcessMeshFunc& processMesh, 104 | const ProcessMaterialFunc& processMaterial); 105 | 106 | // ------------------------------------------------------------------------------------------------ 107 | 108 | /*! 109 | \brief OBJ loader context. 110 | */ 111 | class OBJLoaderContext : public Component { 112 | public: 113 | virtual bool load( 114 | const std::string& path, 115 | OBJSurfaceGeometry& geo, 116 | const ProcessMeshFunc& processMesh, 117 | const ProcessMaterialFunc& processMaterial) = 0; 118 | }; 119 | 120 | /*! 121 | @} 122 | */ 123 | 124 | LM_NAMESPACE_END(objloader) 125 | LM_NAMESPACE_END(LM_NAMESPACE) 126 | -------------------------------------------------------------------------------- /include/lm/parallel.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "common.h" 9 | #include "jsontype.h" 10 | 11 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 12 | LM_NAMESPACE_BEGIN(parallel) 13 | 14 | /*! 15 | \addtogroup parallel 16 | @{ 17 | */ 18 | 19 | //! Default parallel context type 20 | constexpr const char* DefaultType = "openmp"; 21 | 22 | /*! 23 | \brief Initialize parallel context. 24 | \param type Type of parallel subsystem. 25 | \param prop Properties for configuration. 26 | 27 | \rst 28 | This function initializes the logger subsystem specified by logger type ``type``. 29 | The function is implicitly called by the framework 30 | so the user do not want to explicitly call this function. 31 | \endrst 32 | */ 33 | LM_PUBLIC_API void init(const std::string& type = DefaultType, const Json& prop = {}); 34 | 35 | /*! 36 | \brief Shutdown parallel context. 37 | 38 | \rst 39 | This function shutdowns the parallell subsystem. 40 | You do not want to call this function because 41 | it is called implicitly by the framework. 42 | \endrst 43 | */ 44 | LM_PUBLIC_API void shutdown(); 45 | 46 | /*! 47 | \brief Get number of threads configured for the subsystem. 48 | \return Number of threads. 49 | */ 50 | LM_PUBLIC_API int num_threads(); 51 | 52 | /*! 53 | \brief Check if current thread is the main thread. 54 | \return `true` if the current thread is the main thread, `false` otherwise. 55 | */ 56 | LM_PUBLIC_API bool main_thread(); 57 | 58 | /*! 59 | \brief Callback function for parallel process. 60 | \param index Index of iteration. 61 | \param threadId Thread identifier in `0 ... num_threads()-1`. 62 | */ 63 | using ParallelProcessFunc = std::function; 64 | 65 | /*! 66 | \brief Callback function for progress updates. 67 | \param processed Processed number of samples. 68 | */ 69 | using ProgressUpdateFunc = std::function; 70 | 71 | /*! 72 | \brief Parallel for loop. 73 | \param num_samples Total number of samples. 74 | \param process_func Callback function called for each iteration. 75 | \param progress_func Callback function called for each progress update. 76 | 77 | \rst 78 | We provide an abstraction for the parallel loop specifialized for rendering purpose. 79 | \endrst 80 | */ 81 | LM_PUBLIC_API void foreach(long long num_samples, const ParallelProcessFunc& process_func, const ProgressUpdateFunc& progress_func); 82 | 83 | /*! 84 | \brief Parallel for loop. 85 | \param num_samples Total number of samples. 86 | \param process_func Callback function called for each iteration. 87 | */ 88 | LM_INLINE void foreach(long long num_samples, const ParallelProcessFunc& process_func) { 89 | foreach(num_samples, process_func, [](long long) {}); 90 | } 91 | 92 | /*! 93 | @} 94 | */ 95 | 96 | LM_NAMESPACE_END(parallel) 97 | LM_NAMESPACE_END(LM_NAMESPACE) 98 | -------------------------------------------------------------------------------- /include/lm/parallelcontext.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "parallel.h" 9 | #include "component.h" 10 | 11 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 12 | LM_NAMESPACE_BEGIN(parallel) 13 | 14 | /*! 15 | \addtogroup parallel 16 | @{ 17 | */ 18 | 19 | /*! 20 | \brief Parallel context. 21 | 22 | \rst 23 | You may implement this interface to implement user-specific parallel subsystem. 24 | Each virtual function corresponds to API call with a free function 25 | inside ``parallel`` namespace. 26 | \endrst 27 | */ 28 | class ParallelContext : public Component { 29 | public: 30 | virtual int num_threads() const = 0; 31 | virtual bool main_thread() const = 0; 32 | virtual void foreach(long long numSamples, const ParallelProcessFunc& processFunc, const ProgressUpdateFunc& progressFunc) const = 0; 33 | }; 34 | 35 | /*! 36 | @} 37 | */ 38 | 39 | LM_NAMESPACE_END(parallel) 40 | LM_NAMESPACE_END(LM_NAMESPACE) 41 | -------------------------------------------------------------------------------- /include/lm/phase.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "component.h" 9 | #include "math.h" 10 | 11 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 12 | 13 | /*! 14 | \addtogroup phase 15 | @{ 16 | */ 17 | 18 | /*! 19 | \brief Phase function. 20 | */ 21 | class Phase : public Component { 22 | public: 23 | //! Result of direction sampling. 24 | struct DirectionSample { 25 | Vec3 wo; //!< Sampled direction. 26 | Vec3 weight; //!< Contribution divided by probability. 27 | }; 28 | 29 | //! Random number input for direction sampling. 30 | struct DirectionSampleU { 31 | Vec2 ud; 32 | }; 33 | 34 | /*! 35 | \brief Sample a direction. 36 | \param u Random number input. 37 | \param geom Point geometry. 38 | \param wi Incident ray direction. 39 | \return Sampled direction and associated information. 40 | */ 41 | virtual std::optional sample_direction(const DirectionSampleU& u, const PointGeometry& geom, Vec3 wi) const = 0; 42 | 43 | /*! 44 | \brief Evaluate pdf in solid angle measure. 45 | \param geom Point geometry. 46 | \param wi Incident ray direction. 47 | \param wo Outgoing ray direction. 48 | \return Evaluated pdf. 49 | */ 50 | virtual Float pdf_direction(const PointGeometry& geom, Vec3 wi, Vec3 wo) const = 0; 51 | 52 | /*! 53 | \brief Evaluate the phase function. 54 | \param geom Point geometry. 55 | \param wi Incident ray direction. 56 | \param wo Outgoing ray direction. 57 | \return Evaluated value. 58 | */ 59 | virtual Vec3 eval(const PointGeometry& geom, Vec3 wi, Vec3 wo) const = 0; 60 | }; 61 | 62 | /*! 63 | @} 64 | */ 65 | 66 | LM_NAMESPACE_END(LM_NAMESPACE) 67 | -------------------------------------------------------------------------------- /include/lm/progress.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "component.h" 9 | 10 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 11 | LM_NAMESPACE_BEGIN(progress) 12 | 13 | /*! 14 | \addtogroup progress 15 | @{ 16 | */ 17 | 18 | //! Default progress reporter type 19 | constexpr const char* DefaultType = "default"; 20 | 21 | /*! 22 | \brief Initialize progress reporter context. 23 | \param type Type of progress reporter subsystem. 24 | \param prop Properties for configuration. 25 | 26 | \rst 27 | This function initializes exception subsystem of the framework. 28 | The function is implicitly called by the framework 29 | so the user do not want to explicitly call this function. 30 | \endrst 31 | */ 32 | LM_PUBLIC_API void init(const std::string& type = DefaultType, const Json& prop = {}); 33 | 34 | /*! 35 | \brief Shutdown progress reporter context. 36 | 37 | \rst 38 | This function shutdowns the exception subsystem. 39 | You do not want to call this function because it is called implicitly by the framework. 40 | \endrst 41 | */ 42 | LM_PUBLIC_API void shutdown(); 43 | 44 | /*! 45 | \brief Progress reporting mode. 46 | */ 47 | enum class ProgressMode { 48 | Samples, //!< Update sample count. 49 | Time //!< Update time. 50 | }; 51 | 52 | /*! 53 | \brief Start progress reporting. 54 | \param mode Progress reporting mode. 55 | \param total Total number of iterations (used in Samples mode). 56 | \param total_time Total time (used in Time mode). 57 | 58 | \rst 59 | This function specifies the start of the progress reporting. 60 | The argument ``total`` is necessary to calculate the ratio of progress 61 | over the entire workload. 62 | You may use :class:`ScopedReport` class to automatically enable/disable 63 | the floating point exception inside a scope. 64 | \endrst 65 | */ 66 | LM_PUBLIC_API void start(ProgressMode mode, long long total, double total_time); 67 | 68 | /*! 69 | \brief End progress reporting. 70 | 71 | \rst 72 | This function specifies the end of the progress reporting. 73 | You may use :class:`ScopedReport` class to automatically enable/disable 74 | the floating point exception inside a scope. 75 | \endrst 76 | */ 77 | LM_PUBLIC_API void end(); 78 | 79 | /*! 80 | \brief Update progress. 81 | \param processed Processed iterations. 82 | 83 | \rst 84 | This function notifies the update of the progress to the subsystem. 85 | ``processed`` must be between 0 to ``total`` specified 86 | in the :func:`lm::progress::start` function. 87 | \endrst 88 | */ 89 | LM_PUBLIC_API void update(long long processed); 90 | 91 | /*! 92 | \brief Update time progress. 93 | \param elapsed Elapsed time from the start. 94 | */ 95 | LM_PUBLIC_API void update_time(Float elapsed); 96 | 97 | /*! 98 | \brief Scoped guard of `start` and `end` functions. 99 | */ 100 | class ScopedReport { 101 | public: 102 | ScopedReport(long long total) { start(ProgressMode::Samples, total, -1); } 103 | ~ScopedReport() { end(); } 104 | LM_DISABLE_COPY_AND_MOVE(ScopedReport) 105 | }; 106 | 107 | /*! 108 | \brief Scoped guard of `startTime` and `end` functions. 109 | */ 110 | class ScopedTimeReport { 111 | public: 112 | ScopedTimeReport(double totalTime) { start(ProgressMode::Time, -1, totalTime); } 113 | ~ScopedTimeReport() { end(); } 114 | LM_DISABLE_COPY_AND_MOVE(ScopedTimeReport) 115 | }; 116 | 117 | /*! 118 | @} 119 | */ 120 | 121 | LM_NAMESPACE_END(progress) 122 | LM_NAMESPACE_END(LM_NAMESPACE) 123 | -------------------------------------------------------------------------------- /include/lm/progresscontext.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "progress.h" 9 | #include "component.h" 10 | 11 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 12 | LM_NAMESPACE_BEGIN(progress) 13 | 14 | /*! 15 | \addtogroup progress 16 | @{ 17 | */ 18 | 19 | /*! 20 | \brief Progress context. 21 | 22 | \rst 23 | You may implement this interface to implement user-specific progress reporting subsystem. 24 | Each virtual function corresponds to API call with a free function 25 | inside ``progress`` namespace. 26 | \endrst 27 | */ 28 | class ProgressContext : public Component { 29 | public: 30 | virtual void start(ProgressMode mode, long long total, double totalTime) = 0; 31 | virtual void update(long long processed) = 0; 32 | virtual void update_time(Float elapsed) = 0; 33 | virtual void end() = 0; 34 | }; 35 | 36 | /*! 37 | @} 38 | */ 39 | 40 | LM_NAMESPACE_END(progress) 41 | LM_NAMESPACE_END(LM_NAMESPACE) 42 | -------------------------------------------------------------------------------- /include/lm/renderer.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "component.h" 9 | 10 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 11 | 12 | /*! 13 | \addtogroup renderer 14 | @{ 15 | */ 16 | 17 | /*! 18 | \brief Renderer component interface. 19 | */ 20 | class Renderer : public Component { 21 | public: 22 | /*! 23 | \brief Process rendering. 24 | */ 25 | virtual Json render() const = 0; 26 | }; 27 | 28 | /*! 29 | @} 30 | */ 31 | 32 | LM_NAMESPACE_END(LM_NAMESPACE) 33 | -------------------------------------------------------------------------------- /include/lm/scheduler.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "component.h" 9 | #include 10 | 11 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 12 | LM_NAMESPACE_BEGIN(scheduler) 13 | 14 | /*! 15 | \addtogroup scheduler 16 | @{ 17 | */ 18 | 19 | /*! 20 | \brief Scheduler for rendering loop. 21 | 22 | \rst 23 | This interface provides an abstraction of the top-level rendering loop. 24 | \endrst 25 | */ 26 | class Scheduler : public Component { 27 | public: 28 | /*! 29 | \brief Callback function for parallel loop. 30 | \param pixel_index Pixel index. 31 | \param sample_index Pixel sample index. 32 | \param threadid Thread index. 33 | */ 34 | using ProcessFunc = std::function; 35 | 36 | /*! 37 | \brief Dispatch scheduler. 38 | \param process Callback function for parallel loop. 39 | \return Processed samples per pixel. 40 | */ 41 | virtual long long run(const ProcessFunc& process) const = 0; 42 | }; 43 | 44 | /*! 45 | @} 46 | */ 47 | 48 | LM_NAMESPACE_END(scheduler) 49 | LM_NAMESPACE_END(LM_NAMESPACE) 50 | -------------------------------------------------------------------------------- /include/lm/texture.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "component.h" 9 | #include "math.h" 10 | 11 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 12 | 13 | /*! 14 | \addtogroup texture 15 | @{ 16 | */ 17 | 18 | /*! 19 | \brief Texture size. 20 | 21 | \rst 22 | This structure represents a texture size 23 | used as a return type of :cpp:func:`lm::Texture::size` function. 24 | \endrst 25 | */ 26 | struct TextureSize { 27 | int w; // 10 | 11 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 12 | LM_NAMESPACE_BEGIN(timer) 13 | 14 | /*! 15 | \addtogroup timer 16 | @{ 17 | */ 18 | 19 | /*! 20 | \brief Scoped Timer for elapsed time 21 | 22 | \rst 23 | This class provides a compact way to time code using the RAII idiom: 24 | Resource Acquisition Is Initialization 25 | \endrst 26 | */ 27 | class ScopedTimer : public Component { 28 | private: 29 | std::chrono::high_resolution_clock::time_point start; 30 | public: 31 | /*! 32 | \brief Timer initialisation at the time of object creation 33 | */ 34 | ScopedTimer():start(std::chrono::high_resolution_clock::now()){} 35 | 36 | /*! 37 | \brief elapsed time since object creation in seconds 38 | */ 39 | virtual Float now() const{ 40 | return std::chrono::duration(std::chrono::high_resolution_clock::now()-start).count(); 41 | } 42 | }; 43 | 44 | /*! 45 | @} 46 | */ 47 | 48 | LM_NAMESPACE_END(timer) 49 | LM_NAMESPACE_END(LM_NAMESPACE) 50 | -------------------------------------------------------------------------------- /include/lm/user.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "component.h" 9 | #include "assetgroup.h" 10 | #include "math.h" 11 | #include 12 | #include 13 | 14 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 15 | 16 | /*! 17 | \addtogroup user 18 | @{ 19 | */ 20 | 21 | /*! 22 | \brief Initialize the framework. 23 | \param prop Properties for configuration. 24 | \see `example/blank.cpp` 25 | 26 | \rst 27 | The framework must be initialized with this function before any use of other APIs. 28 | The properties are passed as JSON format and used to initialize 29 | the internal subsystems of the framework. 30 | This function initializes some subsystems with default types. 31 | If you want to configure the subsystem, you want to call each ``init()`` function afterwards. 32 | \endrst 33 | */ 34 | LM_PUBLIC_API void init(const Json& prop = {}); 35 | 36 | /*! 37 | \brief Shutdown the framework. 38 | 39 | \rst 40 | This function explicit shutdowns the framework. 41 | \endrst 42 | */ 43 | LM_PUBLIC_API void shutdown(); 44 | 45 | /*! 46 | \brief Reset the internal state of the framework. 47 | 48 | \rst 49 | This function resets underlying states including assets and scene to the initial state. 50 | The global contexts remains same. 51 | \endrst 52 | */ 53 | LM_PUBLIC_API void reset(); 54 | 55 | /*! 56 | \brief Print information about Lightmetrica. 57 | */ 58 | LM_PUBLIC_API void info(); 59 | 60 | /*! 61 | \brief Get underlying collection of assets. 62 | \return Instance. 63 | */ 64 | LM_PUBLIC_API AssetGroup* assets(); 65 | 66 | /*! 67 | \brief Save internal state to a file. 68 | \param path Output path. 69 | */ 70 | LM_PUBLIC_API void save_state_to_file(const std::string& path); 71 | 72 | /*! 73 | \brief Load internal state from a file. 74 | \param path Input path. 75 | */ 76 | LM_PUBLIC_API void load_state_from_file(const std::string& path); 77 | 78 | /*! 79 | \brief Load an asset with given type. 80 | \tparam T Component interface type. 81 | \param name Name of the asset. 82 | \param impl_key Key of component implementation in `interface::implementation` format. 83 | \param prop Properties. 84 | */ 85 | template 86 | T* load(const std::string& name, const std::string& impl_key, const Json& prop) { 87 | return dynamic_cast(assets()->load_asset(name, impl_key, prop)); 88 | } 89 | 90 | /*! 91 | @} 92 | */ 93 | 94 | LM_NAMESPACE_END(LM_NAMESPACE) 95 | -------------------------------------------------------------------------------- /include/lm/version.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "common.h" 9 | 10 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 11 | LM_NAMESPACE_BEGIN(version) 12 | 13 | /*! 14 | \addtogroup version 15 | @{ 16 | */ 17 | 18 | //! Major version. 19 | LM_PUBLIC_API int major_version(); 20 | 21 | //! Minor version. 22 | LM_PUBLIC_API int minor_version(); 23 | 24 | //! Patch version. 25 | LM_PUBLIC_API int patch_version(); 26 | 27 | //! Revision string. 28 | LM_PUBLIC_API std::string revision(); 29 | 30 | //! Build timestamp. 31 | LM_PUBLIC_API std::string build_timestamp(); 32 | 33 | //! Platform. 34 | LM_PUBLIC_API std::string platform(); 35 | 36 | //! Architecture. 37 | LM_PUBLIC_API std::string architecture(); 38 | 39 | //! Formatted version string. 40 | LM_PUBLIC_API std::string formatted(); 41 | 42 | /*! 43 | @} 44 | */ 45 | 46 | LM_NAMESPACE_END(version) 47 | LM_NAMESPACE_END(LM_NAMESPACE) 48 | -------------------------------------------------------------------------------- /include/lm/volume.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "component.h" 9 | #include "math.h" 10 | #include "exception.h" 11 | 12 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 13 | 14 | /*! 15 | \addtogroup volume 16 | @{ 17 | */ 18 | 19 | /*! 20 | \brief Volume data. 21 | */ 22 | class Volume : public Component { 23 | public: 24 | /*! 25 | \brief Get bound of the volume. 26 | \return Bound of the volume. 27 | */ 28 | virtual Bound bound() const = 0; 29 | 30 | // -------------------------------------------------------------------------------------------- 31 | 32 | /*! 33 | \brief Check if the volume has scalar component. 34 | \return True if the volume has scalar component. 35 | */ 36 | virtual bool has_scalar() const = 0; 37 | 38 | /*! 39 | \brief Evaluate maximum scalar value. 40 | */ 41 | virtual Float max_scalar() const = 0; 42 | 43 | /*! 44 | \brief Evaluate scalar value. 45 | \param p Position in volume coordinates. 46 | \return Evaluated scalar value. 47 | */ 48 | virtual Float eval_scalar(Vec3 p) const { 49 | LM_UNUSED(p); 50 | LM_THROW_EXCEPTION_DEFAULT(Error::Unimplemented); 51 | } 52 | 53 | // -------------------------------------------------------------------------------------------- 54 | 55 | /*! 56 | \brief Check if the volume has color component. 57 | \return True if the volume has color component. 58 | */ 59 | virtual bool has_color() const = 0; 60 | 61 | /*! 62 | \brief Evaluate color. 63 | \param p Position in volume coordinates. 64 | \return Evaluated color value. 65 | */ 66 | virtual Vec3 eval_color(Vec3 p) const { 67 | LM_UNUSED(p); 68 | LM_THROW_EXCEPTION_DEFAULT(Error::Unimplemented); 69 | } 70 | 71 | // -------------------------------------------------------------------------------------------- 72 | 73 | /*! 74 | \brief Callback function called for ray marching. 75 | \param t Distance from the ray origin. 76 | \retval true Continue raymarching. 77 | \retval false Abort raymarching. 78 | */ 79 | using RaymarchFunc = std::function; 80 | 81 | /*! 82 | \brief March the volume along with the ray. 83 | \param ray Ray. 84 | \param tmin Lower bound of the valid range of the ray. 85 | \param tmax Upper bound of the valid range of the ray. 86 | \param march_step Ray marching step in world space. 87 | \param raymarch_func Raymarching function. 88 | 89 | \rst 90 | This function performs volume-specific raymarching operations. 91 | \endrst 92 | */ 93 | virtual void march(Ray ray, Float tmin, Float tmax, Float march_step, const RaymarchFunc& raymarch_func) const { 94 | LM_UNUSED(ray, tmin, tmax, march_step, raymarch_func); 95 | LM_THROW_EXCEPTION_DEFAULT(Error::Unimplemented); 96 | } 97 | }; 98 | 99 | /*! 100 | @} 101 | */ 102 | 103 | LM_NAMESPACE_END(LM_NAMESPACE) 104 | -------------------------------------------------------------------------------- /lightmetrica/__init__.py: -------------------------------------------------------------------------------- 1 | """Entry point for Lightmetrica's Python binding""" 2 | 3 | import os 4 | import numpy as np 5 | 6 | try: 7 | # Import lightmetrica module on development. 8 | # sys.path is assumed to include module and binary directories. 9 | from pylm import * 10 | except: 11 | # Otherwise try to import from current directory 12 | from .pylm import * 13 | 14 | def pylm_component(name): 15 | """Decorator for registering a class to lightmetrica""" 16 | def pylm_component_(object): 17 | # Get base class 18 | base = object.__bases__[0] 19 | base.reg(object, name) 20 | return object 21 | return pylm_component_ 22 | 23 | def array(*args, **kwargs): 24 | """Numpy array with default floating point type of lightmetrica""" 25 | kwargs.setdefault('dtype', np.float32) 26 | return np.array(*args, **kwargs) 27 | 28 | def registered_components(): 29 | """Get a list of registered components""" 30 | comps = [] 31 | def print_name(name): 32 | comps.append(name) 33 | comp.foreachRegistered(print_name) 34 | return comps -------------------------------------------------------------------------------- /pch/pch.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include -------------------------------------------------------------------------------- /pch/pch.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #ifdef __GNUC__ 23 | #include 24 | namespace fs = std::experimental::filesystem; 25 | #else 26 | #include 27 | namespace fs = std::filesystem; 28 | #endif 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #define WIN32_LEAN_AND_MEAN 35 | #include 36 | #include 37 | #include 38 | #pragma warning(push) 39 | #pragma warning(disable:4127) // conditional expression is constant 40 | #include 41 | #pragma warning(pop) -------------------------------------------------------------------------------- /pch/pch_pylm.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include -------------------------------------------------------------------------------- /pch/pch_pylm.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include "pch.h" 7 | #include 8 | #include 9 | #include 10 | #include -------------------------------------------------------------------------------- /plugin/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | # Distributed under MIT license. See LICENSE file for details. 4 | # 5 | 6 | add_subdirectory(accel_nanort) 7 | add_subdirectory(accel_embree) 8 | add_subdirectory(objloader_tinyobjloader) 9 | add_subdirectory(model_pbrt) 10 | add_subdirectory(volume_openvdb) -------------------------------------------------------------------------------- /plugin/accel_embree/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | # Distributed under MIT license. See LICENSE file for details. 4 | # 5 | 6 | include(LmAddPlugin) 7 | 8 | # embree 9 | # https://github.com/embree/embree 10 | 11 | find_package(embree 3.0) 12 | if (embree_FOUND) 13 | # Create embree target 14 | add_library(embree_interface INTERFACE) 15 | target_link_libraries(embree_interface INTERFACE "${EMBREE_LIBRARIES}") 16 | target_include_directories(embree_interface INTERFACE "${EMBREE_INCLUDE_DIRS}") 17 | 18 | # Create plugin 19 | lm_add_plugin( 20 | NAME accel_embree 21 | INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" 22 | LIBRARIES embree_interface 23 | SOURCES 24 | "embree_params.h" 25 | "accel_embree.cpp" 26 | "accel_embree_instanced.cpp") 27 | 28 | # Custom command to copy dynamic libraries 29 | if (WIN32) 30 | include(LmCopyDll) 31 | add_custom_target(copydll_embree) 32 | add_custom_command_copy_dll( 33 | TARGET copydll_embree 34 | NAME accel_embree 35 | DLL ${embree_DIR}/bin/embree3.dll) 36 | add_custom_command_copy_dll( 37 | TARGET copydll_embree 38 | NAME accel_embree 39 | DLL ${embree_DIR}/bin/tbb.dll) 40 | add_custom_command_copy_dll( 41 | TARGET copydll_embree 42 | NAME accel_embree 43 | DLL ${embree_DIR}/bin/tbbmalloc.dll) 44 | set_target_properties(copydll_embree PROPERTIES FOLDER "lm/plugin") 45 | endif() 46 | endif() -------------------------------------------------------------------------------- /plugin/accel_nanort/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | # Distributed under MIT license. See LICENSE file for details. 4 | # 5 | 6 | include(LmAddPlugin) 7 | 8 | if (LM_USE_EXTERNAL_DIR AND EXISTS "${LM_EXTERNAL_DIR}/nanort") 9 | set(_NANORT_DIR "${LM_EXTERNAL_DIR}/nanort") 10 | elseif (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/nanort") 11 | set(_NANORT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/nanort") 12 | endif() 13 | 14 | if (DEFINED _NANORT_DIR) 15 | add_library(nanort INTERFACE) 16 | target_include_directories(nanort INTERFACE "${_NANORT_DIR}") 17 | lm_add_plugin( 18 | NAME accel_nanort 19 | LIBRARIES nanort 20 | SOURCES 21 | "accel_nanort.cpp") 22 | endif() -------------------------------------------------------------------------------- /plugin/accel_nanort/accel_nanort.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 14 | 15 | struct FlattenedPrimitiveNode { 16 | Transform global_transform; // Global transform of the primitive 17 | int primitive; // Primitive node index 18 | }; 19 | 20 | /* 21 | \rst 22 | .. function:: accel::nanort 23 | 24 | Acceleration structure with nanort library. 25 | \endrst 26 | */ 27 | class Accel_NanoRT final : public Accel { 28 | private: 29 | std::vector vs_; 30 | std::vector fs_; 31 | nanort::BVHAccel accel_; 32 | std::vector> flatten_node_and_face_per_triangle_; 33 | std::vector flattened_nodes_; 34 | 35 | public: 36 | virtual void build(const Scene& scene) override { 37 | // Make a combined mesh 38 | LM_INFO("Flattening scene"); 39 | vs_.clear(); 40 | fs_.clear(); 41 | flatten_node_and_face_per_triangle_.clear(); 42 | flattened_nodes_.clear(); 43 | scene.traverse_primitive_nodes([&](const SceneNode& node, Mat4 global_transform) { 44 | if (node.type != SceneNodeType::Primitive) { 45 | return; 46 | } 47 | if (!node.primitive.mesh) { 48 | return; 49 | } 50 | 51 | // Record flattened primitive 52 | const int flatten_node_index = int(flattened_nodes_.size()); 53 | flattened_nodes_.push_back({ Transform(global_transform), node.index }); 54 | 55 | // Triangles 56 | node.primitive.mesh->foreach_triangle([&](int face, const Mesh::Tri& tri) { 57 | const auto p1 = global_transform * Vec4(tri.p1.p, 1_f); 58 | const auto p2 = global_transform * Vec4(tri.p2.p, 1_f); 59 | const auto p3 = global_transform * Vec4(tri.p3.p, 1_f); 60 | vs_.insert(vs_.end(), { p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z }); 61 | auto s = (unsigned int)(fs_.size()); 62 | fs_.insert(fs_.end(), { s, s+1, s+2 }); 63 | flatten_node_and_face_per_triangle_.push_back({ flatten_node_index, face }); 64 | }); 65 | }); 66 | 67 | // Build acceleration structure 68 | LM_INFO("Building"); 69 | nanort::BVHBuildOptions options; 70 | nanort::TriangleMesh mesh(vs_.data(), fs_.data(), sizeof(Float) * 3); 71 | nanort::TriangleSAHPred pred(vs_.data(), fs_.data(), sizeof(Float) * 3); 72 | accel_.Build((unsigned int)(fs_.size() / 3), mesh, pred, options); 73 | } 74 | 75 | virtual std::optional intersect(Ray ray, Float tmin, Float tmax) const override { 76 | exception::ScopedDisableFPEx guard_; 77 | 78 | nanort::Ray r; 79 | r.org[0] = ray.o[0]; 80 | r.org[1] = ray.o[1]; 81 | r.org[2] = ray.o[2]; 82 | r.dir[0] = ray.d[0]; 83 | r.dir[1] = ray.d[1]; 84 | r.dir[2] = ray.d[2]; 85 | r.min_t = tmin; 86 | r.max_t = tmax; 87 | 88 | nanort::TriangleIntersector intersector(vs_.data(), fs_.data(), sizeof(Float) * 3); 89 | nanort::TriangleIntersection isect; 90 | if (!accel_.Traverse(r, intersector, &isect)) { 91 | return {}; 92 | } 93 | 94 | const auto [node, face] = flatten_node_and_face_per_triangle_.at(isect.prim_id); 95 | const auto& fn = flattened_nodes_.at(node); 96 | return Hit{ isect.t, Vec2(isect.u, isect.v), fn.global_transform, fn.primitive, face }; 97 | } 98 | }; 99 | 100 | LM_COMP_REG_IMPL(Accel_NanoRT, "accel::nanort"); 101 | 102 | LM_NAMESPACE_END(LM_NAMESPACE) 103 | -------------------------------------------------------------------------------- /plugin/model_pbrt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | # Distributed under MIT license. See LICENSE file for details. 4 | # 5 | 6 | include(LmAddPlugin) 7 | 8 | find_package(pbrtParser) 9 | if (pbrtParser_FOUND) 10 | lm_add_plugin( 11 | NAME model_pbrt 12 | LIBRARIES pbrtParser::pbrtParser 13 | SOURCES 14 | "model_pbrt.cpp") 15 | endif() 16 | -------------------------------------------------------------------------------- /plugin/objloader_tinyobjloader/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | # Distributed under MIT license. See LICENSE file for details. 4 | # 5 | 6 | include(LmAddPlugin) 7 | 8 | if (LM_USE_EXTERNAL_DIR AND EXISTS "${LM_EXTERNAL_DIR}/tinyobjloader") 9 | set(_TINYOBJLOADER_DIR "${LM_EXTERNAL_DIR}/tinyobjloader") 10 | elseif (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tinyobjloader") 11 | set(_TINYOBJLOADER_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tinyobjloader") 12 | endif() 13 | 14 | if (DEFINED _TINYOBJLOADER_DIR) 15 | add_library(tinyobjloader INTERFACE) 16 | target_include_directories(tinyobjloader INTERFACE "${_TINYOBJLOADER_DIR}") 17 | lm_add_plugin( 18 | NAME objloader_tinyobjloader 19 | LIBRARIES tinyobjloader 20 | SOURCES 21 | "objloader_tinyobjloader.cpp") 22 | endif() 23 | -------------------------------------------------------------------------------- /plugin/volume_openvdb/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | # Distributed under MIT license. See LICENSE file for details. 4 | # 5 | 6 | include(LmAddPlugin) 7 | 8 | # Find vdbloader library 9 | find_package(vdbloader) 10 | if (vdbloader_FOUND) 11 | # Create plugin 12 | lm_add_plugin( 13 | NAME volume_openvdb 14 | LIBRARIES 15 | vdbloader::vdbloader 16 | SOURCES 17 | "volume_openvdb.cpp" 18 | "renderer_volraycast.cpp") 19 | endif() -------------------------------------------------------------------------------- /plugin/volume_openvdb/volume_openvdb.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 11 | 12 | class Volume_OpenVDBScalar : public Volume { 13 | private: 14 | VDBLoaderContext context_; 15 | Float scale_; 16 | Bound bound_; 17 | Float max_scalar_; 18 | 19 | public: 20 | Volume_OpenVDBScalar() { 21 | vdbloaderSetErrorFunc(nullptr, [](void*, int errorCode, const char* message) { 22 | const auto errStr = [&]() -> std::string { 23 | if (errorCode == VDBLOADER_ERROR_INVALID_CONTEXT) 24 | return "INVALID_CONTEXT"; 25 | else if (errorCode == VDBLOADER_ERROR_INVALID_ARGUMENT) 26 | return "INVALID_ARGUMENT"; 27 | else if (errorCode == VDBLOADER_ERROR_UNKNOWN) 28 | return "UNKNOWN"; 29 | LM_UNREACHABLE_RETURN(); 30 | }(); 31 | LM_ERROR("vdbloader error: {} [type='{}']", message, errStr); 32 | }); 33 | context_ = vdbloaderCreateContext(); 34 | } 35 | 36 | ~Volume_OpenVDBScalar() { 37 | vdbloaderReleaseContext(context_); 38 | } 39 | 40 | public: 41 | virtual void construct(const Json& prop) override { 42 | // Load VDB file 43 | const auto path = json::value(prop, "path"); 44 | LM_INFO("Opening OpenVDB file [path='{}']", path); 45 | if (!vdbloaderLoadVDBFile(context_, path.c_str())) { 46 | LM_THROW_EXCEPTION(Error::IOError, "Failed to load OpenVDB file [path='{}']", path); 47 | } 48 | 49 | // Density scale 50 | scale_ = json::value(prop, "scale", 1_f); 51 | 52 | // Bound 53 | const auto b = vdbloaderGetBound(context_); 54 | bound_.min = Vec3(b.min.x, b.min.y, b.min.z); 55 | bound_.max = Vec3(b.max.x, b.max.y, b.max.z); 56 | 57 | // Maximum density 58 | max_scalar_ = vbdloaderGetMaxScalar(context_) * scale_; 59 | } 60 | 61 | virtual Bound bound() const override { 62 | return bound_; 63 | } 64 | 65 | virtual Float max_scalar() const override { 66 | return max_scalar_; 67 | } 68 | 69 | virtual bool has_scalar() const override { 70 | return true; 71 | } 72 | 73 | virtual Float eval_scalar(Vec3 p) const override { 74 | const auto d = vbdloaderEvalScalar(context_, VDBLoaderFloat3{ p.x, p.y, p.z }); 75 | return d * scale_; 76 | } 77 | 78 | virtual bool has_color() const override { 79 | return false; 80 | } 81 | 82 | virtual void march(Ray ray, Float tmin, Float tmax, Float marchStep, const RaymarchFunc& raymarchFunc) const override { 83 | exception::ScopedDisableFPEx guard_; 84 | const void* f = reinterpret_cast(&raymarchFunc); 85 | vdbloaderMarchVolume( 86 | context_, 87 | VDBLoaderFloat3{ ray.o.x, ray.o.y, ray.o.z }, 88 | VDBLoaderFloat3{ ray.d.x, ray.d.y, ray.d.z }, 89 | tmin, tmax, marchStep, const_cast(f), 90 | [](void* user, double t) -> bool { 91 | const void* f = const_cast(user); 92 | const RaymarchFunc& raymarchFunc = *reinterpret_cast(f); 93 | return raymarchFunc(t); 94 | }); 95 | } 96 | }; 97 | 98 | LM_COMP_REG_IMPL(Volume_OpenVDBScalar, "volume::openvdb_scalar"); 99 | 100 | LM_NAMESPACE_END(LM_NAMESPACE) 101 | -------------------------------------------------------------------------------- /pytest/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | # Distributed under MIT license. See LICENSE file for details. 4 | # 5 | 6 | # Python module for unit test in python 7 | set(_PROJECT_NAME pylm_test) 8 | set(_SOURCE_FILES 9 | "pylm_test.cpp" 10 | "pylm_test_component.cpp" 11 | "pylm_test_json.cpp" 12 | "pylm_test_simple.cpp" 13 | "pylm_test_math.cpp") 14 | set(_PCH_DIR "${PROJECT_SOURCE_DIR}/pch") 15 | set(_PCH_FILES 16 | "${_PCH_DIR}/pch_pylm.h" 17 | "${_PCH_DIR}/pch_pylm.cpp") 18 | add_library(${_PROJECT_NAME} MODULE ${_SOURCE_FILES} ${_PCH_FILES}) 19 | if (MSVC) 20 | add_precompiled_header(${_PROJECT_NAME} "${_PCH_DIR}/pch_pylm.h" SOURCE_CXX "${_PCH_DIR}/pch_pylm.cpp") 21 | endif() 22 | target_link_libraries(${_PROJECT_NAME} PRIVATE liblm pybind11::module lm_test_plugin::interface) 23 | target_include_directories(${_PROJECT_NAME} PRIVATE "${_PCH_DIR}") 24 | set_target_properties(${_PROJECT_NAME} PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" SUFFIX "${PYTHON_MODULE_EXTENSION}") 25 | set_target_properties(${_PROJECT_NAME} PROPERTIES FOLDER "lm/test") 26 | set_target_properties(${_PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") -------------------------------------------------------------------------------- /pytest/conftest.py: -------------------------------------------------------------------------------- 1 | """ pytest configuration """ 2 | import os 3 | import sys 4 | import time 5 | import json 6 | from argparse import Namespace 7 | import pytest 8 | 9 | def pytest_addoption(parser): 10 | """Add command line options""" 11 | parser.addoption("--lmenv", 12 | action="store", 13 | required=True, 14 | help="Path to .lmenv file") 15 | 16 | def pytest_configure(config): 17 | """Configure pytest""" 18 | # Read .lmeenv file 19 | lmenv_path = config.getoption("--lmenv") 20 | with open(lmenv_path) as f: 21 | config = json.load(f) 22 | 23 | # Add root directory and binary directory to sys.path 24 | if config['path'] not in sys.path: 25 | sys.path.insert(0, config['path']) 26 | if config['bin_path'] not in sys.path: 27 | sys.path.insert(0, config['bin_path']) 28 | 29 | # Make lmenv accessible from the test code 30 | pytest.lmenv = Namespace(**config) 31 | -------------------------------------------------------------------------------- /pytest/pylm_test.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | LM_NAMESPACE_BEGIN(lmtest) 10 | 11 | PYBIND11_MODULE(pylm_test, m) { 12 | m.doc() = "Lightmetrica python test module"; 13 | 14 | // Initialize submodules 15 | std::regex reg(R"(pytestbinder::(\w+))"); 16 | lm::comp::detail::foreach_registered([&](const std::string& name) { 17 | // Find the name matched with 'pytestbinder::*' 18 | std::smatch match; 19 | if (std::regex_match(name, match, reg)) { 20 | auto binder = lm::comp::create(name, ""); 21 | auto submodule = m.def_submodule(match[1].str().c_str()); 22 | binder->bind(submodule); 23 | } 24 | }); 25 | } 26 | 27 | LM_NAMESPACE_END(lmtest) 28 | -------------------------------------------------------------------------------- /pytest/pylm_test_json.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | namespace py = pybind11; 10 | using namespace py::literals; 11 | 12 | LM_NAMESPACE_BEGIN(lmtest) 13 | 14 | class PyTestBinder_Json : public lm::PyBinder { 15 | public: 16 | virtual void bind(py::module& m) const { 17 | m.def("round_trip", [](lm::Json v) -> lm::Json { 18 | return v; 19 | }); 20 | } 21 | }; 22 | 23 | LM_COMP_REG_IMPL(PyTestBinder_Json, "pytestbinder::json"); 24 | 25 | LM_NAMESPACE_END(lmtest) 26 | -------------------------------------------------------------------------------- /pytest/pylm_test_math.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | namespace py = pybind11; 10 | using namespace py::literals; 11 | 12 | LM_NAMESPACE_BEGIN(lmtest) 13 | 14 | class PyTestBinder_Math : public lm::PyBinder { 15 | public: 16 | virtual void bind(py::module& m) const { 17 | // Python -> C++ 18 | m.def("comp_sum2", [](lm::Vec2 v) -> lm::Float { 19 | return v.x + v.y; 20 | }); 21 | m.def("comp_sum3", [](lm::Vec3 v) -> lm::Float { 22 | return v.x + v.y + v.z; 23 | }); 24 | m.def("comp_sum4", [](lm::Vec4 v) -> lm::Float { 25 | return v.x + v.y + v.z + v.w; 26 | }); 27 | m.def("comp_mat4", [](lm::Mat4 m) -> lm::Float { 28 | lm::Float sum = 0; 29 | for (int i = 0; i < 4; i++) { 30 | for (int j = 0; j < 4; j++) { 31 | sum += m[i][j]; 32 | } 33 | } 34 | return sum; 35 | }); 36 | 37 | // C++ -> Python 38 | m.def("get_vec2", []() -> lm::Vec2 { 39 | return lm::Vec2(1, 2); 40 | }); 41 | m.def("get_vec3", []() -> lm::Vec3 { 42 | return lm::Vec3(1, 2, 3); 43 | }); 44 | m.def("get_vec4", []() -> lm::Vec4 { 45 | return lm::Vec4(1, 2, 3, 4); 46 | }); 47 | m.def("get_mat4", []() -> lm::Mat4 { 48 | return lm::Mat4( 49 | 1,1,0,1, 50 | 1,1,1,1, 51 | 0,1,1,0, 52 | 1,0,1,1 53 | ); 54 | }); 55 | } 56 | }; 57 | 58 | LM_COMP_REG_IMPL(PyTestBinder_Math, "pytestbinder::math"); 59 | 60 | LM_NAMESPACE_END(lmtest) 61 | -------------------------------------------------------------------------------- /pytest/pylm_test_simple.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | namespace py = pybind11; 10 | using namespace py::literals; 11 | 12 | LM_NAMESPACE_BEGIN(lmtest) 13 | 14 | class PyTestBinder_Simple : public lm::PyBinder { 15 | public: 16 | virtual void bind(py::module& m) const { 17 | m.def("test", []() -> int { 18 | return 42; 19 | }); 20 | } 21 | }; 22 | 23 | LM_COMP_REG_IMPL(PyTestBinder_Simple, "pytestbinder::simple"); 24 | 25 | LM_NAMESPACE_END(lmtest) 26 | -------------------------------------------------------------------------------- /pytest/test_json.py: -------------------------------------------------------------------------------- 1 | """JSON tests""" 2 | import pytest 3 | from numpy.testing import assert_allclose 4 | import lightmetrica as lm 5 | from pylm_test import json as m 6 | 7 | def test_round_trip_simple(): 8 | """ Tests simple types """ 9 | # Null 10 | assert m.round_trip(None) == None 11 | # Boolean type 12 | assert m.round_trip(True) == True 13 | assert m.round_trip(False) == False 14 | # Integer type 15 | assert m.round_trip(42) == 42 16 | assert m.round_trip(-42) == -42 17 | # Floating point type 18 | assert m.round_trip(3.14) == pytest.approx(3.14) 19 | assert m.round_trip(-3.14) == pytest.approx(-3.14) 20 | # String 21 | assert m.round_trip('') == '' 22 | assert m.round_trip('hai domo') == 'hai domo' 23 | 24 | def test_round_trip_sequence(): 25 | """Tests sequence type""" 26 | # Empty 27 | assert m.round_trip([]) == [] 28 | # Simple 29 | assert m.round_trip([1,2,3]) == [1,2,3] 30 | assert m.round_trip([1.1,2.2,3.3]) == pytest.approx([1.1,2.2,3.3]) 31 | assert m.round_trip(['a','ab','abc']) == ['a','ab','abc'] 32 | # Heterogeneous type 33 | assert m.round_trip([42,3.14,'hai domo']) == [42,pytest.approx(3.14),'hai domo'] 34 | # Nested type 35 | assert m.round_trip([[],[[],[[],[]]]]) == [[],[[],[[],[]]]] 36 | 37 | def test_round_trip_dict(): 38 | """Tests dict type""" 39 | # Empty 40 | assert m.round_trip({}) == {} 41 | # Simple 42 | assert m.round_trip({'1':1,'3':2,'3':3}) == {'1':1,'3':2,'3':3} 43 | assert m.round_trip({'1':1.1,'3':2.2}) == {'1':pytest.approx(1.1),'3':pytest.approx(2.2)} 44 | assert m.round_trip({'1':'a','3':'ab','3':'abc'}) == {'1':'a','3':'ab','3':'abc'} 45 | # Heterogeneous type 46 | assert m.round_trip({'1':42,'3':3.14,'3':'hai domo'}) == {'1':42,'3':pytest.approx(3.14),'3':'hai domo'} 47 | # Nested type 48 | value = { 49 | # Sequence of dict 50 | '1': [ 51 | {'1.1': 42}, 52 | {'1.2': 43} 53 | ], 54 | # Dict of sequence 55 | '2': { 56 | '2.1': [1,2,3], 57 | '2.2': [4,5,6] 58 | }, 59 | # Multiple nesting 60 | '3': {'3.1': {'3.2':{'3.3':{}}}} 61 | } 62 | assert m.round_trip(value) == value 63 | -------------------------------------------------------------------------------- /pytest/test_math.py: -------------------------------------------------------------------------------- 1 | """Math tests""" 2 | import pytest 3 | import numpy as np 4 | from numpy.testing import assert_allclose 5 | import lightmetrica as lm 6 | from pylm_test import math as m 7 | 8 | def to_lmfloat(v): 9 | if lm.FloatT == lm.Float32: 10 | return v.astype(np.float32) 11 | elif lm.FloatT == lm.Float64: 12 | return v.astype(np.float64) 13 | 14 | def test_from_python(): 15 | """Tests conversion of Python -> C++""" 16 | # Vector types 17 | assert m.comp_sum2(to_lmfloat(np.array([1,2]))) == pytest.approx(3) 18 | assert m.comp_sum3(to_lmfloat(np.array([1,2,3]))) == pytest.approx(6) 19 | assert m.comp_sum4(to_lmfloat(np.array([1,2,3,4]))) == pytest.approx(10) 20 | 21 | # Matrix types 22 | mat = np.array([[1,0,0,1], 23 | [0,1,0,1], 24 | [0,0,1,1], 25 | [1,1,0,0]]) 26 | assert m.comp_mat4(to_lmfloat(mat)) == pytest.approx(8) 27 | 28 | def test_to_python(): 29 | """Tests conversion of C++ -> Python""" 30 | # Vector types 31 | assert m.get_vec2() == pytest.approx([1,2]) 32 | assert m.get_vec3() == pytest.approx([1,2,3]) 33 | assert m.get_vec4() == pytest.approx([1,2,3,4]) 34 | 35 | # Matrix types 36 | mat = np.array([[1,1,0,1], 37 | [1,1,1,0], 38 | [0,1,1,1], 39 | [1,1,0,1]]) 40 | assert_allclose(m.get_mat4(), mat) -------------------------------------------------------------------------------- /pytest/test_simple.py: -------------------------------------------------------------------------------- 1 | """Simple tests""" 2 | from pylm_test import simple as m 3 | 4 | def test_simple(): 5 | """Tests simple function""" 6 | assert m.test() == 42 7 | -------------------------------------------------------------------------------- /run_tests.py: -------------------------------------------------------------------------------- 1 | """Helper script to run all unit tests""" 2 | import os 3 | import sys 4 | import json 5 | import argparse 6 | import subprocess as sp 7 | from colorama import Fore, Back, Style 8 | import pytest 9 | from functest.run_all import run_functests 10 | 11 | def add_bool_arg(parser, name, default): 12 | """Add boolean option with mutually exclusive group""" 13 | # cf. https://stackoverflow.com/a/31347222/3127098 14 | dest = name.replace('-','_') 15 | group = parser.add_mutually_exclusive_group(required=False) 16 | group.add_argument('--' + name, dest=dest, action='store_true') 17 | group.add_argument('--no-' + name, dest=dest, action='store_false') 18 | parser.set_defaults(**{dest:default}) 19 | 20 | if __name__ == '__main__': 21 | parser = argparse.ArgumentParser(description='Execute all unit tests') 22 | parser.add_argument('--lmenv', type=str, help='Path to .lmenv file') 23 | parser.add_argument('--output-dir', nargs='?', type=str, default='executed_functest', help='Output directory of executed notebooks') 24 | add_bool_arg(parser, 'cpp-unit', True) 25 | add_bool_arg(parser, 'python-unit', True) 26 | add_bool_arg(parser, 'functest', False) 27 | args = parser.parse_args() 28 | 29 | # Read .lmeenv file 30 | with open(args.lmenv) as f: 31 | config = json.load(f) 32 | 33 | # Set LD_LIBRARY_PATH for Linux environment 34 | if sys.platform == 'linux': 35 | env = os.environ.copy() 36 | env['LD_LIBRARY_PATH'] = config['bin_path'] 37 | 38 | # Execute C++ tests 39 | if args.cpp_unit: 40 | print(Fore.GREEN + "------------------------" + Style.RESET_ALL) 41 | print(Fore.GREEN + "Executing C++ unit tests" + Style.RESET_ALL) 42 | print(Fore.GREEN + "------------------------" + Style.RESET_ALL, flush=True) 43 | command = [os.path.join(config['bin_path'], 'lm_test')] 44 | if sys.platform == 'linux': 45 | sp.check_call(command, env=env) 46 | else: 47 | sp.check_call(command) 48 | 49 | # Execute python tests 50 | if args.python_unit: 51 | print(Fore.GREEN + "---------------------------" + Style.RESET_ALL) 52 | print(Fore.GREEN + "Executing Python unit tests" + Style.RESET_ALL) 53 | print(Fore.GREEN + "---------------------------" + Style.RESET_ALL, flush=True) 54 | base_path = os.path.dirname(os.path.realpath(__file__)) 55 | pytest.main([ 56 | os.path.join(base_path, 'pytest'), 57 | '--lmenv', args.lmenv 58 | ]) 59 | 60 | # Execute functional tests 61 | if args.functest: 62 | print(Fore.GREEN + "--------------------------" + Style.RESET_ALL) 63 | print(Fore.GREEN + "Executing functional tests" + Style.RESET_ALL) 64 | print(Fore.GREEN + "--------------------------" + Style.RESET_ALL, flush=True) 65 | run_functests(args.output_dir, args.lmenv) -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='lightmetrica-py', 5 | version='3.0.0', 6 | description='A research-oriented renderer', 7 | author='Hisanari Otsu', 8 | author_email='hi2p.perim@gmail.com', 9 | url='https://github.com/hi2p-perim/lightmetrica-v3', 10 | packages=find_packages(), 11 | include_package_data=True, 12 | license='MIT', 13 | classifiers=[ 14 | 'Development Status :: 3 - Alpha', 15 | 'Intended Audience :: Developers', 16 | 'Topic :: Software Development :: Libraries :: Python Modules', 17 | 'Topic :: Utilities', 18 | 'Programming Language :: C++', 19 | 'Programming Language :: Python :: 3.7', 20 | 'License :: OSI Approved :: MIT License' 21 | ], 22 | keywords='Computer graphics, Renderer', 23 | long_description='Lightmetrica is a research-oriented renderer. The development of the framework is motivated by the goal to provide a practical environment for rendering research and development, where the researchers and developers need to tackle various challenging requirements through the development process.', 24 | ) -------------------------------------------------------------------------------- /src/ext/README.md: -------------------------------------------------------------------------------- 1 | In-source external dependencies 2 | ==================== 3 | 4 | - https://github.com/agauniyal/rang -------------------------------------------------------------------------------- /src/material/material_diffuse.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 13 | 14 | /* 15 | \rst 16 | .. function:: material::diffuse 17 | 18 | Lambertian diffuse model. 19 | 20 | :param str mapKd: Diffuse reflectance as texture specified by 21 | asset name or locator. 22 | :param color Kd: Diffuse reflectance as color value. 23 | If both ``mapKd`` and ``Kd`` are specified, 24 | ``mapKd`` has priority. Default: ``[1,1,1]``. 25 | 26 | This component implements Lambertian diffuse BRDF defined as 27 | 28 | .. math:: f_r(\omega_i, \omega_o) = \frac{\rho}{\pi}, 29 | 30 | where :math:`\rho` is diffuse reflectance. 31 | \endrst 32 | */ 33 | class Material_Diffuse final : public Material { 34 | private: 35 | Vec3 Kd_; 36 | Texture* mapKd_ = nullptr; 37 | 38 | public: 39 | LM_SERIALIZE_IMPL(ar) { 40 | ar(Kd_, mapKd_); 41 | } 42 | 43 | virtual Component* underlying(const std::string& name) const override { 44 | if (name == "mapKd") { 45 | return mapKd_; 46 | } 47 | return nullptr; 48 | } 49 | 50 | virtual void foreach_underlying(const ComponentVisitor& visit) override { 51 | comp::visit(visit, mapKd_); 52 | } 53 | 54 | public: 55 | virtual void construct(const Json& prop) override { 56 | mapKd_ = json::comp_ref_or_nullptr(prop, "mapKd"); 57 | Kd_ = json::value(prop, "Kd", Vec3(1_f)); 58 | } 59 | 60 | virtual ComponentSample sample_component(const ComponentSampleU&, const PointGeometry&, Vec3) const override { 61 | return { 0, 1_f }; 62 | } 63 | 64 | virtual Float pdf_component(int, const PointGeometry&, Vec3) const override { 65 | return 1_f; 66 | } 67 | 68 | virtual std::optional sample_direction(const DirectionSampleU& us, const PointGeometry& geom, Vec3 wi, int, TransDir) const override { 69 | const auto[n, u, v] = geom.orthonormal_basis_twosided(wi); 70 | const auto Kd = mapKd_ ? mapKd_->eval(geom.t) : Kd_; 71 | const auto d = math::sample_cosine_weighted(us.ud); 72 | return DirectionSample{ 73 | u*d.x + v * d.y + n * d.z, 74 | Kd 75 | }; 76 | } 77 | 78 | virtual Vec3 reflectance(const PointGeometry& geom) const override { 79 | return mapKd_ ? mapKd_->eval(geom.t) : Kd_; 80 | } 81 | 82 | virtual Float pdf_direction(const PointGeometry& geom, Vec3 wi, Vec3 wo, int, bool) const override { 83 | return geom.opposite(wi, wo) ? 0_f : 1_f / Pi; 84 | } 85 | 86 | virtual Vec3 eval(const PointGeometry& geom, Vec3 wi, Vec3 wo, int, TransDir, bool) const override { 87 | if (geom.opposite(wi, wo)) { 88 | return {}; 89 | } 90 | return (mapKd_ ? mapKd_->eval(geom.t) : Kd_) / Pi; 91 | } 92 | 93 | virtual bool is_specular_component(int) const override { 94 | return false; 95 | } 96 | }; 97 | 98 | LM_COMP_REG_IMPL(Material_Diffuse, "material::diffuse"); 99 | 100 | LM_NAMESPACE_END(LM_NAMESPACE) 101 | -------------------------------------------------------------------------------- /src/material/material_mask.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 12 | 13 | /* 14 | \rst 15 | .. function:: material::mask 16 | 17 | Pass-through material. 18 | 19 | This component implements a special material that only sample 20 | the outgoing ray into the same direction as the incoming ray. 21 | This material is used to implement texture-masked materials. 22 | BSDF reads 23 | 24 | .. math:: 25 | f_s(\omega_i, \omega_o) = \delta_\Omega(-\omega_i, \omega_o). 26 | \endrst 27 | */ 28 | class Material_Mask final : public Material { 29 | public: 30 | virtual ComponentSample sample_component(const ComponentSampleU&, const PointGeometry&, Vec3) const override { 31 | return { 0, 1_f }; 32 | } 33 | 34 | virtual Float pdf_component(int, const PointGeometry&, Vec3) const override { 35 | return 1_f; 36 | } 37 | 38 | virtual std::optional sample_direction(const DirectionSampleU&, const PointGeometry&, Vec3 wi, int, TransDir) const override { 39 | return DirectionSample{ 40 | -wi, 41 | Vec3(1_f) 42 | }; 43 | } 44 | 45 | virtual Float pdf_direction(const PointGeometry&, Vec3, Vec3, int, bool eval_delta) const override { 46 | return eval_delta ? 0_f : 1_f; 47 | } 48 | 49 | virtual Vec3 eval(const PointGeometry&, Vec3, Vec3, int, TransDir, bool eval_delta) const override { 50 | return eval_delta ? Vec3(0_f) : Vec3(1_f); 51 | } 52 | 53 | virtual Vec3 reflectance(const PointGeometry&) const override { 54 | return Vec3(0_f); 55 | } 56 | 57 | virtual bool is_specular_component(int) const override { 58 | return true; 59 | } 60 | }; 61 | 62 | LM_COMP_REG_IMPL(Material_Mask, "material::mask"); 63 | 64 | LM_NAMESPACE_END(LM_NAMESPACE) 65 | -------------------------------------------------------------------------------- /src/material/material_mirror.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 12 | 13 | /* 14 | \rst 15 | .. function:: material::mirror 16 | 17 | Ideal mirror reflection. 18 | 19 | This component implements ideal mirror reflection BRDF: 20 | 21 | .. math:: 22 | f_r(\omega_i, \omega_o) = \delta_\Omega(\omega_{\mathrm{refl}}, \omega_o), 23 | 24 | where 25 | :math:`\omega_{\mathrm{refl}}\equiv2(\omega_i\cdot\mathbf{n})\mathbf{n} - \omega_i` 26 | is the reflected direction of :math:`\omega_i`, and 27 | :math:`\delta_\Omega` is the Dirac delta function w.r.t. solid angle measure: 28 | :math:`\int_\Omega \delta_\Omega(\omega', \omega) d\omega = \omega'`. 29 | \endrst 30 | */ 31 | class Material_Mirror final : public Material { 32 | public: 33 | virtual ComponentSample sample_component(const ComponentSampleU&, const PointGeometry&, Vec3) const override { 34 | return { 0, 1_f }; 35 | } 36 | 37 | virtual Float pdf_component(int, const PointGeometry&, Vec3) const override { 38 | return 1_f; 39 | } 40 | 41 | virtual std::optional sample_direction(const DirectionSampleU&, const PointGeometry& geom, Vec3 wi, int, TransDir) const override { 42 | return DirectionSample{ 43 | math::reflection(wi, geom.n), 44 | Vec3(1_f) 45 | }; 46 | } 47 | 48 | virtual Float pdf_direction(const PointGeometry&, Vec3, Vec3, int, bool eval_delta) const override { 49 | return eval_delta ? 0_f : 1_f; 50 | } 51 | 52 | virtual Vec3 eval(const PointGeometry&, Vec3, Vec3, int, TransDir, bool eval_delta) const override { 53 | return eval_delta ? Vec3(0_f) : Vec3(1_f); 54 | } 55 | 56 | virtual Vec3 reflectance(const PointGeometry&) const override { 57 | return Vec3(0_f); 58 | } 59 | 60 | virtual bool is_specular_component(int) const override { 61 | return true; 62 | } 63 | }; 64 | 65 | LM_COMP_REG_IMPL(Material_Mirror, "material::mirror"); 66 | 67 | LM_NAMESPACE_END(LM_NAMESPACE) 68 | -------------------------------------------------------------------------------- /src/material/material_proxy.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 11 | 12 | /* 13 | \rst 14 | .. function:: material::proxy 15 | 16 | Proxy material. 17 | 18 | :param str ref: Asset name or locator of the referencing material. 19 | 20 | This component gives proxy interface to the other predefined material. 21 | This component is useful when we want to reuse predefined material 22 | but we also need to create a new instance. 23 | \endrst 24 | */ 25 | class Material_Proxy final : public Material { 26 | private: 27 | Material* ref_; 28 | 29 | public: 30 | LM_SERIALIZE_IMPL(ar) { 31 | ar(ref_); 32 | } 33 | 34 | virtual void foreach_underlying(const ComponentVisitor& visit) override { 35 | comp::visit(visit, ref_); 36 | } 37 | 38 | public: 39 | virtual void construct(const Json& prop) override { 40 | ref_ = json::comp_ref(prop, "ref"); 41 | } 42 | 43 | virtual ComponentSample sample_component(const ComponentSampleU& u, const PointGeometry& geom, Vec3 wi) const override { 44 | return ref_->sample_component(u, geom, wi); 45 | } 46 | 47 | virtual Float pdf_component(int comp, const PointGeometry& geom, Vec3 wi) const override { 48 | return ref_->pdf_component(comp, geom, wi); 49 | } 50 | 51 | virtual std::optional sample_direction(const DirectionSampleU& u, const PointGeometry& geom, Vec3 wi, int comp, TransDir trans_dir) const override { 52 | return ref_->sample_direction(u, geom, wi, comp, trans_dir); 53 | } 54 | 55 | Float pdf_direction(const PointGeometry& geom, Vec3 wi, Vec3 wo, int comp, bool eval_delta) const override { 56 | return ref_->pdf_direction(geom, wi, wo, comp, eval_delta); 57 | } 58 | 59 | virtual Vec3 eval(const PointGeometry& geom, Vec3 wi, Vec3 wo, int comp, TransDir trans_dir, bool eval_delta) const override { 60 | return ref_->eval(geom, wi, wo, comp, trans_dir, eval_delta); 61 | } 62 | 63 | virtual Vec3 reflectance(const PointGeometry& geom) const override { 64 | return ref_->reflectance(geom); 65 | } 66 | 67 | virtual bool is_specular_component(int comp) const override { 68 | return ref_->is_specular_component(comp); 69 | } 70 | }; 71 | 72 | LM_COMP_REG_IMPL(Material_Proxy, "material::proxy"); 73 | 74 | LM_NAMESPACE_END(LM_NAMESPACE) 75 | -------------------------------------------------------------------------------- /src/medium/medium_homogeneous.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 12 | 13 | /* 14 | \rst 15 | .. function:: medium::homogeneous 16 | 17 | Homogeneous medium. 18 | 19 | :param float density: Density of the medium. 20 | :param color albedo: Albedo of the medium. 21 | :param str phase: Locator to ``phase`` asset. 22 | :param vec3 bound_min: Minimum bound of the volume. 23 | :param vec3 bound_max: Maximum bound of the volume. 24 | \endrst 25 | */ 26 | class Medium_Homogeneous final : public Medium { 27 | private: 28 | Float density_; // Density of volume := extinction coefficient \mu_t 29 | Vec3 albedo_; // Albedo of volume := \mu_s / \mu_t 30 | Vec3 muA_; // Absorption coefficient. 31 | Vec3 muS_; // Scattering coefficient. 32 | const Phase* phase_; // Underlying phase function. 33 | Bound bound_; 34 | 35 | public: 36 | LM_SERIALIZE_IMPL(ar) { 37 | ar(density_, muA_, muS_, phase_, bound_); 38 | } 39 | 40 | public: 41 | virtual void construct(const Json& prop) override { 42 | density_ = json::value(prop, "density"); 43 | albedo_ = json::value(prop, "albedo"); 44 | muS_ = albedo_ * density_; 45 | muA_ = density_ - muS_; 46 | phase_ = json::comp_ref(prop, "phase"); 47 | bound_.min = json::value(prop, "bound_min", Vec3(-Inf)); 48 | bound_.max = json::value(prop, "bound_max", Vec3(Inf)); 49 | } 50 | 51 | /* 52 | Memo. 53 | - Transmittance T(t) = exp[ -\int_0^t \mu_t(x+s\omega) ds ] = exp[-\mu_t t]. 54 | - PDF p(t) = \mu_t exp[-\mu_t t]. 55 | - CDF F(t) = 1 - T(t). 56 | - Sampled t ~ p(t). t = F^-1(U) = -ln(1-U)/\mu_t. 57 | - Prob. of surface interaction P[t>s] = 1-F(s) = T(s). 58 | - Weight for medium interaction. \mu_s T(t)/p(t) = \mu_s/\mu_t 59 | - Weight for surface interaction. T(s)/P[t>s] = 1 60 | */ 61 | virtual std::optional sample_distance(Rng& rng, Ray ray, Float tmin, Float tmax) const override { 62 | // Compute overlapping range between volume and bound 63 | if (!bound_.isect_range(ray, tmin, tmax)) { 64 | // No intersection with volume, use surface interaction 65 | return {}; 66 | } 67 | 68 | // Sample a distance 69 | const auto t = -std::log(1_f-rng.u()) / density_; 70 | 71 | if (t < tmax - tmin) { 72 | // Medium interaction 73 | return DistanceSample{ 74 | ray.o + ray.d*(tmin+t), 75 | albedo_, // \mu_s / \mu_t 76 | true 77 | }; 78 | } 79 | else { 80 | // Surface interaction 81 | return DistanceSample{ 82 | ray.o + ray.d*tmax, 83 | Vec3(1_f), 84 | false 85 | }; 86 | } 87 | } 88 | 89 | virtual Vec3 eval_transmittance(Rng&, Ray ray, Float tmin, Float tmax) const override { 90 | // Compute overlapping range 91 | if (!bound_.isect_range(ray, tmin, tmax)) { 92 | // No intersection with the volume, no attenuation 93 | return Vec3(1_f); 94 | } 95 | return Vec3(std::exp(-density_ * (tmax - tmin))); 96 | } 97 | 98 | virtual bool is_emitter() const override { 99 | return false; 100 | } 101 | 102 | virtual const Phase* phase() const override { 103 | return phase_; 104 | } 105 | }; 106 | 107 | LM_COMP_REG_IMPL(Medium_Homogeneous, "medium::homogeneous"); 108 | 109 | LM_NAMESPACE_END(LM_NAMESPACE) 110 | -------------------------------------------------------------------------------- /src/mesh/mesh_raw.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 11 | 12 | struct MeshFaceIndex { 13 | int p = -1; // Index of position 14 | int t = -1; // Index of texture coordinates 15 | int n = -1; // Index of normal 16 | 17 | template 18 | void serialize(Archive& ar) { 19 | ar(p, t, n); 20 | } 21 | }; 22 | 23 | /* 24 | \rst 25 | .. function:: mesh::raw 26 | 27 | Mesh from raw data. 28 | 29 | :param list ps: Vertex positions of the mesh. 30 | :param list ns: Vertex normals of the mesh. 31 | :param list ts: Texture coordinates for the vertices. 32 | :param dist fs: Index list. Indices for each vertex element are 33 | specified by ``p``, ``t``, and ``n`` respectively. 34 | 35 | \endrst 36 | */ 37 | class Mesh_Raw final : public Mesh { 38 | private: 39 | std::vector ps_; // Positions 40 | std::vector ns_; // Normals 41 | std::vector ts_; // Texture coordinates 42 | std::vector fs_; // Faces 43 | 44 | public: 45 | LM_SERIALIZE_IMPL(ar) { 46 | ar(ps_, ns_, ts_, fs_); 47 | } 48 | 49 | public: 50 | virtual void construct(const Json& prop) override { 51 | const auto& ps = prop["ps"]; 52 | for (int i = 0; i < ps.size(); i+=3) { 53 | ps_.push_back(Vec3(ps[i],ps[i+1],ps[i+2])); 54 | } 55 | const auto& ns = prop["ns"]; 56 | for (int i = 0; i < ns.size(); i+=3) { 57 | ns_.push_back(Vec3(ns[i],ns[i+1],ns[i+2])); 58 | } 59 | const auto& ts = prop["ts"]; 60 | for (int i = 0; i < ts.size(); i+=2) { 61 | ts_.push_back(Vec2(ts[i],ts[i+1])); 62 | } 63 | const auto& fs = prop["fs"]; 64 | const int fs_size = int(fs["p"].size()); 65 | for (int i = 0; i < fs_size; i++) { 66 | fs_.push_back(MeshFaceIndex{ fs["p"][i],fs["t"][i],fs["n"][i] }); 67 | } 68 | } 69 | 70 | virtual void foreach_triangle(const ProcessTriangleFunc& process_triangle) const override { 71 | for (int fi = 0; fi < int(fs_.size())/3; fi++) { 72 | process_triangle(fi, triangle_at(fi)); 73 | } 74 | } 75 | 76 | virtual Tri triangle_at(int face) const override { 77 | const auto f1 = fs_[3*face]; 78 | const auto f2 = fs_[3*face+1]; 79 | const auto f3 = fs_[3*face+2]; 80 | return { 81 | { ps_[f1.p], ns_[f1.n], ts_[f1.t] }, 82 | { ps_[f2.p], ns_[f2.n], ts_[f2.t] }, 83 | { ps_[f3.p], ns_[f3.n], ts_[f3.t] } 84 | }; 85 | } 86 | 87 | virtual InterpolatedPoint surface_point(int face, Vec2 uv) const override { 88 | const auto i1 = fs_[3*face]; 89 | const auto i2 = fs_[3*face+1]; 90 | const auto i3 = fs_[3*face+2]; 91 | return { 92 | math::mix_barycentric(ps_[i1.p], ps_[i2.p], ps_[i3.p], uv), 93 | glm::normalize(math::mix_barycentric(ns_[i1.n], ns_[i2.n], ns_[i3.n], uv)), 94 | math::geometry_normal(ps_[i1.p], ps_[i2.p], ps_[i3.p]), 95 | math::mix_barycentric(ts_[i1.t], ts_[i2.t], ts_[i3.t], uv) 96 | }; 97 | } 98 | 99 | virtual int num_triangles() const override { 100 | return int(fs_.size()) / 3; 101 | } 102 | }; 103 | 104 | LM_COMP_REG_IMPL(Mesh_Raw, "mesh::raw"); 105 | 106 | LM_NAMESPACE_END(LM_NAMESPACE) 107 | -------------------------------------------------------------------------------- /src/mesh/mesh_wavefrontobj.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 12 | 13 | /*! 14 | \rst 15 | .. function:: mesh::wavefrontobj 16 | 17 | Mesh for Wavefront OBJ format. 18 | 19 | This asset considers the geometry contained in the waverfont OBJ as as a single mesh asset. 20 | If you want to use multiple meshes, please try ``model::wavefrontobj``. 21 | \endrst 22 | */ 23 | class Mesh_WavefrontObj final : public Mesh { 24 | private: 25 | objloader::OBJSurfaceGeometry geo_; 26 | objloader::OBJMeshFace fs_; 27 | 28 | public: 29 | LM_SERIALIZE_IMPL(ar) { 30 | ar(geo_, fs_); 31 | } 32 | 33 | public: 34 | virtual void construct(const Json& prop) override { 35 | using namespace objloader; 36 | const std::string path = json::value(prop, "path"); 37 | const bool result = objloader::load(path, geo_, 38 | [&](const OBJMeshFace& fs, const MTLMatParams&) -> bool { 39 | // Accumulate face index 40 | fs_.insert(fs_.end(), fs.begin(), fs.end()); 41 | return true; 42 | }, 43 | [&](const MTLMatParams&) -> bool { 44 | // Ignore materials 45 | return true; 46 | }); 47 | if (!result) { 48 | LM_THROW_EXCEPTION_DEFAULT(Error::IOError); 49 | } 50 | } 51 | 52 | virtual void foreach_triangle(const ProcessTriangleFunc& process_triangle) const override { 53 | for (int fi = 0; fi < int(fs_.size())/3; fi++) { 54 | process_triangle(fi, triangle_at(fi)); 55 | } 56 | } 57 | 58 | virtual Tri triangle_at(int face) const override { 59 | const auto f1 = fs_[3*face]; 60 | const auto f2 = fs_[3*face+1]; 61 | const auto f3 = fs_[3*face+2]; 62 | return { 63 | { geo_.ps[f1.p], f1.n<0 ? Vec3() : geo_.ns[f1.n], f1.t<0 ? Vec2() : geo_.ts[f1.t] }, 64 | { geo_.ps[f2.p], f2.n<0 ? Vec3() : geo_.ns[f2.n], f2.t<0 ? Vec2() : geo_.ts[f2.t] }, 65 | { geo_.ps[f3.p], f3.n<0 ? Vec3() : geo_.ns[f3.n], f3.t<0 ? Vec2() : geo_.ts[f3.t] } 66 | }; 67 | } 68 | 69 | virtual InterpolatedPoint surface_point(int face, Vec2 uv) const override { 70 | const auto i1 = fs_[3*face]; 71 | const auto i2 = fs_[3*face+1]; 72 | const auto i3 = fs_[3*face+2]; 73 | const auto p1 = geo_.ps[i1.p]; 74 | const auto p2 = geo_.ps[i2.p]; 75 | const auto p3 = geo_.ps[i3.p]; 76 | return { 77 | // Position 78 | math::mix_barycentric(p1, p2, p3, uv), 79 | // Normal. Use geometry normal if the attribute is missing. 80 | i1.n < 0 ? math::geometry_normal(p1, p2, p3) 81 | : glm::normalize(math::mix_barycentric( 82 | geo_.ns[i1.n], geo_.ns[i2.n], geo_.ns[i3.n], uv)), 83 | // Geometry normal 84 | math::geometry_normal(p1, p2, p3), 85 | // Texture coordinates 86 | i1.t < 0 ? Vec2(0) : math::mix_barycentric( 87 | geo_.ts[i1.t], geo_.ts[i2.t], geo_.ts[i3.t], uv) 88 | }; 89 | } 90 | 91 | virtual int num_triangles() const override { 92 | return int(fs_.size()) / 3; 93 | } 94 | }; 95 | 96 | LM_COMP_REG_IMPL(Mesh_WavefrontObj, "mesh::wavefrontobj"); 97 | 98 | LM_NAMESPACE_END(LM_NAMESPACE) 99 | -------------------------------------------------------------------------------- /src/objloader/objloader.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | LM_NAMESPACE_BEGIN(LM_NAMESPACE::objloader) 10 | 11 | using Instance = comp::detail::ContextInstance; 12 | 13 | LM_PUBLIC_API void init(const std::string& type, const Json& prop) { 14 | Instance::init("objloader::" + type, prop); 15 | } 16 | 17 | LM_PUBLIC_API void shutdown() { 18 | Instance::shutdown(); 19 | } 20 | 21 | LM_PUBLIC_API bool load( 22 | const std::string& path, 23 | OBJSurfaceGeometry& geo, 24 | const ProcessMeshFunc& process_mesh, 25 | const ProcessMaterialFunc& process_material) { 26 | return Instance::get().load(path, geo, process_mesh, process_material); 27 | } 28 | 29 | LM_NAMESPACE_END(LM_NAMESPACE::objloader) 30 | -------------------------------------------------------------------------------- /src/parallel/parallel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | LM_NAMESPACE_BEGIN(LM_NAMESPACE::parallel) 10 | 11 | using Instance = comp::detail::ContextInstance; 12 | 13 | LM_PUBLIC_API void init(const std::string& type, const Json& prop) { 14 | Instance::init("parallel::" + type, prop); 15 | } 16 | 17 | LM_PUBLIC_API void shutdown() { 18 | Instance::shutdown(); 19 | } 20 | 21 | LM_PUBLIC_API int num_threads() { 22 | return Instance::get().num_threads(); 23 | } 24 | 25 | LM_PUBLIC_API bool main_thread() { 26 | return Instance::get().main_thread(); 27 | } 28 | 29 | LM_PUBLIC_API void foreach(long long num_samples, const ParallelProcessFunc& process_func, const ProgressUpdateFunc& progress_func) { 30 | Instance::get().foreach(num_samples, process_func, progress_func); 31 | } 32 | 33 | LM_NAMESPACE_END(LM_NAMESPACE::parallel) 34 | -------------------------------------------------------------------------------- /src/phase/phase_hg.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 11 | 12 | /* 13 | \rst 14 | .. function:: phase::hg 15 | 16 | Henyey-Greenstein phase function. 17 | 18 | :param float g: Asymmetry parameter in [-1,1]. 19 | \endrst 20 | */ 21 | class Phase_HenyeyGreenstein final : public Phase { 22 | private: 23 | Float g_; // Asymmetry parameter in [-1,1] 24 | 25 | public: 26 | LM_SERIALIZE_IMPL(ar) { 27 | ar(g_); 28 | } 29 | 30 | virtual void construct(const Json& prop) override { 31 | g_ = json::value(prop, "g"); 32 | } 33 | 34 | public: 35 | virtual std::optional sample_direction(const DirectionSampleU& us, const PointGeometry&, Vec3 wi) const override { 36 | const auto cosT = [&]() -> Float { 37 | if (std::abs(g_) < Eps) { 38 | return 1_f - 2_f*us.ud[0]; 39 | } 40 | else { 41 | const auto sq = (1_f-g_*g_)/(1_f-g_+2*g_*us.ud[0]); 42 | return (1_f+g_*g_-sq*sq)/(2_f*g_); 43 | } 44 | }(); 45 | const auto sinT = math::safe_sqrt(1_f-cosT*cosT); 46 | const auto phi = 2_f * Pi * us.ud[1]; 47 | const auto sinP = std::sin(phi); 48 | const auto cosP = std::cos(phi); 49 | const auto local_wo = Vec3(sinT*cosP, sinT*sinP, cosT); 50 | const auto [u, v] = math::orthonormal_basis(-wi); 51 | const auto wo = Mat3(u, v, -wi) * local_wo; 52 | return DirectionSample{ wo, Vec3(1_f) }; 53 | } 54 | 55 | virtual Float pdf_direction(const PointGeometry&, Vec3 wi, Vec3 wo) const override { 56 | Float t = 1_f + g_*g_ + 2_f*g_*glm::dot(wi, wo); 57 | return (1_f-g_*g_) / (t*std::sqrt(t)) / (Pi*4_f); 58 | } 59 | 60 | virtual Vec3 eval(const PointGeometry& geom, Vec3 wi, Vec3 wo) const override { 61 | return Vec3(pdf_direction(geom, wi, wo)); 62 | } 63 | }; 64 | 65 | LM_COMP_REG_IMPL(Phase_HenyeyGreenstein, "phase::hg"); 66 | 67 | LM_NAMESPACE_END(LM_NAMESPACE) 68 | -------------------------------------------------------------------------------- /src/phase/phase_isotropic.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 12 | 13 | /* 14 | \rst 15 | .. function:: phase::isotropic 16 | 17 | Isotropic phase function. 18 | \endrst 19 | */ 20 | class Phase_Isotropic final : public Phase { 21 | public: 22 | virtual std::optional sample_direction(const DirectionSampleU& us, const PointGeometry& geom, Vec3) const override { 23 | LM_UNUSED(geom); 24 | assert(geom.degenerated); 25 | return DirectionSample{ 26 | math::sample_uniform_sphere(us.ud), 27 | Vec3(1_f) 28 | }; 29 | } 30 | 31 | virtual Float pdf_direction(const PointGeometry& geom, Vec3, Vec3) const override { 32 | LM_UNUSED(geom); 33 | assert(geom.degenerated); 34 | return math::pdf_uniform_sphere(); 35 | } 36 | 37 | virtual Vec3 eval(const PointGeometry&, Vec3, Vec3) const override { 38 | // Normalization constant = 1/(4*pi) 39 | return Vec3(math::pdf_uniform_sphere()); 40 | } 41 | }; 42 | 43 | LM_COMP_REG_IMPL(Phase_Isotropic, "phase::isotropic"); 44 | 45 | LM_NAMESPACE_END(LM_NAMESPACE) 46 | -------------------------------------------------------------------------------- /src/renderer/renderer_blank.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 13 | 14 | class Renderer_Blank final : public Renderer { 15 | private: 16 | Vec3 color_; 17 | Film* film_; 18 | 19 | public: 20 | LM_SERIALIZE_IMPL(ar) { 21 | ar(color_, film_); 22 | } 23 | 24 | virtual void foreach_underlying(const ComponentVisitor& visit) override { 25 | comp::visit(visit, film_); 26 | } 27 | 28 | public: 29 | virtual void construct(const Json& prop) override { 30 | color_ = json::value(prop, "color"); 31 | film_ = json::comp_ref(prop, "output"); 32 | } 33 | 34 | virtual Json render() const override { 35 | film_->clear(); 36 | const auto [w, h] = film_->size(); 37 | for (int y = 0; y < h; y++) { 38 | for (int x = 0; x < w; x++) { 39 | film_->set_pixel(x, y, color_); 40 | } 41 | } 42 | return {}; 43 | } 44 | }; 45 | 46 | LM_COMP_REG_IMPL(Renderer_Blank, "renderer::blank"); 47 | 48 | LM_NAMESPACE_END(LM_NAMESPACE) 49 | -------------------------------------------------------------------------------- /src/renderer/renderer_raycast.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 18 | 19 | class Renderer_Raycast final : public Renderer { 20 | private: 21 | Scene* scene_; 22 | Film* film_; 23 | Vec3 bg_color_; 24 | bool use_constant_color_; 25 | bool visualize_normal_; 26 | std::optional color_; 27 | Component::Ptr sched_; 28 | 29 | public: 30 | LM_SERIALIZE_IMPL(ar) { 31 | ar(scene_, film_, bg_color_, use_constant_color_, visualize_normal_, sched_); 32 | } 33 | 34 | virtual void foreach_underlying(const ComponentVisitor& visit) override { 35 | comp::visit(visit, scene_); 36 | comp::visit(visit, film_); 37 | comp::visit(visit, sched_); 38 | } 39 | 40 | virtual Component* underlying(const std::string& name) const override { 41 | if (name == "scheduler") { 42 | return sched_.get(); 43 | } 44 | return nullptr; 45 | } 46 | 47 | public: 48 | virtual void construct(const Json& prop) override { 49 | scene_ = json::comp_ref(prop, "scene"); 50 | bg_color_ = json::value(prop, "bg_color", Vec3(0_f)); 51 | use_constant_color_ = json::value(prop, "use_constant_color", false); 52 | visualize_normal_ = json::value(prop, "visualize_normal", false); 53 | color_ = json::value_or_none(prop, "color"); 54 | film_ = json::comp_ref(prop, "output"); 55 | sched_ = comp::create( 56 | "scheduler::spp::sample", make_loc("scheduler"), { 57 | {"spp", 1}, 58 | {"output", prop["output"]} 59 | }); 60 | } 61 | 62 | virtual Json render() const override { 63 | scene_->require_primitive(); 64 | scene_->require_accel(); 65 | scene_->require_camera(); 66 | 67 | film_->clear(); 68 | const auto size = film_->size(); 69 | timer::ScopedTimer st; 70 | sched_->run([&](long long index, long long, int) { 71 | const int x = int(index % size.w); 72 | const int y = int(index / size.w); 73 | const auto ray = path::primary_ray(scene_, {(x+.5_f)/size.w, (y+.5_f)/size.h}); 74 | const auto sp = scene_->intersect(ray); 75 | if (!sp) { 76 | film_->set_pixel(x, y, bg_color_); 77 | return; 78 | } 79 | if (visualize_normal_) { 80 | film_->set_pixel(x, y, glm::abs(sp->geom.n)); 81 | } 82 | else { 83 | if (sp->geom.infinite) { 84 | // Hit against environment light 85 | const auto& primitive = scene_->node_at(sp->primitive).primitive; 86 | const auto Le = primitive.light->eval(sp->geom, sp->geom.wo, false); 87 | film_->set_pixel(x, y, Le); 88 | } 89 | else { 90 | const auto R = color_ ? *color_ : path::reflectance(scene_ , *sp); 91 | auto C = R ? *R : Vec3(); 92 | if (!use_constant_color_) { 93 | C *= .2_f + .8_f*glm::abs(glm::dot(sp->geom.n, -ray.d)); 94 | } 95 | film_->set_pixel(x, y, C); 96 | } 97 | } 98 | }); 99 | 100 | return { {"elapsed", st.now()} }; 101 | } 102 | }; 103 | 104 | LM_COMP_REG_IMPL(Renderer_Raycast, "renderer::raycast"); 105 | 106 | LM_NAMESPACE_END(LM_NAMESPACE) 107 | -------------------------------------------------------------------------------- /src/serial.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include -------------------------------------------------------------------------------- /src/texture/texture_bitmap.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #pragma warning(push) 10 | #pragma warning(disable:4244) // possible loss of data 11 | #define STB_IMAGE_IMPLEMENTATION 12 | #include 13 | #pragma warning(pop) 14 | 15 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 16 | 17 | std::string sanitize_directory_separator(std::string p) { 18 | std::replace(p.begin(), p.end(), '\\', '/'); 19 | return p; 20 | } 21 | 22 | /* 23 | \rst 24 | .. function:: texture::bitmap 25 | 26 | Bitmap texture. 27 | 28 | :param str path: Path to texture. 29 | :param bool flip: Flip loaded texture if true. 30 | \endrst 31 | */ 32 | class Texture_Bitmap final : public Texture { 33 | private: 34 | int w_; // Width of the image 35 | int h_; // Height of the image 36 | int c_; // Number of components 37 | std::vector data_; 38 | 39 | public: 40 | LM_SERIALIZE_IMPL(ar) { 41 | ar(w_, h_, c_, data_); 42 | } 43 | 44 | public: 45 | virtual TextureSize size() const override { 46 | return { w_, h_ }; 47 | } 48 | 49 | virtual void construct(const Json& prop) override { 50 | // Image path 51 | const std::string path = sanitize_directory_separator(json::value(prop, "path")); 52 | LM_INFO("Loading texture [path='{}']", fs::path(path).filename().string()); 53 | 54 | // Load as HDR image 55 | // LDR image is internally converted to HDR 56 | const bool flip = json::value(prop, "flip", true); 57 | stbi_set_flip_vertically_on_load(flip); 58 | float* data = stbi_loadf(path.c_str(), &w_, &h_, &c_, 0); 59 | if (data == nullptr) { 60 | LM_ERROR("Failed to load image: {} [path='{}']", stbi_failure_reason(), path); 61 | LM_THROW_EXCEPTION_DEFAULT(Error::IOError); 62 | } 63 | // Allocate and copy the data 64 | data_.assign(data, data + (w_*h_*c_)); 65 | stbi_image_free(data); 66 | } 67 | 68 | virtual Vec3 eval(Vec2 t) const override { 69 | const auto u = t.x - floor(t.x); 70 | const auto v = t.y - floor(t.y); 71 | const int x = std::clamp(int(u * w_), 0, w_ - 1); 72 | const int y = std::clamp(int(v * h_), 0, h_ - 1); 73 | const int i = w_ * y + x; 74 | return Vec3(data_[c_*i], data_[c_*i+1], data_[c_*i+2]); 75 | } 76 | 77 | virtual Vec3 eval_by_pixel_coords(int x, int y) const override { 78 | const int i = w_*y + x; 79 | return Vec3(data_[c_*i], data_[c_*i+1], data_[c_*i+2]); 80 | } 81 | 82 | virtual Float eval_alpha(Vec2 t) const override { 83 | const auto u = t.x - floor(t.x); 84 | const auto v = t.y - floor(t.y); 85 | const int x = std::clamp(int(u * w_), 0, w_ - 1); 86 | const int y = std::clamp(int(v * h_), 0, h_ - 1); 87 | const int i = w_ * y + x; 88 | return data_[c_*i+3]; 89 | } 90 | 91 | virtual bool has_alpha() const override { 92 | return c_ == 4; 93 | } 94 | 95 | virtual TextureBuffer buffer() override { 96 | return { w_, h_, c_, data_.data() }; 97 | } 98 | }; 99 | 100 | LM_COMP_REG_IMPL(Texture_Bitmap, "texture::bitmap"); 101 | 102 | LM_NAMESPACE_END(LM_NAMESPACE) 103 | -------------------------------------------------------------------------------- /src/texture/texture_constant.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 11 | 12 | /* 13 | \rst 14 | .. function:: texture::constant 15 | 16 | Texture with constant color. 17 | 18 | :param color color: Color. 19 | :param float alpha: Alpha value. 20 | \endrst 21 | */ 22 | class Texture_Constant final : public Texture { 23 | private: 24 | Vec3 color_; 25 | std::optional alpha_; 26 | 27 | public: 28 | LM_SERIALIZE_IMPL(ar) { 29 | ar(color_); 30 | } 31 | 32 | public: 33 | virtual void construct(const Json& prop) override { 34 | color_ = json::value(prop, "color"); 35 | alpha_ = json::value_or_none(prop, "alpha"); 36 | } 37 | 38 | virtual TextureSize size() const override { 39 | return { 1,1 }; 40 | } 41 | 42 | virtual Vec3 eval(Vec2) const override { 43 | return color_; 44 | } 45 | 46 | virtual Vec3 eval_by_pixel_coords(int, int) const override { 47 | return color_; 48 | } 49 | 50 | virtual bool has_alpha() const override { 51 | return (bool)(alpha_); 52 | } 53 | 54 | virtual Float eval_alpha(Vec2) const override { 55 | return *alpha_; 56 | } 57 | 58 | }; 59 | 60 | LM_COMP_REG_IMPL(Texture_Constant, "texture::constant"); 61 | 62 | LM_NAMESPACE_END(LM_NAMESPACE) 63 | -------------------------------------------------------------------------------- /src/version.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | LM_NAMESPACE_BEGIN(LM_NAMESPACE::version) 11 | 12 | LM_PUBLIC_API int major_version() { 13 | return LM_VERSION_MAJOR; 14 | } 15 | 16 | LM_PUBLIC_API int minor_version() { 17 | return LM_VERSION_MINOR; 18 | } 19 | 20 | LM_PUBLIC_API int patch_version() { 21 | return LM_VERSION_PATCH; 22 | } 23 | 24 | LM_PUBLIC_API std::string revision() { 25 | return LM_VERSION_REVISION; 26 | } 27 | 28 | LM_PUBLIC_API std::string build_timestamp() { 29 | return LM_BUILD_TIMESTAMP; 30 | } 31 | 32 | LM_PUBLIC_API std::string platform() { 33 | #if LM_PLATFORM_WINDOWS 34 | return "Windows"; 35 | #elif LM_PLATFORM_LINUX 36 | return "Linux"; 37 | #elif LM_PLATFORM_APPLE 38 | return "Apple"; 39 | #endif 40 | } 41 | 42 | LM_PUBLIC_API std::string architecture() { 43 | #if LM_ARCH_X64 44 | return "x64"; 45 | #elif LM_ARCH_X86 46 | return "x86"; 47 | #endif 48 | } 49 | 50 | LM_PUBLIC_API std::string formatted() { 51 | return fmt::format("{}.{}.{} (rev. {})", 52 | major_version(), minor_version(), patch_version(), revision()); 53 | } 54 | 55 | LM_NAMESPACE_END(LM_NAMESPACE::version) 56 | -------------------------------------------------------------------------------- /src/versiondef.h.in: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #define LM_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ 9 | #define LM_VERSION_MINOR @PROJECT_VERSION_MINOR@ 10 | #define LM_VERSION_PATCH @PROJECT_VERSION_PATCH@ 11 | #define LM_VERSION_REVISION "@LM_VERSION_REVISION@" 12 | #define LM_BUILD_TIMESTAMP "@LM_BUILD_TIMESTAMP@" 13 | -------------------------------------------------------------------------------- /src/volume/volume_checker.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 11 | 12 | 13 | /* 14 | \rst 15 | .. function:: volume::checker 16 | 17 | Checker volume. 18 | 19 | :param float bound_min: Minimum bound of the volume. 20 | :param float bound_max: Maximum bound of the volume. 21 | \endrst 22 | */ 23 | class Volume_Checker : public Volume { 24 | private: 25 | Bound bound_; // Bound of volume 26 | 27 | public: 28 | LM_SERIALIZE_IMPL(ar) { 29 | ar(bound_); 30 | } 31 | 32 | public: 33 | virtual void construct(const Json& prop) override { 34 | bound_.min = json::value(prop, "bound_min"); 35 | bound_.max = json::value(prop, "bound_max"); 36 | } 37 | 38 | virtual Bound bound() const override { 39 | return bound_; 40 | } 41 | 42 | virtual bool has_scalar() const override { 43 | return true; 44 | } 45 | 46 | virtual Float max_scalar() const override { 47 | return 1_f; 48 | } 49 | 50 | virtual Float eval_scalar(Vec3 p) const override { 51 | const auto Delta = .2_f; 52 | const auto t = (p - bound_.min) / (bound_.max - bound_.min); 53 | const auto x = int(t.x / Delta); 54 | const auto y = int(t.y / Delta); 55 | //const auto z = int(t.z / Delta); 56 | return (x + y) % 2 == 0 ? 1_f : 0_f; 57 | } 58 | 59 | virtual bool has_color() const override { 60 | return false; 61 | } 62 | }; 63 | 64 | LM_COMP_REG_IMPL(Volume_Checker, "volume::checker"); 65 | 66 | LM_NAMESPACE_END(LM_NAMESPACE) 67 | -------------------------------------------------------------------------------- /src/volume/volume_constant.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 11 | 12 | /* 13 | \rst 14 | .. function:: volume::constant 15 | 16 | Constant volume. 17 | 18 | :param color color: Stored color value. 19 | :param float scalar: Stored scalar value. 20 | :param float bound_min: Minimum bound of the volume. 21 | :param float bound_max: Maximum bound of the volume. 22 | \endrst 23 | */ 24 | class Volume_Constant : public Volume { 25 | private: 26 | Bound bound_; 27 | std::optional color_; 28 | std::optional scalar_; 29 | 30 | public: 31 | LM_SERIALIZE_IMPL(ar) { 32 | ar(bound_); 33 | } 34 | 35 | public: 36 | virtual void construct(const Json& prop) override { 37 | bound_.min = json::value(prop, "bound_min", Vec3(Inf)); 38 | bound_.max = json::value(prop, "bound_max", Vec3(-Inf)); 39 | color_ = json::value_or_none(prop, "color"); 40 | scalar_ = json::value_or_none(prop, "scalar"); 41 | if (!color_ && !scalar_) { 42 | LM_THROW_EXCEPTION(Error::InvalidArgument, 43 | "Either 'color' or 'scalar' property is necessary."); 44 | } 45 | } 46 | 47 | virtual Bound bound() const override { 48 | return bound_; 49 | } 50 | 51 | virtual bool has_scalar() const override { 52 | return scalar_.has_value(); 53 | } 54 | 55 | virtual Float max_scalar() const override { 56 | return *scalar_; 57 | } 58 | 59 | virtual Float eval_scalar(Vec3) const override { 60 | return *scalar_; 61 | } 62 | 63 | virtual bool has_color() const override { 64 | return color_.has_value(); 65 | } 66 | 67 | virtual Vec3 eval_color(Vec3) const override { 68 | return *color_; 69 | } 70 | }; 71 | 72 | LM_COMP_REG_IMPL(Volume_Constant, "volume::constant"); 73 | 74 | LM_NAMESPACE_END(LM_NAMESPACE) 75 | -------------------------------------------------------------------------------- /src/volume/volume_gaussian.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 11 | 12 | /* 13 | \rst 14 | .. function:: volume::gaussian 15 | 16 | Gaussian volume. 17 | 18 | :param color color: Stored color value. 19 | :param float scalar: Stored max scalar value. 20 | :param Vec3 pos: Center position of the volume 21 | :param Vec3 sigma: Gaussian parameter of standard deviation for x,y,z axis 22 | \endrst 23 | */ 24 | class Volume_Gaussian : public Volume{ 25 | private: 26 | Bound bound_; // Computed from 0.001% of the max of gaussian, then times two 27 | std::optional color_; 28 | std::optional scalar_; 29 | Vec3 pos_; 30 | Vec3 sigma_; 31 | 32 | public: 33 | LM_SERIALIZE_IMPL(ar){ 34 | ar(bound_, pos_, sigma_, scalar_, color_); 35 | } 36 | 37 | public: 38 | virtual void construct(const Json &prop) override{ 39 | color_ = json::value_or_none(prop, "color"); 40 | scalar_ = json::value_or_none(prop, "scalar"); 41 | pos_ = json::value(prop, "pos", Vec3(.0_f, .0_f, .0_f)); 42 | sigma_ = json::value(prop, "sigma", Vec3(1._f, 1._f, 1._f)); 43 | 44 | if (!color_ && !scalar_) 45 | LM_THROW_EXCEPTION(Error::InvalidArgument, 46 | "Either 'color' or 'scalar' property is necessary."); 47 | 48 | bound_ = computeBound(sigma_); 49 | } 50 | 51 | Bound computeBound(const Vec3 &sigma) const { 52 | auto compAC = [&](Float s) -> Float { 53 | //the first 2._f is empirical: when scalar is large, Eps is not enough 54 | return 2._f*sqrt(-2._f*s*std::log(Eps)); }; 55 | Float ac = std::max(std::max(compAC(sigma.x), compAC(sigma.y)), compAC(sigma.z)); 56 | Bound b; 57 | b.max = pos_ + ac; 58 | b.min = pos_ - ac; 59 | 60 | LM_DEBUG("min bound: {}, {}, {}", b.min.x, b.min.y, b.min.z); 61 | LM_DEBUG("max bound: {}, {}, {}", b.max.x, b.max.y, b.max.z); 62 | return b; 63 | } 64 | 65 | virtual Bound bound() const override{ 66 | return bound_; 67 | } 68 | 69 | virtual bool has_scalar() const override{ 70 | return scalar_.has_value(); 71 | } 72 | 73 | virtual Float max_scalar() const override{ 74 | return *scalar_; 75 | } 76 | 77 | // Compute 3D gaussian value 78 | virtual Float eval_scalar(Vec3 p) const override { 79 | return [&](const Vec3 p, const Float max_v, const Vec3 &s)->Float{ 80 | return max_v * exp(-0.5_f * ((p.x * p.x) / (s.x * s.x) + 81 | (p.y * p.y) / (s.y * s.y) + 82 | (p.z * p.z) / (s.z * s.z))); 83 | }(pos_-p, max_scalar(), sigma_); 84 | } 85 | 86 | virtual bool has_color() const override { 87 | return color_.has_value(); 88 | } 89 | 90 | virtual Vec3 eval_color(Vec3) const override { 91 | return *color_; 92 | } 93 | }; 94 | 95 | LM_COMP_REG_IMPL(Volume_Gaussian, "volume::gaussian"); 96 | 97 | LM_NAMESPACE_END(LM_NAMESPACE) 98 | -------------------------------------------------------------------------------- /src/volume/volume_sphere.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | LM_NAMESPACE_BEGIN(LM_NAMESPACE) 11 | /* 12 | \rst 13 | .. function:: volume::sphere 14 | 15 | Sphere volume. 16 | 17 | :param color color: Stored color value. 18 | :param float scalar: Stored max scalar value. 19 | :param Vec3 pos: Center position of the volume, defaults to [0,0,0] 20 | :param Float radius_: Sphere radius, defaults to 1 21 | \endrst 22 | */ 23 | class Volume_Sphere : public Volume { 24 | private: 25 | Bound bound_; // AABB of the sphere 26 | std::optional color_; 27 | std::optional scalar_; 28 | Vec3 pos_; 29 | Float radius_; 30 | 31 | public: 32 | LM_SERIALIZE_IMPL(ar) { 33 | ar(bound_, pos_, radius_, color_, scalar_); 34 | } 35 | 36 | public: 37 | virtual void construct(const Json &prop) override { 38 | color_ = json::value_or_none(prop, "color"); 39 | pos_ = json::value(prop, "pos", Vec3(.0_f, .0_f, .0_f)); 40 | scalar_ = json::value_or_none(prop, "scalar"); 41 | radius_ = json::value(prop, "radius", 1._f); 42 | if (!color_ && !scalar_){ 43 | LM_THROW_EXCEPTION(Error::InvalidArgument, 44 | "Either 'color' or 'scalar' property is necessary."); 45 | } 46 | computeBound(); 47 | } 48 | 49 | void computeBound() { 50 | // square root of 3 for box computation from sphere center 51 | // Eps ensures the sphere is entirely contained 52 | const Float sq3 = 1.73205080757_f; 53 | bound_.max = pos_ + sq3 * radius_ + Eps; 54 | bound_.min = pos_ - sq3 * radius_ - Eps; 55 | 56 | LM_DEBUG("min bound: {}, {}, {}", bound_.min.x, bound_.min.y, bound_.min.z); 57 | LM_DEBUG("max bound: {}, {}, {}", bound_.max.x, bound_.max.y, bound_.max.z); 58 | } 59 | 60 | virtual Bound bound() const override { 61 | return bound_; 62 | } 63 | 64 | virtual bool has_scalar() const override { 65 | 66 | return scalar_.has_value(); 67 | } 68 | 69 | virtual Float max_scalar() const override { 70 | 71 | return *scalar_; 72 | } 73 | 74 | bool inSphere(const Vec3 &p, const Float r) const { 75 | return (glm::length(p) < r) ? true : false; 76 | } 77 | 78 | virtual Float eval_scalar(Vec3 p) const override { 79 | return max_scalar() * inSphere(pos_ - p, radius_); 80 | } 81 | 82 | virtual bool has_color() const override { 83 | return color_.has_value(); 84 | } 85 | 86 | virtual Vec3 eval_color(Vec3) const override { 87 | return *color_; 88 | } 89 | }; 90 | 91 | LM_COMP_REG_IMPL(Volume_Sphere, "volume::sphere"); 92 | 93 | LM_NAMESPACE_END(LM_NAMESPACE) 94 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | # Distributed under MIT license. See LICENSE file for details. 4 | # 5 | 6 | include(LmAddPlugin) 7 | 8 | # Plugin for unit test 9 | lm_add_plugin( 10 | NAME lm_test_plugin 11 | INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" 12 | HEADERS 13 | "test_interface.h" 14 | SOURCES 15 | "test_interface_impl.cpp") 16 | 17 | # Unit test 18 | set(_PROJECT_NAME lm_test) 19 | set(_HEADER_FILES 20 | "test_common.h") 21 | set(_SOURCE_FILES 22 | "main.cpp" 23 | "test_common.cpp" 24 | "test_exception.cpp" 25 | "test_component.cpp" 26 | "test_assets.cpp" 27 | "test_json.cpp" 28 | "test_serial.cpp" 29 | "test_logger.cpp") 30 | set(_PCH_DIR "${PROJECT_SOURCE_DIR}/pch") 31 | set(_PCH_FILES 32 | "${_PCH_DIR}/pch.h" 33 | "${_PCH_DIR}/pch.cpp") 34 | add_executable(${_PROJECT_NAME} ${_HEADER_FILES} ${_SOURCE_FILES} ${_PCH_FILES}) 35 | if (MSVC) 36 | add_precompiled_header(${_PROJECT_NAME} "${_PCH_DIR}/pch.h" SOURCE_CXX "${_PCH_DIR}/pch.cpp") 37 | endif() 38 | target_link_libraries(${_PROJECT_NAME} 39 | PRIVATE liblm 40 | doctest::doctest 41 | lm_test_plugin::interface 42 | Threads::Threads) 43 | target_include_directories(${_PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_BINARY_DIR}" "${_PCH_DIR}") 44 | set_target_properties(${_PROJECT_NAME} PROPERTIES FOLDER "lm/test") 45 | set_target_properties(${_PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") 46 | source_group("Header Files" FILES ${_HEADER_FILES}) 47 | source_group("Source Files" FILES ${_SOURCE_FILES}) -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 8 | #include -------------------------------------------------------------------------------- /test/test_common.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include "test_common.h" 8 | #include 9 | #include 10 | 11 | LM_NAMESPACE_BEGIN(LM_TEST_NAMESPACE) 12 | 13 | std::string capture_stdout(const std::function& testFunc) { 14 | std::stringstream ss; 15 | auto* old = std::cout.rdbuf(ss.rdbuf()); 16 | testFunc(); 17 | const auto text = ss.str(); 18 | std::cout.rdbuf(old); 19 | return text; 20 | } 21 | 22 | LM_NAMESPACE_END(LM_TEST_NAMESPACE) 23 | -------------------------------------------------------------------------------- /test/test_common.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define LM_TEST_NAMESPACE lmtest 14 | 15 | LM_NAMESPACE_BEGIN(LM_TEST_NAMESPACE) 16 | 17 | // Captures outputs from std::cout 18 | std::string capture_stdout(const std::function& testFunc); 19 | 20 | LM_NAMESPACE_END(LM_TEST_NAMESPACE) 21 | -------------------------------------------------------------------------------- /test/test_interface.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | LM_NAMESPACE_BEGIN(lmtest) 14 | 15 | // ---------------------------------------------------------------------------- 16 | 17 | // Some component classes for tests 18 | // Register the classes in the same translation unit 19 | 20 | // _begin_snippet: A 21 | struct A : public lm::Component { 22 | virtual int f1() = 0; 23 | virtual int f2(int a, int b) = 0; 24 | }; 25 | 26 | struct A1 final : public A { 27 | virtual int f1() override { return 42; } 28 | virtual int f2(int a, int b) override { return a + b; } 29 | }; 30 | 31 | #ifdef LM_TEST_INTERFACE_REG_IMPL 32 | LM_COMP_REG_IMPL(A1, "test::comp::a1"); 33 | #endif 34 | // _end_snippet: A 35 | 36 | // ---------------------------------------------------------------------------- 37 | 38 | struct B : public A { 39 | virtual int f3() = 0; 40 | }; 41 | 42 | struct B1 final : public B { 43 | virtual int f1() override { return 42; } 44 | virtual int f2(int a, int b) override { return a + b; } 45 | virtual int f3() override { return 43; } 46 | }; 47 | 48 | #ifdef LM_TEST_INTERFACE_REG_IMPL 49 | LM_COMP_REG_IMPL(B1, "test::comp::b1"); 50 | #endif 51 | 52 | // ---------------------------------------------------------------------------- 53 | 54 | struct C : public lm::Component { 55 | C() { std::cout << "C"; } 56 | virtual ~C() { std::cout << "~C"; } 57 | }; 58 | 59 | struct C1 : public C { 60 | C1() { std::cout << "C1"; } 61 | virtual ~C1() { std::cout << "~C1"; } 62 | }; 63 | 64 | #ifdef LM_TEST_INTERFACE_REG_IMPL 65 | LM_COMP_REG_IMPL(C1, "test::comp::c1"); 66 | #endif 67 | 68 | // ---------------------------------------------------------------------------- 69 | 70 | struct D : public lm::Component { 71 | virtual int f() = 0; 72 | }; 73 | 74 | struct D1 final : public D { 75 | int v1; 76 | int v2; 77 | virtual void construct(const lm::Json& prop) override { 78 | v1 = lm::json::value(prop, "v1"); 79 | v2 = lm::json::value(prop, "v2"); 80 | } 81 | virtual int f() override { 82 | return v1 + v2; 83 | } 84 | }; 85 | 86 | #ifdef LM_TEST_INTERFACE_REG_IMPL 87 | LM_COMP_REG_IMPL(D1, "test::comp::d1"); 88 | #endif 89 | 90 | // ---------------------------------------------------------------------------- 91 | 92 | template 93 | struct G : public lm::Component { 94 | virtual T f() const = 0; 95 | }; 96 | 97 | template 98 | struct G1 final : public G { 99 | virtual T f() const override { 100 | if constexpr (std::is_same_v) { 101 | return 1; 102 | } 103 | if constexpr (std::is_same_v) { 104 | return 2; 105 | } 106 | } 107 | }; 108 | 109 | #ifdef LM_TEST_INTERFACE_REG_IMPL 110 | // We can register templated components with the same key 111 | LM_COMP_REG_IMPL(G1, "test::comp::g1"); 112 | LM_COMP_REG_IMPL(G1, "test::comp::g1"); 113 | #endif 114 | 115 | // ---------------------------------------------------------------------------- 116 | 117 | // Plugins implemented in a different shared library 118 | struct TestPlugin : public lm::Component { 119 | virtual int f() = 0; 120 | }; 121 | 122 | struct TestPluginWithCtorAndDtor : public lm::Component { 123 | TestPluginWithCtorAndDtor() { std::cout << "A"; } 124 | ~TestPluginWithCtorAndDtor() { std::cout << "~A"; } 125 | }; 126 | 127 | template 128 | struct TestPluginWithTemplate : public lm::Component { 129 | virtual T f() const = 0; 130 | }; 131 | 132 | // ---------------------------------------------------------------------------- 133 | 134 | LM_NAMESPACE_END(lmtest) 135 | -------------------------------------------------------------------------------- /test/test_interface_impl.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include "test_interface.h" 7 | 8 | LM_NAMESPACE_BEGIN(lmtest) 9 | 10 | // ------------------------------------------------------------------------------------------------ 11 | 12 | struct TestPlugin_ final : public TestPlugin { 13 | virtual int f() override { 14 | return 42; 15 | } 16 | }; 17 | 18 | LM_COMP_REG_IMPL(TestPlugin_, "testplugin::default"); 19 | 20 | // ------------------------------------------------------------------------------------------------ 21 | 22 | struct TestPlugin_WithConstruct : public TestPlugin { 23 | int v1; 24 | int v2; 25 | virtual void construct(const lm::Json& prop) override { 26 | v1 = lm::json::value(prop, "v1"); 27 | v2 = lm::json::value(prop, "v2"); 28 | } 29 | virtual int f() override { 30 | return v1 - v2; 31 | } 32 | }; 33 | 34 | LM_COMP_REG_IMPL(TestPlugin_WithConstruct, "testplugin::construct"); 35 | 36 | // ------------------------------------------------------------------------------------------------ 37 | 38 | struct TestPluginWithCtorAndDtor_ final : public TestPluginWithCtorAndDtor { 39 | TestPluginWithCtorAndDtor_() { std::cout << "B"; } 40 | ~TestPluginWithCtorAndDtor_() { std::cout << "~B"; } 41 | }; 42 | 43 | LM_COMP_REG_IMPL(TestPluginWithCtorAndDtor_, "testpluginxtor::default"); 44 | 45 | // ------------------------------------------------------------------------------------------------ 46 | 47 | template 48 | struct TestPluginWithTemplate_ final : public TestPluginWithTemplate { 49 | virtual T f() const override { 50 | if constexpr (std::is_same_v) { 51 | return 1; 52 | } 53 | if constexpr (std::is_same_v) { 54 | return 2; 55 | } 56 | } 57 | }; 58 | 59 | LM_COMP_REG_IMPL(TestPluginWithTemplate_, "testplugin::template"); 60 | LM_COMP_REG_IMPL(TestPluginWithTemplate_, "testplugin::template"); 61 | 62 | // ------------------------------------------------------------------------------------------------ 63 | 64 | LM_NAMESPACE_END(lmtest) 65 | -------------------------------------------------------------------------------- /test/test_json.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2019 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | #include "test_common.h" 8 | #include 9 | 10 | LM_NAMESPACE_BEGIN(LM_TEST_NAMESPACE) 11 | 12 | TEST_CASE("Json") { 13 | SUBCASE("Conversion of Vec types") { 14 | SUBCASE("Vec2") { 15 | SUBCASE("To") { 16 | lm::Json j = lm::Vec2(1, 2); 17 | CHECK(j == "[1,2]"_lmJson); 18 | } 19 | SUBCASE("From") { 20 | lm::Vec2 v = "[1,2]"_lmJson; 21 | CHECK(v == lm::Vec2(1, 2)); 22 | } 23 | } 24 | SUBCASE("Vec3") { 25 | SUBCASE("To") { 26 | lm::Json j = lm::Vec3(1, 2, 3); 27 | CHECK(j == "[1,2,3]"_lmJson); 28 | } 29 | SUBCASE("From") { 30 | lm::Vec3 v = "[1,2,3]"_lmJson; 31 | CHECK(v == lm::Vec3(1, 2, 3)); 32 | } 33 | } 34 | SUBCASE("Vec4") { 35 | SUBCASE("To") { 36 | lm::Json j = lm::Vec4(1, 2, 3, 4); 37 | CHECK(j == "[1,2,3,4]"_lmJson); 38 | } 39 | SUBCASE("From") { 40 | lm::Vec4 v = "[1,2,3,4]"_lmJson; 41 | CHECK(v == lm::Vec4(1, 2, 3, 4)); 42 | } 43 | } 44 | SUBCASE("Invalid type") { 45 | CHECK_THROWS([]{ lm::Vec3 v = "1"_lmJson; }()); 46 | CHECK_THROWS([]{ lm::Vec3 v = "{}"_lmJson; }()); 47 | CHECK_THROWS([]{ lm::Vec3 v = "[1,2]"_lmJson; }()); 48 | CHECK_THROWS([] { lm::Vec3 v = "[1,2,3,4]"_lmJson; }()); 49 | } 50 | } 51 | 52 | SUBCASE("Conversion of pointer types") { 53 | SUBCASE("Non const pointer") { 54 | int v = 42; 55 | int* vp = &v; 56 | lm::Json j = vp; 57 | // User-defined cast operator for pointer types doesn't match the definition. 58 | // We used get<>() as a workaround. 59 | auto vp2 = j.get(); 60 | CHECK(vp == vp2); 61 | } 62 | SUBCASE("const pointer") { 63 | int v = 42; 64 | const int* vp = &v; 65 | lm::Json j = vp; 66 | auto vp2 = j.get(); 67 | CHECK(vp == vp2); 68 | } 69 | } 70 | } 71 | 72 | LM_NAMESPACE_END(LM_TEST_NAMESPACE) 73 | -------------------------------------------------------------------------------- /test/test_python.h.in: -------------------------------------------------------------------------------- 1 | /* 2 | Lightmetrica - Copyright (c) 2018 Hisanari Otsu 3 | Distributed under MIT license. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #define LM_TEST_PYTHON_ROOT L"@_PYTHON_ROOT@" 9 | --------------------------------------------------------------------------------