├── .clang-format ├── .clang-tidy ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── BUILD.md ├── CMakeLists.txt ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── src ├── CMakeLists.txt ├── apps │ ├── CMakeLists.txt │ ├── cli.cpp │ └── export.cpp ├── base │ ├── CMakeLists.txt │ ├── camera.cpp │ ├── camera.h │ ├── environment.cpp │ ├── environment.h │ ├── film.cpp │ ├── film.h │ ├── filter.cpp │ ├── filter.h │ ├── geometry.cpp │ ├── geometry.h │ ├── integrator.cpp │ ├── integrator.h │ ├── interaction.cpp │ ├── interaction.h │ ├── light.cpp │ ├── light.h │ ├── light_sampler.cpp │ ├── light_sampler.h │ ├── medium.cpp │ ├── medium.h │ ├── phase_function.cpp │ ├── phase_function.h │ ├── pipeline.cpp │ ├── pipeline.h │ ├── sampler.cpp │ ├── sampler.h │ ├── scene.cpp │ ├── scene.h │ ├── scene_node.cpp │ ├── scene_node.h │ ├── shape.cpp │ ├── shape.h │ ├── spd.cpp │ ├── spd.h │ ├── spectrum.cpp │ ├── spectrum.h │ ├── surface.cpp │ ├── surface.h │ ├── texture.cpp │ ├── texture.h │ ├── texture_mapping.cpp │ ├── texture_mapping.h │ ├── transform.cpp │ └── transform.h ├── cameras │ ├── CMakeLists.txt │ ├── ortho.cpp │ ├── pinhole.cpp │ └── thin_lens.cpp ├── environments │ ├── CMakeLists.txt │ ├── combined.cpp │ ├── directional.cpp │ ├── grouped.cpp │ ├── null.cpp │ └── spherical.cpp ├── ext │ ├── CMakeLists.txt │ └── tinyexr.cpp ├── films │ ├── CMakeLists.txt │ ├── color.cpp │ └── display.cpp ├── filters │ ├── CMakeLists.txt │ ├── box.cpp │ ├── gaussian.cpp │ ├── lanczos_sinc.cpp │ ├── mitchell.cpp │ └── triangle.cpp ├── integrators │ ├── CMakeLists.txt │ ├── aov.cpp │ ├── direct.cpp │ ├── gpt.cpp │ ├── group.cpp │ ├── mega_path.cpp │ ├── mega_volume_path.cpp │ ├── mega_vpt.cpp │ ├── mega_vpt_naive.cpp │ ├── megapm.cpp │ ├── megawave.cpp │ ├── nfor.cpp │ ├── normal.cpp │ ├── pssmlt.cpp │ ├── wave_path.cpp │ ├── wave_path_readback.cpp │ └── wave_path_v2.cpp ├── lights │ ├── CMakeLists.txt │ ├── diffuse.cpp │ └── null.cpp ├── lightsamplers │ ├── CMakeLists.txt │ └── uniform.cpp ├── media │ ├── CMakeLists.txt │ ├── homogeneous.cpp │ ├── null.cpp │ └── vacuum.cpp ├── phasefunctions │ ├── CMakeLists.txt │ └── henyey_greenstein.cpp ├── samplers │ ├── CMakeLists.txt │ ├── independent.cpp │ ├── padded_sobol.cpp │ ├── pmj02bn.cpp │ ├── sobol.cpp │ ├── tile_shared.cpp │ └── zsobol.cpp ├── sdl │ ├── CMakeLists.txt │ ├── scene_desc.cpp │ ├── scene_desc.h │ ├── scene_node_desc.cpp │ ├── scene_node_desc.h │ ├── scene_node_tag.cpp │ ├── scene_node_tag.h │ ├── scene_parser.cpp │ ├── scene_parser.h │ ├── scene_parser_json.cpp │ └── scene_parser_json.h ├── shapes │ ├── CMakeLists.txt │ ├── group.cpp │ ├── inline_mesh.cpp │ ├── instance.cpp │ ├── loop_subdiv.cpp │ ├── mesh.cpp │ └── sphere.cpp ├── spectra │ ├── CMakeLists.txt │ ├── hero.cpp │ ├── srgb.cpp │ └── srgb2spec.cpp ├── surfaces │ ├── CMakeLists.txt │ ├── disney.cpp │ ├── glass.cpp │ ├── layered.cpp │ ├── matte.cpp │ ├── metal.cpp │ ├── metal_ior.inl.h │ ├── mirror.cpp │ ├── mix.cpp │ ├── null.cpp │ └── plastic.cpp ├── tests │ ├── CMakeLists.txt │ ├── test_alias_method.cpp │ ├── test_sky.cpp │ ├── test_sphere.cpp │ └── test_u64.cpp ├── texturemappings │ ├── CMakeLists.txt │ ├── spherical.cpp │ └── uv.cpp ├── textures │ ├── CMakeLists.txt │ ├── bump2normal.cpp │ ├── bump2normal.cpp.old │ ├── checkerboard.cpp │ ├── concat.cpp │ ├── constant.cpp │ ├── image.cpp │ ├── multiply.cpp │ ├── nishita_precompute.cpp │ ├── nishita_sky.cpp │ ├── scale.cpp │ ├── sky_precompute.h │ └── swizzle.cpp ├── transforms │ ├── CMakeLists.txt │ ├── identity.cpp │ ├── lerp.cpp │ ├── matrix.cpp │ ├── srt.cpp │ ├── stack.cpp │ └── view.cpp └── util │ ├── CMakeLists.txt │ ├── bluenoise.cpp │ ├── bluenoise.h │ ├── colorspace.h │ ├── command_buffer.cpp │ ├── command_buffer.h │ ├── complex.h │ ├── counter_buffer.cpp │ ├── counter_buffer.h │ ├── frame.cpp │ ├── frame.h │ ├── ies.cpp │ ├── ies.h │ ├── imageio.cpp │ ├── imageio.h │ ├── loop_subdiv.cpp │ ├── loop_subdiv.h │ ├── medium_tracker.cpp │ ├── medium_tracker.h │ ├── pmj02tables.cpp │ ├── pmj02tables.h │ ├── polymorphic_closure.h │ ├── progress_bar.cpp │ ├── progress_bar.h │ ├── rng.cpp │ ├── rng.h │ ├── sampling.cpp │ ├── sampling.h │ ├── scattering.cpp │ ├── scattering.h │ ├── sobolmatrices.cpp │ ├── sobolmatrices.h │ ├── spec.cpp │ ├── spec.h │ ├── thread_pool.cpp │ ├── thread_pool.h │ ├── vertex.h │ ├── xform.cpp │ └── xform.h └── tools ├── .gitignore ├── akr2obj.py ├── cie_illum_d6500_spectrum.py ├── cie_y_integral.py ├── diffuse_fresnel.py ├── disney2luisa.py ├── glass_ior.py ├── glslpt2luisa.py ├── hdr2srgb.py ├── lux2luisa.py ├── metal_ior.py ├── mitsuba2tungsten.py ├── obj-analyse.py ├── rgba2rgb.py ├── rgba2trans.py ├── seq2video.py ├── split_obj.py ├── tonemap.py └── tungsten2luisa.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/ext/cxxopts"] 2 | path = src/ext/cxxopts 3 | url = https://github.com/jarro2783/cxxopts.git 4 | [submodule "src/ext/assimp"] 5 | path = src/ext/assimp 6 | url = https://github.com/assimp/assimp.git 7 | [submodule "src/ext/fast_float"] 8 | path = src/ext/fast_float 9 | url = https://github.com/fastfloat/fast_float.git 10 | [submodule "src/ext/tinyexr"] 11 | path = src/ext/tinyexr 12 | url = https://github.com/syoyo/tinyexr.git 13 | [submodule "src/compute"] 14 | path = src/compute 15 | url = https://github.com/LuisaGroup/LuisaCompute.git 16 | branch = next 17 | [submodule "src/ext/json"] 18 | path = src/ext/json 19 | url = https://github.com/nlohmann/json.git 20 | -------------------------------------------------------------------------------- /BUILD.md: -------------------------------------------------------------------------------- 1 | # Build Instructions 2 | 3 | ## Requirements 4 | 5 | - CMake 3.20+ 6 | - C++ compiler which supports C++20 (e.g., clang-13, gcc-11, msvc-17) 7 | - MSVC and Clang (with GNU-style command-line options) are recommended and tested on Windows 8 | - On Linux, `uuid-dev` is required to build the core libraries and the following libraries are required for the GUI module: 9 | - libopencv-dev 10 | - libglfw3-dev 11 | - libxinerama-dev 12 | - libxcursor-dev 13 | - libxi-dev 14 | - On macOS with M1, you need to install `embree` since a pre-built binary is not provided by the official embree repo. We recommend using [Homebrew](https://brew.sh/) to install it: `brew install embree`. 15 | 16 | 17 | ### Backend Requirements 18 | 19 | - CUDA 20 | - CUDA 11.2 or higher 21 | - RTX-compatible graphics cards with appropriate drivers 22 | - OptiX 7.1 or higher 23 | - DirectX 24 | - DirectX 12 with ray tracing support 25 | - RTX-compatible graphics card with appropriate drivers 26 | - ISPC 27 | - x86-64 CPU with AVX256 or Apple M1 CPU with ARM Neon 28 | - (Optional) LLVM 12+ with the corresponding targets and features enabled (for JIT executing the IR emitted by ISPC) 29 | - Metal 30 | - macOS 12 or higher 31 | - Apple M1 chips are recommended (older GPUs are probably supported but not tested) 32 | - LLVM 33 | - x86-64 CPU with AVX256 or Apple M1 CPU with ARM Neon 34 | - LLVM 13+ with the corresponding targets and features enabled 35 | - CMake seems to have trouble with LLVM 15 on Ubuntu, so we recommend using LLVM 13/14; please install LLVM 14 via `wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && sudo ./llvm.sh 14` and use CMake flag `-D LLVM_ROOT=/usr/lib/llvm-14` to specify the LLVM installation directory if you already have LLVM 15 installed 36 | 37 | ## Build Commands 38 | 39 | ```bash 40 | cmake -S . -B build # optionally with CMake flags behind 41 | cmake --build build 42 | ``` 43 | 44 | ## CMake Flags 45 | 46 | The ISPC backend is disabled by default. Other backends will automatically be enabled if the corresponding APIs are detected. You can override the default settings by supplying CMake flags manually, in form of `-DFLAG=value` behind the first cmake command. 47 | 48 | In case you need to run the ISPC backend, download the [ISPC compiler executable](https://ispc.github.io/downloads.html) of your platform and copy it to `src/backends/ispc/ispc_support/` before compiling. 49 | 50 | - `CMAKE_BUILD_TYPE`: Set to Debug/Release to configure build type 51 | - `LUISA_COMPUTE_ENABLE_CUDA`: Enable CUDA backend 52 | - `LUISA_COMPUTE_ENABLE_DX`: Enable DirectX backend 53 | - `LUISA_COMPUTE_ENABLE_ISPC`: Enable ISPC backend 54 | - `LUISA_COMPUTE_ENABLE_METAL`: Enable Metal backend 55 | - `LUISA_COMPUTE_ENABLE_GUI`: Enable GUI display in C++ tests (enabled by default) 56 | 57 | Note: On Windows, please remember to replace the backslashes `\\` in the paths with `/` when passing arguments to CMake. 58 | 59 | ## Usage 60 | 61 | 1. The renderer's executable file is named `luisa-render-cli`, which locates in build directory 62 | 2. Usage can be accessed by running `luisa-render-cli --help` 63 | 3. The renderer can be simply run as 64 | ```bash 65 | luisa-render-cli -b 66 | ``` 67 | 4. Example scene files are under `data/scenes` 68 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.18...3.21) 2 | cmake_policy(VERSION 3.18) 3 | 4 | cmake_policy(SET CMP0069 NEW) 5 | cmake_policy(SET CMP0072 NEW) 6 | cmake_policy(SET CMP0091 NEW) 7 | 8 | set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) 9 | set(CMAKE_POLICY_DEFAULT_CMP0072 NEW) 10 | 11 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 12 | 13 | set(CMAKE_C_STANDARD 11) 14 | set(CMAKE_CXX_STANDARD 20) 15 | set(CMAKE_C_STANDARD_REQUIRED ON) 16 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 17 | set(CMAKE_C_EXTENSIONS OFF) 18 | set(CMAKE_CXX_EXTENSIONS OFF) 19 | set(BUILD_SHARED_LIBS ON) 20 | 21 | project(LuisaRender LANGUAGES C CXX VERSION 0.1) 22 | 23 | if (NOT CMAKE_SIZEOF_VOID_P EQUAL 8) 24 | message(FATAL_ERROR "LuisaRender only supports 64-bit platforms") 25 | endif () 26 | 27 | if (NOT DEFINED LUISA_RENDER_MASTER_PROJECT) 28 | if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) 29 | set(LUISA_RENDER_MASTER_PROJECT ON) 30 | else () 31 | set(LUISA_RENDER_MASTER_PROJECT OFF) 32 | endif () 33 | endif () 34 | 35 | option(LUISA_RENDER_BUILD_TESTS "Build tests for LuisaRender" ${LUISA_RENDER_MASTER_PROJECT}) 36 | option(LUISA_RENDER_ENABLE_UNITY_BUILD "Enable unity build to speed up compilation" OFF) 37 | 38 | include(src/compute/scripts/setup_output_dirs.cmake) 39 | 40 | # rpath 41 | set(CMAKE_MACOSX_RPATH ON) 42 | set(CMAKE_SKIP_BUILD_RPATH OFF) 43 | set(CMAKE_BUILD_RPATH_USE_ORIGIN ON) 44 | set(CMAKE_BUILD_WITH_INSTALL_RPATH ON) 45 | set(CMAKE_INSTALL_RPATH_USE_LINK_PATH ON) 46 | 47 | if (APPLE) 48 | set(CMAKE_INSTALL_RPATH "@loader_path;@loader_path/../bin;@loader_path/../lib") 49 | elseif (UNIX) 50 | set(CMAKE_INSTALL_RPATH "$ORIGIN;$ORIGIN/../bin;$ORIGIN/../lib") 51 | endif () 52 | 53 | if (NOT CMAKE_INSTALL_PREFIX) 54 | set(CMAKE_INSTALL_PREFIX "dist") 55 | endif () 56 | 57 | # set up install directories 58 | include(GNUInstallDirs) 59 | if (NOT WIN32) 60 | # DLLs are installed in the same directory as executables 61 | set(CMAKE_INSTALL_LIBDIR ${CMAKE_INSTALL_BINDIR}) 62 | endif () 63 | 64 | set(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL) 65 | set(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC) 66 | 67 | add_subdirectory(src) 68 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, LuisaGroup 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /src/apps/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | function(luisa_render_add_application name) 2 | cmake_parse_arguments(APP "" "" "SOURCES" ${ARGN}) 3 | add_executable(${name} ${APP_SOURCES}) 4 | target_link_libraries(${name} PRIVATE luisa::render) 5 | install(TARGETS ${name} 6 | LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR} 7 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) 8 | endfunction() 9 | 10 | luisa_render_add_application(luisa-render-cli SOURCES cli.cpp) 11 | luisa_render_add_application(luisa-render-export SOURCES export.cpp) 12 | -------------------------------------------------------------------------------- /src/base/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(LUISA_RENDER_BASE_SOURCES 2 | scene.cpp scene.h 3 | transform.cpp transform.h 4 | camera.cpp camera.h 5 | sampler.cpp sampler.h 6 | filter.cpp filter.h 7 | scene_node.cpp scene_node.h 8 | shape.cpp shape.h 9 | surface.cpp surface.h 10 | film.cpp film.h 11 | integrator.cpp integrator.h 12 | environment.cpp environment.h 13 | light.cpp light.h 14 | pipeline.cpp pipeline.h 15 | interaction.cpp interaction.h 16 | light_sampler.cpp light_sampler.h 17 | texture.cpp texture.h 18 | texture_mapping.cpp texture_mapping.h 19 | spectrum.cpp spectrum.h 20 | geometry.cpp geometry.h 21 | spd.cpp spd.h 22 | medium.cpp medium.h 23 | phase_function.cpp phase_function.h) 24 | 25 | add_library(luisa-render-base SHARED ${LUISA_RENDER_BASE_SOURCES}) 26 | target_link_libraries(luisa-render-base PUBLIC 27 | luisa::compute 28 | luisa-render-sdl 29 | luisa-render-util) 30 | set_target_properties(luisa-render-base PROPERTIES 31 | WINDOWS_EXPORT_ALL_SYMBOLS ON 32 | UNITY_BUILD ${LUISA_RENDER_ENABLE_UNITY_BUILD}) 33 | 34 | install(TARGETS luisa-render-base 35 | LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR} 36 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) 37 | -------------------------------------------------------------------------------- /src/base/environment.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/14. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace luisa::render { 10 | 11 | Environment::Environment(Scene *scene, const SceneNodeDesc *desc) noexcept 12 | : SceneNode{scene, desc, SceneNodeTag::ENVIRONMENT}, 13 | _transform{scene->load_transform(desc->property_node_or_default("transform"))} {} 14 | 15 | Environment::Instance::Instance(Pipeline &pipeline, const Environment *env) noexcept 16 | : _pipeline{pipeline}, _env{env} { pipeline.register_transform(env->transform()); } 17 | 18 | Float3x3 Environment::Instance::transform_to_world() const noexcept { 19 | return make_float3x3(pipeline().transform(node()->transform())); 20 | } 21 | 22 | }// namespace luisa::render 23 | -------------------------------------------------------------------------------- /src/base/environment.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/14. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | namespace luisa::render { 11 | 12 | class Transform; 13 | using compute::Float3x3; 14 | 15 | class Environment : public SceneNode { 16 | 17 | public: 18 | using Evaluation = Light::Evaluation; 19 | 20 | struct Sample { 21 | Evaluation eval; 22 | Float3 wi; 23 | [[nodiscard]] static auto zero(uint spec_dim) noexcept { 24 | return Sample{.eval = Evaluation::zero(spec_dim), .wi = make_float3()}; 25 | } 26 | }; 27 | 28 | public: 29 | class Instance { 30 | 31 | private: 32 | const Pipeline &_pipeline; 33 | const Environment *_env; 34 | 35 | public: 36 | Instance(Pipeline &pipeline, const Environment *env) noexcept; 37 | virtual ~Instance() noexcept = default; 38 | template 39 | requires std::is_base_of_v 40 | [[nodiscard]] auto node() const noexcept { return static_cast(_env); } 41 | [[nodiscard]] auto &pipeline() const noexcept { return _pipeline; } 42 | [[nodiscard]] virtual Evaluation evaluate(Expr wi, 43 | const SampledWavelengths &swl, 44 | Expr time) const noexcept = 0; 45 | [[nodiscard]] virtual Sample sample(const SampledWavelengths &swl, 46 | Expr time, 47 | Expr u) const noexcept = 0; 48 | [[nodiscard]] Float3x3 transform_to_world() const noexcept; 49 | }; 50 | 51 | private: 52 | const Transform *_transform; 53 | 54 | public: 55 | Environment(Scene *scene, const SceneNodeDesc *desc) noexcept; 56 | [[nodiscard]] auto transform() const noexcept { return _transform; } 57 | [[nodiscard]] virtual bool is_black() const noexcept = 0; 58 | [[nodiscard]] virtual luisa::unique_ptr build( 59 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept = 0; 60 | }; 61 | 62 | }// namespace luisa::render 63 | 64 | LUISA_DISABLE_DSL_ADDRESS_OF_OPERATOR(::luisa::render::Environment::Instance) 65 | LUISA_DISABLE_DSL_ADDRESS_OF_OPERATOR(::luisa::render::Environment::Sample) 66 | -------------------------------------------------------------------------------- /src/base/film.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike on 2021/12/14. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | namespace luisa::render { 9 | 10 | Film::Film(Scene *scene, const SceneNodeDesc *desc) noexcept 11 | : SceneNode{scene, desc, SceneNodeTag::FILM} {} 12 | 13 | void Film::Instance::accumulate(Expr pixel, Expr rgb, 14 | Expr effective_spp) const noexcept { 15 | $outline { 16 | #ifndef NDEBUG 17 | $if(all(pixel >= 0u && pixel < node()->resolution())) { 18 | #endif 19 | _accumulate(pixel, rgb, effective_spp); 20 | #ifndef NDEBUG 21 | }; 22 | #endif 23 | }; 24 | } 25 | 26 | }// namespace luisa::render 27 | -------------------------------------------------------------------------------- /src/base/film.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike on 2021/12/14. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | namespace luisa::render { 11 | 12 | class Film : public SceneNode { 13 | 14 | public: 15 | struct Accumulation { 16 | Float3 average; 17 | Float sample_count; 18 | }; 19 | 20 | class Instance { 21 | 22 | private: 23 | const Pipeline &_pipeline; 24 | const Film *_film; 25 | 26 | protected: 27 | virtual void _accumulate(Expr pixel, Expr rgb, 28 | Expr effective_spp) const noexcept = 0; 29 | 30 | public: 31 | explicit Instance(const Pipeline &pipeline, const Film *film) noexcept 32 | : _pipeline{pipeline}, _film{film} {} 33 | 34 | virtual ~Instance() noexcept = default; 35 | template 36 | requires std::is_base_of_v 37 | [[nodiscard]] auto node() const noexcept { return static_cast(_film); } 38 | [[nodiscard]] auto &pipeline() const noexcept { return _pipeline; } 39 | [[nodiscard]] virtual Accumulation read(Expr pixel) const noexcept = 0; 40 | void accumulate(Expr pixel, Expr rgb, Expr effective_spp = 1.f) const noexcept; 41 | virtual void prepare(CommandBuffer &command_buffer) noexcept = 0; 42 | virtual void clear(CommandBuffer &command_buffer) noexcept = 0; 43 | virtual void download(CommandBuffer &command_buffer, float4 *framebuffer) const noexcept = 0; 44 | virtual bool show(CommandBuffer &command_buffer) const noexcept { return false; } 45 | virtual void release() noexcept = 0; 46 | }; 47 | 48 | public: 49 | Film(Scene *scene, const SceneNodeDesc *desc) noexcept; 50 | [[nodiscard]] virtual uint2 resolution() const noexcept = 0; 51 | [[nodiscard]] virtual float3 exposure() const noexcept = 0; 52 | [[nodiscard]] virtual luisa::unique_ptr build( 53 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept = 0; 54 | [[nodiscard]] virtual float clamp() const noexcept { return 1024.0f; } 55 | [[nodiscard]] virtual bool is_display() const noexcept { return false; } 56 | }; 57 | 58 | }// namespace luisa::render 59 | 60 | LUISA_DISABLE_DSL_ADDRESS_OF_OPERATOR(luisa::render::Film::Instance) 61 | LUISA_DISABLE_DSL_ADDRESS_OF_OPERATOR(::luisa::render::Film::Accumulation) 62 | -------------------------------------------------------------------------------- /src/base/filter.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike on 2021/12/8. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace luisa::render { 10 | 11 | Filter::Filter(Scene *scene, const SceneNodeDesc *desc) noexcept 12 | : SceneNode{scene, desc, SceneNodeTag::FILTER}, 13 | _radius{std::max(desc->property_float_or_default("radius", 0.5f), 1e-3f)}, 14 | _shift{desc->property_float2_or_default( 15 | "shift", lazy_construct([desc] { 16 | return make_float2(desc->property_float_or_default("shift", 0.f)); 17 | }))} {} 18 | 19 | luisa::unique_ptr Filter::build( 20 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept { 21 | return luisa::make_unique(pipeline, this); 22 | } 23 | 24 | Filter::Instance::Instance(const Pipeline &pipeline, const Filter *filter) noexcept 25 | : _pipeline{pipeline}, _filter{filter} { 26 | static constexpr auto n = Filter::look_up_table_size - 1u; 27 | static constexpr auto inv_n = 1.0f / static_cast(n); 28 | std::array abs_f{}; 29 | _lut[0u] = filter->evaluate(-filter->radius()); 30 | auto integral = 0.0f; 31 | for (auto i = 0u; i < n; i++) { 32 | auto x = static_cast(i + 1u) * inv_n * 2.0f - 1.0f; 33 | _lut[i + 1u] = filter->evaluate(x * filter->radius()); 34 | auto f_mid = 0.5f * (_lut[i] + _lut[i + 1u]); 35 | integral += f_mid; 36 | abs_f[i] = std::abs(f_mid); 37 | } 38 | auto inv_integral = 1.0f / integral; 39 | for (auto &f : _lut) { f *= inv_integral; } 40 | auto [alias_table, pdf] = create_alias_table(abs_f); 41 | assert(alias_table.size() == n && pdf.size() == n); 42 | for (auto i = 0u; i < n; i++) { 43 | _pdf[i] = pdf[i]; 44 | _alias_probs[i] = alias_table[i].prob; 45 | _alias_indices[i] = alias_table[i].alias; 46 | } 47 | } 48 | 49 | Filter::Sample Filter::Instance::sample(Expr u) const noexcept { 50 | using namespace luisa::compute; 51 | Constant lut = look_up_table(); 52 | Constant pdfs = pdf_table(); 53 | Constant alias_indices = alias_table_indices(); 54 | Constant alias_probs = alias_table_probabilities(); 55 | auto n = look_up_table_size - 1u; 56 | auto [iy, uy] = sample_alias_table(alias_probs, alias_indices, n, u.x); 57 | auto [ix, ux] = sample_alias_table(alias_probs, alias_indices, n, u.y); 58 | auto pdf = pdfs[iy] * pdfs[ix]; 59 | auto f = lerp(lut[ix], lut[ix + 1u], ux) * lerp(lut[iy], lut[iy + 1u], uy); 60 | auto p = make_float2(make_uint2(ix, iy)) + make_float2(ux, uy); 61 | auto inv_size = 1.0f / static_cast(look_up_table_size); 62 | auto pixel = (p * inv_size * 2.0f - 1.0f) * _filter->radius(); 63 | return {pixel + node()->shift(), f / pdf}; 64 | } 65 | 66 | }// namespace luisa::render 67 | -------------------------------------------------------------------------------- /src/base/filter.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike on 2021/12/8. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace luisa::render { 13 | 14 | class Filter : public SceneNode { 15 | 16 | public: 17 | static constexpr auto look_up_table_size = 64u; 18 | 19 | public: 20 | struct Sample { 21 | Float2 offset; 22 | Float weight; 23 | }; 24 | 25 | class Instance { 26 | 27 | private: 28 | const Pipeline &_pipeline; 29 | const Filter *_filter; 30 | std::array _lut{}; 31 | std::array _pdf{}; 32 | std::array _alias_probs{}; 33 | std::array _alias_indices{}; 34 | 35 | public: 36 | Instance(const Pipeline &pipeline, const Filter *filter) noexcept; 37 | virtual ~Instance() noexcept = default; 38 | 39 | template 40 | requires std::is_base_of_v 41 | [[nodiscard]] auto node() const noexcept { return static_cast(_filter); } 42 | [[nodiscard]] auto &pipeline() const noexcept { return _pipeline; } 43 | [[nodiscard]] auto look_up_table() const noexcept { return luisa::span{_lut}; } 44 | [[nodiscard]] auto pdf_table() const noexcept { return luisa::span{_pdf}; } 45 | [[nodiscard]] auto alias_table_indices() const noexcept { return luisa::span{_alias_indices}; } 46 | [[nodiscard]] auto alias_table_probabilities() const noexcept { return luisa::span{_alias_probs}; } 47 | [[nodiscard]] virtual Sample sample(Expr u) const noexcept; 48 | }; 49 | 50 | private: 51 | float _radius; 52 | float2 _shift; 53 | 54 | public: 55 | Filter(Scene *scene, const SceneNodeDesc *desc) noexcept; 56 | [[nodiscard]] auto radius() const noexcept { return _radius; } 57 | [[nodiscard]] auto shift() const noexcept { return _shift; } 58 | [[nodiscard]] virtual float evaluate(float x) const noexcept = 0; 59 | [[nodiscard]] virtual luisa::unique_ptr build( 60 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept; 61 | }; 62 | 63 | }// namespace luisa::render 64 | 65 | LUISA_DISABLE_DSL_ADDRESS_OF_OPERATOR(luisa::render::Filter::Instance) 66 | LUISA_DISABLE_DSL_ADDRESS_OF_OPERATOR(::luisa::render::Filter::Sample) 67 | -------------------------------------------------------------------------------- /src/base/integrator.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike on 2021/12/14. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace luisa::render { 15 | 16 | class Pipeline; 17 | class Display; 18 | 19 | class Integrator : public SceneNode { 20 | 21 | public: 22 | class Instance { 23 | 24 | private: 25 | Pipeline &_pipeline; 26 | const Integrator *_integrator; 27 | luisa::unique_ptr _sampler; 28 | luisa::unique_ptr _light_sampler; 29 | 30 | public: 31 | explicit Instance(Pipeline &pipeline, CommandBuffer &command_buffer, const Integrator *integrator) noexcept; 32 | virtual ~Instance() noexcept = default; 33 | 34 | template 35 | requires std::is_base_of_v 36 | [[nodiscard]] auto 37 | node() const noexcept { return static_cast(_integrator); } 38 | [[nodiscard]] auto &pipeline() noexcept { return _pipeline; } 39 | [[nodiscard]] const auto &pipeline() const noexcept { return _pipeline; } 40 | [[nodiscard]] auto sampler() noexcept { return _sampler.get(); } 41 | [[nodiscard]] auto sampler() const noexcept { return _sampler.get(); } 42 | [[nodiscard]] auto light_sampler() noexcept { return _light_sampler.get(); } 43 | [[nodiscard]] auto light_sampler() const noexcept { return _light_sampler.get(); } 44 | virtual void render(Stream &stream) noexcept = 0; 45 | }; 46 | 47 | private: 48 | const Sampler *_sampler; 49 | const LightSampler *_light_sampler; 50 | 51 | public: 52 | Integrator(Scene *scene, const SceneNodeDesc *desc) noexcept; 53 | [[nodiscard]] auto sampler() const noexcept { return _sampler; } 54 | [[nodiscard]] auto light_sampler() const noexcept { return _light_sampler; } 55 | [[nodiscard]] virtual luisa::unique_ptr build( 56 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept = 0; 57 | }; 58 | 59 | class ProgressiveIntegrator : public Integrator { 60 | 61 | public: 62 | class Instance : public Integrator::Instance { 63 | 64 | protected: 65 | [[nodiscard]] virtual Float3 Li(const Camera::Instance *camera, Expr frame_index, 66 | Expr pixel_id, Expr time) const noexcept; 67 | virtual void _render_one_camera(CommandBuffer &command_buffer, Camera::Instance *camera) noexcept; 68 | 69 | public: 70 | Instance(Pipeline &pipeline, 71 | CommandBuffer &command_buffer, 72 | const ProgressiveIntegrator *node) noexcept; 73 | ~Instance() noexcept override; 74 | void render(Stream &stream) noexcept override; 75 | }; 76 | 77 | public: 78 | ProgressiveIntegrator(Scene *scene, const SceneNodeDesc *desc) noexcept; 79 | }; 80 | 81 | }// namespace luisa::render 82 | 83 | LUISA_DISABLE_DSL_ADDRESS_OF_OPERATOR(luisa::render::Integrator::Instance) 84 | -------------------------------------------------------------------------------- /src/base/interaction.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/12/3. 3 | // 4 | 5 | #include 6 | 7 | namespace luisa::render { 8 | 9 | Bool Interaction::same_sided(Expr wo, Expr wi) const noexcept { 10 | return dot(wi, _ng) * dot(wo, _ng) > 0.0f; 11 | } 12 | 13 | Float3 Interaction::p_robust(Expr w) const noexcept { 14 | auto offset_factor = _shape.intersection_offset_factor(); 15 | auto front = dot(_shading.n(), w) > 0.f; 16 | auto n = ite(front, _ng, -_ng); 17 | // return _pg + 1e-2f * normalize(n); 18 | return offset_ray_origin(_pg, offset_factor * n); 19 | } 20 | 21 | Var Interaction::spawn_ray(Expr wi, Expr t_max) const noexcept { 22 | return make_ray(p_robust(wi), wi, 0.f, t_max); 23 | } 24 | 25 | Var Interaction::spawn_ray_to(Expr p) const noexcept { 26 | auto p_from = p_robust(p - _pg); 27 | auto L = p - p_from; 28 | auto d = length(L); 29 | return make_ray(p_from, L * (1.f / d), 0.f, d * .9999f); 30 | } 31 | 32 | }// namespace luisa::render 33 | -------------------------------------------------------------------------------- /src/base/light.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike on 2021/12/15. 3 | // 4 | 5 | #include 6 | 7 | namespace luisa::render { 8 | 9 | Light::Light(Scene *scene, const SceneNodeDesc *desc) noexcept 10 | : SceneNode{scene, desc, SceneNodeTag::LIGHT} {} 11 | 12 | }// namespace luisa::render 13 | -------------------------------------------------------------------------------- /src/base/medium.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by ChenXin on 2023/2/13. 3 | // 4 | 5 | #include "medium.h" 6 | 7 | namespace luisa::render { 8 | 9 | using compute::Ray; 10 | 11 | Medium::Medium(Scene *scene, const SceneNodeDesc *desc) noexcept 12 | : SceneNode{scene, desc, SceneNodeTag::MEDIUM}, 13 | _priority{desc->property_uint_or_default("priority", 0u)} {} 14 | 15 | unique_ptr Medium::build( 16 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept { 17 | auto instance = _build(pipeline, command_buffer); 18 | return instance; 19 | } 20 | 21 | Medium::Closure::Closure( 22 | const Medium::Instance *instance, Expr ray, 23 | const SampledWavelengths &swl, Expr time, Expr eta, 24 | const SampledSpectrum& sigma_a, const SampledSpectrum& sigma_s, const SampledSpectrum& le, 25 | const PhaseFunction::Instance *phase_function) noexcept 26 | : _instance{instance}, _ray{ray}, _swl{swl}, _time{time}, _eta{eta}, 27 | _sigma_a{sigma_a}, _sigma_s{sigma_s}, _le{le}, _phase_function{phase_function} {} 28 | 29 | }// namespace luisa::render -------------------------------------------------------------------------------- /src/base/phase_function.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by ChenXin on 2023/2/14. 3 | // 4 | 5 | #include "phase_function.h" 6 | 7 | namespace luisa::render { 8 | 9 | PhaseFunction::PhaseFunction(Scene *scene, const SceneNodeDesc *desc) noexcept 10 | : SceneNode(scene, desc, SceneNodeTag::PHASE_FUNCTION) {} 11 | 12 | unique_ptr PhaseFunction::build(Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept { 13 | return _build(pipeline, command_buffer); 14 | } 15 | 16 | }// namespace luisa::render -------------------------------------------------------------------------------- /src/base/phase_function.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by ChenXin on 2023/2/14. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | namespace luisa::render { 15 | 16 | using compute::Expr; 17 | using compute::Var; 18 | 19 | class PhaseFunction : public SceneNode { 20 | public: 21 | struct PhaseFunctionSample { 22 | Float p; 23 | Float3 wi; 24 | Float pdf; 25 | Bool valid; 26 | }; 27 | 28 | class Instance; 29 | 30 | class Instance { 31 | public: 32 | 33 | protected: 34 | const Pipeline &_pipeline; 35 | const PhaseFunction *_phase_function; 36 | friend class PhaseFunction; 37 | 38 | public: 39 | [[nodiscard]] virtual Float p(Expr wo, Expr wi) const = 0; 40 | [[nodiscard]] virtual PhaseFunctionSample sample_p(Expr wo, Expr u) const = 0; 41 | [[nodiscard]] virtual Float pdf(Expr wo, Expr wi) const = 0; 42 | 43 | public: 44 | Instance(const Pipeline &pipeline, const PhaseFunction *phase_function) noexcept 45 | : _pipeline{pipeline}, _phase_function{phase_function} {} 46 | virtual ~Instance() noexcept = default; 47 | template 48 | requires std::is_base_of_v 49 | [[nodiscard]] auto node() const noexcept { return static_cast(_phase_function); } 50 | [[nodiscard]] auto &pipeline() const noexcept { return _pipeline; } 51 | }; 52 | 53 | protected: 54 | [[nodiscard]] virtual luisa::unique_ptr _build( 55 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept = 0; 56 | 57 | public: 58 | PhaseFunction(Scene *scene, const SceneNodeDesc *desc) noexcept; 59 | [[nodiscard]] luisa::unique_ptr build(Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept; 60 | }; 61 | 62 | }// namespace luisa::render 63 | -------------------------------------------------------------------------------- /src/base/sampler.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike on 2021/12/8. 3 | // 4 | 5 | #include 6 | 7 | namespace luisa::render { 8 | 9 | Sampler::Sampler(Scene *scene, const SceneNodeDesc *desc) noexcept 10 | : SceneNode{scene, desc, SceneNodeTag::SAMPLER}, 11 | _seed{desc->property_uint_or_default("seed", 19980810u)} {} 12 | 13 | }// namespace luisa::render 14 | -------------------------------------------------------------------------------- /src/base/sampler.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike on 2021/12/8. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace luisa::render { 12 | 13 | using compute::Expr; 14 | using compute::Float; 15 | using compute::Float2; 16 | 17 | class Sampler : public SceneNode { 18 | 19 | private: 20 | uint _seed; 21 | 22 | public: 23 | class Instance { 24 | 25 | private: 26 | const Pipeline &_pipeline; 27 | const Sampler *_sampler; 28 | 29 | public: 30 | explicit Instance(const Pipeline &pipeline, const Sampler *sampler) noexcept 31 | : _pipeline{pipeline}, _sampler{sampler} {} 32 | virtual ~Instance() noexcept = default; 33 | [[nodiscard]] auto &pipeline() const noexcept { return _pipeline; } 34 | 35 | template 36 | requires std::is_base_of_v 37 | [[nodiscard]] auto node() const noexcept { 38 | return static_cast(_sampler); 39 | } 40 | 41 | // interfaces 42 | virtual void reset(CommandBuffer &command_buffer, uint2 resolution, uint state_count, uint spp) noexcept = 0; 43 | virtual void start(Expr pixel, Expr sample_index) noexcept = 0; 44 | virtual void save_state(Expr state_id) noexcept = 0; 45 | virtual void load_state(Expr state_id) noexcept = 0; 46 | [[nodiscard]] virtual Float generate_1d() noexcept = 0; 47 | [[nodiscard]] virtual Float2 generate_2d() noexcept = 0; 48 | [[nodiscard]] virtual Float2 generate_pixel_2d() noexcept { return generate_2d(); } 49 | }; 50 | 51 | public: 52 | Sampler(Scene *scene, const SceneNodeDesc *desc) noexcept; 53 | [[nodiscard]] virtual luisa::unique_ptr build( 54 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept = 0; 55 | [[nodiscard]] auto seed() const noexcept { return _seed; } 56 | }; 57 | 58 | }// namespace luisa::render 59 | 60 | LUISA_DISABLE_DSL_ADDRESS_OF_OPERATOR(luisa::render::Sampler::Instance) 61 | -------------------------------------------------------------------------------- /src/base/scene.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike on 2021/12/8. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace luisa::render { 17 | 18 | using compute::Context; 19 | 20 | class SceneDesc; 21 | class SceneNodeDesc; 22 | 23 | class Camera; 24 | class Film; 25 | class Filter; 26 | class Integrator; 27 | class Surface; 28 | class Light; 29 | class Sampler; 30 | class Shape; 31 | class Transform; 32 | class LightSampler; 33 | class Environment; 34 | class Texture; 35 | class TextureMapping; 36 | class Spectrum; 37 | class Medium; 38 | class PhaseFunction; 39 | 40 | class Scene { 41 | 42 | public: 43 | using NodeCreater = SceneNode *(Scene *, const SceneNodeDesc *); 44 | using NodeDeleter = void(SceneNode *); 45 | using NodeHandle = luisa::unique_ptr; 46 | 47 | struct Config; 48 | 49 | private: 50 | const Context &_context; 51 | luisa::unique_ptr _config; 52 | std::recursive_mutex _mutex; 53 | 54 | public: 55 | // for internal use only, call Scene::create() instead 56 | explicit Scene(const Context &ctx) noexcept; 57 | ~Scene() noexcept; 58 | Scene(Scene &&scene) noexcept = delete; 59 | Scene(const Scene &scene) noexcept = delete; 60 | Scene &operator=(Scene &&scene) noexcept = delete; 61 | Scene &operator=(const Scene &scene) noexcept = delete; 62 | [[nodiscard]] SceneNode *load_node(SceneNodeTag tag, const SceneNodeDesc *desc) noexcept; 63 | [[nodiscard]] Camera *load_camera(const SceneNodeDesc *desc) noexcept; 64 | [[nodiscard]] Film *load_film(const SceneNodeDesc *desc) noexcept; 65 | [[nodiscard]] Filter *load_filter(const SceneNodeDesc *desc) noexcept; 66 | [[nodiscard]] Integrator *load_integrator(const SceneNodeDesc *desc) noexcept; 67 | [[nodiscard]] Surface *load_surface(const SceneNodeDesc *desc) noexcept; 68 | [[nodiscard]] Light *load_light(const SceneNodeDesc *desc) noexcept; 69 | [[nodiscard]] Sampler *load_sampler(const SceneNodeDesc *desc) noexcept; 70 | [[nodiscard]] Shape *load_shape(const SceneNodeDesc *desc) noexcept; 71 | [[nodiscard]] Transform *load_transform(const SceneNodeDesc *desc) noexcept; 72 | [[nodiscard]] LightSampler *load_light_sampler(const SceneNodeDesc *desc) noexcept; 73 | [[nodiscard]] Environment *load_environment(const SceneNodeDesc *desc) noexcept; 74 | [[nodiscard]] Texture *load_texture(const SceneNodeDesc *desc) noexcept; 75 | [[nodiscard]] TextureMapping *load_texture_mapping(const SceneNodeDesc *desc) noexcept; 76 | [[nodiscard]] Spectrum *load_spectrum(const SceneNodeDesc *desc) noexcept; 77 | [[nodiscard]] Medium *load_medium(const SceneNodeDesc *desc) noexcept; 78 | [[nodiscard]] PhaseFunction *load_phase_function(const SceneNodeDesc *desc) noexcept; 79 | 80 | public: 81 | [[nodiscard]] static luisa::unique_ptr create(const Context &ctx, const SceneDesc *desc) noexcept; 82 | [[nodiscard]] const Integrator *integrator() const noexcept; 83 | [[nodiscard]] const Environment *environment() const noexcept; 84 | [[nodiscard]] const Medium *environment_medium() const noexcept; 85 | [[nodiscard]] const Spectrum *spectrum() const noexcept; 86 | [[nodiscard]] luisa::span shapes() const noexcept; 87 | [[nodiscard]] luisa::span cameras() const noexcept; 88 | [[nodiscard]] float shadow_terminator_factor() const noexcept; 89 | [[nodiscard]] float intersection_offset_factor() const noexcept; 90 | }; 91 | 92 | }// namespace luisa::render 93 | -------------------------------------------------------------------------------- /src/base/scene_node.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike on 2021/12/13. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | namespace luisa::render { 9 | 10 | SceneNode::SceneNode(const Scene *scene, const SceneNodeDesc *desc, SceneNodeTag tag) noexcept 11 | : _scene{reinterpret_cast(scene)}, _tag{tag} { 12 | if (!desc->is_defined()) [[unlikely]] { 13 | LUISA_ERROR_WITH_LOCATION( 14 | "Undefined scene description " 15 | "node '{}' (type = {}::{}).", 16 | desc->identifier(), 17 | scene_node_tag_description(desc->tag()), 18 | desc->impl_type()); 19 | } 20 | if (!desc->is_internal() && desc->tag() != tag) [[unlikely]] { 21 | LUISA_ERROR( 22 | "Invalid tag {} of scene description " 23 | "node '{}' (expected {}). [{}]", 24 | scene_node_tag_description(desc->tag()), 25 | desc->identifier(), 26 | scene_node_tag_description(tag), 27 | desc->source_location().string()); 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/base/scene_node.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike on 2021/12/13. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace luisa::compute { 15 | class Device; 16 | class Stream; 17 | class CommandBuffer; 18 | }// namespace luisa::compute 19 | 20 | namespace luisa::render { 21 | 22 | using compute::Device; 23 | using compute::Stream; 24 | 25 | using compute::Expr; 26 | using compute::Float; 27 | using compute::Float2; 28 | using compute::Float3; 29 | using compute::Float4; 30 | using compute::Var; 31 | 32 | class Scene; 33 | class Pipeline; 34 | 35 | class SceneNode { 36 | 37 | public: 38 | using Tag = SceneNodeTag; 39 | 40 | private: 41 | intptr_t _scene : 56u; 42 | Tag _tag : 8u; 43 | 44 | public: 45 | SceneNode(const Scene *scene, const SceneNodeDesc *desc, Tag tag) noexcept; 46 | SceneNode(SceneNode &&) noexcept = delete; 47 | SceneNode(const SceneNode &) noexcept = delete; 48 | SceneNode &operator=(SceneNode &&) noexcept = delete; 49 | SceneNode &operator=(const SceneNode &) noexcept = delete; 50 | virtual ~SceneNode() noexcept = default; 51 | [[nodiscard]] auto scene() const noexcept { return reinterpret_cast(_scene); } 52 | [[nodiscard]] auto tag() const noexcept { return _tag; } 53 | [[nodiscard]] virtual luisa::string_view impl_type() const noexcept = 0; 54 | }; 55 | 56 | }// namespace luisa::render 57 | 58 | #define LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(cls) \ 59 | LUISA_EXPORT_API luisa::render::SceneNode *create( \ 60 | luisa::render::Scene *scene, \ 61 | const luisa::render::SceneNodeDesc *desc) LUISA_NOEXCEPT { \ 62 | return luisa::new_with_allocator(scene, desc); \ 63 | } \ 64 | LUISA_EXPORT_API void destroy( \ 65 | luisa::render::SceneNode *node) LUISA_NOEXCEPT { \ 66 | luisa::delete_with_allocator(node); \ 67 | } 68 | -------------------------------------------------------------------------------- /src/base/spd.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/9/14. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | namespace luisa::render { 11 | 12 | class Pipeline; 13 | using compute::Float; 14 | using compute::Expr; 15 | 16 | class SPD { 17 | 18 | private: 19 | const Pipeline &_pipeline; 20 | uint _buffer_id; 21 | float _sample_interval; 22 | 23 | public: 24 | SPD(Pipeline &pipeline, uint buffer_id, float sample_interval) noexcept; 25 | [[nodiscard]] static SPD create_cie_x(Pipeline &pipeline, CommandBuffer &cb) noexcept; 26 | [[nodiscard]] static SPD create_cie_y(Pipeline &pipeline, CommandBuffer &cb) noexcept; 27 | [[nodiscard]] static SPD create_cie_z(Pipeline &pipeline, CommandBuffer &cb) noexcept; 28 | [[nodiscard]] static SPD create_cie_d65(Pipeline &pipeline, CommandBuffer &cb) noexcept; 29 | [[nodiscard]] static float cie_y_integral() noexcept; 30 | [[nodiscard]] Float sample(Expr lambda) const noexcept; 31 | }; 32 | 33 | }// namespace luisa::render 34 | -------------------------------------------------------------------------------- /src/base/spectrum.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/3/21. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace luisa::render { 10 | 11 | Float3 Spectrum::Instance::srgb(const SampledWavelengths &swl, const SampledSpectrum &sp) const noexcept { 12 | auto xyz = cie_xyz(swl, sp); 13 | return cie_xyz_to_linear_srgb(xyz); 14 | } 15 | 16 | Float Spectrum::Instance::cie_y(const SampledWavelengths &swl, const SampledSpectrum &sp) const noexcept { 17 | using namespace compute; 18 | auto sum = def(0.f); 19 | constexpr auto safe_div = [](auto &&a, auto &&b) noexcept { 20 | return ite(b == 0.0f, 0.0f, a / b); 21 | }; 22 | for (auto i = 0u; i < swl.dimension(); i++) { 23 | sum += safe_div(_cie_y.sample(swl.lambda(i)) * sp[i], swl.pdf(i)); 24 | } 25 | auto denom = static_cast(swl.dimension()) * SPD::cie_y_integral(); 26 | return sum / denom; 27 | } 28 | 29 | Float3 Spectrum::Instance::cie_xyz(const SampledWavelengths &swl, const SampledSpectrum &sp) const noexcept { 30 | using namespace compute; 31 | constexpr auto safe_div = [](auto a, auto b) noexcept { 32 | return ite(b == 0.f, 0.f, a / b); 33 | }; 34 | auto sum = def(make_float3()); 35 | for (auto i = 0u; i < swl.dimension(); i++) { 36 | auto lambda = swl.lambda(i); 37 | auto pdf = swl.pdf(i); 38 | sum += make_float3(safe_div(_cie_x.sample(lambda) * sp[i], pdf), 39 | safe_div(_cie_y.sample(lambda) * sp[i], pdf), 40 | safe_div(_cie_z.sample(lambda) * sp[i], pdf)); 41 | } 42 | auto denom = static_cast(swl.dimension()) * SPD::cie_y_integral(); 43 | return sum / denom; 44 | } 45 | 46 | SampledWavelengths Spectrum::Instance::sample(Expr u) const noexcept { 47 | LUISA_ERROR_WITH_LOCATION("Spectrum::sample() is not implemented."); 48 | } 49 | //default impl: assuming |wavelength difference|<3nm is same wavelength and accumulate answer 50 | Float3 Spectrum::Instance::wavelength_mul(const SampledWavelengths& target_swl, const SampledSpectrum& target_sp, 51 | const SampledWavelengths& swl, const SampledSpectrum& sp) const noexcept { 52 | SampledSpectrum ret_sp{target_swl.dimension()}; 53 | SampledWavelengths ret_swl{target_swl.dimension()}; 54 | float error_bound = 3.0f; 55 | for (auto i = 0u; i < target_swl.dimension(); ++i) { 56 | auto target_lambda = target_swl.lambda(i); 57 | ret_swl.set_lambda(i,target_lambda); 58 | auto target_pdf = target_swl.pdf(i); 59 | Float accum_pdf = 0.0f; 60 | for (auto j = 0u; j < swl.dimension(); ++j) { 61 | auto lambda = swl.lambda(j); 62 | auto pdf = swl.pdf(j); 63 | Bool is_same = (lambda < target_lambda + error_bound) & (lambda > target_lambda - error_bound); 64 | accum_pdf += ite(is_same, target_pdf * pdf,0.0f); 65 | ret_sp[i] += ite(is_same, target_sp[i] * sp[j],0.0f); 66 | } 67 | //The actual pdf(p(lambda)) is pdf*dimension 68 | ret_swl.set_pdf(i, accum_pdf*swl.dimension()*2*error_bound); 69 | } 70 | return srgb(ret_swl, ret_sp); 71 | } 72 | Spectrum::Instance::Instance(Pipeline &pipeline, CommandBuffer &cb, 73 | const Spectrum *spec) noexcept 74 | : _pipeline{pipeline}, _spectrum{spec}, 75 | _cie_x{SPD::create_cie_x(pipeline, cb)}, 76 | _cie_y{SPD::create_cie_y(pipeline, cb)}, 77 | _cie_z{SPD::create_cie_z(pipeline, cb)} {} 78 | 79 | Spectrum::Spectrum(Scene *scene, const SceneNodeDesc *desc) noexcept 80 | : SceneNode{scene, desc, SceneNodeTag::SPECTRUM} {} 81 | 82 | }// namespace luisa::render 83 | -------------------------------------------------------------------------------- /src/base/surface.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike on 2021/12/14. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace luisa::render { 11 | 12 | Surface::Surface(Scene *scene, const SceneNodeDesc *desc) noexcept 13 | : SceneNode{scene, desc, SceneNodeTag::SURFACE} {} 14 | 15 | luisa::unique_ptr Surface::build( 16 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept { 17 | LUISA_ASSERT(!is_null(), "Building null Surface."); 18 | LUISA_ASSERT(!(is_transmissive() && is_thin()), "Surface cannot be both transmissive and thin."); 19 | return _build(pipeline, command_buffer); 20 | } 21 | 22 | void Surface::Instance::closure(PolymorphicCall &call, 23 | const Interaction &it, const SampledWavelengths &swl, 24 | Expr wo, Expr eta_i, Expr time) const noexcept { 25 | auto cls = call.collect(closure_identifier(), [&] { 26 | return create_closure(swl, time); 27 | }); 28 | populate_closure(cls, it, wo, eta_i); 29 | } 30 | 31 | luisa::string Surface::Instance::closure_identifier() const noexcept { 32 | return luisa::string{node()->impl_type()}; 33 | } 34 | 35 | static auto validate_surface_sides(Expr ng, Expr ns, 36 | Expr wo, Expr wi) noexcept { 37 | static Callable is_valid = [](Float3 ng, Float3 ns, Float3 wo, Float3 wi) noexcept { 38 | auto flip = sign(dot(ng, ns)); 39 | return sign(flip * dot(wo, ns)) == sign(dot(wo, ng)) & 40 | sign(flip * dot(wi, ns)) == sign(dot(wi, ng)); 41 | }; 42 | return is_valid(ng, ns, wo, wi); 43 | } 44 | 45 | Surface::Evaluation Surface::Closure::evaluate( 46 | Expr wo, Expr wi, TransportMode mode) const noexcept { 47 | auto eval = Surface::Evaluation::zero(swl().dimension()); 48 | $outline { 49 | eval = _evaluate(wo, wi, mode); 50 | auto valid = validate_surface_sides(it().ng(), it().shading().n(), wo, wi); 51 | eval.f = ite(valid, eval.f, 0.f); 52 | eval.pdf = ite(valid, eval.pdf, 0.f); 53 | }; 54 | return eval; 55 | } 56 | 57 | Surface::Sample Surface::Closure::sample(Expr wo, 58 | Expr u_lobe, Expr u, 59 | TransportMode mode) const noexcept { 60 | auto s = Surface::Sample::zero(swl().dimension()); 61 | $outline { 62 | s = _sample(wo, u_lobe, u, mode); 63 | auto valid = validate_surface_sides(it().ng(), it().shading().n(), wo, s.wi); 64 | s.eval.f = ite(valid, s.eval.f, 0.f); 65 | s.eval.pdf = ite(valid, s.eval.pdf, 0.f); 66 | }; 67 | return s; 68 | } 69 | 70 | }// namespace luisa::render 71 | -------------------------------------------------------------------------------- /src/base/texture.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/25. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | namespace luisa::render { 9 | 10 | Texture::Texture(Scene *scene, const SceneNodeDesc *desc) noexcept 11 | : SceneNode{scene, desc, SceneNodeTag::TEXTURE} {} 12 | 13 | luisa::optional Texture::evaluate_static() const noexcept { return luisa::nullopt; } 14 | 15 | [[nodiscard]] inline auto extend_color_to_rgb(auto color, uint n) noexcept { 16 | if (n == 1u) { return color.xxx(); } 17 | if (n == 2u) { return make_float3(color.xy(), 1.f); } 18 | return color; 19 | } 20 | 21 | Spectrum::Decode Texture::Instance::evaluate_albedo_spectrum( 22 | const Interaction &it, const SampledWavelengths &swl, Expr time) const noexcept { 23 | // skip the expensive encoding/decoding if the texture is static 24 | if (auto v = node()->evaluate_static()) { 25 | return _evaluate_static_albedo_spectrum(swl, *v); 26 | } 27 | // we have got no luck, do the expensive encoding/decoding 28 | auto v = evaluate(it, time); 29 | v = pipeline().spectrum()->encode_srgb_albedo( 30 | extend_color_to_rgb(v.xyz(), node()->channels())); 31 | return pipeline().spectrum()->decode_albedo(swl, v); 32 | } 33 | 34 | Spectrum::Decode Texture::Instance::evaluate_unbounded_spectrum( 35 | const Interaction &it, const SampledWavelengths &swl, Expr time) const noexcept { 36 | // skip the expensive encoding/decoding if the texture is static 37 | if (auto v = node()->evaluate_static()) { 38 | return _evaluate_static_unbounded_spectrum(swl, *v); 39 | } 40 | // we have got no luck, do the expensive encoding/decoding 41 | auto v = evaluate(it, time); 42 | v = pipeline().spectrum()->encode_srgb_unbounded( 43 | extend_color_to_rgb(v.xyz(), node()->channels())); 44 | return pipeline().spectrum()->decode_unbounded(swl, v); 45 | } 46 | 47 | Spectrum::Decode Texture::Instance::evaluate_illuminant_spectrum( 48 | const Interaction &it, const SampledWavelengths &swl, Expr time) const noexcept { 49 | // skip the expensive encoding/decoding if the texture is static 50 | if (auto v = node()->evaluate_static()) { 51 | return _evaluate_static_illuminant_spectrum(swl, *v); 52 | } 53 | // we have got no luck, do the expensive encoding/decoding 54 | auto v = evaluate(it, time); 55 | v = pipeline().spectrum()->encode_srgb_illuminant(v.xyz()); 56 | return pipeline().spectrum()->decode_illuminant(swl, v); 57 | } 58 | 59 | Spectrum::Decode Texture::Instance::_evaluate_static_albedo_spectrum( 60 | const SampledWavelengths &swl, float4 v) const noexcept { 61 | auto enc = pipeline().spectrum()->node()->encode_static_srgb_albedo( 62 | extend_color_to_rgb(v.xyz(), node()->channels())); 63 | return pipeline().spectrum()->decode_albedo(swl, enc); 64 | } 65 | 66 | Spectrum::Decode Texture::Instance::_evaluate_static_unbounded_spectrum( 67 | const SampledWavelengths &swl, float4 v) const noexcept { 68 | auto enc = pipeline().spectrum()->node()->encode_static_srgb_unbounded( 69 | extend_color_to_rgb(v.xyz(), node()->channels())); 70 | return pipeline().spectrum()->decode_unbounded(swl, enc); 71 | } 72 | 73 | Spectrum::Decode Texture::Instance::_evaluate_static_illuminant_spectrum( 74 | const SampledWavelengths &swl, float4 v) const noexcept { 75 | auto enc = pipeline().spectrum()->node()->encode_static_srgb_illuminant( 76 | extend_color_to_rgb(v.xyz(), node()->channels())); 77 | return pipeline().spectrum()->decode_illuminant(swl, enc); 78 | } 79 | 80 | }// namespace luisa::render 81 | -------------------------------------------------------------------------------- /src/base/texture.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/25. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace luisa::render { 14 | 15 | class Pipeline; 16 | class Interaction; 17 | class SampledWavelengths; 18 | 19 | using compute::Buffer; 20 | using compute::BufferView; 21 | using compute::Float4; 22 | using compute::Image; 23 | using compute::PixelStorage; 24 | using TextureSampler = compute::Sampler; 25 | 26 | class Texture : public SceneNode { 27 | 28 | public: 29 | class Instance { 30 | 31 | private: 32 | const Pipeline &_pipeline; 33 | const Texture *_texture; 34 | 35 | protected: 36 | [[nodiscard]] Spectrum::Decode _evaluate_static_albedo_spectrum( 37 | const SampledWavelengths &swl, float4 v) const noexcept; 38 | [[nodiscard]] Spectrum::Decode _evaluate_static_unbounded_spectrum( 39 | const SampledWavelengths &swl, float4 v) const noexcept; 40 | [[nodiscard]] Spectrum::Decode _evaluate_static_illuminant_spectrum( 41 | const SampledWavelengths &swl, float4 v) const noexcept; 42 | 43 | public: 44 | Instance(const Pipeline &pipeline, const Texture *texture) noexcept 45 | : _pipeline{pipeline}, _texture{texture} {} 46 | virtual ~Instance() noexcept = default; 47 | template 48 | requires std::is_base_of_v 49 | [[nodiscard]] auto node() const noexcept { return static_cast(_texture); } 50 | [[nodiscard]] auto &pipeline() const noexcept { return _pipeline; } 51 | [[nodiscard]] virtual Float4 evaluate( 52 | const Interaction &it, Expr time) const noexcept = 0; 53 | [[nodiscard]] virtual Spectrum::Decode evaluate_albedo_spectrum( 54 | const Interaction &it, const SampledWavelengths &swl, Expr time) const noexcept; 55 | [[nodiscard]] virtual Spectrum::Decode evaluate_unbounded_spectrum( 56 | const Interaction &it, const SampledWavelengths &swl, Expr time) const noexcept; 57 | [[nodiscard]] virtual Spectrum::Decode evaluate_illuminant_spectrum( 58 | const Interaction &it, const SampledWavelengths &swl, Expr time) const noexcept; 59 | }; 60 | 61 | public: 62 | Texture(Scene *scene, const SceneNodeDesc *desc) noexcept; 63 | [[nodiscard]] virtual bool is_black() const noexcept = 0; 64 | [[nodiscard]] virtual bool is_constant() const noexcept = 0; 65 | [[nodiscard]] virtual luisa::optional evaluate_static() const noexcept; 66 | [[nodiscard]] virtual uint channels() const noexcept { return 4u; } 67 | [[nodiscard]] virtual uint2 resolution() const noexcept = 0; 68 | [[nodiscard]] virtual luisa::unique_ptr build( 69 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept = 0; 70 | }; 71 | 72 | }// namespace luisa::render 73 | 74 | LUISA_DISABLE_DSL_ADDRESS_OF_OPERATOR(luisa::render::Texture::Instance) 75 | -------------------------------------------------------------------------------- /src/base/texture_mapping.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/3/11. 3 | // 4 | 5 | #include 6 | 7 | namespace luisa::render { 8 | 9 | TextureMapping::TextureMapping(Scene *scene, const SceneNodeDesc *desc) noexcept 10 | : SceneNode{scene, desc, SceneNodeTag::TEXTURE_MAPPING} {} 11 | 12 | }// namespace luisa::render 13 | -------------------------------------------------------------------------------- /src/base/texture_mapping.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/3/11. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace luisa::render { 12 | 13 | using compute::Float; 14 | using compute::Float2; 15 | 16 | class Pipeline; 17 | class Interaction; 18 | 19 | class TextureMapping : public SceneNode { 20 | 21 | public: 22 | struct Coord2D { 23 | Float2 st; 24 | Float ds_dx; 25 | Float ds_dy; 26 | Float dt_dx; 27 | Float dt_dy; 28 | }; 29 | 30 | class Instance { 31 | 32 | private: 33 | const Pipeline &_pipeline; 34 | const TextureMapping *_mapping; 35 | 36 | public: 37 | Instance(const Pipeline &pipeline, const TextureMapping *mapping) noexcept 38 | : _pipeline{pipeline}, _mapping{mapping} {} 39 | template 40 | requires std::is_base_of_v 41 | [[nodiscard]] auto node() const noexcept { return static_cast(_mapping); } 42 | [[nodiscard]] auto &pipeline() const noexcept { return _pipeline; } 43 | [[nodiscard]] virtual Coord2D map(const Interaction &it, Expr time) const noexcept = 0; 44 | }; 45 | 46 | public: 47 | TextureMapping(Scene *scene, const SceneNodeDesc *desc) noexcept; 48 | [[nodiscard]] virtual luisa::unique_ptr build( 49 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept = 0; 50 | }; 51 | 52 | }// namespace luisa::render 53 | 54 | LUISA_DISABLE_DSL_ADDRESS_OF_OPERATOR(luisa::render::TextureMapping::Instance) 55 | LUISA_DISABLE_DSL_ADDRESS_OF_OPERATOR(luisa::render::TextureMapping::Coord2D) 56 | -------------------------------------------------------------------------------- /src/base/transform.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike on 2021/12/15. 3 | // 4 | 5 | #include 6 | 7 | namespace luisa::render { 8 | 9 | Transform::Transform(Scene *scene, const SceneNodeDesc *desc) noexcept 10 | : SceneNode{scene, desc, SceneNodeTag::TRANSFORM} {} 11 | 12 | TransformTree::Node::Node( 13 | const TransformTree::Node *parent, 14 | const Transform *t) noexcept 15 | : _parent{parent}, _transform{t} {} 16 | 17 | float4x4 TransformTree::Node::matrix(float time) const noexcept { 18 | auto m = _transform->matrix(time); 19 | for (auto node = _parent; node != nullptr; node = node->_parent) { 20 | m = node->_transform->matrix(time) * m; 21 | } 22 | return m; 23 | } 24 | 25 | TransformTree::TransformTree() noexcept { 26 | _node_stack.emplace_back(nullptr); 27 | _static_stack.emplace_back(true); 28 | } 29 | 30 | void TransformTree::push(const Transform *t) noexcept { 31 | if (t != nullptr && !t->is_identity()) { 32 | auto node = luisa::make_unique(_node_stack.back(), t); 33 | _node_stack.emplace_back(_nodes.emplace_back(std::move(node)).get()); 34 | _static_stack.emplace_back(_static_stack.back() && t->is_static()); 35 | } 36 | } 37 | 38 | void TransformTree::pop(const Transform *t) noexcept { 39 | if (t != nullptr && !t->is_identity()) { 40 | assert( 41 | !_node_stack.empty() && 42 | _node_stack.back()->transform() == t); 43 | _node_stack.pop_back(); 44 | _static_stack.pop_back(); 45 | } 46 | } 47 | 48 | std::pair TransformTree::leaf( 49 | const Transform *t) noexcept { 50 | if (t == nullptr || t->is_identity()) { 51 | return std::make_pair( 52 | _node_stack.back(), 53 | _static_stack.back()); 54 | } 55 | auto node = luisa::make_unique(_node_stack.back(), t); 56 | auto p_node = _nodes.emplace_back(std::move(node)).get(); 57 | auto is_static = _static_stack.back() && t->is_static(); 58 | return std::make_pair(p_node, is_static); 59 | } 60 | 61 | }// namespace luisa::render 62 | -------------------------------------------------------------------------------- /src/base/transform.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike on 2021/12/8. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace luisa::render { 13 | 14 | class Transform : public SceneNode { 15 | public: 16 | Transform(Scene *scene, const SceneNodeDesc *desc) noexcept; 17 | [[nodiscard]] virtual bool is_static() const noexcept = 0; 18 | [[nodiscard]] virtual bool is_identity() const noexcept = 0; 19 | [[nodiscard]] virtual float4x4 matrix(float time) const noexcept = 0; 20 | }; 21 | 22 | class TransformTree { 23 | 24 | public: 25 | class Node { 26 | 27 | private: 28 | const Node *_parent; 29 | const Transform *_transform; 30 | 31 | public: 32 | Node(const Node *parent, const Transform *t) noexcept; 33 | [[nodiscard]] auto transform() const noexcept { return _transform; } 34 | [[nodiscard]] float4x4 matrix(float time) const noexcept; 35 | }; 36 | 37 | private: 38 | luisa::vector> _nodes; 39 | luisa::vector _node_stack; 40 | luisa::vector _static_stack; 41 | 42 | public: 43 | TransformTree() noexcept; 44 | [[nodiscard]] auto size() const noexcept { return _nodes.size(); } 45 | [[nodiscard]] auto empty() const noexcept { return _nodes.empty(); } 46 | void push(const Transform *t) noexcept; 47 | void pop(const Transform *t) noexcept; 48 | [[nodiscard]] std::pair leaf(const Transform *t) noexcept; 49 | }; 50 | 51 | class InstancedTransform { 52 | 53 | private: 54 | const TransformTree::Node *_node; 55 | size_t _instance_id; 56 | 57 | public: 58 | InstancedTransform(const TransformTree::Node *node, size_t inst) noexcept 59 | : _node{node}, _instance_id{inst} {} 60 | [[nodiscard]] auto instance_id() const noexcept { return _instance_id; } 61 | [[nodiscard]] auto matrix(float time) const noexcept { 62 | return _node == nullptr ? make_float4x4(1.0f) : _node->matrix(time); 63 | } 64 | }; 65 | 66 | }// namespace luisa::render 67 | -------------------------------------------------------------------------------- /src/cameras/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(luisa-render-cameras INTERFACE) 2 | 3 | luisa_render_add_plugin(pinhole CATEGORY camera SOURCES pinhole.cpp) 4 | luisa_render_add_plugin(thinlens CATEGORY camera SOURCES thin_lens.cpp) 5 | luisa_render_add_plugin(ortho CATEGORY camera SOURCES ortho.cpp) 6 | -------------------------------------------------------------------------------- /src/cameras/ortho.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/3/26. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace luisa::render { 11 | 12 | struct OrthoCameraData { 13 | float2 resolution; 14 | float scale; 15 | }; 16 | 17 | }// namespace luisa::render 18 | 19 | LUISA_STRUCT(luisa::render::OrthoCameraData, resolution, scale){}; 20 | 21 | namespace luisa::render { 22 | 23 | using namespace luisa::compute; 24 | 25 | class OrthoCamera; 26 | class OrthoCameraInstance; 27 | 28 | class OrthoCamera : public Camera { 29 | 30 | private: 31 | float _zoom; 32 | 33 | public: 34 | OrthoCamera(Scene *scene, const SceneNodeDesc *desc) noexcept 35 | : Camera{scene, desc}, _zoom{desc->property_float_or_default("zoom", 0.f)} {} 36 | [[nodiscard]] luisa::unique_ptr build( 37 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept override; 38 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 39 | [[nodiscard]] bool requires_lens_sampling() const noexcept override { return false; } 40 | [[nodiscard]] auto zoom() const noexcept { return _zoom; } 41 | }; 42 | 43 | class OrthoCameraInstance : public Camera::Instance { 44 | 45 | private: 46 | BufferView _device_data; 47 | 48 | public: 49 | explicit OrthoCameraInstance( 50 | Pipeline &ppl, CommandBuffer &command_buffer, 51 | const OrthoCamera *camera) noexcept; 52 | [[nodiscard]] std::pair, Float> _generate_ray_in_camera_space( 53 | Expr pixel, Expr /* u_lens */, Expr /* time */) const noexcept override { 54 | auto data = _device_data->read(0u); 55 | auto p = (pixel * 2.0f - data.resolution) / data.resolution.y * data.scale; 56 | auto ray = make_ray(make_float3(p.x, -p.y, 0.f), make_float3(0.f, 0.f, -1.f)); 57 | return std::make_pair(std::move(ray), 1.0f); 58 | } 59 | }; 60 | 61 | OrthoCameraInstance::OrthoCameraInstance( 62 | Pipeline &ppl, CommandBuffer &command_buffer, const OrthoCamera *camera) noexcept 63 | : Camera::Instance{ppl, command_buffer, camera}, 64 | _device_data{ppl.arena_buffer(1u)} { 65 | OrthoCameraData host_data{make_float2(camera->film()->resolution()), 66 | std::pow(2.f, camera->zoom())}; 67 | command_buffer << _device_data.copy_from(&host_data) 68 | << commit(); 69 | } 70 | 71 | luisa::unique_ptr OrthoCamera::build( 72 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept { 73 | return luisa::make_unique( 74 | pipeline, command_buffer, this); 75 | } 76 | 77 | using ClipPlaneOrthoCamera = ClipPlaneCameraWrapper< 78 | OrthoCamera, OrthoCameraInstance>; 79 | 80 | }// namespace luisa::render 81 | 82 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::ClipPlaneOrthoCamera) 83 | -------------------------------------------------------------------------------- /src/cameras/pinhole.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike on 2022/1/7. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace luisa::render { 11 | 12 | struct PinholeCameraData { 13 | float2 resolution; 14 | float tan_half_fov; 15 | }; 16 | 17 | }// namespace luisa::render 18 | 19 | LUISA_STRUCT(luisa::render::PinholeCameraData, 20 | resolution, tan_half_fov){}; 21 | 22 | namespace luisa::render { 23 | 24 | using namespace luisa::compute; 25 | 26 | class PinholeCamera; 27 | class PinholeCameraInstance; 28 | 29 | class PinholeCamera : public Camera { 30 | 31 | private: 32 | float _fov; 33 | 34 | public: 35 | PinholeCamera(Scene *scene, const SceneNodeDesc *desc) noexcept 36 | : Camera{scene, desc}, 37 | _fov{radians(std::clamp(desc->property_float_or_default("fov", 35.0f), 1e-3f, 180.f - 1e-3f))} {} 38 | [[nodiscard]] luisa::unique_ptr build( 39 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept override; 40 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 41 | [[nodiscard]] bool requires_lens_sampling() const noexcept override { return false; } 42 | [[nodiscard]] auto fov() const noexcept { return _fov; } 43 | }; 44 | 45 | class PinholeCameraInstance : public Camera::Instance { 46 | 47 | private: 48 | BufferView _device_data; 49 | 50 | public: 51 | explicit PinholeCameraInstance( 52 | Pipeline &ppl, CommandBuffer &command_buffer, 53 | const PinholeCamera *camera) noexcept 54 | : Camera::Instance{ppl, command_buffer, camera}, 55 | _device_data{ppl.arena_buffer(1u)} { 56 | PinholeCameraData host_data{make_float2(camera->film()->resolution()), 57 | tan(camera->fov() * 0.5f)}; 58 | command_buffer << _device_data.copy_from(&host_data) << commit(); 59 | } 60 | [[nodiscard]] std::pair, Float> _generate_ray_in_camera_space( 61 | Expr pixel, Expr /* u_lens */, Expr /* time */) const noexcept override { 62 | auto data = _device_data->read(0u); 63 | auto p = (pixel * 2.0f - data.resolution) * (data.tan_half_fov / data.resolution.y); 64 | auto direction = normalize(make_float3(p.x, -p.y, -1.f)); 65 | auto ray = make_ray(make_float3(), direction); 66 | return std::make_pair(std::move(ray), 1.f); 67 | } 68 | }; 69 | 70 | luisa::unique_ptr PinholeCamera::build( 71 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept { 72 | return luisa::make_unique( 73 | pipeline, command_buffer, this); 74 | } 75 | 76 | using ClipPlanePinholeCamera = ClipPlaneCameraWrapper< 77 | PinholeCamera, PinholeCameraInstance>; 78 | 79 | }// namespace luisa::render 80 | 81 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::ClipPlanePinholeCamera) 82 | -------------------------------------------------------------------------------- /src/environments/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(luisa-render-environments INTERFACE) 2 | luisa_render_add_plugin(null CATEGORY environment SOURCES null.cpp) 3 | luisa_render_add_plugin(spherical CATEGORY environment SOURCES spherical.cpp) 4 | luisa_render_add_plugin(directional CATEGORY environment SOURCES directional.cpp) 5 | luisa_render_add_plugin(combined CATEGORY environment SOURCES combined.cpp) 6 | luisa_render_add_plugin(grouped CATEGORY environment SOURCES grouped.cpp) 7 | -------------------------------------------------------------------------------- /src/environments/null.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/14. 3 | // 4 | 5 | #include 6 | 7 | namespace luisa::render { 8 | 9 | struct NullEnvironment final : public Environment { 10 | NullEnvironment(Scene *scene, const SceneNodeDesc *desc) noexcept : Environment{scene, desc} {} 11 | [[nodiscard]] bool is_black() const noexcept override { return true; } 12 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 13 | [[nodiscard]] luisa::unique_ptr build(Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept override { return nullptr; } 14 | }; 15 | 16 | }// namespace luisa::render 17 | 18 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::NullEnvironment) 19 | -------------------------------------------------------------------------------- /src/ext/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(luisa-render-ext INTERFACE) 2 | 3 | add_subdirectory(cxxopts) 4 | target_link_libraries(luisa-render-ext INTERFACE cxxopts::cxxopts) 5 | 6 | add_library(fast_float INTERFACE) 7 | target_include_directories(fast_float INTERFACE fast_float/include) 8 | target_link_libraries(luisa-render-ext INTERFACE fast_float) 9 | 10 | # shared by assimp and tinyexr 11 | find_package(ZLIB) 12 | 13 | # Assimp 14 | if (ZLIB_FOUND) 15 | # work around assimp's broken install when using system zlib 16 | if (NOT TARGET zlib) 17 | add_library(zlib INTERFACE) 18 | endif () 19 | if (NOT TARGET zlibstatic) 20 | add_library(zlibstatic INTERFACE) 21 | endif () 22 | set(ASSIMP_BUILD_ZLIB OFF CACHE BOOL "" FORCE) 23 | else () 24 | set(ASSIMP_BUILD_ZLIB ON CACHE BOOL "" FORCE) 25 | endif () 26 | set(ASSIMP_BUILD_ASSIMP_TOOLS OFF CACHE BOOL "" FORCE) 27 | set(ASSIMP_BUILD_TESTS OFF CACHE BOOL "" FORCE) 28 | set(ASSIMP_INSTALL ON CACHE BOOL "" FORCE) 29 | set(ASSIMP_INJECT_DEBUG_POSTFIX OFF CACHE BOOL "" FORCE) 30 | set(ASSIMP_NO_EXPORT ON CACHE BOOL "" FORCE) 31 | set(ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT ON CACHE BOOL "" FORCE) 32 | set(ASSIMP_WARNINGS_AS_ERRORS OFF CACHE BOOL "" FORCE) 33 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND 34 | CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 15.0) 35 | add_compile_options(-Wno-deprecated-non-prototype) 36 | endif () 37 | add_subdirectory(assimp) 38 | set_target_properties(assimp PROPERTIES 39 | UNITY_BUILD OFF 40 | OUTPUT_NAME "luisa-render-ext-assimp") 41 | if (UNIX AND NOT APPLE) # TODO: fix this 42 | target_compile_definitions(assimp PRIVATE USE_FILE32API=1) 43 | endif () 44 | if (ASSIMP_BUILD_ZLIB) 45 | if (TARGET zlib) 46 | set_target_properties(zlib PROPERTIES 47 | WINDOWS_EXPORT_ALL_SYMBOLS ON 48 | OUTPUT_NAME "luisa-render-ext-zlib") 49 | install(TARGETS zlib 50 | LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR} 51 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) 52 | endif () 53 | endif () 54 | target_link_libraries(luisa-render-ext INTERFACE assimp::assimp) 55 | 56 | # tinyexr 57 | set(TINYEXR_BUILD_SAMPLE OFF CACHE BOOL "" FORCE) 58 | if (ZLIB_FOUND) 59 | add_library(tinyexr SHARED tinyexr.cpp) 60 | target_compile_definitions(tinyexr PUBLIC TINYEXR_USE_MINIZ=0) 61 | target_link_libraries(tinyexr PRIVATE ZLIB::ZLIB) 62 | else () 63 | add_library(tinyexr SHARED tinyexr.cpp tinyexr/deps/miniz/miniz.c) 64 | target_compile_definitions(tinyexr PUBLIC TINYEXR_USE_MINIZ=1) 65 | target_include_directories(tinyexr PRIVATE tinyexr/deps/miniz) 66 | endif () 67 | target_include_directories(tinyexr PUBLIC tinyexr) 68 | target_link_libraries(tinyexr PUBLIC ${CMAKE_DL_LIBS}) 69 | set_target_properties(tinyexr PROPERTIES 70 | WINDOWS_EXPORT_ALL_SYMBOLS ON 71 | OUTPUT_NAME "luisa-render-ext-tinyexr") 72 | target_link_libraries(luisa-render-ext INTERFACE tinyexr) 73 | install(TARGETS tinyexr 74 | LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR} 75 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) 76 | 77 | # nlohmann json 78 | add_library(nlohmann_json INTERFACE) 79 | target_include_directories(nlohmann_json INTERFACE json/single_include) 80 | target_link_libraries(luisa-render-ext INTERFACE nlohmann_json) 81 | -------------------------------------------------------------------------------- /src/ext/tinyexr.cpp: -------------------------------------------------------------------------------- 1 | #if defined(_WIN32) 2 | #ifndef NOMINMAX 3 | #define NOMINMAX 4 | #endif 5 | #endif 6 | 7 | #if !defined(TINYEXR_USE_MINIZ) || TINYEXR_USE_MINIZ == 0 8 | #include 9 | #endif 10 | 11 | #define TINYEXR_IMPLEMENTATION 12 | #include 13 | -------------------------------------------------------------------------------- /src/films/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(luisa-render-films INTERFACE) 2 | luisa_render_add_plugin(color CATEGORY film SOURCES color.cpp) 3 | luisa_render_add_plugin(display CATEGORY film SOURCES display.cpp) 4 | -------------------------------------------------------------------------------- /src/filters/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(luisa-render-filters INTERFACE) 2 | luisa_render_add_plugin(box CATEGORY filter SOURCES box.cpp) 3 | luisa_render_add_plugin(gaussian CATEGORY filter SOURCES gaussian.cpp) 4 | luisa_render_add_plugin(mitchell CATEGORY filter SOURCES mitchell.cpp) 5 | luisa_render_add_plugin(triangle CATEGORY filter SOURCES triangle.cpp) 6 | luisa_render_add_plugin(lanczossinc CATEGORY filter SOURCES lanczos_sinc.cpp) 7 | -------------------------------------------------------------------------------- /src/filters/box.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/10. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | namespace luisa::render { 9 | 10 | struct BoxFilter final : public Filter { 11 | BoxFilter(Scene *scene, const SceneNodeDesc *desc) noexcept : Filter{scene, desc} {} 12 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 13 | [[nodiscard]] float evaluate(float x) const noexcept override { return 1.0f; } 14 | }; 15 | 16 | }// namespace luisa::render 17 | 18 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::BoxFilter) 19 | -------------------------------------------------------------------------------- /src/filters/gaussian.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/16. 3 | // 4 | 5 | #include 6 | 7 | namespace luisa::render { 8 | 9 | class GaussianFilter final : public Filter { 10 | 11 | private: 12 | float _sigma; 13 | 14 | public: 15 | GaussianFilter(Scene *scene, const SceneNodeDesc *desc) noexcept 16 | : Filter{scene, desc}, _sigma{desc->property_float_or_default("sigma", 0.f)} { 17 | if (_sigma <= 0.f) {// invalid sigma, compute from radius 18 | auto r = radius(); 19 | // G = 1.f / (sqrt(2.f * pi) * sigma) * exp(-r * r / (2.f * sigma * sigma)). 20 | // Ignoring the normalization factor, we have 21 | // F(r) = exp(-r * r / (2.f * sigma * sigma)), 22 | // where k is a constant. Let F(radius) = eps, we have 23 | // sigma = sqrt(radius * radius / (-2 * log(eps))) 24 | // = radius / sqrt(-2.f * log(eps)). 25 | // We choose eps = 1e-2, so that approximately, sigma = radius / 3. 26 | _sigma = r / 3.f; 27 | } 28 | } 29 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 30 | [[nodiscard]] float evaluate(float x) const noexcept override { 31 | auto G = [s = 2.0f * _sigma * _sigma](auto x) noexcept { 32 | return 1.0f / std::sqrt(pi * s) * std::exp(-x * x / s); 33 | }; 34 | return G(x) - G(radius()); 35 | } 36 | }; 37 | 38 | }// namespace luisa::render 39 | 40 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::GaussianFilter) 41 | -------------------------------------------------------------------------------- /src/filters/lanczos_sinc.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/16. 3 | // 4 | 5 | #include 6 | 7 | namespace luisa::render { 8 | 9 | class LanczosSincFilter final : public Filter { 10 | 11 | private: 12 | float _tau; 13 | 14 | public: 15 | LanczosSincFilter(Scene *scene, const SceneNodeDesc *desc) noexcept 16 | : Filter{scene, desc}, _tau{desc->property_float_or_default("tau", 3.0f)} {} 17 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 18 | [[nodiscard]] float evaluate(float x) const noexcept override { 19 | x = x / radius(); 20 | static constexpr auto sin_x_over_x = [](auto x) noexcept { 21 | return 1.0f + x * x == 1.0f ? 1.0f : std::sin(x) / x; 22 | }; 23 | static constexpr auto sinc = [](auto x) noexcept { 24 | return sin_x_over_x(pi * x); 25 | }; 26 | if (std::abs(x) > 1.0f) [[unlikely]] { return 0.0f; } 27 | return sinc(x) * sinc(x / _tau); 28 | } 29 | }; 30 | 31 | }// namespace luisa::render 32 | 33 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::LanczosSincFilter) 34 | -------------------------------------------------------------------------------- /src/filters/mitchell.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/16. 3 | // 4 | 5 | #include 6 | 7 | namespace luisa::render { 8 | 9 | class MitchellFilter final : public Filter { 10 | 11 | private: 12 | float _b; 13 | float _c; 14 | 15 | public: 16 | MitchellFilter(Scene *scene, const SceneNodeDesc *desc) noexcept 17 | : Filter{scene, desc}, 18 | _b{desc->property_float_or_default("b", 1.0f / 3.0f)}, 19 | _c{desc->property_float_or_default("c", 1.0f / 3.0f)} {} 20 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 21 | [[nodiscard]] float evaluate(float x) const noexcept override { 22 | x = 2.f * std::abs(x / radius()); 23 | if (x <= 1.0f) { 24 | return ((12.0f - 9.0f * _b - 6.0f * _c) * x * x * x + 25 | (-18.0f + 12.0f * _b + 6.0f * _c) * x * x + 26 | (6.0f - 2.0f * _b)) * 27 | (1.f / 6.f); 28 | } 29 | if (x <= 2.0f) { 30 | return ((-_b - 6.0f * _c) * x * x * x + 31 | (6.0f * _b + 30.0f * _c) * x * x + 32 | (-12.0f * _b - 48.0f * _c) * x + 33 | (8.0f * _b + 24.0f * _c)) * 34 | (1.0f / 6.0f); 35 | } 36 | return 0.0f; 37 | } 38 | }; 39 | 40 | }// namespace luisa::render 41 | 42 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::MitchellFilter) 43 | -------------------------------------------------------------------------------- /src/filters/triangle.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/16. 3 | // 4 | 5 | #include 6 | 7 | namespace luisa::render { 8 | 9 | struct TriangleFilter final : public Filter { 10 | TriangleFilter(Scene *scene, const SceneNodeDesc *desc) noexcept : Filter{scene, desc} {} 11 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 12 | [[nodiscard]] float evaluate(float x) const noexcept override { return std::max(1.0f - std::abs(x / radius()), 0.0f); } 13 | }; 14 | 15 | }// namespace luisa::render 16 | 17 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::TriangleFilter) 18 | -------------------------------------------------------------------------------- /src/integrators/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(luisa-render-integrators INTERFACE) 2 | luisa_render_add_plugin(normal CATEGORY integrator SOURCES normal.cpp) 3 | luisa_render_add_plugin(megapath CATEGORY integrator SOURCES mega_path.cpp) 4 | luisa_render_add_plugin(wavepath CATEGORY integrator SOURCES wave_path.cpp) 5 | luisa_render_add_plugin(wavepathreadback CATEGORY integrator SOURCES wave_path_readback.cpp) 6 | luisa_render_add_plugin(wavepath_v2 CATEGORY integrator SOURCES wave_path_v2.cpp) 7 | luisa_render_add_plugin(group CATEGORY integrator SOURCES group.cpp) 8 | luisa_render_add_plugin(aov CATEGORY integrator SOURCES aov.cpp) 9 | luisa_render_add_plugin(nfor CATEGORY integrator SOURCES nfor.cpp) 10 | luisa_render_add_plugin(direct CATEGORY integrator SOURCES direct.cpp) 11 | luisa_render_add_plugin(pssmlt CATEGORY integrator SOURCES pssmlt.cpp) 12 | luisa_render_add_plugin(gradientpath CATEGORY integrator SOURCES gpt.cpp) 13 | luisa_render_add_plugin(megawave CATEGORY integrator SOURCES megawave.cpp) 14 | luisa_render_add_plugin(megapm CATEGORY integrator SOURCES megapm.cpp) 15 | luisa_render_add_plugin(megavpt CATEGORY integrator SOURCES mega_vpt.cpp) 16 | luisa_render_add_plugin(megavptnaive CATEGORY integrator SOURCES mega_vpt_naive.cpp) 17 | luisa_render_add_plugin(megavolumetricpt CATEGORY integrator SOURCES mega_volume_path.cpp) 18 | -------------------------------------------------------------------------------- /src/integrators/group.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | namespace luisa::render { 7 | 8 | class GroupIntegrator; 9 | 10 | class GroupIntegratorInstance final : public Integrator::Instance { 11 | 12 | private: 13 | luisa::vector> _integrators; 14 | 15 | public: 16 | GroupIntegratorInstance(const GroupIntegrator *group, 17 | Pipeline &pipeline, 18 | CommandBuffer &cb) noexcept; 19 | void render(Stream &stream) noexcept override; 20 | }; 21 | 22 | class GroupIntegrator final : public Integrator { 23 | luisa::vector _integrators; 24 | 25 | public: 26 | GroupIntegrator(Scene *scene, const SceneNodeDesc *desc) noexcept : Integrator{scene, desc} { 27 | auto children = desc->property_node_list_or_default("integrators"); 28 | luisa::vector integrators(children.size()); 29 | for (auto i = 0u; i < children.size(); i++) { 30 | integrators[i] = scene->load_integrator(children[i]); 31 | } 32 | _integrators = std::move(integrators); 33 | } 34 | 35 | [[nodiscard]] luisa::vector integrators() const noexcept { return _integrators; } 36 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 37 | [[nodiscard]] luisa::unique_ptr build(Pipeline &pipeline, CommandBuffer &cb) const noexcept override { 38 | return luisa::make_unique(this, pipeline, cb); 39 | } 40 | }; 41 | 42 | GroupIntegratorInstance::GroupIntegratorInstance( 43 | const GroupIntegrator *group, Pipeline &pipeline, CommandBuffer &cb) noexcept 44 | : Integrator::Instance{pipeline, cb, group} { 45 | luisa::vector> instances(group->integrators().size()); 46 | for (auto i = 0u; i < group->integrators().size(); i++) { 47 | instances[i] = std::move(group->integrators()[i]->build(pipeline, cb)); 48 | } 49 | _integrators = std::move(instances); 50 | } 51 | 52 | void GroupIntegratorInstance::render(Stream &stream) noexcept { 53 | for (auto &_integrator : _integrators) { 54 | _integrator->render(stream); 55 | } 56 | } 57 | 58 | }// namespace luisa::render 59 | 60 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::GroupIntegrator) 61 | -------------------------------------------------------------------------------- /src/integrators/normal.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike on 2022/1/7. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace luisa::render { 11 | 12 | class NormalVisualizer final : public ProgressiveIntegrator { 13 | 14 | private: 15 | bool _remap; 16 | bool _shading; 17 | 18 | public: 19 | NormalVisualizer(Scene *scene, const SceneNodeDesc *desc) noexcept 20 | : ProgressiveIntegrator{scene, desc}, 21 | _remap{desc->property_bool_or_default("remap", true)}, 22 | _shading{desc->property_bool_or_default("shading", true)} {} 23 | [[nodiscard]] auto remap() const noexcept { return _remap; } 24 | [[nodiscard]] auto shading() const noexcept { return _shading; } 25 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 26 | [[nodiscard]] luisa::unique_ptr build( 27 | Pipeline &pipeline, CommandBuffer &cb) const noexcept override; 28 | }; 29 | 30 | class NormalVisualizerInstance final : public ProgressiveIntegrator::Instance { 31 | 32 | public: 33 | using ProgressiveIntegrator::Instance::Instance; 34 | 35 | protected: 36 | [[nodiscard]] Float3 Li(const Camera::Instance *camera, Expr frame_index, 37 | Expr pixel_id, Expr time) const noexcept override { 38 | sampler()->start(pixel_id, frame_index); 39 | auto u_filter = sampler()->generate_pixel_2d(); 40 | auto u_lens = camera->node()->requires_lens_sampling() ? sampler()->generate_2d() : make_float2(.5f); 41 | auto cs = camera->generate_ray(pixel_id, time, u_filter, u_lens); 42 | auto swl = pipeline().spectrum()->sample(sampler()->generate_1d()); 43 | auto path_weight = cs.weight; 44 | auto it = pipeline().geometry()->intersect(cs.ray); 45 | auto ns = def(make_float3(0.f)); 46 | auto wo = -cs.ray->direction(); 47 | $if(it->valid()) { 48 | if (node()->shading()) { 49 | $if(it->shape().has_surface()) { 50 | PolymorphicCall call; 51 | pipeline().surfaces().dispatch(it->shape().surface_tag(), [&](auto surface) noexcept { 52 | surface->closure(call, *it, swl, wo, 1.f, time); 53 | }); 54 | call.execute([&](auto closure) noexcept { 55 | ns = closure->it().shading().n(); 56 | }); 57 | } 58 | $else { 59 | ns = it->shading().n(); 60 | }; 61 | } else { 62 | ns = it->ng(); 63 | } 64 | if (node()->remap()) { 65 | ns = ns * .5f + .5f; 66 | } 67 | }; 68 | return path_weight * ns; 69 | } 70 | }; 71 | 72 | luisa::unique_ptr NormalVisualizer::build( 73 | Pipeline &pipeline, CommandBuffer &cb) const noexcept { 74 | return luisa::make_unique(pipeline, cb, this); 75 | } 76 | 77 | }// namespace luisa::render 78 | 79 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::NormalVisualizer) 80 | -------------------------------------------------------------------------------- /src/lights/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(luisa-render-lights INTERFACE) 2 | luisa_render_add_plugin(null CATEGORY light SOURCES null.cpp) 3 | luisa_render_add_plugin(diffuse CATEGORY light SOURCES diffuse.cpp) 4 | -------------------------------------------------------------------------------- /src/lights/null.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/12. 3 | // 4 | 5 | #include 6 | 7 | namespace luisa::render { 8 | 9 | struct NullLight final : public Light { 10 | NullLight(Scene *scene, const SceneNodeDesc *desc) noexcept : Light{scene, desc} {} 11 | [[nodiscard]] bool is_null() const noexcept override { return true; } 12 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 13 | [[nodiscard]] luisa::unique_ptr build(Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept override { 14 | return nullptr; 15 | } 16 | }; 17 | 18 | }// namespace luisa::render 19 | 20 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::NullLight) 21 | -------------------------------------------------------------------------------- /src/lightsamplers/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(luisa-render-lightsamplers INTERFACE) 2 | luisa_render_add_plugin(uniform CATEGORY lightsampler SOURCES uniform.cpp) 3 | -------------------------------------------------------------------------------- /src/media/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(luisa-render-media INTERFACE) 2 | luisa_render_add_plugin(null CATEGORY medium SOURCES null.cpp) 3 | luisa_render_add_plugin(vacuum CATEGORY medium SOURCES vacuum.cpp) 4 | luisa_render_add_plugin(homogeneous CATEGORY medium SOURCES homogeneous.cpp) 5 | -------------------------------------------------------------------------------- /src/media/null.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by ChenXin on 2023/2/13. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | namespace luisa::render { 9 | 10 | using compute::Ray; 11 | 12 | class NullMedium : public Medium { 13 | 14 | protected: 15 | [[nodiscard]] luisa::unique_ptr _build( 16 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept override { 17 | return nullptr; 18 | } 19 | 20 | public: 21 | NullMedium(Scene *scene, const SceneNodeDesc *desc) noexcept 22 | : Medium{scene, desc} { 23 | _priority = 0u; 24 | } 25 | [[nodiscard]] bool is_null() const noexcept override { return true; } 26 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 27 | 28 | }; 29 | 30 | }// namespace luisa::render 31 | 32 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::NullMedium) -------------------------------------------------------------------------------- /src/media/vacuum.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by ChenXin on 2023/3/8. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | namespace luisa::render { 9 | 10 | using compute::Ray; 11 | 12 | class VacuumMedium : public Medium { 13 | 14 | public: 15 | class VacuumMajorantIterator : public RayMajorantIterator { 16 | private: 17 | uint _dim; 18 | 19 | public: 20 | explicit VacuumMajorantIterator(uint dim) noexcept : _dim(dim) {} 21 | [[nodiscard]] RayMajorantSegment next() noexcept override { 22 | return RayMajorantSegment::one(_dim); 23 | } 24 | }; 25 | 26 | class VacuumMediumInstance; 27 | 28 | class VacuumMediumClosure : public Medium::Closure { 29 | public: 30 | [[nodiscard]] Medium::Sample sample(Expr t_max, PCG32 &rng) const noexcept override { 31 | return Medium::Sample::zero(swl().dimension()); 32 | } 33 | [[nodiscard]] Evaluation transmittance(Expr t, PCG32 &rng) const noexcept override { 34 | return Medium::Evaluation::zero(swl().dimension()); 35 | } 36 | [[nodiscard]] unique_ptr sample_iterator(Expr t_max) const noexcept override { 37 | return luisa::make_unique(swl().dimension()); 38 | } 39 | 40 | public: 41 | VacuumMediumClosure( 42 | const VacuumMediumInstance *instance, Expr ray, 43 | const SampledWavelengths &swl, Expr time) noexcept 44 | : Medium::Closure{instance, ray, swl, time, 1.0f, 45 | SampledSpectrum{swl.dimension(), 0.f}, SampledSpectrum{swl.dimension(), 0.f}, 46 | SampledSpectrum{swl.dimension(), 0.f}, nullptr} {} 47 | }; 48 | 49 | class VacuumMediumInstance : public Medium::Instance { 50 | 51 | private: 52 | friend class VacuumMedium; 53 | 54 | public: 55 | VacuumMediumInstance(const Pipeline &pipeline, const Medium *medium) noexcept 56 | : Medium::Instance(pipeline, medium) {} 57 | [[nodiscard]] luisa::unique_ptr closure( 58 | Expr ray, const SampledWavelengths &swl, Expr time) const noexcept override { 59 | return luisa::make_unique(this, ray, swl, time); 60 | } 61 | }; 62 | 63 | protected: 64 | [[nodiscard]] luisa::unique_ptr _build( 65 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept override { 66 | return luisa::make_unique(pipeline, this); 67 | } 68 | 69 | public: 70 | VacuumMedium(Scene *scene, const SceneNodeDesc *desc) noexcept 71 | : Medium{scene, desc} { 72 | _priority = VACUUM_PRIORITY; 73 | } 74 | [[nodiscard]] bool is_vacuum() const noexcept override { return true; } 75 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 76 | }; 77 | 78 | }// namespace luisa::render 79 | 80 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::VacuumMedium) -------------------------------------------------------------------------------- /src/phasefunctions/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(luisa-render-phasefunctions INTERFACE) 2 | luisa_render_add_plugin(henyeygreenstein CATEGORY phasefunction SOURCES henyey_greenstein.cpp) 3 | -------------------------------------------------------------------------------- /src/phasefunctions/henyey_greenstein.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by ChenXin on 2023/2/14. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace luisa::render { 10 | 11 | // https://pbr-book.org/3ed-2018/Volume_Scattering/Phase_Functions#PhaseHG 12 | class HenyeyGreenstein : public PhaseFunction { 13 | private: 14 | float _g; 15 | 16 | public: 17 | class HenyeyGreensteinInstance : public PhaseFunction::Instance { 18 | protected: 19 | friend class HenyeyGreenstein; 20 | 21 | public: 22 | [[nodiscard]] Float p(Expr wo, Expr wi) const override { 23 | auto g = node()->_g; 24 | auto cosTheta = dot(wo, wi); 25 | auto denom = 1.f + sqr(g) + 2.f * g * cosTheta; 26 | return inv_pi * 0.25f * (1.f - sqr(g)) / (denom * sqrt(max(0.f, denom))); 27 | } 28 | [[nodiscard]] PhaseFunctionSample sample_p(Expr wo, Expr u) const override { 29 | auto g = node()->_g; 30 | // sample cosTheta 31 | auto cosTheta = ite( 32 | std::abs(g) < 1e-3f, 33 | 1.f - 2.f * u.x, 34 | // 1.f + g * g - sqr((1.f - g * g) / (1 - g + 2 * g * u.x)) / (2.f * g) 35 | -1.f / (2.f * g) * (1.f + sqr(g) - sqr((1.f - sqr(g)) / (1.f + g - 2.f * g * u.x)))); 36 | 37 | // compute direction 38 | auto sinTheta = sqrt(max(0.f, 1.f - sqr(cosTheta))); 39 | auto phi = 2.f * pi * u.y; 40 | 41 | auto wi = make_float3(sinTheta * cos(phi), cosTheta, sinTheta * sin(phi)); 42 | auto p_value = p(wo, wi); 43 | return PhaseFunctionSample{ 44 | .p = p_value, 45 | .wi = wi, 46 | .pdf = p_value, 47 | .valid = def(true)}; 48 | } 49 | [[nodiscard]] Float pdf(Expr wo, Expr wi) const override { 50 | return p(wo, wi); 51 | } 52 | 53 | public: 54 | explicit HenyeyGreensteinInstance( 55 | Pipeline &pipeline, const HenyeyGreenstein *phase_function) noexcept 56 | : PhaseFunction::Instance{pipeline, phase_function} {} 57 | }; 58 | 59 | protected: 60 | [[nodiscard]] luisa::unique_ptr _build( 61 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept override { 62 | return make_unique(pipeline, this); 63 | } 64 | 65 | public: 66 | HenyeyGreenstein(Scene *scene, const SceneNodeDesc *desc) noexcept 67 | : PhaseFunction{scene, desc}, 68 | _g{clamp(desc->property_float_or_default("g", 0.f), -1.f, 1.f)} {} 69 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 70 | }; 71 | 72 | }// namespace luisa::render 73 | 74 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::HenyeyGreenstein) -------------------------------------------------------------------------------- /src/samplers/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(luisa-render-samplers INTERFACE) 2 | luisa_render_add_plugin(independent CATEGORY sampler SOURCES independent.cpp) 3 | luisa_render_add_plugin(paddedsobol CATEGORY sampler SOURCES padded_sobol.cpp) 4 | luisa_render_add_plugin(sobol CATEGORY sampler SOURCES sobol.cpp) 5 | luisa_render_add_plugin(zsobol CATEGORY sampler SOURCES zsobol.cpp) 6 | luisa_render_add_plugin(pmj02bn CATEGORY sampler SOURCES pmj02bn.cpp) 7 | luisa_render_add_plugin(tileshared CATEGORY sampler SOURCES tile_shared.cpp) 8 | -------------------------------------------------------------------------------- /src/samplers/independent.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike on 2022/1/7. 3 | // 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace luisa::render { 12 | 13 | using namespace luisa::compute; 14 | 15 | class IndependentSampler; 16 | 17 | class IndependentSamplerInstance final : public Sampler::Instance { 18 | 19 | private: 20 | Buffer _states; 21 | luisa::optional> _state; 22 | 23 | public: 24 | IndependentSamplerInstance(const Pipeline &pipeline, const IndependentSampler *sampler) noexcept; 25 | void reset(CommandBuffer &command_buffer, uint2 resolution, uint state_count, uint spp) noexcept override; 26 | void start(Expr pixel, Expr sample_index) noexcept override; 27 | void save_state(Expr state_id) noexcept override; 28 | void load_state(Expr state_id) noexcept override; 29 | Float generate_1d() noexcept override; 30 | Float2 generate_2d() noexcept override; 31 | }; 32 | 33 | class IndependentSampler final : public Sampler { 34 | 35 | public: 36 | IndependentSampler(Scene *scene, const SceneNodeDesc *desc) noexcept 37 | : Sampler{scene, desc} {} 38 | [[nodiscard]] luisa::unique_ptr build( 39 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept override { 40 | return luisa::make_unique(pipeline, this); 41 | } 42 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 43 | }; 44 | 45 | IndependentSamplerInstance::IndependentSamplerInstance( 46 | const Pipeline &pipeline, const IndependentSampler *sampler) noexcept 47 | : Sampler::Instance{pipeline, sampler} {} 48 | 49 | void IndependentSamplerInstance::reset( 50 | CommandBuffer &command_buffer, uint2, uint state_count, uint) noexcept { 51 | if (!_states || state_count > _states.size()) { 52 | _states = pipeline().device().create_buffer( 53 | next_pow2(state_count)); 54 | } 55 | } 56 | 57 | void IndependentSamplerInstance::start(Expr pixel, Expr index) noexcept { 58 | _state.emplace(xxhash32(make_uint4(pixel, node()->seed(), index))); 59 | } 60 | 61 | void IndependentSamplerInstance::save_state(Expr state_id) noexcept { 62 | _states->write(state_id, *_state); 63 | } 64 | 65 | void IndependentSamplerInstance::load_state(Expr state_id) noexcept { 66 | _state.emplace(_states->read(state_id)); 67 | } 68 | 69 | Float IndependentSamplerInstance::generate_1d() noexcept { 70 | auto u = def(0.f); 71 | $outline { u = lcg(*_state); }; 72 | return u; 73 | } 74 | 75 | Float2 IndependentSamplerInstance::generate_2d() noexcept { 76 | auto u = def(make_float2()); 77 | $outline { 78 | u.x = generate_1d(); 79 | u.y = generate_1d(); 80 | }; 81 | return u; 82 | } 83 | 84 | }// namespace luisa::render 85 | 86 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::IndependentSampler) 87 | -------------------------------------------------------------------------------- /src/samplers/tile_shared.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/9/20. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace luisa::render { 11 | 12 | class TileSharedSampler final : public Sampler { 13 | 14 | private: 15 | Sampler *_base; 16 | uint2 _tile_size; 17 | bool _jitter; 18 | 19 | public: 20 | TileSharedSampler(Scene *scene, const SceneNodeDesc *desc) noexcept 21 | : Sampler{scene, desc}, 22 | _base{scene->load_sampler(desc->property_node("base"))}, 23 | _tile_size{desc->property_uint2_or_default( 24 | "tile_size", lazy_construct([desc] { 25 | auto s = desc->property_uint_or_default("tile_size", 16u); 26 | return make_uint2(s); 27 | }))}, 28 | _jitter{desc->property_bool_or_default("jitter", false)} {} 29 | [[nodiscard]] auto tile_size() const noexcept { return _tile_size; } 30 | [[nodiscard]] auto jitter() const noexcept { return _jitter; } 31 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 32 | [[nodiscard]] luisa::unique_ptr build(Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept override; 33 | }; 34 | 35 | class TileSharedSamplerInstance final : public Sampler::Instance { 36 | 37 | private: 38 | luisa::unique_ptr _base; 39 | uint2 _tile_size; 40 | uint2 _resolution; 41 | 42 | public: 43 | TileSharedSamplerInstance(const Pipeline &pipeline, const TileSharedSampler *sampler, 44 | luisa::unique_ptr base) noexcept 45 | : Sampler::Instance{pipeline, sampler}, _base{std::move(base)} {} 46 | void reset(CommandBuffer &command_buffer, uint2 resolution, 47 | uint state_count, uint spp) noexcept override { 48 | _tile_size = luisa::min(resolution, node()->tile_size()); 49 | _resolution = resolution; 50 | auto tile_count = (resolution + _tile_size - 1u) / _tile_size; 51 | _base->reset(command_buffer, tile_count, state_count, spp); 52 | } 53 | void start(Expr pixel, Expr sample_index) noexcept override { 54 | auto p = def(pixel); 55 | if (node()->jitter()) { 56 | auto offset = xxhash32(sample_index); 57 | auto o = make_float2(make_uint2(offset >> 16u, offset & 0xffffu)) * 0x1p-16f; 58 | p += make_uint2(o * make_float2(_resolution)) % _resolution; 59 | } 60 | auto tile = p / _tile_size; 61 | _base->start(tile, sample_index); 62 | } 63 | void save_state(Expr state_id) noexcept override { 64 | _base->save_state(state_id); 65 | } 66 | void load_state(Expr state_id) noexcept override { 67 | _base->load_state(state_id); 68 | } 69 | [[nodiscard]] Float generate_1d() noexcept override { 70 | return _base->generate_1d(); 71 | } 72 | [[nodiscard]] Float2 generate_2d() noexcept override { 73 | return _base->generate_2d(); 74 | } 75 | [[nodiscard]] Float2 generate_pixel_2d() noexcept override { 76 | return _base->generate_pixel_2d(); 77 | } 78 | }; 79 | 80 | luisa::unique_ptr TileSharedSampler::build( 81 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept { 82 | auto base = _base->build(pipeline, command_buffer); 83 | return luisa::make_unique( 84 | pipeline, this, std::move(base)); 85 | } 86 | 87 | }// namespace luisa::render 88 | 89 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::TileSharedSampler) 90 | -------------------------------------------------------------------------------- /src/sdl/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(LUISA_RENDER_SDL_SOURCES 2 | scene_desc.cpp scene_desc.h 3 | scene_node_desc.cpp scene_node_desc.h 4 | scene_node_tag.h 5 | scene_parser.cpp scene_parser.h scene_parser_json.cpp scene_parser_json.h scene_node_tag.cpp) 6 | 7 | add_library(luisa-render-sdl SHARED ${LUISA_RENDER_SDL_SOURCES}) 8 | target_link_libraries(luisa-render-sdl PUBLIC 9 | luisa::compute 10 | luisa-render-include 11 | luisa-render-ext 12 | luisa-render-util) 13 | set_target_properties(luisa-render-sdl PROPERTIES 14 | WINDOWS_EXPORT_ALL_SYMBOLS ON 15 | UNITY_BUILD ${LUISA_RENDER_ENABLE_UNITY_BUILD}) 16 | 17 | install(TARGETS luisa-render-sdl 18 | LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR} 19 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) 20 | -------------------------------------------------------------------------------- /src/sdl/scene_desc.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike on 2021/12/13. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | namespace luisa::render { 9 | 10 | const SceneNodeDesc *SceneDesc::node(luisa::string_view identifier) const noexcept { 11 | if (auto iter = _global_nodes.find(identifier); 12 | iter != _global_nodes.cend()) { 13 | return iter->get(); 14 | } 15 | LUISA_ERROR_WITH_LOCATION( 16 | "Global node '{}' not found " 17 | "in scene description.", 18 | identifier); 19 | } 20 | 21 | const SceneNodeDesc *SceneDesc::reference(luisa::string_view identifier) noexcept { 22 | if (identifier == root_node_identifier) [[unlikely]] { 23 | LUISA_ERROR_WITH_LOCATION( 24 | "Invalid reference to root node."); 25 | } 26 | std::scoped_lock lock{_mutex}; 27 | auto [iter, _] = _global_nodes.emplace( 28 | lazy_construct([identifier] { 29 | return luisa::make_unique( 30 | luisa::string{identifier}, 31 | SceneNodeTag::DECLARATION); 32 | })); 33 | return iter->get(); 34 | } 35 | 36 | SceneNodeDesc *SceneDesc::define( 37 | luisa::string_view identifier, SceneNodeTag tag, luisa::string_view impl_type, 38 | SceneNodeDesc::SourceLocation location, const SceneNodeDesc *base) noexcept { 39 | 40 | if (identifier == root_node_identifier || 41 | tag == SceneNodeTag::ROOT) [[unlikely]] { 42 | LUISA_ERROR( 43 | "Defining root node as a normal " 44 | "global node is not allowed. " 45 | "Please use SceneNodeDesc::define_root(). [{}]", 46 | location.string()); 47 | } 48 | if (tag == SceneNodeTag::INTERNAL || 49 | tag == SceneNodeTag::DECLARATION) [[unlikely]] { 50 | LUISA_ERROR( 51 | "Defining internal or declaration node " 52 | "as a global node is not allowed. [{}]", 53 | location.string()); 54 | } 55 | 56 | std::scoped_lock lock{_mutex}; 57 | auto [iter, _] = _global_nodes.emplace( 58 | lazy_construct([identifier, tag] { 59 | return luisa::make_unique( 60 | luisa::string{identifier}, tag); 61 | })); 62 | auto node = iter->get(); 63 | if (node->is_defined()) [[unlikely]] { 64 | LUISA_ERROR( 65 | "Redefinition of node '{}' ({}::{}) " 66 | "in scene description. [{}]", 67 | node->identifier(), 68 | scene_node_tag_description(node->tag()), 69 | node->impl_type(), 70 | location.string()); 71 | } 72 | node->define(tag, impl_type, location, base); 73 | return node; 74 | } 75 | 76 | SceneNodeDesc *SceneDesc::define_root(SceneNodeDesc::SourceLocation location) noexcept { 77 | std::scoped_lock lock{_mutex}; 78 | if (_root.is_defined()) [[unlikely]] { 79 | LUISA_ERROR( 80 | "Redefinition of root node " 81 | "in scene description. [{}]", 82 | location.string()); 83 | } 84 | _root.define(SceneNodeTag::ROOT, root_node_identifier, location); 85 | return &_root; 86 | } 87 | 88 | const std::filesystem::path *SceneDesc::register_path(std::filesystem::path path) noexcept { 89 | auto p = luisa::make_unique(std::move(path)); 90 | std::scoped_lock lock{_mutex}; 91 | return _paths.emplace_back(std::move(p)).get(); 92 | } 93 | 94 | }// namespace luisa::render 95 | -------------------------------------------------------------------------------- /src/sdl/scene_desc.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike on 2021/12/13. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | namespace luisa::render { 11 | 12 | class SceneDesc { 13 | 14 | private: 15 | [[nodiscard]] static auto _node_identifier(const luisa::unique_ptr &node) noexcept { return node->identifier(); } 16 | [[nodiscard]] static auto _node_identifier(const SceneNodeDesc *node) noexcept { return node->identifier(); } 17 | [[nodiscard]] static auto _node_identifier(luisa::string_view s) noexcept { return s; } 18 | 19 | public: 20 | struct NodeHash { 21 | using is_transparent = void; 22 | template 23 | [[nodiscard]] auto operator()(T &&node) const noexcept -> uint64_t { 24 | return hash_value(_node_identifier(std::forward(node))); 25 | } 26 | }; 27 | struct NodeEqual { 28 | using is_transparent = void; 29 | template 30 | [[nodiscard]] auto operator()(Lhs &&lhs, Rhs &&rhs) const noexcept -> bool { 31 | return _node_identifier(std::forward(lhs)) == _node_identifier(std::forward(rhs)); 32 | } 33 | }; 34 | static constexpr luisa::string_view root_node_identifier = "render"; 35 | 36 | private: 37 | luisa::unordered_set, NodeHash, NodeEqual> _global_nodes; 38 | luisa::vector> _paths; 39 | SceneNodeDesc _root; 40 | std::recursive_mutex _mutex; 41 | 42 | public: 43 | SceneDesc() noexcept : _root{luisa::string{root_node_identifier}, SceneNodeTag::ROOT} {} 44 | [[nodiscard]] auto &nodes() const noexcept { return _global_nodes; } 45 | [[nodiscard]] const SceneNodeDesc *node(luisa::string_view identifier) const noexcept; 46 | [[nodiscard]] auto root() const noexcept { return &_root; } 47 | [[nodiscard]] const SceneNodeDesc *reference(luisa::string_view identifier) noexcept; 48 | [[nodiscard]] SceneNodeDesc *define( 49 | luisa::string_view identifier, SceneNodeTag tag, luisa::string_view impl_type, 50 | SceneNodeDesc::SourceLocation location = {}, const SceneNodeDesc *base = nullptr) noexcept; 51 | [[nodiscard]] SceneNodeDesc *define_root(SceneNodeDesc::SourceLocation location = {}) noexcept; 52 | const std::filesystem::path *register_path(std::filesystem::path path) noexcept; 53 | }; 54 | 55 | }// namespace luisa::render 56 | -------------------------------------------------------------------------------- /src/sdl/scene_node_desc.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike on 2021/12/13. 3 | // 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace luisa::render { 11 | 12 | void SceneNodeDesc::add_property(luisa::string_view name, SceneNodeDesc::value_list value) noexcept { 13 | if (!_properties.emplace(luisa::string{name}, std::move(value)).second) { 14 | LUISA_ERROR( 15 | "Redefinition of property '{}' in " 16 | "scene description node '{}'. [{}]", 17 | name, _identifier, _location.string()); 18 | } 19 | } 20 | 21 | void SceneNodeDesc::define(SceneNodeTag tag, luisa::string_view t, 22 | SceneNodeDesc::SourceLocation l, const SceneNodeDesc *base) noexcept { 23 | _tag = tag; 24 | _location = l; 25 | _impl_type = t; 26 | _base = base; 27 | for (auto &c : _impl_type) { 28 | c = static_cast(tolower(c)); 29 | } 30 | } 31 | 32 | SceneNodeDesc *SceneNodeDesc::define_internal( 33 | luisa::string_view impl_type, SourceLocation location, const SceneNodeDesc *base) noexcept { 34 | auto unique_node = luisa::make_unique( 35 | luisa::format("{}.$internal{}", _identifier, _internal_nodes.size()), 36 | SceneNodeTag::INTERNAL); 37 | auto node = _internal_nodes.emplace_back(std::move(unique_node)).get(); 38 | node->define(SceneNodeTag::INTERNAL, impl_type, location, base); 39 | return node; 40 | } 41 | 42 | bool SceneNodeDesc::has_property(luisa::string_view prop) const noexcept { 43 | return _properties.find(prop) != _properties.cend() || 44 | (_base != nullptr && _base->has_property(prop)); 45 | } 46 | 47 | const SceneNodeDesc *SceneNodeDesc::shared_default(SceneNodeTag tag, luisa::string impl) noexcept { 48 | static luisa::unordered_map> descriptions; 49 | static std::mutex mutex; 50 | static thread_local const auto seed = hash_value("__scene_node_tag_and_impl_type_hash"); 51 | for (auto &c : impl) { c = static_cast(tolower(c)); } 52 | auto hash = hash_value(impl, hash_value(to_underlying(tag), seed)); 53 | std::scoped_lock lock{mutex}; 54 | if (auto iter = descriptions.find(hash); 55 | iter != descriptions.cend()) { return iter->second.get(); } 56 | auto identifier = luisa::format( 57 | "__shared_default_{}_{}", 58 | scene_node_tag_description(tag), impl); 59 | for (auto &c : identifier) { c = static_cast(tolower(c)); } 60 | auto desc = luisa::make_unique( 61 | std::move(identifier), tag); 62 | desc->define(tag, impl, {}); 63 | return descriptions.emplace(hash, std::move(desc)).first->second.get(); 64 | } 65 | 66 | }// namespace luisa::render 67 | -------------------------------------------------------------------------------- /src/sdl/scene_node_tag.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/9/27. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | namespace luisa::render { 9 | 10 | SceneNodeTag parse_scene_node_tag(luisa::string_view tag_desc) noexcept { 11 | luisa::string tag{tag_desc}; 12 | for (auto &c : tag) { c = static_cast(std::tolower(c)); } 13 | using namespace std::string_view_literals; 14 | static constexpr auto desc_to_tag_count = 30u; 15 | static const luisa::fixed_map desc_to_tag{ 16 | {"camera"sv, SceneNodeTag::CAMERA}, 17 | {"cam"sv, SceneNodeTag::CAMERA}, 18 | {"shape"sv, SceneNodeTag::SHAPE}, 19 | {"object"sv, SceneNodeTag::SHAPE}, 20 | {"obj"sv, SceneNodeTag::SHAPE}, 21 | {"surface"sv, SceneNodeTag::SURFACE}, 22 | {"surf"sv, SceneNodeTag::SURFACE}, 23 | {"lightsource"sv, SceneNodeTag::LIGHT}, 24 | {"light"sv, SceneNodeTag::LIGHT}, 25 | {"illuminant"sv, SceneNodeTag::LIGHT}, 26 | {"illum"sv, SceneNodeTag::LIGHT}, 27 | {"transform"sv, SceneNodeTag::TRANSFORM}, 28 | {"xform"sv, SceneNodeTag::TRANSFORM}, 29 | {"film"sv, SceneNodeTag::FILM}, 30 | {"filter"sv, SceneNodeTag::FILTER}, 31 | {"sampler"sv, SceneNodeTag::SAMPLER}, 32 | {"integrator"sv, SceneNodeTag::INTEGRATOR}, 33 | {"lightsampler"sv, SceneNodeTag::LIGHT_SAMPLER}, 34 | {"environment"sv, SceneNodeTag::ENVIRONMENT}, 35 | {"env"sv, SceneNodeTag::ENVIRONMENT}, 36 | {"texture"sv, SceneNodeTag::TEXTURE}, 37 | {"tex"sv, SceneNodeTag::TEXTURE}, 38 | {"texturemapping"sv, SceneNodeTag::TEXTURE_MAPPING}, 39 | {"texmapping"sv, SceneNodeTag::TEXTURE_MAPPING}, 40 | {"spectrum"sv, SceneNodeTag::SPECTRUM}, 41 | {"spec"sv, SceneNodeTag::SPECTRUM}, 42 | {"generic"sv, SceneNodeTag::DECLARATION}, 43 | {"template"sv, SceneNodeTag::DECLARATION}, 44 | {"medium"sv, SceneNodeTag::MEDIUM}, 45 | {"phasefunction"sv, SceneNodeTag::PHASE_FUNCTION}, 46 | }; 47 | if (auto iter = desc_to_tag.find(tag); iter != desc_to_tag.end()) { 48 | return iter->second; 49 | } 50 | return SceneNodeTag::ROOT; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/sdl/scene_node_tag.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike on 2021/12/20. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace luisa::render { 10 | 11 | enum struct SceneNodeTag : uint32_t { 12 | ROOT, 13 | INTERNAL, 14 | DECLARATION, 15 | CAMERA, 16 | SHAPE, 17 | SURFACE, 18 | LIGHT, 19 | TRANSFORM, 20 | FILM, 21 | FILTER, 22 | SAMPLER, 23 | INTEGRATOR, 24 | LIGHT_SAMPLER, 25 | ENVIRONMENT, 26 | TEXTURE, 27 | TEXTURE_MAPPING, 28 | SPECTRUM, 29 | MEDIUM, 30 | PHASE_FUNCTION, 31 | }; 32 | 33 | constexpr std::string_view scene_node_tag_description(SceneNodeTag tag) noexcept { 34 | using namespace std::string_view_literals; 35 | switch (tag) { 36 | case SceneNodeTag::ROOT: return "__root__"sv; 37 | case SceneNodeTag::INTERNAL: return "__internal__"sv; 38 | case SceneNodeTag::DECLARATION: return "__declaration__"sv; 39 | case SceneNodeTag::CAMERA: return "Camera"sv; 40 | case SceneNodeTag::SHAPE: return "Shape"sv; 41 | case SceneNodeTag::SURFACE: return "Surface"sv; 42 | case SceneNodeTag::LIGHT: return "Light"sv; 43 | case SceneNodeTag::TRANSFORM: return "Transform"sv; 44 | case SceneNodeTag::FILM: return "Film"sv; 45 | case SceneNodeTag::FILTER: return "Filter"sv; 46 | case SceneNodeTag::SAMPLER: return "Sampler"sv; 47 | case SceneNodeTag::INTEGRATOR: return "Integrator"sv; 48 | case SceneNodeTag::LIGHT_SAMPLER: return "LightSampler"sv; 49 | case SceneNodeTag::ENVIRONMENT: return "Environment"sv; 50 | case SceneNodeTag::TEXTURE: return "Texture"sv; 51 | case SceneNodeTag::TEXTURE_MAPPING: return "TextureMapping"sv; 52 | case SceneNodeTag::SPECTRUM: return "Spectrum"sv; 53 | case SceneNodeTag::MEDIUM: return "Medium"sv; 54 | case SceneNodeTag::PHASE_FUNCTION: return "PhaseFunction"sv; 55 | default: break; 56 | } 57 | return "__invalid__"sv; 58 | } 59 | 60 | [[nodiscard]] SceneNodeTag parse_scene_node_tag(luisa::string_view tag_desc) noexcept; 61 | 62 | }// namespace luisa::render 63 | -------------------------------------------------------------------------------- /src/sdl/scene_parser_json.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/9/26. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace luisa::render { 16 | 17 | using nlohmann::json; 18 | 19 | class SceneDesc; 20 | class SceneNodeDesc; 21 | 22 | class SceneParserJSON { 23 | 24 | public: 25 | using MacroMap = luisa::unordered_map; 28 | 29 | private: 30 | SceneDesc &_desc; 31 | const MacroMap &_cli_macros; 32 | SceneNodeDesc::SourceLocation _location; 33 | 34 | private: 35 | void _parse_root(const json &root) const noexcept; 36 | void _parse_import(const json &node) const noexcept; 37 | void _parse_node(SceneNodeDesc &desc, const json &node) const noexcept; 38 | const SceneNodeDesc *_reference(luisa::string_view name) const noexcept; 39 | 40 | public: 41 | SceneParserJSON(SceneDesc &desc, const std::filesystem::path &path, 42 | const MacroMap &cli_macros) noexcept; 43 | void parse() const noexcept; 44 | }; 45 | 46 | }// namespace luisa::render 47 | -------------------------------------------------------------------------------- /src/shapes/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(luisa-render-shapes INTERFACE) 2 | luisa_render_add_plugin(mesh CATEGORY shape SOURCES mesh.cpp) 3 | luisa_render_add_plugin(instance CATEGORY shape SOURCES instance.cpp) 4 | luisa_render_add_plugin(group CATEGORY shape SOURCES group.cpp) 5 | luisa_render_add_plugin(inlinemesh CATEGORY shape SOURCES inline_mesh.cpp) 6 | luisa_render_add_plugin(sphere CATEGORY shape SOURCES sphere.cpp) 7 | luisa_render_add_plugin(loopsubdiv CATEGORY shape SOURCES loop_subdiv.cpp) 8 | -------------------------------------------------------------------------------- /src/shapes/group.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/9. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace luisa::render { 10 | 11 | class ShapeGroup : public Shape { 12 | 13 | private: 14 | luisa::vector _children; 15 | 16 | public: 17 | ShapeGroup(Scene *scene, const SceneNodeDesc *desc) noexcept 18 | : Shape{scene, desc} { 19 | auto shapes = desc->property_node_list("shapes"); 20 | _children.reserve(shapes.size()); 21 | for (auto shape : shapes) { 22 | _children.emplace_back(scene->load_shape(shape)); 23 | } 24 | } 25 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 26 | [[nodiscard]] span children() const noexcept override { return _children; } 27 | }; 28 | 29 | using GroupWrapper = VisibilityShapeWrapper; 30 | 31 | }// namespace luisa::render 32 | 33 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::GroupWrapper) 34 | -------------------------------------------------------------------------------- /src/shapes/inline_mesh.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike on 2022/2/18. 3 | // 4 | 5 | #include 6 | 7 | namespace luisa::render { 8 | 9 | class InlineMesh : public Shape { 10 | 11 | private: 12 | luisa::vector _vertices; 13 | luisa::vector _triangles; 14 | luisa::vector _uvs; 15 | uint _properties{}; 16 | 17 | public: 18 | InlineMesh(Scene *scene, const SceneNodeDesc *desc) noexcept 19 | : Shape{scene, desc} { 20 | 21 | auto triangles = desc->property_uint_list("indices"); 22 | auto positions = desc->property_float_list("positions"); 23 | auto normals = desc->property_float_list_or_default("normals"); 24 | auto uvs = desc->property_float_list_or_default("uvs"); 25 | 26 | if (triangles.size() % 3u != 0u || 27 | positions.size() % 3u != 0u || 28 | normals.size() % 3u != 0u || 29 | uvs.size() % 2u != 0u || 30 | (!normals.empty() && normals.size() != positions.size()) || 31 | (!uvs.empty() && uvs.size() / 2u != positions.size() / 3u)) [[unlikely]] { 32 | LUISA_ERROR_WITH_LOCATION("Invalid vertex or triangle count."); 33 | } 34 | _properties = (!uvs.empty() ? Shape::property_flag_has_vertex_uv : 0u) | 35 | (!normals.empty() ? Shape::property_flag_has_vertex_normal : 0u); 36 | 37 | auto triangle_count = triangles.size() / 3u; 38 | auto vertex_count = positions.size() / 3u; 39 | _triangles.resize(triangle_count); 40 | for (auto i = 0u; i < triangle_count; i++) { 41 | auto t0 = triangles[i * 3u + 0u]; 42 | auto t1 = triangles[i * 3u + 1u]; 43 | auto t2 = triangles[i * 3u + 2u]; 44 | assert(t0 < vertex_count && t1 < vertex_count && t2 < vertex_count); 45 | _triangles[i] = Triangle{t0, t1, t2}; 46 | } 47 | _vertices.resize(vertex_count); 48 | for (auto i = 0u; i < vertex_count; i++) { 49 | auto p0 = positions[i * 3u + 0u]; 50 | auto p1 = positions[i * 3u + 1u]; 51 | auto p2 = positions[i * 3u + 2u]; 52 | auto p = make_float3(p0, p1, p2); 53 | auto n = normals.empty() ? 54 | make_float3(0.f, 0.f, 1.f) : 55 | make_float3(normals[i * 3u + 0u], normals[i * 3u + 1u], normals[i * 3u + 2u]); 56 | auto uv = uvs.empty() ? make_float2(0.f) : make_float2(uvs[i * 2u + 0u], uvs[i * 2u + 1u]); 57 | _vertices[i] = Vertex::encode(p, n, uv); 58 | } 59 | } 60 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 61 | [[nodiscard]] bool is_mesh() const noexcept override { return true; } 62 | [[nodiscard]] MeshView mesh() const noexcept override { return {_vertices, _triangles}; } 63 | [[nodiscard]] bool deformable() const noexcept override { return false; } 64 | [[nodiscard]] uint vertex_properties() const noexcept override { return _properties; } 65 | }; 66 | 67 | using InlineMeshWrapper = 68 | VisibilityShapeWrapper< 69 | ShadowTerminatorShapeWrapper< 70 | IntersectionOffsetShapeWrapper>>; 71 | 72 | }// namespace luisa::render 73 | 74 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::InlineMeshWrapper) 75 | -------------------------------------------------------------------------------- /src/shapes/instance.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/9. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace luisa::render { 10 | 11 | class ShapeInstance : public Shape { 12 | 13 | private: 14 | const Shape *_shape; 15 | 16 | public: 17 | ShapeInstance(Scene *scene, const SceneNodeDesc *desc) noexcept 18 | : Shape{scene, desc}, _shape{scene->load_shape(desc->property_node("shape"))} {} 19 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 20 | [[nodiscard]] span children() const noexcept override { return {&_shape, 1u}; } 21 | }; 22 | 23 | using InstanceWrapper = VisibilityShapeWrapper; 24 | 25 | }// namespace luisa::render 26 | 27 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::InstanceWrapper) 28 | -------------------------------------------------------------------------------- /src/shapes/loop_subdiv.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/11/8. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace luisa::render { 12 | 13 | static constexpr auto max_loop_subdivision_level = 10u; 14 | 15 | // TODO: preserve uv mapping 16 | class LoopSubdiv : public Shape { 17 | 18 | private: 19 | const Shape *_mesh; 20 | std::shared_future, luisa::vector>> _geometry; 21 | 22 | public: 23 | LoopSubdiv(Scene *scene, const SceneNodeDesc *desc) noexcept 24 | : Shape{scene, desc}, 25 | _mesh{scene->load_shape(desc->property_node_or_default( 26 | "mesh", lazy_construct([desc] { 27 | return desc->property_node_or_default( 28 | "shape", lazy_construct([desc] { return desc->property_node("base"); })); 29 | })))} { 30 | LUISA_ASSERT(_mesh->is_mesh(), "LoopSubdiv only supports mesh shapes."); 31 | auto level = std::min(desc->property_uint_or_default("level", 1u), 32 | max_loop_subdivision_level); 33 | if (level == 0u) { 34 | LUISA_WARNING_WITH_LOCATION( 35 | "LoopSubdiv level is 0, which is equivalent to no subdivision."); 36 | } else { 37 | _geometry = global_thread_pool().async([level, mesh = _mesh] { 38 | auto m = mesh->mesh(); 39 | Clock clk; 40 | auto [vertices, triangles, _] = loop_subdivide(m.vertices, m.triangles, level); 41 | LUISA_INFO("LoopSubdiv (level = {}): subdivided {} vertices and {} " 42 | "triangles into {} vertices and {} triangles in {} ms.", 43 | level, m.vertices.size(), m.triangles.size(), 44 | vertices.size(), triangles.size(), clk.toc()); 45 | return std::make_pair(std::move(vertices), std::move(triangles)); 46 | }); 47 | } 48 | } 49 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 50 | [[nodiscard]] bool is_mesh() const noexcept override { return true; } 51 | [[nodiscard]] MeshView mesh() const noexcept override { 52 | return _geometry.valid() ? 53 | MeshView{_geometry.get().first, _geometry.get().second} : 54 | _mesh->mesh(); 55 | } 56 | [[nodiscard]] uint vertex_properties() const noexcept override { 57 | return _geometry.valid() ? 58 | Shape::property_flag_has_vertex_normal : 59 | _mesh->vertex_properties(); 60 | } 61 | [[nodiscard]] AccelOption build_option() const noexcept override { return _mesh->build_option(); } 62 | }; 63 | 64 | using LoopSubdivWrapper = 65 | VisibilityShapeWrapper< 66 | ShadowTerminatorShapeWrapper< 67 | IntersectionOffsetShapeWrapper>>; 68 | 69 | }// namespace luisa::render 70 | 71 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::LoopSubdivWrapper) 72 | -------------------------------------------------------------------------------- /src/spectra/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(luisa-render-spectra INTERFACE) 2 | luisa_render_add_plugin(srgb CATEGORY spectrum SOURCES srgb.cpp) 3 | luisa_render_add_plugin(hero CATEGORY spectrum SOURCES hero.cpp srgb2spec.cpp) 4 | -------------------------------------------------------------------------------- /src/surfaces/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(luisa-render-surfaces INTERFACE) 2 | luisa_render_add_plugin(null CATEGORY surface SOURCES null.cpp) 3 | luisa_render_add_plugin(matte CATEGORY surface SOURCES matte.cpp) 4 | luisa_render_add_plugin(glass CATEGORY surface SOURCES glass.cpp) 5 | luisa_render_add_plugin(metal CATEGORY surface SOURCES metal.cpp) 6 | luisa_render_add_plugin(mirror CATEGORY surface SOURCES mirror.cpp) 7 | luisa_render_add_plugin(disney CATEGORY surface SOURCES disney.cpp) 8 | luisa_render_add_plugin(mix CATEGORY surface SOURCES mix.cpp) 9 | luisa_render_add_plugin(layered CATEGORY surface SOURCES layered.cpp) 10 | luisa_render_add_plugin(plastic CATEGORY surface SOURCES plastic.cpp) 11 | luisa_render_add_plugin(substrate CATEGORY surface SOURCES plastic.cpp) # for compatibility 12 | -------------------------------------------------------------------------------- /src/surfaces/null.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/12. 3 | // 4 | 5 | #include 6 | 7 | namespace luisa::render { 8 | 9 | struct NullSurface final : public Surface { 10 | 11 | public: 12 | NullSurface(Scene *scene, const SceneNodeDesc *desc) noexcept : Surface{scene, desc} {} 13 | [[nodiscard]] bool is_null() const noexcept override { return true; } 14 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 15 | [[nodiscard]] uint properties() const noexcept override { return 0u; } 16 | 17 | private: 18 | [[nodiscard]] luisa::unique_ptr _build( 19 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept override { 20 | LUISA_ERROR_WITH_LOCATION("NullSurface cannot be instantiated."); 21 | } 22 | }; 23 | 24 | }// namespace luisa::render 25 | 26 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::NullSurface) 27 | -------------------------------------------------------------------------------- /src/tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(test_alias_method test_alias_method.cpp) 2 | target_link_libraries(test_alias_method PRIVATE luisa::render) 3 | 4 | add_executable(test_u64 test_u64.cpp) 5 | target_link_libraries(test_u64 PRIVATE luisa::render) 6 | 7 | # sky texture precomputation test 8 | add_executable(test_sky test_sky.cpp) 9 | target_link_libraries(test_sky PRIVATE luisa-render-texture-sky-precompute) 10 | 11 | add_executable(test_sphere test_sphere.cpp) 12 | target_link_libraries(test_sphere PRIVATE luisa::render) 13 | -------------------------------------------------------------------------------- /src/tests/test_alias_method.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/8/18. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace luisa; 10 | using namespace luisa::compute; 11 | using namespace luisa::render; 12 | 13 | template 14 | [[nodiscard]] auto sample_alias_table( 15 | const Table &table, uint n, float u_in) noexcept { 16 | using namespace luisa::compute; 17 | auto u = u_in * static_cast(n); 18 | auto i = std::clamp(static_cast(u), 0u, n - 1u); 19 | auto u_remapped = fract(u); 20 | auto entry = table[i]; 21 | return u_remapped < entry.prob ? i : entry.alias; 22 | } 23 | 24 | int main() { 25 | 26 | std::random_device random_device; 27 | std::mt19937 random{random_device()}; 28 | std::uniform_real_distribution dist{0.f, 1.f}; 29 | 30 | constexpr auto value_count = 128u; 31 | constexpr auto sample_count = 1024ull * 1024ull * 1024ull; 32 | 33 | luisa::vector values; 34 | values.reserve(value_count); 35 | 36 | std::ofstream file{"alias_data.json"}; 37 | file << "{\n"; 38 | 39 | for (auto i = 0u; i < value_count; ++i) { 40 | values.emplace_back(dist(random)); 41 | } 42 | auto [alias_table, pdf_table] = create_alias_table(values); 43 | 44 | file << " \"pdf\": [" << pdf_table[0]; 45 | for (auto p : luisa::span{pdf_table}.subspan(1u)) { 46 | file << ", " << p; 47 | } 48 | file << "],\n"; 49 | 50 | luisa::vector bins(value_count, 0u); 51 | for (auto i = 0ull; i < sample_count; i++) { 52 | auto u = dist(random); 53 | auto index = sample_alias_table(alias_table, value_count, u); 54 | bins[index]++; 55 | } 56 | file << " \"bins\": [" << bins[0]; 57 | for (auto b : luisa::span{bins}.subspan(1u)) { 58 | file << ", " << b; 59 | } 60 | file << "],\n"; 61 | 62 | auto freq = [sample_count](auto x) noexcept { 63 | return static_cast(static_cast(x) / 64 | static_cast(sample_count)); 65 | }; 66 | file << " \"frequencies\": [" << freq(bins[0]); 67 | for (auto b : luisa::span{bins}.subspan(1u)) { 68 | file << ", " << freq(b); 69 | } 70 | file << "],\n"; 71 | 72 | file << " \"error\": [" << freq(bins[0]) - pdf_table[0]; 73 | for (auto i = 1u; i < value_count; i++) { 74 | file << ", " << freq(bins[i]) - pdf_table[i]; 75 | } 76 | file << "],\n"; 77 | file << " \"relative_error\": [" << std::abs((freq(bins[0]) - pdf_table[0]) / pdf_table[0]); 78 | for (auto i = 1u; i < value_count; i++) { 79 | file << ", " << std::abs(freq(bins[i]) - pdf_table[i]) / pdf_table[i]; 80 | } 81 | file << "]\n"; 82 | file << "}\n"; 83 | } 84 | -------------------------------------------------------------------------------- /src/tests/test_sky.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/10/13. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace luisa; 12 | using namespace luisa::compute; 13 | using namespace luisa::render; 14 | 15 | int main() { 16 | NishitaSkyData data{.sun_elevation = radians(23.4f), 17 | .sun_angle = radians(.545f), 18 | .altitude = 670.f, 19 | .air_density = 1.748f, 20 | .dust_density = 7.f, 21 | .ozone_density = 2.783f}; 22 | static constexpr auto resolution = make_uint2(2048u); 23 | luisa::vector image(resolution.x * resolution.y); 24 | global_thread_pool().parallel(resolution.y / 16u, [&](uint32_t y) noexcept { 25 | SKY_nishita_skymodel_precompute_texture( 26 | data, image.data(), resolution, make_uint2(y * 16u, (y + 1u) * 16u)); 27 | }); 28 | auto sun = SKY_nishita_skymodel_precompute_sun(data); 29 | LUISA_INFO("Sun: ({}, {}, {}) -> ({}, {}, {})", 30 | sun.bottom.x, sun.bottom.y, sun.bottom.z, 31 | sun.top.x, sun.top.y, sun.top.z); 32 | global_thread_pool().synchronize(); 33 | save_image("sky_precompute_test.exr", 34 | reinterpret_cast(image.data()), 35 | resolution); 36 | } 37 | -------------------------------------------------------------------------------- /src/tests/test_sphere.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/11/8. 3 | // 4 | 5 | #include 6 | 7 | #define LUISA_RENDER_PLUGIN_NAME "sphere" 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace luisa; 13 | using namespace luisa::compute; 14 | using namespace luisa::render; 15 | 16 | void dump_obj(MeshView m, uint level) noexcept { 17 | auto path = format("sphere-{}.obj", level); 18 | std::ofstream out{path.c_str()}; 19 | auto dump_vertex = [&out](auto v, auto t) noexcept { 20 | if (std::same_as) { 21 | out << luisa::format("{} {} {}\n", t, v[0], v[1]); 22 | } else { 23 | out << luisa::format("{} {} {} {}\n", t, v[0], v[1], v[2]); 24 | } 25 | }; 26 | for (auto v : m.vertices) { dump_vertex(v.position(), "v"); } 27 | for (auto v : m.vertices) { dump_vertex(v.normal(), "vn"); } 28 | for (auto v : m.vertices) { dump_vertex(v.uv(), "vt"); } 29 | for (auto [a, b, c] : m.triangles) { 30 | out << luisa::format("f {}/{}/{} {}/{}/{} {}/{}/{}\n", 31 | a + 1u, a + 1u, a + 1u, 32 | b + 1u, b + 1u, b + 1u, 33 | c + 1u, c + 1u, c + 1u); 34 | } 35 | } 36 | 37 | int main() { 38 | static_cast(global_thread_pool()); 39 | for (auto i = 0u; i <= sphere_max_subdivision_level; i++) { 40 | Clock clk; 41 | auto future = SphereGeometry::create(i); 42 | auto m = future.get().mesh(); 43 | LUISA_INFO("Computed sphere at subdivision level {} " 44 | "with {} vertices and {} triangles in {} ms.", 45 | i, m.vertices.size(), m.triangles.size(), clk.toc()); 46 | dump_obj(m, i); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/texturemappings/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(luisa-render-texturemappings INTERFACE) 2 | luisa_render_add_plugin(uv CATEGORY texturemapping SOURCES uv.cpp) 3 | luisa_render_add_plugin(spherical CATEGORY texturemapping SOURCES spherical.cpp) 4 | -------------------------------------------------------------------------------- /src/texturemappings/spherical.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/3/11. 3 | // 4 | -------------------------------------------------------------------------------- /src/texturemappings/uv.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/3/11. 3 | // 4 | 5 | -------------------------------------------------------------------------------- /src/textures/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(luisa-render-textures INTERFACE) 2 | luisa_render_add_plugin(constant CATEGORY texture SOURCES constant.cpp) 3 | luisa_render_add_plugin(image CATEGORY texture SOURCES image.cpp) 4 | luisa_render_add_plugin(swizzle CATEGORY texture SOURCES swizzle.cpp) 5 | luisa_render_add_plugin(scale CATEGORY texture SOURCES scale.cpp) 6 | luisa_render_add_plugin(multiply CATEGORY texture SOURCES multiply.cpp) 7 | luisa_render_add_plugin(checkerboard CATEGORY texture SOURCES checkerboard.cpp) 8 | luisa_render_add_plugin(concat CATEGORY texture SOURCES concat.cpp) 9 | 10 | # sky texture precomputation 11 | add_library(luisa-render-texture-sky-precompute SHARED nishita_precompute.cpp sky_precompute.h) 12 | target_link_libraries(luisa-render-texture-sky-precompute PUBLIC luisa-render-util luisa-render-base) 13 | set_target_properties(luisa-render-texture-sky-precompute PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON) 14 | 15 | # Nishita sky texture 16 | luisa_render_add_plugin(nishitasky CATEGORY texture SOURCES nishita_sky.cpp) 17 | target_link_libraries(luisa-render-texture-nishitasky PRIVATE luisa-render-texture-sky-precompute) 18 | 19 | # bump to normal 20 | luisa_render_add_plugin(bumptonormal CATEGORY texture SOURCES bump2normal.cpp) 21 | -------------------------------------------------------------------------------- /src/textures/bump2normal.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by mike on 4/17/24. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace luisa::render { 12 | 13 | using namespace luisa::compute; 14 | 15 | class Bump2NormalTexture final : public Texture { 16 | 17 | private: 18 | Texture *_bump_texture; 19 | 20 | public: 21 | Bump2NormalTexture(Scene *scene, const SceneNodeDesc *desc) noexcept 22 | : Texture{scene, desc}, 23 | _bump_texture{scene->load_texture(desc->property_node("bump"))} { 24 | if (_bump_texture->channels() != 1u) { 25 | LUISA_WARNING_WITH_LOCATION("Bump image {} should only have 1 channel. {} found.", 26 | desc->identifier(), 27 | _bump_texture->channels()); 28 | } 29 | } 30 | [[nodiscard]] bool is_black() const noexcept override { return false; } 31 | [[nodiscard]] bool is_constant() const noexcept override { return false; } 32 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 33 | [[nodiscard]] uint channels() const noexcept override { return 3u; } 34 | [[nodiscard]] uint2 resolution() const noexcept override { return _bump_texture->resolution(); } 35 | [[nodiscard]] luisa::unique_ptr build( 36 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept override; 37 | }; 38 | 39 | class Bump2NormalTextureInstance final : public Texture::Instance { 40 | 41 | private: 42 | const Texture::Instance *_bump; 43 | 44 | public: 45 | Bump2NormalTextureInstance(Pipeline &pipeline, 46 | const Texture *node, 47 | const Texture::Instance *bump) noexcept 48 | : Texture::Instance{pipeline, node}, _bump{bump} {} 49 | [[nodiscard]] Float4 evaluate(const Interaction &it, Expr time) const noexcept override { 50 | auto n = def(make_float3(0.f)); 51 | $outline { 52 | auto size = static_cast(std::max(node()->resolution().x, node()->resolution().y)); 53 | auto step = std::min(1.f / size, 1.f / 256.f); 54 | auto dx = _bump->evaluate(Interaction{it.uv() + make_float2(step, 0.f)}, time).x - 55 | _bump->evaluate(Interaction{it.uv() - make_float2(step, 0.f)}, time).x; 56 | auto dy = _bump->evaluate(Interaction{it.uv() + make_float2(0.f, step)}, time).x - 57 | _bump->evaluate(Interaction{it.uv() - make_float2(0.f, step)}, time).x; 58 | auto dxy = make_float2(dx / (2.f * step), -dy / (2.f * step)); 59 | n = normalize(make_float3(dxy, max(1.f, length(dxy)))); 60 | }; 61 | return make_float4(n * 0.5f + 0.5f, 1.f); 62 | } 63 | }; 64 | 65 | luisa::unique_ptr Bump2NormalTexture::build( 66 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept { 67 | auto bump = pipeline.build_texture(command_buffer, _bump_texture); 68 | return luisa::make_unique(pipeline, this, bump); 69 | } 70 | 71 | }// namespace luisa::render 72 | 73 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::Bump2NormalTexture) -------------------------------------------------------------------------------- /src/textures/constant.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/26. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace luisa::render { 11 | 12 | class ConstantTexture final : public Texture { 13 | 14 | private: 15 | float4 _v; 16 | uint _channels{0u}; 17 | bool _black{false}; 18 | bool _should_inline; 19 | 20 | public: 21 | ConstantTexture(Scene *scene, const SceneNodeDesc *desc) noexcept 22 | : Texture{scene, desc}, 23 | _should_inline{desc->property_bool_or_default("inline", true)} { 24 | auto scale = desc->property_float_or_default("scale", 1.f); 25 | auto v = desc->property_float_list_or_default("v"); 26 | if (v.empty()) [[unlikely]] { 27 | LUISA_WARNING( 28 | "No value for ConstantTexture. " 29 | "Fallback to single-channel zero. [{}]", 30 | desc->source_location().string()); 31 | v.emplace_back(0.f); 32 | } else if (v.size() > 4u) [[unlikely]] { 33 | LUISA_WARNING( 34 | "Too many channels (count = {}) for ConstantTexture. " 35 | "Additional channels will be discarded. [{}]", 36 | v.size(), desc->source_location().string()); 37 | v.resize(4u); 38 | } 39 | _channels = v.size(); 40 | for (auto i = 0u; i < v.size(); i++) { _v[i] = scale * v[i]; } 41 | _black = all(_v == 0.f); 42 | } 43 | [[nodiscard]] auto v() const noexcept { return _v; } 44 | [[nodiscard]] bool is_black() const noexcept override { return _black; } 45 | [[nodiscard]] bool is_constant() const noexcept override { return true; } 46 | [[nodiscard]] bool should_inline() const noexcept { return _should_inline; } 47 | [[nodiscard]] uint2 resolution() const noexcept override { return make_uint2(1u); } 48 | [[nodiscard]] optional evaluate_static() const noexcept override { 49 | return _should_inline ? luisa::make_optional(_v) : luisa::nullopt; 50 | } 51 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 52 | [[nodiscard]] uint channels() const noexcept override { return _channels; } 53 | [[nodiscard]] luisa::unique_ptr build( 54 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept override; 55 | }; 56 | 57 | class ConstantTextureInstance final : public Texture::Instance { 58 | 59 | private: 60 | uint _constant_slot{}; 61 | 62 | public: 63 | ConstantTextureInstance(Pipeline &p, 64 | const ConstantTexture *t, 65 | CommandBuffer &cmd_buffer) noexcept 66 | : Texture::Instance{p, t} { 67 | if (!t->should_inline()) { 68 | auto [buffer, buffer_id] = p.allocate_constant_slot(); 69 | auto v = t->v(); 70 | cmd_buffer << buffer.copy_from(&v) << compute::commit(); 71 | _constant_slot = buffer_id; 72 | } 73 | } 74 | [[nodiscard]] Float4 evaluate(const Interaction &it, 75 | Expr time) const noexcept override { 76 | if (auto texture = node(); 77 | texture->should_inline()) { return texture->v(); } 78 | return pipeline().constant(_constant_slot); 79 | } 80 | }; 81 | 82 | luisa::unique_ptr ConstantTexture::build( 83 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept { 84 | return luisa::make_unique( 85 | pipeline, this, command_buffer); 86 | } 87 | 88 | }// namespace luisa::render 89 | 90 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::ConstantTexture) 91 | -------------------------------------------------------------------------------- /src/textures/scale.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by mike on 4/17/24. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace luisa::render { 10 | 11 | class ScaleTexture final : public Texture { 12 | 13 | private: 14 | Texture *_base; 15 | float4 _scale; 16 | float4 _offset; 17 | 18 | public: 19 | ScaleTexture(Scene *scene, const SceneNodeDesc *desc) noexcept 20 | : Texture{scene, desc}, 21 | _base{scene->load_texture(desc->property_node("base"))}, 22 | _scale{[desc] { 23 | auto s = desc->property_float_list_or_default("scale"); 24 | if (s.size() == 1u) { return make_float4(s[0]); } 25 | s.reserve(4u); 26 | if (s.size() < 4u) { s.resize(4u, 1.f); } 27 | s.resize(4u); 28 | return make_float4(s[0], s[1], s[2], s[3]); 29 | }()}, 30 | _offset{[desc] { 31 | auto o = desc->property_float_list_or_default("offset"); 32 | if (o.size() == 1u) { return make_float4(o[0]); } 33 | o.reserve(4u); 34 | if (o.size() < 4u) { o.resize(4u, 0.f); } 35 | o.resize(4u); 36 | return make_float4(o[0], o[1], o[2], o[3]); 37 | }()} {} 38 | [[nodiscard]] auto base() const noexcept { return _base; } 39 | [[nodiscard]] auto scale() const noexcept { return _scale; } 40 | [[nodiscard]] auto offset() const noexcept { return _offset; } 41 | [[nodiscard]] bool is_black() const noexcept override { return false; } 42 | [[nodiscard]] bool is_constant() const noexcept override { return _base->is_constant(); } 43 | [[nodiscard]] luisa::optional evaluate_static() const noexcept override { 44 | if (auto v = _base->evaluate_static()) { 45 | return v.value() * _scale; 46 | } 47 | return nullopt; 48 | } 49 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 50 | [[nodiscard]] uint channels() const noexcept override { return _base->channels(); } 51 | [[nodiscard]] uint2 resolution() const noexcept override { return _base->resolution(); } 52 | [[nodiscard]] luisa::unique_ptr build( 53 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept override; 54 | }; 55 | 56 | class ScaleTextureInstance final : public Texture::Instance { 57 | 58 | private: 59 | const Texture::Instance *_base; 60 | 61 | public: 62 | ScaleTextureInstance(const Pipeline &pipeline, const Texture *node, 63 | const Texture::Instance *base) noexcept 64 | : Texture::Instance{pipeline, node}, _base{base} {} 65 | [[nodiscard]] Float4 evaluate(const Interaction &it, 66 | Expr time) const noexcept override { 67 | return _base->evaluate(it, time) * node()->scale() + node()->offset(); 68 | } 69 | }; 70 | 71 | luisa::unique_ptr ScaleTexture::build( 72 | Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept { 73 | auto base = pipeline.build_texture(command_buffer, _base); 74 | return luisa::make_unique(pipeline, this, base); 75 | } 76 | 77 | }// namespace luisa::render 78 | 79 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::ScaleTexture) 80 | -------------------------------------------------------------------------------- /src/textures/sky_precompute.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/10/13. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace luisa::render { 10 | 11 | struct NishitaSkyData { 12 | float sun_elevation; 13 | float sun_angle; 14 | float altitude; 15 | float air_density; 16 | float dust_density; 17 | float ozone_density; 18 | }; 19 | 20 | struct NishitaSkyPrecomputedSun { 21 | float3 bottom; 22 | float3 top; 23 | }; 24 | 25 | void SKY_nishita_skymodel_precompute_texture( 26 | NishitaSkyData data, float4 *pixels, 27 | uint2 resolution, uint2 y_range) noexcept; 28 | 29 | [[nodiscard]] NishitaSkyPrecomputedSun 30 | SKY_nishita_skymodel_precompute_sun( 31 | NishitaSkyData data) noexcept; 32 | 33 | }// namespace luisa::render 34 | -------------------------------------------------------------------------------- /src/transforms/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(luisa-render-transforms INTERFACE) 2 | luisa_render_add_plugin(identity CATEGORY transform SOURCES identity.cpp) 3 | luisa_render_add_plugin(matrix CATEGORY transform SOURCES matrix.cpp) 4 | luisa_render_add_plugin(srt CATEGORY transform SOURCES srt.cpp) 5 | luisa_render_add_plugin(stack CATEGORY transform SOURCES stack.cpp) 6 | luisa_render_add_plugin(lerp CATEGORY transform SOURCES lerp.cpp) 7 | luisa_render_add_plugin(view CATEGORY transform SOURCES view.cpp) 8 | -------------------------------------------------------------------------------- /src/transforms/identity.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/10. 3 | // 4 | 5 | #include 6 | 7 | namespace luisa::render { 8 | 9 | struct IdentityTransform final : public Transform { 10 | IdentityTransform(Scene *scene, const SceneNodeDesc *desc) noexcept : Transform{scene, desc} {} 11 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 12 | [[nodiscard]] bool is_static() const noexcept override { return true; } 13 | [[nodiscard]] bool is_identity() const noexcept override { return true; } 14 | [[nodiscard]] float4x4 matrix(float time) const noexcept override { return make_float4x4(1.0f); } 15 | }; 16 | 17 | } 18 | 19 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::IdentityTransform) 20 | -------------------------------------------------------------------------------- /src/transforms/matrix.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/15. 3 | // 4 | 5 | #include 6 | 7 | namespace luisa::render { 8 | 9 | class MatrixTransform final : public Transform { 10 | 11 | private: 12 | float4x4 _matrix; 13 | 14 | public: 15 | MatrixTransform(Scene *scene, const SceneNodeDesc *desc) noexcept 16 | : Transform{scene, desc}, _matrix{make_float4x4(1.0f)} { 17 | auto m = desc->property_float_list_or_default("m"); 18 | if (m.size() == 16u) { 19 | if (!all(make_float4(m[12], m[13], m[14], m[15]) == 20 | make_float4(0.0f, 0.0f, 0.0f, 1.0f))) { 21 | LUISA_WARNING( 22 | "Expected affine transform matrices, " 23 | "while the last row is ({}, {}, {}, {}). " 24 | "This will be fixed but might lead to " 25 | "unexpected transforms. [{}]", 26 | m[12], m[13], m[14], m[15], 27 | desc->source_location().string()); 28 | m[12] = 0.0f, m[13] = 0.0f, 29 | m[14] = 0.0f, m[15] = 1.0f; 30 | } 31 | for (auto row = 0u; row < 4u; row++) { 32 | for (auto col = 0u; col < 4u; col++) { 33 | _matrix[col][row] = m[row * 4u + col]; 34 | } 35 | } 36 | } else if (!m.empty()) [[unlikely]] { 37 | LUISA_ERROR( 38 | "Invalid matrix entries. [{}]", 39 | desc->source_location().string()); 40 | } 41 | } 42 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 43 | [[nodiscard]] float4x4 matrix(float) const noexcept override { return _matrix; } 44 | [[nodiscard]] bool is_static() const noexcept override { return true; } 45 | [[nodiscard]] bool is_identity() const noexcept override { 46 | return all(_matrix[0] == make_float4(1.0f, 0.0f, 0.0f, 0.0f)) && 47 | all(_matrix[1] == make_float4(0.0f, 1.0f, 0.0f, 0.0f)) && 48 | all(_matrix[2] == make_float4(0.0f, 0.0f, 1.0f, 0.0f)) && 49 | all(_matrix[3] == make_float4(0.0f, 0.0f, 0.0f, 1.0f)); 50 | } 51 | }; 52 | 53 | }// namespace luisa::render 54 | 55 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::MatrixTransform) 56 | -------------------------------------------------------------------------------- /src/transforms/srt.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/10. 3 | // 4 | 5 | #include 6 | 7 | namespace luisa::render { 8 | 9 | class ScaleRotateTranslate final : public Transform { 10 | 11 | private: 12 | float4x4 _matrix; 13 | 14 | public: 15 | ScaleRotateTranslate(Scene *scene, const SceneNodeDesc *desc) noexcept 16 | : Transform{scene, desc} { 17 | auto scaling = desc->property_float3_or_default("scale", lazy_construct([desc]{ 18 | return make_float3(desc->property_float_or_default("scale", 1.0f)); 19 | })); 20 | auto rotation = desc->property_float4_or_default("rotate", make_float4(0.0f, 0.0f, 1.0f, 0.0f)); 21 | auto translation = desc->property_float3_or_default("translate", make_float3()); 22 | _matrix = luisa::translation(translation) * 23 | luisa::rotation(normalize(rotation.xyz()), radians(rotation.w)) * 24 | luisa::scaling(scaling); 25 | } 26 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 27 | [[nodiscard]] bool is_static() const noexcept override { return true; } 28 | [[nodiscard]] float4x4 matrix(float) const noexcept override { return _matrix; } 29 | [[nodiscard]] bool is_identity() const noexcept override { 30 | return all(_matrix[0] == make_float4(1.0f, 0.0f, 0.0f, 0.0f)) && 31 | all(_matrix[1] == make_float4(0.0f, 1.0f, 0.0f, 0.0f)) && 32 | all(_matrix[2] == make_float4(0.0f, 0.0f, 1.0f, 0.0f)) && 33 | all(_matrix[3] == make_float4(0.0f, 0.0f, 0.0f, 1.0f)); 34 | } 35 | }; 36 | 37 | }// namespace luisa::render 38 | 39 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::ScaleRotateTranslate) 40 | -------------------------------------------------------------------------------- /src/transforms/stack.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/15. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | namespace luisa::render { 9 | 10 | class TransformStack final : public Transform { 11 | 12 | private: 13 | luisa::vector _transforms; 14 | mutable float4x4 _matrix_cache; 15 | mutable float _time_cache; 16 | mutable spin_mutex _mutex; 17 | bool _is_static; 18 | bool _is_identity; 19 | 20 | public: 21 | TransformStack(Scene *scene, const SceneNodeDesc *desc) noexcept 22 | : Transform{scene, desc}, 23 | _matrix_cache{make_float4x4(1.0f)}, _time_cache{0.0f}, 24 | _is_static{true}, _is_identity{true} { 25 | auto children = desc->property_node_list_or_default("transforms"); 26 | luisa::vector transforms(children.size()); 27 | for (auto i = 0u; i < children.size(); i++) { 28 | auto t = scene->load_transform(children[i]); 29 | transforms[i] = t; 30 | _is_static &= t->is_static(); 31 | _is_identity &= t->is_identity(); 32 | _matrix_cache = t->matrix(_time_cache) * _matrix_cache; 33 | } 34 | if (!_is_static) { _transforms = std::move(transforms); } 35 | } 36 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 37 | [[nodiscard]] bool is_static() const noexcept override { return _is_static; } 38 | [[nodiscard]] bool is_identity() const noexcept override { return _is_identity; } 39 | [[nodiscard]] float4x4 matrix(float time) const noexcept override { 40 | if (_is_static) { return _matrix_cache; } 41 | if (_transforms.size() < 4u) { 42 | auto m = make_float4x4(1.0f); 43 | for (auto t : _transforms) { 44 | m = t->matrix(time) * m; 45 | } 46 | return m; 47 | } 48 | std::scoped_lock lock{_mutex}; 49 | if (time != _time_cache) { 50 | _time_cache = time; 51 | _matrix_cache = make_float4x4(1.0f); 52 | for (auto t : _transforms) { 53 | auto m = t->matrix(_time_cache); 54 | _matrix_cache = m * _matrix_cache; 55 | } 56 | } 57 | return _matrix_cache; 58 | } 59 | }; 60 | 61 | }// namespace luisa::render 62 | 63 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::TransformStack) 64 | -------------------------------------------------------------------------------- /src/transforms/view.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/11/7. 3 | // 4 | 5 | #include 6 | 7 | namespace luisa::render { 8 | 9 | class ViewTransform final : public Transform { 10 | 11 | private: 12 | float3 _origin; 13 | float3 _u;// right 14 | float3 _v;// up 15 | float3 _w;// back 16 | 17 | public: 18 | ViewTransform(Scene *scene, const SceneNodeDesc *desc) noexcept 19 | : Transform{scene, desc}, 20 | _origin{desc->property_float3_or_default( 21 | "origin", lazy_construct([desc] { 22 | return desc->property_float3_or_default("position"); 23 | }))} { 24 | auto front = desc->property_float3_or_default("front", make_float3(0.0f, 0.0f, -1.0f)); 25 | auto up = desc->property_float3_or_default("up", make_float3(0.0f, 1.0f, 0.0f)); 26 | _w = normalize(-front); 27 | _u = normalize(cross(up, _w)); 28 | _v = normalize(cross(_w, _u)); 29 | } 30 | [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } 31 | [[nodiscard]] bool is_static() const noexcept override { return true; } 32 | [[nodiscard]] bool is_identity() const noexcept override { 33 | return all(_origin == 0.f) && 34 | all(_v == make_float3(0.0f, 1.0f, 0.0f)) && 35 | all(_w == make_float3(0.0f, 0.0f, 1.0f)); 36 | } 37 | [[nodiscard]] float4x4 matrix(float time) const noexcept override { 38 | return make_float4x4(make_float4(_u, 0.f), 39 | make_float4(_v, 0.f), 40 | make_float4(_w, 0.f), 41 | make_float4(_origin, 1.f)); 42 | } 43 | }; 44 | 45 | }// namespace luisa::render 46 | 47 | LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::ViewTransform) 48 | -------------------------------------------------------------------------------- /src/util/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(luisa-render-util SHARED 2 | sampling.cpp sampling.h 3 | frame.cpp frame.h 4 | imageio.cpp imageio.h 5 | xform.cpp xform.h 6 | spec.cpp spec.h 7 | colorspace.h 8 | rng.cpp rng.h 9 | ies.cpp ies.h 10 | scattering.cpp scattering.h 11 | bluenoise.cpp bluenoise.h 12 | sobolmatrices.cpp sobolmatrices.h 13 | pmj02tables.cpp pmj02tables.h 14 | complex.h 15 | medium_tracker.cpp medium_tracker.h 16 | progress_bar.cpp progress_bar.h 17 | loop_subdiv.cpp loop_subdiv.h 18 | vertex.h 19 | counter_buffer.cpp counter_buffer.h 20 | polymorphic_closure.h 21 | command_buffer.cpp command_buffer.h 22 | thread_pool.cpp thread_pool.h) 23 | 24 | find_package(Threads) 25 | 26 | target_link_libraries(luisa-render-util PUBLIC 27 | Threads::Threads 28 | luisa::compute 29 | luisa-render-include 30 | luisa-render-ext) 31 | 32 | # work around the clang issue: https://github.com/Homebrew/homebrew-core/issues/169820 33 | if (APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") 34 | cmake_path(GET CMAKE_CXX_COMPILER PARENT_PATH CLANG_BIN_PATH) 35 | cmake_path(GET CLANG_BIN_PATH PARENT_PATH CLANG_ROOT_PATH) 36 | set(LIBCXX_PATH "${CLANG_ROOT_PATH}/lib/c++") 37 | if (EXISTS ${LIBCXX_PATH}) 38 | target_link_libraries(luisa-compute-core PUBLIC "-L${LIBCXX_PATH}") 39 | endif () 40 | endif () 41 | 42 | set_target_properties(luisa-render-util PROPERTIES 43 | WINDOWS_EXPORT_ALL_SYMBOLS ON 44 | UNITY_BUILD ${LUISA_RENDER_ENABLE_UNITY_BUILD}) 45 | install(TARGETS luisa-render-util 46 | LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR} 47 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) 48 | -------------------------------------------------------------------------------- /src/util/bluenoise.h: -------------------------------------------------------------------------------- 1 | // pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys. 2 | // The pbrt source code is licensed under the Apache License, Version 2.0. 3 | // SPDX: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace luisa::render { 10 | 11 | static constexpr auto BlueNoiseResolution = 128u; 12 | static constexpr auto NumBlueNoiseTextures = 48u; 13 | 14 | #ifndef LUISA_RENDER_BLUE_NOISE_DEFINITION 15 | LUISA_IMPORT_API const uint16_t BlueNoiseTextures[NumBlueNoiseTextures][BlueNoiseResolution][BlueNoiseResolution]; 16 | #endif 17 | 18 | }// namespace luisa::render 19 | -------------------------------------------------------------------------------- /src/util/colorspace.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/19. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | namespace luisa::render { 11 | 12 | template 13 | [[nodiscard]] inline auto cie_xyz_to_linear_srgb(T &&xyz) noexcept { 14 | constexpr auto m = make_float3x3( 15 | +3.240479f, -0.969256f, +0.055648f, 16 | -1.537150f, +1.875991f, -0.204043f, 17 | -0.498535f, +0.041556f, +1.057311f); 18 | return m * std::forward(xyz); 19 | } 20 | 21 | template 22 | [[nodiscard]] inline auto linear_srgb_to_cie_y(T &&rgb) noexcept { 23 | constexpr auto m = make_float3(0.212671f, 0.715160f, 0.072169f); 24 | return dot(m, std::forward(rgb)); 25 | } 26 | 27 | template 28 | [[nodiscard]] inline auto linear_srgb_to_cie_xyz(T &&rgb) noexcept { 29 | constexpr auto m = make_float3x3( 30 | 0.412453f, 0.212671f, 0.019334f, 31 | 0.357580f, 0.715160f, 0.119193f, 32 | 0.180423f, 0.072169f, 0.950227f); 33 | return m * std::forward(rgb); 34 | } 35 | 36 | template 37 | [[nodiscard]] inline auto cie_xyz_to_lab(T &&xyz) noexcept { 38 | static constexpr auto delta = 6.f / 29.f; 39 | auto f = [](const auto &x) noexcept { 40 | constexpr auto d3 = delta * delta * delta; 41 | constexpr auto one_over_3d2 = 1.f / (3.f * delta * delta); 42 | if constexpr (compute::is_dsl_v) { 43 | return compute::ite(x > d3, compute::pow(x, 1.f / 3.f), one_over_3d2 * x + (4.f / 29.f)); 44 | } else { 45 | return x > d3 ? std::pow(x, 1.f / 3.f) : one_over_3d2 * x + 4.f / 29.f; 46 | } 47 | }; 48 | auto f_y_yn = f(xyz.y); 49 | auto l = 116.f * f_y_yn - 16.f; 50 | auto a = 500.f * (f(xyz.x / .950489f) - f_y_yn); 51 | auto b = 200.f * (f_y_yn - f(xyz.z / 1.08884f)); 52 | return make_float3(l, a, b); 53 | } 54 | 55 | template 56 | [[nodiscard]] inline auto lab_to_cie_xyz(T &&lab) noexcept { 57 | static constexpr auto delta = 6.f / 29.f; 58 | auto f = [](const auto &x) noexcept { 59 | constexpr auto three_dd = 3.f * delta * delta; 60 | if constexpr (compute::is_dsl_v) { 61 | return compute::ite(x > delta, x * x * x, three_dd * x - (three_dd * 4.f / 29.f)); 62 | } else { 63 | return x > delta ? x * x * x : three_dd * x - three_dd * 4.f / 29.f; 64 | } 65 | }; 66 | auto v = (lab.x + 16.f) / 116.f; 67 | auto x = .950489f * f(v + lab.y / 500.f); 68 | auto y = f(v); 69 | auto z = 1.08884f * f(v - lab.z / 200.f); 70 | return make_float3(x, y, z); 71 | } 72 | 73 | }// namespace luisa::render 74 | -------------------------------------------------------------------------------- /src/util/command_buffer.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2023/5/18. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | namespace luisa::render { 9 | 10 | CommandBuffer::CommandBuffer(Stream *stream) noexcept 11 | : _stream{stream} {} 12 | 13 | CommandBuffer::~CommandBuffer() noexcept { 14 | LUISA_ASSERT(_list.empty(), 15 | "Command buffer not empty when destroyed. " 16 | "Did you forget to commit?"); 17 | } 18 | 19 | }// namespace luisa::render 20 | -------------------------------------------------------------------------------- /src/util/command_buffer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2023/5/18. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | namespace luisa::render { 11 | 12 | using compute::CommandList; 13 | using compute::Stream; 14 | 15 | class CommandBuffer { 16 | 17 | private: 18 | Stream *_stream; 19 | CommandList _list; 20 | 21 | public: 22 | explicit CommandBuffer(Stream *stream) noexcept; 23 | ~CommandBuffer() noexcept; 24 | 25 | [[nodiscard]] auto stream() const noexcept { return _stream; } 26 | 27 | template 28 | CommandBuffer &operator<<(T &&cmd) noexcept { 29 | if constexpr (requires { _list << std::forward(cmd); }) { 30 | _list << std::forward(cmd); 31 | } else { 32 | *_stream << _list.commit() 33 | << std::forward(cmd); 34 | } 35 | return *this; 36 | } 37 | 38 | CommandBuffer &operator<<(compute::Stream::Commit) noexcept { 39 | if (!_list.empty()) { *_stream << _list.commit(); } 40 | return *this; 41 | } 42 | 43 | CommandBuffer &operator<<(compute::Stream::Synchronize) noexcept { 44 | if (!_list.empty()) { *_stream << _list.commit(); } 45 | _stream->synchronize(); 46 | return *this; 47 | } 48 | 49 | template 50 | CommandBuffer &operator<<(std::tuple cmds) noexcept { 51 | std::apply( 52 | [&](Cmd &&...cmd) noexcept { 53 | ((*this) << ... << std::forward(cmd)); 54 | }, 55 | std::move(cmds)); 56 | return *this; 57 | } 58 | }; 59 | 60 | }// namespace luisa::render -------------------------------------------------------------------------------- /src/util/complex.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/31. 3 | // 4 | 5 | #pragma once 6 | 7 | namespace luisa::render { 8 | 9 | template 10 | struct Complex { 11 | T re, im; 12 | Complex(T re) : re{re}, im{0.0f} {} 13 | Complex(T re, T im) : re{re}, im{im} {} 14 | [[nodiscard]] Complex operator-() const noexcept { return {-re, -im}; } 15 | [[nodiscard]] Complex operator+(Complex z) const noexcept { return {re + z.re, im + z.im}; } 16 | [[nodiscard]] Complex operator-(Complex z) const noexcept { return {re - z.re, im - z.im}; } 17 | [[nodiscard]] Complex operator*(Complex z) const noexcept { return {re * z.re - im * z.im, re * z.im + im * z.re}; } 18 | [[nodiscard]] Complex operator/(Complex z) const { return Complex{(re * z.re + im * z.im), (im * z.re - re * z.im)} * (1.0f / (z.re * z.re + z.im * z.im)); } 19 | [[nodiscard]] friend Complex operator+(T value, Complex z) noexcept { return Complex(value) + z; } 20 | [[nodiscard]] friend Complex operator-(T value, Complex z) noexcept { return Complex(value) - z; } 21 | [[nodiscard]] friend Complex operator*(T value, Complex z) noexcept { return Complex(value) * z; } 22 | [[nodiscard]] friend Complex operator/(T value, Complex z) noexcept { return Complex(value) / z; } 23 | }; 24 | 25 | }// namespace luisa::render 26 | -------------------------------------------------------------------------------- /src/util/counter_buffer.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike on 2022/11/14. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | namespace luisa::render { 9 | 10 | CounterBuffer::CounterBuffer(Device &device, uint size) noexcept 11 | : _buffer{device.create_buffer(size)} {} 12 | 13 | void CounterBuffer::record(Expr index, Expr count) noexcept { 14 | if (_buffer) { 15 | auto view = _buffer.view().as(); 16 | auto old = view->atomic(index * 2u + 0u).fetch_add(count); 17 | $if(count != 0u & (old + count < old)) { view->atomic(index * 2u + 1u).fetch_add(1u); }; 18 | } 19 | } 20 | 21 | void CounterBuffer::clear(Expr index) noexcept { 22 | if (_buffer) { 23 | auto view = _buffer.view().as(); 24 | view->write(index * 2u + 0u, 0u); 25 | view->write(index * 2u + 1u, 0u); 26 | } 27 | } 28 | 29 | size_t CounterBuffer::size() const noexcept { 30 | return _buffer ? _buffer.size() / 2u : 0u; 31 | } 32 | 33 | luisa::unique_ptr CounterBuffer::copy_to(void *data) const noexcept { 34 | return _buffer ? _buffer.copy_to(data) : nullptr; 35 | } 36 | 37 | CounterBuffer::operator bool() const noexcept { 38 | return static_cast(_buffer); 39 | } 40 | 41 | }// namespace luisa::render 42 | -------------------------------------------------------------------------------- /src/util/counter_buffer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike on 2022/11/14. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace luisa::render { 12 | 13 | using compute::Buffer; 14 | using compute::Command; 15 | using compute::Device; 16 | using compute::Expr; 17 | 18 | class CounterBuffer { 19 | 20 | private: 21 | Buffer _buffer; 22 | 23 | public: 24 | CounterBuffer() noexcept = default; 25 | CounterBuffer(Device &device, uint size) noexcept; 26 | void record(Expr index, Expr count = 1u) noexcept; 27 | void clear(Expr index) noexcept; 28 | [[nodiscard]] size_t size() const noexcept; 29 | [[nodiscard]] luisa::unique_ptr copy_to(void *data) const noexcept; 30 | [[nodiscard]] explicit operator bool() const noexcept; 31 | }; 32 | 33 | }// namespace luisa::render 34 | -------------------------------------------------------------------------------- /src/util/frame.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/13. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace luisa::render { 10 | 11 | Frame::Frame(Expr s, 12 | Expr t, 13 | Expr n) noexcept 14 | : _s{s}, _t{t}, _n{n} {} 15 | 16 | Frame::Frame() noexcept 17 | : _s{make_float3(1.f, 0.f, 0.f)}, 18 | _t{make_float3(0.f, 1.f, 0.f)}, 19 | _n{make_float3(0.f, 0.f, 1.f)} {} 20 | 21 | Frame Frame::make(Expr n) noexcept { 22 | auto sgn = sign(n.z); 23 | auto a = -1.f / (sgn + n.z); 24 | auto b = n.x * n.y * a; 25 | auto s = make_float3(1.f + sgn * sqr(n.x) * a, sgn * b, -sgn * n.x); 26 | auto t = make_float3(b, sgn + sqr(n.y) * a, -n.y); 27 | return {normalize(s), normalize(t), n}; 28 | } 29 | 30 | Frame Frame::make(Expr n, Expr s) noexcept { 31 | auto ss = normalize(s - n * dot(n, s)); 32 | auto tt = normalize(cross(n, ss)); 33 | return {ss, tt, n}; 34 | } 35 | 36 | Float3 Frame::local_to_world(Expr d) const noexcept { 37 | return normalize(d.x * _s + d.y * _t + d.z * _n); 38 | } 39 | 40 | Float3 Frame::world_to_local(Expr d) const noexcept { 41 | return normalize(make_float3(dot(d, _s), dot(d, _t), dot(d, _n))); 42 | } 43 | 44 | void Frame::flip() noexcept { 45 | _n = -_n; 46 | _t = -_t; 47 | } 48 | 49 | Float3 clamp_shading_normal(Expr ns, Expr ng, Expr w) noexcept { 50 | auto w_refl = reflect(-w, ns); 51 | auto w_refl_clip = ite(dot(w_refl, ng) * dot(w, ng) > 0.f, w_refl, 52 | normalize(w_refl - ng * dot(w_refl, ng))); 53 | return normalize(w_refl_clip + w); 54 | } 55 | 56 | }// namespace luisa::render 57 | -------------------------------------------------------------------------------- /src/util/frame.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/13. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace luisa::render { 10 | 11 | using luisa::compute::Bool; 12 | using luisa::compute::Expr; 13 | using luisa::compute::Float; 14 | using luisa::compute::Float3; 15 | 16 | class Frame { 17 | 18 | private: 19 | Float3 _s; 20 | Float3 _t; 21 | Float3 _n; 22 | 23 | public: 24 | Frame() noexcept; 25 | Frame(Expr s, Expr t, Expr n) noexcept; 26 | void flip() noexcept; 27 | [[nodiscard]] static Frame make(Expr n) noexcept; 28 | [[nodiscard]] static Frame make(Expr n, Expr s) noexcept; 29 | [[nodiscard]] Float3 local_to_world(Expr d) const noexcept; 30 | [[nodiscard]] Float3 world_to_local(Expr d) const noexcept; 31 | [[nodiscard]] Expr s() const noexcept { return _s; } 32 | [[nodiscard]] Expr t() const noexcept { return _t; } 33 | [[nodiscard]] Expr n() const noexcept { return _n; } 34 | }; 35 | 36 | using compute::abs; 37 | using compute::clamp; 38 | using compute::cos; 39 | using compute::def; 40 | using compute::dot; 41 | using compute::ite; 42 | using compute::max; 43 | using compute::saturate; 44 | using compute::sign; 45 | using compute::sin; 46 | using compute::sqrt; 47 | 48 | [[nodiscard]] inline auto sqr(auto x) noexcept { return x * x; } 49 | [[nodiscard]] inline auto one_minus_sqr(auto x) noexcept { return 1.f - sqr(x); } 50 | [[nodiscard]] inline auto abs_dot(Expr u, Expr v) noexcept { return abs(dot(u, v)); } 51 | [[nodiscard]] inline auto cos_theta(Expr w) { return w.z; } 52 | [[nodiscard]] inline auto cos2_theta(Expr w) { return sqr(w.z); } 53 | [[nodiscard]] inline auto abs_cos_theta(Expr w) { return abs(w.z); } 54 | [[nodiscard]] inline auto sin2_theta(Expr w) { return saturate(1.0f - cos2_theta(w)); } 55 | [[nodiscard]] inline auto sin_theta(Expr w) { return sqrt(sin2_theta(w)); } 56 | [[nodiscard]] inline auto tan_theta(Expr w) { return sin_theta(w) / cos_theta(w); } 57 | [[nodiscard]] inline auto tan2_theta(Expr w) { return sin2_theta(w) / cos2_theta(w); } 58 | 59 | [[nodiscard]] inline auto cos_phi(Expr w) { 60 | auto sinTheta = sin_theta(w); 61 | return ite(sinTheta == 0.0f, 1.0f, clamp(w.x / sinTheta, -1.0f, 1.0f)); 62 | } 63 | 64 | [[nodiscard]] inline auto sin_phi(Expr w) { 65 | auto sinTheta = sin_theta(w); 66 | return ite(sinTheta == 0.0f, 0.0f, clamp(w.y / sinTheta, -1.0f, 1.0f)); 67 | } 68 | 69 | [[nodiscard]] inline auto cos2_phi(Expr w) { return sqr(cos_phi(w)); } 70 | [[nodiscard]] inline auto sin2_phi(Expr w) { return sqr(sin_phi(w)); } 71 | [[nodiscard]] inline auto same_hemisphere(Expr w, Expr wp) noexcept { return w.z * wp.z > 0.0f; } 72 | 73 | // clamp the shading normal `ns` so that `w` and its reflection will go to the same hemisphere w.r.t. `ng` 74 | [[nodiscard]] Float3 clamp_shading_normal(Expr ns, Expr ng, Expr w) noexcept; 75 | 76 | }// namespace luisa::render 77 | 78 | LUISA_DISABLE_DSL_ADDRESS_OF_OPERATOR(luisa::render::Frame) 79 | -------------------------------------------------------------------------------- /src/util/ies.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/27. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | namespace luisa::render { 9 | 10 | IESProfile IESProfile::parse(const std::filesystem::path &path) noexcept { 11 | std::ifstream file{path}; 12 | if (!file) [[unlikely]] { 13 | LUISA_ERROR_WITH_LOCATION( 14 | "Failed to open IES profile '{}'.", 15 | path.string()); 16 | } 17 | luisa::string line; 18 | line.reserve(1024u); 19 | std::getline(file, line); 20 | if (!line.starts_with("IESNA:")) [[unlikely]] { 21 | LUISA_ERROR_WITH_LOCATION( 22 | "Invalid IES profile '{}' " 23 | "with first line: {}.", 24 | path.string(), line); 25 | } 26 | while (!line.starts_with("TILT")) { 27 | std::getline(file, line); 28 | } 29 | if (line.starts_with("TILE=INCLUDE")) { 30 | std::getline(file, line);// 31 | std::getline(file, line);// 32 | std::getline(file, line);// 33 | std::getline(file, line);// 34 | } 35 | [[maybe_unused]] auto number_of_lamps = 0; 36 | [[maybe_unused]] auto lumens_per_lamp = 0; 37 | auto candela_multiplier = 0.0f; 38 | auto number_of_vertical_angles = 0u; 39 | auto number_of_horizontal_angles = 0u; 40 | file >> 41 | number_of_lamps >> 42 | lumens_per_lamp >> 43 | candela_multiplier >> 44 | number_of_vertical_angles >> 45 | number_of_horizontal_angles; 46 | std::getline(file, line);// 47 | std::getline(file, line);// 48 | luisa::vector vertical_angles; 49 | vertical_angles.reserve(number_of_vertical_angles); 50 | for (auto i = 0u; i < number_of_vertical_angles; i++) { 51 | vertical_angles.emplace_back(0.0f); 52 | file >> vertical_angles.back(); 53 | } 54 | luisa::vector horizontal_angles; 55 | horizontal_angles.reserve(number_of_horizontal_angles); 56 | for (auto i = 0u; i < number_of_horizontal_angles; i++) { 57 | horizontal_angles.emplace_back(0.0f); 58 | file >> horizontal_angles.back(); 59 | } 60 | auto n = number_of_vertical_angles * 61 | number_of_horizontal_angles; 62 | luisa::vector candela_values; 63 | candela_values.reserve(n); 64 | for (auto h = 0u; h < n; h++) { 65 | auto value = 0.0f; 66 | file >> value; 67 | candela_values.emplace_back( 68 | value * candela_multiplier); 69 | } 70 | return {std::move(vertical_angles), 71 | std::move(horizontal_angles), 72 | std::move(candela_values)}; 73 | } 74 | 75 | inline IESProfile::IESProfile( 76 | luisa::vector v_angles, 77 | luisa::vector h_angles, 78 | luisa::vector values) noexcept 79 | : _vertical_angles{std::move(v_angles)}, 80 | _horizontal_angles{std::move(h_angles)}, 81 | _candela_values{std::move(values)} {} 82 | 83 | }// namespace luisa::render 84 | -------------------------------------------------------------------------------- /src/util/ies.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/27. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | namespace luisa::render { 13 | 14 | // TODO 15 | class IESProfile { 16 | 17 | private: 18 | luisa::vector _vertical_angles; 19 | luisa::vector _horizontal_angles; 20 | luisa::vector _candela_values; 21 | 22 | private: 23 | IESProfile(luisa::vector v_angles, 24 | luisa::vector h_angles, 25 | luisa::vector values) noexcept; 26 | 27 | public: 28 | [[nodiscard]] static IESProfile parse(const std::filesystem::path &path) noexcept; 29 | [[nodiscard]] auto vertical_angles() const noexcept { return luisa::span{_vertical_angles}; } 30 | [[nodiscard]] auto horizontal_angles() const noexcept { return luisa::span{_horizontal_angles}; } 31 | [[nodiscard]] auto candela_values() const noexcept { return luisa::span{_candela_values}; } 32 | }; 33 | 34 | }// namespace luisa::render 35 | -------------------------------------------------------------------------------- /src/util/imageio.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/15. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace luisa::render { 13 | 14 | // TODO: texture cache 15 | class LoadedImage { 16 | 17 | public: 18 | using storage_type = compute::PixelStorage; 19 | 20 | private: 21 | void *_pixels{nullptr}; 22 | uint2 _resolution; 23 | storage_type _storage{}; 24 | luisa::function _deleter; 25 | 26 | private: 27 | void _destroy() noexcept; 28 | LoadedImage(void *pixels, storage_type storage, uint2 resolution, luisa::function deleter) noexcept; 29 | [[nodiscard]] static LoadedImage _load_byte(const std::filesystem::path &path, storage_type storage) noexcept; 30 | [[nodiscard]] static LoadedImage _load_half(const std::filesystem::path &path, storage_type storage) noexcept; 31 | [[nodiscard]] static LoadedImage _load_short(const std::filesystem::path &path, storage_type storage) noexcept; 32 | [[nodiscard]] static LoadedImage _load_float(const std::filesystem::path &path, storage_type storage) noexcept; 33 | [[nodiscard]] static LoadedImage _load_int(const std::filesystem::path &path, storage_type storage) noexcept; 34 | 35 | public: 36 | LoadedImage() noexcept = default; 37 | ~LoadedImage() noexcept; 38 | LoadedImage(LoadedImage &&another) noexcept; 39 | LoadedImage &operator=(LoadedImage &&rhs) noexcept; 40 | LoadedImage(const LoadedImage &) noexcept = delete; 41 | LoadedImage &operator=(const LoadedImage &) noexcept = delete; 42 | [[nodiscard]] auto size() const noexcept { return _resolution; } 43 | [[nodiscard]] void *pixels(uint level = 0u) noexcept { return _pixels; } 44 | [[nodiscard]] const void *pixels(uint level = 0u) const noexcept { return _pixels; } 45 | [[nodiscard]] auto pixel_storage() const noexcept { return _storage; } 46 | [[nodiscard]] auto channels() const noexcept { return compute::pixel_storage_channel_count(_storage); } 47 | [[nodiscard]] auto pixel_count() const noexcept { return _resolution.x * _resolution.y; } 48 | [[nodiscard]] explicit operator bool() const noexcept { return _pixels != nullptr; } 49 | [[nodiscard]] static LoadedImage load(const std::filesystem::path &path) noexcept; 50 | [[nodiscard]] static LoadedImage load(const std::filesystem::path &path, storage_type storage) noexcept; 51 | [[nodiscard]] static storage_type parse_storage(const std::filesystem::path &path) noexcept; 52 | [[nodiscard]] static LoadedImage create(uint2 resolution, storage_type storage) noexcept; 53 | }; 54 | 55 | void save_image(std::filesystem::path path, const float *pixels, 56 | uint2 resolution, uint components = 4) noexcept; 57 | 58 | void save_image(std::filesystem::path path, const uint8_t *pixels, 59 | uint2 resolution, uint components = 4) noexcept; 60 | 61 | }// namespace luisa::render 62 | -------------------------------------------------------------------------------- /src/util/loop_subdiv.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/11/8. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace luisa::render { 12 | 13 | using compute::Triangle; 14 | 15 | struct SubdivMesh { 16 | luisa::vector vertices; 17 | luisa::vector triangles; 18 | luisa::vector base_triangle_indices; 19 | }; 20 | 21 | [[nodiscard]] SubdivMesh loop_subdivide(luisa::span vertices, 22 | luisa::span triangles, 23 | uint level) noexcept; 24 | 25 | }// namespace luisa::render 26 | -------------------------------------------------------------------------------- /src/util/medium_tracker.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/2/19. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | namespace luisa::render { 9 | 10 | using namespace luisa::compute; 11 | 12 | Bool equal(Expr a, Expr b) noexcept { 13 | return a.medium_tag == b.medium_tag; 14 | } 15 | 16 | MediumTracker::MediumTracker() noexcept : _size{0u} { 17 | for (auto i = 0u; i < capacity; i++) { 18 | _priority_list[i] = Medium::VACUUM_PRIORITY; 19 | _medium_list[i] = make_medium_info(Medium::VACUUM_PRIORITY, Medium::INVALID_TAG); 20 | } 21 | } 22 | 23 | Bool MediumTracker::true_hit(Expr priority) const noexcept { 24 | return priority <= _priority_list[0u]; 25 | } 26 | 27 | void MediumTracker::enter(Expr priority, Expr value) noexcept { 28 | $if(_size == capacity) { 29 | #ifndef NDEBUG 30 | device_log( 31 | "[error] Medium stack overflow when trying to enter priority={}, medium_tag={}", 32 | priority, value.medium_tag); 33 | #endif 34 | } 35 | $else { 36 | _size += 1u; 37 | auto x = def(priority); 38 | auto v = def(value); 39 | for (auto i = 0u; i < capacity; i++) { 40 | auto p = _priority_list[i]; 41 | auto m = _medium_list[i]; 42 | auto should_swap = p > x; 43 | _priority_list[i] = ite(should_swap, x, p); 44 | _medium_list[i] = ite(should_swap, v, m); 45 | x = ite(should_swap, p, x); 46 | v = ite(should_swap, m, v); 47 | } 48 | }; 49 | } 50 | 51 | void MediumTracker::exit(Expr priority, Expr value) noexcept { 52 | auto remove_num = def(0u); 53 | for (auto i = 0u; i < capacity - 1u; i++) { 54 | auto p = _priority_list[i]; 55 | auto should_remove = (p == priority) & equal(_medium_list[i], value) & (remove_num == 0u); 56 | remove_num += ite(should_remove, 1u, 0u); 57 | _priority_list[i] = _priority_list[i + remove_num]; 58 | _medium_list[i] = _medium_list[i + remove_num]; 59 | } 60 | $if(remove_num != 0u) { 61 | _size -= 1u; 62 | _priority_list[_size] = Medium::VACUUM_PRIORITY; 63 | _medium_list[_size] = make_medium_info(Medium::VACUUM_PRIORITY, Medium::INVALID_TAG); 64 | } 65 | $else { 66 | #ifndef NDEBUG 67 | device_log( 68 | "[error] Medium stack trying to exit nonexistent priority={}, medium_tag={}", 69 | priority, value.medium_tag); 70 | #endif 71 | }; 72 | } 73 | 74 | Bool MediumTracker::exist(Expr priority, Expr value) noexcept { 75 | auto exist = def(false); 76 | for (auto i = 0u; i < capacity - 1u; i++) { 77 | auto p = _priority_list[i]; 78 | exist |= (p == priority) & equal(_medium_list[i], value); 79 | } 80 | return exist; 81 | } 82 | 83 | Var MediumTracker::current() const noexcept { 84 | auto ans = _medium_list[0]; 85 | $if(vacuum()) { 86 | ans = make_medium_info(Medium::VACUUM_PRIORITY, Medium::INVALID_TAG); 87 | }; 88 | return ans; 89 | } 90 | 91 | Bool MediumTracker::vacuum() const noexcept { 92 | return _priority_list[0] == Medium::VACUUM_PRIORITY; 93 | } 94 | 95 | }// namespace luisa::render 96 | -------------------------------------------------------------------------------- /src/util/medium_tracker.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/2/19. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace luisa::render { 13 | 14 | using compute::ArrayVar; 15 | using compute::Bool; 16 | using compute::Expr; 17 | using compute::Float4; 18 | using compute::UInt; 19 | using compute::Var; 20 | 21 | struct MediumInfo { 22 | uint priority{0u}; 23 | uint medium_tag{Medium::INVALID_TAG}; 24 | }; 25 | 26 | [[nodiscard]] Bool equal(Expr a, Expr b) noexcept; 27 | 28 | class MediumTracker { 29 | 30 | public: 31 | static constexpr auto capacity = 32u; 32 | 33 | private: 34 | ArrayVar _priority_list; 35 | ArrayVar _medium_list; 36 | UInt _size; 37 | 38 | public: 39 | explicit MediumTracker() noexcept; 40 | MediumTracker(const MediumTracker &) = default; 41 | 42 | public: 43 | [[nodiscard]] Var current() const noexcept; 44 | [[nodiscard]] Bool vacuum() const noexcept; 45 | [[nodiscard]] Bool true_hit(Expr priority) const noexcept; 46 | void enter(Expr priority, Expr value) noexcept; 47 | void exit(Expr priority, Expr value) noexcept; 48 | [[nodiscard]] Bool exist(Expr priority, Expr value) noexcept; 49 | [[nodiscard]] UInt size() const noexcept { return _size; } 50 | }; 51 | 52 | }// namespace luisa::render 53 | 54 | LUISA_STRUCT( 55 | luisa::render::MediumInfo, 56 | priority, 57 | medium_tag) { 58 | 59 | [[nodiscard]] luisa::compute::Bool equal(luisa::compute::Expr v) const noexcept { 60 | return (this->medium_tag == v.medium_tag) & 61 | (this->priority == v.priority); 62 | } 63 | }; 64 | 65 | LUISA_DISABLE_DSL_ADDRESS_OF_OPERATOR(luisa::render::MediumTracker) 66 | 67 | [[nodiscard]] inline luisa::compute::Var make_medium_info( 68 | luisa::compute::UInt priority, luisa::compute::UInt medium_tag) noexcept { 69 | return luisa::compute::def(priority, medium_tag); 70 | } 71 | -------------------------------------------------------------------------------- /src/util/pmj02tables.h: -------------------------------------------------------------------------------- 1 | // pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys. 2 | // The pbrt source code is licensed under the Apache License, Version 2.0. 3 | // SPDX: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace luisa::render { 10 | 11 | static constexpr auto nPMJ02bnSets = 5u; 12 | static constexpr auto nPMJ02bnSamples = 65536u; 13 | 14 | #ifndef LUISA_RENDER_PMJ02_TABLES_DEFINITION 15 | LUISA_IMPORT_API const uint32_t PMJ02bnSamples[nPMJ02bnSets][nPMJ02bnSamples][2]; 16 | #endif 17 | 18 | }// namespace luisa::render 19 | -------------------------------------------------------------------------------- /src/util/polymorphic_closure.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by ChenXin on 2023/5/9. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | namespace luisa::render { 11 | 12 | class PolymorphicClosure { 13 | 14 | private: 15 | std::any _context; 16 | 17 | public: 18 | virtual ~PolymorphicClosure() noexcept = default; 19 | 20 | template 21 | void bind(T &&ctx) noexcept { 22 | if (!_context.has_value()) { 23 | _context = std::forward(ctx); 24 | } else { 25 | auto p_data = std::any_cast(&_context); 26 | assert(p_data != nullptr); 27 | *p_data = std::forward(ctx); 28 | } 29 | } 30 | 31 | template 32 | [[nodiscard]] const T &context() const noexcept { 33 | auto ctx = std::any_cast(&_context); 34 | assert(ctx != nullptr); 35 | return *ctx; 36 | } 37 | 38 | virtual void pre_eval() noexcept { 39 | /* prepare any persistent data within a single the dispatch */ 40 | } 41 | 42 | virtual void post_eval() noexcept { 43 | /* release any persistent data within a single dispatch */ 44 | } 45 | }; 46 | 47 | template 48 | class PolymorphicCall { 49 | 50 | private: 51 | UInt _tag; 52 | luisa::unordered_map _closure_tags; 53 | luisa::vector> _closures; 54 | 55 | public: 56 | [[nodiscard]] auto tag() const noexcept { return _tag; } 57 | [[nodiscard]] auto empty() const noexcept { return _closure_tags.empty(); } 58 | [[nodiscard]] auto size() const noexcept { return _closure_tags.size(); } 59 | [[nodiscard]] auto closure(uint index) const noexcept { return _closures[index].get(); } 60 | 61 | using ClosureCreator = luisa::function()>; 62 | using ClosureEvaluator = luisa::function; 63 | 64 | template 65 | requires std::derived_from 66 | [[nodiscard]] T *collect( 67 | luisa::string_view identifier, 68 | const ClosureCreator &f = [] { return luisa::make_unique(); }) noexcept { 69 | 70 | auto [iter, first] = _closure_tags.try_emplace( 71 | luisa::string{identifier}, static_cast(_closures.size())); 72 | if (first) { _closures.emplace_back(f()); } 73 | _tag = iter->second; 74 | auto closure = dynamic_cast(_closures[iter->second].get()); 75 | assert(closure != nullptr); 76 | return closure; 77 | } 78 | 79 | void execute(const ClosureEvaluator &f) const noexcept { 80 | if (empty()) [[unlikely]] { return; } 81 | if (size() == 1u) { 82 | _closures.front()->pre_eval(); 83 | f(_closures.front().get()); 84 | _closures.front()->post_eval(); 85 | } else { 86 | compute::detail::SwitchStmtBuilder{_tag} % [&] { 87 | for (auto i = 0u; i < size(); i++) { 88 | compute::detail::SwitchCaseStmtBuilder{i} % [&f, this, i] { 89 | _closures[i]->pre_eval(); 90 | f(_closures[i].get()); 91 | _closures[i]->post_eval(); 92 | }; 93 | } 94 | compute::detail::SwitchDefaultStmtBuilder{} % 95 | [] { compute::unreachable(); }; 96 | }; 97 | } 98 | } 99 | }; 100 | 101 | }// namespace luisa::render 102 | -------------------------------------------------------------------------------- /src/util/progress_bar.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/4/7. 3 | // 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace luisa::render { 11 | 12 | ProgressBar::ProgressBar(uint32_t width) noexcept 13 | : _progress{0.0f}, _width{width}, _start{clock_type::now()} {} 14 | 15 | void ProgressBar::reset() noexcept { 16 | _start = clock_type::now(); 17 | _progress = 0.0f; 18 | } 19 | 20 | void ProgressBar::done() noexcept { 21 | update(1.0); 22 | std::cout << std::endl; 23 | } 24 | 25 | void ProgressBar::update(double progress) noexcept { 26 | using namespace std::chrono_literals; 27 | _progress = std::clamp(std::max(_progress, progress), 0.0, 1.0); 28 | auto pos = static_cast(_width * _progress); 29 | auto dt = static_cast((clock_type::now() - _start) / 1ns) * 1e-9; 30 | std::cout << "\33[2K\r["; 31 | for (auto i = 0; i < _width; ++i) { 32 | if (i < pos) { 33 | std::cout << complete_char; 34 | } else if (i == pos) { 35 | std::cout << heading_char; 36 | } else { 37 | std::cout << incomplete_char; 38 | } 39 | } 40 | if (_progress != 0.0 && _progress != 1.0) [[likely]] { 41 | auto prompt = luisa::format( 42 | "({:.1f}s | {:.1f}% | ETA {:.1f}s)", 43 | dt, _progress * 100, (1.f - _progress) / _progress * dt); 44 | std::cout << "] " << prompt; 45 | } else { 46 | auto prompt = luisa::format( 47 | "({:.1f}s | {:.1f}%)", dt, _progress * 100); 48 | std::cout << "] " << prompt; 49 | } 50 | std::cout.flush(); 51 | } 52 | 53 | }// namespace luisa::render 54 | -------------------------------------------------------------------------------- /src/util/progress_bar.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/4/7. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace luisa::render { 10 | 11 | // credit: https://github.com/AirGuanZ/agz-utils/blob/master/include/agz-utils/console/pbar.h#L137 12 | class ProgressBar { 13 | 14 | public: 15 | static constexpr auto complete_char = '='; 16 | static constexpr auto heading_char = '>'; 17 | static constexpr auto incomplete_char = ' '; 18 | using clock_type = std::chrono::steady_clock; 19 | 20 | private: 21 | double _progress; 22 | uint32_t _width; 23 | clock_type::time_point _start; 24 | 25 | public: 26 | explicit ProgressBar(uint32_t width = 50u) noexcept; 27 | void reset() noexcept; 28 | void update(double progress) noexcept; 29 | void done() noexcept; 30 | }; 31 | 32 | }// namespace luisa::render 33 | -------------------------------------------------------------------------------- /src/util/rng.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/2/12. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace luisa::render { 10 | 11 | using compute::Expr; 12 | using compute::UInt; 13 | using compute::UInt2; 14 | using compute::UInt3; 15 | using compute::UInt4; 16 | using compute::Float; 17 | using compute::ULong; 18 | 19 | [[nodiscard]] UInt xxhash32(Expr p) noexcept; 20 | [[nodiscard]] UInt xxhash32(Expr p) noexcept; 21 | [[nodiscard]] UInt xxhash32(Expr p) noexcept; 22 | [[nodiscard]] UInt xxhash32(Expr p) noexcept; 23 | 24 | [[nodiscard]] UInt pcg(Expr v) noexcept; 25 | [[nodiscard]] UInt2 pcg2d(Expr v_in) noexcept; 26 | [[nodiscard]] UInt3 pcg3d(Expr v_in) noexcept; 27 | [[nodiscard]] UInt4 pcg4d(Expr v_in) noexcept; 28 | 29 | [[nodiscard]] Float uniform_uint_to_float(Expr u) noexcept; 30 | 31 | [[nodiscard]] Float lcg(UInt &state) noexcept; 32 | 33 | class PCG32 { 34 | 35 | private: 36 | static constexpr auto default_state = 0x853c49e6748fea9bull; 37 | static constexpr auto default_stream = 0xda3e39cb94b95bdbull; 38 | static constexpr auto mult = 0x5851f42d4c957f2dull; 39 | 40 | private: 41 | ULong _state; 42 | ULong _inc; 43 | 44 | public: 45 | PCG32() noexcept; 46 | PCG32(ULong state, ULong inc) noexcept; 47 | explicit PCG32(ULong seq_index) noexcept; 48 | explicit PCG32(Expr seq_index) noexcept; 49 | void set_sequence(ULong init_seq) noexcept; 50 | [[nodiscard]] UInt uniform_uint() noexcept; 51 | [[nodiscard]] Float uniform_float() noexcept; 52 | [[nodiscard]] auto state() const noexcept { return _state; } 53 | [[nodiscard]] auto inc() const noexcept { return _inc; } 54 | }; 55 | 56 | }// namespace luisa::render 57 | 58 | LUISA_DISABLE_DSL_ADDRESS_OF_OPERATOR(luisa::render::PCG32) 59 | -------------------------------------------------------------------------------- /src/util/sampling.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/9. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | namespace luisa::render { 11 | 12 | using luisa::compute::Expr; 13 | using luisa::compute::Float; 14 | using luisa::compute::Float2; 15 | using luisa::compute::Float3; 16 | using luisa::compute::UInt; 17 | using luisa::compute::Var; 18 | 19 | [[nodiscard]] Float3 sample_uniform_triangle(Expr u) noexcept; 20 | [[nodiscard]] Float2 sample_uniform_disk_concentric(Expr u) noexcept; 21 | [[nodiscard]] Float3 sample_cosine_hemisphere(Expr u) noexcept; 22 | [[nodiscard]] Float cosine_hemisphere_pdf(Expr cos_theta) noexcept; 23 | [[nodiscard]] Float3 sample_uniform_sphere(Expr u) noexcept; 24 | [[nodiscard]] constexpr float uniform_sphere_pdf() noexcept { return inv_pi * 0.25f; } 25 | [[nodiscard]] Float2 invert_uniform_sphere_sample(Expr w) noexcept; 26 | [[nodiscard]] Float uniform_cone_pdf(Expr cos_theta_max) noexcept; 27 | [[nodiscard]] Float3 sample_uniform_cone(Expr u, Expr cos_theta_max) noexcept; 28 | 29 | struct AliasEntry { 30 | float prob; 31 | uint alias; 32 | }; 33 | 34 | // reference: https://github.com/AirGuanZ/agz-utils 35 | [[nodiscard]] std::pair, luisa::vector /* pdf */> 36 | create_alias_table(luisa::span values) noexcept; 37 | 38 | template 39 | [[nodiscard]] inline auto sample_alias_table( 40 | const Table &table, Expr n, Expr u_in, Expr offset = 0u) noexcept { 41 | using namespace luisa::compute; 42 | auto u = u_in * cast(n); 43 | auto i = clamp(cast(u), 0u, n - 1u); 44 | auto u_remapped = fract(u); 45 | auto entry = table->read(i + offset); 46 | auto index = ite(u_remapped < entry.prob, i, entry.alias); 47 | auto uu = ite(u_remapped < entry.prob, u_remapped / entry.prob, 48 | (u_remapped - entry.prob) / (1.0f - entry.prob)); 49 | return std::make_pair(index, uu); 50 | } 51 | 52 | template 53 | [[nodiscard]] inline auto sample_alias_table( 54 | const ProbTable &probs, const AliasTable &indices, 55 | Expr n, Expr u_in, Expr offset = 0u) noexcept { 56 | using namespace luisa::compute; 57 | auto u = u_in * cast(n); 58 | auto i = clamp(cast(u), 0u, n - 1u); 59 | auto u_remapped = fract(u); 60 | auto prob = probs->read(i + offset); 61 | auto index = ite(u_remapped < prob, i, indices->read(i + offset)); 62 | auto uu = ite( 63 | u_remapped < prob, u_remapped / prob, 64 | (u_remapped - prob) / (1.0f - prob)); 65 | return std::make_pair(index, uu); 66 | } 67 | 68 | [[nodiscard]] Float balance_heuristic(Expr nf, Expr fPdf, Expr ng, Expr gPdf) noexcept; 69 | [[nodiscard]] Float power_heuristic(Expr nf, Expr fPdf, Expr ng, Expr gPdf) noexcept; 70 | [[nodiscard]] Float balance_heuristic(Expr fPdf, Expr gPdf) noexcept; 71 | [[nodiscard]] Float power_heuristic(Expr fPdf, Expr gPdf) noexcept; 72 | 73 | [[nodiscard]] UInt sample_discrete(Expr weights, Expr u) noexcept; 74 | [[nodiscard]] UInt sample_discrete(Expr weights, Expr u) noexcept; 75 | [[nodiscard]] UInt sample_discrete(const SampledSpectrum &weights, Expr u) noexcept; 76 | [[nodiscard]] Float sample_exponential(Expr u, Expr a) noexcept; 77 | 78 | }// namespace luisa::render 79 | 80 | LUISA_STRUCT(luisa::render::AliasEntry, prob, alias){}; 81 | -------------------------------------------------------------------------------- /src/util/sobolmatrices.h: -------------------------------------------------------------------------------- 1 | // pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys. 2 | // The pbrt source code is licensed under the Apache License, Version 2.0. 3 | // SPDX: Apache-2.0 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace luisa::render { 10 | 11 | // Sobol Matrix Declarations 12 | static constexpr auto NSobolDimensions = 1024u; 13 | static constexpr auto SobolMatrixSize = 52u; 14 | 15 | static constexpr auto VdCSobolMatrixSize = 25u; 16 | static constexpr auto VdCSobolMatrixInvSize = 26u; 17 | 18 | #ifndef LUISA_RENDER_SOBOL_MATRICES_DEFINITION 19 | LUISA_IMPORT_API const uint32_t SobolMatrices32[NSobolDimensions * SobolMatrixSize]; 20 | LUISA_IMPORT_API const uint64_t VdCSobolMatrices[VdCSobolMatrixSize][SobolMatrixSize]; 21 | LUISA_IMPORT_API const uint64_t VdCSobolMatricesInv[VdCSobolMatrixInvSize][SobolMatrixSize]; 22 | #endif 23 | 24 | }// namespace luisa::render 25 | -------------------------------------------------------------------------------- /src/util/vertex.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/11/8. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | namespace luisa::render { 11 | 12 | template 13 | requires std::same_as, float3> 14 | [[nodiscard]] inline auto oct_encode(T n_in) noexcept { 15 | auto n = select(normalize(n_in), make_float3(0.f, 0.f, 1.f), all(n_in == 0.f)); 16 | constexpr auto oct_wrap = [](auto v) noexcept { 17 | return (1.f - abs(v.yx())) * select(make_float2(-1.f), make_float2(1.f), v >= 0.0f); 18 | }; 19 | auto abs_n = abs(n); 20 | auto p = n.xy() * (1.f / (abs_n.x + abs_n.y + abs_n.z)); 21 | p = select(oct_wrap(p), p, n.z >= 0.f);// in [-1, 1] 22 | auto u = make_uint2(clamp(round((p * .5f + .5f) * 65535.f), 0.f, 65535.f)); 23 | return u.x | (u.y << 16u); 24 | }; 25 | 26 | template 27 | requires std::same_as, uint> 28 | [[nodiscard]] inline auto oct_decode(T u) noexcept { 29 | auto p = make_float2(make_uint2(u & 0xffffu, u >> 16u)) * ((1.f / 65535.f) * 2.f) - 1.f; 30 | auto abs_p = abs(p); 31 | auto n = make_float3(p, 1.f - abs_p.x - abs_p.y); 32 | auto t = clamp(n.z, -1.f, 0.f); 33 | auto xy = sign(n.xy()) * t + n.xy(); 34 | return make_float3(xy, n.z); 35 | } 36 | 37 | struct alignas(16) Vertex { 38 | 39 | float px; 40 | float py; 41 | float pz; 42 | float nx; 43 | float ny; 44 | float nz; 45 | float u; 46 | float v; 47 | 48 | [[nodiscard]] static auto encode(float3 p, float3 n, float2 uv) noexcept { 49 | return Vertex{p.x, p.y, p.z, n.x, n.y, n.z, uv.x, uv.y}; 50 | }; 51 | [[nodiscard]] auto position() const noexcept { return make_float3(px, py, pz); } 52 | [[nodiscard]] auto normal() const noexcept { return make_float3(nx, ny, nz); } 53 | [[nodiscard]] auto uv() const noexcept { return make_float2(u, v); } 54 | }; 55 | 56 | static_assert(sizeof(Vertex) == 32u); 57 | 58 | }// namespace luisa::render 59 | 60 | // clang-format off 61 | LUISA_STRUCT(luisa::render::Vertex, px, py, pz, nx, ny, nz, u, v) { 62 | [[nodiscard]] auto position() const noexcept { return make_float3(px, py, pz); } 63 | [[nodiscard]] auto normal() const noexcept { return make_float3(nx, ny, nz); } 64 | [[nodiscard]] auto uv() const noexcept { return make_float2(u, v); } 65 | }; 66 | // clang-format on 67 | -------------------------------------------------------------------------------- /src/util/xform.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mike Smith on 2022/1/15. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | namespace luisa::render { 11 | 12 | struct Quaternion { 13 | float3 v; 14 | float w; 15 | constexpr Quaternion() noexcept = default; 16 | constexpr Quaternion(float3 v, float w) noexcept 17 | : v{v}, w{w} {} 18 | constexpr auto operator+(Quaternion rhs) const noexcept { 19 | return Quaternion{v + rhs.v, w + rhs.w}; 20 | } 21 | constexpr auto operator-(Quaternion rhs) const noexcept { 22 | return Quaternion{v - rhs.v, w - rhs.w}; 23 | } 24 | constexpr auto operator*(float s) const noexcept { 25 | return Quaternion{v * s, w * s}; 26 | } 27 | constexpr auto operator/(float s) const noexcept { 28 | return Quaternion{v / s, w / s}; 29 | } 30 | }; 31 | 32 | struct DecomposedTransform { 33 | float3 scaling; 34 | Quaternion quaternion; 35 | float3 translation; 36 | }; 37 | 38 | [[nodiscard]] DecomposedTransform decompose(float4x4 m) noexcept; 39 | [[nodiscard]] Quaternion quaternion(float3x3 m) noexcept; 40 | [[nodiscard]] float4x4 rotation(Quaternion q) noexcept; 41 | [[nodiscard]] float dot(Quaternion q1, Quaternion q2) noexcept; 42 | [[nodiscard]] float length(Quaternion q) noexcept; 43 | [[nodiscard]] float angle_between(Quaternion q1, Quaternion q2) noexcept; 44 | [[nodiscard]] Quaternion normalize(Quaternion q) noexcept; 45 | [[nodiscard]] Quaternion slerp(Quaternion q1, Quaternion q2, float t) noexcept; 46 | 47 | }// namespace luisa::render 48 | -------------------------------------------------------------------------------- /tools/.gitignore: -------------------------------------------------------------------------------- 1 | *.luisa 2 | *.xml 3 | *.pbrt 4 | *.json -------------------------------------------------------------------------------- /tools/akr2obj.py: -------------------------------------------------------------------------------- 1 | from struct import unpack_from, calcsize 2 | from sys import argv 3 | import numpy as np 4 | 5 | 6 | # returns (positions, normals, texcoords, position_indices, normal_indices, texcoord_indices) 7 | def decode_akari_mesh(buffer): 8 | def decode(fmt, offset): 9 | size = calcsize(fmt) 10 | data = unpack_from(fmt, buffer, offset) 11 | return data, offset + size 12 | 13 | # [len] [name (byte) ] 14 | # [len] [v (float3)] 15 | # [len] [vn (float3)] 16 | # [len] [vt (float2)] 17 | # [len] [iv (uint3) ] 18 | # [len] [ivn (uint3) ] 19 | # [len] [ivt (uint3) ] 20 | name_len, offset = decode("Q", 0) 21 | name, offset = decode(f"{name_len[0]}s", offset) 22 | print(f"Processing '{str(name[0].decode('utf-8'))}'") 23 | v_len, offset = decode("Q", offset) 24 | v, offset = decode(f"{v_len[0] * 3}f", offset) 25 | vn_len, offset = decode("Q", offset) 26 | vn, offset = decode(f"{vn_len[0] * 3}f", offset) 27 | vt_len, offset = decode("Q", offset) 28 | vt, offset = decode(f"{vt_len[0] * 2}f", offset) 29 | iv_len, offset = decode("Q", offset) 30 | iv, offset = decode(f"{iv_len[0] * 3}I", offset) 31 | ivn_len, offset = decode("Q", offset) 32 | ivn, offset = decode(f"{ivn_len[0] * 3}I", offset) 33 | ivt_len, offset = decode("Q", offset) 34 | ivt, offset = decode(f"{ivt_len[0] * 3}I", offset) 35 | return np.reshape(v, [-1, 3]), \ 36 | np.reshape(vn, [-1, 3]), \ 37 | np.reshape(vt, [-1, 2]), \ 38 | np.reshape(iv, [-1, 3]), \ 39 | np.reshape(ivn, [-1, 3]), \ 40 | np.reshape(ivt, [-1, 3]) 41 | 42 | 43 | if __name__ == "__main__": 44 | filename = argv[1] 45 | assert filename.endswith(".mesh") 46 | with open(filename, "rb") as file: 47 | [p, n, t, pi, ni, ti] = decode_akari_mesh(file.read()) 48 | assert pi.shape == ni.shape == ti.shape 49 | print(f"vertices: {p.shape}, triangles: {pi.shape}") 50 | with open(f"{filename[:-5]}.obj", "w") as file: 51 | for px, py, pz in p: 52 | print(f"v {px} {py} {pz}", file=file) 53 | for nx, ny, nz in n: 54 | print(f"vn {nx} {ny} {nz}", file=file) 55 | for tx, ty in t: 56 | print(f"vt {tx} {ty}", file=file) 57 | for (pix, piy, piz), (nix, niy, niz), (tix, tiy, tiz) in zip(pi + 1, ni + 1, ti + 1): 58 | print(f"f {pix}/{tix}/{nix} {piy}/{tiy}/{niy} {piz}/{tiz}/{niz}", file=file) 59 | -------------------------------------------------------------------------------- /tools/diffuse_fresnel.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from tqdm import tqdm 3 | 4 | 5 | # static inline float dielectricReflectance(float eta, float cosThetaI) 6 | # { 7 | # if (cosThetaI < 0.0f) { 8 | # eta = 1.0f/eta; 9 | # cosThetaI = -cosThetaI; 10 | # } 11 | # float cosThetaT; 12 | # float sinThetaTSq = eta*eta*(1.0f - cosThetaI*cosThetaI); 13 | # if (sinThetaTSq > 1.0f) { 14 | # cosThetaT = 0.0f; 15 | # return 1.0f; 16 | # } 17 | # cosThetaT = std::sqrt(max(1.0f - sinThetaTSq, 0.0f)); 18 | # 19 | # float Rs = (eta*cosThetaI - cosThetaT)/(eta*cosThetaI + cosThetaT); 20 | # float Rp = (eta*cosThetaT - cosThetaI)/(eta*cosThetaT + cosThetaI); 21 | # 22 | # return (Rs*Rs + Rp*Rp)*0.5f; 23 | # } 24 | 25 | # static inline float computeDiffuseFresnel(float ior, const int sampleCount) 26 | # { 27 | # double diffuseFresnel = 0.0; 28 | # float fb = Fresnel::dielectricReflectance(ior, 0.0f); 29 | # for (int i = 1; i <= sampleCount; ++i) { 30 | # float cosThetaSq = float(i)/sampleCount; 31 | # float fa = Fresnel::dielectricReflectance(ior, min(std::sqrt(cosThetaSq), 1.0f)); 32 | # diffuseFresnel += double(fa + fb)*(0.5/sampleCount); 33 | # fb = fa; 34 | # } 35 | # 36 | # return float(diffuseFresnel); 37 | # } 38 | 39 | 40 | def FrDielectric(eta: np.array, cosThetaI: float): 41 | eta = np.where(cosThetaI < 0, 1 / eta, eta) 42 | cosThetaI = np.abs(cosThetaI) 43 | sinThetaTSq = eta * eta * (1 - cosThetaI * cosThetaI) 44 | cosThetaT = np.sqrt(np.maximum(1 - sinThetaTSq, 0)) 45 | Rs = (eta * cosThetaI - cosThetaT) / (eta * cosThetaI + cosThetaT) 46 | Rp = (eta * cosThetaT - cosThetaI) / (eta * cosThetaT + cosThetaI) 47 | F = 0.5 * (Rs * Rs + Rp * Rp) 48 | return np.where(sinThetaTSq >= 1, 1, F) 49 | 50 | 51 | # computes integral[FrDielectric(eta, cos(theta)) * cos(theta)] 52 | def FrDiffuse(eta: np.array, sampleCount: int): 53 | F = np.zeros_like(eta) 54 | fb = FrDielectric(eta, 0.) 55 | for i in tqdm(range(1, sampleCount + 1)): 56 | cosThetaSq = i / sampleCount 57 | fa = FrDielectric(eta, np.sqrt(cosThetaSq)) 58 | F += (fa + fb) * (0.5 / sampleCount) 59 | fb = fa 60 | return F 61 | 62 | 63 | def fit_small(eta: np.array, F: np.array): 64 | A = np.vstack([np.ones_like(eta), eta, eta ** 2, eta ** 3]).T 65 | c = np.linalg.lstsq(A, F, rcond=None)[0] 66 | print(c) 67 | return A @ c 68 | 69 | 70 | def fit(eta: np.array, F: np.array): 71 | A = np.vstack([np.ones_like(eta), eta ** -1, eta ** -2]).T 72 | c = np.linalg.lstsq(A, F, rcond=None)[0] 73 | print(c) 74 | return A @ c 75 | 76 | 77 | from matplotlib import pyplot as plt 78 | 79 | if __name__ == "__main__": 80 | eta_small = np.arange(0.25, 1.00, 0.01) 81 | eta = np.arange(1.00, 4.00, 0.01) 82 | print(len(eta_small)) 83 | print(len(eta)) 84 | N = 100000 85 | F_small = FrDiffuse(eta_small, N) 86 | F = FrDiffuse(eta, N) 87 | F_small_recon = fit_small(eta_small, F_small) 88 | F_recon = fit(eta, F) 89 | print(F_small_recon) 90 | print(F_small - F_small_recon) 91 | print(np.max(np.abs(F_small - F_small_recon))) 92 | print(F_recon) 93 | print(F - F_recon) 94 | print(np.max(np.abs(F - F_recon))) 95 | plt.plot(eta_small, F_small, label="F small") 96 | plt.plot(eta, F, label="F") 97 | plt.plot(eta, F_recon, label="F recon", alpha=0.5, linestyle="--") 98 | plt.plot(eta_small, F_small_recon, label="F small recon", alpha=0.5, linestyle="--") 99 | plt.legend() 100 | 101 | plt.show() 102 | -------------------------------------------------------------------------------- /tools/hdr2srgb.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import cv2 as cv 3 | from sys import argv 4 | 5 | 6 | def rgb2srgb(image): 7 | return np.uint8(np.round(np.clip(np.where( 8 | image <= 0.00304, 9 | 12.92 * image, 10 | 1.055 * np.power(image, 1.0 / 2.4) - 0.055 11 | ) * 255, 0, 255))) 12 | 13 | 14 | def imread(filename): 15 | return np.maximum( 16 | np.nan_to_num(cv.imread(filename, cv.IMREAD_UNCHANGED)[:, :, :3], nan=0.0, posinf=1e3, neginf=0), 0.0) 17 | 18 | 19 | if __name__ == "__main__": 20 | filename = argv[1] 21 | exp = 0 if len(argv) == 2 else float(argv[2]) 22 | assert filename.endswith(".exr") 23 | image = imread(filename) * (2 ** exp) 24 | cv.imwrite(f"{filename[:-4]}.png", rgb2srgb(image)) 25 | -------------------------------------------------------------------------------- /tools/obj-analyse.py: -------------------------------------------------------------------------------- 1 | from msilib.schema import Error 2 | import os 3 | import sys 4 | 5 | v_count_duplicated = 0 6 | f_count_duplicated = 0 7 | vertex_set = set() 8 | face_set = set() 9 | 10 | def obj_analyse(obj_file): 11 | """ 12 | Analyse the obj file and return the vertex and face set. 13 | """ 14 | global v_count_duplicated, f_count_duplicated 15 | global vertex_set, face_set 16 | 17 | with open(obj_file, 'r') as f: 18 | lines = f.readlines() 19 | 20 | for line in lines: 21 | if line.startswith('v '): 22 | vertex_set.add(line.strip('\n')) 23 | v_count_duplicated += 1 24 | elif line.startswith('f '): 25 | faces = line.split(' ')[1:] 26 | 27 | def load_mesh(faces): 28 | global f_count_duplicated 29 | global face_set 30 | mesh = '' 31 | for face in faces: 32 | face = face.split('/')[0] 33 | mesh += lines[int(face)].strip('\n') + '\n' 34 | face_set.add(mesh) 35 | f_count_duplicated += 1 36 | 37 | if len(faces) == 3: 38 | load_mesh(faces) 39 | elif len(faces) == 4: 40 | load_mesh(faces[0:3]) 41 | load_mesh(faces[1:4]) 42 | else: 43 | raise Error('Invalid face count: {}'.format(len(faces))) 44 | 45 | if __name__ == '__main__': 46 | argc = len(sys.argv) 47 | if argc != 2: 48 | print('Usage: obj-analyse.py ') 49 | exit(1) 50 | 51 | path = sys.argv[1] 52 | if os.path.isfile(path): 53 | obj_analyse(path) 54 | else: 55 | files = [os.path.join(path, f) for f in os.listdir(path) if os.path.isfile(os.path.join(path, f)) and f.endswith('.obj')] 56 | print(len(files)) 57 | for f in files: 58 | obj_analyse(f) 59 | 60 | print('Vertex count: {}'.format(len(vertex_set))) 61 | print('Face count: {}'.format(len(face_set))) 62 | print('Vertex duplicated count: {}'.format(v_count_duplicated)) 63 | print('Face duplicated count: {}'.format(f_count_duplicated)) 64 | # print('Vertex: {}'.format(vertex_set)) 65 | # print('Face: {}'.format(face_set)) 66 | -------------------------------------------------------------------------------- /tools/rgba2rgb.py: -------------------------------------------------------------------------------- 1 | from os import environ 2 | from sys import argv 3 | 4 | 5 | if __name__ == "__main__": 6 | environ["OPENCV_IO_ENABLE_OPENEXR"] = "1" 7 | import cv2 as cv 8 | if len(argv) < 2: 9 | print("Usage: rgba2rgb.py ") 10 | exit(1) 11 | image = cv.imread(argv[1], cv.IMREAD_UNCHANGED) 12 | assert image.shape[2] == 4 13 | ext = f'.{argv[1].split(".")[-1]}' 14 | cv.imwrite(f'{argv[1][:-len(ext)]}-rgb{ext}', image[:, :, :3]) 15 | -------------------------------------------------------------------------------- /tools/rgba2trans.py: -------------------------------------------------------------------------------- 1 | from os import environ 2 | from sys import argv 3 | 4 | 5 | if __name__ == "__main__": 6 | environ["OPENCV_IO_ENABLE_OPENEXR"] = "1" 7 | import cv2 as cv 8 | if len(argv) < 2: 9 | print("Usage: rgba2trans.py ") 10 | exit(1) 11 | image = cv.imread(argv[1], cv.IMREAD_UNCHANGED) 12 | assert image.shape[2] == 4 13 | ext = f'.{argv[1].split(".")[-1]}'.lower() 14 | alpha = image[:, :, -1] 15 | cv.imwrite(f'{argv[1][:-len(ext)]}-trans{ext}', 1 - alpha if ext == ".exr" else 255 - alpha) 16 | -------------------------------------------------------------------------------- /tools/seq2video.py: -------------------------------------------------------------------------------- 1 | from sys import argv 2 | from os import listdir, environ 3 | import numpy as np 4 | 5 | 6 | def rgb2srgb(image): 7 | return np.uint8(np.round(np.clip(np.where( 8 | image <= 0.00304, 9 | 12.92 * image, 10 | 1.055 * np.power(image, 1.0 / 2.4) - 0.055 11 | ) * 255, 0, 255))) 12 | 13 | 14 | def exr2png(images): 15 | for i, image in enumerate(images): 16 | print(f"Transforming frame {i}") 17 | frame = rgb2srgb(image) 18 | cv.imwrite(f"{folder}/{files[i][:-4]}.png", frame) 19 | 20 | 21 | def png2video(images, frame_rate): 22 | writer = cv.VideoWriter(f"{folder}/output.mp4", cv.VideoWriter_fourcc('m', 'p', '4', 'v'), 23 | frame_rate, (images[0].shape[1], images[0].shape[0])) 24 | # writer = cv.VideoWriter(f"{folder}/output.avi", cv.VideoWriter_fourcc('M', 'J', 'P', 'G'), 25 | # frame_rate, (images[0].shape[1], images[0].shape[0])) 26 | for i, image in enumerate(images): 27 | print(f"Processing frame {i}") 28 | writer.write(image) 29 | 30 | writer.release() 31 | cv.waitKey() 32 | cv.destroyAllWindows() 33 | 34 | 35 | if __name__ == "__main__": 36 | environ["OPENCV_IO_ENABLE_OPENEXR"] = "1" 37 | import cv2 as cv 38 | 39 | folder = argv[1] 40 | frame_rate = int(argv[2]) 41 | 42 | print(f"Reading images from '{folder}'...") 43 | files = sorted(f for f in listdir(folder) if f.endswith(".exr") and not f.startswith("dump-")) 44 | images = [cv.imread(f"{folder}/{f}", cv.IMREAD_UNCHANGED) for f in files] 45 | exr2png(images) 46 | 47 | print("") 48 | 49 | files = sorted(f for f in listdir(folder) if f.endswith(".png") and not f.startswith("dump-")) 50 | print(f"Reading images from '{folder}'...") 51 | images = [cv.imread(f"{folder}/{f}", cv.IMREAD_COLOR) for f in files] 52 | png2video(images, frame_rate) 53 | -------------------------------------------------------------------------------- /tools/split_obj.py: -------------------------------------------------------------------------------- 1 | from sys import argv 2 | from os import path 3 | import numpy as np 4 | 5 | 6 | def reindex_vertices(v, iv): 7 | min_i, max_i = np.min(iv), np.max(iv) 8 | return v[min_i - 1:max_i], iv - (min_i - 1) 9 | 10 | 11 | def process_mesh(filename, v, vt, vn, mesh): 12 | print(f"Processing: {filename}") 13 | v, iv = reindex_vertices(v, mesh[..., 0]) 14 | vt, ivt = reindex_vertices(vt, mesh[..., 1]) 15 | vn, ivn = reindex_vertices(vn, mesh[..., 2]) 16 | indices = np.dstack([iv, ivt, ivn]) 17 | with open(filename, "w") as file: 18 | for x in v + vt + vn: 19 | print(x, end="", file=file) 20 | print("g mesh", file=file) 21 | for face in indices: 22 | print("f", end="", file=file) 23 | for x, y, z in face: 24 | print(f" {x}/{y}/{z}", end="", file=file) 25 | print(file=file) 26 | 27 | 28 | if __name__ == "__main__": 29 | v = [] 30 | vn = [] 31 | vt = [] 32 | meshes = {} 33 | with open(argv[1], "r") as file: 34 | for i, line in enumerate(file.readlines()): 35 | if line.startswith("v "): 36 | v.append(line) 37 | elif line.startswith("vn "): 38 | vn.append(line) 39 | elif line.startswith("vt "): 40 | vt.append(line) 41 | elif line.startswith("usemtl ") or line.startswith("g ") or line.startswith("o "): 42 | name = f"Mesh.{len(meshes):05}.{'.'.join(line.split()[1:])}" 43 | meshes[name] = [] 44 | elif line.startswith("f "): 45 | # v/vt/vn 46 | meshes[name].append([[int(x) for x in v.split('/')] for v in line.split()[1:]]) 47 | for name, mesh in meshes.items(): 48 | if mesh: 49 | filename = f"{path.dirname(argv[1])}/{name}.obj" 50 | process_mesh(filename, v, vt, vn, np.array(mesh)) 51 | --------------------------------------------------------------------------------